Can you help me understand macros some more?
I’ve just started writing them (/trying-to actually), but quickly got lost and couldn’t find a way to make them work the way I intended to.
So far I’m mainly working with build macros, so my idea was to create a bunch of functions that would serve as building blocks for more complex work.
Here’s some of them:
#if !macro macro #end
static public function createFieldVar(name:String, value):Field {
var field = {
name: name,
kind: FieldType.FVar(null, macro $v{value}),
access: [AStatic, APublic],
pos: Context.currentPos()
}
return field;
}
From what I can gather, this only works the first time you call it. In the sense that - being non generic - once the compiler infers the type of value
you cannot invoke it with a different type parameter.
Ex:
// inside build macro
Macros.createFieldVar("one", 1); // ok
Macros.createFieldVar("two", "two"); // error
Here I thought explicitly typing the function param as value:Expr
might work, but that’s not the case, and I don’t understand why.
In the previous example I think the code makes the compiler infer the type when doing FVar(null, value)
, but sometimes it could be useful to have the type passed in as a function parameter (or better get it examining the value
).
Is it possible to get a Type
/ComplexType
from a string representation of it?
Put it concisely:
var macroType = macro :Array<Int>;
var resolvedType = resolveFromString("Array<Int>");
same(macroType == resolvedType); // true
I have something like this:
#if !macro macro #end
static public function extractFieldsFromAnon(anon:ExprOf<{}>):ExprOf<Array<{name:String, value:Expr}>> {
var pairs = new Array<{name:String, value:Expr}>();
switch (anon.expr) {
case EObjectDecl(anonFields):
for (f in anonFields) {
switch (f.expr.expr) {
case EConst(_), EArrayDecl(_):
default:
throw "only EConst and EArrayDecl";
}
pairs.push({name:f.field, value:f.expr});
}
default:
throw "only EObjectDecl";
}
var res = {
expr: macro $v{pairs},
pos: Context.currentPos()
}
return macro $v{res};
}
This really confuses me (and makes me think my assumptions are plain wrong! so I’d be glad to have someone explaining to me where and why I’m failing).
PS: code snippets posted have been copy-pasted from different attempts. They should be representative (is that the correct word?), but if they don’t please report back and I’ll come up with better ones.
PPS: I know there’s some useful libs around (notably tink
and thx
) that make working with macros very easy. Thing is: I feel I don’t know the macros system well enough to just start fiddling with those libs.
So if anyone can clarify these points (to start, as I have many more), I’d really appreciate! :smile:
Thanks
Awesome!
back2dos, thanks for the detailed answer!
Now I have 2.
and 3.
working following your directions.
Still can’t make 1.
to work though, so I made the minimal example requested: http://try-haxe.mrcdk.com/#70306.
(I’ve also tried to type value
as Expr
, but that fails too, though it seemed the correct thing to do to me o.O)
Oh, silly me! ;p
I can just parameterize it.
This works:
#if !macro macro #end
static public function makeVarField<T>(name:String, value:T):Field {
var field = {
name: name,
kind: FieldType.FVar(null, macro $v{value}),
access: [AStatic, APublic],
pos: Context.currentPos()
}
return field;
}
(is that the correct way to do it?)
More macro troubles, hope you can help on this one too… (:
I’m trying to write a macro that searches a nested anon for a specified field name (returning null if it’s not there).
I’ve written a non-macro version of it, and then tried to transform that into a macro, unsuccessfully.
Non-macro (using Reflection):
static public function reflectFindAnonField(dotPath:String, object:{}):Null<{value:Dynamic}> {
var parts = dotPath.split(".");
var first = parts.shift();
var res = null;
if (!Reflect.isObject(object)) return null;
var fields = Reflect.fields(object);
for (fName in fields) {
var value = Reflect.field(object, fName);
if (fName == first) {
if (parts.length == 0) {
res = {value:value};
break;
} else {
if (Reflect.isObject(value)) {
return reflectFindAnonField(parts.join("."), value);
}
}
}
}
return res;
}
And this is my (incomplete/non-working) macro attempt:
#if !macro macro #end
static public function findAnonField(dotName:String, anon:Expr) {
var parts = dotName.split(".");
var first = parts.shift();
var res = null;
var t = Context.typeof(anon);
switch (t) {
case TAnonymous(_.get() => anonType):
for (f in anonType.fields) {
if (f.name == first) {
if (parts.length == 0) {
trace("found: " + f.name + " " + f);
res = f;
break;
} /*else if (f.type.match(TAnonymous(_))) {
trace(f.type);
return findAnonField(parts.join("."), macro $v{f});
}*/
}
}
default:
throw "only TAnonymous, was " + anon.expr;
}
return macro null; // macro $v{res}??
}
What am I misunderstanding there?
Should I use & switch on EObjectDecl
instead of TAnonymous
? How?
Side-question: is there a way to make the non-macro version type-safe (i.e. avoid Dynamic)?
Thanks again.
Spent quite some time fiddling with my previous code, and maybe I’ve found a solution for 4.
.
Here’s what I came up with:
#if !macro macro #end
static public function findAnonField(dotPath:String, anon:ExprOf<{}>):Null<{field:String, expr:Expr}> {
var parts = dotPath.split(".");
var first = parts.shift();
var res = null;
switch (anon.expr) {
case EObjectDecl(fields):
for (f in fields) {
if (f.field == first) {
if (parts.length == 0) {
res = f;
break;
} else {
switch (f.expr.expr) {
case EObjectDecl(innerAnon):
return findAnonField(parts.join("."), $v{f.expr});
default:
throw "unreachable";
}
}
}
}
default:
throw "only EObjectDecl, was " + anon.expr;
}
return $v{res};
}
Can you please comment on this?
Does it restrain the usage to build macros (or how can I change it to be used from expr macros)?
--
To post to this group haxe...@googlegroups.com
http://groups.google.com/group/haxelang?hl=en
---
You received this message because you are subscribed to the Google Groups "Haxe" group.
For more options, visit https://groups.google.com/d/optout.
Ok. Let me take a step back, and also explain what was my original intent…
@back2dos (and anyone wishing to contribute/help),
I was tempted to post my intentions at the beginning, but hoped to get there step-by-step (still possible, right?).
Anyway, my macro-related-learning-project can basicly be summed up with this:
Context.addResource()
..get("assetName")
)Now 5.2
and 5.3
are my main target: write a build macro (along with a suite of utility macros), that give users the power of writing something like this:
// Assets.hx
import UserMacro;
@:build(UserMacro.build("assetsAnon", ["assetsDir1"], ["png,jpg"]))
@:build(UserMacro.build("assetsAnon", ["assetsDir2"], ["txt"]))
class Assets { }
// UserMacro.hx
...
import az.Utils;
UserMacro {
static public function build(varName:String, dirs:Array<String>, extFilter:Array<String>):Array<Field> {
...
// create a `varName` field on local building class (if not exisiting)
// find files in specified dirs (matching the filter or any other custom criteria)
// add each file's content as a haxe resource
// update the anon (varName) to match the filesys tree structure and point to the added resource
...
}
...
}
Now, in my use case, the build macro is called twice, as I want multiple calls to do a merge on the same anon object (think of jQuery.extend).
To sum it up the goal is to end up with something like (might forget something but this is the gist of it):
UserMacro {
static public var assetsAnon: {
assetsDir1: {
head: ref to haxe resource,
button: ref to haxe resource,
frame: ref to haxe resource,
nestedDir: {
cursor: ref to haxe resource,
}
},
assetsDir2: {
lorem: ref to haxe resource,
introText: ref to haxe resource,
}
}
}
Hope you’re still with me at this point, and that what I’ve written makes some kind of sense. Does it? ;D
To make this work I’ll need some more missing pieces that have eluded me till now:
I’d guess 1
leads to 2
, but my current code has some problems. This:
//#if !macro macro #end
macro static public function transform(anon:ExprOf<{}>):Expr {
function _transform(e:Expr) {
switch(e.expr) {
case EConst(CString(s)):
e.expr = EConst(CString(s + "_xformed"));
case EObjectDecl(fields):
for (f in fields) {
f.field = f.field + "_xformed";
}
ExprTools.iter(e, _transform);
case _:
ExprTools.iter(e, _transform);
}
}
//trace(ExprTools.toString(anon));
_transform(anon);
return anon;
}
used like this
var nested1 = { three: { inner:"deep", first:"fssfsfsf" }};
var expr_xformed = Macro.transform({ three: { inner:"deep", first:"fssfsfsf" }}); // working as expr is inline
var ident_xformed = Macro.transform(nested1); // not working
gets what I want for expr_xformed
, but not for ident_formed
(so a side-question is how to extract fields from a CIdent
, but I’m starting to feel this rabbit hole is deeper than I thought :P).
Yeah, I know, quite a long post!
If you can help out I’d really appreciate it.
PS: if I haven't explained my intentions clearly enough please follow up, and I'll get back to you. Thanks in advance.
Cheers, azrafe7
PPS: Oh… and another great piece of advice from you would be how to properly use #if macro ...
vs/with macro
keyword. That would be really helpful!