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 expr
s 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 expr
s,
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-seconds
filename)))
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
message
field,immutable-string
-- error messagecontinuation-marks
field,mark-set
-- value returned bycurrent-continuation-marks
immediately after the error is detected
exn:variable
: unbound global or module variable at run time
id
field,symbol
-- the unbound variable's global identifier
exn:application
: not instantiated directly
value
field,value
-- the error-specific inappropriate value
exn:syntax
: syntax error, but not a read
error
expr
field,syntax object or #f
-- illegal expression (or #f if unknown)form
field,symbol or #f
-- the syntactic form name that detected the error (or #f if unknown)module
field,symbol, module path index, or #f
-- the form-defining module (or #f if unknown)
source
field,value
-- source nameline
field,positive exact integer or #f
-- source linecolumn
field,positive exact integer or #f
-- source columnposition
field,positive exact integer or #f
-- source positionspan
field,non-negative exact integer or #f
-- source span
exn:break
: asynchronous thread break
continuation
field,continuation
-- a continuation that resumes from the break
exn:special-comment
: raised by a custom input port's special-reading procedure
width
field,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/cc
exn:application:type
if the arity is incorrect.
The procedure
raises the exception error
exn: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 v
s
(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 raise
name-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 v
s, 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 raise
name-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 raise
name
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/cc
call-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-wind
value-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-wind
value-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-wind
pre-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-wind
post-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-wind
post-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/cc
out)) #f) (lambda () (display
"out "))))]) (when v (v "post "))) =>s
display
in pre out in post out
(let/ec k0 (let/ec k1 (dynamic-wind
void
(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
(beginexpr1
expr2
)
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 expr2
begin
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-marks
skip-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 (sub1
n))))) ; =>'(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-break
with-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-wait
s))
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-break
s)))
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-handler
orig))
See also
in section 14.1 for a simpler
implementation of this example.read-eval-print-loop