* AutoLISP: load and run .lsp routines (#758) CADCraft runs AutoLISP routines. `.lsp` files load with APPLOAD, (load) or by dropping them on the window, and at startup from the support folder (acad.lsp once per session or per drawing with ACADLSPASDOC, acaddoc.lsp per drawing, then S::STARTUP; never from a drawing's own folder). (defun c:NAME …) defines commands, (autoload) defers loading to first use, and (expression) and !variable work at the command line and at prompts. docs/autolisp.md lists what is supported. The interpreter (crates/engine/src/lisp) reads source with its own stack and evaluates on an explicit-stack machine, so a routine can stop for input (getpoint, pause in command) and carry on later on one thread, deep recursion is an error rather than a crash, and long loops run a slice per frame (Esc stops them). It follows AutoLISP: 32-bit wrapping integers, dynamic scope, *error*, vl-catch-all-apply, AutoLISP's error wording. About 200 built-ins cover the language, command (with pause and commands left open), entity lists (entget/entmake/entmod/entdel/entlast/ entnext/handent, through the DXF reader and writer's groups, exposed by cadcraft-io and handed to the engine through a hook), ssget with modes and filters, tables, system variables, get… input with initget, and units. A routine is one undo step; each drawing has its own symbols. Also: keywords typed as _KEYWORD match at prompts; TEXT run by a routine's command takes one line; DIMENSION records carry their measurement (42); Prompt::raw shows a get… prompt as written; the interface asks Session::prompting() (a command's or a routine's prompt) where it asked whether a command runs, so clicks, right-click and Delete work at a routine's prompts; getcorner and based get… prompts drag a rubber band. Tests: the language, errors, startup files, interactive input, hostile input (lisp/tests.rs); the #648 corpus, 18/18, when CADCRAFT_LISP_CORPUS points at it (tests/lisp_corpus.rs); drop, frames and canvas clicks in the app (ui-egui/tests/lisp_files.rs). * AutoLISP: bound memory, drop nested values iteratively, guard the per-frame tick - Cons cells drop with a work list over both car and cdr (and function bodies), so a list nested a million deep in its car no longer overflows the stack when it goes away. - Memory caps so a runaway routine fails with a LISP error instead of an uncatchable out-of-memory abort: at most 5,000,000 live cons cells (counted in Value::cons / Drop, checked every step; cells the drawing's variables already held at the run's start don't count against it), no string a built-in makes longer than 8 MiB (strcat checks before joining, printed text is cut), and a 2 GiB per-run budget for new strings, printed text and files read. - Session::lisp_tick runs under catch_unwind, and so does every slice of the LISP machine: a panic ends the routine with "internal error", puts the drawing back and leaves the session usable. - Regression tests for each (deep car nesting, nested functions, the cell cap, string length and budget, a panicking built-in in a blocking run and in a ticked one). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
5.0 KiB
Markdown
88 lines
5.0 KiB
Markdown
# AutoLISP
|
|
|
|
CADCraft runs AutoLISP routines (`.lsp` files). What is here and what is not yet:
|
|
[gaps.md](gaps.md) (E1).
|
|
|
|
## Loading and running
|
|
|
|
- **APPLOAD** (Tools > Load Application…, alias `AP`) asks for a `.lsp` file and runs it. Dropping
|
|
a `.lsp` file on the window does the same.
|
|
- **`(load "name")`** loads a file by path, with `.lsp` added when the name has none. A file
|
|
APPLOAD loaded is found again by its name, also on the web where there is no file system.
|
|
- **`(defun c:NAME …)`** defines a command: type `NAME` at the command line. Built-in commands
|
|
keep their names.
|
|
- **At the command line**, `(expression)` evaluates and shows the value, and `!variable` shows a
|
|
variable. Typed at a command's prompt (not a free-text one), an expression's value answers it:
|
|
`CIRCLE 0,0 (* 2 r)`.
|
|
- **Programmatically** (scripts, the control channel, MCP, the CLI): `lisp {code, inputs?}` and
|
|
`appload {path} | {name, text | data}` run to the end and return `{value, output, error?}`.
|
|
`inputs` answer `get…` calls in order (numbers, strings, `[x, y]` points); a call with none left
|
|
gets nil.
|
|
|
|
### At startup
|
|
|
|
CADCraft loads LISP from its **support folder**, `support` in the settings folder (on Windows
|
|
`%APPDATA%\CADCraft\support`, on macOS `~/Library/Application Support/CADCraft/support`, on
|
|
Linux `~/.config/cadcraft/support`; created at first start):
|
|
|
|
- `acad.lsp` once per session, into the first drawing; with `ACADLSPASDOC` set to 1, into every
|
|
drawing;
|
|
- `acaddoc.lsp` into every drawing opened or created;
|
|
- then `S::STARTUP`, when they define it.
|
|
|
|
`(autoload "file" '("CMD1" "CMD2"))` in a startup file makes `CMD1` and `CMD2` commands that load
|
|
`file` (from the support folder too) the first time they are typed. Startup files run to the end
|
|
without asking for input. A drawing's own folder is never searched: an `acaddoc.lsp` that came
|
|
with a downloaded drawing does not run.
|
|
|
|
Each drawing has its own variables and functions. A routine is one undo step, whatever commands
|
|
it runs. A routine that waits for input (`getpoint`, `pause`) lets the window work meanwhile;
|
|
Esc stops it with "Function cancelled", which `*error*` receives. A long loop runs a slice per
|
|
frame, so the window stays live and Esc stops it too.
|
|
|
|
## The language
|
|
|
|
Integers are 32-bit and wrap as AutoLISP's do; any real makes arithmetic real. Variables are
|
|
dynamically scoped: a function's arguments and its locals (after `/`) are bound while it runs.
|
|
Symbols are case-insensitive. Reals print with six significant digits.
|
|
|
|
Nothing a routine does can crash the program: deep recursion is the error "internal stack limit
|
|
reached", malformed source is a reader error, and every built-in checks its arguments.
|
|
|
|
## Built-in functions
|
|
|
|
- **Forms:** `quote` `'` `function` `setq` `set` `if` `cond` `while` `repeat` `foreach` `progn`
|
|
`and` `or` `defun` `defun-q` `lambda` `eval` `apply` `mapcar` `load` `autoload` `exit` `quit`
|
|
- **Numbers:** `+ - * / rem 1+ 1- abs min max sqrt exp log sin cos atan expt fix float gcd
|
|
logand logior lsh ~ = /= < > <= >= zerop minusp numberp`
|
|
- **Lists:** `car cdr` and the `c…r` combinations up to four levels, `cons list append reverse
|
|
length nth last member assoc subst acad_strlsort`, `vl-position vl-remove vl-sort
|
|
vl-remove-if vl-remove-if-not vl-member-if vl-every vl-some vl-list-length vl-consp`
|
|
- **Predicates:** `atom listp null not type eq equal boundp vl-symbolp`
|
|
- **Strings:** `strcat strlen substr strcase itoa atoi atof chr ascii read wcmatch`,
|
|
`vl-string-search vl-string-subst vl-string-trim vl-string-left-trim vl-string-right-trim
|
|
vl-string-translate vl-string->list vl-list->string vl-prin1-to-string vl-princ-to-string
|
|
vl-symbol-name vl-symbol-value vl-filename-base vl-filename-extension vl-filename-directory`
|
|
- **Geometry:** `polar distance angle inters trans` (`trans` returns the point: no UCS yet)
|
|
- **Output and files:** `princ prin1 print prompt terpri alert open close read-line write-line
|
|
findfile`
|
|
- **Errors:** `*error*`, `vl-catch-all-apply vl-catch-all-error-p vl-catch-all-error-message`
|
|
- **Drawing:** `command` (also `command-s`, `vl-cmdf`) with `pause`, a command left open for the
|
|
next call, and `(command)` to cancel; `getvar setvar`; `entget entmake entmod entdel entlast
|
|
entnext entupd handent`; `ssget` (modes X, L, P, I, W, C, F, WP, CP, a point, or the user
|
|
selecting; filters with wildcards and `-4` operators) `ssadd ssdel sslength ssname ssmemb
|
|
sssetfirst ssgetfirst`; `tblsearch tblnext` (LAYER, LTYPE, STYLE, BLOCK, DIMSTYLE, APPID)
|
|
- **Input:** `initget getpoint getcorner getdist getangle getorient getreal getint getstring
|
|
getkword entsel nentsel`
|
|
- **Units:** `rtos angtos distof angtof`
|
|
- **No-ops:** `vl-load-com gc redraw`; `ver`
|
|
|
|
Entity lists use the DXF group codes of CADCraft's DXF files: what `entget` returns is what a
|
|
DXF file holds for the object, plus `-1` (its name) and `410` (its layout).
|
|
|
|
## Testing against the corpus
|
|
|
|
The AutoLISP conformance corpus (#648) lives in its own repository. Point `CADCRAFT_LISP_CORPUS`
|
|
at it and run `cargo test -p cadcraft-engine --test lisp_corpus`; without the variable the test
|
|
says it was skipped.
|