|
PLT MzScheme: Language Manual
|
( |
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.4.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.4.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).
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) full continuations are not allowed to cross certain boundaries (e.g., error handling) that escape continuations can safely 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.1). 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.4.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.2), 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.4.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