mirror of
https://gitcode.com/JianFeeeee/LuaCangjia_api.git
synced 2026-09-20 00:48:47 +00:00
232 lines
7.5 KiB
Plaintext
232 lines
7.5 KiB
Plaintext
package luacangjie_api
|
||
|
||
// ==================== 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 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 => "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<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 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
|
||
}
|
||
}
|