5

I want to create a macro that generates this code for me:

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());

In code I'd like to declare/use it like this:

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

The current macro function looks like this:

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 };
}

This macro function is incorrect, I get the error:

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

The problem is I don't know how to define new $componentClass(). How would I solve this?

I also want to avoid to have Type.createInstance in the output code.

Mark Knol
  • 9,663
  • 3
  • 29
  • 44

3 Answers3

5

One way to programmatically generate instantiation code is by using "old school" enums AST building (compatible Haxe 3.0.1+):

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

An improved syntax using reification is possible:

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

Now, for a convenient "instantiation helper" function syntax, we need to do some contorsions to extract a type path from the expression we receive in the macro function:

// 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
Philippe
  • 2,621
  • 1
  • 19
  • 20
2

In case anyone is in need for answers, I got this Thanks to ousado on the Haxe IRC chat:

If you do it in macro alone you can do this:

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

..or, if you explicitly construct tp:

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

As you can see, the complex type path is explicitly given here.

Unfortunately, Haxe don't really have a concise syntax for types in expression positions. You can pass ( _ : TypeName ) to provide an expression that contains a ComplexType.

But if you want to pass a type as argument, you could do it like this:

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) );
    }
}

..if you want a list of types, you might want to go for a parameter list like ( _ : L< One,Two,Three> ).

Mark Knol
  • 9,663
  • 3
  • 29
  • 44
  • After getting this information I realized I don't want a macro (if syntax is weirder) but Ill just write it out by hand. – Mark Knol Aug 27 '15 at 14:16
  • Ha, so there is a reification way - I updated my response and even found a way to keep the syntax clean! – Philippe Aug 27 '15 at 23:06
  • I think the docs can be improved on this, I see the reification ways here http://haxe.org/manual/macro-reification-expression.html it only says `function $name() { }` from which you could guess the syntax exist. – Mark Knol Aug 28 '15 at 07:22
1

The accepted answer is problematic because it breaks when type parameters are involved, or when support for non-nominal types should be included.

I updated the example using two alternatives for a more concise notation for the list of types, while still allowing syntax for actual types.

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>>()  );
    }
}

Edit: The L in L< ... > could be any valid type name. Its only purpose is allowing to write a comma-separated list of types in valid syntax. Since macro functions take expressions as arguments, we have to use an expression that allows/requires a type, like: ( _ :T ), new T(), var v:T, function(_:T):T {}.

ousado
  • 11
  • 2
  • Thanks for this addition! What is `L` exactly in this case? – Mark Knol Aug 28 '15 at 08:57
  • L isn't used - you could use any conceivable valid name for a type. Alternatively, you could enforce the use of a specific name, perhaps something that's descriptive, e.g. `new FOR_ALL()`. – ousado Aug 28 '15 at 09:13