Skip to documentation
SLOP

tiny.smg.tree.runtime.parser

Reference tiny.smg tree runtime parser

Defined in tree.runtime.

API (3)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callstree.runtimeparser
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsprivate; no linktools.smg.src.tree.parseparseWithGrammarprivate; no linktools.smg.src.tree.runtime.parsercheckAmbiguousParseAllocationFailurestest; no linktools.smg.src.tree.runtime.parsertest: focused operation exhaustion is...test; no linktools.smg.src.tree.validationtest: native parser selects focused c...private; no linktools.smg.src.tree.validationvalidatetree.runtime.language.Languageinitprivate; no linktools.smg.src.tree.runtime.parseradvanceprivate; no linktools.smg.src.tree.runtime.parsercompactprivate; no linktools.smg.src.tree.runtime.parserdeinitVersionsprivate; no linktools.smg.src.tree.runtime.parserlex+5 moretree.runtime.parserparse
Static calls · unresolved targets: 5 · external targets: 1.

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.

Audit

Definitions4
Public names4
Members5
Version26.7.0
Revisiondaab053ee433