Reputation: 10143
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.
Upvotes: 5
Views: 782
Reputation: 11
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 {}
.
Upvotes: 1
Reputation: 2631
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
Upvotes: 5
Reputation: 10143
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> )
.
Upvotes: 2