Table of Contents
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 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.
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 theoverflown
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 totrue
. Additional annotations are produced whenemitDebuggingSymbols
is set totrue
. See the section called “Configuration Parameters”. The annotations can be retrieved using theannotation
function with the keyssource-file
,line-number
,column-number
,source-kind
, andproc-name
.Stack trace entries for expressions with a
source-kind
mentioned onsuppressed-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 forsuppressed-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 ifstack-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”).
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.
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
andbody
have identical semantics tolambda
.
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
, untiltrace
oruntrace
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.
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.
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.