Search code examples
lispcommon-lisprestartcondition-system

In common lisp how do you restart where the error was thrown not caught?


This question is really about my lack of understanding of restarts.

In the encoder for cl-json there exists a tempting macro I would like to use

with-substitute-printed-representation-restart

But alas I do not quite understand how.

This

(cl-json::encode-json-plist (list :boo "boo" :foo "foo"))

returns

{"boo":"boo","foo":"foo"}

This

(cl-json::encode-json-plist (list :boo "boo" :foo  (lambda (a b) (+ a b))))

signals an UNENCODABLE-VALUE-ERROR

I would like to restart from that point where cl-json finds the function and have it return something of my choosing when it runs into that adding lambda I included in the list.

(defun my-func ()
 (handler-bind ((cl-json::UNENCODABLE-VALUE-ERROR 
                #'(lambda (e) (invoke-restart 'return-default))))
   (myencode (list :boo "boo" :foo (lambda (a b) (+ a b))))
 )
)

(defun myencode (alist)
 (restart-case
  (cl-json::encode-json-plist-to-string alist)
  (return-default () :report "Just return a default could not do this string" "barf")
 )
)

returns "barf"

I want it to return

{"boo":"boo","foo":"barf"}

How do I use that macro do to this? In other words I want the restart to happen where the error was thrown not where the error was caught. Can I do that?


Solution

  • I don't understand if the doc is wrong or if I am reading the code badly, but there should already be a restart available whenever an object cannot be encoded. If you redefined cl-json default method for encode-json as follows, then you have a restart.

    (defmethod encode-json (anything &optional (stream *json-output*))
      "If OBJECT is not handled by any specialized encoder signal an error
    which the user can correct by choosing to encode the string which is
    the printed representation of the OBJECT."
      (with-substitute-printed-representation-restart (anything stream)
        (unencodable-value-error anything 'encode-json)))
    

    By the way you could redefine so that the restart accepts an argument, the string to print instead:

    (defmethod encode-json (anything &optional (stream *json-output*))
      "If OBJECT is not handled by any specialized encoder signal an error
    which the user can correct by choosing to encode the string which is
    the printed representation of the OBJECT."
      (with-substitute-printed-representation-restart (anything stream)
        (restart-case (unencodable-value-error anything 'encode-json)
          (use-value (v)
            :report "Use a different encoding"
            (check-type v string)
            (write v :stream stream :escape t)))))
    

    For example:

    CL-USER> (handler-bind
                    ((json:unencodable-value-error
                      (lambda (err)
                        (declare (ignore err))
                        (invoke-restart 'use-value "UNKNOWN"))))
                  (json:encode-json
                   `(((foo . ,#'print) (bar . "baz")))))
    [{"foo":"UNKNOWN","bar":"baz"}]
    

    You may want to ask directly the author of the library