# 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`,**不消耗该函数**,可多次重复调用。与管道模式互不干扰。 | 函数执行的字符串结果 | | `callFunctionInt(name: String, val: Int64): Int64` | **typed 直通调用**。传 Int64 参数(映射为 Lua integer),返回整数结果。结果非数值时抛 5016。 | 整数结果 | | `callFunctionNum(name: String, val: Float64): Float64` | **typed 直通调用**。传 Float64 参数(映射为 Lua number),返回浮点结果。结果非数值时抛 5016。 | 浮点结果 | | `callFunctionBool(name: String, val: Bool): Bool` | **typed 直通调用**。传 Bool 参数(映射为 Lua boolean),返回布尔结果。结果为 nil/string 时抛 5016。 | 布尔结果 | | `runScriptInt(path: String, val: Int64): Int64` | 运行脚本并传 Int64 参数,返回整数结果。独立调用(执行后弹出返回值、保持虚拟栈清洁),非管道模式。 | 整数结果 | | `runScriptNum(path: String, val: Float64): Float64` | 同上,Float64 版本。 | 浮点结果 | | `runScriptBool(path: String, val: Bool): Bool` | 同上,Bool 版本。 | 布尔结果 | | `resultType(): Int32` | 获取最近一次调用的结果类型:`0`=nil `1`=bool `2`=int `3`=num `4`=str | 类型常量 | | `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~5016`,宏前缀为 `LUAERR_`。 | 错误码 | 宏定义 | 描述 | | :----- | :--------------------- | :------------------------------------------------------- | | 5001 | `LUAERR_LOAD_FILE` | 文件加载错误,包含路径、权限以及脚本编译期语法错误。 | | 5002 | `LUAERR_LUA_STATE` | Lua 状态机初始化失败或已损坏。 | | 5003 | `LUAERR_SCRIPT_ERROR` | 脚本内部错误,请通过 `result()` 获取详细信息。 | | 5004 | `LUAERR_SCRIPT_BAD_RET` | 脚本返回值类型不符合要求(如返回非字符串且无法转换)。 | | 5005 | `LUAERR_UNLOADLIB_FAIL` | 尝试卸载未加载的库。 | | 5006 | `LUAERR_LUA_STACK` | 函数调用时栈布局与约定不符。 | | 5007 | `LUAERR_INIT_FAIL` | Lua 状态机初始化错误。 | | 5008 | `LUAERR_LIB_OVERFLOW` | 加载的库数量超过上限(最大 20 个)。 | | 5009 | `LUAERR_CLASS_LOST` | 底层 LuaRunner 对象丢失。 | | 5010 | `LUAERR_RESET_INPUT_FILE` | 重置输入文件失败,可能导致输入污染。 | | 5011 | `LUAERR_CALLBACK` | 回调函数执行过程中发生错误(含 `doString` 语法错误)。 | | 5012 | `LUAERR_NOCHUNK_FOUND` | 没有找到待调用的函数。 | | 5013 | `LUAERR_FUNCTION_NOT_FOUND` | `callFunction` 调用时未找到指定名称的预加载函数。 | | 5014 | `LUAERR_FUNC_OVERFLOW` | 预加载函数数量超过上限(最大 20 个)。 | | 5015 | `LUAERR_FUNCTION_INVALID` | 预加载的文件顶层返回的不是函数。 | | 5016 | `LUAERR_RESULT_TYPE` | typed interop 中请求的结果类型与实际返回类型不兼容(如将 string 读作 int)。 | ## 预加载函数模式 (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") ``` ## 类型化交互 (Typed Interop) — v0.2.2 除字符串形式外,仓颉侧的 `Int64`/`Float64`/`Bool` 可与 Lua 的 `integer`/`number`/`boolean` **直接互转**,不经过字符串序列化。 ### 支持的类型映射 | 仓颉 | Lua | 说明 | | :--- | :--- | :--- | | `Int64` | `integer` | 64 位整数,可跨 `2^53` 保真 | | `Float64` | `number` | 64 位浮点 | | `Bool` | `boolean` | 布尔 | | `String` | `string` | 原 callFunction/runScript 路径 | ### 调用规范 ```cangjie let runner = LuaRunner() // 预加载:inc.lua 顶层返回 function(n) return n + 1 end runner.loadFunction("./scripts/inc.lua", "inc") // typed 调用:参数与结果均为整数,不转字符串 let n = runner.callFunctionInt("inc", 41) // n == 42 (Int64) let d = runner.callFunctionNum("mul", 4.0) // Float64 let b = runner.callFunctionBool("neg", true) // Bool // 脚本直通:脚本直接返回入参(如 return (...) ) let r = runner.runScriptInt("./scripts/echo.lua", 777) // 777 // 查询结果类型:0=nil 1=bool 2=int 3=num 4=str let t = runner.resultType() ``` ### 类型校验规则 - `callFunctionInt`/`runScriptInt`:接受 Lua `integer`(CJT_INT)与 `number`(CJT_NUM)结果;其余(nil/string/bool/table)抛 **5016**。 - `callFunctionNum`/`runScriptNum`:同上,接受 int/num 结果。 - `callFunctionBool`/`runScriptBool`:接受 bool/int/num 结果;nil 与 string 抛 **5016**。 - 现有字符串 API(`callFunction`/`runScript`)行为不变;每次调用都会同步更新类型化结果字段,因此 `callFunction("x", "10")` 之后 `resultType()` 返回 `4`(str)。 ### 与管道模式的关系 `runScriptInt/Num/Bool` 是**独立调用**:执行后弹出返回值、保持虚拟栈清洁,不参与管道链。管道模式请继续使用字符串版 `runScript(path, arg)`(其结果留在栈上供下一节点消费)。 ### 内部实现 - C 层新增 `callfunction_int/num/bool/void`、`run_int/num/bool/void`、`result_type/int/num/bool` 共 11 个 FFI 函数。 - C++ 层 `Lua_runner` 新增 `last_result_type/int/num/bool` 字段,`capture_result()` 读取 Lua 栈顶并记录类型,`finish_call()` 计算 `lua_pcall` 实际返回值数量(`nres = post_top - pre_top + nargs + 1`)以正确处理无返回值场景。 - typed 结果同时同步到字符串缓冲区 `self->result`,因此 `result()`/`getresult` 与 typed getter 可以混用。 ## 管道模式 (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` 非空时,脚本会先完成编译并压入内部栈,再进入执行阶段。 - 若脚本在编译阶段发生语法错误,当前实现返回 `LUAERR_LOAD_FILE` (5001),详细信息可通过 `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`、数字或无返回值),将抛出 `LUAERR_SCRIPT_BAD_RET` (5004) 错误 - 若代码存在语法错误,将抛出 `LUAERR_CALLBACK` (5011) 错误 - `doString` 与 `runScript` 共享同一个 Lua 状态机,全局变量和已加载的模块可互相访问