[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Errors
- To: Common-Lisp@su-ai
- Subject: Errors
- From: David A. Moon <Moon%SCRC-TENEX%MIT-MC@SU-DSN>
- Date: Sat, 07 May 1983 03:19:00 -0000
Now that the mailing list appears to be working again, I'm re-sending my
proposal to replace chapter 23 of the laser manual (on errors). My
apologies to anyone who has seen this before (but I would advise you
to wade through it again, because the previous proposal was sent out
as an initial letter and a couple of appendices, so you might not
have really seen all of it.)
Our experience is that error-handling of adequate flexibility and power
requires a complex error classification system, using flavors or the
equivalent. Not everyone in the Common Lisp community agrees, and our
implementation of these ideas has only been in existence for half a
year, not long enough to consider it mature and stable. Chapter 23 of
the laser manual is mainly a rehash of the old Lisp machine condition
system (documented in The Lisp Machine Manual), which was generally
found to be totally inadequate. Therefore we propose that for the
present the language only standardize on ways to SIGNAL errors, not on
ways to HANDLE errors. Of course a complete language requires
error-handling mechanisms, but many useful portable programs do not
require them. Thus it makes sense to defer standardizing this until we
understand it better.
The rest of this proposal replaces everything in chapter 23 of the laser
manual. Most uppercase text should be read as boldface. Italics are
not indicated.
- Handling errors
When an error is signalled, either by calling one of the functions
documented in this section or directly by the Lisp system, it is handled in
an implementation-dependent way. The general assumption is that each
implementation will provide an interactive debugger that prints the error
message, along with suitable contextual information such as what function
detected the error. The user may interact with the debugger to examine or
modify the state of the program in various ways, including abandoning the
current computation ("aborting to top level") and continuing from the
error. What "continuing" means depends on how the error is signalled;
details are given with each error signalling function.
[The Lisp machine calls continuing "proceeding".]
- General error signalling functions
ERROR format-string &rest args
Signal an error, with the message constructed by applying FORMAT to
the arguments. It is impossible to continue from this kind of error;
thus the ERROR function will never return (barring someone meddling
with it by redefining it or by using an interactive debugger to force
it to return).
The error message should not contain a carriage return at the beginning
or end, and should not contain any sort of herald indicating that it is
an error. The system will take care of these according to whatever its
preferred style may be. Conventionally error messages are complete
English sentences, ending with a period. Carriage returns in the middle
of long messages are okay. The debugger printout in the following example
is not to be taken as a Common Lisp requirement; it is only an example
of what an implementation might print when ERROR is called.
Example:
(ERROR "The command ~S is unrecognized." CMD)
Error: The command EMERGECNY-SHUTDOWN is unrecognized.
Error signalled by function OPERATOR-COMMAND-EXECUTE.
>
[The Lisp machine calls this FERROR].
CERROR format-string &rest args
Signal an error, with the message constructed by applying FORMAT to
the arguments. Continuing from such an error will cause CERROR to
return NIL. Use CERROR rather than ERROR to signal errors for which
you have written recovery code. The name stands for "continuable
error," which is too verbose to use for such a common function.
Examples:
(UNLESS (= (LIST-LENGTH FORM) 3)
(CERROR "Wrong number of arguments in ~S; continuing will ~
:~[ignore extra args~;assume 0 for missing args~]."
FORM (< (LIST-LENGTH FORM) 3))
(SETQ FORM (APPEND FORM '(0 0))))
(DO () ((KNOWN-WORDP X) X)
(CERROR "~S is unknown, probably misspelled. Continue if you ~
want to replace it and try again." X)
(FORMAT T "~&New word: ")
(SETQ X (READ)))
;This example could be done more briefly with ASSERT or
;even with CHECK-TYPE (and SATISFIES).
[The Lisp machine calls this FSIGNAL, except that it returns :NO-ACTION
rather than NIL.]
WARN format-string &rest args
Print an error message constructed by applying FORMAT to the arguments,
but don't go into the debugger. This would just be FORMAT with output
directed to *ERROR-OUTPUT*, except that WARN takes care of advancing to
a fresh line before and after the error message and may do other
implementation-dependent things (in Symbolics Common Lisp, WARN messages
printed during a compilation are associated with the function being
compiled and saved for later perusal by editor tools. Furthermore WARN
automatically prints the name of the function associated with the
warning.) If it is desired to make WARN behave like CERROR (pause in
the debugger after each warning), use TRACE on the WARN function. [The
manual should state explicitly that this is supposed to work; calls to
WARN mustn't be compiled in a special way that would make TRACE not find
them. I realize that currently the portable subset of TRACE does not
define a way to make a function pause into the debugger.]
[The Lisp machine calls this COMPILER:WARN, approximately.]
BREAK format-string &rest args
Print the message and go directly into the debugger, without allowing
any possibility of interception by error-handling facilities. There
aren't any error-handling facilities in Common Lisp, but there might
be in particular implementations, and there will be in Common Lisp
in the future. The lack of interception is the only program-visible
difference between BREAK and CERROR. The interactive debugger may choose
to display them differently, for instance a CERROR's message might be
prefixed with "Error:" and a BREAK's message prefixed with "Break:".
This depends on the user-interface style of the particular implementation.
When continued, BREAK returns NIL. It is permissible to call BREAK with no
arguments; it will supply some default message.
Compatibility note: Maclisp's BREAK takes two optional arguments. The
first would be a string if Maclisp had strings. The second is a boolean
value specifying whether BREAK should break or return immediately. In
Common Lisp one makes a BREAK conditional by putting it inside a conditional
form such as WHEN or UNLESS.
- Specialized error-signalling special forms (or macros)
CHECK-TYPE place typespec [string] Special Form
Signal an error if the contents of place is not of the desired type.
Continuing from this error will ask for a new value, store it into
place, and start over, checking the type of the new value and signalling
another error if it is still not of the desired type. Subforms of
place may be evaluated multiple times, because of the implicit
loop generated. CHECK-TYPE returns NIL.
place must be a generalized variable reference acceptable to SETF.
typespec must be a type expression; it is not evaluated.
string must be an English description of the type, starting with
an indefinite article (a or an); it is not evaluated. If string is
not supplied, it is computed automatically from typespec.
The error message will mention place, its contents, and the desired
type. Some implementations may generate a somewhat differently worded
error message if they recognize that place is one of the arguments to
the function that called CHECK-TYPE. Example:
(SETQ X 'FOO)
(CHECK-TYPE X (INTEGER 0 *) "a positive integer")
Error: The value of X, FOO, is not a positive integer.
[The Lisp machine calls this CHECK-ARG-TYPE.]
ASSERT test-form [reference]* [string [arg]*] Special Form
Signal an error if the value of test-form is false. Continuing
from this error will allow the user to alter the values of some
variables and will then start over, evaluating the test-form again.
ASSERT returns NIL.
test-form is any form. Each reference (there may be any number of them,
or none) is a generalized-variable reference acceptable to SETF.
These are variables that test-form depends on and that it makes sense to
allow the user to change when continuing from the error. Subforms of
the references are only evaluated if an error is signalled, and may be
re-evaluated if the error is signalled again (after continuing without
actually fixing the problem). string is an error message string, not
evaluated. args are forms evaluated only if an error is signalled, and
re-evaluated if the error is signalled again. FORMAT is applied to
string and args to get the error message. If string is omitted, a
default error message such as "assertion failed" is used; in this case
the args must be omitted, since the string serves to delimit the
references from the args.
The test-form and references are not directly included in the error
message, but might be made available for the user's perusal by the
debugger. If the user gives the continue command, he should be
presented with the opportunity to alter the values of any or all of the
references; the details of this depend on each particular
implementation's user-interface style, of course.
I'm willing to change the syntax of ASSERT to include some parentheses,
e.g. around the references, if the use of the error message string as
a delimiter is felt to be too untasteful. Note however that that would
make it non-upward-compatible with the definition in the laser edition.
I don't know whether that matters.
Examples:
(ASSERT (VALVE-CLOSED-P V1))
(ASSERT (VALVE-CLOSED-P V1) "Live steam is escaping!")
(ASSERT (VALVE-CLOSED-P V1) (VALVE-MANUAL-CONTROL V1)
"Live steam is escaping!")
(ASSERT (<= MINBASE BASE MAXBASE) BASE
"Base ~D is out of the range ~D-~D" BASE MINBASE MAXBASE)
;Note that the user is invited to change BASE, but not the bounds.
(ASSERT (= (ARRAY-DIMENSION A 1) (ARRAY-DIMENSION B 0)) A B
"The matrix multiplication ~S x ~S cannot be performed" A B)
- Exhaustive case analysis special forms (or macros)
[Better names for these special forms are solicited!]
ETYPECASE value [clauses]* Special Form
The syntax is the same as TYPECASE, except that no OTHERWISE clause is
permitted. If no clause is satisfied, ETYPECASE signals an error with
a message constructed from the clauses. It is not permissible to
continue from this error. To supply your own error message, use
TYPECASE with an OTHERWISE clause containing a call to ERROR. The
name of this function stands for either "exhaustive type case" or
"error-checking type case".
Example:
(SETQ X 1/3)
(ETYPECASE X (INTEGER (- X)) (SYMBOL (INVERSE X)))
Error: The value of X, 1/3, was neither an integer nor a symbol.
CTYPECASE reference [clauses]* Special Form
The syntax is the same as TYPECASE, except that no OTHERWISE clause is
permitted. The reference must be a generalized variable reference
acceptable to SETF. If no clause is satisfied, CTYPECASE signals an
error with a message constructed from the clauses. Continuing from this
error accepts a new value from the user, stores it into reference, and
starts over, making the type tests again. Subforms of reference may be
evaluated multiple times. The name of this function stands for
"continuable exhaustive type case".
ECASE value [clauses]* Special Form
The syntax is the same as CASE, except that no OTHERWISE clause is
permitted. If no clause is satisfied, ECASE signals an error with a
message constructed from the clauses. It is not permissible to continue
from this error. To supply your own error message, use CASE with an
OTHERWISE clause containing a call to ERROR. The name of this function
stands for either "exhaustive case" or "error-checking case".
Example:
(SETQ X 1/3)
(ECASE X (ALPHA (FOO)) (OMEGA (BAR)))
Error: The value of X, 1/3, is neither ALPHA nor OMEGA.
CCASE reference [clauses]* Special Form
The syntax is the same as CASE, except that no OTHERWISE clause is
permitted. The reference must be a generalized variable reference
acceptable to SETF. If no clause is satisfied, CCASE signals an error
with a message constructed from the clauses. Continuing from this error
accepts a new value from the user, stores it into reference, and starts
over, making the clause tests again. Subforms of reference may be
evaluated multiple times. The name of this function stands for
"continuable exhaustive case".
- Issues
Should the following function exist? It's not clear how to define what
it means in an implementation-independent way.
ABORT-PROGRAM
This function never returns. It throws out of the user's program
and restores control to the Lisp system. (In the future, when
Common Lisp includes error-handling mechanisms, it will also include
a way to define to where this function, and the equivalent debugger
command, transfers control.)
[The Lisp machine calls this (SIGNAL 'SYS:ABORT), but very few programs
do this explicitly. Probably no "user" programs do it.]
In CHECK-TYPE, ASSERT, CTYPECASE, and CCASE, I have specified that
subforms of the references may be evaluated multiple times. Would it
be better to specify that they are always evaluated exactly once? This
would be "more consistent" with SETF, but might present implementation
difficulties. In ASSERT there is an efficiency issue, since you would
like not to deal with anything but the test-form in the usual, no-error
case; thus ASSERT shouldn't be required to evaluate the references and
args if it doesn't signal an error. Furthermore, "the same" references
presumably appear somewhere inside the test-form, or somewhere inside
something it calls, so in fact the references would really be evaluated
twice anyway. In CHECK-TYPE, CTYPECASE, and CCASE the naive user might
plausibly expect subforms of the reference to be evaluated only once.
It might not be a good idea to make ASSERT be inconsistent with the
other three, though. I'm unsure what to do here.
- Possible future extensions
We anticipate that some or all of the following will be added to Common
Lisp in the future, when they are better understood.
A "condition system" providing names for unusual conditions (including
but not limited to errors) and a taxonomic system for classifying those
names.
Extension of FERROR, CERROR, and WARN so that if the first argument
is a symbol, it is taken to be a condition name. In this case the
format-string is the second argument. If no condition name is specified,
a default condition name with no interesting properties is assumed.
Ways to define the behavior and relationships of conditions. Ways to
use conditions as inter-module interfaces. Ways to use conditions to
customize the behavior of the interactive debugger.
Ways to establish condition handlers, so that programs may respond to
conditions, by throwing out, by continuing, or by correcting the reason
for the condition and retrying.
A way to trap errors without going into the debugger, within a certain
dynamic scope.
Portable debugger details, e.g. TRACE and BREAKON commands.
Facilities making it possible for a user to write a portable debugger.
Portable floating-point exception, overflow, and rounding control.