Skip to content

Etapa 2 PR2: DAP backend (live stack, breakpoints, step over/in/out)#46

Merged
jig merged 3 commits into
developfrom
etapa2/dap
Apr 30, 2026
Merged

Etapa 2 PR2: DAP backend (live stack, breakpoints, step over/in/out)#46
jig merged 3 commits into
developfrom
etapa2/dap

Conversation

@jig
Copy link
Copy Markdown
Owner

@jig jig commented Apr 30, 2026

Summary

Phase B of the Etapa 2 plan: a Debug Adapter Protocol server compiled
into lispdebug builds. Combined with PR1's EvalHook foundation, this
lets a VSCode (or any DAP client) drive a Lisp debug session: launch a
script, set breakpoints, inspect the call stack and locals, and step.

Three commits for incremental review:

  1. runtime: live stack + modules (A2 + A3)

    • runtime.Frame / runtime.Thread propagated via ctx; EVAL pushes
      on entry, mutates each TCO iteration, pops on exit. Release stubs
      keep the if runtime.Enabled guard dead code.
    • runtime.ModuleResolver mapping Position.Module → absolute path
      for DAP source.path and breakpoint matching.
  2. debugadapter: DAP server

    • protocol.go message types, transport.go Content-Length framing,
      state.go session state with pause/resume cond var, hook.go
      StepHook implementing runtime.EvalHook, server.go request
      dispatch + eval goroutine.
    • Implements: initialize, launch, setBreakpoints, configurationDone,
      threads, stackTrace, scopes, variables, continue, next, stepIn,
      stepOut, pause, disconnect, terminate.
    • Events: initialized, stopped, continued, output, exited,
      terminated.
    • Composite values (List/Vector/HashMap/Set) exposed via lazy
      variablesReference so the client can expand them on demand.
  3. command: --dap / --dap-listen

    • --dap runs DAP on stdio (the path VSCode uses).
    • --dap-listen HOST:PORT runs DAP on a TCP listener (raspi remote
      case from CLAUDE.md).
    • Both gated by lispdebug; release returns an explicit error.
    • ExecuteFile now registers the script's absolute path with
      runtime.Modules.

Build

go build -tags lispdebug ./cmd/lisp
./lisp --dap script.lisp           # stdio
./lisp --dap-listen :5678 script.lisp

In release builds (go build ./cmd/lisp) the flags fail with
"--dap requires a debug build: rebuild with -tags lispdebug" — same
shape as --debug.

Out of scope (PR3, PR4, PR5)

  • VSCode extension (PR3)
  • LSP server + def cursors (PR4)
  • VSCode LSP client + docs (PR5)

Conditional breakpoints, watch expressions, function breakpoints, and
multi-thread debugging stay deferred to Etapa 3.

Test plan

  • go build ./... and go build -tags lispdebug ./...
  • go vet ./... and go vet -tags lispdebug ./...
  • go test ./... — all green
  • go test -tags lispdebug ./... — all green, including new
    mal_stack_test.go (TCO frame mutation) and
    debugadapter/server_test.go (initialize/launch handshake,
    stopOnEntry → continue)
  • go test -race ./... and
    go test -race -tags lispdebug ./... — only the pre-existing
    lib/concurrent race documented in PR Etapa 1: cleanup, dependency hygiene, and DAP/LSP foundation #44 (out of scope)
  • Smoke release: ./lisp --dap /tmp/foo.lisp
    "--dap requires a debug build" and exits non-zero
  • gofmt -l . clean

🤖 Generated with Claude Code

jig and others added 3 commits April 30, 2026 17:24
Both pieces are gated by the `lispdebug` build tag, like the EvalHook
foundation.

- A2: Frame, Thread, WithThread/ThreadFromContext, plus PushFrame/
  PopFrame/MakeFrame/UpdateFrame helpers. EVAL pushes a Frame on entry,
  mutates its fields each TCO iteration, and pops on return — but only
  when a Thread is carried by ctx. Release builds get empty stubs and
  the `if runtime.Enabled` guard makes the whole block dead code.
- A3: ModuleResolver mapping module identifiers (carried in
  Position.Module) to absolute filesystem paths. The DAP server consumes
  this to populate `source.path` on stack frames and to match
  setBreakpoints requests against incoming Cursors.

mal_stack_test.go covers stack growth/shrink and TCO frame mutation
under `-tags lispdebug`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New package implementing a Debug Adapter Protocol server. Compiled only
into `lispdebug` builds.

Implements the request/response set needed for a usable VSCode session:

  - initialize, launch, configurationDone (handshake + stop-on-entry)
  - setBreakpoints (per-file, per-line; absolute path matching)
  - threads, stackTrace, scopes, variables (live stack + locals;
    composite values exposed via lazy variablesReference)
  - continue, next (step over), stepIn, stepOut, pause
  - disconnect, terminate

Events: initialized, stopped, continued, output, exited, terminated.

Architecture:

  - protocol.go — DAP message types.
  - transport.go — Content-Length-framed bidirectional channel
    over an io.Reader/io.Writer pair (stdio or net.Conn).
  - state.go — session state: mode, breakpoints, variables refs,
    pause/resume condition variable.
  - hook.go — StepHook implements runtime.EvalHook; checks
    breakpoints first, then step mode, blocks on the cond var when
    paused.
  - server.go — request dispatch, eval goroutine, event sender.

Tests cover initialize/launch handshake and stopOnEntry → continue
under the race detector, using net.Pipe for an in-memory transport.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds two CLI flags that route through build-tag-gated helpers:

  - --dap                  start DAP server on stdio
  - --dap-listen HOST:PORT  start DAP server on a TCP listener (for
                            remote debugging, e.g. lisp embedded on a
                            raspi from the laptop)

Both require a `lispdebug` build. In release builds the helpers fail
with an explicit error pointing at the build tag, mirroring --debug.

Also: ExecuteFile now registers the script's absolute path with
runtime.Modules so the DAP server can emit accurate source.path on
stack frames and match setBreakpoints against the same path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jig jig merged commit 9e684be into develop Apr 30, 2026
4 checks passed
@jig jig deleted the etapa2/dap branch April 30, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant