mirror of
https://gitcode.com/JianFeeeee/LuaCangjia_api.git
synced 2026-09-19 16:42:48 +00:00
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 辅助编程标识及预加载函数模式说明
This commit is contained in:
19
README.md
19
README.md
@ -3,9 +3,25 @@
|
||||
[](LICENSE)
|
||||
[](https://www.lua.org/)
|
||||
[](https://cangjie-lang.cn/)
|
||||
[](README.md#ai-%E8%BE%85%E5%8A%A9%E7%BC%96%E7%A8%8B%E6%A0%87%E8%AF%86)
|
||||

|
||||
**v0.2.1版本更新**:新增doString方法,可执行单句lua,更新了文档布局与readme优化了文档布局。
|
||||
**v0.2.1版本更新**:新增doString方法,可执行单句lua,新增loadFunction/callFunction预加载能力(AI辅助),更新了文档布局与readme优化了文档布局。
|
||||
|
||||
---
|
||||
|
||||
## 🤖 AI 辅助编程标识
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 本项目部分代码由 **AI 辅助生成**(包括但不限于:`loadFunction`/`callFunction`/`unloadFunction` 预加载函数能力、相关单元测试与文档)。
|
||||
> AI 生成代码均经过人工审查与实机验证(本仓库在本机以 Cangjie 1.1.0 实编译并通过功能运行验证),但请在使用前自行复核。
|
||||
|
||||
| 模块 | AI 辅助程度 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| `loadFunction`/`callFunction`/`unloadFunction` | 高(AI 主写,人工审查) | 预加载函数能力:加载文件→顶层执行一次→返回函数存入 Registry→可多次调用 |
|
||||
| 单元测试(新增部分) | 中(AI 主写,人工修正) | 预加载相关 GTest / 仓颉单测 |
|
||||
| 文档(新增部分) | 低(AI 起草,人工定稿) | README / api.md 预加载部分 |
|
||||
|
||||
---
|
||||
|
||||
Lua Runner for Cangjie 是一个专为仓颉语言设计的轻量级、高性能 Lua 脚本执行引擎。它通过 C FFI 桥接 C++,提供了稳定且易用的 Lua 虚拟机管理能力。
|
||||
**注:本项目是开发原生鸿蒙应用时产生的副产物,当前版本依然存在局限性与不足,请详细检查后再使用。**。
|
||||
@ -18,6 +34,7 @@ Lua Runner for Cangjie 是一个专为仓颉语言设计的轻量级、高性能
|
||||
|
||||
- **轻量级集成**:基于 Lua 5.4,通过 FFI 直接与仓颉语言交互,性能损耗小。
|
||||
- **栈式管道模式**:支持脚本间基于栈的数据隐式传递,实现类似函数式管道的调用链。
|
||||
- **预加载函数**:提供 `loadFunction`/`callFunction`/`unloadFunction`,预加载一次 Lua 函数到内存(Registry),顶层代码仅执行一次,后续可多次调用,与管道模式互不干扰。
|
||||
- **doString**:直接执行 Lua 代码字符串,无需写入文件。
|
||||
- **I/O 重定向**:可自定义 Lua 标准输入/输出(`print`, `io.read`)的回调函数,支持文件交换目录重定向。
|
||||
- **模块化管理**:提供 `load` 和 `unload` 方法,支持按名称动态加载和卸载 Lua 脚本模块,避免全局污染。
|
||||
|
||||
57
doc/api.md
57
doc/api.md
@ -20,8 +20,11 @@ Lua 虚拟机的主要管理类。
|
||||
|
||||
| 方法 | 描述 | 返回值 |
|
||||
| :--- | :--- | :--- |
|
||||
| `load(path: String, name: String): This` | 加载指定路径的 Lua 文件,编译后以 `name` 为标识压入内部栈。 | 实例本身 (`This`) |
|
||||
| `unload(name: String): This` | 从内部栈中卸载由 `name` 标识的模块。 | 实例本身 (`This`) |
|
||||
| `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`) |
|
||||
@ -63,6 +66,56 @@ Lua 虚拟机的主要管理类。
|
||||
| 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)
|
||||
|
||||
|
||||
@ -23,5 +23,8 @@
|
||||
#define NAPI_ERROR_FUNCS 5020 //函数调用错误
|
||||
#define NAPI_CHDIR_ERROR 5021 //切换工作目录失败,可能导致包搜索错误,日志写入路径错误等
|
||||
#define NAPI_NOCHUNK_FOUND 5022 //没有找到待调用的函数
|
||||
#define NAPI_FUNCTION_NOT_FOUND 5023 //通过callfunction调用时未找到指定函数
|
||||
#define NAPI_LOAD_FUNCTION_OVER 5024 //预加载函数数量超过上限
|
||||
#define NAPI_FUNCTION_NOT_VALID 5025 //预加载文件顶层执行后返回的不是函数
|
||||
|
||||
#endif
|
||||
@ -86,6 +86,65 @@ int run(void *selfd,const char *path,const char *arg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// @brief 预加载lua文件到内存:加载文件、执行顶层代码、捕获返回的函数并存入 Registry,供后续 callfunction 多次调用
|
||||
/// @param selfd init_lua_runner返回的void指针
|
||||
/// @param path 需要加载的文件路径
|
||||
/// @param name 函数名称(callfunction/unloadfunction时使用)
|
||||
/// @return 0代表正常结束,-1表示异常退出
|
||||
int loadfunction(void *selfd,const char *path,const char *name)
|
||||
{
|
||||
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->loadfunction(path,name)==-1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// @brief 卸载预加载的函数(释放 Registry 引用)
|
||||
/// @param selfd init_lua_runner返回的void指针
|
||||
/// @param name loadfunction加载时的函数名称
|
||||
/// @return 0代表正常结束,-1表示异常退出
|
||||
int unloadfunction(void *selfd,const char *name)
|
||||
{
|
||||
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->unloadfunction(name)==-1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// @brief 按名称调用已预加载的函数(从 Registry 取出,不消耗该函数,可重复调用)
|
||||
/// @param selfd init_lua_runner返回的void指针
|
||||
/// @param name loadfunction加载时的函数名称
|
||||
/// @param arg 传递给函数的单参数(可为NULL)
|
||||
/// @return 成功返回0,失败返回-1,并将lua出错原因拷贝至result中
|
||||
int callfunction(void *selfd,const char *name,const char *arg)
|
||||
{
|
||||
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->callfunction(name,arg)==-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 selfd init_lua_runner返回的void指针
|
||||
/// @param target 执行语句(字符串类型)
|
||||
|
||||
@ -55,6 +55,26 @@ int load_lib(void *selfd,const char *path,const char *name);
|
||||
/// @return 成功返回0,失败返回-1
|
||||
int unload_lib(void *selfd,const char *name);
|
||||
|
||||
/// @brief 预加载lua文件到内存:加载文件、执行顶层代码、捕获返回的函数并存入 Registry,供后续 callfunction 多次调用
|
||||
/// @param selfd init_lua_runner返回的void指针
|
||||
/// @param path 需要加载的文件路径
|
||||
/// @param name 函数名称(callfunction/unloadfunction时使用)
|
||||
/// @return 0代表正常结束,-1表示异常退出
|
||||
int loadfunction(void *selfd,const char *path,const char *name);
|
||||
|
||||
/// @brief 卸载预加载的函数(释放 Registry 引用)
|
||||
/// @param selfd init_lua_runner返回的void指针
|
||||
/// @param name loadfunction加载时的函数名称
|
||||
/// @return 0代表正常结束,-1表示异常退出
|
||||
int unloadfunction(void *selfd,const char *name);
|
||||
|
||||
/// @brief 按名称调用已预加载的函数(从 Registry 取出,不消耗该函数,可重复调用)
|
||||
/// @param selfd init_lua_runner返回的void指针
|
||||
/// @param name loadfunction加载时的函数名称
|
||||
/// @param arg 传递给函数的单参数(可为NULL)
|
||||
/// @return 成功返回0,失败返回-1,并将lua出错原因拷贝至result中
|
||||
int callfunction(void *selfd,const char *name,const char *arg);
|
||||
|
||||
/// @brief 运行lua脚本
|
||||
/// @param selfd init_lua_runner返回的void指针
|
||||
/// @param path 脚本路径
|
||||
|
||||
@ -25,7 +25,7 @@ Lua_runner::Lua_runner(const char *path)
|
||||
luaL_dostring(this->L, save_stdlib_code);
|
||||
/*if(luaL_dostring(this->L,
|
||||
"package.path = \";/data/storage/el2/luapkg/?/init.lua\"") != LUA_OK)//TODO 修改加载库的默认路径指向沙盒内路径
|
||||
errno = NAPI_LUA_INITFAIL;//加载默认路径*/
|
||||
errno = NAPI_LUA_INITFAIL;//加载默认路径*/
|
||||
if(path !=NULL)
|
||||
{
|
||||
char buf[1024];
|
||||
@ -36,6 +36,7 @@ Lua_runner::Lua_runner(const char *path)
|
||||
errno = NAPI_LUA_INITFAIL;
|
||||
}
|
||||
this->pkg_cont = 0;
|
||||
this->func_cont = 0;
|
||||
}
|
||||
|
||||
Lua_runner::~Lua_runner()
|
||||
@ -160,12 +161,161 @@ int Lua_runner::clean()//进行新一轮调用前一定要先clean清除上个
|
||||
lua_remove(this->L,this->pkgs[i].ref);
|
||||
}
|
||||
this->pkg_cont = 0;
|
||||
// 清理预加载函数
|
||||
for(int i = 0; i < this->func_cont; i++)
|
||||
{
|
||||
luaL_unref(this->L, LUA_REGISTRYINDEX, this->funcs[i].ref);
|
||||
}
|
||||
this->func_cont = 0;
|
||||
this->reslt ="nil";
|
||||
lua_settop(this->L, 0);//清空栈
|
||||
return 0;
|
||||
}
|
||||
|
||||
//TODO 支持更多类型的参数
|
||||
int Lua_runner::loadfunction(const char *path,const char *name)
|
||||
{
|
||||
if(!this->check_luastatue())
|
||||
return -1;
|
||||
if(!this->check_file_exists(path))
|
||||
{
|
||||
errno = NAPI_LOAD_FILE_ERROR;
|
||||
return -1;
|
||||
}
|
||||
if(this->func_cont >= MAX_LUA_FUNC)
|
||||
{
|
||||
errno = NAPI_LOAD_FUNCTION_OVER;
|
||||
return -1;
|
||||
}
|
||||
// 检查是否已存在同名函数
|
||||
for(int i = 0; i < this->func_cont; i++)
|
||||
{
|
||||
if(strcmp(this->funcs[i].name, name) == 0)
|
||||
{
|
||||
errno = NAPI_FUNCTION_NOT_FOUND;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 加载文件到栈顶(编译成 chunk)
|
||||
if(luaL_loadfile(this->L, path) != LUA_OK)
|
||||
{
|
||||
const char *ret = lua_tostring(this->L, -1);
|
||||
this->reslt = ret;
|
||||
errno = NAPI_LOAD_FILE_ERROR;
|
||||
lua_pop(this->L, 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 2. 执行 chunk(顶层代码),期望返回一个函数
|
||||
if(lua_pcall(this->L, 0, 1, 0) != LUA_OK)
|
||||
{
|
||||
errno = NAPI_SCRIPT_ERROR;
|
||||
this->reslt = lua_tostring(this->L, -1);
|
||||
lua_pop(this->L, 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 3. 检查栈顶是否为函数
|
||||
if(!lua_isfunction(this->L, -1))
|
||||
{
|
||||
errno = NAPI_FUNCTION_NOT_VALID;
|
||||
lua_pop(this->L, 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 4. 存入 Lua Registry(绝对引用,不受栈变化影响)
|
||||
int ref = luaL_ref(this->L, LUA_REGISTRYINDEX);
|
||||
|
||||
// 5. 记录到 funcs 表
|
||||
snprintf(this->funcs[this->func_cont].name, 128, "%s", name);
|
||||
this->funcs[this->func_cont].ref = ref;
|
||||
this->func_cont++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Lua_runner::unloadfunction(const char *name)
|
||||
{
|
||||
int sig = -1;
|
||||
for(int i = 0; i < this->func_cont; i++)
|
||||
{
|
||||
if(strcmp(this->funcs[i].name, name) == 0)
|
||||
{
|
||||
sig = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(sig == -1)
|
||||
{
|
||||
errno = NAPI_FUNCTION_NOT_FOUND;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!this->check_luastatue())
|
||||
return -1;
|
||||
|
||||
// 释放 Registry 引用
|
||||
luaL_unref(this->L, LUA_REGISTRYINDEX, this->funcs[sig].ref);
|
||||
|
||||
// 从 funcs 表中移除
|
||||
for(int j = sig + 1; j < this->func_cont; j++)
|
||||
{
|
||||
this->funcs[j - 1] = this->funcs[j];
|
||||
}
|
||||
this->func_cont--;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Lua_runner::callfunction(const char *name,const char *arg)
|
||||
{
|
||||
if(!this->check_luastatue())
|
||||
return -1;
|
||||
|
||||
// 在 funcs 表中查找(预加载函数表,独立于管道栈 pkgs[])
|
||||
int sig = -1;
|
||||
for(int i = 0; i < this->func_cont; i++)
|
||||
{
|
||||
if(strcmp(this->funcs[i].name, name) == 0)
|
||||
{
|
||||
sig = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(sig == -1)
|
||||
{
|
||||
errno = NAPI_FUNCTION_NOT_FOUND;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 从 Lua Registry 中取出函数(绝对引用,不受栈变化影响)
|
||||
lua_rawgeti(this->L, LUA_REGISTRYINDEX, this->funcs[sig].ref);
|
||||
|
||||
// 如果有参数,压入参数
|
||||
if(arg != NULL)
|
||||
{
|
||||
lua_pushlstring(this->L, arg, strlen(arg));
|
||||
}
|
||||
|
||||
int argcount = (arg != NULL) ? 1 : 0;
|
||||
|
||||
if(lua_pcall(this->L, argcount, LUA_MULTRET, 0) == LUA_OK)
|
||||
{
|
||||
if(lua_isstring(this->L, -1))
|
||||
this->reslt = lua_tostring(this->L, -1);
|
||||
lua_pop(this->L, 1);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
errno = NAPI_SCRIPT_ERROR;
|
||||
this->reslt = lua_tostring(this->L, -1);
|
||||
lua_pop(this->L, 1);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int Lua_runner::run(const char *path,const char *arg)
|
||||
{
|
||||
if(!this->check_luastatue())//检查lua状态机状态
|
||||
|
||||
@ -15,17 +15,28 @@ typedef struct loaded_package
|
||||
|
||||
}ld_pkg;
|
||||
|
||||
// 预加载函数表:使用 luaL_ref 存入 Lua Registry,ref 为绝对索引,不受栈变化影响
|
||||
#define MAX_LUA_FUNC 20
|
||||
typedef struct loaded_function
|
||||
{
|
||||
char name[128];
|
||||
int ref; // luaL_ref 返回的绝对索引
|
||||
|
||||
}ld_func;
|
||||
|
||||
#ifdef __cplusplus
|
||||
class Lua_runner//lua运行器对象
|
||||
{
|
||||
public:
|
||||
Lua_runner(const char *path = NULL);//创建lua状态机并持久化持有
|
||||
~Lua_runner();//销毁状态机
|
||||
int load_lib(const char *path,const char *name);//加载库
|
||||
int load_lib(const char *path,const char *name);//加载库(块压栈)
|
||||
int unload_lib(const char *name);//解除加载
|
||||
int loadfunction(const char *path,const char *name);//预加载函数(顶层执行,结果存入独立函数表)
|
||||
int unloadfunction(const char *name);//卸载预加载函数
|
||||
int callfunction(const char *name,const char *arg);//按名称调用已预加载的函数
|
||||
int run(const char *path = NULL,const char *arg = NULL);
|
||||
int clean();//清除状态机缓存
|
||||
//int callfunction();
|
||||
int dostring(const char *target);
|
||||
std::string reslt;
|
||||
lua_State *get_lua_State();
|
||||
@ -33,6 +44,8 @@ class Lua_runner//lua运行器对象
|
||||
lua_State *L;
|
||||
int pkg_cont;
|
||||
ld_pkg pkgs[MAX_LUA_LIB];
|
||||
int func_cont;
|
||||
ld_func funcs[MAX_LUA_FUNC];
|
||||
bool check_luastatue();
|
||||
bool check_file_exists(const char *path);
|
||||
};
|
||||
|
||||
@ -15,6 +15,12 @@ foreign func run(selfd: CPointer<Unit>, path: CString, arg: CString): Int32
|
||||
@C
|
||||
foreign func dostring(selfd: CPointer<Unit>,target: CString): Int32
|
||||
@C
|
||||
foreign func loadfunction(selfd: CPointer<Unit>,path: CString, name: CString): Int32
|
||||
@C
|
||||
foreign func unloadfunction(selfd: CPointer<Unit>,name: CString): Int32
|
||||
@C
|
||||
foreign func callfunction(selfd: CPointer<Unit>,name: CString, arg: CString): Int32
|
||||
@C
|
||||
foreign func cleanup(selfd: CPointer<Unit>): Int32
|
||||
@C
|
||||
foreign func get_errno(): Int32
|
||||
@ -94,6 +100,9 @@ public class LuaError <: Exception {
|
||||
case 5020 => "Error in callback functions"
|
||||
case 5021 => "change workdir error"
|
||||
case 5022 => "No callable chunk found"
|
||||
case 5023 => "Function not found"
|
||||
case 5024 => "Too many preloaded functions"
|
||||
case 5025 => "Preloaded file must return a function"
|
||||
case _ => "Unknown error"
|
||||
}
|
||||
}
|
||||
@ -230,6 +239,82 @@ public class LuaRunner {
|
||||
return this.cachedResult
|
||||
}
|
||||
|
||||
/// 加载函数到内存(预加载)
|
||||
///
|
||||
/// 加载 Lua 文件 → 编译成 chunk → 执行顶层代码(顶层代码只执行一次)→ 捕获返回的 Lua 函数 → 存入独立的 Registry 引用表。
|
||||
/// 与 load() 的块压栈不同:
|
||||
/// - load() 把 chunk 留在虚拟栈上(管道模式用),每次调用会重新执行
|
||||
/// - loadFunction() 执行一次顶层代码,保留返回的函数对象,后续 callFunction 多次调用不重复执行
|
||||
/// 注意:被加载的 Lua 文件必须在其顶层 return 一个函数。
|
||||
///
|
||||
/// @param path 函数所在文件路径
|
||||
/// @param name 函数名称(callFunction/unloadFunction 时使用)
|
||||
/// @return 当前实例
|
||||
/// @throws LuaError 当文件不存在/顶层未返回函数/已存在同名函数时抛出
|
||||
public func loadFunction(path: String, name: String): This {
|
||||
let pathPtr = toCStr(path)
|
||||
let namePtr = toCStr(name)
|
||||
let result = unsafe { loadfunction(this.handle, pathPtr, namePtr) }
|
||||
|
||||
unsafe {
|
||||
if (!pathPtr.isNull()) { LibC.free(pathPtr) }
|
||||
if (!namePtr.isNull()) { LibC.free(namePtr) }
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
throw LuaError(unsafe { get_errno() })
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/// 卸载预加载的函数(释放 Registry 引用)
|
||||
///
|
||||
/// @param name loadFunction 时指定的函数名称
|
||||
/// @return 当前实例
|
||||
/// @throws LuaError 当函数未找到时抛出
|
||||
public func unloadFunction(name: String): This {
|
||||
let namePtr = toCStr(name)
|
||||
let result = unsafe { unloadfunction(this.handle, namePtr) }
|
||||
|
||||
unsafe {
|
||||
if (!namePtr.isNull()) { LibC.free(namePtr) }
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
throw LuaError(unsafe { get_errno() })
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/// 调用已预加载的函数
|
||||
///
|
||||
/// 通过 name 在预加载函数表中查找,从 Lua Registry 取出函数对象后 pcall 调用。
|
||||
/// 不消耗该函数,因此可对同一函数重复调用(顶层代码只在 loadFunction 时执行一次)。
|
||||
/// 注意:与 runScript 管道模式完全独立,互不干扰。
|
||||
///
|
||||
/// @param name loadFunction 时指定的函数名称
|
||||
/// @param arg 传递给函数的单参数;传空字符串表示无参数(底层会转为 NULL)
|
||||
/// @return 函数执行的字符串结果
|
||||
/// @throws LuaError 当函数不存在或执行出错时抛出
|
||||
public func callFunction(name: String, arg: String): String {
|
||||
let namePtr = toCStr(name)
|
||||
// 空字符串会被 toCStr 转为 NULL,C 层据此识别“无参数”
|
||||
let argPtr = toCStr(arg)
|
||||
let code = unsafe { callfunction(this.handle, namePtr, argPtr) }
|
||||
|
||||
unsafe {
|
||||
if (!namePtr.isNull()) { LibC.free(namePtr) }
|
||||
if (!argPtr.isNull()) { LibC.free(argPtr) }
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
|
||||
@ -323,3 +323,178 @@ func testDefaultIOCallback(): Unit {
|
||||
let result = unsafe { defaultIO() }
|
||||
@Expect(result, 0)
|
||||
}
|
||||
|
||||
// ====================
|
||||
// 8. loadFunction / callFunction 预加载测试
|
||||
// ====================
|
||||
|
||||
@Test
|
||||
func testLoadFunctionAndCall(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_add.lua"), "add")
|
||||
let res = runner.callFunction("add", "hello")
|
||||
// 闭包带计数器,第一次调用应返回 call #1
|
||||
@Expect(res, "result: hello (call #1)")
|
||||
}
|
||||
|
||||
@Test
|
||||
func testCallFunctionMultipleTimes(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_multi_call.lua"), "multi")
|
||||
|
||||
let r1 = runner.callFunction("multi", "")
|
||||
@Expect(r1, "multi:init-done")
|
||||
|
||||
let r2 = runner.callFunction("multi", "")
|
||||
@Expect(r2, "multi:init-done")
|
||||
|
||||
let r3 = runner.callFunction("multi", "")
|
||||
@Expect(r3, "multi:init-done")
|
||||
|
||||
let r4 = runner.callFunction("multi", "")
|
||||
@Expect(r4, "multi:init-done")
|
||||
}
|
||||
|
||||
@Test
|
||||
func testCallFunctionCallCount(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_add.lua"), "add")
|
||||
|
||||
@Expect(runner.callFunction("add", "a"), "result: a (call #1)")
|
||||
@Expect(runner.callFunction("add", "b"), "result: b (call #2)")
|
||||
@Expect(runner.callFunction("add", "c"), "result: c (call #3)")
|
||||
}
|
||||
|
||||
@Test
|
||||
func testCallFunctionNotFound(): Unit {
|
||||
let runner = LuaRunner()
|
||||
try {
|
||||
runner.callFunction("nonexistent", "")
|
||||
fail("Should throw function not found error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5023)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testLoadFunctionFileNotFound(): Unit {
|
||||
let runner = LuaRunner()
|
||||
try {
|
||||
runner.loadFunction("/no/such/file.lua", "bad")
|
||||
fail("Should throw file not found error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5003)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testLoadFunctionNotAFunction(): Unit {
|
||||
let runner = LuaRunner()
|
||||
try {
|
||||
runner.loadFunction(scriptPath("func_not_a_function.lua"), "bad")
|
||||
fail("Should throw function not valid error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5025)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testLoadFunctionDuplicate(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_add.lua"), "dup")
|
||||
try {
|
||||
runner.loadFunction(scriptPath("func_add.lua"), "dup")
|
||||
fail("Should throw on duplicate name")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5023)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testCallFunctionRuntimeError(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_runtime_error.lua"), "bad")
|
||||
try {
|
||||
runner.callFunction("bad", "")
|
||||
fail("Should throw runtime error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5010)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testCallFunctionWithUnloadFunction(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_add.lua"), "add")
|
||||
@Expect(runner.callFunction("add", "before"), "result: before (call #1)")
|
||||
|
||||
runner.unloadFunction("add")
|
||||
|
||||
try {
|
||||
runner.callFunction("add", "")
|
||||
fail("Should throw function not found after unloadFunction")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5023)
|
||||
}
|
||||
|
||||
// 卸载后可重新加载同名函数
|
||||
runner.loadFunction(scriptPath("func_add.lua"), "add")
|
||||
@Expect(runner.callFunction("add", "after"), "result: after (call #1)")
|
||||
}
|
||||
|
||||
@Test
|
||||
func testLoadFunctionAndPipelineNoInterference(): Unit {
|
||||
let runner = LuaRunner()
|
||||
// 预加载函数(用独立 func 脚本)
|
||||
runner.loadFunction(scriptPath("func_add.lua"), "add")
|
||||
@Expect(runner.callFunction("add", "x"), "result: x (call #1)")
|
||||
|
||||
// 管道模式(用 pipeline 脚本,走 pkgs 栈)
|
||||
runner.load(scriptPath("pipeline_add_suffix.lua"), "add_suffix")
|
||||
.load(scriptPath("pipeline_to_upper.lua"), "to_upper")
|
||||
.load(scriptPath("pipeline_add_prefix.lua"), "add_prefix")
|
||||
|
||||
let initialData = runner.runScript(scriptPath("pipeline_generate_data.lua"), "hello")
|
||||
@Expect(initialData, "hello")
|
||||
|
||||
@Expect(runner.runScript("", ""), "[PREFIX] hello")
|
||||
@Expect(runner.runScript("", ""), "[PREFIX] HELLO")
|
||||
@Expect(runner.runScript("", ""), "[PREFIX] HELLO [SUFFIX]")
|
||||
|
||||
// 管道结束后,预加载函数仍可调用,且计数器继续
|
||||
@Expect(runner.callFunction("add", "y"), "result: y (call #2)")
|
||||
}
|
||||
|
||||
@Test
|
||||
func testLoadFunctionAndCleanCoexists(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_add.lua"), "add")
|
||||
@Expect(runner.callFunction("add", "x"), "result: x (call #1)")
|
||||
|
||||
runner.clear()
|
||||
|
||||
try {
|
||||
runner.callFunction("add", "")
|
||||
fail("Should throw after clean")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5023)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testLoadFunctionOverflow(): Unit {
|
||||
ensureGeneratedScriptsDir()
|
||||
let runner = LuaRunner()
|
||||
|
||||
for (i in 0..20) {
|
||||
let name = "overflow_func_${i}"
|
||||
let path = generatedScriptPath(name + ".lua")
|
||||
File.writeTo(path, ("return function() return \"" + name + "\" end").toArray())
|
||||
try {
|
||||
runner.loadFunction(path, name)
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5024)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
test/scripts/func_add.lua
Normal file
6
test/scripts/func_add.lua
Normal file
@ -0,0 +1,6 @@
|
||||
-- func_add.lua: 顶层执行一次(计数),返回闭包函数供 callFunction 多次调用
|
||||
local callCount = 0
|
||||
return function(input)
|
||||
callCount = callCount + 1
|
||||
return "result: " .. input .. " (call #" .. callCount .. ")"
|
||||
end
|
||||
5
test/scripts/func_multi_call.lua
Normal file
5
test/scripts/func_multi_call.lua
Normal file
@ -0,0 +1,5 @@
|
||||
-- func_multi_call.lua: 顶层执行一次(初始化),返回无参闭包
|
||||
local init = "init-done"
|
||||
return function()
|
||||
return "multi:" .. init
|
||||
end
|
||||
2
test/scripts/func_not_a_function.lua
Normal file
2
test/scripts/func_not_a_function.lua
Normal file
@ -0,0 +1,2 @@
|
||||
-- func_not_a_function.lua: 顶层返回非函数,loadFunction 应报 5025
|
||||
return "not a function"
|
||||
5
test/scripts/func_runtime_error.lua
Normal file
5
test/scripts/func_runtime_error.lua
Normal file
@ -0,0 +1,5 @@
|
||||
-- func_runtime_error.lua: 返回会在运行时抛错的函数
|
||||
return function()
|
||||
local x = nil
|
||||
return x.nonexistent
|
||||
end
|
||||
@ -326,6 +326,166 @@ TEST_F(LuaCjApiTest, DoStringNilReturn) {
|
||||
EXPECT_EQ(get_errno(), NAPI_SCRIPT_BAD_RET);
|
||||
}
|
||||
|
||||
// ==================== loadfunction / callfunction 预加载测试 ====================
|
||||
|
||||
// 基础预加载 + 调用
|
||||
TEST_F(LuaCjApiTest, LoadFunctionAndCall) {
|
||||
int ret = loadfunction(runner, get_script_path("func_add.lua").c_str(), "add");
|
||||
EXPECT_EQ(ret, 0);
|
||||
ret = callfunction(runner, "add", "hello");
|
||||
EXPECT_EQ(ret, 0);
|
||||
EXPECT_STREQ(getresult(runner), "result: hello (call #1)");
|
||||
}
|
||||
|
||||
// 同一函数多次调用:顶层只执行一次,计数器持续累加
|
||||
TEST_F(LuaCjApiTest, CallFunctionMultipleTimes) {
|
||||
ASSERT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "add"), 0);
|
||||
|
||||
EXPECT_EQ(callfunction(runner, "add", "a"), 0);
|
||||
EXPECT_STREQ(getresult(runner), "result: a (call #1)");
|
||||
|
||||
EXPECT_EQ(callfunction(runner, "add", "b"), 0);
|
||||
EXPECT_STREQ(getresult(runner), "result: b (call #2)");
|
||||
|
||||
EXPECT_EQ(callfunction(runner, "add", "c"), 0);
|
||||
EXPECT_STREQ(getresult(runner), "result: c (call #3)");
|
||||
}
|
||||
|
||||
// 无参函数多次调用
|
||||
TEST_F(LuaCjApiTest, CallFunctionNoArgMultipleTimes) {
|
||||
ASSERT_EQ(loadfunction(runner, get_script_path("func_multi_call.lua").c_str(), "multi"), 0);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
EXPECT_EQ(callfunction(runner, "multi", NULL), 0);
|
||||
EXPECT_STREQ(getresult(runner), "multi:init-done");
|
||||
}
|
||||
}
|
||||
|
||||
// 调用未预加载的函数
|
||||
TEST_F(LuaCjApiTest, CallFunctionNotFound) {
|
||||
int ret = callfunction(runner, "nonexistent", NULL);
|
||||
EXPECT_EQ(ret, -1);
|
||||
EXPECT_EQ(get_errno(), NAPI_FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 预加载不存在的文件
|
||||
TEST_F(LuaCjApiTest, LoadFunctionFileNotFound) {
|
||||
int ret = loadfunction(runner, "/no/such/file.lua", "bad");
|
||||
EXPECT_EQ(ret, -1);
|
||||
EXPECT_EQ(get_errno(), NAPI_LOAD_FILE_ERROR);
|
||||
}
|
||||
|
||||
// 预加载顶层未返回函数的文件
|
||||
TEST_F(LuaCjApiTest, LoadFunctionNotAFunction) {
|
||||
int ret = loadfunction(runner, get_script_path("func_not_a_function.lua").c_str(), "bad");
|
||||
EXPECT_EQ(ret, -1);
|
||||
EXPECT_EQ(get_errno(), NAPI_FUNCTION_NOT_VALID);
|
||||
}
|
||||
|
||||
// 预加载同名函数
|
||||
TEST_F(LuaCjApiTest, LoadFunctionDuplicate) {
|
||||
ASSERT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "dup"), 0);
|
||||
int ret = loadfunction(runner, get_script_path("func_add.lua").c_str(), "dup");
|
||||
EXPECT_EQ(ret, -1);
|
||||
EXPECT_EQ(get_errno(), NAPI_FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 运行时错误
|
||||
TEST_F(LuaCjApiTest, CallFunctionRuntimeError) {
|
||||
ASSERT_EQ(loadfunction(runner, get_script_path("func_runtime_error.lua").c_str(), "bad"), 0);
|
||||
int ret = callfunction(runner, "bad", NULL);
|
||||
EXPECT_EQ(ret, -1);
|
||||
EXPECT_EQ(get_errno(), NAPI_SCRIPT_ERROR);
|
||||
}
|
||||
|
||||
// unloadfunction 后再调用应报 5023,且可重新加载
|
||||
TEST_F(LuaCjApiTest, CallFunctionWithUnloadFunction) {
|
||||
ASSERT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "add"), 0);
|
||||
EXPECT_EQ(callfunction(runner, "add", "before"), 0);
|
||||
EXPECT_STREQ(getresult(runner), "result: before (call #1)");
|
||||
|
||||
EXPECT_EQ(unloadfunction(runner, "add"), 0);
|
||||
|
||||
int ret = callfunction(runner, "add", NULL);
|
||||
EXPECT_EQ(ret, -1);
|
||||
EXPECT_EQ(get_errno(), NAPI_FUNCTION_NOT_FOUND);
|
||||
|
||||
// 卸载后可重新预加载,计数器重置
|
||||
EXPECT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "add"), 0);
|
||||
EXPECT_EQ(callfunction(runner, "add", "after"), 0);
|
||||
EXPECT_STREQ(getresult(runner), "result: after (call #1)");
|
||||
}
|
||||
|
||||
// 预加载与管道模式共存,互不干扰:
|
||||
// 预加载函数走独立的 funcs[]+Registry,管道走 pkgs[] 栈
|
||||
TEST_F(LuaCjApiTest, LoadFunctionAndPipelineNoInterference) {
|
||||
// 预加载一个函数(独立于管道栈)
|
||||
ASSERT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "add"), 0);
|
||||
EXPECT_EQ(callfunction(runner, "add", "x"), 0);
|
||||
EXPECT_STREQ(getresult(runner), "result: x (call #1)");
|
||||
|
||||
// 管道模式照常(pkgs 栈)
|
||||
ASSERT_EQ(load_lib(runner, get_script_path("pipeline_add_suffix.lua").c_str(), "add_suffix"), 0);
|
||||
ASSERT_EQ(load_lib(runner, get_script_path("pipeline_to_upper.lua").c_str(), "to_upper"), 0);
|
||||
ASSERT_EQ(load_lib(runner, get_script_path("pipeline_add_prefix.lua").c_str(), "add_prefix"), 0);
|
||||
|
||||
int ret = run(runner, get_script_path("pipeline_generate_data.lua").c_str(), "hello");
|
||||
EXPECT_EQ(ret, 0);
|
||||
EXPECT_STREQ(getresult(runner), "hello");
|
||||
|
||||
ret = run(runner, nullptr, nullptr);
|
||||
EXPECT_EQ(ret, 0);
|
||||
EXPECT_STREQ(getresult(runner), "[PREFIX] hello");
|
||||
|
||||
ret = run(runner, nullptr, nullptr);
|
||||
EXPECT_EQ(ret, 0);
|
||||
EXPECT_STREQ(getresult(runner), "[PREFIX] HELLO");
|
||||
|
||||
ret = run(runner, nullptr, nullptr);
|
||||
EXPECT_EQ(ret, 0);
|
||||
EXPECT_STREQ(getresult(runner), "[PREFIX] HELLO [SUFFIX]");
|
||||
|
||||
// 管道跑完后,预加载函数仍可调用,计数器继续
|
||||
EXPECT_EQ(callfunction(runner, "add", "y"), 0);
|
||||
EXPECT_STREQ(getresult(runner), "result: y (call #2)");
|
||||
}
|
||||
|
||||
// 预加载数量上限:默认 MAX_LUA_FUNC=20
|
||||
TEST_F(LuaCjApiTest, LoadFunctionOverflow) {
|
||||
for (int i = 0; i < MAX_LUA_FUNC; i++) {
|
||||
char name[20];
|
||||
snprintf(name, sizeof(name), "func%d", i);
|
||||
std::string content = "return function() return '" + std::to_string(i) + "' end";
|
||||
std::string path = temp_dir + "/func" + std::to_string(i) + ".lua";
|
||||
FILE* f = fopen(path.c_str(), "w");
|
||||
ASSERT_NE(f, nullptr);
|
||||
fprintf(f, "%s", content.c_str());
|
||||
fclose(f);
|
||||
int ret = loadfunction(runner, path.c_str(), name);
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
|
||||
// 第 21 个应报 5024
|
||||
std::string path = temp_dir + "/overflow.lua";
|
||||
FILE* f = fopen(path.c_str(), "w");
|
||||
ASSERT_NE(f, nullptr);
|
||||
fprintf(f, "return function() return 'overflow' end");
|
||||
fclose(f);
|
||||
int ret = loadfunction(runner, path.c_str(), "overflow");
|
||||
EXPECT_EQ(ret, -1);
|
||||
EXPECT_EQ(get_errno(), NAPI_LOAD_FUNCTION_OVER);
|
||||
}
|
||||
|
||||
// cleanup 后预加载函数应失效
|
||||
TEST_F(LuaCjApiTest, CallFunctionAfterCleanup) {
|
||||
ASSERT_EQ(loadfunction(runner, get_script_path("func_add.lua").c_str(), "add"), 0);
|
||||
EXPECT_EQ(callfunction(runner, "add", "x"), 0);
|
||||
EXPECT_EQ(cleanup(runner), 0);
|
||||
|
||||
int ret = callfunction(runner, "add", NULL);
|
||||
EXPECT_EQ(ret, -1);
|
||||
EXPECT_EQ(get_errno(), NAPI_FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
|
||||
Reference in New Issue
Block a user