1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-06-12 23:00:22 +02:00

(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.
This commit is contained in:
Neil Jerram 2006-02-04 14:35:53 +00:00
parent ba28d59428
commit 7b4c914e49
2 changed files with 228 additions and 86 deletions

View 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> 2006-02-04 Kevin Ryde <user42@zip.com.au>
* api-options.texi (Build Config): pxref libtool on libguileinterface * api-options.texi (Build Config): pxref libtool on libguileinterface

View file

@ -600,8 +600,9 @@ more conveniently.
@menu @menu
* Exception Terminology:: Different ways to say the same thing. * Exception Terminology:: Different ways to say the same thing.
* Catch:: Setting up to catch exceptions. * 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. * Lazy Catch:: Catch without unwinding the stack.
* Throw:: Throwing an exception.
* Exception Implementation:: How Guile implements exceptions. * Exception Implementation:: How Guile implements exceptions.
@end menu @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. @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 The arguments of a @code{catch} expression are a @dfn{key}, which
restricts the set of exceptions to which this @code{catch} applies, a restricts the set of exceptions to which this @code{catch} applies, a
thunk that specifies the code to execute and a @dfn{handler} procedure thunk that specifies the code to execute and one or two @dfn{handler}
that says what to do if an exception is thrown while executing the code. procedures that say what to do if an exception is thrown while executing
Note that if the execution thunk executes @dfn{normally}, which means the code. If the execution thunk executes @dfn{normally}, which means
without throwing any exceptions, the handler procedure is not called at without throwing any exceptions, the handler procedures are not called
all. at all.
When an exception is thrown using the @code{throw} function, the first 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 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 corresponds to the number of arguments in all @code{throw} expressions
that can be caught by this @code{catch}. 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) @deffnx {C Function} scm_catch (key, thunk, handler)
Invoke @var{thunk} in the dynamic context of @var{handler} for Invoke @var{thunk} in the dynamic context of @var{handler} for
exceptions matching @var{key}. If thunk throws to the symbol 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 If the key is @code{#t}, then a throw to @emph{any} symbol will
match this call to @code{catch}. 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 @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 expressions with varying numbers of arguments, you should write it like
this: 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 another, but should be specified by the documentation for each exception
type. type.
Note that, once the handler procedure is invoked, the catch that led to Note that, once the normal (post-unwind) handler procedure is invoked,
the handler procedure being called is no longer active. Therefore, if the catch that led to the handler procedure being called is no longer
the handler procedure itself throws an exception, that exception can active. Therefore, if the handler procedure itself throws an exception,
only be caught by another active catch higher up the call stack, if that exception can only be caught by another active catch higher up the
there is one. call stack, if there is one.
@sp 1 @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) @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)
The above @code{scm_catch} takes Scheme procedures as body and handler @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)
arguments. @code{scm_internal_catch} is an equivalent taking C The above @code{scm_catch_with_pre_unwind_handler} and @code{scm_catch}
functions. 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 @var{body} is called as @code{@var{body} (@var{body_data})} with a catch
catch on exceptions of the given @var{tag} type. If an exception is on exceptions of the given @var{tag} type. If an exception is caught,
caught, @var{handler} is called @code{@var{handler} @var{pre_unwind_handler} and @var{handler} are called as
(@var{handler_data}, @var{key}, @var{args})}. @var{key} and @code{@var{handler} (@var{handler_data}, @var{key}, @var{args})}.
@var{args} are the @code{SCM} key and argument list from the @var{key} and @var{args} are the @code{SCM} key and argument list from
@code{throw}. the @code{throw}.
@tpindex scm_t_catch_body @tpindex scm_t_catch_body
@tpindex scm_t_catch_handler @tpindex scm_t_catch_handler
@ -761,73 +790,58 @@ Operations}).
@end deftypefn @end deftypefn
@node Throw @node Throw Handlers
@subsubsection Throwing Exceptions @subsubsection Throw Handlers
The @code{throw} primitive is used to throw an exception. One argument, It's sometimes useful to be able to intercept an exception that is being
the @var{key}, is mandatory, and must be a symbol; it indicates the type thrown, but without changing where in the dynamic context that exception
of exception that is being thrown. Following the @var{key}, will eventually be caught. This could be to clean up some related state
@code{throw} accepts any number of additional arguments, whose meaning or to pass information about the exception to a debugger, for example.
depends on the exception type. The documentation for each possible type The @code{with-throw-handler} procedure provides a way to do this.
of exception should specify the additional arguments that are expected
for that kind of exception.
@deffn {Scheme Procedure} throw key . args @deffn {Scheme Procedure} with-throw-handler key thunk handler
@deffnx {C Function} scm_throw (key, args) @deffnx {C Function} scm_with_throw_handler (key, thunk, handler)
Invoke the catch form matching @var{key}, passing @var{args} to the Add @var{handler} to the dynamic context as a throw handler
@var{handler}. for key @var{key}, then invoke @var{thunk}.
@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 @end deffn
When an exception is thrown, it will be caught by the innermost @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)
@code{catch} expression that applies to the type of the thrown The above @code{scm_with_throw_handler} takes Scheme procedures as body
exception; in other words, the innermost @code{catch} whose @var{key} is (thunk) and handler arguments. @code{scm_c_with_throw_handler} is an
@code{#t} or is the same symbol as that used in the @code{throw} equivalent taking C functions. See @code{scm_c_catch} (@pxref{Catch})
expression. Once Guile has identified the appropriate @code{catch}, it for a description of the parameters, the behaviour however of course
handles the exception by applying that @code{catch} expression's handler follows @code{with-throw-handler}.
procedure to the arguments of the @code{throw}. @end deftypefn
If there is no appropriate @code{catch} for a thrown exception, Guile If @var{thunk} throws an exception, Guile handles that exception by
prints an error to the current error port indicating an uncaught invoking the innermost @code{catch} or throw handler whose key matches
exception, and then exits. In practice, it is quite difficult to that of the exception. When the innermost thing is a throw handler,
observe this behaviour, because Guile when used interactively installs a Guile calls the specified handler procedure using @code{(apply
top level @code{catch} handler that will catch all exceptions and print @var{handler} key args)}. The handler procedure may either return
an appropriate error message @emph{without} exiting. For example, this normally or exit non-locally. If it returns normally, Guile passes the
is what happens if you try to throw an unhandled exception in the exception on to the next innermost @code{catch} or throw handler. If it
standard Guile REPL; note that Guile's command loop continues after the exits non-locally, that exit determines the continuation.
error message:
@lisp The behaviour of a throw handler is very similar to that of a
guile> (throw 'badex) @code{catch} expression's optional pre-unwind handler. In particular, a
<unnamed port>:3:1: In procedure gsubr-apply @dots{} throw handler's handler procedure is invoked in the exact dynamic
<unnamed port>:3:1: unhandled-exception: badex context of the @code{throw} expression, just as a pre-unwind handler is.
ABORT: (misc-error) @code{with-throw-handler} may be seen as a half-@code{catch}: it does
guile> everything that a @code{catch} would do until the point where
@end lisp @code{catch} would start unwinding the stack and dynamic context, but
then it rethrows to the next innermost @code{catch} or throw handler
The default uncaught exception behaviour can be observed by evaluating a instead.
@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 Lazy Catch @node Lazy Catch
@subsubsection Catch Without Unwinding @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}, 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 with @var{key}, @var{thunk} and @var{handler} arguments specifying the
exception type, normal case code and handler procedure, but differs in 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) @deffnx {C Function} scm_lazy_catch (key, thunk, handler)
This behaves exactly like @code{catch}, except that it does This behaves exactly like @code{catch}, except that it does
not unwind the stack before invoking @var{handler}. not unwind the stack before invoking @var{handler}.
The @var{handler} procedure is not allowed to return: If the @var{handler} procedure returns normally, Guile
it must throw to another catch, or otherwise exit non-locally. 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 @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) @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}. follows @code{lazy-catch}.
@end deftypefn @end deftypefn
Typically, @var{handler} should save any desired state associated with Typically @var{handler} is used to display a backtrace of the stack at
the stack at the point where the corresponding @code{throw} occurred, the point where the corresponding @code{throw} occurred, or to save off
and then throw an exception itself --- usually the same exception as the this information for possible display later.
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}.
Not unwinding the stack means that throwing an exception that is caught Not unwinding the stack means that throwing an exception that is caught
by a @code{lazy-catch} is @emph{almost} equivalent to calling the 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 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. 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 @node Exception Implementation
@subsubsection How Guile Implements Exceptions @subsubsection How Guile Implements Exceptions