[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Stripped down version of LispM error system
- To: CL-ERROR-HANDLING@SU-AI.ARPA
- Subject: Stripped down version of LispM error system
- From: Kent M Pitman <KMP@SCRC-STONY-BROOK.ARPA>
- Date: Fri, 8 Feb 85 03:33 EST
- In-reply-to: <850207194706.3.KMP@CHAGRIN.SCRC.Symbolics.COM>, The message of 7 Feb 85 17:34-EST from Mary <Fontana%ti-csl.csnet@csnet-relay.arpa>
The following proposal amounts to a stripped down version of what the
LispM provides. Most of the stripping down has to do with hiding
the fact that the LispM had flavors to play with and I didn't want
to depend on that.
I have an implementation of this for Maclisp. I'll see about working
out a CL implementation from that. I don't think it will be hard.
If any parts of this seem vague, let me know and I can elaborate.
This message covers really only technical details. I have given a lot
of thought on how to describe this conceptually and will do so under
separate cover at a later date.
Anyway, maybe this will give everyone something concrete to center the
discussion around.
-kmp
!
(SIGNAL condition-type key1 val1 key2 val2 ...) Function
Signals a condition of the given CONDITION-TYPE with attributes
given in keyed format. (Note: the keys are not keywords, but
packaged symbols.)
If the signal is not handled, then some default action will be
taken. The default for ERROR and conditions built on ERROR is
to enter the debugger. The default for other conditions is to
return NIL.
Example:
(SIGNAL 'BAD-FOOD-COLOR
'FOOD 'MILK
'COLOR 'GREENISH-BLUE)
This signals a BAD-FOOD-COLOR condition, providing information
to the condition that the FOOD was MILK and the COLOR was
GREENISH-BLUE.
!
(SIGNAL-CASE (condition-type
key1 key-value1
key2 key-value2
...)
((proceed-type-1 . bvl1) . body1)
((proceed-type-2 . bvl2) . body2)
...) Special Form
Signals a condition of the given CONDITION-TYPE with attributes
as given by the KEYs and KEY-VALUEs. The CONDITION-TYPE and KEYs
are not evaluated, but the KEY-VALUEs are.
If the condition is not handled, the default action is according to
the rules for the simple case of SIGNAL.
If the condition is handled, it may be proceeded by calling a proceed
function which has the name of a PROCEED-TYPE given in the body of the
SIGNAL-CASE. (The only valid names for PROCEED-TYPEs are those
that have been defined using DEFINE-PROCEED-TYPE.) If a proceed function
is called, the BODY of its corresponding in the SIGNAL-CASE is executed
with the variables in its BVL bound to the arguments given the proceed
function, and the return value of that BODY becomes the return value of
the SIGNAL-CASE.
Example:
(LET ((MY-FOOD 'MILK)
(MY-FOOD-COLOR 'GREENISH-BLUE))
(DO ()
((REASONABLE-COLOR-FOR-FOODP FOOD COLOR))
(SIGNAL-CASE (BAD-FOOD-COLOR
FOOD MY-FOOD
COLOR MY-FOOD-COLOR)
((USE-COLOR NEW-COLOR) (SETQ MY-FOOD-COLOR NEW-COLOR))
((USE-FOOD NEW-FOOD) (SETQ MY-FOOD NEW-FOOD))))
(LIST MY-FOOD MY-FOOD-COLOR))
This signals a BAD-FOOD-COLOR condition, specifying that FOOD
was MY-FOOD and COLOR was MY-FOOD-COLOR. If the caller wants
to proceed the condition, he may do something like:
(USE-COLOR condition 'WHITE)
or (USE-FOOD condition 'CHEESE)
In this case, the return value of the SIGNAL-CASE is
ignored because both handlers work by side-effect.
!
(CONDITION-BIND ((condition-name1 handler1)
(condition-name2 handler2)
...)
. body) Special Form
When a condition is signalled, handlers are searched for in the dynamic
environment of the signal. Handlers can be established by use of
CONDITION-BIND.
Handlers are functions of one argument (an object representing the
data associated with the condition). They may wish to first inspect
the object using one of the following primitives:
(CONDITION-SLOT condition slot-name)
Reads a named slot in the given condition.
(CONDITION-PROCEED-TYPES condition)
Returns a list of the valid proceed types for the
given condition.
After inspecting the condition, the handler must take one of
the following kinds of actions:
It may decline to handle the condition, by executing:
(DECLINE condition)
The effect of this will be as if the handler had been invisible to the
mechanism seeking to find a handler. The next handler in line will be
tried, or if no such handler, the default action for the given
condition will be taken.
It may perform some non-local transfer of control. For example,
. It can throw to a tag.
. It may signal an error (which will force implicit transfer
of control).
. It may call the function (ABORT) to unwind back to toplevel
or the innermost (CATCH-ABORT ...) form.
It may proceed the condition, using
(proceed-type condition . values)
For example, if the signal was done via
(SIGNAL-CASE (FOO-ERROR)
((USE-VALUE X) (* X X)))
and the handler does
(USE-VALUE condition 7)
then the SIGNAL expression will return 49.
There is also a function (INTERACTIVE-PROCEED condition) which will
prompt the user for a selection of how to proceed by inspecting
the condition to see what proceed options are available. This is
primarily useful in implementing the debugger, but may have other
applications from time to time.
The debugger may be entered, by invoking:
(INTERACTIVE-DEBUGGER condition)
!
(DEFINE-SIMPLE-CONDITION name slots parents
. report-method) Special Form
NAME is the name of the new condition to be defined.
PARENTS is a (possibly null) list of condition types that the
new condition types is to inherit from.
SLOTS is described by:
slot-name ! (slot-name) ! (slot-name slot-default-value)
If the SLOT-DEFAULT-VALUE is not given (as in the first two cases),
a value must be given for the slot at SIGNAL time. If a default is
given, an initialization for the slot is optional at SIGNAL time.
The REPORT-METHOD is a body of forms will be run in an environment
where variables are bound which have the names of the slot names for
the condition being defined and its parents. The REPORT-METHOD should
do typeout to the default output stream. It should have NO side-effects
other than this typeout. Its return value will be discarded. If no
REPORT-METHOD is specified, then the first condition in the parents
list which (explicitly or implicitly) has a report method will be the
error reporter for this condition.
Example:
(DEFINE-SIMPLE-CONDITION MACHINE-ERROR (MACHINE-NAME) (ERROR)
(FORMAT T "There is a problem with ~A." MACHINE-NAME))
This defines a MACHINE-ERROR condition which inherits from ERROR.
Initialization of MACHINE-NAME is required at SIGNAL time.
(DEFINE-SIMPLE-CONDITION MACHINE-NOT-AVAILABLE () (MACHINE-ERROR)
(FORMAT T "The machine ~A is not available." MACHINE-NAME))
This defines a new, more specific, condition for use when machines
are not available. Like MACHINE-ERROR, a value for the the
MACHINE-ERROR slot must be specified when the error is signaled.
(DEFINE-SIMPLE-CONDITION MY-FAVORITE-MACHINE-NOT-AVAILABLE
((MACHINE-NAME "MIT-MC.ARPA"))
(MACHINE-NOT-AVAILABLE))
The required nature of the MACHINE-NAME slot is over-ridden here
because the new condition type makes it optional (and gives it a
default value). Since no REPORT-METHOD was given, however, the
report method for MACHINE-NOT-AVAILABLE will be used if the
condition is asked to report.
!
(DEFINE-PROCEED-TYPE name pretty-name
(var1 exp1)
(var2 exp2)
...) Special Form
Defines a proceed type, which may appear in a handler clause of a
SIGNAL-CASE. The PRETTY-NAME is for use in the debugger. eg, if
the debuggers sees this proceed type is a proceed option for a given
condition, it will show its PRETTY-NAME. It should be a complete
sentence but not uppercased (unless it starts with a word that is
always seen upcased) and not followed by a period. The VARs name
values that are needed by this proceed type. The EXPs should compute
or prompt for any relevant values (they will be used only
interactively). The debugger (or other tool) will upcase the first
letter and add a period if contextually appropriate.
Example:
(DEFINE-PROCEED-TYPE USE-FOOD "use some other kind of food"
(FOOD (PROMPT-AND-READ :STRING "What kind of food? ")))
(DEFINE-PROCEED-TYPE USE-COLOR "use some other color"
(FOOD (PROMPT-AND-READ :STRING "What color? ")))
Using these definitions, a session with the debugger (from the
food example above) might look like:
...
>>Error: The food MILK was found to have color GREENISH-BLUE.
The following commands may be used to proceed:
meta-A: Use some other kind of food.
meta-B: Use some other color.
control-G: Return to toplevel.
DBG>meta-A
What color? white
...
or perhaps, depending on the implementation, like:
...
The milk was greenish-blue.
Use some other kind of food? (Y or N) No.
Use some other color? (Y or N) Yes.
What color? white
...
!
(ABORT) Function
Returns control to "toplevel", which is defined as the innermost
(CATCH-ABORT ...) expression. The outermost expression in any process
will always have a (CATCH-ABORT ...) around it, return from which will
either terminate or restart the process as appropriate to the
application.
(CATCH-ABORT . forms) Special Form
Executes the forms in its body. If no (ABORT) is done, the value returned
by the last of the forms will be the value returned by the CATCH-ABORT
form. If an (ABORT) is done, then NIL will be returned instead.
!
Notes:
Which condition types will be initially defined. At the very least ERROR
should be.
Which proceed types will be initially defined. I would argue that
something like
(DEFINE-PROCEED-TYPE USE-VALUE "use some other value"
(VALUE (PROMPT-AND-READ :EXPRESSION "Value to use: ")))
is quite handy and might want to come pre-defined.
What condition types are signaled by the already-defined error functions,
such as ERROR and CERROR.
This intentionally does not need flavors or any kind of fancy object
system, though it could snap smoothly into such if
CL-OBJECT-ORIENTED-PROGRAMMING finally comes up with one. I've implemented
this for Maclisp, so I know it works. eg, I've intentionally left aspects
of the REPORT-METHOD for a condition vague enough that it does not require
"instance variables" in any magic sense, by not defining what happens if
you assign the variables. Internally, just a LET that binds a bunch of
variables to data structure accesses will work just fine.
DEFINE-SIMPLE-CONDITION is not called DEFINE-CONDITION in case we
later want to make a hairier version that offers more than just a
REPORT-METHOD. Giving it this less-generic name will avoid possible
naming confusion later.
There may want to be some analogs of the LispM's CONDITION-CASE and other
more "abstract" operations, but those are easily implementable once we get
this much mechanism agreed upon, so I've left them out of this proposal.