diff --git a/hscript/macros/CustomClassMacro.hx b/hscript/macros/CustomClassMacro.hx new file mode 100644 index 0000000..1ab1e11 --- /dev/null +++ b/hscript/macros/CustomClassMacro.hx @@ -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 = [":bitmap", ":noCustomClass", ":generic"]; + private static var classImports:Map> = []; + + 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 { + 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) { + var classesToApply:Array = []; + 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) { + for(cl in classes) + buildClass(cl); + } + + private static function buildClass(cl:ClassType) { + var classFields:Array = cl.fields.get(); + var fields:Array = classFields.concat(getSuperFields(cl)); + + var shadowClass:TypeDefinition = macro class { + + }; + + var definedFields:Array = []; + 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 = [ + 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 = [ + 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), + access: [APublic] + }); + + shadowClass.fields.push({ + name: "__class__fields", + pos: cl.pos, + kind: FVar(macro :Array), + 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 { + 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 = []; + + if(field.expr() == null) return []; + + var fnAccess:Array = [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 { + var fields:Array = []; + 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, pos):Field { + var superCallArgs:Array = [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; + public var className:String; +} +#end \ No newline at end of file