package luaRunner // ==================== FFI底层 ==================== type IOCallback = CFunc<() -> Int32> @C foreign func init_lua_runner(pathio: CString, pkgpath: CString, input: IOCallback, output: IOCallback): CPointer @C foreign func load_lib(selfd: CPointer, path: CString, name: CString): Int32 @C 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 loadfunction(selfd: CPointer,path: CString, name: CString): Int32 @C foreign func unloadfunction(selfd: CPointer,name: CString): Int32 @C foreign func callfunction(selfd: CPointer,name: CString, arg: CString): Int32 // typed interop(v0.2.2):原始类型直通映射 @C foreign func callfunction_int(selfd: CPointer, name: CString, val: Int64): Int32 @C foreign func callfunction_num(selfd: CPointer, name: CString, val: Float64): Int32 @C foreign func callfunction_bool(selfd: CPointer, name: CString, val: Int32): Int32 @C foreign func callfunction_void(selfd: CPointer, name: CString): Int32 @C foreign func run_int(selfd: CPointer, path: CString, val: Int64): Int32 @C foreign func run_num(selfd: CPointer, path: CString, val: Float64): Int32 @C foreign func run_bool(selfd: CPointer, path: CString, val: Int32): Int32 @C foreign func run_void(selfd: CPointer, path: CString): Int32 @C foreign func result_type(selfd: CPointer): Int32 @C foreign func result_int(selfd: CPointer): Int64 @C foreign func result_num(selfd: CPointer): Float64 @C foreign func result_bool(selfd: CPointer): Int32 @C foreign func cleanup(selfd: CPointer): Int32 @C foreign func get_errno(): Int32 @C foreign func getresult(selfd: CPointer): CString @C foreign func free_lua(selfd: CPointer): Int32 // ==================== 工具函数 ==================== @C func defaultIO(): Int32 { // 默认输入回调,返回0表示成功 0 } // 生成NULL指针 private func nullCString(): CString { unsafe { CString(CPointer()) } } // String转CString,空字符串转为NULL private func toCStr(s: String): CString { if (s.isEmpty()) { nullCString() } else { // LibC.mallocCString 返回 Option,需要解包 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 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() } } /// 加载动态库 /// /// @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 转为 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 } // ==================== 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 } /// 获取错误信息 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 } }