# Macro to generate nested Lua tables from typedef

**URL:** <https://community.haxe.org/t/macro-to-generate-nested-lua-tables-from-typedef/3795>\
**Category:** Haxe\
**Tags:** macro\
**Created:** [December 10, 2022, 9:21am UTC](https://community.haxe.org/t/macro-to-generate-nested-lua-tables-from-typedef/3795 "2022-12-10T09:21:50Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![danielo515](https://community.haxe.org/user_avatar/community.haxe.org/danielo515/32/2052_2.png) [@danielo515](https://community.haxe.org/u/danielo515)\
**Post date:** [December 10, 2022, 9:21am UTC](https://community.haxe.org/t/macro-to-generate-nested-lua-tables-from-typedef/3795/1 "2022-12-10T09:21:51Z")

</div>

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:

```haxe
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:

```haxe
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:

> [@Macro to generate function calls from TypeDef](https://community.haxe.org/t/macro-to-generate-function-calls-from-typedef/1125/9):
>
> The conversion is fair enough because you have to go from expression to type somehow. There are multiple avenues here, and going through Context.getType is fine. What isn’t fine is that EnumValuesTools usage. Don’t use that, use normal switches. You can simplify your code like so: import haxe.macro.Context; import haxe.macro.Expr; using haxe.macro.Tools; class Macros { macro public static function build(td:Expr):Array\<Field\> { var t = Context.getType(td.toString()).follow(); va…

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

```lua
  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

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

```
