AutoLISP: The Complete Guide — Chapter 7 / 16

Control Structures

A program that always executes the same instructions in the same order is quickly limited. Control structures allow your code to make decisions and repeat actions. In AutoLISP, they follow the same prefix syntax as everything else.

Comparison Operators

Before you can make decisions, you need to be able to compare values. AutoLISP provides the following operators:

(= 5 5)      ; → T (equality)
(/= 5 3)     ; → T (not equal)
(< 3 5)      ; → T (less than)
(> 5 3)      ; → T (greater than)
(<= 3 3)     ; → T (less than or equal)
(>= 5 3)     ; → T (greater than or equal)

These functions return T (true) or nil (false).

Comparing Strings: Watch Out for Case!

String comparison with = is case-sensitive:

(= "hello" "hello")    ; → T
(= "hello" "Hello")    ; → nil (case-sensitive!)
(= "HELLO" "hello")    ; → nil

This is a common pitfall, especially when comparing user input or AutoCAD entity names. To perform a case-insensitive comparison, convert both strings to uppercase (or lowercase) with strcase before comparing:

(= (strcase "hello") (strcase "Hello"))  ; → T
(= (strcase "HELLO") (strcase "hello"))  ; → T

Best practice: when comparing user input to an expected value, always normalize the case with strcase.

Comparing Real Numbers: Watch Out for Rounding!

With real numbers (floating-point), comparison with = can produce surprising results due to rounding errors inherent in computer calculations:

(setq result (+ 0.1 0.2))
(= result 0.3)    ; → nil (!!)

The result of (+ 0.1 0.2) is not exactly 0.3 but something like 0.30000000000000004. This is a classic problem that affects all programming languages, not just AutoLISP.

The solution is to use the equal function with a tolerance argument:

(equal (+ 0.1 0.2) 0.3 1e-10)  ; → T

The third argument (1e-10, i.e., 0.0000000001) is the tolerance: if the difference between the two values is less than this tolerance, equal considers them equal.

Golden rule: never compare two real numbers with =. Always use equal with an appropriate tolerance. This is particularly important in CAD where coordinates are real numbers and geometric calculations accumulate rounding errors.

;; Compare two points (lists of reals) with tolerance
(setq point1 '(10.0 20.0 0.0))
(setq point2 '(10.0000001 19.9999999 0.0))
(equal point1 point2 1e-6)  ; → T

equal also works with lists: it compares elements one by one with the given tolerance, making it ideal for comparing points.

Logical Operators

To combine conditions:

(and T T)          ; → T (logical AND)
(and T nil)        ; → nil
(or nil T)         ; → T (logical OR)
(or nil nil)       ; → nil
(not nil)          ; → T (negation)
(not T)            ; → nil

and and or accept multiple arguments:

(and (> 5 3) (< 10 20) (= 1 1))  ; → T (all conditions are true)
(or (> 1 10) (< 5 3) (= 2 2))    ; → T (at least one is true)

The if Condition

The if structure is the simplest decision-making structure:

(if condition
  expression-if-true
  expression-if-false
)

Example:

(setq age 25)

(if (>= age 18)
  (alert "You are an adult.")
  (alert "You are a minor.")
)

Dialog box displaying "You are an adult."

if Without else

The "if false" expression is optional:

(if (> temperature 100)
  (alert "Warning: overheating!")
)

If the condition is false and there is no "else" branch, if returns nil.

Executing Multiple Expressions: progn

if only accepts a single expression per branch. If you need to execute several, group them with progn:

(if (>= grade 10)
  (progn
    (princ "\nWell done!")
    (princ "\nYou passed the exam.")
    (setq result "passed")
  )
  (progn
    (princ "\nToo bad.")
    (princ "\nYou will have to retake the exam.")
    (setq result "failed")
  )
)

progn executes a sequence of expressions and returns the value of the last one.

The Multiple Condition cond

When you have more than two cases to handle, cond is more readable than a cascade of nested if statements:

(cond
  (condition1 expression1)
  (condition2 expression2)
  (condition3 expression3)
  (T defaultExpression)
)

cond evaluates the conditions from top to bottom and executes the first one whose condition is true. The last line with T as the condition serves as a default case (since T is always true).

Example: Angle Classification

(defun c:angle-type (/ angle)
  (setq angle (getreal "\nEnter an angle in degrees: "))
  (cond
    ((= angle 0)   (alert "Zero angle"))
    ((< angle 90)  (alert "Acute angle"))
    ((= angle 90)  (alert "Right angle"))
    ((< angle 180) (alert "Obtuse angle"))
    ((= angle 180) (alert "Straight angle"))
    ((< angle 360) (alert "Reflex angle"))
    (T              (alert "Invalid or full angle"))
  )
  (princ)
)

angle-type command in action with a 45-degree angle classified as "acute"

Multiple Expressions per Case

Each cond case can contain multiple expressions without needing progn:

(cond
  ((< grade 10)
    (princ "\nFailed.")
    (setq honors "insufficient")
  )
  ((< grade 12)
    (princ "\nPass.")
    (setq honors "pass")
  )
  ((< grade 14)
    (princ "\nFairly good.")
    (setq honors "fairly-good")
  )
  (T
    (princ "\nGood or very good!")
    (setq honors "good")
  )
)

The while Loop

while repeats a block of instructions as long as a condition is true:

(while condition
  expression1
  expression2
  ...
)

Example: Countdown

(setq counter 10)

(while (> counter 0)
  (princ (strcat "\n" (itoa counter) "..."))
  (setq counter (- counter 1))
)

(princ "\nLiftoff!")

Result in the command line:

10...
9...
8...
...
1...
Liftoff!

Practical Example: Requesting a Valid Value

(defun c:valid-radius (/ radius)
  (setq radius 0)
  (while (<= radius 0)
    (setq radius (getreal "\nEnter a positive radius: "))
    (if (<= radius 0)
      (princ "\nError: the radius must be greater than 0.")
    )
  )
  (alert (strcat "Radius accepted: " (rtos radius 2 2)))
  (princ)
)

Validation loop with error message then acceptance

The repeat Loop

repeat executes a block a fixed number of times:

(repeat count
  expression1
  expression2
  ...
)

Example:

(setq total 0)
(repeat 5
  (setq total (+ total 10))
)
total  ; → 50

Example: Drawing a Regular Polygon

(defun c:polygon (/ side-count radius center angle increment current-point)
  (setq side-count (getint "\nNumber of sides: "))
  (setq radius (getreal "\nRadius: "))
  (setq center (getpoint "\nCenter: "))
  (setq increment (/ (* 2 pi) side-count))
  (setq angle 0)

  ;; Calculate the first point
  (setq current-point
    (list
      (+ (car center) (* radius (cos angle)))
      (+ (cadr center) (* radius (sin angle)))
    )
  )

  (repeat side-count
    (setq angle (+ angle increment))
    (setq next-point
      (list
        (+ (car center) (* radius (cos angle)))
        (+ (cadr center) (* radius (sin angle)))
      )
    )
    ;; Draw a line between the two points
    (command "LINE" current-point next-point "")
    (setq current-point next-point)
  )

  (princ)
)

This program draws a regular polygon by calculating the vertices using trigonometry. Don't worry if you don't understand all the functions used (car, cadr, list, command) — we will study them in the following chapters.

Summary

Structure Purpose Syntax
if Simple condition (if test true false)
progn Group expressions (progn expr1 expr2 ...)
cond Multiple conditions (cond (test1 expr) (test2 expr) ...)
while Conditional loop (while test expr ...)
repeat Fixed loop (repeat n expr ...)
and Logical AND (and cond1 cond2)
or Logical OR (or cond1 cond2)
not Negation (not condition)

In the next chapter, we dive into the heart of LISP: lists, the most important data structure in the language.


Helping hand Need an AutoCAD (AutoLISP, ObjectARX, .NET, VBA) development? Contact me for a free quote.