Proposal for XCL Error System Andy Daniels. Last revised March 13, 1986. The Common Lisp defintion includes an extremely primitive error system. It specifies two forms, error and cerror for signalling errors, but no standard way to handle them. In addition, the form unwind-protect is provided as a limited way of restoring state when a context is unwound either due to an error or non-local control transfer. There is a proposed error system for Common Lisp that is based on the ZetaLisp Condition package, which shall be referred to as KMP. This document makes a somewhat different proposal. Differences between this proposal and KMP are discussed below. Defining conditions condition [Type] A condition is a kind of object that represents an exceptional situation. All conditions are objects of type condition, or some type which inherits from condition, i.e., those objects c for which (typep c 'condition) is true. This implies some kind of graph of condition types. In implementations that allow non-hierarchial type systems this will typically be an acyclic graph. In others, it will typically be a tree with condition at the root. Condition types are named by symbols. conditionp object [Function] Synonym for (typep object 'condition) define-condition name &rest slot-descriptions [Macro] This defines a new condition type; it is only way to define a new condition type. The syntax of define-condition is similar to that of defstruct. The only option allowed is :include, with slot default overrides. The slot-descriptions remain as for defstruct. Condition slots are accessed just as structure slots are, with the difference that no :conc-name is possible. The implementation may allow more than one :include option, but this is not required. If no :include option that names a subtype of condition is specified, a default of (:include condition) is assumed. This is not meant to imply that define-condition is implemented by defstruct, although the resulting objects look remarkably like structures. Is is imperative, however, that typep behave correctly with respect to :include. Condition types are "named", i.e., type-of is guaranteed to return the same name that was passed to make-condition to create the object. All condition slots are read-only, that is, they may only be set by slot initizations at make-condition time. make-condition type &rest slot-initializations [Function] This function creates an instance of the given condition type using the given slot initializations. This is provided because Common Lisp has no generic make-instance function. copy-condition condition [Function] This function is provided to conveniently copy a condition object so that it can be re-signalled. define-condition-reporter type bvl &body forms [Macro] All conditions may have a reporter function. This is a function that is used to report the condition in a user-sensible form. It should be a function of two arguments: the condition to be reported and the stream to report to. define-condition-reporter sets the reporter for a condition. If a condition has no reporter, its parent's reporter is used instead. Implementations that allow multiple parents are free to define how a reporter is determined in this case. The reporter is also used to print the condition when *print-escape* is nil, e.g., princ or the ~A format specification. condition-reporter type-or-condition [Function] Returns the condition reporter which would be used to report the condition or condition type. setf may be used with this function. Here are some examples of creating conditions. These forms define a condition called machine-error which inherits from error. (define-condition (machine-error (:include error)) machine-name) (define-condition-reporter machine-error (condition stream) (format stream "There is a problem with ~A." (machine-error-machine-name condition))) This defines a new error condition (a subtype of machine-error) for use when machines are not available. (define-condition (machine-not-available (:include machine-error))) (define-condition-reporter machine-not-available-error (condition stream) (format stream "The machine ~A is not available." (machine-error-machine-name condition))) This defines a still more specific condition, built upon machine-not-available-error, which provides a default for machine-name but which does not provide any new slots. (define-condition (my-favorite-machine-not-available-error (:include machine-not-available-error (machine-name "pele:parc:xerox")))) This makes the machine-name slot no longer be required by giving it a default initialization. Unless a report function is defined later, the report function for machine-not-available-error will be used. The syntax for define-condition, although identical to that of defstruct, is somewhat cumbersome. In particular, since :include is the only allowed option, we could simplify it somewhat to define-condition name ( parents ) &rest slot-descriptions. Each parent is the cdr of a structure's :include option. Using this syntax, the last example would be (define-condition my-favorite-machine-not-available-error ((machine-not-available-error (machine-name "hotlips:osbu north:xerox")))) Signalling Conditions signal condition &key :resumable :must-be-handled &allow-other-keys [Function] An exceptional situation is indicated by signalling the corresponding condition. The condition may be either a condition object previously created by make-condition, or a condition type. In the latter case, signal will create an instance of the condition type, passing along to make-condition any slot initializations provided as other keys. If must-be-handled is true or the condition is of type error and the condition is not handled, the debugger will be entered. The condition may be resumed only if :resumable is true. Attempting to resume an unresumable condition will cause the error resume-error to be signalled. If the condition is not handled, signal simply returns nil. If the condition is resumed, signal returns whatever values were given to resume. This causes the usual ambiguity about resuming with nil, but I don't see a good way to get out that and allow mutiple values to be resumed. error condition-or-format-string &rest data [Function] There are two forms of error. The first form is as described in the Common Lisp spec. It is implemented by signalling the condition si:ferror. The second form is a synonym for (signal condition :resumable nil :must-be-handled t . data). Which form is used is determined by the type of the first argument. If it is a string, then the first form of error is used, if a symbol or condition, the second. error is guaranteed never to return to its caller. error [Condition type] error and its subtypes are condition types which must be handle, if they are not handled cause the debugger to be invoked regardless of the value of the :must-be-handled parameter to signal. It is an error to signal a condition object that has already been signalled. If you wish to re-signal a same condition from within a handler, use copy-condition to create a new object. Handling conditions Conditions are handled by condition handlers. A handler is a function of one argument, the condition being signalled. A handler may do one of three things: it may perform a non-local jump via throw, go or return-from; it may decline to handle the signal, either by explicitly calling decline or "falling off the end"; or it may resume the signal, causing some values to be returned to the signaller. If the condition is not resumable, an attempt to resume it will cause an error to be signalled. error and its subtypes are not resumable. When a handler is invoked, the "handler binding stack" is temporarily popped back to that level. This is so that handlers will not need to keep any state around to prevent recursion. condition-bind (bindings) &rest body [Macro] This is the basic form for binding condition handlers. It executes body in a dynamic context in which the given handler bindings are in effect. The value of the last form in the body is returned as the value of the entire expression. Each binding is of the form (type handler) or ((type1 type2 ...) handler). If a condition is signalled during execution of the body, the handler bindings will be examined in the order that they appear in the form. When an appropriate handler is found, i.e., a binding whose type matches (in the sense of typep) the condition being signalled, that handler is invoked. If it declines the condition, the search is resumed. condition-bind is an implicit block, so that return from locally defined handlers may be used to exit the condition-bind. If a bound handler is not found and the condition type has a default handler, that is used. define-condition-default-handler type bvl &body forms [Macro] Defines a default handler for the condition. The handler should expect a single argument of a condition. condition-default-handler type-or-condition [Function] Returns the default handler for the given condition or condition type. setf may be used with this function. decline condition [Macro] The explicit way to decline a condition. condition should be the object that was originally passed to the handler. The same effect may be achieved by simply returning from the handler. resume condition &rest values [Macro] Causes the condition to be resumed, returning values to the signaller of the condition. condition should be the same object that was passed to the handler. If the condition was not resumable, the error resume-error will be signalled. catch-condition-case form &rest bindings [Macro] Evaluates form in a context that handles the conditions given in the bindings by returning control to this form. The bindings are of the form (type (var) . body) or ((type1 type2 ...) (var) . body). Additionally, a clause of the form (:no-error (var1 var2 ...) . body) may appear. If it does, it must be the last clause. If a condition is signalled that matches one of the selectors in the bindings, the stack is unwound back to the catch-condition-case form, var is bound to the condition signalled, and the body of the clause is evaluated. If form returns normally and there is a :no-error clause, the values that form produced are bound to the vars in that clause, and its body evaluated. The value of the entire form is the value of the last form evaluated. The bound variable lists have an implicit &optional ... &rest ignore wrapped around them. Here are some examples of catch-condition-case (catch-condition-case (/ x y) (err:divide-by-zero () nil)) (catch-condition-case (open *the-file* :direction :input) (err:file-error (condition) (format t "~&Open failed: ~A~%" condition))) (catch-condition-case (some-user-function) (err:file-error (condition) condition) (err:divide-by-zero () 0) ((err:unbound-variable err:undefined-function) 'unbound) (:no-error (x y z) (values z y x))) catch-condition type &body forms [Macro] Executes its body in a context that handles conditions of the given type by returning control to this form. If no such condition is signalled, the principal (first) value of the last form evaluated is returned as a first value and nil is returned as a second value. Otherwise, nil is returned as the first value and the condition object that was signalled is the second. This is essentially (catch-condition-case (progn . forms) (type (condition) (values nil condition)) (:no-error (val) (values val nil))) ignore-errors &body forms [Macro] A synonym for (catch-condition 'error . forms). Comparison with KMP KMP and this proposal agree on several basic principles. Error conditions are represented by objects that are discriminated by type. The condition type system allows for multiple inheritance, but does not require it; it does require some form of subtyping. Both proposals allow for conditions that are not errors. Condition handlers are bound dynamically, but are allowed to decline to handle a condition once they get control, causing previous bindings to be examined. Some conditions may be resumed (proceeded from). Aside from some syntactic differences, the major difference between the two proposals is how conditions are resumed. KMP defines a set of proceed cases for each condition. When any proceedable condition is signalled, some (possibly empty) subset of the proceed types is specified as being available. A condition is proceeded by naming the proceed type and providing some values to be returned to the signaller. If no proceed type is specified, the user is (somehow) asked to choose from one of the available proceed types. If a condition was not handled, signal returns nil; by convention, if a signal is proceeded, it returns the proceed type as the first value. This proposal suggests a somewhat simpler mechanism. resume simply takes a set of values to be returned as the value of signal. The signaller is free to make whatever use it wants of the returned values. Any user intervention is left to the handler and signaller. For the most part, KMP is upward-compatible with this proposal. The major difference is in the handling of error. KMP provides 4 different forms to cover the four combinations of "resumable" and "must be handled." They are signal, error, signal-case, error-case, which correspond to (signal :resumable nil :must-be-handled nil), (signal :resumable nil :must-be-handled t), (signal :resumable t :must-be-handled nil), and (signal :resumable t :must-be-handled t) in this proposal. The resumability of a condition is determined only by which function was used to signal the condition. To implement this in the current proposal would require 2 changes. First, the definitions of signal and error would change to match KMP. This would have the unfortunate property that error no longer be guaranteed not to return. This property can be restored either by having four signalling functions or by not allowing the resumable condition slot to have default values specified for it. It appears that the areas that have been simplified are those in which KMP has wandered off into the area of programming environment issues rather than language issues. In particular, proceed types can certainly be implemented using resume and the convention that the first value returned from signal indicated the proceed type, nil, of course, meaning that the condition was not handled. Other Outstanding Issues For the error system to be complete, a standard set of conditions and their relations must be defined. This proposal does not attempt to do this.  .˜.<<@ø.00@ø.°.<<@ø.HH@ø.00@ø.ø((  TIMESROMAN TERMINAL TIMESROMAN TERMINAL TERMINAL  TIMESROMAN  TIMESROMAN TIMESROMAN MODERN   ,`R [   b #  Î 4  ` # X 9)     b"#:p d4   bâ ^!U A<V1 +DJ2)9 +;J †  />96:K=) c+A5%f M 3/4T&h #;p0.”’”  4%¢ÜCkP6­o7^ iG! ) ‡.$ i Ž %"¥R! š;:I+'9$ç+o&*$    u  £[5=Š U j  ,**(× G <’8Êêzº