Debugging

Langoost ships two debuggers: a terminal (TUI) debugger for quick command-line sessions, and a Debug Adapter Protocol (DAP) server for editors.

Terminal debugger

langoost debug <file.goost> opens an interactive TUI — a source viewer with breakpoints and stepping, no editor required:

$ langoost debug app.goost

It pauses on the first line by default so you can set breakpoints; pass --stop-on-entry=false to start running immediately.

Editor debugging (DAP)

Langoost also ships a Debug Adapter Protocol (DAP) server, so any DAP-aware editor — VS Code, Neovim (nvim-dap), JetBrains, Helix, Zed — can debug .goost programs with breakpoints, stepping, and variable inspection.

Running the adapter

$ langoost dap                 # speak DAP over stdin/stdout (editor default)
$ langoost dap --port 4711     # listen on a TCP port (long-running adapter)

Editors normally use stdio: the editor spawns langoost dap as a child process and the framing handshake happens over the pipes automatically.

VS Code

Add a launch configuration in .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "langoost",
      "name": "Run current Langoost file",
      "request": "launch",
      "program": "${file}",
      "stopOnEntry": false
    }
  ]
}

VS Code also needs a small extension shell that tells it how to spawn the adapter — a package.json contributing a debuggers entry with "program": "langoost", "args": ["dap"]. Package it with vsce package and install the .vsix; then F5 debugs the active file.

Neovim (nvim-dap)

local dap = require('dap')
dap.adapters.langoost = {
  type = 'executable',
  command = 'langoost',
  args = { 'dap' },
}
dap.configurations.langoost = {
  {
    type = 'langoost',
    request = 'launch',
    name = 'Run current file',
    program = function() return vim.fn.expand('%:p') end,
    stopOnEntry = false,
  },
}

What works

Capability
Set / unset breakpoints by file:line
Continue, Step Over, Step Into, Step Out
Stop on entry (stopOnEntry: true)
Stack trace with function names and source paths
Locals panel — function arguments and let bindings
Globals panel — top-level let declarations
Stdout / stderr forwarded to the debug console
Evaluate a single identifier in scope (watch / hover)

Not yet

Conditional breakpoints (the condition is accepted but ignored), log points, evaluating arbitrary expressions (only bare identifiers today), and per-task debugging of thread.core.spawn child VMs are planned but not yet implemented.

How it works

On launch, the adapter compiles the script, attaches DebugHooks to a fresh VM, and runs it in a goroutine. Each iteration of the run loop checks the hooks: if the next instruction’s line has a breakpoint (or you’re stepping), the VM emits a stopped event and blocks until a resume command arrives. When no debugger is attached the per-instruction cost is a single nil check. See architecture for the VM internals.