Files
LuaCangjia_api/doc/api.md
JianFeeeee 4af121c1f3 refactor!: 错误码体系重构 删除特殊平台语境残留
BREAKING CHANGE: 错误码编号全部变更, 外部依赖旧编号的代码需按映射表更新

- 删除 10 个从未使用的错误码:
  JSON/MCP 系列(5001/5002/5006/5007/5008) + 其余死码(5004/5009/5013/5018/5021)
- 重排为连续 5001~5016
- 宏前缀 NAPI_(鸿蒙 NAPI 语境残留) 改为 LUAERR_
- 同步更新: errors.h / bridge.cj getErrorMessage+typed抛出点 /
  lua_runner_test.cj 断言 / api.md 对照表 / README.md / GTest 宏名
- 清理注释残留: 鸿蒙 el2 沙盒路径 TODO / 沙盒内路径描述
- 版本号 0.2.1 -> 0.2.2

验证: GTest 44/44 通过; cjpm build 成功; 独立程序运行时 9/9 通过
(含新编号 5016/5013 错误路径)
2026-08-25 09:29:48 +08:00

13 KiB
Raw Permalink Blame History

API 文档

类:LuaRunner

Lua 虚拟机的主要管理类。

构造函数

public init(input: IOCallback = defaultIO, output: IOCallback = defaultIO, pathio: String = "", pkgpath: String = "")

  • 参数:
    • input: IOCallback 类型。Lua 调用 io.read 时触发的回调函数。
    • output: IOCallback 类型。Lua 调用 printio.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 获取最近一次 runScriptdoString 成功执行的返回值。 字符串
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 一个函数,函数的闭包状态(如计数器、配置)在预加载时初始化,后续调用不会重置。

    -- 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 Registryfuncs[],绝对引用)
顶层代码 每次调用都重新执行 预加载时执行一次
调用方式 runScript("", arg) 触发 callFunction(name, arg)
生命周期 调用后消耗(pkg_cont-- 不消耗,可重复调用
与另一方关系 互不干扰(存储完全解耦) 互不干扰(存储完全解耦)

示例

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 路径

调用规范

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 integerCJT_INTnumberCJT_NUM结果其余nil/string/bool/table5016
  • callFunctionNum/runScriptNum:同上,接受 int/num 结果。
  • callFunctionBool/runScriptBool:接受 bool/int/num 结果nil 与 string 抛 5016
  • 现有字符串 APIcallFunction/runScript)行为不变;每次调用都会同步更新类型化结果字段,因此 callFunction("x", "10") 之后 resultType() 返回 4str

与管道模式的关系

runScriptInt/Num/Bool独立调用:执行后弹出返回值、保持虚拟栈清洁,不参与管道链。管道模式请继续使用字符串版 runScript(path, arg)(其结果留在栈上供下一节点消费)。

内部实现

  • C 层新增 callfunction_int/num/bool/voidrun_int/num/bool/voidresult_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 的变长参数语法 ... 来接收上一个节点传递的数据。

    -- 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 栈顶字符串结果;若栈顶内容是模块解析路径,则会直接返回该路径字符串。

管道模式示例

// 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 重定向机制

当配置了 inputoutput 回调以及 pathio 路径时Lua 的标准 I/O 将被重定向:

  • 输出重定向Lua 调用 printio.write 时,内容会写入 {pathio} 目录下的临时文件,并触发仓颉侧的 output 回调。您可以在回调中实现自己的输出逻辑(如写入日志、发送网络消息等)。
  • 输入重定向Lua 调用 io.read 时,会触发仓颉侧的 input 回调。您需要在回调中准备并提供输入数据(例如从文件读取或返回模拟数据),供底层读取。

doString 使用说明

doString 允许直接执行 Lua 代码字符串,而无需写入文件。

基本用法

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) 错误
  • doStringrunScript 共享同一个 Lua 状态机,全局变量和已加载的模块可互相访问