diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..f00821a --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,25 @@ +name: Main + +on: [push, workflow_dispatch] + +jobs: + SEX: + runs-on: ubuntu-latest + steps: + - name: Checkout Commit + uses: actions/checkout@v4 + + - name: Install Haxe + uses: krdlab/setup-haxe@v1 + with: + haxe-version: 4.3.6 + + #忘加库了😅 + - name: Add Lib + run: | + haxelib install hxcpp --quiet + + - name: Testing + run: | + cd ./tests + haxe linux.hxml diff --git a/crowplexus/hscript/Bytes.hx b/crowplexus/hscript/Bytes.hx index d3df742..0c3c2e7 100644 --- a/crowplexus/hscript/Bytes.hx +++ b/crowplexus/hscript/Bytes.hx @@ -371,7 +371,7 @@ class Bytes { case EIdent(v): doEncodeExprType(EIdent); doEncodeString(v); - case EVar(n, _, e, c): + case EVar(n, _, e, getter, setter, c, s): doEncodeExprType(EVar); doEncodeString(n); if (e == null) diff --git a/crowplexus/hscript/Expr.hx b/crowplexus/hscript/Expr.hx index 9d8a8a2..a1922a9 100644 --- a/crowplexus/hscript/Expr.hx +++ b/crowplexus/hscript/Expr.hx @@ -64,7 +64,7 @@ enum Expr EConst(c:Const); EIdent(v:String); EImport(v:String, as:String); - EVar(n:String, ?t:CType, ?e:Expr, ?isConst:Bool); + EVar(n:String, ?t:CType, ?e:Expr, ?getter:String, ?setter:String, ?isConst:Bool, ?staticModifer: Bool); EParent(e:Expr); EBlock(e:Array); EField(e:Expr, f:String, s:Bool); @@ -76,7 +76,7 @@ enum Expr EFor(v:String, it:Expr, e:Expr); EBreak; EContinue; - EFunction(args:Array, e:Expr, ?name:String, ?ret:CType); + EFunction(args:Array, e:Expr, ?name:String, ?ret:CType, ?staticModifer: Bool); EReturn(?e:Expr); EArray(e:Expr, index:Expr); EArrayDecl(e:Array); diff --git a/crowplexus/hscript/IHScriptCustomBehaviour.hx b/crowplexus/hscript/IHScriptCustomBehaviour.hx new file mode 100644 index 0000000..570bb0b --- /dev/null +++ b/crowplexus/hscript/IHScriptCustomBehaviour.hx @@ -0,0 +1,6 @@ +package crowplexus.hscript; + +interface IHScriptCustomBehaviour { + public function hset(name:String, val:Dynamic):Dynamic; + public function hget(name:String):Dynamic; +} \ No newline at end of file diff --git a/crowplexus/hscript/Interp.hx b/crowplexus/hscript/Interp.hx index 751c9bd..a7651c4 100644 --- a/crowplexus/hscript/Interp.hx +++ b/crowplexus/hscript/Interp.hx @@ -24,11 +24,13 @@ package crowplexus.hscript; import Type.ValueType; import crowplexus.hscript.Expr; +import crowplexus.hscript.IHScriptCustomBehaviour; import crowplexus.hscript.Tools; import crowplexus.iris.Iris; import crowplexus.iris.IrisUsingClass; import crowplexus.iris.utils.UsingEntry; import haxe.Constraints.IMap; +import haxe.EnumTools; import haxe.PosInfos; private enum Stop { @@ -49,37 +51,80 @@ class DeclaredVar { public var old: LocalVar; } +@:allow(crowplexus.hscript.PropertyAccessor) class Interp { + public static var staticVariables: #if haxe3 Map = new Map() #else Hash = new Hash() #end; + public static var getRedirects: MapString->Dynamic> = []; + public static var setRedirects: MapString->Dynamic->Dynamic> = []; + + public var scriptObject(default, set): Dynamic; + public var parentInstance(default, set): Dynamic; + #if haxe3 public var variables: Map; public var imports: Map; - + public var props: Map; var locals: Map; var binops: MapExpr->Dynamic>; + var propertyLinks: Map; #else public var variables: Hash; public var imports: Hash; - + public var props: Hash; var locals: Hash; var binops: HashExpr->Dynamic>; + var propertyLinks: Hash; #end + var _parentFields: Array = []; + var __instanceFields: Array = []; var depth: Int; var inTry: Bool; var declared: Array; var returnValue: Dynamic; + @:noCompletion static var unpackClassCache: #if haxe3 Map = new Map() #else Hash = new Hash() #end; + #if hscriptPos var curExpr: Expr; #end public var showPosOnLog: Bool = true; + function set_scriptObject(v: Dynamic) { + __instanceFields = Type.getInstanceFields(Type.getClass(v)); + return scriptObject = v; + } + + function set_parentInstance(val: Dynamic): Dynamic { + if (val != null) { + switch (Type.typeof(val)) { + case Type.ValueType.TObject if (!(val is Enum)): + _parentFields = if (val is Class) Type.getClassFields(val) else Reflect.fields(val); + case Type.ValueType.TClass(_): + _parentFields = Type.getInstanceFields(Type.getClass(val)); + case _: + } + } + if (_parentFields == null) _parentFields = []; + return parentInstance = val; + } + public function new() { #if haxe3 locals = new Map(); + variables = new Map(); + props = new Map(); + imports = new Map(); + binops = new Map(); + propertyLinks = new Map(); #else locals = new Hash(); + variables = new Hash(); + props = new Hash(); + imports = new Hash(); + binops = new Hash(); + propertyLinks = new Hash(); #end declared = new Array(); resetVariables(); @@ -87,14 +132,6 @@ class Interp { } private function resetVariables() { - #if haxe3 - variables = new Map(); - imports = new Map(); - #else - variables = new Hash(); - imports = new Hash(); - #end - variables.set("null", null); variables.set("true", true); variables.set("false", false); @@ -117,11 +154,6 @@ class Interp { function initOps() { var me = this; - #if haxe3 - binops = new Map(); - #else - binops = new Hash(); - #end binops.set("+", function(e1, e2) return me.expr(e1) + me.expr(e2)); binops.set("-", function(e1, e2) return me.expr(e1) - me.expr(e2)); binops.set("*", function(e1, e2) return me.expr(e1) * me.expr(e2)); @@ -162,7 +194,34 @@ class Interp { } public inline function setVar(name: String, v: Dynamic) { - variables.set(name, v); + if (propertyLinks.exists(name)) { + var l = propertyLinks.get(name); + if (l.inState) + l.set(name, v); + else + l.link_setFunc(v); + return; + } + + if (staticVariables.exists(name)) { + staticVariables.set(name, v); + } else if (staticVariables.exists('$name;const')) { + warn(ECustom("Cannot reassign final, for constant expression -> " + name)); + } else if (parentInstance != null && (_parentFields.contains(name) || _parentFields.contains('set_$name'))) { + Reflect.setProperty(parentInstance, name, v); + } else if (scriptObject != null) { + if (Type.typeof(scriptObject) == TObject) { + Reflect.setField(scriptObject, name, v); + } else if (__instanceFields.contains(name)) { + Reflect.setProperty(scriptObject, name, v); + } else if (__instanceFields.contains('set_$name')) { + Reflect.getProperty(scriptObject, 'set_$name')(v); + } else { + variables.set(name, v); + } + } else { + variables.set(name, v); + } } function assign(e1: Expr, e2: Expr): Dynamic { @@ -170,9 +229,23 @@ class Interp { switch (Tools.expr(e1)) { case EIdent(id): var l = locals.get(id); - if (l == null) - setVar(id, v) - else { + if (l == null) { + if (parentInstance != null && (_parentFields.contains(id) || _parentFields.contains('set_$id'))) { + Reflect.setProperty(parentInstance, id, v); + } else if (scriptObject != null) { + if (Type.typeof(scriptObject) == TObject) { + Reflect.setField(scriptObject, id, v); + } else if (__instanceFields.contains(id)) { + Reflect.setProperty(scriptObject, id, v); + } else if (__instanceFields.contains('set_$id')) { + Reflect.getProperty(scriptObject, 'set_$id')(v); + } else { + setVar(id, v); + } + } else { + setVar(id, v); + } + } else { if (l.const != true) l.r = v; else @@ -194,7 +267,6 @@ class Interp { } else { arr[index] = v; } - default: error(EInvalidOp("=")); } @@ -212,9 +284,21 @@ class Interp { case EIdent(id): var l = locals.get(id); v = fop(expr(e1), expr(e2)); - if (l == null) - setVar(id, v) - else { + if (l == null) { + if (parentInstance != null && (_parentFields.contains(id) || _parentFields.contains('set_$id'))) { + Reflect.setProperty(parentInstance, id, v); + } else if (scriptObject != null) { + if (__instanceFields.contains(id)) { + Reflect.setProperty(scriptObject, id, v); + } else if (__instanceFields.contains('set_$id')) { + Reflect.getProperty(scriptObject, 'set_$id')(v); + } else { + setVar(id, v); + } + } else { + setVar(id, v); + } + } else { if (l.const != true) l.r = v; else @@ -256,7 +340,7 @@ class Interp { var v: Dynamic = (l == null) ? resolve(id) : l.r; function setTo(a) { if (l == null) - setVar(id, a) + setVar(id, a); else { if (l.const != true) l.r = a; @@ -264,13 +348,11 @@ class Interp { error(ECustom("Cannot reassign final, for constant expression -> " + id)); } } - if (l == null) { - if (prefix) { - v += delta; - setTo(v); - } else - setTo(v + delta); - } + if (prefix) { + v += delta; + setTo(v); + } else + setTo(v + delta); return v; case EField(e, f, s): var obj = expr(e); @@ -326,7 +408,7 @@ class Interp { function exprReturn(e): Dynamic { try { return expr(e); - } catch (e:Stop) { + } catch (e: Stop) { switch (e) { case SBreak: throw "Invalid break"; @@ -383,23 +465,56 @@ class Interp { } function resolve(id: String): Dynamic { - if (locals.exists(id)) { - var l = locals.get(id); - return l.r; + var l = locals.get(id); + if (l != null) return l.r; + + if (propertyLinks.exists(id)) { + var l = propertyLinks.get(id); + if (l.inState) + return l.get(id); + else + return l.link_getFunc(); } - if (variables.exists(id)) { - var v = variables.get(id); - return v; + if (staticVariables.exists(id)) + return staticVariables.get(id); + else if (staticVariables.exists('$id;const')) + return staticVariables.get('$id;const'); + + if (variables.exists(id)) + return variables.get(id); + + if (parentInstance != null) { + if (id == "this") + return parentInstance; + if (_parentFields.contains(id) || _parentFields.contains('get_$id')) + return Reflect.getProperty(parentInstance, id); } - if (imports.exists(id)) { - var v = imports.get(id); - return v; + if (scriptObject != null) { + if (id == "this") + return scriptObject; + if (Type.typeof(scriptObject) == TObject && Reflect.hasField(scriptObject, id)) + return Reflect.field(scriptObject, id); + if (__instanceFields.contains(id)) + return Reflect.getProperty(scriptObject, id); + if (__instanceFields.contains('get_$id')) + return Reflect.getProperty(scriptObject, 'get_$id')(); } - error(EUnknownVariable(id)); + if (imports.exists(id)) + return imports.get(id); + + if (unpackClassCache.exists(id)) + return unpackClassCache.get(id); + + var cl = Type.resolveClass(id); + if (cl != null) { + unpackClassCache.set(id, cl); + return cl; + } + error(EUnknownVariable(id)); return null; } @@ -427,9 +542,54 @@ class Interp { } case EIdent(id): return resolve(id); - case EVar(n, _, v, isConst): - declared.push({n: n, old: locals.get(n)}); - locals.set(n, {r: (v == null) ? null : expr(v), const: isConst}); + case EVar(n, _, v, getter, setter, isConst, s): + if (getter == null) getter = "default"; + if (setter == null) setter = "default"; + var v = (v == null ? null : expr(v)); + if (s == true) { + if (!staticVariables.exists(n) && !staticVariables.exists(n + ";const")) { + if (isConst) + staticVariables.set(n + ";const", v); + else { + staticVariables.set(n, v); + if (getter != "default" || setter != "default") { + propertyLinks.set(n, new PropertyAccessor(this, () -> { + if (staticVariables.exists(n)) + return staticVariables.get(n); + else + throw error(EUnknownVariable(n)); + return null; + }, (val) -> { + if (staticVariables.exists(n)) + staticVariables.set(n, val); + else + throw error(EUnknownVariable(n)); + return val; + }, getter, setter, true)); + } + } + } + } else { + if (!isConst && (getter != "default" || setter != "default")) { + props.set(n, v); + propertyLinks.set(n, new PropertyAccessor(this, () -> { + if (props.exists(n)) + return props.get(n); + else + throw error(EUnknownVariable(n)); + return null; + }, (val) -> { + if (props.exists(n)) + props.set(n, val); + else + throw error(EUnknownVariable(n)); + return val; + }, getter, setter)); + } else { + declared.push({n: n, old: locals.get(n)}); + locals.set(n, {r: v, const: isConst}); + } + } return null; case EParent(e): return expr(e); @@ -454,29 +614,22 @@ class Interp { return fop(e1, e2); case EUnop(op, prefix, e): return switch (op) { - case "!": - expr(e) != true; - case "-": - -expr(e); - case "++": - increment(e, prefix, 1); - case "--": - increment(e, prefix, -1); + case "!": expr(e) != true; + case "-": -expr(e); + case "++": increment(e, prefix, 1); + case "--": increment(e, prefix, -1); case "~": #if (neko && !haxe3) haxe.Int32.complement(expr(e)); #else ~expr(e); #end - default: - error(EInvalidOp(op)); - null; + default: error(EInvalidOp(op)); null; } case ECall(e, params): var args = new Array(); for (p in params) args.push(expr(p)); - switch (Tools.expr(e)) { case EField(e, f, s): var obj = expr(e); @@ -506,29 +659,24 @@ class Interp { returnValue = e == null ? null : expr(e); throw SReturn; case EImport(v, as): - final aliasStr = (as != null ? " named " + as : ""); // for errors + final aliasStr = (as != null ? " named " + as : ""); if (Iris.blocklistImports.contains(v)) { error(ECustom("You cannot add a blacklisted import, for class " + v + aliasStr)); return null; } - var n = Tools.last(v.split(".")); if (imports.exists(n)) return imports.get(n); - var c: Dynamic = getOrImportClass(v); - if (c == null) // if it's still null then throw an error message. + if (c == null) return warn(ECustom("Import" + aliasStr + " of class " + v + " could not be added")); else { imports.set(n, c); if (as != null) imports.set(as, c); - // resembles older haxe versions where you could use both the alias and the import - // for all the "Colour" enjoyers :D } - return null; // yeah. -Crow - - case EFunction(params, fexpr, name, _): + return null; + case EFunction(params, fexpr, name, _, s): var capturedLocals = duplicate(locals); var me = this; var hasOpt = false, minParams = 0; @@ -545,7 +693,6 @@ class Interp { str += " for function '" + name + "'"; error(ECustom(str)); } - // make sure mandatory args are forced var args2 = []; var extraParams = args.length - minParams; var pos = 0; @@ -570,7 +717,7 @@ class Interp { if (inTry) try { r = me.exprReturn(fexpr); - } catch (e:Dynamic) { + } catch (e: Dynamic) { me.locals = old; me.depth = depth; #if neko @@ -589,14 +736,16 @@ class Interp { var f = Reflect.makeVarArgs(f); if (name != null) { if (depth == 0) { - // global function - variables.set(name, f); + if (s == true) { + if (!staticVariables.exists(name)) + staticVariables.set(name, f); + } else + variables.set(name, f); } else { - // function-in-function is a local function declared.push({n: name, old: locals.get(name)}); var ref: LocalVar = {r: f, const: false}; locals.set(name, ref); - capturedLocals.set(name, ref); // allow self-recursion + capturedLocals.set(name, ref); } } return f; @@ -611,15 +760,15 @@ class Interp { for (e in arr) { switch (Tools.expr(e)) { case EBinop("=>", eKey, eValue): { - var key: Dynamic = expr(eKey); - var value: Dynamic = expr(eValue); - isAllString = isAllString && (key is String); - isAllInt = isAllInt && (key is Int); - isAllObject = isAllObject && Reflect.isObject(key); - isAllEnum = isAllEnum && Reflect.isEnumValue(key); - keys.push(key); - values.push(value); - } + var key: Dynamic = expr(eKey); + var value: Dynamic = expr(eValue); + isAllString = isAllString && (key is String); + isAllInt = isAllInt && (key is Int); + isAllObject = isAllObject && Reflect.isObject(key); + isAllEnum = isAllEnum && Reflect.isEnumValue(key); + keys.push(key); + values.push(value); + } default: throw("=> expected"); } } @@ -670,14 +819,12 @@ class Interp { restore(old); inTry = oldTry; return v; - } catch (err:Stop) { + } catch (err: Stop) { inTry = oldTry; throw err; - } catch (err:Dynamic) { - // restore vars + } catch (err: Dynamic) { restore(old); inTry = oldTry; - // declare 'v' declared.push({n: n, old: locals.get(n)}); locals.set(n, {r: err, const: false}); var v: Dynamic = expr(ecatch); @@ -733,7 +880,6 @@ class Interp { str += " for enum '" + enumName + "'"; error(ECustom(str)); } - // make sure mandatory args are forced var args2 = []; var extraParams = args.length - minParams; var pos = 0; @@ -751,11 +897,9 @@ class Interp { return new EnumValue(enumName, name, index, args); }; var f = Reflect.makeVarArgs(f); - Reflect.setField(obj, name, f); } } - variables.set(enumName, obj); case EDirectValue(value): return value; @@ -770,7 +914,7 @@ class Interp { do { try { expr(e); - } catch (err:Stop) { + } catch (err: Stop) { switch (err) { case SContinue: case SBreak: @@ -788,7 +932,7 @@ class Interp { while (expr(econd) == true) { try { expr(e); - } catch (err:Stop) { + } catch (err: Stop) { switch (err) { case SContinue: case SBreak: @@ -808,7 +952,7 @@ class Interp { #else try v = v.iterator() - catch (e:Dynamic) {}; + catch (e: Dynamic) {}; #end if (v.hasNext == null || v.next == null) error(EInvalidIterator(v)); @@ -825,7 +969,7 @@ class Interp { locals.set(n, {r: _itNext(), const: false}); try { expr(e); - } catch (err:Stop) { + } catch (err: Stop) { switch (err) { case SContinue: case SBreak: @@ -853,30 +997,55 @@ class Interp { function get(o: Dynamic, f: String): Dynamic { if (o == null) error(EInvalidAccess(f)); - return { - #if php - // https://github.com/HaxeFoundation/haxe/issues/4915 - try { - Reflect.getProperty(o, f); - } catch (e:Dynamic) { - Reflect.field(o, f); - } - #else - Reflect.getProperty(o, f); - #end + var cl: String = switch (Type.typeof(o)) { + case TNull: "Null"; + case TInt: "Int"; + case TFloat: "Float"; + case TBool: "Bool"; + case _: null; + }; + var redirect: Dynamic->String->Dynamic = getRedirects.exists(cl = Type.getClassName(Type.getClass(o))) ? getRedirects[cl] : null; + if (redirect != null) + return redirect(o, f); + if (o is IHScriptCustomBehaviour) { + var obj = cast(o, IHScriptCustomBehaviour); + return obj.hget(f); } + #if php + try { + return Reflect.getProperty(o, f); + } catch (e: Dynamic) { + return Reflect.field(o, f); + } + #else + var v = Reflect.getProperty(o, f); + if (v == null) + v = Reflect.getProperty(Type.getClass(o), f); + return v; + #end } function set(o: Dynamic, f: String, v: Dynamic): Dynamic { if (o == null) error(EInvalidAccess(f)); + var cl: String = switch (Type.typeof(o)) { + case TNull: "Null"; + case TInt: "Int"; + case TFloat: "Float"; + case TBool: "Bool"; + case _: null; + }; + var redirect: Dynamic->String->Dynamic->Dynamic = setRedirects.exists(cl = Type.getClassName(Type.getClass(o))) ? setRedirects[cl] : null; + if (redirect != null) + return redirect(o, f, v); + if (o is IHScriptCustomBehaviour) { + var obj = cast(o, IHScriptCustomBehaviour); + return obj.hset(f, v); + } Reflect.setProperty(o, f, v); return v; } - /** - * Meant for people to add their own usings. - **/ function registerUsingLocal(name: String, call: UsingCall): UsingEntry { var entry = new UsingEntry(name, call); usings.push(entry); @@ -891,7 +1060,6 @@ class Interp { return; } } - var cls = Tools.getClass(name); if (cls != null) { var fieldName = '__irisUsing_' + StringTools.replace(name, ".", "_"); @@ -899,62 +1067,41 @@ class Interp { var fields = Reflect.field(cls, fieldName); if (fields == null) return; - var entry = new UsingEntry(name, function(o: Dynamic, f: String, args: Array): Dynamic { if (!fields.exists(f)) return null; var type: ValueType = Type.typeof(o); var valueType: ValueType = fields.get(f); - - // If we figure out a better way to get the types as the real ValueType, we can use this instead - // if (Type.enumEq(valueType, type)) - // return Reflect.callMethod(cls, Reflect.field(cls, f), [o].concat(args)); - var canCall = valueType == null ? true : switch (valueType) { - case TEnum(null): - type.match(TEnum(_)); - case TClass(null): - type.match(TClass(_)); - case TClass(IMap): // if we don't check maps like this, it just doesn't work + case TEnum(null): type.match(TEnum(_)); + case TClass(null): type.match(TClass(_)); + case TClass(IMap): type.match(TClass(IMap) | TClass(haxe.ds.ObjectMap) | TClass(haxe.ds.StringMap) | TClass(haxe.ds.IntMap) | TClass(haxe.ds.EnumValueMap)); - default: - Type.enumEq(type, valueType); + default: Type.enumEq(type, valueType); } - return canCall ? Reflect.callMethod(cls, Reflect.field(cls, f), [o].concat(args)) : null; }); - #if IRIS_DEBUG trace("Registered macro based using entry for " + name); #end - Iris.registeredUsingEntries.push(entry); usings.push(entry); return; } - - // Use reflection to generate the using entry var entry = new UsingEntry(name, function(o: Dynamic, f: String, args: Array): Dynamic { if (!Reflect.hasField(cls, f)) return null; var field = Reflect.field(cls, f); if (!Reflect.isFunction(field)) return null; - - // invalid if the function has no arguments var totalArgs = Tools.argCount(field); if (totalArgs == 0) return null; - - // todo make it check if the first argument is the correct type - return Reflect.callMethod(cls, field, [o].concat(args)); }); - #if IRIS_DEBUG trace("Registered reflection based using entry for " + name); #end - Iris.registeredUsingEntries.push(entry); usings.push(entry); return; @@ -962,18 +1109,6 @@ class Interp { warn(ECustom("Unknown using class " + name)); } - /** - * List of components that allow using static methods on objects. - * This only works if you do - * ```haxe - * var result = "Hello ".trim(); - * ``` - * and not - * ```haxe - * var trim = "Hello ".trim; - * var result = trim(); - * ``` - */ var usings: Array = []; function fcall(o: Dynamic, f: String, args: Array): Dynamic { @@ -995,4 +1130,4 @@ class Interp { c = resolve(cl); return Type.createInstance(c, args); } -} + } diff --git a/crowplexus/hscript/Parser.hx b/crowplexus/hscript/Parser.hx index 2a53001..9f7cfe4 100644 --- a/crowplexus/hscript/Parser.hx +++ b/crowplexus/hscript/Parser.hx @@ -78,10 +78,10 @@ class Parser { /** allows to check for #if / #else in code **/ - public var preprocesorValues: Map = new Map(); + public var preprocessorValues: Map = new Map(); /** - activate JSON compatiblity + activate JSON compatibility **/ public var allowJSON: Bool; @@ -113,6 +113,9 @@ class Parser { var ops: Array; var idents: Array; var uid: Int = 0; + var abductCount: Int = 0; + var abducts = ["function", "if", "for", "while", "try", "switch"]; + var sureStaticModifier: Bool = false; #if hscriptPos var origin: String; @@ -132,6 +135,7 @@ class Parser { var tokens: haxe.FastList; #end #end + public function new() { line = 1; opChars = "+*/-=!><&|^%~"; @@ -182,6 +186,9 @@ class Parser { } public inline function error(err, pmin, pmax) { + if (sureStaticModifier) sureStaticModifier = false; + if (abductCount > 0) abductCount = 0; + if (!resumeErrors) #if hscriptPos throw new Error(err, pmin, pmax, origin, line); @@ -195,7 +202,7 @@ class Parser { } function initParser(origin) { - // line=1 - don't reset line : it might be set manualy + // line=1 - don't reset line : it might be set manually preprocStack = []; #if hscriptPos this.origin = origin; @@ -332,8 +339,8 @@ class Parser { return false; return switch (expr(e)) { case EBlock(_), EObject(_), ESwitch(_), EEnum(_, _): true; - case EFunction(_, e, _, _): isBlock(e); - case EVar(_, t, e, _): e != null ? isBlock(e) : t != null ? t.match(CTAnon(_)) : false; + case EFunction(_, e, _, _, _): isBlock(e); + case EVar(_, t, e, _, _, _, _): e != null ? isBlock(e) : t != null ? t.match(CTAnon(_)) : false; case EIf(_, e1, e2): if (e2 != null) isBlock(e2) else isBlock(e1); case EBinop(_, _, e): isBlock(e); case EUnop(_, prefix, e): !prefix && isBlock(e); @@ -413,7 +420,9 @@ class Parser { #end switch (tk) { case TId(id): + if (abducts.contains(id)) abductCount++; var e = parseStructure(id); + if (abducts.contains(id)) abductCount--; if (e == null) e = mk(EIdent(id)); return parseExprNext(e); @@ -423,7 +432,9 @@ class Parser { tk = token(); if (tk == TPClose) { ensureToken(TOp("->")); + abductCount++; var eret = parseExpr(); + abductCount--; return mk(EFunction([], mk(EReturn(eret), p1)), p1); } push(tk); @@ -487,6 +498,7 @@ class Parser { push(tk); } var a = new Array(); + abductCount++; while (true) { parseFullExpr(a); tk = token(); @@ -494,6 +506,7 @@ class Parser { break; push(tk); } + abductCount--; return mk(EBlock(a), p1); case TOp(op): if (op == "-") { @@ -659,10 +672,70 @@ class Parser { push(TSemicolon); } mk(EIf(cond, e1, e2), p1, (e2 == null) ? tokenMax : pmax(e2)); + case "static": + if (abductCount == 0) { + var t = token(); + switch (t) { + case TId(byd): + if (byd == "var" || byd == "final" || byd == "function") { + sureStaticModifier = true; + var ret = parseStructure(byd); + sureStaticModifier = false; + return ret; + } else if (byd == "inline") { + if (!maybe(TId("function"))) + return unexpected(TId(byd)); + sureStaticModifier = true; + var ret = parseStructure("function"); + sureStaticModifier = false; + return ret; + } else unexpected(TId(byd)); + default: unexpected(t); + } + } else error(ECustom('Cannot Set-up "$id" In Local.'), tokenMin, tokenMax); + null; case "var", "final": + var getter: String = "default"; + var setter: String = "default"; var ident = getIdent(); var tk = token(); var t = null; + if (tk == TPOpen) { + if (abductCount == 0 && id == "var") { + var getter1: Null = null; + var setter1: Null = null; + var displayComma: Bool = false; + var closed: Bool = false; + while (true) { + var t = token(); + switch (t) { + case TComma: + if (getter1 != null && !displayComma) { + displayComma = true; + } else unexpected(t); + case TId(byd): + if (getter1 == null && !displayComma) { + if (byd == "get" || byd == "never" || byd == "default" || byd == "null") { + getter1 = byd; + } else unexpected(t); + } else if (setter1 == null && displayComma) { + if (byd == "set" || byd == "never" || byd == "default" || byd == "null") { + setter1 = byd; + } else unexpected(t); + } else unexpected(t); + case TPClose: + if (getter1 != null && setter1 != null) closed = true; + else unexpected(t); + default: + unexpected(t); + } + if (closed) break; + } + if (getter1 != null) getter = getter1; + if (setter1 != null) setter = setter1; + tk = token(); + } else unexpected(tk); + } if (tk == TDoubleDot && allowTypes) { t = parseType(); tk = token(); @@ -672,7 +745,7 @@ class Parser { e = parseExpr(); else push(tk); - mk(EVar(ident, t, e, id == "final"), p1, (e == null) ? tokenMax : pmax(e)); + mk(EVar(ident, t, e, getter, setter, id == "final", abductCount == 0 ? sureStaticModifier : false), p1, (e == null) ? tokenMax : pmax(e)); case "while": var econd = parseExpr(); var e = parseExpr(); @@ -698,9 +771,29 @@ class Parser { case "continue": mk(EContinue); case "else": unexpected(TId(id)); case "inline": - if (!maybe(TId("function"))) - unexpected(TId("inline")); - return parseStructure("function"); + var t = token(); + switch (t) { + case TId(id): + if (id == "static") { + if (!sureStaticModifier && abductCount == 0) { + var t = token(); + switch (t) { + case TId(byd): + if (byd == "function") { + sureStaticModifier = true; + var ret = parseStructure(byd); + sureStaticModifier = false; + return ret; + } else unexpected(TId(id)); + default: unexpected(t); + } + } + } else if (id == "function") { + return parseStructure("function"); + } + default: + } + unexpected(t); case "function": var tk = token(); var name = null; @@ -709,7 +802,7 @@ class Parser { default: push(tk); } var inf = parseFunctionDecl(); - mk(EFunction(inf.args, inf.body, name, inf.ret), p1, pmax(inf.body)); + mk(EFunction(inf.args, inf.body, name, inf.ret, abductCount == 0 ? sureStaticModifier : false), p1, pmax(inf.body)); case "return": var tk = token(); push(tk); @@ -767,9 +860,6 @@ class Parser { case TComma: // next expr case TId("if"): - // if( Type.enumEq(e, EIdent("_")) ) - // unexpected(TId("if")); - var e = parseExpr(); c.ifExpr = e; switch tk = token() { @@ -801,7 +891,6 @@ class Parser { } c.expr = 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])); - for (i in c.values) { switch Tools.expr(i) { case EIdent("_"): @@ -840,7 +929,6 @@ class Parser { var path = [getIdent()]; var asStr: String = null; var star: Bool = false; - while (true) { var t = token(); if (t != TDot) { @@ -854,9 +942,7 @@ class Parser { default: unexpected(t); } } - final asErr = " -> " + path.join(".") + " as " + asStr; - if (maybe(TId("as"))) { asStr = getIdent(); final uppercased: Bool = asStr.charAt(0) == asStr.charAt(0).toUpperCase(); @@ -865,25 +951,13 @@ class Parser { if (!uppercased) error(ECustom("Import aliases must begin with an uppercase letter." + asErr), readPos, readPos); } - // trace(asStr); - /* - if (token() != TSemicolon) { - error(ECustom("Missing semicolon at the end of a \"import\" declaration. -> "+asErr), readPos, readPos); - null; - } - */ mk(EImport(path.join('.'), asStr)); - case "enum": var name = getIdent(); - ensure(TBrOpen); - var fields = []; - var currentName = ""; var currentArgs: Array = null; - while (true) { var tk = token(); switch (tk) { @@ -892,7 +966,6 @@ class Parser { case TSemicolon | TComma: if (currentName == "") continue; - if (currentArgs != null && currentArgs.length > 0) { fields.push(EnumType.EConstructor(currentName, currentArgs)); currentArgs = null; @@ -915,27 +988,11 @@ class Parser { currentName = name; } } - mk(EEnum(name, fields)); case "typedef": - // typedef Name = Type; - - /* - Ignore parsing if its, typedef Name = { - > Person - var name:String; - var age:Int; - } - - If the value is a class then it will be parsed as a EVar(Name, value); - */ - var name = getIdent(); - ensureToken(TOp("=")); - var t = parseType(); - switch (t) { case CTAnon(_) | CTExtend(_) | CTIntersection(_) | CTFun(_): mk(EIgnore(true)); @@ -944,10 +1001,8 @@ class Parser { var params = tp.params; if (params != null && params.length > 1) error(ECustom("Typedefs can't have parameters"), tokenMin, tokenMax); - if (path.length == 0) error(ECustom("Typedefs can't be empty"), tokenMin, tokenMax); - { var className = path.join("."); var cl = Tools.getClass(className); @@ -955,33 +1010,26 @@ class Parser { return mk(EVar(name, null, mk(EDirectValue(cl)))); } } - var expr = mk(EIdent(path.shift())); while (path.length > 0) { expr = mk(EField(expr, path.shift(), false)); } - - // todo? add import to the beginning of the file? mk(EVar(name, null, expr)); default: error(ECustom("Typedef, unknown type " + t), tokenMin, tokenMax); null; } - case "using": var path = parsePath(); mk(EUsing(path.join("."))); case "package": - // ignore package var tk = token(); push(tk); packageName = ""; if (tk == TSemicolon) return mk(EIgnore(false)); - var path = parsePath(); - // mk(EPackage(path.join("."))); - packageName = path.join("."); + packageName = path.join("."); mk(EIgnore(false)); default: null; @@ -993,19 +1041,21 @@ class Parser { switch (tk) { case TOp(op): if (op == "->") { - // single arg reinterpretation of `f -> e` , `(f) -> e` and `(f:T) -> e` switch (expr(e1)) { case EIdent(i), EParent(expr(_) => EIdent(i)): + abductCount++; var eret = parseExpr(); + abductCount--; return mk(EFunction([{name: i}], mk(EReturn(eret), pmin(eret))), pmin(e1)); case ECheckType(expr(_) => EIdent(i), t): + abductCount++; var eret = parseExpr(); + abductCount--; return mk(EFunction([{name: i, t: t}], mk(EReturn(eret), pmin(eret))), pmin(e1)); default: } unexpected(tk); } - if (opPriority.get(op) == -1) { if (isBlock(e1) || switch (expr(e1)) { case EParent(_): true; @@ -1013,7 +1063,7 @@ class Parser { }) { push(tk); return e1; - } + } return parseExprNext(mk(EUnop(op, false, e1), pmin(e1))); } return makeBinop(op, e1, parseExpr()); @@ -1154,20 +1204,16 @@ class Parser { })); case TPOpen: var a = token(), b = token(); - push(b); push(a); - function withReturn(args) { - switch token() { // I think it wouldn't hurt if ensure used enumEq + switch token() { case TOp('->'): case t: unexpected(t); } - return CTFun(args, parseType()); } - switch [a, b] { case [TPClose, _] | [TId(_), TDoubleDot]: var args = [ @@ -1177,18 +1223,15 @@ class Parser { case v: error(ECustom('Default values not allowed in function types'), #if hscriptPos v.pmin, v.pmax #else 0, 0 #end); } - CTNamed(arg.name, if (arg.opt) CTOpt(arg.t) else arg.t); } ]; - return withReturn(args); default: var t = parseType(); return switch token() { case TComma: var args = [t]; - while (true) { args.push(parseType()); if (!maybe(TComma)) @@ -1202,7 +1245,6 @@ class Parser { } } case TBrOpen: - var curType = null; var fields = []; var tps = []; var meta = null; @@ -1216,6 +1258,14 @@ class Parser { fields.push({name: name, t: parseType(), meta: meta}); meta = null; ensure(TSemicolon); + case TId("final"): + var name = getIdent(); + ensure(TDoubleDot); + if (meta == null) meta = []; + meta.push({name: ":final", params: []}); + fields.push({name: name, t: parseType(), meta: meta}); + meta = null; + ensure(TSemicolon); case TId(name): ensure(TDoubleDot); fields.push({name: name, t: parseType(), meta: meta}); @@ -1328,6 +1378,8 @@ class Parser { switch (tk) { case TMeta(name): meta.push({name: name, params: parseMetaArgs()}); + case TId("final"): + meta.push({name: ":final", params: []}); default: push(tk); break; @@ -1389,7 +1441,6 @@ class Parser { var params = parseParams(); var extend = null; var implement = []; - while (true) { var t = token(); switch (t) { @@ -1402,12 +1453,10 @@ class Parser { break; } } - var fields = []; ensure(TBrOpen); while (!maybe(TBrClose)) fields.push(parseField()); - return DClass({ name: name, meta: meta, @@ -1478,7 +1527,6 @@ class Parser { } var type = maybe(TDoubleDot) ? parseType() : null; var expr = maybe(TOp("=")) ? parseExpr() : null; - if (expr != null) { if (isBlock(expr)) maybe(TSemicolon); @@ -1488,7 +1536,6 @@ class Parser { maybe(TSemicolon); } else ensure(TSemicolon); - return { name: name, meta: meta, @@ -1586,24 +1633,24 @@ class Parser { } function token() { - #if hscriptPos - var t = tokens.pop(); - if (t != null) { - tokenMin = t.min; - tokenMax = t.max; - return t.t; - } - oldTokenMin = tokenMin; - oldTokenMax = tokenMax; - tokenMin = (this.char < 0) ? readPos : readPos - 1; - var t = _token(); - tokenMax = (this.char < 0) ? readPos - 1 : readPos - 2; - return t; - } function _token() { - #else - if (!tokens.isEmpty()) - return tokens.pop(); - #end + #if hscriptPos + var t = tokens.pop(); + if (t != null) { + tokenMin = t.min; + tokenMax = t.max; + return t.t; + } + oldTokenMin = tokenMin; + oldTokenMax = tokenMax; + tokenMin = (this.char < 0) ? readPos : readPos - 1; + var t = _token(); + tokenMax = (this.char < 0) ? readPos - 1 : readPos - 2; + return t; + } function _token() { + #else + if (!tokens.isEmpty()) + return tokens.pop(); + #end var char; if (this.char < 0) char = readChar(); @@ -1701,9 +1748,6 @@ class Parser { case '_'.code: default: this.char = char; - // we allow to parse hexadecimal Int32 in Neko, but when the value will be - // evaluated by Interpreter, a failure will occur if no Int32 operation is - // performed var v = try CInt(haxe.Int32.toInt(n)) catch (e:Dynamic) CInt32(n); return TConst(v); } @@ -1736,9 +1780,6 @@ class Parser { case '_'.code: default: this.char = char; - // we allow to parse binary Int32 in Neko, but when the value will be - // evaluated by Interpreter, a failure will occur if no Int32 operation is - // performed var v = try CInt(haxe.Int32.toInt(n)) catch (e:Dynamic) CInt32(n); return TConst(v); } @@ -1886,7 +1927,7 @@ class Parser { } function preprocValue(id: String): Dynamic { - return preprocesorValues.get(id); + return preprocessorValues.get(id); } var preprocStack: Array; @@ -1964,11 +2005,9 @@ class Parser { while (true) { var tk = token(); if (tk == TEof) { - // @see https://github.com/CodenameCrew/hscript-improved/pull/5/ if (preprocStack.length != 0) { error(EInvalidPreprocessor("Unclosed"), pos, pos); } else { - // trace("line: " + pos); break; } } @@ -2056,6 +2095,10 @@ class Parser { case TQuestionDot: "?."; } } + + // @:noCompletion public var preprocessorValues(get, set): Map; + inline function get_preprocessorValues() return this.preprocessorValues; + inline function set_preprocessorValues(v) return this.preprocessorValues = v; } @:structInit diff --git a/crowplexus/hscript/Printer.hx b/crowplexus/hscript/Printer.hx index 19a4764..a7c18fd 100644 --- a/crowplexus/hscript/Printer.hx +++ b/crowplexus/hscript/Printer.hx @@ -186,12 +186,21 @@ class Printer { } case EIdent(v): add(v); - case EVar(n, t, e, c): + case EVar(n, t, e, gt, st, c, s): + if(gt == null) gt = "default"; + if(st == null) st = "default"; + + if(s == true) add("static "); if (c) { add("final " + n); } else { add("var " + n); } + if(!c && (gt != "default" || st != "default")) { + add("("); + add(gt + ", " + st); + add(")"); + } addType(t); if (e != null) { add(" = "); @@ -287,7 +296,8 @@ class Printer { add("break"); case EContinue: add("continue"); - case EFunction(params, e, name, ret): + case EFunction(params, e, name, ret, s): + if(s == true) add("static "); add("function"); if (name != null) add(" " + name); diff --git a/crowplexus/hscript/PropertyAccessor.hx b/crowplexus/hscript/PropertyAccessor.hx new file mode 100644 index 0000000..5ffcc41 --- /dev/null +++ b/crowplexus/hscript/PropertyAccessor.hx @@ -0,0 +1,77 @@ +package crowplexus.hscript; + +//我又干了 +//呵呵,我乱命名的,别在意 +class PropertyAccessor { + public var getter(default, null):String; + public var setter(default, null):String; + public var proxy(default, null):Interp; + public var inState(default, null):Bool = true; + public var link_getFunc(default, null):Void->Dynamic; + public var link_setFunc(default, null):Dynamic->Dynamic; + + var isStatic:Bool; + + public function new(proxy:Interp, link_getFunc:Void->Dynamic, link_setFunc:Dynamic->Dynamic, getter1:String = "default", setter1:String = "default", isStatic:Bool = false) { + this.proxy = proxy; + this.link_getFunc = link_getFunc; + this.link_setFunc = link_setFunc; + this.getter = getter1; + this.setter = setter1; + this.isStatic = isStatic; + } + + public function get(name:String):Dynamic { + if(link_getFunc == null && proxy == null) return null; + return switch(getter) { + case "default": + link_getFunc(); + case "never": + throw proxy.error(ECustom('Cannot Access Read This Property -> "$name"')); + null; + case "null": + link_getFunc(); + case "get": + final variables = { + if(this.isStatic) Interp.staticVariables; + else proxy.variables; + } + if(Reflect.isFunction(variables.get('get_$name'))) { + inState = false; + var ret:Dynamic = Reflect.callMethod(null, variables.get('get_$name'), []); + inState = true; + return ret; + } else proxy.error(ECustom('Cannot Access Read This Property "$name" Due To Invalid Function -> "get_$name"')); + null; + default: + null; + } + } + + public function set(name:String, value:Dynamic) { + if(link_setFunc == null && proxy == null) return null; + return switch(setter) { + case "default": + link_setFunc(value); + case "never": + throw proxy.error(ECustom('Cannot Access Write This Property -> "$name"')); + null; + case "null": + link_setFunc(value); + case "set": + final variables = { + if(this.isStatic) Interp.staticVariables; + else proxy.variables; + } + if(Reflect.isFunction(variables.get('set_$name'))) { + inState = false; + var ret:Dynamic = Reflect.callMethod(null, variables.get('set_$name'), [value]); + inState = true; + return ret; + } else proxy.error(ECustom('Cannot Access Write This Property "$name" Due To Invalid Function -> "set_$name"')); + null; + default: + null; + } + } +} \ No newline at end of file diff --git a/crowplexus/hscript/Tools.hx b/crowplexus/hscript/Tools.hx index 67513a6..4c07dca 100644 --- a/crowplexus/hscript/Tools.hx +++ b/crowplexus/hscript/Tools.hx @@ -28,7 +28,7 @@ class Tools { public static function iter(e: Expr, f: Expr->Void) { switch (expr(e)) { case EConst(_), EIdent(_): - case EVar(_, _, e, _): + case EVar(_, _, e, getter, setter, _, s): if (e != null) f(e); case EParent(e): @@ -62,7 +62,7 @@ class Tools { f(it); f(e); case EBreak, EContinue: - case EFunction(_, e, _, _): + case EFunction(_, e, _, _, s): f(e); case EReturn(e): if (e != null) diff --git a/crowplexus/iris/Iris.hx b/crowplexus/iris/Iris.hx index 6c013d9..776427b 100644 --- a/crowplexus/iris/Iris.hx +++ b/crowplexus/iris/Iris.hx @@ -289,9 +289,9 @@ class Iris { * Appends Default Classes/Enums for the Script to use. **/ public function preset(): Void { - set("Std", Std); // TODO: add a proxy for std + /*set("Std", Std); set("StringTools", StringTools); - set("Math", Math); + set("Math", Math);*/ #if hscriptPos // overriding trace for good measure. // if you're a game developer or a fnf modder (hi guys), diff --git a/tests/assets/static_test1.hx b/tests/assets/static_test1.hx new file mode 100644 index 0000000..10bf496 --- /dev/null +++ b/tests/assets/static_test1.hx @@ -0,0 +1,11 @@ +static var allRight = "Come Heaven"; +static final allBad = "Go on The Hell"; + +function new() { + //reading + trace("static_test2 static var fan: " + fan); + var old = sad; + sad += " forever"; + trace("static_test2 static var sad: " + sad); + trace("writing sad compare: [" + old + "<=>" + sad + "]"); +} \ No newline at end of file diff --git a/tests/assets/static_test2.hx b/tests/assets/static_test2.hx new file mode 100644 index 0000000..2a6e9df --- /dev/null +++ b/tests/assets/static_test2.hx @@ -0,0 +1,11 @@ +static final fan:Int = 666; +static var sad:String = "alive"; + +function new() { + //reading + trace("static_test1 static var allbad: " + allBad); + var old = allRight; + allRight += " forever"; + trace("static_test1 static var allright: " + allRight); + trace("writing sad compare: [" + old + "<=>" + allRight + "]"); +} \ No newline at end of file diff --git a/tests/build-generic.hxml b/tests/build-generic.hxml index 7701093..aee6974 100644 --- a/tests/build-generic.hxml +++ b/tests/build-generic.hxml @@ -21,6 +21,8 @@ --resource assets/using.hx@assets/using.hx --resource assets/bytes.hx@assets/bytes.hx +--resource assets/static_test1.hx@assets/static_test1.hx +--resource assets/static_test2.hx@assets/static_test2.hx --resource assets/test.hx@assets/test.hx ../extraParams.hxml diff --git a/tests/src/Main.hx b/tests/src/Main.hx index b2db617..d3fe058 100644 --- a/tests/src/Main.hx +++ b/tests/src/Main.hx @@ -4,6 +4,7 @@ import sys.io.File; import haxe.io.Path; import sys.FileSystem; import crowplexus.iris.Iris; +import crowplexus.hscript.Interp; import crowplexus.hscript.Parser; import crowplexus.hscript.Printer; import crowplexus.hscript.Bytes; @@ -14,10 +15,19 @@ using StringTools; @:access(crowplexus.iris.Iris) class Main { static function main() { - // mainTest(); - // mainBytes(); - // testIndenticalNames(); + mainTest(); + mainBytes(); + testIndenticalNames(); testUsing(); + mainStatic(); + } + + static function mainStatic() { + var sm1:Iris = new Iris(Resource.getString("assets/static_test1.hx")); + var sm2:Iris = new Iris(Resource.getString("assets/static_test2.hx")); + sm1.call("new"); + sm2.call("new"); + trace("Static State: " + Interp.staticVariables); } /**