Chapter 1

Debugging

It will become apparent to you many times during a program’s evolution that the program is not entirely right. Instead of blaming the program for these faults, strange creatures called bugs are blamed. In this way, the programmer remains free of guilt. Errors and incorrect results are sure signs that bugs lurk inside a program, for the bugs cause the aberrant behavior.

Debugging usually depends on empirical testing, thus it can serve to reinforce the programmer’s belief in the correctness of his program, and it can aid in correction of a program which is not behaving as expected, but it cannot prove that the program is correct. If the program succeeds during debugging, there may be some yet-to-be-uncovered bug lurking within, waiting to show itself in some later run. The best protection against program bugs is a program that is well thought out and clearly set down in code.

Scheme provides several features to aid in debugging programs. Among these are the ability to trace the execution of a procedure, the ability to examine the current environment, and the ability to set breakpoints. From a breakpoint, you can examine the recent process history of your program.

Bugs are sometimes detected by the occurrence of an error while the program is running. When Scheme encounters an error, execution is abruptly halted, you are notified, and you are given thechoice of either aborting from the error, or entering an error read-eval-print-loop. (See section ??). Here is a typical error response:

    (+ 1 'a)
    Illegal datum in second argument position A
    Within procedure #[PRIMITIVE PROCEDURE &+]
    There is no environment available;

    using the current read-eval-print environment.

The error message—a concise description of the error—is the first thing printed. In this case, the error was a bad argument: the symbol a to the + procedure.

The error message is sometimes followed by information about the environment in which the error occurred. This is useful because the debugger includes some advanced features that allow you to probe or modify the environment. (See sections ?? and ??.) Sometimes, as in the example above, the system will tell you that no environment was found at all, which means that you are in the environment of the previous REP loop.

If you choose not to abort, the mode line information (indicating Scheme’s status) will change from

    I 1 [REP Loop]

to

    I 2 [Error]

to indicate that you are in an error REP loop. The number 2 signals that this REP loop is subordinate to a level 1 (top level) REP loop. See sections ?? and ?? for information on REP loops.

If you do choose to abort, you will return to the previous REP loop and you can try another expression. Beginning programmers have a tendency to automatically abort as soon as an error is encountered.^{1} This is unfortunate, because it destroys information about the error that can be used to aid in debugging. When you encounter an error, take some time to think about the error, rather than immediately aborting as a “knee-jerk” reaction.

Scheme provides system features that make use of the error environment in order to help you to try and locate where the error occurred. That is, you can see what expressions were being executed at the time of the error. The environment and history examiners, where and debug, respectively, are used for this. Of course, much can be determined about the state of the system at the time of the error by simply examining the values of variables in the error environment.

1This is similar to the behavior of children writing or drawing, who will ordinary crumple up the paper and start over again as soon as they make a mistake. Presumably, the error triggers some kind of “abort” reaction whose goal is to return the cognitive process to a simple known state as rapidly as possible. One of the useful things we can learn from programming is the possibility of debugging to correct our mistakes.

The program may run without error, but the result produced may be preposterous—another indication of bugs. For instance,

    (prime? 7)  ⟹ #f

In this situation, Scheme offers no clue as to the location of the bug—you’re on your own. You might produce a theory about the cause of the deviant behavior and then test it using the trace facility. You can also edit the program to insert checks and traces.

Debugging, like programming, is an acquired skill and an art. The tools available to you are described in the sections below. Use them creatively. The laboratory assistants are there to help you. Feel free to ask them for help.

1.1 Read-Eval-Print Levels

The Scheme interpreter works by running a read-eval-print loop (or REP loop). This means that expressions are read, evaluated, and the result of the evaluation is printed on the console. A REP loop can be nested inside another REP loop. This is what happens at a breakpoint or an error. The mode line indicator is changed to Break or Error to signify that another nested REP loop has been entered. The Emacs commands ↑c G, ↑c U, and ↑c X all affect the REP level you are at. The Level is the degree of nesting of REP loops, where level 1 is the topmost REP loop. Below are listed some control characters used to interrupt programs on various levels, also listed are other useful control characters.

↑c B Enters a Breakpoint. The new level will be one higher than it was before the breakpoint.

↑c G Halts execution at all levels. Returns to the top level (level 1) REP loop.

↑c U Halts execution locally. Returns to the previous REP loop, unless already at the top level loop. That is, the new level is one less than it used to be, unless you were already at level 1.

↑c X Halts execution locally. Returns to the current REP loop. The level number remains unchanged.

Once again, the different mode line indicators are: REP, ordinary readeval-print loop; Break, a breakpoint REP loop; and Error an error REP loop.

1.2 Breakpoint and Error REP Loops

As mentioned above, you can choose to enter an error REP loop whenever an error is signalled. This error may have been signalled internally by Scheme, or it may have been signalled by an error statement that you inserted in the program yourself. Loops of a slightly different flavor can be entered via the bkpt special form or by typing a ↑c B at the keyboard.

The Scheme breakpoint facility allows you to pause execution of a program in midstream, whereupon you can examine and modify the values of local variables, among other things. When you pause execution and enter a breakpoint, Scheme runs a REP loop just as it normally does—you type expressions and the interpreter evaluates them and prints their value. The difference is that the environment in which these expressions are evaluated is not the global environment, but rather the environment from which the breakpoint was entered.

Breakpoints can be entered in the following ways:

• Typing ↑c B at the keyboard. This will always leave you in the environment of the previous (lower level number) REP loop.

• Executing a procedure that had a breakpoint installed via one of the

     commands break, break-entry, break-exit, or break-both. This

will leave you in the environment created to evaluate the body of the “broken” procedure.

• Executing the procedure bkpt. This will leave you in the environment in which the bkpt is evaluated.

• Encountering an error. You are left in the environment in which the expression causing the error was evaluated. Because the Scheme interpreter is optimized so that it saves only environments that are needed for subsequent evaluations, errors may occur when there is no environment at all. In this case a warning is printed and you are left in the environment of the previous (lower level number) REP loop.

To continue from a breakpoint, evaluate the expression (proceed). The interpreter will continue as though the breakpoint had not occurred. If the REP loop was entered because of a bkpt or error special form, evaluating (proceed expression) will evaluate the expression and return that value for the value of the special form.

If the REP loop was entered because of a break-entry or break-exit, (proceed expression) will evaluate expression and return that value in place of the value which would be calculated by the “broken” procedure. This is convenient if you realize that a procedure is calculating the wrong answer. You may want to quickly try out the rest of your program as though this procedure really worked. You can break-entry the questionable procedure, and then use (proceed expression) to simulate the way the procedure should work.

Instead of continuing the program by using proceed, you can type ↑c G which aborts execution and returns to the top-level REP loop. This is frequently done after “snooping around” to discover the problem: type ↑cG and try again. Similarly, ↑c U halts execution and returns to the preceding (lower level number) REP loop. ↑cX resumes the current REP loop. This is useful, for example, when you ask Scheme to pretty-print a long procedure and want to stop it in the middle of the printing and get back to the current REP loop prompt.

For REP loops caused by errors in Scheme primitives, evaluating (proceed) will simply call the primitive again. This is rarely what you want to do. You should investigate the problem, fix the program, and try it all over again.

1.3 Trace and Break

Scheme provides procedures that allow you to trace the execution of a program and set breakpoints in the program. A procedure can be traced on entry or on exit. In addition, a breakpoint can be set when the procedure is entered or exited. When the entry of a program is traced, the arguments passed to it are shown at each entry. When the exit of a program is traced the arguments passed and the result returned are shown at each exit. Here is an example. The indicators preceding the user’s input show the level and REP loop information that appears in the mode line at each point.

     [1 REP] (define (fact n)
               (cond ((zero? n) 1)
                     (else (* n (fact (-1+ n))))))
    FACT

6 CHAPTER 1. DEBUGGING

[1 REP] (trace-entry fact)
[1 REP] (trace-exit fact)
[1 REP] (fact 3)
Entering <#<COMPOUND-PROCEDURE FACT> 3>
Entering <#<COMPOUND-PROCEDURE FACT> 2>
Entering <#<COMPOUND-PROCEDURE FACT> 1>
Entering <#<COMPOUND-PROCEDURE FACT> 0>
1 <== <#<COMPOUND-PROCEDURE FACT> 0>
1 <== <#<COMPOUND-PROCEDURE FACT> 1>
2 <== <#<COMPOUND-PROCEDURE FACT> 2>
6 <== <#<COMPOUND-PROCEDURE FACT> 3>
6
[1 REP] (untrace fact)
[1 REP] (fact 3)
6
[1 REP] (break-exit fact)
[1 REP] (fact 2)
1 <== <#<COMPOUND-PROCEDURE FACT> 0>
Breakpoint on exit
[2 Break] (*args*)
(0)
[2 Break] (*result*)
1
[2 Break] (proceed -1)
-1 <== <#<COMPOUND-PROCEDURE FACT> 1>
Breakpoint! Level: 2
[2 Break] (proceed)
-2 <== <#<COMPOUND-PROCEDURE FACT> 2>
Breakpoint on exit
[2 Break] (proceed)

During the breakpoint inserted by break-exit, the procedures *proc*, *args*, and *result* can be called to see the “broken” procedure, the values of the arguments, and the computed result (available only during an exit break), respectively. Type (proceed) to proceed from the breakpoint and return the value shown by (*result*), or type (proceed expression) to return the value of expression. In the above example, we made (fact 0) return −1 instead of 1.

1.4 The Environment Examiner: Where

The where procedure provides a method of examining and modifying environments. Where is a procedure of 0 or 1 arguments that enters a readexecute command loop when called. Unlike a REP loop, this loop reads one letter and treats it as a command to be performed. Scheme will prompt for the one-character command at the bottom of the screen in the Emacs echo area. To give a command, just type the character—you needn’t press return.

If called with no arguments, where examines the current REP loop environment. The optional argument can be an environment or an object with an environment associated with it, such as a procedure. Below is an example of where being called at a breakpoint in a Scheme procedure. This illustrates some of the things which can be done with where.

While the where procedure is a very useful debugging aid, using it effectively requires a good understanding of Scheme’s environment structures. For this reason you may find it less confusing to use only the C command initially.

Here is a brief description of each where command.

? Prints a summary of the where command characters.

A (for all) Displays the bindings in all of the frames in the current environment chain.

C (for current) Displays the bindings in the current frame.

E (for eval) Enters a REP loop in the current environment.

N (for name) Shows the name of procedure that created current environment frame.

P (for parent) Moves up to the parent of the current environment frame, then does a C command.

Q (for quit) Exits where.

S (for son) Move to the son of the current environment frame. This undoes a P command.

V (for value) Evaluates one expression in the current frame and pretty-prints the result.

W (for where) Evaluate an expression in the current frame and call where on the result.

Here is an example of using where. The indicators at the left of the user input show the information that would appear in the buffer mode line, and in the prompts in the echo area:

     [1 REP] (define (one x y z)
               (define (two x y z)
                 (define (three x y z)
                   (bkpt "In procedure three environment"
                         (list x y z)))
                 (three 1 2 3))
               (two 4 5 6))
     ONE
     [1 REP] (one 'a 'b 'c)
     In procedure three environment (1 2 3)
     Breakpoint
     [2 Break] (where)
     Environment Inspector
     [3 Where] c
     Frame created by the procedure THREE.
     Depth (relative to starting frame): 0
     Has bindings:
     X = 1
     Y = 2
     Z = 3

The C command prints the variable bindings in the currently selected environment frame. Since the breakpoint occurred inside the execution of the procedure three, the current environment is the one resulting from the application of C to the arguments 1 2 3.

The P command moves to the frame in which three was defined. In this case the parent is the frame created by the application of the procedure two to the arguments 4 5 6.

     [3 Where] p
     Frame created by the procedure TWO.
     Depth (relative to starting frame): 1
     Has bindings:
     X = 4
     Y = 5
     Z = 6
     THREE = #<COMPOUND-PROCEDURE THREE>

     [3 Where] p
     Frame created by the procedure ONE.
     Depth (relative to starting frame): 2
     Has bindings:
     X = A
     Y = B
     Z = C
     TWO = #<COMPOUND-PROCEDURE TWO>

     [3 Where] p
     Frame created by a MAKE-PACKAGE special form.
     Depth (relative to starting frame): 3
     Has bindings:
     USER-INITIAL-ENVIRONMENT = #<ENVIRONMENT 13679124>
     ONE = #<COMPOUND-PROCEDURE ONE>

Here we have reached the user initial environment in which the top most procedure one was defined. That’s enough …

     [3 Where] q

and we are back in the REP loop where we started. We can continue the program by typing (proceed).

Notice that we could stop at any environment and create a REP loop in it by using the E command. This allows you, for example, to define missing variables when you encounter an unbound variable error. Similarly, you can use the V (one expression) command to evaluate a single expression in any of the frames in the environment chain.

1.5 The Scheme Debugger: Debug

A breakpoint will allow you only to examine local variables and to evaluate expressions at the breakpoint. Often, however, tracking down a bug will require a more extensive investigation of the circumstances surrounding the error. The Scheme debugger system provides additional capabilities to aid in debugging.

The history examiner, entered using the debug command, enables you to examine the history of execution of an expression. The environment of any procedure application may then be examined and the interrupted process may be continued from any of the displayed nodes of evaluation.

Calling the debug procedure from within a Scheme breakpoint enters a read-execute loop, which accepts single-character commands which enable you to move backwards and forwards in the history of the expressions which have been evaluated. You can also examine procedures and variables at the different evaluation levels. The prompt is Debug. Just as with where, Scheme prompts for the commands in the mode line at the bottom of the screen.

In order to make good use of the debugger, you must understand the concept of a reduction and a subproblem. The interpreter evaluates an expression by reducing it to a simpler expression. In general, Scheme’s evaluation rules designate that evaluation proceeds from one expression to the next by either starting to work on a subexpression of the given expression, or by reducing the entire expression to a new (simpler, or reduced) form. Thus, a history of the successive forms processed during the evaluation of an expression will show a sequence of subproblems, where each subproblem may involve a sequence of reductions. The best way to get a feeling for how reductions and subproblems work is to write some test expressions and play with them using the debug procedure.

Here is a summary of the debug commands.

? Prints a summary of the debug commands.

B (for back) Moves back to the “previous expression,” that was being processed. What this means is as follows: One goes backwards along the sequence of reductions that produced the current expression until reaching the subproblem that led to this chain of reductions. Then one moves “upward” in the tree of subproblems, until reaching the expression that was typed at the REP loop which initiated the whole process.

C (for current) Shows all the variable bindings in the environment in which the current subproblem was being evaluated.

D (for down) Moves down to a lower subproblem level. This is the subproblem which was evaluated after (in time) the current one.

E (for eval) Enters a REP loop in the environment in which the current subproblem was being evaluated.

F (for forward) Moves to the “next” expression after the current one. The order is (the opposite of) the one described for the B command.

G (for go) Goes to a particular subproblem and reduction level. Debug asks you for the numbers. Each expression has two numbers to indicate a subproblem and a reduction of that subproblem. Simplified (more reduced) expressions have lower reduction numbers. Lower level (smaller subproblem) expressions have lower subproblem numbers. Thus the current error expression is numbered (0, 0).

H (for history) Prints a summary of the entire history. This is like typing B repeatedly.

I (for information) Redisplays the error message.

L (for list current expression) Pretty-prints the current expression.

P (for procedure) Pretty-prints current procedure.

Q (for quit) Exits debug returning to the environment in which the error occurred, and at the expression which caused the error.

R (for reductions) Prints all the reductions of the current subproblem level.

S (for subproblem) Prints the current subproblem and reduction showing where in the evaluation history the user is.

U (for up) Moves up to the previous subproblem level. This is a subproblem which includes as one of its parts the current subproblem. It has a higher subproblem number.

V (for value) Asks for an expression and evaluates it in the environment in which the current subproblem was being evaluated.

W (for where) Enters where on the current environment

Z (for zap) Asks for an expression and evaluates it in the current environment. The value of the expression is used in place of the value the current subproblem would have returned, and execution is resumed from this point in the original evaluation.

For a first look at the debugger we will use several of the common commands just to see how they work. Here is a demonstration.

     [1 REP] (define (fact n)
               (if (zero? n)
                   l
                   (* n (fact (-1+ n)))))
     FACT
     [1 REP] (fact 3)
     Unbound Variable L
     Error!
     [2 Error] (debug)
     Subproblem Level: 0  Reduction Number: 0
     Expression:
     L
     within the procedure FACT.
     applied to (0)
     [3 Debug]

The application of the procedure fact caused an error. We then called the debugger, which printed the expression whose evaluation caused the error to occur. The “Subproblem Level” and the “Reduction Number” are ways of numbering expressions in the history. The procedure whose body is currently being evaluated, as well as the arguments passed to it are displayed. The debugger accepts one letter commands which can be listed by typing ?.

The factorial problem above has a simple typo; the letter L is used instead of the number 1 in the if form, resulting in an unbound variable error. The debugger indicates that the error occurred when fact was called with 0 as its argument.

Although the bug in this case is easy to spot, we can look through the evaluation history, to see how the evaluation proceeds. The command H lists a summary of the history.

     [3 Debug] h
     Sub Prb. Procedure Name    Expression
     0                  FACT    L
     0                  FACT    (IF (ZERO? N) L (* N (FACT (-1+ N))))
     0                  FACT    (FACT (-1+ N))
     1                  FACT    (* N (FACT (-1+ N)))
     2                  FACT    (* N (FACT (-1+ N)))
     3                  FACT    (* N (FACT (-1+ N)))
     4                          (PRINT (SCODE-EVAL SCODE USER-CURRENT-ENVI ...
     5                          (SEQUENCE (SETUP-HISTORY! MAX-SUBPROBLEMS  ...
     6                  LOOP    (LOOP (CATCH (LAMBDA (AGAIN) (FLUID-LET (( ...
     7           DRIVER-LOOP    (DRIVER-LOOP (CATCH (LAMBDA (QUIT) (SET! T ...
     8  STARTUP-TOP-LEVEL-DR... (SEQUENCE (CATCH (LAMBDA (CONT) (SET! TOP- ...
     [3 Debug]
    Quit! Level: 1

Lines 4 through 8 printed by the examiner are pieces of the Scheme REP loop implementation itself, and should be ignored.

The most common error that can be corrected easily from the debugger is an unbound variable error. In this case, there are two possibilities. If you forgot to define the variable you can use the B command to search back through the history until you find the place where the variable should have been defined. Then, use the E command to enter a REP loop, and define the variable.

1.6 An Extended Debugging Session

This section illustrates use of the Scheme debug procedure in an extended session. The first example is the buggy fact procedure that was used as an example in section ?? above. The sequence begins with the interaction already described in section ??.

[1 REP] (define (fact n)
      (if (zero? n)
          l
          (* n (fact (-1+ n)))))
FACT
[1 REP] (fact 3)
Unbound Variable L
Error!
[2 Error] (debug)
Subproblem Level: 0  Reduction Number: 0
Expression:
L
within the procedure FACT.
applied to (0)
[3 Debug] s
Subproblem Level: 0  Reduction Number: 0
Expression:
L
within Procedure FACT
applied to (0)

The command S displays the current subproblem. The command B moves backwards in time.

[3 Debug] b
Subproblem Level: 0  Reduction Number: 1
Expression:
(IF (ZERO? N) L (* N (FACT (-1+ N))))
within Procedure FACT
applied to (0)

1.6. AN EXTENDED DEBUGGING SESSION 15

[3 Debug] b
Subproblem Level: 0  Reduction Number: 2
Expression:
(FACT (-1+ N))
within Procedure FACT
applied to (1)
[3 Debug] b
Subproblem Level: 1  Reduction Number: 0
Expression:
(* N (FACT (-1+ N)))
within Procedure FACT
applied to (1)

The F command moves forward through reductions one at a time, in the opposite direction to B.

[3 Debug] f
Subproblem Level: 0  Reduction Number: 2
Expression:
(FACT (-1+ N))
within Procedure FACT
applied to (1)
[3 Debug] f
Subproblem Level: 0  Reduction Number: 1
Expression:
(IF (ZERO? N) L (* N (FACT (-1+ N))))
within Procedure FACT
applied to (0)
[3 Debug] f
Subproblem Level: 0  Reduction Number: 0
Expression:
L
within Procedure FACT
applied to (0)

We are now back at the subproblem level containing the offending expression. The variable L is unbound. The Z (for ZAP) command asks for an expression to evaluate and returns this value as the value of the current subproblem to the previous subproblem, that is, the containing subproblem. Here we wanted L to be 1.

[3 Debug] z
    Enter an expression to EVALUATE and CONTINUE with: 1
    That evaluates to:
    1
    Confirm: [Y or N] -> y
    6

And here is the correct result. Let’s look at another example:

[1 REP] (define (square-root x)
          (define (square-root-iter ans)
            (if (close? ans)
                ans
                (square-root-iter (average (/ x ans) ans))))
          (define (close? ans)(< (abs (- (* ans asn) x)) .0001))
          (square-root-iter 1))
SQUARE-ROOT
[1 REP] (square-root 4)
Unbound Variable ASN
Error! Level: 2
[2 Error] (debug)
Subproblem Level: 0  Reduction Number: 0
Expression:
ASN
within Procedure CLOSE?
applied to (1)
[3 Debug] b
Subproblem Level: 1  Reduction Number: 0
Expression:
(* ANS ASN)
within Procedure CLOSE?
applied to (1)
[3 Debug] b

Subproblem Level: 2  Reduction Number: 0
Expression:
(- (* ANS ASN) X)
within Procedure CLOSE?
applied to (1)
[3 Debug] b
Subproblem Level: 3  Reduction Number: 0
Expression:
(ABS (- (* ANS ASN) X))
within Procedure CLOSE?
applied to (1)
[3 Debug] b
Subproblem Level: 4  Reduction Number: 0
Expression:
(< (ABS (- (* ANS ASN) X)) .0001)
within Procedure CLOSE?
applied to (1)

Above is a square root program that is divided into sub-procedures. In the program there is a bug. The debugger was called to reveal that the unbound variable ASN is in the expression (* ans asn) in the procedure CLOSE?.

Here is another example:

[1 REP] (define (sum term low high)
          (define (sum-iter sum index)
            (if (> index high)
                sum
                (sum-iter (+ sum (term index)) (1+ index))))
          (sum-iter 0 low))
SUM
[1 REP] (define (term-proc i)(if (evin? i) 1 0))
TERM-PROC
[1 REP] (pp term-proc)
(DEFINE (TERM-PROC I) (IF (EVIN? I) 1 0))
[1 REP] (sum term-proc 1 5)
Unbound Variable EVIN?
Error!

[2 Error] (debug)
Subproblem Level: 0  Reduction Number: 0
Expression:
EVIN?
within Procedure TERM-PROC
applied to (1)
[3 Debug] b
Subproblem Level: 1  Reduction Number: 0
Expression:
(EVIN? I)
within Procedure TERM-PROC
applied to (1)
[3 Debug] b
Subproblem Level: 2  Reduction Number: 0
Expression:
(IF (EVIN? I) 1 0)
within Procedure TERM-PROC
applied to (1)
[3 Debug] b
Subproblem Level: 3  Reduction Number: 0
Expression:
(+ SUM (TERM INDEX))
within Procedure SUM-ITER
applied to (0 1)
[3 Debug] h
Sub Prb. Procedure Name    Expression
0             TERM-PROC    EVIN?
1             TERM-PROC    (EVIN? I)
2             TERM-PROC    (IF (EVIN? I) 1 0)
3              SUM-ITER    (+ SUM (TERM INDEX))
4              SUM-ITER    (SUM-ITER (+ SUM (TERM INDEX)) (1+ INDEX))

The bug here is that the procedure EVEN? was spelled EVIN?, in the definition of the procedure TERM-PROC. By looking at the history we can see that the procedure SUM was called somewhere at subproblem 4. The command G used below goes to a specified subproblem and reduction. Below we see the call to SUM in subproblem 4, reduction 4.

[3 Debug] g
Subproblem number: 4
Reduction Number (0 through 4 inclusive): 4
Subproblem Level: 4  Reduction Number: 4
Expression:
(SUM TERM-PROC 1 5)
within a MAKE-PACKAGE special form
applied to (#<ENVIRONMENT 13033164>)
[3 Debug] c
Frame created by a MAKE-PACKAGE special form.
Has bindings:
USER-INITIAL-ENVIRONMENT = #<ENVIRONMENT 13833016>
SUM = #<COMPOUND-PROCEDURE SUM>
TERM-PROC = #<COMPOUND-PROCEDURE TERM-PROC>

The environment in which subproblem 4 was being evaluated is the user-initial-environment. The C command displayes the variable bindings in the evaluation environment of the current subproblem. Which in this case is the user-initial-environment. This is where evin? should be defined. The V command evaluates an expression in the current environment.

[3 Debug] v
[3 Eval] (define evin? even?)
EVIN?
[3 Debug] c
Frame created by a MAKE-PACKAGE special form
Has bindings:
USER-INITIAL-ENVIRONMENT = #<ENVIRONMENT 13833016>
SUM = #<COMPOUND-PROCEDURE SUM>
TERM-PROC = #<COMPOUND-PROCEDURE TERM-PROC>
EVIN? = #'<COMPOUND-PROCEDURE EVEN?>
[3 Debug] q
[2 Error] (proceed)

Above we defined the variable evin? to have as its value the procedure even?. We then left the debugger. By typing (proceed) at the error prompt we cause the program to continue with the evaluation of the expression that caused the error, and the procedure completes returning the correct answer.