There are two debuggers available with MIT/GNU Scheme. One of them runs under Edwin, and is described in that section of this document (see Edwin Debugger). The other is command-line oriented, does not require Edwin, and is described here.
The command-line debugger, called
debug, is the tool you
should use when Scheme signals an error and you want to find out what
caused the error. When Scheme signals an error, it records all the
information necessary to continue running the Scheme program that caused
the error; the debugger provides you with the means to inspect this
information. For this reason, the debugger is sometimes called a
Here is the transcript of a typical Scheme session, showing a user
evaluating the expression `(fib 10)', Scheme responding with an
unbound variable error for the variable
fob, and the user
starting the debugger:
1 ]=> (fib 10) ;Unbound variable: fob ;To continue, call RESTART with an option number: ; (RESTART 3) => Specify a value to use instead of fob. ; (RESTART 2) => Define fob to a given value. ; (RESTART 1) => Return to read-eval-print level 1. 2 error> (debug) There are 6 subproblems on the stack. Subproblem level: 0 (this is the lowest subproblem level) Expression (from stack): fob Environment created by the procedure: FIB applied to: (10) The execution history for this subproblem contains 1 reduction. You are now in the debugger. Type q to quit, ? for commands. 3 debug>
This tells us that the error occurred while trying to evaluate the expression `fob' while running `(fib 10)'. It also tells us this is subproblem level 0, the first of 6 subproblems that are available for us to examine. The expression shown is marked `(from stack)', which tells us that this expression was reconstructed from the interpreter's internal data structures. Another source of information is the execution history, which keeps a record of expressions evaluated by the interpreter. The debugger informs us that the execution history has recorded some information for this subproblem, specifically a description of one reduction.
What follows is a description of the commands available in the debugger. To understand how the debugger works, you need to understand that the debugger has an implicit state that is examined and modified by commands. The state consists of three pieces of information: a subproblem, a reduction, and an environment frame. Each of these parts of the implicit state is said to be selected; thus one refers to the selected subproblem, and so forth. The debugger provides commands that examine the selected state, and allow you to select different states.
Here are the debugger commands. Each of these commands consists of a single letter, which is to be typed by itself at the debugger prompt. It is not necessary to type <RET> after these commands.
0representing the most recent time point, and ascending integers numbering older time points. The u command moves up to older points in time, and the d command moves down to newer points in time. The g command allows you to select a subproblem by number, and the h command will show you a brief summary of all of the subproblems.
pp) the subproblem's expression.
let. These frames collectively represent the block structure of a given environment.
Once an environment frame is selected by the debugger, it is possible to
select the parent frame of that frame (in other words, the enclosing
block) using the p command. You can subsequently return to the
original child frame using the s command. The s command
works because the p command keeps track of the frames that you
step through as you move up the environment hierarchy; the s
command just retraces the path of saved frames. Note that selecting a
frame using p or s will print the bindings of the newly
restart. The v command prompts for a single expression and evaluates it in the selected environment. The w command invokes the environment inspector (
where); quitting the environment inspector returns to the debugger. Finally, the o command pretty-prints the procedure that was called to create the selected environment frame.
The other two commands allow you to invoke internal continuations. This should not be done lightly; invoking an internal continuation can violate assumptions that the programmer made and cause unexpected results. Each of these commands works in the same way: it prompts you for an expression, which is evaluated in the selected environment to produce a value. The appropriate internal continuation is then invoked with that value as its sole argument. The two commands differ only in which internal continuation is to be invoked.
The j command invokes the continuation associated with the selected subproblem. What this means is as follows: when the description of a subproblem is printed, it consists of two parts, and “expression” and a “subproblem being executed”. The latter is usually marked in the former by the specific character sequence `###'. The internal continuation of the subproblem is the code that is waiting for the “subproblem being executed” to return a value. So, in effect, you are telling the program what the “subproblem being executed” will evaluate to, and bypassing further execution of that code.
The z command is slightly different. It instead invokes the
continuation that is waiting for the outer “expression” to finish. In
other words, it is the same as invoking the j command in the next
frame up. So you can think of this as an abbreviation for the u
command followed by the j command.