Custom Classes Rework

This commit is contained in:
Jaime Humberto Macias Bustamante
2025-01-21 00:27:11 -07:00
parent fe673c21b2
commit c45373aa55
8 changed files with 1040 additions and 25 deletions
+1
View File
@@ -1,2 +1,3 @@
bin/build-each.hxml
-D hscriptPos
-hl bin/Test.hl
+1
View File
@@ -155,6 +155,7 @@ class CustomClassHandler implements IHScriptCustomConstructor {
class CustomTemplateClass implements IHScriptCustomClassBehaviour implements IHScriptCustomAccessBehaviour {
public var __interp:Interp;
public var __customClass:hscript.customclass.CustomClass;
public var __allowSetGet:Bool = true;
public var __custom__variables:Map<String, Dynamic>;
public var __real_fields:Array<String>;
+1
View File
@@ -2,6 +2,7 @@ package hscript;
interface IHScriptCustomClassBehaviour {
public var __interp:Interp;
public var __customClass:hscript.customclass.CustomClass;
public var __custom__variables:Map<String, Dynamic>;
public var __allowSetGet:Bool;
public var __real_fields:Array<String>;
+338 -4
View File
@@ -28,6 +28,8 @@
*/
package hscript;
import hscript.customclass.CustomClassDecl;
import hscript.customclass.CustomClass;
import haxe.CallStack;
import hscript.utils.UnsafeReflect;
import haxe.PosInfos;
@@ -65,8 +67,30 @@ class RedeclaredVar {
public var depth:Int;
}
@:access(hscript.customclass.CustomClass)
@:access(hscript.customclass.CustomClassDecl)
@:analyzer(optimize, local_dce, fusion, user_var_fusion)
class Interp {
private static var _customClasses:Map<String, CustomClassDecl> = new Map<String, CustomClassDecl>();
public static function getCustomClass(name:String) {
return _customClasses.get(name);
}
public static function customClassExist(name:String):Bool {
return _customClasses.exists(name);
}
private static function registerCustomClass(c:CustomClassDecl) {
#if CUSTOM_CLASSES
var name = c.classDecl.name;
if (c.pkg != null) {
name = c.pkg.join(".") + "." + name;
}
_customClasses.set(name, c);
#end
}
public var scriptObject(default, set):Dynamic;
private var _hasScriptObject(default, null):Bool = false;
private var _scriptObjectType(default, null):ScriptObjectType = SNull;
@@ -137,11 +161,21 @@ class Interp {
var curExpr:Expr;
#end
public function new() {
var _proxy(default, null):Null<CustomClass> = null;
var _nextCallObject(default, null):Dynamic = null;
var _inCustomClass(get, never):Bool;
private function get__inCustomClass():Bool {
return _proxy != null;
}
public function new(?proxy:CustomClass) {
locals = new Map();
declared = [];
resetVariables();
initOps();
this._proxy = proxy;
}
private function resetVariables():Void {
@@ -232,6 +266,12 @@ class Interp {
}
public function setVar(name:String, v:Dynamic):Void {
if (_inCustomClass && _proxy.superClass != null) {
if (_proxy.superHasField(name)) {
Reflect.setProperty(_proxy.superClass, name, v);
}
}
if (allowStaticVariables && staticVariables.exists(name))
staticVariables.set(name, v);
else if (allowPublicVariables && publicVariables.exists(name))
@@ -244,6 +284,28 @@ class Interp {
var v = expr(e2);
switch (Tools.expr(e1)) {
case EIdent(id):
// Make sure setting superclass/static fields directly works.
// Also ensures property functions are accounted for.
if (_inCustomClass) {
if (_proxy.__class.hasField(id)) {
var v = expr(e2);
_proxy.__class.hset(id, v);
return v;
} else if (_proxy.superClass != null && _proxy.superHasField(id)) {
var v = expr(e2);
Reflect.setProperty(_proxy.superClass, id, v);
return v;
} else if (_proxy.hasVar(id)) {
var v = expr(e2);
_proxy.hset(id, v);
return v;
} else if (_proxy.superClass == null && _proxy.__class.classDecl.extend != null) {
// Caches the declaration to set it once superClass is created
var v = expr(e2);
_proxy.cacheSuperField(id, v);
return v;
}
}
var l = locals.get(id);
if (l == null) {
if (_hasScriptObject && !varExists(id)) {
@@ -300,6 +362,33 @@ class Interp {
}
// TODO
case EField(e, f, s):
if(_inCustomClass) {
switch (Tools.expr(e)) {
case EIdent(id0):
if (id0 == "this") {
if (_proxy.hasField(f)) {
var v = expr(e2);
_proxy.hset(f, v);
return v;
}
if (_proxy.superClass != null) {
if (_proxy.superHasField(f)) {
var v = expr(e2);
Reflect.setProperty(_proxy.superClass, f, v);
return v;
}
}
else if(_proxy.superClass == null && _proxy.__class.classDecl.extend != null){
// Caches the declaration to set it once superClass is created
var v = expr(e2);
_proxy.cacheSuperField(f, v);
return v;
}
}
default:
// Do nothing
}
}
var obj = expr(e);
if(s && obj == null) return null;
v = set(obj, f, v);
@@ -556,6 +645,20 @@ class Interp {
return null;
id = StringTools.trim(id);
// Custom Class
if (_inCustomClass) {
_nextCallObject = null;
if (id == "super") {
if (_proxy.superClass == null) {
return _proxy.superConstructor;
} else {
return _proxy.superClass;
}
}
else if (id == "this")
return _proxy;
}
if (locals.exists(id)) {
var l = locals.get(id);
if(l != null)
@@ -571,6 +674,35 @@ class Interp {
if(customClasses.exists(id))
return customClasses.get(id);
if(customClassExist(id))
return getCustomClass(id);
// Custom Class
if (_inCustomClass) {
// We are calling a LOCAL function from the same module.
if (_proxy.__class.hasField(id)) {
// Static access
return _proxy.__class.hget(id);
}
if (_proxy.hasFunction(id)) {
_nextCallObject = _proxy;
return _proxy.resolveField(id);
}
else if (_proxy.superHasField(id)) {
_nextCallObject = _proxy.superClass;
return Reflect.getProperty(_proxy.superClass, id);
} else {
try {
var r = _proxy.resolveField(id);
_nextCallObject = _proxy;
return r;
} catch (e:Dynamic) {
if(doException)
error(EUnknownVariable(id));
}
}
}
if (_hasScriptObject) {
// search in object
if (id == "this") {
@@ -639,7 +771,7 @@ class Interp {
var e = e.e;
#end
switch (e) {
case EClass(name, fields, extend, interfaces):
case EClass(name, fields, extend, interfaces): //Not sure if leave the old system or implement the new one.
if (customClasses.exists(name))
error(EAlreadyExistingClass(name));
@@ -779,7 +911,21 @@ class Interp {
error(EInvalidOp(op));
}
case ECall(e, params):
var args:Array<Dynamic> = [for(p in params) expr(p)];
var args:Array<Dynamic> = []; // [for (p in params) expr(p)];
for (p in params) {
switch (Tools.expr(p)) {
case EIdent(id):
var ident = resolve(id);
if (ident is CustomClass) {
var customClass:CustomClass = cast ident; // Pass the underlying superclass if exist
args.push(customClass.superClass != null ? customClass.superClass : customClass);
} else {
args.push(ident);
}
default:
args.push(expr(p));
}
}
switch (Tools.expr(e)) {
case EField(e, f, s):
@@ -1195,6 +1341,13 @@ class Interp {
if (o == null)
error(EInvalidAccess(f));
if (o is CustomClass) {
if (cast(o, CustomClass).__class.hasField(f)) {
var clsName:String = cast(o, CustomClass).className;
error(ECustom('The field ${clsName}.${f} should be accessed in a static way.'));
}
}
var cls = Type.getClass(o);
if (useRedirects && {
var cl:Null<String> = getClassType(o, cls);
@@ -1246,6 +1399,13 @@ class Interp {
if (o == null)
error(EInvalidAccess(f));
if (o is CustomClass) {
if (cast(o, CustomClass).__class.hasField(f)) {
var clsName:String = cast(o, CustomClass).className;
error(ECustom('The field ${clsName}.${f} should be accessed in a static way.'));
}
}
if (useRedirects && {
var cl:Null<String> = getClassType(o);
cl != null && setRedirects.exists(cl) && (_setRedirect = setRedirects[cl]) != null;
@@ -1288,20 +1448,140 @@ class Interp {
}
function fcall(o:Dynamic, f:String, args:Array<Dynamic>):Dynamic {
// Custom logic to handle super calls to prevent infinite recursion
if(_inCustomClass) {
// Force call super function.
if(o == _proxy.superClass)
return call(o, Reflect.field(_proxy.superClass, '_HX_SUPER__${f}'), args);
}
else if (o is CustomClass) {
_nextCallObject = null;
var proxy:CustomClass = cast o;
return proxy.callFunction(f, args);
}
else if (o is CustomClassDecl) {
// Static function call
var staticClass:CustomClassDecl = cast o;
if(staticClass.hasFunction(f))
return staticClass.callFunction(f, args);
}
if(_hasScriptObject && o == CustomClassHandler.staticHandler) {
return UnsafeReflect.callMethodUnsafe(scriptObject, UnsafeReflect.field(scriptObject, "_HX_SUPER__" + f), args);
}
return call(o, get(o, f), args);
var func = get(o, f);
// Workaround for an HTML5-specific issue.
// https://github.com/HaxeFoundation/haxe/issues/11298
#if js
if (func == null && f == "contains") {
func = get(o, "includes");
}
#end
return call(o, func, args);
}
function call(o:Dynamic, f:Dynamic, args:Array<Dynamic>):Dynamic {
if(f == CustomClassHandler.staticHandler) {
return null;
}
if (_inCustomClass) {
if (o == null && _nextCallObject != null) {
o = _nextCallObject;
}
if (f == null) {
error(EInvalidAccess(f));
}
if (o == _proxy) {
// If we are calling this.fn(), special handling is needed to prevent the local scope from being destroyed.
// By checking `o == _proxy`, we handle BOTH fn() and this.fn().
// super.fn() is exempt since it is not scripted.
return callThis(f, args);
}
try {
var r = Reflect.callMethod(o, f, args);
_nextCallObject = null;
return r;
} catch (e) {
error(ECustom('Script threw an exception: \n${f}'));
_nextCallObject = null;
return null;
}
}
return UnsafeReflect.callMethodSafe(o, f, args);
}
/**
* Call a given function on the current proxy with the given arguments.
* Ensures that the local scope is not destroyed.
* @param fun The function to call.
* @param args The arguments to apply to that function.
* @return The result of the function call.
*/
function callThis(fun:Dynamic, args:Array<Dynamic>):Dynamic {
// If we are calling this.fn(), special handling is needed to prevent the local scope from being destroyed.
// Store the local scope.
var capturedLocals = this.duplicate(locals);
var capturedDeclared = this.declared;
var capturedDepth = this.depth;
this.depth++;
// Call the function.
try {
var result = Reflect.callMethod(_proxy, fun, args);
// Restore the local scope.
this.locals = capturedLocals;
this.declared = capturedDeclared;
this.depth = capturedDepth;
return result;
} catch (e) {
error(ECustom('Script threw an exception: \n${fun}'));
// Restore the local scope.
this.locals = capturedLocals;
this.declared = capturedDeclared;
this.depth = capturedDepth;
return null;
}
}
function cnew(cl:String, args:Array<Dynamic>):Dynamic {
// Custom Class
if (customClassExist(cl)) {
var proxy:CustomClass = new CustomClass(getCustomClass(cl), args, null, this);
return proxy;
}
if (_inCustomClass) {
if (_proxy.__class.pkg != null) {
var packagedClass = _proxy.__class.pkg.join(".") + "." + cl;
if (customClassExist(packagedClass)) {
var proxy:CustomClass = new CustomClass(getCustomClass(cl), args, null, this);
return proxy;
}
}
if (_proxy.__class.imports != null && _proxy.__class.imports.exists(cl)) {
var importedClass = _proxy.__class.imports.get(cl).fullPath;
if (customClassExist(importedClass)) {
var proxy:CustomClass = new CustomClass(getCustomClass(cl), args, null, this);
return proxy;
}
var c = Type.resolveClass(importedClass);
if (c != null) {
return Type.createInstance(c, args);
}
}
}
var c:Dynamic = resolve(cl);
if (c == null)
c = Type.resolveClass(cl);
@@ -1312,4 +1592,58 @@ class Interp {
return Type.createInstance(c, args);
}
}
public function registerModule(module:Array<ModuleDecl>) {
var pkg:Array<String> = null;
var imports:Map<String, CustomClassImport> = [];
for (decl in module) {
switch (decl) {
case DPackage(path):
pkg = path;
case DImport(path, _):
if (importBlocklist.contains(path.join(".")))
continue;
var last = path[path.length - 1];
var importedClass:CustomClassImport = {
name: last,
pkg: [for (e in path) e.trim()],
fullPath: path.join(".")
}
imports.set(importedClass.name, importedClass);
case DClass(c):
var extend = c.extend;
if (extend != null) {
var superClassPath = new Printer().typeToString(extend);
if (imports.exists(superClassPath)) {
switch (extend) {
case CTPath(_, params):
extend = CTPath(imports.get(superClassPath).pkg, params);
case _:
}
}
}
var classDecl:ClassDecl = {
name: c.name,
params: c.params,
meta: c.meta,
isPrivate: c.isPrivate,
extend: extend,
implement: c.implement,
fields: c.fields,
isExtern: c.isExtern
};
var customClassDecl:CustomClassDecl = {
classDecl: classDecl,
imports: imports,
pkg: pkg
}
customClassDecl.cacheFields();
registerCustomClass(customClassDecl);
case DTypedef(_):
//TODO: maybe make this work :3
}
}
}
}
+412
View File
@@ -0,0 +1,412 @@
package hscript.customclass;
import hscript.utils.UnsafeReflect;
import haxe.Constraints.Function;
import hscript.Expr;
import hscript.Expr.VarDecl;
import hscript.Expr.FunctionDecl;
import hscript.Expr.FieldDecl;
using Lambda;
using StringTools;
/**
* Provides handlers for custom classes.
* Based on Polymod Hscript class system
* @see https://github.com/larsiusprime/polymod/tree/master/polymod/hscript/_internal
*/
@:access(hscript.customclass.CustomClassDecl)
class CustomClass implements IHScriptCustomAccessBehaviour{
public var interp:Interp;
public var superClass:Dynamic;
public var superConstructor(default, null):Dynamic;
public var className(get, never):String;
private var __class:CustomClassDecl;
private var _cachedSuperFields:Null<Map<String, Dynamic>> = null;
private var _cachedFieldDecls:Map<String, FieldDecl> = null;
private var _cachedFunctionDecls:Map<String, FunctionDecl> = null;
private var _cachedVarDecls:Map<String, VarDecl> = null;
public var __allowSetGet:Bool = true;
private function get_className():String {
var name = "";
if (__class.pkg != null) {
name += __class.pkg.join(".");
}
name += __class.classDecl.name;
return name;
}
public function new(__class:CustomClassDecl, args:Array<Dynamic>, ?extendFieldDecl:Map<String, Dynamic>, ?ogInterp:Interp) {
this.__class = __class;
this.interp = new Interp(this);
if(ogInterp != null && ogInterp.importFailedCallback != null && ogInterp.errorHandler != null) {
interp.importFailedCallback = ogInterp.importFailedCallback;
interp.errorHandler = ogInterp.errorHandler;
interp.allowStaticVariables = ogInterp.allowStaticVariables;
interp.staticVariables = ogInterp.staticVariables;
}
buildImports();
if(extendFieldDecl != null)
_cachedSuperFields = extendFieldDecl;
buildClass();
if(hasFunction('new')) {
buildSuperConstructor();
callFunction('new', args);
if(this.superClass == null && this.__class.classDecl.extend != null)
this.interp.error(ECustom("super() not called"));
}
else if(__class.classDecl.extend != null) {
createSuperClass(args);
}
}
function buildClass() {
_cachedFieldDecls = [];
_cachedFunctionDecls = [];
_cachedVarDecls = [];
if(_cachedSuperFields == null) _cachedSuperFields = [];
for (f in __class.classDecl.fields) {
if(f.access.contains(AStatic)) continue; // Skip static field. It's handled by CustomClassDecl.hx
_cachedFieldDecls.set(f.name, f);
switch (f.kind) {
case KFunction(fn):
_cachedFunctionDecls.set(f.name, fn);
case KVar(v):
_cachedVarDecls.set(f.name, v);
if (v.expr != null) {
var varValue = this.interp.expr(v.expr);
this.interp.variables.set(f.name, varValue);
}
}
}
if(!_cachedSuperFields.empty()) {
for (f => v in _cachedSuperFields) {
this.hset(f, v);
}
_cachedSuperFields.clear();
}
}
function buildSuperConstructor() {
superConstructor = Reflect.makeVarArgs(function(args:Array<Dynamic>) {
createSuperClass(args);
});
}
private function createSuperClass(args:Array<Dynamic> = null) {
if (args == null)
args = [];
var extendString = new Printer().typeToString(__class.classDecl.extend);
if (__class.pkg != null && extendString.indexOf(".") == -1) {
extendString = __class.pkg.join(".") + "." + extendString;
}
if (Interp.customClassExist(extendString)) {
var abstractSuperClass:CustomClass = new CustomClass(Interp.getCustomClass(extendString), args, _cachedSuperFields, this.interp);
superClass = abstractSuperClass;
} else {
var c = Type.resolveClass('${extendString}_HSX');
if (c == null) {
interp.error(ECustom("could not resolve super class: " + extendString));
}
if(_cachedSuperFields != null) {
Reflect.setField(c, "__cachedFields", _cachedSuperFields); // Static field
}
superClass = Type.createInstance(c, args);
superClass.__customClass = this;
superClass.__real_fields = Type.getInstanceFields(c);
}
}
function buildImports() {
// TODO: implement Alias imports
var i:Int = 0;
for(_import in __class.imports) {
var importedClass = _import.fullPath;
if(Interp.customClassExist(importedClass) && this.interp.importFailedCallback != null) {
this.interp.importFailedCallback(importedClass.split("."));
continue;
}
#if hscriptPos
var e:Expr = {
e: ExprDef.EImport(importedClass),
pmin: 0,
pmax: 0,
origin: this.className,
line: i
};
#else
var e = Expr.EImport(importedClass);
#end
this.interp.expr(e);
i++;
}
}
public function callFunction(name:String, args:Array<Dynamic> = null):Dynamic {
var r:Dynamic = null;
if (hasField(name)) {
var fn = getFunction(name);
var previousValues:Map<String, Dynamic> = [];
var i = 0;
for (a in fn.args) {
var value:Dynamic = null;
if (args != null && i < args.length) {
value = args[i];
} else if (a.value != null) {
value = interp.expr(a.value);
}
// NOTE: We assign these as variables rather than locals because those get wiped when we enter the function.
if (interp.variables.exists(a.name)) {
previousValues.set(a.name, interp.variables.get(a.name));
}
interp.variables.set(a.name, value);
i++;
}
try{
r = interp.execute(fn.body);
}
catch(e:hscript.Expr.Error){
// A script error occurred while executing the script function.
// Purge the function from the cache so it is not called again.
purgeFunction(name);
}
for (a in fn.args) {
if (previousValues.exists(a.name)) {
interp.variables.set(a.name, previousValues.get(a.name));
} else {
interp.variables.remove(a.name);
}
}
} else {
var fixedArgs = [];
// OVERRIDE CHANGE: Use _HX_SUPER__ when calling superclass
var fixedName = '_HX_SUPER__${name}';
for (a in args) {
if ((a is CustomClass)) {
fixedArgs.push(cast(a, CustomClass).superClass);
} else {
fixedArgs.push(a);
}
}
var superFn = Reflect.field(superClass, fixedName);
if(superFn == null) {
this.interp.error(ECustom('Error while calling function super.${name}(): EInvalidAccess'
+ '\n'
+ 'InvalidAccess error: Super function "${name}" does not exist! Define it or call the correct superclass function.'));
}
r = Reflect.callMethod(superClass, superFn, fixedArgs);
}
return r;
}
private function hasField(name:String):Bool {
return _cachedFieldDecls != null ? _cachedFieldDecls.exists(name) : false;
}
private function getField(name:String):FieldDecl {
return _cachedFieldDecls != null ? _cachedFieldDecls.get(name) : null;
}
private function hasVar(name:String):Bool {
return _cachedVarDecls != null ? _cachedVarDecls.exists(name) : false;
}
private function getVar(name:String):VarDecl {
return _cachedVarDecls != null ? _cachedVarDecls.get(name): null;
}
private function hasFunction(name:String):Bool {
return _cachedFunctionDecls != null ? _cachedFunctionDecls.exists(name) : false;
}
private function getFunction(name:String):FunctionDecl {
return _cachedFunctionDecls != null ? _cachedFunctionDecls.get(name) : null;
}
private function cacheSuperField(name:String, value:Dynamic) {
if(_cachedSuperFields != null) {
_cachedSuperFields.set(name, value);
}
}
var __superClassFieldList:Array<String> = null;
public function superHasField(name:String):Bool {
if(superClass == null) return false;
// Reflect.hasField(this, name) is REALLY expensive so we use a cache.
if(__superClassFieldList == null) {
__superClassFieldList = Reflect.fields(superClass).concat(Type.getInstanceFields(Type.getClass(superClass)));
}
return __superClassFieldList.indexOf(name) != -1;
}
/**
* Remove a function from the cache.
* This is useful when a function is broken and needs to be skipped.
* @param name The name of the function to remove from the cache.
*/
private function purgeFunction(name:String):Void {
if (_cachedFunctionDecls != null) {
_cachedFunctionDecls.remove(name);
}
}
// Access fields
public function hget(name:String):Dynamic {
return resolveField(name);
}
public function hset(name:String, val:Dynamic):Dynamic {
switch (name) {
default:
if (hasVar(name)) {
if(__allowSetGet && hasFunction('set_${name}'))
return __callSetter(name, val);
this.interp.variables.set(name, val);
}
else if (this.superClass != null) {
if(Type.getClass(this.superClass) == null) {
// Anonymous structure
if(Reflect.hasField(this.superClass, name)) {
Reflect.setField(this.superClass, name, val);
}
return val;
}
else if (this.superClass is CustomClass) {
var superCustomClass:CustomClass = cast(this.superClass, CustomClass);
try {
superCustomClass.__allowSetGet = this.__allowSetGet;
return superCustomClass.hset(name, val);
} catch (e:Dynamic) {}
}
else if(superHasField(name)){
if(__allowSetGet)
Reflect.setProperty(this.superClass, name, val);
else
Reflect.setField(this.superClass, name, val);
}
else {
throw "field '" + name + "' does not exist in custom class '" + this.className + "' or super class '"
+ Type.getClassName(Type.getClass(this.superClass)) + "'";
}
}
else {
throw "field '" + name + "' does not exist in custom class '" + this.className + "'";
}
}
return val;
}
public function __callGetter(name:String):Dynamic {
__allowSetGet = false;
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]);
__allowSetGet = true;
return r;
}
private function resolveField(name:String):Dynamic {
switch (name) {
case "superClass":
return this.superClass;
case "createSuperClass":
return this.createSuperClass;
case "hasFunction":
return this.hasFunction;
case "callFunction":
return this.callFunction;
default:
if (hasFunction(name)) {
var fn:Function = Reflect.makeVarArgs(function(args:Array<Dynamic>) {
return this.callFunction(name, args);
});
return fn;
}
if (hasVar(name)) {
var value:Dynamic = null;
if(__allowSetGet && hasFunction('get_${name}')) {
value = __callGetter(name);
}
else if (this.interp.variables.exists(name)) {
value = this.interp.variables.get(name);
} else {
var v = getVar(name);
if (v.expr != null) {
value = this.interp.expr(v.expr);
this.interp.variables.set(name, value);
}
}
return value;
}
if (this.superClass != null) {
if (Type.getClass(this.superClass) == null) {
// Anonymous structure
if (Reflect.hasField(this.superClass, name)) {
return Reflect.field(this.superClass, name);
} else {
throw "field '" + name + "' does not exist in custom class '" + this.className + "' or super class '"
+ Type.getClassName(Type.getClass(this.superClass)) + "'";
}
}
if (this.superClass is CustomClass) {
var superCustomClass:CustomClass = cast(this.superClass, CustomClass);
try {
superCustomClass.__allowSetGet = this.__allowSetGet;
return superCustomClass.hget(name);
} catch (e:Dynamic) {}
}
var fields = Type.getInstanceFields(Type.getClass(this.superClass));
if (fields.contains(name)) {
return __allowSetGet ? Reflect.getProperty(this.superClass, name) : Reflect.field(this.superClass, name);
} else {
throw "field '" + name + "' does not exist in custom class '" + this.className + "' or super class '"
+ Type.getClassName(Type.getClass(this.superClass)) + "'";
}
}
else {
throw "field '" + name + "' does not exist in custom class '" + this.className + "'";
}
}
return null;
}
}
+150
View File
@@ -0,0 +1,150 @@
package hscript.customclass;
import hscript.customclass.utils.FunctionUtils;
import haxe.Constraints.Function;
import hscript.Expr.FieldDecl;
import hscript.Expr.VarDecl;
import hscript.Expr.FunctionDecl;
@:structInit
class CustomClassDecl implements IHScriptCustomAccessBehaviour {
public var classDecl:Expr.ClassDecl;
public var imports:Map<String, CustomClassImport>;
public var pkg:Null<Array<String>> = null;
public var staticInterp:Interp = new Interp();
var _cachedStaticFields:Map<String, FieldDecl> = [];
var _cachedStaticFunctions:Map<String, FunctionDecl> = [];
var _cachedStaticVariables:Map<String, VarDecl> = [];
public var __allowSetGet:Bool = true;
public function cacheFields() {
for (f in classDecl.fields) {
if (f.access.contains(AStatic)) {
_cachedStaticFields.set(f.name, f);
switch (f.kind) {
case KFunction(fn):
_cachedStaticFunctions.set(f.name, fn);
case KVar(v):
_cachedStaticVariables.set(f.name, v);
if (v.expr != null) {
var varValue = this.staticInterp.expr(v.expr);
this.staticInterp.variables.set(f.name, varValue);
}
}
}
}
}
public function callFunction(name:String, ?args:Array<Dynamic>) {
var func:FunctionDecl = getFunction(name);
if(func != null) {
return FunctionUtils.callStaticFunction(name, this, staticInterp, func, args != null ? args : []);
}
return null;
}
public function hasField(name:String):Bool {
return _cachedStaticFields.exists(name);
}
private function hasFunction(name:String) {
return _cachedStaticFunctions.exists(name);
}
private function getFunction(name:String):FunctionDecl {
return _cachedStaticFunctions.get(name);
}
private function hasVar(name:String):Bool {
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;
}
/**
* Remove a function from the cache.
* This is useful when a function is broken and needs to be skipped.
* @param name The name of the function to remove from the cache.
*/
private function purgeFunction(name:String):Void {
if (_cachedStaticFunctions != null) {
_cachedStaticFunctions.remove(name);
}
}
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);
return r;
}
if(hasFunction(name)) {
var fn:Function = Reflect.makeVarArgs(function(args:Array<Dynamic>) {
return this.callFunction(name, args);
});
return fn;
}
throw "static field '" + name + "' does not exist in custom class '" + this.classDecl.name + "'";
}
public function hset(name:String, val:Dynamic):Dynamic {
if (hasVar(name)) {
if (__allowSetGet && hasFunction('set_${name}'))
return __callSetter(name, val);
else {
this.staticInterp.variables.set(name, val);
return val;
}
}
throw "static field '" + name + "' does not exist in custom class '" + this.classDecl.name + "'";
}
public function __callGetter(name:String):Dynamic {
__allowSetGet = false;
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]);
__allowSetGet = true;
return r;
}
}
typedef CustomClassImport = {
var ?name:String;
var ?pkg:Array<String>;
var ?fullPath:String; // pkg.pkg.pkg.name
}
@@ -0,0 +1,46 @@
package hscript.customclass.utils;
import hscript.Expr.FunctionDecl;
@:access(hscript.customclass.CustomClassDecl)
class FunctionUtils {
public static function callStaticFunction(name:String, classDecl:CustomClassDecl, interp:Interp, fn:FunctionDecl, args:Array<Dynamic> = null) {
var r:Dynamic = null;
var previousValues:Map<String, Dynamic> = [];
var i = 0;
for (a in fn.args) {
var value:Dynamic = null;
if (args != null && i < args.length) {
value = args[i];
} else if (a.value != null) {
value = interp.expr(a.value);
}
// NOTE: We assign these as variables rather than locals because those get wiped when we enter the function.
if (interp.variables.exists(a.name)) {
previousValues.set(a.name, interp.variables.get(a.name));
}
interp.variables.set(a.name, value);
i++;
}
try {
r = interp.execute(fn.body);
} catch (e:hscript.Expr.Error) {
// A script error occurred while executing the script function.
// Purge the function from the cache so it is not called again.
classDecl.purgeFunction(name);
interp.error(#if hscriptPos e.e #else e #end);
}
for (a in fn.args) {
if (previousValues.exists(a.name)) {
interp.variables.set(a.name, previousValues.get(a.name));
} else {
interp.variables.remove(a.name);
}
}
return r;
}
}
+91 -21
View File
@@ -211,6 +211,15 @@ class ClassExtendMacro {
continue;
if (f.name == "new") {
hasNew = true;
switch (f.kind) {
case FFun(fn):
var constructor:Field = buildConstructor(fn.args);
shadowClass.fields.push(constructor);
definedFields.push(f.name);
default:
continue;
}
continue;
}
if (f.name.startsWith(FUNC_PREFIX))
@@ -254,6 +263,11 @@ class ClassExtendMacro {
}
}
}
if (__customClass != null && @:privateAccess __customClass.hasFunction(name)) {
return __customClass.callFunction(name, [$a{arguments}]);
}
return super.$name($a{arguments});
};
} else {
@@ -269,6 +283,12 @@ class ClassExtendMacro {
}
}
}
if (__customClass != null && @:privateAccess __customClass.hasFunction(name)) {
__customClass.callFunction(name, [$a{arguments}]);
return;
}
super.$name($a{arguments});
};
}
@@ -339,6 +359,13 @@ class ClassExtendMacro {
Utils.setupMetas(shadowClass, imports);
Utils.processImport(imports, "hscript.utils.UnsafeReflect", "UnsafeReflect");
shadowClass.fields.push({
name: "__cachedFields",
pos: Context.currentPos(),
kind: FVar(macro: Map<String, Dynamic>),
access: [APublic, AStatic]
});
// Adding hscript getters and setters
shadowClass.fields.push({
@@ -348,6 +375,13 @@ class ClassExtendMacro {
access: [APublic]
});
shadowClass.fields.push({
name: "__customClass",
pos: Context.currentPos(),
kind: FVar(macro: hscript.customclass.CustomClass),
access: [APublic]
});
shadowClass.fields.push({
name: "__custom__variables",
pos: Context.currentPos(),
@@ -472,31 +506,38 @@ class ClassExtendMacro {
var hgetField = if(hasHgetInSuper) {
macro {
if(__allowSetGet && __custom__variables.exists("get_" + name))
return __callGetter(name);
if (__custom__variables.exists(name))
return __custom__variables.get(name);
if(__custom__variables != null) {
if(__allowSetGet && __custom__variables.exists("get_" + name))
return __callGetter(name);
if (__custom__variables.exists(name))
return __custom__variables.get(name);
}
return super.hget(name);
}
} else {
macro {
if(__allowSetGet && __custom__variables.exists("get_" + name))
return __callGetter(name);
if (__custom__variables.exists(name))
return __custom__variables.get(name);
if(__custom__variables != null) {
if(__allowSetGet && __custom__variables.exists("get_" + name))
return __callGetter(name);
if (__custom__variables.exists(name))
return __custom__variables.get(name);
}
return UnsafeReflect.getProperty(this, name);
}
}
var hsetField = if(hasHsetInSuper) {
macro {
if(__allowSetGet && __custom__variables.exists("set_" + name))
return __callSetter(name, val);
if (__custom__variables.exists(name)) {
__custom__variables.set(name, val);
return val;
if(__custom__variables != null) {
if(__allowSetGet && __custom__variables.exists("set_" + name))
return __callSetter(name, val);
if (__custom__variables.exists(name)) {
__custom__variables.set(name, val);
return val;
}
}
if(__real_fields.contains(name)) {
if(__real_fields != null && __real_fields.contains(name)) {
UnsafeReflect.setProperty(this, name, val);
return UnsafeReflect.field(this, name);
}
@@ -504,17 +545,20 @@ class ClassExtendMacro {
}
} else {
macro {
if(__allowSetGet && __custom__variables.exists("set_" + name))
return __callSetter(name, val);
if (__custom__variables.exists(name)) {
__custom__variables.set(name, val);
return val;
if(__custom__variables != null) {
if(__allowSetGet && __custom__variables.exists("set_" + name))
return __callSetter(name, val);
if (__custom__variables.exists(name)) {
__custom__variables.set(name, val);
return val;
}
}
if(__real_fields.contains(name)) {
if(__real_fields != null && __real_fields.contains(name)) {
UnsafeReflect.setProperty(this, name, val);
return UnsafeReflect.field(this, name);
}
__custom__variables.set(name, val);
if(__custom__variables != null) __custom__variables.set(name, val);
return val;
}
}
@@ -579,6 +623,32 @@ class ClassExtendMacro {
return fields;
}
static function buildConstructor(constArgs:Array<FunctionArg>):Field {
var superCallArgs:Array<Expr> = [for (arg in constArgs) macro $i{arg.name}];
return {
name: 'new',
access: [APublic],
pos: Context.currentPos(),
kind: FFun({
args: constArgs,
expr: macro
{
// Call the super constructor with appropriate args
super($a{superCallArgs});
if(__cachedFields != null) {
for(k => v in __cachedFields) {
Reflect.setProperty(this, k, v);
trace(k);
}
__cachedFields = null;
}
},
}),
};
}
}
#else
class ClassExtendMacro {