Files
LuaCangjia_api/doc/api.md
JianFeeeee 42e8b7676d feat: 实现loadFunction预加载函数能力 + AI辅助编程标识
- 新增 loadFunction/callFunction/unloadFunction 预加载函数 API
- 预加载语义:加载文件→顶层执行一次→返回函数存入Lua Registry→可多次调用
- 与管道模式(load/runScript)存储完全解耦:funcs[]+Registry vs pkgs[]+虚拟栈
- 新增错误码 5023/5024/5025
- 新增单元测试:仓颉侧 8 个 + C++ GTest 侧 11 个
- 本机以 Cangjie 1.1.0 实编译并通过功能运行验证
- README/doc 增加 AI 辅助编程标识及预加载函数模式说明
2026-08-23 12:03:04 +08:00

204 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# API 文档
### 类:`LuaRunner`
Lua 虚拟机的主要管理类。
### 构造函数
`public init(input: IOCallback = defaultIO, output: IOCallback = defaultIO, pathio: String = "", pkgpath: String = "")`
- **参数**:
- `input`: `IOCallback` 类型。Lua 调用 `io.read` 时触发的回调函数。
- `output`: `IOCallback` 类型。Lua 调用 `print``io.write` 时触发的回调函数。
- `pathio`: `String` 类型。指定 I/O 重定向的文件交换目录路径。为空则不重定向。
- `pkgpath`: `String` 类型。设置 Lua 的 `package.path`,用于自定义模块搜索路径(如 `"./lib/?.lua"`)。
### 方法
| 方法 | 描述 | 返回值 |
| :--- | :--- | :--- |
| `load(path: String, name: String): This` | **块压栈**。加载 Lua 文件、编译成 chunk、压入虚拟栈并记录栈位置。`name` 仅作为内部标识。与管道模式配套使用。 | 实例本身 (`This`) |
| `loadFunction(path: String, name: String): This` | **预加载函数到内存**。加载文件 → 执行顶层代码(仅执行一次) → 捕获返回的函数对象 → 存入 Lua Registry。与 `load` 完全独立的两套存储(不共用 `pkgs` 栈),被加载的 Lua 文件顶层必须 `return` 一个函数。 | 实例本身 (`This`) |
| `callFunction(name: String, arg: String): String` | **按名称调用预加载的函数**。从 Registry 取出函数后 `pcall`**不消耗该函数**,可多次重复调用。与管道模式互不干扰。 | 函数执行的字符串结果 |
| `unloadFunction(name: String): This` | 卸载预加载的函数(释放 Registry 引用)。 | 实例本身 (`This`) |
| `unload(name: String): This` | 从内部栈中卸载由 `name` 标识的块(仅适用于 `load` 加载的块)。 | 实例本身 (`This`) |
| `runScript(path: String, arg: String): String` | **核心执行方法**。 - 若 `path` 非空:加载并执行该脚本,`arg` 作为单参数传入。 - 若 `path` 为空:触发**管道模式**,调用栈顶函数,并按内部栈布局自动计算参数数量。 | 脚本执行的字符串结果 |
| `doString(target: String): String` | 执行单条 Lua 代码字符串。`target` 为合法的 Lua 代码片段,必须返回字符串类型结果。 | 执行结果的字符串 |
| `clear(): This` | 清理 Lua 状态机,清空全局变量和所有加载的库,恢复到初始状态。 | 实例本身 (`This`) |
| `result(): String` | 获取最近一次 `runScript``doString` 成功执行的返回值。 | 字符串 |
| `error(): String` | 获取最近一次错误的原始描述信息。 | 字符串 |
## 异常类:`LuaError`
所有 Lua 相关操作失败时抛出的异常。
- **属性**:
- `code: Int32`:错误码,用于程序逻辑判断。
- **方法**:
- `getMessage(): String`:获取错误的详细描述。
### 错误码对照表
| 错误码 | 宏定义 | 描述 |
| :----- | :--------------------- | :------------------------------------------------------- |
| 5001 | `NAPI_JSON_LOAD_FAIL` | JSON 文件加载失败。 |
| 5002 | `NAPI_MALLOC_FAIL` | 内存分配失败。 |
| 5003 | `NAPI_LOAD_FILE_ERROR` | 文件加载错误,包含路径、权限以及脚本编译期语法错误。 |
| 5004 | `NAPI_SCRIPT_RUNNER_INVALID` | 脚本运行器初始化失败或实例无效。 |
| 5005 | `NAPI_LUA_STATE_ERROR` | Lua 状态机初始化失败或已损坏。 |
| 5006 | `NAPI_JSON_RESOLVE_FAIL` | JSON 解析错误,可能是格式问题。 |
| 5007 | `NAPI_JSON_FORMATE_ERROR` | JSON 格式错误,不符合 MCP 常见格式。 |
| 5008 | `NAPI_MISSING_JSON_ARG` | 无法从 JSON 中解析到所需参数。 |
| 5009 | `NAPI_LUA_STACK_NOSPACE` | Lua 栈空间不足。 |
| 5010 | `NAPI_SCRIPT_ERROR` | 脚本内部错误,请通过 `result()` 获取详细信息。 |
| 5011 | `NAPI_SCRIPT_BAD_RET` | 脚本返回值错误,当前版本仅支持字符串返回。 |
| 5012 | `NAPI_UNLOADLIB_FAIL` | 尝试卸载未加载的库。 |
| 5013 | `NAPI_LUAFUN_NOFOUND` | 调用不符合单输入单输出约定。 |
| 5014 | `NAPI_LUA_STACK_ERROR` | 函数调用时栈大小小于最小约定大小。 |
| 5015 | `NAPI_LUA_INITFAIL` | Lua 状态机初始化错误。 |
| 5016 | `NAPI_LUALIB_LOAD_OVER_STACK` | 加载的库数量超过上限(最大 20 个)。 |
| 5017 | `NAPI_LUA_CLASS_LOST` | 底层 LuaRunner 对象丢失。 |
| 5018 | `NAPI_LUA_MISS_REDIRECT` | 缺少用于重定向的文件路径。 |
| 5019 | `NAPI_RESET_INPUT_FILE_ERROR` | 重置输入文件失败,可能导致输入污染。 |
| 5020 | `NAPI_ERROR_FUNCS` | 回调函数执行过程中发生错误(含 `doString` 语法错误)。 |
| 5021 | `NAPI_CHDIR_ERROR` | 切换工作目录失败,可能导致包搜索错误,日志写入路径错误等。 |
| 5022 | `NAPI_NOCHUNK_FOUND` | 没有找到待调用的函数。 |
| 5023 | `NAPI_FUNCTION_NOT_FOUND` | `callFunction` 调用时未找到指定名称的预加载函数。 |
| 5024 | `NAPI_LOAD_FUNCTION_OVER` | 预加载函数数量超过上限(最大 20 个)。 |
| 5025 | `NAPI_FUNCTION_NOT_VALID` | 预加载的文件顶层返回的不是函数。 |
## 预加载函数模式 (Preload Function Mode)
与管道模式不同,预加载模式适合“加载一次、多次调用”的场景。
### 调用规范
1. **脚本编写**:被预加载的 Lua 文件**必须在顶层 `return` 一个函数**,函数的闭包状态(如计数器、配置)在预加载时初始化,后续调用不会重置。
```lua
-- counter.lua
local count = 0 -- 预加载时初始化一次
return function(arg)
count = count + 1
return "arg=" .. arg .. " count=" .. count
end
```
2. **预加载**`runner.loadFunction("counter.lua", "counter")`
3. **多次调用**`runner.callFunction("counter", "x")` 可重复调用,`count` 持续累加。
4. **卸载**`runner.unloadFunction("counter")` 释放函数。
### 与管道模式的区别
| 维度 | `load` (管道模式) | `loadFunction` (预加载模式) |
| :--- | :--- | :--- |
| 存储位置 | 虚拟栈(`pkgs[]`,相对索引) | Lua Registry`funcs[]`,绝对引用) |
| 顶层代码 | 每次调用都重新执行 | 预加载时执行一次 |
| 调用方式 | `runScript("", arg)` 触发 | `callFunction(name, arg)` |
| 生命周期 | 调用后消耗(`pkg_cont--` | 不消耗,可重复调用 |
| 与另一方关系 | 互不干扰(存储完全解耦) | 互不干扰(存储完全解耦) |
### 示例
```cangjie
let runner = LuaRunner()
// 预加载:顶层代码执行一次
runner.loadFunction("./scripts/counter.lua", "counter")
// 多次调用:计数器持续累加
println(runner.callFunction("counter", "a")) // arg=a count=1
println(runner.callFunction("counter", "b")) // arg=b count=2
println(runner.callFunction("counter", "c")) // arg=c count=3
// 卸载
runner.unloadFunction("counter")
```
## 管道模式 (Pipeline Mode)
这是该引擎的核心特性,允许数据在多个脚本间自动流转,无需在仓颉层进行手动传递。
### 调用规范
1. **脚本编写**:作为管道节点的 Lua 脚本,必须使用 Lua 的变长参数语法 `...` 来接收上一个节点传递的数据。
```lua
-- node_b.lua
local input = ...
return "处理结果: " .. input
```
2. **加载顺序(逆序)**利用栈的后进先出LIFO特性需按照执行顺序的**逆序**进行加载。
- 期望的执行顺序:`A` -> `B` -> `C`
- 仓颉侧加载顺序:
1. `runner.load("C.lua", "C")`
2. `runner.load("B.lua", "B")`
3. `runner.load("A.lua", "A")`
3. **触发执行**
- 首先执行初始脚本,将其返回值压入栈顶。
- 然后调用 `runScript("", "")`,引擎将自动调用栈顶下方的函数(即最后加载的脚本 `B`),并将栈顶数据作为参数传入。
- `B` 执行完毕后的返回值成为新的栈顶,等待下一次 `runScript` 调用。
### 运行时语义说明
- 当 `path` 非空时,脚本会先完成编译并压入内部栈,再进入执行阶段。
- 若脚本在编译阶段发生语法错误,当前实现返回 `NAPI_LOAD_FILE_ERROR`,详细信息可通过 `result()` 获取。
- 在 `require` / `package.path` 场景下,`runScript` 返回当前 Lua 栈顶字符串结果;若栈顶内容是模块解析路径,则会直接返回该路径字符串。
### 管道模式示例
```cangjie
// 1. 按逆序加载A -> B -> C
runner.load("./scripts/add_suffix.lua", "add_suffix") // 脚本 C: 添加后缀
runner.load("./scripts/to_upper.lua", "to_upper") // 脚本 B: 转大写
runner.load("./scripts/add_prefix.lua", "add_prefix") // 脚本 A: 添加前缀
// 2. 执行初始数据生成脚本
let initialData = runner.runScript("./scripts/generate_data.lua", "hello")
// 3. 依次触发管道节点
let afterA = runner.runScript("","")//数据经过A
let afterB = runner.runScript("", "") // 数据经过脚本 B (to_upper)
let finalResult = runner.runScript("", "") // 数据经过脚本 C (add_suffix)
println(finalResult) // 最终输出: [PREFIX] HELLO [SUFFIX]
```
## I/O 重定向机制
当配置了 `input`、`output` 回调以及 `pathio` 路径时Lua 的标准 I/O 将被重定向:
- **输出重定向**Lua 调用 `print` 或 `io.write` 时,内容会写入 `{pathio}` 目录下的临时文件,并触发仓颉侧的 `output` 回调。您可以在回调中实现自己的输出逻辑(如写入日志、发送网络消息等)。
- **输入重定向**Lua 调用 `io.read` 时,会触发仓颉侧的 `input` 回调。您需要在回调中准备并提供输入数据(例如从文件读取或返回模拟数据),供底层读取。
## doString 使用说明
`doString` 允许直接执行 Lua 代码字符串,而无需写入文件。
### 基本用法
```cangjie
let runner = LuaRunner()
// 执行简单表达式
let res = runner.doString("return 1 + 1")
println(res) // 输出: 2
// 设置全局变量并读取
let res2 = runner.doString("g = 42; return g")
println(res2) // 输出: 42
```
### 注意事项
- `doString` 要求代码执行后栈顶必须是**字符串类型**的返回值
- 若代码不返回字符串(如返回 `nil`、数字或无返回值),将抛出 `NAPI_SCRIPT_BAD_RET` (5011) 错误
- 若代码存在语法错误,将抛出 `NAPI_ERROR_FUNCS` (5020) 错误
- `doString` 与 `runScript` 共享同一个 Lua 状态机,全局变量和已加载的模块可互相访问