diff --git a/polymod/hscript/_internal/HLWrapperMacro.hx b/polymod/hscript/_internal/HLWrapperMacro.hx new file mode 100644 index 00000000..226fff66 --- /dev/null +++ b/polymod/hscript/_internal/HLWrapperMacro.hx @@ -0,0 +1,187 @@ +package polymod.hscript._internal; + +#if (!macro && hl) +@:build(polymod.hscript._internal.HLWrapperMacro.buildWrapperClass()) +class HLMath extends Math {} + +@:build(polymod.hscript._internal.HLWrapperMacro.buildWrapperClass()) +@:haxe.warning("-WDeprecated") +class HLStd extends Std +{ + public static function is(v:Dynamic, t:Dynamic) + return isOfType(v, t); + + public static function isOfType(v:Dynamic, t:Dynamic):Bool + { + // HL oddly uses hl.Bytes for strings sometimes + // We do a hack since we can't just do isOfType(v, hl.Bytes) + if (t == String && !Std.isOfType(v, String)) + { + function bytesTest(_:hl.Bytes) {} + try + { + bytesTest(v); + return true; + } + catch (_) + { + return false; + } + } + + return Std.isOfType(v, t); + } +} +#else +import haxe.macro.MacroStringTools; +import haxe.macro.TypedExprTools; +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.Type; + +using Lambda; +using haxe.macro.TypeTools; +using haxe.macro.ComplexTypeTools; +using haxe.macro.ExprTools; +using StringTools; + +/** + * Macro that generates wrapper fields for substitutes of `std` classes to make them avaliable to Reflection. + * Currently only works for static fields. + */ +class HLWrapperMacro +{ + public static macro function buildWrapperClass():Array + { + var localClass = Context.getLocalClass().get(); + var superClass = localClass.superClass; + if (superClass == null) throw 'Class ${localClass.name} does not extend class it wants to wrap'; + var cls = superClass.t.get(); + var buildFields = Context.getBuildFields(); + + for (field in cls.statics.get()) + { + if (field.isPublic && !buildFields.exists((f) -> f.name == field.name)) + { + var wrapper = generateWrapper(field, cls); + if (wrapper != null) buildFields.push(wrapper); + } + } + buildFields.push(generateToString(cls)); + + return buildFields; + } + + static function generateWrapper(field:ClassField, cls:ClassType):Null + { + if (field == null) throw 'Field is null'; + + var newField:Field = + { + name: field.name, + doc: field.doc, + meta: null, + pos: field.pos, + access: [APublic, AStatic, AInline], + kind: null + }; + + function populateNewField(t:Type):Bool + { + return switch (t) + { + case TLazy(lz): + var ty = lz(); + return populateNewField(ty); + case TFun(args, ret): + var funcArgs:Array = [ + for (arg in args) + { + name: arg.name, + opt: arg.opt, + type: Context.toComplexType(arg.t) + } + ]; + var ret = Context.toComplexType(ret); + var callArgs:Array = [for (arg in funcArgs) macro $i{arg.name}]; + var funcParams:Array = [for (p in field.params) {name: p.name, constraints: getConstraints(p.t)}]; + var body:Expr = macro $p{[cls.name, field.name]}($a{callArgs}); + + newField.kind = FFun( + { + args: funcArgs, + params: funcParams, + ret: ret, + expr: doesReturnVoid(ret) ? (macro $body) : (macro return $body) + }); + return true; + default: false; + } + } + + if (populateNewField(field.type)) + { + return newField; + } + else if (field.expr() == null) + { + var overKind = switch (field.kind) + { + case FVar(_, _): + // We're overriding a VARIABLE, it shouldn't be modifiable + FieldType.FProp('default', 'null', Context.toComplexType(field.type), macro $p{[cls.name, field.name]}); + default: throw "Not implemented!"; + } + + return { + name: field.name, + doc: field.doc, + meta: field.meta.get(), + pos: field.pos, + access: [APublic, AStatic], + kind: overKind + }; + } + + return null; + } + + static function generateToString(cls:ClassType):Field + { + return { + name: 'toString', + access: [APublic], + pos: cls.pos, + kind: FFun( + { + args: [], + ret: macro :String, + expr: macro return $v{cls.name} + }) + }; + } + + static function getConstraints(t:Type) + { + return switch (t) + { + case TInst(_.get() => c, _): + switch (c.kind) + { + case KTypeParameter(consts): [for (c in consts) Context.toComplexType(c)]; + default: throw 'Invalid class kind, it is not a TypeParameter'; + } + case _: throw '$t has not been implemented!'; + } + } + + static inline function doesReturnVoid(rt:ComplexType) + { + return switch (rt) + { + case TPath(tp): tp.name == "Void"; + default: false; + } + } +} +#end diff --git a/polymod/hscript/_internal/PolymodAbstractScriptClass.hx b/polymod/hscript/_internal/PolymodAbstractScriptClass.hx index c79de764..33b02f12 100644 --- a/polymod/hscript/_internal/PolymodAbstractScriptClass.hx +++ b/polymod/hscript/_internal/PolymodAbstractScriptClass.hx @@ -225,6 +225,7 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass @: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)) + "'"; } + return null; } private static function retrieveClassObjectFields(o:Dynamic):Array diff --git a/polymod/hscript/_internal/PolymodInterpEx.hx b/polymod/hscript/_internal/PolymodInterpEx.hx index 94e0f781..002493e0 100644 --- a/polymod/hscript/_internal/PolymodInterpEx.hx +++ b/polymod/hscript/_internal/PolymodInterpEx.hx @@ -236,8 +236,8 @@ class PolymodInterpEx extends Interp { super.resetVariables(); - variables.set("Math", Math); - variables.set("Std", Std); + variables.set("Math", #if hl polymod.hscript._internal.HLWrapperMacro.HLMath #else Math #end); + variables.set("Std", #if hl polymod.hscript._internal.HLWrapperMacro.HLStd #else Std #end); variables.set("Array", Array); variables.set("Bool", Bool); @@ -1109,7 +1109,12 @@ class PolymodInterpEx extends Interp { try { + #if hl + // HL is a bit weird with iterators with arguments + v = Reflect.callMethod(v, v.iterator, []); + #else v = v.iterator(); + #end } catch (e:Dynamic) {}; } @@ -1257,6 +1262,7 @@ class PolymodInterpEx extends Interp if (o == null) errorEx(ENullObjectReference(f)); var oCls:String = Util.getTypeNameOf(o); + #if hl oCls = oCls.replace('$', ''); #end // Check if the field is a blacklisted static field. if (PolymodScriptClass.blacklistedStaticFields.exists(o) && PolymodScriptClass.blacklistedStaticFields.get(o).contains(f)) @@ -1336,6 +1342,19 @@ class PolymodInterpEx extends Interp // #end // return result; } + #if (hl && haxe4) + else if (Std.isOfType(o, Enum)) + { + try + { + return (o : Enum).createByName(f); + } + catch (e) + { + errorEx(EInvalidAccess(f)); + } + } + #end var abstractKey:String = '$oCls.$f'; if (PolymodScriptClass.abstractClassStatics.exists(abstractKey)) @@ -1344,6 +1363,12 @@ class PolymodInterpEx extends Interp } // Default behavior + #if hl + // On HL, hasField on properties returns true but Reflect.field + // might return null so we have to check if a getter exists too. + // This happens mostly when the programmer mistakenly makes the field access (get, null) instead of (get, never) + return Reflect.getProperty(o, f); + #else if (Reflect.hasField(o, f)) { return Reflect.field(o, f); @@ -1359,6 +1384,7 @@ class PolymodInterpEx extends Interp return Reflect.field(o, f); } } + #end // return super.get(o, f); } @@ -1367,6 +1393,7 @@ class PolymodInterpEx extends Interp if (o == null) errorEx(ENullObjectReference(f)); var oCls:String = Util.getTypeNameOf(o); + #if hl oCls = oCls.replace('$', ''); #end // Check if the field is a blacklisted static field. if (PolymodScriptClass.blacklistedStaticFields.exists(o) && PolymodScriptClass.blacklistedStaticFields.get(o).contains(f))