7.4 Non-Local Exits

The functions catch and throw are very useful for discontinuing a computation. As return provides for local exit, this pair of functions provide for non-local exit. They should not, however, be used indiscriminately. The lexical restrictions on their more local counterparts ensure that the flow of control can be ascertained by looking at a single piece of code. With catch and throw, control may be passed to and from totally unrelated pieces of code.

(catch TAG:id [FORM:form]): any Open-Compiled fexpr
Catch evaluates TAG to establish a name for this catcher, called the catch-tag, and then evaluates the FORM’s in a protected environment. If during this evaluation a throw occurs with a tag that is the same as the catch-tag (as defined by the function eq), catch immediately returns the result of the form given as the second argument to the throw. If no throw occurs, the results returned by the last FORM are returned as the result of the catch. A catch-tag of nil is considered special, it serves to match any catch-tag specified by throw.

(throw TAG:id VALUE:any): None Returned expr
Throw evaluates TAG to produce a catch-tag and evaluates VALUE to produce a result value. At this point, an error is signalled if there is no active catch with the same catch-tag (as determined by the function eq). Otherwise, control is passed to the most recent such catch, and the results of evaluating VALUE become the results of the catch.

In the process of transferring control to the catch, all intervening constructs are exited. Exiting a construct that binds variables has the effect of unbinding those variables.

throwsignal* [Initially: nil] global This fluid variable is set to t if the most recent invocation of Catch was thrown to. throwsignal* is set to nil upon a normal exit from a catch and to t upon a normal exit from a throw.

throwtag* [Initially: nil] global This fluid variable is set to the catch-tag of the most recent throw

The catch, throw pair supply a construct which allows for some control over the evaluation of an expression. Exceptions can be detected during the evaluation of the expression and appropriate action can be taken. The functions which follow define a simple parser. The parse is done inside a catch. If there are no errors then the result of the parse is returned. When an error arises the computation is aborted with a call on throw. An error message is printed prior to aborting the parse.

(de parse (⋆buffer⋆)  
  (catch 'parse-error (list 's (parse-np) (parse-vp))))  
(de parse-np ()  
  (if (memq (car ⋆buffer⋆) '(a an the))  
    ‘(np (det ,(pop ⋆buffer⋆)) (n ,(pop ⋆buffer⋆)))  
    (parse-error "Bad word in noun phrase: %w%n")))  
(de parse-vp ()  
  (if (memq (car ⋆buffer⋆) '(sings talks))  
    ‘(vp (v ,(pop ⋆buffer⋆)))  
    (parse-error "Not a verb: %w%n")))  
(de parse-error (format-string)  
  (throw 'parse-error (printf format-string (car ⋆buffer⋆))))

1 lisp> (parse '(the bird sings))  
(S (NP (DET THE) (N BIRD)) (VP (V SINGS)))  
2 lisp> (parse '(the bird eats))  
Not a verb: eats  
3 lisp> (parse '(it is small))  
Bad word in noun phrase: it  

The following macros are provided to aid in the use of catch and throw with a nil catch-tag, by examining throwsignal* and throwtag*:

(catch-all HANDLER:function [FORM:form]): any macro
Has the same semantics as catch except that all throws, independent of catch-tag, will be caught. The HANDLER must be a function of two arguments. If a throw occurs, the HANDLER will be called on the catch-tag and the value passed by the throw. The HANDLER may itself issue a throw, in which case the catch-all acts as a filter.

(unwind-all HANDLER:function [FORM:form]): any macro
This function is very similar to catch-all. However, if no throw occurs the HANDLER will be called on nil and the value returned.

(unwind-protect FORM:form [CLEANUPFORM:form]): any macro
The FORM is evaluated and, regardless of how it is exited (a normal return, throw, or error), the CLEANUPFORMs will be evaluated. One common use of unwind-protect is to ensure that a file will be closed after processing.
    (setq channel (open file ....))  
    (unwind-protect (process-file)  
                (close channel))

This primitive can be used to implement arbitrary kinds of state-binding without fear that an unusual return (an error or throw), will violate the binding.

    (defmacro bind ((name value) . body)  
      (let ((old-value (gensym)))  
        ‘(let ((,old-value ,name))  
        (progn (setq ,name ,value)  
        (setq ,name ,old-value)))))

    1 lisp> (setq number 5)  
    2 lisp> (bind (number 2) (print number) (/ number 0))  
    ⋆⋆⋆⋆⋆ Attempt to divide by zero in Quotient  
    3 lisp> number  

Note: Certain special tags are used in the PSL system, and should not be interfered with casually:

$error$ Used by error and errorset which are
implemented in terms of catch and throw,
(see Chapter 16).
$unwind-protect$A special catch-tag placed to ensure
that all throws pause at the
unwind-protect ”mark”.
$prog$ Used to communicate between interpreted progs, gos.