diff --git a/polymod/hscript/_internal/Parser.hx b/polymod/hscript/_internal/Parser.hx index c3f8c12d..6ea03d35 100644 --- a/polymod/hscript/_internal/Parser.hx +++ b/polymod/hscript/_internal/Parser.hx @@ -1800,8 +1800,10 @@ class Parser input = oldInput; readPos = oldPos; offset = oldOffset; + #if hscriptPos tokenMin = oldTokenMin; tokenMax = oldTokenMax; + #end char = -1; b = new StringBuf(); diff --git a/polymod/hscript/_internal/PolymodClassDeclEx.hx b/polymod/hscript/_internal/PolymodClassDeclEx.hx index fc73ddea..61190d39 100644 --- a/polymod/hscript/_internal/PolymodClassDeclEx.hx +++ b/polymod/hscript/_internal/PolymodClassDeclEx.hx @@ -4,6 +4,8 @@ import polymod.hscript._internal.Expr.ClassDecl; import polymod.hscript._internal.Expr.FieldDecl; import polymod.hscript._internal.PolymodScriptClass; +using StringTools; + /** * A scripted class declaration, with a package declaration, imports, and potentially static fields. */ @@ -31,10 +33,17 @@ typedef PolymodClassImport = @:optional var name:String; @:optional var pkg:Array; @:optional var fullPath:String; // pkg.pkg.pkg.name + @:optional var cls:Class; @:optional var enm:Enum; + @:optional var abs:PolymodStaticAbstractReference; } +/** + * A class which holds a reference to another scripted class, + * for use in a static context. This allows for instantiation, + * or for accessing static fields or methods. + */ class PolymodStaticClassReference { public var cls:PolymodClassDeclEx; @@ -125,3 +134,150 @@ class PolymodStaticClassReference return 'PolymodStaticClassReference(${getFullyQualifiedName()})'; } } + +/** + * A class which holds a reference to an abstract class implementation, + * or (if the implementation class is not available) redirects + * to the abstract static value store built by PolymodScriptClassMacro at compile time. + */ +@:nullSafety +class PolymodStaticAbstractReference +{ + /** + * The name of the abstract class, as it was imported. + */ + public var absName:String; + + /** + * The internal class that implements the abstract class's static functions, + * if it exists. + */ + public var absImpl:Null>; + + /** + * The path of the implementation class. + * Used for resolving static fields cached at macro time. + */ + public var absImplPath:String; + + public function new(absName:String, absImpl:Null>, absImplPath:String) + { + this.absName = absName; + this.absImpl = absImpl; + this.absImplPath = absImplPath; + } + + /** + * Instantiate an instance of the underlying implementation class. + * @param args The arguments to pass to the constructor. + * @return The resulting instance. + * @throws ex Thrown if the underlying implementation class is not available. + */ + public function instantiate(args:Array):Dynamic + { + if (this.absImpl == null) + { + throw 'Could not resolve abstract class ${absName}.'; + } + + var ctor = Reflect.field(this.absImpl, '_new'); + if (ctor == null) + { + throw 'Could not find constructor for abstract class ${absName}'; + } + + return Reflect.callMethod(this.absImpl, ctor, args); + } + + /** + * Retrieve a static field of the abstract class. + * @param fieldName The name of the field to retrieve. + * @return The value of the field. + */ + public function getField(fieldName:String):Dynamic + { + if (this.absImpl != null) + { + if (Reflect.hasField(this.absImpl, fieldName)) + { + var result:Dynamic = Reflect.getProperty(this.absImpl, fieldName); + if (result != null) return result; + } + var getterName:String = 'get_$fieldName'; + if (Reflect.hasField(this.absImpl, getterName)) + { + var getter = Reflect.field(this.absImpl, getterName); + return Reflect.callMethod(this.absImpl, getter, []); + } + } + + return fetchAbstractClassStatic(fieldName); + } + + /** + * Assign a static field of the abstract class. + * @param fieldName The name of the field to assign. + * @param fieldValue The value to assign to the field. + * @return The value of the field. + */ + public function setField(fieldName:String, fieldValue:Dynamic):Dynamic + { + if (this.absImpl != null) + { + if (Reflect.hasField(this.absImpl, fieldName)) + { + Reflect.setProperty(this.absImpl, fieldName, fieldValue); + return fieldValue; + } + var setterName:String = 'set_$fieldName'; + if (Reflect.hasField(this.absImpl, setterName)) + { + var setter = Reflect.field(this.absImpl, setterName); + var result = Reflect.callMethod(this.absImpl, setter, [fieldValue]); + return result; + } + } + + throw 'Could not resolve abstract static field ${fieldName}'; + } + + /** + * Call a static function of the abstract class. + * @param funcName The name of the function to call. + * @param args The arguments to pass to the function. + * @return The return value of the function. + */ + public function callFunction(funcName:String, args:Array):Dynamic + { + // If we can just call the method directly, do that. + var func = getField(funcName); + if (func != null) + { + return Reflect.callMethod(this.absImpl, func, args); + } + + throw 'Could not resolve abstract class static function ${funcName}'; + } + + function fetchAbstractClassStatic(fieldName:String):Dynamic + { + var key:String = '${this.absImplPath}.${fieldName}'; + + if (PolymodScriptClass.abstractClassStatics.exists(key)) + { + var holder = PolymodScriptClass.abstractClassStatics.get(key); + var property = key.replace('.', '_'); + + return Reflect.getProperty(holder, property); + } + else + { + throw 'Could not resolve abstract class static field ${fieldName}'; + } + } + + public function toString():String + { + return 'PolymodStaticAbstractReference(${absName} => ${absImpl})'; + } +} diff --git a/polymod/hscript/_internal/PolymodInterpEx.hx b/polymod/hscript/_internal/PolymodInterpEx.hx index 94e0f781..a0305fc3 100644 --- a/polymod/hscript/_internal/PolymodInterpEx.hx +++ b/polymod/hscript/_internal/PolymodInterpEx.hx @@ -2,6 +2,7 @@ package polymod.hscript._internal; import polymod.hscript._internal.Expr; import polymod.hscript._internal.PolymodClassDeclEx.PolymodClassImport; +import polymod.hscript._internal.PolymodClassDeclEx.PolymodStaticAbstractReference; import polymod.hscript._internal.PolymodClassDeclEx.PolymodStaticClassReference; import polymod.hscript._internal.PolymodExprEx; import polymod.hscript._internal.Printer; @@ -114,15 +115,26 @@ class PolymodInterpEx extends Interp // Ignore importedClass.enm as enums cannot be instantiated. // Importing a blacklisted module creates an import with a `null` class, so we check for that here. - var c = importedClass.cls; - if (c == null) + var abs = importedClass.abs; + if (abs != null) { - errorEx(EBlacklistedModule(importedClass.fullPath)); + try + { + return abs.instantiate(args); + } + catch (e) + { + errorEx(EInvalidModule(importedClass.fullPath)); + } } - else + + var cls = importedClass.cls; + if (cls != null) { - return Type.createInstance(c, args); + return Type.createInstance(cls, args); } + + errorEx(EBlacklistedModule(importedClass.fullPath)); } // Attempt to resolve the class without overrides. @@ -144,6 +156,11 @@ class PolymodInterpEx extends Interp // Force call super function. return super.fcall(o, '__super_${f}', args); } + else if (Std.isOfType(o, PolymodStaticAbstractReference)) + { + var ref:PolymodStaticAbstractReference = cast(o, PolymodStaticAbstractReference); + return ref.callFunction(f, args); + } else if (Std.isOfType(o, PolymodStaticClassReference)) { var ref:PolymodStaticClassReference = cast(o, PolymodStaticClassReference); @@ -339,7 +356,7 @@ class PolymodInterpEx extends Interp continue; } - Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not import ${imp.fullPath}', clsPath); + Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not import ${imp.fullPath}. Check to ensure the module exists and is spelled correctly.', clsPath); } // Check if the scripted classes extend the right type. @@ -353,7 +370,7 @@ class PolymodInterpEx extends Interp case CTPath(path, params): if (params != null && params.length > 0) { - Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not extend ${superClassPath}, do not include type parameters in super class name', clsPath); + Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not extend ${superClassPath}, do not include type parameters in super class name.', clsPath); } default: @@ -361,7 +378,7 @@ class PolymodInterpEx extends Interp } // Default - Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not extend ${superClassPath}, is the type imported?', clsPath); + Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not extend ${superClassPath}. Make sure the type to extend has been imported.', clsPath); } else { @@ -1276,7 +1293,13 @@ class PolymodInterpEx extends Interp } // Otherwise, we assume the field is fine to use. - if (Std.isOfType(o, PolymodStaticClassReference)) + if (Std.isOfType(o, PolymodStaticAbstractReference)) + { + var ref:PolymodStaticAbstractReference = cast(o, PolymodStaticAbstractReference); + + return ref.getField(f); + } + else if (Std.isOfType(o, PolymodStaticClassReference)) { var ref:PolymodStaticClassReference = cast(o, PolymodStaticClassReference); @@ -1337,12 +1360,6 @@ class PolymodInterpEx extends Interp // return result; } - var abstractKey:String = '$oCls.$f'; - if (PolymodScriptClass.abstractClassStatics.exists(abstractKey)) - { - return Reflect.getProperty(PolymodScriptClass.abstractClassStatics[abstractKey], abstractKey.replace('.', '_')); - } - // Default behavior if (Reflect.hasField(o, f)) { @@ -1386,7 +1403,20 @@ class PolymodInterpEx extends Interp } // Otherwise, we assume the field is fine to use. - if (Std.isOfType(o, PolymodStaticClassReference)) + if (Std.isOfType(o, PolymodStaticAbstractReference)) + { + var ref:PolymodStaticAbstractReference = cast(o, PolymodStaticAbstractReference); + + try + { + return ref.setField(f, v); + } + catch (e:Dynamic) + { + errorEx(EInvalidFinalSet(f)); + } + } + else if (Std.isOfType(o, PolymodStaticClassReference)) { var ref:PolymodStaticClassReference = cast(o, PolymodStaticClassReference); @@ -1512,6 +1542,7 @@ class PolymodInterpEx extends Interp { if (importedClass.cls != null) return importedClass.cls; if (importedClass.enm != null) return importedClass.enm; + if (importedClass.abs != null) return importedClass.abs; // Resolve imported scripted classes. var result = PolymodStaticClassReference.tryBuild(importedClass.fullPath); @@ -1917,7 +1948,7 @@ class PolymodInterpEx extends Interp } } - public function registerModules(module:Array, ?origin:String = "hscript") + public function registerModules(module:Array, ?origin:String = "hscript"):Void { var pkg:Array = null; var imports:Map = []; @@ -1967,7 +1998,8 @@ class PolymodInterpEx extends Interp pkg: path.slice(0, path.length - 1), fullPath: path.join("."), cls: null, - enm: null + enm: null, + abs: null }; if (PolymodScriptClass.importOverrides.exists(importedClass.fullPath)) @@ -1980,9 +2012,11 @@ class PolymodInterpEx extends Interp else if (PolymodScriptClass.abstractClassImpls.exists(importedClass.fullPath)) { // We used a macro to map each abstract to its implementation. - importedClass.cls = PolymodScriptClass.abstractClassImpls.get(importedClass.fullPath); - trace('RESOLVED ABSTRACT CLASS ${importedClass.fullPath} -> ${Type.getClassName(importedClass.cls)}'); - trace(Type.getClassFields(importedClass.cls)); + importedClass.abs = PolymodScriptClass.abstractClassImpls.get(importedClass.fullPath); + } + else if (PolymodScriptClass.typedefs.exists(importedClass.fullPath)) + { + importedClass.cls = PolymodScriptClass.typedefs.get(importedClass.fullPath); } else if (_scriptEnumDescriptors.exists(importedClass.fullPath)) { @@ -2038,7 +2072,8 @@ class PolymodInterpEx extends Interp pkg: path.slice(0, path.length - 1), fullPath: path.join("."), cls: null, - enm: null + enm: null, + abs: null }; if (PolymodScriptClass.importOverrides.exists(importedClass.fullPath)) @@ -2051,9 +2086,7 @@ class PolymodInterpEx extends Interp else if (PolymodScriptClass.abstractClassImpls.exists(importedClass.fullPath)) { // We used a macro to map each abstract to its implementation. - importedClass.cls = PolymodScriptClass.abstractClassImpls.get(importedClass.fullPath); - trace('RESOLVED ABSTRACT CLASS ${importedClass.fullPath} -> ${Type.getClassName(importedClass.cls)}'); - trace(Type.getClassFields(importedClass.cls)); + importedClass.abs = PolymodScriptClass.abstractClassImpls.get(importedClass.fullPath); } else if (_scriptEnumDescriptors.exists(importedClass.fullPath)) { @@ -2157,7 +2190,9 @@ class PolymodInterpEx extends Interp _clone._nextCallObject = this._nextCallObject; _clone._classDeclOverride = this._classDeclOverride; _clone.depth = this.depth; + #if hscriptPos _clone.curExpr = this.curExpr; + #end _clone.inTry = this.inTry; return _clone; } diff --git a/polymod/hscript/_internal/PolymodScriptClass.hx b/polymod/hscript/_internal/PolymodScriptClass.hx index 95220013..900a6f63 100644 --- a/polymod/hscript/_internal/PolymodScriptClass.hx +++ b/polymod/hscript/_internal/PolymodScriptClass.hx @@ -54,7 +54,7 @@ class PolymodScriptClass /** * Register a scripted class by parsing the text of that script. */ - static function registerScriptClassByString(body:String, path:String = null):Void + static function registerScriptClassByString(body:String, ?path:String):Void { scriptInterp.addModule(body, path == null ? 'hscriptClass' : 'hscriptClass($path)'); } @@ -92,28 +92,39 @@ class PolymodScriptClass * and map them to internal implementation classes. * We use this to access the functions of these abstracts. */ - public static var abstractClassImpls(get, never):Map>; + public static var abstractClassImpls(get, never):Map; - static var _abstractClassImpls:Map> = null; + static var _abstractClassImpls:Map = null; - static function get_abstractClassImpls():Map> + static function get_abstractClassImpls():Map { if (_abstractClassImpls == null) { - _abstractClassImpls = new Map>(); + _abstractClassImpls = new Map(); - var baseAbstractClassImpls:Map> = PolymodScriptClassMacro.listAbstractImpls(); + var baseAbstractClassImpls:Map, + clsName:String, + }> = PolymodScriptClassMacro.listAbstractImpls(); for (key => value in baseAbstractClassImpls) { - _abstractClassImpls.set(key, value); + if (value == null) continue; + + _abstractClassImpls.set(key, new PolymodStaticAbstractReference(key, value.cls, value.clsName)); } } return _abstractClassImpls; } + /** + * Define a list of `fieldName -> Class` pointing to the generated class containing a reference + * to each static field of each abstract. + */ public static var abstractClassStatics(get, never):Map>; + static var _abstractClassStatics:Map> = null; static function get_abstractClassStatics():Map> @@ -133,6 +144,31 @@ class PolymodScriptClass return _abstractClassStatics; } + /** + * Define a list of `typeName -> Class` which provides a reference to each typedef, + * since typedefs can't be normally resolved at runtime. + */ + public static var typedefs(get, never):Map>; + + static var _typedefs:Map> = null; + + static function get_typedefs():Map> + { + if (_typedefs == null) + { + _typedefs = new Map>(); + + var baseTypedefs:Map> = PolymodScriptClassMacro.listTypedefs(); + + for (key => value in baseTypedefs) + { + _typedefs.set(key, value); + } + } + + return _typedefs; + } + /** * Register a scripted class by retrieving the script from the given path. */ diff --git a/polymod/hscript/_internal/PolymodScriptClassMacro.hx b/polymod/hscript/_internal/PolymodScriptClassMacro.hx index 82afa8e2..f94b3b9e 100644 --- a/polymod/hscript/_internal/PolymodScriptClassMacro.hx +++ b/polymod/hscript/_internal/PolymodScriptClassMacro.hx @@ -17,9 +17,16 @@ using StringTools; */ class PolymodScriptClassMacro { + /** + * The name for the Haxe resource that stores the abstract static field paths. + */ + static inline final ABSTRACT_STATICS_RES_NAME:String = 'PolymodScriptClassMacro_AbstractStatics'; + /** * Returns a `Map>` which maps superclass paths to scripted classes. - * So `class ScriptedStage extends Stage implements HScriptable` will be `"Stage" -> ScriptedStage` + * So `class ScriptedStage extends Stage implements HScriptable` will be `"Stage" -> ScriptedStage` + * + * @return An expression containing a map of superclasses to their scripted classes */ public static macro function listHScriptedClasses():ExprOf>> { @@ -32,7 +39,10 @@ class PolymodScriptClassMacro return macro polymod.hscript._internal.PolymodScriptClassMacro.fetchHScriptedClasses(); } - public static macro function listAbstractImpls():ExprOf>> + /** + * @return An expression containing a map of abstract classes to their implementations + */ + public static macro function listAbstractImpls():ExprOf> { if (!onGenerateCallbackRegistered) { @@ -43,6 +53,10 @@ class PolymodScriptClassMacro return macro polymod.hscript._internal.PolymodScriptClassMacro.fetchAbstractImpls(); } + /** + * @return An expression containing a map of abstract field names to + * the internal class, generated by Polymod, to store that value (accessible via Reflection). + */ public static macro function listAbstractStatics():ExprOf>> { if (!onAfterTypingCallbackRegistered) @@ -60,6 +74,21 @@ class PolymodScriptClassMacro return macro polymod.hscript._internal.PolymodScriptClassMacro.fetchAbstractStatics(); } + /** + * @return An expression containing a map of each typedef name to + * the underlying class type. + */ + public static macro function listTypedefs():ExprOf>> + { + if (!onGenerateCallbackRegistered) + { + onGenerateCallbackRegistered = true; + haxe.macro.Context.onGenerate(onGenerate); + } + + return macro polymod.hscript._internal.PolymodScriptClassMacro.fetchTypedefs(); + } + #if macro static var onGenerateCallbackRegistered:Bool = false; static var onAfterTypingCallbackRegistered:Bool = false; @@ -71,14 +100,18 @@ class PolymodScriptClassMacro var hscriptedClassEntries:Array = []; var abstractImplEntries:Array = []; - var abstractStaticEntries:Array = []; + var abstractStaticEntries:Array<{}> = []; + var typedefEntries:Array = []; + + var startTime:Float = Sys.time(); for (type in allTypes) { switch (type) { - // Class instances case TInst(t, _params): + // Parse classes to check if they are `HScriptedClass` implementations, for processing later. + var classType:ClassType = t.get(); var classPath:String = '${classType.pack.concat([classType.name]).join(".")}'; @@ -88,74 +121,134 @@ class PolymodScriptClassMacro } else if (MacroUtil.implementsInterface(classType, hscriptedClassType)) { - // Context.info('${classPath} implements HScriptedClass? YEAH', Context.currentPos()); - // TODO: Do we need to parameterize? var superClass:Null = classType.superClass != null ? classType.superClass.t.get() : null; if (superClass == null) throw 'No superclass for ' + classPath; var superClassPath:String = '${superClass.pack.concat([superClass.name]).join(".")}'; - var entryData = [ - macro $v{superClassPath}, - // TODO: How do we do reification to get a class? - macro $v{classPath} - ]; + var entryData = [macro $v{superClassPath}, macro $v{classPath}]; hscriptedClassEntries.push(macro $a{entryData}); } - else {} + + case TType(t, _params): + var typedefType:DefType = t.get(); + var typedefPath:String = t.toString(); + var typedefTarget:Type = Context.followWithAbstracts(type); + + switch (typedefTarget) + { + case TAnonymous(_): + // Ignore typedefs to anonymous structures. + continue; + case TDynamic(_): + // Ignore typedefs to Dynamic. + continue; + case TFun(_args, _ret): + // Ignore typedefs to functions. + continue; + + case TAbstract(t, _params): + var targetPath:String = t.toString(); + + var entryData = [macro $v{typedefPath}, macro $v{targetPath}]; + + typedefEntries.push(macro $a{entryData}); + + case TEnum(t, _params): + var targetEnum:EnumType = t.get(); + var targetEnumPath:String = '${targetEnum.pack.concat([targetEnum.name]).join(".")}'; + + var entryData = [macro $v{typedefPath}, macro $v{targetEnumPath}]; + + typedefEntries.push(macro $a{entryData}); + + case TInst(t, _params): + var targetClass:ClassType = t.get(); + var targetClassPath:String = '${targetClass.pack.concat([targetClass.name]).join(".")}'; + + var entryData = [macro $v{typedefPath}, macro $v{targetClassPath}]; + + typedefEntries.push(macro $a{entryData}); + + default: + // Unknown typedef target type? + trace('TYPEDEF: ${typedefPath} -> ${typedefTarget}'); + } + case TAbstract(t, _params): + // Parse abstracts to cache their static values to allow scripts to access them later. + var abstractPath:String = t.toString(); - if (abstractPath == 'flixel.util.FlxColor') - { - var abstractType = t.get(); - var abstractImpl = abstractType.impl.get(); - var abstractImplPath = abstractType.impl.toString(); - // Context.info('${abstractImplPath} implements FlxColor', Context.currentPos()); + var abstractType = t.get(); + var abstractImpl = abstractType.impl?.get(); - var entryData = [macro $v{abstractPath}, macro $v{abstractImplPath}]; + if (abstractImpl == null) + { + // If the abstract doesn't have an implementation, it's usually an extern or something, so we always want to ignore it. + continue; + } - abstractImplEntries.push(macro $a{entryData}); + var abstractImplPath:String = abstractType.impl?.toString() ?? ''; - for (field in abstractImpl.statics.get()) - { - switch (field.type) - { - case TAbstract(_, _): - // - case TType(_, _): - // - default: - continue; - } + var entryData = [macro $v{abstractPath}, macro $v{abstractImplPath}]; - var key:String = '${abstractImplPath}.${field.name}'; + abstractImplEntries.push(macro $a{entryData}); - if (!staticFieldToClass.exists(key)) - { + for (field in abstractImpl.statics.get()) + { + switch (field.type) + { + case TAbstract(_, _): + // + case TType(_, _): + // + default: continue; - } + } - var staticEntryData = [macro $v{key}, macro $v{staticFieldToClass[key]},]; + var key:String = '${abstractImplPath}.${field.name}'; - abstractStaticEntries.push(macro $a{staticEntryData}); + if (!staticFieldToClass.exists(key)) + { + continue; } - // Try to apply RTTI? - abstractType.meta.add(':rtti', [], Context.currentPos()); - abstractImpl.meta.add(':rtti', [], Context.currentPos()); + var staticEntryData = + { + fieldPath: key, + reflectClassPath: staticFieldToClass[key] + }; + + abstractStaticEntries.push(staticEntryData); } default: continue; } } + var endTime:Float = Sys.time(); + + var duration:Float = endTime - startTime; + + Context.info('PolymodScriptClassMacro: ' + + 'Registered ${hscriptedClassEntries.length} HScriptedClasses, ' + + '${abstractImplEntries.length} abstract impls, ' + + '${abstractStaticEntries.length} abstract statics, ' + + '${typedefEntries.length} typedefs ' + + 'in ${duration} sec.', + Context.currentPos()); + var polymodScriptClassClassType:ClassType = MacroUtil.getClassType('polymod.hscript._internal.PolymodScriptClassMacro'); polymodScriptClassClassType.meta.remove('hscriptedClasses'); polymodScriptClassClassType.meta.add('hscriptedClasses', hscriptedClassEntries, Context.currentPos()); polymodScriptClassClassType.meta.remove('abstractImpls'); polymodScriptClassClassType.meta.add('abstractImpls', abstractImplEntries, Context.currentPos()); - polymodScriptClassClassType.meta.remove('abstractStatics'); - polymodScriptClassClassType.meta.add('abstractStatics', abstractStaticEntries, Context.currentPos()); + polymodScriptClassClassType.meta.remove('typedefs'); + polymodScriptClassClassType.meta.add('typedefs', typedefEntries, Context.currentPos()); + + // It can get VERY BIG so we have to resort to store it as a resource instead. + var absStaticsData:String = haxe.Serializer.run(abstractStaticEntries); + Context.addResource(ABSTRACT_STATICS_RES_NAME, haxe.io.Bytes.ofString(absStaticsData)); } static var iteration:Int = 0; @@ -165,6 +258,8 @@ class PolymodScriptClassMacro { var fields:Array = []; + var startTime:Float = Sys.time(); + for (type in types) { switch (type) @@ -173,141 +268,152 @@ class PolymodScriptClassMacro var abstractPath = a.toString(); var abstractType = a.get(); - if (abstractPath != 'flixel.util.FlxColor') - { - continue; - } - if (abstractType.impl == null) { + // Only a few classes end up here, generally ones that are implemented directly in code. + // Includes like StdTypes.Float, StdTypes.Dynamic, StdTypes.Void, cpp.Int16, cpp.SizeT, Class, Enum continue; } - - var abstractImplPath = abstractType.impl.toString(); - var abstractImplType = abstractType.impl.get(); - - for (field in abstractImplType.statics.get()) + else { - switch (field.kind) + var abstractImplPath = abstractType.impl.toString(); + var abstractImplType = abstractType.impl.get(); + var abstractImplStatics:Array = abstractImplType.statics.get(); + + for (field in abstractImplStatics) { - case FVar(read, write): - trace(field.name, read, write); - - var canGet:Bool = read == AccInline || read == AccNormal; - if (read == AccCall) - { - var getter:Null = null; - for (f in abstractImplType.statics.get()) + switch (field.kind) + { + case FVar(read, write): + var canGet:Bool = read == AccInline || read == AccNormal; + if (read == AccCall) { - if (f.name == 'get_${field.name}') + var getter:Null = null; + for (f in abstractImplStatics) { - getter = f; + if (f.name == 'get_${field.name}') + { + getter = f; + break; + } } - } - - if (getter == null) - { - throw 'This should not happen'; - } - - switch (getter.type) - { - case TFun(args, _): - if (args.length != 0) continue; - default: - throw 'This should not happen'; - } - canGet = true; - } + if (getter == null) + { + throw 'Getter is null?'; + } - var canSet:Bool = write == AccNormal; - if (write == AccCall) - { - var setter:Null = null; - for (f in abstractImplType.statics.get()) - { - if (f.name == 'set_${field.name}') + switch (getter.type) { - setter = f; + case TFun(args, _): + if (args.length != 0) continue; + default: + throw 'Getter has an unknown type?'; } - } - if (setter == null) - { - throw 'This should not happen'; + canGet = true; } - switch (setter.type) + var canSet:Bool = write == AccNormal; + if (write == AccCall) { - case TFun(args, _): - if (args.length != 1) continue; - default: - throw 'This should not happen'; - } + var setter:Null = null; + for (f in abstractImplType.statics.get()) + { + if (f.name == 'set_${field.name}') + { + setter = f; + break; + } + } - canSet = true; - } + if (setter == null) + { + throw 'Setter is null?'; + } - if (!canGet && !canSet) - { - continue; - } + switch (setter.type) + { + case TFun(args, _): + if (args.length != 1) continue; + default: + throw 'Setter has an unknown type?'; + } - var fieldName:String = '${abstractImplPath.replace('.', '_')}_${field.name}'; + canSet = true; + } - fields.push( + if (canGet || canSet) { - pos: Context.currentPos(), - name: fieldName, - access: [Access.APublic, Access.AStatic], - kind: FProp(canGet ? 'get' : 'never', canSet ? 'set' : 'never', Context.toComplexType(field.type), null) - }); - - if (canGet) - { - fields.push( + var fieldName:String = '${abstractImplPath.replace('.', '_')}_${field.name}'; + + fields.push( + { + pos: Context.currentPos(), + name: fieldName, + access: [Access.APublic, Access.AStatic], + kind: FProp(canGet ? 'get' : 'never', canSet ? 'set' : 'never', (macro :Dynamic), null) + }); + + var fieldExpr:Expr = null; + try + { + // when this fails, this should mean that we are dealing with an enum abstract + // so we need to handle it differently + var fullPath:String = '${abstractType.module}.${abstractType.name}'; + Context.getType(fullPath); + fieldExpr = Context.parse('${fullPath}.${field.name}', Context.currentPos()); + } + catch (_) { - pos: Context.currentPos(), - name: 'get_${fieldName}', - access: [Access.APublic, Access.AStatic], - kind: FFun( + fieldExpr = Context.getTypedExpr(field.expr()); + } + + if (canGet) + { + fields.push( { - args: [], - ret: null, - expr: macro - { - @:privateAccess - return ${Context.parse(abstractPath + '.' + field.name, Context.currentPos())}; - } - }) - }); - } - - if (canSet) - { - fields.push( + pos: Context.currentPos(), + name: 'get_${fieldName}', + access: [Access.APublic, Access.AStatic], + kind: FFun( + { + args: [], + ret: null, + expr: macro + {@:privateAccess return ${fieldExpr};} + }) + }); + } + + if (canSet) { - pos: Context.currentPos(), - name: 'set_${fieldName}', - access: [Access.APublic, Access.AStatic], - kind: FFun( + fields.push( { - args: [ - {name: 'value'}], - ret: null, - expr: macro - { - @:privateAccess - return ${Context.parse(abstractPath + '.' + field.name, Context.currentPos())} = value; - } - }) - }); - } - - staticFieldToClass.set('${abstractImplPath}.${field.name}', 'polymod.hscript._internal.AbstractStaticMembers_${iteration}'); - default: - continue; + pos: Context.currentPos(), + name: 'set_${fieldName}', + access: [Access.APublic, Access.AStatic], + kind: FFun( + { + args: [ + {name: 'value'}], + ret: null, + expr: macro + {@:privateAccess return ${fieldExpr} = value;} + }) + }); + } + + staticFieldToClass.set('${abstractImplPath}.${field.name}', 'polymod.hscript._internal.AbstractStaticMembers_${iteration}'); + } + + case FMethod(k): + // Skip methods, since we will be able to access the implementation class directly via Reflection. + continue; + + default: + continue; + } } } default: @@ -329,6 +435,12 @@ class PolymodScriptClassMacro fields: fields }); + var endTime:Float = Sys.time(); + + var duration:Float = endTime - startTime; + + Context.info('PolymodScriptClassMacro: Processed ${fields.length} static fields in ${duration} sec (iteration #${iteration}).', Context.currentPos()); + iteration++; } #end @@ -337,12 +449,8 @@ class PolymodScriptClassMacro { var metaData = Meta.getType(PolymodScriptClassMacro); - // trace('Got metaData: ' + metaData); - if (metaData.hscriptedClasses != null) { - trace('Got hscriptedClasses: ' + metaData.hscriptedClasses); - var result:Map> = []; // Each element is formatted as `[superClassPath, classPath]`. @@ -368,13 +476,13 @@ class PolymodScriptClassMacro } } - public static function fetchAbstractImpls():Map> + public static function fetchAbstractImpls():Map { var metaData = Meta.getType(PolymodScriptClassMacro); if (metaData.abstractImpls != null) { - var result:Map> = []; + var result:Map = []; // Each element is formatted as `[abstractPath, abstractImplPath]`. @@ -387,9 +495,7 @@ class PolymodScriptClassMacro var abstractPath:String = element[0]; var abstractImplPath:String = element[1]; - // var abstractType:Class = cast Type.resolveClass(abstractPath); #if js - trace('Resolving using JS method'); var abstractImplType:Class = resolveClass(abstractPath); if (abstractImplType == null) @@ -397,16 +503,21 @@ class PolymodScriptClassMacro throw 'Could not resolve ' + abstractPath; } #else - // trace('Resolving using native method'); var abstractImplType:Class = cast Type.resolveClass(abstractImplPath); if (abstractImplType == null) { - throw 'Could not resolve ' + abstractImplPath; + // If the abstract type was found at compile time, but couldn't resolve at runtime, + // it probably got optimized out. + // We'll have to construct a PolymodStaticAbstractReference for it later. } #end - result.set(abstractPath, abstractImplType); + result.set(abstractPath, + { + cls: abstractImplType, + clsName: abstractImplPath + }); } return result; @@ -419,21 +530,54 @@ class PolymodScriptClassMacro public static function fetchAbstractStatics():Map> { - var metaData = Meta.getType(PolymodScriptClassMacro); + var resDataContent:Null = haxe.Resource.getString(ABSTRACT_STATICS_RES_NAME); + if (resDataContent == null) + { + throw '"$ABSTRACT_STATICS_RES_NAME" resource not found!'; + } + var abstractStatics:Array<{fieldPath:String, reflectClassPath:String}> = cast haxe.Unserializer.run(resDataContent); - if (metaData.abstractStatics != null) + if (abstractStatics != null) { var result:Map> = []; // Each element is formatted as `[abstractPathImpl.fieldName, reflectClass]`. - for (element in metaData.abstractStatics) + for (element in abstractStatics) { - if (element.length != 2) + if ((element.fieldPath?.length ?? 0) == 0 || (element.reflectClassPath?.length ?? 0) == 0) { throw 'Malformed element in abstractStatics: ' + element; } + var reflectClass:Class = cast Type.resolveClass(element.reflectClassPath); + + result.set(element.fieldPath, reflectClass); + } + + return result; + } + else + { + throw 'No abstractStatics found in "${ABSTRACT_STATICS_RES_NAME}" resource!'; + } + } + + public static function fetchTypedefs():Map> + { + var metaData = Meta.getType(PolymodScriptClassMacro); + + if (metaData.typedefs != null) + { + var result:Map> = []; + + for (element in metaData.typedefs) + { + if (element.length != 2) + { + throw 'Malformed element in typedefs: ' + element; + } + var fieldPath:String = element[0]; var reflectClassPath:String = element[1]; var reflectClass:Class = cast Type.resolveClass(reflectClassPath); @@ -445,7 +589,7 @@ class PolymodScriptClassMacro } else { - throw 'No abstractStatics found in PolymodScriptClassMacro!'; + throw 'No typedefs found in PolymodScriptClassMacro!'; } } @@ -462,3 +606,9 @@ class PolymodScriptClassMacro } #end } + +typedef AbstractImplEntry = +{ + cls:Class, + clsName:String, +};