************************************************************************* ***** *** *** The prompter and protected-eval module consists of the...

advertisement
*************************************************************************
*****
***
*** The prompter and protected-eval module consists of the following
files:
***
error.lisp
garnet-errors.lisp
kr-doc.lisp
new-protected-eval-loader.lisp
new-protected-eval.lisp
prompter-compiler.lisp
loader.lisp
prompter-loader.lisp
prompter.doc
prompter.lisp
protected-eval-loader.lisp
protected-eval.lisp
protected-process.lisp
scrolling-unlabeled-boxscrolling-unlabeled-box.lisp
*************************************************************************
*****
Date: Thu, 25 Jun 92 12:55:51 PDT
From: "Russell G. Almond" <almond@statsci.com>
To: garnet-users@cs.cmu.edu
Subject: Error Handling
Status: R
I just sent some code off to CMU (which I hope should appear shortly
in the contrib library) which uses Garnet gadgets to do lisp error
handling.
The problem is as follows: Suppose you have an application in which
you wish to allow the user to read and eval an arbitrary lisp
expression, and that expression generates an error. Who should handle
the error? Throwing the user into the Lisp debugger, which is the
default behavior is almost certainly unacceptable for a user
application. Therefore, it is necessary to create contexts in which
expressions can be evaled or read (remember reading can cause an
error) and in which the error is handled by some convenient mechanism.
Ignore-errors, the default common lisp mechanism is a little bit
blunt. You probably at least want to report the error, and if the
application is written with a garnet user interface, this should be
done in the garnet style: I.e., pop up an error window with an error
message.
The way to do this (without getting into horrible messy details of the
Common Lisp Condition system, which are far too likely to change
anyway) is to create a specific garnet error handler and evaluate in a
context where that error handler works.
In the contributed software, I've created two error handlers:
garnet-error-handler and garnet-user-error-handler. The garnet error
handler, puts up a gadget with two or three buttons "Abort", "Debug"
and "Continue." "Abort" returns the user to a restart named abort
(more about that later), "Debug" throws the user into the debugger.
The "Continue" button only appears if the error is continuable, and
continues from the error. The garnet-user-error-handler is the same
except it doesn't allow the "Debug" option. Thus user level code can
generate errors with the normal "error" and "cerror" commands and
garnet will handle them in an approximately reasonable way.
About restarts (in particular abort). If the user wants to abandon
the computation, the program needs to know where to return to. (This
is usually the routine in which the protected evaluation first
started, but the program needs to be told.) The way to do this is to
set up a restart named "abort" in the appropriate placed, which
performs the appropriate abort clean-up, usually to return from a given
selection-function without performing the action. If there is no
restart named "abort" the error handler tries to throw the user back
to the lisp-top-level, or restart the garnet-event-loop. This is
handled through a routine called do-abort. As I've only tested this
on Allegro CL 4.1, other users of other Lisps are going to need to
hack that function to make it behave propertly.
I've provided an number of functions and macros in the distribution to
try and hide the details of the Condition System. I've currently put
all of the stuff in the garnet-gadget package, but that may be changed
by the library maintainers.
PROTECTED-EVAL-ERROR-GADGET [gg:query-gadget]
--- special error gadget which displays the lisp errors.
garnet-error-handler (context condition) [Function]
garnet-user-error-handler (context condition) [Function]
--- These are the error handlers and are meant to be used with
handler-bind or some similar mechanism. Note that handlers
usually take one arg, and that these take an extra arg, the
<context>. <context> is meant to be a string describing the a
user meaningful context in which the error happened:
(handler-bind ((frobbing-error #,(lambda (condition)
(garnet-error-handler "Frobnacating"
condition))))
(frob widget))
with-garnet-error-handling (context &body forms) [Macro]
with-garnet-user-error-handling (context &body forms) [Macro]
--- These macros execute forms which binding the error handler to the
appropriate garnet error handler. <context> should be a litteral
string describing the context of the evaluation in a what
meaningful to the user. Try:
(with-garnet-error-handling "Running" (/ 0 0))
with-abort (&body forms) [Macro]
--- This macro creates an abort restart which will abandon the
computation of forms and retrun the two values nil and :abort.
It is thus a simple way of setting up an abort restart.
The following two functions are probably the most useful. They are
meant to eval and read user expressions. Thus you can use them to set
up a protected read-eval-print loop.
garnet-protected-eval (arg &key (default-value nil) (allow-debug t)
(local-abort nil) (abort-val nil))
[Function]
--- This is a function which behaves very much like eval, except that
while it evaluates its arg, the error handler is bound to the
appropriate garnet error handler (with context "Evaluating
<arg>"). Note that as garnet-protected-eval is a function <arg>
is evaluated twice, once during the function call (and hence
outside of the scope of the garnet-error-handler) and once in the
scope of the function (and hence in the context of the
garnet-error-handler). This is usually what you want (i.e.,
first eval fetches the expression from the gadget, the second
evaluates it.)
:default-value (if supplied) allows a "continue" which returns
this value.
:allow-debug (default t) if nil uses the
garnet-user-error-handler instead (no "Debug")
:local-abort (if t) sets up a local abort. the local abort
return two values <abort-val> (default nil) and :abort.
garnet-protected-read-from-string (string &key start end read-package
read-bindings
default-value
allow-debug local-abort
abort-val)
[Function]
--- Like read-from-string (without the optional args) except read is
done in a context where the garnet user error handler is active.
:start and :end allow substring selection.
:read-package (default (find-package :user)) controls the
package newly read symbols are interred in. NOTE: the default
is (find-package :user), not *package*. This is because with
multiprocessing and whatnot, I don't want to make any rash
assumptions about the binding of *package*, thus I do it
explicitly.
:read-bindings (default nil) This expression should be a list
of the type ((<var1> <exp1>) (<var2> <exp2>)) like the variable
bindings in a let statement. These are bindings are put in
place while the read is being done. This allows you to bind
readtable or some other special variable during the course of
the read.
:default-value (default nil) As for garnet-protected-eval,
except the continue, return nil (or other value) is always
present.
:allow-debug (default nil) As before, except defaults to nil
:local-abort (default nil), :abort-val (default nil) as before.
There are a couple of known bugs with this system, which I hope
somebody will be able to help me out with:
(1) Sometimes, I get an error:
"Attempt to throw to the non-existent tag
INTERACTORS::EXIT-WAIT-INTERACTION-COMPLETE"
This is a problem somewhere in the Garnet internals that I don't fully
understand.
(2) The gadget is supposed to run at an extra high recrusive
error-priority. This doesn't seem to work. This means the gadget
will not lock out other gadgets running at the error priority level.
(3) If the first time the gadget is draw, it doesn't have a
"continue" button, the buttons will be two narrow. Functionality
works, but it is esthetically un-appealing.
(4) Lisps are not guarenteed to have an "abort" restart at the top
level. What happens if you press <abort> and there is no abort
restart is system dependent. I've added a function in do-abort which
handles the situaltion gracefully in Allegro CL 4.1. Users of other
CL's will need to figure this out for themselves.
Needless to say, this code requires a fair amount of complience to the
draft standard on the condition system. It will thus probably not work
in
a lot of older lisps.
Good Luck, and let me know what you think.
Russell Almond
Statistical Sciences, Inc.
1700 Westlake Ave., N Suite 500
Seattle, WA 98109
(206) 283-8802
almond@statsci.com
U. Washington
Statistics, GN-22
Seattle, WA 98195
almond@stat.washington.edu
Date: Thu, 25 Jun 92 14:03:30 PDT
From: "Russell G. Almond" <almond@statsci.com>
To: garnet-users@CS.CMU.EDU
Subject: Prompter Gadget
Status: R
As a sort of an application of the error handling stuff (in fact, why
I got into it in the first place), I've created and just submitted to
the contrib library a prompter gadget which works sort of like the
existing query gadget, except that it reads an arbitrary lisp
expression as its response, and it even allows the user to eval the
expression first.
There are three files that go with the prompter stuff:
protected-eval.lisp --- (See my previous message) this does the error
handling inside the prompter gadget
scrolling-unlabeled-box.lisp --- The obvious hack of
scrolling-labeled-box to remove the label.
prompter.lisp --- The prompter stuff.
It defines a prompter-gadget (which is a kind of query-gadget).
operates the same way, except:
It
(1) the appropriate functions are display-prompt and
display-prompt-and-wait
(2) display-prompt-and-wait returns two values: the value typed in
and the button pressed to end the dialog (should be checked to see if
it is :cancel or :abort)
(3) The :selection-function takes three args, the gadget, the value
read and the button pressed.
There are a number of customizeable slots to control both the obvious
garnet things and special features such as the "EVAL" button and the
binding of *package* and other reader variables during the read.
I'm currently having a problem with it generating:
WARNING: Interaction-Complete called but not inside Wait-InteractionComplete
error. (If you generate an error during the execution of this dialog
in the modal mode, it should put two wait-interaction-complete
requests on the stack, but my LISP only seems to see one). If anybody
can figure out what is going on here, I would be grateful.
Thanks,
Russell Almond
Statistical Sciences, Inc.
1700 Westlake Ave., N Suite 500
Seattle, WA 98109
(206) 283-8802
almond@statsci.com
U. Washington
Statistics, GN-22
Seattle, WA 98195
almond@stat.washington.edu
Date: Thu, 1 Dec 94 13:01 PST
From: "Russell G. Almond" <almond@statsci.com>
To: garnet-bugs@cs.cmu.edu
Subject: New prompter stuff
Status: RO
I've made some changes to the prompter stuff to adapt it to Garnet
3.0a (mostly these are just correcting in-package statements). I've
also added a new file protected-process which is a hack to the main
event loop to make all garnet interactions protected.
I'm not sure that the prompter-compiler file works properly. It
should work alright if you are in garnet-prepare-compile mode or if
you have already compiled them once, but otherwise it fails.
I'll send them as following files.
--Russell
Date: Thu, 1 Dec 94 13:31 PST
From: "Russell G. Almond" <almond@statsci.com>
To: garnet-users@cs.cmu.edu
Subject: Error handling in the Main Event Loop
Status: RO
For a long time, I've been bothered by the issue of how to avoid
throwing the user into the Lisp debugger from a Garnet UI. Presuming
that the UI is complete, the user need never know that the system was
built in Lisp.
To that end, I built the protected-eval code (which is in
contrib/prompter in the 3.0 alpha release) and produced some
protected-evaluation macros which would execute expressions in an
environment where Lisp would use a graphic pop-up error handler
instead of throwing the user rudely into the Lisp debugger.
This didn't work very consistently, because very often the errors
would occur when Garnet was trying to redraw a section of window which
was somehow incorrect. Thus, sometimes I'd get thrown into the
debugger anyway.
The solution is to put the (gg:with-protected-errors) call just around
the main event loop. I've done this for Allegro CL 4.2, and will
put the code in the file contrib/prompter/protected-process.lisp for
the 3.0 final distribution. If anybody wants to try hacking the main
event loop for other Lisp versions, please do. I don't have access to
them for testing.
Russell Almond
StatSci (a division of MathSoft)
1700 Westlake Ave., N Suite 500, Seattle, WA 98109
(206) 283-8802 x234 FAX: (206) 283-6310 Email: almond@statsci.com
Download