(workshop logo)

Debugging Chez Scheme programs

Debugging in Scheme usually involves overwriting the potentially buggy versions of procedure definitions with new versions that print diagnostic information or activate debugging facilities, studying the results, and then overwriting the procedure definitions again with corrected versions. A general principle that novice Scheme programmers should master early on is that one should use the editor make a copy of the potentially buggy procedure definition and edit the debugging commands into the copy, which is then submitted to the interactive interpreter. When the bugs are identified, one can then correct them in the original and discard the copy, instead of having to locate and remove all of the debugging commands from a single, repeatedly modified text. Here is an overview of the debugging facilities provided by Chez Scheme; they are described in more detail below. Chapter 3 of the Chez Scheme version 5 system manual describes all of these facilities in exhaustive detail.

Tracing

Chez Scheme provides a facility for tracing the execution of a Scheme program. This facility generates a line of output at the beginning of each call to a traced procedure, giving the name of the procedure and the values of the arguments in that particular call, and another line of output at the end of the call, giving the value returned by the procedure. Here's what the trace for a call to a recursively defined factorial function looks like:

> (factorial 12)
|(factorial 12)
| (factorial 11)
| |(factorial 10)
| | (factorial 9)
| | |(factorial 8)
| | | (factorial 7)
| | | |(factorial 6)
| | | | (factorial 5)
| | | | |(factorial 4)
| | | | | (factorial 3)
| | | |[10](factorial 2)
| | | |[11](factorial 1)
| | | |[12](factorial 0)
| | | |[12]1
| | | |[11]1
| | | |[10]2
| | | | | 6
| | | | |24
| | | | 120
| | | |720
| | | 5040
| | |40320
| | 362880
| |3628800
| 39916800
|479001600
479001600
>
The vertical bars and bracketed numbers indicate the depth of the run-time stack (the number of procedure calls that have been initiated but not yet completed).

To turn on tracing for a procedure, the programmer re-enters the definition of the procedure, replacing the keyword define with trace-define. To turn it off again, the programmer re-enters the original definition. Tracing can be turned on or off independently for any number of procedures.

Warning points

To attach a warning point to any expression e inside a procedure definition, so that the warning line will be printed just before the expression is evaluated, the programmer replaces e with

(begin (warning f s) e)
where f is a symbol identifying the warning point and s is a string offering advisory information. A typical instance might look like this:

(begin (warning 'extract "An empty list was encountered") 0)
This would result in the printing of the line

Warning in extract: An empty list was encountered.
Note that the program does not stop at warning points; it prints out the advisory and continues running without interruption.

Break points

The installation of a break point uses exactly the same mechanism as the installation of a warning point, except that one invokes the break procedure rather than the warning procedure:
(begin (break 'extract "An empty list was encountered") 0)
When this expression is evaluated, Chez Scheme prints

Break in extract: An empty list was encountered.
and fires up the interactive debugger.

Pressing <Control/C> in the middle of a computation has the same effect as a break point except that no advisory line is printed before the debugger is started.

The interactive debugger

When activating the interactive debugger, Chez Scheme stores enough information about the interrupted computation to be able to resume it later at exactly the point of interruption. It then issues the debugger's distinctive prompt:

debug>
The debugger is extremely simple in design. There are only seven commands, each of which is a single letter:

The interactive inspector

The interactive inspector is an application inside which one can examine and traverse data structures that the currently running program has constructed. It too has a distinctive prompt, consisting of a printed representation of the object currently under inspection (in a field sixty-eight columns wide) and a colon:

debug> i
#<system continuation in break>                                   : 
UCs are expected to know only three commands for the interactive inspector:

Errors and the error procedure

As a Scheme program runs, it checks to make sure that certain preconditions are met for each operation that is requested. A procedure is not invoked unless the call contains the correct number of arguments and they are of acceptable types, a file cannot be opened unless it exists and the user has read permission for it, the value of a variable cannot be recovered if the variable has never been defined, and so on. When one of these preconditions is not met, Chez Scheme issues an error message, stores the information about the context of the error and the nature of the interrupted computation, and generates a new prompt. At this point, the user can either issue any commands that may be necessary to repair the damage and re-start the program, or type (debug) to enter the interactive debugger to collect further information.

The two-line error message produced when Chez Scheme detects an error looks like this (the diagnosis varies with the nature of the error, of course):

Error: variable frogs is not bound.
Type (debug) to enter the debugger.
Here are the ``diagnosis'' parts of some typical error messages and their usual causes:

attempt to apply non-procedure ...
The program tried to evaluate some expression that looks superficially like a procedure call, but the value of the first expression turned out not to be a procedure.

incorrect number of arguments to #<procedure ...>
The program tried to call a procedure, but gave it either too few arguments or too many.

variable ... is not bound
The program tried to evaluate a variable that was never defined or otherwise given a value. It may be a misspelling for some other variable.

... is not an integer
The program tried to call some procedure that accepts only integer arguments, such as modulo or gcd, but gave it a non-integer value.

unexpected end-of-file on #<input port ...>
A file from which Chez Scheme was loading a program ended without terminating all of the syntactic constructs that it started. The likely causes are (1) a missing right parenthesis; (2) a missing double quotation mark at the end of a string literal -- note that a Scheme string literal can extend over more than one line; (3) a comment that accidently blocks out part of the syntactic structure of the program.

invalid vector size request
The program tried to allocate a vector (array) of negative size.

cannot read from #<closed input port ...>
The program tried to read from a file after closing it.

A programmer can also induce a run-time error by invoking the error procedure, which is analogous to the warning and break procedures; evaluating the expression

(error 'extract "An empty list was encountered")
cuts off the computation and generates the error message

Error in extract: An empty list was encountered.
Type (debug) to enter the debugger.
followed by a new prompt.

Inspection points

To install an inspection point in a procedure definition, so that the program will pause and permit the inspection of a datum at that point, the programmer inserts a call to the inspect procedure, giving it the value to be inspected as argument:
(inspect frog-tree)
No advisory message is printed when this procedure is invoked; the next thing the user sees is the inspector prompt.


Workshop front door ... Running Chez Scheme ... Editing ... Scheme bibliography


created December 4, 1996
last revised December 5, 1996

John David Stone (stone@math.grin.edu)