From: Donald H. Mitchell

Subject: Re: error-handling

Date: 1996-12-20 0:57

Bruce Tobin wrote:
> > I have a very basic understanding of the condition system, e.g. how to > use (ignore-errors) and (handler-case). ... > > Here's the kind of thing I want to be able to handle. Suppose > I design a dialog with one button, whose set-value-fn is > > (defun button-1-set-value-fn (x y z) > (error "Oops! Wrong button!")) > > Now I want to be able to run this dialog, trapping this and any other > errors that may be generated by functions called from the dialog's event > loop. I tried > > (ignore-errors (dialog-1)) > > but this of course did not trap the error. Obviously I don't want to > have to put error-trapping code at the beginning of every event-handling > function, but what is my alternative?
I presume you have an outer loop that calls process-pending-events (or is it single-event that doesn't block?). First, IMHO you should never call error but always define and signal your own condition types. You should also, as I show below, provide a nice way to catch system errors. Let's see if I can produce something you can easily enough follow. Let me know if it's too muddled. I'm just following our basic flow but writing extemporaneous code; so, I won't guarantee that it runs, but it can't be far wrong. (define-condition my-applications-conditions () (;any slots you want all your applications conditions to have such as (default-restart :reader default-restart :initarg :default-restart :documentation "if present, is either a symbol naming the restart, a list whose car is the restart name and cdr is the list of args, or a restart object. Of course, the code below is too lazy to handle anything but a symbol or restart object.")) (:default-initargs :default-restart nil) ;;what to say to the user (:report (lambda (condition stream) (format stream "This wonderous application signalled an abnormal <[ at condition~> and wants to jump to ~A~]." (find-restart (if (consp (default-restart condition)) (car (default-restart condition)) (default-restart condition)) condition))))) (define-condition bad-button (my-applications-conditions) (;;whatever slots make sense such as (help-context :type integer :initarg :help-context :reader help-context :documentation "The help context to pass to WinHelp to get relevant information for when this button applies.") ;;or (simple-explanation ;... ...)) (:report (lambda (condition stream) (format stream "..." ...)))) ;;even a specific condition for each button (define-condition bad-button-quux (bad-button) ...) ;;in your button action fn, you'd then signal the right type of condition (defun button-1-set-value-fn (x y z) (restart-case (signal 'bad-button-quux :simple-explanation "You may only ask for dessert after you eat your vegetables" :default-restart 's-all-right) (s-all-right () :test (lambda (condition) (typep condition 'bad-button-quux)) :report "Oh, yeah, just ignore the button press, thanks.") (just-eat-the-vegies-for-me () :test (lambda (condition) (typep condition 'bad-button-quux)) :report (lambda (stream) (format stream "Go ahead and eat ~A with ~A for me." x z)) (eat-vegies x z)))) ;;in your top loop (restart-case (loop do (handler-bind ((my-applications-conditions #'my-applications-handler) (t #'my-application-system-error-handler)) (process-pending-events) ;; anything else in my main loop )) (exit-and-farewell () :test true :report "Exit this wonderous application")) ;;given the above restart, you can define the exit menu choice as ... (invoke-restart 'exit-and-farewell)) ;;you could even define my-applications-conditions as a method ;;and specialize it for different types of conditions (defun my-applications-conditions (condition) ;;important, wrap the body w/ a handler-bind b/c you're ;;outside your top loop handler. You don't need to wrap ;;any other fn bodies with handler-bind except for ;;my-application-system-error-handler (handler-bind ((my-applications-conditions #'my-applications-handler) (t #'my-application-system-error-handler)) (let* ((restarts (restarts-with-default-first condition)) ;;actually, you should ensure that your top window ;;is shown (not minimized). Define what happens when the user ;;hits <escape>. (choice (a-single-item-list-dialog (or *my-top-window* *screen*) "August Wizarg" (format nil "~A" condition) (mapcar #'princ-to-string restarts)))) (invoke-restart (nth choice restarts))))) (defun restarts-with-default-first (condition) (let ((restarts (compute-restarts condition))) (if (default-restart condition) (cons (find (default-restart condition) restarts :key #'restart-name) (delete (default-restart condition) restarts :key #'restart-name)) restarts))) ;;my-application-system-error-handler would be similar to my-applications-conditions ;;except should know that some errors are irrecoverable such as out of stack/ ;;out of memory and you can do more harm by trying to open a dialog in those cases. ;;Of course, Franz doesn't have much of a condition hierarchy nor even well documented ;;condition strings; so, you kind of have to do partial string matching on the basis ;;of lessons learned or guesses. Another aspect of this is for errors like out of ;;disk space, you may want to provide additional advice beyond the error message. -- Donald H. Mitchell <pgh.net at dhm> Proactive Solutions, Inc. 412.835.2410 5858 Horseshoe Dr. 412.835.2411 (fax) Bethel Park, PA 15102