Common Lisp the Language, 2nd Edition
Next: Object-Oriented Basis
of Up: Survey of
Concepts Previous: Trapping
Errors
Blind transfer of control to a handler-case
is only one
possible kind of recovery action that can be taken when a condition is
signaled. The low-level mechanism offers great flexibility in how to
continue once a condition has been signaled.
The basic idea behind condition handling is that a piece of code
called the signaler recognizes and announces the existence of
an exceptional situation using signal
or some function
built on signal
(such as error
).
The process of signaling involves the search for and invocation of a handler, a piece of code that will attempt to deal appropriately with the situation.
If a handler is found, it may either handle the situation, by performing some non-local transfer of control, or decline to handle it, by failing to perform a non-local transfer of control. If it declines, other handlers are sought.
Since the lexical environment of the signaler might not be available
to handlers, a data structure called a condition is created to
represent explicitly the relevant state of the situation. A condition
either is created explicitly using make-condition
and then
passed to a function such as signal
, or is created
implicitly by a function such as signal
when given
appropriate non-condition arguments.
In order to handle the error, a handler is permitted to use any
non-local transfer of control such as go
to a tag in a
tagbody
, return
from a block
, or
throw
to a catch
. In addition, structured
abstractions of these primitives are provided for convenience in
exception handling.
A handler can be made dynamically accessible to a program by use of
handler-bind
. For example, to create a handler for a
condition of type arithmetic-error
, one might write:
(handler-bind ((arithmetic-error handler))body)
The handler is a function of one argument, the condition. If a
condition of the designated type is signaled while the body is
executing (and there are no intervening handlers), the handler would be
invoked on the given condition, allowing it the option of transferring
control. For example, one might write a macro that executes a body,
returning either its value(s) or the two values nil
and the
condition:
(defmacro without-arithmetic-errors (&body forms)
(let ((tag (gensym)))
`(block ,tag
(handler-bind ((arithmetic-error
#'(lambda (c) ;Argument c is a condition
(return-from ,tag (values nil c)))))
,@body))))
The handler is executed in the dynamic context of the signaler,
except that the set of available condition handlers will have been
rebound to the value that was active at the time the condition handler
was made active. If a handler declines (that is, it does not transfer
control), other handlers are sought. If no handler is found and the
condition was signaled by error
or cerror
(or
some function such as assert
that behaves like these
functions), the debugger is entered, still in the dynamic context of the
signaler.
Next: Object-Oriented Basis
of Up: Survey of
Concepts Previous: Trapping
Errors
AI.Repository@cs.cmu.edu