Files
LuaCangjia_api/src/bridge.cj

232 lines
7.5 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 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
}
}