summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2020-04-06 06:31:11 -0700
committerKaz Kylheku <kaz@kylheku.com>2020-04-06 06:31:11 -0700
commit9e4be16274554d469b64e5b240b04fe8549f8a1f (patch)
tree203677f307471db6df8861912b710d74e17771cd
parent49d82c3b34227ec0520eb1cf1bb22083453b49a7 (diff)
downloadtxr-9e4be16274554d469b64e5b240b04fe8549f8a1f.tar.gz
txr-9e4be16274554d469b64e5b240b04fe8549f8a1f.tar.bz2
txr-9e4be16274554d469b64e5b240b04fe8549f8a1f.zip
exceptions: unhandled non-error exceptions now return.
This patch makes a fundamental change in exception behavior. Going forward, if an exception that is not derived from error is not handled (no catch intercepts it, and no handler accepts it) then the throw call simply returns nil to the caller instead of unwinding and terminating the process. For error exceptions, the behavior is the same: the *uhandled-hook* is called, if it exists, and if it doesn't exist or returns, unwinding and termination with diagnostics ensues. The rationale for not treating non-error exceptions fatally is that this simplifies the use of code that throws exceptions for non-error situations like progress updates. The code can be used without the caller having to establish a handler. * txr.1: Documentation updates and comaptibility notes. * unwind.c (uw_rthrow): New returning throw function based on the implementation of uw_throw. (uw_rthrowv, uw_rthrowvf): New functions. (uw_throw): Now a wrapper for uw_rthrow. Because uw_throw still does not return, it calls abort if uw_rthrow returns. uw_throw is used internally only for error exceptions. (uw_throwv, uw_throwfv): Functions removed. (uw_late_init): Register throw and throwf to the new functions uw_rthrowv an uw_rthrowfv. * unwind.h (uw_rthrow, uw_rthrowv, uw_rthrowfv): Declared. (uw_throwv, uw_throwfv): Declarations removed.
-rw-r--r--txr.1145
-rw-r--r--unwind.c54
-rw-r--r--unwind.h5
3 files changed, 161 insertions, 43 deletions
diff --git a/txr.1 b/txr.1
index 36daace0..963b2f89 100644
--- a/txr.1
+++ b/txr.1
@@ -41031,14 +41031,20 @@ which must be an integer.
An
.I exception
in \*(TX is a special event in the execution of the program which
-results in transfer of control. An exception is identified by a symbol,
-known as the
+potentially results in a transfer of control. An exception is identified by a
+symbol, known as the
.IR "exception type" ,
and it carries zero or more arguments, called the
.IR "exception arguments" .
When an exception is initiated, it is said to be
.IR thrown .
+This action is initiated by the following functions:
+.codn throw ,
+.code throwf
+and
+.codn error ,
+and possibly other functions which invoke these.
When an exception is thrown, \*(TX enters into exception processing
mode. Exception processing mode terminates in one of several ways:
.IP -
@@ -41054,22 +41060,48 @@ Handlers are defined by the
.code handler-bind
operator or
.code handle
-macro.
-.IP -
-If no catch or accepting handler is found, control is transferred
-to the function stored in the
-.code *unhandled-hook*
-variable. If that function returns, then unwinding is performed
-after which the process terminates (unless the unwinding actions
-intercept the control to prevent that).
+macro. If a handler returns, then by so doing it declines to
+handle the exception.
.IP -
-If no catch or accepting handler is found and
+If no catch or accepting handler is found for an exception derived from
+.code error
+exception and
.code *unhandled-hook*
is
.codn nil ,
then a built-in strategy for handling the exception is invoked,
consisting of unwinding, and then printing some informational messages and
terminating.
+If the
+.code *unhandled-hook*
+variable contains a value that isn't
+.codn nil ,
+then control is transferred to the function stored in the
+that variable first; only if that function returns is the above
+built-in strategy invoked.
+.IP -
+If no catch or accepting handler is found for an exception derived from
+.codn warning ,
+then a warning diagnostic is issued on the
+.code *stderr*
+stream and a
+.code continue
+exception is thrown with no arguments. If no catch or handler is found
+for that exception, then control returns normally to the site which
+threw the warning exception.
+.IP -
+If no catch or accepting handler is found for an exception that is
+neither derived from
+.code error
+nor from
+.codn warning ,
+then no control transfer takes place; control returns to the
+.code throw
+or
+.code throwf
+function which returns normally, with a return value of
+.codn nil .
+
.PP
.NP* Catches and Handlers
@@ -41403,6 +41435,26 @@ using the
.code format
string and additional arguments.
+Because
+.code error
+throws an error exception, it does not return. If an error exception
+is not handled, \*(TX will issue diagnostic messages and terminate.
+Likewise,
+.code throw
+or
+.code throwf
+are used to generate an error exception, they do not return.
+
+If the
+.code throw
+and
+.code throwf
+functions are used to generate an exception not derived from
+.codn error ,
+and no handler is found which accepts the exception, they return normally, with
+a value of
+.codn nil .
+
.coNP Macros @, catch @ catch* and @ catch**
.synb
.mets (catch < try-expression
@@ -42335,18 +42387,25 @@ more information which is deduced.
.code warning
to identify certain situations of interest. Ordinary non-deferrable
warnings have a structure identical to errors, except for the exception
-symbol. \*(TX's built-in handling of warnings expects these exceptions
-to be continuable. What this means is that a
-.code catch
-for the
+symbol. \*(TX's provides built-in "auto continue" handling for warnings. If a warning
+exception is not intercepted by a catch or an accepting handler, then a
+diagnostic is issued on the
+.code *stderr*
+stream, after which a
.code continue
-exception is expected to be visible. The handler for a warning exception
-issues a diagnostic which incorporates the warning message. Then the
-handler throws a
+exception is thrown with no arguments. If that
.code continue
+exception is not handled, then control returns normally to the point that
exception to resume the computation which generated the warning.
-The generation of a warning thus conforms to the following pattern:
+Callers which invoke code that may generate warning exceptions are therefore
+not required to handle them. However, callers which do handle warning
+exceptions expect to be able to throw a
+.code continue
+exception in order to resume the computation that triggered the warning,
+without allowing other handlers to see the exception.
+
+The generation of a warning should thus conform to the following pattern:
.verb
(catch
@@ -42424,11 +42483,26 @@ The
.code compile-warning
function throws an exception of type
.code warning
-and internally provides the expected
+and internally provides a
.code catch
for the
.code continue
-exception needed to resume after the warning.
+exception which allow a warning handler to resume execution
+after the warning. If a handler throws a
+.code continue
+exception which is caught by
+.codn compile-warning ,
+then
+.code compile-warning
+returns
+.codn nil .
+
+Because
+.code compile-warning
+throws a non-error exception, it returns
+.code nil
+in the event that no catch is found for the exception, and no handler which
+accepts it.
The argument conventions are the same for both functions.
The
@@ -42457,7 +42531,7 @@ The
.code compile-defr-warning
function throws an exception of type
.code defr-warning
-and internally provides the expected
+and internally provides a
.code catch
for the
.code continue
@@ -42478,6 +42552,15 @@ argument of the exception. The
.meta tag
argument is taken as the second argument.
+If the exception isn't intercepted by a catch or by
+an accepting handler,
+.code compile-defr-warning
+returns
+.codn nil .
+In also returns nil if it catches a
+.code continue
+exception.
+
.coNP Function @ purge-deferred-warning
.synb
.mets (purge-deferred-warning << tag )
@@ -72921,6 +73004,24 @@ of these version values, the described behaviors are provided if
is given an argument which is equal or lower. For instance
.code "-C 103"
selects the behaviors described below for version 105, but not those for 102.
+.IP 234
+In \*(TX 234 and older versions, the exception throwing functions
+.code throw
+and
+.code throwf
+did not return, regardless of the exception type. All unhandled exceptions
+triggered internal handling leading to unwinding and termination.
+The current behavior is that only
+.code error
+exceptions lead to termination. When a non-error exception isn't intercepted
+by a catch or handler, the
+.code throw
+or
+.code throwf
+returns normally, yielding the value
+.codn nil .
+If a compatibility value equal to or lower than 234 is requested,
+the old behavior occurs: all unhandled exceptions terminate.
.IP 227
In \*(TX 227 and older versions, the functions
.codn carray-uint ,
diff --git a/unwind.c b/unwind.c
index 54847c75..9f0950da 100644
--- a/unwind.c
+++ b/unwind.c
@@ -658,7 +658,7 @@ static void invoke_handler(uw_frame_t *fr, struct args *args)
uw_catch_end;
}
-val uw_throw(val sym, val args)
+val uw_rthrow(val sym, val args)
{
uw_frame_t *ex;
static int reentry_count = 0;
@@ -705,18 +705,28 @@ val uw_throw(val sym, val args)
}
if (ex == 0) {
- if (std_error == 0) {
- fprintf(stderr, "txr: unhandled exception in early initialization\n");
- abort();
- }
-
if (uw_exception_subtype_p(sym, warning_s)) {
--reentry_count;
if (uw_exception_subtype_p(sym, defr_warning_s))
uw_defer_warning(args);
- else
+ else if (std_error != 0)
format(std_error, lit("warning: ~a\n"), car(args), nao);
+ if (!opt_compat || opt_compat >= 234) {
+ uw_rthrow(continue_s, nil);
+ return nil;
+ }
uw_throw(continue_s, nil);
+ }
+
+ if (!opt_compat || opt_compat >= 234) {
+ if (!uw_exception_subtype_p(sym, error_s)) {
+ --reentry_count;
+ return nil;
+ }
+ }
+
+ if (std_error == 0) {
+ fprintf(stderr, "txr: unhandled exception in early initialization\n");
abort();
}
@@ -746,9 +756,23 @@ val uw_throw(val sym, val args)
abort();
}
-val uw_throwv(val sym, struct args *arglist)
+val uw_rthrowv(val sym, struct args *arglist)
{
- uw_throw(sym, args_get_list(arglist));
+ return uw_rthrow(sym, args_get_list(arglist));
+}
+
+val uw_rthrowfv(val sym, val fmt, struct args *args)
+{
+ val stream = make_string_output_stream();
+ (void) formatv(stream, fmt, args);
+ return uw_rthrow(sym, get_string_from_stream(stream));
+ abort();
+}
+
+val uw_throw(val sym, val args)
+{
+ uw_rthrow(sym, args);
+ abort();
}
val uw_throwf(val sym, val fmt, ...)
@@ -764,14 +788,6 @@ val uw_throwf(val sym, val fmt, ...)
abort();
}
-val uw_throwfv(val sym, val fmt, struct args *args)
-{
- val stream = make_string_output_stream();
- (void) formatv(stream, fmt, args);
- uw_throw(sym, get_string_from_stream(stream));
- abort();
-}
-
val uw_errorf(val fmt, ...)
{
va_list vl;
@@ -1280,8 +1296,8 @@ void uw_late_init(void)
reg_mac(intern(lit("defex"), user_package), func_n2(me_defex));
reg_var(unhandled_hook_s = intern(lit("*unhandled-hook*"),
user_package), nil);
- reg_fun(throw_s, func_n1v(uw_throwv));
- reg_fun(intern(lit("throwf"), user_package), func_n2v(uw_throwfv));
+ reg_fun(throw_s, func_n1v(uw_rthrowv));
+ reg_fun(intern(lit("throwf"), user_package), func_n2v(uw_rthrowfv));
reg_fun(error_s, func_n1v(uw_errorfv));
reg_fun(intern(lit("purge-deferred-warning"), user_package),
func_n1(uw_purge_deferred_warning));
diff --git a/unwind.h b/unwind.h
index ad438ca9..2159a1a4 100644
--- a/unwind.h
+++ b/unwind.h
@@ -324,10 +324,11 @@ void uw_push_fcall(uw_frame_t *, val fun, struct args *args);
void uw_push_eval(uw_frame_t *, val form, val env);
void uw_push_expand(uw_frame_t *, val form, val env);
#endif
+val uw_rthrow(val sym, val exception);
+val uw_rthrowv(val sym, struct args *);
+val uw_rthrowfv(val sym, val fmt, struct args *);
noreturn val uw_throw(val sym, val exception);
-noreturn val uw_throwv(val sym, struct args *);
noreturn val uw_throwf(val sym, val fmt, ...);
-noreturn val uw_throwfv(val sym, val fmt, struct args *);
noreturn val uw_errorf(val fmt, ...);
noreturn val uw_errorfv(val fmt, struct args *args);
val uw_warningf(val fmt, ...);