Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions polymod/hscript/_internal/HLWrapperMacro.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
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<Field>
{
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<Field>
{
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<FunctionArg> = [
for (arg in args)
{
name: arg.name,
opt: arg.opt,
type: Context.toComplexType(arg.t)
}
];
var ret = Context.toComplexType(ret);
var callArgs:Array<Expr> = [for (arg in funcArgs) macro $i{arg.name}];
var funcParams:Array<TypeParamDecl> = [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
2 changes: 2 additions & 0 deletions polymod/hscript/_internal/PolymodAbstractScriptClass.hx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ 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<String>
Expand Down
41 changes: 36 additions & 5 deletions polymod/hscript/_internal/PolymodInterpEx.hx
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ class PolymodInterpEx extends Interp

override function resetVariables() {
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);
}

public function clearScriptClassDescriptors():Void {
Expand Down Expand Up @@ -1040,7 +1040,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)
{
Expand Down Expand Up @@ -1192,6 +1197,7 @@ class PolymodInterpEx extends Interp
errorEx(ENullObjectReference(f));

var oCls:String = Util.getTypeName(Type.typeof(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))
Expand Down Expand Up @@ -1270,13 +1276,36 @@ class PolymodInterpEx extends Interp
// #end
// return result;
}
#if (hl && haxe4)
else if (Std.isOfType(o, Enum))
{
try
{
return (o:Enum<Dynamic>).createByName(f);
}
catch (e)
{
errorEx(EInvalidAccess(f));
}
}
#end

var abstractKey:String = Type.getClassName(o) + '.' + f;
if (PolymodScriptClass.abstractClassStatics.exists(abstractKey)) {
return Reflect.getProperty(PolymodScriptClass.abstractClassStatics[abstractKey], abstractKey.replace('.', '_'));
try
{
var abstractKey:String = #if hl !Reflect.isObject(o) ? Type.getClassName(o) : o.__name__ #else Type.getClassName(o) #end + '.' + f;
if (PolymodScriptClass.abstractClassStatics.exists(abstractKey)) {
return Reflect.getProperty(PolymodScriptClass.abstractClassStatics[abstractKey], abstractKey.replace('.', '_'));
}
}
catch (e) {}

// 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);
} else {
Expand All @@ -1286,6 +1315,7 @@ class PolymodInterpEx extends Interp
return Reflect.field(o, f);
}
}
#end
// return super.get(o, f);
}

Expand All @@ -1295,6 +1325,7 @@ class PolymodInterpEx extends Interp
errorEx(ENullObjectReference(f));

var oCls:String = Util.getTypeName(Type.typeof(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))
Expand Down