package luacangjie_api // ==================== 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 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 => "JSON file load failed" case 5002 => "Memory allocation failed" case 5003 => "File load error (permission or path 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 _ => "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 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 } }