Exceptions and Control Flow

6.1  Exceptions

MzScheme supports a variant of the exception system proposed by Friedman, Haynes, and Dybvig.14 MzScheme's implementation extends that proposal by defining the specific exception values that are raised by each primitive error.

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 exception handler of the enclosing continuation handle other exceptions, including breaks:

(define (file-date-if-there filename)
  (with-handlers ([exn:fail:filesystem? (lambda (exn) #f)])
    (file-or-directory-modify-seconds filename)))

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 error-display-handler in section 7.9.1.7).

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 exn-message, while exn: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, (make-exn "Hello" #f) raises exn:fail:contract because the second argument is not a continuation mark set. A mutable string, however, is allowed as the first argument to an exception constructor, in which case it is converted to an immutable string by the guard. All built-in exn structure types are transparent to all inspectors (see section 4.5).

  • exn:fail:contract:arity : application with the wrong number of arguments

  • exn:fail:contract:divide-by-zero : divide by zero

  • exn:fail:contract:continuation : attempt to cross a continuation barrier

  • exn:fail:contract:variable : unbound/not-yet-defined global or module variable

    id field, symbol -- the variable's identifier

  • exn:fail:read:eof : unexpected end-of-file

  • exn:fail:read:non-char : unexpected non-character

  • exn:fail:filesystem:exists : attempt to create a file that exists already

  • exn:fail:filesystem:version : version mismatch loading an extension

In addition to the built-in structure types for exceptions, MzScheme provides one built-in structure-type property (see section 4.4):

Primitive procedures that accept a procedure argument with a particular required arity (e.g., call-with-input-file, call/cc) check the argument's arity immediately, raising exn:fail:contract if the arity is incorrect.

6.2  Errors

The procedure error raises the exception exn:fail (which contains an error string). The error procedure has three forms:

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.6), 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 Errors

(raise-type-error name-symbol expected-string v) creates an exn:fail:contract value and raises it as an exception. The 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 vs, the exn:fail:contract exception is raised.

(raise-mismatch-error name-symbol message-string v) creates an exn:fail:contract value and raises it as an exception. The 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.9.1.7).

(raise-arity-error name-symbol-or-procedure arity-v [arg-v ···]) creates an exn:fail:contract:arity value and raises it as an exception. The name-symbol-or-procedure is used for the source procedure's name in the error message. The arity-v value must be a possible result from procedure-arity (see section 3.12.1), and it is used for the procedure's arity in the error message; if name-symbol-or-procedure is a procedure, its actual arity is ignored. The arg-v arguments are the actual supplied arguments, which are shown in the error message (using the error value conversion handler; see section 7.9.1.7); also, the number of supplied arg-vs is explicitly mentioned in the message.

6.2.2  Syntax Errors

(raise-syntax-error name message-string [expr sub-expr]) creates an exn:fail:syntax value and raises it as an exception. Macros use this procedure to report syntax errors. The 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) 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:

See also section 7.9.1.7.

6.2.3  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 delimited continuations, and even continuations captured by call-with-current-continuation (or call/cc) are delimited by a prompt. Prompt instances are tagged, and continuations are captured with respect to a particular prompt. Thus, MzScheme's call-with-current-continuation accepts an optional prompt-tag argument: (call-with-current-continuation proc [prompt-tag]), where prompt-tag must be a result from either default-continuation-prompt-tag (the default) or make-continuation-prompt-tag. Prompts, prompt tags, and composable continuations are described further in section 6.5.

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.6) up to the relevant prompt. The current parameterization (see section 7.9) is captured if it was extended via paramaterize or installed via call-with-parameterization since the prompt.

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:

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 call/cc, MzScheme provides call-with-escape-continuation (or call/ec) and let/ec. A continuation obtained from call/ec is actually a kind of prompt: applying an escape continuation can only escape back to the continuation (possibly past a continuation barrier); that is, an escape continuation is only valid when the current continuation is an extension of the escape continuation. Further, the application of call/ec's argument is not a tail call. Escape continuations are provided mainly for backward compatibility, since they pre-date general prompts in MzScheme.

The exn:fail:contract:continuation exception is raised when a continuation application would cross a continuation barrier, or when an escape continuation is applied outside of its dynamic scope.

(continuation? v) returns #t if v is a continuation produced by call-with-current-continuation, call-with-composable-continuation, or call-with-escape-continuation, #f otherwise.

6.4  Dynamic Wind

(dynamic-wind pre-thunk value-thunk post-thunk) applies its three thunk arguments in order. The value of a dynamic-wind expression is the value returned by 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 dynamic-wind are manifest when control jumps into or out of the value-thunk application (either due to a prompt abort 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 dynamic-wind calls pre-thunk for normal evaluation of value-thunk, the continuation of the pre-thunk application calls value-thunk (with dynamic-wind's special jump handling) and then 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 dynamic-wind application.

When pre-thunk is called due to a continuation jump, the continuation of pre-thunk

  1. jumps to a more deeply nested pre-thunk, if any, or jumps to the destination continuation; then

  2. continues with the context of the pre-thunk's dynamic-wind call.

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 dynamic-wind call. Furthermore, it means that the continuation marks (see section 6.6) and parameterization (see section 7.9) for pre-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.7).

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 dynamic-wind application. As for pre-thunk, the parameterization of the original dynamic-wind call is restored for the call, and the call is parameterize-breaked to disable breaks.

In both cases, the target for a jump is recomputed after each pre-thunk or post-thunk completes. When a prompt-delimited continuation (see section 6.5) is captured in a post-thunk, it might be delimited and instantiated in such a way that the target of a jump turns out to be different when the continuation is applied than when the continuation was captured. There may even be no appropriate target, if a relevant prompt or escape continuation is not in the continuation after the restore; in that case, the first step in a pre-thunk or post-thunk's continuation can raise an exception.

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 "))) 
 ; => displays in pre out in post out

(let/ec k0
  (let/ec k1
    (dynamic-wind
     void
     (lambda () (k0 'cancel))
     (lambda () (k1 'cancel-canceled)))))
 ; => 'cancel-canceled

(let* ([x (make-parameter 0)]
       [l null]
       [add (lambda (a b)
              (set! l (append l (list (cons a b)))))])
  (let ([k (parameterize ([x 5])
             (dynamic-wind
                 (lambda () (add 1 (x)))
                 (lambda () (parameterize ([x 6])
                              (let ([k+e (let/cc k (cons k void))])
                                (add 2 (x))
                                ((cdr k+e))
                                (car k+e))))
                 (lambda () (add 3 (x)))))])
    (parameterize ([x 7])
      (let/cc esc
        (k (cons void esc)))))
  l) ; => '((1 . 5) (2 . 6) (3 . 5) (1 . 5) (2 . 6) (3 . 5))

6.5  Prompts and Composable Continuations

For an introduction to composable continuations, see Sitaram and Felleisen, ``Control Delimiters and Their Hierarchies,'' Lisp and Symbolic Computation, 1990.

MzScheme's support for prompts and composable continuations most closely resembles Dorai Sitaram's % and fcontrol operators (see ``Handling Control,'' Proc. Conference on Programming Language Design and Implementation, 1993). Since composable continuations capture and invoke dynamic-wind thunks, however, the fcontrol operator is split into separate capture and abort operations, giving programmers more flexibility with respect to escapes. Composable continuations also capture continuation marks (see section 6.6) and exception handlers (see section 6.1).

See also Chapter 15 in PLT MzLib: Libraries Manual for wrappers of MzScheme's primitives. The wrapper are generally simpler to use and have more standard names.

(call-with-continuation-prompt thunk [prompt-tag handler-proc-or-false]) calls thunk with the current continuation extended by a prompt. The prompt is tagged by prompt-tag, which must be a result from either default-continuation-prompt-tag (the default) or make-continuation-prompt-tag. The handler-proc-or-false argument specifies a handler procedure; the handler is called in tail position with repsect to the call-with-continuation-prompt call when the installed prompt is the target of a abort-current-continuation call with prompt-tag, and the remaining arguments of abort-current-continuation are supplied to the handler procedure. If handler-proc-or-false is #f or not supplied, the default handler accepts a single abort-thunk argument and calls (call-with-continuation-prompt abort-thunk prompt-tag #f); that is, the default handler re-installs the prompt and continues with a given thunk.

(abort-current-continuation prompt-tag obj ···1) resets the current continuation to that of the nearest prompt tagged by prompt-tag in the current continuation; if no such prompt exists, the exn:fail:contract:continuation exception is raised. The objs are delivered as arguments to the target prompt's handler procedure.

The protocol for objs supplied to an abort is specific to the prompt-tag. When abort-current-continuation is used with (default-continuation-prompt-tag), generally a single thunk should be supplied that is suitable for use with the default prompt handler. Similarly, when call-with-continuation-prompt is used with (default-continuation-prompt-tag), the associated handler should generally accept a single thunk argument.

(make-continuation-prompt-tag [symbol]) creates a prompt tag that is not equal? to the result of any other value (including prior or future results from make-continuation-prompt-tag). The optional symbol argument, if supplied, is used when printing the prompt tag.

(default-continuation-prompt-tag) returns a fixed, constant prompt tag for a which a prompt is installed at the start of every thread's continuation; the handler for each thread's initial prompt accepts any number of values and returns. The result of default-continuation-prompt-tag is the default tag for more any procedure that accepts a prompt tag.

A continuation captured by (call-with-current-continuation ... promt-tag) is truncated at the nearest prompt tagged by prompt-tag in the current continuation; if no such prompt exists, the exn:fail:contract:continuation exception is raised. The truncated continuation includes only dynamic-wind thunks (see section 6.4) installed since the prompt.

When a continuation procedure is applied, it removes the portion of the current continuation up to the nearest prompt tagged by prompt-tag (not including the prompt; if no such prompt exists, the exn:fail:contract:continuation exception is raised), or up to the nearest continuation frame (if any) shared by the current and captured continuations -- whichever is first. While removing continuation frames, dynamic-wind post-thunks are executed. Finally, the (unshared portion of the) captured continuation is appended to the remaining continuation, applying dynamic-wind pre-thunks.

(continuation-prompt-tag? v) returns #t if v is a prompt tag produced by make-continuation-prompt-tag or default-continuation-prompt-tag, #f otherwise.

(call-with-composable-continuation proc [prompt-tag]) is similar to call-with-current-continuation, but applying the resulting continuation procedure does not remove any portion of the current continuation. Instead, application always extends the current continuation with the captured continuation (without installing any prompts other than those be captured in the continuation). When call-with-composable-continuation is called, if a continuation barrier appears in the continuation before the closest prompt tagged by prompt-tag, the exn:fail:contract:continuation exception is raised.

(continuation-prompt-available? prompt-tag [cont]) returns #t if cont includes a prompt tagged by prompt-tag, #f otherwise. The cont argument defaults to the current continuation.

6.6  Continuation Marks

To evaluate a sub-expression, MzScheme creates a continuation for the sub-expression that extends the current continuation. For example, to evaluate expr1 in the expression

(begin 
  expr1
  expr2)

MzScheme extends the continuation of the begin expression with one continuation frame to create the continuation for expr1. In contrast, expr2 is in tail position for the 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 (up to the nearest prompt for a specified prompt tag).

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.16 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:

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 continuation-marks procedure extracts the complete set of continuation marks from a continuation (up to a prompt), and the continuation-mark-set->list procedure extracts mark values for a particular key from a continuation mark set. The complete set of continuation-mark procedures follows:

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 continuation-marks field with the value of (current-continuation-marks), thus providing a snapshot of the continuation marks at the time of the exception.

When a continuation procedure returned by call-with-current-continuation or call-with-composable-continuation 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 or call-with-composable-continuation was invoked.

6.7  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 break-thread 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.

Breaks are enabled through the break-enabled parameter-like procedure, and through the parameterize-break form, which is analogous to parameterize (see section 7.9). The break-enabled procedure does not represent a parameter to be used with parameterize, 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.

Certain procedures, such as semaphore-wait/enable-break, 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 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.

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 dynamic-wind (see section 6.4), the call is parameterize-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 dynamic-wind, 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-wind pre-thunk or post-thunk explicitly enables breaks.

If a break is triggered for a thread that is blocked on a nested thread (see call-in-nested-thread), and if breaks are enabled in the blocked thread, the break is implicitly handled by transferring it to the 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 semaphore-wait. 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.

In general, it is impossible using only semaphore-wait to implement the guarantee that either the semaphore is decremented or an exception is raised, but not both. MzScheme therefore supplies semaphore-wait/enable-break (see section 7.4), which does permit the implementation of such an exclusive guarantee:

(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 breaks are disabled, in which case a break exception is propagated to the enclosing exception handler. Otherwise, the break can only occur within semaphore-wait/enable-break, which guarantees that if a break exception is raised, the semaphore will not have been decremented.

To allow similar implementation patterns over blocking port operations, MzScheme provides read-bytes-avail!/enable-break (see section 11.2.1), write-bytes-avail/enable-break (see section 11.2.2), and other procedures.

6.8  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 abort to a prompt (see section 6.5) or 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 raise (see section 6.1) or error (see section 6.2).

The default error escape handler escapes using (abort-current-continuation (default-continuation-prompt-tag) void). Thus, error escapes are best handled by installing a prompt, rather than by changing the error escape handler.

In the following example, a prompt is installed that so errors do not escape from a custom read-eval-print loop:

(let retry-loop ()
  (call-with-continuation-prompt
   (lambda ()
     (let loop ()
       (let ([e (my-read)])
         (unless (eof-object? e)
           (let ([v (my-eval e)])
             (my-print v))
           (loop)))))
   (default-continuation-prompt-tag)
   (lambda args (retry-loop))))

See also read-eval-print-loop in section 14.1 for a simpler and more complete implementation of a custom read-eval-print loop.


14 See http://www.cs.indiana.edu/scheme-repository/doc.proposals.exceptions.html

15 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.

16 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.