Author Topic: Universal Error Function  (Read 15102 times)

0 Members and 1 Guest are viewing this topic.

GDF

  • Water Moccasin
  • Posts: 2081
Re: Universal Error Function
« Reply #15 on: February 07, 2006, 10:35:22 AM »
Luis

I have tried that site in the past....broken links to the lessons.
Why is there never enough time to do it right, but always enough time to do it over?
BricsCAD 2020x64 Windows 10x64

LE

  • Guest
Re: Universal Error Function
« Reply #16 on: February 07, 2006, 10:44:45 AM »
Luis

I have tried that site in the past....broken links to the lessons.

I see, I saw them before.... though they were still in there.... my bad - [I will get rid of my previous post, is not valid then....]

JohnK

  • Administrator
  • Seagull
  • Posts: 10646
Re: Universal Error Function
« Reply #17 on: February 07, 2006, 10:45:13 AM »
There has been many of 'these' over the years. Although, I think my all time  favorite would be one by Vladimir Nesterovsky.
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Universal Error Function
« Reply #18 on: February 07, 2006, 11:03:34 AM »
From the AU 2003 handouts listed on AUGI

There’s an Error in My Code! Now What?
Instructor:  R. Robert Bell

http://www.augi.com/education/auhandouts/2003/CP12-3.zip

kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Universal Error Function
« Reply #19 on: February 08, 2006, 04:27:56 AM »
Luis

I have tried that site in the past....broken links to the lessons.

I've sent a note to Bobby C Jones .. so keep the link handy .. perhaps ..

http://www.acadx.com/articles/008.htm
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Jeff_M

  • King Gator
  • Posts: 4096
  • C3D user & customizer
Re: Universal Error Function
« Reply #20 on: February 08, 2006, 09:23:39 AM »
I sent an email to Frank O. about the broken links.....it was returned due to "Mailbox Full"

dan19936

  • Guest
Re: Universal Error Function
« Reply #21 on: February 09, 2006, 04:24:03 PM »
I scavanged those pdfs a while back, attached (I hope).

Dan

I sent an email to Frank O. about the broken links.....it was returned due to "Mailbox Full"

Bobby C. Jones

  • Swamp Rat
  • Posts: 516
  • Cry havoc and let loose the dogs of war.
Re: Universal Error Function
« Reply #22 on: February 10, 2006, 02:20:01 PM »
I scavanged those pdfs a while back, attached (I hope).

I just got the files from Bob, but it looks like you beat me to it :-)  I hope this helps!
Bobby C. Jones

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Universal Error Function
« Reply #23 on: February 28, 2006, 05:17:09 PM »
I scavanged those pdfs a while back, attached (I hope).

Dan

I sent an email to Frank O. about the broken links.....it was returned due to "Mailbox Full"

Thanks Dan. Great !

and thanks Bobby for sanctioning the attachment .. not that there would be any doubt :)
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Bobby C. Jones

  • Swamp Rat
  • Posts: 516
  • Cry havoc and let loose the dogs of war.
Re: Universal Error Function
« Reply #24 on: March 02, 2006, 01:53:35 PM »
and thanks Bobby for sanctioning the attachment .. not that there would be any doubt :)

Glad to help out.  And just to add to an already long list of posts, which I regretfully have not had a chance to read, here is something that I wrote many moons ago on this topic.  This covers the error function that I use in my lisp code.  Yes, I have lots of LISP code :-)

Quote
First we should have a short review of what an AutoLISP error handler is, what it does, and how it works.  An error handler is simply a function that is called when AutoCAD detects an error in a lisp routine.  When the error is detected, execution of the lisp routine halts and the error function is automatically called with an error message argument.  This message will vary depending on what caused the error.  For example; dividing a number by zero, (/ 2 0), will call the error handler with the message "divide by zero", (+ 1 "two") will return the message "bad argument type: numberp: "two"".  VisualLISP has a built-in error handler that will run in the event that we have not defined our own.  It does nothing more than exit cleanly from the running routine and report the error message to the command line.  It is also important to note that, starting with AutoCAD 2000 and beyond, all variables declared as local to a function will be cleared in the event of an error.  In the example below, the variables A, B, & C would all be set to nil without a custom error handler doing anything.

(defun myfunc (/ A B C)
  (setq A "A"
           B "B"
           C "C"
  )
  ;;Force an error
  (/ 2 0)
)

Note:  The most common error in a lisp routine is caused when the user hits the Escape key to exit the command.

Since variables are now automatically released, why do we need to bother with error handlers at all?   The program exits fairly cleanly to the command line, and cleans up the local variables.  That's a good start, but what about when a routine changes a system variable?  You do not want your program changing a users’ running object snaps just because it crashed.  Imagine how annoying and frustrating that is to the user.  Your routines should always reset the environment back to the way it was before it started, whether it exits normally or must handle an unusual event.  This is the main reason for implementing custom error handlers.  A custom error handler can also do things such as erase partially drawn objects that a lisp routine was drawing when a user hit the escape key.  It can be complex or simple.  Please note that custom error handlers are lisp routines themselves and should be kept as short and quick running as possible.  You don't want an error handler running long enough for a user to hit <escape> while it is running.  This will cause an error in the error handler and will keep the error handler from completing.  An error in a custom error handler will execute the default error handler, not recursively call itself.

Now that we know what an error handler is and what it's supposed to do, let's look at some code. This code is based upon functionality in AutoCAD 2000, or greater.

;;;example function #1
(defun myFunc (/ *error* blp osm)

  ;;Custom error handler
  (defun *error* (msg)
    (if (not (member
                 msg
                 '(nil "console break" "Function cancelled" "quit / exit abort")
                )
          )
      (princ (strcat "\nError: " msg))
    )

  (setvar "blipmode" blp)
  (setvar "osmode" omd)
  (princ)
  )
  (setq blp(getvar "blipmode")
          osm (getvar "osmode")
  )
  (setvar "blipmode" 0)
  (setvar "osmode" 0)
  ;;User interface functions
  (get some info from the user)
  ;;Core functions
  (draw some stuff with that info)
  (*error* nil)
)

  The first thing that some of you “old timers” may notice is that we no longer must go thru the gyrations of saving the old error handler to a variable and setting the error handler to our custom error handler.  That is accomplished by defining the *error* function in the scope of our routine by and declaring it as local to our routine.  Next you'll notice that, instead of resetting all of our system variables at the end of our routine, we are calling our error routine with a nil message argument to perform the reset for us.  This isn't really new, per say, but is most likely a new way of thinking for many.  This method keeps us from having to type in (setvar "cmdecho" cmd) etc. ad  nauseum, once at the end of a routine and again in the error handler.  If your routine changes several variables then that can be quite a bit of typing and prone to omissions.  This method works great and will make your routines have a professional look and feel to the end user.  Let’s step through the myFunc example function #1 above and determine what happens in the event of an error.

First, notice that we’ve defined the *error* function inside of the myFunc function and declared *error* as a local variable in our routine.  This way we will not interfere with any global custom *error* handler that may be running on the user’s system.  Next, we save the current value of some system variables and then change them to values that make sense in our routine.  At the end of the routine we manually call our *error* function with a nil argument.  The error function contains all the necessary code to reset our system variables back to their original settings and exit cleanly from the routine.  This is an outline for a simple error handler.  Nothing more elaborate is necessary; however I do suggest that you add a little cinnamon to that scoop of vanilla and spice up your error handler.

Listed below are several functions that make up my custom error handling routine.  These functions make the error handler “dynamic”.  This means that you can change what the error handler does in the middle of your routines. The original ideas that this code are based on come from an error handling system described in the book “Maximizing AutoLISP R12”, by Rusty Gesner & Joe Smith.

;;;Initialize custom *error* handler
(defun initerr ()
  ;;Function for adding application specific
  ;;actions to the *error* handler
  (defun-q reset () (setvar "menuecho" 1) (setvar "cmdecho" 1) (princ))
  ;;main error routine, redefines standard *error* function
  (defun *error* (msg)
    (setvar "cmdecho" 0)
    (if
      (not (member
             msg
             '("console break" "Function cancelled" "quit / exit abort")
           )
      )
       (progn
         (princ (strcat "\nError #" (itoa (getvar "errno")) ": " msg))
         (setvar "errno" 0)
       )
    )
    ;;cancel any active commands
    (while (< 0 (getvar "cmdactive"))
      (command)
    )
    (reset)
  )
)

;;;adds an item to the error list
;;;item must be a quoted expression
;;;example: (adderr '(command "erase" "last" ""))
;;;or in the form (adderr (list 'setvar "osmode" (getvar "osmode")))
(defun adderr (item)
  (if (not (member item reset))
    (setq reset (append '(nil) (list item) (cdr reset)))
  )
)

;;;deletes an item from the error list
;;;item is same format as adderr
;;;example (delerr '(command "erase" "last" ""))
(defun delerr (item)
  (setq reset (subst '() item reset))
)

;;;Adds a list of variables to be saved
;;;to the error list
;;;example (VarSave '(("highlight" . 0)
;;;                   "osmode")
;;;        )
;;;vars in the list with an included value
;;;will be changed to that value
(defun VarSave (varLst / varName)
  (foreach var varLst
    (setq varName (if (listp var) (car var) var))
    (adderr (list 'setvar varName (getvar varName)))
    (if (listp var)
      (setvar VarName (cdr var))
    )
  )
)

"How do you use these things?" you ask.  Let's look at the same example from above utilizing this code.

;;;example function #2
(defun myFunc (/ *error* A B C)
  ;;Initialize custom error handler
  (initerr)
  (varSave ‘((“blipmode . 1) (“osmode” . 0)))
  (get some info from the user)
  (draw some stuff with that info)
  (reset)
)

This function accomplishes the exact same thing as the first example in this article in a slightly different manner and with much less typing on your end.  Let's take a closer look at each individual function.

(initerr)
This function initializes the basic custom error handler by 1) defining (*error*) and 2) defining the (reset) function.  We know what the *error* function is, but what is this other (reset) function and why is it defined with that defun-q?

(reset)
This is the dynamic function that we can add to and delete from as our routines dictate, using either (varSave…), (adder…), (delerr…), or any other method available to us, to manipulate a list structure.  Before VisualLisp there was very little difference between our functions and our data.  A function defined with DEFUN was really just a glorified list.  We could add to and modify it with the same functions that we used to modify lists.  Now with the advent of VisualLisp, this is only possible if we define a function with (DEFUN-Q).  Therefore, in order for us to make our (reset) function dynamic, we must define it with (DEFUN-Q).  We'll demonstrate this technique in a minute.

(varSave)
This function helps to keep us from get carpel tunnel syndrome by reducing the amount of typing we have to do.  We simply provide a list of system variables, and the values that we want to temporarily switch them to, and the current values are saved and restored when the program completes or in the event of an error.  Look at these four lines from example #1 above:

  (setq blp(getvar "blipmode")
          osm (getvar "osmode")
  )
  (setvar "blipmode" 0)
  (setvar "osmode" 0)

All four of these lines have been replaced with this one simple call:
(varSave ‘((“blipmode . 1) (“osmode” . 0)))

(adderr)
This is the function that actually puts info into the (reset) function on the fly.

(delerr)
This is the antithesis of the (adderr) function.  It will remove items from the (reset) function.

So how does all this code work?   Look at the second example above.  We start by declaring *error* as local to each function that we are adding our error handler to.  Then, (initerr) is called to define the custom *error* handler.  When it is run, (initerr) defines the initial (reset) function which, when run, will execute this code:

  (setvar "menuecho" 1)
  (setvar "cmdecho" 1)
  (princ)

Next, we call (varSave) with our list of system variables and the values to change them to.  (varSave) is simply a front end to the (adderr) function.  It doesn’t really do anything more than call (adderr) with each provided system variable and then calls (setvar) with each corresponding value.  The first system variable in our list is “blipmode”.  (varSave) calls (adderr) which adds the code to (reset) to set the blipmode system variable to it's current setting and sets the current value to 0.  Running the (reset) function would now execute this code:

  (setvar "blipmode" 1)
  (setvar "menuecho 1)
  (setvar "cmdecho" 1)
  (princ)

and after (varSave) makes the second call to (adderr), if the original osmode system variable setting was 35:

  (setvar "osmode" 35)
  (setvar "blipmode" 1)
  (setvar "menuecho 1)
  (setvar "cmdecho" 1)
  (princ)

Now if an error occurs in our routine, the (*error*) function is called. It prints, or suppresses, the error message depending on what the error is, and then executes the (reset) function.  In fact, the (reset) function is called whether our code completes normally, or is terminated with an error.  This means that all of our system variables will be set back to their original settings in any event.  As far as the end user is concerned, the first code example runs exactly the same way as the second code example.  The difference to you, as the developer, is that you eliminated setting some variables, you are now able to dynamically add expressions to your error handler, and you did it all with much less typing.

Let’s look at another example of adding items dynamically to the error handler.  Let's say that we are collecting points from the user and interactively drawing a polyline between those points.  Now let us say that in the middle of this routine the user has selected several points and a pline was been drawn between those points, but he changes his mind and hits the escape key to terminate the custom command.  Normally this would leave the partial pline in the drawing and the user would have to go back and erase it.  But if just prior to collecting the points we add this to our routine:

  (adderr '(command "_.U"))

Now our (reset) function would execute this:
  (command "_.U")
  (setvar "osmode" 35)
  (setvar "blipmode" 1)
  (setvar "menuecho 1)
  (setvar "cmdecho" 1)
  (princ)

This would undo the partial pline, freeing the user from having to go back and erase it.  Very 'professional'.  I know you're thinking, "But what about when the routine ends? It will undo the pline then also!!".  To fix that we simply call the (delerr) function after we are sure that the user is finished selecting points.

  (delerr '(command "_.U"))

Now our (reset) function is set back to:

  (setvar "osmode" 35)
  (setvar "blipmode" 1)
  (setvar "menuecho 1)
  (setvar "cmdecho" 1)
  (princ)

Bobby C. Jones

GDF

  • Water Moccasin
  • Posts: 2081
Re: Universal Error Function
« Reply #25 on: March 02, 2006, 02:29:52 PM »
Thanks Bobby, I will read it.

Gary
Why is there never enough time to do it right, but always enough time to do it over?
BricsCAD 2020x64 Windows 10x64

CAB

  • Global Moderator
  • Seagull
  • Posts: 10401
Re: Universal Error Function
« Reply #26 on: March 02, 2006, 11:43:16 PM »
I just did, read it that is.
Well worth the time it took to read it.
Nice job.
I've reached the age where the happy hour is a nap. (°¿°)
Windows 10 core i7 4790k 4Ghz 32GB GTX 970
Please support this web site.

ElpanovEvgeniy

  • Water Moccasin
  • Posts: 1569
  • Moscow (Russia)
Re: Universal Error Function
« Reply #27 on: March 03, 2006, 08:51:10 AM »
In the beginning of function I establish a list of the necessary environment variables,
list variable always miscellaneous:
Code: [Select]
(SETQ
  ERROR-LST-
             '("AUTOSNAP" "OSMODE" "APERTURE" "HPSPACE" "HPASSOC" "MIRRTEXT" "AUPREC" "LUPREC"
               "DIMZIN" "cecolor"
              )
  ERROR-LST- (mapcar (function (lambda (a) (list 'setvar a (getvar a)))) ERROR-LST-)
) ;_  SETQ
Function *error*
Code: [Select]
(defun *error* (msg) (MAPCAR 'eval ERROR-LST-))
It is a universal *error* function :-)

whdjr

  • Guest
Re: Universal Error Function
« Reply #28 on: March 03, 2006, 10:13:08 AM »
........, but what about when a routine changes a system variable?  You do not want your program changing a users’ running object snaps just because it crashed.  Imagine how annoying and frustrating that is to the user.  Your routines should always reset the environment back to the way it was before it started, whether it exits normally or must handle an unusual event.  This is the main reason for implementing custom error handlers.  A custom error handler can also do things such as erase partially drawn objects that a lisp routine was drawing when a user hit the escape key.  It can be complex or simple........

.........An error in a custom error handler will execute the default error handler........

..........First, notice that we’ve defined the *error* function inside of the myFunc function and declared *error* as a local variable in our routine.  This way we will not interfere with any global custom *error* handler that may be running on the user’s system......

..........At the end of the routine we manually call our *error* function with a nil argument.  The error function contains all the necessary code to reset our system variables back to their original settings and exit cleanly from the routine.  This is an outline for a simple error handler.  Nothing more elaborate is necessary; however I do suggest that you add a little cinnamon to that scoop of vanilla and spice up your error handler.

Bobby,
After we run our error handler would it be wise to force an error in the last line of our error handler so as to call the global error handler in case the user has modified the global error handler to his liking?

JohnK

  • Administrator
  • Seagull
  • Posts: 10646
Re: Universal Error Function
« Reply #29 on: March 03, 2006, 10:17:23 AM »
In the beginning of function I establish a list of the necessary environment variables,
list variable always miscellaneous:
<snip>
It is a universal *error* function :-)

Unbelievable!
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org