Skip to main content
Walrus includes a full-featured interactive debugger for step-by-step program execution, breakpoint management, and runtime state inspection.

Starting the debugger

Launch a program with the --debugger flag:
walrus --debugger program.walrus
The debugger starts paused at the first instruction:
-> Line 1, IP 0: LoadConst0
   let x = 42
(walrus-debug)

Debugger prompt

The (walrus-debug) prompt accepts commands to control execution and inspect state.

Execution control

Step (s)

Execute one instruction and pause (step into functions):
(walrus-debug) s
(walrus-debug) step
This steps through every bytecode instruction, entering function calls.

Next (n)

Step over function calls:
(walrus-debug) n
(walrus-debug) next  
Executes the current line/instruction. If it’s a function call, runs the entire function and stops at the next line.

Continue (c)

Resume execution until the next breakpoint:
(walrus-debug) c
(walrus-debug) continue
Runs the program normally until a breakpoint is hit or the program ends.

Finish

Run until the current function returns:
(walrus-debug) finish
Continues execution until the current function completes, then pauses.

Breakpoints

Set breakpoint (b)

Set a breakpoint at a specific line number:
(walrus-debug) b 15
Breakpoint set at line 15 (IP: [45, 46])
Multiple instruction pointers (IPs) may correspond to one source line.

List breakpoints

Show all breakpoints:
(walrus-debug) b
Breakpoints:
  #1: line 15 (enabled)
  #2: line 42 (enabled)

Delete breakpoint

Remove a breakpoint by line number:
(walrus-debug) delete 15
Breakpoint at line 15 removed

Variable inspection

Show current execution state:
(walrus-debug) p
IP: 23
Call depth: 2
Stack size: 3
Locals count: 4
Inspect a specific variable by name:
(walrus-debug) p x
x = Int(42)
(walrus-debug) print counter
counter = Int(10)
Requires debug information to be available (automatically enabled with --debugger).

Show locals

Display all local variables:
(walrus-debug) locals
Locals:
  [0] x = Int(42)
  [1] y = Float(3.14)
  [2] name = Str("Alice")
  [3] active = Bool(true)
Shows variable names when debug info is available, otherwise shows indices.

Call stack inspection

Backtrace (bt)

Show the call stack:
(walrus-debug) bt
Call stack (most recent first):
  #0: helper (return IP: 67) at line 15
  #1: process (return IP: 34) at line 8
  #2: main (return IP: 12) at line 3
(walrus-debug) backtrace
Displays function names, return addresses, and source line numbers.

Source code viewing

List source (l)

Show source code context around the current line:
(walrus-debug) l
Source context:
     12: fn factorial : n {
     13:     if n <= 1 {
     14:         return 1;
->   15:     }
     16:     return n * factorial(n - 1);
     17: }
     18:
Shows 7 lines of context with -> marking the current line.

List at specific line

View source around a different line:
(walrus-debug) l 42
Source context:
     38: let result = 0;
     39: for i in 0..100 {
     40:     result = result + i;
     41: }
->   42: println(result);
     43:
     44: fn helper : x {
(walrus-debug) list 42

Stack inspection

View operand stack

Show the VM’s operand stack:
(walrus-debug) stack
Operand stack (top first):
  [0] Int(42)
  [1] Int(10)
  [2] Str("test")
Displays values from top to bottom. Useful for understanding bytecode execution.

Help and quit

Help (h)

Show all available commands:
(walrus-debug) h
(walrus-debug) help
Displays complete command reference.

Quit (q)

Exit the debugger and stop execution:
(walrus-debug) q
(walrus-debug) quit

Complete command reference

CommandAliasDescription
stepsExecute one instruction (step into)
nextnStep over function calls
continuecResume until next breakpoint
finish-Run until current function returns
printpShow current state
print <var>p <var>Print variable by name
locals-Show all local variables
backtracebtShow call stack
listlShow source context
list <line>l <line>Show source at line
b-List breakpoints
b <line>-Set breakpoint at line
delete <line>-Remove breakpoint
stack-Show operand stack
helphShow command help
quitqExit debugger

Debugging workflow

Basic debugging session

# Start debugger
$ walrus --debugger factorial.walrus
-> Line 1, IP 0: LoadConst0
   fn factorial : n {

# Set breakpoint at recursive call
(walrus-debug) b 5
Breakpoint set at line 5 (IP: [12])

# Continue to breakpoint
(walrus-debug) c
-> Line 5, IP 12: LoadLocal0
   return n * factorial(n - 1);

# Inspect variables
(walrus-debug) locals
Locals:
  [0] n = Int(5)

# View call stack
(walrus-debug) bt
Call stack (most recent first):
  #0: factorial (return IP: 45) at line 5

# Step through multiplication
(walrus-debug) s
(walrus-debug) s

# Continue execution
(walrus-debug) c

Finding bugs

# Debug a problematic function
$ walrus --debugger bug.walrus

# Set breakpoint where bug occurs
(walrus-debug) b 23

# Run to breakpoint
(walrus-debug) c

# Inspect state
(walrus-debug) p
(walrus-debug) locals

# Check specific variable
(walrus-debug) p counter
counter = Int(-1)  # Found it!

# Step through to understand flow
(walrus-debug) n
(walrus-debug) n
(walrus-debug) p counter

Tracing execution

# Start with debugger + trace logging
$ walrus --debugger --trace program.walrus

# Step instruction by instruction
(walrus-debug) s
[TRACE] Executing: LoadConst0
(walrus-debug) s  
[TRACE] Executing: StoreLocal
(walrus-debug) stack
Operand stack (top first):
  [0] Int(42)

Debug information

The debugger requires debug information, which is automatically built when using --debugger. Debug info includes:
  • Line table: Maps bytecode IPs to source line numbers
  • Local names: Variable names for each local slot
  • Global names: Names of global variables and functions
Without debug info, the debugger falls back to numeric indices:
(walrus-debug) locals
Locals:
  [0] = Int(42)
  [1] = Float(3.14)

Debugging with JIT

The debugger can be combined with JIT compilation:
walrus --debugger --jit program.walrus
Note: The debugger pauses before JIT compilation occurs. Hot loops will be compiled during continue execution.

Performance impact

The debugger adds overhead:
  • Debug info generation during compilation
  • Execution checks at every instruction
  • State tracking for breakpoints and stepping
For production runs, omit --debugger:
# Production: no debugger overhead
walrus --jit program.walrus

# Development: full debugging
walrus --debugger --debug program.walrus

Tips

Combine with logging

Use debug flags for more context:
# Debugger + debug logs
walrus --debugger --debug program.walrus

# Debugger + full trace
walrus --debugger --trace program.walrus

Set strategic breakpoints

Place breakpoints at:
  • Function entry points
  • Loop bodies
  • Conditional branches
  • Before suspected bugs

Use finish for recursion

When debugging recursive functions:
(walrus-debug) b 15  # Inside recursive function
(walrus-debug) c     # Run to first call
(walrus-debug) bt    # Check depth
(walrus-debug) finish # Return from this call
(walrus-debug) locals # Inspect after return

Inspect before errors

When tracking runtime errors, set breakpoints just before the problematic line:
# If error occurs at line 42
(walrus-debug) b 41
(walrus-debug) c
(walrus-debug) locals  # Check state before error
(walrus-debug) s       # Step into error

Build docs developers (and LLMs) love