LISP and Variables

LISP and variables

There is often confusion about using variables. We are not talking about what they are and how they are described in the manuals, that is clear, otherwise search the net for “cad lisp variables”. What is less clear is how variables propagate between drawings and CAD sessions and even installations. So lets shine a light.

Brief on naming

With so many code on the net there are so many styles. That is why it is useful to read this first. A recognisable style makes reading code so much easier.

CAD LISP is what is used in BricsCAD or AutoCAD ( named AutoLISP and Visual LISP). CAD LISP is not used like, for example, Common LISP. You may find “Naming conventions” at http://www.cliki.net/Naming+conventions helpful but also overkill for CAD LISP.

Many examples use CamelCase or mixedCase names like MyVar or myVar. This can lead to less easy to track errors in a case sensitive environment, but is is probably the easiest way to type.

An alternative is suggested in the previous LISP link: my-var. Double clicking on my-var in your editor often does not select the word, but only my or var. That can be a valid excuse to use underscores in some cases: my_var. For example, in Notepad++ (NPP), double clicking a variable highlights all variables with that name – a valuable feature. On the other hand, adding -_:* as delimiter is useful, see Settings > Preferences > Delimiter > add -_:* characters without spaces.

A habit is also to use asterisks for global variables: *my-var*. But it is just a recommendation. Adding an asterisk itself does not make it global, it is just a naming convention, please read the rest to understand this.

All in all: Do what you want. You may tend to follow the LISP naming conventions, as-i-do. Short variables are common too, like dx, dy, ss1 (selection set 1), but here variable ll does not ring one bell while list_length keeps code readable.

Another LISP variant seen often is package:variable. This helps reducing collision chances. Some authors on the net use it like authorinitials:variablename. You could use it for your “Nuts” program like nuts:var1. As an escape for word selection: nuts_var1 or nutsvar1 or nuts-var1. But, if you think about it, limitations of word selection are welcome in this case: Use nuts:size-list and be able to highlight all “size-list” OR all “nuts” occurrences. On the other hand, adding “:” as NPP Delimiter works fine too and is, for what it is worth, my personal favourite. That is because when I double click on a variable, I instantly see all other occurrences of that variable.

One last remark, variable names are called “symbols”. So get used to people talking about “symbols”, “symbol values” and “symbol names” in order to understand what they talk about. A symbol is a representation of a variable like a list, function, object, value. Atoms are values that can not be divided, like symbol names, symbol values and not (non empty) lists. You may consider string “foo bar” as one atom because (atom "foo bar") returns T, but (read "foo bar") returns atom "foo", because (read ...) considers the string to be a list. Summarized: An atom is the value of every symbol that is not a list.

Local and global

There are local and global variables. Lets paste some code fragments on the CAD command line:

(setq existingvar "existingvar")

… and paste:

(defun test (/ localvar existingvar) (setq localvar "localvar") (setq globalvar "globalvar") (setq existingvar localvar) (princ (strcat "local, global and existing are now: " localvar " - " globalvar " - " existingvar)) (princ))

After pasting, don’t make the mistake that you think  globalvar is available, doing  !globalvar on the command line returns nil. Loading a function in memory (defun...) and executing it are two different things. So we do execute it by entering (test).

The function returns exactly what we expected:

local, global and existing are now: localvar - globalvar - localvar.

Do the next on the command line:

!globalvar returns "globalvar", where !localvar returns nil, meaning !localvar simply does not exist (anymore). However, !existingvar returns "existingvar", and that may surprise you.

Summarized: Local variable values evaporate after a function (defun ...) is finished. However, when a local variable existed prior to running that function, it gets the old value back. Beautiful!

Names spaces

Each drawing has a created “name space”. That is where the global variables lives. So a global variable can have different settings in each drawing. This is an important thing to remember because it can be confusing.

Repeated in other words, each name space – and as a consequence each drawing – has its own variables. A global variable or a function name (defun function-name ...) in one document can be different or absent in another document.

Managing global variables with the same symbol name

We want access to variables for all drawings with their own name spaces – or not. What are the tools? How does it work?

  • When you load a drawing and you use acaddoc.lsp for AutoCAD or on_doc_load.lsp for BricsCAD, you can make a copy of that global variable (setq ...) in each drawing’s namespace.
    • When you use Autodesk autoloader (don’t if possible), you can do the same in file PackageContents.xml:
      <?xml version="1.0" encoding="utf-8"?>
      <ApplicationPackage
        <Components>
        <ComponentEntry
          ModuleName="./contents/load_for_each_dwg.lsp"
          PerDocument="True" />
        </Components>
      </ApplicationPackage>
    • As a tool to make life easier for programmers and to work around Autodesk autoloader, NedCAD developed CADchUP ACME. With ACME you can mark LISP files with variable definitions to load on document load or on program start.
  • When you use strings you can use (setenv ...) and (getenv ...) and write to and read from the registry. Very handy but there is one thing you must keep in mind:
    • This mechanism is limited to strings. This means you often have to do conversions.
    •  (getenv ...) reads not only the registry, but also the operating system’s environment (do SET or ENV in a shell to see them), like  (getenv "username"). As long as you don’t use  (setenv ...) nothing happens. But if you do, the value is written to the CAD registry part and the next time you do getenv, you will get that value and not the OS value.
    • Another way for poking around in Windows registry: (vl-registry-write ...) and (vl-registry-read ...). Also limited to strings. Permanent available.
  • Put your variables in a file and remember, you are using LISP: write and read lists when possible. It is easier to retrieve the 13th member from a list (nth 12 ...)  on one line of a file than to read line 13 from a file (while ... (read-line ... )) with counters and performing string conversions.
  • Probably the most practical and clean way: Use the black board name space. What you do with a file can also be done with (vl-bb-set ...) and (vl-bb-ref ...). Support is not limited to strings. They cease to exist after the CAD program is closed and don’t clutter the registry. If you use the black board, then *global-var* syntax is very distinguishing and attractive. Just publish the global variable once with (vl-bb-set '*global-var* global-var) at the end of your code and get it somewhere else with (vl-bb-ref '*global-var*)
  • If you use global variables in multiple drawings and they must all be updated, change the variable in one place and use (vl-propagate ... ) to update in all name spaces.

Variables, memory and nil

Variables use memory, but not much. It is good practise to free memory and kill variables to prevent collisions. Example: (setq test-val1 "Hi!") returns string “Hi!“, (setq test-val2 T) returns T (True, logical, not a string).

We want to get rid of variable test-val1 and the only thing we need to do is (setq test-val1 nil). That is it. Same for functions, for our function (defun function-name ...), you do  (setq function-name nil). That is the proper way and prevents collisions of multiple used names. Closing a drawing eliminates the name space and does the same.

A few words about (defun function-name (arg-1 ... / var-1 ...). When the function is finished, variable function-name persists, just as the variables not included after the slash. However, arg-1 is vanished after the function is finished – or gets its old value as explained above.

But what if a function returns nil like (not T)? It is the same: (setq test-val1 (not T)) simply destroys test-val1. An overwhelming list of used symbols can be retrieved by (atoms-family 1), you can check it yourself.

You can say that a variable, with a value of nil, can not have value nil because a variable evaluating to nil does not exist. Yes, read this twice. Now that is chicken egg stuff! Just a bit confusing so forget it, cheat ourselves a bit, and let’s live on with the idea that a variable can be nil.

That is it, in short.