Chapter 2. Debugging

Chez Scheme has several features that support debugging. In addition to providing error messages when fully type-checked code is run, Chez Scheme also permits tracing of procedure calls, interruption of any computation, redefinition of error and interrupt handlers, and inspection of any object, including the continuations of errors and interrupts.


Section 2.1. Tracing

Tracing is one of the most useful mechanisms for debugging Scheme programs. Chez Scheme permits any primitive or user-defined procedure to be traced. The trace package prints the arguments and return values for each traced procedure with a compact indentation mechanism that shows the nesting depth of calls. The distinction between tail calls and nontail calls is reflected properly by an increase in indentation for nontail calls only. For nesting depths of 10 or greater, a number in brackets is used in place of indentation to signify nesting depth.

This section covers the mechanisms for tracing procedures and controlling trace output.


syntax: (trace-lambda name formals exp1 exp2 ...)
returns: a traced procedure

A trace-lambda expression is equivalent to a lambda expression with the same formals and body except that trace information is printed to the current output port whenever the procedure is invoked, using name to identify the procedure. The trace information shows the value of the arguments passed to the procedure and the values returned by the procedure, with indentation to show the nesting of calls.

The traced procedure half defined below returns the integer quotient of its argument and 2.

(define half
  (trace-lambda half (x)
    (cond
      [(zero? x) 0]
      [(odd? x) (half (- x 1))]
      [(even? x) (+ (half (- x 1)) 1)])))

A trace of the call (half 5), which returns 2, is shown below.

|(half 5)
|(half 4)
| (half 3)
| (half 2)
| |(half 1)
| |(half 0)
| |0
| 1
|2

This example highlights the proper treatment of tail and nontail calls by the trace package. Since half tail calls itself when its argument is odd, the call (half 4) appears at the same level of indentation as the call (half 5). Furthermore, since the return values of (half 5) and (half 4) are necessarily the same, only one return value is shown for both calls.


syntax: (trace-let name ((var val) ...) exp1 exp2 ...)
returns: value of the last expression

A trace-let expression is equivalent to a named let expression with the same name, bindings, and body except that trace information is printed to the current output port on entry or reentry (via invocation of the procedure bound to name) into the trace-let expression.

A trace-let expression of the form

(trace-let name ((var val) ...)
  exp1 exp2 ...)

can be rewritten in terms of trace-lambda as follows:

((letrec ((name
           (trace-lambda name (var ...)
             exp1 exp2 ...)))
   name)
 val ...)

trace-let may be used to trace ordinary let expressions as well as let expressions as long as the name inserted along with the trace-let keyword in place of let does not appear free within the body of the let expression. It is also sometimes useful to insert a trace-let expression into a program simply to display the value of an arbitrary expression at the current trace indentation. For example, a call to the following variant of half

(define half
  (trace-lambda half (x)
    (cond
      [(zero? x) 0]
      [(odd? x) (half (trace-let decr-value () (- x 1)))]
      [(even? x) (+ (half (- x 1)) 1)])))

with argument 5 results in the trace:

|(half 5)
| (decr-value)
| 4
|(half 4)
| (half 3)
| |(decr-value)
| |2
| (half 2)
| |(half 1)
| | (decr-value)
| | 0
| |(half 0)
| 1
|2


syntax: (trace var1 var2 ...)
returns: a list of var1 var2 ...
syntax: (trace)
returns: a list of all currently traced top-level variables

In the first form, trace reassigns the top-level values of var1 var2 ..., whose values must be procedures, to equivalent procedures that display trace information in the manner of trace-lambda.

trace works by encapsulating the old value of each var in a traced procedure. It could be defined approximately as follows. (The actual version records and returns information about traced variables.)

(define-syntax trace
  (syntax-rules ()
    [(_ var ...)
     (begin
       (set-top-level-value! 'var
         (let ((p (top-level-value 'var)))
           (trace-lambda var args (apply p args))))
       ...)]))

Tracing for a procedure traced in this manner may be disabled via untrace (see below), an assignment of the corresponding variable to a different, untraced value, or a subsequent use of trace for the same variable. Because the value is traced and not the binding, however, a traced value obtained before tracing is disabled and retained after tracing is disabled will remain traced.

trace without subexpressions evaluates to a list of all currently traced variables. A variable is currently traced if it has been traced and not subsequently untraced or assigned to a different value.

The following transcript demonstrates the use of trace in an interactive session.

> (define half
    (lambda (x)
      (cond
        [(zero? x) 0]
        [(odd? x) (half (- x 1))]
        [(even? x) (+ (half (- x 1)) 1)])))
> (half 5)
2
> (trace half)
(half)
> (half 5)
|(half 5)
|(half 4)
| (half 3)
| (half 2)
| |(half 1)
| |(half 0)
| |0
| 1
|2
2
> (define traced-half half)
> (untrace half)
(half)
> (half 2)
1
> (traced-half 2)
|(half 2)
|1
1


syntax: (untrace var1 var2 ...)
syntax: (untrace)
returns: a list of untraced variables

untrace restores the original (pre-trace) top-level values of each currently traced variable in var1 var2 ..., effectively disabling the tracing of the values of these variables. Any variable in var1 var2 ... that is not currently traced is ignored. If untrace is called without arguments, the values of all currently traced variables are restored.

The following transcript demonstrates the use of trace and untrace in an interactive session to debug an incorrect procedure definition.

> (define square-minus-one
    (lambda (x)
      (- (* x x) 2)))
> (square-minus-one 3)
7
> (trace square-minus-one * -)
(square-minus-one * -)
> (square-minus-one 3)
|(square-minus-one 3)
| (* 3 3)
| 9
|(- 9 2)
|7
7
> (define square-minus-one
    (lambda (x)
      (- (* x x) 1))) ; change the 2 to 1
> (trace)
(- *)
> (square-minus-one 3)
|(* 3 3)
|9
|(- 9 1)
|8
8
> (untrace square-minus-one)
()
> (untrace * -)
(- *)
> (square-minus-one 3)
8

The first call to square-minus-one indicates there is an error, the second (traced) call indicates the step at which the error occurs, the third call demonstrates that the fix works, and the fourth call demonstrates that untrace does not wipe out the fix.


parameter: trace-output-port

trace-output-port is a parameter that determines the output port to which tracing information is sent. When called with no arguments, trace-output-port returns the current trace output port. When called with an output port argument, trace-output-port changes the value of the current trace output port.


parameter: trace-print

The value of trace-print must be a procedure of two arguments, an object and an output port. The trace package uses the value of trace-print to print the arguments and return values for each call to a traced procedure. trace-print is set to pretty-print by default.

The trace package sets pretty-initial-indent to an appropriate value for the current nesting level before calling the value of trace-print so that multiline output can be indented properly.


syntax: (trace-define var exp)
syntax: (trace-define (var . idspec) exp1 exp2 ...)
returns: unspecified

trace-define is a convenient shorthand for defining variables bound to traced procedures of the same name. The first form is equivalent to

(define var
  (let ([x exp])
    (trace-lambda var args
      (apply x args))))

and the second is equivalent to

(define var
  (trace-lambda var idspec
    exp1 exp2 ...))

In the former case, exp must evaluate to a procedure.

> (let ()
    (trace-define plus
      (lambda (x y) 
        (+ x y)))
    (list (plus 3 4) (+ 5 6)))
|(plus 3 4)
|7
(7 11)


Section 2.2. The Interactive Debugger

The interactive debugger is entered as a result of

The default error handler, default keyboard-interrupt handler, mechanisms for signaling errors, and break are described in Section 10.1.

Within the debugger, the command "?" lists the debugger command options. These include commands to:

It is also possible to exit from the debugger in some installations by typing the end-of-file character ("control-D" under Unix, "control-Z" under Windows).


procedure: (debug)
returns: does not return

The default error handler saves the continuation of the error and resets. After an error occurs, debug may be used to return to the continuation of the error and enter the debugger. From within the debugger the inspector can be invoked to examine the continuation of the original call to the error handler. If the debugger exits normally a reset occurs.

Since debug invokes the continuation of the last call to the default error handler, the in thunks associated with any call to dynamic-wind (see page 94 of The Scheme Programming Language, Second Edition) that was active when the error occurred are invoked as well. If this causes difficulties, it may be preferable to set up an error handler that directly invokes the debugger (see page 186), so that active calls to dynamic-wind are neither exited from when an error occurs nor reentered when debug is invoked.


Section 2.3. The Interactive Inspector

The inspector may be called directly via the procedure inspect or indirectly from the debugger. It allows the programmer to examine circular objects, objects such as ports and procedures that do not have a reader syntax, and objects such as continuations and variables that are not directly accessible by the programmer, as well as ordinary printable Scheme objects.

The primary intent of the inspector is examination, not alteration, of objects. The values of assignable variables may be changed from within the inspector, however. Assignable variables are generally limited to those for which assignments occur in the source program. It is also possible to invoke arbitrary procedures (including mutation procedures such as set-car!) on an object. No mechanism is provided for altering objects that are inherently immutable, e.g., nonassignable variables, procedures, and bignums, since doing so can violate assumptions made by the compiler and run-time system.

The user is presented with a prompt line that includes a printed representation of the current object, abbreviated if necessary to fit on the line. Various commands are provided for displaying objects and moving around inside of objects. On-line descriptions of the command options are provided. The command "?" displays commands that apply specifically to the current object. The command "??" displays commands that are always applicable. The command "h" provides a brief description of how to use the inspector. The end-of-file character or the command "q" exits the inspector.


procedure: (inspect obj)
returns: unspecified

Invokes the inspector on obj, as described above. The commands recognized by the inspector are listed below, categorized by the type of the current object.

Generally applicable commands

?? displays the generally applicable commands.

? displays commands applicable only to the current type of object.

help or h displays a brief description of how to use the inspector.

quit or q exits from the inspector.

print or p prints the current object (using pretty-print).

write or w writes the current object (using write).

file path opens the source file at the specified path for listing. The parameter source-directories determines the set of directories searched for source files identified by relative path names.

list line count lists count lines of the current source file (see file) starting at line. line defaults to the end of the previous set of lines listed and count defaults to ten or the number of lines previously listed. If line is negative, listing begins line lines before the previous set of lines listed.

files shows the currently open source files.

up or u n returns to the nth previous level. Used to move outwards in the structure of the inspected object. n defaults to 1.

top or t returns to the outermost level of the inspected object.

forward or f moves to the nth next expression. Used to move from one element to another of an object containing a sequence of elements, such as a list, vector, frame, or closure. n defaults to 1.

back or b moves to the nth previous expression. Used to move from one element to another of an object containing a sequence of elements, such as a list, vector, frame, or closure. n defaults to 1.

=> expr sends the current object to the procedure value of expr. expr may begin on the current or following line and may span multiple lines.

mark or m m marks the current location with the symbolic mark m. If m is not specified, the current location is marked with a unique default mark.

goto or g m returns to the location marked m. If m is not specified, the inspector returns to the location marked with the default mark.

new-cafe or n enters a new read-eval-print loop (café), giving access to the normal top-level environment.

Continuation commands

show-frames or sf shows the next n frames. If n is not specified, all frames are displayed.

depth displays the number of frames in the continuation.

down or d n move to the nth frame down in the continuation. n defaults to 1.

show or s shows the calling procedure source, pending call source, and frame elements. Source is available only if generation of inspector information was enabled during compilation of the corresponding lambda expression.

length or l displays the number of elements in the topmost frame of the continuation.

ref or r moves to the nth frame element. n defaults to 0.

code or c moves to the source for the calling procedure.

call moves to the source for the pending call.

file opens the source file containing the pending call, if known. The parameter source-directories determines the set of directories searched for source files identified by relative path names. file may fail to find a source file even if a file by the expected name is found in the path if the file has been modified since the source code was compiled. Pass an explicit filename argument to force opening of a particular file (see the generally applicable commands above).

eval or e expr evaluates the expression expr in an environment containing bindings for the elements of the frame. Within the evaluated expression, the value of each frame element n is accessible via the variable %n. Named elements are accessible via their names as well. Names are available only if generation of inspector information was enabled during compilation of the corresponding lambda expression.

set! or ! n v sets the value of the nth frame element to v, if the frame element corresponds to an assignable variable. n defaults to 0.

Procedure commands

show or s shows the source and free variables of the procedure. Source is available only if generation of inspector information was enabled during compilation of the corresponding lambda expression.

code or c moves to the source for the procedure.

file opens the file containing the procedure's source code, if known. The parameter source-directories determines the set of directories searched for source files identified by relative path names. file may fail to find a source file even if a file by the expected name is found in the path if the file has been modified since the source code was compiled. Pass an explicit filename argument to force opening of a particular file (see the generally applicable commands above).

length or l displays the number of free variables whose values are recorded in the procedure object.

ref or r n moves to the nth free variable. n defaults to 0.

set! or ! n v sets the value of the nth free variable to v, if the variable is assignable. n defaults to 0.

eval or e expr evaluates the expression expr in an environment containing bindings for the free variables of the procedure. Within the evaluated expression, the value of each free variable n is accessible via the variable %n. Named free variables are accessible via their names as well. Names are available only if generation of inspector information was enabled during compilation of the corresponding lambda expression.

info or i moves to the system's internal info structure for the procedure's code.

reloc moves to the system's internal relocation entries for the procedure's code.

Pair (list) commands

show or s n shows the first n elements of the list. If n is not specified, all elements are displayed.

length or l displays the list length.

car moves to the object in the car of the current object.

cdr moves to the object in the cdr.

ref or r n moves to the nth element of the list. n defaults to 0.

tail n moves to the nth cdr of the list. n defaults to 1.

Vector commands

show or s n shows the first n elements of the vector. If n is not specified, all elements are displayed.

length or l displays the vector length.

ref or r n moves to the nth element of the vector. n defaults to 0.

String commands

show or s n shows the first n elements of the string. If n is not specified, all elements are displayed.

length or l displays the string length.

ref or r n moves to the nth element of the string. n defaults to 0.

ascii n displays the first n elements of the string as hexadecimal ascii codes.

Symbol commands

show or s shows the fields of the symbol.

value or v moves to the top-level value of the symbol.

name or n moves to the name of the symbol.

property-list or pl moves to the property list of the symbol.

ref or r n moves to the nth field of the symbol. Field 0 is the top-level value of the symbol, field 1 is the symbol's name, and field 2 is its property list. n defaults to 0.

Character commands

ascii n displays the hexadecimal ascii code for the character.

Box commands

show or s shows the contents of the box.

unbox or ref or r moves to the boxed object.

Port commands

show or s shows the fields of the port, including the input and output size, index, and buffer fields.

handler moves to the port's handler.

output-buffer or ob moves to the port's output buffer.

input-buffer or ib moves to the port's input buffer.

Record commands

show or s shows the contents of the record.

fields or f inspects the list of field names of the record.

name inspects the name of the record.

print-method inspects the print-method of the record.

ref or r name inspects the named field of the record, if accessible.

set! or ! name value sets the value of the named field of the record, if mutable.


parameter: source-directories

The value of source-directories must be a list of strings, each of which names a directory path. source-directories determines the set of directories searched for source files when a syntax error is detected or a source file is opened in the interactive inspector.


Section 2.4. The Object Inspector

A facility for noninteractive inspection is also provided to allow construction of different inspection interfaces. Like the interactive facility, it allows objects to be examined in ways not ordinarily possible. The noninteractive system follows a simple, object-oriented protocol. Ordinary Scheme objects are encapsulated in procedures, or inspector objects, that take symbolic messages and return either information about the encapsulated object or new inspector objects that encapsulate pieces of the object.


procedure: (inspect/object object)
returns: an inspector object procedure

inspect/object is used to turn an ordinary Scheme object into an inspector object. All inspector objects accept the messages type, print, and write. The type message returns a symbolic representation of the type of the object. The print and write messages must be accompanied by a port parameter. They cause a representation of the object to be written to the port, using the Scheme procedures pretty-print and write. All inspector objects except for variable inspector objects accept the message value, which returns the actual object encapsulated in the inspector object.

(define x (inspect/object '(1 2 3)))
(x 'type)  pair
(define p (open-output-string))
(x 'write p)
(get-output-string p)  "(1 2 3)"
(x 'length)  (proper 3)
(define y (x 'car))
(y 'type)  simple
(y 'value)  1

Pair inspector objects. Pair inspector objects contain Scheme pairs.

(pair-object 'type) returns the symbol pair.

(pair-object 'car) returns an inspector object containing the "car" field of the pair.

(pair-object 'cdr) returns an inspector object containing the "cdr" field of the pair.

(pair-object 'length) returns a list of the form (type count). The type field contains the symbol proper, the symbol improper, or the symbol circular, depending on the structure of the list. The count field contains the number of distinct pairs in the list.

Box inspector objects. Box inspector objects contain Chez Scheme boxes.

(box-object 'type) returns the symbol box.

(box-object 'unbox) returns an inspector object containing the contents of the box.

Vector inspector objects. Vector inspector objects contain Scheme vectors.

(vector-object 'type) returns the symbol vector.

(vector-object 'length) returns the number of elements in the vector.

(vector-object 'ref n) returns an inspector object containing the nth element of the vector.

String inspector objects. String inspector objects contain Scheme strings.

(string-object 'type) returns the symbol string.

(string-object 'length) returns the number of characters in the string.

(string-object 'ref n) returns an inspector object containing the nth character of the string.

Simple inspector objects. Simple inspector objects contain unstructured, unmodifiable objects. These include numbers, booleans, the empty list, the end-of-file object, and the void object. They may be examined directly by asking for the value of the object.

(simple-object 'type) returns the symbol simple.

Unbound inspector objects. Although unbound objects are not normally accessible to Scheme programs, they may be encountered when inspecting variables.

(unbound-object 'type) returns the symbol unbound.

Procedure inspector objects. Procedure inspector objects contain Scheme procedures.

(procedure-object 'type) returns the symbol procedure.

(procedure-object 'length) returns the number of free variables.

(procedure-object 'ref n) returns an inspector object containing the nth free variable of the procedure. See the description below of variable inspector objects. n must be nonnegative and less than the length of the procedure.

(procedure-object 'eval) expr) evaluates expr and returns its value. The values of the procedure's free variables are bound within the evaluated expression to identifiers of the form %n, where n is the location number displayed by the inspector. The values of named variables are also bound to their names.

(procedure-object 'code) returns an inspector object containing the procedure's code object. See the description below of code inspector objects.

Continuation inspector objects. Continuations created by call/cc are actually procedures. However, when inspecting such a procedure the underlying data structure that embodies the continuation may be exposed. A continuation structure contains the location at which computation is to resume, the variable values necessary to perform the computation, and a link to the next continuation.

(continuation-object 'type) returns the symbol continuation.

(continuation-object 'length) returns the number of free variables.

(continuation-object 'ref n) returns an inspector object containing the nth free variable of the continuation. See the description below of variable inspector objects. n must be nonnegative and less than the length of the continuation.

(continuation-object 'eval) expr) evaluates expr and returns its value. The values of frame locations are bound within the evaluated expression to identifiers of the form %n, where n is the location number displayed by the inspector. The values of named locations are also bound to their names.

(continuation-object 'code) returns an inspector object containing the code object for the procedure that was active when the current continuation frame was created. See the description below of code inspector objects.

(continuation-object 'depth) returns the number of frames in the continuation.

(continuation-object 'link) returns an inspector object containing the next continuation frame. The depth must be greater than 1.

(continuation-object 'link* n) returns an inspector object containing the nth continuation link. n must be less than the depth.

(continuation-object 'source) returns an inspector object containing the source information attached to the continuation (representing the source for the application that resulted in the formation of the continuation) or #f if no source information is attached.

(continuation-object 'source-path) attempts to find the pathname of the file containing the source for the procedure application that resulted in the formation of the continuation. If successful, three values are returned to identify the file and position of the application within the file: path, line, and char. If unsuccessful, zero values are returned. The parameter source-directories determines the set of directories searched for source files identified by relative path names. The search may be unsuccessful even if a file by the expected name is found in the path if the file has been modified since the source code was compiled.

Code inspector objects. Code inspector objects contain Chez Scheme code objects.

(code-object 'type) returns the symbol code.

(code-object 'name) returns a string or #f. The name associated with a code inspector object is the name of the variable to which the procedure was originally bound or assigned. Since the binding of a variable can be changed, this name association may not always be accurate. #f is returned if the inspector cannot determine a name for the procedure.

(code-object 'source) returns an inspector object containing the source information attached to the code object or #f if no source information is attached.

(code-object 'source-path) attempts to find the pathname of the file containing the source for the lambda expression that produced the code object. If successful, three values are returned to identify the file and position of the expression within the file: path, line, and char. If unsuccessful, zero values are returned. The parameter source-directories determines the set of directories searched for source files identified by relative path names. The search may be unsuccessful even if a file by the expected name is found in the path if the file has been modified since the source code was compiled.

(code-object 'free-count) returns the number of free variables in any procedure for which this is the corresponding code.

(code-object 'info) returns an inspector object containing the system's internal info structure for the code object.

(code-object 'reloc) returns an inspector object containing the system's internal relocation entries for the code object.

Variable inspector objects. Variable inspector objects encapsulate variable bindings. Although the actual underlying representation varies, the variable inspector object provides a uniform interface.

(variable-object 'type) returns the symbol variable.

(variable-object 'name) returns a symbol or #f. #f is returned if the name is not available or if the variable is a compiler-generated temporary variable. Variable names are not retained when the parameter generate-inspector-information (page 10.3) is false during compilation.

(variable-object 'ref) returns an inspector object containing the current value of the variable.

(variable-object 'set! v) returns unspecified, after setting the current value of the variable to v. An error is signaled if the variable is not assignable.

Port inspector objects. Port inspector objects contain Chez Scheme ports.

(port-object 'type) returns the symbol port.

(port-object 'input?) returns #t if the port is an input port, #f otherwise.

(port-object 'output?) returns #t if the port is an output port, #f otherwise.

(port-object 'closed?) returns #t if the port is closed, #f if the port is open.

(port-object 'handler) returns a procedure inspector object encapsulating the port handler, such as would be returned by port-handler.

(port-object 'output-size) returns the output buffer size as a fixnum if the port is an output port (otherwise the value is unspecified).

(port-object 'output-index) returns the output buffer index as a fixnum if the port is an output port (otherwise the value is unspecified).

(port-object 'output-buffer) returns an inspector object containing the string used for buffered output.

(port-object 'input-size) returns the input buffer size as a fixnum if the port is an input port (otherwise the value is unspecified).

(port-object 'input-index) returns the input buffer index as a fixnum if the port is an input port (otherwise the value is unspecified).

(port-object 'input-buffer) returns an inspector object containing the string used for buffered input.

Symbol inspector objects. Symbol inspector objects contain Chez Scheme symbols. These include interned and uninterned symbols.

(symbol-object 'type) returns the symbol symbol.

(symbol-object 'name) returns a string inspector object. The string name associated with a symbol inspector object is the print representation of a symbol, such as would be returned by the procedure symbol->string.

(symbol-object 'uninterned?) returns #t if the symbol is an uninterned symbol, #f otherwise. Uninterned symbols are created by string->uninterned-symbol and gensym.

(symbol-object 'top-level-value) returns an inspector object containing the global value of the symbol.

(symbol-object 'property-list) returns an inspector object containing the property list for the symbol.

Record inspector objects. Record inspector objects contain Chez Scheme records.

(record-object 'type) returns the symbol record.

(record-object 'name) returns a string inspector object corresponding to the name of the record type.

(record-object 'fields) returns an inspector object containing a list of the field names of the record type.

(record-object 'print-method) returns an inspector object containing the print method of the record type.

(record-object 'accessible? name) returns #t if the named field is accessible, #f otherwise. A field may be inaccessible if optimized away by the compiler.

(record-object 'ref name) inspects the value of the named field. An error is signaled if the named field is not accessible.

(record-object 'mutable? name) returns #t if the named field is mutable, #f otherwise. A field is immutable if it is not declared mutable or if the compiler optimizes away all assignments to the field.

(record-object 'set! name value) sets the value of the named field to value. An error is signaled if the named field is not assignable.


Chez Scheme User's Guide
© 1998 R. Kent Dybvig
Cadence Research Systems
http://www.scheme.com
Illustrations © 1998 Jean-Pierre Hébert
about this book