(Throw Handlers): New node.
authorNeil Jerram <neil@ossau.uklinux.net>
Sat, 4 Feb 2006 14:35:53 +0000 (14:35 +0000)
committerNeil Jerram <neil@ossau.uklinux.net>
Sat, 4 Feb 2006 14:35:53 +0000 (14:35 +0000)
(Throw): Moved to after the Lazy Catch node.
(Catch): Enhance to cover the optional pre-unwind handler, and new
APIs scm_c_catch, scm_catch_with_pre_unwind_handler.
(Lazy Catch): Describe relationship to with-throw-handler.
Document that the handler may return, and what happens if it does.
(Throw): Mention that a throw can be handled by a throw handler as
well as by a catch.

doc/ref/ChangeLog
doc/ref/api-control.texi

index ba6494b..f0b8cde 100644 (file)
@@ -1,3 +1,14 @@
+2006-02-04  Neil Jerram  <neil@ossau.uklinux.net>
+
+       * api-control.texi (Throw Handlers): New node.
+       (Throw): Moved to after the Lazy Catch node.
+       (Catch): Enhance to cover the optional pre-unwind handler, and new
+       APIs scm_c_catch, scm_catch_with_pre_unwind_handler.
+       (Lazy Catch): Describe relationship to with-throw-handler.
+       Document that the handler may return, and what happens if it does.
+       (Throw): Mention that a throw can be handled by a throw handler as
+       well as by a catch.
+
 2006-02-04  Kevin Ryde  <user42@zip.com.au>
 
        * api-options.texi (Build Config): pxref libtool on libguileinterface
index 247f1af..424ce45 100644 (file)
@@ -600,8 +600,9 @@ more conveniently.
 @menu
 * Exception Terminology::       Different ways to say the same thing.
 * Catch::                       Setting up to catch exceptions.
-* Throw::                       Throwing an exception.
+* Throw Handlers::              Adding extra handling to a throw.
 * Lazy Catch::                  Catch without unwinding the stack.
+* Throw::                       Throwing an exception.
 * Exception Implementation::    How Guile implements exceptions.
 @end menu
 
@@ -640,11 +641,11 @@ this terminology matches the corresponding Guile primitives.
 @code{catch} is used to set up a target for a possible non-local jump.
 The arguments of a @code{catch} expression are a @dfn{key}, which
 restricts the set of exceptions to which this @code{catch} applies, a
-thunk that specifies the code to execute and a @dfn{handler} procedure
-that says what to do if an exception is thrown while executing the code.
-Note that if the execution thunk executes @dfn{normally}, which means
-without throwing any exceptions, the handler procedure is not called at
-all.
+thunk that specifies the code to execute and one or two @dfn{handler}
+procedures that say what to do if an exception is thrown while executing
+the code.  If the execution thunk executes @dfn{normally}, which means
+without throwing any exceptions, the handler procedures are not called
+at all.
 
 When an exception is thrown using the @code{throw} function, the first
 argument of the @code{throw} is a symbol that indicates the type of the
@@ -679,7 +680,18 @@ procedure must be designed to accept a number of arguments that
 corresponds to the number of arguments in all @code{throw} expressions
 that can be caught by this @code{catch}.
 
-@deffn {Scheme Procedure} catch key thunk handler
+The fourth, optional argument of a @code{catch} expression is another
+handler procedure, called the @dfn{pre-unwind} handler.  It differs from
+the third argument in that if an exception is thrown, it is called,
+@emph{before} the third argument handler, in exactly the dynamic context
+of the @code{throw} expression that threw the exception.  This means
+that it is useful for capturing or displaying the stack at the point of
+the @code{throw}, or for examining other aspects of the dynamic context,
+such as fluid values, before the context is unwound back to that of the
+prevailing @code{catch}.
+
+@deffn {Scheme Procedure} catch key thunk handler [pre-unwind-handler]
+@deffnx {C Function} scm_catch_with_pre_unwind_handler (key, thunk, handler, pre_unwind_handler)
 @deffnx {C Function} scm_catch (key, thunk, handler)
 Invoke @var{thunk} in the dynamic context of @var{handler} for
 exceptions matching @var{key}.  If thunk throws to the symbol
@@ -699,9 +711,24 @@ from further up the call chain is invoked.
 
 If the key is @code{#t}, then a throw to @emph{any} symbol will
 match this call to @code{catch}.
+
+If a @var{pre-unwind-handler} is given and @var{thunk} throws
+an exception that matches @var{key}, Guile calls the
+@var{pre-unwind-handler} before unwinding the dynamic state and
+invoking the main @var{handler}.  @var{pre-unwind-handler} should
+be a procedure with the same signature as @var{handler}, that
+is @code{(lambda (key . args))}.  It is typically used to save
+the stack at the point where the exception occurred, but can also
+query other parts of the dynamic state at that point, such as
+fluid values.
+
+A @var{pre-unwind-handler} can exit either normally or non-locally.
+If it exits normally, Guile unwinds the stack and dynamic context
+and then calls the normal (third argument) handler.  If it exits
+non-locally, that exit determines the continuation.
 @end deffn
 
-If the handler procedure needs to match a variety of @code{throw}
+If a handler procedure needs to match a variety of @code{throw}
 expressions with varying numbers of arguments, you should write it like
 this:
 
@@ -717,24 +744,26 @@ interpretation of the @var{args} varies from one type of exception to
 another, but should be specified by the documentation for each exception
 type.
 
-Note that, once the handler procedure is invoked, the catch that led to
-the handler procedure being called is no longer active.  Therefore, if
-the handler procedure itself throws an exception, that exception can
-only be caught by another active catch higher up the call stack, if
-there is one.
+Note that, once the normal (post-unwind) handler procedure is invoked,
+the catch that led to the handler procedure being called is no longer
+active.  Therefore, if the handler procedure itself throws an exception,
+that exception can only be caught by another active catch higher up the
+call stack, if there is one.
 
 @sp 1
-@deftypefn {C Function} SCM scm_internal_catch (SCM tag, scm_t_catch_body body, void *body_data, scm_t_catch_handler handler, void *handler_data)
-The above @code{scm_catch} takes Scheme procedures as body and handler
-arguments.  @code{scm_internal_catch} is an equivalent taking C
-functions.
-
-@var{body} is called as @code{@var{body} (@var{body_data})} with a
-catch on exceptions of the given @var{tag} type.  If an exception is
-caught, @var{handler} is called @code{@var{handler}
-(@var{handler_data}, @var{key}, @var{args})}.  @var{key} and
-@var{args} are the @code{SCM} key and argument list from the
-@code{throw}.
+@deftypefn {C Function} SCM scm_c_catch (SCM tag, scm_t_catch_body body, void *body_data, scm_t_catch_handler handler, void *handler_data, scm_t_catch_handler pre_unwind_handler, void *pre_unwind_handler_data)
+@deftypefnx {C Function} SCM scm_internal_catch (SCM tag, scm_t_catch_body body, void *body_data, scm_t_catch_handler handler, void *handler_data)
+The above @code{scm_catch_with_pre_unwind_handler} and @code{scm_catch}
+take Scheme procedures as body and handler arguments.
+@code{scm_c_catch} and @code{scm_internal_catch} are equivalents taking
+C functions.
+
+@var{body} is called as @code{@var{body} (@var{body_data})} with a catch
+on exceptions of the given @var{tag} type.  If an exception is caught,
+@var{pre_unwind_handler} and @var{handler} are called as
+@code{@var{handler} (@var{handler_data}, @var{key}, @var{args})}.
+@var{key} and @var{args} are the @code{SCM} key and argument list from
+the @code{throw}.
 
 @tpindex scm_t_catch_body
 @tpindex scm_t_catch_handler
@@ -761,73 +790,58 @@ Operations}).
 @end deftypefn
 
 
-@node Throw
-@subsubsection Throwing Exceptions
+@node Throw Handlers
+@subsubsection Throw Handlers
 
-The @code{throw} primitive is used to throw an exception.  One argument,
-the @var{key}, is mandatory, and must be a symbol; it indicates the type
-of exception that is being thrown.  Following the @var{key},
-@code{throw} accepts any number of additional arguments, whose meaning
-depends on the exception type.  The documentation for each possible type
-of exception should specify the additional arguments that are expected
-for that kind of exception.
+It's sometimes useful to be able to intercept an exception that is being
+thrown, but without changing where in the dynamic context that exception
+will eventually be caught.  This could be to clean up some related state
+or to pass information about the exception to a debugger, for example.
+The @code{with-throw-handler} procedure provides a way to do this.
 
-@deffn {Scheme Procedure} throw key . args
-@deffnx {C Function} scm_throw (key, args)
-Invoke the catch form matching @var{key}, passing @var{args} to the
-@var{handler}.  
-
-@var{key} is a symbol.  It will match catches of the same symbol or of
-@code{#t}.
-
-If there is no handler at all, Guile prints an error and then exits.
+@deffn {Scheme Procedure} with-throw-handler key thunk handler
+@deffnx {C Function} scm_with_throw_handler (key, thunk, handler)
+Add @var{handler} to the dynamic context as a throw handler
+for key @var{key}, then invoke @var{thunk}.
 @end deffn
 
-When an exception is thrown, it will be caught by the innermost
-@code{catch} expression that applies to the type of the thrown
-exception; in other words, the innermost @code{catch} whose @var{key} is
-@code{#t} or is the same symbol as that used in the @code{throw}
-expression.  Once Guile has identified the appropriate @code{catch}, it
-handles the exception by applying that @code{catch} expression's handler
-procedure to the arguments of the @code{throw}.
-
-If there is no appropriate @code{catch} for a thrown exception, Guile
-prints an error to the current error port indicating an uncaught
-exception, and then exits.  In practice, it is quite difficult to
-observe this behaviour, because Guile when used interactively installs a
-top level @code{catch} handler that will catch all exceptions and print
-an appropriate error message @emph{without} exiting.  For example, this
-is what happens if you try to throw an unhandled exception in the
-standard Guile REPL; note that Guile's command loop continues after the
-error message:
-
-@lisp
-guile> (throw 'badex)
-<unnamed port>:3:1: In procedure gsubr-apply @dots{}
-<unnamed port>:3:1: unhandled-exception: badex
-ABORT: (misc-error)
-guile> 
-@end lisp
-
-The default uncaught exception behaviour can be observed by evaluating a
-@code{throw} expression from the shell command line:
-
-@example
-$ guile -c "(begin (throw 'badex) (display \"here\\n\"))"
-guile: uncaught throw to badex: ()
-$ 
-@end example
+@deftypefn {C Function} SCM scm_c_with_throw_handler (SCM tag, scm_t_catch_body body, void *body_data, scm_t_catch_handler handler, void *handler_data, int lazy_catch_p)
+The above @code{scm_with_throw_handler} takes Scheme procedures as body
+(thunk) and handler arguments.  @code{scm_c_with_throw_handler} is an
+equivalent taking C functions.  See @code{scm_c_catch} (@pxref{Catch})
+for a description of the parameters, the behaviour however of course
+follows @code{with-throw-handler}.
+@end deftypefn
 
-@noindent
-That Guile exits immediately following the uncaught exception
-is shown by the absence of any output from the @code{display}
-expression, because Guile never gets to the point of evaluating that
-expression.
+If @var{thunk} throws an exception, Guile handles that exception by
+invoking the innermost @code{catch} or throw handler whose key matches
+that of the exception.  When the innermost thing is a throw handler,
+Guile calls the specified handler procedure using @code{(apply
+@var{handler} key args)}.  The handler procedure may either return
+normally or exit non-locally.  If it returns normally, Guile passes the
+exception on to the next innermost @code{catch} or throw handler.  If it
+exits non-locally, that exit determines the continuation.
+
+The behaviour of a throw handler is very similar to that of a
+@code{catch} expression's optional pre-unwind handler.  In particular, a
+throw handler's handler procedure is invoked in the exact dynamic
+context of the @code{throw} expression, just as a pre-unwind handler is.
+@code{with-throw-handler} may be seen as a half-@code{catch}: it does
+everything that a @code{catch} would do until the point where
+@code{catch} would start unwinding the stack and dynamic context, but
+then it rethrows to the next innermost @code{catch} or throw handler
+instead.
 
 
 @node Lazy Catch
 @subsubsection Catch Without Unwinding
 
+Before version 1.8, Guile's closest equivalent to
+@code{with-throw-handler} was @code{lazy-catch}.  From version 1.8
+onwards we recommend using @code{with-throw-handler} because its
+behaviour is more useful than that of @code{lazy-catch}, but
+@code{lazy-catch} is still supported as well.
+
 A @dfn{lazy catch} is used in the same way as a normal @code{catch},
 with @var{key}, @var{thunk} and @var{handler} arguments specifying the
 exception type, normal case code and handler procedure, but differs in
@@ -839,8 +853,10 @@ that caused the handler to be invoked.
 @deffnx {C Function} scm_lazy_catch (key, thunk, handler)
 This behaves exactly like @code{catch}, except that it does
 not unwind the stack before invoking @var{handler}.
-The @var{handler} procedure is not allowed to return:
-it must throw to another catch, or otherwise exit non-locally.
+If the @var{handler} procedure returns normally, Guile
+rethrows the same exception again to the next innermost catch,
+lazy-catch or throw handler.  If the @var{handler} exits
+non-locally, that exit determines the continuation.
 @end deffn
 
 @deftypefn {C Function} SCM scm_internal_lazy_catch (SCM tag, scm_t_catch_body body, void *body_data, scm_t_catch_handler handler, void *handler_data)
@@ -851,11 +867,9 @@ a description of the parameters, the behaviour however of course
 follows @code{lazy-catch}.
 @end deftypefn
 
-Typically, @var{handler} should save any desired state associated with
-the stack at the point where the corresponding @code{throw} occurred,
-and then throw an exception itself --- usually the same exception as the
-one it caught.  If @var{handler} is invoked and does @emph{not} throw an
-exception, Guile itself throws an exception with key @code{misc-error}.
+Typically @var{handler} is used to display a backtrace of the stack at
+the point where the corresponding @code{throw} occurred, or to save off
+this information for possible display later.
 
 Not unwinding the stack means that throwing an exception that is caught
 by a @code{lazy-catch} is @emph{almost} equivalent to calling the
@@ -926,6 +940,123 @@ debuggers and other reflective programming tools that need to access the
 state of the call stack at the exact point where an exception or an
 error is thrown.  For an example of this, see REFFIXME:stack-catch.
 
+It should be obvious from the above that @code{lazy-catch} is very
+similar to @code{with-throw-handler}.  In fact Guile implements
+@code{lazy-catch} in exactly the same way as @code{with-throw-handler},
+except with a flag set to say ``where there are slight differences
+between what @code{with-throw-handler} and @code{lazy-catch} would do,
+do what @code{lazy-catch} has always done''.  There are two such
+differences:
+
+@enumerate
+@item
+@code{with-throw-handler} handlers execute in the full dynamic context
+of the originating @code{throw} call.  @code{lazy-catch} handlers
+execute in the dynamic context of the @code{lazy-catch} expression,
+excepting only that the stack has not yet been unwound from the point of
+the @code{throw} call.
+
+@item
+If a @code{with-throw-handler} handler throws to a key that does not
+match the @code{with-throw-handler} expression's @var{key}, the new
+throw may be handled by a @code{catch} or throw handler that is _closer_
+to the throw than the first @code{with-throw-handler}.  If a
+@code{lazy-catch} handler throws, it will always be handled by a
+@code{catch} or throw handler that is higher up the dynamic context than
+the first @code{lazy-catch}.
+@end enumerate
+
+Here is an example to illustrate the second difference:
+
+@lisp
+(catch 'a
+  (lambda ()
+    (with-throw-handler 'b
+      (lambda ()
+        (catch 'a
+          (lambda ()
+            (throw 'b))
+          inner-handler))
+      (lambda (key . args)
+        (throw 'a))))
+  outer-handler)
+@end lisp
+
+@noindent
+This code will call @code{inner-handler} and then continue with the
+continuation of the inner @code{catch}.  If the
+@code{with-throw-handler} was changed to @code{lazy-catch}, however, the
+code would call @code{outer-handler} and then continue with the
+continuation of the outer @code{catch}.
+
+Modulo these two differences, any statements in the previous and
+following subsections about throw handlers apply to lazy catches as
+well.
+
+
+@node Throw
+@subsubsection Throwing Exceptions
+
+The @code{throw} primitive is used to throw an exception.  One argument,
+the @var{key}, is mandatory, and must be a symbol; it indicates the type
+of exception that is being thrown.  Following the @var{key},
+@code{throw} accepts any number of additional arguments, whose meaning
+depends on the exception type.  The documentation for each possible type
+of exception should specify the additional arguments that are expected
+for that kind of exception.
+
+@deffn {Scheme Procedure} throw key . args
+@deffnx {C Function} scm_throw (key, args)
+Invoke the catch form matching @var{key}, passing @var{args} to the
+@var{handler}.  
+
+@var{key} is a symbol.  It will match catches of the same symbol or of
+@code{#t}.
+
+If there is no handler at all, Guile prints an error and then exits.
+@end deffn
+
+When an exception is thrown, it will be caught by the innermost
+@code{catch} or throw handler that applies to the type of the thrown
+exception; in other words, whose @var{key} is either @code{#t} or the
+same symbol as that used in the @code{throw} expression.  Once Guile has
+identified the appropriate @code{catch} or throw handler, it handles the
+exception by applying the relevant handler procedure(s) to the arguments
+of the @code{throw}.
+
+If there is no appropriate @code{catch} or throw handler for a thrown
+exception, Guile prints an error to the current error port indicating an
+uncaught exception, and then exits.  In practice, it is quite difficult
+to observe this behaviour, because Guile when used interactively
+installs a top level @code{catch} handler that will catch all exceptions
+and print an appropriate error message @emph{without} exiting.  For
+example, this is what happens if you try to throw an unhandled exception
+in the standard Guile REPL; note that Guile's command loop continues
+after the error message:
+
+@lisp
+guile> (throw 'badex)
+<unnamed port>:3:1: In procedure gsubr-apply @dots{}
+<unnamed port>:3:1: unhandled-exception: badex
+ABORT: (misc-error)
+guile> 
+@end lisp
+
+The default uncaught exception behaviour can be observed by evaluating a
+@code{throw} expression from the shell command line:
+
+@example
+$ guile -c "(begin (throw 'badex) (display \"here\\n\"))"
+guile: uncaught throw to badex: ()
+$ 
+@end example
+
+@noindent
+That Guile exits immediately following the uncaught exception
+is shown by the absence of any output from the @code{display}
+expression, because Guile never gets to the point of evaluating that
+expression.
+
 
 @node Exception Implementation
 @subsubsection How Guile Implements Exceptions