Screams, intermittent yells and bangs, kid's and dogs cowering in the corner. What is going on? Have I gone nuts? Have I finally cracked and lost my mind? No, it's just me trying to debug my latest program. Go on laugh, but I'll guarantee we've all done it, and we'll all do it again.
Debugging can be a real pain. "But it worked just now!" "Why did it crash?" "The coding looks perfect. I've checked it line by line 23 times." "There must be something wrong with my computer."
This tutorial will start off by showing you some of the more common AutoLisp bugs that I've come across, and then show you a couple of things to assist you in your debugging efforts. Let's start with some common bugs.
Common AutoLISP Bugs
The simplest and most common is yourself. I don't know if you are aware of the fact that most programming errors occur between the seat and the keyboard. Yes, sad but true. So, how do we fix you up? A couple of tips :
- Don't program or debug when you're tired. Walk away from it. Have a break, go shopping. You'll be amazed at how a break from a program clears the mind.
- Very rarely will it be a computer or system bug. 99.99999 times out of a hundred, it's YOU.
- The computer or any other sinister source will not change your program coding or the variable values therein. YOU DO.
The second type of bug is the syntax bug. A syntax bug is where the command is in error. For example when you've miss-spelt an AutoLisp function or command. e.g. (setq ang1 (angl pt1 pt2)) This is normally quite easy to spot and rectify as AutoLisp will not recognise the command and your program will stop.
Another VERY common syntax bug is naming variables after an AutoLisp function or command. The most common? Angle and T ( angle and t ). I must have seen these two used as variables a thousand times. Length (length) is another very common one.
The third type of bug is the logical bug. Now this little beastie has numerous faces and comes in all shapes and forms. The simplest form is when you pass incorrect information to a function.
(setq ang (angle pt1 pt2))
But what happens if pt1 is nil. Crash! Another good example is trying to pass string values to a function that expects numbers. (and vice-a-versa.) Again, Crash! Check your variable values and data types. To check if a variable is a list, you can use the (type) command, which returns a variable type. But since a list is not a string you must test it :
( if ( = ( eval v ) LIST)…
You could also use (listp) to see whether a variable is a list :
( if ( = T ( listp a ) )…
(listp) returns T if it is a list and nil if it isn't.
To test whether a variable is a number, use the same test as earlier but test for REAL or INT. You can also use (numberp), which works the same as (listp) on lists. If the variable is a real number or an integer, it returns T. If not, it returns nil.
To test for a positive or negative number use (minusp). This returns T if a number is negative and nil if it is positive.
Oh, and before I forget, another good example of wrong data types is confusing radians with degrees. AutoLisp functions use radians, AutoCAD commands expect degrees.
Loops are another area that seem extremely susceptible to attracting bugs. Have a look at this :
(while flag (+ cntr 1) (program statements here) ( if ( = cntr 5) (setq flag nil) );if );while
What is supposed to happen here is that the program will loop until flag is set to nil. cntr is supposed to be incremented by one at each loop. But, as you can see from this coding, (at least I hope you can see), cntr will never reach the value of 5. It will always be equal to 1. The correct coding should be :
(setq cntr (+ cntr 1))
System variables can also create untold problems, especially Snaps. Switch them On only when you need them and Off once finished or you'll find yourself snapping to some weird and wonderful parts of your drawing. And please remember the golden rule. "Reset the system back to the way you found it." Before changing system variables, save their existing values then reset them back at the end of your program. And don't forget to to include these settings in your error traps so that if something untoward does happen, your error trap will reset them back to their original values.
Now let's have a quick look at some DCL coding bugs.
Have a look at the first one :
(action_tile "cancel" "(done_dialog) (setq bryt 1) (exit)")
I was told that every time the user clicked the "Cancel" button, AutoCAD would freeze. Any idea why? The second example has a similar error :
(action_tile "accept" "(progn (setq rimel (get_tile \"ebrim\")) (setq maxel (get_tile \"ebmax\")) (done_dialog) (setq flag T) (cutt))" )
The answer to both examples is that they were trying to call a function (exit) and (cutt) from within a dialog. In other words, the dialog was still open when they called the function. An easy error to make and one to look out for. Please everybody, be careful out there!
Now we'll look at a couple of handy ideas for making your debugging alittle be easier.
When writing an AutoLisp routine it's quite handy to have the program stop if it encounters an error. It's also nice to be able to view the variables whilst the program is running to ensure that they contain the correct values.
In Visual Lisp and Visual Basic you can insert "breakpoints" into your coding that do just that and use the Watch window to check on their values. If you are not using Visual Lisp or Visual Basic this can still easily be accomplished. Place the following statement within your coding where you would like your program to stop :
(setq bp (getstring))
This statement will stop your program and only continue once you hit the space bar.
To print the variables, put a (princ) statement to print the variable before the break point.
(princ variablename) (setq bp (getstring))
Do remember to remove your print statements and breakpoints on completion of debugging.
You could if you wish turn this little trick into a sub function. This would be quite handy if you have a large program and can foresee a lot of debugging. Have a look at this small AutoLisp routine that changes an objects colour to "Bylayer."
;CODING BEGINS HERE (defun c:clay () ;clear the loop control variables (setq i 0 n 0) ;prompt the user (prompt "\n Select entities to analyze ") ;get the selection set (setq sel (ssget)) ;get the number of objects (setq n (sslength sel)) ;start the loop (repeat n ;get the entity name (setq entity (ssname sel i)) ;now get the entity list (setq name (entget entity)) ;retrieve the layer name (setq layern (cdr (assoc 8 name))) ;get the layer data (setq layerinf (tblsearch "LAYER" layern)) ;extract the default layer colour (setq layercol (cdr (assoc 62 layerinf))) (bpt) (setq bp (getstring)) ;construct an append the new list (setq name (append name (list (cons 62 layercol)))) ;update the entity (entmod name) ;update the screen (entupd entity) ;increment the counter (setq i (1+ i)) ;loop );repeat (bpt) (setq bp (getstring)) (princ) );defun (defun bpt () (princ i) (princ "\n") (princ n) (princ "\n") (princ sel) (princ "\n") (princ layern) (princ "\n") (princ layercol) (princ "\n") (princ i) );defun (princ) ;CODING END HERE
I included a sub routine that simply stops the program and lists the values of selected variables at two places, one within the loop and one at the end of the program. This way you can easily track the value of the variables whilst your program is in progress. You could even include this type of breakpoint function within your error trap whilst debugging.
Another good trick is to always make your variables "global" at the beginning of a program. If you make them "local", they may have no value at the end of the program and you won't be able to see what was in them. The same as for the breakpoints, remember to declare your variables as "local" after debugging.
Leaving your variables "global" can though, create it's own problems whilst debugging. You run various programs that use the same variable names and suddenly you find your variables "tripping" all over themselves. This can be difficult to find as sometimes your program runs perfectly well and the next minute Crash!! Look out for this one.
I also found this little tit bit at AcadX, which I'm sure they won't mind me sharing with you. Prior to A2K, AutoLisp would dump a bug trace to the command line when an error was encountered. While developers went out of their way to suppress this behavior in a released product, it was a handy tool during the debugging cycle. To get that same behavior in A2K and higher, replace the standard error handler with this one :
(defun errdump (s) (vl-bt) (princ) )
Oh, and one more thing! Please remember the power of your eyes. Watch the screen as your program runs. You can pick up a lot of clues from watching what happens to entities whilst your program runs.
Well, that's it for debugging. I hope this helped you and didn't leave you even more frustrated than before. Remember, test, test, test and test.
If you would like to know how "NOT" to write your AutoLisp coding, then check this out.