A common requirement is to be able to show as much useful context as
possible when a Scheme program hits an error. The most immediate
information about an error is the kind of error that it is – such as
“division by zero” – and any parameters that the code which signalled
the error chose explicitly to provide. This information originates with
the error
or throw
call (or their C code equivalents, if
the error is detected by C code) that signals the error, and is passed
automatically to the handler procedure of the innermost applicable
catch
, lazy-catch
or with-throw-handler
expression.
Therefore, to catch errors that occur within a chunk of Scheme code, and
to intercept basic information about those errors, you need to execute
that code inside the dynamic context of a catch
,
lazy-catch
or with-throw-handler
expression, or the
equivalent in C. In Scheme, this means you need something like this:
(catch #t (lambda () ;; Execute the code in which ;; you want to catch errors here. ...) (lambda (key . parameters) ;; Put the code which you want ;; to handle an error here. ...))
The catch
here can also be lazy-catch
or
with-throw-handler
; see Throw Handlers and Lazy Catch
for the details of how these differ from catch
. The #t
means that the catch is applicable to all kinds of error; if you want to
restrict your catch to just one kind of error, you can put the symbol
for that kind of error instead of #t
. The equivalent to this in
C would be something like this:
SCM my_body_proc (void *body_data) { /* Execute the code in which you want to catch errors here. */ ... } SCM my_handler_proc (void *handler_data, SCM key, SCM parameters) { /* Put the code which you want to handle an error here. */ ... } { ... scm_c_catch (SCM_BOOL_T, my_body_proc, body_data, my_handler_proc, handler_data, NULL, NULL); ... }
Again, as with the Scheme version, scm_c_catch
could be replaced
by scm_internal_lazy_catch
or scm_c_with_throw_handler
,
and SCM_BOOL_T
could instead be the symbol for a particular kind
of error.
The other interesting information about an error is the full Scheme stack at the point where the error occurred; in other words what innermost expression was being evaluated, what was the expression that called that one, and so on. If you want to write your code so that it captures and can display this information as well, there are three important things to understand.
Firstly, the code in question must be executed using the debugging
version of the evaluator, because information about the Scheme stack is
only available at all from the debugging evaluator. Using the debugging
evaluator means that the debugger option (see Debugger options)
called debug
must be enabled; this can be done by running
(debug-enable 'debug)
or (turn-on-debugging)
at the top
level of your program; or by running guile with the --debug
command line option, if your program begins life as a Scheme script.
Secondly, the stack at the point of the error needs to be explicitly
captured by a make-stack
call (or the C equivalent
scm_make_stack
). The Guile library does not do this
“automatically” for you, so you will need to write code with a
make-stack
or scm_make_stack
call yourself. (We emphasise
this point because some people are misled by the fact that the Guile
interactive REPL code does capture and display the stack
automatically. But the Guile interactive REPL is itself a Scheme
program1
running on top of the Guile library, and which uses catch
and
make-stack
in the way we are about to describe to capture the
stack when an error occurs.)
Thirdly, in order to capture the stack effectively at the point where
the error occurred, the make-stack
call must be made before Guile
unwinds the stack back to the location of the prevailing catch
expression. This means that the make-stack
call must be made
within the handler of a lazy-catch
or with-throw-handler
expression, or the optional "pre-unwind" handler of a catch
.
(For the full story of how these alternatives differ from each other,
see Exceptions. The main difference is that catch
terminates the error, whereas lazy-catch
and
with-throw-handler
only intercept it temporarily and then allow
it to continue propagating up to the next innermost handler.)
So, here are some examples of how to do all this in Scheme and in C. For the purpose of these examples we assume that the captured stack should be stored in a variable, so that it can be displayed or arbitrarily processed later on. In Scheme:
(let ((captured-stack #f)) (catch #t (lambda () ;; Execute the code in which ;; you want to catch errors here. ...) (lambda (key . parameters) ;; Put the code which you want ;; to handle an error after the ;; stack has been unwound here. ...) (lambda (key . parameters) ;; Capture the stack here: (set! captured-stack (make-stack #t)))) ... (if captured-stack (begin ;; Display or process the captured stack. ...)) ...)
And in C:
SCM my_body_proc (void *body_data) { /* Execute the code in which you want to catch errors here. */ ... } SCM my_handler_proc (void *handler_data, SCM key, SCM parameters) { /* Put the code which you want to handle an error after the stack has been unwound here. */ ... } SCM my_preunwind_proc (void *handler_data, SCM key, SCM parameters) { /* Capture the stack here: */ *(SCM *)handler_data = scm_make_stack (SCM_BOOL_T, SCM_EOL); } { SCM captured_stack = SCM_BOOL_F; ... scm_c_catch (SCM_BOOL_T, my_body_proc, body_data, my_handler_proc, handler_data, my_preunwind_proc, &captured_stack); ... if (captured_stack != SCM_BOOL_F) { /* Display or process the captured stack. */ ... } ... }
Note that you don't have to wait until after the catch
or
scm_c_catch
has returned. You can also do whatever you like with
the stack immediately after it has been captured in the pre-unwind
handler, or in the normal (post-unwind) handler. (Except that for the
latter case in C you will need to change handler_data
in the
scm_c_catch(...)
call to &captured_stack
, so that
my_handler_proc
has access to the captured stack.)
Once you have a captured stack, you can interrogate and display its
details in any way that you want, using the stack-...
and
frame-...
API described in Examining the Stack and
Examining Stack Frames.
If you want to print out a backtrace in the same format that the Guile
REPL does, you can use the display-backtrace
procedure to do so.
You can also use display-application
to display an individual
application frame – that is, a frame that satisfies the
frame-procedure?
predicate – in the Guile REPL format.
The Guile REPL code (in ice-9/boot-9.scm) uses a catch
with a pre-unwind handler to capture the stack when an error occurs in
an expression that was typed into the REPL, and saves the captured stack
in a fluid (see Fluids and Dynamic States) called
the-last-stack
. You can then use the (backtrace)
command,
which is basically equivalent to (display-backtrace (fluid-ref
the-last-stack))
, to print out this stack at any time until it is
overwritten by the next error that occurs.
Display a backtrace of the stack saved by the last error to the current output port. If highlights is given it should be a list; the elements of this list will be highlighted wherever they appear in the backtrace.
You can also use the (debug)
command to explore the saved stack
using an interactive command-line-driven debugger. See Interactive Debugger for more information about this.
[1] In effect, it is the default program which is run when no commands or script file are specified on the Guile command line.