How to show error message from eval in Scheme?

458 Views Asked by At

I'm trying to create code that evaluates expression and return error as string for error:

(cond-expand
  (gambit)
  (gauche)
  (kawa)
  (guile
   (import (rnrs base)
           (rnrs exceptions)
           (rnrs conditions))
   (define (error-object-message cond)
     (condition-message cond))))

(define (evaluate expr env)
  (call-with-current-continuation
   (lambda (exit)
     (with-exception-handler
      (lambda (e)
        (exit (error-object-message e)))
      (lambda ()
        (eval expr env))))))

;; trigger error
(display (evaluate 'xxx (interaction-environment)))
(newline)

I've got

  • Guile message Unbound variable: ~S how to get actual error message and not a template?
  • Kawa exception: Argument #1 'unbound location: xxx' to 'error-object-message' has wrong type (gnu.mapping.UnboundLocationException) (gnu.mapping.UnboundLocationException cannot be cast to kawa.lang.NamedException)
  • Gauche core dump
  • Gambit freezes

NOTE: this is part of REPL that I'm testing in all Scheme implementations that I have on my system. It almost work, it can run itself, but I would like to show proper error message when exception happen, instead of exiting the REPL.

3

There are 3 best solutions below

0
On

Gauche core dump

Oops. It's not ideal, for sure, but it can be explained.

  • By default, Gauche's with-exception-handler is SRFI-18's, not R7RS's, because of a historical reason. That means the exception handler is called in the dynamic environment where the exception occurs, including exception handler settings. If an exception occurs within the exception handler, the same exception handler is invoked, causing unbounded recursion. Apparently Gauche's runtime eats up C stack or something.
  • error-object-message is not defined in Gauche's default namespace. So that triggers an exception in the first place.

Add

(import (scheme base) (scheme write) (scheme r5rs))

at the beginning of code makes the program run in R7RS bindings. Then you'll get:

unbound variable: xxx 

Actually, your code is not a valid R7RS program (which should begin with at least one import declaration), so anything can happen, depending on the default interpretation of the non-conforming code in the implementation.

[Edit] IMHO, with-exception-handler should be considered as the lowest level construct on which easy-to-use utilities are built, and thus should be used with extra care. In general use case, guard provides a good abstraction.

0
On

The reason you get an infinite loop with Gambit is that the variable xxx is unbound so the exception handler (lambda (e) (exit (error-object-message e))) is called with an unbound-global-exception object and this causes error-object-message to be called but the parameter is not an error-object (which is specific to the exceptions raised by a call to the error procedure) so this raises a type-exception object that causes the same exception handler to be called, and so on forever.

If you want an exception handling that "pops" the current exception handler, use with-exception-catcher instead of with-exception-handler. This will avoid the infinite loop.

Converting an exception object to a string can be done in Gambit this way:

(define (exception->string exc)
  (with-output-to-string
    (lambda ()
      (display-exception exc))))

It works for error-objects and other kinds of exceptions, and also any non-exception object.

This is a more portable solution (superficially tested):

(import (scheme base)
        (scheme r5rs))

(cond-expand
  ((or lips kawa gauche)
   (define (exception->string exc)
     (error-object-message exc)))
  (gambit
   (define (exception->string exc)
     (with-output-to-string
       (lambda ()
         (display-exception exc)))))
  (guile
   (import (rnrs base)
           (rnrs exceptions)
           (rnrs conditions))
   (define (exception->string exc)
     (condition-message exc))))

(define (evaluate expr env)
  (call-with-current-continuation
   (lambda (exit)
     (with-exception-handler
      (lambda (e)
        (exit (exception->string e)))
      (lambda ()
        (eval expr env))))))

(display (evaluate 'xxx (interaction-environment)))
(newline)
0
On

For Kawa:

   (define (exception->string exc)
     (call-with-port (open-output-string)
                     (lambda (port)
                       (display exc port)
                       (get-output-string port)))))

This will convert exception to string and get error message