[Openmcl-devel] documentation from the string to the error print

Pascal J. Bourguignon pjb at informatimago.com
Tue Jun 21 16:16:31 PDT 2011


Janusz Podrazik <info at mracpublishing.com> writes:

> What I am trying to do is to include function documentation from the
> string to the error print.
>
> (defMacro do-quietly (name &body body)      ; with name
>   `(let ((temp diagnose-verbose)
>          (return-value nil))
>      (handler-case 
>        (progn
>          (setq diagnose-verbose nil)
>          (setq return-value (progn , at body))
>          (setq diagnose-verbose temp)
>          return-value)
>        (error (c)
>               (princ "ERROR! Check Arguments.")
>               (princ (documentation name 'function))
>               (print c)
>               (setq diagnose-verbose temp)
>               (abort)))))
>
> (defun collect-sum (n)
>  "COLLECT-SUM (numbers)
>   Returns the sum of the elements in a series of numbers.
>   Examples:
>   (collect-sum '(1 2 3 4)
>   (collect-sum '(1 2 3 -4))"
>   (do-quietly 'collect-sum       ;function name
>    (reduce #'+ n)))
>
> 4 > ERROR! Check Arguments.
>> Error: Unbound variable: name
>> While executing: collect-sum, in process Listener(13).
>> Type cmd-. to abort, cmd-\ for a list of available restarts.
>> Type :? for other options.
>
> whats wrong?

You wrote a macro that generates code that makes reference to a variable
named NAME, while no such variable exist.

The parameters of the macros are known at compilation time only, when
macro expansion occurs.  At run time, the macro doesn't exist anymore,
and neither do its parameters.

So you need to insert the name of the function in the code generated:

    `... (princ (documentation ',name 'function)) ...

(Remember some function names are lists, so you must quote it for those
cases).


Now, your macro has a lot of problems:

    (defmacro do-quietly (name &body body)
      `(let ((temp diagnose-verbose)
             (return-value nil))
         (handler-case 
             (progn
               (setq diagnose-verbose nil)
               (setq return-value (progn , at body))
               (setq diagnose-verbose temp)
               return-value)
           (error (c)
             (princ "ERROR! Check Arguments.")
             (princ (documentation ',name 'function))
             (print c)
             (setq diagnose-verbose temp)
             (abort)))))

What's this free variable diagnose-verbose?
If that's a special variable, it should be named *diagnose-verbose*.
In that case, you can just use a dynamic binding to shadow its value:

    (defmacro do-quietly (name &body body)
      `(let ((return-value nil))
         (handler-case 
             (let ((*diagnose-verbose* nil))
               (setq return-value (progn , at body))
               return-value)
           (error (c)
             (princ "ERROR! Check Arguments.")
             (princ (documentation ',name 'function))
             (print c)
             (abort)))))

Then, bodies may return several values, are you sure you want to throw
away all but the first?  In any case, you should not use SETQ, but LET:

    (defmacro do-quietly (name &body body)
      `(handler-case 
           (let ((*diagnose-verbose* nil))
             (let ((return-value (progn , at body)))
               return-value))
         (error (c)
           (princ "ERROR! Check Arguments.")
           (princ (documentation ',name 'function))
           (print c)
           (abort))))

If you wanted to return only the first value, it might be clearer to use

      (values (progn , at body)) ; values returns as many values as
                              ; arguments it is given, so we see only
                              ; one value is returned here.

or even:

      (nth-value 0 (progn , at body)) ; explicitely return just the first value.


So if I wanted to throw away the other values of the body, I'd write:

    (defmacro do-quietly (name &body body)
      `(handler-case 
           (let ((*diagnose-verbose* nil))
             (values (progn , at body)))
         (error (c)
           (princ "ERROR! Check Arguments.")
           (princ (documentation ',name 'function))
           (print c)
           (abort))))

but there's really no reason to do it in general, so let's just let body
return all its values:

    (defmacro do-quietly (name &body body)
      `(handler-case 
           (let ((*diagnose-verbose* nil))
             , at body)
         (error (c)
           (princ "ERROR! Check Arguments.")
           (princ (documentation ',name 'function))
           (print c)
           (abort))))



On the other hand, diagnose-verbose could be a symbol macro, in which
case you'd have to use #+clisp ext:letf to bind it instead of cl:let, 
ext:letf expands to something like:

    (let ((saved-value diagnose-verbose))
      (unwind-protect
           (progn (setf diagnose-verbose nil)
                  (do-something))
        (setf diagnose-verbose saved-value)))

so you'd write something similar:

    (defmacro do-quietly (name &body body)
      (let ((saved-variable (gensym "SAVED-VALUE-")))
        `(handler-case 
             (let ((,saved-variable diagnose-verbose))
               (unwind-protect
                    (progn
                      (setf diagnose-verbose nil)
                      , at body)
                 (setf diagnose-verbose ,saved-variable)))
           (error (c)
             (princ "ERROR! Check Arguments.")
             (princ (documentation ',name 'function))
             (print c)
             (abort)))))





It is better to use princ for the condition too, if you want a human
readable error message.  You could also use format:

    (defmacro do-quietly (name &body body)
      `(handler-case 
           (let ((*diagnose-verbose* nil))
             , at body)
         (error (err)
           (format t "ERROR! Check Arguments.  ~&~A~%~A~%"
                   (documentation ',name 'function) err)
           (abort))))

Finally, are you sure you want to call ABORT here?  This will probably
stop the execution of your program, since I doubt you installed an ABORT
restart.  It might be better to not call ABORT, and let the program run
other tests.  You may also want to enter the debugger to better explore
why the form failed, in the context (you'd rather use HANDLER-BIND in
this case thought).  Or if you just wanted to issue the message, you
could also re-signal the error (ERROR ERR), so that the next handler has
a chance of handling it too.


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
A bad day in () is better than a good day in {}.




More information about the Openmcl-devel mailing list