const std = @import("std"); const NonTerminal = @import("../non-terminal.zig"); const Rule = @import("../rule.zig"); const Node = @import("node.zig"); const Self = @This(); id: usize, heads: std.ArrayListUnmanaged(*Node) = .empty, pub fn compile( non_terminal: *NonTerminal, node_pool: *std.heap.MemoryPool(Node), allocator: std.mem.Allocator ) !Self { var self: Self = .{ .id = non_terminal.id }; for (non_terminal.rules()) |*rule| { try self.heads.append(allocator, try compile_variant(rule, node_pool)); } return self; } pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { for (self.heads.items) |head| { head.deinit(); } self.heads.deinit(allocator); self.* = undefined; } pub fn clone( self: *const Self, allocator: std.mem.Allocator, node_pool: *std.heap.MemoryPool(Node), ) !Self { var self_clone = Self{ .id = self.id, .heads = try .initCapacity(allocator, self.heads.items.len), }; errdefer self_clone.deinit(allocator); for (self.heads.items) |head| { try self_clone.heads.append(allocator, try head.clone(node_pool)); } return self_clone; } fn compile_variant( rule: *Rule, node_pool: *std.heap.MemoryPool(Node), ) !*Node { const first = blk: { for (rule.items, 0..) |item, index| { switch (item) { .terminal, .non_terminal => break :blk index, .epsilon => {}, } } break :blk rule.items.len; }; if (first == rule.items.len) { return try Node.create(.{ .@"return" = void{} }, node_pool); } const head = switch (rule.items[0]) { .terminal => |c| try Node.create(.{ .@"check" = c }, node_pool), .non_terminal => |id| try Node.create(.{ .@"call" = id }, node_pool), .epsilon => unreachable, }; var current = head; for (rule.items[1..]) |item| { const node = switch (item) { .terminal => |c| try Node.create(.{ .@"check" = c }, node_pool), .non_terminal => |id| try Node.create(.{ .@"call" = id }, node_pool), .epsilon => continue, }; current.next = node; current = node; } current.next = try Node.create(.{ .@"return" = void{} }, node_pool); return head; }