MzScheme supports the exception system proposed by Friedman, Haynes, and Dybvig.7 MzScheme's implementation extends that proposal by defining the specific exception values that are raised by each primitive error.
(raise exn) raises an exception, where exn represents
the exception being raised. The exn argument can be anything;
it is passed to the current exception handler. Breaks are
disabled while the exception handler is called; see
section 6.6 for more information.
(current-exception-handler) returns the current exception
handler that is used by , and
raise(current-exception-handler installs the procedure
f)f as the current exception handler. The
procedure is a parameter; see
section 7.7.1.7 for more information.current-exception-handler
Any procedure that takes one argument can be an exception handler,
but it is an error if the exception handler returns to its caller
when invoked by . (If an exception handler returns, the
current error display handler and current error escape handler are
called directly to report the handler's mistake.)raise
The default exception handler prints an error message using the
current error display handler (see in
section 7.7.1.7) and then escapes by calling the
current error escape handler (see error-display-handler in
section 7.7.1.7). If an exception is raised while an
exception handler is executing, an error message is printed using a
primitive error printer and the primitive error escape handler is
invoked.error-escape-handler
( is a syntactic form that evaluates the
with-handlers ((pred handler)
···) expr ···1)expr body, installing a new exception handler before evaluating
the exprs and restoring the handler when a value is returned
(or when control escapes from the expression). The pred and
handler expressions are evaluated in the order that they are
specified, before the first expr and before the exception
handler is changed. The exception handler is installed and restored
with parameterize (see section 7.7.2).
The new exception handler processes an exception only if one of the
pred procedures returns a true value when applied to the
exception, otherwise the original exception handler is invoked (by
raising the exception again). If an exception is handled by one of
the handler procedures, the result of the entire
with-handlers expression is the return value of the handler.
When an exception is raised during the evaluation of exprs,
each predicate procedure pred is applied to the exception
value; if a predicate returns a true value, the corresponding
handler procedure is invoked with the exception as an
argument. The predicates are tried in the order that they are
specified.
Before any predicate or handler procedure is invoked, the
continuation of the entire with-handlers expression is
restored. The ``original'' exception handler (the one present before
the with-handlers expression was evaluated) is therefore
re-installed before any predicate or handler procedure is invoked.
A particularly useful predicate procedure is .
not-break-exn?(not-break-exn? v) returns #f if v is an instance
of exn:break (representing an asynchronous break exception),
#t otherwise.
The following example defines a divide procedure that returns
+inf.0 when dividing by zero instead of signaling an exception
(other exceptions raised by / are signaled):
(define div-w-inf
(lambda (n d)
(with-handlers ([exn:application:divide-by-zero?
(lambda (exn) +inf.0)])
(/ n d))))
The following example catches and ignores file exceptions, but lets the enclosing context handle breaks:
(define (file-date-if-there filename) (with-handlers ([not-break-exn?(lambda (exn) #f)]) (file-or-directory-modify-secondsfilename)))
Whenever a primitive error occurs in MzScheme, an exception is raised.
The value that is passed to the current exception handler is always
an instance of the exn structure type. Every exn
structure value has a message field that is a string, the
primitive error message. The default exception handler recognizes
exception values with the exn? predicate and passes the
error message to the current error display handler (see
in section 7.7.1.7).error-display-handler
Primitive errors do not create immediate instances of the exn
structure type. Instead, an instance from a hierarchy of subtypes of
exn is instantiated. The subtype more precisely identifies the
error that occurred and may contain additional information about the
error. The table below defines the type hierarchy that is used by
primitive errors and matches each subtype with the primitive errors
that instantiate it.
In the table, each bulleted line is a separate structure type. A type
is nested under another when it is a subtype.
For example, applying a procedure to the wrong number of arguments
raises an exception as an instance of exn:application:arity.
An exception handler can test for this kind of exception using the
global predicate. Given such an
exception, the (incorrect) number of arguments provided is obtained
from the exception with exn:application:arity?, while
exn:application-value accesses the actual arity of the
procedure.exn:application:arity-expected
exn : not instantiated directly
messagefield,immutable-string-- error messagecontinuation-marksfield,mark-set-- value returned bycurrent-continuation-marksimmediately after the error is detected
exn:variable : unbound or not-yet-defined global or module variable at run time
idfield,symbol-- the variable's identifier
exn:application : not instantiated directly
valuefield,value-- the error-specific inappropriate value
exn:syntax : syntax error, but not a read error
exprfield,syntax object or #f-- illegal expression (or #f if unknown)formfield,symbol or #f-- the syntactic form name that detected the error (or #f if unknown)modulefield,symbol, module path index, or #f-- the form-defining module (or #f if unknown)
sourcefield,value-- source namelinefield,positive exact integer or #f-- source linecolumnfield,non-negative exact integer or #f-- source columnpositionfield,positive exact integer or #f-- source positionspanfield,non-negative exact integer or #f-- source span
exn:break : asynchronous thread break
continuationfield,continuation-- a continuation that resumes from the break
exn:special-comment : raised by a custom input port's special-reading procedure
widthfield,non-negative exact integer-- width of the special comment in port positions
Primitive procedures that accept a procedure argument with a
particular required arity (e.g., ,
call-with-input-file) check the argument's arity immediately, raising
call/ccexn:application:type if the arity is incorrect.
The procedure raises the exception errorexn:user (which
contains an error string). The procedure has three forms:
error
(error symbol) creates a message string by concatenating
"error: " with the string form of symbol.
(error msg-string v ···) creates a message string by
concatenating msg-string with string versions of the vs
(as produced by the current error value conversion handler; see
section 7.7.1.7). A space is inserted before
each v.
(error src-symbol format-string v ···) creates a message
string equivalent to the string created by:
( |
In all cases, the constructed message string is passed to
and the resulting exception is raised.make-exn:user
(raise-type-error name-symbol expected-string v) creates an
exn:application:type value and s it as an exception.
The raisename-symbol argument is used as the source procedure's name
in the error message. The expected-string argument is used as a
description of the expected type, and v is the value
received by the procedure that does not have the expected type.
(raise-type-error name-symbol expected-string bad-k v) is
similar, except that the bad argument is indicated by an index (from
0), and all of the original arguments v are provided (in
order). The resulting error message names the bad argument and also
lists the other arguments. If bad-k is not less than the number
of vs, the exn:application:mismatch exception is raised.
(raise-mismatch-error name-symbol message-string v) creates an
exn:application:mismatch value and s it as an
exception. The raisename-symbol is used as the source procedure's
name in the error message. The message-string is the error
message. The v argument is the improper argument received by
the procedure. The printed form of v is appended to
message-string (using the error value conversion handler; see
section 7.7.1.7).
(raise-syntax-error name message-string [expr sub-expr]) creates
an exn:syntax value and s it as an exception.
Macros use this procedure to report syntax errors. The raisename
argument is usually #f when expr is provided; it is
described in more detail below. The message-string is used as
the main body of the error message. The optional expr argument
is the erroneous source syntax object or S-expression. The optional
sub-expr argument is a syntax object or S-expression within
expr that more precisely locates the error. If sub-expr
is provided, it is used (in syntax form) as the expr field
of the generated exception record, else the expr is used if
provided, otherwise the expr field is #f. Source
location information for the error message is similarly extracted
from sub-expr or expr, when at least one is a syntax
object.
The form name used in the generated error message and the values of
the form and module fields of the generated
exception are determined through a combination of the name,
expr, and sub-expr arguments. The name argument
can be any of three kinds of values:
#f: When name is #f, and when
expr is either an identifier or a syntax pair containing an
identifier as its first element, then the form name from the error
message is the identifier's symbol, the form field of the
exception is the third result of identifier-binding applied
to the identifier, and the module field of the exception is
the fourth result of identifier-binding applied to the
identifier. (See section 12.3.2 for information about
identifier-binding.)
If expr is not provided, or if it is not an identifier or a
syntax pair containing and identifier as its first element, then the
form name in the error message is "?", the form
field of the exception is #f, and the module field
of the exception is #f.
symbol: When name is a symbol, then the symbol
is used as the form name in the generated error message. If
expr is provided, and it is either an identifier or a syntax
pair whose first element is an identifier, then the exception fields
are computed in the same way as when name is
#f. Otherwise, the form field of the exception is
name, and the module field of the exception is
#f.
(: When list msg-symbol form mod)name is a
list of three items, the first is used as the form name in the
generated error message, the second (which can be a symbol or
#f) is used as the form field of the generated
exception, and the last (which can be a module index path, a symbol,
or #f) is used as the module field of the
generated exception.
See also section 7.7.1.7.
To improve error reporting, names are inferred at compile-time for certain kinds of values, such as procedures. For example, evaluating the following expression:
(let ([f (lambda () 0)]) (f 1 2 3))
produces an error message because too many arguments are provided to
the procedure. The error message is able to report ``f'' as the name
of the procedure. In this case, MzScheme decides, at compile-time, to
name as f all procedures created by the let-bound
lambda.
Names are inferred whenever possible for procedures. Names closer to an expression take precedence. For example, in
(define my-f (let ([f (lambda () 0)]) f))
the procedure bound to my-f will have the inferred name ``f''.
When an 'inferred-name property is attached to a syntax
object for an expression (see section 12.6.2), the property value
is used for naming the expression, and it overrides any name that was
inferred from the expression's context.
When an inferred name is not available, but a source location is
available, a name is constructed using the source location
information. Inferred and property-assigned names are also available
to syntax transformers, via syntax-local-name; see
section 12.6 for more information.
(object-name v) returns a symbol or immutable string for the name
of v if v has a name, #f otherwise. The argument
v can be any value, but only (some) procedures, structs, struct
types, struct type properties, regexp values, and input ports have
names. Only regexp values and input ports have string names (the
source of the regexp, or an absolute path for file input ports);
other names are symbols. All primitive procedures have names (see
section 3.10.2).
MzScheme supports fully re-entrant
call-with-current-continuation (or
call/cc). The macro let/cc binds a
variable to the continuation in an immediate body of expressions:
(let/cc k expr ···1)
=expands=>
(call/cc (lambda (k) expr ···1))
A continuation can only be invoked from the thread (see Chapter 7) in which it was captured. Multiple return values can be passed to a continuation (see section 2.2).
MzScheme installs a continuation boundary around evaluation in the following contexts, preventing full-continuation jumps across the boundary:
applying an exception handler, an error escape handler, or an error display handler (see section 6.1);
applying a macro transformer (see section 12.6), evaluating a compile-time expression, or applying a module name resolver (see section 5.4.1);
applying a custom-port procedure (see section 11.1.6), a waitable guard procedure (see section 7.6), or a parameter guard procedure (see section 7.7);
applying a security-guard procedure (see section 9.1);
applying a will procedure (see section 13.2); or
evaluating or loading code from the stand-alone MzScheme command line (see section 17).
In addition, extensions of MzScheme may install boundaries in additional contexts. In particular, MrEd installs a continuation boundary around most every callback.
In addition to regular , MzScheme provides
call/cccall-with-escape-continuation (or
call/ec) and let/ec. A continuation
obtained from can only be used to escape back to
the continuation; i.e., an escape continuation is only valid when the
current continuation is an extension of the escape continuation. The
application of call/ec's argument is not a tail
call. call/ec
Escape continuations are provided for two reasons: 1) they are significantly cheaper than full continuations; and 2) they can cross continuation boundaries (which full continuations cannot cross).
The exn:application:continuation exception is raised when a continuation is
applied by the wrong thread, a continuation application would violate
a continuation boundary, or an escape continuation is applied outside
of its dynamic scope.
(dynamic-wind pre-thunk value-thunk post-thunk) applies its three
thunk arguments in order. The value of a
expression is the value returned by dynamic-windvalue-thunk. The
pre-thunk procedure is invoked before calling value-thunk
and post-thunk is invoked after value-thunk returns. The
special properties of are manifest when control
jumps into or out of the dynamic-windvalue-thunk application (either due to
an exception or a continuation invocation): every time control jumps
into the value-thunk application, pre-thunk is invoked,
and every time control jumps out of value-thunk,
post-thunk is invoked. (No special handling is performed for
jumps into or out of the pre-thunk and post-thunk
applications.)
When calls dynamic-windpre-thunk for normal evaluation of
value-thunk, the continuation of the pre-thunk
application calls value-thunk (with 's
special jump handling) and then dynamic-windpost-thunk. Similarly,
the continuation of the post-thunk application returns the
value of the preceding value-thunk application to the
continuation of the entire application.dynamic-wind
When pre-thunk is called due to a continuation jump, the
continuation of pre-thunk
jumps to a more deeply nested pre-thunk, if any, or jumps
to the destination continuation; then
continues with the context of the pre-thunk's
call.dynamic-wind
Normally, the second part of this continuation is never reached, due
to a jump in the first part. However, the second part is relevant
because it enables jumps to escape continuations that are contained
in the context of the call. Similarly, when
dynamic-windpost-thunk is called due to a continuation jump, the
continuation of post-thunk jumps to a less deeply nested
post-thunk, if any, or jumps to a pre-thunk protecting
the destination, if any, or jumps to the destination continuation,
then continues from the post-thunk's
application.dynamic-wind
Example:
(let ([v (let/ec out (dynamic-wind(lambda () (display"in ")) (lambda () (display"pre ") (display(call/ccout)) #f) (lambda () (display"out "))))]) (when v (v "post "))) =>sdisplayin pre out in post out
(let/ec k0 (let/ec k1 (dynamic-windvoid(lambda () (k0 'cancel)) (lambda () (k1 'cancel-canceled))))) =>'cancel-canceled
To evaluate a sub-expression, MzScheme creates a continuation for the
sub-expression that extends the current continuation. For example, to
evaluate in the expression
expr1
(beginexpr1expr2)
MzScheme extends the continuation of the begin expression
with one continuation frame to create the continuation for
. In contrast, expr1 is in tail
position for the expr2begin expression, so its continuation is the
same as the continuation of the begin expression.
A continuation mark is a keyed mark in a continuation frame. A program can install a mark in the first frame of its current continuation, and it can extract the marks from all of the frames in any continuation. Continuation marks support debuggers and other program-tracing facilities; in particular, continuation frames roughly correspond to stack frames in traditional languages. For example, a debugger can annotate a source program to store continuation marks that relate each expression to its source location; when an exception occurs, the marks are extracted from the current continuation to produce a ``stack trace'' for the exception.
The list of continuation marks for a key k and a continuation
C that extends C0 is defined as follows:
If C is an empty continuation, then the mark list is
.null
If C's first frame contains a mark m for k,
then the mark list for C is (cons ,
where m l0)l0 is the mark list for k in C0.
If C's first frame does not contain a mark keyed by
k, then the mark list for C is the mark list for
C0.
The with-continuation-mark form installs a mark on the
first frame of the current continuation:
(with-continuation-mark key-expr mark-expr body-expr)
The key-expr, mark-expr, and body-expr expressions
are evaluated in order. After key-expr is evaluated to obtain a
key and mark-expr is evaluated to obtain a mark, the key is
mapped to the mark in the current continuation's initial frame. If
the frame already has a mark for the key, it is replaced. Finally,
the body-expr is evaluated; the continuation for evaluating
body-expr is the continuation of the
with-continuation-mark expression (so the result of the
body-expr is the result of the with-continuation-mark
expression, and body-expr is in tail position for the
with-continuation-mark expression).
The procedure extracts the complete
set of continuation marks from a continuation:
continuation-marks
(continuation-marks cont) returns an opaque value
containing the set of continuation marks for all keys in the
continuation cont.
(current-continuation-marks) returns an opaque value
containing the set of continuation marks for all keys in the current
continuation. In other words, it produces the same value as
(.call-with-current-continuation continuation-marks)
The procedure extracts mark values
for a particular key from a continuation mark set:
continuation-mark-set->list
(continuation-mark-set->list mark-set key-v [skip-v])
returns a newly-created list containing the marks for key-v in
mark-set, which is a set of marks returned by
. If current-continuation-marksskip-v is provided,
then it is inserted into the list once for every consecutive sequence
of frames without a key-v mark.
(continuation-mark-set? v) returns #t if v
is a mark set created by or
continuation-marks, current-continuation-marks#f otherwise.
Examples:
(define (extract-current-continuation-marks key) (continuation-mark-set->list(current-continuation-marks) key)) (with-continuation-mark 'key 'mark (extract-current-continuation-marks 'key)) ; =>'(mark)(with-continuation-mark 'key1 'mark1 (with-continuation-mark 'key2 'mark2 (list(extract-current-continuation-marks 'key1) (extract-current-continuation-marks 'key2)))) ; =>'((mark1) (mark2))(with-continuation-mark 'key 'mark1 (with-continuation-mark 'key 'mark2 ; replaces the previous mark (extract-current-continuation-marks 'key)))) ; =>'(mark2)(with-continuation-mark 'key 'mark1 (list; continuation extended to evaluate the argument (with-continuation-mark 'key 'mark2 (extract-current-continuation-marks 'key)))) ; =>'((mark1 mark2))(let loop ([n 1000]) (if (zero?n) (extract-current-continuation-marks 'key) (with-continuation-mark 'key n (loop (sub1n))))) ; =>'(1)
In the final example, the continuation mark is set 1000 times, but
extract-current-continuation-marks returns only one mark
value. Because loop is called tail-recursively, the
continuation of each call to loop is always the continuation of
the entire expression. Therefore, the with-continuation-mark
expression replaces the existing mark each time rather than adding a
new one.
Whenever MzScheme creates an exception record, it fills the
field with the value of
continuation-marks(current-continuation-marks), thus providing a snapshot of the
continuation marks at the time of the exception.
When a continuation procedure returned by
is invoked, it restores the
captured continuation, and also restores the marks in the
continuation's frames to the marks that were present when
call-with-current-continuation was invoked.call-with-current-continuation
A break is an asynchronous exception, usually triggered
through an external source controlled by the user, or through the
break-thread procedure (see section 7.3). A break
exception can only occur in a thread while breaks are enabled. When a
break is detected and enabled, the exn:break exception is raised in the thread
sometime afterward; if breaking is disabled when
is called, the break is suspended until
breaking is again enabled for the thread. While a thread has a
suspended break, additional breaks are ignored.break-thread
Breaks are enabled through the parameter (see
section 7.7.1.8). Certain procedures, such as
break-enabled, enable breaks temporarily while
performing a blocking action. However, breaks are always disabled
while an exception handler is executing, and cannot be enabled
through semaphore-wait/enable-break or uses of procedures like
break-enabled. Note that the handling
procedures supplied to semaphore-wait/enable-breakwith-handlers are not exception
handlers, so breaking within such procedures is controlled by
. Breaks are also disabled (independent of
break-enabled and break-enabled.../enable-break) during the
evaluation of the ``pre'' and ``post'' thunks for a
, whether called during the normal
dynamic-wind calling sequence or via a continuation
jump.dynamic-wind
If breaks are enabled for a thread, and if a break is triggered for
the thread but not yet delivered as an exn:break exception,
then the break is guaranteed to be delivered before breaks can be
disabled in the thread. The timing of exn:break exceptions is
not guaranteed in any other way.
If a break is triggered for a thread that is blocked on a nested
thread (see ), and if breaks are
enabled in the blocked thread, the break is implicitly handled by
transferring it to the nested thread.call-in-nested-thread
When breaks are enabled, they can occur at any point within execution, which makes certain implementation tasks subtle. For example, assuming breaks are enabled when the following code is executed,
(with-handlers ([exn:break?(lambda (x) (void))]) (semaphore-waits))
then it is not the case that a void result means the
semaphore was decremented or a break was received, exclusively. It is possible that both occur: the break may
occur after the semaphore is successfully decremented but before a
void result is returned by . A break
exception will never damage a semaphore, or any other built-in
construct, but many built-in procedures (including
semaphore-wait) contain internal sub-expressions that can be
interrupted by a break.semaphore-wait
In general, it is impossible using only to
implement the guarantee that either the semaphore is decremented or
an exception is raised, but not both. MzScheme therefore supplies
semaphore-wait (see section 7.4), which
does permit the implementation of such an exclusive guarantee:
semaphore-wait/enable-break
(parameterize ([break-enabled#f]) (with-handlers ([exn:break?(lambda (x) (void))]) (semaphore-wait/enable-breaks)))
In the above expression, a break can occur at any point until break
are disabled, in which case a break exception is propagated to the
enclosing exception handler. Otherwise, the break can only occur
within , which guarantees that if a
break exception is raised, the semaphore will not have been
decremented.semaphore-wait/enable-break
To allow similar implementation patterns over blocking port
operations, MzScheme provides
(see section 11.2.1),
read-string-avail!/enable-break (see section 11.2.2), and
other procedures.write-string-avail/enable-break
Special control flow for exceptions is performed by an error
escape handler that is called by the default exception handler. An
error escape handler takes no arguments and must escape from the
expression that raised the exception. The error escape handler is
obtained or set using the error-escape-handler parameter
(see section 7.7.1.7).
An error escape handler cannot invoke a full continuation that was created prior to the exception, but it can invoke an escape continuation (see section 6.3).
The error escape handler is normally called directly by an exception
handler. To escape from a run-time error, use (see
section 6.1) or raise (see section 6.2) instead.error
If an exception is raised while the error escape handler is executing, an error message is printed using a primitive error printer and a primitive error escape handler is invoked.
In the following example, the error escape handler is set so that
errors do not escape from a custom read-eval-print loop:
(let ([orig (error-escape-handler)]) (let/ecexit(let retry-loop () (let/ec escape (error-escape-handler(lambda () (escape #f))) (let loop () (let ([e(my-read)]) (if (eof-object?e) (exit'done) (let ([v (my-evale)]) (my-print v) (loop)))))) (retry-loop))) (error-escape-handlerorig))
See also in section 14.1 for a simpler
implementation of this example.read-eval-print-loop