From 8dce40354a7f04a64c977ff90ea6fe9dacbf09b2 Mon Sep 17 00:00:00 2001 From: Jaime Humberto Macias Bustamante Date: Tue, 13 May 2025 00:38:11 -0700 Subject: [PATCH] "null" access handling + get/set for variables - It handles "null" allowing access only inside of the class. - "final" variables can be initialized in the constructor if they doesn't have a default value. - "var a(get, set):Int" is parsed outside of Custom Class, but for now it doesn't call their accessors. almost finished :3 --- hscript/Expr.hx | 16 ++++-- hscript/Interp.hx | 34 +++++++++++-- hscript/Parser.hx | 57 +++++++++++++++++++--- hscript/customclass/CustomClass.hx | 38 ++++++++++----- hscript/customclass/CustomClassDecl.hx | 67 ++++++++++++++++---------- 5 files changed, 158 insertions(+), 54 deletions(-) diff --git a/hscript/Expr.hx b/hscript/Expr.hx index a8d5317..0fc23a1 100644 --- a/hscript/Expr.hx +++ b/hscript/Expr.hx @@ -54,7 +54,7 @@ enum Expr { EIgnore(skip:Bool); EConst( c : Const ); EIdent( v : String ); - EVar( n : String, ?t : CType, ?e : Expr, ?isPublic : Bool, ?isStatic : Bool, ?isPrivate : Bool, ?isFinal : Bool, ?isInline : Bool, ?get : String, ?set : String ); + EVar( n : String, ?t : CType, ?e : Expr, ?isPublic : Bool, ?isStatic : Bool, ?isPrivate : Bool, ?isFinal : Bool, ?isInline : Bool, ?get : FieldPropertyAccess, ?set : FieldPropertyAccess ); EParent( e : Expr ); EBlock( e : Array ); EField( e : Expr, f : String , ?safe : Bool ); @@ -216,11 +216,11 @@ final class FunctionDecl { } typedef VarDecl = { - var get : Null; - var set : Null; + var get : FieldPropertyAccess; + var set : FieldPropertyAccess; + var isFinal : Bool; var expr : Null; var type : Null; - var isFinal : Null; } typedef TypeParamDecl = {}; @@ -229,3 +229,11 @@ enum EnumType { ESimple(name: String); EConstructor(name: String, args: Array); } + +enum abstract FieldPropertyAccess(UInt8) { + var ADefault; + var ANull; + var ANever; + var AGet; + var ASet; +} diff --git a/hscript/Interp.hx b/hscript/Interp.hx index ee2d4f9..e97b883 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -275,7 +275,9 @@ class Interp { public function setVar(name:String, v:Dynamic):Void { if (_inCustomClass && _proxy.superClass != null) { if (_proxy.superIsCustomClass) { + _proxy.__allowInnerAccess = true; cast(_proxy.superClass, CustomClass).hset(name, v); + _proxy.__allowInnerAccess = false; } else if (_proxy.superHasField(name)) { Reflect.setProperty(_proxy.superClass, name, v); @@ -299,7 +301,9 @@ class Interp { if (_inCustomClass) { if (_proxy.__class.hasField(id)) { var v = expr(e2); + _proxy.__class.__allowInnerAccess = true; _proxy.__class.hset(id, v); + _proxy.__class.__allowInnerAccess = false; return v; } /* @@ -324,7 +328,9 @@ class Interp { else { try { var v = expr(e2); + _proxy.__allowInnerAccess = true; _proxy.hset(id, v); // superClass check already handled in Custom Class + _proxy.__allowInnerAccess = false; return v; } catch(e) {} @@ -405,7 +411,9 @@ class Interp { else { try { var v = expr(e2); + _proxy.__allowInnerAccess = true; _proxy.hset(f, v); + _proxy.__allowInnerAccess = false; return v; } catch(e){} @@ -448,7 +456,9 @@ class Interp { // Also ensures property functions are accounted for. if(_inCustomClass) { if (_proxy.__class.hasField(id)) { + _proxy.__class.__allowInnerAccess = true; _proxy.__class.hset(id, v); + _proxy.__class.__allowInnerAccess = false; return v; } /* @@ -467,7 +477,9 @@ class Interp { } else { try { + _proxy.__allowInnerAccess = true; _proxy.hset(id, v); // superClass check already handled in Custom Class + _proxy.__allowInnerAccess = false; return v; } catch(e) {} @@ -552,7 +564,9 @@ class Interp { else { try { v = fop(get(obj, f), expr(e2)); + _proxy.__allowInnerAccess = true; _proxy.hset(f, v); // superClass check already handled in Custom Class + _proxy.__allowInnerAccess = false; return v; } catch(e) {} @@ -762,11 +776,20 @@ class Interp { // Custom Class if (_inCustomClass) { // Static access - if (_proxy.__class.hasField(id)) - return _proxy.__class.hget(id); + if (_proxy.__class.hasField(id)) { + _proxy.__class.__allowInnerAccess = true; + var r = _proxy.__class.hget(id); + _proxy.__class.__allowInnerAccess = false; + return r; + } - if (_proxy.hasVar(id)) - return _proxy.hget(id); + + if (_proxy.hasVar(id)) { + _proxy.__allowInnerAccess = true; + var r = _proxy.hget(id); + _proxy.__allowInnerAccess = false; + return r; + } // We are calling a LOCAL function from the same module. if (_proxy.hasFunction(id)) { _nextCallObject = _proxy; @@ -777,12 +800,15 @@ class Interp { return Reflect.getProperty(_proxy.superClass, id); } else { try { + _proxy.__allowInnerAccess = true; var r = _proxy.hget(id); _nextCallObject = _proxy; + _proxy.__allowInnerAccess = false; return r; } catch (e:Dynamic) { if(doException) error(EUnknownVariable(id)); + _proxy.__allowInnerAccess = false; } } } diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 3fc1d6a..d674ad7 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -22,6 +22,7 @@ package hscript; import haxe.rtti.Meta; import hscript.Expr; +import hscript.Expr.FieldPropertyAccess; enum Token { TEof; @@ -854,9 +855,33 @@ class Parser { } } var ident = getIdent(); - var get = null, set = null; - // TODO: getter and setter + var get = ADefault, set = ADefault; var tk = token(); + // TODO: throw "Missing ;" if tried to use (get, set) on final + // and "Property requires type-hint or initialization" on missing type. + if( tk == TPOpen && id != "final" ) { + var getId = getIdent(); + switch (getId) { + case 'default': // Do nothing + case 'get': get = AGet; + case 'null': get = ANull; + case 'never': get = ANever; + default: unexpected(TId(getId)); + } + ensure(TComma); + var setId = getIdent(); + switch (setId) { + case 'default': // Do nothing + case 'set': set = ASet; + case 'null': set = ANull; + case 'never': set = ANever; + default: unexpected(TId(getId)); + } + ensure(TPClose); + + tk = token(); + } + var t = null; nextType = null; if( tk == TDoubleDot && allowTypes ) { @@ -1779,11 +1804,27 @@ class Parser { }; case "var" | "final": var name = getIdent(); - var get = null, set = null; - if(id != "final" && maybe(TPOpen) ) { - get = getIdent(); + var get = ADefault, set = ADefault; + // TODO: throw "Missing ;" if tried to use (get, set) on final + // and "Property requires type-hint or initialization" on missing type. + if(maybe(TPOpen) && id != "final") { + var getId = getIdent(); + switch(getId) { + case 'default': // Do nothing + case 'get': get = AGet; + case 'null': get = ANull; + case 'never': get = ANever; + default: unexpected(TId(getId)); + } ensure(TComma); - set = getIdent(); + var setId = getIdent(); + switch(setId) { + case 'default': // Do nothing + case 'set': set = ASet; + case 'null': set = ANull; + case 'never': set = ANever; + default: unexpected(TId(getId)); + } ensure(TPClose); } var type = maybe(TDoubleDot) ? parseType() : null; @@ -1806,9 +1847,9 @@ class Parser { kind : KVar({ get : get, set : set, + isFinal: id == "final", type : type, - expr : expr, - isFinal: id == "final" + expr : expr }), }; default: diff --git a/hscript/customclass/CustomClass.hx b/hscript/customclass/CustomClass.hx index d1af6b3..f5a6d96 100644 --- a/hscript/customclass/CustomClass.hx +++ b/hscript/customclass/CustomClass.hx @@ -3,6 +3,7 @@ package hscript.customclass; import hscript.utils.UnsafeReflect; import haxe.Constraints.Function; import hscript.Expr; +import hscript.Expr.FieldPropertyAccess; import hscript.Expr.VarDecl; import hscript.Expr.FunctionDecl; import hscript.Expr.FieldDecl; @@ -44,9 +45,11 @@ class CustomClass implements IHScriptCustomAccessBehaviour { private var __cachedVarDecls:Map = []; public var __allowSetGet:Bool = false; + public var __allowInnerAccess:Bool = false; private var isInline(default, null):Bool = false; private var ogVariables(default, null):Map; + private var initializing(default, null):Bool = false; // Allows final variables to be initialized public function new(__class:CustomClassDecl, args:Array, ?extendFieldDecl:Map, ?ogInterp:Interp, ?callNew:Bool = true) { this.__class = __class; @@ -76,7 +79,10 @@ class CustomClass implements IHScriptCustomAccessBehaviour { if (hasFunction('new') && callNew) { buildSuperConstructor(); + initializing = true; callFunction('new', args); + initializing = false; + if (this.superClass == null && this.__class.classDecl.extend != null) this.interp.error(ECustom("super() not called")); } else if (__class.classDecl.extend != null) { @@ -94,8 +100,6 @@ class CustomClass implements IHScriptCustomAccessBehaviour { __cachedFieldDecls.set(f.name, f); switch (f.kind) { case KFunction(fn): - if(f.name.startsWith('set_') || f.name.startsWith('get_')) - __allowSetGet = true; __cachedFunctionDecls.set(f.name, fn); #if hscriptPos var fexpr:Expr = { @@ -111,6 +115,8 @@ class CustomClass implements IHScriptCustomAccessBehaviour { var func:Function = this.interp.expr(fexpr); this.interp.variables.set(f.name, func); case KVar(v): + if(v.get != ADefault || v.set != ADefault) + __allowSetGet = true; __cachedVarDecls.set(f.name, v); if (v.expr != null) { var varValue = this.interp.expr(v.expr); @@ -316,15 +322,19 @@ class CustomClass implements IHScriptCustomAccessBehaviour { if (hasVar(name)) { var v = getVar(name); - var getter = v.get != null ? v.get : 'default'; + var getter = v.get; var value:Dynamic = null; - if(getter == "never") + if(getter == ANever || getter == ANull && !__allowInnerAccess) throw 'field $name cannot be accessed for reading'; - if (__allowSetGet && getter == "get" && hasFunction('get_${name}')) - value = __callGetter(name); + if (__allowSetGet && getter == AGet) { + if (hasFunction('get_$name')) + value = __callGetter(name); + else + throw 'Method get_$name required by property $name is missing'; + } else if (this.interp.variables.exists(name)) value = this.interp.variables.get(name); else { @@ -370,13 +380,17 @@ class CustomClass implements IHScriptCustomAccessBehaviour { default: if (hasVar(name)) { var v = getVar(name); - var setter = v.set != null ? v.set : 'default'; + var setter = v.set; - if (setter == "never" || (v.isFinal != null && v.isFinal)) + if (setter == ANever || setter == ANull && !__allowInnerAccess || (v.isFinal && !initializing)) throw 'field $name cannot be accessed for writing'; - if (__allowSetGet && setter == "set" && hasFunction('set_${name}')) - return __callSetter(name, val); + if (__allowSetGet && setter == ASet) { + if (hasFunction('set_$name')) + return __callSetter(name, val); + else + throw 'Method set_$name required by property $name is missing'; + } this.interp.variables.set(name, val); } else if (this.superClass != null) { @@ -411,14 +425,14 @@ class CustomClass implements IHScriptCustomAccessBehaviour { public function __callGetter(name:String):Dynamic { __allowSetGet = false; - var r = callFunction('get_${name}'); + var r = callFunction('get_$name'); __allowSetGet = true; return r; } public function __callSetter(name:String, val:Dynamic):Dynamic { __allowSetGet = false; - var r = callFunction('set_${name}', [val]); + var r = callFunction('set_$name', [val]); __allowSetGet = true; return r; } diff --git a/hscript/customclass/CustomClassDecl.hx b/hscript/customclass/CustomClassDecl.hx index a532f90..518ef00 100644 --- a/hscript/customclass/CustomClassDecl.hx +++ b/hscript/customclass/CustomClassDecl.hx @@ -31,6 +31,8 @@ class CustomClassDecl implements IHScriptCustomAccessBehaviour { public var __allowSetGet:Bool = true; + public var __allowInnerAccess:Bool = false; + public function new(classDecl:Expr.ClassDecl, imports:Map, usings:Array, ?pkg:Array, ?ogInterp:Interp, ?isInline:Bool) { this.classDecl = classDecl; this.imports = imports; @@ -111,6 +113,8 @@ class CustomClassDecl implements IHScriptCustomAccessBehaviour { var func:Function = this.staticInterp.expr(fexpr); this.staticInterp.variables.set(f.name, func); case KVar(v): + if(v.get != ADefault || v.set != ADefault) + __allowSetGet = true; _cachedStaticVariables.set(f.name, v); if (v.expr != null) { var varValue = this.staticInterp.expr(v.expr); @@ -166,24 +170,8 @@ class CustomClassDecl implements IHScriptCustomAccessBehaviour { return _cachedStaticVariables.exists(name); } - private function getVar(name:String):Dynamic { - var staticVar = _cachedStaticVariables.get(name); - - if(staticVar != null) { - var varValue:Dynamic = null; - if(!this.staticInterp.variables.exists(name)) { - if(staticVar.expr != null) { - varValue = this.staticInterp.expr(staticVar.expr); - this.staticInterp.variables.set(name, varValue); - } - } - else { - varValue = this.staticInterp.variables.get(name); - } - return varValue; - } - - return null; + private function getVar(name:String):VarDecl { + return _cachedStaticVariables.get(name); } /** @@ -198,16 +186,33 @@ class CustomClassDecl implements IHScriptCustomAccessBehaviour { } public function hget(name:String):Dynamic { - var r:Dynamic = null; - if(hasVar(name)) { - if(__allowSetGet && hasFunction('get_${name}')) - r = __callGetter(name); - else - r = getVar(name); + var v = getVar(name); + var getter = v.get; + + var r:Dynamic = null; + + if (getter == ANever || getter == ANull && !__allowInnerAccess) + throw 'field $name cannot be accessed for reading'; + + if(__allowSetGet && getter == AGet){ + if(hasFunction('get_$name')) + r = __callGetter(name); + else + throw 'Method get_$name required by property $name is missing'; + } + else if (this.staticInterp.variables.exists(name)) + r = this.staticInterp.variables.get(name); + else { + if(v.expr != null) { + r = this.staticInterp.expr(v.expr); + this.staticInterp.variables.set(name, r); + } + } return r; } if(hasFunction(name)) { + // TODO: optimize this var fn:Function = Reflect.makeVarArgs(function(args:Array) { return this.callFunction(name, args); }); @@ -219,8 +224,18 @@ class CustomClassDecl implements IHScriptCustomAccessBehaviour { public function hset(name:String, val:Dynamic):Dynamic { if (hasVar(name)) { - if (__allowSetGet && hasFunction('set_${name}')) - return __callSetter(name, val); + var v = getVar(name); + var setter = v.set; + + if (setter == ANever || setter == ANull && !__allowInnerAccess || v.isFinal) + throw 'field $name cannot be accessed for writing'; + + if (__allowSetGet && setter == ASet) { + if (hasFunction('set_$name')) + return __callSetter(name, val); + else + throw 'Method set_$name required by property $name is missing'; + } else { this.staticInterp.variables.set(name, val); return val;