/** * Incremental parse document — incremental re-parse over a rules() registry. */ import type { Combinator, ParseContext, ParseError, ParseResult } from '../types.ts'; import type { NodeLike, CSTLeaf, CSTError } from '../cst/types.ts'; /** A single compiled (or interpreted) rule: parse from `pos`, producing node `N`. */ export type RuleFn = (input: string, pos: number, ctx: ParseContext) => ParseResult; /** * Rule name → parser. Each entry is either a bare parse function or a * `Combinator` (what `rules()` returns). Passing the `rules()` combinators * directly lets `.edit()` inspect the grammar — required for **sound** structural * list-reuse (see `structuralReuse`): only a rule the grammar proves is a genuine * repetition is ever spliced. A bare-function registry still parses correctly; it * just can't be structurally reused (the splice is skipped, never guessed). */ export type Registry = Record | Combinator>; export type ParseDocOptions = { /** Initial grammar state threaded into ctx.state for the root parse. */ state?: unknown; /** * Reconstruct a parent node with one child replaced (used when grafting a * re-parsed subtree into its ancestors). Defaults to a shallow spread, which * works for plain-object nodes; class-instance ASTs should supply their own. */ rebuild?: (node: N, children: ReadonlyArray) => N; /** * Mode host for a linkable/fused grammar (RULE_ABI_PLAN §7): threaded into * `ctx.build` on every (re)parse so `node()` rules build a positioned CST / * language-service tree instead of their own eval-AST. Unset → the grammar's * own builders (eval mode). */ build?: ParseContext['build']; /** * Enable structural list-reuse: on a length-changing *structural* edit (adding * or removing a whole element in a collection) that would otherwise force a full * reparse, reparse only the disturbed span and reuse the collection's untouched * tail elements by identity — turning an insert near the top of a large list from * O(list) into O(edit + trailing siblings). * * OFF by default because it is sound only when a rule whose CST children form a * homogeneous, separator-delimited element list is a genuine REPETITION * (`many` / `sepBy` / `oneOrMore`), not a fixed-arity sequence of same-typed * tokens (e.g. `Triple = Num ',' Num ',' Num`) — the two are structurally * indistinguishable without the grammar, and splicing the latter would accept an * element count it shouldn't. Turn it on when your list rules are true * repetitions (the common case: JSON arrays/objects, CSS value lists, argument * lists). Every splice is still guarded (exact tiling + lookahead probe + * stateless-tail check) and falls back to a full, correct reparse when unproven; * the flag only authorises *attempting* the reuse. */ structuralReuse?: boolean; /** * Parse tolerantly: `many`/`sepBy`/`oneOrMore` recover from a failed element * (skip to an inferred sync point, embed a `ParseError` over the skipped span), * so a broken edit keeps producing a tree instead of collapsing to `null` — the * editor-backend path. Off by default (strict, byte-identical). Recovery is a cold * path: well-formed input never triggers it. Every reparse the doc does — root, * localized rule, and list-splice — inherits this flag, and the reuse-soundness * probes run at the same tolerance so incremental reuse stays valid under recovery * (a scan that would cross a splice boundary just falls back to a full reparse). */ tolerant?: boolean; /** * The grammar's trivia rule, used ONLY to compute `unconsumedFrom`: a root rule * consumes trivia BETWEEN terms but not after the last, so trailing * whitespace/comments would otherwise read as leftover input. Given the trivia * rule, the tail is skipped before reporting the first unconsumed offset — * matching `run()`'s semantics. Defaults to the root rule's own * `_meta.grammarTrivia` (from `rules({ trivia })`); set it only to override. */ trivia?: Combinator; }; export interface ParseDoc { /** * The parse tree with PARENT-RELATIVE spans — each node's `span` is relative to * its parent's start (root base 0). This is the shareable representation: a * length-changing `.edit()` keeps every untouched subtree shared by identity, * and reading the tree is O(1) (no offset rewrite). For absolute positions use * the O(depth) cursor `spanAt(path)`, or `absolutizeCST(doc.tree)` to * materialize the whole absolute tree. A fresh non-incremental `node().parse()` * result is unchanged — still absolute. */ readonly tree: N | null; /** * Recovery diagnostics collected during the (re)parse — the missing-token * `expect()` errors and tolerant-list recovery errors that ride the tree as * `parseError` nodes, surfaced here as a flat list too (spans ABSOLUTE). Empty * in strict mode. On a hard (non-recovered) parse failure this holds the single * top-level failure. This is what makes an editor document able to see syntax * errors — a blank `errors: []` (the prior behaviour) hid every recovery. */ readonly errors: ParseError[]; /** * Offset where unparsed input begins — the first non-trivia character the parse * left unconsumed (trailing trivia skipped when a trivia rule is available), or * `null` if the whole input was consumed. This is how a document detects "the * grammar stopped short, there's junk here"; computed exactly as `run()` does. */ readonly unconsumedFrom: number | null; readonly input: string; /** * Absolute span of the node at `path` (child indices from the root) — O(depth), * without materializing the absolute tree. The projection cursor for the * relative representation; use it for spot queries on a large incremental doc. */ spanAt(path: readonly number[]): { start: number; end: number; }; /** * Incrementally re-parse after a text change. `from`/`to` are byte offsets in * the OLD input; `replacement` fills that range (editor change-event shape). * Sound: the result tree is always structurally identical to a fresh * `parseDoc` of the edited text (the Stage-2 guard falls back to a full * reparse whenever reuse can't be proven safe). Reuse/strategy is intentionally * NOT reported here — an observer derives it by diffing this tree against the * previous one (see the incremental tests); the runtime's job is to be fast, * not to measure itself. */ edit(from: number, to: number, replacement: string): ParseDoc; } /** * Deep structural equality on parse trees: `_tag`, `span`, node `type`, leaf * `value`, and children pairwise. This is the oracle relation `.edit()` must * preserve against a full reparse; it's also what the Stage-2 guard compares * probe results with. */ export declare function structurallyEqual(a: unknown, b: unknown): boolean; /** * The relative (parent-offset) tree backing a doc. Currently identical to the * public `doc.tree` (which is relative); kept as a named internal handle for the * reuse-metric tests, which assert on the shareable representation explicitly. */ export declare function relTreeOf(doc: ParseDoc): N | null; /** * Parse `input` from `rootRule` and wrap the result in a ParseDoc that can * be incrementally re-parsed via `.edit()`. */ export declare function parseDoc(registry: Registry, rootRule: string, input: string, opts?: ParseDocOptions): ParseDoc; //# sourceMappingURL=doc.d.ts.map