Skip to content

Instantly share code, notes, and snippets.

@nadako
Last active March 14, 2024 07:57
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save nadako/9200026 to your computer and use it in GitHub Desktop.
Save nadako/9200026 to your computer and use it in GitHub Desktop.
Recursive read-only type generator for JSON structures (based on @:genericBuild)
abstract ArrayRead<T>(Array<T>) from Array<T> {
@:arrayAccess inline function get(i:Int):T return this[i];
public var length(get,never):Int;
inline function get_length() return this.length;
}
@:genericBuild(ConstMacro.build())
class Const<T> {}
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
using haxe.macro.Tools;
class ConstMacro {
static var anonStructId = 0;
static var typeDefCache = new Map<String,Type>();
static function build():Type {
return switch (Context.getLocalType()) {
case TInst(_.get() => {pack: [], name: "Const"}, [t]):
createReadType(t, Context.currentPos());
default:
throw false;
}
}
static function createReadType(t:Type, pos:Position, ?pack:Array<String>, ?name:String):Type {
switch (t) {
case TAbstract(_.get() => {pack: [], name: "Int" | "Float" | "Bool"}, []):
return t;
case TInst(_.get() => {pack: [], name: "String"}, []):
return t;
case TInst(_.get() => {pack: [], name: "Array"}, [elemType]):
var elemReadCT = createReadType(elemType, pos, pack, name).toComplexType();
var readCT = macro : ArrayRead<$elemReadCT>;
return readCT.toType();
case TType(_.get() => dt, []):
var key = dt.pack.join(".") + dt.module + "." + dt.name;
var readType = typeDefCache.get(key);
if (readType == null) {
readType = createReadType(dt.type, dt.pos, dt.pack, dt.name);
typeDefCache.set(key, readType);
}
return readType;
case TAnonymous(_.get() => (a = _)):
if (pack == null) {
pack = [];
name = "Struct" + (++anonStructId);
}
return createReadStruct(a.fields, pos, pack, name);
default:
throw new Error('Unsupported type ${t.toString()}', pos);
}
}
static function createReadStruct(fields:Array<ClassField>, pos:Position, pack:Array<String>, name:String):Type {
var readFields:Array<Field> = [];
for (f in fields) {
var readType = createReadType(f.type, f.pos, pack, name + "_" + f.name);
readFields.push({
name: f.name,
pos: f.pos,
meta: f.meta.get(),
kind: FProp("default", "never", readType.toComplexType())
});
}
var readName = "$" + name + "Read";
Context.defineType({
pack: pack,
name: readName,
pos: pos,
kind: TDStructure,
fields: readFields
});
return TPath({pack: pack, name: readName, params: []}).toType();
}
}
class Main {
static function main() {
var a:Const<Array<House>> = [];
a[0].tenants[1] = {name: "Dan"}; // No @:arrayAccess function accepts arguments of Int and { name : String }
a[0].tenants[1].name = "Dan"; // Cannot access field or identifier name for writing
}
}
typedef House = {
itemId:Int,
typeId:String,
tenants:Array<{name:String}>,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment