diff --git a/.gitignore b/.gitignore index 61c8867..d4cb69c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ hxformat.json /test/dump /.DS_Store /examples +rulescript/vscode-project.hxml diff --git a/haxelib.json b/haxelib.json index 7f935b8..54c1adf 100644 --- a/haxelib.json +++ b/haxelib.json @@ -6,7 +6,7 @@ "version": "0.3.0-alpha", "releasenote": "RuleScripted Classes, Script Properties, Rest and more.", "dependencies": { - "hscript":"" + "hscript": "" }, "contributors": ["Kriptel"] } \ No newline at end of file diff --git a/rulescript/interps/RuleScriptInterp.hx b/rulescript/interps/RuleScriptInterp.hx index d75608b..24f4174 100644 --- a/rulescript/interps/RuleScriptInterp.hx +++ b/rulescript/interps/RuleScriptInterp.hx @@ -27,8 +27,12 @@ class RuleScriptInterp extends hscript.Interp implements IInterp public var access:RuleScriptAccess; + public var strictMode:Bool = true; + public var imports:Map = []; public var usings:Map = []; + public var finalVariables:Map = []; + public var declaredVariableTypes:Map = []; public var superInstance(default, set):Dynamic; @@ -58,6 +62,16 @@ class RuleScriptInterp extends hscript.Interp implements IInterp imports = []; usings = []; typePaths = []; + finalVariables.clear(); + declaredVariableTypes.clear(); + + if (rulescript.scriptedClass.RuleScriptedClassUtil.autoWrappers != null) + { + for (nativeName => wrapperClass in rulescript.scriptedClass.RuleScriptedClassUtil.autoWrappers) + { + variables.set(nativeName, wrapperClass); + } + } } override public function posInfos():haxe.PosInfos @@ -75,14 +89,59 @@ class RuleScriptInterp extends hscript.Interp implements IInterp override function initOps() { super.initOps(); - binops.set("??", (e1, e2) -> this.expr(e1) ?? this.expr(e2)); - assignOp("??=", function(v1:Dynamic, v2:Dynamic) return v1 ?? v2); + + var me = this; + + function resolveOp(op:String, v1:Dynamic, v2:Dynamic):Dynamic { + if (v1 is rulescript.types.ScriptedAbstract.ScriptedAbstractInstance) { + var inst = cast(v1, rulescript.types.ScriptedAbstract.ScriptedAbstractInstance); + if (inst.impl.hasOperator(op)) return inst.impl.callOperator(op, inst, v2, false); + } + if (v2 is rulescript.types.ScriptedAbstract.ScriptedAbstractInstance) { + var inst = cast(v2, rulescript.types.ScriptedAbstract.ScriptedAbstractInstance); + if (inst.impl.hasOperator(op)) return inst.impl.callOperator(op, inst, v1, true); + } + return null; + } + + binops.set("+", function(e1, e2):Dynamic { + var v1:Dynamic = me.expr(e1); + var v2:Dynamic = me.expr(e2); + var res:Dynamic = resolveOp("+", v1, v2); + if (res != null) return res; + if (Std.isOfType(v1, String) || Std.isOfType(v2, String)) return Std.string(v1) + Std.string(v2); + return v1 + v2; + }); + + binops.set("-", function(e1, e2):Dynamic { var v1:Dynamic = me.expr(e1); var v2:Dynamic = me.expr(e2); var res:Dynamic = resolveOp("-", v1, v2); if (res != null) return res; return v1 - v2; }); + binops.set("*", function(e1, e2):Dynamic { var v1:Dynamic = me.expr(e1); var v2:Dynamic = me.expr(e2); var res:Dynamic = resolveOp("*", v1, v2); if (res != null) return res; return v1 * v2; }); + binops.set("/", function(e1, e2):Dynamic { var v1:Dynamic = me.expr(e1); var v2:Dynamic = me.expr(e2); var res:Dynamic = resolveOp("/", v1, v2); if (res != null) return res; return v1 / v2; }); + binops.set("%", function(e1, e2):Dynamic { var v1:Dynamic = me.expr(e1); var v2:Dynamic = me.expr(e2); var res:Dynamic = resolveOp("%", v1, v2); if (res != null) return res; return v1 % v2; }); + + binops.set("==", function(e1, e2):Dynamic { var v1:Dynamic = me.expr(e1); var v2:Dynamic = me.expr(e2); var res:Dynamic = resolveOp("==", v1, v2); if (res != null) return res; return v1 == v2; }); + binops.set("!=", function(e1, e2):Dynamic { var v1:Dynamic = me.expr(e1); var v2:Dynamic = me.expr(e2); var res:Dynamic = resolveOp("!=", v1, v2); if (res != null) return res; return v1 != v2; }); + binops.set(">", function(e1, e2):Dynamic { var v1:Dynamic = me.expr(e1); var v2:Dynamic = me.expr(e2); var res:Dynamic = resolveOp(">", v1, v2); if (res != null) return res; return v1 > v2; }); + binops.set("<", function(e1, e2):Dynamic { var v1:Dynamic = me.expr(e1); var v2:Dynamic = me.expr(e2); var res:Dynamic = resolveOp("<", v1, v2); if (res != null) return res; return v1 < v2; }); + binops.set(">=", function(e1, e2):Dynamic { var v1:Dynamic = me.expr(e1); var v2:Dynamic = me.expr(e2); var res:Dynamic = resolveOp(">=", v1, v2); if (res != null) return res; return v1 >= v2; }); + binops.set("<=", function(e1, e2):Dynamic { var v1:Dynamic = me.expr(e1); var v2:Dynamic = me.expr(e2); var res:Dynamic = resolveOp("<=", v1, v2); if (res != null) return res; return v1 <= v2; }); + + assignOp("+=", function(v1:Dynamic, v2:Dynamic):Dynamic { + var res:Dynamic = resolveOp("+", v1, v2); + if (res != null) return res; + if (Std.isOfType(v1, String) || Std.isOfType(v2, String)) return Std.string(v1) + Std.string(v2); + return v1 + v2; + }); + + assignOp("-=", function(v1:Dynamic, v2:Dynamic):Dynamic { var res:Dynamic = resolveOp("-", v1, v2); if (res != null) return res; return v1 - v2; }); } override function resolve(id:String):Dynamic { - if (id == 'this') - return this; + if (id == 'this') + { + if (superInstance != null) return superInstance; + return this; + } if (id == 'super' && superInstance != null) return superInstance; @@ -116,15 +175,43 @@ class RuleScriptInterp extends hscript.Interp implements IInterp { var v = expr(e2); + #if hscriptPos + curExpr = e1; + #end + switch (hscript.Tools.expr(e1)) { case EIdent(id): - var l = locals.get(id); + if (id == "this" && superInstance != null && Std.isOfType(superInstance, rulescript.types.ScriptedAbstract.ScriptedAbstractInstance)) { + cast(superInstance, rulescript.types.ScriptedAbstract.ScriptedAbstractInstance).value = v; + return v; + } + + var l:Dynamic = locals.get(id); + + if (strictMode) { + if (l == null && !variables.exists(id) && !finalVariables.exists(id) && + (context == null || (!context.staticVariables.exists(id) && !context.publicVariables.exists(id))) && + (superInstance == null || (!superFields.contains(id) && !superFields.contains('set_' + id)))) + { + throw new haxe.Exception('Strict Mode Error: Undeclared variable "$id". Did you forget to write "var $id"?'); + } + + if (declaredVariableTypes.exists(id)) { + var expected = declaredVariableTypes.get(id); + if (!checkRuntimeType(v, expected)) { + var got = Type.getClassName(Type.getClass(v)) ?? Std.string(Type.typeof(v)); + throw new haxe.Exception('Type Mismatch Error: Variable "$id" expects type $expected, but got $got'); + } + } + } if (l == null) setVar(id, v); else { + if (l.isFinal) throw new haxe.Exception('Cannot reassign final variable: ' + id); + if (l.r is Property) cast(l.r, Property).value = v; else { @@ -150,6 +237,10 @@ class RuleScriptInterp extends hscript.Interp implements IInterp case EArray(e, index): var arr:Dynamic = expr(e); var index:Dynamic = expr(index); + + if (Std.isOfType(arr, rulescript.types.ScriptedAbstract.ScriptedAbstractInstance)) + arr = cast(arr, rulescript.types.ScriptedAbstract.ScriptedAbstractInstance).value; + if (isMap(arr)) { setMapValue(arr, index, v); @@ -168,15 +259,22 @@ class RuleScriptInterp extends hscript.Interp implements IInterp override function evalAssignOp(op:String, fop:(Dynamic, Dynamic) -> Dynamic, e1:Expr, e2:Expr):Dynamic { var v; + + #if hscriptPos + curExpr = e1; + #end + switch (hscript.Tools.expr(e1)) { case EIdent(id): - var l = locals.get(id); + var l:Dynamic = locals.get(id); v = fop(expr(e1), expr(e2)); if (l == null) setVar(id, v); else { + if (l.isFinal) throw new haxe.Exception('Cannot reassign final variable: ' + id); + if (l.r is Property) cast(l.r, Property).value = v; else @@ -195,6 +293,10 @@ class RuleScriptInterp extends hscript.Interp implements IInterp case EArray(e, index): var arr:Dynamic = expr(e); var index:Dynamic = expr(index); + + if (Std.isOfType(arr, rulescript.types.ScriptedAbstract.ScriptedAbstractInstance)) + arr = cast(arr, rulescript.types.ScriptedAbstract.ScriptedAbstractInstance).value; + if (isMap(arr)) { v = fop(getMapValue(arr, index), expr(e2)); @@ -220,7 +322,9 @@ class RuleScriptInterp extends hscript.Interp implements IInterp switch (e) { case EIdent(id): - var l = locals.get(id); + var l:Dynamic = locals.get(id); + if (l != null && l.isFinal) throw new haxe.Exception('Cannot increment/decrement final variable: ' + id); + var v:Dynamic = (l == null) ? resolve(id) : (l.r is Property ? cast(l.r, Property).value : l.r); if (prefix) @@ -263,6 +367,10 @@ class RuleScriptInterp extends hscript.Interp implements IInterp case EArray(e, index): var arr:Dynamic = expr(e); var index:Dynamic = expr(index); + + if (Std.isOfType(arr, rulescript.types.ScriptedAbstract.ScriptedAbstractInstance)) + arr = cast(arr, rulescript.types.ScriptedAbstract.ScriptedAbstractInstance).value; + if (isMap(arr)) { var v = getMapValue(arr, index); @@ -296,6 +404,8 @@ class RuleScriptInterp extends hscript.Interp implements IInterp override function setVar(name:String, v:Dynamic) { + if (finalVariables.exists(name)) throw new haxe.Exception('Cannot reassign global final variable: ' + name); + if (superInstance != null && (superFields.contains(name) || superFields.contains('set_' + name))) Reflect.setProperty(superInstance, name, v); else if (context != null && context.staticVariables.exists(name)) @@ -418,37 +528,64 @@ class RuleScriptInterp extends hscript.Interp implements IInterp } null; + case ':multiCatch': + final errInfo = locals.get("__err__"); + final err = errInfo?.r ?? null; + + @:privateAccess + final unwrappedErr = Std.isOfType(err, haxe.Exception) ? cast(err, haxe.Exception).unwrap() : err; + + for (catchNode in args) { + switch (catchNode.getExpr()) { + case EFunction(fargs, catchExpr, _, _): + final cname = fargs[0].name; + final expectedTypeStr = fargs[0].t != null ? rulescript.Tools.typeToString(fargs[0].t) : "Dynamic"; + + if (checkRuntimeType(unwrappedErr, expectedTypeStr)) { + declared.push({n: cname, old: locals.get(cname)}); + locals.set(cname, {r: unwrappedErr}); + return this.expr(catchExpr); + } + default: + } + } + #if hl hl.Api.rethrow(err); #else throw err; #end + default: (onMeta != null) ? onMeta(n, args, e) : exprMeta(n, args, e); } - case EVar(n, _, e, global, _): - if (global) - { - if (context == null || (!context.staticVariables.exists(n) && !context.publicVariables.exists(n))) + case EVar(n, tExpr, e, global, isFinal): + if (tExpr != null) declaredVariableTypes.set(n, rulescript.Tools.typeToString(tExpr)); + + if (global) { + if (context == null || (!context.staticVariables.exists(n) && !context.publicVariables.exists(n))) { variables.set(n, (e == null) ? null : this.expr(e)); - } - else - { + if (isFinal) finalVariables.set(n, true); + } + } else { declared.push({n: n, old: locals.get(n)}); - locals.set(n, {r: (e == null) ? null : this.expr(e)}); + var ref:Dynamic = {r: (e == null) ? null : this.expr(e)}; + if (isFinal) ref.isFinal = true; + locals.set(n, ref); } return null; case EProp(n, g, s, type, e, global): + if (type != null) declaredVariableTypes.set(n, rulescript.Tools.typeToString(type)); + var prop = createScriptProperty(n, g, s, type); - if (global) - variables.set(n, prop); - else - { + if (global) variables.set(n, prop); + else { declared.push({n: n, old: locals.get(n)}); locals.set(n, {r: prop}); } - if (e != null) - prop._lazyValue = () -> this.expr(e); + if (e != null) prop._lazyValue = () -> this.expr(e); return null; + case EIdent(id): - return resolve(id); // wuh + return resolve(id); + case ECall(e, params): var args = new Array(); for (p in params) @@ -517,7 +654,7 @@ class RuleScriptInterp extends hscript.Interp implements IInterp error(ECustom("Rest should only be used for the last function argument")); } - if (p.opt) + if (p.opt || p.value != null) hasOpt = true; else if (!isRest) minParams++; @@ -536,17 +673,15 @@ class RuleScriptInterp extends hscript.Interp implements IInterp error(ECustom(str)); } - if (params.length != args.length || hasRest) + if (hasOpt || hasRest || params.length != args.length) { final args2:Array = []; var argId = 0; var extraParams = args.length - minParams; - for (id => param in params) { var isRest = hasRest && id == params.length - 1 && Tools.isRest(param.t); - var arg:Dynamic = null; if (isRest) @@ -554,7 +689,7 @@ class RuleScriptInterp extends hscript.Interp implements IInterp arg = args.slice(argId); argId = args.length; } - else if (param.opt) + else if (param.opt || param.value != null) { if (extraParams > 0 && argId < args.length) { @@ -572,7 +707,7 @@ class RuleScriptInterp extends hscript.Interp implements IInterp } if (arg == null && param.value != null) - arg = this.expr(param.value); + arg = me.expr(param.value); args2.push(arg); } @@ -582,8 +717,19 @@ class RuleScriptInterp extends hscript.Interp implements IInterp var old = me.locals, depth = me.depth; me.depth++; me.locals = me.duplicate(capturedLocals); + for (i in 0...params.length) - me.locals.set(params[i].name, {r: args[i]}); + { + var pName = params[i].name; + if (pName != null) { + if (pName.indexOf(':') != -1) pName = pName.substring(0, pName.indexOf(':')); + if (pName.indexOf('=') != -1) pName = pName.substring(0, pName.indexOf('=')); + pName = StringTools.trim(pName); + } + + me.locals.set(pName, {r: args[i]}); + } + var r = null; var oldDecl = declared.length; if (inTry) @@ -640,20 +786,27 @@ class RuleScriptInterp extends hscript.Interp implements IInterp var match = false; for (c in cases) { - var old = declared.length; + final old = declared.length; + + final isGuard = c.expr != null && c.expr.getExpr().match(EMeta(":guard", _, _)); + final guardCond = isGuard ? switch(c.expr.getExpr()) { case EMeta(_, args, _): args[0]; default: null; } : null; + final actualExpr = isGuard ? switch(c.expr.getExpr()) { case EMeta(_, _, e): e; default: null; } : c.expr; - for (v in c.values) + for (v in c.values) { if (caseMatch(v, e, val)) { - match = true; - break; + if (guardCond == null || this.expr(guardCond) == true) { + match = true; + break; + } } - else - restore(old); + restore(old); + } if (match) { - val = this.expr(c.expr); + val = this.expr(actualExpr); + restore(old); break; } @@ -771,6 +924,9 @@ class RuleScriptInterp extends hscript.Interp implements IInterp */ override function get(o:Dynamic, f:String):Dynamic { + if (strictMode) + validateFieldAccess(o, f); + if (Tools.isEnum(o)) { if (Type.getEnumConstructs(o).contains(f)) @@ -825,6 +981,9 @@ class RuleScriptInterp extends hscript.Interp implements IInterp override function set(o:Dynamic, f:String, v:Dynamic):Dynamic { + if (strictMode) + validateFieldAccess(o, f); + if (o == null) error(EInvalidAccess(f)); @@ -859,27 +1018,25 @@ class RuleScriptInterp extends hscript.Interp implements IInterp override function call(o:Dynamic, f:Dynamic, args:Array):Dynamic { - if (o == superInstance) - isSuperCall = true; - if (f == superInstance) return call(o, resolve('__super_new'), args); #if rulescript_use_hl_fixes - final result:Dynamic = Tools.__hl_callMethod(f, args); + return Tools.__hl_callMethod(f, args); #else - final result:Dynamic = super.call(o, f, args); + return super.call(o, f, args); #end - - isSuperCall = false; - - return result; } override function fcall(o:Dynamic, f:String, args:Array):Dynamic { - return call(o, ((o == superInstance - && (locals.exists('__super_$f') || variables.exists('__super_$f'))) ? (resolve('__super_$f')) : get(o, f)), args); + if (o == superInstance) + { + final nativeSuper = Reflect.field(o, '__super_' + f); + if (nativeSuper != null) return call(o, nativeSuper, args); + } + + return call(o, get(o, f), args); } override function cnew(cl:String, args:Array):Dynamic @@ -903,7 +1060,20 @@ class RuleScriptInterp extends hscript.Interp implements IInterp case CLASS: return cast(c, ScriptedClass).createInstance(args); case ABSTRACT: - return cast(c, ScriptedAbstract).createInstance(args); + final inst = cast(c, ScriptedAbstract).createInstance(args); + + final ctor = inst.getVariable("new"); + if (ctor != null) { + final oldSuper = this.superInstance; + + this.superInstance = inst; + + Reflect.callMethod(inst, ctor, args); + + this.superInstance = oldSuper; + } + + return inst; default: } @@ -914,8 +1084,62 @@ class RuleScriptInterp extends hscript.Interp implements IInterp #end } + private function checkRuntimeType(value:Dynamic, expectedType:String):Bool { + if (value == null || expectedType == null || expectedType == "Dynamic" || expectedType == "Any") return true; + + switch(expectedType) { + case "Int": return Std.isOfType(value, Int); + case "Float": return Std.isOfType(value, Float) || Std.isOfType(value, Int); + case "Bool": return Std.isOfType(value, Bool); + case "String": return Std.isOfType(value, String); + default: + var cls = resolveType(expectedType); + return cls != null ? Std.isOfType(value, cls) : true; + } + } + + private function validateFieldAccess(obj:Dynamic, field:String):Void { + if (obj == null || obj == this) return; + + if (obj is RuleScriptedClass || obj is ScriptedType || obj is haxe.Constraints.IMap) return; + + final cls = Type.getClass(obj); + if (cls == null) return; + + final className = Type.getClassName(cls); + if (className == null || ["String", "Array"].contains(className)) return; + + function checkField(c:Class):Bool { + if (c == null) return false; + if (Type.getInstanceFields(c).contains(field) || Type.getClassFields(c).contains(field)) return true; + if (Type.getInstanceFields(c).contains('get_$field') || Type.getInstanceFields(c).contains('set_$field')) return true; + return checkField(Type.getSuperClass(c)); + } + + if (!checkField(cls)) { + throw new haxe.Exception('Strict Mode Error: Field "$field" does not exist on class $className'); + } + } + function caseMatch(ecase:Expr, evalue:Expr, value:Dynamic):Bool { + if (ecase != null) { + switch (ecase.getExpr()) { + case EIdent("_"): + return true; + case EIdent(id): + if (id != "true" && id != "false" && id != "null") { + final charCode = id.charCodeAt(0); + if (charCode >= 97 && charCode <= 122) { + declared.push({n: id, old: locals.get(id)}); + locals.set(id, {r: value}); + return true; + } + } + default: + } + } + if (value is ScriptedEnumInstance || Reflect.isEnumValue(value)) { final isEnumValue:Bool = Reflect.isEnumValue(value); @@ -1009,7 +1233,29 @@ class RuleScriptInterp extends hscript.Interp implements IInterp { hasErrorHandler = v != null; - return errorHandler = v; + if (v != null) + { + errorHandler = (exception:haxe.Exception) -> + { + #if hscriptPos + var pos = posInfos(); + @:privateAccess + if (pos != null && pos.lineNumber > 0 && !Std.isOfType(exception.unwrap(), hscript.Expr.Error)) + { + var msg = exception.message + ' (at ' + pos.fileName + ':' + pos.lineNumber + ')'; + exception = new haxe.Exception(msg, exception.previous != null ? exception.previous : exception); + } + #end + + v(exception); + }; + } + else + { + errorHandler = null; + } + + return v; } @:noCompletion @@ -1065,10 +1311,10 @@ class RuleScriptInterp extends hscript.Interp implements IInterp preExpr = rulescript.Tools.toExpr(EBlock(exprs.slice(0, superID))); postExpr = rulescript.Tools.toExpr(EBlock(exprs.slice(superID + 1))); - superCallArgs = superID == exprs.length ? null : switch (rulescript.Tools.getExpr(exprs[superID])) + superCallArgs = superID == exprs.length ? [] : switch (rulescript.Tools.getExpr(exprs[superID])) { case ECall(_, params): params; - default: null; + default: []; }; } } diff --git a/rulescript/macro/AbstractMacro.hx b/rulescript/macro/AbstractMacro.hx index 7fb5903..e9b6eb3 100644 --- a/rulescript/macro/AbstractMacro.hx +++ b/rulescript/macro/AbstractMacro.hx @@ -424,4 +424,4 @@ class AbstractMacro return macro $v{alias ?? typePath.fullPath} => $e{value}; } } -#end +#end \ No newline at end of file diff --git a/rulescript/macro/RuleScriptedClassMacro.hx b/rulescript/macro/RuleScriptedClassMacro.hx index 91d2d6a..89109b6 100644 --- a/rulescript/macro/RuleScriptedClassMacro.hx +++ b/rulescript/macro/RuleScriptedClassMacro.hx @@ -14,82 +14,57 @@ class RuleScriptedClassMacro public static macro function build():Array { + if (Context.getDisplayMode() != None) return null; + var pos = Context.currentPos(); var fields:Array = Context.getBuildFields(); - var typefields:Map = []; var curType = Context.getLocalClass().get(); - if (curType.meta.has(':noBuild')) - return fields; + if (curType.meta.has(':noBuild')) return fields; curType = curType.superClass.t.get(); - var constructor = curType.constructor?.get(); - var inlinedFields:Array = []; + while (curType != null) { for (field in curType.fields.get()) { if (!inlinedFields.contains(field.name) && field.kind.match(FMethod(MethInline))) - { inlinedFields.push(field.name); - } if (!typefields.exists(field.name) && !field.isFinal && field.kind.match(FMethod(_)) && !inlinedFields.contains(field.name)) typefields.set(field.name, field); } curType = curType.superClass?.t.get(); - constructor ??= curType.constructor?.get(); } createAliasMap(); - curType = Context.getLocalClass().get(); var ignoredFields:Array = []; - for (meta in curType.meta.extract(':ignoreFields')) - { - switch (meta.params[0].expr) - { + for (meta in curType.meta.extract(':ignoreFields')) { + switch (meta.params[0].expr) { case EArrayDecl(values): - for (value in values) - switch (value.expr) - { - case EConst(CIdent(s)): - ignoredFields.push(s); - default: - } - + for (value in values) switch (value.expr) { case EConst(CIdent(s)): ignoredFields.push(s); default: } default: } - }; + } final forceOverride = curType.meta.has(':forceOverride'); var forceOverrideFields:Array = null; - if (forceOverride) - { - for (meta in curType.meta.extract(':forceOverride')) - { - if (meta.params[0] != null) - switch (meta.params[0].expr) - { - case EArrayDecl(values): - forceOverrideFields ??= []; - - for (value in values) - switch (value.expr) - { - case EConst(CIdent(s)): - forceOverrideFields.push(s); - default: - } - default: - } + if (forceOverride) { + for (meta in curType.meta.extract(':forceOverride')) { + if (meta.params[0] != null) switch (meta.params[0].expr) { + case EArrayDecl(values): + forceOverrideFields ??= []; + for (value in values) switch (value.expr) { case EConst(CIdent(s)): forceOverrideFields.push(s); default: } + default: + } } } @@ -97,77 +72,56 @@ class RuleScriptedClassMacro { final forceOverrideField = forceOverrideFields?.contains(name) ?? forceOverride; if (!ignoredFields.contains(name)) - fields.push(overrideField(field, forceOverrideField)); - } + { + if (field.meta.has(':generic')) continue; + if (field.params.length > 0) continue; + + var hasPrivateArg = false; + switch(field.type) { + case TFun(args, ret): + for(arg in args) { + switch(arg.t) { + case TInst(ty, _): if(ty.get().isPrivate) hasPrivateArg = true; + default: + } + } + default: + } + if (hasPrivateArg) continue; - if (constructor.isFinal) - Context.error("Constructor can't be final in RuleScriptedClass", pos); + final overridenArray = overrideField(field, forceOverrideField); + if (overridenArray != null) + for (f in overridenArray) fields.push(f); + } + } + if (constructor.isFinal) Context.error("Constructor can't be final in RuleScriptedClass", pos); final strict = curType.meta.has(':strictScriptedConstructor') || curType.meta.has(':strictConstructor'); - final forceOverrideConstructor = forceOverrideFields?.contains('new') ?? forceOverride; - fields.push({ - name: 'new', - access: [APublic], - kind: FFun(createConstructor(constructor, strict, forceOverrideConstructor)), - pos: pos - }); - - fields.push({ - name: '__rulescript_strict', - access: [AStatic, AFinal], - kind: FVar(macro :Bool, macro $v{strict}), - pos: pos - }); - - fields.push({ - name: '__rulescript_type', - access: [APublic], - kind: FProp('get', 'never', macro :rulescript.types.ScriptedType.TypeID), - pos: pos, - meta: [{name: ':noCompletion', pos: pos}] - }); - - fields.push({ - name: '__rulescript', - access: [APublic], - kind: FVar(macro :rulescript.RuleScript), - pos: pos, - meta: [{name: ':noCompletion', pos: pos}] - }); + fields.push({ name: 'new', access: [APublic], kind: FFun(createConstructor(constructor, strict, forceOverrideConstructor)), pos: pos }); + fields.push({ name: '__rulescript_strict', access: [AStatic, AFinal], kind: FVar(macro :Bool, macro $v{strict}), pos: pos }); + fields.push({ name: '__rulescript_type', access: [APublic], kind: FProp('get', 'never', macro :rulescript.types.ScriptedType.TypeID), pos: pos, meta: [{name: ':noCompletion', pos: pos}] }); + fields.push({ name: '__rulescript', access: [APublic], kind: FVar(macro :rulescript.RuleScript), pos: pos, meta: [{name: ':noCompletion', pos: pos}] }); final functions = [ - 'getVariables' => macro function():Map - { - return __rulescript.variables; - }, - 'variableExists' => macro function(name:String):Bool - { - return __rulescript?.variables.exists(name); - }, - 'getVariable' => macro function(name:String):Dynamic - { - return __rulescript.variables[name]; - }, - 'setVariable' => macro function(name:String, value:Dynamic):Dynamic - { - return __rulescript.variables[name] = value; - }, - 'get___rulescript_type' => macro function():rulescript.types.ScriptedType.TypeID - { - return rulescript.types.ScriptedType.TypeID.CLASS; - } + 'getVariables' => macro function():Map { return __rulescript.variables; }, + 'variableExists' => macro function(name:String):Bool { return __rulescript?.variables.exists(name) || Reflect.hasField(this, name) || Reflect.getProperty(this, name) != null; }, + 'getVariable' => macro function(name:String):Dynamic { if (__rulescript.variables.exists(name)) return __rulescript.variables[name]; return Reflect.getProperty(this, name); }, + 'setVariable' => macro function(name:String, value:Dynamic):Dynamic { try { if (__rulescript.variables.exists(name) || (Reflect.getProperty(this, name) == null && !Reflect.hasField(this, name))) { return __rulescript.variables[name] = value; } Reflect.setProperty(this, name, value); return value; } catch(e:Dynamic) { return __rulescript.variables[name] = value; } }, + 'get___rulescript_type' => macro function():rulescript.types.ScriptedType.TypeID { return rulescript.types.ScriptedType.TypeID.CLASS; }, + '__super_new' => macro function(...args:Dynamic):Void {} ]; for (name => func in functions) - fields.push({ - name: name, - access: [APublic], - kind: FFun(MacroTools.toFunction(func)), - pos: pos, - meta: (name == 'get___rulescript_type') ? [{name: ':noCompletion', pos: pos}] : [] - }); + fields.push({ name: name, access: [APublic], kind: FFun(MacroTools.toFunction(func)), pos: pos, meta: (name == 'get___rulescript_type') ? [{name: ':noCompletion', pos: pos}] : [] }); + + var localClassName = curType.name; + var nativeClassName = curType.superClass.t.get().name; + + fields.push({ + name: '__init__', access: [AStatic], kind: FFun({ args: [], ret: macro :Void, expr: macro { rulescript.scriptedClass.RuleScriptedClassUtil.registerAutoWrapper($v{nativeClassName}, $i{localClassName}); } }), pos: pos + }); return fields; } @@ -175,251 +129,146 @@ class RuleScriptedClassMacro static function createConstructor(constructor:ClassField, strict:Bool = false, forceOverride:Bool):Function { var args = null; - - switch (constructor.type) - { - case TFun(_args, ret): - args = _args; - case TLazy(type): - switch (type()) - { - case TFun(_args, ret): - args = _args; - default: - }; + switch (constructor.type) { + case TFun(_args, ret): args = _args; + case TLazy(type): try { switch (type()) { case TFun(_args, ret): args = _args; default: } } catch (e:Dynamic) {} default: } + if (args == null) args = []; var fieldArgs = strict ? [for (argument in args) macro $i{argument.name}] : [macro args]; + var scriptSuperCall = [for (i in 0...args.length) macro superCallArgs[$v{i}]]; - var scriptSuperCall = [ - for (i in 0...args.length) - macro superCallArgs[$v{i}] - ]; - - var funcArgs:Array = [ - { - name: 'typeName', - type: macro :String - } - ]; - - if (strict) - funcArgs = funcArgs.concat([ - for (arg in args) - { - name: arg.name, - opt: arg.opt, - type: forceOverride ? macro :Dynamic : getOverrideType(arg.t) - } - ]); - else - funcArgs.push({ - name: 'args', - opt: true, - type: macro :Array - }); + var funcArgs:Array = [{ name: 'typeName', type: macro :String }]; + if (strict) funcArgs = funcArgs.concat([for (arg in args) { name: arg.name, opt: arg.opt, type: forceOverride ? macro :Dynamic : getOverrideType(arg.t) }]); + else funcArgs.push({ name: 'args', opt: true, type: macro :Array }); return { args: funcArgs, - expr: Context.getLocalClass().get().superClass != null ? macro - { - __rulescript = rulescript.scriptedClass.RuleScriptedClassUtil.buildRuleScript(typeName, this); - - $e{!strict ? macro args ??= [] : macro {}} // If args equals null - - if (__rulescript.access.hasConstructor) - { - final c = __rulescript.access.createConstructor($ - { - if (strict) - macro $a{fieldArgs} - else - macro args - }); - - c.preCall(); - - final superCallArgs:Array = c.lastSuperConstructor.getSuperArgs(); - - super($a{scriptSuperCall}); - c.postCall(); - } - else - { - super($a - { - strict ? fieldArgs : [for (i in 0...args.length) macro args[$v{i}]] - }); - } - } : macro {}, + expr: Context.getLocalClass().get().superClass != null ? macro { + __rulescript = rulescript.scriptedClass.RuleScriptedClassUtil.buildRuleScript(typeName, this); + $e{!strict ? macro args ??= [] : macro {}} + if (__rulescript.access.hasConstructor) { + final c = __rulescript.access.createConstructor(${if (strict) macro $a{fieldArgs} else macro args}); + c.preCall(); + final superCallArgs:Array = c.lastSuperConstructor.getSuperArgs(); + super($a{scriptSuperCall}); + c.postCall(); + } else { + super($a{strict ? fieldArgs : [for (i in 0...args.length) macro args[$v{i}]]}); + } + } : macro {}, params: forceOverride ? [] : [for (param in constructor.params) {name: param.name}] } } - static function overrideField(field:ClassField, forceOverride:Bool):Field + static function overrideField(field:ClassField, forceOverride:Bool):Array { var kind = null; - + var superKind = null; var fieldName = field.name; - var tFunToExpr:(Array, ret:haxe.macro.Type) -> Function = (args, ret) -> + var tFunToExpr:(Array, ret:haxe.macro.Type) -> {over: Function, sup: Function} = (args, ret) -> { - var fieldArgs = [ - for (argument in args) - macro $i{argument.name} + var fieldArgs = [for (argument in args) macro $i{argument.name}]; + final returnsVoid:Bool = getOverrideType(ret).match(TPath({name: 'StdTypes', params: [], sub: 'Void', pack: []})); + + var funcArgs:Array = [ + for (id => arg in args) { + name: arg.name, + type: forceOverride ? macro :Dynamic : getOverrideType(arg.t), + value: switch (Context.getTypedExpr(field.expr()).expr) { case EFunction(kind, f): f.args[id].value; default: null; } + } ]; - final returnsVoid:Bool = getOverrideType(ret).match(TPath({ - name: 'StdTypes', - params: [], - sub: 'Void', - pack: [] - })); + var funcRet:ComplexType = forceOverride ? (returnsVoid ? macro :StdTypes.Void : null) : getOverrideType(ret); - return { - args: [ - for (id => arg in args) - { - name: arg.name, - type: forceOverride ? macro :Dynamic : getOverrideType(arg.t), - value: switch (Context.getTypedExpr(field.expr()).expr) - { - case EFunction(kind, f): - f.args[id].value; - default: - null; - } - } - ], - ret: forceOverride ? (returnsVoid ? macro :StdTypes.Void : null) : getOverrideType(ret), - expr: macro - { - return if (!__rulescript.access.isSuperCall && __rulescript.access.variableExists($v{field.name})) - { - __rulescript.access.getVariable($v{field.name})($a{fieldArgs}); + var overExpr = if (returnsVoid) macro { + if (__rulescript != null && __rulescript.access != null && __rulescript.access.variableExists($v{fieldName})) { + try { + __rulescript.access.getVariable($v{fieldName})($a{fieldArgs}); + } catch (e:Dynamic) { + trace("Scripted Class Error: Purging broken function '" + $v{fieldName} + "' to prevent soft-lock: " + e); + __rulescript.variables.remove($v{fieldName}); + super.$fieldName($a{fieldArgs}); } - else - { - final lastIsSuperCall:Bool = __rulescript.access.isSuperCall; - $ - { - if (returnsVoid) - macro - { - __rulescript.access.isSuperCall = false; - super.$fieldName($a{fieldArgs}); - __rulescript.access.isSuperCall = lastIsSuperCall; - } - else - macro - { - __rulescript.access.isSuperCall = false; - final value = cast super.$fieldName($a{fieldArgs}); - __rulescript.access.isSuperCall = lastIsSuperCall; - value; - } - } + } else { + super.$fieldName($a{fieldArgs}); + } + } else macro { + if (__rulescript != null && __rulescript.access != null && __rulescript.access.variableExists($v{fieldName})) { + try { + return cast __rulescript.access.getVariable($v{fieldName})($a{fieldArgs}); + } catch (e:Dynamic) { + trace("Scripted Class Error: Purging broken function '" + $v{fieldName} + "' to prevent soft-lock: " + e); + __rulescript.variables.remove($v{fieldName}); + return cast super.$fieldName($a{fieldArgs}); } - }, - params: if (forceOverride) - [] - else - [ - for (param in field.params) - {name: param.name} - ] - } + } else { + return cast super.$fieldName($a{fieldArgs}); + } + }; + + var superExpr = if (returnsVoid) macro { + super.$fieldName($a{fieldArgs}); + } else macro { + return cast super.$fieldName($a{fieldArgs}); + }; + + return { + over: { args: funcArgs, ret: funcRet, expr: overExpr, params: forceOverride ? [] : [for (param in field.params) {name: param.name}] }, + sup: { args: funcArgs, ret: funcRet, expr: superExpr, params: forceOverride ? [] : [for (param in field.params) {name: param.name}] } + }; } - switch (field.type) - { + switch (field.type) { case TFun(args, ret): - kind = tFunToExpr(args, ret); + var res = tFunToExpr(args, ret); + kind = res.over; superKind = res.sup; case TLazy(type): - switch (type()) - { - case TFun(args, ret): - kind = tFunToExpr(args, ret); - default: - }; + try { switch (type()) { case TFun(args, ret): var res = tFunToExpr(args, ret); kind = res.over; superKind = res.sup; default: } } catch (e:Dynamic) {} default: } - return { - name: field.name, - access: [AOverride], - kind: FFun(kind), - pos: Context.currentPos() - }; - } + if (kind == null) return null; - inline static function getOverrideType(type:haxe.macro.Type):ComplexType - { - return type != null ? Context.toComplexType(transformTypeParams(type)) : null; + return [ + { name: field.name, access: [AOverride], kind: FFun(kind), pos: Context.currentPos() }, + { name: '__super_' + field.name, access: [APublic], kind: FFun(superKind), pos: Context.currentPos(), meta: [{name: ':noCompletion', pos: Context.currentPos()}] } + ]; } + inline static function getOverrideType(type:haxe.macro.Type):ComplexType { return type != null ? Context.toComplexType(transformTypeParams(type)) : null; } + static function transformTypeParams(type:haxe.macro.Type):haxe.macro.Type { - switch (type) - { + switch (type) { case TInst(t, params): - var _t = t; - var _params = params; - + var _t = t; var _params = params; var className = Context.getLocalClass().get().name; - - while (aliasMap.exists(className + _t.toString())) - { - _t = switch (aliasMap.get(className + _t.toString())) - { - case TInst(t, params): - _params = params; - t; - default: null; - }; + while (aliasMap.exists(className + _t.toString())) { + _t = switch (aliasMap.get(className + _t.toString())) { case TInst(t, params): _params = params; t; default: null; }; } - - for (id => param in _params) - _params[id] = transformTypeParams(param); - + for (id => param in _params) _params[id] = transformTypeParams(param); type = TInst(_t, _params); case TFun(args, ret): - var _args = args; - var _ret = ret; - - for (arg in _args) - { - arg.t = transformTypeParams(arg.t); - } - + var _args = args; var _ret = ret; + for (arg in _args) arg.t = transformTypeParams(arg.t); type = TFun(_args, transformTypeParams(_ret)); case TAbstract(t, params): type = TAbstract(t, [for (param in params) transformTypeParams(param)]); - default: - null; + default: null; } - return type; } static function createAliasMap():Void { var t:ClassType = Context.getLocalClass().get(); - - while (t != null) - { - for (id => param in t.superClass?.params ?? []) - { - switch (param) - { - case TInst(_t, params): - aliasMap.set(Context.getLocalClass().get().name + switch (t.superClass?.t.get().params[id].t) - { - case TInst(t, params): - t.toString(); - default: null; - }, param); + while (t != null) { + for (id => param in t.superClass?.params ?? []) { + switch (param) { + case TInst(_t, params): aliasMap.set(Context.getLocalClass().get().name + switch (t.superClass?.t.get().params[id].t) { case TInst(t, params): t.toString(); default: null; }, param); default: } } @@ -427,4 +276,4 @@ class RuleScriptedClassMacro } } } -#end +#end \ No newline at end of file diff --git a/rulescript/parsers/HxParser.hx b/rulescript/parsers/HxParser.hx index 71ef0b7..6c6d498 100644 --- a/rulescript/parsers/HxParser.hx +++ b/rulescript/parsers/HxParser.hx @@ -220,11 +220,8 @@ class HScriptParser extends hscript.Parser override function parseString(s:String, ?origin:String = "hscript", ?position:Int = 0):Expr { isMainBlock = true; - var e = super.parseString(s, origin, position); - isMainBlock = false; - return e; } @@ -692,6 +689,136 @@ class HScriptParser extends hscript.Parser parseContext(id, false); case 'static' if (mode == DEFAULT && allowStaticVariables): parseContext(id, true); + case "switch": + var e = parseExpr(); + var def = null, cases = []; + ensure(TBrOpen); + while (true) + { + var tk = token(); + switch (tk) + { + case TId("case"): + var c:Dynamic = {values: [], expr: null}; + cases.push(c); + + var guardCond:Expr = null; + + while (true) + { + var e = parseExpr(); + c.values.push(e); + tk = token(); + switch (tk) + { + case TComma: + case TId("if"): + ensure(TPOpen); + guardCond = parseExpr(); + ensure(TPClose); + ensure(TDoubleDot); + break; + case TDoubleDot: + break; + default: + unexpected(tk); + break; + } + if (tk == TDoubleDot || Type.enumEq(tk, TId("if"))) break; + } + + var exprs = []; + while (true) + { + tk = token(); + push(tk); + switch (tk) + { + case TId("case"), TId("default"), TBrClose: + break; + case TEof if (resumeErrors): + break; + default: + parseFullExpr(exprs); + } + } + + var caseExpr = if (exprs.length == 1) + exprs[0]; + else if (exprs.length == 0) + mk(EBlock([]), tokenMin, tokenMin); + else + mk(EBlock(exprs), pmin(exprs[0]), pmax(exprs[exprs.length - 1])); + + if (guardCond != null) + caseExpr = mk(EMeta(":guard", [guardCond], caseExpr), pmin(caseExpr), pmax(caseExpr)); + c.expr = caseExpr; + + case TId("default"): + if (def != null) unexpected(tk); + ensure(TDoubleDot); + var exprs = []; + while (true) + { + tk = token(); + push(tk); + switch (tk) + { + case TId("case"), TId("default"), TBrClose: + break; + case TEof if (resumeErrors): + break; + default: + parseFullExpr(exprs); + } + } + def = if (exprs.length == 1) + exprs[0]; + else if (exprs.length == 0) + mk(EBlock([]), tokenMin, tokenMin); + else + mk(EBlock(exprs), pmin(exprs[0]), pmax(exprs[exprs.length - 1])); + case TBrClose: + break; + default: + unexpected(tk); + break; + } + } + mk(ESwitch(e, cases, def), p1, tokenMax); + + case "try": + var e = parseExpr(); + var catches = []; + + while (true) { + var tk = token(); + if (!Type.enumEq(tk, TId("catch"))) { + push(tk); + break; + } + ensure(TPOpen); + + var vname = getIdent(); + ensure(TDoubleDot); + + var t = allowTypes ? parseType() : null; + if (!allowTypes) ensureToken(TId("Dynamic")); + + ensure(TPClose); + + var ec = parseExpr(); + catches.push(mk(EFunction([{name: vname, t: t}], ec, null, null))); + } + + if (catches.length == 0) unexpected(TId("try")); + + if (catches.length == 1) { + var f = switch (catches[0].getExpr()) { case EFunction(args, expr, _, _): {n: args[0].name, t: args[0].t, e: expr}; default: null; }; + return mk(ETry(e, f.n, f.t, f.e), p1, tokenMax); + } else { + return mk(ETry(e, "__err__", null, mk(EMeta(":multiCatch", catches, mk(EBlock([]))))), p1, tokenMax); + } default: super.parseStructure(id); } @@ -1719,6 +1846,12 @@ class HScriptParser extends hscript.Parser while (true) { var tk = token(); + + if (tk == TApostr) { + parseStringInterpolation(); + continue; + } + if (preprocStack[spos] != obj) { push(tk); diff --git a/rulescript/scriptedClass/RuleScriptedClass.hx b/rulescript/scriptedClass/RuleScriptedClass.hx index 4beca44..bbd650d 100644 --- a/rulescript/scriptedClass/RuleScriptedClass.hx +++ b/rulescript/scriptedClass/RuleScriptedClass.hx @@ -135,6 +135,8 @@ abstract Access(RuleScriptedClass) classImpl: impl })); + rulescript.scriptedClass.RuleScriptedClassUtil.registerRuleScriptedClass(toString(), this); + if (impl.extend != null) { var type = Tools.typeToString(impl.extend); @@ -167,7 +169,7 @@ abstract Access(RuleScriptedClass) } initialize = if (nativeClass == null && (superClass == null || superClass is ScriptedClass)) - ScriptedInstance.new.bind(this, _) + function(args) { return new ScriptedInstance(this, args); } else if (nativeClass != null) { var type = toString(); @@ -204,21 +206,51 @@ abstract Access(RuleScriptedClass) public function getVariables():Map { - return interp.access.getVariables(); + init(); + var vars:Map = []; + for (k => v in interp.access.getVariables()) vars.set(k, v); + + if (module.context != null) { + for (k => v in module.context.staticVariables) vars.set(k, v); + for (k => v in module.context.publicVariables) vars.set(k, v); + } + return vars; } public function variableExists(name:String):Bool { - return interp.access.variableExists(name); + init(); + return interp.access.variableExists(name) || (module.context != null && (module.context.staticVariables.exists(name) || module.context.publicVariables.exists(name))); } public function getVariable(name:String):Dynamic { - return interp.access.getVariable(name); + init(); + if (interp.access.variableExists(name)) + return interp.access.getVariable(name); + + if (module.context != null) { + if (module.context.staticVariables.exists(name)) + return module.context.staticVariables.get(name); + if (module.context.publicVariables.exists(name)) + return module.context.publicVariables.get(name); + } + return null; } public function setVariable(name:String, value:Dynamic):Dynamic { + init(); + if (module.context != null) { + if (module.context.staticVariables.exists(name)) { + module.context.staticVariables.set(name, value); + return value; + } + if (module.context.publicVariables.exists(name)) { + module.context.publicVariables.set(name, value); + return value; + } + } return interp.access.setVariable(name, value); } @@ -229,9 +261,54 @@ abstract Access(RuleScriptedClass) return CLASS; } - public function createInstance(args:Array) + public function createInstance(args:Array):Dynamic { - return initialize(args ?? []); + init(); + + if (impl?.extend != null) + { + final extendName = rulescript.Tools.typeToString(impl.extend); + final shortName = extendName.split(".").pop(); + + if (rulescript.scriptedClass.RuleScriptedClassUtil.autoWrappers != null) + { + final wrapperClass = rulescript.scriptedClass.RuleScriptedClassUtil.autoWrappers.get(extendName) + ?? rulescript.scriptedClass.RuleScriptedClassUtil.autoWrappers.get(shortName); + + if (wrapperClass != null) + { + final isStrict:Bool = Reflect.field(wrapperClass, "__rulescript_strict") == true; + final scriptName = this.toString(); + + try { + if (isStrict) { + final wrapperArgs:Array = [scriptName]; + if (args != null) for (a in args) wrapperArgs.push(a); + return Type.createInstance(wrapperClass, wrapperArgs); + } else { + return Type.createInstance(wrapperClass, [scriptName, args ?? []]); + } + } catch(e:Dynamic) { + trace('Scripted Class Error: Failed to create wrapper for "' + shortName + '": ' + e); + return null; + } + } else { + trace('Scripted Class Warning: Wrapper for "' + shortName + '" not found in registry. It might have been removed by Dead Code Elimination (DCE).'); + } + } + } + + if (nativeClass != null) { + try { + return Type.createInstance(nativeClass, args ?? []); + } catch(e:Dynamic) { + trace('Scripted Class Error: Failed to create native fallback class for "' + toString() + '": ' + e); + } + } + + initialize(args ?? []); + + return interp.access.superInstance ?? this; } @:access(rulescript.RuleScriptAccess) @@ -298,6 +375,8 @@ abstract Access(RuleScriptedClass) interp.access.superInstance = this; + interp.access.setVariable("this", this); + if (args != null) if (variableExists('new')) Reflect.callMethod(this, getVariable('new'), args); diff --git a/rulescript/scriptedClass/RuleScriptedClassUtil.hx b/rulescript/scriptedClass/RuleScriptedClassUtil.hx index 5204c6f..cf7733b 100644 --- a/rulescript/scriptedClass/RuleScriptedClassUtil.hx +++ b/rulescript/scriptedClass/RuleScriptedClassUtil.hx @@ -29,6 +29,18 @@ class RuleScriptedClassUtil public static var buildBridge:(typePath:String, superInstance:Dynamic) -> RuleScript; + public static var autoWrappers:Map; + + public static function registerAutoWrapper(nativeName:String, wrapperClass:Dynamic):Void + { + if (autoWrappers == null) + { + autoWrappers = new Map(); + } + + autoWrappers.set(nativeName, wrapperClass); + } + public static function buildRuleScript(typePath:String, superInstance:Dynamic):RuleScript { return if (buildBridge != null) @@ -71,6 +83,16 @@ class RuleScriptedClassUtil return cast types[typePath]; } + public static function listScriptClasses():Array + { + var result:Array = []; + for (key in types.keys()) + { + result.push(key); + } + return result; + } + public static function buildScriptedClass(cl:ScriptedClass, rulescript:RuleScript):Void { rulescript.access.setVariable('new', () -> {}); diff --git a/rulescript/types/ScriptedAbstract.hx b/rulescript/types/ScriptedAbstract.hx index 2744a72..da3436c 100644 --- a/rulescript/types/ScriptedAbstract.hx +++ b/rulescript/types/ScriptedAbstract.hx @@ -11,6 +11,8 @@ class ScriptedAbstract implements ScriptedType public var module:ScriptedModule; public var impl:AbstractDecl; + var opMap:Map; + var pack:String; public function new(impl:AbstractDecl, module:ScriptedModule) @@ -74,6 +76,53 @@ class ScriptedAbstract implements ScriptedType { return new ScriptedAbstractInstance(this, args); } + + function buildOpMap() { + if (opMap != null) return; + opMap = new Map(); + + for (field in impl.fields) { + if (field.meta != null) { + for (m in field.meta) { + if (m.name == ":op" || m.name == "op") { + if (m.params != null && m.params.length > 0) { + var exprDef = rulescript.Tools.getExpr(m.params[0]); + var opStr = null; + + switch (exprDef) { + case EBinop(o, _, _), EUnop(o, _, _): opStr = o; + default: + } + + if (opStr != null) { + var isStatic = field.access != null && field.access.contains(AStatic); + opMap.set(opStr, {field: field.name, isStatic: isStatic}); + } + } + } + } + } + } + } + + public function hasOperator(op:String):Bool { + if (opMap == null) buildOpMap(); + return opMap.exists(op); + } + + public function callOperator(op:String, a:Dynamic, b:Dynamic, isRight:Bool):Dynamic { + if (opMap == null) buildOpMap(); + + final opData = opMap.get(op); + if (opData == null) throw new haxe.Exception('Operator overload for "$op" not found in abstract ${impl.name}'); + + final inst:ScriptedAbstractInstance = cast a; + + final func:Dynamic = __impl.getVariable(opData.field); + if (func == null) throw new haxe.Exception('Function "${opData.field}" for operator "$op" is null or not found.'); + + return opData.isStatic ? Reflect.callMethod(null, func, isRight ? [b, inst] : [inst, b]) : Reflect.callMethod(inst, func, [b]); + } } @:noBuild @@ -89,29 +138,45 @@ class ScriptedAbstractInstance implements RuleScriptedClass return ABSTRACT; } - public function new(impl:ScriptedAbstract, value:Dynamic) + public function new(impl:ScriptedAbstract, args:Array) { this.impl = impl; - this.value = value; } - public function getVariables():Map - { - return null; - } + public function getVariables():Map return null; public function variableExists(name:String):Bool { - return impl.__impl.variableExists(name); + if (impl.__impl.variableExists(name)) return true; + if (value != null) { + try { return Reflect.hasField(value, name) || Reflect.getProperty(value, name) != null; } catch(e:Dynamic) {} + } + return false; } public function getVariable(name:String):Dynamic { - return impl.__impl.getVariable(name); + if (impl.__impl.variableExists(name)) return impl.__impl.getVariable(name); + + if (value != null) { + try { return Reflect.getProperty(value, name); } catch(e:Dynamic) {} + } + return null; } - public function setVariable(name:String, value:Dynamic):Dynamic + public function setVariable(name:String, val:Dynamic):Dynamic { - return null; + if (impl.__impl.variableExists(name)) { + var field = impl.__impl.getVariable(name); + if (field is rulescript.types.Property) { + cast(field, rulescript.types.Property).value = val; + return val; + } + } + + if (value != null) { + try { Reflect.setProperty(value, name, val); } catch(e:Dynamic) {} + } + return val; } -} +} \ No newline at end of file