[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Stripped down version of LispM error system



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.