AutoLISP Lesson 4 - PROGRAM FORMAT
An AutoLISP program can be written many ways, but there are certain structural points that are common to all programs. Here you will learn how to put together a program, structured in the manner that best suits your current application.
One of the main differences between programming in AutoLISP and programming in other languages, is AutoLISP is an interpreted language, as I have stated before. That means it will take one line of code at a time and evaluate it by itself. You don't have to write a complete program for AutoLISP to be of value to you.
AutoLISP has three different forms a program can take.
Every line of code you write in AutoLISP will be a list. The one thing all lists have in common is that they are all enclosed in parenthesis "()". This is the most important point to get right now. You must put everything in parenthesis, and for every open parenthesis, there must be a closing parenthesis.
An expression is a list that starts with a function as the first item in the list, the remainder of the list is passed to the function as arguments. The function used in the expression can be a predefined function from AutoLISP, or it can be a function you have defined yourself. The remainder of the list must pass the correct number of arguments to the function, or it will not run.
(/ 50 25.4)
The example above converts 50 in metric to standard units. It can be entered from the command line, or used as part of a larger program.
If I had only passed one number to the / function above, you can be certain it would not have ran. The same rule will apply to the functions you write later. You must pass the correct number of arguments to a function.
A Function is one or more expressions gathered into a form where they can do work. A function must be defined before it can be used. A function is defined in AutoLISP using the DEFUN statement.
(defun m ( / ) (/ 50 25.4) )
The example above shows the form for defining functions. The list starts with DEFUN as the function doing the work. DEFUN needs three or more arguments. The first argument is the name for the function you are defining, in the example this was "m". The second argument is the parameter list, and in the example there are no parameters. You will notice in the parameter list a slash separator, this is to separate the arguments to the function on the left of the slash, from the local variables on the right side of the slash. The third and all subsequent arguments are expressions for the function to perform when it is run. In the example the function will always return 50 divided by 25.4.
The function can be called by: (m)
The Command Function
The function definition for the command function has exactly the same form as a normal function definition with one exception. When it is being defined "c:" is placed in front of the command name argument.
(defun c:m ( / ) (/ 50 25.4) )
When making the call to this type of function you must call the name just as it's written in the function definition.
When called from inside the lisp interpreter the command function must be treated as any other function. The real power of the command function comes when calling it from the command line. Any function may be called from the command line, but the command function does not need the parenthesis when calling it from the command line.
Example: Normal function call from the command line:
Command: (m) Command function call from the command line: Command: m
As you can see it will be much easier for the user to deal will command function than normal functions. Command functions are what we will use to build future programs.
Now it is time to put it all together and see what a real program will look like. Every program you write should come out looking basically the same. It will contain certain components that are going to be common to all your programs.
The first thing we will do is create our header comment so we know what we are working on, and more importantly, when we return later to modify it, we will know what we worked on. The next thing you will need, is the program definition.
;; Title: Program ;; Purpose: Program to do things ;; Written: YZ ;; Creation: 2-5-1998 (defun c:prog ( / ) (stuff_to_do) )
The first thing you will notice in the program above is I have defined a command function. The filename for the lisp should be the same as the command name given.In this case the filename will be PROG.LSP. The next thing that stands out is the parameter list. In this case it is empty, but I have included the separator anyway. I do this so I will know later when I come back to it, that I intentionally have no parameters, or local variables. With the parameter and local variable list there, I will be less likely to forget to add values to the list, as I need them.
Next we see the stuff_to_do function which will be defined later.
Then finally we close the program with the closing parenthesis to match the opening one at the beginning of our definition.
The next thing we need is some sort of error trapping routine so if something unforeseen happens the entire program will not go flashing across the screen, leaving our user totally confused.
(defun c:prog ( / ) (stuff_to_do) (defun *ERROR* (ErrStr) (print ErrStr) ) )
Now we have an error checking routine, which is quite basic, but will get the job done for now. All it will do is catch the error string passed back from AutoLISP when the error occurs and print the error string on the command line.
The program listing shown above is sufficient to make a simple program that can be used at the command line. As programs get more complex you will find it necessary to do a little more work to keep things in order. If for example we wanted to pass some parameters to our program and do a little logic based on what we pass in, the program gets more complex. Our job is to keep it from getting so complex we no longer understand what is going on.
(defun c:prog ( Param1 Param2 / ) (if (= Param1 Param2) (progn (do_this_stuff) (do_that_stuff)) (progn (do_this_other_stuff) (do_that_other_stuff))) (defun *ERROR* (ErrStr) (print ErrStr) ) )
As you can see I have to parameters I'm checking against each other, and I've decided I need to do several things if my cases work out.
Now things are starting to get a little harder and it might be best if I broke certain things into their own functions, just to make it clearer what is going on. The first thing I will do is create a main function so all the logic can happen in the same place. What I want from my main function is to only to see the logic part of the program, and leave the actual work to be done for the other functions.
(defun c:prog ( Param1 Param2 / prog_main check_params stuff_to_do do_other_stuff *error* ) (defun prog_main ( / ) (if (check_params Param1 Param2) (stuff_to_do) (do_other_stuff)) ) (defun check_params ( Item1 Item2 / ) (= Item1 Item2) ) (defun stuff_to_do ( / ) (do_this_stuff) (do_that_stuff) ) (defun do_other_stuff ( / ) (do_this_other_stuff) (do_that_other_stuff) ) (defun *ERROR* (ErrStr) (print ErrStr) ) (prog_main) )
As you can see the program grows but is less complex because we now have our logic contained within a few lines of code. You can follow the logic without having to understand the details of what the program is doing. The larger a program becomes the more important it is to hide the details, and leave only the logic out front. Many other things have happened here, that makes this style work so well. We now have local variables in the parameter list for our program. Putting the function names in the parameter list means when our program ends, the life of the functions we are using end also. If we had not included them in the parameter list they would still be occupying memory, even after our program had ended. We must leave memory as clean as possible when we end our program, and parameter lists are the easiest way to keep memory clean.
You may have noticed that Param1 and Param2 are not included in the parameter list. This is because Param1 and Param2 are parameters being passed to a function and their life ends when the function ends, so no cleanup is necessary. Also notice when passing Param1 and Param2 to check_params I have given them different names in the function definition for check_params. When passing variables to a function you should use different names inside the function so no confusion over which variable is being worked on.
The variable named in the parameter list is not the variable that exists outside the function. Even if they have the same name they are not the same variable. The variable in the parameter list is local to the function being called, and is just a copy of the variable being passed. What that means is, if you change the variable inside the function the variable being passed to the function is not changed. This shouldn't really matter though, because it's not a good idea to work on variables passed to a function. Variables passed to a function should only be used for working on the value you are trying to return from the function.
You may have noticed I have given the check in the IF statement its own function. This makes the logic in the prog_main function easier to follow. The way it is written I don't have to know what is being checked, I only have to know whether it was true or not.
You now should know the basic layout of an AutoLISP program. The reasons we need to structure our programs are varied but certain reasons keep coming up again and again, so I will list them here.
Any area, which seems complex, should probably be moved to its own function where you can control the interface to the function.
Your program should clean up memory. Functions should be localized to only those areas of the program needing them. Variables should be kept local to the function using them. Global variables should be avoided if at all possible.
Proper commenting should be included to make every part of the program obvious. If you name your functions and variables correctly you can cut down on the number of comments needed to clarify things.
Featured blocks of the month