如何在Haxe宏函数中声明实例化

How to declare instantiation in Haxe macro function

我想创建一个为我生成此代码的宏:

if (myEntity.get(Attack) == null) myEntity.add(new Attack());
if (myEntity.get(Confused) == null) myEntity.add(new Confused());
if (myEntity.get(Defend) == null) myEntity.add(new Defend());
if (myEntity.get(Offense) == null) myEntity.add(new Offense());

在代码中我想 declare/use 像这样:

EntityMacroUtils.addComponents(myEntity, Attack, Confused, Defend, Offense);

当前宏函数如下所示:

macro public static function addComponents(entity:ExprOf<Entity>, components:Array<ExprOf<Class<Component>>>):Expr
{
    var exprs:Array<Expr> = [];
    for (componentClass in components)
    {
        var instance = macro $e { new $componentClass() }; // problem is here
        var expr = macro if ($entity.get($componentClass) == null) $entity.add(instance);
        exprs.push(expr);
    }
    return macro $b{ exprs };
}

这个宏函数不正确,我得到错误:

EntityMacroUtils.hx:17: characters 22-43 : Type not found : $componentClass

问题是我不知道如何定义new $componentClass()。我该如何解决?

我还想避免在 输出 代码中包含 Type.createInstance

以编程方式生成实例化代码的一种方法是使用 "old school" 枚举 AST 构建(兼容 Haxe 3.0.1+):

// new pack.age.TheClass()
return {
    expr:ENew({name:"TheClass", pack:["pack", "age"], params:[]}, []),
    pos:Context.currentPos()
};

可以使用具体化改进语法:

// new pack.age.TheClass()
var typePath = { name:"TheClass", pack:["pack", "age"], params:[] };
return macro new $typePath();

现在,为了方便的 "instantiation helper" 函数语法,我们需要做一些扭曲以从我们在宏函数中收到的表达式中提取类型路径:

// new Foo(), new pack.Bar(), new pack.age.Baz()
instantiate(Foo, pack.Bar, pack.age.Baz);

macro static function instantiate(list:Array<Expr>)
{
    var news = [for (what in list) {
        var tp = makeTypePath(what);
        macro new $tp();
    }];
    return macro $b{news};
}

#if macro
static function makeTypePath(of:Expr, ?path:Array<String>):TypePath 
{
    switch (of.expr)
    {
        case EConst(CIdent(name)):
            if (path != null) {
                path.unshift(name);
                name = path.pop();
            }
            else path = [];
            return { name:name, pack:path, params:[] };

        case EField(e, field):
            if (path == null) path = [field];
            else path.unshift(field);
            return makeTypePath(e, path);

        default:
            throw "nope";
    }
}
#end

如果有人需要答案,我收到了感谢 Haxe IRC 聊天中的 ousado:

如果你单独在宏中做,你可以这样做:

var ct = macro : pack.age.SomeTypename;
var tp = switch ct { case TPath(tp):tp; case _: throw "nope"; }
var expr = macro new $tp();

..或者,如果您显式构造 tp

var tp = {sub:'SomeTypeName',params:[],pack:['pack','age'],name:"SomeModuleName"}

如您所见,这里明确给出了复杂类型路径。

不幸的是,Haxe 对于表达式位置中的类型并没有真正的简洁语法。您可以传递 ( _ : TypeName ) 以提供包含 ComplexType 的表达式。

但是如果你想传递一个类型作为参数,你可以这样做:

import haxe.macro.Expr;
using haxe.macro.Tools;

class Thing {
    public function new(){}
}
class OtherThing {
    public function new(){}
}

class TMacroNew {

    macro static function instances( arr:Array<Expr> ) {

        var news = [for (e in arr) {
            var ct = switch e.expr { case EParenthesis({expr:ECheckType(_,ct)}):ct; case _: throw "nope"; };
            var tp = switch ct { case TPath(tp):tp; case _: throw "nope"; };
            macro new $tp();
        }];
        trace( (macro $b{news}).toString());
        return macro $b{news};
    }


    static function main(){
        instances( (_:Thing), (_:Thing), (_:OtherThing) );
    }
}

..如果你想要一个类型列表,你可能想要一个像 ( _ : L< One,Two,Three> ).

这样的参数列表

接受的答案是有问题的,因为当涉及类型参数时,或者当应包括对非标称类型的支持时,它会中断。

我使用两种替代方法更新了示例,以便为类型列表提供更简洁的表示法,同时仍然允许实际类型的语法。

import haxe.macro.Expr;
using haxe.macro.Tools;

class Thing {
    public function new(){}
}
class OtherThing {
    public function new(){}
}

class TPThing<T>{
    public function new(){}
}

class TMacroNew {

    macro static function instances( e:Expr ) {
        var tps = switch e.expr { 
            case EParenthesis({expr:ECheckType(_,TPath({params:tps}))}):tps; 
            case ENew({params:tps},_):tps;
            case _: throw "not supported"; 
        }
        var type_paths = [ for (tp in tps) switch tp { 
            case TPType(TPath(tp)):tp; 
            case _: throw "not supported"; 
        }];
        var news = [for (tp in type_paths) macro new $tp()];
        trace( (macro $b{news}).toString());
        return macro $b{news};
    }


    static function main(){
        instances( (_:L<Thing,Thing,OtherThing,TPThing<Int>> ) );
        instances( new L<Thing,Thing,OtherThing,TPThing<Int>>()  );
    }
}

编辑: L< ... > 中的 L 可以是任何有效的类型名称。它的唯一目的是允许以有效语法编写逗号分隔的类型列表。由于宏函数将表达式作为参数,因此我们必须使用 allows/requires 类型的表达式,例如:( _ :T ), new T(), var v:T, function(_:T):T {}