Error handling is often seen as: "Does that have to be done?" The code works and then it is tempting to say that we are ready. But users do other things than you have in mind. They press Esc halfway through a LISP routine. You said that they should not do that, but they have long forgotten about it. And you, by the way, too.
So when an error occurs, whether it is by pressing Esc or because the program fails, things like settings and added entities should be restored to the state they were before the program started. Actually, that is it: settings and entities.
Good error handling is important and you have to put effort into it. Fortunately, it is not a big job. And if you change variables such as object snap during programming, and you have already written the basic error handling, then it is a small effort to immediately include the object snap value in the error handling. Does someone press Esc? Then the original setting from before the routine is restored and users will not start complaining.
Error handling is an essential part of good programs.
You want to have a good understanding of how symbols behave inside and outside functions. Our blog entry on https://nedcad.nl/en/lisp-and-variables/ under header "Local and global" explains that, actually, the whole page is worth reading.
Let's see, we have error traps inside a function, WET, Write Everything Twice. We are tempted to create one universal trap as a separate function - DRY, Don't Repeat Yourself. And there is something in between too, a bit of both- WET&DRY. That does not make things easier so focus is on WET. The rest... Maybe later.
A local *error* symbol is used. That means that the symbol gets its differing value back after the function is executed. This can be achieved placing function *error* inside function c:justacommand. Considering plenty of examples on the net that simply do not use this construction, it is important to stress and understand this
It looks like this:
(defun c:justacommand ( / *error* orthomode-org) ; Our function... ; First we create our error handler as a function inside c:justacommand. (defun *error* (error-msg) (if (not (member error-msg '("Function cancelled" "quit / exit abort"))) ; If not a typical error due to escaping... (princ (strcat "\nError: " error-msg)) ; Then show the error, else show nothing in addition to standard output. ) ; End of verbose part, next we want to reset things (princ "All code here that restore settings when things go south, like next line...") (setvar 'orthomode orthomode-org) ; Restore settings like this example. (command nil nil nil) ; Before actual UNDO, we first cancel running commands that otherwise can ruin UNDO. (command "undo" "b") ; See discussion below, basically undoing everything that the function did. ) ; End of error function ; The actual routine: ; Restoring settings means first recording settings: (setq orthomode-org (getvar 'orthomode)) ; And so on... ; Then set your undo mark: (command "undo" "m") ; And then all your code, like: (setvar 'orthomode 0) (princ "\nHi") ; And at the end of the program original settings are restored: (setvar 'orthomode orthomode-org) (princ) )
When we talk about the store and restore section, there are two constructions that overlap. Naturally UNDO does undo a lot of things, except exporting, storing, and so on. However, if a command is active then UNDO can cause problems at that time. A statement (command nil nil nil) is the solution for this, this actually means pressing the esc key three times. UNDO has other peculiarities. For example, CVPORT is not reset in AutoCAD. Then there are other methods such as vla-startundomark. From a programmers perspective this looks better, but it does virtually the same. Especially the overlap between UNDO and CAD variables makes this subject look complex - which it is not. The conclusion is that you may consider using both constructions and if you do, keep the order in mind:
- The order in your routine: Store variables, change variables, set undo mark, run the code you want and finally restore variables.
- The order in your error handler: clear the sky with command nil (i.e. esc), do undo back and finally restore variables.
The way you invoke a command can be done with (command "undo" "...") or (vl-cmdf "undo" "..."). You may want to be aware that command executes straight away, where vl-cmdf first evaluates it own arguments before executing.