diff --git a/README.md b/README.md index 0578a73..1a0507d 100644 --- a/README.md +++ b/README.md @@ -3,18 +3,20 @@ [![License](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](LICENSE) [![Lua](https://img.shields.io/badge/Lua-5.4-blue)](https://www.lua.org/) [![Cangjie](https://img.shields.io/badge/Cangjie-SDK-orange)](https://cangjie-lang.cn/) -![AigcAssets.png](https://raw.atomgit.com/user-images/assets/9445015/a1976a82-4d44-493a-8efd-fb12a104268e/AigcAssets.png 'AigcAssets.png') +![AigcAssets.png](https://raw.atomgit.com/user-images/assets/9445015/a1976a82-4d44-493a-8efd-fb12a104268e/AigcAssets.png "AigcAssets.png") + Lua Runner for Cangjie 是一个专为仓颉语言设计的轻量级、高性能 Lua 脚本执行引擎。它通过 C FFI 桥接 C++,提供了稳定且易用的 Lua 虚拟机管理能力。 -**注:本项目是开发原生鸿蒙应用时产生的副产物,当前版本依然存在局限性与不足,请详细检查后再使用。** +**注:本项目是开发原生鸿蒙应用时产生的副产物,当前版本依然存在局限性与不足,请详细检查后再使用。**。 -安全与发布说明:仓库中不应提交任何中心仓访问令牌、私有凭据或其他敏感配置。发布前请使用本地环境变量、用户级配置或 CI 密钥注入方式提供认证信息,并检查待上传制品中不包含明文凭据。 +除了基础的脚本嵌入功能外,该引擎的核心特色在于支持一种独特的 **"栈式管道执行模式"**,能够实现脚本间的隐式参数传递,非常适合构建数据处理管道、游戏脚本系统或插件化架构。同时,它提供了完善的异常处理机制、灵活的 I/O 重定向功能,以及直接执行 Lua 代码片段的 `doString` 方法。 -除了基础的脚本嵌入功能外,该引擎的核心特色在于支持一种独特的 **“栈式管道执行模式”**,能够实现脚本间的隐式参数传递,非常适合构建数据处理管道、游戏脚本系统或插件化架构。同时,它提供了完善的异常处理机制和灵活的 I/O 重定向功能。 +详细 API 文档请查看 [doc/api.md](doc/api.md)。 ## 特性 - **轻量级集成**:基于 Lua 5.4,通过 FFI 直接与仓颉语言交互,性能损耗小。 - **栈式管道模式**:支持脚本间基于栈的数据隐式传递,实现类似函数式管道的调用链。 +- **doString**:直接执行 Lua 代码字符串,无需写入文件。 - **I/O 重定向**:可自定义 Lua 标准输入/输出(`print`, `io.read`)的回调函数,支持文件交换目录重定向。 - **模块化管理**:提供 `load` 和 `unload` 方法,支持按名称动态加载和卸载 Lua 脚本模块,避免全局污染。 - **完善的异常处理**:定义了详细的错误码体系,通过 `LuaError` 类统一抛出,便于调试和逻辑控制。 @@ -30,20 +32,21 @@ Lua Runner for Cangjie 是一个专为仓颉语言设计的轻量级、高性能 ### 基本用法 ```cangjie -import LuaCangjie_api.* +import luaRunner.* main(): Int64 { try { // 1. 创建 Lua 运行实例 let runner = LuaRunner() - // 2. 加载一个脚本模块,命名为 "myScript" + // 2. 执行 Lua 代码片段 + let res = runner.doString("return 1 + 1") + println("Result: ${res}") // 输出: Result: 2 + + // 3. 加载并执行脚本 runner.load("./scripts/hello.lua", "myScript") - - // 3. 执行脚本,传入参数 "World" - let result = runner.runScript("", "World") - println("Script result: ${result}") // 输出: Script result: Hello, World! - + let result = runner.runScript("", "World") + println("Script result: ${result}") } catch (e: LuaError) { println("Lua Error [${e.code}]: ${e.getMessage()}") @@ -53,135 +56,6 @@ main(): Int64 { } ``` -**脚本示例 (`hello.lua`)**: - -```lua --- 使用 ... 接收从 runScript 传入的参数 -local name = ... -return "Hello, " .. name -``` - -## 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 文件,编译后以 `name` 为标识压入内部栈。 | 实例本身 (`This`) | -| `unload(name: String): This` | 从内部栈中卸载由 `name` 标识的模块。 | 实例本身 (`This`) | -| `runScript(path: String, arg: String): String` | **核心执行方法**。 - 若 `path` 非空:加载并执行该脚本,`arg` 作为单参数传入。 - 若 `path` 为空:触发**管道模式**,调用栈顶函数,并按内部栈布局自动计算参数数量。 | 脚本执行的字符串结果 | -| `clear(): This` | 清理 Lua 状态机,清空全局变量和所有加载的库,恢复到初始状态。 | 实例本身 (`This`) | -| `result(): String` | 获取最近一次 `runScript` 成功执行的返回值。 | 字符串 | -| `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` | 回调函数执行过程中发生错误。 | - -## 管道模式 (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` 回调。您需要在回调中准备并提供输入数据(例如从文件读取或返回模拟数据),供底层读取。 - ## 当前局限性 - **数据类型限制**:当前版本在仓颉与 Lua 交互层仅支持**字符串**类型。虽然 Lua 内部可以处理复杂数据结构,但传递给仓颉或从仓颉传入时必须进行序列化/反序列化(如 JSON)。 diff --git a/cjpm.toml b/cjpm.toml index 73a1f10..ce10a0b 100644 --- a/cjpm.toml +++ b/cjpm.toml @@ -1,6 +1,6 @@ [package] -name = "luarunner" -version = "0.1.1" +name = "luaRunner" +version = "0.2.1" description = "Lite and High performance LuaRunner" cjc-version = "1.1.0" license = "LGPL-3.0-or-later" diff --git a/doc/api.md b/doc/api.md new file mode 100644 index 0000000..5e554fa --- /dev/null +++ b/doc/api.md @@ -0,0 +1,148 @@ +# 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 文件,编译后以 `name` 为标识压入内部栈。 | 实例本身 (`This`) | +| `unload(name: String): This` | 从内部栈中卸载由 `name` 标识的模块。 | 实例本身 (`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` | 没有找到待调用的函数。 | + +## 管道模式 (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 状态机,全局变量和已加载的模块可互相访问 diff --git a/lib/lua_cj_api.cpp b/lib/lua_cj_api.cpp index c6051ff..4d7f3cb 100755 --- a/lib/lua_cj_api.cpp +++ b/lib/lua_cj_api.cpp @@ -85,6 +85,28 @@ int run(void *selfd,const char *path,const char *arg) snprintf(self->result,MAX_RESULT,"%s",runner->reslt.c_str()); return 0; } + +/// @brief 执行单句lua语句 +/// @param selfd init_lua_runner返回的void指针 +/// @param target 执行语句(字符串类型) +/// @return 成功返回0,失败返回-1,并将lua出错原因拷贝至result中 +int dostring(void *selfd,const char *target){ + lua_runner* self = (lua_runner*)selfd; + //重定向输入到回调函数 + Lua_runner *runner = (Lua_runner*)self->lua_obj;//还原对象 + if(runner == NULL) + { + errno = NAPI_LUA_STATE_ERROR; + return -1; + } + if(runner->dostring(target)==-1){ + snprintf(self->result,MAX_RESULT,"%s",runner->reslt.c_str()); + return -1; + } + snprintf(self->result,MAX_RESULT,"%s",runner->reslt.c_str()); + return 0; +} + /// @brief 清理lua状态机缓存,虚拟栈,加载的包等 /// @param self 使用init_lua_runner初始化的lua_runner结构体指针 /// @return 0代表正常结束,-1表示异常退出 diff --git a/lib/lua_cj_api.h b/lib/lua_cj_api.h index 213c6ca..449f85a 100755 --- a/lib/lua_cj_api.h +++ b/lib/lua_cj_api.h @@ -62,6 +62,12 @@ int unload_lib(void *selfd,const char *name); /// @return 成功返回0,失败返回-1,并将lua出错原因拷贝至result中 int run(void *selfd,const char *path,const char *arg); +/// @brief 执行单句lua语句 +/// @param selfd init_lua_runner返回的void指针 +/// @param target 执行语句(字符串类型) +/// @return 成功返回0,失败返回-1,并将lua出错原因拷贝至result中 +int dostring(void *selfd,const char *target); + /// @brief 清理lua状态机缓存,不销毁状态机 /// @param selfd init_lua_runner返回的void指针 /// @return 成功返回0,失败返回-1 diff --git a/lib/lua_runner.cpp b/lib/lua_runner.cpp index 60d33d4..090178f 100755 --- a/lib/lua_runner.cpp +++ b/lib/lua_runner.cpp @@ -209,6 +209,37 @@ int Lua_runner::run(const char *path,const char *arg) return -1; } } + +int Lua_runner::dostring(const char *target) +{ + if(target == NULL) + { + errno = NAPI_ERROR_FUNCS; + return -1; + } + if(luaL_dostring(this->L,target) != LUA_OK) + { + errno = NAPI_ERROR_FUNCS; + this->reslt = lua_tostring(this->L,-1); + lua_pop(L,1);//错误出栈 + return -1; + } + if(lua_gettop(this->L) == 0) { + errno = NAPI_SCRIPT_BAD_RET; + this->reslt = ""; + return -1; + } + if(lua_isstring(this->L,-1)){ + this->reslt = lua_tostring(this->L,-1); + lua_pop(L,1); + return 0; + } + else{ + errno = NAPI_SCRIPT_BAD_RET; + lua_pop(L,1); + return -1; + } +} //TODO 实现函数调用 achieve func call via file /* int Lua_runner::callfunction() diff --git a/lib/lua_runner.hpp b/lib/lua_runner.hpp index 96c7686..0376e2e 100644 --- a/lib/lua_runner.hpp +++ b/lib/lua_runner.hpp @@ -25,7 +25,8 @@ class Lua_runner//lua运行器对象 int unload_lib(const char *name);//解除加载 int run(const char *path = NULL,const char *arg = NULL); int clean();//清除状态机缓存 - int callfunction(); + //int callfunction(); + int dostring(const char *target); std::string reslt; lua_State *get_lua_State(); private: diff --git a/src/bridge.cj b/src/bridge.cj index 782d508..acb116f 100644 --- a/src/bridge.cj +++ b/src/bridge.cj @@ -1,4 +1,4 @@ -package luarunner +package luaRunner // ==================== FFI底层 ==================== @@ -13,6 +13,8 @@ foreign func unload_lib(selfd: CPointer, name: CString): Int32 @C foreign func run(selfd: CPointer, path: CString, arg: CString): Int32 @C +foreign func dostring(selfd: CPointer,target: CString): Int32 +@C foreign func cleanup(selfd: CPointer): Int32 @C foreign func get_errno(): Int32 @@ -212,6 +214,21 @@ public class LuaRunner { } return this.cachedResult } + + public func doString(target: String): String + { + let targetptr = toCStr(target); + let code = unsafe { dostring(this.handle, targetptr) } + unsafe{ + if(!targetptr.isNull()){LibC.free(targetptr)} + } + this.cachedResult = toString(unsafe { getresult(this.handle) }) + + if (code != 0) { + throw LuaError(unsafe { get_errno() }, this.cachedResult) + } + return this.cachedResult + } /// 获取缓存的结果 public func result(): String { this.cachedResult } diff --git a/src/lua_runner_test.cj b/src/lua_runner_test.cj index 771401e..2fbb973 100644 --- a/src/lua_runner_test.cj +++ b/src/lua_runner_test.cj @@ -1,4 +1,4 @@ -package luarunner +package luaRunner import std.unittest.* // cjlint-ignore G.PKG.01 import std.fs.* // cjlint-ignore G.PKG.01 @@ -254,7 +254,58 @@ func testRedirectIoRead(): Unit { } // ==================== -// 6. 错误码与回调 +// 6. doString 测试 +// ==================== + +@Test +func testDoStringSimple(): Unit { + let runner = LuaRunner() + let res = runner.doString("return 1 + 1") + @Expect(res, "2") +} + +@Test +func testDoStringWithGlobal(): Unit { + let runner = LuaRunner() + let res = runner.doString("g = 42; return g") + @Expect(res, "42") +} + +@Test +func testDoStringNoReturn(): Unit { + let runner = LuaRunner() + try { + runner.doString("x = 10") + fail("Should throw bad ret error") + } catch (e: LuaError) { + @Expect(e.code, 5011) + } +} + +@Test +func testDoStringSyntaxError(): Unit { + let runner = LuaRunner() + try { + runner.doString("if true then") + fail("Should throw syntax error") + } catch (e: LuaError) { + @Expect(e.code, 5020) + } +} + +@Test +func testDoStringNilReturn(): Unit { + let runner = LuaRunner() + try { + runner.doString("return nil") + fail("Should throw bad ret error") + } catch (e: LuaError) { + @Expect(e.code, 5011) + } +} + +// ==================== +// 7. 错误码与回调 // ==================== @Test diff --git a/test/test_lua_cj_api.cpp b/test/test_lua_cj_api.cpp index 8adaa6c..9ed6f9e 100644 --- a/test/test_lua_cj_api.cpp +++ b/test/test_lua_cj_api.cpp @@ -285,6 +285,47 @@ TEST_F(LuaCjApiTest, PackagePathConfig) { EXPECT_STREQ(res, lib_path.c_str()); } +// ==================== 新增测试:doString 功能 ==================== + +// 测试简单表达式 +TEST_F(LuaCjApiTest, DoStringSimple) { + int ret = dostring(runner, "return 1 + 1"); + EXPECT_EQ(ret, 0); + char* res = getresult(runner); + EXPECT_STREQ(res, "2"); +} + +// 测试全局变量设置与读取 +TEST_F(LuaCjApiTest, DoStringWithGlobal) { + int ret = dostring(runner, "g = 42; return g"); + EXPECT_EQ(ret, 0); + char* res = getresult(runner); + EXPECT_STREQ(res, "42"); +} + +// 测试语法错误 +TEST_F(LuaCjApiTest, DoStringSyntaxError) { + int ret = dostring(runner, "if true then"); + EXPECT_EQ(ret, -1); + EXPECT_EQ(get_errno(), NAPI_ERROR_FUNCS); + char* res = getresult(runner); + EXPECT_NE(strstr(res, "'end' expected"), nullptr); +} + +// 测试无返回值 +TEST_F(LuaCjApiTest, DoStringNoReturn) { + int ret = dostring(runner, "x = 10"); + EXPECT_EQ(ret, -1); + EXPECT_EQ(get_errno(), NAPI_SCRIPT_BAD_RET); +} + +// 测试返回 nil +TEST_F(LuaCjApiTest, DoStringNilReturn) { + int ret = dostring(runner, "return nil"); + EXPECT_EQ(ret, -1); + EXPECT_EQ(get_errno(), NAPI_SCRIPT_BAD_RET); +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();