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

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 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.

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 my personal favourite – for what it is worth.

One last remark, variable names are called “symbols”. So get used to people talking about “symbols” and “symbol names” in order to understand what they talk about.

Local and global

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

(defun test (/ localvar ) (setq localvar "localvar") (setq globalvar "globalvar"))

You expect globalvar to be available. However this example illustrates very good the difference between loading a function in memory (defun...) and executing it. !globalvar returns nil, but after running (test), !globalvar returns "globalvar", where !localvar returns nil, meaning !localvar simply does not exist (anymore).

Local variables evaporate after running a function (defun ...) is finished.

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"?>
          PerDocument="True" />
    • 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.

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.