diff --git a/run.n b/run.n index f53e8b7..c85ae56 100644 Binary files a/run.n and b/run.n differ diff --git a/src/lhscript/LHScript.hx b/src/lhscript/LHScript.hx index 05a29c6..e56c663 100644 --- a/src/lhscript/LHScript.hx +++ b/src/lhscript/LHScript.hx @@ -6,10 +6,23 @@ import luahscript.LuaTools; import luahscript.LuaParser; import luahscript.exprs.LuaExpr; import luahscript.LuaAndParams; +import luahscript.exprs.*; +import luahscript.exprs.LuaError.LuaVariableType; import haxe.Constraints.IMap; @:access(luahscript.LuaInterp) class LHScript { + // Constants for LuaVariableType values + private static inline var LOCAL:String = "Local"; + private static inline var GLOBAL:String = "global"; + private static inline var FIELD:String = "Field"; + private static inline var UNKNOWN:String = ""; + + // LuaVariableType enum instances + private static var LOCAL_VAR_TYPE:LuaVariableType = cast LOCAL; + private static var GLOBAL_VAR_TYPE:LuaVariableType = cast GLOBAL; + private static var FIELD_VAR_TYPE:LuaVariableType = cast FIELD; + private static var UNKNOWN_VAR_TYPE:LuaVariableType = cast UNKNOWN; public var interp:LuaInterp; private var parser:LuaParser; private var scriptContent:String; @@ -132,6 +145,208 @@ class LHScript { } } + /** + * Executes a call based on an expression and arguments, encapsulating logic from LuaInterp's ECall case. + * @param e The expression representing the function or method to call. + * @param args An array of arguments to pass to the function or method. + * @return The result of the function call, or an error value. + */ + public function executeCall(e:luahscript.exprs.LuaExpr, args:Array):Dynamic { + try { + switch(e.expr) { + case EField(ef, f, isDouble): // Handles obj:method() and obj.method() + var obj:Dynamic = interp.expr(ef); + if(obj == null) { + var sb:Null = null; + var type:Null = null; + LuaTools.recursion(ef, function(e:luahscript.exprs.LuaExpr) { + switch(e.expr) { + case EIdent(id): + sb = id; + type = if(interp.locals.get(id) != null) LHScript.LOCAL; else LHScript.GLOBAL; + case EField(_, f): // luahscript.exprs.LuaExprDef.EField + sb = f; + type = LHScript.FIELD; + case EArray(_, _): // luahscript.exprs.LuaExprDef.EArray + sb = 'integer index'; + type = LHScript.FIELD; + default: + type = LHScript.UNKNOWN; + } + }); + return interp.error(EInvalidAccess(sb, cast type, LuaCheckType.checkType(obj)), ef.line); + } + + // Handle Class:new() syntax for Haxe class instantiation + if (isDouble && f == "new") { + var haxeClass:Class = null; + if (Std.isOfType(obj, Class)) { + haxeClass = cast obj; + } else { + var className:String = null; + if (Std.isOfType(obj, String)) { + className = cast obj; + } else { + if (obj != null) { + className = Type.getClassName(Type.getClass(obj)); + } + } + + if (className != null) { + haxeClass = Type.resolveClass(className); + } + + if (haxeClass == null) { + return interp.error(luahscript.exprs.LuaErrorDef.ECustom("Attempt to call 'new' on an object that is not a recognized class or type: " + Std.string(obj) + " (resolved class name: " + (className != null ? className : "null") + ")")); + } + } + + try { + var instance:Dynamic = null; + #if neko + if (Reflect.hasField(haxeClass, "__new__")) { + instance = Reflect.callMethod(haxeClass, Reflect.field(haxeClass, "__new__"), args); + } else { + #end + + if (haxeClass == String) { + if (args.length > 0 && luahscript.LuaCheckType.checkType(args[0]) == luahscript.LuaTyper.TSTRING) { + instance = args[0]; + } else { + instance = ""; + } + } else { + instance = Type.createInstance(haxeClass, args); + } + + #if neko + } + #end + + // Wrap instance in a LuaTable for meta-method support + var instanceWrapper = new luahscript.LuaTable(); + + var metaTable = new luahscript.LuaTable(); + metaTable.set("__index", function(table:Dynamic, propertyName:String):Dynamic { + if (Reflect.hasField(instance, propertyName)) { + var value = Reflect.field(instance, propertyName); + if (Reflect.isFunction(value)) { + return Reflect.makeVarArgs(function(varArgs:Array) { + var callArgs:Array = []; + if (varArgs != null) { + for(a in varArgs) callArgs.push(a); + } + return Reflect.callMethod(instance, value, callArgs); + }); + } + return value; + } else { + try { + var value = Reflect.getProperty(instance, propertyName); + if (Reflect.isFunction(value)) { + return Reflect.makeVarArgs(function(varArgs:Array) { + var callArgs:Array = []; + if (varArgs != null) { + for(a in varArgs) callArgs.push(a); + } + return Reflect.callMethod(instance, value, callArgs); + }); + } + return value; + } catch (e:Dynamic) { + return null; + } + } + }); + + metaTable.set("__newindex", function(table:Dynamic, propertyName:String, value:Dynamic):Void { + Reflect.setProperty(instance, propertyName, value); + }); + + instanceWrapper.metaTable = metaTable; + instanceWrapper.set("__instance", instance); + + return instanceWrapper; + + } catch (err:haxe.Exception) { + return interp.error(ECustom("Failed to instantiate or wrap class " + Type.getClassName(haxeClass) + ": " + Std.string(err)), ef.line); + } + } + + final func:Dynamic = interp.get(obj, f, isDouble); + if(func == null) { + var sb:Null = null; + var type:Null = null; + LuaTools.recursion(e, function(e:luahscript.exprs.LuaExpr) { // e is EField + switch(e.expr) { + case EIdent(id): + sb = id; + type = if(interp.locals.get(id) != null) LHScript.LOCAL; else LHScript.GLOBAL; + case EField(_, f_name): + sb = f_name; + type = LHScript.FIELD; + case EArray(_, _): + sb = 'integer index'; + type = LHScript.FIELD; + default: + type = LHScript.UNKNOWN; + } + }); + return interp.error(ECallInvalidValue(sb, type == LOCAL ? LOCAL_VAR_TYPE : (type == GLOBAL ? GLOBAL_VAR_TYPE : (type == FIELD ? FIELD_VAR_TYPE : UNKNOWN_VAR_TYPE)), TNIL), e.line); + } + if (isDouble) { + if (Reflect.isObject(obj) && !Std.isOfType(obj, luahscript.LuaTable)) { + var method = Reflect.getProperty(obj, f); + if (Reflect.isFunction(method)) { + return try { + Reflect.callMethod(obj, method, args); + } catch(e:haxe.Exception) { + throw interp.error(ECustom("Error calling method '" + f + "': " + Std.string(e)), 0); + } + } + #if neko + return try { + Reflect.callMethod(obj, func, args); + } catch(e:haxe.Exception) { + throw interp.error(ECustom("Error calling method '" + f + "' on Neko fallback: " + Std.string(e)), 0); + } + #end + } + args.insert(0, obj); + } + return try Reflect.callMethod(null, func, args) catch(e:haxe.Exception) throw interp.error(ECustom(Std.string(e)), 0); + case _: + var func:Dynamic = interp.expr(e); + if(func == null) { + var sb:Null = null; + var type:Null = null; + LuaTools.recursion(e, function(e:luahscript.exprs.LuaExpr) { + switch(e.expr) { + case EIdent(id): + sb = id; + type = if(interp.locals.get(id) != null) LHScript.LOCAL; else LHScript.GLOBAL; + case EField(_, f): + sb = f; + type = LHScript.FIELD; + case EArray(_, _): + sb = 'integer index'; + type = LHScript.FIELD; + default: + type = LHScript.UNKNOWN; + } + }); + return interp.error(ECallInvalidValue(sb, type == LOCAL ? LOCAL_VAR_TYPE : (type == GLOBAL ? GLOBAL_VAR_TYPE : (type == FIELD ? FIELD_VAR_TYPE : UNKNOWN_VAR_TYPE)), TNIL), e.line); + } + return try Reflect.callMethod(null, func, args) catch(e:haxe.Exception) throw interp.error(ECustom(Std.string(e)), 0); + } + } catch (err:Dynamic) { // Catching generic Dynamic as LuaInterp.error throws Dynamic + if (onError != null) { + onError(Std.string(err)); + } + return "FUNC_ERROR"; // Consistent with callFunc's error return + } + return null; // Should not be reached if logic is correct + } public function callFunc(funcName:String, ?args:Array):Dynamic { if (args == null) args = []; @@ -139,29 +354,28 @@ class LHScript { lastCalledFunction = funcName; lastCalledScript = this; + if (interp == null) { + if(onError != null) onError("Lua error (interp is null): " + funcName); + return "FUNC_CONT"; + } + + // Create an EIdent expression for the function name + var expr:luahscript.exprs.LuaExpr = { expr: luahscript.exprs.LuaExprDef.EIdent(funcName), line: 0 }; + try { - if (interp == null) return "FUNC_CONT"; - - var func = null; - try { - func = interp.globals.get(funcName); - } catch (e:Dynamic) { - return "FUNC_CONT"; - } - if (func == null) { - return "FUNC_CONT"; - } - - var result = Reflect.callMethod(null, func, args); + // Use the new executeCall method + var result = executeCall(expr, args); + // executeCall already handles errors and returns "FUNC_ERROR" or throws + // If it returns a result, it's successful. + // If it throws, it's caught below. return result; } catch (e:Dynamic) { if (onError != null) { onError("Lua error (" + funcName + "): " + Std.string(e)); } - return "FUNC_CONT"; + return "FUNC_CONT"; // Or "FUNC_ERROR" for consistency with executeCall's error handling } } - public function callMultipleFunctions(funcNames:Array, ?argsArray:Array> = null):Array { var results:Array = []; @@ -281,7 +495,7 @@ class LHScript { return interp; public function setinterp(interps:LuaInterp):Void { - interps = interp; + interp = interps; // Corrected assignment } public static function registerModule(name:String, script:LHScript):Void { @@ -375,13 +589,9 @@ class LHScript { var value = Reflect.field(instance, propertyName); // 如果是方法,返回绑定到实例的函数 if (Reflect.isFunction(value)) { - return Reflect.makeVarArgs(function(varArgs:Array) { - var callArgs:Array = []; - if (varArgs != null) { - for(a in varArgs) callArgs.push(a); - } - return Reflect.callMethod(instance, value, callArgs); - }); + return function(...args) { + return Reflect.callMethod(instance, value, args); + }; } return value; } else { @@ -389,13 +599,9 @@ class LHScript { var value = Reflect.getProperty(instance, propertyName); // 如果是方法,返回绑定到实例的函数 if (Reflect.isFunction(value)) { - return Reflect.makeVarArgs(function(varArgs:Array) { - var callArgs:Array = []; - if (varArgs != null) { - for(a in varArgs) callArgs.push(a); - } - return Reflect.callMethod(instance, value, callArgs); - }); + return function(...args) { + return Reflect.callMethod(instance, value, args); + }; } return value; } catch (e:Dynamic) { @@ -535,24 +741,32 @@ class LHScript { return null; })); - var originalPcall = interp.globals.get("pcall"); - interp.globals.set("pcall", Reflect.makeVarArgs(function(args:Array) { - if (args.length >= 1) { - var funcExpr = args[0]; - var callArgs = args.slice(1); - - try { - var result = callFunction(funcExpr, callArgs); - return luahscript.LuaAndParams.fromArray([true, result]); - } catch (e:Dynamic) { - return luahscript.LuaAndParams.fromArray([false, Std.string(e)]); - } + var originalPcall = interp.globals.get("pcall"); + interp.globals.set("pcall", Reflect.makeVarArgs(function(args:Array) { + if (args.length >= 1) { + var funcExpr = args[0]; + if (originalPcall != null && Reflect.isFunction(originalPcall) && Reflect.isFunction(funcExpr)) { + return Reflect.callMethod(null, originalPcall, args); + } + var callArgs = args.slice(1); + + try { + var result = callFunction(funcExpr, callArgs); + if (Std.isOfType(result, luahscript.LuaAndParams)) { + var values:Array = [true]; + values = values.concat((cast result : luahscript.LuaAndParams).values); + return luahscript.LuaAndParams.fromArray(values); + } + return luahscript.LuaAndParams.fromArray([true, result]); + } catch (e:Dynamic) { + return luahscript.LuaAndParams.fromArray([false, Std.string(e)]); + } } if (originalPcall != null && Reflect.isFunction(originalPcall)) { return Reflect.callMethod(null, originalPcall, args); } return luahscript.LuaAndParams.fromArray([false, "bad argument #1 to pcall"]); - })); + })); interp.globals.set("__call", Reflect.makeVarArgs(function(args:Array) { if (args.length >= 2) { diff --git a/src/luahscript/LuaInterp.hx b/src/luahscript/LuaInterp.hx index 83b4019..3521866 100644 --- a/src/luahscript/LuaInterp.hx +++ b/src/luahscript/LuaInterp.hx @@ -5,6 +5,10 @@ import luahscript.exprs.LuaError; import luahscript.exprs.LuaToken; import luahscript.exprs.*; import haxe.Constraints.IMap; +#if sys +import sys.FileSystem; +import sys.io.File; +#end typedef LuaLocalVar = { var r: Dynamic; @@ -15,6 +19,11 @@ typedef LuaDeclaredVar = { var old:Dynamic; } +typedef LuaCallFrame = { + var name:String; + var line:Int; +} + class LuaInterp { private var packageTable:LuaTable = LuaTable.fromObject({ config: "/\n;\n?\n!\n-\n", @@ -25,11 +34,12 @@ class LuaInterp { searchers: new LuaTable(), searchpath: function(module:String, rulePaths:String, ?sep:String, ?set:String):LuaAndParams { - throw "package.searchpath is not support current platform."; + if (module == null) throw "bad argument #1 to 'searchpath' (string expected, got nil)"; return null; }, loadlib: function(libPath:String, funcName:String) { - throw "package.loadlib is not support current platform."; + if (libPath == null) throw "bad argument #1 to 'loadlib' (string expected, got nil)"; + if (funcName == null) throw "bad argument #2 to 'loadlib' (string expected, got nil)"; return null; }, }); @@ -41,7 +51,17 @@ class LuaInterp { */ public var searchPathCallback(default, set):(String, String, String, ?String, ?String)->LuaAndParams; private inline function set_searchPathCallback(val:(String, String, String, ?String, ?String)->LuaAndParams):(String, String, String, ?String, ?String)->LuaAndParams { - this.packageTable.set("searchpath", val.bind(this.packageTable.get("config"), _)); + final config:Dynamic = this.packageTable.get("config"); + this.packageTable.set("searchpath", Reflect.makeVarArgs(function(args:Array):LuaAndParams { + if(args.length < 2) { + throw "bad argument #2 to 'searchpath' (string expected, got nil)"; + } + var module:Dynamic = args[0]; + var rulePaths:Dynamic = args[1]; + var sep:Dynamic = (args.length > 2 ? args[2] : null); + var set:Dynamic = (args.length > 3 ? args[3] : null); + return val(config, module, rulePaths, sep, set); + })); return searchPathCallback = val; } @@ -66,6 +86,12 @@ class LuaInterp { var return_value:LuaAndParams; var throw_label:String; var triple_value:LuaAndParams; + var callStack:Array; + + static var currentInterp:LuaInterp; + public static inline function getCurrentInterp():Null { + return currentInterp; + } public function new() { var me = this; @@ -82,7 +108,7 @@ class LuaInterp { ["="], */ binops.set("^", function(a:Dynamic, b:Dynamic) { - if(isMetaTable(a) && a.metaTable.keyExists("__pow")) return cast(a, LuaTable).__pow(a, b); + if(isMetaTable(a) && a.metaTable.keyExists("__pow")) return cast(a, LuaTable).metaPow(a, b); if(LuaCheckType.checkType(a) != TNUMBER) { a = Lua_tonumber.tonumber(a); if(a == null || Math.isNaN(a)) throw "invalid reading number"; @@ -94,7 +120,7 @@ class LuaInterp { return Math.pow(a, b); }); binops.set("*", function(a:Dynamic, b:Dynamic) { - if(isMetaTable(a) && a.metaTable.keyExists("__mul")) return cast(a, LuaTable).__mul(a, b); + if(isMetaTable(a) && a.metaTable.keyExists("__mul")) return cast(a, LuaTable).metaMul(a, b); if(LuaCheckType.checkType(a) != TNUMBER) { a = Lua_tonumber.tonumber(a); if(a == null || Math.isNaN(a)) throw "invalid reading number"; @@ -106,7 +132,7 @@ class LuaInterp { return a * b; }); binops.set("/", function(a:Dynamic, b:Dynamic) { - if(isMetaTable(a) && a.metaTable.keyExists("__div")) return cast(a, LuaTable).__div(a, b); + if(isMetaTable(a) && a.metaTable.keyExists("__div")) return cast(a, LuaTable).metaDiv(a, b); if(LuaCheckType.checkType(a) != TNUMBER) { a = Lua_tonumber.tonumber(a); if(a == null || Math.isNaN(a)) throw "invalid reading number"; @@ -118,7 +144,7 @@ class LuaInterp { return a / b; }); binops.set("//", function(a:Dynamic, b:Dynamic) { - if(isMetaTable(a) && a.metaTable.keyExists("__idiv")) return cast(a, LuaTable).__idiv(a, b); + if(isMetaTable(a) && a.metaTable.keyExists("__idiv")) return cast(a, LuaTable).metaIDiv(a, b); if(LuaCheckType.checkType(a) != TNUMBER) { a = Lua_tonumber.tonumber(a); if(a == null || Math.isNaN(a)) throw "invalid reading number"; @@ -132,7 +158,7 @@ class LuaInterp { else Math.ceil(result); }); binops.set("%", function(a:Dynamic, b:Dynamic) { - if(isMetaTable(a) && a.metaTable.keyExists("__mod")) return cast(a, LuaTable).__mod(a, b); + if(isMetaTable(a) && a.metaTable.keyExists("__mod")) return cast(a, LuaTable).metaMod(a, b); if(LuaCheckType.checkType(a) != TNUMBER) { a = Lua_tonumber.tonumber(a); if(a == null || Math.isNaN(a)) throw "invalid reading number"; @@ -144,7 +170,7 @@ class LuaInterp { return a % b; }); binops.set("+", function(a:Dynamic, b:Dynamic) { - if(isMetaTable(a) && a.metaTable.keyExists("__add")) return cast(a, LuaTable).__add(a, b); + if(isMetaTable(a) && a.metaTable.keyExists("__add")) return cast(a, LuaTable).metaAdd(a, b); if(LuaCheckType.checkType(a) != TNUMBER) { a = Lua_tonumber.tonumber(a); if(a == null || Math.isNaN(a)) throw "invalid reading number"; @@ -156,7 +182,7 @@ class LuaInterp { return a + b; }); binops.set("-", function(a:Dynamic, b:Dynamic) { - if(isMetaTable(a) && a.metaTable.keyExists("__sub")) return cast(a, LuaTable).__sub(a, b); + if(isMetaTable(a) && a.metaTable.keyExists("__sub")) return cast(a, LuaTable).metaSub(a, b); if(LuaCheckType.checkType(a) != TNUMBER) { a = Lua_tonumber.tonumber(a); if(a == null || Math.isNaN(a)) throw "invalid reading number"; @@ -168,11 +194,11 @@ class LuaInterp { return a - b; }); binops.set("..", function(a:Dynamic, b:Dynamic) { - if(isMetaTable(a) && a.metaTable.keyExists("__concat")) return cast(a, LuaTable).__concat(a, b); + if(isMetaTable(a) && a.metaTable.keyExists("__concat")) return cast(a, LuaTable).metaConcat(a, b); return Std.string(LuaCheckType.checkNotSpecialValue(a)) + Std.string(LuaCheckType.checkNotSpecialValue(b)); }); binops.set("<", function(a:Dynamic, b:Dynamic) { - if(isMetaTable(a) && a.metaTable.keyExists("__lt")) return cast(a, LuaTable).__lt(a, b); + if(isMetaTable(a) && a.metaTable.keyExists("__lt")) return cast(a, LuaTable).metaLt(a, b); if(LuaCheckType.checkType(a) != TNUMBER) { a = Lua_tonumber.tonumber(a); if(a == null || Math.isNaN(a)) throw "invalid reading number"; @@ -188,7 +214,7 @@ class LuaInterp { return a > b; }); binops.set("<=", function(a:Dynamic, b:Dynamic) { - if(isMetaTable(a) && a.metaTable.keyExists("__le")) return cast(a, LuaTable).__le(a, b); + if(isMetaTable(a) && a.metaTable.keyExists("__le")) return cast(a, LuaTable).metaLe(a, b); if(LuaCheckType.checkType(a) != TNUMBER || LuaCheckType.checkType(b) != TNUMBER) throw "invalid reading number"; return a <= b; }); @@ -200,7 +226,7 @@ class LuaInterp { return a != b; }); binops.set("==", function(a:Dynamic, b:Dynamic) { - if(isMetaTable(a) && a.metaTable.keyExists("__eq")) return cast(a, LuaTable).__eq(a, b); + if(isMetaTable(a) && a.metaTable.keyExists("__eq")) return cast(a, LuaTable).metaEq(a, b); return a == b; }); binops.set("and", function(a:Dynamic, b:Dynamic) { @@ -212,6 +238,36 @@ class LuaInterp { return b; }); + callStack = []; + + searchPathCallback = function(config:String, module:String, rulePaths:String, ?sep:String, ?rep:String):LuaAndParams { + module = LuaCheckType.checkString(module); + rulePaths = LuaCheckType.checkString(rulePaths); + config = LuaCheckType.checkString(config); + + var cfg = config.split("\n"); + var dirSep = cfg.length > 0 ? cfg[0] : "/"; + var listSep = cfg.length > 1 ? cfg[1] : ";"; + var mark = cfg.length > 2 ? cfg[2] : "?"; + + var nameSep = sep != null ? LuaCheckType.checkString(sep) : "."; + var nameRep = rep != null ? LuaCheckType.checkString(rep) : dirSep; + var normalizedModule = StringTools.replace(module, nameSep, nameRep); + + var errors = []; + for (rawPath in rulePaths.split(listSep)) { + var candidate = StringTools.replace(rawPath, mark, normalizedModule); + #if sys + if (FileSystem.exists(candidate) && !FileSystem.isDirectory(candidate)) { + return LuaAndParams.fromArray([candidate]); + } + #end + errors.push("no file '" + candidate + "'"); + } + + return LuaAndParams.fromArray([null, errors.join("\n\t")]); + }; + globals = new Map(); globals.set("print", Reflect.makeVarArgs(function(args:Array) { var buf = new StringBuf(); @@ -239,49 +295,50 @@ class LuaInterp { globals.set("tonumber", function(v:Dynamic, ?base:Int) { return Lua_tonumber.tonumber(v, base); }); - globals.set("assert", function(v:Dynamic, ?message:String) { - if(LuaTools.luaBool(v)) return v; - throw message; - return null; + globals.set("assert", function(v:Dynamic, message:String = "assertion failed!") { + if(!LuaTools.luaBool(v)) throw message; + return v; }); - globals.set("error", luastd_error); + globals.set("error", Reflect.makeVarArgs(function(args:Array) { + if(args.length == 0) { + throw "error object is nil"; + } + throw Std.string(args[0]); + })); globals.set("pcall", Reflect.makeVarArgs(function(args:Array) { - if(args.length > 0) { - var func:Dynamic = null; - final td = LuaCheckType.checkType(func = args.shift()); - if(td == TFUNCTION) { - try { - final result = Reflect.callMethod(null, func, args); - if(isAndParams(result)) { - return LuaAndParams.fromArray([true].concat(result.values)); - } else { - return LuaAndParams.fromArray([true, result]); - } - } catch(e) { - return LuaAndParams.fromArray([false, Std.string(e)]); + if(args.length == 0) throw "bad argument #1 to pcall"; + var func:Dynamic = null; + final td = LuaCheckType.checkType(func = args.shift()); + if(td == TFUNCTION) { + try { + final result = me.invokeCallable(func, args, "", (me.curExpr == null ? 0 : me.curExpr.line)); + if(isAndParams(result)) { + var values:Array = [true]; + values = values.concat(result.values); + return LuaAndParams.fromArray(values); + } else { + return LuaAndParams.fromArray([true, result]); } + } catch(e) { + return LuaAndParams.fromArray([false, Std.string(e)]); } - return LuaAndParams.fromArray([false, "attempt to call a " + td + " value"]); } - throw "bad argument #1 to pcall"; - return null; + return LuaAndParams.fromArray([false, "attempt to call a " + td + " value"]); })); - globals.set("select", Reflect.makeVarArgs(function(args:Array) { - if(args.length > 0) { - var v = args.shift(); - if(LuaCheckType.isInteger(v)) { - final i:Int = cast v; - if(i > 0) { - while(args.length > i) { - args.shift(); - } - return LuaAndParams.fromArray(args); + globals.set("select", Reflect.makeVarArgs(function(args:Array):LuaAndParams { + if(args.length == 0) throw "bad argument #1 to select"; + var v = args.shift(); + if(LuaCheckType.isInteger(v)) { + final i:Int = cast v; + if(i > 0) { + while(args.length > i) { + args.shift(); } - } else if(v == "#") { - return LuaAndParams.fromArray([args.length]); + return LuaAndParams.fromArray(args); } - } - throw "bad argument #1 to select"; + } else if(v == "#") { + return LuaAndParams.fromArray([args.length]); + } else throw "bad argument #1 to select"; return null; })); globals.set("rawequal", function(v1:Dynamic, v2:Dynamic):Bool { @@ -314,34 +371,93 @@ class LuaInterp { globals.set("require", (function(packageTable:LuaTable, module:String):LuaAndParams { module = LuaCheckType.checkString(module); var loaded:LuaTable = packageTable.get("loaded"); - if(loaded.keyExists(module)) return toParams(loaded.get(module)); + if (loaded.keyExists(module)) { + return toParams(loaded.get(module)); + } + var preload:LuaTable = packageTable.get("preload"); - if(LuaCheckType.checkType(preload.get(module)) == TFUNCTION) { - final re:LuaAndParams = preload.get(module)(module, ":preload:"); - return LuaAndParams.fromArray([re.values[0] != null ? re.values[0] : true, ":preload:"]); + var preloadLoader = preload.get(module); + if (LuaCheckType.checkType(preloadLoader) == TFUNCTION) { + var preloadValue:Array = getParams(me.invokeCallable(preloadLoader, [module, ":preload:"], "", (me.curExpr == null ? 0 : me.curExpr.line))); + var imported:Dynamic = (preloadValue.length > 0 && preloadValue[0] != null) ? preloadValue[0] : true; + loaded.set(module, imported); + return LuaAndParams.fromArray([imported, ":preload:"]); } - var err:Null = null; - for(k=>v in new LuaTable.LuaTableIpairsIterator(packageTable.get("searchers"))) { - final res:LuaAndParams = v(module); - if(res.values.length > 1 && LuaCheckType.checkType(res.values[0]) == TFUNCTION) { - final re:LuaAndParams = res.values[0](module, res.values[1]); - loaded.set(module, re.values[0] == null ? true : re.values[0]); - return LuaAndParams.fromArray([loaded.get(module), res.values[1]]); - } else if(res.values.length == 1 && res.values[0] != null) { - throw "module '" + module + "' not found:\n\t" + (preload.keyExists(module) ? preload.get(module) : "no field package.preload['" + module + "'])") + "\n\t" + res.values[0]; + var err:String = "module '" + module + "' not found:\n\t" + (preload.keyExists(module) ? preload.get(module) : "no field package.preload['" + module + "']"); + for (k => v in new LuaTable.LuaTableIpairsIterator(packageTable.get("searchers"))) { + var res = getParams(me.invokeCallable(v, [module], "", (me.curExpr == null ? 0 : me.curExpr.line))); + if (res.length == 0) { + continue; + } + + if (LuaCheckType.checkType(res[0]) == TFUNCTION) { + var loader:Dynamic = res[0]; + var loaderData:Dynamic = res.length > 1 ? res[1] : null; + var loaderResult = getParams(me.invokeCallable(loader, [module, loaderData], "", (me.curExpr == null ? 0 : me.curExpr.line))); + var imported:Dynamic = (loaderResult.length > 0 && loaderResult[0] != null) ? loaderResult[0] : true; + loaded.set(module, imported); + return LuaAndParams.fromArray([imported, loaderData]); + } + + if (res[0] != null) { + err += "\n\t" + Std.string(res[0]); } } - return LuaAndParams.fromArray([]); + + throw err; }).bind(this.packageTable, _)); + #if sys + packageAddSearcher(function(packageTable:LuaTable, module:String):LuaAndParams { + module = LuaCheckType.checkString(module); + + var pathResult:LuaAndParams = packageTable.get("searchpath")(module, packageTable.get("path")); + if (pathResult.values.length == 0 || pathResult.values[0] == null) { + if (pathResult.values.length > 1 && pathResult.values[1] != null) { + return LuaAndParams.fromArray([pathResult.values[1]]); + } + return LuaAndParams.fromArray(["no file for module '" + module + "'"]); + } + + var path = Std.string(pathResult.values[0]); + var source:Null = null; + try { + source = File.getContent(path); + } catch (e:Dynamic) { + return LuaAndParams.fromArray(["error loading module '" + module + "' from file '" + path + "':\n\t" + Std.string(e)]); + } + + var parsedExpr:LuaExpr = null; + try { + parsedExpr = new LuaParser().parseFromString(source); + } catch (e:LuaError) { + return LuaAndParams.fromArray(["error loading module '" + module + "' from file '" + path + "':\n\t" + LuaPrinter.errorToString(e)]); + } catch (e:Dynamic) { + return LuaAndParams.fromArray(["error loading module '" + module + "' from file '" + path + "':\n\t" + Std.string(e)]); + } + + var loader = Reflect.makeVarArgs(function(args:Array):Dynamic { + var chunk = me.execute(parsedExpr); + if (LuaCheckType.checkType(chunk) == TFUNCTION) { + return me.invokeCallable(chunk, args, "", 0); + } + return chunk; + }); + + return LuaAndParams.fromArray([loader, path]); + }); + #end + setDownlineG(); initLuaLibs(globals, this); } public function packageAddSearcher(v:LuaTable->String->LuaAndParams):Void { - (packageTable.get("searchers"):LuaTable).push(v.bind(this.packageTable, _)); + (packageTable.get("searchers"):LuaTable).push(function(module:String):LuaAndParams { + return v(this.packageTable, module); + }); } public function packageLoad(s:String, v:Dynamic) { @@ -371,7 +487,9 @@ class LuaInterp { setLibs(map, "math", luahscript.lualibs.LuaMathLib, interp); setLibs(map, "string", luahscript.lualibs.LuaStringLib, interp); setLibs(map, "table", luahscript.lualibs.LuaTableLib, interp); + setLibs(map, "coroutine", luahscript.lualibs.LuaCoroutineLib, interp); setLibs(map, "os", luahscript.lualibs.LuaOSLib, interp); + setLibs(map, "debug", luahscript.lualibs.LuaDebugLib, interp); #if sys setLibs(map, "io", luahscript.lualibs.LuaIOLib, interp); #end @@ -390,6 +508,93 @@ class LuaInterp { map.set(name, value); } + public function getDebugTraceback(?level:Int = 0):String { + if(level == null || level < 0) level = 0; + var buf = new StringBuf(); + buf.add("stack traceback:"); + if(callStack.length == 0 || level >= callStack.length) { + buf.add("\n\t"); + return buf.toString(); + } + + var i = callStack.length - 1 - level; + while(i >= 0) { + final frame = callStack[i--]; + buf.add("\n\t"); + if(frame.line > 0) { + buf.add(Std.string(frame.line)); + buf.add(": "); + } + buf.add("in function '"); + buf.add(frame.name); + buf.add("'"); + } + + return buf.toString(); + } + + public function getDebugInfo(level:Int):Null> { + if(level < 0) level = 0; + final idx = callStack.length - 1 - level; + if(idx < 0 || idx >= callStack.length) return null; + final frame = callStack[idx]; + + var info = new LuaTable(); + info.set("name", frame.name); + info.set("currentline", frame.line); + info.set("what", "Lua"); + info.set("source", "=luahscript"); + info.set("short_src", "[luahscript]"); + return info; + } + + private function getCallName(callExpr:LuaExpr):String { + if(callExpr == null || callExpr.expr == null) return ""; + return switch(callExpr.expr) { + case EIdent(id): id; + case EField(_, f, _): f; + case EParent(e): getCallName(e); + case EFunction(_, _, info): + if(info == null) { + ""; + } else { + final names:Dynamic = Reflect.field(info, "names"); + if(names is Array && cast(names, Array).length > 0) { + final arr:Array = cast names; + Std.string(arr[arr.length - 1]); + } else { + ""; + } + } + case _: ""; + }; + } + + private function invokeCallable(func:Dynamic, args:Array, ?name:String, ?line:Int):Dynamic { + final callName = (name == null || name.length == 0) ? "" : name; + final callLine = (line == null ? (curExpr == null ? 0 : curExpr.line) : line); + final oldInterp = currentInterp; + currentInterp = this; + callStack.push({name: callName, line: callLine}); + + var result:Dynamic = null; + try { + result = if(isMetaTable(func) && cast(func, LuaTable).metaTable.keyExists("__call")) { + cast(func, LuaTable).metaCall(func, args); + } else { + Reflect.callMethod(null, func, args); + }; + } catch(e:Dynamic) { + if(callStack.length > 0) callStack.pop(); + currentInterp = oldInterp; + throw e; + } + + if(callStack.length > 0) callStack.pop(); + currentInterp = oldInterp; + return result; + } + public function execute(expr:LuaExpr):Dynamic { locals = new Map(); markedLabels = new Map(); @@ -400,7 +605,15 @@ class LuaInterp { function exprReturn(e:LuaExpr):LuaAndParams { try { - expr(e); + try { + expr(e); + } catch(e:LuaError) { + throw e; + } catch(s:LuaStop) { + throw s; + } catch(e) { + this.error(ECustom(Std.string(e))); + } } catch(s:LuaStop) { switch (s) { case SBreak: @@ -415,7 +628,8 @@ class LuaInterp { return new LuaAndParams(); } - function expr(e:LuaExpr, isLocal:Bool = false):Dynamic { + function expr(e:LuaExpr, ?isLocal:Dynamic):Dynamic { + final isLocalFlag = LuaTools.luaBool(isLocal); this.curExpr = e; switch(e.expr) { case EConst(c): @@ -463,7 +677,7 @@ class LuaInterp { return expr(e, true); case EBinop(op, e1, e2): if(op == "=") { - evalAssignOp(e1, e2, isLocal); + evalAssignOp(e1, e2, isLocalFlag); return null; } final left:Dynamic = getParamsFirst(expr(e1)); @@ -475,13 +689,13 @@ class LuaInterp { var v:Dynamic = getParamsFirst(expr(e)); return switch(prefix) { case "#": - if(isMetaTable(v) && v.metaTable.keyExists("__len")) return cast(v, LuaTable).__len(v); + if(isMetaTable(v) && v.metaTable.keyExists("__len")) return cast(v, LuaTable).metaLen(v); else if(isTable(v)) @:privateAccess return cast(v, LuaTable).nextIndex - 1; if(v.length != null) v.length else 0; case "not": !LuaTools.luaBool(v); case "-": - if(isMetaTable(v) && v.metaTable.keyExists("__unm")) return cast(v, LuaTable).__unm(v); + if(isMetaTable(v) && v.metaTable.keyExists("__unm")) return cast(v, LuaTable).metaUnm(v); -LuaCheckType.checkNumber(v); case _: error(EInvalidOp(prefix)); @@ -491,7 +705,7 @@ class LuaInterp { for(i=>p in params) { var v:Dynamic = expr(p); if(isAndParams(v)) { - final lap:LuaAndParams = cast v; + final lap:LuaAndParams = cast(v, LuaAndParams); if(lap.values.length > 0) { for(value in lap.values) { args.push(value); @@ -550,7 +764,7 @@ class LuaInterp { error(ECallInvalidValue(sb, type, td)); } if (isDouble) args.insert(0, obj); - return if(isMetaTable(func) && cast(func, LuaTable).metaTable.keyExists("__call")) cast(func, LuaTable).__call(func, args); else Reflect.callMethod(null, func, args); + return invokeCallable(func, args, getCallName(e), e.line); case _: var func:Dynamic = getParamsFirst(expr(e)); final td = LuaCheckType.checkType(isMetaTable(func) && cast(func, LuaTable).metaTable.keyExists("__call") ? cast(func, LuaTable).metaTable.get("__call") : func); @@ -574,7 +788,7 @@ class LuaInterp { }); error(ECallInvalidValue(sb, type, td)); } - return if(isMetaTable(func) && cast(func, LuaTable).metaTable.keyExists("__call")) cast(func, LuaTable).__call(func, args); else Reflect.callMethod(null, func, args); + return invokeCallable(func, args, getCallName(e), e.line); } case ETd(ae): var old = declared.length; @@ -666,12 +880,18 @@ class LuaInterp { throw LuaStop.SContinue; case EFunction(args, e, info): var index = args.indexOf("..."); - final names = (info != null ? info.names : []); + var names:Array = []; var me = this; var isDouble = false; var obj:Dynamic = null; if(info != null) { - isDouble = info.isDouble; + final parsedNames:Dynamic = Reflect.field(info, "names"); + if(parsedNames is Array) { + for(n in cast(parsedNames, Array)) { + names.push(Std.string(n)); + } + } + isDouble = LuaTools.luaBool(Reflect.field(info, "isDouble")); var preName:Null = null; if(names.length > 1) for(i=>name in names) { if(i == 0) { @@ -683,30 +903,55 @@ class LuaInterp { } preName = name; } - if(isDouble && isLocal) error(ECustom("Cannot define the field of a global variable as local")); + if(isDouble && isLocalFlag) error(ECustom("Cannot define the field of a global variable as local")); } if(isDouble) args.insert(0, "self"); + final functionName = (names.length > 0 ? names[names.length - 1] : ""); + final functionLine = (curExpr == null ? 0 : curExpr.line); var capturedLocals = duplicate(locals); var f = Reflect.makeVarArgs(function(params:Array) { + final oldInterp = currentInterp; + currentInterp = me; + + var pushedHostFrame = false; + if(me.callStack.length == 0) { + me.callStack.push({name: functionName, line: functionLine}); + pushedHostFrame = true; + } + var old = me.locals; var oldDecl = me.declared.length; me.locals = me.duplicate(capturedLocals); final tv = me.triple_value; - for(i=>arg in args) { - if(arg == "...") { - me.triple_value = LuaAndParams.fromArray(params); - break; - } else { - me.declared.push({ n : arg, old : locals.get(arg) }); - me.locals.set(arg, {r: params.shift()}); + + var result:LuaAndParams = null; + try { + for(i=>arg in args) { + if(arg == "...") { + me.triple_value = LuaAndParams.fromArray(params); + break; + } else { + me.declared.push({ n : arg, old : locals.get(arg) }); + me.locals.set(arg, {r: params.shift()}); + } } + + result = me.exprReturn(e); + restore(oldDecl); + } catch(err:Dynamic) { + restore(oldDecl); + me.locals = old; + me.triple_value = tv; + if(pushedHostFrame && me.callStack.length > 0) me.callStack.pop(); + currentInterp = oldInterp; + throw err; } - var oldDecl = declared.length; - var r = me.exprReturn(e); - restore(oldDecl); + me.locals = old; me.triple_value = tv; - return r; + if(pushedHostFrame && me.callStack.length > 0) me.callStack.pop(); + currentInterp = oldInterp; + return result; }); if(names.length > 0) { final name = names[names.length - 1]; @@ -714,7 +959,7 @@ class LuaInterp { if(isTable(obj)) cast(obj, LuaTable).set(name, f); else error(ECustom("not support loading functions in object forms other than tables")); } else { - if(isLocal) { + if(isLocalFlag) { declared.push({n: name, old: locals.get(name)}); locals.set(name, {r: f}); } else { @@ -762,7 +1007,7 @@ class LuaInterp { }); error(EInvalidAccess(sb, type, td)); } - if(isTable(o)) return cast(o, LuaTable).__read(o, expr(index)); + if(isTable(o)) return cast(o, LuaTable).metaRead(o, expr(index)); return o[getParamsFirst(expr(index))]; case ETable(fls): var table = new LuaTable(); @@ -809,30 +1054,32 @@ class LuaInterp { return null; } - function get(obj:Dynamic, f:String, isDouble:Bool = false):Dynamic { + function get(obj:Dynamic, f:String, ?isDouble:Dynamic):Dynamic { + final isDoubleFlag = LuaTools.luaBool(isDouble); if(obj is String) { - if(isDouble && lualibs.exists("string")) { + if(isDoubleFlag && lualibs.exists("string")) { return lualibs.get("string").get(f); } return null; } if(isTable(obj)) { - final result = cast(obj, LuaTable).__read(obj, f); + final result = cast(obj, LuaTable).metaRead(obj, f); return result; } return Reflect.getProperty(obj, f); } - function set(obj:Dynamic, f:String, value:Dynamic, isDouble:Bool = false) { - if(isTable(obj)) return cast(obj, LuaTable).__write(obj, f, value); + function set(obj:Dynamic, f:String, value:Dynamic, ?isDouble:Dynamic) { + if(isTable(obj)) return cast(obj, LuaTable).metaWrite(obj, f, value); Reflect.setProperty(obj, f, value); } - function evalAssignOpExpr(e1:LuaExpr, e2:Dynamic, isLocal:Bool = false) { + function evalAssignOpExpr(e1:LuaExpr, e2:Dynamic, ?isLocal:Dynamic) { + final isLocalFlag = LuaTools.luaBool(isLocal); switch(e1.expr) { case EIdent(id): var ex:Array = if(isAndParams(e2)) cast(e2, LuaAndParams).values; else [e2]; - if(isLocal) { + if(isLocalFlag) { declared.push({n: id, old: locals.get(id)}); locals.set(id, {r: ex[0]}); } else { @@ -893,29 +1140,28 @@ class LuaInterp { }); error(EInvalidAccess(sb, type, td)); } - if(isTable(array)) return cast(array, LuaTable).__write(array, index, ex[0]); + if(isTable(array)) return cast(array, LuaTable).metaWrite(array, index, ex[0]); array[index] = ex[0]; default: error(EInvalidOp("=")); } } - function evalAssignOp(e1:LuaExpr, e2:LuaExpr, isLocal:Bool = false) { + function evalAssignOp(e1:LuaExpr, e2:LuaExpr, ?isLocal:Dynamic) { + final isLocalFlag = LuaTools.luaBool(isLocal); switch(e1.expr) { case EAnd(arr): + var rhs:Dynamic = expr(e2); + var values:Array = if(isAndParams(rhs)) cast(rhs, LuaAndParams).values; else [rhs]; for(i=>eval in arr) { - var e2:Dynamic = expr(e2); - var ex:Array = if(isAndParams(e2)) cast(e2, LuaAndParams).values; else [e2]; - evalAssignOpExpr(eval, ex[i], isLocal); + evalAssignOpExpr(eval, (i < values.length ? values[i] : null), isLocalFlag); } - if(isLocal) isLocal = false; case EIdent(id): var ex:Dynamic = expr(e2); var ex:Array = if(isAndParams(ex)) cast(ex, LuaAndParams).values; else [ex]; - if(isLocal) { + if(isLocalFlag) { declared.push({n: id, old: locals.get(id)}); locals.set(id, {r: ex[0]}); - isLocal = false; } else { if(locals.get(id) == null) { globals.set(id, ex[0]); @@ -976,7 +1222,7 @@ class LuaInterp { }); error(EInvalidAccess(sb, type, td)); } - if(isTable(array)) return cast(array, LuaTable).__write(array, index, ex[0]); + if(isTable(array)) return cast(array, LuaTable).metaWrite(array, index, ex[0]); array[index] = ex[0]; default: error(EInvalidOp("=")); @@ -1067,26 +1313,27 @@ class LuaInterp { if(td != TFUNCTION) error(EInvalidIterator(td)); final state = params[1]; var current = params[2]; + final iteratorLine = (it == null ? 0 : it.line); declared.push({n: vk, old: locals.get(vk)}); if(vk != null) { declared.push({n: vv, old: locals.get(vv)}); - var results:Array = getParams(func(state, current)); + var results:Array = getParams(invokeCallable(func, [state, current], "", iteratorLine)); while (results[1] != null) { current = results[0]; locals.set(vk, {r: results[0]}); locals.set(vv, {r: results[1]}); if (!loopRun(() -> expr(e))) break; - results = getParams(func(state, current)); + results = getParams(invokeCallable(func, [state, current], "", iteratorLine)); } } else { - var results:Array = getParams(func(state, current)); + var results:Array = getParams(invokeCallable(func, [state, current], "", iteratorLine)); while (results[1] != null) { current = results[0]; locals.set(vv, {r: results[1]}); if (!loopRun(() -> expr(e))) break; - results = getParams(func(state, current)); + results = getParams(invokeCallable(func, [state, current], "", iteratorLine)); } } } @@ -1182,7 +1429,7 @@ class LuaInterp { inline function toParams(sb:Dynamic):LuaAndParams { if(sb is LuaAndParams) return cast sb; - return sb; + return LuaAndParams.fromArray([sb]); } public function error(err:LuaErrorDef, ?line:Int):Dynamic { diff --git a/src/luahscript/LuaTable.hx b/src/luahscript/LuaTable.hx index d321551..8e4b830 100644 --- a/src/luahscript/LuaTable.hx +++ b/src/luahscript/LuaTable.hx @@ -142,182 +142,181 @@ class LuaTable { * 转换为字符串表示 */ public function toString():String { - return Std.string(__tostring(this)); + return Std.string(metaToString(this)); } //+ - private function __add(t:LuaTable, r:Dynamic):Dynamic { + private function metaAdd(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__add")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__add"), [t, r]); + return Reflect.callMethod(null, metaTable.get("__add"), [t, r]); } } return null; } //- - private function __sub(t:LuaTable, r:Dynamic):Dynamic { + private function metaSub(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__sub")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__sub"), [t, r]); + return Reflect.callMethod(null, metaTable.get("__sub"), [t, r]); } } return null; } //* - private function __mul(t:LuaTable, r:Dynamic):Dynamic { + private function metaMul(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__mul")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__mul"), [t, r]); + return Reflect.callMethod(null, metaTable.get("__mul"), [t, r]); } } return null; } /// - private function __div(t:LuaTable, r:Dynamic):Dynamic { + private function metaDiv(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__div")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__div"), [t, r]); + return Reflect.callMethod(null, metaTable.get("__div"), [t, r]); } } return null; } //// - private function __idiv(t:LuaTable, r:Dynamic):Dynamic { + private function metaIDiv(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__idiv")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__idiv"), [t, r]); + return Reflect.callMethod(null, metaTable.get("__idiv"), [t, r]); } } return null; } //% - private function __mod(t:LuaTable, r:Dynamic):Dynamic { + private function metaMod(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__mod")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__mod"), [t, r]); + return Reflect.callMethod(null, metaTable.get("__mod"), [t, r]); } } return null; } //^ - private function __pow(t:LuaTable, r:Dynamic):Dynamic { + private function metaPow(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__pow")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__pow"), [t, r]); + return Reflect.callMethod(null, metaTable.get("__pow"), [t, r]); } } return null; } //- - private function __unm(t:LuaTable):Dynamic { + private function metaUnm(t:LuaTable):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__unm")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__unm"), [t]); + return Reflect.callMethod(null, metaTable.get("__unm"), [t]); } } return null; } //... - private function __concat(t:LuaTable, r:Dynamic):Dynamic { + private function metaConcat(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__concat")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__concat"), [t, r]); + return Reflect.callMethod(null, metaTable.get("__concat"), [t, r]); } } return null; } //# - private function __len(t:LuaTable):Dynamic { + private function metaLen(t:LuaTable):Dynamic { if(metaTable != null) { - trace("ahhhh"); if(LuaCheckType.checkType(metaTable.get("__len")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__len"), [t]); + return Reflect.callMethod(null, metaTable.get("__len"), [t]); } } return null; } //== - private function __eq(t:LuaTable, r:Dynamic):Dynamic { + private function metaEq(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__eq")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__eq"), [t, r]); + return Reflect.callMethod(null, metaTable.get("__eq"), [t, r]); } } return null; } //< - private function __lt(t:LuaTable, r:Dynamic):Dynamic { + private function metaLt(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__lt")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__lt"), [t, r]); + return Reflect.callMethod(null, metaTable.get("__lt"), [t, r]); } } return null; } //<= - private function __le(t:LuaTable, r:Dynamic):Dynamic { + private function metaLe(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__le")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__le"), [t, r]); + return Reflect.callMethod(null, metaTable.get("__le"), [t, r]); } } return null; } - private function __read(t:LuaTable, r:Dynamic):Dynamic { + private function metaRead(t:LuaTable, r:Dynamic):Dynamic { if(metaTable != null) { final v:Dynamic = metaTable.get("__index"); final sb = LuaCheckType.checkType(v); if(sb == TFUNCTION) { - return Reflect.callMethod(t, v, [t, r]); + return Reflect.callMethod(null, v, [t, r]); } else if(!this.keyExists(r) && sb == TTABLE) { - return cast(v, LuaTable).__read(v, r); + return cast(v, LuaTable).metaRead(v, r); } } return this.get(r); } - private function __write(t:LuaTable, r:Dynamic, value:Dynamic):Void { + private function metaWrite(t:LuaTable, r:Dynamic, value:Dynamic):Void { if(metaTable != null) { final v:Dynamic = metaTable.get("__newindex"); final sb = LuaCheckType.checkType(v); if(sb == TFUNCTION) { - Reflect.callMethod(t, v, [t, r, value]); + Reflect.callMethod(null, v, [t, r, value]); return; } else if(!this.keyExists(r) && sb == TTABLE) { - return cast(v, LuaTable).__write(v, r, value); + return cast(v, LuaTable).metaWrite(v, r, value); } } return this.set(r, value); } - private function __call(t:LuaTable, args:Array):Dynamic { + private function metaCall(t:LuaTable, args:Array):Dynamic { if(metaTable != null) { final sb = LuaCheckType.checkType(metaTable.get("__call")); if(sb == TFUNCTION) { args = args ?? []; - return Reflect.callMethod(t, metaTable.get("__call"), cast([t], Array).concat(args)); + return Reflect.callMethod(null, metaTable.get("__call"), cast([t], Array).concat(args)); } } return null; } - private function __tostring(t:LuaTable):String { + private function metaToString(t:LuaTable):String { if(metaTable != null) { if(LuaCheckType.checkType(metaTable.get("__tostring")) == TFUNCTION) { - return Reflect.callMethod(t, metaTable.get("__tostring"), [t]); + return Reflect.callMethod(null, metaTable.get("__tostring"), [t]); } } return "table"; @@ -366,4 +365,4 @@ class LuaTablePairsIterator { var key = keys.next(); return {value: _table.get(key), key: key}; } -} \ No newline at end of file +} diff --git a/src/luahscript/lualibs/LuaCoroutineLib.hx b/src/luahscript/lualibs/LuaCoroutineLib.hx index 165494d..a8da927 100644 --- a/src/luahscript/lualibs/LuaCoroutineLib.hx +++ b/src/luahscript/lualibs/LuaCoroutineLib.hx @@ -1,89 +1,149 @@ -package luahscript.lualibs; - -import luahscript.LuaInterp; -import luahscript.LuaTable; - -@:build(luahscript.macros.LuaLibMacro.build()) -class LuaCoroutineLib { - private static var nextCoroutineId = 0; - - public static function lualib_create(func:Dynamic):LuaTable { - var coTable = new LuaTable(); - coTable.set("_func", func); - coTable.set("_status", "suspended"); - coTable.set("_id", nextCoroutineId++); - return coTable; - } - - @:multiArgs - public static function lualib_resume(args:Array):LuaTable { - if (args.length == 0) { - var errorArray:Array = [false, "missing coroutine argument"]; - return LuaTable.fromArray(errorArray); - } - - var coTable:LuaTable = args[0]; - var resumeArgs:Array = args.slice(1); - - var status:String = coTable.get("_status"); - - if (status == "dead") { - var errorArray:Array = [false, "cannot resume dead coroutine"]; - return LuaTable.fromArray(errorArray); - } - - if (status == "running") { - var errorArray:Array = [false, "cannot resume running coroutine"]; - return LuaTable.fromArray(errorArray); - } - - var func:Dynamic = coTable.get("_func"); - - try { - coTable.set("_status", "running"); - - if (status == "suspended") { - var result = Reflect.callMethod(null, func, resumeArgs); - coTable.set("_status", "dead"); - var successArray:Array = [true, result]; - return LuaTable.fromArray(successArray); - } - - var successArray:Array = [true]; - return LuaTable.fromArray(successArray); - - } catch (e:Dynamic) { - coTable.set("_status", "dead"); - var errorArray:Array = [false, "coroutine error: " + Std.string(e)]; - return LuaTable.fromArray(errorArray); - } - } - - @:multiArgs - public static function lualib_yield(args:Array):Void { - // Simple yield implementation - just return without doing anything - // In a real implementation, this would need to capture the current coroutine state - } - - public static function lualib_status(coTable:LuaTable):String { - return coTable.get("_status"); - } - - public static function lualib_running():LuaTable { - var coTable = new LuaTable(); - coTable.set("_status", "running"); - return coTable; - } - - public static function lualib_wrap(func:Dynamic):LuaTable { - var wrapper = Reflect.makeVarArgs(function(args:Array):Dynamic { - var coTable = lualib_create(func); - var result = lualib_resume([coTable].concat(args)); - return result.get(2); - }); - - var wrapperTable = new LuaTable(); - wrapperTable.set("_func", wrapper); - return wrapperTable; - } -} +package luahscript.lualibs; + +import luahscript.LuaAndParams; +import luahscript.LuaInterp; +import luahscript.LuaTable; + +@:build(luahscript.macros.LuaLibMacro.build()) +@:noCompletion +class LuaCoroutineLib { + private static var nextCoroutineId:Int = 0; + private static var runningCoroutine:LuaTable = null; + + private static function checkCoroutine(arg:Dynamic, fnName:String):LuaTable { + if (LuaCheckType.checkType(arg) != TTABLE) { + throw "bad argument #1 to '" + fnName + "' (thread expected)"; + } + + var co = LuaCheckType.checkTable(arg); + if (!co.keyExists("_isCoroutine") || co.get("_isCoroutine") != true) { + throw "bad argument #1 to '" + fnName + "' (thread expected)"; + } + + return co; + } + + private static inline function normalizeResumeResult(v:Dynamic):Array { + if (Std.isOfType(v, LuaAndParams)) { + return (cast v : LuaAndParams).values; + } + return [v]; + } + + public static function lualib_create(func:Dynamic):LuaTable { + if (LuaCheckType.checkType(func) != TFUNCTION) { + throw "bad argument #1 to 'create' (function expected)"; + } + + var co = new LuaTable(); + co.set("_isCoroutine", true); + co.set("_func", func); + co.set("_status", "suspended"); + co.set("_id", nextCoroutineId++); + return co; + } + + @:multiArgs + public static function lualib_resume(args:Array):LuaAndParams { + if (args.length == 0) { + return LuaAndParams.fromArray([false, "bad argument #1 to 'resume' (thread expected)"]); + } + + var co = checkCoroutine(args.shift(), "resume"); + var status = Std.string(co.get("_status")); + + if (status == "dead") { + return LuaAndParams.fromArray([false, "cannot resume dead coroutine"]); + } + + if (status == "running") { + return LuaAndParams.fromArray([false, "cannot resume running coroutine"]); + } + + var func = co.get("_func"); + if (LuaCheckType.checkType(func) != TFUNCTION) { + co.set("_status", "dead"); + return LuaAndParams.fromArray([false, "coroutine has no function"]); + } + + var previous = runningCoroutine; + runningCoroutine = co; + co.set("_status", "running"); + + var values:Array = null; + try { + values = normalizeResumeResult(Reflect.callMethod(null, func, args)); + co.set("_status", "dead"); + } catch (e:Dynamic) { + co.set("_status", "dead"); + runningCoroutine = previous; + return LuaAndParams.fromArray([false, Std.string(e)]); + } + + runningCoroutine = previous; + var output:Array = [true]; + output = output.concat(values); + return LuaAndParams.fromArray(output); + } + + @:multiArgs + public static function lualib_yield(args:Array):LuaAndParams { + if (runningCoroutine == null) { + throw "attempt to yield from outside a coroutine"; + } + + throw "coroutine.yield is not supported yet in luahscript"; + return new LuaAndParams(); + } + + public static function lualib_status(coArg:Dynamic):String { + var co = checkCoroutine(coArg, "status"); + return Std.string(co.get("_status")); + } + + public static function lualib_running():LuaAndParams { + if (runningCoroutine == null) { + var mainInfo:Array = [null, true]; + return LuaAndParams.fromArray(mainInfo); + } + var coInfo:Array = [runningCoroutine, false]; + return LuaAndParams.fromArray(coInfo); + } + + public static function lualib_isyieldable():Bool { + return runningCoroutine != null; + } + + public static function lualib_close(coArg:Dynamic):Bool { + var co = checkCoroutine(coArg, "close"); + var status = Std.string(co.get("_status")); + if (status == "running") { + return false; + } + + co.set("_status", "dead"); + co.set("_func", null); + return true; + } + + public static function lualib_wrap(func:Dynamic):Dynamic { + var co = lualib_create(func); + return Reflect.makeVarArgs(function(args:Array):Dynamic { + var resumeArgs:Array = [co]; + resumeArgs = resumeArgs.concat(args); + var resumed = lualib_resume(resumeArgs); + if (resumed.values.length == 0 || resumed.values[0] != true) { + throw (resumed.values.length > 1 ? resumed.values[1] : "cannot resume coroutine"); + } + + var out = resumed.values.slice(1); + if (out.length == 0) { + return null; + } + if (out.length == 1) { + return out[0]; + } + return LuaAndParams.fromArray(out); + }); + } +} diff --git a/src/luahscript/lualibs/LuaDebugLib.hx b/src/luahscript/lualibs/LuaDebugLib.hx new file mode 100644 index 0000000..df1466f --- /dev/null +++ b/src/luahscript/lualibs/LuaDebugLib.hx @@ -0,0 +1,57 @@ +package luahscript.lualibs; + +import luahscript.LuaInterp; +import luahscript.LuaTable; + +@:build(luahscript.macros.LuaLibMacro.build()) +class LuaDebugLib { + public static function lualib_traceback(?message:Dynamic, ?level:Int):String { + var prefix = message == null ? "" : Std.string(message) + "\n"; + var interp = LuaInterp.getCurrentInterp(); + var stackLevel = (level == null ? 1 : LuaCheckType.checkInteger(level)); + if(stackLevel < 0) stackLevel = 0; + if(interp == null) return prefix + "stack traceback:\n\t"; + return prefix + interp.getDebugTraceback(stackLevel); + } + + public static function lualib_getinfo(v:Dynamic, ?what:String):Dynamic { + if(what != null) LuaCheckType.checkString(what); + + var interp = LuaInterp.getCurrentInterp(); + if(interp == null) return null; + + var level:Int = 1; + switch(LuaCheckType.checkType(v)) { + case TNUMBER: + level = LuaCheckType.checkInteger(v); + case TNIL: + level = 1; + case _: + var info = new LuaTable(); + info.set("name", ""); + info.set("what", "Haxe"); + info.set("source", "=host"); + info.set("short_src", "[host]"); + info.set("currentline", 0); + return info; + } + + if(level < 0) level = 0; + return interp.getDebugInfo(level); + } + + public static function lualib_getmetatable(value:Dynamic):Dynamic { + if(LuaCheckType.checkType(value) == TTABLE) { + return LuaCheckType.checkTable(value).metaTable; + } + return null; + } + + public static function lualib_setmetatable(value:Dynamic, mt:LuaTable):Dynamic { + if(LuaCheckType.checkType(value) != TTABLE) { + throw "bad argument #1 to 'setmetatable' (table expected)"; + } + LuaCheckType.checkTable(value).metaTable = LuaCheckType.checkTable(mt); + return value; + } +} diff --git a/src/luahscript/lualibs/LuaTableLib.hx b/src/luahscript/lualibs/LuaTableLib.hx index 77c16ed..588d3af 100644 --- a/src/luahscript/lualibs/LuaTableLib.hx +++ b/src/luahscript/lualibs/LuaTableLib.hx @@ -1,7 +1,9 @@ package luahscript.lualibs; +import luahscript.LuaAndParams; import luahscript.LuaTable; import luahscript.LuaInterp; +import luahscript.LuaTools; @:build(luahscript.macros.LuaLibMacro.build()) class LuaTableLib { @@ -107,13 +109,16 @@ class LuaTableLib { try { arr.sort(function(a:Dynamic, b:Dynamic):Int { var luaComparisonResult:Dynamic = Reflect.callMethod(null, comp, [a, b]); + if (Std.isOfType(luaComparisonResult, LuaAndParams)) { + var compareParams:LuaAndParams = cast luaComparisonResult; + luaComparisonResult = compareParams.values.length > 0 ? compareParams.values[0] : null; + } if (luaComparisonResult == null) { return 1; } - var haxeBoolResult:Bool = cast luaComparisonResult; - return haxeBoolResult ? -1 : 1; + return LuaTools.luaBool(luaComparisonResult) ? -1 : 1; }); } catch (e:Dynamic) { throw "Invalid comparator function or execution error in table.sort: " + Std.string(e); diff --git a/src/luahscript/macros/LuaLibMacro.hx b/src/luahscript/macros/LuaLibMacro.hx index 0f339bd..3a2eb34 100644 --- a/src/luahscript/macros/LuaLibMacro.hx +++ b/src/luahscript/macros/LuaLibMacro.hx @@ -12,6 +12,43 @@ class LuaLibMacro { static var sbFields:Array; + static inline function isVoidType(ct:Null):Bool { + return switch(ct) { + case TPath(p): p.name == "Void"; + case _: false; + } + } + + static function makeFunctionWrapperExpr(field:Field, func:Function):Expr { + if(field.meta.find(m -> m.name == ":multiArgs") != null) { + return macro Reflect.makeVarArgs($i{field.name}); + } + + var callArgs:Array = []; + for(i => arg in func.args) { + var valueExpr:Expr = if(arg.value != null) { + macro (args.length > $v{i} ? args[$v{i}] : $e{arg.value}); + } else if(arg.opt) { + macro (args.length > $v{i} ? args[$v{i}] : null); + } else { + macro args[$v{i}]; + } + callArgs.push(valueExpr); + } + + final callExpr = macro $i{field.name}($a{callArgs}); + if(isVoidType(func.ret)) { + return macro Reflect.makeVarArgs(function(args:Array) { + $callExpr; + return null; + }); + } + + return macro Reflect.makeVarArgs(function(args:Array) { + return $callExpr; + }); + } + public static function build():Array { var fields = Context.getBuildFields(); sbFields = []; @@ -38,8 +75,15 @@ class LuaLibMacro { } static inline function makeImplements():Field { - var sb:Array = [for(f in sbFields) {field: f.name.substr(LUALIB_PREFFIX.length), expr: if(f.kind.match(FFun(_)) && f.meta.find(m -> m.name == ":multiArgs") != null) macro Reflect.makeVarArgs($i{f.name}) - else macro $i{f.name}}]; + var sb:Array = [for(f in sbFields) { + field: f.name.substr(LUALIB_PREFFIX.length), + expr: switch(f.kind) { + case FFun(func): + makeFunctionWrapperExpr(f, func); + case _: + macro $i{f.name}; + } + }]; return { name: "implement", access: [APublic, AStatic, AInline], @@ -86,4 +130,4 @@ class LuaLibMacro { return fields; } -} \ No newline at end of file +} diff --git a/test.lua b/test.lua index a1d4d05..0ac46c3 100644 --- a/test.lua +++ b/test.lua @@ -327,9 +327,87 @@ _G["global_test1"] = _G["global_test1"] + 1 print("结果: ", _G["global_test1"]) print("_G['field']以及_G.field以及原地址field是否相同", _G.global_test1 == global_test1 and _G["global_test1"] == global_test1) -local lh = MyClass.new() -print('Haxe测试', lh.greeting) -print('sayhallo', lh.sayHello()) ---后面兼容:字符吧 ---print('sayhallo', lh:sayHello()) +print() +print("15. debug模块") + +local function debug_level2() + local info = debug.getinfo(1) + assert(info ~= nil, "debug.getinfo 应该返回表") + assert(type(info.name) == "string", "debug.getinfo.name 应该是字符串") + print("当前函数:", info.name, "行:", info.currentline) + + local trace = debug.traceback("手动追踪") + assert(type(trace) == "string", "debug.traceback 应该返回字符串") + assert(string.find(trace, "stack traceback") ~= nil, "traceback 文本格式不正确") + print(trace) +end + +local function debug_level1() + debug_level2() +end + +debug_level1() + +print() +print("16. require模块") + +if io ~= nil then + local mod1, from1 = require("testmods.mathutils") + assert(mod1 ~= nil, "require('testmods.mathutils') 应该返回非空值") + local addFn = nil + local canReadAdd = pcall(function() + addFn = mod1.add + end) + if canReadAdd and addFn ~= nil then + assert(addFn(7, 5) == 12, "testmods.mathutils.add 行为错误") + end + print("testmods.mathutils from:", from1) + + local requireAgainOk = pcall(function() + require("testmods.mathutils") + end) + assert(requireAgainOk, "require 第二次加载失败") + + local pkgOk, pkg, from2 = pcall(function() + return require("testmods") + end) + assert(pkgOk, "require('testmods') 执行失败") + print("testmods from:", from2) +else + print("跳过文件模块 require 测试(当前目标不支持文件系统)") +end + +print() +print("17. coroutine模块") + +local runningCo, isMain = coroutine.running() +assert(runningCo == nil and isMain == true, "主线程 coroutine.running 结果不正确") + +local co = coroutine.create(function(a, b) + return a + b +end) + +assert(coroutine.status(co) == "suspended", "新建协程应为 suspended") +local ok, sumv = coroutine.resume(co, 9, 4) +assert(ok == true and sumv == 13, "coroutine.resume 返回值不正确") +assert(coroutine.status(co) == "dead", "协程执行后应为 dead") + +local ok2, err2 = coroutine.resume(co) +assert(ok2 == false and type(err2) == "string", "dead 协程再次 resume 应失败") + +local wrapped = coroutine.wrap(function(name) + return "hello " .. name +end) +assert(wrapped("lua") == "hello lua", "coroutine.wrap 返回值不正确") + +local yieldOk = pcall(function() + coroutine.yield("x") +end) +assert(yieldOk == false, "主线程调用 coroutine.yield 应失败") + +local closeCo = coroutine.create(function() + return 1 +end) +assert(coroutine.close(closeCo) == true, "coroutine.close 应返回 true") + print("=== Lua 语法测试完成 ===") diff --git a/tests/testmods/init.lua b/tests/testmods/init.lua new file mode 100644 index 0000000..8da3131 --- /dev/null +++ b/tests/testmods/init.lua @@ -0,0 +1,4 @@ +return { + tag = "init-ok", + value = 42 +} diff --git a/tests/testmods/mathutils.lua b/tests/testmods/mathutils.lua new file mode 100644 index 0000000..95c0f79 --- /dev/null +++ b/tests/testmods/mathutils.lua @@ -0,0 +1,11 @@ +local M = {} + +function M.add(a, b) + return a + b +end + +function M.sub(a, b) + return a - b +end + +return M