mirror of
https://gitcode.com/JianFeeeee/LuaCangjia_api.git
synced 2026-09-23 02:18:09 +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:
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user