Exceptions and Control Flow
6.1 Exceptions
MzScheme supports the exception system proposed by Friedman, Haynes, and Dybvig.13 MzScheme's implementation extends that proposal by defining the specific exception values that are raised by each primitive error.
(raisev)raises an exception, wherevrepresents the exception being raised. Thevargument can be anything; it is passed to the current exception handler. Breaks are disabled from the time the exception is raised until the exception handler obtains control, and the handler itself isparameterize-breaked to disable breaks initially; see section 6.6 for more information on breaks.(current-exception-handler)returns the current exception handler that is used by, andraise(current-exception-handlerinstalls the proceduref)fas the current exception handler. Theprocedure is a parameter (see section 7.9).current-exception-handlerAny 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. Furthermore, the call to an exception handler israiseparameterized to setcurrent-exception-handlerto the default exception handler, and it isparameterize-breaked to disable breaks.The default exception handler prints an error message using the current error display handler (see
in section 7.9.1.7) and then escapes by calling the current error escape handler (seeerror-display-handlerin section 7.9.1.7). The call to each handler iserror-escape-handlerparameterized to seterror-display-handlerto the default error display handler,14 and it isparameterize-breaked to disable breaks. The call to the error escape handler is further parameterized to seterror-escape-handlerto the default error escape handler.(is a syntactic form that evaluates thewith-handlers((predhandler) ···)expr···1)exprbody, installing a new exception handler before evaluating theexprs and restoring the handler when a value is returned (or when control escapes from the expression). Thepredandhandlerexpressions are evaluated in the order that they are specified, before the firstexprand before the exception handler is changed. The exception handler is installed and restored withparameterize(see section 7.9.2).The new exception handler processes an exception only if one of the
predprocedures 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 thehandlerprocedures, the result of the entirewith-handlersexpression is the return value of the handler.When an exception is raised during the evaluation of
exprs, each predicate procedurepredis applied to the exception value; if a predicate returns a true value, the correspondinghandlerprocedure 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-handlersexpression is restored, but alsoparameterize-breaked to disable breaks. Thus, breaks are disabled by default during the predicate and handler procedures (see section 6.6), and the ``original'' exception handler is active (i.e., the one present before thewith-handlersexpression was evaluated).The
procedure is useful as a handler predicate to catch all error exceptions. Avoid usingexn:fail?(lambda (x) #t)as a predicate, because theexn:breakexception typically should not be caught (unless it will be re-raised to cooperatively break). Beware, also, of catching and discarding exceptions, because discarding an error message can make debugging unnecessarily difficult.(is the same aswith-handlers*((predhandler) ···)expr···1)with-handlers, but if ahandlerprocedure is called, breaks are not explicitly disabled, and the call is in tail position with respect to thewith-handlers*form.
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:fail:contract: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 ([exn:fail:filesystem?(lambda (exn) #f)]) (file-or-directory-modify-secondsfilename)))
6.1.1 Primitive Exceptions
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.9.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, reading an ill-formed expression raises an exception as
an instance of exn:fail:read. An exception handler can test
for this kind of exception using the global exn:fail:read?
predicate. Given such an exception, an error string can be extracted
using , while exn-messageexn:fail:read-source
accesses a list of source locations for the error.
Fields of the built-in exn structure types are immutable, so
field mutators are not provided. Field-type contracts are enforced
through guards; for example, ( raises
make-exn "Hello" #f)exn:fail:contract because the second argument is not a
continuation mark set. All built-in exn structure types are
transparent to all inspectors (see section 4.5).
exn: not instantiated directlymessagefield,immutable string-- error messagecontinuation-marksfield,mark set-- value returned bycurrent-continuation-marksimmediately before the exception is raised
exn:fail:read:readparsing errorsrclocsfield,immutable list of-- source location(s) of errorsrclocs (see section 11.2.1.1)
exn:break: asynchronous break signalcontinuationfield,escape continuation-- resumes from the break
In addition to the built-in structure types for exceptions, MzScheme provides one built-in structure-type property (see section 4.4):
The
prop:exn:srclocsproperty identifies exceptions that have a list of source locations, which includesexn:fail:readandexn:fail:syntax. Theexn:srclocs?predicate recognizes structures and structure types that have theprop:exn:srclocsproperty. Theexn:srclocs-accessorprocedure takes a structure or structure type with theexn:srclocsproperty and returns a procedure; when the procedure is applied to a structure of the same type, it returns a list ofsrclocs (see section 11.2.1.1).
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:fail:contract if the arity is incorrect.
6.2 Errors
The procedure raises the exception errorexn:fail
(which contains an error string). The procedure has
three forms:
error
(errorsymbol)creates a message string by concatenating"error: "with the string form ofsymbol.(errormsg-string v···)creates a message string by concatenatingmsg-stringwith string versions of thevs (as produced by the current error value conversion handler; see section 7.9.1.7). A space is inserted before eachv.(errorsrc-symbol format-string v···)creates a message string equivalent to the string created by:(
format(string-append"~s: " format-string) src-symbol v ···)
In all cases, the constructed message string is passed to
make-exn:fail and the resulting exception is raised.
The raise-user-error procedure is the same as error,
except that it constructs an exception
with make-exn:fail:user instead
of make-exn:fail. The default error display handler does not
show a ``stack trace'' for exn:fail:user exceptions
(see section 6.5), so raise-user-error should be used
for errors that are intended for end
users. Like error, raise-user-error has three forms:
6.2.1 Application Type Errors
(raise-type-error name-symbol expected-string v)
creates an exn:fail:contract 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:fail:contract exception is raised.
6.2.2 Application Mismatch Errors
(raise-mismatch-error name-symbol message-string v) creates an
exn:fail:contract 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.9.1.7).
6.2.3 Syntax Errors
(raise-syntax-error name message-string [expr sub-expr]) creates
an exn:fail: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) for the exprs field
of the generated exception record, else the expr is used if
provided, otherwise the exprs field is the empty
list. Source location information in the error-message text 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 is determined
through a combination of the name, expr, and
sub-expr arguments. The name argument can #f or
a symbol:
#f: Whennameis#f, and whenexpris 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.If
expris 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"?".symbol: Whennameis a symbol, then the symbol is used as the form name in the generated error message.
See also section 7.9.1.7.
6.2.4 Inferred Value Names
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 value 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 ports have names. The name
of a procedure, struct, struct type, or struct type property is
always a symbol. The name of a regexp value is a string, and a
byte-regexp value's name is a byte string. The name of a port is
typically a path or a string, but it can be arbitrary. All primitive
procedures have names (see section 3.12.2).
6.3 Continuations
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))
Capturing a continuation also captures the current continuation marks (see section 6.5) and parameterization (see section 7.9). A continuation can be invoked from the thread (see Chapter 7) other than the one where it was captured. Multiple return values can be passed to a continuation (see section 2.2).
MzScheme installs a continuation barrier around evaluation in the following contexts, preventing full-continuation jumps across the barrier:
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.7), an event guard procedure (see section 7.7), or a parameter guard procedure (see section 7.9);
applying a security-guard procedure (see section 9.1);
applying a will procedure (see section 13.3); or
evaluating or loading code from the stand-alone MzScheme command line (see section 17).
In addition, extensions of MzScheme may install barriers in additional
contexts. In particular, MrEd installs a continuation barrier around
most every callback. Finally,
(call-with-continuation-barrier thunk) applies thunk with a
barrier between the application and the current continuation.
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 a tail call, just as for call/ec. call/cc
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:fail:contract:continuation exception is raised when a continuation
application would cross a continuation barrier, or an escape
continuation is applied outside of its dynamic scope.
6.4 Dynamic Wind
(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; thencontinues with the context of the
pre-thunk'scall.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. Furthermore, it
means that the continuation marks (see section 6.5) and
parameterization (see section 7.9) for dynamic-windpre-thunk
correspond to those of the dynamic-wind call that installed
pre-thunk. The pre-thunk call, however, is
parameterize-breaked to disable breaks (see also
section 6.6).
Similarly, when 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. As for dynamic-windpre-thunk, the parameterization of the
original dynamic-wind call is restored for the call, and the
call is parameterize-breaked to disable breaks.
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
(let* ([x (make-parameter0)] [lnull] [add (lambda (a b) (set! l (appendl (list(consa b)))))]) (let ([k (parameterize ([x 5]) (dynamic-wind(lambda () (add 1 (x))) (lambda () (parameterize ([x 6]) (let ([k+e (let/cc k (conskvoid))]) (add 2 (x)) ((cdrk+e)) (cark+e)))) (lambda () (add 3 (x)))))]) (parameterize ([x 7]) (let/cc esc (k (consvoidesc))))) l) ; =>'((1 . 5) (2 . 6) (3 . 5) (1 . 5) (2 . 6) (3 . 5))
6.5 Continuation Marks
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, when a procedure is called, MzScheme automatically installs a continuation mark with the procedure's name and source location; when an exception occurs, the marks can be extracted from the current continuation to produce a ``stack trace'' for the exception.15 A more sophisticated debugger can annotate a source program to store continuation marks that relate individual expressions to source locations.
The list of continuation marks for a key k and a continuation
C that extends C0 is defined as follows:
If
Cis an empty continuation, then the mark list is.nullIf
C's first frame contains a markmfork, then the mark list forCis(cons, whereml0)l0is the mark list forkinC0.If
C's first frame does not contain a mark keyed byk, then the mark list forCis the mark list forC0.
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, and The
continuation-marks procedure extracts mark values
for a particular key from a continuation mark set. The complete set
of continuation-mark procedures follows:
continuation-mark-set->list
(continuation-markscont)returns an opaque value containing the set of continuation marks for all keys in the continuationcont. Ifcontis an escape continuation (see section 6.3), then the current continuation must extendcont, or theexn:fail:contractexception is raised.(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-continuationcontinuation-marks)(continuation-mark-set->listmark-set key-v)returns a newly-created list containing the marks forkey-vinmark-set, which is a set of marks returned by.current-continuation-marks(continuation-mark-set->list*mark-set key-list[none-v])returns a newly-created list containing vectors of marks inmark-setfor the keys inkey-list. The length of each vector in the result list is the same as the length ofkey-list, and a value in a particular vector position is the value for the corresponding key inkey-list. Values for multiple keys appear in a single vector only when the marks are for the same continuation frame inmark-set. Ifnone-vis supplied, it is used for vector elements to indicate the lack of a value; the default is#f.(continuation-mark-set-firstoptional-mark-set key-v)returns the first element of the list that would be returned by(, orcontinuation-mark-set->list(or optional-mark-set (current-continuation-marks)) key-v)#fif the result would be the empty list. Typically, this result can be computed more quickly usingcontinuation-mark-set-first.(continuation-mark-set?v)returns#tifvis a mark set created byorcontinuation-marks,current-continuation-marks#fotherwise.(continuation-mark-set->contextmark-set)returns a list representing a ``stack trace'' formark-set's continuation. The list contains pairs, where theof each pair contains eithercar#for a symbol for a procedure name, and theof each pair contains eithercdr#for asrclocvalue for the procedure's source location (see section 11.2.1.1); theandcarare never bothcdr#f.The stack-trace list is the result of
continuation-mark-set->listwithmark-setand MzScheme's private key for procedure-call marks. A stack trace is extracted from an exception and displayed by the default error display handler (see section 6) for exceptions other thanexn:fail:user(seeraise-user-errorin section 6.2).
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
6.6 Breaks
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-like
procedure, and through the break-enabledparameterize-break form,
which is analogous to parameterize (see
section 7.9). The procedure does not
represent a parameter to be used with break-enabledparameterize, because
changing the break-enabled state of a thread requires an explicit
check for breaks, and this check is incompatible with the tail
evaluation of a parameterize expression's body.
(break-enabled[on?])-- gets or sets the break enabled state of the current thread. Ifon?is not supplied, the result is#tif break are currently enabled,#fotherwise. Ifon?is supplied as#f, breaks are disabled, and ifon?is a true value, breaks are enabled.(parameterize-break boolean-expr expr ···1)evaluatesboolean-exprto determine whether breaks are initially enabled in while evaluatingexprs in sequence. The result of theparameter-breakexpression is the result of the lastexpr.Like
parameterize(see section 7.9), a fresh thread cell (see section 7.8) is allocated to hold the break-enabled state of the continuation, and calls tobreak-enabledwithin the continuation access or modify the new cell.(current-break-parameterization)is analogous to(current-parameterization)(see section 7.9); it returns a break-parameterization (effectively a thread cell) that holds the current continuation's break-enable state.(call-with-break-parameterizationbreak-param thunk)is analogous to(call-with-parameterization parameterization thunk)(see section 7.9); it callsthunkin a continuation whose break-enabled state is inbreak-param. Thethunkis not called in tail position with respect to thecall-with-break-parameterizationcall.
Certain procedures, such as ,
enable breaks temporarily while performing a blocking action. If
breaks are enabled for a thread, and if a break is triggered for the
thread but not yet delivered as an semaphore-wait/enable-breakexn: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.
Before calling a with-handlers predicate or handler, an
exception handler, an error display handler, an error escape handler,
an error value conversion handler, or a pre-thunk or
post-thunk for a (see
section 6.4), the call is dynamic-windparameterize-breaked to
disable breaks. Furthermore, breaks are disabled during the
transitions among handlers related to exceptions, during the
transitions between pre-thunks and post-thunks for
, and during other transitions for a
continuation jump. For example, if breaks are disabled when a
continuation is invoked, and if breaks are also disabled in the
target continuation, then breaks will remain disabled until from the
time of the invocation until the target continuation executes unless
a relevant dynamic-winddynamic-windpre-thunk or post-thunk
explicitly enables breaks.
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 breaks
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-bytes-avail!/enable-breakwrite-bytes-avail/enable-break (see section 11.2.2), and
other procedures.
6.7 Error Escape Handler
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.9.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, in a parameterization that sets the error display and escape
handlers to the default handlers, and parameterize-breaked to
disable breaks. 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
13 See http://www.cs.indiana.edu/scheme-repository/doc.proposals.exceptions.html
14 If the current error display handler is the default handler, then the error-display call is parameterized to install an emergency error display handler that attempts to print directly to a console and never fails.
15 Since stack-trace marks are applied dynamically,
they do not necessarily correspond to uses
of with-continuation-mark on the source, and stack-trace marks
can be affected by optimization or just-in-time compilation of the
code. A stack traces is therefore useful as a debugging hint only.