Reference

torb debug

torb debug is the debugger - a debug adapter over the Debug Adapter Protocol on standard input and output - with breakpoints, stepping, the call stack, locals and evaluate for a program or a test run in the VM.

torb debug is what an editor starts to debug TorbScript. It speaks the Debug Adapter Protocol over standard input and output, runs the program or the tests a launch names in the VM, and stops where a breakpoint, a step, a pause or a panic says so; while it stands, the editor reads the call stack, the locals of every frame with their values, and the answers of expressions over them. Nobody runs it by hand: the VS Code extension of this repository starts it for every Debug button, and so can any editor with a client of the protocol (Set up your editor).

Synopsis#

torb debug                   The debug adapter, until the editor ends the session
torb debug --help            What it takes and what a launch names
torb debug --debuggee        Internal: the process the program runs in, which the adapter starts

What it does#

Two processes#

The adapter answers initialize itself and starts a second torb on launch, torb debug --debuggee, whose standard output and standard error are the program's: what the program prints arrives in the editor as output events, and a panic or Process.exit ends the program's process and not the session. The adapter hands every request to it, and it answers them in their order; when it ends, the editor hears exited with its exit code and terminated. The program's standard input carries the adapter's requests, so a program that is launched this way cannot read standard input of its own: it reads those requests. A program that reads its input is launched with console (below), and then runs in the editor's terminal.

In the editor's terminal#

A client that says supportsRunInTerminalRequest in initialize and a launch with console set to integratedTerminal or externalTerminal run the program in a terminal of the editor, with the terminal's standard input, output and error. The adapter listens on the loopback address (127.0.0.1, a port the system chooses), asks the client with the reverse request runInTerminal to run torb debug --debuggee --connect <port> --token <token>, and takes the connection of that process, which has to say the one-time token first. The requests and answers go over that connection instead; the program's output stays in the terminal and is no output event. The terminal's process reports its exit code over the connection, and the editor hears exited and terminated as before. If the process does not connect within a minute, or the client's answer to runInTerminal is a failure, launch fails with the reason.

A client that cannot runInTerminal, a console of internalConsole or none, and a launch with test run the program with pipes as before. While the program waits for a line of the terminal it cannot answer a pause: the request is answered once the program reads on.

A launch#

ArgumentMeaning
programA file or a directory, as torb run takes it
argsThe program's arguments, what Process.arguments() answers
testA test file or a directory of tests instead, as torb test takes it
filterWith test: the full name of a test or a group (Group > test), or a list of them, as torb test --filter takes them
cwdThe program's working directory; a relative program is found from it
envEnvironment variables of the program, an object of texts
stopOnEntryStop at the first statement
justMyCodetrue unless it says false: a step does not stop in a file of the standard library, and its frames are shown faded
noDebugRun without stopping: VS Code's Run Without Debugging
consoleinternalConsole (the default): the program's pipes, shown as output. integratedTerminal or externalTerminal: the program runs in the editor's terminal and reads its standard input

The program is checked and lowered as torb run lowers it, with a marker in front of every statement and every binding kept alive to the end of its block - what a debugger needs to stop at a line and to show a value there (the design record, sections 5 and 6). A program that does not check is not run: what torb check would say arrives as output, and launch fails.

Requests#

RequestWhat the debugger does
setBreakpointsBinds each line to the first statement of it in every function that has one there, or to the next line that has one, and answers the id and the line it bound to; reads a condition, a hitCondition and a logMessage (Breakpoints with a condition)
setExceptionBreakpointsThe filter panic, "Panics", on by default: the program stops where a panic begins - a failing assert too - before it ends the program or fails its test
configurationDoneThe program starts
threadsmain (id 1), the program's own stack, and one thread task <n> (id n + 1, kept for the task's life) for every task that runs, waits or has not started: a debugged program runs its tasks on one worker, so one stop stops every thread
stackTraceThe frames, innermost first: a closure's body above the function that called it, a test body above the test file's top level, a task above the frames that resumed it; a test's and a group's body named after it, test "doubles", group "Math"; threadId picks the thread, and a task that waits has one frame, at the statement it waits in, with its locals
scopes, variablesOne scope per frame, Locals: its parameters, its captures and every binding in sight at its line, with their values; a record, a case, a list, a map or a set opens into its fields, items or entries, a long list in pages
evaluateA name of the frame with fields, tuple fields, items and entries below it - point.x, pair.0, names[2], ages["Ada"] - for a hover, a watch and the debug console alike
setVariableWrites a number, a Bool or a Char into the local, field, tuple field, array item or var parameter that variables shows - VS Code's Set Value; see Setting a variable below
next, stepIn, stepOutOver the rest of the line, into the next call, out to the caller's next line
continue, pauseOn until the next stop; a stop at the next statement the program runs
exceptionInfoThe message of the panic it stopped at
disconnect, terminateThe program's process ends at once

A request that needs a stopped program and arrives while it runs waits until it stops; pause, disconnect and terminate are acted on at once. A running program reads its requests every thousand statements, so a program that waits in a native call - reading a file, sleeping, waiting for a task - pauses when it runs its next statement.

Setting a variable#

setVariable writes one scalar at a stop, and nothing runs. The value is one literal - 42, -1, 2.5, 'a', true, false - that fits the variable: a whole number inside the range of its integer type (300 is refused for a UInt8), a number for a float, true or false for a Bool, a character for a Char. The answer carries the new value, its type and a variablesReference of 0, and the next variables request shows the change in the parent and in every view of it.

It can write a local, a field of a record, a tuple or a shared type object, an item of a fixed Array, a captured var and a var parameter (the caller's variable changes with it). It refuses, with success: false and a message, a String, a list, a map, a case or a whole record (Only numbers, Bool and Char can be set), an item of a List, a Map or a Set, a field of a case's payload, constant data of the program, a value that does not fit, a name or a reference the stop does not have, and a request that arrives while the program runs - it waits for the stop, where its reference is gone. A record held by several bindings shows the write through all of them, as it does in any native debugger.

Breakpoints with a condition, a hit count or a message#

A breakpoint may carry three refinements, which the debugger reads in the innermost frame and which never run code of the program:

FieldWhat it isExample
conditionNames, fields, items and literals compared with == != < <= > >=, joined with !, &&, `
hitConditionWhich hits stop: N, == N, >= N, > N, <= N, < N, or % N for every Nth3, >= 4, % 2
logMessageWritten as console output instead of stopping; {expression} is replaced by the value, \{ and \} are bracesi={i} total={total}

A hit is a time the line is reached with its condition true; the counter starts at 0 at launch, survives a setBreakpoints that sends the breakpoint again unchanged, and restarts when its line, condition, hit condition or message changed. A condition that cannot be read - or a hit condition - is answered verified: false with a message. One that cannot be evaluated while the program runs (a name that is not there, operands of different kinds, a result that is no Bool) stops the program and says why in a console output (Breakpoint condition 'missing == 1' at main.trb:15 could not be evaluated: ...). A stopped event of a breakpoint carries hitBreakpointIds. A logpoint never stops the program; an expression of its message that cannot be evaluated is written <error: why>. A step in progress still stops, with the reason step, at a line whose condition was false.

Values#

A value is shown the way the source writes it: 42, 2.5, true, 'a', "text", Point(x: 1, y: 2), Some(3), Shape.Circle(radius: 2.0), [10, 20, 30], ["a": 1], and List<Int>(1200) for a list too long for one line. A binding in sight always holds its value: nothing a debugger shows is ever read out of freed memory.

Exit codes#

The adapter ends with 0. The debuggee ends with its program's exit code - 101 after a panic - which the exited event carries, and with 1 where the program could not be launched. 2 for an argument either does not take.

Examples#

A session as it goes over the wire, without the headers, for a program whose line 18 calls a function:

--> {"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"torbscript"}}
<-- {"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true, ...}}
--> {"seq":2,"type":"request","command":"launch","arguments":{"program":"/work/app/main.trb"}}
<-- {"seq":2,"type":"response","request_seq":2,"success":true,"command":"launch","body":{}}
<-- {"seq":3,"type":"event","event":"initialized","body":{}}
--> {"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"path":"/work/app/main.trb"},"breakpoints":[{"line":18}]}}
<-- {"seq":4,"type":"response","request_seq":3,"success":true,"command":"setBreakpoints","body":{"breakpoints":[{"id":1,"verified":true,"line":18}]}}
--> {"seq":4,"type":"request","command":"configurationDone"}
<-- {"seq":5,"type":"response","request_seq":4,"success":true,"command":"configurationDone","body":{}}
<-- {"seq":6,"type":"event","event":"stopped","body":{"reason":"breakpoint","threadId":1,"allThreadsStopped":true,"hitBreakpointIds":[1]}}
--> {"seq":5,"type":"request","command":"evaluate","arguments":{"expression":"origin.x","frameId":1}}
<-- {"seq":7,"type":"response","request_seq":5,"success":true,"command":"evaluate","body":{"result":"1","type":"Int","variablesReference":0}}
  • Set up your editor - the Debug buttons of VS Code, and another editor's settings.
  • The debugger - the design record: why the VM first, what a debugged program carries, and what is left.
  • torb run - what runs, without a debugger.
  • torb test - the tests and the filter a debug session of tests takes.
Esc