The operation of the condition system depends on the ordering of active applicable handlers from most recent to least recent.
Each handler is associated with a type specifier
that must designate a subtype of type condition
. A handler
is said to be applicable to a condition if that
condition is of the type designated by the associated
type specifier.
Active handlers are established by using
handler-bind
(or an abstraction based on handler-bind
,
such as handler-case
or ignore-errors
).
Active handlers can be established within the dynamic scope of other active handlers. At any point during program execution, there is a set of active handlers. When a condition is signaled, the most recent active applicable handler for that condition is selected from this set. Given a condition, the order of recentness of active applicable handlers is defined by the following two rules:
Once a handler in a handler binding form (such as
handler-bind
or handler-case
) has been selected, all
handlers in that form become inactive for
the remainder of the signaling process.
While the selected handler runs, no other handler established
by that form is active. That is, if the handler declines,
no other handler established by that form will be considered for possible invocation.
The next figure shows operators relating to the handling of conditions.
When a condition is signaled, the most recent applicable active handler is invoked. Sometimes a handler will decline by simply returning without a transfer of control. In such cases, the next most recent applicable active handler is invoked.
If there are no applicable handlers for a condition that has been signaled, or if all applicable handlers decline, the condition is unhandled.
The functions cerror
and error
invoke the
interactive condition handler (the debugger) rather than
return if the condition being signaled, regardless of
its type, is unhandled. In contrast, signal
returns nil
if the condition being signaled,
regardless of its type, is unhandled.
The variable *break-on-signals*
can be used to cause the
debugger to be entered before the signaling process begins.
The next figure shows defined names relating to the signaling of conditions.
|
Figure 9.5: Defined names relating to signaling conditions.
During the dynamic extent of the signaling process for
a particular condition object,
signaling
the same condition object again
is permitted if and only if the situation represented in both
cases are the same.
For example, a handler might legitimately signal the condition object that is its argument in order to allow outer handlers first opportunity to handle the condition. (Such a handlers is sometimes called a “default handler.”) This action is permitted because the situation which the second signaling process is addressing is really the same situation.
On the other hand, in an implementation that implemented asynchronous
keyboard events by interrupting the user process with a call to signal
,
it would not be permissible for two distinct asynchronous keyboard events
to signal identical condition objects
at the same time for different
situations.
The interactive condition handler returns only through
non-local transfer of control to specially defined restarts
that can be set up either by the system or by user code. Transferring
control to a restart is called “invoking” the restart. Like
handlers, active restarts are established
dynamically, and
only active restarts
can be invoked. An active
restart can be invoked by the user from
the debugger or by a program by using invoke-restart
.
A restart contains a function to be called when the restart is invoked, an optional name that can be used to find or invoke the restart, and an optional set of interaction information for the debugger to use to enable the user to manually invoke a restart.
The name of a restart is
used by invoke-restart
. Restarts that can be invoked
only within the debugger do not need names.
Restarts can be established by using restart-bind
,
restart-case
, and with-simple-restart
.
A restart function can itself invoke any other restart
that was active at the time of establishment of the restart
of which the function is part.
The restarts established by
a restart-bind
form,
a restart-case
form,
or a with-simple-restart
form
have dynamic extent
which extends for the duration of that form's execution.
Restarts of the same name can be ordered from least recent to most recent according to the following two rules:
If a restart is invoked but does not transfer control,
the values resulting from the restart function are
returned by the function that invoked the restart, either
invoke-restart
or invoke-restart-interactively
.
For interactive handling, two pieces of information are needed from a restart: a report function and an interactive function.
The report function
is used by a program such as the debugger to
present a description of the action the restart will take.
The report function is specified and established by the
:report-function keyword to
restart-bind
or the
:report keyword to restart-case
.
The interactive function, which can be specified using the
:interactive-function keyword to
restart-bind
or :interactive keyword
to restart-case
, is used when the restart
is invoked
interactively, such as from the debugger, to produce a suitable
list of arguments.
invoke-restart
invokes the most recently established
restart whose
name is the same as the first argument to invoke-restart
.
If a restart is invoked interactively by the debugger and does
not transfer control but rather returns values, the precise
action of the debugger on those values is implementation-defined.
Some restarts have functional interfaces,
such as abort
, continue
,
muffle-warning
, store-value
, and
use-value
.
They are ordinary functions that use
find-restart
and invoke-restart
internally,
that have the same name as the restarts they manipulate,
and that are provided simply for notational convenience.
The next figure shows defined names relating to restarts.
|
Figure 9.6: Defined names relating to restarts.
Each restart has an associated test, which is a function of one
argument (a condition or nil
) which returns true if the restart
should be visible in the current situation. This test is created by
the :test-function option to restart-bind
or
the :test option to restart-case
.
A restart can be “associated with” a condition explicitly
by with-condition-restarts
, or implicitly by restart-case
.
Such an assocation has dynamic extent.
A single restart may be associated with several conditions at the same time. A single condition may have several associated restarts at the same time.
Active restarts associated with a particular condition can be detected
by calling a function such as find-restart
, supplying
that condition as the condition argument.
Active restarts can also be detected without regard to any associated
condition by calling such a function without a condition argument,
or by supplying a value of nil
for such an argument.