# LISP 与模板

## 数据与代码分开

`com.cad.lispValue(value)` 将一个数据值转成 LISP 字面量：字符串转义并加引号，false/null 为 nil，true 为 T，列表与字典分别转换为 LISP 列表、关联列表。buffer 也可传入，按字节作为字符串处理；文本 buffer 请使用 UTF-8，内嵌 NUL 会报错。

```aardio
import com.cad;
var text = '引号 " 与换行\n以及路径 C:\\test';
var expression = "(princ " ++ com.cad.lispValue(text) ++ ")";
print(expression); // 此例只生成代码，不连接 CAD。
```

`lispValue` 不把字符串变成符号或可执行代码。它编码数据，不是 LISP 沙箱。不要执行来自不可信文件、模型或网页的任意代码。

## 列表和点对

```aardio
import com.cad;
print(com.cad.list([1,2,[3,4]]));        // (1 2 (3 4))
print(com.cad.list([false,0,true]));    // (nil 0 T)
print(com.cad.list({car=1;cdr=2}));    // (1 . 2)
print(com.cad.list({car=1}));          // (1 . nil)
print(com.cad.list({cons=true}));      // (nil . nil)
print(com.cad.list({name="Tom"}));    // (("name" . "Tom"))
print(com.cad.list([1,2],true));       // 1 2
```

`part=true` 仅取消普通参数列表的外层括号，点对和关联列表始终带括号。空表/空数组为 `()`。嵌套过深或循环引用会报错。列表使用 1 到 100000 的正整数索引；通过 `table.range` 取得稀疏数组范围，仅检查范围边界，不逐键检查混入的非整数键。开头空位也保留位置：`{[2]=1}` 输出 `(nil 1)`，而不是把索引 2 的值移到首位。

aardio 的 null 元素不保存为表成员，无法恢复原本写在尾部的 null 个数。序列化保留到最后一个实际数值索引为止；需要明确保留 LISP nil 的位置（尤其是尾部）时使用 **false**，例如 `[false,0,false]` 输出 `(nil 0 nil)`。

## 模板

`com.cad.loadcode` 只渲染模板，不连接 CAD。成功返回代码字符串；编译或执行模板失败返回 `null,错误信息`，恢复原来的 `print`。

```aardio
import com.cad;
var code,err = com.cad.loadcode(`(list <?= [1,owner.text,false,0] ?>)`,{text="中文"});
if(!code) error(err);
print(code); // (list 1 "中文" nil 0)
```

- 第一个模板参数同时作为 `owner`。
- 数组或多个输出参数按 LISP 数据规则输出，以空格分隔。
- 单个布尔值转为 T/nil。
- **单个字符串是原样代码**，不会自动转义、加引号。外部数据应放入数组，或者先调用 `lispValue`。

## 执行入口的区别

需要结果、错误阶段、超时恢复和去重时，优先使用 [CreateLispTask / RunLisp](tasks.html)。这些入口传入原始源码和独立 args，不解析 aardio 模板。下面是仍保留的低层兼容接口：

| 入口 | 用途与边界 |
| --- | --- |
| `cad.SendCommand(code,args,doc)` | 发送命令或表达式，通常同步；交互/事件上下文可能异步；没有业务返回值。 |
| `cad.LoadLisp(code,args,doc)` | `.lsp` 文件交给 LoadLispFile；内联源码作为 progn 经命令通道提交，不再创建 ANSI 临时 LSP。 |
| `cad.LoadLispFile(path,doc)` | 显式加载已经部署且可信的 LSP 文件；文件需要保持可读直到 CAD 完成加载。 |

旧接口省略 doc 时仍使用活动文档，无文档时创建文档。自动化任务建议始终传入明确的 doc。

大型 LISP 程序建议放在明确部署的可信目录中，通过 LoadLispFile 加载；不要未经验证就把任意长的生成代码塞进命令通道。

### 保留语法安全位置的物理换行

**不要把多行 LISP 源码用 `string.crlf(code," ")` 压成一条长命令行。** 本机 AutoCAD 2026 实测：2076 字符的单行探针进入续输等待；相同逻辑保留安全位置的换行后成功。原本失败的执行包装器保留换行后，总长 4197 字符、最长行 368 字符，能够正常执行并回传结果。这是单行输入风险，不是程序总代码约 4 KB 的限制；具体阈值和其他 CAD 版本的行为未据此确定。

已有正确源码换行可用 `string.crlf(code,'\r')` 规范化后提交。不要按固定字符数硬切 LISP：不能切断字符串、转义、符号或注释语义。模板中的外部数据仍必须先用 lispValue 编码；单个超长字符串/单行仍需结构化拆分或使用可信文件路线。仅在发送前确认括号平衡，不能保证 CAD 输入端接收的表达式完整。

### 命令通道使用 CR，不使用 CRLF

命令输入不是普通 Windows 文本。保留库原来的 `\r`（CR）策略，把发送用的物理换行统一为 CR；不要直接发送 `\r\n` 并假定它始终代表一次 Enter。Autodesk 的 SendCommand 文档明确使用 vbCr 终止命令。不同产品/通道对 LF 的处理须实测，不把“CRLF 必然等于两次回车”作为跨版本承诺。

**增强 `cad` 与原生 `doc` 不同：** `cad.SendCommand("MYCMD",,doc)` 规范物理换行，末尾缺少 CR 才补一个；已有 CR 不重复添加，调用者明确写出的多个尾部 CR 仍保留。增强 `cad.PostCommand` 规则相同，不裁掉用于结束参数输入的空行。

原生调用须自行提供终止符，例如 `doc.SendCommand('MYCMD\r')` 或 `doc.PostCommand('MYCMD\r')`；这两个方法不补回车，也不解析 aardio 模板。即使 `doc` 来自增强对象的 `cad.ActiveDocument`，它仍然是原生 Document。注意 `"MYCMD\r"` 包含的是可见反斜杠和字母 r，不是实际 CR。

新任务接口的传输代码只使用 CR，末尾一个 CR；数据本身的换行用转义保存，不与命令终止混淆。

## 文件加载与编码

内联 `LoadLisp` 和 `RunLisp` 直接经命令通道执行，不生成临时 LSP，也不修改加载设置。日常自动化无需处理文件加载确认。

显式使用 `LoadLispFile` 时，文件仍按 CAD 原生策略加载；确认提示或拒绝交由 CAD 处理。内联代码若自行加载外部文件，也遵循其规则。

DLL 插件直接使用 [NetLoad](csharp.html)，库负责临时调整与恢复，不需要调用者手动管理设置；这与内联 LISP 的执行通道相互独立。

内联代码不再先转为系统 ANSI，但其在 CAD 中的解释仍取决于 CAD 版本与 LISP 字符集模式。文件编码应与 LISPSYS 及所用版本匹配；库不会修改需重启的 LISPSYS。

## Visual LISP COM 接口

`GetLispFunctions` 与 `LispExp` 保留作为高级兼容能力，但不作为 AI 默认执行通道；新任务接口不会调用它们。它们绑定活动文档，命令函数受上下文约束。`LispExp` 仅处理第一个表达式且没有业务返回值，多表达式要自行用 progn 包裹。不要把 `vl.application.16` 中的 16 换成 CAD 主版本号。

若怀疑这类原生 COM 接口造成宿主崩溃，先在独立进程中验证；普通异常捕获不是进程级防护。调用失败不能自动改走另一条通道重放有副作用的表达式。
