Hello.
I think I will start this post with a clear code example of what I want to achieve.
I want to have this code as input:
typedef Job_opts = {
final command:String;
final cwd:Null<String>;
final arguments:Array<String>;
}
@:build(Macro.build()) // <--- here
extern class Job {
private static inline function create(args:Table<String, Dynamic>):Job {
return untyped __lua__("{0}:new({1})", Job, args);
}
static inline function make(args:Job_opts):Job {
return create(args);
}
}
// Used like this:
function main() {
var job = Job.make({
command: "chezmoi",
cwd: "/Users/danielo/",
arguments: ['-v']
});
Vim.print(job);
}
And have a build macro that produces this code as output:
var job = Job.create(
Table.create(null,{
command: "chezmoi",
cwd: "/Users/danielo/",
arguments: Table.create(['-v'])
})
);
This post helped me finding the named arguments of the typedef, and I figured out how to get the type of each one:
However, there is a limitation, and that is that Table.create
only accepts literal values, so somehow everything must be converted to literal values first, and there is where the problem lies.
My objective is to reduce runtime overhead as much as possible, so I don’t want to resort to methods like Table.fromObject
or stuff like that.
If possible, I would also like to avoid creating unnecessary calls to creating objects. That is something that Haxe does by default when you create any object.
I managed to get a macro that is capable of, at least, create a table from all the arguments the typedef has, but I am not able to get rid of the intermediary object creation because my macro gets a reference to the generated object, not to the literal value that is being passed to the function, so the best I can achieve is a call to Table.create
with all the fields of the passed object, and then all gets inlined, which is not that bad, but only does for shallow objects. I would like to do to any level of nest. I think I will ignore arrays, since they are probably not that much problematic.
For reference, here is the code my macro generates
local jobargs = _hx_o({__fields__={command=true,cwd=true,arguments=true},command="chezmoi",cwd="/Users/danielo/",arguments=_hx_tab_array({[0]="-v"}, 1)});
local args = ({arguments = jobargs.arguments, command = jobargs.command, cwd = jobargs.cwd});
local job = __plenary_Job:new(args);
and this is my macro
using haxe.macro.Tools;
// using haxe.macro.TypeTools;
import haxe.macro.ExprTools;
import haxe.macro.Expr;
import haxe.macro.Context;
// Thanks to
// https://stackoverflow.com/a/74711862/1734815
class TableBuilder {
macro public static function getFields(td:Expr):Array<Field> {
var t = Context.getType(td.toString()).follow();
var anon = switch (t) {
case TAnonymous(ref): ref.get();
case _: Context.error("Structure expected", td.pos);
}
for (tdf in anon.fields) {
trace('generate function: resolve_${tdf.name};');
}
return null;
}
public static macro function build():Array<Field> {
var fields = Context.getBuildFields();
for (field in fields) {
if (field.name != "make")
continue; // ignore other methods
var f = switch (field.kind) { // ... that's a function
case FFun(_f): _f;
default: continue;
}
// Locate the function call within the function body
// we will inject the table creation there
var val = switch (f.expr.expr) {
case EBlock([{expr: EReturn({expr: ECall(_, params)})}]): params;
default: continue;
}
var objFields:Array<ObjectField> = [];
for (arg in f.args) {
var argVal = arg.value;
switch (arg.type) {
case TPath({
pack: pack,
name: x,
params: params,
sub: sub
}):
var theType = (Context.getType(x).follow());
switch (theType) {
case TAnonymous(ref):
final fields = ref.get();
for (field in fields.fields) {
var name = field.name;
trace(field.name, field.type);
var plain_expr = macro($i{arg.name}).$name;
var expr = switch (field.type) {
case TInst(_.get().name => "Array", _):
plain_expr;
// macro(lua.Table.create($plain_expr));
case other: plain_expr;
};
objFields.push({
field: name,
expr: expr,
});
}
case other:
trace("other", other);
continue;
}
default:
continue;
}
}
var objExpr:Expr = {expr: EObjectDecl(objFields), pos: Context.currentPos()};
val[0].expr = (macro lua.Table.create(null, $objExpr)).expr;
// trace(val[0].toString());
}
return fields;
}
}