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.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
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 startsWhat 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#
| Argument | Meaning |
|---|---|
program | A file or a directory, as torb run takes it |
args | The program's arguments, what Process.arguments() answers |
test | A test file or a directory of tests instead, as torb test takes it |
filter | With test: the full name of a test or a group (Group > test), or a list of them, as torb test --filter takes them |
cwd | The program's working directory; a relative program is found from it |
env | Environment variables of the program, an object of texts |
stopOnEntry | Stop at the first statement |
justMyCode | true unless it says false: a step does not stop in a file of the standard library, and its frames are shown faded |
noDebug | Run without stopping: VS Code's Run Without Debugging |
console | internalConsole (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#
| Request | What the debugger does |
|---|---|
setBreakpoints | Binds 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) |
setExceptionBreakpoints | The 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 |
configurationDone | The program starts |
threads | main (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 |
stackTrace | The 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, variables | One 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 |
evaluate | A 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 |
setVariable | Writes 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, stepOut | Over the rest of the line, into the next call, out to the caller's next line |
continue, pause | On until the next stop; a stop at the next statement the program runs |
exceptionInfo | The message of the panic it stopped at |
disconnect, terminate | The 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:
| Field | What it is | Example |
|---|---|---|
condition | Names, fields, items and literals compared with == != < <= > >=, joined with !, &&, ` | |
hitCondition | Which hits stop: N, == N, >= N, > N, <= N, < N, or % N for every Nth | 3, >= 4, % 2 |
logMessage | Written as console output instead of stopping; {expression} is replaced by the value, \{ and \} are braces | i={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}}Related#
- 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.