Chapter 4. Debugging Facilities
Prev |  | Next

# Chapter 4. Debugging Facilities

No Scheme system would be complete without facilities to assist the programmer in debugging his or her code. SISC provides aid for passive debugging (requiring no action on the part of the programmer) and active debugging (requiring code instrumentation to facilitate debugging).

## Passive Debugging

Passive debugging facilities are provided that collect information on an error that occurred at runtime and was not caught by the executing code. The programmer can then inspect the last error, obtain information about the call stack of the error, or even attempt to restart the computation.

> procedure: `(get-last-exception) => exception`

> > Retrieves the last exception that occurred in SISC.

One of the most common desires is to obtain a trace of the call stack, to determine what sequence of calls resulted in the error. SISC provides procedures for accessing the call stack of any continuation.

> Requires: (import debugging)

> procedure: `(stack-trace continuation) => list`

> > Returns the stack trace for `continuation` in form of a list. The format of the list is

> > stack-trace | := | `(`call-frame ...`)`
> > call-frame | := | sisc-expr | `(`sisc-expr overflown `.` stack`)`
> > overflown | := | #t | #f
> > stack | := | `(`stack-entry ...`)`
> > stack-entry | := | sisc-expr | `(`repetitions `.` stack`)`
> > repetitions | := | integer

> > Each element in the list represents one level in the SISC interpreter's call stack, starting from the top. The element contains the SISC virtual machine expression that would be executed next in that frame, and, if available, a compact representation of a virtual stack created by collecting information on tail calls carried out in that frame.

> > The virtual stack is bounded in size by the value of the see `maxStackTraceDepth` configuration parameter (see the section called “Configuration Parameters”). If old information was dropped due to the bound being exceeded then the `overflown` flag is set.

> > Each entry in the virtual stack contains either the SISC virtual machine expression that was executed, or a sub-stack annotated with a repetition count, indicating that the entries in that sub-stack were repeated several times.

> > SISC expressions are annotated with source locations if the `emitAnnotations` parameter is set to `true`. Additional annotations are produced when `emitDebuggingSymbols` is set to `true`. See the section called “Configuration Parameters”. The annotations can be retrieved using the `annotation` function with the keys `source-file`, `line-number`, `column-number`, `source-kind`, and `proc-name`.

> > Stack trace entries for expressions with a `source-kind` mentioned on `suppressed-stack-trace-source-kinds` are suppressed.

> procedure: `(suppressed-stack-trace-source-kinds [list]) => list`

> > Retrieves or sets the list of source kinds to suppress in stack traces returned by `stack-trace`. This is a dynamic parameter.

> > The default value is `(#f)` which causes all stack trace entries for expressions with no specified source kind to be suppressed.

> > The annotation of expressions with source kinds and other information is controlled by the `source-annotations` parameter.

> procedure: `(source-annotations [alist]) => alist`

> > Retrieves or sets the association list of additional annotations for expressions that are being read. This is a dynamic parameter.

> > All system and core library code is loaded with this parameter set to `()`, resulting in no additional annotations being produced. However, on entry to the REPL, the parameter is set to `((source-kind . user))`. In combination with the default settings for `suppressed-stack-trace-source-kinds` this results in system code being omitted from stack traces.

> procedure: `(print-stack-trace continuation)`

> > Displays the call stack of the `continuation` in a human-readable form.

The error message, location information and call stack associated with an exception can be displayed in human-readable form using the following procedure.

> procedure: `(print-exception exception [stack-trace?])`

> > Displays the error message and location of `exception`. A stack trace is displayed if `stack-trace?` is absent or set to #t. Furthermore the procedure calls itself recursively in order to display similar information for nested exceptions.

In order to obtain the source file location of a call, your Scheme code must have been loaded while SISC's reader had annotations enabled. Annotations are a means of attaching metadata to compiled Scheme code. To allow the reader to attach annotations related to the source file position of the code it reads, enable the emission of annotations with the `emitAnnotations` configuration parameter (see the section called “Configuration Parameters”).

SISC can also produce more detailed stack traces if code was generated with debugging symbols. These are extra annotations generated by the compiler that track function and variable names that would ordinarily be discarded. By including these annotations, the stack trace can display the name of more of the calls involved. To enable the generation of debugging symbols, the `emitDebuggingSymbols` configuration parameter must be set to `true` (see the section called “Configuration Parameters”).

Finally, when debugging a program for a long period of time, it may be desirable to have stack traces displayed whenever an error occurs, rather than needing to invoke `print-exception` or other functions each time. For this, the `stackTraceOnError` configuratin parameter must be set to `true` (see the section called “Configuration Parameters”).

## Active Debugging

Requires: (import debugging)

SISC provides active debugging aids; procedures and syntax that can be used in source code to assist in tracing the activities of running Scheme code.

### Runtime Tracing

When a function is traced, each call to the function will be displayed to the console with the function's trace identifier and the values of the operands the function is being applied to. Each nested call is indented slightly, so as to illustrate the depth of calls. When the function application returns, the value of the function-call is displayed at the same indentation as the call itself. Once indented to a certain depth, the same indentation is kept for further nesting, but the depth of the call is displayed as an integer preceding the call.

> syntax: `(trace-lambda trace-name formals body) => procedure`

> > When replaced with a trace-lambda, all calls to a lambda defined procedure are traced on the console. `trace-name` is an identifier which will disambiguate the procedure in the trace. `formals` and `body` have identical semantics to `lambda`.

> syntax: `(trace-let loop-identifier formal-bindings body) => value`

> > Replaces a named-let expression in a similar manner to trace-lambda.

> procedure: `(trace [symbol] ...) => undefined`

> > Begins traces on the procedures named by the symbols given. The procedures must be defined in the top-level environment.

> > If no procedures are given, a message is displayed indicating the names of top-level procedures currently being traced.

> > If a traced procedure is redefined, it will not retain the instrumenting installed by `trace`, until `trace` or `untrace` is called again (with any arguments). At that time, the traced procedures are reinspected and instrumenting reinstalled on redefined procedures.

> procedure: `(untrace [symbol] ...) => undefined`

> > Stops tracing the top-level procedures named by the symbols given.

> > If no procedures are given, a message is displayed indicating the names of top-level procedures currently being traced.

`trace-lambda` and `trace-let` are useful for debugging anonymous lambdas and named-lets respectively. `trace` and `untrace` ar useful for tracing any top-level bound procedure, including calls to builtin procedures and stored continuations.

### Note

Tracing a function installs instrumentation code around the procedure which does not preserve the continuation of a call to that function. Thus, tail calls made in a traced function are no longer tail calls. This may affect the memory usage characteristics of running code.

### Breakpoints

A user may wish to halt execution of a running Scheme program when a given procedure is called. SISC provides means to install breakpoints at top-level visible functions without having to redefine the function.

When a breakpoint is set using `set-breakpoint!`, and the function is called, execution will halt, returning to the REPL and displaying an informational message indicating a break, the procedure called, the arguments passed to the breakpointed procedure, and, if possible, the location in a source file of the call. The user may then continue execution using the `continue` procedure.

> procedure: `(set-breakpoint! symbol) => undefined`

> > Instruments the top-level procedure named by the given symbol, such that when called, execution will halt and return to the REPL and the name of the breakpointed function and its arguments are displayed.

> procedure: `(clear-breakpoint! symbol) => undefined`

> > Removes the instrumentation on the named top-level procedure, if present. Execution will continue normally through occurances of the formally breakpointed procedure.

> procedure: `(continue) => does not return`

> > Continues execution from the most recent break. It is an error to call this procedure if a breakpoint has not been hit, or to call this procedure more than once for a given break.

> procedure: `(current-breakpoint-continuation) => continuation`

> > Returns the continuation of the most recent breakpoint, or #f if execution is not currently interrupted at a breakpoint.

> > The continuation is useful for obtaining stack traces, e.g. with `(print-stack-trace (current-breakpoint-continuation))`.

> procedure: `(current-breakpoint-args) => list`

> > Returns a list containing the current breakpoint's continuation procedure and arguments that will be used when execution is resumed with `continue`, or #f if execution is not currently interrupted at a breakpoint.

> > This procedure is useful for performing deep inspection of the breakpointed procedure and its arguments. The returned values are also handy for constructing modified breakpoint continuations with `set-current-breakpoint-args!`.

> procedure: `(set-current-breakpoint-args! procedure value ...) => #t/#f`

> > Sets the current breakpoint's continuation procedure and arguments that will be used when execution is resumed with `continue`. If execution is not currently interrupted at a breakpoint then invoking this procedure has no effect and it returns #f. Otherwise it returns #t.

Prev |  | Next
Chapter 3. Scheme Language | Home | Chapter 5. I/O