diff --git a/polymod/Polymod.hx b/polymod/Polymod.hx index acb14301..5e09ffda 100644 --- a/polymod/Polymod.hx +++ b/polymod/Polymod.hx @@ -9,7 +9,7 @@ import polymod.format.JsonHelp; import polymod.format.ParseRules; import polymod.fs.PolymodFileSystem; #if hscript -import polymod.hscript._internal.PolymodScriptClass; +import polymod.hscript._internal.PolymodScriptManager; #end import polymod.util.DependencyUtil; import polymod.util.VersionUtil; @@ -193,7 +193,7 @@ enum Framework } -@:allow(polymod.hscript._internal.PolymodScriptClass) +@:allow(polymod.hscript._internal.PolymodScriptManager) class Polymod { /** @@ -337,9 +337,11 @@ class Polymod } else { Polymod.registerAllScriptClasses(); - var classList = polymod.hscript._internal.PolymodScriptClass.listScriptClasses(); + var classList = PolymodScriptManager.instance.listScriptClasses(); Polymod.notice(PolymodErrorCode.SCRIPT_PARSED, 'Parsed and registered ${classList.length} scripted classes.'); } + + PolymodScriptManager.instance.validateImports(); } #else if (params.useScriptedClasses) @@ -654,7 +656,7 @@ class Polymod { #if hscript @:privateAccess - polymod.hscript._internal.PolymodScriptClass.clearScriptedClasses(); + PolymodScriptManager.instance.clearScriptedClasses(); polymod.hscript.HScriptable.ScriptRunner.clearScripts(); #else Polymod.warning(SCRIPT_HSCRIPT_NOT_INSTALLED, "Cannot register script classes, HScript is not available."); @@ -688,7 +690,7 @@ class Polymod if (!Polymod.assetLibrary.exists(path)) throw 'Couldn\'t find file "$textPath"'; } Polymod.debug('Registering script class "$path"'); - polymod.hscript._internal.PolymodScriptClass.registerScriptClassByPath(path); + PolymodScriptManager.instance.registerScriptClassByPath(path); } } } @@ -724,7 +726,7 @@ class Polymod if (!Polymod.assetLibrary.exists(path)) throw 'Couldn\'t find file "$textPath" (tried libraries ${libraryIds})'; } Polymod.debug('Fetching script class "$path"'); - var future = polymod.hscript._internal.PolymodScriptClass.registerScriptClassByPathAsync(path); + var future = PolymodScriptManager.instance.registerScriptClassByPathAsync(path); if (future != null) futures.push(future); } } @@ -794,7 +796,7 @@ class Polymod */ public static function addImportAlias(importAlias:String, importClass:Class):Void { #if hscript - PolymodScriptClass.importOverrides.set(importAlias, importClass); + PolymodScriptManager.instance.importOverrides.set(importAlias, importClass); #else Polymod.warning(PolymodErrorCode.SCRIPT_HSCRIPT_NOT_INSTALLED, 'Scripted classes imports were requested, but hscript is not installed.'); #end @@ -802,7 +804,7 @@ class Polymod public static function removeImportAlias(importAlias:String):Void { #if hscript - PolymodScriptClass.importOverrides.remove(importAlias); + PolymodScriptManager.instance.importOverrides.remove(importAlias); #else Polymod.warning(PolymodErrorCode.SCRIPT_HSCRIPT_NOT_INSTALLED, 'Scripted classes imports were requested, but hscript is not installed.'); #end @@ -815,7 +817,7 @@ class Polymod */ public static function addDefaultImport(importClass:Class, ?importAlias:String):Void { #if hscript - PolymodScriptClass.defaultImports.set(importAlias == null ? Type.getClassName(importClass) : importAlias, importClass); + PolymodScriptManager.instance.defaultImports.set(importAlias == null ? Type.getClassName(importClass) : importAlias, importClass); #else Polymod.warning(PolymodErrorCode.SCRIPT_HSCRIPT_NOT_INSTALLED, 'Scripted classes imports were requested, but hscript is not installed.'); #end diff --git a/polymod/hscript/HScriptable.hx b/polymod/hscript/HScriptable.hx index bc7633a8..479628f5 100644 --- a/polymod/hscript/HScriptable.hx +++ b/polymod/hscript/HScriptable.hx @@ -284,7 +284,7 @@ class Script public static function buildInterp():polymod.hscript._internal.PolymodInterpEx { // Arguments are only needed in a scripted class context. - return new polymod.hscript._internal.PolymodInterpEx(null, null); + return new polymod.hscript._internal.PolymodInterpEx(null); } public function new(script:String, ?origin:String = null) diff --git a/polymod/hscript/HScriptedClass.hx b/polymod/hscript/HScriptedClass.hx deleted file mode 100644 index 638e3231..00000000 --- a/polymod/hscript/HScriptedClass.hx +++ /dev/null @@ -1,87 +0,0 @@ -package polymod.hscript; - -/** - * This interface triggers the execution of a macro which redirects all function calls to a scripted class. - */ -@:autoBuild(polymod.hscript._internal.HScriptedClassMacro.build()) -interface HScriptedClass -{ - // The following functions ARE REAL, and are generated and applied to any class that implements this interface. - // I attempted to uncomment the below functions previously, and spent a good chunk of time tackling it, - // but found that cyclic dependencies resulted (building some methods of the class relies on building other classes, - // and those classes rely on the scripted class functions having already been defined). - // - // The alternatives are using expensive reflection or adding a verbose warning, and I chose the latter. - - // - // STATIC METHODS - // - - /** - * Initialize an instance of this scripted class. - * - * @param clsName The full classpath of the scripted class to instantiate. - * @param constArgs The arguments to pass to the constructor. - * @return The instance of the scripted class. - */ - // public static function init(clsName:String, constArgs:Array):ThisType; - - /** - * Call a custom static function on a scripted class, by the given name, with the given arguments. - * - * @param clsName The full classpath of the scripted class to invoke on. - * @param funcName The name of the function to call. - * @param funcArgs The arguments to pass to the function. - * @return The result of the function call. - */ - // public static function scriptStaticCall(clsName:String, funcName:String, funcArgs:Array):Dynamic; - - /** - * Retrieves a custom static variable on a scripted class, by the given name. - * - * @param clsName The full classpath of the scripted class to invoke on. - * @param fieldName The name of the variable to retrieve. - * @return The value of the variable. - */ - // public static function scriptStaticGet(clsName:String, fieldName:String):Dynamic; - - /** - * Sets the value of a custom static variable on a scripted class, by the given name. - * - * @param clsName The full classpath of the scripted class to invoke on. - * @param fieldName The name of the variable to set. - * @param value The value to set. - * @return The newly set value. - */ - // public static function scriptStaticSet(clsName:String, fieldName:String, value:Dynamic):Dynamic; - - // - // INSTANCE METHODS - // - - /** - * Call a custom instance function on a scripted class, by the given name, with the given arguments. - * - * @param funcName The name of the function to call. - * @param funcArgs The arguments to pass to the function. - * @return The result of the function call. - */ - // public function scriptCall(funcName:String, funcArgs:Array):Dynamic; - - /** - * Returns the value of a custom instancefield of the scripted class, by the given name. - * - * @param fieldName The name of the field to return. - * @return The value of the field, if any. - */ - // public function scriptGet(fieldName:String):Dynamic; - - /** - * Sets the value of a custom field of the scripted class, by the given name. - * - * @param fieldName The name of the field to set. - * @param value The value to set. - * @return The newly set value. - */ - // public function scriptSet(fieldName:String, value:Dynamic):Dynamic; -} diff --git a/polymod/hscript/_internal/HScriptedClassMacro.hx b/polymod/hscript/_internal/HScriptedClassMacro.hx deleted file mode 100644 index 44dae55e..00000000 --- a/polymod/hscript/_internal/HScriptedClassMacro.hx +++ /dev/null @@ -1,1049 +0,0 @@ -package polymod.hscript._internal; - -import haxe.macro.Context; -import haxe.macro.Expr; - -using Lambda; -using haxe.macro.ComplexTypeTools; -using haxe.macro.ExprTools; -using haxe.macro.TypeTools; - -class HScriptedClassMacro -{ - static var secondaryPassInitialized:Bool = false; - - /** - * The first step creates the interface functions. - * The second build step (called in an onAfterTyping callback) creates the rest of the functions, - * which require initial typing to be completed before they can be created. - */ - public static macro function build():Array - { - var cls:haxe.macro.Type.ClassType = Context.getLocalClass().get(); - var initialFields:Array = Context.getBuildFields(); - var fields:Array = [].concat(initialFields); - - // If the class already has `@:hscriptClassPreProcessed` on it, we don't need to do anything. - var alreadyProcessed_metadata = cls.meta.get().find(function(m) return m.name == ':hscriptClassPreProcessed'); - - if (alreadyProcessed_metadata == null) - { - // Context.info('HScriptedClass: Class ' + cls.name + ' ready to pre-process...', Context.currentPos()); - - var superCls:haxe.macro.Type.ClassType = cls.superClass.t.get(); - - var newFields:Array = buildScriptedClassUtils(cls, superCls); - fields = fields.concat(newFields); - - fields = buildHScriptClass(cls, fields); - - // Ensure unused scripted classes are still available to initialize in scripts. - // SORRY, DCE gets run before this, so we can't use the @:keep metadata. - cls.meta.add(":hscriptClassPreProcessed", [], cls.pos); - return fields; - } - else - { - // Already processed. - } - - // Returning null is equal to "don't do anything". - return null; - } - - /** - * Parse `@:hscriptClass`. - */ - static function parseHScriptClassParams(metaEntry:MetadataEntry):HScriptClassParams - { - var result:HScriptClassParams = {}; - - switch (metaEntry.params[0].expr) - { - case EObjectDecl(paramFields): - // paramFields - for (paramField in paramFields) - { - switch (paramField.field) - { - case 'baseClass': - switch (paramField.expr.expr) - { - case EConst(CIdent(baseClassName)): - result.baseClass = baseClassName; - default: - Context.error("Error: @:hscriptClass baseClass must be a string", Context.currentPos()); - } - break; - } - } - default: - Context.error("Error: @:hscriptClass({}) must contain an object", Context.currentPos()); - } - - return result; - } - - /** - * Create the complicated parts of the generated class, - * specifically the `init()` function and the override methods. - */ - public static function buildHScriptClass(cls:haxe.macro.Type.ClassType, fields:Array):Array - { - // var cls:haxe.macro.Type.ClassType = Context.getLocalClass().get(); - - var script_class_meta = cls.meta.get().find(function(m) return m.name == ':hscriptClass'); - if (script_class_meta != null) - { - var superCls:haxe.macro.Type.ClassType = cls.superClass.t.get(); - - // Create scripted class override for constructor. - var constructor = fields.find(function(field) return field.name == 'new'); - - if (constructor != null) - { - Context.error("Error: Constructor already defined for this class", Context.currentPos()); - } - else - { - if (superCls.constructor != null) - { - var superClsConstType:haxe.macro.Type = superCls.constructor.get().type; - // Context.follow(superCls.constructor.get().type); - switch (superClsConstType) - { - case TFun(args, ret): - // Build a new constructor, which has the same signature as the superclass constructor. - var constArgs = [ - for (arg in args) - {name: arg.name, opt: arg.opt, type: Context.toComplexType(arg.t)} - ]; - var initField:Field = buildScriptedClassInit(cls, superCls, constArgs); - fields.push(initField); - constructor = buildScriptedClassConstructor(constArgs); - case TLazy(builder): - var builtValue = builder(); - switch (builtValue) - { - case TFun(args, ret): - // Build a new constructor, which has the same signature as the superclass constructor. - var constArgs = [ - for (arg in args) - {name: arg.name, opt: arg.opt, type: Context.toComplexType(arg.t)} - ]; - var initField:Field = buildScriptedClassInit(cls, superCls, constArgs); - fields.push(initField); - constructor = buildScriptedClassConstructor(constArgs); - default: - Context.error('Error: Lazy superclass constructor is not a function (got ${builtValue})', Context.currentPos()); - } - default: - Context.error('Error: super constructor is not a function (got ${superClsConstType})', Context.currentPos()); - } - } - else - { - constructor = buildEmptyScriptedClassConstructor(); - // Create scripted class utility functions. - Context.info(' Creating scripted class utils...', Context.currentPos()); - var initField:Field = buildScriptedClassInit(cls, superCls, []); - fields.push(initField); - fields.push(constructor); - } - } - - // Create scripted class overrides for all fields (except constructor). - // Create scripted class overrides for non-constructor fields. - fields = fields.concat(buildScriptedClassFieldOverrides(cls)); - } - // Else, do nothing. - - return fields; - } - - static function buildScriptedClassInit(cls:haxe.macro.Type.ClassType, superCls:haxe.macro.Type.ClassType, superConstArgs:Array):Field - { - // Context.info(' Building scripted class init() function', Context.currentPos()); - var clsTypeName:String = cls.pack.join('.') != '' ? '${cls.pack.join('.')}.${cls.name}' : cls.name; - var superClsTypeName:String = superCls.pack.join('.') != '' ? '${superCls.pack.join('.')}.${superCls.name}' : superCls.name; - - var constArgs = [for (arg in superConstArgs) macro $i{arg.name}]; - var typePath:haxe.macro.TypePath = { - pack: cls.pack, - name: cls.name, - }; - var function_init:Field = { - name: 'init', - doc: "Initializes a scripted class instance using the given scripted class name and constructor arguments.", - access: [APublic, AStatic], - meta: null, - pos: cls.pos, - kind: FFun({ - args: [{name: 'clsName', type: Context.toComplexType(Context.getType('String'))},].concat(superConstArgs), - params: null, - ret: Context.toComplexType(Context.getType(clsTypeName)), - expr: macro - { - var clsRef = polymod.hscript._internal.PolymodClassDeclEx.PolymodStaticClassReference.tryBuild(clsName); - - if (clsRef == null) - { - polymod.Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'Could not construct instance of scripted class (${clsName} extends ' + $v{clsTypeName} + ')'); - return null; - } - - var result = clsRef.instantiate([$a{constArgs}]); - - if (result == null) - { - polymod.Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'Could not construct instance of scripted class (${clsName} extends ' + $v{clsTypeName} + ')'); - return null; - } - - return result; - }, - }), - }; - - return function_init; - } - - static function buildScriptedClassUtils(cls:haxe.macro.Type.ClassType, superCls:haxe.macro.Type.ClassType):Array - { - var clsTypeName:String = cls.pack.join('.') != '' ? '${cls.pack.join('.')}.${cls.name}' : cls.name; - var superClsTypeName:String = superCls.pack.join('.') != '' ? '${superCls.pack.join('.')}.${superCls.name}' : superCls.name; - - var function_scriptGet:Field = { - name: 'scriptGet', - doc: 'Retrieves the value of a local variable of a scripted class.', - access: [APublic], - meta: null, - pos: cls.pos, - kind: FFun({ - args: [{name: 'varName', type: Context.toComplexType(Context.getType('String'))}], - params: null, - ret: Context.toComplexType(Context.getType('Dynamic')), - expr: macro - { - return _asc.fieldRead(varName); - }, - }), - } - - var function_scriptSet:Field = { - name: 'scriptSet', - doc: 'Directly modifies the value of a local variable of a scripted class.', - access: [APublic], - meta: null, - pos: cls.pos, - kind: FFun({ - args: [ - {name: 'varName', type: Context.toComplexType(Context.getType('String'))}, - { - name: 'varValue', - type: Context.toComplexType(Context.getType('Dynamic')), - value: macro null, - } - ], - params: null, - ret: Context.toComplexType(Context.getType('Dynamic')), - expr: macro - { - return _asc.fieldWrite(varName, varValue); - }, - }), - } - - var function_scriptCall:Field = { - name: 'scriptCall', - doc: 'Calls a function of the scripted class with the given name and arguments.', - access: [APublic], - meta: null, - pos: cls.pos, - kind: FFun({ - args: [ - {name: 'funcName', type: Context.toComplexType(Context.getType('String'))}, - { - name: 'funcArgs', - type: toComplexTypeArray(Context.toComplexType(Context.getType('Dynamic'))), - value: macro null, - } - ], - params: null, - ret: Context.toComplexType(Context.getType('Dynamic')), - expr: macro - { - return _asc.callFunction(funcName, funcArgs == null ? [] : funcArgs); - }, - }), - }; - - var var__asc:Field = { - name: '_asc', - doc: "The AbstractScriptClass instance which any variable or function calls are redirected to internally.", - access: [APrivate], // Private instance variable - kind: FVar(Context.toComplexType(Context.getType('polymod.hscript._internal.PolymodAbstractScriptClass'))), - pos: cls.pos, - }; - - var function_listScriptClasses:Field = { - name: 'listScriptClasses', - doc: "Returns a list of all the scripted classes which extend this class.", - access: [APublic, AStatic], - meta: null, - pos: cls.pos, - kind: FFun({ - args: [], - params: null, - ret: toComplexTypeArray(Context.toComplexType(Context.getType('String'))), - expr: macro - { - return polymod.hscript._internal.PolymodScriptClass.listScriptClassesExtending($v{superClsTypeName}); - }, - }), - }; - - var function_scriptStaticCall:Field = { - name: 'scriptStaticCall', - doc: "Call a custom static function on a scripted class, by the given name, with the given arguments.", - access: [APublic, AStatic], - meta: null, - pos: cls.pos, - kind: FFun({ - args: [ - {name: 'clsName', type: Context.toComplexType(Context.getType('String'))}, - {name: 'funcName', type: Context.toComplexType(Context.getType('String'))}, - ], - params: null, - ret: Context.toComplexType(Context.getType('Dynamic')), - expr: macro - { - return polymod.hscript._internal.PolymodScriptClass.callScriptClassStaticFunction(clsName, funcName); - } - }) - }; - - var function_scriptStaticGet:Field = { - name: 'scriptStaticGet', - doc: "Retrieves a custom static variable on a scripted class, by the given name.", - access: [APublic, AStatic], - meta: null, - pos: cls.pos, - kind: FFun({ - args: [ - {name: 'clsName', type: Context.toComplexType(Context.getType('String'))}, - {name: 'fieldName', type: Context.toComplexType(Context.getType('String'))}, - ], - params: null, - ret: Context.toComplexType(Context.getType('Dynamic')), - expr: macro - { - return polymod.hscript._internal.PolymodScriptClass.getScriptClassStaticField(clsName, fieldName); - } - }) - }; - - var function_scriptStaticSet:Field = { - name: 'scriptStaticSet', - doc: "Sets the value of a custom static variable on a scripted class, by the given name.", - access: [APublic, AStatic], - meta: null, - pos: cls.pos, - kind: FFun({ - args: [ - {name: 'clsName', type: Context.toComplexType(Context.getType('String'))}, - {name: 'fieldName', type: Context.toComplexType(Context.getType('String'))}, - { - name: 'fieldValue', - type: Context.toComplexType(Context.getType('Dynamic')), - value: macro null, - }, - ], - params: null, - ret: Context.toComplexType(Context.getType('Dynamic')), - expr: macro - { - return polymod.hscript._internal.PolymodScriptClass.setScriptClassStaticField(clsName, fieldName, fieldValue); - } - }) - }; - - return [ - var__asc, - function_listScriptClasses, - function_scriptCall, - function_scriptGet, - function_scriptSet, - function_scriptStaticCall, - function_scriptStaticGet, - function_scriptStaticSet - ]; - } - - /** - * For each function in the superclass, create a function in the subclass - * that redirects to the internal abstract script class. - */ - static function buildScriptedClassFieldOverrides(cls:haxe.macro.Type.ClassType):Array - { - var fieldDone:Array = []; - var fieldArray:Array = []; - - var targetClass:haxe.macro.Type.ClassType = cls; - var mappedParams:Map = new Map(); - var tType = Context.getType(cls.name); - var tClass = Context.toComplexType(tType); - - // Start with a custom implementation of .toString() - var func_toString:haxe.macro.Expr.Field = buildScriptedClass_toString(targetClass); - fieldArray.push(func_toString); - fieldDone.push('toString'); - - while (targetClass != null) - { - // Context.info('Processing overrides for class: ${targetClass.name}<${mappedParams}>', Context.currentPos()); - // Values will be either of type haxe.macro.Expr.Field or Bool. This is because setting a Map value to null removes the key. - var newFields:Map = buildScriptedClassFieldOverrides_inner(targetClass, mappedParams); - for (newFieldName => newField in newFields) - { - if (Std.isOfType(newField, Bool)) - { - // Sometimes a child version needs to be skipped but the parent version doesn't. - // In this case, the parent needs to be skipped also. - // Example: A child function override can be inline when the parent isn't. - fieldDone.push(newFieldName); - } - else - { - if (!fieldDone.contains(newFieldName)) - { - fieldArray.push(newField); - fieldDone.push(newFieldName); - } - else - { - // Context.info(' Redundant: ${newField.name}', Context.currentPos()); - } - } - } - if (targetClass.superClass != null) - { - var targetParams:Array = targetClass.superClass.params; - targetClass = targetClass.superClass.t.get(); - for (paramIndex in 0...targetClass.params.length) - { - var paramType = targetParams[paramIndex]; - var paramName = targetClass.params[paramIndex].name; - var paramFullName = '${targetClass.pack.join('.')}.${targetClass.name}.${paramName}'; - mappedParams.set(paramFullName, paramType); - } - } - else - { - targetClass = null; - } - } - - return fieldArray; - } - - static function buildScriptedClass_toString(cls:haxe.macro.Type.ClassType):Field - { - return { - name: 'toString', - doc: null, - access: [APublic, AOverride], - meta: null, - pos: cls.pos, - kind: FFun({ - args: [], - params: null, - ret: Context.toComplexType(Context.getType('String')), - expr: macro - { - if (_asc == null) - { - var clsName = $v{cls.name}; - var superName = $v{cls.superClass.t.get().name}; - return 'PolymodScriptedClass<${clsName} extends ${superName}>(NO ASC)'; - } - else - { - return _asc.callFunction('toString', []); - } - }, - }), - }; - } - - static function buildScriptedClassFieldOverrides_inner(cls:haxe.macro.Type.ClassType, targetParams:Map):Map - { - // Values will be either of type haxe.macro.Expr.Field or Bool. This is because setting a Map value to null removes the key. - var fields:Map = new Map(); - - for (field in cls.fields.get()) - { - if (field.name == 'new') - { - // Do nothing - } - else - { - var results:Array = overrideField(field, false, targetParams); - if (results.length == 0) - { - fields.set(field.name, false); - } - else - { - for (result in results) - { - fields.set(result.name, result); - } - } - } - } - for (field in cls.statics.get()) - { - // Context.info(' Skipping: ${field.name} is static', Context.currentPos()); - } - return fields; - } - - static function getBaseParamsOfType(parentType:haxe.macro.Type, paramTypes:Array):Array - { - var parentParams:Array = []; - - switch (parentType) - { - case TMono(t): - var ty = t.get(); - return getBaseParamsOfType(ty, paramTypes); - - case TInst(t, params): - // Continue - parentParams = t.get().params; - - case TType(t, params): - // Recurse - var ty:haxe.macro.Type = t.get().type; - return getBaseParamsOfType(ty, paramTypes); - - case TDynamic(t): - // Recurse - return getBaseParamsOfType(t, paramTypes); - - case TLazy(f): - // Recurse - var ty:haxe.macro.Type = f(); - return getBaseParamsOfType(ty, paramTypes); - - case TAbstract(t, _params): - // Continue - parentParams = t.get().params; - - // case TEnum(t:Ref, params:Array): - // case TFun(args:Array<{name:String, opt:Bool, t:Type}>, ret:Type): - // case TAnonymous(a:Ref): - default: - Context.error('Unsupported type: ${parentType}', Context.currentPos()); - } - - var result:Array = []; - - for (i => parentParam in parentParams) - { - var newParam:haxe.macro.Type.TypeParameter = { - name: parentParam.name, - t: paramTypes[i], - }; - result.push(newParam); - } - - return result; - } - - static function scanBaseTypes(targetType:haxe.macro.Type):Array - { - switch (targetType) - { - case TFun(args, ret): - var results:Array = []; - - for (result in scanBaseTypes(ret)) - { - results.push(result); - } - for (arg in args) - { - for (result in scanBaseTypes(arg.t)) - { - results.push(result); - } - } - return results; - case TAbstract(ty, params): - if (params.length == 0) - { - return [targetType]; - } - else - { - var results:Array = []; - for (param in params) - { - for (result in scanBaseTypes(param)) - { - results.push(result); - } - } - return results; - } - default: - return [targetType]; - } - } - - /** - * Insert real types into a parameterized type. - * For example, `TypeA>>` becomes `TypeA>>` if T is `int`. - * - * Note, function runs recursively. - */ - static function deparameterizeType(targetType:haxe.macro.Type, targetParams:Map):haxe.macro.Type - { - var resultType:haxe.macro.Type = targetType; - - switch (targetType) - { - case TFun(args, ret): - // Function type. - // This is not referring to functions of a class, but rather a function taken as a parameter (like a callback). - - // Deparameterize the return type. - var retType:haxe.macro.Type = deparameterizeType(ret, targetParams); - // Deparameterize the argument types. - var argTypes:Array<{name:String, opt:Bool, t:haxe.macro.Type}> = args.map(function(arg) - { - return { - name: arg.name, - opt: arg.opt, - t: deparameterizeType(arg.t, targetParams), - }; - }); - - // Construct the new type. - resultType = TFun(argTypes, retType); - - case TAbstract(ty, params): - // Abstract type. Sometimes used by types like Null. - - var name = ty.toString(); - var typ = ty.get(); - - // Check if the Abstract type is a parameter we recognize and can replace. - if (targetParams.exists(ty.toString())) - { - // If so, replace it with the real type. - resultType = targetParams.get(ty.toString()); - // recursive call in case result is a parameter - resultType = deparameterizeType(resultType, targetParams); - } - else if (params.length != 0) - { - var oldParams:Array = []; - var newParams:Array = []; - for (param in params) - { - var baseTypes = scanBaseTypes(param); - - for (baseType in baseTypes) - { - var newParam = deparameterizeType(baseType, targetParams); - if (newParam.toString() == "Void") - { - // Skipping Void... - } - else - { - oldParams.push(baseType); - newParams.push(newParam); - } - } - } - var baseParams = getBaseParamsOfType(resultType, oldParams); - newParams = newParams.slice(0, baseParams.length); - - if (newParams.length > 0) - { - // Context.info('Building new abstract (${baseParams} + ${newParams})...', Context.currentPos()); - resultType = resultType.applyTypeParameters(baseParams, newParams); - // Context.info('Deparameterized abstract type: ${resultType.toString()}', Context.currentPos()); - } - else - { - // Leave the type as is. - } - } - else - { - // Else, there are no parameters related this type and we don't need to mutate it. - } - case TInst(ty, params): - // Instance type. Used by most variables. - - // Check if the Instance type is a parameter we recognize and can replace. - if (targetParams.exists(ty.toString())) - { - // If so, replace it with the real type. - resultType = targetParams.get(ty.toString()); - // recursive call in case result is a parameter - resultType = deparameterizeType(resultType, targetParams); - } - else if (params.length != 0) - { - var oldParams:Array = []; - var newParams:Array = []; - for (param in params) - { - var baseTypes = scanBaseTypes(param); - - for (baseType in baseTypes) - { - var newParam = deparameterizeType(baseType, targetParams); - if (newParam.toString() == "Void") - { - // Skipping Void... - } - else - { - oldParams.push(baseType); - newParams.push(newParam); - } - } - } - var baseParams = getBaseParamsOfType(resultType, oldParams); - newParams = newParams.slice(0, baseParams.length); - - if (newParams.length > 0) - { - // Context.info('Building new abstract (${baseParams} + ${newParams})...', Context.currentPos()); - resultType = resultType.applyTypeParameters(baseParams, newParams); - // Context.info('Deparameterized abstract type: ${resultType.toString()}', Context.currentPos()); - } - else - { - // Leave the type as is. - } - } - else - { - // Else, there are no parameters related this type and we don't need to mutate it. - } - - default: - // Do nothing. - // Muted because I haven't actually seen any issues caused by this. Maybe investigate in the future. - // Context.warning('You failed to handle this! ${targetType}', Context.currentPos()); - } - - return resultType; - } - - /** - * Given a ClassField from the target class, create one or more Fields that override the target field, - * redirecting any calls to the internal AbstractScriptedClass. - */ - static function overrideField(field:haxe.macro.Type.ClassField, isStatic:Bool, targetParams:Map, - ?type:haxe.macro.Type = null):Array - { - if (type == null) - { - type = field.type; - } - - switch (type) - { - case TLazy(lt): - // A lazy wrapper for another field. - // We have to call the function to get the true value. - var ltv:haxe.macro.Type = lt(); - return overrideField(field, isStatic, targetParams, ltv); - case TFun(args, ret): - // This field is a function of the class. - // We need to redirect to the scripted class in case our scripted class overrides it. - // If it isn't overridden, the AbstractScriptClass will call the original function. - - // We need to skip overriding functions which meet have a private type as an argument. - // Normal Haxe classes can't override these functions anyway, so we can skip them. - for (arg in args) - { - switch (arg.t) - { - case TInst(ty, pa): - var typ = ty.get(); - if (typ != null && typ.isPrivate) - { - // Context.info(' Skipping: "${field.name}" contains private type ${typ.module}.${typ.name}', Context.currentPos()); - return []; - } - default: // Do nothing. - } - } - - // We need to skip overriding functions which are inline. - // Normal Haxe classes can't override these functions anyway, so we can skip them. - switch (field.kind) - { - case FMethod(k): - switch (k) - { - case MethInline: - // Context.info(' Skipping: "${field.name}" is inline function', Context.currentPos()); - return []; - default: // Do nothing. - } - default: // Do nothing. - } - - // Skip overriding functions which are Generics. - // This is because this actually creates several different functions at compile time. - // TODO: Can we somehow override these functions? - for (fieldMeta in field.meta.get()) - { - if (fieldMeta.name == ':generic') - { - // Context.info(' Skipping: "${field.name}" is marked with @:generic', Context.currentPos()); - return []; - } - } - - var func_inputArgs:Array = []; - - // We only get limited information about the args from Type, we need to use TypedExprDef. - - if (field == null || field.expr() == null) - { - // Context.info(' Skipping: "${field.name}" is not an expression', Context.currentPos()); - return []; - } - - var func_access = [field.isPublic ? APublic : APrivate]; - if (field.isFinal) - func_access.push(AFinal); - if (isStatic) - { - func_access.push(AStatic); - } - else - { - func_access.push(AOverride); - } - - switch (field.expr().expr) - { - case TFunction(tfunc): - // Create an array of FunctionArg from the TFunction's argument objects. - // Context.info(' Processing args of function "${field.name}"', Context.currentPos()); - for (arg in tfunc.args) - { - // Whether the argument is optional. - var isOptional = (arg.value != null); - // The argument's metadata (if any). - var tfuncMeta:haxe.macro.Metadata = arg.v.meta.get(); - // The argument's expression/default value (if any). - var tfuncExpr:haxe.macro.Expr = arg.value == null ? null : Context.getTypedExpr(arg.value); - // The argument type. We have to handle any type parameters, and deparameterizeType does so recursively. - var tfuncType:haxe.macro.ComplexType = Context.toComplexType(deparameterizeType(arg.v.t, targetParams)); - - var tfuncArg:FunctionArg = { - name: arg.v.name, - type: tfuncType, - // opt: isOptional, - meta: tfuncMeta, - value: tfuncExpr, - }; - func_inputArgs.push(tfuncArg); - } - case TConst(tcon): - // Okay, so uh, this is actually a VARIABLE storing a function. - // Don't attempt to re-define it. - - return []; - default: - Context.warning('Expected a function and got ${field.expr().expr}', Context.currentPos()); - } - - // Is there a better way to do this? - var doesReturnVoid:Bool = ret.toString() == "Void"; - - // Generate the list of call arguments for the function. - // Context.info('${args}', Context.currentPos()); - var func_callArgs:Array = [for (arg in args) macro $i{arg.name}]; - - var func_params = [for (param in field.params) {name: param.name}]; - - // Context.info(' Processing return of function "${field.name}"', Context.currentPos()); - var func_ret = doesReturnVoid ? null : Context.toComplexType(deparameterizeType(ret, targetParams)); - - var funcName:String = field.name; - var func_over:Field = { - name: funcName, - doc: field.doc == null ? 'Polymod HScriptedClass override of ${field.name}.' : 'Polymod HScriptedClass override of ${field.name}.\n${field.doc}', - access: func_access, - meta: field.meta.get(), - pos: field.pos, - kind: FFun({ - args: func_inputArgs, - params: func_params, - ret: func_ret, - expr: macro - { - var fieldName:String = $v{funcName}; - if (_asc != null) - { - // trace('ASC: Calling $fieldName() in macro-generated function...'); - $ - { - doesReturnVoid ? (macro _asc.callFunction(fieldName, - [$a{func_callArgs}])) : (macro return _asc.callFunction(fieldName, [$a{func_callArgs}])) - } - } - else - { - // Fallback, call the original function. - // trace('ASC: Fallback to original ${fieldName}'); - $ - { - doesReturnVoid ? (macro super.$funcName($a{func_callArgs})) : (macro return super.$funcName($a{func_callArgs})) - } - } - }, - }), - }; - var func_superCall:Field = { - name: '__super_' + funcName, - doc: 'Calls the original ${field.name} function while ignoring the ScriptedClass override.', - access: [APrivate], - meta: field.meta.get(), - pos: field.pos, - kind: FFun({ - args: func_inputArgs, - params: func_params, - ret: func_ret, - expr: macro - { - var fieldName:String = $v{funcName}; - // Fallback, call the original function. - // trace('ASC: Force call to super ${fieldName}'); - $ - { - doesReturnVoid ? (macro super.$funcName($a{func_callArgs})) : (macro return super.$funcName($a{func_callArgs})) - } - }, - }), - } - - return [func_over, func_superCall]; - case TInst(_t, _params): - // This field is an instance of a class. - // Example: var test:TestClass = new TestClass(); - - // Originally, I planned to replace all variables on the class with properties, - // however this is not possible because properties are merely a compile-time feature. - - // However, since scripted classes correctly access the superclass variables anyway, - // there is no need to override the value. - // Context.info('Field: Instance variable "${field.name}"', Context.currentPos()); - return []; - case TEnum(_t, _params): - // Enum instance - // Context.info('Field: Enum variable "${field.name}"', Context.currentPos()); - return []; - case TMono(_t): - // Monomorph instance - // https://haxe.org/manual/types-monomorph.html - // Context.info('Field: Monomorph variable "${field.name}"', Context.currentPos()); - return []; - case TAnonymous(_t): - // Context.info('Field: Anonymous variable "${field.name}"', Context.currentPos()); - return []; - case TDynamic(_t): - // Context.info('Field: Dynamic variable "${field.name}"', Context.currentPos()); - return []; - case TAbstract(_t, _params): - // Context.info('Field: Abstract variable "${field.name}"', Context.currentPos()); - return []; - default: - // Context.info('Unknown field type: ${field}', Context.currentPos()); - return []; - } - } - - static function buildScriptedClassConstructor(superConstArgs:Array):Field - { - var constArgs:Array = superConstArgs; - var superCallArgs:Array = [for (arg in superConstArgs) macro $i{arg.name}]; - - // Context.info(' Generating constructor for scripted class with super(${superCallArgs})', Context.currentPos()); - - return { - name: 'new', - access: [APrivate], - pos: Context.currentPos(), - kind: FFun({ - args: superConstArgs, - expr: macro - { - // Call the super constructor with appropriate args - super($a{superCallArgs}); - }, - }), - }; - } - - /** - * Create the type corresponding to an array of the given type. - * For example, toComplexTypeArray(String) will return Array. - */ - static function toComplexTypeArray(inputType:ComplexType):haxe.macro.ComplexType - { - var typeParams = (inputType != null) ? [TPType(inputType)] : [ - TPType(TPath({ - pack: [], - name: 'Dynamic', - sub: null, - params: [] - })) - ]; - - var result:ComplexType = TPath({ - pack: [], - name: 'Array', - sub: null, - params: typeParams, - }); - - return result; - } - - static function buildEmptyScriptedClassConstructor():Field - { - return ({ - name: "new", - access: [APrivate], - pos: Context.currentPos(), - kind: FFun({ - args: [], - expr: macro - { - } - }) - }); - } -} - -typedef HScriptClassParams = -{ - ?baseClass:String, -} diff --git a/polymod/hscript/_internal/PolymodAbstractScriptClass.hx b/polymod/hscript/_internal/PolymodAbstractScriptClass.hx deleted file mode 100644 index 628d5db6..00000000 --- a/polymod/hscript/_internal/PolymodAbstractScriptClass.hx +++ /dev/null @@ -1,159 +0,0 @@ -package polymod.hscript._internal; - -@:forward -@:access(polymod.hscript._internal.PolymodScriptClass) -abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass -{ - private function resolveField(name:String):Dynamic - { - switch (name) - { - case "superClass": - return this.superClass; - case "createSuperClass": - return this.createSuperClass; - case "findFunction": - return this.findFunction; - case "callFunction": - return this.callFunction; - case _: - if (this.findFunction(name) != null) - { - var fn = this.findFunction(name); - var nargs = 0; - if (fn.args != null) - { - nargs = fn.args.length; - } - switch (nargs) - { - case 0: return this.callFunction0.bind(name); - case 1: return this.callFunction1.bind(name, _); - case 2: return this.callFunction2.bind(name, _, _); - case 3: return this.callFunction3.bind(name, _, _, _); - case 4: return this.callFunction4.bind(name, _, _, _, _); - #if neko - case _: @:privateAccess this._interp.error(ECustom("only 4 params allowed in script class functions (.bind limitation)")); - #else - case 5: return this.callFunction5.bind(name, _, _, _, _, _); - case 6: return this.callFunction6.bind(name, _, _, _, _, _, _); - case 7: return this.callFunction7.bind(name, _, _, _, _, _, _, _); - case 8: return this.callFunction8.bind(name, _, _, _, _, _, _, _, _); - case _: @:privateAccess this._interp.error(ECustom("only 8 params allowed in script class functions (.bind limitation)")); - #end - } - } - else if (this.findVar(name) != null) - { - var v = this.findVar(name); - - var varValue:Dynamic = null; - if (this._interp.variables.exists(name) == false) - { - if (v.expr != null) - { - varValue = this._interp.expr(v.expr); - this._interp.variables.set(name, varValue); - } - } - else - { - varValue = this._interp.variables.get(name); - } - return varValue; - } - else if (this.superClass == null) { - // @:privateAccess this._interp.error(EInvalidAccess(name)); - throw 'field "$name" does not exist in script class ${this.fullyQualifiedName}"'; - } else if (Type.getClass(this.superClass) == null) { - // Anonymous structure - if (Reflect.hasField(this.superClass, name)) { - return Reflect.field(this.superClass, name); - } else { - // @:privateAccess this._interp.error(EInvalidAccess(name)); - throw 'field "$name" does not exist in script class ${this.fullyQualifiedName}" or super class "${Type.getClassName(Type.getClass(this.superClass))}"'; - } - } else if (Std.isOfType(this.superClass, PolymodScriptClass)) { - // PolymodScriptClass - var superScriptClass:PolymodAbstractScriptClass = cast(this.superClass, PolymodScriptClass); - try - { - return superScriptClass.fieldRead(name); - } - catch (e:Dynamic) - { - } - } else { - // Class object - var fields = Type.getInstanceFields(Type.getClass(this.superClass)); - if (fields.contains(name) || fields.contains('get_$name')) { - return Reflect.getProperty(this.superClass, name); - } else { - // @:privateAccess this._interp.error(EInvalidAccess(name)); - throw "field '" + name + "' does not exist in script class '" + this.fullyQualifiedName + "' or super class '" + Type.getClassName(Type.getClass(this.superClass)) + "'"; - } - } - } - - if (this.superClass == null) - { - throw "field '" + name + "' does not exist in script class '" + this.fullyQualifiedName + "'"; - } - else - { - throw "field '" + name + "' does not exist in script class '" + this.fullyQualifiedName + "' or super class '" - + Type.getClassName(Type.getClass(this.superClass)) + "'"; - } - } - - @:op(a.b) public function fieldRead(name:String):Dynamic - { - return resolveField(name); - } - - @:op(a.b) public function fieldWrite(name:String, value:Dynamic):Dynamic - { - switch (name) - { - case _: - if (this.findVar(name) != null) - { - this._interp.variables.set(name, value); - return value; - } - else if (this.superClass != null && Std.isOfType(this.superClass, PolymodScriptClass)) - { - var superScriptClass:PolymodAbstractScriptClass = cast(this.superClass, PolymodScriptClass); - try - { - return superScriptClass.fieldWrite(name, value); - } - catch (e:Dynamic) - { - } - } - else { - // Class object - var fields = Type.getInstanceFields(Type.getClass(this.superClass)); - if (fields.contains(name) || fields.contains('get_$name')) { - Reflect.setProperty(this.superClass, name, value); - return value; - } else { - @:privateAccess this._interp.error(EInvalidAccess(name)); - // throw "field '" + name + "' does not exist in script class '" + this.fullyQualifiedName + "' or super class '" + Type.getClassName(Type.getClass(this.superClass)) + "'"; - } - } - } - - if (this.superClass == null) - { - @:privateAccess this._interp.error(EInvalidAccess(name)); - // throw "field '" + name + "' does not exist in script class '" + this.fullyQualifiedName + "'"; - } - else - { - @:privateAccess this._interp.error(EInvalidAccess(name)); - // throw "field '" + name + "' does not exist in script class '" + this.fullyQualifiedName + "' or super class '" + Type.getClassName(Type.getClass(this.superClass)) + "'"; - } - } -} diff --git a/polymod/hscript/_internal/PolymodClassDeclEx.hx b/polymod/hscript/_internal/PolymodClassDeclEx.hx index a5d45619..65d624b4 100644 --- a/polymod/hscript/_internal/PolymodClassDeclEx.hx +++ b/polymod/hscript/_internal/PolymodClassDeclEx.hx @@ -1,5 +1,6 @@ package polymod.hscript._internal; +#if hscript import hscript.Expr.ClassDecl; import hscript.Expr.FieldDecl; import polymod.hscript._internal.PolymodScriptClass; @@ -14,6 +15,7 @@ typedef PolymodClassDeclEx = * Save performance and improve sandboxing by resolving imports at interpretation time. */ @:optional var imports:Map; + @:optional var importsToValidate:Map; @:optional var pkg:Array; @:optional var staticFields:Array; @@ -29,67 +31,4 @@ typedef PolymodClassImport = { @:optional var cls:Class; @:optional var enm:Enum; } - -class PolymodStaticClassReference { - public var cls:PolymodClassDeclEx; - - public function new(cls:PolymodClassDeclEx) { - this.cls = cls; - } - - public static function tryBuild(clsName:String):Null { - @:privateAccess { - if (PolymodInterpEx._scriptClassDescriptors.exists(clsName)) { - return new PolymodStaticClassReference(PolymodInterpEx._scriptClassDescriptors.get(clsName)); - } else { - return null; - } - } - } - - /** - * Return a scripted instance of this script class. - * @param args - * @return Dynamic - */ - public function instantiate(?args:Array):Dynamic { - var asc:PolymodAbstractScriptClass = buildASC(args); - - if (asc == null) - { - polymod.Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'Could not construct instance of scripted class (${getFullyQualifiedName()})'); - return null; - } - - var scriptedObj = asc.superClass; - Reflect.setField(scriptedObj, '_asc', asc); - return scriptedObj; - } - - public function buildASC(?args:Array):PolymodAbstractScriptClass { - return new PolymodScriptClass(cls, args); - } - - public function callFunction(funcName:String, ?args:Array):Dynamic { - return PolymodScriptClass.callScriptClassStaticFunction(getFullyQualifiedName(), funcName, args); - } - - public function getField(fieldName:String):Dynamic { - return PolymodScriptClass.getScriptClassStaticField(getFullyQualifiedName(), fieldName); - } - - public function setField(fieldName:String, fieldValue:Dynamic):Dynamic { - return PolymodScriptClass.setScriptClassStaticField(getFullyQualifiedName(), fieldName, fieldValue); - } - - public function getFullyQualifiedName():String { - if (this.cls.pkg != null && this.cls.pkg.length > 0) { - return this.cls.pkg.join(".") + "." + this.cls.name; - } - return this.cls.name; - } - - public function toString():String { - return 'PolymodStaticClassReference(${getFullyQualifiedName()})'; - } -} +#end \ No newline at end of file diff --git a/polymod/hscript/_internal/PolymodExprEx.hx b/polymod/hscript/_internal/PolymodExprEx.hx index 0be927b7..3b2a4bf5 100644 --- a/polymod/hscript/_internal/PolymodExprEx.hx +++ b/polymod/hscript/_internal/PolymodExprEx.hx @@ -1,5 +1,6 @@ package polymod.hscript._internal; +#if hscript #if hscriptPos class ErrorEx { @@ -132,3 +133,4 @@ ECustom(msg:String); throw "Unimplemented error type " + err; } } +#end \ No newline at end of file diff --git a/polymod/hscript/_internal/PolymodInterpEx.hx b/polymod/hscript/_internal/PolymodInterpEx.hx index a8c69388..70decba3 100644 --- a/polymod/hscript/_internal/PolymodInterpEx.hx +++ b/polymod/hscript/_internal/PolymodInterpEx.hx @@ -4,253 +4,77 @@ package polymod.hscript._internal; import hscript.Expr; import hscript.Interp; import hscript.Tools; +import polymod.hscript._internal.PolymodClassDeclEx; import polymod.hscript._internal.PolymodExprEx; -import polymod.hscript._internal.PolymodClassDeclEx.PolymodClassImport; -import polymod.hscript._internal.PolymodClassDeclEx.PolymodStaticClassReference; /** * Based on code by Ian Harrigan * @see https://github.com/ianharrigan/hscript-ex */ @:access(polymod.hscript._internal.PolymodScriptClass) -@:access(polymod.hscript._internal.PolymodAbstractScriptClass) class PolymodInterpEx extends Interp { - var targetCls:Class; + public var scriptManager(get, never):PolymodScriptManager; - private var _proxy:PolymodAbstractScriptClass = null; + public function get_scriptManager():PolymodScriptManager + { + return PolymodScriptManager.instance; + } - var _classDeclOverride:PolymodClassDeclEx = null; + private var _proxy:PolymodScriptClass = null; - function getClassDecl():PolymodClassDeclEx { - if (_classDeclOverride != null) { - return _classDeclOverride; - } else { - return _proxy._c; - } - } + private var _overrideProxies:Array = []; - public function new(targetCls:Class, proxy:PolymodAbstractScriptClass) + private function getProxy():PolymodScriptClass { - super(); - _proxy = proxy; - variables.set("Math", Math); - variables.set("Std", Std); - this.targetCls = targetCls; + if (_overrideProxies.length >= 1) + return _overrideProxies[_overrideProxies.length - 1] + else + return _proxy; } - function errorEx(e:#if hscriptPos ErrorDefEx #else ErrorEx #end, rethrow = false):Dynamic + public function new(proxy:PolymodScriptClass) { - #if hscriptPos var e = new ErrorEx(e, curExpr?.pmin ?? 0, curExpr?.pmax ?? 0, curExpr?.origin ?? 'unknown', curExpr?.line ?? 0); #end - if (rethrow) - this.rethrow(e) - else - throw e; - return null; + super(); + _proxy = proxy; + variables.set('Math', Math); + variables.set('Std', Std); } - override function cnew(cl:String, args:Array):Dynamic + override function cnew(cls:String, args:Array):Dynamic { - // Try to retrieve a scripted class with this name in the same package. - if (getClassDecl().pkg != null && getClassDecl().pkg.length > 0) { - var localClassId = getClassDecl().pkg.join('.') + "." + cl; - var clsRef = PolymodStaticClassReference.tryBuild(localClassId); - if (clsRef != null) return clsRef.instantiate(args); - } - - // Try to retrieve a scripted class with this name in the base package. - var clsRef = PolymodStaticClassReference.tryBuild(cl); - if (clsRef != null) return clsRef.instantiate(args); - - if (_proxy != null) + if (getProxy() != null) { - @:privateAccess - if (getClassDecl().pkg != null) + if (getProxy()._c.imports.exists(cls)) { - @:privateAccess - var packagedClass = getClassDecl().pkg.join(".") + "." + cl; - if (_scriptClassDescriptors.exists(packagedClass)) - { - // OVERRIDE CHANGE: Create a PolymodScriptClass instead of a hscript.ScriptClass - var proxy:PolymodAbstractScriptClass = new PolymodScriptClass(_scriptClassDescriptors.get(packagedClass), args); - return proxy; - } - } + var imp = getProxy()._c.imports.get(cls); - @:privateAccess - if (getClassDecl().imports != null && getClassDecl().imports.exists(cl)) - { - var importedClass:PolymodClassImport = getClassDecl().imports.get(cl); - if (_scriptClassDescriptors.exists(importedClass.fullPath)) + if (imp.cls != null) { - // OVERRIDE CHANGE: Create a PolymodScriptClass instead of a hscript.ScriptClass - var proxy:PolymodAbstractScriptClass = new PolymodScriptClass(_scriptClassDescriptors.get(importedClass.fullPath), args); - return proxy; + if (imp.cls == PolymodScriptClass) + return new PolymodScriptClass(scriptManager.scriptClassDecls.get(imp.fullPath), args); + else + return Type.createInstance(imp.cls, args); } - // Ignore importedClass.enm as enums cannot be instantiated. - var c = importedClass.cls; - if (c == null) + if (imp.enm != null) { - errorEx(EBlacklistedModule(importedClass.fullPath)); - } else { - return Type.createInstance(c, args); + errorEx(ECustom('Cannot instantiate an enum')); + return null; } } - } - - // Attempt to resolve the class without overrides. - var cls = Type.resolveClass(cl); - if (cls == null) - cls = resolve(cl); - if (cls == null) - errorEx(EInvalidModule(cl)); - return Type.createInstance(cls,args); - } - /** - * Note to self: Calls to `this.xyz()` will have the type of `o` as `polymod.hscript.PolymodScriptClass`. - * Calls to `super.xyz()` will have the type of `o` as `stage.ScriptedStage`. - */ - override function fcall(o:Dynamic, f:String, args:Array):Dynamic - { - // OVERRIDE CHANGE: Custom logic to handle super calls to prevent infinite recursion - if (_proxy != null && o == _proxy.superClass) - { - // Force call super function. - return super.fcall(o, '__super_${f}', args); + var clsPath = getProxy().getFullyQualifiedPath(cls); + if (scriptManager.scriptClassDecls.exists(clsPath)) + return new PolymodScriptClass(scriptManager.scriptClassDecls.get(clsPath), args); } - else if (Std.isOfType(o, PolymodStaticClassReference)) { - var ref:PolymodStaticClassReference = cast(o, PolymodStaticClassReference); - return ref.callFunction(f, args); - } - else if (Std.isOfType(o, PolymodScriptClass)) - { - _nextCallObject = null; - var proxy:PolymodScriptClass = cast(o, PolymodScriptClass); - return proxy.callFunction(f, args); - } - - var func = get(o, f); - - // Workaround for an HTML5-specific issue. - // https://github.com/HaxeFoundation/haxe/issues/11298 - if (func == null && f == "contains") { - func = get(o, "includes"); - } - - if (func == null) - { - if (Std.isOfType(o, HScriptedClass)) - { - // This is a scripted class! - // We should try to call the function on the scripted class. - // If it doesn't exist, `asc.callFunction()` will handle generating an error message. - if (o.scriptCall != null) { - return o.scriptCall(f, args); - } - - errorEx(EInvalidScriptedFnAccess(f)); - } - else - { - // Throw an error for a missing function. - errorEx(EInvalidAccess(f)); - } - } - return call(o, func, args); - } - - private static var _scriptClassDescriptors:Map = new Map(); - - private static function registerScriptClass(c:PolymodClassDeclEx) - { - var name = c.name; - if (c.pkg != null) - { - name = c.pkg.join(".") + "." + name; - } - - if (_scriptClassDescriptors.exists(name)) { - Polymod.error(SCRIPT_CLASS_ALREADY_REGISTERED, 'A scripted class with the fully qualified name "$name" has already been defined. Please change the class name or the package name to ensure a unique name.'); - return; - } else { - Polymod.debug('Registering scripted class $name'); - _scriptClassDescriptors.set(name, c); - } - } - - public function clearScriptClassDescriptors():Void { - // Clear the script class descriptors. - _scriptClassDescriptors.clear(); - - // Also destroy local variable scope. - this.resetVariables(); - } - - public static function findScriptClassDescriptor(name:String) - { - return _scriptClassDescriptors.get(name); - } - - override function setVar(id:String, v:Dynamic) - { - if (_proxy != null && _proxy.superClass != null) - { - if (_proxy.superHasField(id)) - { - // Set in super class. - Reflect.setProperty(_proxy.superClass, id, v); - return; - } - } - - // Fallback to setting in local scope. - super.setVar(id, v); - } - - override function assign(e1:Expr, e2:Expr):Dynamic - { - switch (Tools.expr(e1)) - { - case EIdent(id): - // Make sure setting superclass fields directly works. - // Also ensures property functions are accounted for. - if (_proxy != null && _proxy.superClass != null) - { - if (_proxy.superHasField(id)) - { - var v = expr(e2); - Reflect.setProperty(_proxy.superClass, id, v); - return v; - } - } - case EField(e0, id): - // Make sure setting superclass fields works when using this. - // Also ensures property functions are accounted for. - switch (Tools.expr(e0)) - { - case EIdent(id0): - if (id0 == "this") - { - if (_proxy != null && _proxy.superClass != null) - { - if (_proxy.superHasField(id)) - { - var v = expr(e2); - Reflect.setProperty(_proxy.superClass, id, v); - return v; - } - } - } - default: - // Do nothing - } - default: - } - // Fallback, which calls set() - return super.assign(e1, e2); + var clsType = Type.resolveClass(cls); + if (clsType == null) + clsType = resolve(cls); + if (clsType == null) + errorEx(EInvalidModule(cls)); + return Type.createInstance(clsType, args); } public override function expr(e:Expr):Dynamic @@ -298,9 +122,9 @@ class PolymodInterpEx extends Interp { if (args.length < minParams) { - var str = "Invalid number of parameters. Got " + args.length + ", required " + minParams; + var str = 'Invalid number of parameters. Got ' + args.length + ', required ' + minParams; if (name != null) - str += " for function '" + name + "'"; + str += ' for function "' + name + '"'; errorEx(ECustom(str)); } // make sure mandatory args are forced @@ -365,12 +189,12 @@ class PolymodInterpEx extends Interp } catch (err:PolymodExprEx.ErrorEx) { - _proxy.reportErrorEx(err, 'anonymous'); + getProxy().reportErrorEx(err, 'anonymous'); r = null; } catch (err:hscript.Expr.Error) { - _proxy.reportError(err, 'anonymous'); + getProxy().reportError(err, 'anonymous'); r = null; } catch (err:Dynamic) @@ -405,7 +229,7 @@ class PolymodInterpEx extends Interp case EArrayDecl(arr): // Initialize an array (or map) from a declaration. var hasElements = arr.length > 0; - var hasMapElements = (hasElements && Tools.expr(arr[0]).match(EBinop("=>", _))); + var hasMapElements = (hasElements && Tools.expr(arr[0]).match(EBinop('=>', _))); if( hasMapElements ) { return exprMap(arr); @@ -443,7 +267,7 @@ class PolymodInterpEx extends Interp } } catch( err : Dynamic ) { // I can't handle this error the normal way because Stop is private GRAAAAA - if (Type.getEnumName(err) == "hscript.Interp.Stop") { + if (Type.getEnumName(err) == 'hscript.Interp.Stop') { inTry = oldTry; throw err; } @@ -463,6 +287,71 @@ class PolymodInterpEx extends Interp // If there is a try/catch block, the error will be caught. // If there is no try/catch block, the error will be reported. errorEx(EScriptThrow(str)); + case EField(e, f): + switch (Tools.expr(e)) + { + case EIdent(v): + var o = expr(e); + + if (o == PolymodScriptClass) + { + var classPath = v; + if (getProxy()._c.imports.exists(v)) + classPath = getProxy()._c.imports.get(v).fullPath; + return scriptManager.staticFieldRead(classPath, f); + } + else + { + return get(o, f); + } + default: + return get(expr(e), f); + } + case ECall(e, params): + switch (Tools.expr(e)) + { + case EField(e, f): + switch (Tools.expr(e)) + { + case EIdent(id): + var classPath:Null = null; + + if (id == getProxy()._c.name) + classPath = getProxy().getFullyQualifiedPath(id); + else if (getProxy()._c.imports.get(id)?.cls == PolymodScriptClass) + classPath = getProxy()._c.imports.get(id).fullPath; + + if (classPath != null) + return scriptManager.staticCallFunction(classPath, f, getArgs(params, false)); + default: + } + var o = expr(e); + + if (o == null) + errorEx(EInvalidAccess(f)); + + if (Std.isOfType(o, PolymodScriptClass)) + { + var scriptClass:PolymodScriptClass = cast(o, PolymodScriptClass); + if (!scriptClass.regularSuperHasField(f)) + return fcall(o, f, getArgs(params, false)); + } + + return fcall(o, f, getArgs(params, true)); + case EIdent(id): + if (id == 'trace') + return call(null, expr(e), getArgs(params, false)); + else if (getProxy() != null && getProxy().findField(id) != null) + return callThis(expr(e), getProxy().regularSuperHasField(id) ? getArgs(params, true) : getArgs(params, false)); + else if (getProxy() != null && getProxy().regularSuperHasField(id)) + return call(getProxy().getRegularSuper(), expr(e), getArgs(params, true)); + else if (getProxy() != null && scriptManager.staticFieldExists(getProxy().getFullyQualifiedPath(getProxy()._c.name), id)) + return scriptManager.staticCallFunction(getProxy().getFullyQualifiedPath(getProxy()._c.name), id, getArgs(params, false)); + else + return call(null, expr(e), getArgs(params, true)); + default: + return call(null, expr(e), getArgs(params, true)); + } default: // Do nothing. } @@ -477,7 +366,8 @@ class PolymodInterpEx extends Interp * @param t The explicit type of the expression, if provided. * @return The parsed expression. */ - public function exprWithType(e:Expr, ?t:CType):Dynamic { + public function exprWithType(e:Expr, ?t:CType):Dynamic + { if (t == null) { return this.expr(e); } @@ -540,13 +430,14 @@ class PolymodInterpEx extends Interp default: // Whatever. - } - - // Fallthrough. - return this.expr(e); } - function exprMap(entries:Array):Dynamic { + // Fallthrough. + return this.expr(e); + } + + function exprMap(entries:Array):Dynamic + { if (entries.length == 0) return super.makeMap([],[]); var keys = []; @@ -571,7 +462,8 @@ class PolymodInterpEx extends Interp return super.makeMap(keys, values); } - function makeMapEmpty(keyType:CType):Dynamic { + function makeMapEmpty(keyType:CType):Dynamic + { switch (keyType) { case CTPath(path, params): if (path.length > 0) { @@ -593,7 +485,8 @@ class PolymodInterpEx extends Interp return super.makeMap([], []); } - function exprArray(entries:Array):Dynamic { + function exprArray(entries:Array):Dynamic + { // Create an Array var a = new Array(); for( e in entries ) @@ -601,68 +494,48 @@ class PolymodInterpEx extends Interp return a; } - override function makeIterator(v:Dynamic):Iterator + override function fcall(o:Dynamic, f:String, args:Array):Dynamic { - if (v.iterator != null) + if (Std.isOfType(o, PolymodScriptClass)) { - try - { - v = v.iterator(); - } - catch (e:Dynamic) - { - }; - } - if (Std.isOfType(v, Array)) - { - v = new ArrayIterator(v); - } - if (v.hasNext == null || v.next == null) - { - errorEx(EInvalidIterator(v)); + var proxy:PolymodScriptClass = cast(o, PolymodScriptClass); + return proxy.callFunction(f, args); } - return v; + + var func = get(o, f); + + // Workaround for an HTML5-specific issue. + // https://github.com/HaxeFoundation/haxe/issues/11298 + if (func == null && f == "contains") + func = get(o, "includes"); + + if (func == null) + errorEx(EInvalidAccess(f)); + + return call(o, func, args); } - /** + /** * Call a given function on a given target with the given arguments. * @param target The object to call the function on. - * If null, defaults to `this`. * @param fun The function to call. * @param args The arguments to apply to that function. * @return The result of the function call. */ override function call(target:Dynamic, fun:Dynamic, args:Array):Dynamic { - // Calling fn() in hscript won't resolve an object first. Thus, we need to change it to use this.fn() instead. - if (target == null && _nextCallObject != null) - { - target = _nextCallObject; - } - if (fun == null) - { errorEx(EInvalidAccess(fun)); - } - if (target != null && target == _proxy) + try { - // If we are calling this.fn(), special handling is needed to prevent the local scope from being destroyed. - // By checking `target == _proxy`, we handle BOTH fn() and this.fn(). - // super.fn() is exempt since it is not scripted. - return callThis(fun, args); - } - else + var result = Reflect.callMethod(target, fun, args); + return result; + } + catch (e) { - try { - var result = Reflect.callMethod(target, fun, args); - _nextCallObject = null; - return result; - } catch (e) { - errorEx(EScriptCallThrow(e)); - _nextCallObject = null; - return null; - } + errorEx(EScriptCallThrow(e)); + return null; } } @@ -684,8 +557,9 @@ class PolymodInterpEx extends Interp this.depth++; // Call the function. - try { - var result = Reflect.callMethod(_proxy, fun, args); + try + { + var result = Reflect.callMethod(getProxy(), fun, args); // Restore the local scope. this.locals = capturedLocals; @@ -693,7 +567,9 @@ class PolymodInterpEx extends Interp this.depth = capturedDepth; return result; - } catch (e) { + } + catch (e) + { errorEx(EScriptCallThrow(e)); // Restore the local scope. @@ -703,690 +579,328 @@ class PolymodInterpEx extends Interp return null; } - - } - - override function execute(expr:Expr):Dynamic - { - // If this function is being called (and not executeEx), - // PolymodScriptClass is not being used to call the expression. - // This happens during callbacks and in some other niche cases. - // In this case, we know the parent caller doesn't have error handling! - // That means we have to do it here. - try - { - return super.execute(expr); - } - catch (err:PolymodExprEx.ErrorEx) - { - _proxy.reportErrorEx(err, 'anonymous'); - return null; - } - catch (err:hscript.Expr.Error) - { - _proxy.reportError(err, 'anonymous'); - return null; - } - catch (err:Dynamic) - { - throw err; - } - } - - public function executeEx(expr:Expr):Dynamic - { - // Directly call execute (assume error handling happens higher). - return super.execute(expr); } override function get(o:Dynamic, f:String):Dynamic { - if (o == null) errorEx(EInvalidAccess(f)); - if (Std.isOfType(o, PolymodStaticClassReference)) { - var ref:PolymodStaticClassReference = cast(o, PolymodStaticClassReference); + if (o == null) + errorEx(EInvalidAccess(f)); - return ref.getField(f); - } else if (Std.isOfType(o, PolymodScriptClass)) + if (Std.isOfType(o, PolymodScriptClass)) { - var proxy:PolymodAbstractScriptClass = cast(o, PolymodScriptClass); - if (proxy._interp.variables.exists(f)) + var proxy:PolymodScriptClass = cast o; + try { - return proxy._interp.variables.get(f); + return proxy.fieldRead(f); } - else if (proxy.superClass != null && proxy.superHasField(f)) - { - return Reflect.getProperty(proxy.superClass, f); - } - else + catch (e:Dynamic) { - try - { - return proxy.resolveField(f); - } - catch (e:Dynamic) - { - } errorEx(EUnknownVariable(f)); - } - } - else if (Std.isOfType(o, HScriptedClass)) - { - if (o.scriptGet != null) { - return o.scriptGet(f); - } - - errorEx(EInvalidScriptedVarGet(f)); - - // var result = Reflect.getProperty(o, f); - // To save a bit of performance, we only query for the existence of the property - // if the value is reported as null, AND only in debug builds. - - // #if debug - // if (!Reflect.hasField(o, f)) - // { - // var propertyList = Type.getInstanceFields(Type.getClass(o)); - // if (propertyList.indexOf(f) == -1) - // { - // errorEx(EInvalidScriptedVarGet(f)); - // } - // } - // #end - // return result; + } } // Default behavior - if (Reflect.hasField(o, f)) { - return Reflect.field(o, f); - } else { - try { - return Reflect.getProperty(o, f); - } catch (e:Dynamic) { - return Reflect.field(o, f); - } + try + { + return Reflect.getProperty(o, f); } - // return super.get(o, f); + catch (e:Dynamic) {} + return null; } override function set(o:Dynamic, f:String, v:Dynamic):Dynamic { if (o == null) errorEx(EInvalidAccess(f)); - if (Std.isOfType(o, PolymodStaticClassReference)) { - var ref:PolymodStaticClassReference = cast(o, PolymodStaticClassReference); - return ref.setField(f, v); - } else if (Std.isOfType(o, PolymodScriptClass)) + if (Std.isOfType(o, PolymodScriptClass)) { - var proxy:PolymodScriptClass = cast(o, PolymodScriptClass); - if (proxy._interp.variables.exists(f)) - { - proxy._interp.variables.set(f, v); - } - else if (proxy.superClass != null && Reflect.hasField(proxy.superClass, f)) - { - Reflect.setProperty(proxy.superClass, f, v); - } - else if (proxy.superClass != null && Type.getInstanceFields(Type.getClass(_proxy.superClass)).contains(f)) + var proxy:PolymodScriptClass = cast o; + try { - Reflect.setProperty(proxy.superClass, f, v); + return proxy.fieldWrite(f, v); } - else + catch (e:Dynamic) { errorEx(EUnknownVariable(f)); } return v; } - else if (Std.isOfType(o, HScriptedClass)) - { - if (o.scriptSet != null) { - return o.scriptSet(f, v); - } - - errorEx(EInvalidScriptedVarSet(f)); - - // Reflect.setProperty(o, f, v); - // return v; - } + // Default behavior try { Reflect.setProperty(o, f, v); } - catch (e) - { - errorEx(EInvalidAccess(f)); - } + catch (e:Dynamic) {} return v; } - private var _nextCallObject:Dynamic = null; - - override function exprReturn(expr:Expr):Dynamic + override function assign(e1:Expr, e2:Expr):Dynamic { - return super.exprReturn(expr); - // catch (err:hscript.Expr.Error) - // { - // #if hscriptPos - // throw err; - // #else - // throw err; - // #end - // } + if (getProxy() != null) + { + switch (Tools.expr(e1)) + { + case EIdent(id): + // Make sure setting superclass fields directly works. + // Also ensures property functions are accounted for. + if (getProxy().getRegularSuper() != null) + { + if (getProxy().regularSuperHasField(id)) + { + var v = expr(e2); + Reflect.setProperty(getProxy().getRegularSuper(), id, v); + return v; + } + } + + var classPath = getProxy().getFullyQualifiedPath(getProxy()._c.name); + if (scriptManager.staticFieldExists(classPath, id)) + { + var v = expr(e2); + return scriptManager.staticFieldWrite(classPath, id, v); + } + case EField(e0, f): + switch (Tools.expr(e0)) + { + case EIdent(id): + // Make sure setting superclass fields works when using this. + // Also ensures property functions are accounted for. + if (id == "this") + { + if (getProxy().getRegularSuper() != null) + { + if (getProxy().regularSuperHasField(f)) + { + var v = expr(e2); + Reflect.setProperty(getProxy().getRegularSuper(), f, v); + return v; + } + } + } + else + { + var classPath:Null = null; + + if (id == getProxy()._c.name) + classPath = getProxy().getFullyQualifiedPath(id); + else if (getProxy()._c.imports.get(id)?.cls == PolymodScriptClass) + classPath = getProxy()._c.imports.get(id).fullPath; + + if (classPath != null) + { + var v = expr(e2); + return scriptManager.staticFieldWrite(classPath, f, v); + } + } + default: + // Do nothing + } + default: + } + } + // Fallback, which calls set() + return super.assign(e1, e2); } - override function resolve(id:String):Dynamic + override function evalAssignOp(op, fop, e1, e2):Dynamic { - _nextCallObject = null; - if (id == "super") + if (getProxy() != null) { - if (_proxy == null) { - errorEx(EInvalidInStaticContext("super")); - } - else if (_proxy.superClass == null) + switch (Tools.expr(e1)) { - return _proxy.superConstructor; - } - else - { - return _proxy.superClass; + case EIdent(id): + var classPath = getProxy().getFullyQualifiedPath(getProxy()._c.name); + if (scriptManager.staticFieldExists(classPath, id)) + { + var v = fop(scriptManager.staticFieldRead(classPath, id), expr(e2)); + return scriptManager.staticFieldWrite(classPath, id, v); + } + case EField(e, f): + switch (Tools.expr(e)) + { + case EIdent(id): + var classPath:Null = null; + + if (id == getProxy()._c.name) + classPath = getProxy().getFullyQualifiedPath(id); + else if (getProxy()._c.imports.get(id)?.cls == PolymodScriptClass) + classPath = getProxy()._c.imports.get(id).fullPath; + + if (classPath != null) + { + var v = fop(scriptManager.staticFieldRead(classPath, f), expr(e2)); + return scriptManager.staticFieldWrite(classPath, f, v); + } + default: + } + default: } } - else if (id == "this") + return super.evalAssignOp(op, fop, e1, e2); + } + + override function execute(expr:Expr):Dynamic + { + // If this function is being called (and not executeEx), + // PolymodScriptClass is not being used to call the expression. + // This happens during callbacks and in some other niche cases. + // In this case, we know the parent caller doesn't have error handling! + // That means we have to do it here. + try { - if (_proxy != null) { - return _proxy; - } else { - errorEx(EInvalidInStaticContext("this")); - } + return super.execute(expr); } - else if (id == "null") + catch (err:PolymodExprEx.ErrorEx) { + getProxy().reportErrorEx(err, 'anonymous'); return null; } - - if (locals.exists(id)) - { - // NOTE: id may exist but be null - return locals.get(id).r; - } - if (variables.exists(id)) + catch (err:hscript.Expr.Error) { - // NOTE: id may exist but be null - return variables.get(id); + getProxy().reportError(err, 'anonymous'); + return null; } - - // OVERRIDE CHANGE: Allow access to modules for calling static functions. - - // Attempt to access an import. - if (_proxy != null) + catch (err:Dynamic) { - var importedClass:PolymodClassImport = getClassDecl().imports.get(id); - if (importedClass != null) { - if (importedClass.cls != null) return importedClass.cls; - if (importedClass.enm != null) return importedClass.enm; - } + throw err; } + } - // Allow access to scripted classes for calling static functions. - - if (getClassDecl().name == id) { - // Self-referencing - return new PolymodStaticClassReference(getClassDecl()); - } else { - // Try to retrieve a scripted class with this name in the same package. - if (getClassDecl().pkg != null && getClassDecl().pkg.length > 0){ - var localClassId = getClassDecl().pkg.join('.') + "." + id; - var result = PolymodStaticClassReference.tryBuild(localClassId); - if (result != null) return result; - } - - // Try to retrieve a scripted class with this name in the base package. - var result = PolymodStaticClassReference.tryBuild(id); - if (result != null) return result; - } + public function executeEx(expr:Expr):Dynamic + { + // Directly call execute (assume error handling happens higher). + return super.execute(expr); + } - var prop:Dynamic; - // We are calling a LOCAL function from the same module. - if (_proxy != null && _proxy.findFunction(id, true) != null) - { - _nextCallObject = _proxy; - return _proxy.resolveField(id); - } - else if (_proxy != null && _proxy.superHasField(id)) - { - _nextCallObject = _proxy.superClass; - return Reflect.getProperty(_proxy.superClass, id); - } - else if (_proxy != null) + override function makeIterator(v:Dynamic):Iterator + { + if (v.iterator != null) { try { - var r = _proxy.resolveField(id); - _nextCallObject = _proxy; - return r; + v = v.iterator(); } catch (e:Dynamic) { - // Skip and fall through to the next case. - } + }; } - if (getClassDecl() != null) { - // We are retrieving an adjacent field from a static context. - var cls = getClassDecl(); - var name = cls.name; - if (cls.pkg != null && cls.pkg.length > 0) { - name = cls.pkg.join('.') + "." + name; - } - return PolymodScriptClass.getScriptClassStaticField(name, id); + if (Std.isOfType(v, Array)) + { + v = new ArrayIterator(v); } - - errorEx(EUnknownVariable(id)); - - return null; + if (v.hasNext == null || v.next == null) + { + errorEx(EInvalidIterator(v)); + } + return v; } - public function addModule(moduleContents:String, ?origin:String = "hscript") + override function resolve(id:String):Dynamic { - var parser = new PolymodParserEx(); - var decls = parser.parseModule(moduleContents, origin); - registerModules(decls, origin); - } - - /** - * Call a static function of a scripted class. - * @param clsName The full classpath of the scripted class. - * @param fnName 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 callScriptClassStaticFunction(clsName:String, fnName:String, args:Array = null):Dynamic { - var fn:Null = null; - var imports:Map = []; - - var cls:Null = _scriptClassDescriptors.get(clsName); - if (cls != null) { - imports = cls.imports; - - // TODO: Optimize with a cache? - for (f in cls.staticFields) - { - if (f.name == fnName) - { - switch (f.kind) - { - case KFunction(func): - fn = func; - case _: - } - } - } - } else { - Polymod.error(SCRIPT_CLASS_NOT_REGISTERED, 'Scripted class $clsName has not been defined.'); - return null; + if (id == 'super') + { + if (getProxy() == null) + errorEx(EInvalidInStaticContext('super')); + else if (getProxy().superClass == null) + return getProxy().superConstructor; + else + return getProxy().superClass; } - - if (fn != null) { - // Populate function arguments. - - // previousValues is used to restore variables after they are shadowed in the local scope. - var previousClassDecl = _classDeclOverride; - var previousValues:Map = []; - 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 = this.expr(a.value); - } - - // NOTE: We assign these as variables rather than locals because those get wiped when we enter the function. - if (this.variables.exists(a.name)) - { - previousValues.set(a.name, this.variables.get(a.name)); - } - this.variables.set(a.name, value); - i++; - } - - this._classDeclOverride = cls; - - var result:Dynamic = null; - try - { - result = this.executeEx(fn.expr); - } - catch (err:PolymodExprEx.ErrorEx) - { - PolymodScriptClass.reportErrorEx(err, clsName, fnName); - // A script error occurred while executing the script function. - // Purge the function from the cache so it is not called again. - // purgeFunction(fnName); - return null; - } - catch (err:hscript.Expr.Error) - { - PolymodScriptClass.reportError(err, clsName, fnName); - // A script error occurred while executing the script function. - // Purge the function from the cache so it is not called again. - // purgeFunction(fnName); - return null; - } - - // Restore previous values. - for (a in fn.args) - { - if (previousValues.exists(a.name)) - { - this.variables.set(a.name, previousValues.get(a.name)); - } - else - { - this.variables.remove(a.name); - } - } - this._classDeclOverride = previousClassDecl; - - return result; - } else { - Polymod.error(SCRIPT_RUNTIME_EXCEPTION, - 'Error while calling static function ${fnName}(): EInvalidAccess' + '\n' + - 'InvalidAccess error: Static function "${fnName}" does not exist! Define it or call the correct function.'); + else if (id == 'this') + { + if (getProxy() != null) + return getProxy(); + else + errorEx(EInvalidInStaticContext('this')); + } + else if (id == 'null') + { return null; } - } - public function hasScriptClassStaticFunction(clsName:String, fnName:String, args:Array = null):Bool { - var imports:Map = []; + if (locals.exists(id)) + return locals.get(id).r; - var cls:Null = _scriptClassDescriptors.get(clsName); - if (cls != null) { - imports = cls.imports; + if (variables.exists(id)) + return variables.get(id); - // TODO: Optimize with a cache? - for (f in cls.staticFields) - { - if (f.name == fnName) - { - switch (f.kind) - { - case KFunction(func): - return true; - case _: - } - } - } - } else { - Polymod.error(SCRIPT_CLASS_NOT_REGISTERED, 'Scripted class $clsName has not been defined.'); - return false; + // Access static field of this class + if (getProxy() != null) + { + var classPath = getProxy().getFullyQualifiedPath(getProxy()._c.name); + if (scriptManager.staticFieldExists(classPath, id)) + return scriptManager.staticFieldRead(classPath, id); } - return false; - } - - public function getScriptClassStaticField(clsName:String, fieldName:String):Dynamic { - var prefixedName = clsName + '#' + fieldName; - var fieldDecl = getScriptClassStaticFieldDecl(clsName, fieldName); - - if (fieldDecl != null) { - if (!this.variables.exists(prefixedName)) { - switch (fieldDecl.kind) { - case KFunction(fn): - var result = buildScriptClassStaticFunction(clsName, fieldName, fn); - this.variables.set(prefixedName, result); - return result; - case KVar(v): - var result = this.expr(v.expr); - this.variables.set(prefixedName, result); - return result; - default: - throw 'Wuh?'; - } + // Attempt to access this class statically + if (getProxy() != null && id == getProxy()._c.name) + return PolymodScriptClass; - } else { - return this.variables.get(prefixedName); - } - } else { - Polymod.error(SCRIPT_RUNTIME_EXCEPTION, - 'Error while retrieving static field ${fieldName}(): EInvalidAccess' + '\n' + - 'InvalidAccess error: Static field "${fieldName}" does not exist! Define it or access the correct variable.'); - return null; + // Attempt to access an import. + if (getProxy() != null && getProxy()._c.imports.exists(id)) + { + var imp:PolymodClassImport = getProxy()._c.imports.get(id); + if (imp.cls != null) return imp.cls; + if (imp.enm != null) return imp.enm; } - } - - private function buildScriptClassStaticFunction(clsName:String, fieldName:String, fn:FunctionDecl):Dynamic { - var argCount = fn.args.length; - switch(argCount) { - case 0: return function():Dynamic { - return callScriptClassStaticFunction(clsName, fieldName, []); - }; - - case 1: return function(a:Dynamic):Dynamic { - return callScriptClassStaticFunction(clsName, fieldName, [a]); - }; - - case 2: return function(a:Dynamic, b:Dynamic):Dynamic { - return callScriptClassStaticFunction(clsName, fieldName, [a, b]); - }; - case 3: return function(a:Dynamic, b:Dynamic, c:Dynamic):Dynamic { - return callScriptClassStaticFunction(clsName, fieldName, [a, b, c]); - } - - case 4: return function(a:Dynamic, b:Dynamic, c:Dynamic, d:Dynamic):Dynamic { - return callScriptClassStaticFunction(clsName, fieldName, [a, b, c, d]); - } - - #if neko - case _: @:privateAccess error(ECustom("only 4 params allowed in script class functions (.bind limitation)")); - #else - case 5: return function(a:Dynamic, b:Dynamic, c:Dynamic, d:Dynamic, e:Dynamic):Dynamic { - return callScriptClassStaticFunction(clsName, fieldName, [a, b, c, d, e]); - } + // We are calling a LOCAL function from the same module. + if (getProxy() != null) + { + if (getProxy().findFunction(id) != null) + return getProxy().fieldRead(id); - case 6: return function(a:Dynamic, b:Dynamic, c:Dynamic, d:Dynamic, e:Dynamic, f:Dynamic):Dynamic { - return callScriptClassStaticFunction(clsName, fieldName, [a, b, c, d, e, f]); - } + if (getProxy().regularSuperHasField(id)) + return Reflect.getProperty(getProxy().getRegularSuper(), id); - case 7: return function(a:Dynamic, b:Dynamic, c:Dynamic, d:Dynamic, e:Dynamic, f:Dynamic, g:Dynamic):Dynamic { - return callScriptClassStaticFunction(clsName, fieldName, [a, b, c, d, e, f, g]); + try + { + return getProxy().fieldRead(id); } - - case 8: return function(a:Dynamic, b:Dynamic, c:Dynamic, d:Dynamic, e:Dynamic, f:Dynamic, g:Dynamic, h:Dynamic):Dynamic { - return callScriptClassStaticFunction(clsName, fieldName, [a, b, c, d, e, f, g, h]); + catch (e:Dynamic) + { + // Skip and fall through to the next case. } - - case _: @:privateAccess error(ECustom("only 8 params allowed in script class functions (.bind limitation)")); - #end } - // Fallthrough - return null; - } + errorEx(EUnknownVariable(id)); - public function setScriptClassStaticField(clsName:String, fieldName:String, value:Dynamic):Dynamic { - var v = getScriptClassStaticFieldDecl(clsName, fieldName); - if (v != null) { - var prefixedName = clsName + '#' + fieldName; - this.variables.set(prefixedName, value); - return value; - } else { - Polymod.error(SCRIPT_RUNTIME_EXCEPTION, - 'Error while modifying static field ${fieldName}(): EInvalidAccess' + '\n' + - 'InvalidAccess error: Static field "${fieldName}" does not exist! Define it or access the correct variable.'); - return null; - } + return null; } - /** - * Retrieve a static field declaration of a scripted class. - * @param clsName The full classpath of the scripted class. - * @param fieldName The name of the field to retrieve. - * @return The value of the field. - */ - public function getScriptClassStaticFieldDecl(clsName:String, fieldName:String):Null { - if (_scriptClassDescriptors.exists(clsName)) { - var cls = _scriptClassDescriptors.get(clsName); - var staticFields = cls.staticFields; - - // TODO: Optimize with a cache? - for (f in staticFields) + function getArgs(params:Array, autoCast:Bool):Array + { + var args = new Array(); + for (p in params) + { + var o = expr(p); + if (autoCast && Std.isOfType(o, PolymodScriptClass)) { - if (f.name == fieldName) + var scriptClass:PolymodScriptClass = cast(o, PolymodScriptClass); + switch (Tools.expr(p)) { - return f; + case EMeta(name, _, _): + if (name != ':noCast') + o = scriptClass.getRegularSuper() ?? o; + default: + o = scriptClass.getRegularSuper() ?? o; } } - - // Fallthrough. - return null; - } else { - Polymod.error(SCRIPT_CLASS_NOT_REGISTERED, 'Scripted class $clsName has not been defined.'); - return null; + args.push(o); } + return args; } - public function registerModules(module:Array, ?origin:String = "hscript") + function errorEx(e:#if hscriptPos ErrorDefEx #else ErrorEx #end, rethrow = false):Dynamic { - var pkg:Array = null; - var imports:Map = []; - - for (importPath in PolymodScriptClass.defaultImports.keys()) - { - var splitPath = importPath.split("."); - var clsName = splitPath[splitPath.length - 1]; - - imports.set(clsName, { - name: clsName, - pkg: splitPath.slice(0, splitPath.length - 1), - fullPath: importPath, - cls: PolymodScriptClass.defaultImports.get(importPath), - }); - } - - for (decl in module) - { - switch (decl) - { - case DPackage(path): - pkg = path; - case DImport(path, _): - var clsName = path[path.length - 1]; - - if (imports.exists(clsName)) - { - if (imports.get(clsName) == null) { - Polymod.error(SCRIPT_CLASS_MODULE_BLACKLISTED, 'Scripted class ${clsName} is blacklisted and cannot be used in scripts.', origin); - } else { - Polymod.warning(SCRIPT_CLASS_MODULE_ALREADY_IMPORTED, 'Scripted class ${clsName} has already been imported.', origin); - } - continue; - } - - var importedClass:PolymodClassImport = { - name: clsName, - pkg: path.slice(0, path.length - 1), - fullPath: path.join("."), - cls: null, - enm: null - }; - - if (PolymodScriptClass.importOverrides.exists(importedClass.fullPath)) { - // importOverrides can exist but be null (if it was set to null). - // If so, that means the class is blacklisted. - - importedClass.cls = PolymodScriptClass.importOverrides.get(importedClass.fullPath); - } 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)); - } else { - var resultCls:Class = Type.resolveClass(importedClass.fullPath); - - // If the class is not found, try to find it as an enum. - var resultEnm:Enum = null; - if (resultCls == null) - resultEnm = Type.resolveEnum(importedClass.fullPath); - - // If the class is still not found, skip this import entirely. - if (resultCls == null && resultEnm == null) { - Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not import class ${importedClass.fullPath}', origin); - continue; - } else if (resultCls != null) { - importedClass.cls = resultCls; - } else if (resultEnm != null) { - importedClass.enm = resultEnm; - } - } - - imports.set(importedClass.name, importedClass); - case DClass(c): - var extend = c.extend; - if (extend != null) - { - var superClassPath = new hscript.Printer().typeToString(extend); - if (!imports.exists(superClassPath)) { - switch (extend) { - case CTPath(path, params): - if (params != null && params.length > 0) { - errorEx(EClassUnresolvedSuperclass(superClassPath, 'do not include type parameters in super class name')); - } - default: - // Other error handling? - } - // Default - errorEx(EClassUnresolvedSuperclass(superClassPath, 'not recognized, is the type imported?')); - } - - if (imports.exists(superClassPath)) - { - var extendImport = imports.get(superClassPath); - if (extendImport.cls == null) - errorEx(EClassUnresolvedSuperclass(superClassPath, 'expected a class')); - - switch (extend) - { - case CTPath(_, params): - extend = CTPath(imports.get(superClassPath).fullPath.split('.'), params); - case _: - } - } - } - - var instanceFields = []; - var staticFields = []; - for (f in c.fields) - { - if (f.access.contains(AStatic)) { - staticFields.push(f); - } else { - instanceFields.push(f); - } - } - - var classDecl:PolymodClassDeclEx = { - imports: imports, - pkg: pkg, - name: c.name, - params: c.params, - meta: c.meta, - isPrivate: c.isPrivate, - extend: extend, - implement: c.implement, - fields: instanceFields, - isExtern: c.isExtern, - staticFields: staticFields, - }; - registerScriptClass(classDecl); - case DTypedef(_): - } - } + #if hscriptPos var e = new ErrorEx(e, curExpr?.pmin ?? 0, curExpr?.pmax ?? 0, curExpr?.origin ?? 'unknown', curExpr?.line ?? 0); #end + if (rethrow) + this.rethrow(e) + else + throw e; + return null; } } diff --git a/polymod/hscript/_internal/PolymodPrinterEx.hx b/polymod/hscript/_internal/PolymodPrinterEx.hx index 7e7d581e..3a039879 100644 --- a/polymod/hscript/_internal/PolymodPrinterEx.hx +++ b/polymod/hscript/_internal/PolymodPrinterEx.hx @@ -1,5 +1,6 @@ package polymod.hscript._internal; +#if hscript import hscript.Printer; class PolymodPrinterEx extends Printer @@ -37,3 +38,4 @@ class PolymodPrinterEx extends Printer #end } } +#end \ No newline at end of file diff --git a/polymod/hscript/_internal/PolymodScriptClass.hx b/polymod/hscript/_internal/PolymodScriptClass.hx index 8616d11e..17619c21 100644 --- a/polymod/hscript/_internal/PolymodScriptClass.hx +++ b/polymod/hscript/_internal/PolymodScriptClass.hx @@ -1,471 +1,89 @@ package polymod.hscript._internal; #if hscript -import hscript.Expr.FieldDecl; -import hscript.Expr.FunctionDecl; -import hscript.Expr.VarDecl; -import hscript.Printer; -import polymod.hscript._internal.PolymodClassDeclEx; - -using StringTools; +import hscript.Expr; +import polymod.hscript._internal.PolymodExprEx; enum Param { Unused; } -/** - * Provides handlers for scripted classes - * Based on code by Ian Harrigan - * @see https://github.com/ianharrigan/hscript-ex - */ -@:access(hscript.Interp) -@:allow(polymod.Polymod) -class PolymodScriptClass +@:allow(polymod.hscript._internal.PolymodScriptMacro) +class PolymodScriptClass { - /* - * STATIC VARIABLES - */ - private static final scriptInterp = new PolymodInterpEx(null, null); - - /** - * Provide a class name along with a corresponding class to override imports. - * You can set the value to `null` to prevent the class from being imported. - */ - public static final importOverrides:Map> = new Map>(); - - /** - * Provide a class name along with a corresponding class to import it in every scripted class. - */ - public static final defaultImports:Map> = new Map>(); - - /* - * STATIC METHODS - */ - /** - * Register a scripted class by parsing the text of that script. - */ - static function registerScriptClassByString(body:String, path:String = null):Void - { - scriptInterp.addModule(body, path == null ? 'hscriptClass' : 'hscriptClass($path)'); - } - - /** - * STATIC PROPERTIES - */ - - /** - * Define a list of script classes to override the default behavior of Polymod. - * For example, script classes should import `ScriptedSprite` instead of `Sprite`. - */ - public static var scriptClassOverrides(get, never):Map>; - static var _scriptClassOverrides:Map> = null; - - static function get_scriptClassOverrides():Map> { - if (_scriptClassOverrides == null) { - _scriptClassOverrides = new Map>(); + private static final SCRIPT_IMPL_SUFFIX:String = '_ScriptImpl_'; - var baseScriptClassOverrides:Map> = PolymodScriptClassMacro.listHScriptedClasses(); - - for (key => value in baseScriptClassOverrides) { - _scriptClassOverrides.set(key, value); - } - } - - return _scriptClassOverrides; - } - - /** - * Define a list of all the abstracts we have available at compile time, - * and map them to internal implementation classes. - * We use this to access the functions of these abstracts. - */ - public static var abstractClassImpls(get, never):Map>; - static var _abstractClassImpls:Map> = null; - - static function get_abstractClassImpls():Map> { - if (_abstractClassImpls == null) { - _abstractClassImpls = new Map>(); - - var baseAbstractClassImpls:Map> = PolymodScriptClassMacro.listAbstractImpls(); - - for (key => value in baseAbstractClassImpls) { - _abstractClassImpls.set(key, value); - } - } - - return _abstractClassImpls; - } - - /** - * Register a scripted class by retrieving the script from the given path. - */ - static function registerScriptClassByPath(path:String):Void - { - @:privateAccess { - var scriptBody = Polymod.assetLibrary.getText(path); - if (scriptBody == null) { - Polymod.error(SCRIPT_PARSE_ERROR, 'Error while loading script "${path}", could not retrieve script contents!'); - return; - } - try - { - registerScriptClassByString(scriptBody, path); - } - catch (err:PolymodExprEx.ErrorEx) - { - var errLine:String = #if hscriptPos '${err.line}' #else "#???" #end; - #if hscriptPos - switch (err.e) - #else - switch (err) - #end - { - case EUnexpected(s): - Polymod.error(SCRIPT_PARSE_ERROR, - 'Error while parsing script ${path}#${errLine}: EUnexpected' + '\n' + - 'Unexpected error: Unexpected token "${s}", is there invalid syntax on this line?'); - default: - Polymod.error(SCRIPT_PARSE_ERROR, 'Error while executing script ${path}#${errLine}: ' + '\n' + 'An unknown error occurred: ${err}'); - } - } catch (err:hscript.Expr.Error) { - var errLine:String = #if hscriptPos '${err.line}' #else "#???" #end; - #if hscriptPos - switch (err.e) - #else - switch (err) - #end - { - case EUnexpected(s): - Polymod.error(SCRIPT_PARSE_ERROR, - 'Error while parsing script ${path}#${errLine}: EUnexpected' + '\n' + - 'Unexpected error: Unexpected token "${s}", is there invalid syntax on this line?'); - default: - Polymod.error(SCRIPT_PARSE_ERROR, 'Error while executing script ${path}#${errLine}: ' + '\n' + 'An unknown error occurred: ${err}'); - } - } - } - } - - static function registerScriptClassByPathAsync(path:String):lime.app.Future - { - var promise = new lime.app.Promise(); + public var superClass(default, null):Null; - if (!Polymod.assetLibrary.exists(path)) { - Polymod.error(SCRIPT_PARSE_ERROR, 'Error while loading script "${path}", could not retrieve contents of non-existent script!'); - return null; - } + private var _c:PolymodClassDeclEx; - Polymod.assetLibrary.loadText(path).onComplete((text) -> - { - try - { - Polymod.debug('Fetched script class "$path", parsing...'); - registerScriptClassByString(text); - promise.complete(true); - } - catch (err:PolymodExprEx.ErrorEx) - { - var errLine:String = #if hscriptPos '${err.line}' #else "#???" #end; - #if hscriptPos - switch (err.e) - #else - switch (err) - #end - { - case EUnexpected(s): - Polymod.error(SCRIPT_PARSE_ERROR, - 'Error while parsing script ${path}#${errLine}: EUnexpected' + '\n' + - 'Unexpected error: Unexpected token "${s}", is there invalid syntax on this line?'); - default: - Polymod.error(SCRIPT_PARSE_ERROR, - 'Error while parsing script ${path}#${errLine}: ' + '\n' + 'An unknown error occurred: ${err}'); - } - promise.error(err); - } - }).onError((err) -> - { - if (err == "404") { - Polymod.error(SCRIPT_PARSE_ERROR, 'Error while loading script "${path}", could not retrieve script contents (404 error)!'); - } else { - Polymod.error(SCRIPT_PARSE_ERROR, 'Error while parsing script ${path}: ' + '\n' + 'An unknown error occurred: ${err}'); - promise.error(err); - } - }); - // Await the promise - return promise.future; - } + private var _extendingScriptedClass:Null; - /** - * Returns a list of all registered classes. - * @return Array - */ - public static function listScriptClasses():Array - { - var result = []; - @:privateAccess - for (key => _value in PolymodInterpEx._scriptClassDescriptors) - { - result.push(key); - } - return result; - } + private var _interp:PolymodInterpEx; /** - * Clears all parsed scripted class descriptors. - * You can call `Polymod.registerAllScriptClasses()` to re-register them later. + * Cache variables */ - public static function clearScriptedClasses():Void { - scriptInterp.clearScriptClassDescriptors(); - } - /** - * Returns a list of all registered classes which extend the class specified by the given name. - * @return Array - */ - public static function listScriptClassesExtending(clsPath:String):Array - { - var result = []; - @:privateAccess - for (key => value in PolymodInterpEx._scriptClassDescriptors) - { - var superClasses = getSuperClasses(value); - if (superClasses.indexOf(clsPath) != -1) - { - result.push(key); - } - } - return result; - } + private var _cachedVarDecls:Map = []; + private var _cachedFunctionDecls:Map = []; + private var _cachedFieldDecls:Map = []; + + // only used if extending a regular class + private var _cachedSuperFields:Array = []; - /** - * Returns a list of all registered classes which extend the specified class. - * @param cls Any Class which you expect scripted classes to be extending. - * @return Array - */ - static function listScriptClassesExtendingClass(cls:Class):Array - { - return listScriptClassesExtending(Type.getClassName(cls)); - } + private var _childClass:Null; - static function getSuperClasses(classDecl:PolymodClassDeclEx):Array + public function new(c:PolymodClassDeclEx, args:Array, ?childClass:PolymodScriptClass) { - if (classDecl.extend == null) - { - // No superclasses. - return []; - } + var targetClass:Null> = null; - // Get the super class name. - var extendString = (new hscript.Printer()).typeToString(classDecl.extend); - // Prepend the package name. - if (classDecl.pkg != null && extendString.indexOf('.') == -1) + if (c.extend != null) { - var extendPkg = classDecl.pkg.join('.'); - extendString = '$extendPkg.$extendString'; - } - - // Check if the superclass is a scripted class. - var classDescriptor:PolymodClassDeclEx = PolymodInterpEx.findScriptClassDescriptor(extendString); - - if (classDescriptor != null) - { - var result = [extendString]; - - // Parse the parent scripted class. - return result.concat(getSuperClasses(classDescriptor)); - } - else - { - // Templates are ignored completely since there's no type checking in HScript. - if (extendString.indexOf('<') != -1) + switch (c.extend) { - extendString = extendString.split('<')[0]; - } + case CTPath(path, params): + var clsPath = path.join('.'); + var clsName = path[path.length - 1]; - var superCls:Class = null; + if (PolymodScriptManager.instance.scriptClassDecls.exists(clsPath)) + targetClass = PolymodScriptClass; - if (classDecl.imports.exists(extendString)) - { - var importedClass:PolymodClassImport = classDecl.imports.get(extendString); - if (importedClass != null && importedClass.cls == null) { - // importedClass was defined but `cls` was null. This class must have been blacklisted. - var clsName = classDecl.pkg != null ? '${classDecl.pkg.join('.')}.${classDecl.name}' : classDecl.name; - Polymod.error(SCRIPT_PARSE_ERROR, 'Could not parse superclass "${classDecl.name}" of scripted class "${clsName}". The superclass may be blacklisted.'); - return []; - } else if (importedClass != null) { - superCls = importedClass.cls; - } - } + if (targetClass == null) + targetClass = PolymodScriptManager.instance.scriptOverrides.get(clsPath); - if (superCls == null) { - // Check if the superclass is a native class. - superCls = Type.resolveClass(extendString); - } + if (targetClass == null) + targetClass = c.imports.get(clsName).cls; - // Check if the superclass was resolved. - if (superCls != null) - { - var result = []; - // The superclass is a native class. - while (superCls != null) - { - // Recursively add this class's superclasses. - result.push(Type.getClassName(superCls)); + if (targetClass == null) + Polymod.error(SCRIPT_PARSE_ERROR, 'Could not find class "${clsPath}"'); - // This returns null when the class has no superclass. - superCls = Type.getSuperClass(superCls); - } - return result; - } - else - { - // The superclass is not a scripted class or native class. Probably doesn't exist, throw an error. - var clsName = classDecl.pkg != null ? '${classDecl.pkg.join('.')}.${classDecl.name}' : classDecl.name; - Polymod.error(SCRIPT_PARSE_ERROR, 'Could not parse superclass "$extendString" of scripted class "${clsName}". Are you sure that the superclass exists?'); - return []; + _extendingScriptedClass = targetClass == PolymodScriptClass; + default: + Polymod.error(SCRIPT_PARSE_ERROR, 'Could not determine target class for "${c.extend}" (unknown type?)'); } } - } - - public static function callScriptClassStaticFunction(clsName:String, funcName:String, args:Array = null):Dynamic { - return scriptInterp.callScriptClassStaticFunction(clsName, funcName, args); - } - - public static function hasScriptClassStaticFunction(clsName:String, funcName:String):Bool { - return scriptInterp.hasScriptClassStaticFunction(clsName, funcName); - } - - public static function getScriptClassStaticField(clsName:String, fieldName:String):Dynamic { - return scriptInterp.getScriptClassStaticField(clsName, fieldName); - } - - public static function setScriptClassStaticField(clsName:String, fieldName:String, fieldValue:Dynamic):Dynamic { - return scriptInterp.setScriptClassStaticField(clsName, fieldName, fieldValue); - } - /** - * INSTANCE METHODS - */ - public function new(c:PolymodClassDeclEx, args:Array) - { - var targetClass:Class = null; - switch (c.extend) - { - case CTPath(pth, params): - var clsPath = pth.join('.'); - var clsName = pth[pth.length - 1]; - - if (scriptClassOverrides.exists(clsPath)) { - targetClass = scriptClassOverrides.get(clsPath); - } - else if (c.imports.exists(clsName)) - { - var importedClass:PolymodClassImport = c.imports.get(clsName); - if (importedClass != null && importedClass.cls != null) { - targetClass = importedClass.cls; - } else if (importedClass != null && importedClass.cls == null) { - Polymod.error(SCRIPT_PARSE_ERROR, 'Could not determine target class for "${pth.join('.')}" (blacklisted type?)'); - } else { - Polymod.error(SCRIPT_PARSE_ERROR, 'Could not determine target class for "${pth.join('.')}" (unregistered type?)'); - } - } else { - Polymod.error(SCRIPT_PARSE_ERROR, 'Could not determine target class for "${pth.join('.')}" (unregistered type?)'); - } - default: - Polymod.error(SCRIPT_PARSE_ERROR, 'Could not determine target class for "${c.extend}" (unknown type?)'); - } - _interp = new PolymodInterpEx(targetClass, this); _c = c; - buildCaches(); - - var ctorField = findField("new"); - if (ctorField != null) - { - callFunction("new", args); - if (superClass == null && _c.extend != null) - { - @:privateAccess _interp.errorEx(EClassSuperNotCalled); - } - } - else if (_c.extend != null) - { - createSuperClass(args); - } - } - - var __superClassFieldList:Array = 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; - } - - private function createSuperClass(args:Array = null) - { - if (args == null) - { - args = []; - } - - var fullExtendString = new hscript.Printer().typeToString(_c.extend); - - // Templates are ignored completely since there's no type checking in HScript. - if (fullExtendString.indexOf('<') != -1) - { - fullExtendString = fullExtendString.split('<')[0]; - } - - // Build an unqualified path too. - var fullExtendStringParts = fullExtendString.split('.'); - var extendString = fullExtendStringParts[fullExtendStringParts.length - 1]; - - var classDescriptor = PolymodInterpEx.findScriptClassDescriptor(extendString); - if (classDescriptor != null) - { - var abstractSuperClass:PolymodAbstractScriptClass = new PolymodScriptClass(classDescriptor, args); - superClass = abstractSuperClass; - } - else - { - var clsToCreate:Class = null; + _interp = childClass?._interp ?? new PolymodInterpEx(this); + _childClass = childClass; - if (scriptClassOverrides.exists(fullExtendString)) { - clsToCreate = scriptClassOverrides.get(fullExtendString); + buildCache(targetClass); - if (clsToCreate == null) - { - @:privateAccess _interp.errorEx(EClassUnresolvedSuperclass(fullExtendString, 'WHY?')); - } - } else if (_c.imports.exists(extendString)) { - clsToCreate = _c.imports.get(extendString).cls; + construct(targetClass, args); - if (clsToCreate == null) - { - @:privateAccess _interp.errorEx(EClassUnresolvedSuperclass(extendString, 'target class blacklisted')); - } - } else { - @:privateAccess _interp.errorEx(EClassUnresolvedSuperclass(extendString, 'missing import')); - } - - superClass = Type.createInstance(clsToCreate, args); - } + if (!_extendingScriptedClass && superClass != null) + superClass.__scriptClass__ = _childClass ?? this; } - public static function reportError(err:hscript.Expr.Error, className:String = null, fnName:String = null) + public function reportError(err:Error, className:String = null, fnName:String = null) { - var errEx = PolymodExprEx.ErrorExUtil.toErrorEx(err); + var errEx = ErrorExUtil.toErrorEx(err); reportErrorEx(errEx, fnName); } - public static function reportErrorEx(err:PolymodExprEx.ErrorEx, className:String = null, fnName:String = null):Void + public function reportErrorEx(err:ErrorEx, className:String = null, fnName:String = null):Void { var errLine:String = #if hscriptPos '${err.line}' #else "#???" #end; @@ -531,132 +149,373 @@ class PolymodScriptClass } } - @:privateAccess(hscript.Interp) - public function callFunction(fnName:String, args:Array = null):Dynamic + public function findVar(name:String):Null + { + if (_cachedVarDecls.exists(name)) + return _cachedVarDecls.get(name); + + if (_extendingScriptedClass) + return superClass?.findVar(name); + + return null; + } + + public function findFunction(name:String):Null + { + if (_cachedFunctionDecls.exists(name)) + return _cachedFunctionDecls.get(name); + + if (_extendingScriptedClass) + return superClass?.findFunction(name); + + return null; + } + + public function findField(name:String):Null + { + if (_cachedFieldDecls.exists(name)) + return _cachedFieldDecls.get(name); + + if (_extendingScriptedClass) + return superClass?.findField(name); + + return null; + } + + /** + * This returns true if the super class has a field with the given name. + * Only use this if the super class is a regular class + * @param name Field name + * @return Bool + */ + public function regularSuperHasField(name:String):Bool + { + if (superClass == null) + return false; + + if (_extendingScriptedClass) + return superClass?.regularSuperHasField(name); + + return _cachedSuperFields.contains(name) || _cachedSuperFields.contains('get_$name'); + } + + /** + * Get the super class that isn't a scripted class + * @return Null + */ + public function getRegularSuper():Null + { + if (superClass == null) + return null; + + if (_extendingScriptedClass) + return superClass?.getRegularSuper(); + + return superClass; + } + + public function superConstructor(arg0:Dynamic = Unused, arg1:Dynamic = Unused, arg2:Dynamic = Unused, arg3:Dynamic = Unused #if !neko , arg4:Dynamic = Unused, arg5:Dynamic = Unused, arg6:Dynamic = Unused, arg7:Dynamic = Unused #end):Void + { + var args = []; + if (arg0 != Unused) + args.push(arg0); + if (arg1 != Unused) + args.push(arg1); + if (arg2 != Unused) + args.push(arg2); + if (arg3 != Unused) + args.push(arg3); + + #if !neko + if (arg4 != Unused) + args.push(arg4); + if (arg5 != Unused) + args.push(arg5); + if (arg6 != Unused) + args.push(arg6); + if (arg7 != Unused) + args.push(arg7); + #end + + createSuperClass(args); + } + + public function callFunction(fnName:String, ?args:Array):Dynamic { - var field = findField(fnName); + switch (fnName) + { + case 'castType': + return Reflect.callMethod(this, Reflect.field(this, 'castType'), args); + case 'castPath': + return Reflect.callMethod(this, Reflect.field(this, 'castPath'), args); + case 'toString': + return Reflect.callMethod(this, Reflect.field(this, 'toString'), args); + case 'scriptGet': + return Reflect.callMethod(this, Reflect.field(this, 'scriptGet'), args); + case 'scriptSet': + return Reflect.callMethod(this, Reflect.field(this, 'scriptSet'), args); + case 'scriptCall': + return Reflect.callMethod(this, Reflect.field(this, 'scriptCall'), args); + } + var r:Dynamic = null; - var fn = (field != null) ? findFunction(fnName, true) : null; + var fn:Null = findFunction(fnName); + args = args ?? []; if (fn != null) { - // previousValues is used to restore variables after they are shadowed in the local scope. - var previousValues:Map = []; - var i = 0; - for (a in fn.args) + @:privateAccess + if (_childClass != null) + _interp._overrideProxies.push(this); + + var prev:Map = []; + for (i => a in fn.args) { - var value:Dynamic = null; + prev.set(a.name, _interp.variables.get(a.name)); - if (args != null && i < args.length) - { - value = args[i]; - } + if (i < args.length) + _interp.variables.set(a.name, 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++; + _interp.variables.set(a.name, _interp.expr(a.value)); } - try - { - r = _interp.executeEx(fn.expr); - } - catch (err:PolymodExprEx.ErrorEx) - { - reportErrorEx(err, fullyQualifiedName, fnName); - // A script error occurred while executing the script function. - // Purge the function from the cache so it is not called again. - purgeFunction(fnName); - return null; - } - catch (err:hscript.Expr.Error) + r = _interp.executeEx(fn.expr); + + for (k => v in prev) + _interp.variables.set(k, v); + + @:privateAccess _interp._overrideProxies.pop(); + } + else if (getRegularSuper() != null) + { + var fn = Reflect.field(getRegularSuper(), fnName); + + if (fn == null) { - reportError(err, fullyQualifiedName, fnName); - // A script error occurred while executing the script function. - // Purge the function from the cache so it is not called again. - purgeFunction(fnName); + Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'Could not find function "${fnName}" in super class "${Type.getClassName(Type.getClass(getRegularSuper()))}"'); return null; } - for (a in fn.args) - { - if (previousValues.exists(a.name)) + r = Reflect.callMethod(getRegularSuper(), fn, args); + } + else + { + Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'Could not find function "${fnName}" in class "${_c.name}"'); + return null; + } + + return r; + } + + public function fieldRead(name:String):Dynamic + { + switch (name) + { + case 'superClass': + return superClass; + default: + if (findVar(name) != null) { - _interp.variables.set(a.name, previousValues.get(a.name)); + return _interp.variables.get(name); } - else - { - _interp.variables.remove(a.name); + else if (findFunction(name) != null) + { + var fn = findFunction(name); + var nargs = fn.args != null ? fn.args.length : 0; + switch (nargs) + { + case 0: return callFunction0.bind(name); + case 1: return callFunction1.bind(name, _); + case 2: return callFunction2.bind(name, _, _); + case 3: return callFunction3.bind(name, _, _, _); + case 4: return callFunction4.bind(name, _, _, _, _); + #if neko + default: @:privateAccess _interp.error(ECustom('only 4 params allowed in script class functions')); + #else + case 5: return callFunction5.bind(name, _, _, _, _, _); + case 6: return callFunction6.bind(name, _, _, _, _, _, _); + case 7: return callFunction7.bind(name, _, _, _, _, _, _, _); + case 8: return callFunction8.bind(name, _, _, _, _, _, _, _, _); + default: @:privateAccess _interp.error(ECustom('only 8 params allowed in script class functions')); + #end + } + } + else if (regularSuperHasField(name)) + { + return Reflect.getProperty(getRegularSuper(), name); } - } } + + throw 'field "${name}" does not exist in script class "${_c.name}" or super class "${Type.getClassName(Type.getClass(getRegularSuper()))}"'; + } + + public function fieldWrite(name:String, value:Dynamic):Dynamic + { + if (findVar(name) != null) + _interp.variables.set(name, value); + else if (getRegularSuper() != null && regularSuperHasField(name)) + Reflect.setProperty(getRegularSuper(), name, value); else + throw 'field "${name}" does not exist in script class "${_c.name}" or super class "${Type.getClassName(Type.getClass(getRegularSuper()))}"'; + + return value; + } + + public function getFullyQualifiedPath(cls:String):String + { + var path:String = cls; + if (_c.pkg != null) + path = '${_c.pkg.join('.')}.${cls}'; + return path; + } + + /** + * Cast to a super class using the given type + * @param type Type of the super class + * @return Null + */ + public function castType(type:Class):Null + { + var parentClass = superClass; + while (parentClass != null) { - var fixedArgs = []; - // OVERRIDE CHANGE: Use __super_ when calling superclass - var fixedName = '__super_${fnName}'; - for (a in args) + if (Std.isOfType(parentClass, type)) + return parentClass; + if (Std.isOfType(parentClass, PolymodScriptClass)) + parentClass = superClass.superClass; + else + break; + } + Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'Could not cast ${this.toString()} to ${Type.getClassName(type)}'); + return null; + } + + /** + * Cast to a super class using the fully qualified name of the class + * @param path Fully qualified name of the class + * @return Null + */ + public function castPath(path:String):Null + { + var parentClass = superClass; + while (parentClass != null) + { + if (Std.isOfType(parentClass, PolymodScriptClass)) { - if (Std.isOfType(a, PolymodScriptClass)) - { - fixedArgs.push(cast(a, PolymodScriptClass).superClass); - } + if (parentClass.getFullyQualifiedPath(parentClass._c.name) == path) + return parentClass; else - { - fixedArgs.push(a); - } - } - var fn = Reflect.field(superClass, fixedName); - if (fn == null) + parentClass = superClass.superClass; + } + else { - Polymod.error(SCRIPT_RUNTIME_EXCEPTION, - 'Error while calling function ${fnName}(): EInvalidAccess' + '\n' + - 'InvalidAccess error: Script does not have function "${fnName}"! Define it or call the correct script function or superclass function.'); - return null; + var cls = Type.resolveClass(path); + if (cls != null && Std.isOfType(parentClass, cls)) + return parentClass; + else + break; } - r = Reflect.callMethod(superClass, fn, fixedArgs); } - return r; + Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'Could not cast ${this.toString()} to ${path}'); + return null; } - private var _c:PolymodClassDeclEx; - private var _interp:PolymodInterpEx; + public function toString():String + { + return 'PolymodScriptClass(${getFullyQualifiedPath(_c.name)})'; + } - public var superClass:Dynamic = null; + private function construct(targetClass:Null>, args:Array):Void + { + if (findFunction('new') != null) + { + callFunction('new', args); - public var fullyQualifiedName(get, null):String; + if (superClass == null && targetClass != null) + Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'Missing super() call in "${_c.name}"'); + } + else if (targetClass != null) + { + createSuperClass(args); + + if (superClass == null) + Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'Could not create super class "${Type.getClassName(Type.getClass(superClass))}" in "${_c.name}"'); + } + } - private function get_fullyQualifiedName():String + private function createSuperClass(args:Array = null):Void { - var name = ""; - if (_c.pkg != null && _c.pkg.length > 0) + if (args == null) { - name += _c.pkg?.join(".") ?? '' + "."; + args = []; + } + + var fullExtendString = new hscript.Printer().typeToString(_c.extend); + + // Templates are ignored completely since there's no type checking in HScript. + if (fullExtendString.indexOf('<') != -1) + { + fullExtendString = fullExtendString.split('<')[0]; + } + + // Build an unqualified path too. + var fullExtendStringParts = fullExtendString.split('.'); + var extendString = fullExtendStringParts[fullExtendStringParts.length - 1]; + + if (PolymodScriptManager.instance.scriptClassDecls.exists(extendString)) + { + superClass = new PolymodScriptClass(PolymodScriptManager.instance.scriptClassDecls.get(extendString), args, _childClass ?? this); + } + else + { + var clsToCreate:Class = null; + + if (PolymodScriptManager.instance.scriptOverrides.exists(fullExtendString)) + { + clsToCreate = PolymodScriptManager.instance.scriptOverrides.get(fullExtendString); + + if (clsToCreate == null) + @:privateAccess _interp.errorEx(EClassUnresolvedSuperclass(fullExtendString, 'WHY?')); + } + else if (_c.imports.exists(extendString)) + { + clsToCreate = _c.imports.get(extendString).cls; + + if (clsToCreate == null) + @:privateAccess _interp.errorEx(EClassUnresolvedSuperclass(extendString, 'target class blacklisted')); + } + else + { + @:privateAccess _interp.errorEx(EClassUnresolvedSuperclass(extendString, 'missing import')); + } + + if (clsToCreate == PolymodScriptClass) + superClass = new PolymodScriptClass(PolymodScriptManager.instance.scriptClassDecls.get(_c.imports.get(extendString).fullPath), args, _childClass ?? this); + else + superClass = Type.createInstance(clsToCreate, args); } - name += _c.name; - return name; } - private function superConstructor(arg0:Dynamic = Unused, arg1:Dynamic = Unused, arg2:Dynamic = Unused, arg3:Dynamic = Unused) + private function buildCache(?targetClass:Class):Void { - var args = []; - if (arg0 != Unused) - args.push(arg0); - if (arg1 != Unused) - args.push(arg1); - if (arg2 != Unused) - args.push(arg2); - if (arg3 != Unused) - args.push(arg3); - createSuperClass(args); + for (field in _c.fields) + { + _cachedFieldDecls.set(field.name, field); + switch (field.kind) + { + case KVar(v): + _cachedVarDecls.set(field.name, v); + if (v.expr != null) + _interp.variables.set(field.name, _interp.expr(v.expr)); + case KFunction(f): + _cachedFunctionDecls.set(field.name, f); + default: + throw 'Unknown field kind "${field.kind}"'; + } + } + + if (!_extendingScriptedClass && targetClass != null) + _cachedSuperFields = Type.getInstanceFields(targetClass); } private inline function callFunction0(name:String):Dynamic @@ -684,6 +543,7 @@ class PolymodScriptClass return callFunction(name, [arg0, arg1, arg2, arg3]); } + #if !neko private inline function callFunction5(name:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic, arg4:Dynamic):Dynamic { return callFunction(name, [arg0, arg1, arg2, arg3, arg4]); @@ -694,149 +554,36 @@ class PolymodScriptClass return callFunction(name, [arg0, arg1, arg2, arg3, arg4, arg5]); } - private inline function callFunction7(name:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic, arg4:Dynamic, arg5:Dynamic, - arg6:Dynamic):Dynamic + private inline function callFunction7(name:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic, arg4:Dynamic, arg5:Dynamic, arg6:Dynamic):Dynamic { return callFunction(name, [arg0, arg1, arg2, arg3, arg4, arg5, arg6]); } - private inline function callFunction8(name:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic, arg4:Dynamic, arg5:Dynamic, arg6:Dynamic, - arg7:Dynamic):Dynamic + private inline function callFunction8(name:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic, arg4:Dynamic, arg5:Dynamic, arg6:Dynamic, arg7:Dynamic):Dynamic { return callFunction(name, [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7]); } + #end - /** - * Search for a function field with the given name. Excludes variables and static functions. - * @param name The name of the function to search for. - * @param cacheOnly If false, scan the full list of fields. - * If true, ignore uncached fields. - * @param excludeStatic If true, exclude static fields. - */ - private function findFunction(name:String, cacheOnly:Bool = true, excludeStatic:Bool = true):Null - { - if (_cachedFunctionDecls != null) - { - return _cachedFunctionDecls.get(name); - } - if (cacheOnly) return null; - - for (f in _c.fields) - { - if (f.name == name) - { - switch (f.kind) - { - case KFunction(fn): - if (excludeStatic && f.access.contains(AStatic)) continue; - return fn; - case _: - } - } - } - - 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 (_cachedFunctionDecls != null) - { - _cachedFunctionDecls.remove(name); - } - } - - /** - * Search for a variable field with the given name. Excludes functions and static variables. - * @param name The name of the variable to search for. - * @param cacheOnly If false, scan the full list of fields. - * If true, ignore uncached fields. - */ - private function findVar(name:String, cacheOnly:Bool = false):Null - { - if (_cachedVarDecls != null) - { - _cachedVarDecls.get(name); - } - if (cacheOnly) return null; - - for (f in _c.fields) - { - if (f.name == name) - { - switch (f.kind) - { - case KVar(v): - return v; - case _: - } - } - } - - return null; - } +/** + * DEPRECATED FUNCTIONS + * These functions are deprecated and should not be used. + * They exist for backwards compatibility. + */ - /** - * Search for a field (function OR variable) with the given name. - * @param name The name of the field to search for. - * @param cacheOnly If false, scan the full list of fields. - * If true, ignore uncached fields. - */ - private function findField(name:String, cacheOnly:Bool = true):Null + public function scriptGet(name:String):Dynamic { - if (_cachedFieldDecls != null) - { - return _cachedFieldDecls.get(name); - } - if (cacheOnly) return null; - - for (f in _c.fields) - { - if (f.name == name) - { - return f; - } - } - return null; + return fieldRead(name); } - public function listFunctions():Map + public function scriptSet(name:String, value:Dynamic):Dynamic { - return _cachedFunctionDecls; + return fieldWrite(name, value); } - private var _cachedFieldDecls:Map = null; - private var _cachedFunctionDecls:Map = null; - private var _cachedVarDecls:Map = null; - - private function buildCaches() + public function scriptCall(name:String, ?args:Array):Dynamic { - _cachedFieldDecls = []; - _cachedFunctionDecls = []; - _cachedVarDecls = []; - - for (f in _c.fields) - { - _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); - } - default: - throw 'Unknown field kind: ${f.kind}'; - } - } + return callFunction(name, args); } } -#end +#end \ No newline at end of file diff --git a/polymod/hscript/_internal/PolymodScriptClassMacro.hx b/polymod/hscript/_internal/PolymodScriptClassMacro.hx deleted file mode 100644 index 7d84bc43..00000000 --- a/polymod/hscript/_internal/PolymodScriptClassMacro.hx +++ /dev/null @@ -1,189 +0,0 @@ -package polymod.hscript._internal; - -#if macro -import haxe.macro.Context; -import haxe.macro.Expr; -import haxe.macro.Type; -import haxe.macro.Type.ClassType; -import polymod.util.MacroUtil; -#end - -import haxe.rtti.Meta; - -/** - * Provides a macro which, after types are generated, populates a list of classes which extend `polymod.hscript.HScriptedClass`. - * We have to do weird shenanigans to make the data accessible at runtime though. - */ -class PolymodScriptClassMacro { - /** - * Returns a `Map>` which maps superclass paths to scripted classes. - * So `class ScriptedStage extends Stage implements HScriptable` will be `"Stage" -> ScriptedStage` - */ - public static macro function listHScriptedClasses():ExprOf>> { - if (!onGenerateCallbackRegistered) - { - onGenerateCallbackRegistered = true; - haxe.macro.Context.onGenerate(onGenerate); - } - - return macro polymod.hscript._internal.PolymodScriptClassMacro.fetchHScriptedClasses(); - } - - public static macro function listAbstractImpls():ExprOf>> { - if (!onGenerateCallbackRegistered) - { - onGenerateCallbackRegistered = true; - haxe.macro.Context.onGenerate(onGenerate); - } - - return macro polymod.hscript._internal.PolymodScriptClassMacro.fetchAbstractImpls(); - } - - #if macro - static var onGenerateCallbackRegistered:Bool = false; - - static function onGenerate(allTypes:Array) { - // Reset these, since onGenerate persists across multiple builds. - var hscriptedClassType:ClassType = MacroUtil.getClassType('polymod.hscript.HScriptedClass'); - - var hscriptedClassEntries:Array = []; - var abstractImplEntries:Array = []; - - for (type in allTypes) { - switch (type) { - // Class instances - case TInst(t, _params): - var classType:ClassType = t.get(); - var classPath:String = '${classType.pack.join(".")}.${classType.name}'; - - if (classType.isInterface) { - // Ignore interfaces. - } 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.join(".")}.${superClass.name}'; - var entryData = [ - macro $v{superClassPath}, - // TODO: How do we do reification to get a class? - macro $v{classPath} - ]; - hscriptedClassEntries.push(macro $a{entryData}); - } else { } - case TAbstract(t, _params): - 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 entryData = [ - macro $v{abstractPath}, - macro $v{abstractImplPath} - ]; - - abstractImplEntries.push(macro $a{entryData}); - - // Try to apply RTTI? - abstractType.meta.add(':rtti', [], Context.currentPos()); - abstractImpl.meta.add(':rtti', [], Context.currentPos()); - } - default: - continue; - } - } - - var polymodScriptClassClassType:ClassType = MacroUtil.getClassType('polymod.hscript._internal.PolymodScriptClassMacro'); - polymodScriptClassClassType.meta.add('hscriptedClasses', hscriptedClassEntries, Context.currentPos()); - polymodScriptClassClassType.meta.add('abstractImpls', abstractImplEntries, Context.currentPos()); - - polymodScriptClassClassType.meta.add('hello', [macro $v{'world'}], Context.currentPos()); - } - #end - - public static function fetchHScriptedClasses():Map> { - 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]`. - - for (element in metaData.hscriptedClasses) { - if (element.length != 2) { - throw 'Malformed element in hscriptedClasses: ' + element; - } - - var superClassPath:String = element[0]; - var classPath:String = element[1]; - var classType:Class = cast Type.resolveClass(classPath); - result.set(superClassPath, classType); - } - - return result; - } else { - throw 'No hscriptedClasses found in PolymodScriptClassMacro!'; - } - } - - public static function fetchAbstractImpls():Map> { - var metaData = Meta.getType(PolymodScriptClassMacro); - - if (metaData.abstractImpls != null) { - var result:Map> = []; - - // Each element is formatted as `[abstractPath, abstractImplPath]`. - - for (element in metaData.abstractImpls) { - if (element.length != 2) { - throw 'Malformed element in abstractImpls: ' + element; - } - - 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) { - 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; - } - #end - - result.set(abstractPath, abstractImplType); - } - - return result; - } else { - throw 'No abstractImpls found in PolymodScriptClassMacro!'; - } - } - - #if js - static var PACKAGE_NAME_INVALID = ~/[^.a-zA-Z0-9]/; - - // Fucked up workaround, volatile and could break at any moment. - static function resolveClass(clsName:String):Class { - // Sanitize just in case someone tries to exploit this. - var sanitizedName = PACKAGE_NAME_INVALID.replace(clsName, ''); - var parsedName = StringTools.replace(sanitizedName, '.', '_'); - return js.Syntax.code('eval({0})', parsedName); - } - #end -} diff --git a/polymod/hscript/_internal/PolymodScriptMacro.hx b/polymod/hscript/_internal/PolymodScriptMacro.hx new file mode 100644 index 00000000..b00f18b8 --- /dev/null +++ b/polymod/hscript/_internal/PolymodScriptMacro.hx @@ -0,0 +1,588 @@ +package polymod.hscript._internal; + +#if hscript +import EReg; +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.Type; +import haxe.rtti.CType.Abstractdef; +import haxe.rtti.Meta; +import polymod.util.MacroUtil; + +using StringTools; +using haxe.macro.Tools; + +class PolymodScriptMacro +{ + public static macro function listAbstracts():ExprOf>> + { + if (!onGenerateCallbackRegistered) + { + onGenerateCallbackRegistered = true; + Context.onGenerate(onGenerate); + } + return macro polymod.hscript._internal.PolymodScriptMacro.fetchAbstracts(); + } + + public static macro function listScriptOverrides():ExprOf>> + { + if (!onGenerateCallbackRegistered) + { + onGenerateCallbackRegistered = true; + Context.onGenerate(onGenerate); + } + return macro polymod.hscript._internal.PolymodScriptMacro.fetchScriptOverrides(); + } + + #if macro + static var onGenerateCallbackRegistered:Bool = false; + + static function onGenerate(types:Array):Void + { + var abstracts:Array = []; + + for (type in types) + { + switch (type) + { + case TAbstract(t, _): + if (t.toString() != "flixel.util.FlxColor") + continue; + + var abstractPath = t.toString(); + var abstractClass = t.get(); + + var implementationPath = abstractClass.impl.toString(); + var implementationClass = abstractClass.impl.get(); + + var metaData = [macro $v{abstractPath}, macro $v{implementationPath}]; + + abstracts.push(macro $a{metaData}); + default: + continue; + } + } + + var macroClass = MacroUtil.getClassType('polymod.hscript._internal.PolymodScriptMacro'); + macroClass.meta.add('abstracts', abstracts, Context.currentPos()); + macroClass.meta.add('scriptOverrides', scriptOverrides, Context.currentPos()); + } + + // classes to exclude + static final excludes:Array = [ + // basic classes: cannot extend basic class + 'Array', 'Std', 'String', 'Reflect', 'Sys', 'Date', 'EReg', 'Type', 'Math', 'Xml', + + // compile errors on cpp: cannot open include file + 'haxe.EnumTools', 'haxe.EnumValueTools', + + // compile errors + 'cpp', + + // some additional necessities + 'polymod.hscript._internal.PolymodScriptClass', + ]; + static var scriptOverrides:Array = []; + public static function buildScriptImpls(?filters:Array):Void + { + // if no filters are given + // create a scripted implementation + // for every class + filters = filters ?? ['']; + + Context.onAfterTyping((modules) -> { + for (m in modules) + { + var cls:ClassType = switch (m) + { + case TClassDecl(c): + var cls = c.get(); + + var exclude:Bool = false; + for (f in excludes) + if (isInFilter(cls, f)) + exclude = true; + if (exclude) + continue; + + if (cls.name.endsWith(PolymodScriptClass.SCRIPT_IMPL_SUFFIX)) + continue; + + var inFilter:Bool = false; + for (f in filters) + if (isInFilter(c.get(), f)) + inFilter = true; + if (!inFilter) + continue; + + cls; + default: + continue; + } + + if (cls.isFinal || cls.isPrivate || cls.isInterface) + continue; + + if (cls.meta.has(':coreApi')) + continue; + + switch (cls.kind) + { + case KAbstractImpl(_): + continue; + case KGenericBuild | KGeneric: + continue; + case KGenericInstance(_, _): + continue; + default: + } + + var fields:Array = (macro class + { + private var __scriptClass__:Null = null; + private var __skipScriptClass__:Bool = false; + }).fields; + + var classFields:Array = []; + var classParams:Array<{name: String, type: ComplexType}> = []; + var clsToCheck:Null = cls; + while (clsToCheck != null) + { + for (i => p in clsToCheck.superClass?.params ?? []) + { + var name = clsToCheck.superClass.t.get().params[i].name; + if (classParams.filter((cp) -> cp.name == name).length == 0) + classParams.push({ + name: name, + type: p.toComplexType() + }); + } + + for (f in clsToCheck.fields.get()) + { + if (classFields.filter((cf) -> cf.name == f.name).length > 0) + continue; + classFields.push(f); + } + clsToCheck = clsToCheck.superClass?.t.get(); + } + + for (f in classFields) + { + switch (f.kind) + { + case FMethod(k): + switch (k) + { + case MethNormal: + default: + continue; + } + default: + continue; + } + + if (f.isFinal) + continue; + + // skip generic function implementations + if (f.meta.has(':generic') && f.params.length == 0) + continue; + + var fun:{ + args:Array<{t:Type, opt: Bool, name:String}>, + ret:Type + } = switch (f.type) + { + case TFun(args, ret): + {args: args, ret: ret}; + default: + continue; + } + + var funDecl:Null = switch (f.expr()?.expr) + { + case TFunction(tfunc): + tfunc; + default: + null; + } + + var params:Array = [ + for (p in f.params) + { + { + name: p.name, + constraints: switch (p.t.getClass().kind) + { + case KTypeParameter(cs): + [for (c in cs) c.toComplexType()]; + default: + throw 'This should never happen'; + } + }; + } + ]; + + var args:Array = [ + for (i => a in fun.args) + { + var t = a.t?.toComplexType(); + + if (a.t != null) + { + switch(a.t) + { + case TType(dt, params): + var type = dt.get(); + var tc = type.type.toComplexType(); + switch (tc) + { + case TPath(p): + if (p.params != null) + { + for (i => tp in params) + { + for (j in 0...p.params.length) + { + switch (p.params[j]) + { + case TPType(t): + if (t.toString() == type.params[i].name) + p.params[j] = TPType(tp.toComplexType()); + default: + } + } + } + } + tc = TPath(p); + default: + } + t = tc; + default: + } + } + + t = deparameterizeType(t, classParams); + + try + { + t?.toType(); + } + catch (_) // overriding functions using private types is not possible + { + if (t.toString() != 'StdTypes.Void') + { + switch (t) + { + case TPath(p): + if (params.filter((tp) -> tp.name == p.name).length == 0) + { + try + { + var name = p.name; + if (p.pack.length > 0) + name = p.pack.join('.') + '.' + name; + if (p.sub != null) + name = name + '.' + p.sub; + Context.getType(name); + } + catch (_) // this happens for private types + { + break; + } + } + default: + } + } + } + // get default value expr + var value = funDecl?.args[i].value != null ? Context.getTypedExpr(funDecl.args[i].value) : null; + + // wrap the expr in a cast statement + // necessary for abstracts to work properly + if (value != null) + value = (macro cast($e{value})); + + { + name: a.name, + type: t, + value: value + }; + } + ]; + + if (args.length != fun.args.length) + continue; + + var scriptArgs:Array = [ + for (a in fun.args) + macro $i{a.name} + ]; + + var superArgs:String = [ + for (a in fun.args) + a.name + ].join(', '); + + var ret = fun.ret.toComplexType(); + + if (ret != null) + ret = deparameterizeType(ret, classParams); + + // we have to decuce the return type + // when the return type is of an import + // that is using `as` + var deduceRet:Bool = try + { + ret.toType(); + false; + } + catch (_) + { + true; + } + + var body = if (ret?.toString() == 'StdTypes.Void') + { + macro + { + if (!__skipScriptClass__ && __scriptClass__?.findFunction($v{f.name}) != null) + { + __skipScriptClass__ = true; + __scriptClass__.callFunction($v{f.name}, $a{scriptArgs}); + return; + } + else + { + __skipScriptClass__ = false; + ${Context.parse('super.${f.name}(${superArgs})', Context.currentPos())}; + return; + } + }; + } + else + { + macro + { + if (!__skipScriptClass__ && __scriptClass__?.findFunction($v{f.name}) != null) + { + __skipScriptClass__ = true; + return __scriptClass__.callFunction($v{f.name}, $a{scriptArgs}); + } + else + { + __skipScriptClass__ = false; + return ${Context.parse('super.${f.name}(${superArgs})', Context.currentPos())}; + } + }; + } + + var access = [AOverride]; + access.push(f.isPublic ? APublic : APrivate); + + fields.push({ + name: f.name, + kind: FFun({ + args: args, + ret: deduceRet == false ? ret : null, + params: params, + expr: body + }), + access: access, + pos: Context.currentPos(), + }); + } + + var params:Array = [ + for (p in cls.params) + { + { + name: p.name, + constraints: switch (p.t.getClass().kind) + { + case KTypeParameter(cs): + [for (c in cs) c.toComplexType()]; + default: + throw 'This should never happen'; + } + }; + } + ]; + + var mod = cls.module.split('.'); + + var superClass:TypePath = { + pack: cls.pack, + name: mod[mod.length - 1], + sub: cls.name, + params: [ + for (p in params) + TPType(TPath({pack: [], name: p.name})) + ] + }; + + Context.defineModule(cls.module, [{ + pack: cls.pack, + name: cls.name + PolymodScriptClass.SCRIPT_IMPL_SUFFIX, + kind: TDClass(superClass, [], false, false, false), + fields: fields, + params: params, + meta: [ + {name: ':keep', pos: Context.currentPos()}, + {name: ':haxe.warning', params: [macro '-WDeprecated'], pos: Context.currentPos()}, + ], + pos: Context.currentPos() + }]); + + var clsPath = cls.name; + if (cls.pack.length > 0) + clsPath = '${cls.pack.join('.')}.${cls.name}'; + + var scriptOverride = [ + macro $v{clsPath}, + macro $v{clsPath + PolymodScriptClass.SCRIPT_IMPL_SUFFIX} + ]; + scriptOverrides.push(macro $a{scriptOverride}); + } + }); + } + + static function deparameterizeType(type:ComplexType, replaceParams:Array<{name:String, type:ComplexType}>):ComplexType + { + if (type == null) + return type; + + switch (type) + { + case TPath(p): + for (rp in replaceParams.filter((rp) -> rp.name == p.name)) + { + return rp.type; + } + + for (i => tp in p.params) + { + switch (tp) + { + case TPType(t): + p.params[i] = TPType(deparameterizeType(t, replaceParams)); + default: + } + } + + return TPath(p); + case TFunction(args, ret): + for (i => a in args) + { + args[i] = deparameterizeType(a, replaceParams); + } + + ret = deparameterizeType(ret, replaceParams); + + return TFunction(args, ret); + case TOptional(t): + return TOptional(deparameterizeType(t, replaceParams)); + case TParent(t): + return TParent(deparameterizeType(t, replaceParams)); + case TNamed(n, t): + return TNamed(n, deparameterizeType(t, replaceParams)); + default: + } + return type; + } + + static function isInFilter(cls:ClassType, filter:String):Bool + { + if (filter == '') + return true; + + var split = filter.split('.'); + + var filterType = 'package'; + + for (s in split) + if (s.charAt(0).toUpperCase() == s.charAt(0) && s.charAt(0) != '_') + filterType = filterType == 'package' ? 'module' : 'class'; + + var pattern = '^$filter(\\.|$)'; + var regex = new EReg(pattern, ''); + + switch (filterType) + { + case 'package': + return regex.match(cls.pack.join('.')); + case 'module': + return regex.match(cls.module); + case 'class': + return (cls.module + '.' + cls.name) == filter; + default: + throw 'how'; + } + } + #end + + public static function fetchAbstracts():Map> + { + var meta = Meta.getType(PolymodScriptMacro); + + if (meta.abstracts == null) + throw 'Did not find "abstracts" meta field in "PolymodScriptMacro"'; + + var abstracts:Map> = []; + for (elm in meta.abstracts) + { + if (elm.length != 2) + throw 'Malformed element in "abstracts" meta field: $elm'; + + var abstractPath = elm[0]; + var implementationPath = elm[1]; + + #if js + // Fucked up workaround, volatile and could break at any moment. + // Sanitize just in case someone tries to exploit this. + var invalidName = ~/[^.a-zA-Z0-9]/; + var sanitizedName = invalidName.replace(clsName, ''); + var parsedName = StringTools.replace(sanitizedName, '.', '_'); + var implementation:Class = cast js.Syntax.code('eval({0})', parsedName); + #else + var implementation:Class = cast Type.resolveClass(implementationPath); + #end + + if (implementation == null) + throw 'Could not resolve $abstractPath'; + + abstracts.set(abstractPath, implementation); + } + + return abstracts; + } + + public static function fetchScriptOverrides():Map> + { + var meta = Meta.getType(PolymodScriptMacro); + + if (meta.scriptOverrides == null) + throw 'Did not find "scriptOverrides" meta field in "PolymodScriptMacro"'; + + var scriptOverrides:Map> = []; + for (elm in meta.scriptOverrides) + { + if (elm.length != 2) + throw 'Malformed element in "scriptOverrides" meta field: $elm'; + + var clsPath = elm[0]; + var scriptPath = elm[1]; + + var scriptCls:Class = cast Type.resolveClass(scriptPath); + + if (scriptCls == null) + throw 'Could not resolve $scriptPath'; + + scriptOverrides.set(clsPath, scriptCls); + } + + return scriptOverrides; + } +} +#end \ No newline at end of file diff --git a/polymod/hscript/_internal/PolymodScriptManager.hx b/polymod/hscript/_internal/PolymodScriptManager.hx new file mode 100644 index 00000000..31c0371a --- /dev/null +++ b/polymod/hscript/_internal/PolymodScriptManager.hx @@ -0,0 +1,554 @@ +package polymod.hscript._internal; + +#if hscript +import hscript.Expr; +import polymod.hscript._internal.PolymodClassDeclEx; + +@:access(hscript.Interp) +class PolymodScriptManager +{ + private static var _instance:PolymodScriptManager = null; + public static var instance(get, never):PolymodScriptManager; + + static function get_instance():PolymodScriptManager + { + if (_instance == null) + _instance = new PolymodScriptManager(); + return _instance; + } + + public var importOverrides(default, null):Map>; + + public var defaultImports(default, null):Map>; + + public var abstracts(default, null):Map>; + + public var scriptOverrides(default, null):Map>; + + public var scriptClassDecls(default, null):Map; + + private var _staticAccess:PolymodInterpEx; + + private var _staticFunctionDecls:Map; + + public function new() + { + this.importOverrides = []; + this.defaultImports = []; + this.abstracts = PolymodScriptMacro.listAbstracts(); + this.scriptOverrides = PolymodScriptMacro.listScriptOverrides(); + this.scriptClassDecls = []; + this._staticAccess = new PolymodInterpEx(null); + this._staticFunctionDecls = []; + } + + /** + * Register a scripted class by parsing the text of that script. + */ + public function registerScriptClassByString(body:String, path:String = null):Void + { + addModule(body, path == null ? 'hscriptClass' : 'hscriptClass($path)'); + } + + /** + * Register a scripted class by retrieving the script from the given path. + */ + public function registerScriptClassByPath(path:String):Void + { + @:privateAccess { + var scriptBody = Polymod.assetLibrary.getText(path); + if (scriptBody == null) + { + Polymod.error(SCRIPT_PARSE_ERROR, 'Error while loading script "${path}", could not retrieve script contents!'); + return; + } + try + { + registerScriptClassByString(scriptBody, path); + } + catch (err:PolymodExprEx.ErrorEx) + { + var errLine:String = #if hscriptPos '${err.line}' #else "#???" #end; + #if hscriptPos + switch (err.e) + #else + switch (err) + #end + { + case EUnexpected(s): + Polymod.error(SCRIPT_PARSE_ERROR, + 'Error while parsing script ${path}#${errLine}: EUnexpected' + '\n' + + 'Unexpected error: Unexpected token "${s}", is there invalid syntax on this line?'); + default: + Polymod.error(SCRIPT_PARSE_ERROR, 'Error while executing script ${path}#${errLine}: ' + '\n' + 'An unknown error occurred: ${err}'); + } + } + catch (err:hscript.Expr.Error) + { + var errLine:String = #if hscriptPos '${err.line}' #else "#???" #end; + #if hscriptPos + switch (err.e) + #else + switch (err) + #end + { + case EUnexpected(s): + Polymod.error(SCRIPT_PARSE_ERROR, + 'Error while parsing script ${path}#${errLine}: EUnexpected' + '\n' + + 'Unexpected error: Unexpected token "${s}", is there invalid syntax on this line?'); + default: + Polymod.error(SCRIPT_PARSE_ERROR, 'Error while executing script ${path}#${errLine}: ' + '\n' + 'An unknown error occurred: ${err}'); + } + } + } + } + + #if lime + public function registerScriptClassByPathAsync(path:String):lime.app.Future + { + var promise = new lime.app.Promise(); + + if (!Polymod.assetLibrary.exists(path)) + { + Polymod.error(SCRIPT_PARSE_ERROR, 'Error while loading script "${path}", could not retrieve contents of non-existent script!'); + return null; + } + + Polymod.assetLibrary.loadText(path).onComplete((text) -> + { + try + { + Polymod.debug('Fetched script class "$path", parsing...'); + registerScriptClassByString(text); + promise.complete(true); + } + catch (err:PolymodExprEx.ErrorEx) + { + var errLine:String = #if hscriptPos '${err.line}' #else "#???" #end; + #if hscriptPos + switch (err.e) + #else + switch (err) + #end + { + case EUnexpected(s): + Polymod.error(SCRIPT_PARSE_ERROR, + 'Error while parsing script ${path}#${errLine}: EUnexpected' + '\n' + + 'Unexpected error: Unexpected token "${s}", is there invalid syntax on this line?'); + default: + Polymod.error(SCRIPT_PARSE_ERROR, + 'Error while parsing script ${path}#${errLine}: ' + '\n' + 'An unknown error occurred: ${err}'); + } + promise.error(err); + } + }).onError((err) -> + { + if (err == "404") + { + Polymod.error(SCRIPT_PARSE_ERROR, 'Error while loading script "${path}", could not retrieve script contents (404 error)!'); + } + else + { + Polymod.error(SCRIPT_PARSE_ERROR, 'Error while parsing script ${path}: ' + '\n' + 'An unknown error occurred: ${err}'); + promise.error(err); + } + }); + // Await the promise + return promise.future; + } + #end + + public function validateImports():Void + { + for (clsPath => cls in scriptClassDecls) + { + for (other in scriptClassDecls) + { + if (other == cls) + continue; + + var pkg = other.pkg ?? []; + + var importedClass:PolymodClassImport = { + name: other.name, + pkg: other.pkg, + fullPath: (pkg.length > 0) ? '${pkg.join('.')}.${other.name}' : other.name, + cls: PolymodScriptClass, + enm: null + }; + + if (pkg.length == 0) + { + if (!cls.imports.exists(other.name)) + cls.imports.set(other.name, importedClass); + } + else if (cls.pkg != null && cls.pkg.length >= pkg.length) + { + var inSamePkg:Bool = true; + for (i => subPkg in pkg) + if (subPkg != cls.pkg[i]) + inSamePkg = false; + if (inSamePkg) + cls.imports.set(other.name, importedClass); + } + } + + for (key => imp in cls.importsToValidate) + { + if (scriptClassDecls.exists(imp.fullPath)) + { + imp.cls = PolymodScriptClass; + cls.imports.set(key, imp); + continue; + } + + Polymod.error(SCRIPT_CLASS_MODULE_NOT_FOUND, 'Could not import ${imp.fullPath}', clsPath); + } + } + } + + public function listScriptClasses():Array + { + var result = []; + for (key => _ in scriptClassDecls) + result.push(key); + return result; + } + + public function registerScriptClass(c:PolymodClassDeclEx):Void + { + var path = c.name; + if (c.pkg != null) + path = '${c.pkg.join('.')}.${c.name}'; + + if (scriptClassDecls.exists(path)) + { + var message = + 'A scripted class with the fully qualified name "$path" has already been defined.' + + ' Please change the class name or the package name to ensure a unique name.'; + Polymod.error(SCRIPT_CLASS_ALREADY_REGISTERED, message); + } + else + { + for (f in c.staticFields) + { + var fieldPath = '${path}.${f.name}'; + + switch (f.kind) + { + case KVar(v): + if (v.expr != null) + _staticAccess.variables.set(fieldPath, _staticAccess.expr(v.expr)); + else + _staticAccess.variables.set(fieldPath, null); + case KFunction(f): + _staticFunctionDecls.set(fieldPath, f); + } + } + scriptClassDecls.set(path, c); + } + } + + public function clearScriptedClasses():Void + { + scriptClassDecls.clear(); + _staticFunctionDecls.clear(); + _staticAccess.resetVariables(); + } + + public function instantiateScriptedClass(path:String, ?args:Array):PolymodScriptClass + { + if (args == null) + args = []; + return new PolymodScriptClass(scriptClassDecls.get(path), args); + } + + public function staticFieldExists(path:String, field:String):Bool + { + var variableKey = '$path.$field'; + return _staticAccess.variables.exists(variableKey) || _staticFunctionDecls.exists(variableKey); + } + + public function staticFieldRead(path:String, field:String):Dynamic + { + var variableKey = '$path.$field'; + + if (_staticAccess.variables.exists(variableKey)) + { + return _staticAccess.variables.get(variableKey); + } + else if (_staticFunctionDecls.exists(variableKey)) + { + var fn = _staticFunctionDecls.get(variableKey); + var nargs = fn.args != null ? fn.args.length : 0; + switch (nargs) + { + case 0: return staticCallFunction0.bind(path, field); + case 1: return staticCallFunction1.bind(path, field, _); + case 2: return staticCallFunction2.bind(path, field, _, _); + case 3: return staticCallFunction3.bind(path, field, _, _, _); + case 4: return staticCallFunction4.bind(path, field, _, _, _, _); + #if neko + default: @:privateAccess _staticAccess.error(ECustom('only 4 params allowed in script class functions')); + #else + case 5: return staticCallFunction5.bind(path, field, _, _, _, _, _); + case 6: return staticCallFunction6.bind(path, field, _, _, _, _, _, _); + case 7: return staticCallFunction7.bind(path, field, _, _, _, _, _, _, _); + case 8: return staticCallFunction8.bind(path, field, _, _, _, _, _, _, _, _); + default: @:privateAccess _staticAccess.error(ECustom('only 8 params allowed in script class functions')); + #end + } + } + + Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'The scripted class "$path" does not have $field.'); + return null; + } + + public function staticFieldWrite(path:String, field:String, value:Dynamic):Dynamic + { + var variableKey = '$path.$field'; + if (!_staticAccess.variables.exists(variableKey)) + { + Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'The scripted class "$path" does not have $field.'); + return null; + } + _staticAccess.variables.set(variableKey, value); + return value; + } + + public function staticCallFunction(path:String, fnName:String, ?args:Array):Dynamic + { + var variableKey = '$path.$fnName'; + + var r = null; + var fn = _staticFunctionDecls.get(variableKey); + args = args ?? []; + + if (fn == null) + { + Polymod.error(SCRIPT_RUNTIME_EXCEPTION, 'The scripted class "$path" does not have $fnName.'); + return null; + } + + var prev:Map = []; + for (i => a in fn.args) + { + prev.set(a.name, _staticAccess.variables.get(a.name)); + + if (i < args.length) + _staticAccess.variables.set(a.name, args[i]); + else if (a.value != null) + _staticAccess.variables.set(a.name, _staticAccess.expr(a.value)); + } + + r = _staticAccess.executeEx(fn.expr); + + for (k => v in prev) + _staticAccess.variables.set(k, v); + + return r; + } + + private function addModule(moduleContents:String, ?origin:String = "hscript") + { + var parser = new PolymodParserEx(); + var decls = parser.parseModule(moduleContents, origin); + registerModules(decls, origin); + } + + private function registerModules(module:Array, ?origin:String = "hscript") + { + var pkg:Array = null; + var imports:Map = []; + var importsToValidate:Map = []; + + for (importPath in defaultImports.keys()) + { + var splitPath = importPath.split("."); + var clsName = splitPath[splitPath.length - 1]; + + imports.set(clsName, { + name: clsName, + pkg: splitPath.slice(0, splitPath.length - 1), + fullPath: importPath, + cls: defaultImports.get(importPath), + }); + } + + for (decl in module) + { + switch (decl) + { + case DPackage(path): + pkg = path; + case DImport(path, _): + var clsName = path[path.length - 1]; + + if (imports.exists(clsName)) + { + if (imports.get(clsName) == null) { + Polymod.error(SCRIPT_CLASS_MODULE_BLACKLISTED, 'Scripted class ${clsName} is blacklisted and cannot be used in scripts.', origin); + } else { + Polymod.warning(SCRIPT_CLASS_MODULE_ALREADY_IMPORTED, 'Scripted class ${clsName} has already been imported.', origin); + } + continue; + } + + var importedClass:PolymodClassImport = { + name: clsName, + pkg: path.slice(0, path.length - 1), + fullPath: path.join("."), + cls: null, + enm: null + }; + + if (importOverrides.exists(importedClass.fullPath)) + { + // importOverrides can exist but be null (if it was set to null). + // If so, that means the class is blacklisted. + + importedClass.cls = importOverrides.get(importedClass.fullPath); + } + else if (abstracts.exists(importedClass.fullPath)) + { + // We used a macro to map each abstract to its implementation. + importedClass.cls = abstracts.get(importedClass.fullPath); + trace('RESOLVED ABSTRACT CLASS ${importedClass.fullPath} -> ${Type.getClassName(importedClass.cls)}'); + trace(Type.getClassFields(importedClass.cls)); + } + else + { + var resultCls:Class = Type.resolveClass(importedClass.fullPath); + + // If the class is not found, try to find it as an enum. + var resultEnm:Enum = null; + if (resultCls == null) + resultEnm = Type.resolveEnum(importedClass.fullPath); + + if (resultCls == null && resultEnm == null) + { + importsToValidate.set(importedClass.name, importedClass); + continue; + } + else if (resultCls != null) + { + importedClass.cls = resultCls; + } + else if (resultEnm != null) + { + importedClass.enm = resultEnm; + } + } + + imports.set(importedClass.name, importedClass); + case DClass(c): + var extend = c.extend; + if (extend != null) + { + var superClassPath = new hscript.Printer().typeToString(extend); + if (!imports.exists(superClassPath) && !importsToValidate.exists(superClassPath)) + { + switch (extend) + { + case CTPath(path, params): + if (params != null && params.length > 0) + Polymod.error(SCRIPT_PARSE_ERROR, 'do not include type parameters in super class name: ${superClassPath}', origin); + default: + } + Polymod.error(SCRIPT_PARSE_ERROR, 'not recognized, is the type imported?: ${superClassPath}', origin); + } + + if (imports.exists(superClassPath) || importsToValidate.exists(superClassPath)) + { + var extendImport = imports.get(superClassPath); + if (extendImport != null && extendImport.cls == null) + Polymod.error(SCRIPT_PARSE_ERROR, 'expected a class: ${superClassPath}', origin); + + switch (extend) + { + case CTPath(_, params): + extend = CTPath((imports.get(superClassPath) ?? importsToValidate.get(superClassPath)).fullPath.split('.'), params); + case _: + } + } + } + + var instanceFields = []; + var staticFields = []; + for (f in c.fields) + { + if (f.access.contains(AStatic)) { + staticFields.push(f); + } else { + instanceFields.push(f); + } + } + + var classDecl:PolymodClassDeclEx = { + imports: imports, + importsToValidate: importsToValidate, + pkg: pkg, + name: c.name, + params: c.params, + meta: c.meta, + isPrivate: c.isPrivate, + extend: extend, + implement: c.implement, + fields: instanceFields, + isExtern: c.isExtern, + staticFields: staticFields, + }; + registerScriptClass(classDecl); + case DTypedef(_): + } + } + } + + private inline function staticCallFunction0(path:String, fnName:String):Dynamic + { + return staticCallFunction(path, fnName); + } + + private inline function staticCallFunction1(path:String, fnName:String, arg0:Dynamic):Dynamic + { + return staticCallFunction(path, fnName, [arg0]); + } + + private inline function staticCallFunction2(path:String, fnName:String, arg0:Dynamic, arg1:Dynamic):Dynamic + { + return staticCallFunction(path, fnName, [arg0, arg1]); + } + + private inline function staticCallFunction3(path:String, fnName:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic):Dynamic + { + return staticCallFunction(path, fnName, [arg0, arg1, arg2]); + } + + private inline function staticCallFunction4(path:String, fnName:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic):Dynamic + { + return staticCallFunction(path, fnName, [arg0, arg1, arg2, arg3]); + } + + #if !neko + private inline function staticCallFunction5(path:String, fnName:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic, arg4:Dynamic):Dynamic + { + return staticCallFunction(path, fnName, [arg0, arg1, arg2, arg3, arg4]); + } + + private inline function staticCallFunction6(path:String, fnName:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic, arg4:Dynamic, arg5:Dynamic):Dynamic + { + return staticCallFunction(path, fnName, [arg0, arg1, arg2, arg3, arg4, arg5]); + } + + private inline function staticCallFunction7(path:String, fnName:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic, arg4:Dynamic, arg5:Dynamic, arg6:Dynamic):Dynamic + { + return staticCallFunction(path, fnName, [arg0, arg1, arg2, arg3, arg4, arg5, arg6]); + } + + private inline function staticCallFunction8(path:String, fnName:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic, arg4:Dynamic, arg5:Dynamic, arg6:Dynamic, arg7:Dynamic):Dynamic + { + return staticCallFunction(path, fnName, [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7]); + } + #end +} +#end \ No newline at end of file