Etapa 2 PR2: DAP backend (live stack, breakpoints, step over/in/out)#46
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase B of the Etapa 2 plan: a Debug Adapter Protocol server compiled
into
lispdebugbuilds. Combined with PR1's EvalHook foundation, thislets 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:
runtime: live stack + modules (A2 + A3)
runtime.Frame/runtime.Threadpropagated via ctx; EVAL pusheson entry, mutates each TCO iteration, pops on exit. Release stubs
keep the
if runtime.Enabledguard dead code.runtime.ModuleResolvermappingPosition.Module→ absolute pathfor DAP
source.pathand breakpoint matching.debugadapter: DAP server
protocol.gomessage types,transport.goContent-Length framing,state.gosession state with pause/resume cond var,hook.goStepHook implementing
runtime.EvalHook,server.gorequestdispatch + eval goroutine.
threads, stackTrace, scopes, variables, continue, next, stepIn,
stepOut, pause, disconnect, terminate.
terminated.
variablesReferenceso the client can expand them on demand.command: --dap / --dap-listen
--dapruns DAP on stdio (the path VSCode uses).--dap-listen HOST:PORTruns DAP on a TCP listener (raspi remotecase from CLAUDE.md).
lispdebug; release returns an explicit error.ExecuteFilenow registers the script's absolute path withruntime.Modules.Build
go build -tags lispdebug ./cmd/lisp ./lisp --dap script.lisp # stdio ./lisp --dap-listen :5678 script.lispIn 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)
Conditional breakpoints, watch expressions, function breakpoints, and
multi-thread debugging stay deferred to Etapa 3.
Test plan
go build ./...andgo build -tags lispdebug ./...go vet ./...andgo vet -tags lispdebug ./...go test ./...— all greengo test -tags lispdebug ./...— all green, including newmal_stack_test.go(TCO frame mutation) anddebugadapter/server_test.go(initialize/launch handshake,stopOnEntry → continue)
go test -race ./...andgo test -race -tags lispdebug ./...— only the pre-existinglib/concurrentrace documented in PR Etapa 1: cleanup, dependency hygiene, and DAP/LSP foundation #44 (out of scope)./lisp --dap /tmp/foo.lisp→"--dap requires a debug build" and exits non-zero
gofmt -l .clean🤖 Generated with Claude Code