When an exception is raised, control escapes out of an arbitrary deep
evaluation context to the point where the exception is caught—
But if control escapes “all the way out,” why does the REPL keep going after an error is printed? You might think that it’s because the REPL wraps every interaction in a with-handlers form that catches all exceptions, but that’s not quite the reason.
The actual reason is that the REPL wraps the interaction with a prompt, which effectively marks the evaluation context with an escape point. If an exception is not caught, then information about the exception is printed, and then evaluation aborts to the nearest enclosing prompt. More precisely, each prompt has a prompt tag, and there is a designated default prompt tag that the uncaught-exception handler uses to abort.
The call-with-continuation-prompt function installs a prompt with a given prompt tag, and then it evaluates a given thunk under the prompt. The default-continuation-prompt-tag function returns the default prompt tag. The abort-current-continuation function escapes to the nearest enclosing prompt that has a given prompt tag.
> (define (escape v) (abort-current-continuation (default-continuation-prompt-tag) (lambda () v)))
> (+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (escape 0)))))))
> (+ 1 (call-with-continuation-prompt (lambda () (+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (escape 0)))))))) (default-continuation-prompt-tag)))
In escape above, the value v is wrapped in a procedure that is called after escaping to the enclosing prompt.
Prompts and aborts look very much like exception handling and raising. Indeed, prompts and aborts are essentially a more primitive form of exceptions, and with-handlers and raise are implemented in terms of prompts and aborts. The power of the more primitive forms is related to the word “continuation” in the operator names, as we discuss in the next section.