debugger

package
v1.22.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 25, 2026 License: BSD-3-Clause Imports: 9 Imported by: 0

Documentation

Overview

Package debugger implements the ELPS debugger engine (Layer 1). It provides breakpoint management, stepping, variable inspection, and debug evaluation without any external protocol dependencies.

The engine implements the lisp.Debugger interface and communicates with external consumers (such as a DAP server) through event callbacks and a channel-based pause/resume mechanism.

Concurrency model: The ELPS eval goroutine calls the Debugger hook methods (OnEval, WaitIfPaused, etc.). When paused, it blocks on a channel. The external consumer (DAP server goroutine) sends commands via Resume/StepInto/StepOver/StepOut. This is thread-safe by design.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EvalCondition

func EvalCondition(env *lisp.LEnv, condition string) bool

EvalCondition evaluates a breakpoint's condition expression in the given environment. Returns true if the condition is satisfied (or if there is no condition). The evaluatingCondition flag on the engine must be set before calling this to prevent re-entrancy.

func EvalInContext

func EvalInContext(env *lisp.LEnv, source string) *lisp.LVal

EvalInContext parses and evaluates an expression in the paused environment. This allows the debug console to inspect and mutate state. Returns the result or an error LVal.

func FormatValue

func FormatValue(v *lisp.LVal) string

FormatValue returns a human-readable string representation of an LVal, suitable for display in a debugger variables view.

func FormatValueWith added in v1.22.0

func FormatValueWith(v *lisp.LVal, eng *Engine) string

FormatValueWith returns a human-readable string representation of an LVal, using the engine's registered formatters for LNative values. For all other types it delegates to FormatValue. If eng is nil, falls back to FormatValue.

func InterpolateLogMessage added in v1.22.0

func InterpolateLogMessage(env *lisp.LEnv, template string) string

InterpolateLogMessage evaluates a log point message template, replacing {expr} placeholders with the result of evaluating each expression.

Types

type Breakpoint

type Breakpoint struct {
	ID           int
	File         string
	Line         int
	Condition    string // optional: Lisp expression to evaluate
	HitCondition string // optional: hit count expression (e.g., ">5", "==3", "%2")
	LogMessage   string // optional: log point message template with {expr} interpolation
	Enabled      bool
	// contains filtered or unexported fields
}

Breakpoint represents a location where execution should pause.

func (*Breakpoint) IncrementHitCount added in v1.22.0

func (bp *Breakpoint) IncrementHitCount() bool

IncrementHitCount increments the breakpoint's hit count and returns whether the hit condition is satisfied.

type BreakpointSpec added in v1.22.0

type BreakpointSpec struct {
	Line         int
	Condition    string
	HitCondition string
	LogMessage   string
}

BreakpointSpec describes a breakpoint to set (used by SetForFileSpecs).

type BreakpointStore

type BreakpointStore struct {
	// contains filtered or unexported fields
}

BreakpointStore manages breakpoints indexed by file:line for O(1) lookup. All methods are safe for concurrent use from the DAP server goroutine while the eval goroutine reads via Match.

func NewBreakpointStore

func NewBreakpointStore() *BreakpointStore

NewBreakpointStore returns an empty breakpoint store.

func (*BreakpointStore) All

func (s *BreakpointStore) All() []*Breakpoint

All returns all breakpoints in the store.

func (*BreakpointStore) ClearFile

func (s *BreakpointStore) ClearFile(file string)

ClearFile removes all breakpoints for the given file.

func (*BreakpointStore) ExceptionBreak

func (s *BreakpointStore) ExceptionBreak() ExceptionBreakMode

ExceptionBreak returns the current exception breakpoint mode.

func (*BreakpointStore) Match

func (s *BreakpointStore) Match(src *token.Location) *Breakpoint

Match returns the breakpoint at the given source location, or nil.

func (*BreakpointStore) Remove

func (s *BreakpointStore) Remove(file string, line int) bool

Remove removes the breakpoint at file:line. Returns true if it existed.

func (*BreakpointStore) Set

func (s *BreakpointStore) Set(file string, line int, condition string) *Breakpoint

Set adds or replaces a breakpoint at the given file:line. Returns the breakpoint with its assigned ID.

func (*BreakpointStore) SetExceptionBreak

func (s *BreakpointStore) SetExceptionBreak(mode ExceptionBreakMode)

SetExceptionBreak sets the exception breakpoint mode.

func (*BreakpointStore) SetForFile

func (s *BreakpointStore) SetForFile(file string, lines []int, conditions []string) []*Breakpoint

SetForFile replaces all breakpoints in a file. This implements the DAP setBreakpoints semantics (full replacement, not incremental).

func (*BreakpointStore) SetForFileSpecs added in v1.22.0

func (s *BreakpointStore) SetForFileSpecs(file string, specs []BreakpointSpec) []*Breakpoint

SetForFileSpecs replaces all breakpoints in a file using full specs. This supports hit conditions and log messages in addition to conditions.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine implements lisp.Debugger and provides the core debugging primitives: breakpoints, stepping, variable inspection, and debug eval.

func New

func New(opts ...Option) *Engine

New creates a new debugger engine.

func (*Engine) AfterFunCall added in v1.22.0

func (e *Engine) AfterFunCall(env *lisp.LEnv) bool

AfterFunCall implements lisp.Debugger. Called in Eval after EvalSExpr returns. Uses the stepper's ShouldPausePostCall to check if step-out should fire at the current (post-pop) depth, and clears the stepOutReturned flag.

func (*Engine) AllocSourceRef added in v1.22.0

func (e *Engine) AllocSourceRef(name, content string) int

AllocSourceRef allocates a source reference ID for virtual source content. Returns the reference ID that can be used in DAP source requests.

func (*Engine) Breakpoints

func (e *Engine) Breakpoints() *BreakpointStore

Breakpoints returns the breakpoint store for external management (e.g., by a DAP server handling setBreakpoints requests).

func (*Engine) Disable

func (e *Engine) Disable()

Disable deactivates the debugger without detaching it.

func (*Engine) Disconnect

func (e *Engine) Disconnect()

Disconnect atomically disables the debugger and resumes execution if paused.

func (*Engine) Enable

func (e *Engine) Enable()

Enable activates the debugger. Hook calls are only made when enabled.

func (*Engine) EvalCount added in v1.22.0

func (e *Engine) EvalCount() int64

EvalCount returns the number of times OnEval has been called. This is useful for tests that need to wait for evaluation to start.

func (*Engine) EvalInContext added in v1.22.0

func (e *Engine) EvalInContext(env *lisp.LEnv, source string) *lisp.LVal

EvalInContext evaluates an expression string in a paused environment, setting the evaluatingCondition guard so that the OnEval hook does not re-enter breakpoint logic (which would deadlock since EvalInContext runs on the DAP server goroutine, not the eval goroutine).

func (*Engine) FormatNative added in v1.22.0

func (e *Engine) FormatNative(v any) string

FormatNative returns a formatted string for a native Go value using the registered formatter. Returns empty string if no formatter is registered for the value's type.

func (*Engine) GetSourceRef added in v1.22.0

func (e *Engine) GetSourceRef(id int) (string, bool)

GetSourceRef retrieves virtual source content by reference ID. Returns the content and true if found, or empty string and false if not.

func (*Engine) IsEnabled

func (e *Engine) IsEnabled() bool

IsEnabled implements lisp.Debugger.

func (*Engine) IsPaused

func (e *Engine) IsPaused() bool

IsPaused returns true if the eval goroutine is currently blocked in WaitIfPaused.

func (*Engine) NativeChildren added in v1.22.0

func (e *Engine) NativeChildren(v any) []NativeChild

NativeChildren returns the expandable child bindings for a native Go value using the registered formatter. Returns nil if no formatter is registered or the formatter returns no children.

func (*Engine) NotifyExit added in v1.22.0

func (e *Engine) NotifyExit(exitCode int)

NotifyExit fires an EventExited event to notify the DAP server that the program has finished. This should be called from the eval goroutine after evaluation completes, while the DAP server is still running.

func (*Engine) OnError

func (e *Engine) OnError(env *lisp.LEnv, lerr *lisp.LVal) bool

OnError implements lisp.Debugger. Called when an error condition is created. Returns true if execution should pause.

func (*Engine) OnEval

func (e *Engine) OnEval(env *lisp.LEnv, expr *lisp.LVal) bool

OnEval implements lisp.Debugger. Called before each expression with a real source location.

func (*Engine) OnFunEntry

func (e *Engine) OnFunEntry(env *lisp.LEnv, fun *lisp.LVal, fenv *lisp.LEnv)

OnFunEntry implements lisp.Debugger. Checks function breakpoints.

func (*Engine) OnFunReturn

func (e *Engine) OnFunReturn(env *lisp.LEnv, fun, result *lisp.LVal)

OnFunReturn implements lisp.Debugger. Detects the step-out condition before the frame is popped: if the stepper is in StepOut mode and the post-return depth will be less than the recorded depth, set the stepOutReturned flag so that AfterFunCall (or OnEval as a safety net) can pause at the call-site expression.

func (*Engine) PausedState

func (e *Engine) PausedState() (*lisp.LEnv, *lisp.LVal)

PausedState returns the env and expr where execution is paused, or nil if not paused.

func (*Engine) ReadyCh added in v1.22.0

func (e *Engine) ReadyCh() <-chan struct{}

ReadyCh returns a channel that is closed when SignalReady is called. Embedders can select on this to wait for the DAP client to finish setting breakpoints before starting evaluation.

func (*Engine) RegisterFormatter added in v1.22.0

func (e *Engine) RegisterFormatter(typeName string, f VariableFormatter)

RegisterFormatter registers a custom formatter for a native Go type. typeName should match fmt.Sprintf("%T", value) for the values you want to format. Safe to call before debugging starts.

func (*Engine) RequestPause added in v1.22.0

func (e *Engine) RequestPause()

RequestPause requests that the eval goroutine pause at the next expression. This is used by the DAP pause command. The flag is cleared when the engine actually pauses in WaitIfPaused.

func (*Engine) Resume

func (e *Engine) Resume()

Resume sends a Continue action to the paused eval goroutine.

func (*Engine) SetEventCallback

func (e *Engine) SetEventCallback(cb EventCallback)

SetEventCallback sets or replaces the event callback. It is safe to call after construction (e.g., when a DAP handler wires itself up).

func (*Engine) SetFunctionBreakpoints added in v1.22.0

func (e *Engine) SetFunctionBreakpoints(names []string) []string

SetFunctionBreakpoints replaces the set of function breakpoints. Each name should be a function name as the user would type it (e.g., "add" or "user:add"). Returns the names that were set.

func (*Engine) SetSourceLibrary added in v1.22.1

func (e *Engine) SetSourceLibrary(lib lisp.SourceLibrary)

SetSourceLibrary sets or replaces the source library used to serve source content for the DAP source request handler. This is safe to call after construction, allowing embedders to wire the library once it becomes available (e.g., after loading a phylum archive).

func (*Engine) SignalReady added in v1.22.0

func (e *Engine) SignalReady()

SignalReady signals that the external consumer has finished its configuration (e.g., DAP configurationDone). Embedders waiting on ReadyCh() will be unblocked. Safe to call multiple times.

func (*Engine) SourceLibrary added in v1.22.0

func (e *Engine) SourceLibrary() lisp.SourceLibrary

SourceLibrary returns the configured source library, or nil if not set.

func (*Engine) SourceRoot added in v1.22.0

func (e *Engine) SourceRoot() string

SourceRoot returns the configured source root directory, or empty string if not set.

func (*Engine) StepInto

func (e *Engine) StepInto()

StepInto sends a StepInto action to the paused eval goroutine.

func (*Engine) StepOut

func (e *Engine) StepOut()

StepOut sends a StepOut action to the paused eval goroutine.

func (*Engine) StepOver

func (e *Engine) StepOver()

StepOver sends a StepOver action to the paused eval goroutine.

func (*Engine) WaitIfPaused

func (e *Engine) WaitIfPaused(env *lisp.LEnv, expr *lisp.LVal) lisp.DebugAction

WaitIfPaused implements lisp.Debugger. Blocks the eval goroutine until the DAP server sends a resume command.

type Event

type Event struct {
	Type     EventType
	Reason   StopReason
	ExitCode int    // set for EventExited
	Output   string // set for EventOutput (log points)
	Env      *lisp.LEnv
	Expr     *lisp.LVal
	BP       *Breakpoint // non-nil for breakpoint stops
}

Event is sent to the event callback when the debugger state changes.

type EventCallback

type EventCallback func(Event)

EventCallback is called when the debugger state changes. It runs on the eval goroutine, so it must not block.

type EventType

type EventType int

EventType identifies the kind of debug event.

const (
	// EventStopped indicates execution has paused (breakpoint, step, exception).
	EventStopped EventType = iota
	// EventContinued indicates execution has resumed.
	EventContinued
	// EventExited indicates the program has finished.
	EventExited
	// EventOutput indicates the program produced output.
	EventOutput
)

type ExceptionBreakMode

type ExceptionBreakMode int

ExceptionBreakMode controls when the debugger pauses on exceptions.

const (
	// ExceptionBreakNever disables exception breakpoints.
	ExceptionBreakNever ExceptionBreakMode = iota
	// ExceptionBreakAll pauses on all exceptions.
	ExceptionBreakAll
	// ExceptionBreakUncaught pauses only on uncaught exceptions (not yet implemented).
	ExceptionBreakUncaught
)

type FormatterFunc added in v1.22.0

type FormatterFunc func(v any) string

FormatterFunc adapts a simple format function into a VariableFormatter with no children. Useful when you only need custom display text.

func (FormatterFunc) Children added in v1.22.0

func (f FormatterFunc) Children(v any) []NativeChild

func (FormatterFunc) FormatValue added in v1.22.0

func (f FormatterFunc) FormatValue(v any) string

type NativeChild added in v1.22.0

type NativeChild struct {
	Name  string
	Value *lisp.LVal
}

NativeChild represents a single child binding of a native value, exposed for drill-down in the debugger variables view.

type Option

type Option func(*Engine)

Option configures an Engine.

func WithEventCallback

func WithEventCallback(cb EventCallback) Option

WithEventCallback sets the function called on debugger state changes.

func WithFormatters added in v1.22.0

func WithFormatters(fmts map[string]VariableFormatter) Option

WithFormatters sets the custom native type formatters for LNative values. Keys are Go type names as returned by fmt.Sprintf("%T", value).

func WithSourceLibrary added in v1.22.0

func WithSourceLibrary(lib lisp.SourceLibrary) Option

WithSourceLibrary sets the source library used to serve source content for the DAP source request handler.

func WithSourceRoot added in v1.22.0

func WithSourceRoot(dir string) Option

WithSourceRoot sets an absolute directory path used to resolve relative Source.Path values into absolute paths. This allows DAP clients (VS Code) to open source files from stack frames. Embedders should pass the root directory of the ELPS source files (e.g., the phylum directory).

func WithStopOnEntry

func WithStopOnEntry(stop bool) Option

WithStopOnEntry makes the debugger pause before the first expression.

type ScopeBinding

type ScopeBinding struct {
	Name  string
	Value *lisp.LVal
}

ScopeBinding represents a single variable binding in a scope.

func InspectFunctionLocals added in v1.22.0

func InspectFunctionLocals(env *lisp.LEnv) []ScopeBinding

InspectFunctionLocals returns all bindings visible from the given environment up through parent scopes, stopping at the root env (Parent==nil) which contains builtins. In ELPS, package symbols live in Runtime.Package, not in the env chain, so walking up to (but not including) the root env collects exactly the function-local bindings. This gives users the local variables they expect to see in a debugger, even when paused inside a sub-expression like (if ...).

func InspectLocals

func InspectLocals(env *lisp.LEnv) []ScopeBinding

InspectLocals returns the local variable bindings from the given environment's immediate scope (not parent scopes). Bindings are returned sorted by name.

func InspectScope

func InspectScope(env *lisp.LEnv) []ScopeBinding

InspectScope returns all bindings visible from the given environment, walking up the parent chain. Local bindings shadow parent bindings. Bindings are returned sorted by name.

type StepMode

type StepMode int

StepMode represents the current stepping behavior.

const (
	// StepNone means no stepping is active (free-running).
	StepNone StepMode = iota
	// StepInto pauses on the next OnEval call regardless of depth.
	StepInto
	// StepOver pauses on the next OnEval where stack depth <= recorded depth.
	StepOver
	// StepOut pauses on the next OnEval where stack depth < recorded depth.
	StepOut
)

type Stepper

type Stepper struct {
	// contains filtered or unexported fields
}

Stepper implements the step state machine. It tracks the step mode and the reference stack depth to determine when stepping should pause.

Thread safety: Stepper is NOT safe for concurrent use. All access must occur on the eval goroutine. The write path (SetStep*/Reset) runs inside WaitIfPaused after the channel receive, and the read path (ShouldPause) runs in OnEval before the next pause — both on the eval goroutine.

func NewStepper

func NewStepper() *Stepper

NewStepper returns a stepper in the StepNone state.

func (*Stepper) Depth added in v1.22.0

func (s *Stepper) Depth() int

Depth returns the reference stack depth recorded when the step command was issued. Used by Engine to detect the step-out condition in OnFunReturn (before the frame is popped).

func (*Stepper) Mode

func (s *Stepper) Mode() StepMode

Mode returns the current step mode.

func (*Stepper) Reset

func (s *Stepper) Reset()

Reset clears the stepper to StepNone (free-running).

func (*Stepper) SetStepInto

func (s *Stepper) SetStepInto()

SetStepInto configures the stepper to pause on the next OnEval.

func (*Stepper) SetStepOut

func (s *Stepper) SetStepOut(currentDepth int)

SetStepOut configures the stepper to pause on the next OnEval at a lesser stack depth (i.e., after the current function returns).

func (*Stepper) SetStepOver

func (s *Stepper) SetStepOver(currentDepth int)

SetStepOver configures the stepper to pause on the next OnEval at the same or lesser stack depth.

func (*Stepper) ShouldPause

func (s *Stepper) ShouldPause(currentDepth int) bool

ShouldPause returns true if the stepper should cause a pause at the given stack depth. After returning true, the stepper resets to StepNone.

func (*Stepper) ShouldPausePostCall added in v1.22.0

func (s *Stepper) ShouldPausePostCall(currentDepth int) bool

ShouldPausePostCall returns true if a step-out should pause after a function call returns (post-call check in Eval). This is the primary mechanism for detecting step-out from tail-position functions, where the normal OnEval-based ShouldPause never fires because execution flows back through the call chain without visiting any new expressions at a lesser depth. After returning true, the stepper resets to StepNone.

type StopReason

type StopReason string

StopReason describes why execution paused.

const (
	StopBreakpoint         StopReason = "breakpoint"
	StopStep               StopReason = "step"
	StopException          StopReason = "exception"
	StopEntry              StopReason = "entry"
	StopPause              StopReason = "pause"
	StopFunctionBreakpoint StopReason = "function breakpoint"
)

type VariableFormatter added in v1.22.0

type VariableFormatter interface {
	// FormatValue returns a human-readable string for the native value.
	FormatValue(v any) string
	// Children returns expandable child bindings for the native value.
	// Return nil if the value has no children.
	Children(v any) []NativeChild
}

VariableFormatter customizes how native Go values wrapped in LNative are displayed in the debugger. Embedders register formatters by Go type name (fmt.Sprintf("%T", value)) to provide rich display and drill-down.

Directories

Path Synopsis
Package dapserver implements a DAP (Debug Adapter Protocol) server for the ELPS debugger engine.
Package dapserver implements a DAP (Debug Adapter Protocol) server for the ELPS debugger engine.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL