Files
LuaCangjia_api/src/bridge.cj
JianFeeeee 237aab3f36 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直通/大整数保真/错误路径)
2026-08-25 09:29:24 +08:00

455 lines
18 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package luaRunner
// ==================== FFI底层 ====================
type IOCallback = CFunc<() -> Int32>
@C
foreign func init_lua_runner(pathio: CString, pkgpath: CString, input: IOCallback, output: IOCallback): CPointer<Unit>
@C
foreign func load_lib(selfd: CPointer<Unit>, path: CString, name: CString): Int32
@C
foreign func unload_lib(selfd: CPointer<Unit>, name: CString): Int32
@C
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
// typed interopv0.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
foreign func get_errno(): Int32
@C
foreign func getresult(selfd: CPointer<Unit>): CString
@C
foreign func free_lua(selfd: CPointer<Unit>): Int32
// ==================== 工具函数 ====================
@C
func defaultIO(): Int32 {
// 默认输入回调返回0表示成功
0
}
// 生成NULL指针
private func nullCString(): CString {
unsafe { CString(CPointer<UInt8>()) }
}
// String转CString空字符串转为NULL
private func toCStr(s: String): CString {
if (s.isEmpty()) {
nullCString()
} else {
// LibC.mallocCString 返回 Option<CString>,需要解包
unsafe { LibC.mallocCString(s) }
}
}
// CString转String
private func toString(ptr: CString): String {
if (ptr.isNull()) { return "" }
ptr.toString()
}
// ==================== 异常 ====================
/// Lua运行时错误类
public class LuaError <: Exception {
public let code: Int32
public init(code: Int32, msg: String) {
super(msg)
this.code = code
}
/// 便捷构造函数:只传入错误码,自动获取描述
public init(code: Int32) {
super(getErrorMessage(code))
this.code = code
}
/// 根据错误码获取错误描述
public static func getErrorMessage(code: Int32): String {
match (code) {
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"
}
}
/// 获取当前错误的描述
public func getMessage(): String {
getErrorMessage(this.code)
}
}
// ==================== 主类 ====================
/// Lua运行管理类
public class LuaRunner {
private var handle: CPointer<Unit>
private var cachedResult: String = ""
private var ioInput: IOCallback
private var ioOutput: IOCallback
/// 构造lua状态机
///
/// @param input 类型function 用于lua内部触发输入时的调用的回调函数
/// @param output 类型function 用于lua内部触发输出时调用的回调函数
/// @param pathio 重定向io输出到pathio路径下
/// @param pkgpath 重定向lua状态机包搜索路径
/// @throws LuaError 当初始化失败(句柄为空)时抛出
public init(input!: IOCallback = defaultIO, output!: IOCallback = defaultIO, pathio!: String = "", pkgpath!: String = "") {
this.ioInput = input
this.ioOutput = output
let tmpio = toCStr(pathio)
let tmppkg = toCStr(pkgpath)
this.handle = unsafe { init_lua_runner(tmpio, tmppkg, input, output) }
// 释放非NULL的C字符串内存
unsafe {
if (!tmpio.isNull()) { LibC.free(tmpio) }
if (!tmppkg.isNull()) { LibC.free(tmppkg) }
}
if (this.handle.isNull()) {
throw LuaError(-1, "LuaRunner initialization failed: handle is null")
}
}
~init() {
if (!this.handle.isNull()) {
unsafe { free_lua(this.handle) }
this.handle = CPointer<Unit>()
}
}
/// 加载动态库
///
/// @param path 库路径
/// @param name 库名称
/// @return 当前实例
/// @throws LuaError 当加载库失败时抛出
public func load(path: String, name: String): This {
let pathPtr = toCStr(path)
let namePtr = toCStr(name)
let result = unsafe { load_lib(this.handle, pathPtr, namePtr) }
// 释放临时分配的C字符串
unsafe {
if (!pathPtr.isNull()) { LibC.free(pathPtr) }
if (!namePtr.isNull()) { LibC.free(namePtr) }
}
if (result != 0) {
// [修正] 直接使用错误码构造异常,复用 LuaError 的错误码转描述机制
// 避免调用 getresult 读取未初始化的 C 层字符串
throw LuaError(unsafe { get_errno() })
}
return this
}
/// 卸载动态库
///
/// @param name 库名称
/// @return 当前实例
/// @throws LuaError 当卸载库失败时抛出
public func unload(name: String): This {
let namePtr = toCStr(name)
let result = unsafe { unload_lib(this.handle, namePtr) }
unsafe {
if (!namePtr.isNull()) { LibC.free(namePtr) }
}
if (result != 0) {
// [修正] 直接使用错误码构造异常,复用 LuaError 的错误码转描述机制
throw LuaError(unsafe { get_errno() })
}
return this
}
/// 运行Lua脚本
///
/// @param path 脚本路径
/// @param arg 传递给脚本的单参数;管道模式下由底层按栈布局自动计算调用参数数量
/// @return 脚本执行结果字符串
/// @throws LuaError 当脚本编译或运行出错时抛出
public func runScript(path: String, arg: String): String {
let pathPtr = toCStr(path)
let argPtr = toCStr(arg)
let code = unsafe { run(this.handle, pathPtr, argPtr) }
unsafe {
if (!pathPtr.isNull()) { LibC.free(pathPtr) }
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 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
}
/// 加载函数到内存(预加载)
///
/// 加载 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 转为 NULLC 层据此识别“无参数”
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
}
// ==================== typed interopv0.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 }
/// 获取错误信息
public func error(): String { toString(unsafe { getresult(this.handle) }) }
/// 清理Lua状态机资源
///
/// @return 当前实例
/// @throws LuaError 当清理失败时抛出
public func clear(): This {
let result = unsafe { cleanup(this.handle) }
if (result != 0) {
throw LuaError(unsafe { get_errno() }, "Cleanup failed")
}
return this
}
}