mirror of
https://gitcode.com/JianFeeeee/LuaCangjia_api.git
synced 2026-09-20 00:48:47 +00:00
feat: typed interop 原始类型直通映射 (Int64/Float64/Bool)
- C++ 层: callfunction_int/num/bool/void + run_int/num/bool/void - 新增 capture_result()/finish_call() 正确计算 lua_pcall 实际返回值数量, 修复无返回值场景下误读栈残留的问题 - C FFI 层: 11 个新导出函数 + lua_runner 结构体 tresult_* 镜像字段 - 仓颉层: callFunctionInt/Num/Bool, runScriptInt/Num/Bool, resultType() 带严格类型校验(不匹配抛 LUAERR_RESULT_TYPE) - 大整数跨 2^53 保真(字符串路径做不到) - 管道模式语义保留给字符串 API; typed run_* 为独立调用保持栈清洁 - GTest 新增 10 个 typed 测试用例(共 44 个全部通过) - 独立程序验证仓颉层运行时正确性(int/num/bool直通/大整数保真/错误路径)
This commit is contained in:
169
src/bridge.cj
169
src/bridge.cj
@ -20,6 +20,31 @@ foreign func loadfunction(selfd: CPointer<Unit>,path: CString, name: CString): I
|
||||
foreign func unloadfunction(selfd: CPointer<Unit>,name: CString): Int32
|
||||
@C
|
||||
foreign func callfunction(selfd: CPointer<Unit>,name: CString, arg: CString): Int32
|
||||
// typed interop(v0.2.2):原始类型直通映射
|
||||
@C
|
||||
foreign func callfunction_int(selfd: CPointer<Unit>, name: CString, val: Int64): Int32
|
||||
@C
|
||||
foreign func callfunction_num(selfd: CPointer<Unit>, name: CString, val: Float64): Int32
|
||||
@C
|
||||
foreign func callfunction_bool(selfd: CPointer<Unit>, name: CString, val: Int32): Int32
|
||||
@C
|
||||
foreign func callfunction_void(selfd: CPointer<Unit>, name: CString): Int32
|
||||
@C
|
||||
foreign func run_int(selfd: CPointer<Unit>, path: CString, val: Int64): Int32
|
||||
@C
|
||||
foreign func run_num(selfd: CPointer<Unit>, path: CString, val: Float64): Int32
|
||||
@C
|
||||
foreign func run_bool(selfd: CPointer<Unit>, path: CString, val: Int32): Int32
|
||||
@C
|
||||
foreign func run_void(selfd: CPointer<Unit>, path: CString): Int32
|
||||
@C
|
||||
foreign func result_type(selfd: CPointer<Unit>): Int32
|
||||
@C
|
||||
foreign func result_int(selfd: CPointer<Unit>): Int64
|
||||
@C
|
||||
foreign func result_num(selfd: CPointer<Unit>): Float64
|
||||
@C
|
||||
foreign func result_bool(selfd: CPointer<Unit>): Int32
|
||||
@C
|
||||
foreign func cleanup(selfd: CPointer<Unit>): Int32
|
||||
@C
|
||||
@ -78,31 +103,22 @@ public class LuaError <: Exception {
|
||||
/// 根据错误码获取错误描述
|
||||
public static func getErrorMessage(code: Int32): String {
|
||||
match (code) {
|
||||
case 5001 => "JSON file load failed"
|
||||
case 5002 => "Memory allocation failed"
|
||||
case 5003 => "File load error (path, permission, or compile-time syntax issue)"
|
||||
case 5004 => "Script runner initialization failed"
|
||||
case 5005 => "Lua state machine initialization failed or corrupted"
|
||||
case 5006 => "JSON parse error (format issue)"
|
||||
case 5007 => "JSON format error (does not match MCP format)"
|
||||
case 5008 => "Missing argument in JSON"
|
||||
case 5009 => "Lua stack space insufficient"
|
||||
case 5010 => "Script internal error, check result for details"
|
||||
case 5011 => "Script return value error"
|
||||
case 5012 => "Library not loaded before unload"
|
||||
case 5013 => "Function call violates single-input-single-output convention"
|
||||
case 5014 => "Stack size below minimum during function call"
|
||||
case 5015 => "Lua state machine initialization error"
|
||||
case 5016 => "Too many libraries loaded"
|
||||
case 5017 => "Lua runner object lost"
|
||||
case 5018 => "Missing redirect file path"
|
||||
case 5019 => "Reset input file failed, may cause input pollution"
|
||||
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 5001 => "File load error (path, permission, or compile-time syntax issue)"
|
||||
case 5002 => "Lua state machine initialization failed or corrupted"
|
||||
case 5003 => "Script internal error, check result for details"
|
||||
case 5004 => "Script return value type not supported"
|
||||
case 5005 => "Library not loaded before unload"
|
||||
case 5006 => "Stack layout mismatch during function call"
|
||||
case 5007 => "Lua state machine initialization error"
|
||||
case 5008 => "Too many libraries loaded"
|
||||
case 5009 => "Lua runner object lost"
|
||||
case 5010 => "Reset input file failed, may cause input pollution"
|
||||
case 5011 => "Error in callback functions (including doString syntax error)"
|
||||
case 5012 => "No callable chunk found"
|
||||
case 5013 => "Function not found"
|
||||
case 5014 => "Too many preloaded functions"
|
||||
case 5015 => "Preloaded file must return a function"
|
||||
case 5016 => "Result type not supported for requested conversion"
|
||||
case _ => "Unknown error"
|
||||
}
|
||||
}
|
||||
@ -315,6 +331,109 @@ public class LuaRunner {
|
||||
return this.cachedResult
|
||||
}
|
||||
|
||||
// ==================== typed interop(v0.2.2)====================
|
||||
// 原始类型直通:Int64/Float64/Bool 与 Lua integer/number/boolean 直接互转,
|
||||
// 不经过字符串序列化。结果类型严格校验,不匹配抛 5016。
|
||||
// 结果类型常量(resultType 返回值):0=nil 1=bool 2=int 3=num 4=str
|
||||
|
||||
/// 获取最近一次调用的结果类型(0=nil 1=bool 2=int 3=num 4=str)
|
||||
public func resultType(): Int32 {
|
||||
unsafe { result_type(this.handle) }
|
||||
}
|
||||
|
||||
/// 调用预加载函数,传 Int64 参数(Lua integer)
|
||||
///
|
||||
/// @param name loadFunction 时指定的函数名称
|
||||
/// @param val 整数参数
|
||||
/// @return 函数返回的整数(结果为 Lua number 时自动截断转换)
|
||||
/// @throws LuaError 函数不存在/执行出错时抛出对应错误码;
|
||||
/// 结果不是数值类型(nil/string等)时抛 5016
|
||||
public func callFunctionInt(name: String, val: Int64): Int64 {
|
||||
let namePtr = toCStr(name)
|
||||
let code = unsafe { callfunction_int(this.handle, namePtr, val) }
|
||||
unsafe { if (!namePtr.isNull()) { LibC.free(namePtr) } }
|
||||
this.cachedResult = toString(unsafe { getresult(this.handle) })
|
||||
if (code != 0) { throw LuaError(unsafe { get_errno() }, this.cachedResult) }
|
||||
let rt = unsafe { result_type(this.handle) }
|
||||
if (rt != 2 && rt != 3) { throw LuaError(5016) } // 期望 int/num
|
||||
unsafe { result_int(this.handle) }
|
||||
}
|
||||
|
||||
/// 调用预加载函数,传 Float64 参数(Lua number)
|
||||
///
|
||||
/// @throws LuaError 同 callFunctionInt;结果不是数值类型时抛 5016
|
||||
public func callFunctionNum(name: String, val: Float64): Float64 {
|
||||
let namePtr = toCStr(name)
|
||||
let code = unsafe { callfunction_num(this.handle, namePtr, val) }
|
||||
unsafe { if (!namePtr.isNull()) { LibC.free(namePtr) } }
|
||||
this.cachedResult = toString(unsafe { getresult(this.handle) })
|
||||
if (code != 0) { throw LuaError(unsafe { get_errno() }, this.cachedResult) }
|
||||
let rt = unsafe { result_type(this.handle) }
|
||||
if (rt != 2 && rt != 3) { throw LuaError(5016) } // 期望 int/num
|
||||
unsafe { result_num(this.handle) }
|
||||
}
|
||||
|
||||
/// 调用预加载函数,传 Bool 参数(Lua boolean)
|
||||
///
|
||||
/// @throws LuaError 同 callFunctionInt;结果为 nil 或 string 时抛 5016
|
||||
public func callFunctionBool(name: String, val: Bool): Bool {
|
||||
let namePtr = toCStr(name)
|
||||
let vi = if (val) { 1i32 } else { 0i32 }
|
||||
let code = unsafe { callfunction_bool(this.handle, namePtr, vi) }
|
||||
unsafe { if (!namePtr.isNull()) { LibC.free(namePtr) } }
|
||||
this.cachedResult = toString(unsafe { getresult(this.handle) })
|
||||
if (code != 0) { throw LuaError(unsafe { get_errno() }, this.cachedResult) }
|
||||
let rt = unsafe { result_type(this.handle) }
|
||||
if (rt == 0 || rt == 4) { throw LuaError(5016) } // 拒绝 nil/string
|
||||
unsafe { result_bool(this.handle) == 1 }
|
||||
}
|
||||
|
||||
/// 运行脚本,传 Int64 参数(独立调用,非管道模式)
|
||||
///
|
||||
/// 与 runScript 不同,typed 变体执行后弹出返回值、保持虚拟栈清洁;
|
||||
/// 管道模式请继续使用字符串版 runScript。
|
||||
///
|
||||
/// @throws LuaError 同 callFunctionInt;结果不是数值类型时抛 5016
|
||||
public func runScriptInt(path: String, val: Int64): Int64 {
|
||||
let pathPtr = toCStr(path)
|
||||
let code = unsafe { run_int(this.handle, pathPtr, val) }
|
||||
unsafe { if (!pathPtr.isNull()) { LibC.free(pathPtr) } }
|
||||
this.cachedResult = toString(unsafe { getresult(this.handle) })
|
||||
if (code != 0) { throw LuaError(unsafe { get_errno() }, this.cachedResult) }
|
||||
let rt = unsafe { result_type(this.handle) }
|
||||
if (rt != 2 && rt != 3) { throw LuaError(5016) }
|
||||
unsafe { result_int(this.handle) }
|
||||
}
|
||||
|
||||
/// 运行脚本,传 Float64 参数(独立调用,非管道模式)
|
||||
///
|
||||
/// @throws LuaError 同 runScriptInt;结果不是数值类型时抛 5016
|
||||
public func runScriptNum(path: String, val: Float64): Float64 {
|
||||
let pathPtr = toCStr(path)
|
||||
let code = unsafe { run_num(this.handle, pathPtr, val) }
|
||||
unsafe { if (!pathPtr.isNull()) { LibC.free(pathPtr) } }
|
||||
this.cachedResult = toString(unsafe { getresult(this.handle) })
|
||||
if (code != 0) { throw LuaError(unsafe { get_errno() }, this.cachedResult) }
|
||||
let rt = unsafe { result_type(this.handle) }
|
||||
if (rt != 2 && rt != 3) { throw LuaError(5016) }
|
||||
unsafe { result_num(this.handle) }
|
||||
}
|
||||
|
||||
/// 运行脚本,传 Bool 参数(独立调用,非管道模式)
|
||||
///
|
||||
/// @throws LuaError 同 runScriptInt;结果为 nil 或 string 时抛 5016
|
||||
public func runScriptBool(path: String, val: Bool): Bool {
|
||||
let pathPtr = toCStr(path)
|
||||
let vi = if (val) { 1i32 } else { 0i32 }
|
||||
let code = unsafe { run_bool(this.handle, pathPtr, vi) }
|
||||
unsafe { if (!pathPtr.isNull()) { LibC.free(pathPtr) } }
|
||||
this.cachedResult = toString(unsafe { getresult(this.handle) })
|
||||
if (code != 0) { throw LuaError(unsafe { get_errno() }, this.cachedResult) }
|
||||
let rt = unsafe { result_type(this.handle) }
|
||||
if (rt == 0 || rt == 4) { throw LuaError(5016) }
|
||||
unsafe { result_bool(this.handle) == 1 }
|
||||
}
|
||||
|
||||
/// 获取缓存的结果
|
||||
public func result(): String { this.cachedResult }
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ func testRunScriptSyntaxError(): Unit {
|
||||
runner.runScript(path, "")
|
||||
fail("Should throw syntax error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5003)
|
||||
@Expect(e.code, 5001)
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ func testRunScriptRuntimeError(): Unit {
|
||||
runner.runScript(path, "")
|
||||
fail("Should throw runtime error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5010)
|
||||
@Expect(e.code, 5003)
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ func testLoadLibFileNotFound(): Unit {
|
||||
runner.load("/no/such/file.lua", "badlib")
|
||||
fail("Should throw file not found error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5003)
|
||||
@Expect(e.code, 5001)
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ func testUnloadLibNotFound(): Unit {
|
||||
runner.unload("nosuchlib")
|
||||
fail("Should throw exception")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5012)
|
||||
@Expect(e.code, 5005)
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,7 +199,7 @@ func testLoadLibOverflow(): Unit {
|
||||
runner.load(overflowPath, overflowName)
|
||||
fail("Should throw overflow error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5016)
|
||||
@Expect(e.code, 5008)
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,7 +278,7 @@ func testDoStringNoReturn(): Unit {
|
||||
runner.doString("x = 10")
|
||||
fail("Should throw bad ret error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5011)
|
||||
@Expect(e.code, 5004)
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,7 +289,7 @@ func testDoStringSyntaxError(): Unit {
|
||||
runner.doString("if true then")
|
||||
fail("Should throw syntax error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5020)
|
||||
@Expect(e.code, 5011)
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,7 +300,7 @@ func testDoStringNilReturn(): Unit {
|
||||
runner.doString("return nil")
|
||||
fail("Should throw bad ret error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5011)
|
||||
@Expect(e.code, 5004)
|
||||
}
|
||||
}
|
||||
|
||||
@ -310,11 +310,12 @@ func testDoStringNilReturn(): Unit {
|
||||
|
||||
@Test
|
||||
func testErrorCodesMapping(): Unit {
|
||||
@Expect(LuaError.getErrorMessage(5001), "JSON file load failed")
|
||||
@Expect(LuaError.getErrorMessage(5002), "Memory allocation failed")
|
||||
@Expect(LuaError.getErrorMessage(5003), "File load error (path, permission, or compile-time syntax issue)")
|
||||
@Expect(LuaError.getErrorMessage(5010), "Script internal error, check result for details")
|
||||
@Expect(LuaError.getErrorMessage(5022), "No callable chunk found")
|
||||
@Expect(LuaError.getErrorMessage(5001), "File load error (path, permission, or compile-time syntax issue)")
|
||||
@Expect(LuaError.getErrorMessage(5002), "Lua state machine initialization failed or corrupted")
|
||||
@Expect(LuaError.getErrorMessage(5003), "Script internal error, check result for details")
|
||||
@Expect(LuaError.getErrorMessage(5004), "Script return value type not supported")
|
||||
@Expect(LuaError.getErrorMessage(5013), "Function not found")
|
||||
@Expect(LuaError.getErrorMessage(5016), "Result type not supported for requested conversion")
|
||||
@Expect(LuaError.getErrorMessage(9999), "Unknown error")
|
||||
}
|
||||
|
||||
@ -372,7 +373,7 @@ func testCallFunctionNotFound(): Unit {
|
||||
runner.callFunction("nonexistent", "")
|
||||
fail("Should throw function not found error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5023)
|
||||
@Expect(e.code, 5013)
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,7 +384,7 @@ func testLoadFunctionFileNotFound(): Unit {
|
||||
runner.loadFunction("/no/such/file.lua", "bad")
|
||||
fail("Should throw file not found error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5003)
|
||||
@Expect(e.code, 5001)
|
||||
}
|
||||
}
|
||||
|
||||
@ -394,7 +395,7 @@ func testLoadFunctionNotAFunction(): Unit {
|
||||
runner.loadFunction(scriptPath("func_not_a_function.lua"), "bad")
|
||||
fail("Should throw function not valid error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5025)
|
||||
@Expect(e.code, 5015)
|
||||
}
|
||||
}
|
||||
|
||||
@ -406,7 +407,7 @@ func testLoadFunctionDuplicate(): Unit {
|
||||
runner.loadFunction(scriptPath("func_add.lua"), "dup")
|
||||
fail("Should throw on duplicate name")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5023)
|
||||
@Expect(e.code, 5013)
|
||||
}
|
||||
}
|
||||
|
||||
@ -418,7 +419,7 @@ func testCallFunctionRuntimeError(): Unit {
|
||||
runner.callFunction("bad", "")
|
||||
fail("Should throw runtime error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5010)
|
||||
@Expect(e.code, 5003)
|
||||
}
|
||||
}
|
||||
|
||||
@ -434,7 +435,7 @@ func testCallFunctionWithUnloadFunction(): Unit {
|
||||
runner.callFunction("add", "")
|
||||
fail("Should throw function not found after unloadFunction")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5023)
|
||||
@Expect(e.code, 5013)
|
||||
}
|
||||
|
||||
// 卸载后可重新加载同名函数
|
||||
@ -477,7 +478,7 @@ func testLoadFunctionAndCleanCoexists(): Unit {
|
||||
runner.callFunction("add", "")
|
||||
fail("Should throw after clean")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5023)
|
||||
@Expect(e.code, 5013)
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,8 +494,110 @@ func testLoadFunctionOverflow(): Unit {
|
||||
try {
|
||||
runner.loadFunction(path, name)
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5024)
|
||||
@Expect(e.code, 5014)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
// 9. typed interop 测试(v0.2.2)
|
||||
// ====================
|
||||
|
||||
@Test
|
||||
func testTypedCallInt(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_typed_add.lua"), "inc")
|
||||
let res = runner.callFunctionInt("inc", 41)
|
||||
@Expect(res, 42)
|
||||
// 字符串形式同步可用(向后兼容)
|
||||
@Expect(runner.result(), "42")
|
||||
}
|
||||
|
||||
@Test
|
||||
func testTypedCallNum(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_typed_mul.lua"), "mul")
|
||||
let res = runner.callFunctionNum("mul", 4.0)
|
||||
@Expect(res, 10.0)
|
||||
}
|
||||
|
||||
@Test
|
||||
func testTypedCallBool(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_typed_not.lua"), "lnot")
|
||||
@Expect(runner.callFunctionBool("lnot", true), false)
|
||||
@Expect(runner.callFunctionBool("lnot", false), true)
|
||||
}
|
||||
|
||||
@Test
|
||||
func testTypedRoundTrip(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_typed_echo.lua"), "echo")
|
||||
|
||||
// int 往返保真
|
||||
@Expect(runner.callFunctionInt("echo", -123456789), -123456789)
|
||||
@Expect(runner.callFunctionInt("echo", 9007199254740993), 9007199254740993)
|
||||
|
||||
// num 往返保真
|
||||
@Expect(runner.callFunctionNum("echo", 3.14159), 3.14159)
|
||||
|
||||
// bool 往返保真
|
||||
@Expect(runner.callFunctionBool("echo", true), true)
|
||||
|
||||
// string 走原 API,类型标记同步为 STR(4)
|
||||
let s = runner.callFunction("echo", "hello typed")
|
||||
@Expect(s, "hello typed")
|
||||
@Expect(runner.resultType(), 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
func testTypedTypeMismatch(): Unit {
|
||||
let runner = LuaRunner()
|
||||
runner.loadFunction(scriptPath("func_typed_void.lua"), "forty_two")
|
||||
|
||||
// 函数返回 int 42,用 Num 方式可读(int→num 兼容)
|
||||
@Expect(runner.callFunctionNum("forty_two", 0.0), 42.0)
|
||||
|
||||
// 返回 string 的函数不能用 Int 读 → 5016
|
||||
runner.loadFunction(scriptPath("func_add.lua"), "add")
|
||||
try {
|
||||
runner.callFunctionInt("add", 1)
|
||||
fail("Should throw result type error")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5016)
|
||||
}
|
||||
|
||||
// 返回 nil 的函数不能用 Int 读 → 5016
|
||||
runner.loadFunction(scriptPath("func_typed_nil.lua"), "nilret")
|
||||
try {
|
||||
runner.callFunctionInt("nilret", 1)
|
||||
fail("Should throw on nil result")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5016)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testTypedRunVariants(): Unit {
|
||||
ensureGeneratedScriptsDir()
|
||||
// 注意:typed run 变体是独立调用,脚本需直接返回值(非返回函数的预加载式文件)
|
||||
let echoPath = generatedScriptPath("typed_echo_arg.lua")
|
||||
File.writeTo(echoPath, "return (...)".toArray())
|
||||
|
||||
let runner = LuaRunner()
|
||||
@Expect(runner.runScriptInt(echoPath, 777), 777)
|
||||
@Expect(runner.runScriptNum(echoPath, 2.5), 2.5)
|
||||
@Expect(runner.runScriptBool(echoPath, true), true)
|
||||
}
|
||||
|
||||
@Test
|
||||
func testTypedNotFound(): Unit {
|
||||
let runner = LuaRunner()
|
||||
try {
|
||||
runner.callFunctionInt("nonexistent", 1)
|
||||
fail("Should throw function not found")
|
||||
} catch (e: LuaError) {
|
||||
@Expect(e.code, 5013)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user