Yes, Haxe macros are quite a bit more complex, although the AST itself is relatively straight forward. The explicitness actually takes a bit of problems away, e.g. an if
is an EIf
while in a lisp macro you may well encounter (if x y z wtf)
.
The additional complexity comes from the fact that there are many different kinds of macros (expression macros, build macros, genericBuild macros, init macros, static extension macros, implicit cast macros … insert Forrest Gump shrimps meme here). And the fact that you may interact with the compiler (in particular the typer), even defining hooks such as:
onTypeNotFound
: allows you to provide a type if none is found
onGenerate
: receives all types and allows you to do additional static analysis
onAfterGenerate
: runs after all code is generated, so you may post-process the output, e.g. pass it to a minifier or whatever
Yeah, that’s easily accomplished in many different ways, but with so little context I can’t tell you which way to go.
You can use a build macro to make it so that @meta'string'
patterns are all converted into such calls:
import Dummy.html;
@:build(TemplateLiterals.use())
class Usage {
static function main() {
var Comp1 = data -> @html'
<div>
<a href="${data.url}">${data.title}</a>
</div>
';
trace(Comp1({
url: 'http://example.com/?foo=2&bar=blargh',
title: "Let's rock!"
}));
}
}
Note that variadic functions are not really a thing in Haxe. You can use haxe.extern.Rest
to define the signatures. Implementing these yourself is a bit more awkward - normally you’ll be taking this from a JavaScript lib instead:
class Dummy {
static public final html:(literals:Array<String>, values:haxe.extern.Rest<Any>)->String
= Reflect.makeVarArgs(function (a:Array<Dynamic>) {
var literals:Array<String> = a.shift();
var parts:Array<Any> = cast a;
return [for (i in 0...literals.length)
literals[i] + switch parts[i] {
case null: '';
case v: StringTools.htmlEscape(Std.string(v));
}
].join('');
});
}
As for the build macro itself, it is reasonably small:
#if macro
import haxe.macro.*;
using haxe.macro.Tools;
#end
class TemplateLiterals {
#if macro
static function crawl(e:Expr)
return switch e.map(crawl) {
case { expr: EMeta(m, { pos: pos, expr: EConst(CString(s)) }) }:
var literals = [];
var args = [macro $a{literals}];
var parts = {
var cur:Expr = MacroStringTools.formatString(s, e.pos),
ret = [];
while (true)
switch cur {
case macro $lh + $rh:
ret.push(rh);
cur = lh;
default:
ret.push(cur);
break;
}
ret.reverse();
ret;
}
for (i in 0...parts.length)
(
if (i % 2 == 0) literals
else args
).push(parts[i]);
macro @:pos(m.pos) $i{m.name}($a{args});
case v: v;
}
static function use() {
var ret = Context.getBuildFields();
for (f in ret)
f.kind = switch f.kind {
case FVar(t, e):
FVar(t, crawl(e));
case FProp(get, set, t, e):
FProp(get, set, t, crawl(e));
case FFun(f):
FFun({
args: f.args,
ret: f.ret,
expr: crawl(f.expr)
});
}
return ret;
}
#end
}