stylewarning / quickutil

The solution to the Utility Library problem.
87 stars 8 forks source link

HANDLER-CASE* - HANDLER-BIND and HANDLER-CASE rolled into one. #56

Open heegaiximephoomeeghahyaiseekh opened 8 years ago

heegaiximephoomeeghahyaiseekh commented 8 years ago

;; Write the code here.

(defun expand-handler-bind (type lambda-list &key before-unwind after-unwind)
    "Helper for the HANDLER-CASE* macro. This one creates the HANDLER-BIND lambdas out of the :BEFORE-UNWIND form."
    (declare (ignorable after-unwind))
    (when before-unwind
      (when (null lambda-list)
        (error ":BEFORE-UNWIND must bind the condtion to a variable."))
      `(,type (lambda ,lambda-list (declare (ignorable ,@lambda-list)) ,before-unwind))))

(defun expand-handler-case (type lambda-list &key before-unwind after-unwind)
  "Helper for the HANDLER-CASE* macro. This one creates the HANDLER-CASE handler-clauses out of the :AFTER-UNWIND form"
  (declare (ignorable before-unwind))
  (if after-unwind
      `(,type ,lambda-list (declare (ignorable ,@lambda-list)) ,after-unwind))))

(defmacro handler-case* (form &rest cases)
  "Like HANDLER-CASE and HANDLER-BIND rolled into one. Example usage:

      (handler-case*
            (restart-case
                 (error \"ZOMG! ERROR!\")
               (fuck-it () 'ignored))
         (t (condition)
            :before-unwind
               (progn (format t \"An error occurred Ignore it (y/N)? \")
                      (if (eq (read) 'y)
                          (invoke-restart 'fuck-it)))
            :after-unwind
               (format t \"You just couldn't fucking ignore it, could you?~%\")))

:before-unwind is, of course, executed before the stack unrolls, so you can
invoke restarts from there. If no restart is invoked, the error falls through
to the :after-unwind case, where you can handle it like a regular handler-case.

The above paragraph is not 100% accurate: If the :BEFORE-UNWIND case does not
invoke a restart, another HANDLER-CASE or HANDLER-CASE* form may get control
of the error *before* the :AFTER-UNWIND case:

    (defun handles-own-error ()
        (handler-case
            (error \"ZOMG! ERROR!\")
         (t () :i-got-this)))

      (handler-case*
            (restart-case
                 (handles-own-error)
               (fuck-it () 'ignored))
         (t (condition)
            :before-unwind
               (progn (format t \"An error occurred Ignore it (y/N)? \")
                      (if (eq (read) 'y)
                          (invoke-restart 'fuck-it)))
            :after-unwind
               (format t \"You just couldn't fucking ignore it, could you?~%\")))

In this case, first the :BEFORE-UNWIND case is evaluated, and then the error is given
to the HANDLER-CASE form *inside* HANDLES-OWN-ERROR. The :AFTER-UNWIND case is never
evaluated.

If no :after-unwind form is provided and no restart is invoked, the condition is not trapped."
  `(handler-case
       (handler-bind ,(remove nil (mapply #'expand-handler-bind cases))
         ,form)
     ,@(remove nil (mapply #'expand-handler-case cases))))

Provides: HANDLER-CASE* Requires: MAPPLY Author: Jeremy Phelps phelpsj@nuvox.net License: Public Domain