tiny.smg.tree.runtime.stack
Defined in tree.runtime.
API (15)
Actions
Public operations.
Branch.acceptedRootBranch.cloneBranch.deinitBranch.equivalentBranch.initBranch.precedenceBranch.reduceBranch.shiftBranch.stateContext.deinitContext.initContext.merge
Types and contracts
Public types and contracts.
Source
Source: tools/smg/src/tree/runtime/root.zig:8
zig
pub const stack = @import("stack.zig");Source: tools/smg/src/tree/runtime/stack.zig
zig
const std = @import("std");const runtime = @import("root.zig");const limits_mod = @import("../../limits/root.zig");const abi = runtime.abi;const language = runtime.language;const lexer = runtime.lexer;const scanner = runtime.scanner;const subtree = runtime.subtree;const testing_limits = @import("../../root.zig").default_limits.parser;const Link = struct { previous: *Node, tree: *const subtree.Subtree,};const Node = struct { state: abi.State, position: lexer.Position, links: []Link, link_count: usize = 0, precedence: i64, fn linkSlice(self: *const Node) []const Link { return self.links[0..self.link_count]; }};pub const Budget = struct { operations: *usize, limit: usize, fn charge(self: Budget) !void { if (self.operations.* >= self.limit) return error.OperationLimitExceeded; self.operations.* += 1; }};pub const Context = struct { arena: std.heap.ArenaAllocator, base: *Node, limits: limits_mod.Parser, pub fn init(allocator: std.mem.Allocator, limits: limits_mod.Parser) !Context { var self = Context{ .arena = std.heap.ArenaAllocator.init(allocator), .base = undefined, .limits = limits, }; errdefer self.arena.deinit(); self.base = try self.arena.allocator().create(Node); self.base.* = .{ .state = 1, .position = .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, .links = try self.arena.allocator().alloc(Link, limits.link_count), .precedence = 0, }; return self; } pub fn deinit(self: *Context) void { self.arena.deinit(); self.* = undefined; } fn push(self: *Context, previous: *Node, tree: *const subtree.Subtree, state: abi.State) !*Node { const allocator = self.arena.allocator(); const result = try allocator.create(Node); result.* = .{ .state = state, .position = tree.end, .links = try allocator.alloc(Link, self.limits.link_count), .link_count = 1, .precedence = previous.precedence + tree.dynamic_precedence, }; result.links[0] = .{ .previous = previous, .tree = tree }; return result; } pub fn merge(self: *Context, left: *Node, right: *Node) !bool { if (!canMerge(left, right)) return error.IncompatibleStackHeads; if (left == right) return false; var changed = false; for (right.linkSlice()) |link| changed = self.addLink(left, link) or changed; return changed; } fn addLink(self: *Context, node: *Node, candidate: Link) bool { if (candidate.previous == node) return false; for (node.links[0..node.link_count]) |*existing| { if (!linksEquivalent(existing.*, candidate)) continue; if (existing.previous == candidate.previous) { if (candidate.tree.dynamic_precedence <= existing.tree.dynamic_precedence) return false; existing.tree = candidate.tree; node.precedence = candidate.previous.precedence + candidate.tree.dynamic_precedence; return true; } if (canMerge(existing.previous, candidate.previous)) { var changed = false; for (candidate.previous.linkSlice()) |link| changed = self.addLink(existing.previous, link) or changed; const candidate_precedence = candidate.previous.precedence + candidate.tree.dynamic_precedence; if (candidate_precedence > node.precedence) { node.precedence = candidate_precedence; changed = true; } return changed; } } if (node.link_count == node.links.len) return false; node.links[node.link_count] = candidate; node.link_count += 1; node.precedence = @max(node.precedence, candidate.previous.precedence + candidate.tree.dynamic_precedence); return true; }};fn canMerge(left: *const Node, right: *const Node) bool { return left.state == right.state and left.position.byte == right.position.byte;}fn linksEquivalent(left: Link, right: Link) bool { if (left.tree == right.tree) return true; return left.tree.symbol == right.tree.symbol and left.tree.start.byte -| left.previous.position.byte == right.tree.start.byte -| right.previous.position.byte and left.tree.end.byte -| left.tree.start.byte == right.tree.end.byte -| right.tree.start.byte and left.tree.children.len == right.tree.children.len and left.tree.extra == right.tree.extra and scannerStatesEqual(left.tree.scanner_state, right.tree.scanner_state);}fn scannerStatesEqual(left: ?scanner.State, right: ?scanner.State) bool { if (left) |left_state| { if (right) |right_state| return left_state.eql(right_state); return left_state.isEmpty(); } return right == null or right.?.isEmpty();}pub const Branch = struct { head: *Node, position: lexer.Position, scanner_state: scanner.State, pub fn init(context: *Context, kind: scanner.Kind) Branch { return .{ .head = context.base, .position = .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, .scanner_state = scanner.State.init(kind), }; } pub fn deinit(self: *Branch, _: std.mem.Allocator) void { self.* = undefined; } pub fn clone(self: Branch, _: std.mem.Allocator) !Branch { return self; } pub fn state(self: Branch) abi.State { return self.head.state; } pub fn shift( self: *Branch, context: *Context, tree: *const subtree.Subtree, state_after: abi.State, position: lexer.Position, scanner_after: scanner.State, ) !void { self.head = try context.push(self.head, tree, state_after); self.position = position; self.scanner_state = scanner_after; } pub fn reduce( self: Branch, allocator: std.mem.Allocator, context: *Context, tree: *subtree.Tree, lang: language.Language, action: abi.Reduce, output: *std.ArrayList(Branch), budget: Budget, ) !void { var candidates: std.ArrayList(Reduction) = .empty; defer deinitReductions(allocator, &candidates); var visit = ReductionVisit{ .allocator = allocator, .tree = tree, .action = action, .position = self.position, .candidates = &candidates, }; try walkPaths( allocator, self.head, action.child_count, context.limits.iterator_count, budget, &visit, visitReduction, ); for (candidates.items) |candidate| { const next_state = lang.nextState(candidate.predecessor.state, action.symbol); if (next_state == 0) continue; var branch = Branch{ .head = candidate.predecessor, .position = self.position, .scanner_state = self.scanner_state, }; branch.head = try context.push(branch.head, candidate.parent, next_state); for (candidate.extras.items) |extra| branch.head = try context.push(branch.head, extra, next_state); try output.append(allocator, branch); } } pub fn acceptedRoot( self: Branch, allocator: std.mem.Allocator, context: *Context, tree: *subtree.Tree, budget: Budget, ) !?*const subtree.Subtree { var visit = RootVisit{ .allocator = allocator, .tree = tree }; try walkPaths( allocator, self.head, null, context.limits.iterator_count, budget, &visit, visitRoot, ); return visit.selected; } pub fn precedence(self: Branch) i64 { return self.head.precedence; } pub fn equivalent(left: Branch, right: Branch) bool { return left.state() == right.state() and std.meta.eql(left.position, right.position) and left.scanner_state.eql(right.scanner_state); }};const PathIterator = struct { node: *Node, structural_count: usize, path: std.ArrayList(*const subtree.Subtree) = .empty,};fn walkPaths( allocator: std.mem.Allocator, head: *Node, goal: ?usize, iterator_count: usize, budget: Budget, context: anytype, comptime visit: anytype,) !void { var iterators: std.ArrayList(PathIterator) = .empty; defer { for (iterators.items) |*iterator| iterator.path.deinit(allocator); iterators.deinit(allocator); } try iterators.append(allocator, .{ .node = head, .structural_count = 0 }); while (iterators.items.len > 0) { var index: usize = 0; var round_count = iterators.items.len; while (index < round_count) { try budget.charge(); const node = iterators.items[index].node; const complete = if (goal) |count| iterators.items[index].structural_count == count else node.link_count == 0; if (complete or node.link_count == 0) { if (complete) try visit(context, node, iterators.items[index].path.items); var finished = iterators.orderedRemove(index); finished.path.deinit(allocator); round_count -= 1; continue; } const alternative_count = @min( @as(usize, node.link_count - 1), iterator_count -| iterators.items.len, ); try iterators.ensureUnusedCapacity(allocator, alternative_count); for (node.links[1 .. alternative_count + 1]) |link| { var alternative = PathIterator{ .node = link.previous, .structural_count = iterators.items[index].structural_count + @intFromBool(!link.tree.extra), }; errdefer alternative.path.deinit(allocator); try alternative.path.appendSlice(allocator, iterators.items[index].path.items); try alternative.path.append(allocator, link.tree); iterators.appendAssumeCapacity(alternative); } const first = node.links[0]; iterators.items[index].node = first.previous; iterators.items[index].structural_count += @intFromBool(!first.tree.extra); try iterators.items[index].path.append(allocator, first.tree); index += 1; } }}const ReductionVisit = struct { allocator: std.mem.Allocator, tree: *subtree.Tree, action: abi.Reduce, position: lexer.Position, candidates: *std.ArrayList(Reduction),};fn visitReduction(visit: *ReductionVisit, predecessor: *Node, reverse_path: []const *const subtree.Subtree) !void { try addReduction(visit.allocator, visit.tree, visit.action, visit.position, predecessor, reverse_path, visit.candidates);}const RootVisit = struct { allocator: std.mem.Allocator, tree: *subtree.Tree, selected: ?*const subtree.Subtree = null,};fn visitRoot(visit: *RootVisit, _: *Node, reverse_path: []const *const subtree.Subtree) !void { const candidate = try rootForPath(visit.allocator, visit.tree, reverse_path) orelse return; if (visit.selected == null or try preferred(visit.allocator, visit.selected.?, candidate) == candidate) visit.selected = candidate;}const Reduction = struct { predecessor: *Node, parent: *const subtree.Subtree, extras: std.ArrayList(*const subtree.Subtree),};fn addReduction( allocator: std.mem.Allocator, tree: *subtree.Tree, action: abi.Reduce, position: lexer.Position, predecessor: *Node, reverse_path: []const *const subtree.Subtree, candidates: *std.ArrayList(Reduction),) !void { var children: std.ArrayList(*const subtree.Subtree) = .empty; defer children.deinit(allocator); try children.ensureTotalCapacity(allocator, reverse_path.len); var index = reverse_path.len; while (index > 0) { index -= 1; children.appendAssumeCapacity(reverse_path[index]); } var children_end = children.items.len; while (children_end > 0 and children.items[children_end - 1].extra) children_end -= 1; const parent = try tree.node( action.symbol, children.items[0..children_end], action.production_id, predecessor.state, action.dynamic_precedence, position, false, ); var extras: std.ArrayList(*const subtree.Subtree) = .empty; errdefer extras.deinit(allocator); try extras.appendSlice(allocator, children.items[children_end..]); for (candidates.items) |*candidate| { if (candidate.predecessor != predecessor) continue; const winner = try preferred(allocator, candidate.parent, parent); if (winner == parent) { candidate.parent = parent; candidate.extras.deinit(allocator); candidate.extras = extras; } else { extras.deinit(allocator); } return; } try candidates.append(allocator, .{ .predecessor = predecessor, .parent = parent, .extras = extras, });}fn preferred(allocator: std.mem.Allocator, left: *const subtree.Subtree, right: *const subtree.Subtree) !*const subtree.Subtree { if (left.dynamic_precedence > right.dynamic_precedence) return left; if (right.dynamic_precedence > left.dynamic_precedence) return right; return if (try subtree.order(allocator, left, right) == .gt) right else left;}fn rootForPath( allocator: std.mem.Allocator, tree: *subtree.Tree, reverse_path: []const *const subtree.Subtree,) !?*const subtree.Subtree { var entries: std.ArrayList(*const subtree.Subtree) = .empty; defer entries.deinit(allocator); try entries.ensureTotalCapacity(allocator, reverse_path.len); var reverse_index = reverse_path.len; while (reverse_index > 0) { reverse_index -= 1; entries.appendAssumeCapacity(reverse_path[reverse_index]); } var root_index = entries.items.len; while (root_index > 0) { root_index -= 1; if (!entries.items[root_index].extra) break; } else return null; const base = entries.items[root_index]; if (root_index == 0 and entries.items.len == 1) return base; var children: std.ArrayList(*const subtree.Subtree) = .empty; defer children.deinit(allocator); try children.ensureTotalCapacity(allocator, entries.items.len - 1 + base.children.len); for (entries.items[0..root_index]) |entry| children.appendAssumeCapacity(entry); for (base.children) |child| children.appendAssumeCapacity(child); for (entries.items[root_index + 1 ..]) |entry| children.appendAssumeCapacity(entry); var child_precedence: i32 = 0; for (base.children) |child| child_precedence += child.dynamic_precedence; return try tree.node( base.symbol, children.items, base.production_id, base.parse_state, base.dynamic_precedence - child_precedence, base.start, base.extra, );}fn deinitReductions(allocator: std.mem.Allocator, reductions: *std.ArrayList(Reduction)) void { for (reductions.items) |*reduction| reduction.extras.deinit(allocator); reductions.deinit(allocator);}test "graph stack merges parser heads without merging scanner states" { var context = try Context.init(std.testing.allocator, testing_limits); defer context.deinit(); const left = Branch.init(&context, .cpp); var right = Branch.init(&context, .cpp); try std.testing.expect(left.equivalent(right)); right.scanner_state.cpp.delimiter_length = 1; try std.testing.expect(!left.equivalent(right));}test "focused graph links compare serialized external scanner state" { var context = try Context.init(std.testing.allocator, testing_limits); defer context.deinit(); var left_tree = subtree.Subtree{ .symbol = 1, .children = &.{}, .start = .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, .end = .{ .byte = 1, .point = .{ .row = 0, .column = 1 } }, .production_id = 0, .dynamic_precedence = 0, .parse_state = 1, .visible_descendant_count = 0, .named_descendant_count = 0, .visible = true, .named = true, .extra = false, .scanner_state = null, }; var right_tree = left_tree; var empty_state = scanner.State.init(.cpp); left_tree.scanner_state = empty_state; try std.testing.expect(linksEquivalent( .{ .previous = context.base, .tree = &left_tree }, .{ .previous = context.base, .tree = &right_tree }, )); empty_state.cpp.delimiter_length = 1; right_tree.scanner_state = empty_state; try std.testing.expect(!linksEquivalent( .{ .previous = context.base, .tree = &left_tree }, .{ .previous = context.base, .tree = &right_tree }, ));}test "focused saturated graph stack ignores precedence from a dropped ninth link" { var context = try Context.init(std.testing.allocator, testing_limits); defer context.deinit(); var trees: [9]subtree.Subtree = undefined; for (&trees, 0..) |*tree, index| tree.* = .{ .symbol = @intCast(index + 1), .children = &.{}, .start = .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, .end = .{ .byte = 1, .point = .{ .row = 0, .column = 1 } }, .production_id = 0, .dynamic_precedence = if (index == 8) 100 else 0, .parse_state = 1, .visible_descendant_count = 0, .named_descendant_count = 0, .visible = true, .named = true, .extra = false, .scanner_state = null, }; var left_links: [testing_limits.link_count]Link = undefined; var left = Node{ .state = 2, .position = trees[0].end, .links = &left_links, .link_count = testing_limits.link_count, .precedence = 0, }; for (left.links[0..testing_limits.link_count], 0..) |*link, index| link.* = .{ .previous = context.base, .tree = &trees[index] }; var right_links: [testing_limits.link_count]Link = undefined; var right = Node{ .state = 2, .position = trees[8].end, .links = &right_links, .link_count = 1, .precedence = 100, }; right.links[0] = .{ .previous = context.base, .tree = &trees[8] }; try std.testing.expect(!try context.merge(&left, &right)); try std.testing.expectEqual(testing_limits.link_count, left.link_count); try std.testing.expectEqual(@as(i64, 0), left.precedence);}const WalkWitness = struct { count: usize = 0, expected_bottom: *const subtree.Subtree,};fn visitWalkWitness(witness: *WalkWitness, _: *Node, path: []const *const subtree.Subtree) !void { try std.testing.expectEqual(@as(usize, 3), path.len); try std.testing.expectEqual(witness.expected_bottom, path[2]); witness.count += 1;}test "focused saturated pop-count and pop-all retain the pinned 64-path frontier" { var context = try Context.init(std.testing.allocator, testing_limits); defer context.deinit(); var trees: [24]subtree.Subtree = undefined; for (&trees, 0..) |*tree, index| tree.* = .{ .symbol = @intCast(index + 1), .children = &.{}, .start = .{ .byte = @intCast(index), .point = .{ .row = 0, .column = @intCast(index) } }, .end = .{ .byte = @intCast(index + 1), .point = .{ .row = 0, .column = @intCast(index + 1) } }, .production_id = 0, .dynamic_precedence = 0, .parse_state = 1, .visible_descendant_count = 0, .named_descendant_count = 0, .visible = true, .named = true, .extra = false, .scanner_state = null, }; var nodes: [3]Node = undefined; var node_links: [3][testing_limits.link_count]Link = undefined; for (&nodes, 0..) |*node, level| { node.* = .{ .state = @intCast(level + 2), .position = trees[level * testing_limits.link_count].end, .links = &node_links[level], .link_count = testing_limits.link_count, .precedence = 0, }; const previous = if (level == 0) context.base else &nodes[level - 1]; for (node.links[0..testing_limits.link_count], 0..) |*link, index| { link.* = .{ .previous = previous, .tree = &trees[level * testing_limits.link_count + index] }; } } for ([_]?usize{ 3, null }) |goal| { var operations: usize = 0; var witness = WalkWitness{ .expected_bottom = &trees[0] }; try walkPaths( std.testing.allocator, &nodes[2], goal, testing_limits.iterator_count, .{ .operations = &operations, .limit = 137 }, &witness, visitWalkWitness, ); try std.testing.expectEqual(@as(usize, 64), witness.count); try std.testing.expectEqual(@as(usize, 137), operations); } var operations: usize = 0; var witness = WalkWitness{ .expected_bottom = &trees[0] }; try std.testing.expectError( error.OperationLimitExceeded, walkPaths( std.testing.allocator, &nodes[2], 3, testing_limits.iterator_count, .{ .operations = &operations, .limit = 136 }, &witness, visitWalkWitness, ), );}Complete caller list for tree.runtime.stack.Context.deinit
10 direct callers.
tiny.smg.tree.runtime.parser.parse[function] attools/smg/src/tree/runtime/parser.zig:50tools.smg.src.tree.runtime.parser.test_focused_active_version_compaction_ranks_precedence_before_stable_identities[function] — test; no exact target attools/smg/src/tree/runtime/parser.zig:472in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.parser.test_focused_an_allowed_boundary_merge_frees_the_next_temporary_version[function] — test; no exact target attools/smg/src/tree/runtime/parser.zig:623in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.parser.test_focused_equal-precedence_active_version_compaction_retains_the_first_six_identities[function] — test; no exact target attools/smg/src/tree/runtime/parser.zig:428in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.parser.test_focused_temporary_overflow_cannot_merge_a_dropped_late_candidate[function] — test; no exact target attools/smg/src/tree/runtime/parser.zig:565in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.parser.test_focused_temporary_version_overflow_is_global_and_retains_FIFO_identities[function] — test; no exact target attools/smg/src/tree/runtime/parser.zig:516in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.stack.test_focused_graph_links_compare_serialized_external_scanner_state[function] — test; no exact target attools/smg/src/tree/runtime/stack.zig:452in nearest public ownertiny.smg.tree.runtime.stacktools.smg.src.tree.runtime.stack.test_focused_saturated_graph_stack_ignores_precedence_from_a_dropped_ninth_link[function] — test; no exact target attools/smg/src/tree/runtime/stack.zig:485in nearest public ownertiny.smg.tree.runtime.stacktools.smg.src.tree.runtime.stack.test_focused_saturated_pop-count_and_pop-all_retain_the_pinned_64-path_frontier[function] — test; no exact target attools/smg/src/tree/runtime/stack.zig:538in nearest public ownertiny.smg.tree.runtime.stacktools.smg.src.tree.runtime.stack.test_graph_stack_merges_parser_heads_without_merging_scanner_states[function] — test; no exact target attools/smg/src/tree/runtime/stack.zig:442in nearest public ownertiny.smg.tree.runtime.stack
Complete caller list for tree.runtime.stack.Context.init
10 direct callers.
tiny.smg.tree.runtime.parser.parse[function] attools/smg/src/tree/runtime/parser.zig:50tools.smg.src.tree.runtime.parser.test_focused_active_version_compaction_ranks_precedence_before_stable_identities[function] — test; no exact target attools/smg/src/tree/runtime/parser.zig:472in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.parser.test_focused_an_allowed_boundary_merge_frees_the_next_temporary_version[function] — test; no exact target attools/smg/src/tree/runtime/parser.zig:623in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.parser.test_focused_equal-precedence_active_version_compaction_retains_the_first_six_identities[function] — test; no exact target attools/smg/src/tree/runtime/parser.zig:428in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.parser.test_focused_temporary_overflow_cannot_merge_a_dropped_late_candidate[function] — test; no exact target attools/smg/src/tree/runtime/parser.zig:565in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.parser.test_focused_temporary_version_overflow_is_global_and_retains_FIFO_identities[function] — test; no exact target attools/smg/src/tree/runtime/parser.zig:516in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.stack.test_focused_graph_links_compare_serialized_external_scanner_state[function] — test; no exact target attools/smg/src/tree/runtime/stack.zig:452in nearest public ownertiny.smg.tree.runtime.stacktools.smg.src.tree.runtime.stack.test_focused_saturated_graph_stack_ignores_precedence_from_a_dropped_ninth_link[function] — test; no exact target attools/smg/src/tree/runtime/stack.zig:485in nearest public ownertiny.smg.tree.runtime.stacktools.smg.src.tree.runtime.stack.test_focused_saturated_pop-count_and_pop-all_retain_the_pinned_64-path_frontier[function] — test; no exact target attools/smg/src/tree/runtime/stack.zig:538in nearest public ownertiny.smg.tree.runtime.stacktools.smg.src.tree.runtime.stack.test_graph_stack_merges_parser_heads_without_merging_scanner_states[function] — test; no exact target attools/smg/src/tree/runtime/stack.zig:442in nearest public ownertiny.smg.tree.runtime.stack
Audit
| Definitions | 16 |
|---|---|
| Public names | 16 |
| Members | 8 |
| Version | 26.7.0 |
| Revision | daab053ee433 |