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
nil
3 lisp> (parse '(it is small))
Bad word in noun phrase: it
nil
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))
(unwind-protect
(progn (setq ,name ,value)
,@body)
(setq ,name ,old-value)))))
1 lisp> (setq number 5)
5
2 lisp> (bind (number 2) (print number) (/ number 0))
2
⋆⋆⋆⋆⋆ Attempt to divide by zero in Quotient
3 lisp> number
5
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. |
|