This is the full code of the schemaed graph data structure:
const std = @import("std");
const Type = std.builtin.Type;
var prng = std.rand.DefaultPrng.init(@as(u64, 468_357_246_135));
const rand = prng.random();
const Tag: type = [24]u8;
pub fn generate() Tag {
const chars = "abcdefghijkmlnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
var tag: Tag = undefined;
var i: usize = 1;
while (i < tag.len) : (i += 1) {
const j = rand.uintLessThan(usize, chars.len);
tag[i] = chars[j];
}
tag[0] = '#';
tag[8] = '-';
tag[16] = '-';
return tag;
}
pub fn Graph(comptime T: type) type {
if (@typeInfo(T) != .Union)
@compileError("Invalid Schema");
return struct {
nodes: std.ArrayList(*Node),
arena: std.heap.ArenaAllocator,
const Self = @This();
pub const Node: type = @Type(Type{ .Union = .{
.decls = &[_]Type.Declaration{},
.layout = .Auto,
.tag_type = @typeInfo(T).Union.tag_type,
.fields = blk: {
var union_fields: [std.meta.fields(T).len]Type.UnionField = undefined;
for (std.meta.fields(T), 0..) |union_field, i| {
const struct_fields = std.meta.fields(union_field.type);
const tag_field = [_]Type.StructField{.{ .type = Tag, .name = "tag", .alignment = 0, .is_comptime = false, .default_value = null }};
const edges_field = [_]Type.StructField{.{ .type = std.ArrayList(Edge), .name = "edges", .alignment = 0, .is_comptime = false, .default_value = null }};
const node_struct = @Type(Type{ .Struct = .{
.fields = tag_field ++ struct_fields ++ edges_field,
.layout = .Auto,
.decls = &[_]Type.Declaration{},
.is_tuple = false,
} });
union_fields[i] = Type.UnionField{
.alignment = union_field.alignment,
.name = union_field.name,
.type = node_struct,
};
}
break :blk &union_fields;
},
} });
pub const Edge: type = struct {
label: []const u8,
nodes: std.ArrayList(*Node),
};
pub fn init(alloc: std.mem.Allocator) !*Self {
var self = try alloc.create(Self);
self.arena = std.heap.ArenaAllocator.init(alloc);
self.nodes = std.ArrayList(*Node).init(self.arena.allocator());
return self;
}
pub fn deinit(self: *Self) void {
for (self.nodes.items) |value| {}
self.nodes.deinit();
self.arena.allocator().destroy(self);
}
pub fn addNode(self: *Self, comptime data: T) Tag {
const union_tag: [:0]const u8 = @tagName(data);
const field_type = std.meta.TagPayloadByName(Node, union_tag);
var node: field_type = undefined;
node.tag = generate();
node.edges = std.ArrayList(Edge).init(self.arena.allocator());
const data_field = @field(data, union_tag);
inline for (std.meta.fields(@TypeOf(data_field))) |field| {
@field(node, field.name) = @field(data_field, field.name);
}
self.nodes.append(@constCast(&@unionInit(Node, union_tag, node))) catch unreachable;
return node.tag;
}
};
}
test "Graph Nodes" {
const Test: type = union(enum) {
Foo: struct {
field1: usize,
field2: usize,
field3: usize,
field4: usize,
},
Bar: struct {
field1: usize,
field2: usize,
field3: usize,
field4: usize,
},
Baz: struct {
field1: usize,
field2: usize,
field3: usize,
field4: usize,
},
};
const node_fields: []const Type.UnionField = std.meta.fields(Graph(Test).Node);
const test_fields: []const Type.UnionField = std.meta.fields(Test);
try std.testing.expect(node_fields.len == test_fields.len);
try std.testing.expect(std.meta.fields(node_fields[0].type).len - 2 == std.meta.fields(test_fields[0].type).len);
try std.testing.expect(std.meta.fields(node_fields[1].type).len - 2 == std.meta.fields(test_fields[1].type).len);
try std.testing.expect(std.meta.fields(node_fields[2].type).len - 2 == std.meta.fields(test_fields[2].type).len);
}
test "addNode" {
const Test: type = union(enum) {
Foo: struct {
field1: usize,
field2: usize,
field3: usize,
field4: usize,
},
Bar: struct {
field1: usize,
field2: usize,
field3: usize,
field4: usize,
},
Baz: struct {
field1: usize,
field2: usize,
field3: usize,
field4: usize,
},
};
const graph = try Graph(Test).init(std.testing.allocator);
defer graph.deinit();
const ini_len = graph.nodes.items.len;
const tag = graph.addNode(.{ .Foo = .{
.field3 = 4,
.field1 = 7,
.field4 = 9,
.field2 = 17,
} });
const fin_len = graph.nodes.items.len;
try std.testing.expect(@TypeOf(tag) == Tag);
try std.testing.expect(fin_len > ini_len);
try std.testing.expect(fin_len - ini_len == 1);
}