In order to be able to single step the evaluation process, the debugger must be able to gain control both before and after each expression is evaluated. In Psd this is accomplished by packaging each expression inside a procedure. This procedure is then passed to the debugger command loop. When the user wants to continue, the command loop simply calls the procedure that was passed to it. The command loop then gains control again, and finally returns the value that the procedure returned. For example, the expression (+ x 1) in figure 2 is transformed to
(psd-debug (lambda () (+ x 1)))
The transformation is done recursively, so the expression is really transformed into
(psd-debug (lambda () ((psd-debug (lambda () +) (psd-debug (lambda () x) (psd-debug (lambda () 1)))))))In reality the debugger gets some more information (the current location in source code etc.), but the basic idea is the same.
It may seem that there is no point in instrumenting expressions like + and 1, but the instrumentation is needed for supporting breakpoints. Breakpoints are implemented by maintaining a list of source code locations of breakpoints. Each time psd-debug is called, it checks if there is a breakpoint for the current source line, and starts a command loop if needed. If primitive expressions like x would not be instrumented, there would be source lines for which breakpoints could not be set, for example in
(foo bar baz zap)
Single stepping is implemented similarly. Stepping by line is implemented by keeping track of the line number corresponding to the previous call to psd-debug.