tiny.smg.tree.runtime.parser
Defined in tree.runtime.
API (3)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
Source: tools/smg/src/tree/runtime/parser.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 selection = runtime.selection;const stack = runtime.stack;const subtree = runtime.subtree;const testing_limits = @import("../../root.zig").default_limits.parser;pub const Grammar = struct { raw: *const abi.Language, scanner_kind: scanner.Kind,};pub const Options = struct { limits: limits_mod.Parser, operation_limit: ?usize = null, selection_trace: ?*selection.Trace = null,};const Lookahead = struct { tree: *const subtree.Subtree, end: lexer.Position, scanner_after: scanner.State, fallback_symbol: ?abi.Symbol,};const Candidate = struct { version: usize, branch: stack.Branch, lookahead: Lookahead,};const Version = struct { id: usize, branch: stack.Branch,};const AdvanceOutcome = union(enum) { replacement: Candidate, shifted: Version, done,};pub fn parse(allocator: std.mem.Allocator, grammar: Grammar, source: []const u8, options: Options) !?*subtree.Tree { const lang = language.Language.init(grammar.raw) catch return null; const tree = try subtree.Tree.init(allocator, lang); errdefer tree.deinit(); var stack_context = try stack.Context.init(allocator, options.limits); defer stack_context.deinit(); var active: std.ArrayList(Version) = .empty; defer deinitVersions(allocator, &active); try active.append(allocator, .{ .id = 0, .branch = stack.Branch.init(&stack_context, grammar.scanner_kind) }); var accepted: ?selection.Accepted = null; var operations: usize = 0; var next_version: usize = 1; const source_budget = std.math.mul(usize, source.len, 4096) catch std.math.maxInt(usize); const operation_limit = options.operation_limit orelse (std.math.add(usize, source_budget, 100_000) catch std.math.maxInt(usize)); var last_position: usize = 0; while (active.items.len > 0) { var shifted: std.ArrayList(Version) = .empty; errdefer deinitVersions(allocator, &shifted); var first_version = true; while (active.items.len > 0) { var version = active.orderedRemove(0); while (true) { const lookahead = lex(tree, grammar, source, version.branch) catch |err| { version.branch.deinit(allocator); return err; } orelse { version.branch.deinit(allocator); break; }; const advanced = try advance( allocator, &stack_context, tree, .{ .version = version.id, .branch = version.branch, .lookahead = lookahead }, &active, &shifted, &accepted, options.selection_trace, &operations, operation_limit, &next_version, ) orelse break; version = advanced; const position = version.branch.position.byte; if (position > last_position or (!first_version and position == last_position)) { last_position = position; shifted.append(allocator, version) catch |err| { version.branch.deinit(allocator); return err; }; break; } } first_version = false; } active.deinit(allocator); active = shifted; shifted = .empty; try compact(allocator, &stack_context, &active); } if (accepted) |result| { tree.root = result.root; return tree; } tree.deinit(); return null;}fn lex( tree: *subtree.Tree, grammar: Grammar, source: []const u8, branch: stack.Branch,) !?Lookahead { const lang = tree.language; const state = branch.state(); const mode = lang.mode(state); if (mode.external_lex_state != 0) { var external_lexer = lexer.Lexer.init(source, branch.position); external_lexer.start(); var scanner_after = branch.scanner_state; if (scanner.scan(grammar.scanner_kind, &scanner_after, &external_lexer, lang.externalValid(mode.external_lex_state))) { const token = external_lexer.finish(); if (token.symbol >= grammar.raw.external_token_count) return null; const symbol = grammar.raw.external_scanner.symbol_map.?[token.symbol]; return .{ .tree = try tree.leaf(symbol, token.start, token.end, state, false, scanner_after), .end = token.end, .scanner_after = scanner_after, .fallback_symbol = null, }; } } var internal_lexer = lexer.Lexer.init(source, branch.position); internal_lexer.start(); if (!grammar.raw.lex_fn.?(&internal_lexer.abi, mode.lex_state)) return null; const token = internal_lexer.finish(); var symbol = token.symbol; var fallback_symbol: ?abi.Symbol = null; if (symbol != 0 and symbol == grammar.raw.keyword_capture_token) { if (grammar.raw.keyword_lex_fn) |keyword_lex_fn| { var keyword_lexer = lexer.Lexer.init(source, token.start); keyword_lexer.start(); if (keyword_lex_fn(&keyword_lexer.abi, 0)) { const keyword = keyword_lexer.finish(); if (keyword.end.byte == token.end.byte and (lang.hasActions(state, keyword.symbol) or lang.isReserved(mode, keyword.symbol))) { fallback_symbol = symbol; symbol = keyword.symbol; } } } } return .{ .tree = try tree.leaf(symbol, token.start, token.end, state, false, null), .end = token.end, .scanner_after = branch.scanner_state, .fallback_symbol = fallback_symbol, };}fn advance( allocator: std.mem.Allocator, stack_context: *stack.Context, tree: *subtree.Tree, initial: Candidate, ready: *std.ArrayList(Version), shifted: *std.ArrayList(Version), accepted: *?selection.Accepted, selection_trace: ?*selection.Trace, operations: *usize, operation_limit: usize, next_version: *usize,) !?Version { var candidate = initial; while (true) { if (operations.* >= operation_limit) { var exhausted = candidate.branch; exhausted.deinit(allocator); return error.OperationLimitExceeded; } operations.* += 1; const outcome = try expandCandidate( allocator, stack_context, tree, candidate, ready, shifted, accepted, selection_trace, next_version, .{ .operations = operations, .limit = operation_limit }, ); switch (outcome) { .replacement => |replacement| candidate = replacement, .shifted => |version| return version, .done => return null, } }}fn expandCandidate( allocator: std.mem.Allocator, stack_context: *stack.Context, tree: *subtree.Tree, candidate: Candidate, ready: *std.ArrayList(Version), shifted: *std.ArrayList(Version), accepted: *?selection.Accepted, selection_trace: ?*selection.Trace, next_version: *usize, budget: stack.Budget,) !AdvanceOutcome { var owned = candidate.branch; defer owned.deinit(allocator); var lookahead = candidate.lookahead; var entry = tree.language.entry(owned.state(), lookahead.tree.symbol); if (entry.actions.len == 0) { if (lookahead.fallback_symbol) |fallback| { if (!tree.language.isReserved(tree.language.mode(owned.state()), lookahead.tree.symbol)) { lookahead.tree = try tree.leaf(fallback, lookahead.tree.start, lookahead.tree.end, lookahead.tree.parse_state, false, null); lookahead.fallback_symbol = null; entry = tree.language.entry(owned.state(), fallback); } } } if (entry.actions.len == 0) return .done; var replacement_version: ?usize = null; for (entry.actions) |action| { switch (@as(abi.ActionType, @fromBackingInt(@intCast(action.type)))) { .shift => { if (action.shift.repetition) continue; var fork = try owned.clone(allocator); errdefer fork.deinit(allocator); const shifted_tree = try tree.withExtra(lookahead.tree, action.shift.extra); const next_state = if (action.shift.extra) fork.state() else action.shift.state; try fork.shift(stack_context, shifted_tree, next_state, lookahead.end, lookahead.scanner_after); return .{ .shifted = .{ .id = candidate.version, .branch = fork } }; }, .reduce => { var reductions: std.ArrayList(stack.Branch) = .empty; defer deinitBranches(allocator, &reductions); try owned.reduce(allocator, stack_context, tree, tree.language, action.reduce, &reductions, budget); var action_replacement: ?usize = null; for (reductions.items) |fork| { const version = next_version.*; next_version.* += 1; const retained = try integrateReductionVersion( allocator, stack_context, .{ .id = version, .branch = fork }, ready, shifted, ); if (retained and action_replacement == null) action_replacement = version; } if (action_replacement) |version| replacement_version = version; }, .accept => { if (lookahead.tree.symbol != abi.end_symbol) continue; const structural_root = try owned.acceptedRoot( allocator, stack_context, tree, budget, ) orelse continue; const root = try tree.withSpan( structural_root, if (structural_root.start.byte == structural_root.end.byte) lookahead.end else structural_root.start, lookahead.end, ); try selection.consider(allocator, accepted, root, owned.precedence(), selection_trace); return .done; }, .recover => return .done, } } if (replacement_version) |version| { const index = versionIndex(ready.items, version) orelse return .done; const replacement = ready.orderedRemove(index); return .{ .replacement = .{ .version = candidate.version, .branch = replacement.branch, .lookahead = lookahead, } }; } return .done;}fn integrateReductionVersion( allocator: std.mem.Allocator, context: *stack.Context, version: Version, ready: *std.ArrayList(Version), shifted: *std.ArrayList(Version),) !bool { const retained_count = std.math.add( usize, context.limits.active_version_count, context.limits.version_count_overflow, ) catch return error.CapacityOverflow; if (shifted.items.len + ready.items.len + 1 > retained_count) { var dropped = version.branch; dropped.deinit(allocator); return false; } for (shifted.items) |existing| { if (!version.branch.equivalent(existing.branch)) continue; _ = try context.merge(existing.branch.head, version.branch.head); var merged = version.branch; merged.deinit(allocator); return false; } for (ready.items) |existing| { if (!version.branch.equivalent(existing.branch)) continue; _ = try context.merge(existing.branch.head, version.branch.head); var merged = version.branch; merged.deinit(allocator); return false; } ready.append(allocator, version) catch |err| { var failed = version.branch; failed.deinit(allocator); return err; }; return true;}fn versionIndex(versions: []const Version, id: usize) ?usize { for (versions, 0..) |version, index| if (version.id == id) return index; return null;}fn compact(allocator: std.mem.Allocator, context: *stack.Context, versions: *std.ArrayList(Version)) !void { var index: usize = 0; while (index < versions.items.len) { var other: usize = 0; var removed = false; while (other < index) : (other += 1) { if (!versions.items[index].branch.equivalent(versions.items[other].branch)) continue; _ = try context.merge(versions.items[other].branch.head, versions.items[index].branch.head); var merged = versions.orderedRemove(index); merged.branch.deinit(allocator); removed = true; break; } if (!removed) index += 1; } var rank_index: usize = 1; while (rank_index < versions.items.len) : (rank_index += 1) { const current = versions.items[rank_index]; var destination = rank_index; while (destination > 0 and current.branch.precedence() > versions.items[destination - 1].branch.precedence()) : (destination -= 1) { versions.items[destination] = versions.items[destination - 1]; } versions.items[destination] = current; } while (versions.items.len > context.limits.active_version_count) { var discarded = versions.orderedRemove(context.limits.active_version_count); discarded.branch.deinit(allocator); }}fn deinitBranches(allocator: std.mem.Allocator, branches: *std.ArrayList(stack.Branch)) void { for (branches.items) |*branch| branch.deinit(allocator); branches.deinit(allocator); branches.* = .empty;}fn deinitVersions(allocator: std.mem.Allocator, versions: *std.ArrayList(Version)) void { for (versions.items) |*version| version.branch.deinit(allocator); versions.deinit(allocator); versions.* = .empty;}extern fn tree_sitter_zig() callconv(.c) *const abi.Language;test "focused operation exhaustion is a resource error" { const grammar = Grammar{ .raw = tree_sitter_zig(), .scanner_kind = .zig }; try std.testing.expectError( error.OperationLimitExceeded, parse(std.testing.allocator, grammar, "const value = 1;", .{ .limits = testing_limits, .operation_limit = 0 }), );}test "focused ambiguous parse propagates every allocation failure" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkAmbiguousParseAllocationFailures, .{testing_limits}, );}fn checkAmbiguousParseAllocationFailures(allocator: std.mem.Allocator, limits: limits_mod.Parser) !void { const grammar = Grammar{ .raw = tree_sitter_zig(), .scanner_kind = .zig }; const result = try parse( allocator, grammar, "const order_fn: ?*const fn (*anyopaque, *anyopaque) std.math.Order = null;", .{ .limits = limits }, ); const tree = result orelse return error.UnexpectedInvalidTree; defer tree.deinit();}test "focused equal-precedence active version compaction retains the first six identities" { var raw = std.mem.zeroes(abi.Language); raw.abi_version = 15; raw.symbol_count = 2; raw.lex_fn = testLex; var actions = [_]abi.ActionEntry{std.mem.zeroes(abi.ActionEntry)}; raw.parse_actions = &actions; var metadata = [_]abi.SymbolMetadata{ .{ .visible = false, .named = false, .supertype = false }, .{ .visible = true, .named = true, .supertype = false }, }; raw.symbol_metadata = &metadata; const lang = try language.Language.init(&raw); const tree = try subtree.Tree.init(std.testing.allocator, lang); defer tree.deinit(); var context = try stack.Context.init(std.testing.allocator, testing_limits); defer context.deinit(); var versions: std.ArrayList(Version) = .empty; defer deinitVersions(std.testing.allocator, &versions); for (0..7) |index| { var branch = stack.Branch.init(&context, .zig); const parent = try tree.node( 1, &.{}, 0, 1, 0, .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, false, ); try branch.shift( &context, parent, @intCast(index + 2), .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, scanner.State.init(.zig), ); try versions.append(std.testing.allocator, .{ .id = index, .branch = branch }); } try compact(std.testing.allocator, &context, &versions); try std.testing.expectEqual(testing_limits.active_version_count, versions.items.len); for (versions.items, 0..) |version, index| try std.testing.expectEqual(@as(abi.State, @intCast(index + 2)), version.branch.state());}test "focused active version compaction ranks precedence before stable identities" { var raw = std.mem.zeroes(abi.Language); raw.abi_version = 15; raw.symbol_count = 2; raw.lex_fn = testLex; var actions = [_]abi.ActionEntry{std.mem.zeroes(abi.ActionEntry)}; raw.parse_actions = &actions; var metadata = [_]abi.SymbolMetadata{ .{ .visible = false, .named = false, .supertype = false }, .{ .visible = true, .named = true, .supertype = false }, }; raw.symbol_metadata = &metadata; const lang = try language.Language.init(&raw); const tree = try subtree.Tree.init(std.testing.allocator, lang); defer tree.deinit(); var context = try stack.Context.init(std.testing.allocator, testing_limits); defer context.deinit(); var versions: std.ArrayList(Version) = .empty; defer deinitVersions(std.testing.allocator, &versions); for (0..7) |index| { var branch = stack.Branch.init(&context, .zig); const parent = try tree.node( 1, &.{}, 0, 1, if (index == 6) 10 else 0, .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, false, ); try branch.shift( &context, parent, @intCast(index + 2), .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, scanner.State.init(.zig), ); try versions.append(std.testing.allocator, .{ .id = index, .branch = branch }); } try compact(std.testing.allocator, &context, &versions); const expected = [_]abi.State{ 8, 2, 3, 4, 5, 6 }; for (versions.items, expected) |version, state| try std.testing.expectEqual(state, version.branch.state());}test "focused temporary version overflow is global and retains FIFO identities" { var raw = std.mem.zeroes(abi.Language); raw.abi_version = 15; raw.symbol_count = 2; raw.lex_fn = testLex; var actions = [_]abi.ActionEntry{std.mem.zeroes(abi.ActionEntry)}; raw.parse_actions = &actions; var metadata = [_]abi.SymbolMetadata{ .{ .visible = false, .named = false, .supertype = false }, .{ .visible = true, .named = true, .supertype = false }, }; raw.symbol_metadata = &metadata; const lang = try language.Language.init(&raw); const tree = try subtree.Tree.init(std.testing.allocator, lang); defer tree.deinit(); const lookahead_tree = try tree.leaf( 1, .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, .{ .byte = 1, .point = .{ .row = 0, .column = 1 } }, 1, false, null, ); var context = try stack.Context.init(std.testing.allocator, testing_limits); defer context.deinit(); var ready: std.ArrayList(Version) = .empty; defer deinitVersions(std.testing.allocator, &ready); var shifted: std.ArrayList(Version) = .empty; defer deinitVersions(std.testing.allocator, &shifted); try appendTestVersions(&context, &ready, lookahead_tree, 4, 20, 100); try appendTestVersions(&context, &shifted, lookahead_tree, 2, 30, 200); for (0..6) |index| { const version = try testVersion(&context, lookahead_tree, @intCast(index + 2), index); const retained = try integrateReductionVersion( std.testing.allocator, &context, version, &ready, &shifted, ); try std.testing.expectEqual(index < 4, retained); } try std.testing.expectEqual(@as(usize, 8), ready.items.len); for (ready.items[4..], 0..) |version, index| { try std.testing.expectEqual(index, version.id); try std.testing.expectEqual(@as(abi.State, @intCast(index + 2)), version.branch.state()); }}test "focused temporary overflow cannot merge a dropped late candidate" { var raw = std.mem.zeroes(abi.Language); raw.abi_version = 15; raw.symbol_count = 3; raw.lex_fn = testLex; var actions = [_]abi.ActionEntry{std.mem.zeroes(abi.ActionEntry)}; raw.parse_actions = &actions; var metadata = [_]abi.SymbolMetadata{ .{ .visible = false, .named = false, .supertype = false }, .{ .visible = true, .named = true, .supertype = false }, .{ .visible = true, .named = true, .supertype = false }, }; raw.symbol_metadata = &metadata; const lang = try language.Language.init(&raw); const tree = try subtree.Tree.init(std.testing.allocator, lang); defer tree.deinit(); const retained_tree = try tree.node( 1, &.{}, 0, 1, 0, .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, false, ); const dropped_tree = try tree.node( 2, &.{}, 0, 1, 10, .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, false, ); var context = try stack.Context.init(std.testing.allocator, testing_limits); defer context.deinit(); var ready: std.ArrayList(Version) = .empty; defer deinitVersions(std.testing.allocator, &ready); var shifted: std.ArrayList(Version) = .empty; defer deinitVersions(std.testing.allocator, &shifted); try appendTestVersions(&context, &shifted, retained_tree, 8, 20, 100); const trees = [_]*const subtree.Subtree{ retained_tree, retained_tree, dropped_tree }; const states = [_]abi.State{ 2, 3, 2 }; for (trees, states, 0..) |candidate_tree, state, index| { const version = try testVersion(&context, candidate_tree, state, index); const retained = try integrateReductionVersion( std.testing.allocator, &context, version, &ready, &shifted, ); try std.testing.expectEqual(index < 2, retained); } try std.testing.expectEqual(@as(usize, 2), ready.items.len); try std.testing.expectEqual(@as(i64, 0), ready.items[0].branch.precedence());}test "focused an allowed boundary merge frees the next temporary version" { var raw = std.mem.zeroes(abi.Language); raw.abi_version = 15; raw.symbol_count = 3; raw.lex_fn = testLex; var actions = [_]abi.ActionEntry{std.mem.zeroes(abi.ActionEntry)}; raw.parse_actions = &actions; var metadata = [_]abi.SymbolMetadata{ .{ .visible = false, .named = false, .supertype = false }, .{ .visible = true, .named = true, .supertype = false }, .{ .visible = true, .named = true, .supertype = false }, }; raw.symbol_metadata = &metadata; const lang = try language.Language.init(&raw); const tree = try subtree.Tree.init(std.testing.allocator, lang); defer tree.deinit(); const retained_tree = try tree.node( 1, &.{}, 0, 1, 0, .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, false, ); const merging_tree = try tree.node( 2, &.{}, 0, 1, 10, .{ .byte = 0, .point = .{ .row = 0, .column = 0 } }, false, ); var context = try stack.Context.init(std.testing.allocator, testing_limits); defer context.deinit(); var ready: std.ArrayList(Version) = .empty; defer deinitVersions(std.testing.allocator, &ready); var shifted: std.ArrayList(Version) = .empty; defer deinitVersions(std.testing.allocator, &shifted); try appendTestVersions(&context, &shifted, retained_tree, 8, 20, 100); const trees = [_]*const subtree.Subtree{ retained_tree, merging_tree, retained_tree }; const states = [_]abi.State{ 2, 2, 3 }; for (trees, states, 0..) |candidate_tree, state, index| { const version = try testVersion(&context, candidate_tree, state, index); _ = try integrateReductionVersion( std.testing.allocator, &context, version, &ready, &shifted, ); } try std.testing.expectEqual(@as(usize, 2), ready.items.len); try std.testing.expectEqual(@as(i64, 10), ready.items[0].branch.precedence()); try std.testing.expectEqual(@as(abi.State, 3), ready.items[1].branch.state());}fn testVersion( context: *stack.Context, candidate_tree: *const subtree.Subtree, state: abi.State, id: usize,) !Version { var branch = stack.Branch.init(context, .zig); try branch.shift(context, candidate_tree, state, candidate_tree.end, scanner.State.init(.zig)); return .{ .id = id, .branch = branch };}fn appendTestVersions( context: *stack.Context, versions: *std.ArrayList(Version), candidate_tree: *const subtree.Subtree, count: usize, state_base: abi.State, id_base: usize,) !void { for (0..count) |index| { try versions.append( std.testing.allocator, try testVersion(context, candidate_tree, state_base + @as(abi.State, @intCast(index)), id_base + index), ); }}fn testLex(_: *abi.Lexer, _: abi.State) callconv(.c) bool { return false;}Source: tools/smg/src/tree/runtime/root.zig:5
zig
pub const parser = @import("parser.zig");Complete call list for tree.runtime.parser.parse
10 direct calls.
tiny.smg.tree.runtime.language.Language.init[function] attools/smg/src/tree/runtime/language.zig:20tools.smg.src.tree.runtime.parser.advance[function] — private; no exact target attools/smg/src/tree/runtime/parser.zig:181in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.parser.compact[function] — private; no exact target attools/smg/src/tree/runtime/parser.zig:355in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.parser.deinitVersions[function] — private; no exact target attools/smg/src/tree/runtime/parser.zig:392in nearest public ownertiny.smg.tree.runtime.parsertools.smg.src.tree.runtime.parser.lex[function] — private; no exact target attools/smg/src/tree/runtime/parser.zig:125in nearest public ownertiny.smg.tree.runtime.parsertiny.smg.tree.runtime.stack.Branch.init[function] attools/smg/src/tree/runtime/stack.zig:145tiny.smg.tree.runtime.stack.Context.deinit[method] attools/smg/src/tree/runtime/stack.zig:62tiny.smg.tree.runtime.stack.Context.init[function] attools/smg/src/tree/runtime/stack.zig:45tiny.smg.tree.runtime.subtree.Tree.deinit[method] attools/smg/src/tree/runtime/subtree.zig:42tiny.smg.tree.runtime.subtree.Tree.init[function] attools/smg/src/tree/runtime/subtree.zig:31
Audit
| Definitions | 4 |
|---|---|
| Public names | 4 |
| Members | 5 |
| Version | 26.7.0 |
| Revision | daab053ee433 |