There are as many error handling strategies as programming languages, ranging from “drop everything and quit” to “don’t have errors.”
Here’s a simple one: if an error occurs, put it on the stack and call the function current-error-handler. If that’s not defined, the interpreter has no choice but to quit altogether.
All the pieces are already in place: interp-call, defined in Core operations, is already set up to use interp-try-eval.
; Custom error type that can be put on the stack in its component parts (struct builtin-failure exn:fail ([name : Symbol] [irritants : (Listof Any)]) #:transparent #:type-name Builtin-Failure) ; Utility for builtins to signal an error (: interp-error (All (A) ((Symbol) #:rest Any . ->* . A))) (define (interp-error name . irritants) (raise (builtin-failure "Builtin failure" (current-continuation-marks) name irritants))) (: interp-handle-error (Context Stack Symbol (Listof Any) . -> . (Values Context Stack))) (define (interp-handle-error ctx stack name irritants) (if (context-resolve ctx 'current-error-handler) ; Put the error on the stack (interp-call ctx (list* name irritants stack) 'current-error-handler) ; Kill the interpreter if current-error-handler isn't defined (apply error "Unhandled error" name irritants))) (: interp-try-eval (Context Stack Symbol Function . -> . (Values Context Stack))) (define (interp-try-eval ctx stack name f) (with-handlers ; Builtin failures get the arguments given to interp-error ([builtin-failure? (lambda ([e : Builtin-Failure]) (interp-handle-error ctx stack (builtin-failure-name e) (cons name (builtin-failure-irritants e))))] [exn:fail? ; should this catch all exceptions, including breaks? (lambda ([e : exn]) ; Try to deal with this error in a reasonable way (interp-handle-error ctx stack name (list (exn-message e))))]) (interp-eval ctx stack f))) ; TODO tests for this.