CAD-Lisp: configuration file syntax

There are plenty of ways to create configuration files. They all have pros and cons. What are the possibilities for CAD-Lisp?

Choosing a configuration file syntax

Win.ini is nice because of its simple structure. The downside is that it is limited, lacking array functionality for example. On the other side of the spectrum there is XML. Way to complex for simple tasks.

How about JSON?

For CAD-Lisp I often used Unix style configuration, and doing arrays in semicolon format. Nowadays JSON is popular and that looks a bit like structured lists. The example on https://en.wikipedia.org/wiki/JSON:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
  "firstName": "John",
  "lastName": "Smith",
  "isAlive": true,
  "age": 27,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "office",
      "number": "646 555-4567"
    },
    {
      "type": "mobile",
      "number": "123 456-7890"
    }
  ],
  "children": [],
  "spouse": null
}

Or simply… Lisp!

Nobody can deny the similarities found in Lisp. So it is very tempting to go back to basic Lisp format, simply because it supports all that Lisp supports, because, well, now we get to the core, it is Lisp in itself!

This is how the file looks, translated to Lisp format:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(
   (firstName "John")
   (lastName "Smith")
   (isAlive T)
   (age 27)
   (address
      (
         (streetAddress "21 2nd Street")
         (city "New York")
         (state "NY")
         (postalCode "10021-3100")
      )
   )
   (phoneNumbers
      (
         (
            (type "home")
            (number "212 555-1234")
         )
         (
            (type "office")
            (number "646 555-4567")
         )
         (
            (type "mobile")
            (number "123 456-7890")
         )
      )
   (children ())
   (spouse nil)
)

Just like with JSON, the structure and nesting is immediately clear. Pros: simplest syntax. Cons: bracket matching is the only separation (that is a pro too). A real problem arises when reading the file, (read-line ...) checks for matching brackets and the first line will result in a “malformed list” error.

However, making sure all brackets match per line means also that (read-line …) directly applies data type list and not data type string.

And that can be consolidated to, for example, only main level entries without loosing information:

1
2
3
4
5
6
7
8
   (firstName "John")
   (lastName "Smith")
   (isAlive T)
   (age 27)
   (address ((streetAddress "21 2nd Street")(city "New York")(state "NY")(postalCode "10021-3100")))
   (phoneNumbers (((type "home")(number "212 555-1234"))((type "office")(number "646 555-4567"))((type "mobile")(number "123 456-7890")))
   (children ())
   (spouse nil)

Needless to say, a proper parenthesis aware editor, such as Notepad++ and Geany are of great help here.

Suppose this file is saved as “cfg2bb-test.cfg” in the search path, it is used below. But first, as a one liner, the configuration becomes this:

1
((firstName "John")(lastName "Smith")(isAlive T)(age 27)(address ((streetAddress "21 2nd Street")(city "New York")(state "NY")(postalCode "10021-3100")))(phoneNumbers (((type "home")(number "212 555-1234"))((type "office")(number "646 555-4567"))((type "mobile")(number "123 456-7890")))(children ())(spouse nil))

Parsing it

Let’s create some code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
; (c) NedCAD 2020
; cfg2bb parses a list config file to blackboard variable "bb<cfg".
; syntax: (load "cfg2bb")(cfg2bb "cfg-uri")(setq cfg2bb nil)
 
(defun cfg2bb ( cfg-uri / cfg-file cfg-obj file-line list-line list-result)
 
; Error handler
  (defun *error* (msg)
    (princ "error: ")
    (princ msg)
    (princ)
  )
 
; Getting default settings
  (if (setq cfg-file (findfile cfg-uri))
    (progn
      (princ (strcat "\nParsing " cfg-file ". "))
      (setq cfg-obj (open cfg-file "r"))
      (while (setq file-line (read-line cfg-obj))
        (setq list-line (read file-line))
        (setq list-result (append list-result (list list-line)))
      )
      (close cfg-obj)
      (vl-bb-set 'bb<cfg list-result)
      (princ "\n") (princ (vl-bb-ref 'bb<cfg))
    )
    (princ (strcat "\nError: Can't find " cfg-uri ". Check existence and or support file search paths. "))
  )
  (princ)
)

What happens if we do:

1
(load "cfg2bb")

and then:

1
(cfg2bb "cfg2bb-test.cfg")

Output will be a list:

1
2
Parsing C:\Users\nanos\AppData\Roaming\NedCAD\Config\cfg2bb-test.cfg. 
((FIRSTNAME John) (LASTNAME Smith) (ISALIVE T) (AGE 27) (ADDRESSES (STREETADDRESS 21 2nd Street) (CITY New York) (STATE NY) (POSTALCODE 10021-3100)) (PHONENUMBERS ((TYPE home) (NUMBER 212 555-1234)) ((TYPE office) (NUMBER 646 555-4567)) ((TYPE mobile) (NUMBER 123 456-7890))) (CHILDREN NIL) (SPOUSE NIL))

So if we wanted the phone numbers, we could do:

1
(nth 5 (vl-bb-ref 'bb<cfg))

Resulting in the list:

1
(PHONENUMBERS ((TYPE "home") (NUMBER "212 555-1234")) ((TYPE "office") (NUMBER "646 555-4567")) ((TYPE "mobile") (NUMBER "123 456-7890")))

Tips

  • In order to parse the file only once per CAD session, the value is written to the blackboard, or better, assign the blackboard variable to a blackboard variable for your application. Then, when opening a drawing, check if the blackboard variable exists, if not, parse the configuration, else, propagate the existing blackboard variable.

Leave a comment