New Custom Class Macro (NOT READY TO USE)
This commit is contained in:
@@ -0,0 +1,558 @@
|
||||
package hscript.macros;
|
||||
import haxe.macro.Type.Ref;
|
||||
#if macro
|
||||
import haxe.macro.Type.ClassType;
|
||||
import haxe.macro.Type.ModuleType;
|
||||
import Type.ValueType;
|
||||
import haxe.macro.Expr.Function;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type.MetaAccess;
|
||||
import haxe.macro.Type.FieldKind;
|
||||
import haxe.macro.Type.ClassField;
|
||||
import haxe.macro.Type.VarAccess;
|
||||
import haxe.macro.*;
|
||||
|
||||
using StringTools;
|
||||
using Lambda;
|
||||
|
||||
@:nullSafety(Loose)
|
||||
class CustomClassMacro {
|
||||
public static inline final FUNC_PREFIX = "_HX_SUPER__";
|
||||
public static inline final CLASS_SUFFIX = "_HSX";
|
||||
|
||||
public static var unallowedMetas:Array<String> = [":bitmap", ":noCustomClass", ":generic"];
|
||||
private static var classImports:Map<String, Array<ImportExpr>> = [];
|
||||
|
||||
public static function init() {
|
||||
#if !display
|
||||
#if CUSTOM_CLASSES
|
||||
if(Context.defined("display")) return;
|
||||
for(apply in Config.ALLOWED_CUSTOM_CLASSES) {
|
||||
//Compiler.addGlobalMetadata(apply, "@:build(hscript.macros.CustomClassMacro.fetchImports())");
|
||||
}
|
||||
//Context.onAfterTyping(buildTyped);
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
public static function fetchImports():Array<Field> {
|
||||
var fields = Context.getBuildFields();
|
||||
var clRef = Context.getLocalClass();
|
||||
if (clRef == null) return fields;
|
||||
var cl = clRef.get();
|
||||
if (cl.isAbstract || cl.isExtern || cl.isFinal || cl.isInterface) return fields;
|
||||
if (!cl.name.endsWith("_Impl_") && !cl.name.endsWith(CLASS_SUFFIX) && !cl.name.endsWith("_HSC")) {
|
||||
var key = cl.module;
|
||||
var fkey = '${cl.module}.${cl.name}';
|
||||
if (Config.DISALLOW_CUSTOM_CLASSES.contains(key) || Config.DISALLOW_CUSTOM_CLASSES.contains(fkey))
|
||||
return fields;
|
||||
if (key.contains("_"))
|
||||
return fields;
|
||||
|
||||
var imports = Context.getLocalImports().copy();
|
||||
classImports[fkey] = imports;
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
public static function build(modules:Array<ModuleType>) {
|
||||
var classesToApply:Array<ClassType> = [];
|
||||
for(m in modules) {
|
||||
switch(m) {
|
||||
case TClassDecl(c):
|
||||
if(c == null) continue;
|
||||
var cl = c.get();
|
||||
if(cl.isAbstract || cl.isExtern || cl.isFinal || cl.isInterface) continue;
|
||||
if(!cl.name.endsWith("_Impl_") && !cl.name.endsWith(CLASS_SUFFIX) && !cl.name.endsWith("_HSC")) {
|
||||
var key = cl.module;
|
||||
var fkey = '${cl.module}.${cl.name}';
|
||||
if(Config.DISALLOW_CUSTOM_CLASSES.contains(key) || Config.DISALLOW_CUSTOM_CLASSES.contains(fkey)) continue;
|
||||
if(key.contains("_")) continue;
|
||||
var isValid = Config.ALLOWED_CUSTOM_CLASSES.exists((s:String) -> return fkey.startsWith(s) || key.startsWith(s));
|
||||
if (isValid) {
|
||||
classesToApply.push(cl);
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if(classesToApply.length > 0) buildClasses(classesToApply);
|
||||
}
|
||||
|
||||
private static function buildClasses(classes:Array<ClassType>) {
|
||||
for(cl in classes)
|
||||
buildClass(cl);
|
||||
}
|
||||
|
||||
private static function buildClass(cl:ClassType) {
|
||||
var classFields:Array<ClassField> = cl.fields.get();
|
||||
var fields:Array<ClassField> = classFields.concat(getSuperFields(cl));
|
||||
|
||||
var shadowClass:TypeDefinition = macro class {
|
||||
|
||||
};
|
||||
|
||||
var definedFields:Array<String> = [];
|
||||
var hasNew:Bool = false;
|
||||
|
||||
for(f in fields) {
|
||||
if(f == null) continue;
|
||||
|
||||
if(f.name == "new") {
|
||||
hasNew = true;
|
||||
switch(f.type) {
|
||||
case TFun(args, ret):
|
||||
// TODO: constructor
|
||||
var constArgs:Array<FunctionArg> = [
|
||||
for (arg in args)
|
||||
{name: arg.name, opt: arg.opt, type: Context.toComplexType(arg.t)}
|
||||
];
|
||||
|
||||
var constructor:Field = buildConstructor(constArgs, f.pos);
|
||||
shadowClass.fields.push(constructor);
|
||||
definedFields.push(f.name);
|
||||
case TLazy(builder):
|
||||
var v = builder();
|
||||
switch(v) {
|
||||
case TFun(args, ret):
|
||||
var constArgs:Array<FunctionArg> = [
|
||||
for (arg in args)
|
||||
{name: arg.name, opt: arg.opt, type: Context.toComplexType(arg.t)}
|
||||
];
|
||||
|
||||
var constructor:Field = buildConstructor(constArgs, f.pos);
|
||||
shadowClass.fields.push(constructor);
|
||||
definedFields.push(f.name);
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(f.name.startsWith(FUNC_PREFIX))
|
||||
continue;
|
||||
if(f.isExtern || f.isFinal)
|
||||
continue;
|
||||
switch(f.kind) {
|
||||
case FMethod(k):
|
||||
switch (k) {
|
||||
case MethInline | MethDynamic | MethMacro: continue;
|
||||
default:
|
||||
}
|
||||
case FVar(read, write):
|
||||
switch([read, write]) {
|
||||
case [AccInline, AccInline]: continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if(f.name == "hget" || f.name == "hset") continue;
|
||||
if(definedFields.contains(f.name)) continue;
|
||||
|
||||
for(m in f.meta.get())
|
||||
if(unallowedMetas.contains(m.name))
|
||||
continue;
|
||||
|
||||
switch (f.kind) {
|
||||
case FMethod(k):
|
||||
var newFields = overrideField(f, cl);
|
||||
|
||||
var overField:Field = newFields[0];
|
||||
var superField:Field = newFields[1];
|
||||
|
||||
if (!overField.access.contains(AOverride))
|
||||
overField.access.push(AOverride);
|
||||
if (superField.access.contains(AOverride))
|
||||
superField.access.remove(AOverride);
|
||||
|
||||
shadowClass.fields.push(overField);
|
||||
shadowClass.fields.push(superField);
|
||||
definedFields.push(f.name);
|
||||
default:
|
||||
// No :>
|
||||
}
|
||||
}
|
||||
|
||||
if(definedFields.length == 0 && !hasNew)
|
||||
return;
|
||||
|
||||
shadowClass.kind = TDClass({
|
||||
pack: cl.pack.copy(),
|
||||
name: cl.name
|
||||
}, [
|
||||
{name: "IHScriptCustomClassBehaviour", pack: ["hscript"]}
|
||||
], false, true, false);
|
||||
|
||||
var fkey = '${cl.module}.${cl.name}';
|
||||
var imports = classImports[fkey];
|
||||
if(imports != null) {
|
||||
Utils.setupMetas(shadowClass, imports);
|
||||
Utils.processImport(imports, "hscript.utils.UnsafeReflect", "UnsafeReflect");
|
||||
}
|
||||
|
||||
shadowClass.fields.push({
|
||||
name: "__interp",
|
||||
kind: FVar(macro: hscript.Interp),
|
||||
pos: cl.pos,
|
||||
access: [APublic]
|
||||
});
|
||||
|
||||
shadowClass.fields.push({
|
||||
name: "__allowSetGet",
|
||||
pos: cl.pos,
|
||||
kind: FVar(macro :Bool, macro true),
|
||||
access: [APublic]
|
||||
});
|
||||
|
||||
shadowClass.fields.push({
|
||||
name: "__real_fields",
|
||||
pos: cl.pos,
|
||||
kind: FVar(macro :Array<String>),
|
||||
access: [APublic]
|
||||
});
|
||||
|
||||
shadowClass.fields.push({
|
||||
name: "__class__fields",
|
||||
pos: cl.pos,
|
||||
kind: FVar(macro :Array<String>),
|
||||
access: [APublic]
|
||||
});
|
||||
|
||||
shadowClass.fields.push({
|
||||
name: "__callGetter",
|
||||
pos: cl.pos,
|
||||
kind: FFun({
|
||||
ret: macro :Dynamic,
|
||||
params: [],
|
||||
expr: macro {
|
||||
return null;
|
||||
},
|
||||
args: [
|
||||
{
|
||||
name: "name",
|
||||
opt: false,
|
||||
meta: [],
|
||||
type: macro :String
|
||||
}
|
||||
]
|
||||
}),
|
||||
access: [APublic]
|
||||
});
|
||||
|
||||
shadowClass.fields.push({
|
||||
name: "__callSetter",
|
||||
pos: cl.pos,
|
||||
kind: FFun({
|
||||
ret: macro :Dynamic,
|
||||
params: [],
|
||||
expr: macro {
|
||||
return null;
|
||||
},
|
||||
args: [
|
||||
{
|
||||
name: "name",
|
||||
opt: false,
|
||||
meta: [],
|
||||
type: macro :String
|
||||
},
|
||||
{
|
||||
name: "val",
|
||||
opt: false,
|
||||
meta: [],
|
||||
type: macro :Dynamic
|
||||
}
|
||||
]
|
||||
}),
|
||||
access: [APublic]
|
||||
});
|
||||
|
||||
if(cl.name == "FunkinShader" || cl.name == "CustomShader" || cl.name == "MultiThreadedScript") {
|
||||
Context.defineModule(cl.module, [shadowClass], null);
|
||||
return;
|
||||
}
|
||||
|
||||
var hasHgetInSuper = false;
|
||||
var hasHsetInSuper = false;
|
||||
|
||||
// TODO: hget & hset
|
||||
|
||||
for(f in fields) {
|
||||
if(f.name == "new") continue;
|
||||
if(f.name.startsWith(FUNC_PREFIX)) continue;
|
||||
if(f.isExtern || f.isFinal) continue;
|
||||
switch (f.kind) {
|
||||
case FMethod(k):
|
||||
switch (k) {
|
||||
case MethInline | MethDynamic | MethMacro: continue;
|
||||
default:
|
||||
}
|
||||
case FVar(read, write):
|
||||
switch ([read, write]) {
|
||||
case [AccInline, AccInline]: continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
switch(f.type) {
|
||||
case TFun(args, ret):
|
||||
if(f.params != null && f.params.length > 0) continue;
|
||||
|
||||
hasHgetInSuper = f.name == "hget";
|
||||
hasHsetInSuper = f.name == "hset";
|
||||
|
||||
if(hasHgetInSuper && hasHsetInSuper)
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
var hgetField = if (hasHgetInSuper) {
|
||||
macro {
|
||||
if (__interp != null) {
|
||||
if (__class__fields.contains(name)) {
|
||||
var v:Dynamic = __interp.variables.get(name);
|
||||
if (v != null && v is hscript.Property)
|
||||
return cast(v, hscript.Property).callGetter(name);
|
||||
return v;
|
||||
} else
|
||||
@:privateAccess {
|
||||
var cls:hscript.CustomClass = cast __interp.__customClass.__upperClass;
|
||||
while (cls != null) {
|
||||
if (cls.hasField(name))
|
||||
return cls.getField(name);
|
||||
|
||||
var prev:hscript.CustomClass = cast cls.__upperClass;
|
||||
if (prev == null)
|
||||
break;
|
||||
cls = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.hget(name);
|
||||
}
|
||||
} else {
|
||||
macro {
|
||||
if (__interp != null) {
|
||||
if (__class__fields.contains(name)) {
|
||||
var v:Dynamic = __interp.variables.get(name);
|
||||
if (v != null && v is hscript.Property)
|
||||
return cast(v, hscript.Property).callGetter(name);
|
||||
return v;
|
||||
} else
|
||||
@:privateAccess {
|
||||
var cls:hscript.CustomClass = cast __interp.__customClass.__upperClass;
|
||||
while (cls != null) {
|
||||
if (cls.hasField(name))
|
||||
return cls.getField(name);
|
||||
|
||||
var prev:hscript.CustomClass = cast cls.__upperClass;
|
||||
if (prev == null)
|
||||
break;
|
||||
cls = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return UnsafeReflect.getProperty(this, name);
|
||||
}
|
||||
}
|
||||
|
||||
var hsetField = if (hasHsetInSuper) {
|
||||
macro {
|
||||
if (__interp != null) {
|
||||
if (__class__fields.contains(name)) {
|
||||
var v:Dynamic = __interp.variables.get(name);
|
||||
if (v != null && v is hscript.Property)
|
||||
return cast(v, hscript.Property).callSetter(name, val);
|
||||
__interp.variables.set(name, val);
|
||||
return val;
|
||||
} else
|
||||
@:privateAccess {
|
||||
var cls:hscript.CustomClass = cast __interp.__customClass.__upperClass;
|
||||
while (cls != null) {
|
||||
if (cls.hasField(name))
|
||||
return cls.setField(name, val);
|
||||
|
||||
var prev:hscript.CustomClass = cast cls.__upperClass;
|
||||
if (prev == null)
|
||||
break;
|
||||
cls = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (__real_fields.contains(name)) {
|
||||
UnsafeReflect.setProperty(this, name, val);
|
||||
return UnsafeReflect.field(this, name);
|
||||
}
|
||||
return super.hset(name, val);
|
||||
}
|
||||
} else {
|
||||
macro {
|
||||
if (__interp != null) {
|
||||
if (__class__fields.contains(name)) {
|
||||
var v:Dynamic = __interp.variables.get(name);
|
||||
if (v != null && v is hscript.Property)
|
||||
return cast(v, hscript.Property).callSetter(name, val);
|
||||
__interp.variables.set(name, val);
|
||||
return val;
|
||||
} else
|
||||
@:privateAccess {
|
||||
var cls:hscript.CustomClass = cast __interp.__customClass.__upperClass;
|
||||
while (cls != null) {
|
||||
if (cls.hasField(name))
|
||||
return cls.setField(name, val);
|
||||
|
||||
var prev:hscript.CustomClass = cast cls.__upperClass;
|
||||
if (prev == null)
|
||||
break;
|
||||
cls = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (__real_fields.contains(name)) {
|
||||
UnsafeReflect.setProperty(this, name, val);
|
||||
return UnsafeReflect.field(this, name);
|
||||
}
|
||||
// __custom__variables.set(name, val);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
shadowClass.fields.push({
|
||||
name: "hset",
|
||||
pos: cl.pos,
|
||||
access: hasHsetInSuper ? [AOverride, APublic] : [APublic],
|
||||
kind: FFun({
|
||||
ret: macro :Dynamic,
|
||||
params: [],
|
||||
expr: hsetField,
|
||||
args: [
|
||||
{
|
||||
name: "name",
|
||||
opt: false,
|
||||
meta: [],
|
||||
type: macro :String
|
||||
},
|
||||
{
|
||||
name: "val",
|
||||
opt: false,
|
||||
meta: [],
|
||||
type: macro :Dynamic
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
shadowClass.fields.push({
|
||||
name: "hget",
|
||||
pos: cl.pos,
|
||||
access: hasHgetInSuper ? [AOverride, APublic] : [APublic],
|
||||
kind: FFun({
|
||||
ret: macro :Dynamic,
|
||||
params: [],
|
||||
expr: hgetField,
|
||||
args: [
|
||||
{
|
||||
name: "name",
|
||||
opt: false,
|
||||
meta: [],
|
||||
type: macro :String
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
Context.defineModule(cl.module, [shadowClass], null);
|
||||
}
|
||||
|
||||
private static function overrideField(field:ClassField, cl:ClassType, ?type:haxe.macro.Type):Array<Field> {
|
||||
if(type == null)
|
||||
type = field.type;
|
||||
|
||||
switch(type) {
|
||||
case TLazy(f):
|
||||
var fv = f();
|
||||
return overrideField(field, cl, fv);
|
||||
case TFun(args, ret):
|
||||
for (a in args)
|
||||
switch (a.t) {
|
||||
case TInst(t, p):
|
||||
var tp = t.get();
|
||||
if (tp != null && tp.isPrivate) return [];
|
||||
}
|
||||
// Inline functions are already skipped
|
||||
// As well for "@:generic" fields
|
||||
|
||||
var inputArgs:Array<FunctionArg> = [];
|
||||
|
||||
if(field.expr() == null) return [];
|
||||
|
||||
var fnAccess:Array<Access> = [field.isPublic ? APublic : APrivate];
|
||||
|
||||
switch(field.expr().expr) {
|
||||
case TFunction(tfunc):
|
||||
for(arg in tfunc.args) {
|
||||
|
||||
}
|
||||
case TConst(_):
|
||||
return [];
|
||||
default:
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
/* case TInst(_, _): return [];
|
||||
case TEnum(_, _): return [];
|
||||
case TMono(_): return [];
|
||||
case TAnonymous(_): return [];
|
||||
case TDynamic(_): return [];
|
||||
case TAbstract(_, _): return []; */
|
||||
default:
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private static function getSuperFields(cl:ClassType):Array<ClassField> {
|
||||
var fields:Array<ClassField> = [];
|
||||
var superClsRef = cl.superClass;
|
||||
while(superClsRef != null) {
|
||||
var superCls:ClassType = superClsRef.t.get();
|
||||
fields = fields.concat(superCls.fields.get());
|
||||
var next = superCls.superClass;
|
||||
if(next == null)
|
||||
break;
|
||||
superClsRef = next;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static function buildConstructor(args:Array<FunctionArg>, pos):Field {
|
||||
var superCallArgs:Array<Expr> = [for (arg in args) macro $i{arg.name}];
|
||||
|
||||
return {
|
||||
name: 'new',
|
||||
access: [APublic],
|
||||
pos: pos,
|
||||
kind: FFun({
|
||||
args: args,
|
||||
expr: macro {
|
||||
// Call the super constructor with appropriate args
|
||||
super($a{superCallArgs});
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
class CustomClassMacro {
|
||||
public var usedClass:Class<Dynamic>;
|
||||
public var className:String;
|
||||
}
|
||||
#end
|
||||
Reference in New Issue
Block a user