Skip to main content

tabnas/
parser.rs

1// Copyright (c) 2013-2026 Richard Rodger, MIT License
2
3use crate::builtins::run_builtin_action_with_info;
4use crate::context::{Context, ContextSeed, InstanceInfo};
5use crate::error::TabnasError;
6use crate::lexer::{compile_number_exclude, Lexer, RelexCheckpoint};
7use crate::options::Options;
8use crate::rule::{
9    resolved_action_order, resolved_alt_action_order, ActionBinding, AltActionBinding, AltMatch,
10    AltSpec, CompareOp, Condition, Rule, RuleDone, RuleDoneAlt, RuleName, RuleSnapshot, RuleSpec,
11    RuleState, StateAction,
12};
13use crate::token::{Tin, Token, TIN_AA, TIN_BD, TIN_ZZ};
14use crate::value::Value;
15use crate::{
16    Action, AltAction, ContextAction, LexSubscriber, ParseGuard, RuleDoneSubscriber,
17    RuleSubscriber, TokenSubscriber,
18};
19use indexmap::IndexMap;
20use std::borrow::Cow;
21use std::cell::RefCell;
22use std::collections::{BTreeSet, HashMap};
23use std::panic::{catch_unwind, AssertUnwindSafe};
24use std::rc::Rc;
25use std::sync::Arc;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct Continuations {
29    pub tins: Vec<Tin>,
30    pub tokens: Vec<String>,
31}
32
33#[derive(Debug, Clone)]
34pub struct ParseRecovery {
35    pub value: Option<Value>,
36    pub errors: Vec<TabnasError>,
37    pub fatal: Option<TabnasError>,
38}
39
40#[derive(Default)]
41struct ContinuationCapture {
42    at_end: BTreeSet<Tin>,
43    have_end: bool,
44    failure: Vec<Tin>,
45}
46
47struct ParseMode<'a> {
48    continuation: Option<&'a mut ContinuationCapture>,
49    recovering: bool,
50    errors: &'a mut Vec<TabnasError>,
51    partial: Option<PartialValue>,
52    /// Recovery gave up, and the error it gave up on is already accounted
53    /// for in `errors`: recorded there, or dropped as a cascade of the
54    /// fault before it. TypeScript records an error as it is constructed
55    /// and gives up by rethrowing the last one it recorded (`RuleSpec.bad`,
56    /// ts/src/rules.ts), or the bad token it has just recorded, coalesced
57    /// or dropped (the fetch in `parse_alts`), so the error that ends the
58    /// parse is never listed a second time. Every other terminal error is
59    /// listed once, where the parse ends.
60    gave_up: bool,
61}
62
63/// A recovery result that stays borrowed from the live parse until needed.
64///
65/// Holding a cloned `Value` while its container was still being built kept a
66/// second `Arc` handle alive. Every following append then made
67/// `Arc::make_mut` copy the whole prefix. A node cell follows TypeScript's
68/// recovery path instead: keep the live node and read it once when parsing
69/// ends. `Complete` is the already-finalized value retained for a post-loop
70/// trailing-content failure.
71enum PartialValue {
72    Node(Rc<RefCell<Value>>),
73    Complete(Value),
74}
75
76impl PartialValue {
77    fn into_value(self) -> Option<Value> {
78        match self {
79            Self::Complete(value) => Some(value),
80            Self::Node(node) => {
81                let value = node.borrow().clone();
82                (!matches!(value, Value::Undefined | Value::Null)).then(|| value.unwrap_undefined())
83            }
84        }
85    }
86}
87
88struct RelexUndo {
89    position: usize,
90    token: Token,
91    checkpoint: RelexCheckpoint,
92    tokens: Vec<Token>,
93}
94
95/// Who is asking [`Parser::ensure_lookahead`] for a token, which decides
96/// what a bad token does under recovery.
97#[derive(Clone, Copy, PartialEq, Eq)]
98enum Fetch {
99    /// A rule reading its lookahead, as TypeScript's `parse_alts` does:
100    /// under recovery a bad token is recorded and skipped, and the fetch
101    /// goes on.
102    Rule,
103    /// The check for content after the parse has finished, as
104    /// TypeScript's `Parser.start` makes it: a bad token ends the parse
105    /// with its own error, recovering or not.
106    Trailing,
107}
108
109#[derive(Clone, Copy)]
110struct ParseSite<'a> {
111    source: &'a str,
112    stack: &'a [Rule],
113    alts: &'a [AltSpec],
114}
115
116pub struct Parser {
117    /// Shared with the lexer, which never writes to them. Cloning a
118    /// whole `Options` was 22% of a small parse, and it was happening
119    /// twice.
120    pub options: Arc<Options>,
121    ignore_tins: Vec<Tin>,
122    /// `options.number.exclude` compiled once, here, and shared with
123    /// the lexer of every parse instead of compiled by each of them.
124    ///
125    /// `options` is public, so this is a cache whose source has a second
126    /// writer: a caller on the low-level API may replace the whole `Arc`
127    /// between parses, and before this was cached each lexer compiled
128    /// whatever pattern was in force. `exclude_pattern` is the pattern
129    /// this regex was built from, and a parse whose options no longer
130    /// carry it compiles the one they do carry instead -- the old cost,
131    /// paid only by the callers who change the pattern under the parser.
132    exclude_regex: Option<Arc<regex::Regex>>,
133    exclude_pattern: Option<String>,
134    /// Installed rules by name.
135    ///
136    /// Private, and read through [`Parser::rules`]. Two derived tables below
137    /// are keyed by the same names and are written only by `add_rule`; while
138    /// this was public an embedder could insert or replace a rule straight
139    /// into it, leaving those tables describing the rule that used to be
140    /// there. Lookahead would then gate the custom matchers on the previous
141    /// rule's token identities, or on none at all for a name that was never
142    /// installed, and a valid document could fail to parse. Before those
143    /// identities were derived once per rule instead of once per lookahead
144    /// there was nothing to go stale, so this hazard arrived with the table.
145    rules: IndexMap<String, Arc<RuleSpec>>,
146    /// The `rule.include` and `rule.exclude` every `PreparedAlt::groups`
147    /// was worked out against, and every `expected_tins` row collated
148    /// against. Both are public and a callback may write either between
149    /// one step and the next, so the step compares these two strings
150    /// before trusting the prepared answer, and `expected_match_tins`
151    /// before trusting the row -- the same shape of guard the compiled
152    /// `number.exclude` pattern carries, and for the same reason: a cache
153    /// derived from a public mutable field is a cache with a second
154    /// writer.
155    prepared_include: String,
156    prepared_exclude: String,
157    /// The token identities each rule can accept at each lookahead slot,
158    /// worked out once per installed rule rather than once per token, and
159    /// positional in `rules` the way `names` is: a rule carries the slot
160    /// it was installed at, so reaching its row costs an index rather than
161    /// a hash of its name on every token fetch.
162    expected_tins: Vec<ExpectedTins>,
163    /// One shared copy of each installed rule's name, so pushing a rule
164    /// clones a pointer rather than reallocating the name per push.
165    ///
166    /// Parallel to `rules`, indexed by position rather than keyed by the
167    /// name again: every route already has to find the rule's spec, and
168    /// `IndexMap::get_full` hands back that rule's index with it, so the
169    /// shared name costs an array index instead of a second hash of the
170    /// same bytes. `Parser::rules` is public and hands out the map itself,
171    /// which is why the handle lives beside the map rather than in it.
172    /// `add_rule` keeps the two in step; `IndexMap` gives a replaced key
173    /// back its existing index, so a replacement overwrites in place.
174    names: Vec<RuleName>,
175    /// Per-rule state worked out once, when the grammar is installed.
176    ///
177    /// Parallel to `rules` and `names`, and written only by `add_rule`,
178    /// which rebuilds the whole table -- a route may name a rule that is
179    /// installed later, so an entry is only right once every rule is in.
180    /// The loop reaches an entry by the `slot` the rule carries and checks
181    /// it against the rule's own spec and name before trusting it, so a
182    /// callback that rewrites either just falls back to the lookup.
183    prepared: Vec<PreparedRule>,
184    pub actions: HashMap<String, Action>,
185    pub context_actions: HashMap<String, ContextAction>,
186    pub matched_actions: HashMap<String, AltAction>,
187    pub state_actions: HashMap<String, StateAction>,
188    pub token_subscribers: Vec<TokenSubscriber>,
189    pub lex_subscribers: Vec<LexSubscriber>,
190    pub rule_subscribers: Vec<RuleSubscriber>,
191    pub rule_done_subscribers: Vec<RuleDoneSubscriber>,
192    pub parse_guards: Vec<ParseGuard>,
193    pub instance: InstanceInfo,
194}
195
196/// One installed rule, with what the parse loop can work out about it
197/// before a parse starts.
198///
199/// Routing, and the order the rule's actions run in. The order is a
200/// question about the spec alone -- which of the named, callback and
201/// state lists a binding came from, and where the three interleave -- and
202/// the spec behind the `Arc` this record is checked against cannot change
203/// while a parse runs, so it is answered once here instead of rebuilt into
204/// a fresh `Vec` on every rule step. What a *named* binding resolves to is
205/// a different question: it is looked up in `Parser::actions`,
206/// `matched_actions` and `state_actions`, all of them public maps an
207/// embedder may write after `add_rule` has run, so that lookup stays where
208/// it was, on the step -- a table derived from a field with a second
209/// writer goes stale in silence, which is the hazard `rules` was made
210/// private to close.
211struct PreparedRule {
212    /// The rule's shared name handle and the spec it was installed with.
213    ///
214    /// `spec` is what the loop borrows in place of the rule's own; `name`
215    /// is only ever compared. Together they are what says the rule in hand
216    /// is still the rule this record describes.
217    name: RuleName,
218    spec: Arc<RuleSpec>,
219    open: Vec<PreparedAlt>,
220    close: Vec<PreparedAlt>,
221    /// The alternates of each state indexed by the tin they can take at
222    /// position 0, so a step tries the candidates for the first
223    /// lookahead token rather than every alternate.
224    open_first: AltIndex,
225    close_first: AltIndex,
226    /// The four lifecycle action orders, in the same order the rule runs
227    /// them. Most grammars declare none of them, and an empty list here is
228    /// what lets a step skip the whole phase rather than walk an empty
229    /// one: `bind_spec` sets a rule's `bo`/`ao`/`bc`/`ac` true
230    /// unconditionally, so those flags say the phase is *enabled*, never
231    /// that it has anything to run.
232    bo: Vec<ActionBinding>,
233    ao: Vec<ActionBinding>,
234    bc: Vec<ActionBinding>,
235    ac: Vec<ActionBinding>,
236}
237
238impl PreparedRule {
239    /// The first-token index of the open or close alternates.
240    fn first(&self, is_open: bool) -> &AltIndex {
241        if is_open {
242            &self.open_first
243        } else {
244            &self.close_first
245        }
246    }
247
248    fn alt(&self, is_open: bool, idx: usize) -> Option<&PreparedAlt> {
249        if is_open {
250            self.open.get(idx)
251        } else {
252            self.close.get(idx)
253        }
254    }
255
256    /// The before-open or before-close action order.
257    fn before(&self, is_open: bool) -> &[ActionBinding] {
258        if is_open {
259            &self.bo
260        } else {
261            &self.bc
262        }
263    }
264
265    /// The after-open or after-close action order.
266    fn after(&self, is_open: bool) -> &[ActionBinding] {
267        if is_open {
268            &self.ao
269        } else {
270            &self.ac
271        }
272    }
273}
274
275/// One alternate's two routing channels and its action order, resolved at
276/// install time.
277struct PreparedAlt {
278    p: PreparedRoute,
279    r: PreparedRoute,
280    /// The alternate's action bindings in the order they run.
281    actions: Vec<AltActionBinding>,
282    /// Whether anything a step on this alternate runs can read the
283    /// `AltMatch` record, and so see the action list the engine publishes
284    /// into `matched.actions`.
285    ///
286    /// The list is published for the callbacks that receive the record --
287    /// the `c`/`e`/`p`/`r`/`b` matched hooks, the matched modifier, and a
288    /// matched action itself. When the alternate declares none of them
289    /// nothing in the step can tell whether the field was filled, so the
290    /// step runs these bindings straight from here: no `Vec` built, no
291    /// second copy taken to iterate, no reference counts touched. When it
292    /// declares any of them the step fills the record exactly as before.
293    ///
294    /// A `Named` binding may also resolve to a matched action, and that
295    /// cannot be settled here -- `add_matched_action` may be called after
296    /// `add_rule`, and `matched_actions` is public. `named` records only
297    /// that the question arises; the step asks it of the map it is asking
298    /// anyway.
299    observed: bool,
300    named: bool,
301    /// Whether `rule.include` and `rule.exclude` leave this alternate
302    /// active, worked out when the rule was installed.
303    ///
304    /// The question is about `alt.g` against two option strings, and the
305    /// answer cannot change during a parse unless a callback rewrites the
306    /// options -- so `prepared_include`/`prepared_exclude` on the parser
307    /// record what this was computed against, and the step checks those
308    /// two strings ONCE rather than re-deriving the answer per alternate.
309    groups: bool,
310    /// `alt.g` split into the tags the step publishes into `matched.g`,
311    /// split when the rule was installed rather than per rule step.
312    ///
313    /// Unlike `groups` this is derived from the alternate alone, so no
314    /// option string can stale it; the only alternate it cannot answer for
315    /// is one a modifier rewrote, which the step detects the same way it
316    /// does for the action order.
317    group_tags: Vec<String>,
318}
319
320enum PreparedRoute {
321    /// Nothing was resolved: the alternate declares no route on this
322    /// channel, or names a rule that is not installed, or routes through a
323    /// callback. All three take the same path they took before the table --
324    /// one lookup by name, which is also what raises `unknown_rule` for a
325    /// route naming no rule.
326    ByName,
327    Static {
328        slot: usize,
329        name: RuleName,
330        spec: Arc<RuleSpec>,
331    },
332}
333
334impl PreparedRoute {
335    /// The prepared answer, if the route the parse is actually taking is
336    /// still the one that was prepared.
337    ///
338    /// The test is byte-equality of the name, unconditionally, and not a
339    /// pointer comparison of the alternate: `h` rewrites the whole
340    /// `AltSpec` per step, `p_fn`/`r_fn` and `p_match`/`r_match` produce
341    /// the name at run time, and a matched action may write `matched.p` or
342    /// `matched.r` outright -- all supported routing channels. A route that
343    /// arrives at the same name resolves to the same rule whichever of them
344    /// produced it, so the bytes are the whole question.
345    fn resolved(&self, name: &str) -> Option<(usize, RuleName, &Arc<RuleSpec>)> {
346        match self {
347            PreparedRoute::Static {
348                slot,
349                name: prepared,
350                spec,
351            } if prepared.as_str() == name => Some((*slot, prepared.clone(), spec)),
352            _ => None,
353        }
354    }
355}
356
357/// Per-slot accepted token identities for one rule, in each state.
358///
359/// This used to be derived on every lookahead: a map lookup, a `BTreeSet`
360/// built from the alternates, and a `Vec` collected out of it, once per
361/// token. None of it can change while a parse runs, so it is derived once
362/// when the rule is installed.
363/// What the names on one slot resolve to against `options` now: a slot
364/// naming a token set takes the set's current members, and a slot naming a
365/// token takes that token. `None` leaves the slot with the tins it has, and
366/// is the answer for a slot with no names (an alternate built from tins
367/// directly, or a slot set by hand before a merge carried it, whose names
368/// the merge drops), a slot whose `s` was set by hand since it was
369/// installed (it no longer equals what the names last resolved to,
370/// `s_bound`: the edit wins over the names, as it did before the names
371/// were kept), and a slot naming something the options do not know (a
372/// parser build must not mint a token).
373pub(crate) fn resolved_slot(alt: &AltSpec, slot: usize, options: &Options) -> Option<Vec<Tin>> {
374    let names = alt.s_names.get(slot)?;
375    if names.is_empty() || alt.s.get(slot) != alt.s_bound.get(slot) {
376        return None;
377    }
378    let mut tins = Vec::with_capacity(names.len());
379    for name in names {
380        if let Some(set) = options.token_set.get(name.trim_start_matches('#')) {
381            tins.extend(set.iter().copied());
382        } else {
383            tins.push(options.token(name)?);
384        }
385    }
386    Some(tins)
387}
388
389/// Resolve every slot that was declared by name against `options`
390/// (`resolved_slot`).
391fn resolve_slot_names(spec: &mut RuleSpec, options: &Options) {
392    for alt in spec.open.iter_mut().chain(spec.close.iter_mut()) {
393        for slot in 0..alt.s.len() {
394            if let Some(tins) = resolved_slot(alt, slot, options) {
395                alt.s[slot] = tins;
396            }
397        }
398    }
399}
400
401/// Alternates indexed by the tin they can take at position 0.
402///
403/// `by_tin` maps each tin some alternate names at position 0 to the
404/// ascending indices of the alternates naming it; `wild` holds the
405/// alternates that constrain nothing at position 0 (an empty sequence,
406/// an empty slot, or a slot naming `#AA`, exactly the slots
407/// `slot_matches` accepts every tin for), which are candidates for every
408/// tin. The step walks the two lists together in index order, so the
409/// candidates for a tin are the original scan with the alternates that
410/// cannot take it left out, and first-match-wins is preserved. The lists
411/// stay separate rather than being merged per tin: a rule with W
412/// wildcard alternates and T distinct first tins would otherwise cost
413/// W×T entries.
414#[derive(Debug, Default)]
415struct AltIndex {
416    by_tin: HashMap<Tin, Vec<usize>>,
417    wild: Vec<usize>,
418}
419
420const NO_ALTS: &[usize] = &[];
421
422impl AltIndex {
423    fn of(alts: &[AltSpec]) -> Self {
424        let mut by_tin: HashMap<Tin, Vec<usize>> = HashMap::new();
425        let mut wild = Vec::new();
426        for (idx, alt) in alts.iter().enumerate() {
427            match alt.s.first() {
428                Some(slot) if !slot.is_empty() && !slot.contains(&TIN_AA) => {
429                    for tin in slot {
430                        let list = by_tin.entry(*tin).or_default();
431                        // A slot naming one tin twice must not try the
432                        // alternate twice: a condition could observe it.
433                        if list.last() != Some(&idx) {
434                            list.push(idx);
435                        }
436                    }
437                }
438                _ => wild.push(idx),
439            }
440        }
441        AltIndex { by_tin, wild }
442    }
443
444    /// The ascending alternates naming `tin` at position 0; the
445    /// wildcards are `wild`, walked beside them.
446    fn named(&self, tin: Tin) -> &[usize] {
447        self.by_tin.get(&tin).map_or(NO_ALTS, Vec::as_slice)
448    }
449}
450
451#[derive(Debug, Default)]
452struct ExpectedTins {
453    open: Vec<Vec<Tin>>,
454    close: Vec<Vec<Tin>>,
455}
456
457impl ExpectedTins {
458    /// Collated over the alternates `options` enable, as TypeScript's
459    /// `tcol` is: `filterRules` has removed the others from the spec
460    /// before `norm()` collates it. An excluded alternate is never tried,
461    /// so it must not decide which matchers run in the position-expected
462    /// pass either. It did until 0.12.4, and a grammar that excludes a
463    /// group while redefining a set that group's alternates name had the
464    /// set's members expected where the grammar never takes them (toml
465    /// excludes jsonic and sets `KEY` to `#ST #ID`, and its `val` came to
466    /// expect `#ID`, whose matcher then claimed every number).
467    ///
468    /// The filters are the ones in force at install, which the parser
469    /// records as `prepared_include` and `prepared_exclude`. Both are
470    /// public options a caller may rewrite afterwards, and the step then
471    /// chooses among the alternates the live filters enable, so
472    /// `Parser::expected_match_tins` reads this table only while the live
473    /// filters are still the recorded ones, and collates from them with
474    /// `live` otherwise.
475    fn of(spec: &RuleSpec, options: &Options) -> Self {
476        Self {
477            open: Self::by_slot(&spec.open, options),
478            close: Self::by_slot(&spec.close, options),
479        }
480    }
481
482    fn by_slot(alts: &[AltSpec], options: &Options) -> Vec<Vec<Tin>> {
483        let alts: Vec<&AltSpec> = alts
484            .iter()
485            .filter(|alt| groups_enabled(alt, options))
486            .collect();
487        let slots = alts.iter().map(|alt| alt.s.len()).max().unwrap_or(0);
488        (0..slots)
489            .map(|slot| Self::collate(alts.iter().copied(), slot))
490            .collect()
491    }
492
493    /// One slot's row, collated from the alternates `options` enable now
494    /// rather than the ones they enabled at install.
495    fn live(alts: &[AltSpec], options: &Options, slot: usize) -> Vec<Tin> {
496        Self::collate(alts.iter().filter(|alt| groups_enabled(alt, options)), slot)
497    }
498
499    fn collate<'a>(alts: impl Iterator<Item = &'a AltSpec>, slot: usize) -> Vec<Tin> {
500        let mut expected = BTreeSet::new();
501        for alt in alts {
502            if let Some(tins) = alt.s.get(slot) {
503                expected.extend(tins.iter().copied());
504            }
505        }
506        expected.into_iter().collect()
507    }
508
509    fn at(&self, is_open: bool, slot: usize) -> &[Tin] {
510        let slots = if is_open { &self.open } else { &self.close };
511        slots.get(slot).map(Vec::as_slice).unwrap_or_default()
512    }
513}
514
515impl Parser {
516    pub fn new(options: Options) -> Self {
517        let mut options = options;
518        options.sort_for_lexing();
519        Self::from_shared(Arc::new(options))
520    }
521
522    /// Parse against a configuration that is already prepared and
523    /// ordered, shared with every other parse of the same grammar.
524    pub fn from_shared(options: Arc<Options>) -> Self {
525        if let Err(error) = options.validate_comment_definitions() {
526            panic!("invalid options: {error}");
527        }
528        Parser {
529            ignore_tins: options.ignore_tins(),
530            exclude_regex: compile_number_exclude(&options),
531            exclude_pattern: options.number.exclude.clone(),
532            options,
533            rules: IndexMap::new(),
534            prepared_include: String::new(),
535            prepared_exclude: String::new(),
536            expected_tins: Vec::new(),
537            names: Vec::new(),
538            prepared: Vec::new(),
539            actions: HashMap::new(),
540            context_actions: HashMap::new(),
541            matched_actions: HashMap::new(),
542            state_actions: HashMap::new(),
543            token_subscribers: Vec::new(),
544            lex_subscribers: Vec::new(),
545            rule_subscribers: Vec::new(),
546            rule_done_subscribers: Vec::new(),
547            parse_guards: Vec::new(),
548            instance: InstanceInfo::default(),
549        }
550    }
551
552    /// The installed rules, in declaration order.
553    ///
554    /// Read-only: every write goes through [`Parser::add_rule`], which is
555    /// what keeps the derived tables in step with it.
556    pub fn rules(&self) -> &IndexMap<String, Arc<RuleSpec>> {
557        &self.rules
558    }
559
560    pub fn add_rule(&mut self, spec: RuleSpec) {
561        // A slot declared by name is resolved against the options this
562        // parser is built with, so a token set overridden after the rule
563        // was installed reaches it (tabnas/parser#217). The parser is
564        // rebuilt whenever the options change, which is what makes this
565        // the late binding TypeScript's `norm()` and Go's `altS` provide.
566        let mut spec = spec;
567        resolve_slot_names(&mut spec, &self.options);
568        // `rebuild_prepared` below records the live filters as the ones
569        // every accepted-token row was collated against, so a row collated
570        // under filters rewritten since then is collated again first. Only
571        // a rewrite between two installs reaches this.
572        if self.options.rule.include != self.prepared_include
573            || self.options.rule.exclude != self.prepared_exclude
574        {
575            for (row, installed) in self.expected_tins.iter_mut().zip(self.rules.values()) {
576                *row = ExpectedTins::of(installed, &self.options);
577            }
578        }
579        let expected = ExpectedTins::of(&spec, &self.options);
580        let shared = RuleName::from(spec.name.as_str());
581        let (index, _) = self.rules.insert_full(spec.name.clone(), Arc::new(spec));
582        // A replacement keeps the key's index, so it overwrites its own
583        // name handle; a new rule is appended and takes the next slot. The
584        // accepted-token table is positional for the same reason and is
585        // written here, not in `rebuild_prepared`: it is derived from one
586        // rule's spec alone, so rebuilding it for every rule on every
587        // install would be quadratic for no gain.
588        match self.names.get_mut(index) {
589            Some(existing) => *existing = shared,
590            None => self.names.push(shared),
591        }
592        match self.expected_tins.get_mut(index) {
593            Some(existing) => *existing = expected,
594            None => self.expected_tins.push(expected),
595        }
596        self.rebuild_prepared();
597    }
598
599    /// Rebuild the prepared table for every installed rule.
600    ///
601    /// Every rule, not just the one just installed: routes point across the
602    /// grammar, so a rule installed now can be the destination an earlier
603    /// rule named and could not resolve, and a replaced rule is a new
604    /// `Arc<RuleSpec>` that every route into it has to start handing out.
605    /// That makes installing a grammar quadratic in its size, which for the
606    /// grammars this engine runs -- tens of rules, installed once -- is
607    /// nothing next to a single parse, and it is the only shape that stays
608    /// right for a `Parser` that has rules added to it after it has already
609    /// parsed something.
610    fn rebuild_prepared(&mut self) {
611        let mut prepared = Vec::with_capacity(self.rules.len());
612        for (index, spec) in self.rules.values().enumerate() {
613            prepared.push(PreparedRule {
614                name: self.names[index].clone(),
615                spec: Arc::clone(spec),
616                open: Self::prepared_alts(&spec.open, &self.rules, &self.names, &self.options),
617                close: Self::prepared_alts(&spec.close, &self.rules, &self.names, &self.options),
618                open_first: AltIndex::of(&spec.open),
619                close_first: AltIndex::of(&spec.close),
620                bo: resolved_action_order(
621                    &spec.bo,
622                    &spec.bo_fns,
623                    &spec.bo_state_fns,
624                    &spec.bo_order,
625                ),
626                ao: resolved_action_order(
627                    &spec.ao,
628                    &spec.ao_fns,
629                    &spec.ao_state_fns,
630                    &spec.ao_order,
631                ),
632                bc: resolved_action_order(
633                    &spec.bc,
634                    &spec.bc_fns,
635                    &spec.bc_state_fns,
636                    &spec.bc_order,
637                ),
638                ac: resolved_action_order(
639                    &spec.ac,
640                    &spec.ac_fns,
641                    &spec.ac_state_fns,
642                    &spec.ac_order,
643                ),
644            });
645        }
646        self.prepared = prepared;
647        self.prepared_include.clone_from(&self.options.rule.include);
648        self.prepared_exclude.clone_from(&self.options.rule.exclude);
649    }
650
651    fn prepared_alts(
652        alts: &[AltSpec],
653        rules: &IndexMap<String, Arc<RuleSpec>>,
654        names: &[RuleName],
655        options: &Options,
656    ) -> Vec<PreparedAlt> {
657        alts.iter()
658            .map(|alt| {
659                // The same normalisation the step used to run: a grammar
660                // that appended to `a` or `action_fns` after the builder
661                // last touched `action_order` gets the fallback chain built
662                // for it here instead of there.
663                let actions = resolved_alt_action_order(
664                    &alt.a,
665                    &alt.action_fns,
666                    &alt.matched_action_fns,
667                    &alt.action_order,
668                );
669                let observed = alt.c_match.is_some()
670                    || alt.c_lex_match.is_some()
671                    || alt.e_match.is_some()
672                    || alt.p_match.is_some()
673                    || alt.r_match.is_some()
674                    || alt.b_match.is_some()
675                    || alt.h_match.is_some()
676                    || actions
677                        .iter()
678                        .any(|binding| matches!(binding, AltActionBinding::Matched(_)));
679                let named = actions
680                    .iter()
681                    .any(|binding| matches!(binding, AltActionBinding::Named(_)));
682                PreparedAlt {
683                    p: Self::prepared_route(alt.p.as_deref(), rules, names),
684                    r: Self::prepared_route(alt.r.as_deref(), rules, names),
685                    actions,
686                    observed,
687                    named,
688                    groups: groups_enabled(alt, options),
689                    group_tags: listed(&alt.g).map(str::to_owned).collect(),
690                }
691            })
692            .collect()
693    }
694
695    fn prepared_route(
696        route: Option<&str>,
697        rules: &IndexMap<String, Arc<RuleSpec>>,
698        names: &[RuleName],
699    ) -> PreparedRoute {
700        // An empty route name is "no route" at the transition, so it is not
701        // one here either.
702        let Some(route) = route.filter(|route| !route.is_empty()) else {
703            return PreparedRoute::ByName;
704        };
705        match rules.get_full(route) {
706            Some((slot, _, spec)) => PreparedRoute::Static {
707                slot,
708                name: names[slot].clone(),
709                spec: Arc::clone(spec),
710            },
711            None => PreparedRoute::ByName,
712        }
713    }
714
715    /// Resolve a rule name to the shared name handle and the installed
716    /// spec in one lookup.
717    ///
718    /// Every route into a rule needs both, and used to hash the same
719    /// three-byte name three times over to get them: once to ask whether
720    /// the rule existed, once for the shared handle, once for the spec.
721    /// `get_full` answers all three at once -- absence, the index the
722    /// handle sits at, and the spec. The index is also the rule's slot in
723    /// the prepared table, which the rule carries so that the next step can
724    /// find its state without asking again.
725    fn installed(&self, name: &str) -> Option<(usize, RuleName, &Arc<RuleSpec>)> {
726        let (index, _, spec) = self.rules.get_full(name)?;
727        Some((index, self.names[index].clone(), spec))
728    }
729
730    pub fn add_action(&mut self, name: String, action: Action) {
731        self.actions.insert(name, action);
732    }
733
734    pub fn add_context_action(&mut self, name: String, action: ContextAction) {
735        self.context_actions.insert(name, action);
736    }
737
738    pub fn add_matched_action(&mut self, name: String, action: AltAction) {
739        self.matched_actions.insert(name, action);
740    }
741
742    pub fn add_state_action(&mut self, name: String, action: StateAction) {
743        self.state_actions.insert(name, action);
744    }
745
746    pub fn add_token_subscriber(&mut self, subscriber: TokenSubscriber) {
747        self.token_subscribers.push(subscriber);
748    }
749
750    pub fn add_lex_subscriber(&mut self, subscriber: LexSubscriber) {
751        self.lex_subscribers.push(subscriber);
752    }
753
754    pub fn add_rule_subscriber(&mut self, subscriber: RuleSubscriber) {
755        self.rule_subscribers.push(subscriber);
756    }
757
758    pub fn add_parse_guard(&mut self, guard: ParseGuard) {
759        self.parse_guards.push(guard);
760    }
761
762    pub fn add_rule_done_subscriber(&mut self, subscriber: RuleDoneSubscriber) {
763        self.rule_done_subscribers.push(subscriber);
764    }
765
766    pub fn set_instance_info(&mut self, instance: InstanceInfo) {
767        self.instance = instance;
768    }
769
770    fn run_action(
771        &self,
772        name: &str,
773        rule: &mut Rule,
774        context: &mut Context,
775    ) -> Result<(), TabnasError> {
776        self.run_action_with_config(name, rule, context, None)
777    }
778
779    fn run_after_actions(
780        &self,
781        spec: &RuleSpec,
782        prepared: Option<&PreparedRule>,
783        is_open: bool,
784        rule: &mut Rule,
785        context: &mut Context,
786        site: ParseSite<'_>,
787    ) -> Result<(), TabnasError> {
788        if (is_open && !rule.ao) || (!is_open && !rule.ac) {
789            return Ok(());
790        }
791        // `ao`/`ac` above are run control, not presence: a rule carries
792        // them set whether or not it declares an after action. The order
793        // itself says whether there is anything to run, and when there is
794        // not the phase costs nothing -- not the list, and not the
795        // reference count on the next rule the state callbacks would have
796        // been handed.
797        let by_spec;
798        let bindings: &[ActionBinding] = match prepared {
799            Some(prepared) => prepared.after(is_open),
800            None => {
801                let (actions, callbacks, states, order) = if is_open {
802                    (&spec.ao, &spec.ao_fns, &spec.ao_state_fns, &spec.ao_order)
803                } else {
804                    (&spec.ac, &spec.ac_fns, &spec.ac_state_fns, &spec.ac_order)
805                };
806                by_spec = resolved_action_order(actions, callbacks, states, order);
807                &by_spec
808            }
809        };
810        if bindings.is_empty() {
811            return Ok(());
812        }
813        let next = rule.next_rule.clone();
814        let mut output = None;
815        for binding in bindings {
816            output = match binding {
817                ActionBinding::Named(action) => {
818                    if let Some(callback) = self.state_actions.get(action) {
819                        self.run_state_callback(
820                            "named lifecycle after action",
821                            callback,
822                            rule,
823                            context,
824                            next.as_deref(),
825                            output,
826                        )
827                        .map_err(|error| {
828                            self.attach_action_error(
829                                error,
830                                site.source,
831                                rule,
832                                site.stack,
833                                site.alts,
834                            )
835                        })?
836                    } else {
837                        self.run_action(action, rule, context).map_err(|error| {
838                            self.attach_action_error(
839                                error,
840                                site.source,
841                                rule,
842                                site.stack,
843                                site.alts,
844                            )
845                        })?;
846                        None
847                    }
848                }
849                ActionBinding::Callback(callback) => {
850                    self.run_context_callback("lifecycle after action", callback, rule, context)
851                        .map_err(|error| {
852                            self.attach_action_error(
853                                error,
854                                site.source,
855                                rule,
856                                site.stack,
857                                site.alts,
858                            )
859                        })?;
860                    None
861                }
862                ActionBinding::State(callback) => self
863                    .run_state_callback(
864                        "lifecycle after action",
865                        callback,
866                        rule,
867                        context,
868                        next.as_deref(),
869                        output,
870                    )
871                    .map_err(|error| {
872                        self.attach_action_error(error, site.source, rule, site.stack, site.alts)
873                    })?,
874            };
875            output = self.check_lifecycle_output(output, rule, site)?;
876        }
877        Ok(())
878    }
879
880    fn run_context_callback(
881        &self,
882        label: &str,
883        callback: &ContextAction,
884        rule: &mut Rule,
885        context: &mut Context,
886    ) -> Result<(), TabnasError> {
887        context.set_rule(rule);
888        match catch_unwind(AssertUnwindSafe(|| callback(rule, context))) {
889            Ok(result) => result.map_err(|action_error| {
890                let token = match rule.state {
891                    RuleState::Open => rule.o0().or_else(|| rule.c0()),
892                    RuleState::Close => rule.c0().or_else(|| rule.o0()),
893                };
894                let mut error = TabnasError::new(
895                    action_error.code,
896                    token.map_or("", |value| value.src.as_str()),
897                    "",
898                    token.map_or(0, |value| value.site.pos),
899                    token.map_or(1, |value| value.site.ri),
900                    token.map_or(1, |value| value.site.ci),
901                );
902                error.detail = action_error.detail;
903                error
904            }),
905            Err(payload) => Err(self.action_panic(payload, label, rule)),
906        }
907    }
908
909    fn run_state_callback(
910        &self,
911        label: &str,
912        callback: &StateAction,
913        rule: &mut Rule,
914        context: &mut Context,
915        next: Option<&RuleSnapshot>,
916        out: Option<Token>,
917    ) -> Result<Option<Token>, TabnasError> {
918        context.set_rule(rule);
919        match catch_unwind(AssertUnwindSafe(|| callback(rule, context, next, out))) {
920            Ok(result) => result.map_err(|action_error| {
921                let token = match rule.state {
922                    RuleState::Open => rule.o0().or_else(|| rule.c0()),
923                    RuleState::Close => rule.c0().or_else(|| rule.o0()),
924                };
925                let mut error = TabnasError::new(
926                    action_error.code,
927                    token.map_or("", |value| value.src.as_str()),
928                    "",
929                    token.map_or(0, |value| value.site.pos),
930                    token.map_or(1, |value| value.site.ri),
931                    token.map_or(1, |value| value.site.ci),
932                );
933                error.detail = action_error.detail;
934                error
935            }),
936            Err(payload) => Err(self.action_panic(payload, label, rule)),
937        }
938    }
939
940    fn check_lifecycle_output(
941        &self,
942        output: Option<Token>,
943        rule: &Rule,
944        site: ParseSite<'_>,
945    ) -> Result<Option<Token>, TabnasError> {
946        let Some(token) = output.as_ref().filter(|token| !token.err.is_empty()) else {
947            return Ok(output);
948        };
949        Err(self.raised_token_error(token, rule, site))
950    }
951
952    fn raised_token_error(&self, token: &Token, rule: &Rule, site: ParseSite<'_>) -> TabnasError {
953        let error = TabnasError::new(
954            raised_error_code(token),
955            token.src.clone(),
956            site.source,
957            token.site.pos,
958            token.site.ri,
959            token.site.ci,
960        );
961        self.attach_error(error, rule, site.stack, site.alts, Some(token))
962    }
963
964    fn attach_action_error(
965        &self,
966        mut error: TabnasError,
967        src: &str,
968        rule: &Rule,
969        stack: &[Rule],
970        alts: &[AltSpec],
971    ) -> TabnasError {
972        error.full_source = src.into();
973        let token = match rule.state {
974            RuleState::Open => rule.o0().or_else(|| rule.c0()),
975            RuleState::Close => rule.c0().or_else(|| rule.o0()),
976        };
977        self.attach_error(error, rule, stack, alts, token)
978    }
979
980    fn run_action_with_config(
981        &self,
982        name: &str,
983        rule: &mut Rule,
984        context: &mut Context,
985        config: Option<&Value>,
986    ) -> Result<(), TabnasError> {
987        context.set_rule(rule);
988        match catch_unwind(AssertUnwindSafe(|| {
989            run_builtin_action_with_info(name, rule, context, config, &self.options.info)
990        })) {
991            Ok(true) => return Ok(()),
992            Ok(false) => {}
993            Err(payload) => return Err(self.action_panic(payload, name, rule)),
994        }
995        if let Some(action) = self.actions.get(name) {
996            return match catch_unwind(AssertUnwindSafe(|| action(rule))) {
997                Ok(()) => Ok(()),
998                Err(payload) => Err(self.action_panic(payload, name, rule)),
999            };
1000        }
1001        if let Some(action) = self.context_actions.get(name) {
1002            return self.run_context_callback(name, action, rule, context);
1003        }
1004        let token = match rule.state {
1005            RuleState::Open => rule.o0().or_else(|| rule.c0()),
1006            RuleState::Close => rule.c0().or_else(|| rule.o0()),
1007        };
1008        let mut error = TabnasError::new(
1009            "unknown",
1010            name,
1011            "",
1012            token.map_or(0, |value| value.site.pos),
1013            token.map_or(1, |value| value.site.ri),
1014            token.map_or(1, |value| value.site.ci),
1015        );
1016        error.detail = format!("unknown action: {name}");
1017        Err(error)
1018    }
1019
1020    fn action_panic(
1021        &self,
1022        payload: Box<dyn std::any::Any + Send>,
1023        name: &str,
1024        rule: &Rule,
1025    ) -> TabnasError {
1026        let token = match rule.state {
1027            RuleState::Open => rule.o0().or_else(|| rule.c0()),
1028            RuleState::Close => rule.c0().or_else(|| rule.o0()),
1029        };
1030        TabnasError::from_panic(
1031            payload,
1032            &format!("action {name}"),
1033            "",
1034            token.map_or(0, |value| value.site.pos),
1035            token.map_or(1, |value| value.site.ri),
1036            token.map_or(1, |value| value.site.ci),
1037            &self.options,
1038        )
1039    }
1040
1041    fn attach_error(
1042        &self,
1043        mut error: TabnasError,
1044        rule: &Rule,
1045        stack: &[Rule],
1046        alts: &[AltSpec],
1047        token: Option<&Token>,
1048    ) -> TabnasError {
1049        let mut rule_stack: Vec<String> = stack.iter().map(|item| item.name.to_string()).collect();
1050        rule_stack.push(rule.name.to_string());
1051        let expected = alts
1052            .iter()
1053            .filter_map(|alt| alt.s.first())
1054            .flat_map(|tins| tins.iter().copied())
1055            .map(|tin| self.options.token_name(tin))
1056            .collect();
1057        error.attach_context(
1058            &rule.name,
1059            if rule.state == RuleState::Open {
1060                "o"
1061            } else {
1062                "c"
1063            },
1064            rule_stack,
1065            token,
1066            expected,
1067        );
1068        self.decorate_error(&mut error);
1069        error
1070    }
1071
1072    fn decorate_error(&self, error: &mut TabnasError) {
1073        error.apply_options(&self.options);
1074        error.plugins = self.instance.plugins.clone();
1075    }
1076
1077    fn catch_callback<T>(
1078        &self,
1079        api: &str,
1080        src: &str,
1081        callback: impl FnOnce() -> T,
1082    ) -> Result<T, TabnasError> {
1083        catch_unwind(AssertUnwindSafe(callback))
1084            .map_err(|payload| TabnasError::from_panic(payload, api, src, 0, 1, 1, &self.options))
1085    }
1086
1087    /// The `cancel` error a budget or a guard stops the parse with, at the
1088    /// token about to be read.
1089    fn cancelled(&self, src: &str, context: &Context, rule: &Rule, stack: &[Rule]) -> TabnasError {
1090        let token = context.t.first();
1091        let pnt = token
1092            .map(|token| {
1093                (
1094                    token.src.as_str(),
1095                    token.site.pos,
1096                    token.site.ri,
1097                    token.site.ci,
1098                )
1099            })
1100            .unwrap_or(("", 0, 1, 1));
1101        let error = TabnasError::new("cancel", pnt.0, src, pnt.1, pnt.2, pnt.3);
1102        self.attach_active_error(error, rule, stack, token)
1103    }
1104
1105    fn attach_active_error(
1106        &self,
1107        mut error: TabnasError,
1108        rule: &Rule,
1109        stack: &[Rule],
1110        token: Option<&Token>,
1111    ) -> TabnasError {
1112        if let Some(spec) = self.rules.get(&*rule.name) {
1113            let alts = if rule.state == RuleState::Open {
1114                &spec.open
1115            } else {
1116                &spec.close
1117            };
1118            self.attach_error(error, rule, stack, alts, token)
1119        } else {
1120            self.decorate_error(&mut error);
1121            error
1122        }
1123    }
1124
1125    fn phase_token(rule: &Rule) -> Option<&Token> {
1126        match rule.state {
1127            RuleState::Open => rule.o0().or_else(|| rule.c0()),
1128            RuleState::Close => rule.c0().or_else(|| rule.o0()),
1129        }
1130    }
1131
1132    fn ancestors_for<'a>(rule: &Rule, stack: &'a [Rule]) -> &'a [Rule] {
1133        if stack.last().is_some_and(|ancestor| ancestor.i == rule.i) {
1134            &stack[..stack.len() - 1]
1135        } else {
1136            stack
1137        }
1138    }
1139
1140    /// A copy of the rule that just finished, when something will read it.
1141    ///
1142    /// [`Parser::notify_rule_done`] is its only reader, and that returns
1143    /// without looking when no `ruleDone` subscriber is installed. Cloning
1144    /// a `Rule` copies the value tree it has built with it, which is 3.3%
1145    /// of a 1 MB parse spent for nobody in a grammar that subscribes to
1146    /// nothing. This is the treatment `RuleDoneAlt` already gets at the
1147    /// transition arms, for the same reason.
1148    fn rule_done_copy(&self, rule: &Rule) -> Option<Rule> {
1149        (!self.rule_done_subscribers.is_empty()).then(|| rule.clone())
1150    }
1151
1152    fn notify_rule_done(
1153        &self,
1154        rule: &Rule,
1155        context: &Context,
1156        state: RuleState,
1157        alt: Option<RuleDoneAlt>,
1158        src: &str,
1159        stack: &[Rule],
1160    ) -> Result<(), TabnasError> {
1161        if self.rule_done_subscribers.is_empty() {
1162            return Ok(());
1163        }
1164        let done = RuleDone {
1165            state,
1166            alt,
1167            forced: false,
1168        };
1169        let mut site_rule = rule.clone();
1170        site_rule.state = state;
1171        for subscriber in &self.rule_done_subscribers {
1172            let result = self.catch_callback("ruleDone subscriber", src, || {
1173                subscriber(rule, context, &done)
1174            });
1175            result.map_err(|error| {
1176                self.attach_active_error(
1177                    error,
1178                    &site_rule,
1179                    Self::ancestors_for(&site_rule, stack),
1180                    Self::phase_token(&site_rule),
1181                )
1182            })?;
1183        }
1184        Ok(())
1185    }
1186
1187    fn notify_forced_close(
1188        &self,
1189        rule: &Rule,
1190        context: &Context,
1191        src: &str,
1192        stack: &[Rule],
1193    ) -> Result<(), TabnasError> {
1194        if self.rule_done_subscribers.is_empty() {
1195            return Ok(());
1196        }
1197        let done = RuleDone {
1198            state: RuleState::Close,
1199            alt: None,
1200            forced: true,
1201        };
1202        let mut site_rule = rule.clone();
1203        site_rule.state = RuleState::Close;
1204        for subscriber in &self.rule_done_subscribers {
1205            let result = self.catch_callback("ruleDone subscriber", src, || {
1206                subscriber(rule, context, &done)
1207            });
1208            result.map_err(|error| {
1209                self.attach_active_error(
1210                    error,
1211                    &site_rule,
1212                    Self::ancestors_for(&site_rule, stack),
1213                    Self::phase_token(&site_rule),
1214                )
1215            })?;
1216        }
1217        Ok(())
1218    }
1219
1220    fn attempt_recover(
1221        &self,
1222        error: TabnasError,
1223        current_rule: &mut Rule,
1224        stack: &mut Vec<Rule>,
1225        context: &mut Context,
1226        lexer: &mut Lexer,
1227        mode: &mut ParseMode<'_>,
1228    ) -> Result<bool, TabnasError> {
1229        let src = error.full_source.clone();
1230        let recover = &self.options.parse.recover;
1231
1232        // The order is TypeScript's `attemptRecover`: the error is recorded
1233        // as it is built, dropped again when it falls inside the suppress
1234        // window of the recovery before it, and only then is the cap read.
1235        // So a give-up leaves the error listed, once and without recovery
1236        // metadata, unless it was a cascade; and a cascade at the cap still
1237        // recovers, since dropping it keeps the list within the cap.
1238        let suppressed = context
1239            .recover_at
1240            .is_some_and(|at| context.v_abs.saturating_sub(at) < recover.suppress);
1241        let recorded = (!suppressed).then(|| {
1242            mode.errors.push(error.clone());
1243            context.errs.push(error.clone());
1244            mode.errors.len() - 1
1245        });
1246        if recover.max_recoveries < mode.errors.len() {
1247            return Ok(false);
1248        }
1249
1250        let no_progress = context.recover_at == Some(context.v_abs);
1251        let last_si = context.recover_si;
1252        context.recover_at = Some(context.v_abs);
1253
1254        let sync = compute_sync_tins(current_rule, stack, &self.rules, &self.options);
1255        let mut pending: std::collections::VecDeque<Token> = std::mem::take(&mut context.t).into();
1256        let mut skipped = 0usize;
1257
1258        let candidate = loop {
1259            let next = if let Some(token) = pending.pop_front() {
1260                Some(token)
1261            } else {
1262                loop {
1263                    let next_raw = self.catch_callback("lexer callback", &src, || {
1264                        lexer.next_raw_for_rule(current_rule, context)
1265                    });
1266                    let next_raw = next_raw.map_err(|error| {
1267                        self.attach_active_error(
1268                            error,
1269                            current_rule,
1270                            stack,
1271                            Self::phase_token(current_rule),
1272                        )
1273                    })?;
1274                    match next_raw {
1275                        Ok(mut token) => {
1276                            for subscriber in &self.lex_subscribers {
1277                                let result = self.catch_callback("lex subscriber", &src, || {
1278                                    subscriber(&mut token, current_rule, context)
1279                                });
1280                                result.map_err(|error| {
1281                                    self.attach_active_error(
1282                                        error,
1283                                        current_rule,
1284                                        stack,
1285                                        Some(&token),
1286                                    )
1287                                })?;
1288                            }
1289                            if self.ignore_tins.contains(&token.tin) {
1290                                continue;
1291                            }
1292                            for subscriber in &self.token_subscribers {
1293                                let result = self.catch_callback("token subscriber", &src, || {
1294                                    subscriber(&token)
1295                                });
1296                                result.map_err(|error| {
1297                                    self.attach_active_error(
1298                                        error,
1299                                        current_rule,
1300                                        stack,
1301                                        Some(&token),
1302                                    )
1303                                })?;
1304                            }
1305                            break Some(token);
1306                        }
1307                        Err(lex_error) => {
1308                            let mut token = error_token(&lex_error);
1309                            for subscriber in &self.lex_subscribers {
1310                                let result = self.catch_callback("lex subscriber", &src, || {
1311                                    subscriber(&mut token, current_rule, context)
1312                                });
1313                                result.map_err(|error| {
1314                                    self.attach_active_error(
1315                                        error,
1316                                        current_rule,
1317                                        stack,
1318                                        Some(&token),
1319                                    )
1320                                })?;
1321                            }
1322                            lexer.skip_bad(&token, mid_construct(&lex_error.code));
1323                            if skipped >= recover.max_skip {
1324                                break None;
1325                            }
1326                            skipped += 1;
1327                        }
1328                    }
1329                }
1330            };
1331            let Some(token) = next else {
1332                return Ok(false);
1333            };
1334            if token.tin == TIN_ZZ
1335                || (sync.contains(&token.tin)
1336                    && !(no_progress && last_si.is_some_and(|si| token.site.pos <= si)))
1337            {
1338                break token;
1339            }
1340            // A bad token is skipped like any other, but it did not move
1341            // the cursor when it was cut: a custom matcher's `Lexer::bad`
1342            // leaves it where it was, and so does one buffered under
1343            // relexing. Step past it, as TypeScript's `advanceLexPast` does,
1344            // or the next fetch would cut the same bad token again.
1345            if token.tin == TIN_BD {
1346                lexer.skip_bad(&token, mid_construct(&token.why));
1347            }
1348            if skipped >= recover.max_skip {
1349                return Ok(false);
1350            }
1351            skipped += 1;
1352        };
1353
1354        if candidate.tin == TIN_ZZ
1355            && no_progress
1356            && last_si.is_some_and(|si| candidate.site.pos <= si)
1357        {
1358            return Ok(false);
1359        }
1360        context.recover_si = Some(candidate.site.pos);
1361        if let Some(index) = recorded {
1362            let recovered = Some(crate::RecoveredAt {
1363                skipped,
1364                sync: Some(candidate.tin),
1365                bad: false,
1366            });
1367            if let Some(entry) = context.errs.get_mut(index) {
1368                entry.recovered.clone_from(&recovered);
1369            }
1370            if let Some(entry) = mode.errors.get_mut(index) {
1371                entry.recovered = recovered;
1372            }
1373        }
1374
1375        context.t.push(candidate.clone());
1376        context.t.extend(pending);
1377        context.bad_to = None;
1378        context.bad_error = None;
1379
1380        if !recover.pop_until_valid {
1381            // The only pop that resumes a parent WITHOUT accepting a
1382            // child node over the top of it, so the only one that would
1383            // see a parked `child_node`. `Rule::park_child_node` is
1384            // skipped for the whole parse when this option is off, so
1385            // nothing is parked to see.
1386            if let Some(parent) = stack.pop() {
1387                *current_rule = parent;
1388                return Ok(true);
1389            }
1390            return Ok(false);
1391        }
1392
1393        if accepts_close(current_rule, candidate.tin, &self.rules, &self.options) {
1394            if current_rule.state == RuleState::Open {
1395                current_rule.state = RuleState::Close;
1396            } else {
1397                current_rule.skip_befores = true;
1398            }
1399            return Ok(true);
1400        }
1401
1402        self.notify_forced_close(current_rule, context, &src, stack)?;
1403        while let Some(mut parent) = stack.pop() {
1404            parent.accept_child(current_rule);
1405            if accepts_close(&parent, candidate.tin, &self.rules, &self.options) {
1406                *current_rule = parent;
1407                return Ok(true);
1408            }
1409            self.notify_forced_close(&parent, context, &src, stack)?;
1410            *current_rule = parent;
1411        }
1412        Ok(false)
1413    }
1414
1415    #[allow(clippy::too_many_arguments)]
1416    fn recover_error_pass(
1417        &self,
1418        error: TabnasError,
1419        state: RuleState,
1420        mut alt: Option<RuleDoneAlt>,
1421        fallback_error_token: bool,
1422        src: &str,
1423        current_rule: &mut Rule,
1424        stack: &mut Vec<Rule>,
1425        context: &mut Context,
1426        lexer: &mut Lexer,
1427        mode: &mut ParseMode<'_>,
1428    ) -> Result<(), TabnasError> {
1429        // TypeScript's RuleSpec.bad performs recovery inside the rule pass;
1430        // the ordinary ruleDone event is dispatched only after that pass
1431        // returns. Preserve that ordering so any synthesized forced-close
1432        // events precede this final attempted-pass event.
1433        let event_rule = current_rule.clone();
1434        let recovered = mode.recovering
1435            && self.attempt_recover(error.clone(), current_rule, stack, context, lexer, mode)?;
1436        if !recovered && fallback_error_token {
1437            if let Some(alt) = alt.as_mut().filter(|alt| alt.err.is_none()) {
1438                let tin = self.options.token(&error.token.name).unwrap_or(TIN_BD);
1439                let mut token = Token::new(
1440                    error.token.name.clone(),
1441                    tin,
1442                    Value::Undefined,
1443                    error.token.src.clone(),
1444                    crate::Point {
1445                        len: error.len,
1446                        site: crate::Site {
1447                            si: error.pos,
1448                            pos: error.pos,
1449                            ri: error.row,
1450                            ci: error.col,
1451                        },
1452                    },
1453                );
1454                token.bad(&error.code);
1455                alt.err = Some(token);
1456            }
1457        }
1458        self.notify_rule_done(&event_rule, context, state, alt, src, stack)?;
1459        if recovered {
1460            Ok(())
1461        } else {
1462            // TypeScript's `RuleSpec.bad` gives up by rethrowing the last
1463            // error it recorded, which `attempt_recover` has just recorded
1464            // or dropped as a cascade, so the list is already complete. Only
1465            // an empty list makes it raise, and record, a fresh one.
1466            if mode.recovering && !mode.errors.is_empty() {
1467                mode.gave_up = true;
1468            }
1469            Err(error)
1470        }
1471    }
1472
1473    #[allow(clippy::too_many_arguments)]
1474    fn recover_after_actions(
1475        &self,
1476        result: Result<(), TabnasError>,
1477        state: RuleState,
1478        alt: Option<RuleDoneAlt>,
1479        src: &str,
1480        current_rule: &mut Rule,
1481        stack: &mut Vec<Rule>,
1482        context: &mut Context,
1483        lexer: &mut Lexer,
1484        mode: &mut ParseMode<'_>,
1485    ) -> Result<bool, TabnasError> {
1486        let Err(error) = result else {
1487            return Ok(false);
1488        };
1489        self.recover_error_pass(
1490            error,
1491            state,
1492            alt,
1493            true,
1494            src,
1495            current_rule,
1496            stack,
1497            context,
1498            lexer,
1499            mode,
1500        )?;
1501        Ok(true)
1502    }
1503
1504    pub fn parse(&self, src: &str) -> Result<Value, TabnasError> {
1505        self.parse_with_meta(src, Value::Undefined)
1506    }
1507
1508    pub fn parse_with_meta(&self, src: &str, meta: Value) -> Result<Value, TabnasError> {
1509        self.parse_with_owner(src, meta, None, None)
1510    }
1511
1512    pub(crate) fn parse_for(
1513        &self,
1514        owner: &crate::Tabnas,
1515        src: &str,
1516        meta: Value,
1517    ) -> Result<Value, TabnasError> {
1518        self.parse_with_owner(src, meta, Some(owner), None)
1519    }
1520
1521    pub(crate) fn parse_for_with_context(
1522        &self,
1523        owner: &crate::Tabnas,
1524        src: &str,
1525        meta: Value,
1526        parent: &ContextSeed,
1527    ) -> Result<Value, TabnasError> {
1528        self.parse_with_owner(src, meta, Some(owner), Some(parent))
1529    }
1530
1531    fn parse_with_owner(
1532        &self,
1533        src: &str,
1534        meta: Value,
1535        owner: Option<&crate::Tabnas>,
1536        parent: Option<&ContextSeed>,
1537    ) -> Result<Value, TabnasError> {
1538        match catch_unwind(AssertUnwindSafe(|| {
1539            self.parse_uncaught(src, meta, owner, parent)
1540        })) {
1541            Ok(result) => result,
1542            Err(payload) => {
1543                let mut error =
1544                    TabnasError::from_panic(payload, "Parser::parse", src, 0, 1, 1, &self.options);
1545                self.decorate_error(&mut error);
1546                Err(error)
1547            }
1548        }
1549    }
1550
1551    fn parse_uncaught(
1552        &self,
1553        src: &str,
1554        meta: Value,
1555        owner: Option<&crate::Tabnas>,
1556        parent: Option<&ContextSeed>,
1557    ) -> Result<Value, TabnasError> {
1558        if let Some(result) = self.run_parser_start(src, &meta, owner, parent) {
1559            return result.map_err(|mut error| {
1560                self.decorate_error(&mut error);
1561                error
1562            });
1563        }
1564        let mut errors = Vec::new();
1565        let recovering = self.options.parse.recover.enabled;
1566        let mut mode = ParseMode {
1567            continuation: None,
1568            recovering,
1569            errors: &mut errors,
1570            partial: None,
1571            gave_up: false,
1572        };
1573        let result = self
1574            .parse_inner(src, meta, owner, parent, &mut mode)
1575            .map_err(|mut error| {
1576                self.decorate_error(&mut error);
1577                error
1578            });
1579        match result {
1580            Err(_) if recovering => Ok(mode
1581                .partial
1582                .and_then(PartialValue::into_value)
1583                .unwrap_or(Value::Undefined)),
1584            other => other,
1585        }
1586    }
1587
1588    pub fn parse_recover(&self, src: &str) -> ParseRecovery {
1589        self.parse_recover_with_meta(src, Value::Undefined)
1590    }
1591
1592    pub fn parse_recover_with_meta(&self, src: &str, meta: Value) -> ParseRecovery {
1593        self.parse_recover_with_owner(src, meta, None, None)
1594    }
1595
1596    pub(crate) fn parse_recover_for(
1597        &self,
1598        owner: &crate::Tabnas,
1599        src: &str,
1600        meta: Value,
1601    ) -> ParseRecovery {
1602        self.parse_recover_with_owner(src, meta, Some(owner), None)
1603    }
1604
1605    pub(crate) fn parse_recover_for_with_context(
1606        &self,
1607        owner: &crate::Tabnas,
1608        src: &str,
1609        meta: Value,
1610        parent: &ContextSeed,
1611    ) -> ParseRecovery {
1612        self.parse_recover_with_owner(src, meta, Some(owner), Some(parent))
1613    }
1614
1615    fn parse_recover_with_owner(
1616        &self,
1617        src: &str,
1618        meta: Value,
1619        owner: Option<&crate::Tabnas>,
1620        parent: Option<&ContextSeed>,
1621    ) -> ParseRecovery {
1622        match catch_unwind(AssertUnwindSafe(|| {
1623            self.parse_recover_uncaught(src, meta, owner, parent)
1624        })) {
1625            Ok(result) => result,
1626            Err(payload) => {
1627                let mut error = TabnasError::from_panic(
1628                    payload,
1629                    "Parser::parse_recover",
1630                    src,
1631                    0,
1632                    1,
1633                    1,
1634                    &self.options,
1635                );
1636                self.decorate_error(&mut error);
1637                ParseRecovery {
1638                    value: None,
1639                    errors: Vec::new(),
1640                    fatal: Some(error),
1641                }
1642            }
1643        }
1644    }
1645
1646    fn parse_recover_uncaught(
1647        &self,
1648        src: &str,
1649        meta: Value,
1650        owner: Option<&crate::Tabnas>,
1651        parent: Option<&ContextSeed>,
1652    ) -> ParseRecovery {
1653        if let Some(result) = self.run_parser_start(src, &meta, owner, parent) {
1654            return match result {
1655                Ok(value) => ParseRecovery {
1656                    value: Some(value),
1657                    errors: Vec::new(),
1658                    fatal: None,
1659                },
1660                Err(mut error) => {
1661                    self.decorate_error(&mut error);
1662                    ParseRecovery {
1663                        value: None,
1664                        errors: Vec::new(),
1665                        fatal: Some(error),
1666                    }
1667                }
1668            };
1669        }
1670        let mut errors = Vec::new();
1671        let recovering = self.options.parse.recover.enabled;
1672        let (result, partial, gave_up) = {
1673            let mut mode = ParseMode {
1674                continuation: None,
1675                recovering,
1676                errors: &mut errors,
1677                partial: None,
1678                gave_up: false,
1679            };
1680            let result = self
1681                .parse_inner(src, meta, owner, parent, &mut mode)
1682                .map_err(|mut error| {
1683                    self.decorate_error(&mut error);
1684                    error
1685                });
1686            (
1687                result,
1688                mode.partial.and_then(PartialValue::into_value),
1689                mode.gave_up,
1690            )
1691        };
1692        for error in &mut errors {
1693            self.decorate_error(error);
1694        }
1695        match result {
1696            Ok(value) => ParseRecovery {
1697                value: Some(value),
1698                errors,
1699                fatal: None,
1700            },
1701            Err(error) => {
1702                // The error that ended the parse is listed once, where the
1703                // parse ended, as TypeScript's constructor lists it, unless
1704                // recovery gave up on an error it had already accounted for.
1705                if !gave_up {
1706                    errors.push(error.clone());
1707                }
1708                ParseRecovery {
1709                    value: recovering.then_some(partial).flatten(),
1710                    errors,
1711                    fatal: (!recovering).then_some(error),
1712                }
1713            }
1714        }
1715    }
1716
1717    fn run_parser_start(
1718        &self,
1719        src: &str,
1720        meta: &Value,
1721        owner: Option<&crate::Tabnas>,
1722        parent: Option<&ContextSeed>,
1723    ) -> Option<Result<Value, TabnasError>> {
1724        let result = if let Some(start) = self.options.parser.start_with_context.as_ref() {
1725            let Some(owner) = owner else {
1726                let mut error = TabnasError::new("internal", "", src, 0, 1, 1);
1727                error.detail =
1728                    "parser.start requires an owning Tabnas instance; call Tabnas::parse".into();
1729                self.decorate_error(&mut error);
1730                return Some(Err(error));
1731            };
1732            catch_unwind(AssertUnwindSafe(|| start(src, owner, meta, parent)))
1733        } else if let Some(start) = self.options.parser.start_with_instance.as_ref() {
1734            let Some(owner) = owner else {
1735                let mut error = TabnasError::new("internal", "", src, 0, 1, 1);
1736                error.detail =
1737                    "parser.start requires an owning Tabnas instance; call Tabnas::parse".into();
1738                self.decorate_error(&mut error);
1739                return Some(Err(error));
1740            };
1741            catch_unwind(AssertUnwindSafe(|| start(src, owner, meta)))
1742        } else {
1743            let start = self.options.parser.start.as_ref()?;
1744            catch_unwind(AssertUnwindSafe(|| start(src)))
1745        };
1746        Some(match result {
1747            Ok(result) => result.map_err(|error| *error),
1748            Err(payload) => Err(TabnasError::from_panic(
1749                payload,
1750                "parser.start",
1751                src,
1752                0,
1753                1,
1754                1,
1755                &self.options,
1756            )),
1757        })
1758    }
1759
1760    /// Return the token kinds that can legally follow `src` when it is
1761    /// treated as a prefix. The result is an intentional over-approximation:
1762    /// runtime conditions and counters may still reject a listed token.
1763    pub fn continuations(&self, src: &str) -> Continuations {
1764        self.continuations_with_owner(src, None)
1765    }
1766
1767    pub(crate) fn continuations_for(&self, owner: &crate::Tabnas, src: &str) -> Continuations {
1768        self.continuations_with_owner(src, Some(owner))
1769    }
1770
1771    fn continuations_with_owner(&self, src: &str, owner: Option<&crate::Tabnas>) -> Continuations {
1772        catch_unwind(AssertUnwindSafe(|| self.continuations_uncaught(src, owner)))
1773            .unwrap_or_else(|_| self.start_continuations())
1774    }
1775
1776    fn continuations_uncaught(&self, src: &str, owner: Option<&crate::Tabnas>) -> Continuations {
1777        let mut capture = ContinuationCapture::default();
1778        let mut errors = Vec::new();
1779        let result = {
1780            let mut mode = ParseMode {
1781                continuation: Some(&mut capture),
1782                recovering: false,
1783                errors: &mut errors,
1784                partial: None,
1785                gave_up: false,
1786            };
1787            self.parse_inner(src, Value::Undefined, owner, None, &mut mode)
1788        };
1789        let mut tins = if result.is_ok() {
1790            if capture.have_end {
1791                capture.at_end.insert(TIN_ZZ);
1792                capture.at_end.into_iter().collect()
1793            } else {
1794                self.start_openers()
1795            }
1796        } else if capture.failure.is_empty() {
1797            self.start_openers()
1798        } else {
1799            capture.failure
1800        };
1801        tins.sort_unstable();
1802        tins.dedup();
1803        let tokens = tins
1804            .iter()
1805            .map(|tin| self.options.token_name(*tin))
1806            .collect();
1807        Continuations { tins, tokens }
1808    }
1809
1810    fn start_continuations(&self) -> Continuations {
1811        let tins = self.start_openers();
1812        let tokens = tins
1813            .iter()
1814            .map(|tin| self.options.token_name(*tin))
1815            .collect();
1816        Continuations { tins, tokens }
1817    }
1818
1819    fn start_openers(&self) -> Vec<Tin> {
1820        let start = self.rules.get(&self.options.rule.start);
1821        let mut out = BTreeSet::new();
1822        if let Some(spec) = start {
1823            for alt in &spec.open {
1824                if groups_enabled(alt, &self.options) {
1825                    if let Some(slot) = alt.s.first() {
1826                        out.extend(completion_tins(slot));
1827                    }
1828                }
1829            }
1830        }
1831        out.into_iter().collect()
1832    }
1833
1834    /// The tins the rule can accept at `slot`, as the lexer's custom-matcher
1835    /// gate wants them, or nothing when there is no custom matcher to gate.
1836    ///
1837    /// Both consumers (the `fix_len` filter and the expected-first pass in
1838    /// `Lexer::next_raw_inner`) only read this list to decide WHICH of
1839    /// `options.match_tokens` may fire; with no match tokens the answer is
1840    /// the same for any list, and the walk over an empty table yields
1841    /// nothing either way. The empty slice is therefore exact, and it
1842    /// spares every token fetch the rule's accepted-token row entirely --
1843    /// the last work that sat on the fetch path of a grammar with no
1844    /// custom matcher at all. TS `makeMatchMatcher`
1845    /// returns null on an empty table and never asks (ts/src/lexer.ts).
1846    ///
1847    /// `options` is the one `Arc<Options>` the lexer reads too, so this
1848    /// guard consults the field its consumers consult and cannot drift
1849    /// from it.
1850    ///
1851    /// The table was collated over the alternates the group filters
1852    /// enabled at install. `rule.include` and `rule.exclude` are public and
1853    /// may have been rewritten since, and the step then chooses among the
1854    /// alternates the live filters enable (its `groups_prepared` guard),
1855    /// so the row is collated from the live filters too, rather than
1856    /// letting an alternate the step will not try decide which matcher
1857    /// runs first.
1858    fn expected_match_tins(&self, rule: &Rule, slot: usize) -> Cow<'_, [Tin]> {
1859        if self.options.match_tokens.is_empty() {
1860            return Cow::Borrowed(&[]);
1861        }
1862        // The table is the rule's, chosen by what the rule is called --
1863        // exactly as the name-keyed map this replaced chose it. The slot
1864        // is only a hint that skips the hash: `name` is public and a
1865        // callback may have written it, so the slot is trusted only while
1866        // the name installed there is still the rule's own, and anything
1867        // else falls back to the lookup by name. A slot never answers for
1868        // a name that is not at it.
1869        let index = match self.names.get(rule.slot) {
1870            Some(installed) if *installed == rule.name => rule.slot,
1871            _ => match self.rules.get_index_of(&*rule.name) {
1872                Some(index) => index,
1873                None => return Cow::Borrowed(&[]),
1874            },
1875        };
1876        let is_open = rule.state == RuleState::Open;
1877        if self.options.rule.include == self.prepared_include
1878            && self.options.rule.exclude == self.prepared_exclude
1879        {
1880            return Cow::Borrowed(self.expected_tins[index].at(is_open, slot));
1881        }
1882        let installed = &self.rules[index];
1883        let alts = if is_open {
1884            &installed.open
1885        } else {
1886            &installed.close
1887        };
1888        Cow::Owned(ExpectedTins::live(alts, &self.options, slot))
1889    }
1890
1891    /// Record, for `continuations()`, what could have followed where a
1892    /// fetch raised a bad token: the lexer's own fault, or a `#BD` token a
1893    /// custom matcher returned.
1894    ///
1895    /// TypeScript records nothing at such a fetch (`_contTins` is cleared
1896    /// when `parse_alts` begins and written only when every alternate has
1897    /// failed on a buffered token), so `Tabnas.continuations` computes the
1898    /// answer from the buffer for the rule that fetched, at position 0
1899    /// (`continuationTins(ctx, rule)`), or, when that rule is `NORULE`
1900    /// because the check for trailing content fetched, answers with the
1901    /// start rule's openers. Here a rule's fetch records the same set, and
1902    /// the trailing check records nothing, which `continuations_uncaught`
1903    /// turns into the start openers.
1904    fn capture_fetch_failure(
1905        &self,
1906        context: &Context,
1907        rule: &Rule,
1908        stack: &[Rule],
1909        mode: &mut ParseMode<'_>,
1910        fetch: Fetch,
1911    ) {
1912        if fetch != Fetch::Rule {
1913            return;
1914        }
1915        if let Some(capture) = mode.continuation.as_deref_mut() {
1916            capture.failure =
1917                continuation_tins(context, rule, stack, &self.rules, &self.options, 0, None);
1918        }
1919    }
1920
1921    #[allow(clippy::too_many_arguments)]
1922    fn ensure_lookahead(
1923        &self,
1924        lexer: &mut Lexer,
1925        context: &mut Context,
1926        rule: &mut Rule,
1927        count: usize,
1928        mode: &mut ParseMode<'_>,
1929        site: ParseSite<'_>,
1930        fetch: Fetch,
1931    ) -> Result<(), TabnasError> {
1932        while context.t.len() < count {
1933            if context.t.last().is_some_and(|token| token.tin == TIN_ZZ) {
1934                break;
1935            }
1936            let expected_match_tins = self.expected_match_tins(rule, context.t.len());
1937            let token = loop {
1938                let next = match context.next_replay() {
1939                    Some(token) => Ok(token),
1940                    None => {
1941                        let result = self.catch_callback("lexer callback", site.source, || {
1942                            lexer.next_rule_token(&expected_match_tins, rule, context)
1943                        });
1944                        result.map_err(|error| {
1945                            self.attach_active_error(
1946                                error,
1947                                rule,
1948                                site.stack,
1949                                Self::phase_token(rule),
1950                            )
1951                        })?
1952                    }
1953                };
1954                let mut token = match next {
1955                    Ok(token) => token,
1956                    // The lexer's own fault is a `#BD` token from here on,
1957                    // as it is in TypeScript, where `lex.next` builds the
1958                    // token, hands it to the lex subscribers and returns
1959                    // it, and `parse_alts` reads what came back. A
1960                    // subscriber may rewrite it, its code and position
1961                    // included, and the parse sees what it left: so the
1962                    // fault takes the path below, the one a `#BD` token a
1963                    // custom matcher returned takes, and the lexer's error
1964                    // itself goes no further than this token.
1965                    Err(error) => error_token(&error),
1966                };
1967                for subscriber in &self.lex_subscribers {
1968                    let result = self.catch_callback("lex subscriber", site.source, || {
1969                        subscriber(&mut token, rule, context)
1970                    });
1971                    result.map_err(|error| {
1972                        self.attach_active_error(error, rule, site.stack, Some(&token))
1973                    })?;
1974                }
1975                // A bad token a custom matcher returned (`Lexer::bad`,
1976                // `Lexer::bad_span`) is a lexer fault like any other, and
1977                // is handled here exactly as the lexer's own faults are
1978                // above, which is what TypeScript's fetch in `parse_alts`
1979                // does with every `#BD` token it is handed: raised at once,
1980                // with its own code at its own position, or under recovery
1981                // recorded, coalesced with the run it continues, and
1982                // stepped past. Only relexing leaves it in the lookahead,
1983                // for an alternate to re-cut. Buffered, it failed every
1984                // alternate it met, and the error named the first token of
1985                // the lookahead instead: `unexpected`, at the good token in
1986                // front of it.
1987                if token.tin == TIN_BD && !self.options.lex.relex {
1988                    let code = if token.why.is_empty() {
1989                        "unexpected"
1990                    } else {
1991                        token.why.as_str()
1992                    };
1993                    let error = TabnasError::new(
1994                        code,
1995                        token.src.clone(),
1996                        site.source,
1997                        token.site.pos,
1998                        token.site.ri,
1999                        token.site.ci,
2000                    );
2001                    let error = self.attach_error(error, rule, site.stack, site.alts, Some(&token));
2002                    if mode.recovering && fetch == Fetch::Rule {
2003                        if absorb_lex_error(&error, context, &self.options, mode.errors) {
2004                            lexer.skip_bad(&token, mid_construct(&error.code));
2005                            continue;
2006                        }
2007                        mode.gave_up = true;
2008                    }
2009                    self.capture_fetch_failure(context, rule, site.stack, mode, fetch);
2010                    return Err(error);
2011                }
2012                if token.tin == TIN_ZZ {
2013                    if let Some(capture) = mode.continuation.as_deref_mut() {
2014                        // The end is a legal continuation of a prefix that
2015                        // parses, whoever fetched it. What else is legal
2016                        // there is what the fetching rule's alternates could
2017                        // still take; the check for trailing content has no
2018                        // rule, and TypeScript's capture (the lex subscriber
2019                        // in `Tabnas.continuations`) answers it with nothing,
2020                        // since its `rule` is then `NORULE`.
2021                        capture.have_end = true;
2022                        if fetch == Fetch::Rule {
2023                            capture.at_end.extend(continuation_tins(
2024                                context,
2025                                rule,
2026                                site.stack,
2027                                &self.rules,
2028                                &self.options,
2029                                context.t.len(),
2030                                None,
2031                            ));
2032                        }
2033                    }
2034                }
2035                if !self.ignore_tins.contains(&token.tin) {
2036                    break token;
2037                }
2038            };
2039            for subscriber in &self.token_subscribers {
2040                let result =
2041                    self.catch_callback("token subscriber", site.source, || subscriber(&token));
2042                result.map_err(|error| {
2043                    self.attach_active_error(error, rule, site.stack, Some(&token))
2044                })?;
2045            }
2046            let is_end = token.tin == TIN_ZZ;
2047            context.t.push(token);
2048            if is_end {
2049                break;
2050            }
2051        }
2052        Ok(())
2053    }
2054
2055    fn parse_inner(
2056        &self,
2057        src: &str,
2058        meta: Value,
2059        owner: Option<&crate::Tabnas>,
2060        parent: Option<&ContextSeed>,
2061        mode: &mut ParseMode<'_>,
2062    ) -> Result<Value, TabnasError> {
2063        let input_meta = meta.clone();
2064        let mut context = Context::new(
2065            self.options.rewind.history,
2066            src,
2067            meta,
2068            Arc::clone(&self.options),
2069            self.instance.clone(),
2070        );
2071        if let Some(parent) = parent {
2072            context.apply_seed(parent);
2073        }
2074        for prepare in &self.options.parse.prepare {
2075            let outcome = self.catch_callback("parse.prepare", src, || {
2076                prepare.run(owner, &mut context, &input_meta)
2077            })?;
2078            if let Err(detail) = outcome {
2079                let mut error = TabnasError::new("internal", "", src, 0, 1, 1);
2080                error.detail = detail.into();
2081                return Err(error);
2082            }
2083        }
2084        for prepare in self.options.parse.named_prepare.values() {
2085            let outcome = self.catch_callback("parse.prepare", src, || {
2086                prepare.run(owner, &mut context, &input_meta)
2087            })?;
2088            if let Err(detail) = outcome {
2089                let mut error = TabnasError::new("internal", "", src, 0, 1, 1);
2090                error.detail = detail.into();
2091                return Err(error);
2092            }
2093        }
2094
2095        if src.is_empty() {
2096            return if self.options.lex.empty {
2097                Ok(self.options.lex.empty_result.clone())
2098            } else {
2099                Err(TabnasError::new("unexpected", "", src, 0, 1, 1))
2100            };
2101        }
2102
2103        // The cached regex is the one the parser was built with; use it
2104        // only while the options still carry the pattern it came from.
2105        let exclude_regex = if self.exclude_pattern == self.options.number.exclude {
2106            self.exclude_regex.clone()
2107        } else {
2108            compile_number_exclude(&self.options)
2109        };
2110        let mut lexer = Lexer::with_shared(src, Arc::clone(&self.options), exclude_regex);
2111
2112        // One lookup for the start rule: whether it exists, its shared
2113        // name handle and the spec to bind, which used to be three.
2114        let start_name = self.options.rule.start.as_str();
2115        let Some((start_slot, start_shared, start_spec)) = self.installed(start_name) else {
2116            return Ok(Value::Undefined);
2117        };
2118
2119        let mut current_rule = Rule::new(start_shared.clone(), Value::Undefined);
2120        current_rule.bind_spec(start_spec, start_shared, start_slot);
2121        current_rule.i = 0;
2122        let root_node = current_rule.node.clone();
2123        context.set_root(root_node.clone());
2124        let mut stack: Vec<Rule> = Vec::new();
2125        // Whether a pushed rule may let go of `child_node` while it is
2126        // buried. Read once: `self.options` is reached through `&self`,
2127        // so this cannot change under the parse, and the rule that parks
2128        // is the rule `attempt_recover` will resume.
2129        let park_child_nodes = self.options.parse.recover.pop_until_valid;
2130        let mut next_rule_id = 1;
2131        #[allow(unused_assignments)]
2132        let mut final_value = None;
2133
2134        let mut iterations = 0usize;
2135        let maxmul = if self.options.rule.maxmul == 0 {
2136            3
2137        } else {
2138            self.options.rule.maxmul
2139        };
2140        let max_iterations = self
2141            .rules
2142            .len()
2143            .saturating_mul(src.encode_utf16().count())
2144            .saturating_mul(4)
2145            .saturating_mul(maxmul)
2146            .max(100);
2147        let budget = &self.options.parse.budget;
2148
2149        // One match record for the whole parse, reset at the head of each
2150        // rule step. It used to be born twice per step -- a seed and a
2151        // per-alternate candidate -- and then moved twice more, at 320
2152        // bytes a move, to end up holding what one record could have held
2153        // all along. TypeScript keeps exactly one per context
2154        // (`ctx._palt`); a nested parse runs `parse_inner` again and so
2155        // gets its own, which is why this is a local and not a field.
2156        let mut matched = AltMatch::default();
2157
2158        'parse: loop {
2159            context.set_active(&current_rule, &stack);
2160            update_partial(mode, &root_node, &current_rule, &stack);
2161            iterations += 1;
2162            if iterations > max_iterations {
2163                let pnt = context
2164                    .t
2165                    .first()
2166                    .map(|t| (t.site.pos, t.site.ri, t.site.ci))
2167                    .unwrap_or((0, 1, 1));
2168                return Err(TabnasError::new("unexpected", "", src, pnt.0, pnt.1, pnt.2));
2169            }
2170            context.iteration = iterations - 1;
2171            // The guards run first, and at every step the budget can run
2172            // on: a grammar's bound holds whatever budget the caller set.
2173            if context.iteration > 0 {
2174                for guard in &self.parse_guards {
2175                    let result = self.catch_callback("parse guard", src, || guard(&context));
2176                    let keep_going = result.map_err(|error| {
2177                        self.attach_active_error(
2178                            error,
2179                            &current_rule,
2180                            &stack,
2181                            Self::phase_token(&current_rule).or_else(|| context.t.first()),
2182                        )
2183                    })?;
2184                    if !keep_going {
2185                        return Err(self.cancelled(src, &context, &current_rule, &stack));
2186                    }
2187                }
2188            }
2189            if budget.check_every_n > 0
2190                && context.iteration > 0
2191                && context.iteration % budget.check_every_n == 0
2192            {
2193                if let Some(check) = &budget.on_check {
2194                    let result =
2195                        self.catch_callback("parse.budget.onCheck", src, || check(&context));
2196                    let keep_going = result.map_err(|error| {
2197                        self.attach_active_error(
2198                            error,
2199                            &current_rule,
2200                            &stack,
2201                            Self::phase_token(&current_rule).or_else(|| context.t.first()),
2202                        )
2203                    })?;
2204                    if !keep_going {
2205                        return Err(self.cancelled(src, &context, &current_rule, &stack));
2206                    }
2207                }
2208            }
2209
2210            // The rule was bound to its prepared state when it was routed
2211            // to, and carries the slot that state sits at. Taking the spec
2212            // from there rather than from the rule costs an array index in
2213            // place of comparing the two names byte by byte, and hands out a
2214            // borrow of the parser instead of a reference count on the spec
2215            // -- the clone existed only to release the borrow on the rule
2216            // that the callbacks below need mutably, and the parser is not
2217            // mutable here at all.
2218            //
2219            // The record is a hint. `spec` and `name` are both public on a
2220            // rule and reachable from any callback, so the slot is trusted
2221            // only while the record still describes the rule in hand: same
2222            // spec by pointer, same name. Anything else falls back to the
2223            // rule's own spec, and to the lookup by name as the guard for a
2224            // rule that was never bound.
2225            let by_name;
2226            let (prepared, spec) = match self.prepared.get(current_rule.slot).filter(|prepared| {
2227                Arc::ptr_eq(&prepared.spec, &current_rule.spec)
2228                    && prepared.name == current_rule.name
2229            }) {
2230                Some(prepared) => (Some(prepared), &prepared.spec),
2231                None => {
2232                    by_name = if current_rule.spec.name == *current_rule.name {
2233                        Arc::clone(&current_rule.spec)
2234                    } else {
2235                        match self.rules.get(&*current_rule.name) {
2236                            Some(s) => s.clone(),
2237                            None => {
2238                                let pnt = context
2239                                    .t
2240                                    .first()
2241                                    .map(|t| (t.site.pos, t.site.ri, t.site.ci))
2242                                    .unwrap_or((0, 1, 1));
2243                                return Err(TabnasError::new(
2244                                    "unknown_rule",
2245                                    &*current_rule.name,
2246                                    src,
2247                                    pnt.0,
2248                                    pnt.1,
2249                                    pnt.2,
2250                                ));
2251                            }
2252                        }
2253                    };
2254                    (None, &by_name)
2255                }
2256            };
2257
2258            let is_open = current_rule.state == RuleState::Open;
2259            let alts = if is_open { &spec.open } else { &spec.close };
2260
2261            for subscriber in &self.rule_subscribers {
2262                let result = self.catch_callback("rule subscriber", src, || {
2263                    subscriber(&mut current_rule, &mut context)
2264                });
2265                result.map_err(|error| {
2266                    self.attach_error(
2267                        error,
2268                        &current_rule,
2269                        &stack,
2270                        alts,
2271                        Self::phase_token(&current_rule).or_else(|| context.t.first()),
2272                    )
2273                })?;
2274            }
2275            update_partial(mode, &root_node, &current_rule, &stack);
2276
2277            // 1. Run before-actions. Recovery can retry a failed close pass;
2278            // its before-close actions have already run and must not replay.
2279            let skip_befores = current_rule.skip_befores;
2280            current_rule.skip_befores = false;
2281            let before_enabled = if is_open {
2282                current_rule.bo
2283            } else {
2284                current_rule.bc
2285            };
2286            // `bo`/`bc` are run control, not presence -- see
2287            // `run_after_actions`. An empty order is the phase having
2288            // nothing to run, and skipping it here also skips the
2289            // snapshot the state callbacks would have been handed.
2290            let by_spec;
2291            let before_bindings: &[ActionBinding] = if skip_befores || !before_enabled {
2292                &[]
2293            } else {
2294                match prepared {
2295                    Some(prepared) => prepared.before(is_open),
2296                    None => {
2297                        let (actions, callbacks, states, order) = if is_open {
2298                            (&spec.bo, &spec.bo_fns, &spec.bo_state_fns, &spec.bo_order)
2299                        } else {
2300                            (&spec.bc, &spec.bc_fns, &spec.bc_state_fns, &spec.bc_order)
2301                        };
2302                        by_spec = resolved_action_order(actions, callbacks, states, order);
2303                        &by_spec
2304                    }
2305                }
2306            };
2307            if !before_bindings.is_empty() {
2308                let label = if is_open {
2309                    "before-open action"
2310                } else {
2311                    "before-close action"
2312                };
2313                let next = is_open.then(|| current_rule.snapshot());
2314                let site = ParseSite {
2315                    source: src,
2316                    stack: &stack,
2317                    alts,
2318                };
2319                let mut output = None;
2320                let mut lifecycle_error = None;
2321                for binding in before_bindings {
2322                    output = match binding {
2323                        ActionBinding::Named(action) => {
2324                            if let Some(callback) = self.state_actions.get(action) {
2325                                self.run_state_callback(
2326                                    label,
2327                                    callback,
2328                                    &mut current_rule,
2329                                    &mut context,
2330                                    next.as_deref(),
2331                                    output,
2332                                )
2333                                .map_err(|error| {
2334                                    self.attach_action_error(
2335                                        error,
2336                                        src,
2337                                        &current_rule,
2338                                        &stack,
2339                                        alts,
2340                                    )
2341                                })?
2342                            } else {
2343                                self.run_action(action, &mut current_rule, &mut context)
2344                                    .map_err(|error| {
2345                                        self.attach_action_error(
2346                                            error,
2347                                            src,
2348                                            &current_rule,
2349                                            &stack,
2350                                            alts,
2351                                        )
2352                                    })?;
2353                                None
2354                            }
2355                        }
2356                        ActionBinding::Callback(callback) => {
2357                            self.run_context_callback(
2358                                label,
2359                                callback,
2360                                &mut current_rule,
2361                                &mut context,
2362                            )
2363                            .map_err(|error| {
2364                                self.attach_action_error(error, src, &current_rule, &stack, alts)
2365                            })?;
2366                            None
2367                        }
2368                        ActionBinding::State(callback) => self
2369                            .run_state_callback(
2370                                label,
2371                                callback,
2372                                &mut current_rule,
2373                                &mut context,
2374                                next.as_deref(),
2375                                output,
2376                            )
2377                            .map_err(|error| {
2378                                self.attach_action_error(error, src, &current_rule, &stack, alts)
2379                            })?,
2380                    };
2381                    match self.check_lifecycle_output(output, &current_rule, site) {
2382                        Ok(next_output) => output = next_output,
2383                        Err(error) => {
2384                            lifecycle_error = Some(error);
2385                            break;
2386                        }
2387                    }
2388                }
2389                if let Some(error) = lifecycle_error {
2390                    update_partial(mode, &root_node, &current_rule, &stack);
2391                    self.recover_error_pass(
2392                        error,
2393                        if is_open {
2394                            RuleState::Open
2395                        } else {
2396                            RuleState::Close
2397                        },
2398                        None,
2399                        false,
2400                        src,
2401                        &mut current_rule,
2402                        &mut stack,
2403                        &mut context,
2404                        &mut lexer,
2405                        mode,
2406                    )?;
2407                    continue 'parse;
2408                }
2409            }
2410            update_partial(mode, &root_node, &current_rule, &stack);
2411
2412            // 2. Select alternates
2413            let mut matched_alt_idx: Option<usize> = None;
2414            let mut matched_count = 0;
2415            // Nothing has read the record since the last step ended, so
2416            // this is the only point it has to be clean by.
2417            matched.reset();
2418            // Set once either of the two condition callbacks that receive
2419            // the record has written into it, so a rejected alternate's
2420            // writes are wiped before the next alternate is tried. Rust
2421            // keeps the alternates isolated from each other here; only the
2422            // candidates that were actually offered a record pay for it.
2423            let mut record_written = false;
2424            // The winning alternate's matched tokens, when they are known to
2425            // still describe `context.t`. See the assignment below.
2426            let mut matched_tokens: Option<Rc<Vec<Token>>> = None;
2427
2428            // Asked once per step rather than once per alternate. When
2429            // the options still carry what the prepared answers were
2430            // worked out against, every alternate below reads a `bool`;
2431            // when a callback has rewritten either list, every alternate
2432            // falls back to deriving it, exactly as before.
2433            let groups_prepared = self.options.rule.include == self.prepared_include
2434                && self.options.rule.exclude == self.prepared_exclude;
2435
2436            // First-token index. Once the first lookahead token is in
2437            // hand, and the lexer is not renegotiating token identity
2438            // (under relex an alternate may re-cut a token it does not
2439            // name, so every alternate stays a candidate), only the
2440            // alternates that can take that token at position 0 are
2441            // tried, in their original order. Until the first fetch,
2442            // alternates are tried in order as before: the first
2443            // alternate with a sequence fetches the token.
2444            let first_index = if self.options.lex.relex {
2445                None
2446            } else {
2447                prepared.map(|prepared| prepared.first(is_open))
2448            };
2449            // The two candidate lists (alternates naming the first tin,
2450            // and the wildcards), walked together in index order once
2451            // selected, and the tin they were selected for.
2452            let mut lists: Option<(&[usize], &[usize])> = None;
2453            let mut key_tin = TIN_BD;
2454            let (mut ni, mut wi) = (0, 0);
2455            let mut next_idx = 0;
2456            loop {
2457                if lists.is_none() {
2458                    if let (Some(index), Some(t0)) = (first_index, context.t.first()) {
2459                        if t0.tin != TIN_BD {
2460                            key_tin = t0.tin;
2461                            let named = index.named(key_tin);
2462                            let wild = index.wild.as_slice();
2463                            (ni, wi) = (0, 0);
2464                            while ni < named.len() && named[ni] < next_idx {
2465                                ni += 1;
2466                            }
2467                            while wi < wild.len() && wild[wi] < next_idx {
2468                                wi += 1;
2469                            }
2470                            lists = Some((named, wild));
2471                        }
2472                    }
2473                }
2474                let idx = match lists {
2475                    Some((named, wild)) => {
2476                        let n = named.get(ni).copied().unwrap_or(alts.len());
2477                        let w = wild.get(wi).copied().unwrap_or(alts.len());
2478                        if alts.len() <= n && alts.len() <= w {
2479                            // No remaining alternate can take the first token.
2480                            break;
2481                        }
2482                        if n < w {
2483                            ni += 1;
2484                            n
2485                        } else {
2486                            wi += 1;
2487                            w
2488                        }
2489                    }
2490                    None => {
2491                        if alts.len() <= next_idx {
2492                            break;
2493                        }
2494                        next_idx += 1;
2495                        next_idx - 1
2496                    }
2497                };
2498                let alt = &alts[idx];
2499                let enabled = match prepared
2500                    .filter(|_| groups_prepared)
2501                    .and_then(|prepared| prepared.alt(is_open, idx))
2502                {
2503                    Some(prepared_alt) => prepared_alt.groups,
2504                    None => groups_enabled(alt, &self.options),
2505                };
2506                if !enabled {
2507                    continue;
2508                }
2509                let s_len = alt.s.len();
2510                let mut alt_matches = true;
2511                if record_written {
2512                    matched.reset();
2513                    record_written = false;
2514                }
2515                let mut relex_undo: Option<RelexUndo> = None;
2516                for (pos, pos_tins) in alt.s.iter().enumerate() {
2517                    if let Err(error) = self.ensure_lookahead(
2518                        &mut lexer,
2519                        &mut context,
2520                        &mut current_rule,
2521                        pos + 1,
2522                        mode,
2523                        ParseSite {
2524                            source: src,
2525                            stack: &stack,
2526                            alts,
2527                        },
2528                        Fetch::Rule,
2529                    ) {
2530                        return Err(self.attach_error(error, &current_rule, &stack, alts, None));
2531                    }
2532                    let Some(token) = context.t.get(pos).cloned() else {
2533                        alt_matches = false;
2534                        break;
2535                    };
2536                    if !slot_matches(pos_tins, token.tin) {
2537                        let recut = if self.options.lex.relex
2538                            && !token.src.is_empty()
2539                            && !pos_tins.is_empty()
2540                        {
2541                            let result = self.catch_callback("lexer relex callback", src, || {
2542                                lexer.relex(&token, pos_tins, &mut current_rule, &mut context)
2543                            });
2544                            result.map_err(|error| {
2545                                self.attach_error(error, &current_rule, &stack, alts, Some(&token))
2546                            })?
2547                        } else {
2548                            None
2549                        };
2550                        let Some((mut recut, checkpoint)) = recut else {
2551                            alt_matches = false;
2552                            break;
2553                        };
2554                        for subscriber in &self.lex_subscribers {
2555                            let result = self.catch_callback("lex subscriber", src, || {
2556                                subscriber(&mut recut, &mut current_rule, &mut context)
2557                            });
2558                            result.map_err(|error| {
2559                                self.attach_error(error, &current_rule, &stack, alts, Some(&recut))
2560                            })?;
2561                        }
2562                        if !pos_tins.contains(&recut.tin) {
2563                            lexer.unrelex(checkpoint, &mut context);
2564                            alt_matches = false;
2565                            break;
2566                        }
2567                        if relex_undo.is_none() {
2568                            relex_undo = Some(RelexUndo {
2569                                position: pos,
2570                                token,
2571                                checkpoint,
2572                                tokens: context.t.clone(),
2573                            });
2574                        }
2575                        context.t[pos] = recut;
2576                        context.t.truncate(pos + 1);
2577                    }
2578                }
2579
2580                if alt_matches {
2581                    let tokens: Rc<Vec<Token>> =
2582                        Rc::new(context.t.iter().take(s_len).cloned().collect());
2583                    // The declarative conditions are the only readers of a
2584                    // candidate rule; the callback tiers below run against
2585                    // `current_rule` itself, after its matched tokens are in
2586                    // place. Most alternates declare no declarative
2587                    // condition, and cloning a whole rule to answer a
2588                    // question nobody asks was the parse loop's largest
2589                    // single copy.
2590                    if alt.c_ref.is_some() || !alt.c.is_empty() {
2591                        let mut candidate = current_rule.clone();
2592                        if is_open {
2593                            candidate.o = Rc::clone(&tokens);
2594                        } else {
2595                            candidate.c = Rc::clone(&tokens);
2596                        }
2597                        if !builtin_condition_matches(alt.c_ref.as_deref(), &candidate)
2598                            || !conditions_match(&alt.c, &candidate, &stack)
2599                        {
2600                            alt_matches = false;
2601                        }
2602                    }
2603                    if alt_matches {
2604                        if is_open {
2605                            current_rule.o = Rc::clone(&tokens);
2606                        } else {
2607                            current_rule.c = Rc::clone(&tokens);
2608                        }
2609                        if let Some(condition) = &alt.c_fn {
2610                            context.set_rule(&current_rule);
2611                            let result = self.catch_callback("alternate condition", src, || {
2612                                condition(&mut current_rule, &mut context)
2613                            });
2614                            alt_matches = result.map_err(|error| {
2615                                self.attach_error(
2616                                    error,
2617                                    &current_rule,
2618                                    &stack,
2619                                    alts,
2620                                    Self::phase_token(&current_rule),
2621                                )
2622                            })?;
2623                        }
2624                        if alt_matches {
2625                            if let Some(condition) = &alt.c_match {
2626                                context.set_rule(&current_rule);
2627                                record_written = true;
2628                                let result =
2629                                    self.catch_callback("matched alternate condition", src, || {
2630                                        condition(&mut current_rule, &mut context, &mut matched)
2631                                    });
2632                                alt_matches = result.map_err(|error| {
2633                                    self.attach_error(
2634                                        error,
2635                                        &current_rule,
2636                                        &stack,
2637                                        alts,
2638                                        Self::phase_token(&current_rule),
2639                                    )
2640                                })?;
2641                            }
2642                        }
2643                        if alt_matches {
2644                            if let Some(condition) = &alt.c_lex_match {
2645                                context.set_rule(&current_rule);
2646                                record_written = true;
2647                                let result = self.catch_callback(
2648                                    "matched alternate lexer condition",
2649                                    src,
2650                                    || {
2651                                        condition(
2652                                            &mut current_rule,
2653                                            &mut context,
2654                                            &mut matched,
2655                                            &mut lexer,
2656                                        )
2657                                    },
2658                                );
2659                                alt_matches = result.map_err(|error| {
2660                                    self.attach_error(
2661                                        error,
2662                                        &current_rule,
2663                                        &stack,
2664                                        alts,
2665                                        Self::phase_token(&current_rule),
2666                                    )
2667                                })?;
2668                            }
2669                        }
2670                        if alt_matches {
2671                            if let Some(condition) = &alt.c_lex {
2672                                context.set_rule(&current_rule);
2673                                let result =
2674                                    self.catch_callback("alternate lexer condition", src, || {
2675                                        condition(&mut current_rule, &mut context, &mut lexer)
2676                                    });
2677                                alt_matches = result.map_err(|error| {
2678                                    self.attach_error(
2679                                        error,
2680                                        &current_rule,
2681                                        &stack,
2682                                        alts,
2683                                        Self::phase_token(&current_rule),
2684                                    )
2685                                })?;
2686                            }
2687                        }
2688                    }
2689                    if alt_matches {
2690                        matched_alt_idx = Some(idx);
2691                        matched_count = s_len;
2692                        // `tokens` is `context.t[..s_len]`, which is what the
2693                        // matched-token copy after this loop rebuilds from
2694                        // the same buffer. Between building it and here, the
2695                        // only things holding a `&mut Context` are the two
2696                        // condition callbacks, so without them the rebuild
2697                        // cannot differ and the vector below is reused
2698                        // instead of allocated and cloned a second time.
2699                        // `break` leaves the loop before the relex undo, so
2700                        // that cannot restore `context.t` underneath either.
2701                        if alt.c_fn.is_none() && alt.c_match.is_none() {
2702                            matched_tokens = Some(Rc::clone(&tokens));
2703                        }
2704                        break;
2705                    }
2706                }
2707                if let Some(undo) = relex_undo {
2708                    lexer.unrelex(undo.checkpoint, &mut context);
2709                    context.t = undo.tokens;
2710                    for subscriber in &self.lex_subscribers {
2711                        let mut restored = undo.token.clone();
2712                        let result = self.catch_callback("lex subscriber", src, || {
2713                            subscriber(&mut restored, &mut current_rule, &mut context)
2714                        });
2715                        result.map_err(|error| {
2716                            self.attach_error(error, &current_rule, &stack, alts, Some(&restored))
2717                        })?;
2718                    }
2719                    debug_assert_eq!(context.t.get(undo.position), Some(&undo.token));
2720                }
2721                // A condition can retag the first token, or replace it,
2722                // and then reject. The lists were selected for a tin the
2723                // token no longer has, and the plain scan would test every
2724                // later alternate against the token as it is now: resume
2725                // after this alternate, and select again at the top.
2726                if lists.is_some() && context.t.first().map(|t0| t0.tin) != Some(key_tin) {
2727                    lists = None;
2728                    next_idx = idx + 1;
2729                }
2730            }
2731
2732            if let Some(idx) = matched_alt_idx {
2733                // Copy matched tokens
2734                let matched_tokens = matched_tokens.unwrap_or_else(|| {
2735                    Rc::new(context.t.iter().take(matched_count).cloned().collect())
2736                });
2737                if is_open {
2738                    current_rule.o = matched_tokens;
2739                } else {
2740                    current_rule.c = matched_tokens;
2741                }
2742
2743                // Compatibility modifier for the original two-argument Rust
2744                // callback tier. It rewrites the source spec before dynamic
2745                // fields are resolved. The full `h_match` callback below runs
2746                // at the canonical point over the resolved AltMatch.
2747                //
2748                // Rewriting is the only thing here that needs an alternate of
2749                // its own; everything below reads one. A grammar that
2750                // declares no modifier — which is most of them, and both
2751                // benchmark grammars — now borrows the installed alternate
2752                // instead of copying it once per rule step.
2753                let rewritten: AltSpec;
2754                let alt: &AltSpec = if let Some(modifier) = alts[idx].h.clone() {
2755                    context.set_rule(&current_rule);
2756                    let source = alts[idx].clone();
2757                    let result = self.catch_callback("alternate modifier", src, || {
2758                        modifier(source, &mut current_rule, &mut context)
2759                    });
2760                    rewritten = result.map_err(|error| {
2761                        self.attach_error(
2762                            error,
2763                            &current_rule,
2764                            &stack,
2765                            alts,
2766                            Self::phase_token(&current_rule),
2767                        )
2768                    })?;
2769                    &rewritten
2770                } else {
2771                    &alts[idx]
2772                };
2773
2774                matched.h = alt.h_match.clone();
2775                if !alt.n.is_empty() {
2776                    matched.n = alt.n.clone();
2777                }
2778                if !alt.u.is_empty() {
2779                    matched.u = alt.u.clone();
2780                }
2781                if !alt.k.is_empty() {
2782                    matched.k = alt.k.clone();
2783                }
2784                // The alternate's group tags and action order, both worked
2785                // out when the rule was installed. A modifier rewrites the
2786                // whole `AltSpec` per step, so a rule that carries one is
2787                // resolved from what the modifier produced, not from the
2788                // record.
2789                let prepared_alt = if alts[idx].h.is_some() {
2790                    None
2791                } else {
2792                    prepared.and_then(|prepared| prepared.alt(is_open, idx))
2793                };
2794                match prepared_alt {
2795                    // `clone_from` writes into the list the last step left
2796                    // behind -- `AltMatch::reset` clears it without giving
2797                    // up its capacity -- rather than growing a fresh one.
2798                    Some(prepared_alt) => matched.g.clone_from(&prepared_alt.group_tags),
2799                    None if !alt.g.is_empty() => {
2800                        matched.g = listed(&alt.g).map(str::to_owned).collect();
2801                    }
2802                    None => {}
2803                }
2804                // Publishing the order into the record is only observable
2805                // when something this step runs receives the record. When
2806                // nothing does -- no matched condition, error, route,
2807                // backtrack, modifier or action, and no named binding that
2808                // could resolve to a matched action -- the step runs the
2809                // prepared order in place and the record keeps the empty
2810                // list it was born with. `matched_actions` is public and
2811                // may be written after `add_rule`, so whether a name can
2812                // reach it is asked here, per step, never cached.
2813                let prepared_alt = prepared_alt.filter(|prepared_alt| {
2814                    !prepared_alt.observed
2815                        && (!prepared_alt.named || self.matched_actions.is_empty())
2816                });
2817                if prepared_alt.is_none() {
2818                    let actions = resolved_alt_action_order(
2819                        &alt.a,
2820                        &alt.action_fns,
2821                        &alt.matched_action_fns,
2822                        &alt.action_order,
2823                    );
2824                    if !actions.is_empty() {
2825                        matched.actions = actions;
2826                    }
2827                }
2828                if !alt.action_configs.is_empty() {
2829                    matched.action_configs = alt.action_configs.clone();
2830                }
2831
2832                if let Some(route) = &alt.p_fn {
2833                    context.set_rule(&current_rule);
2834                    matched.p = self
2835                        .catch_callback("alternate push", src, || {
2836                            route(&mut current_rule, &mut context)
2837                        })
2838                        .map_err(|error| {
2839                            self.attach_error(
2840                                error,
2841                                &current_rule,
2842                                &stack,
2843                                alts,
2844                                Self::phase_token(&current_rule),
2845                            )
2846                        })?
2847                        .filter(|name| !name.is_empty());
2848                }
2849                if let Some(route) = &alt.p_match {
2850                    context.set_rule(&current_rule);
2851                    matched.p = self
2852                        .catch_callback("matched alternate push", src, || {
2853                            route(&mut current_rule, &mut context, &mut matched)
2854                        })
2855                        .map_err(|error| {
2856                            self.attach_error(
2857                                error,
2858                                &current_rule,
2859                                &stack,
2860                                alts,
2861                                Self::phase_token(&current_rule),
2862                            )
2863                        })?
2864                        .filter(|name| !name.is_empty());
2865                } else if alt.p_fn.is_none() {
2866                    if let Some(route) = alt.p.clone() {
2867                        matched.p = (!route.is_empty()).then_some(route);
2868                    }
2869                }
2870                if let Some(route) = &alt.r_fn {
2871                    context.set_rule(&current_rule);
2872                    matched.r = self
2873                        .catch_callback("alternate replace", src, || {
2874                            route(&mut current_rule, &mut context)
2875                        })
2876                        .map_err(|error| {
2877                            self.attach_error(
2878                                error,
2879                                &current_rule,
2880                                &stack,
2881                                alts,
2882                                Self::phase_token(&current_rule),
2883                            )
2884                        })?
2885                        .filter(|name| !name.is_empty());
2886                }
2887                if let Some(route) = &alt.r_match {
2888                    context.set_rule(&current_rule);
2889                    matched.r = self
2890                        .catch_callback("matched alternate replace", src, || {
2891                            route(&mut current_rule, &mut context, &mut matched)
2892                        })
2893                        .map_err(|error| {
2894                            self.attach_error(
2895                                error,
2896                                &current_rule,
2897                                &stack,
2898                                alts,
2899                                Self::phase_token(&current_rule),
2900                            )
2901                        })?
2902                        .filter(|name| !name.is_empty());
2903                } else if alt.r_fn.is_none() {
2904                    if let Some(route) = alt.r.clone() {
2905                        matched.r = (!route.is_empty()).then_some(route);
2906                    }
2907                }
2908                if let Some(backtrack) = &alt.b_fn {
2909                    context.set_rule(&current_rule);
2910                    matched.b = self
2911                        .catch_callback("alternate backtrack", src, || {
2912                            backtrack(&mut current_rule, &mut context)
2913                        })
2914                        .map_err(|error| {
2915                            self.attach_error(
2916                                error,
2917                                &current_rule,
2918                                &stack,
2919                                alts,
2920                                Self::phase_token(&current_rule),
2921                            )
2922                        })?;
2923                }
2924                if let Some(backtrack) = &alt.b_match {
2925                    context.set_rule(&current_rule);
2926                    matched.b = self
2927                        .catch_callback("matched alternate backtrack", src, || {
2928                            backtrack(&mut current_rule, &mut context, &mut matched)
2929                        })
2930                        .map_err(|error| {
2931                            self.attach_error(
2932                                error,
2933                                &current_rule,
2934                                &stack,
2935                                alts,
2936                                Self::phase_token(&current_rule),
2937                            )
2938                        })?;
2939                } else if alt.b_fn.is_none() && alt.b != 0 {
2940                    matched.b = alt.b;
2941                }
2942
2943                if let Some(modifier) = alt.h_match.clone() {
2944                    context.set_rule(&current_rule);
2945                    let next = is_open.then(|| current_rule.snapshot());
2946                    matched = self
2947                        .catch_callback("matched alternate modifier", src, || {
2948                            modifier(
2949                                std::mem::take(&mut matched),
2950                                &mut current_rule,
2951                                &mut context,
2952                                next.as_deref(),
2953                            )
2954                        })
2955                        .map_err(|error| {
2956                            self.attach_error(
2957                                error,
2958                                &current_rule,
2959                                &stack,
2960                                alts,
2961                                Self::phase_token(&current_rule),
2962                            )
2963                        })?;
2964                }
2965
2966                // The alternate's error hook runs AFTER the routing forms
2967                // have resolved and after both modifiers, and sees what the
2968                // modifier produced: routing, then modify, then check, in
2969                // every runtime (#154). It ran before the routing forms and
2970                // between the two modifiers here, which no grammar could
2971                // observe and no other port did.
2972                if let Some(error_hook) = alt.e.clone() {
2973                    context.set_rule(&current_rule);
2974                    let result = self.catch_callback("alternate error", src, || {
2975                        error_hook(&mut current_rule, &mut context)
2976                    });
2977                    matched.e = result
2978                        .map_err(|error| {
2979                            self.attach_error(
2980                                error,
2981                                &current_rule,
2982                                &stack,
2983                                alts,
2984                                Self::phase_token(&current_rule),
2985                            )
2986                        })?
2987                        .map(Box::new);
2988                }
2989                if let Some(error_hook) = alt.e_match.clone() {
2990                    context.set_rule(&current_rule);
2991                    let result = self.catch_callback("matched alternate error", src, || {
2992                        error_hook(&mut current_rule, &mut context, &mut matched)
2993                    });
2994                    matched.e = result
2995                        .map_err(|error| {
2996                            self.attach_error(
2997                                error,
2998                                &current_rule,
2999                                &stack,
3000                                alts,
3001                                Self::phase_token(&current_rule),
3002                            )
3003                        })?
3004                        .map(Box::new);
3005                }
3006                // Function-valued alternate errors are raised at the match
3007                // site, before counters, actions and consumption.
3008                if let Some(token) = matched.e.clone() {
3009                    let code = raised_error_code(&token);
3010                    let error = TabnasError::new(
3011                        code,
3012                        token.src.clone(),
3013                        src,
3014                        token.site.pos,
3015                        token.site.ri,
3016                        token.site.ci,
3017                    );
3018                    let done_alt = (!self.rule_done_subscribers.is_empty()).then(|| RuleDoneAlt {
3019                        b: matched.b,
3020                        g: matched.g.clone(),
3021                        p: matched.p.clone().unwrap_or_default(),
3022                        r: matched.r.clone().unwrap_or_default(),
3023                        err: Some((*token).clone()),
3024                    });
3025                    let error = self.attach_error(error, &current_rule, &stack, alts, Some(&token));
3026                    self.recover_error_pass(
3027                        error,
3028                        if is_open {
3029                            RuleState::Open
3030                        } else {
3031                            RuleState::Close
3032                        },
3033                        done_alt,
3034                        false,
3035                        src,
3036                        &mut current_rule,
3037                        &mut stack,
3038                        &mut context,
3039                        &mut lexer,
3040                        mode,
3041                    )?;
3042                    continue;
3043                }
3044
3045                // Update counters n
3046                for (k, v) in &matched.n {
3047                    if *v == 0 {
3048                        current_rule.n_mut().insert(k.clone(), 0);
3049                    } else {
3050                        *current_rule.n_mut().entry(k.clone()).or_insert(0) += *v;
3051                    }
3052                }
3053
3054                // Update user props u
3055                for (k, v) in &matched.u {
3056                    current_rule.u_mut().insert(k.clone(), v.clone());
3057                }
3058
3059                // Update keep props k
3060                for (k, v) in &matched.k {
3061                    current_rule.k_mut().insert(k.clone(), v.clone());
3062                }
3063
3064                let backtrack = matched.b;
3065                let consumed = matched_count.saturating_sub(backtrack);
3066                context.record_consumed(consumed);
3067
3068                // Run action. A bad token returned by a canonical action is
3069                // raised through the same recovery path as alt.e and
3070                // lifecycle actions; later actions must not run.
3071                let mut matched_action_error = None;
3072                let mut matched_action_token = None;
3073                // The published list has to be copied to be walked -- an
3074                // action may write the record it is being read out of, and
3075                // appending to `matched.actions` from inside this loop has
3076                // never reached it. The prepared list is not the record, so
3077                // it is walked where it lies.
3078                let published;
3079                let bindings: &[AltActionBinding] = match prepared_alt {
3080                    Some(prepared_alt) => &prepared_alt.actions,
3081                    None => {
3082                        published = matched.actions.clone();
3083                        &published
3084                    }
3085                };
3086                for binding in bindings {
3087                    let act_name = match binding {
3088                        AltActionBinding::Context(callback) => {
3089                            self.run_context_callback(
3090                                "alternate action",
3091                                callback,
3092                                &mut current_rule,
3093                                &mut context,
3094                            )
3095                            .map_err(|error| {
3096                                self.attach_action_error(error, src, &current_rule, &stack, alts)
3097                            })?;
3098                            continue;
3099                        }
3100                        AltActionBinding::Matched(callback) => {
3101                            context.set_rule(&current_rule);
3102                            let result = self
3103                                .catch_callback("matched alternate action", src, || {
3104                                    callback(&mut current_rule, &mut context, &mut matched)
3105                                })
3106                                .map_err(|error| {
3107                                    self.attach_action_error(
3108                                        error,
3109                                        src,
3110                                        &current_rule,
3111                                        &stack,
3112                                        alts,
3113                                    )
3114                                })?;
3115                            let token = result.map_err(|action_error| {
3116                                self.attach_action_error(
3117                                    action_error.into(),
3118                                    src,
3119                                    &current_rule,
3120                                    &stack,
3121                                    alts,
3122                                )
3123                            })?;
3124                            if let Some(token) = token.filter(|token| !token.err.is_empty()) {
3125                                matched_action_error = Some(self.raised_token_error(
3126                                    &token,
3127                                    &current_rule,
3128                                    ParseSite {
3129                                        source: src,
3130                                        stack: &stack,
3131                                        alts,
3132                                    },
3133                                ));
3134                                matched_action_token = Some(token);
3135                                break;
3136                            }
3137                            continue;
3138                        }
3139                        AltActionBinding::Named(name) => name,
3140                    };
3141                    if let Some(callback) = self.matched_actions.get(act_name) {
3142                        context.set_rule(&current_rule);
3143                        let result = self
3144                            .catch_callback("named matched alternate action", src, || {
3145                                callback(&mut current_rule, &mut context, &mut matched)
3146                            })
3147                            .map_err(|error| {
3148                                self.attach_action_error(error, src, &current_rule, &stack, alts)
3149                            })?;
3150                        let token = result.map_err(|action_error| {
3151                            self.attach_action_error(
3152                                action_error.into(),
3153                                src,
3154                                &current_rule,
3155                                &stack,
3156                                alts,
3157                            )
3158                        })?;
3159                        if let Some(token) = token.filter(|token| !token.err.is_empty()) {
3160                            matched_action_error = Some(self.raised_token_error(
3161                                &token,
3162                                &current_rule,
3163                                ParseSite {
3164                                    source: src,
3165                                    stack: &stack,
3166                                    alts,
3167                                },
3168                            ));
3169                            matched_action_token = Some(token);
3170                            break;
3171                        }
3172                        continue;
3173                    }
3174                    match act_name.as_str() {
3175                        "@probeInit$" => {
3176                            current_rule
3177                                .k_mut()
3178                                .insert("pd_phase".into(), Value::Number(0.0));
3179                            let mark = Value::Number(context.mark() as f64);
3180                            current_rule.k_mut().insert("pd_mark".into(), mark);
3181                        }
3182                        "@probeDecide$" => {
3183                            let mark = current_rule.k.get("pd_mark").and_then(|value| {
3184                                if let Value::Number(mark) = value {
3185                                    usize::try_from(*mark as u64).ok()
3186                                } else {
3187                                    None
3188                                }
3189                            });
3190                            let Some(mark) = mark.filter(|mark| *mark <= context.v_abs) else {
3191                                let mut error = TabnasError::new("internal", "", src, 0, 1, 1);
3192                                error.detail =
3193                                    "@probeDecide$: phase-0 @probeInit$ did not record a valid mark"
3194                                        .into();
3195                                return Err(error);
3196                            };
3197                            if let Err(error) = self.ensure_lookahead(
3198                                &mut lexer,
3199                                &mut context,
3200                                &mut current_rule,
3201                                1,
3202                                mode,
3203                                ParseSite {
3204                                    source: src,
3205                                    stack: &stack,
3206                                    alts,
3207                                },
3208                                Fetch::Rule,
3209                            ) {
3210                                return Err(self.attach_error(
3211                                    error,
3212                                    &current_rule,
3213                                    &stack,
3214                                    alts,
3215                                    None,
3216                                ));
3217                            }
3218                            let disambiguator =
3219                                current_rule.k.get("pd_d").and_then(|value| match value {
3220                                    Value::String(name) => Some(name.as_str()),
3221                                    _ => None,
3222                                });
3223                            let phase = if context
3224                                .t
3225                                .first()
3226                                .is_some_and(|token| Some(token.name.as_str()) == disambiguator)
3227                            {
3228                                1.0
3229                            } else {
3230                                2.0
3231                            };
3232                            context.rewind(mark)?;
3233                            current_rule
3234                                .k_mut()
3235                                .insert("pd_phase".into(), Value::Number(phase));
3236                        }
3237                        _ => self
3238                            .run_action_with_config(
3239                                act_name,
3240                                &mut current_rule,
3241                                &mut context,
3242                                matched.action_configs.get(act_name),
3243                            )
3244                            .map_err(|error| {
3245                                self.attach_action_error(error, src, &current_rule, &stack, alts)
3246                            })?,
3247                    }
3248                }
3249                if let Some(error) = matched_action_error {
3250                    let recovered_alt = Some(RuleDoneAlt {
3251                        b: matched.b,
3252                        g: matched.g.clone(),
3253                        p: matched.p.clone().unwrap_or_default(),
3254                        r: matched.r.clone().unwrap_or_default(),
3255                        err: None,
3256                    });
3257                    self.recover_error_pass(
3258                        error,
3259                        if is_open {
3260                            RuleState::Open
3261                        } else {
3262                            RuleState::Close
3263                        },
3264                        recovered_alt,
3265                        matched_action_token.is_some(),
3266                        src,
3267                        &mut current_rule,
3268                        &mut stack,
3269                        &mut context,
3270                        &mut lexer,
3271                        mode,
3272                    )?;
3273                    update_partial(mode, &root_node, &current_rule, &stack);
3274                    continue 'parse;
3275                }
3276                update_partial(mode, &root_node, &current_rule, &stack);
3277
3278                // The canonical action receives the live match record. Its
3279                // post-action p/r writes are a supported routing channel, so
3280                // resolve the transition only after the action sequence.
3281                // Nothing below reads `matched.p` or `matched.r` again: the
3282                // record's remaining readers take `b` and `g`. So the names
3283                // move out of it rather than being copied, which is the
3284                // second `String` each of them cost per rule step.
3285                let push_name = matched.p.take();
3286                let replace_name = matched.r.take();
3287                // Only a ruleDone subscriber ever reads this, and it is
3288                // cloned again at each of the transition arms below. A
3289                // grammar with no subscriber was building and copying it
3290                // several times per matched alternate for nobody.
3291                let done_alt = (!self.rule_done_subscribers.is_empty()).then(|| RuleDoneAlt {
3292                    b: matched.b,
3293                    g: matched.g.clone(),
3294                    p: push_name.clone().unwrap_or_default(),
3295                    r: replace_name.clone().unwrap_or_default(),
3296                    err: None,
3297                });
3298
3299                // Callback routes and action mutations cannot be validated at
3300                // grammar-install time. Reject an unknown destination at the
3301                // canonical point: after the matched action, but before any
3302                // lifecycle after-action or transition.
3303                //
3304                // Resolving it here also settles the transition below: the
3305                // arms take the shared name and the spec out of this one
3306                // lookup rather than hashing the same name twice more.
3307                // `push` wins over `replace` in the arms below, so this
3308                // resolves whichever of the two the parse will take.
3309                //
3310                // A route the grammar declared on this alternate was already
3311                // resolved when the rule was installed; all that is left of
3312                // it here is checking that the name the step is actually
3313                // routing to is still that name, which is a byte compare
3314                // against a handle rather than a hash of the same three
3315                // bytes for the tens of thousands of steps that route where
3316                // the grammar said they would.
3317                let mut route = match push_name.as_deref().or(replace_name.as_deref()) {
3318                    Some(name) => match prepared
3319                        .and_then(|prepared| prepared.alt(is_open, idx))
3320                        .map(|alt| if push_name.is_some() { &alt.p } else { &alt.r })
3321                        .and_then(|route| route.resolved(name))
3322                        .or_else(|| self.installed(name))
3323                    {
3324                        Some(resolved) => Some(resolved),
3325                        None => {
3326                            let mut token = Self::phase_token(&current_rule)
3327                                .cloned()
3328                                .or_else(|| context.t.first().cloned())
3329                                .unwrap_or_else(Token::no_token);
3330                            token.bad("unknown_rule");
3331                            token
3332                                .use_data_mut()
3333                                .insert("rulename".into(), Value::String(name.to_string()));
3334                            let error = self.raised_token_error(
3335                                &token,
3336                                &current_rule,
3337                                ParseSite {
3338                                    source: src,
3339                                    stack: &stack,
3340                                    alts,
3341                                },
3342                            );
3343                            self.recover_error_pass(
3344                                error,
3345                                if is_open {
3346                                    RuleState::Open
3347                                } else {
3348                                    RuleState::Close
3349                                },
3350                                done_alt,
3351                                false,
3352                                src,
3353                                &mut current_rule,
3354                                &mut stack,
3355                                &mut context,
3356                                &mut lexer,
3357                                mode,
3358                            )?;
3359                            update_partial(mode, &root_node, &current_rule, &stack);
3360                            continue 'parse;
3361                        }
3362                    },
3363                    None => None,
3364                };
3365
3366                // Resolve the transition before running lifecycle after-actions,
3367                // so they can inspect rule.next just like the canonical engine.
3368                // The action still belongs to the rule whose alternate matched.
3369                let completed_rule: Option<Rule>;
3370                let mut completed_value = None;
3371                if push_name.is_some() {
3372                    let (push_slot, push_shared, push_spec) =
3373                        route.take().expect("a push route was resolved above");
3374                    let mut child = Rule::bound(
3375                        push_shared.clone(),
3376                        current_rule.node.clone(),
3377                        Some(push_spec),
3378                        push_slot,
3379                    );
3380                    child.i = next_rule_id;
3381                    next_rule_id += 1;
3382                    child.d = stack.len() + 1;
3383                    child.parent_node = Some(current_rule.node.clone());
3384                    child.n = Rc::clone(&current_rule.n);
3385                    child.k = Rc::clone(&current_rule.k);
3386                    child.parent_rule = Some(bounded_history(
3387                        current_rule.snapshot(),
3388                        self.options.rule.history,
3389                        Link::PusherBefore,
3390                    ));
3391                    current_rule.next_rule_name = Some(push_shared);
3392                    current_rule.child_rule = Some(child.snapshot());
3393                    current_rule.next_rule = current_rule.child_rule.clone();
3394                    current_rule.note_child_push(&child);
3395                    // The link as this arm made it, so the relink below can
3396                    // tell whether an after action has changed it.
3397                    let linked = current_rule.child_rule.clone();
3398                    let after = self.run_after_actions(
3399                        spec,
3400                        prepared,
3401                        is_open,
3402                        &mut current_rule,
3403                        &mut context,
3404                        ParseSite {
3405                            source: src,
3406                            stack: &stack,
3407                            alts,
3408                        },
3409                    );
3410                    update_partial(mode, &root_node, &current_rule, &stack);
3411                    if self.recover_after_actions(
3412                        after,
3413                        if is_open {
3414                            RuleState::Open
3415                        } else {
3416                            RuleState::Close
3417                        },
3418                        done_alt.clone(),
3419                        src,
3420                        &mut current_rule,
3421                        &mut stack,
3422                        &mut context,
3423                        &mut lexer,
3424                        mode,
3425                    )? {
3426                        continue 'parse;
3427                    }
3428                    if is_open {
3429                        current_rule.state = RuleState::Close;
3430                    }
3431                    child.parent_rule = Some(bounded_history(
3432                        current_rule.snapshot(),
3433                        self.options.rule.history,
3434                        Link::Parent,
3435                    ));
3436                    // TypeScript and Go link the live rules, so from the
3437                    // child, `parent.child.parent.child` is the child
3438                    // itself. The snapshot linked above as `child` carries
3439                    // this rule as it stood before the push, whose `child`
3440                    // is the one pushed before (parser #259). With no
3441                    // bound, link the child again from a snapshot that
3442                    // carries this rule with the child linked, and give
3443                    // the child this rule as it then stands, so that a
3444                    // snapshot of the child reads the same chain: the
3445                    // pre-link record is two hops further down, past the
3446                    // four-hop read. The cost, measured on
3447                    // `rs/tests/rule_history_test.rs`'s flat array, is two
3448                    // more records kept per push, 8 for 6 per element.
3449                    // Under a bound the links stay as the canonical engine
3450                    // copies them, where that path reads nothing
3451                    // (`rule-history-bounded-pusher`). An after action that
3452                    // cleared or repointed `child` or `next` keeps its edit:
3453                    // the canonical engine links before the after actions
3454                    // and never overwrites them afterwards.
3455                    if crate::options::effective_rule_history(self.options.rule.history).is_none()
3456                        && current_rule.links_unchanged(&linked)
3457                    {
3458                        current_rule.relink_child(&child);
3459                        child.parent_rule = Some(current_rule.snapshot());
3460                    }
3461                    completed_rule = self.rule_done_copy(&current_rule);
3462                    // The child about to run shares this rule's node cell,
3463                    // and `child_node` may be a second handle on the very
3464                    // container it will write into. Let go of it for the
3465                    // duration -- see `Rule::park_child_node`. Taken after
3466                    // `rule_done_copy`, so a ruleDone subscriber still sees
3467                    // the rule exactly as it stood, and only when every pop
3468                    // that can resume this rule overwrites the field first
3469                    // (see `park_child_node` and `attempt_recover`).
3470                    if park_child_nodes {
3471                        current_rule.park_child_node();
3472                    }
3473                    stack.push(current_rule);
3474                    current_rule = child;
3475                } else if replace_name.is_some() {
3476                    let (replace_slot, replace_shared, replace_spec) =
3477                        route.take().expect("a replace route was resolved above");
3478                    let mut next = Rule::bound(
3479                        replace_shared.clone(),
3480                        current_rule.node.clone(),
3481                        Some(replace_spec),
3482                        replace_slot,
3483                    );
3484                    next.i = next_rule_id;
3485                    next_rule_id += 1;
3486                    next.d = current_rule.d;
3487                    next.parent_node = current_rule.parent_node.clone();
3488                    next.parent_rule = current_rule.parent_rule.clone();
3489                    next.n = Rc::clone(&current_rule.n);
3490                    next.k = Rc::clone(&current_rule.k);
3491                    current_rule.next_rule_name = Some(replace_shared);
3492                    current_rule.next_rule = Some(next.snapshot());
3493                    let after = self.run_after_actions(
3494                        spec,
3495                        prepared,
3496                        is_open,
3497                        &mut current_rule,
3498                        &mut context,
3499                        ParseSite {
3500                            source: src,
3501                            stack: &stack,
3502                            alts,
3503                        },
3504                    );
3505                    update_partial(mode, &root_node, &current_rule, &stack);
3506                    if self.recover_after_actions(
3507                        after,
3508                        if is_open {
3509                            RuleState::Open
3510                        } else {
3511                            RuleState::Close
3512                        },
3513                        done_alt.clone(),
3514                        src,
3515                        &mut current_rule,
3516                        &mut stack,
3517                        &mut context,
3518                        &mut lexer,
3519                        mode,
3520                    )? {
3521                        continue 'parse;
3522                    }
3523                    if is_open {
3524                        current_rule.state = RuleState::Close;
3525                    }
3526                    next.prev_rule = Some(bounded_history(
3527                        current_rule.snapshot(),
3528                        self.options.rule.history,
3529                        Link::Prev,
3530                    ));
3531                    // The rule being replaced stops existing here. If it is
3532                    // the one its parent PUSHED, the parent's `child` link
3533                    // stays on it -- TypeScript never relinks `rule.child`
3534                    // (rules.ts:665) and neither does Go (rule.go:1280) --
3535                    // so freeze the node cell and the record it ended on
3536                    // before it goes.
3537                    if let Some(parent) = stack.last_mut() {
3538                        parent.freeze_child(&current_rule);
3539                    }
3540                    // Moved rather than cloned: this arm hands the
3541                    // finished rule over instead of copying it, so the
3542                    // gate above has nothing to save here.
3543                    completed_rule = Some(current_rule);
3544                    current_rule = next;
3545                } else if is_open {
3546                    current_rule.next_rule_name = Some(current_rule.name.clone());
3547                    current_rule.next_rule = Some(current_rule.snapshot());
3548                    let after = self.run_after_actions(
3549                        spec,
3550                        prepared,
3551                        true,
3552                        &mut current_rule,
3553                        &mut context,
3554                        ParseSite {
3555                            source: src,
3556                            stack: &stack,
3557                            alts,
3558                        },
3559                    );
3560                    update_partial(mode, &root_node, &current_rule, &stack);
3561                    if self.recover_after_actions(
3562                        after,
3563                        RuleState::Open,
3564                        done_alt.clone(),
3565                        src,
3566                        &mut current_rule,
3567                        &mut stack,
3568                        &mut context,
3569                        &mut lexer,
3570                        mode,
3571                    )? {
3572                        continue 'parse;
3573                    }
3574                    current_rule.state = RuleState::Close;
3575                    completed_rule = self.rule_done_copy(&current_rule);
3576                } else {
3577                    // Close phase pop
3578                    current_rule.next_rule_name = stack.last().map(|rule| rule.name.clone());
3579                    current_rule.next_rule = stack.last().map(Rule::snapshot);
3580                    let after = self.run_after_actions(
3581                        spec,
3582                        prepared,
3583                        false,
3584                        &mut current_rule,
3585                        &mut context,
3586                        ParseSite {
3587                            source: src,
3588                            stack: &stack,
3589                            alts,
3590                        },
3591                    );
3592                    update_partial(mode, &root_node, &current_rule, &stack);
3593                    if self.recover_after_actions(
3594                        after,
3595                        RuleState::Close,
3596                        done_alt.clone(),
3597                        src,
3598                        &mut current_rule,
3599                        &mut stack,
3600                        &mut context,
3601                        &mut lexer,
3602                        mode,
3603                    )? {
3604                        continue 'parse;
3605                    }
3606                    let parent = stack.pop();
3607                    completed_rule = self.rule_done_copy(&current_rule);
3608                    if let Some(mut parent) = parent {
3609                        parent.accept_child(&current_rule);
3610                        current_rule = parent;
3611                    } else {
3612                        // Root rule popped! Done.
3613                        completed_value = Some(current_rule.node.borrow().clone());
3614                    }
3615                }
3616                if let Some(completed_rule) = &completed_rule {
3617                    self.notify_rule_done(
3618                        completed_rule,
3619                        &context,
3620                        if is_open {
3621                            RuleState::Open
3622                        } else {
3623                            RuleState::Close
3624                        },
3625                        done_alt,
3626                        src,
3627                        &stack,
3628                    )?;
3629                }
3630                if let Some(value) = completed_value {
3631                    final_value = Some(value);
3632                    break;
3633                }
3634                update_partial(mode, &root_node, &current_rule, &stack);
3635            } else if alts.is_empty() {
3636                // A state with no alternatives performs an implicit empty
3637                // pass. It still resolves next and runs lifecycle after-actions.
3638                let completed_rule: Option<Rule>;
3639                let mut completed_value = None;
3640                if is_open {
3641                    current_rule.next_rule_name = Some(current_rule.name.clone());
3642                    current_rule.next_rule = Some(current_rule.snapshot());
3643                    let after = self.run_after_actions(
3644                        spec,
3645                        prepared,
3646                        true,
3647                        &mut current_rule,
3648                        &mut context,
3649                        ParseSite {
3650                            source: src,
3651                            stack: &stack,
3652                            alts,
3653                        },
3654                    );
3655                    update_partial(mode, &root_node, &current_rule, &stack);
3656                    if self.recover_after_actions(
3657                        after,
3658                        RuleState::Open,
3659                        None,
3660                        src,
3661                        &mut current_rule,
3662                        &mut stack,
3663                        &mut context,
3664                        &mut lexer,
3665                        mode,
3666                    )? {
3667                        continue 'parse;
3668                    }
3669                    current_rule.state = RuleState::Close;
3670                    completed_rule = self.rule_done_copy(&current_rule);
3671                } else {
3672                    current_rule.next_rule_name = stack.last().map(|rule| rule.name.clone());
3673                    current_rule.next_rule = stack.last().map(Rule::snapshot);
3674                    let after = self.run_after_actions(
3675                        spec,
3676                        prepared,
3677                        false,
3678                        &mut current_rule,
3679                        &mut context,
3680                        ParseSite {
3681                            source: src,
3682                            stack: &stack,
3683                            alts,
3684                        },
3685                    );
3686                    update_partial(mode, &root_node, &current_rule, &stack);
3687                    if self.recover_after_actions(
3688                        after,
3689                        RuleState::Close,
3690                        None,
3691                        src,
3692                        &mut current_rule,
3693                        &mut stack,
3694                        &mut context,
3695                        &mut lexer,
3696                        mode,
3697                    )? {
3698                        continue 'parse;
3699                    }
3700                    let parent = stack.pop();
3701                    completed_rule = self.rule_done_copy(&current_rule);
3702                    if let Some(mut parent) = parent {
3703                        parent.accept_child(&current_rule);
3704                        current_rule = parent;
3705                    } else {
3706                        completed_value = Some(current_rule.node.borrow().clone());
3707                    }
3708                }
3709                if let Some(completed_rule) = &completed_rule {
3710                    self.notify_rule_done(
3711                        completed_rule,
3712                        &context,
3713                        if is_open {
3714                            RuleState::Open
3715                        } else {
3716                            RuleState::Close
3717                        },
3718                        None,
3719                        src,
3720                        &stack,
3721                    )?;
3722                }
3723                if let Some(value) = completed_value {
3724                    final_value = Some(value);
3725                    break;
3726                }
3727                update_partial(mode, &root_node, &current_rule, &stack);
3728            } else {
3729                // Declared alternatives exist, but none matched.
3730                if is_open {
3731                    if let Err(error) = self.ensure_lookahead(
3732                        &mut lexer,
3733                        &mut context,
3734                        &mut current_rule,
3735                        1,
3736                        mode,
3737                        ParseSite {
3738                            source: src,
3739                            stack: &stack,
3740                            alts,
3741                        },
3742                        Fetch::Rule,
3743                    ) {
3744                        return Err(self.attach_error(error, &current_rule, &stack, alts, None));
3745                    }
3746                    if let Some(capture) = mode.continuation.as_deref_mut() {
3747                        let base = failed_alt_tins(&context, alts, &self.options);
3748                        capture.failure = continuation_tins(
3749                            &context,
3750                            &current_rule,
3751                            &stack,
3752                            &self.rules,
3753                            &self.options,
3754                            0,
3755                            Some(&base),
3756                        );
3757                    }
3758                    let t0 = context.t.first().cloned();
3759                    let (src_token, si, ri, ci) = if let Some(t) = t0.as_ref() {
3760                        (t.src.to_string(), t.site.pos, t.site.ri, t.site.ci)
3761                    } else {
3762                        (String::new(), src.len(), 1, 1)
3763                    };
3764                    let code = t0
3765                        .as_ref()
3766                        .map_or("unexpected", |t| deferred_error_code(t, mode.recovering));
3767                    let error = TabnasError::new(code, src_token, src, si, ri, ci);
3768                    let done_alt = (!alts.is_empty() && !self.rule_done_subscribers.is_empty())
3769                        .then(|| RuleDoneAlt {
3770                            b: 0,
3771                            g: Vec::new(),
3772                            p: String::new(),
3773                            r: String::new(),
3774                            err: t0.clone(),
3775                        });
3776                    let error = self.attach_error(error, &current_rule, &stack, alts, t0.as_ref());
3777                    self.recover_error_pass(
3778                        error,
3779                        RuleState::Open,
3780                        done_alt,
3781                        false,
3782                        src,
3783                        &mut current_rule,
3784                        &mut stack,
3785                        &mut context,
3786                        &mut lexer,
3787                        mode,
3788                    )?;
3789                    continue;
3790                } else {
3791                    if let Err(error) = self.ensure_lookahead(
3792                        &mut lexer,
3793                        &mut context,
3794                        &mut current_rule,
3795                        1,
3796                        mode,
3797                        ParseSite {
3798                            source: src,
3799                            stack: &stack,
3800                            alts,
3801                        },
3802                        Fetch::Rule,
3803                    ) {
3804                        return Err(self.attach_error(error, &current_rule, &stack, alts, None));
3805                    }
3806                    if let Some(capture) = mode.continuation.as_deref_mut() {
3807                        let base = failed_alt_tins(&context, alts, &self.options);
3808                        capture.failure = continuation_tins(
3809                            &context,
3810                            &current_rule,
3811                            &stack,
3812                            &self.rules,
3813                            &self.options,
3814                            0,
3815                            Some(&base),
3816                        );
3817                    }
3818                    let token = context.t.first().cloned();
3819                    let (source, pos, row, col) = token.as_ref().map_or_else(
3820                        || (String::new(), src.chars().count(), 1, 1),
3821                        |value| {
3822                            (
3823                                value.src.to_string(),
3824                                value.site.pos,
3825                                value.site.ri,
3826                                value.site.ci,
3827                            )
3828                        },
3829                    );
3830                    let code = token
3831                        .as_ref()
3832                        .map_or("unexpected", |t| deferred_error_code(t, mode.recovering));
3833                    let error = TabnasError::new(code, source, src, pos, row, col);
3834                    let done_alt = Some(RuleDoneAlt {
3835                        b: 0,
3836                        g: Vec::new(),
3837                        p: String::new(),
3838                        r: String::new(),
3839                        err: token.clone(),
3840                    });
3841                    let error =
3842                        self.attach_error(error, &current_rule, &stack, alts, token.as_ref());
3843                    self.recover_error_pass(
3844                        error,
3845                        RuleState::Close,
3846                        done_alt,
3847                        false,
3848                        src,
3849                        &mut current_rule,
3850                        &mut stack,
3851                        &mut context,
3852                        &mut lexer,
3853                        mode,
3854                    )?;
3855                    continue;
3856                }
3857            }
3858        }
3859
3860        let res = final_value.unwrap_or(Value::Null).unwrap_undefined();
3861        if mode.recovering {
3862            mode.partial = Some(PartialValue::Complete(res.clone()));
3863        }
3864
3865        // Post-loop check: ensure no unexpected trailing tokens. Recovery
3866        // keeps the completed value and reports the trailing fault. No
3867        // option turns the check off: `rule.finish` is the grammar's
3868        // end-of-source switch, kept in the options for its `@finish` to
3869        // read, not this check's (#250).
3870        if let Err(error) = self.ensure_lookahead(
3871            &mut lexer,
3872            &mut context,
3873            &mut current_rule,
3874            1,
3875            mode,
3876            ParseSite {
3877                source: src,
3878                stack: &stack,
3879                alts: &[],
3880            },
3881            Fetch::Trailing,
3882        ) {
3883            let error = self.attach_error(error, &current_rule, &stack, &[], None);
3884            if mode.recovering {
3885                mode.errors.push(error.clone());
3886                context.errs.push(error);
3887                return Ok(res);
3888            }
3889            return Err(error);
3890        }
3891        if let Some(t0) = context.t.first() {
3892            if t0.tin != TIN_ZZ {
3893                let code = if t0.tin == TIN_BD && !t0.why.is_empty() {
3894                    t0.why.as_str()
3895                } else {
3896                    "unexpected"
3897                };
3898                let error =
3899                    TabnasError::new(code, &*t0.src, src, t0.site.pos, t0.site.ri, t0.site.ci);
3900                let error = self.attach_error(
3901                    error,
3902                    &current_rule,
3903                    &stack,
3904                    &[AltSpec {
3905                        s: vec![vec![TIN_ZZ]],
3906                        ..Default::default()
3907                    }],
3908                    Some(t0),
3909                );
3910                if mode.recovering {
3911                    mode.errors.push(error.clone());
3912                    context.errs.push(error);
3913                    return Ok(res);
3914                }
3915                return Err(error);
3916            }
3917        }
3918        if self
3919            .options
3920            .result
3921            .fail
3922            .iter()
3923            .any(|failed| failed.deep_equal(&res))
3924        {
3925            let token = context.t.first();
3926            let error = token.map_or_else(
3927                || TabnasError::new("unexpected", "", src, 0, 1, 1),
3928                |token| {
3929                    TabnasError::new(
3930                        "unexpected",
3931                        &*token.src,
3932                        src,
3933                        token.site.pos,
3934                        token.site.ri,
3935                        token.site.ci,
3936                    )
3937                },
3938            );
3939            if mode.recovering {
3940                mode.errors.push(error.clone());
3941                context.errs.push(error);
3942                return Ok(res);
3943            }
3944            return Err(error);
3945        }
3946        Ok(res)
3947    }
3948}
3949
3950/// Keep a handle to the best partial node the recovery path would return.
3951///
3952/// Fifteen sites in the parse loop call this, twelve times per input
3953/// construct on the benchmark grammars, and outside recovery every one of them is a
3954/// load and a branch wrapped in a call. The guard is inline so the call
3955/// goes away. Inside recovery this deliberately clones only an `Rc` node
3956/// handle, never the `Value` being built: a `Value` snapshot keeps its
3957/// container shared and turns the next mutation into a copy of the entire
3958/// accumulated prefix.
3959///
3960/// The value read at the end is the one the last site would have taken:
3961/// the node is chosen by the same test, in the same order, and it keeps
3962/// the node even when recovery then pops the rules that held it. Only a
3963/// write to that node between the last site and the failure can tell the
3964/// two apart -- an action or callback that changes the node and then
3965/// fails, or is followed by a matched action whose error recovery gives up
3966/// on -- and the value is then the node as the failure left it, which is
3967/// what TypeScript returns: it reads the node in its `catch`
3968/// (`ts/src/parser.ts`), not before the step that failed.
3969#[inline]
3970fn update_partial(
3971    mode: &mut ParseMode<'_>,
3972    root_node: &Rc<RefCell<Value>>,
3973    current_rule: &Rule,
3974    stack: &[Rule],
3975) {
3976    if mode.recovering {
3977        mode.partial = best_partial_node(root_node, current_rule, stack).map(PartialValue::Node);
3978    }
3979}
3980
3981#[inline(never)]
3982fn best_partial_node(
3983    root_node: &Rc<RefCell<Value>>,
3984    current_rule: &Rule,
3985    stack: &[Rule],
3986) -> Option<Rc<RefCell<Value>>> {
3987    let usable = |node: &Rc<RefCell<Value>>| {
3988        (!matches!(*node.borrow(), Value::Undefined | Value::Null)).then(|| Rc::clone(node))
3989    };
3990
3991    usable(root_node)
3992        .or_else(|| stack.iter().find_map(|rule| usable(&rule.node)))
3993        .or_else(|| usable(&current_rule.node))
3994}
3995
3996fn error_token(error: &TabnasError) -> Token {
3997    let byte_position = error
3998        .full_source
3999        .char_indices()
4000        .nth(error.pos)
4001        .map_or(error.full_source.len(), |(index, _)| index);
4002    let mut token = Token::new(
4003        "#BD",
4004        TIN_BD,
4005        Value::Undefined,
4006        error.src.clone(),
4007        crate::Point {
4008            len: error.len,
4009            site: crate::Site {
4010                si: byte_position,
4011                pos: error.pos,
4012                ri: error.row,
4013                ci: error.col,
4014            },
4015        },
4016    );
4017    token.err = crate::TokenCode::from(error.code.as_str());
4018    token.why = token.err.clone();
4019    token
4020}
4021
4022/// The code an error with no matching alternative takes from the first
4023/// token of the lookahead, read as the canonical engine reads it at the
4024/// same point. Fail-fast, its deferred throw takes a bad token's why, else
4025/// `unexpected` (`bad.why || UNEXPECTED` in ts/src/rules.ts); when the
4026/// parse is recovering, `attemptRecover` takes the token's err ahead of
4027/// its why (`tkn.err || tkn.why`). This engine's own lexer faults set why
4028/// and err alike, so only a token a matcher built with err alone tells
4029/// the two apart: `unexpected` fail-fast, its err under recovery, in every
4030/// runtime (#285).
4031fn deferred_error_code(token: &Token, recovering: bool) -> &str {
4032    if token.tin != TIN_BD {
4033        "unexpected"
4034    } else if recovering && !token.err.is_empty() {
4035        &token.err
4036    } else if !token.why.is_empty() {
4037        &token.why
4038    } else {
4039        "unexpected"
4040    }
4041}
4042
4043fn raised_error_code(token: &Token) -> &str {
4044    if !token.err.is_empty() {
4045        &token.err
4046    } else if !token.why.is_empty() {
4047        &token.why
4048    } else {
4049        "unexpected"
4050    }
4051}
4052
4053fn mid_construct(code: &str) -> bool {
4054    matches!(code, "unprintable" | "invalid_unicode" | "invalid_ascii")
4055}
4056
4057/// Record a bad token met at a rule's fetch under recovery, in the order
4058/// TypeScript's `parse_alts` does it: a token inside the run the last one
4059/// began grows that run's error rather than being listed again; a new run
4060/// inside the suppress window of the recovery before it is a cascade of
4061/// that fault and is dropped; anything else is recorded. Returns whether
4062/// the fetch goes on past the token. It does not when the run outgrows
4063/// `maxSkip`, or when the list has outgrown `maxRecoveries`, a cap read
4064/// after recording, so the error that stops the parse is listed.
4065fn absorb_lex_error(
4066    error: &TabnasError,
4067    context: &mut Context,
4068    options: &Options,
4069    errors: &mut Vec<TabnasError>,
4070) -> bool {
4071    let recover = &options.parse.recover;
4072    let run = context
4073        .bad_error
4074        .filter(|index| *index < errors.len())
4075        .filter(|_| context.bad_to.is_some_and(|bad_to| error.pos <= bad_to));
4076    if let Some(index) = run {
4077        let previous = &mut errors[index];
4078        let recovered = previous.recovered.get_or_insert(crate::RecoveredAt {
4079            skipped: 0,
4080            sync: None,
4081            bad: true,
4082        });
4083        recovered.skipped = recovered.skipped.saturating_add(1);
4084        let skipped = recovered.skipped;
4085        if let Some(context_previous) = context.errs.get_mut(index) {
4086            *context_previous = previous.clone();
4087        }
4088        if recover.max_skip < skipped {
4089            return false;
4090        }
4091    } else if context
4092        .recover_at
4093        .is_some_and(|at| context.v_abs.saturating_sub(at) < recover.suppress)
4094    {
4095        context.bad_error = None;
4096    } else {
4097        let mut recorded = error.clone();
4098        recorded.recovered = Some(crate::RecoveredAt {
4099            skipped: 1,
4100            sync: None,
4101            bad: true,
4102        });
4103        errors.push(recorded.clone());
4104        context.errs.push(recorded);
4105        context.bad_error = Some(errors.len() - 1);
4106        context.recover_at = Some(context.v_abs);
4107    }
4108    context.bad_to = Some(error.pos.saturating_add(error.len.max(1)));
4109    errors.len() <= recover.max_recoveries
4110}
4111
4112fn slot_matches(slot: &[Tin], tin: Tin) -> bool {
4113    tin != TIN_BD && (slot.is_empty() || slot.contains(&tin) || slot.contains(&TIN_AA))
4114}
4115
4116fn alt_match_depth(alt: &AltSpec, context: &Context) -> usize {
4117    let mut depth = 0;
4118    while depth < alt.s.len() {
4119        let Some(token) = context.t.get(depth) else {
4120            break;
4121        };
4122        if !slot_matches(&alt.s[depth], token.tin) {
4123            break;
4124        }
4125        depth += 1;
4126    }
4127    depth
4128}
4129
4130fn completion_tins(slot: &[Tin]) -> impl Iterator<Item = Tin> + '_ {
4131    slot.iter()
4132        .copied()
4133        .chain(slot.is_empty().then_some(TIN_AA))
4134}
4135
4136fn failed_alt_tins(context: &Context, alts: &[AltSpec], options: &Options) -> Vec<Tin> {
4137    let mut out = BTreeSet::new();
4138    for alt in alts {
4139        if !groups_enabled(alt, options) {
4140            continue;
4141        }
4142        let depth = alt_match_depth(alt, context);
4143        if let Some(slot) = alt.s.get(depth) {
4144            out.extend(completion_tins(slot));
4145        }
4146    }
4147    out.into_iter().collect()
4148}
4149
4150fn lead_tins(alts: &[AltSpec], options: &Options, out: &mut BTreeSet<Tin>) {
4151    for alt in alts {
4152        if !groups_enabled(alt, options) {
4153            continue;
4154        }
4155        if let Some(slot) = alt.s.first() {
4156            out.extend(completion_tins(slot));
4157        }
4158    }
4159}
4160
4161fn has_empty_close(spec: &RuleSpec, options: &Options) -> bool {
4162    spec.close
4163        .iter()
4164        .any(|alt| groups_enabled(alt, options) && alt.s.is_empty())
4165}
4166
4167fn alt_has_sync_group(alt: &AltSpec, sync_groups: &[String]) -> bool {
4168    alt.g
4169        .split(',')
4170        .map(str::trim)
4171        .any(|tag| sync_groups.iter().any(|wanted| wanted == tag))
4172}
4173
4174fn add_close_tins(
4175    rule: &Rule,
4176    rules: &IndexMap<String, Arc<RuleSpec>>,
4177    options: &Options,
4178    tagged_only: bool,
4179    out: &mut BTreeSet<Tin>,
4180) {
4181    let Some(spec) = rules.get(&*rule.name) else {
4182        return;
4183    };
4184    for alt in &spec.close {
4185        if !groups_enabled(alt, options)
4186            || (tagged_only && !alt_has_sync_group(alt, &options.parse.recover.sync_groups))
4187        {
4188            continue;
4189        }
4190        if let Some(slot) = alt.s.first() {
4191            out.extend(completion_tins(slot));
4192        }
4193    }
4194}
4195
4196fn compute_sync_tins(
4197    rule: &Rule,
4198    stack: &[Rule],
4199    rules: &IndexMap<String, Arc<RuleSpec>>,
4200    options: &Options,
4201) -> BTreeSet<Tin> {
4202    let mut out = BTreeSet::new();
4203    add_close_tins(rule, rules, options, true, &mut out);
4204    for parent in stack.iter().rev() {
4205        add_close_tins(parent, rules, options, true, &mut out);
4206    }
4207    if out.is_empty() {
4208        add_close_tins(rule, rules, options, false, &mut out);
4209        for parent in stack.iter().rev() {
4210            add_close_tins(parent, rules, options, false, &mut out);
4211        }
4212    }
4213    for name in &options.parse.recover.sync_tokens {
4214        if let Some(tin) = options.token(name) {
4215            out.insert(tin);
4216        }
4217        if let Some(tins) = options.token_set.get(name.trim_start_matches('#')) {
4218            out.extend(tins.iter().copied());
4219        }
4220    }
4221    out
4222}
4223
4224fn accepts_close(
4225    rule: &Rule,
4226    tin: Tin,
4227    rules: &IndexMap<String, Arc<RuleSpec>>,
4228    options: &Options,
4229) -> bool {
4230    rules.get(&*rule.name).is_some_and(|spec| {
4231        spec.close.iter().any(|alt| {
4232            groups_enabled(alt, options)
4233                && (alt.s.is_empty() || alt.s.first().is_some_and(|slot| slot_matches(slot, tin)))
4234        })
4235    })
4236}
4237
4238fn add_openers(
4239    name: &str,
4240    rules: &IndexMap<String, Arc<RuleSpec>>,
4241    options: &Options,
4242    opened: &mut BTreeSet<String>,
4243    out: &mut BTreeSet<Tin>,
4244) {
4245    if name.is_empty() || !opened.insert(name.to_owned()) {
4246        return;
4247    }
4248    let Some(spec) = rules.get(name) else {
4249        return;
4250    };
4251    lead_tins(&spec.open, options, out);
4252    for alt in &spec.open {
4253        if !groups_enabled(alt, options) || !alt.s.is_empty() {
4254            continue;
4255        }
4256        if let Some(push) = alt.p.as_deref() {
4257            add_openers(push, rules, options, opened, out);
4258        }
4259        if let Some(replace) = alt.r.as_deref() {
4260            add_openers(replace, rules, options, opened, out);
4261        }
4262    }
4263}
4264
4265fn continuation_tins(
4266    context: &Context,
4267    rule: &Rule,
4268    stack: &[Rule],
4269    rules: &IndexMap<String, Arc<RuleSpec>>,
4270    options: &Options,
4271    query_pos: usize,
4272    failed: Option<&[Tin]>,
4273) -> Vec<Tin> {
4274    let Some(spec) = rules.get(&*rule.name) else {
4275        return Vec::new();
4276    };
4277    let state_alts = if rule.state == RuleState::Open {
4278        &spec.open
4279    } else {
4280        &spec.close
4281    };
4282    let mut out = BTreeSet::new();
4283
4284    if let Some(failed) = failed.filter(|tins| !tins.is_empty()) {
4285        out.extend(failed.iter().copied());
4286    } else {
4287        for alt in state_alts {
4288            if !groups_enabled(alt, options) {
4289                continue;
4290            }
4291            let depth = alt_match_depth(alt, context);
4292            if depth == query_pos {
4293                if let Some(slot) = alt.s.get(depth) {
4294                    out.extend(completion_tins(slot));
4295                }
4296            }
4297        }
4298    }
4299
4300    // If the current rule can close without consuming a token, closing
4301    // tokens accepted by each parent are legal at the same point too.
4302    let mut close_rule = rule;
4303    let mut parent_index = stack.len();
4304    while let Some(close_spec) = rules.get(&*close_rule.name) {
4305        if !has_empty_close(close_spec, options) || parent_index == 0 {
4306            break;
4307        }
4308        parent_index -= 1;
4309        let parent = &stack[parent_index];
4310        if let Some(parent_spec) = rules.get(&*parent.name) {
4311            lead_tins(&parent_spec.close, options, &mut out);
4312        }
4313        close_rule = parent;
4314    }
4315
4316    // A fully matched alternate can immediately hand control to a pushed or
4317    // replacement rule. Follow empty opening hand-offs transitively.
4318    let mut opened = BTreeSet::new();
4319    for alt in state_alts {
4320        if !groups_enabled(alt, options) || alt_match_depth(alt, context) != alt.s.len() {
4321            continue;
4322        }
4323        // A callback backtrack is only knowable while executing the match.
4324        // Do not speculate that its static default is the handover point.
4325        if alt.b_fn.is_some() {
4326            continue;
4327        }
4328        if alt.s.len().checked_sub(alt.b) != Some(query_pos) {
4329            continue;
4330        }
4331        if let Some(push) = alt.p.as_deref() {
4332            add_openers(push, rules, options, &mut opened, &mut out);
4333        }
4334        if let Some(replace) = alt.r.as_deref() {
4335            add_openers(replace, rules, options, &mut opened, &mut out);
4336        }
4337    }
4338
4339    out.into_iter().collect()
4340}
4341
4342/// The group tags a comma-separated group list declares.
4343///
4344/// One definition, because two places ask: the include/exclude filter in
4345/// `groups_enabled`, and the list the engine publishes into `matched.g`.
4346/// They have to agree on what a tag is -- trimmed, and never empty.
4347fn listed(list: &str) -> impl Iterator<Item = &str> {
4348    list.split(',')
4349        .map(str::trim)
4350        .filter(|entry| !entry.is_empty())
4351}
4352
4353pub(crate) fn groups_enabled(alt: &AltSpec, options: &Options) -> bool {
4354    // With neither an include nor an exclude list there is nothing to
4355    // test against, so every alternate is enabled whatever groups it
4356    // declares. That is the usual case, and it is asked once per
4357    // alternate per iteration.
4358    let include = options.rule.include.as_str();
4359    let exclude = options.rule.exclude.as_str();
4360    if include.is_empty() && exclude.is_empty() {
4361        return true;
4362    }
4363    // Iterators rather than three collected `Vec<&str>`. The question is
4364    // the same one and the answer is the same answer; the three heap
4365    // allocations per call were not part of either. The shipped JSON
4366    // preset sets `rule.include` to "json", so the early return above
4367    // never fires for it and every alternate of every rule step paid
4368    // them.
4369    let declares = |wanted: &str| listed(&alt.g).any(|group| group == wanted);
4370    let included = listed(include).next().is_none() || listed(include).any(&declares);
4371    included && !listed(exclude).any(&declares)
4372}
4373
4374fn builtin_condition_matches(reference: Option<&str>, rule: &Rule) -> bool {
4375    let phase = match rule.k.get("pd_phase") {
4376        Some(Value::Number(value)) => *value as i32,
4377        _ => 0,
4378    };
4379    match reference {
4380        None => true,
4381        Some("@probePhase0$") => phase == 0,
4382        Some("@probePhase1$") => phase == 1,
4383        Some("@probePhase2$") => phase == 2,
4384        Some(_) => false,
4385    }
4386}
4387
4388fn conditions_match(conditions: &[Condition], rule: &Rule, ancestors: &[Rule]) -> bool {
4389    conditions.iter().all(|condition| {
4390        let resolved = resolve_condition_path(rule, ancestors, &condition.path);
4391        if condition.op == CompareOp::Exist {
4392            let exists = condition_exists(rule, ancestors, &condition.path);
4393            let wanted = matches!(condition.value, Value::Bool(true));
4394            return exists == wanted;
4395        }
4396        let Some(actual) = resolved else {
4397            return !matches!(condition.op, CompareOp::Eq);
4398        };
4399        match condition.op {
4400            CompareOp::Eq => actual.deep_equal(&condition.value),
4401            CompareOp::Ne => !actual.deep_equal(&condition.value),
4402            CompareOp::Lt => ordered(&actual, &condition.value).is_none_or(|value| value < 0),
4403            CompareOp::Lte => ordered(&actual, &condition.value).is_none_or(|value| value <= 0),
4404            CompareOp::Gt => ordered(&actual, &condition.value).is_none_or(|value| value > 0),
4405            CompareOp::Gte => ordered(&actual, &condition.value).is_none_or(|value| value >= 0),
4406            CompareOp::Exist => unreachable!("handled above"),
4407        }
4408    })
4409}
4410
4411fn ordered(left: &Value, right: &Value) -> Option<i8> {
4412    match (left, right) {
4413        (Value::Number(left), Value::Number(right)) => Some(if left < right {
4414            -1
4415        } else if left > right {
4416            1
4417        } else {
4418            0
4419        }),
4420        (Value::String(left), Value::String(right)) => Some(match left.cmp(right) {
4421            std::cmp::Ordering::Less => -1,
4422            std::cmp::Ordering::Equal => 0,
4423            std::cmp::Ordering::Greater => 1,
4424        }),
4425        _ => None,
4426    }
4427}
4428
4429fn condition_exists(rule: &Rule, ancestors: &[Rule], path: &[String]) -> bool {
4430    if path.first().map(String::as_str) == Some("n") && path.len() == 2 {
4431        rule.n.contains_key(&path[1])
4432    } else if path.first().map(String::as_str) == Some("parent") {
4433        ancestors
4434            .split_last()
4435            .is_some_and(|(parent, parent_ancestors)| {
4436                condition_exists(parent, parent_ancestors, &path[1..])
4437            })
4438    } else if path.first().map(String::as_str) == Some("child") {
4439        rule.child_rule
4440            .as_deref()
4441            .is_some_and(|child| snapshot_condition_exists(child, &path[1..]))
4442    } else if path.first().map(String::as_str) == Some("prev") {
4443        rule.prev_rule
4444            .as_deref()
4445            .is_some_and(|prev| snapshot_condition_exists(prev, &path[1..]))
4446    } else if path.first().map(String::as_str) == Some("next") {
4447        if let Some(next) = rule.next_rule.as_deref() {
4448            snapshot_condition_exists(next, &path[1..])
4449        } else if rule.next_rule_name.as_deref() == Some(&*rule.name) {
4450            condition_exists(rule, ancestors, &path[1..])
4451        } else {
4452            false
4453        }
4454    } else {
4455        resolve_condition_path(rule, ancestors, path).is_some()
4456    }
4457}
4458
4459fn resolve_condition_path(rule: &Rule, ancestors: &[Rule], path: &[String]) -> Option<Value> {
4460    let root = path.first()?.as_str();
4461    let rest = &path[1..];
4462    match root {
4463        "n" if rest.len() == 1 => Some(Value::Number(*rule.n.get(&rest[0]).unwrap_or(&0) as f64)),
4464        "u" => map_path(&rule.u, rest),
4465        "k" => map_path(&rule.k, rest),
4466        "d" if rest.is_empty() => Some(Value::Number(rule.d as f64)),
4467        "i" if rest.is_empty() => Some(Value::Number(rule.i as f64)),
4468        "name" if rest.is_empty() => Some(Value::String(rule.name.to_string())),
4469        "state" if rest.is_empty() => Some(Value::String(
4470            match rule.state {
4471                RuleState::Open => "o",
4472                RuleState::Close => "c",
4473            }
4474            .into(),
4475        )),
4476        "node" => value_path(rule.node.borrow().clone(), rest),
4477        "need" if rest.is_empty() => Some(Value::Number(rule.need as f64)),
4478        "oN" if rest.is_empty() => Some(Value::Number(rule.o.len() as f64)),
4479        "cN" if rest.is_empty() => Some(Value::Number(rule.c.len() as f64)),
4480        "o" => token_list_path(&rule.o, rest),
4481        "c" => token_list_path(&rule.c, rest),
4482        "o0" => token_path(rule.o.first(), rest),
4483        "o1" => token_path(rule.o.get(1), rest),
4484        "c0" => token_path(rule.c.first(), rest),
4485        "c1" => token_path(rule.c.get(1), rest),
4486        "parent" => {
4487            let (parent, parent_ancestors) = ancestors.split_last()?;
4488            resolve_condition_path(parent, parent_ancestors, rest)
4489        }
4490        "child" => resolve_snapshot_path(rule.child_rule.as_deref()?, rest),
4491        "prev" => resolve_snapshot_path(rule.prev_rule.as_deref()?, rest),
4492        "next" => {
4493            if let Some(next) = rule.next_rule.as_deref() {
4494                resolve_snapshot_path(next, rest)
4495            } else if rule.next_rule_name.as_deref() == Some(&*rule.name) {
4496                resolve_condition_path(rule, ancestors, rest)
4497            } else {
4498                None
4499            }
4500        }
4501        "spec" if rest == ["name"] => Some(Value::String(rule.name.to_string())),
4502        _ => None,
4503    }
4504}
4505
4506/// Which link a bounded snapshot becomes (see [`bounded_history`]).
4507#[derive(Clone, Copy, PartialEq, Eq)]
4508enum Link {
4509    /// The `parent_rule` a pushed child keeps: the pusher once it has
4510    /// linked the child, made at the end of the push arm.
4511    Parent,
4512    /// The pusher before it links the child, made at the start of the
4513    /// push arm. It survives as the `parent_rule` of the child's snapshot
4514    /// taken at the push: under a bound the one the pusher links as
4515    /// `child`, and with none the one the child reads as
4516    /// `parent.child.parent.child`, since the push arm then links the
4517    /// child again once the pusher has linked it, and gives the child the
4518    /// pusher as it then stands (see [`Rule::relink_child`]). That second
4519    /// link is made after the pushing alternate's after-actions have run,
4520    /// where TypeScript and Go link the live rules before them: a state
4521    /// action on the pusher's close still reads the child pushed before,
4522    /// and six hops from the child still reach this record.
4523    PusherBefore,
4524    /// A replacement's `prev_rule`: the rule it replaces.
4525    Prev,
4526}
4527
4528/// A snapshot to link, bounded by `options.rule.history`
4529/// (`doc/rule-history-bound.md`): a copy that keeps `history - 1`
4530/// predecessors, each a copy the same way, with no `child` or `next`
4531/// links. Those links are what defeat a cut of the predecessors alone:
4532/// a rule's `child` is its finished child, whose `next` leads back to
4533/// the rule's own record, whose `prev` is a copy of its predecessor,
4534/// whose `child` does the same, a ladder through the whole sequence. So
4535/// `prev.child` and `prev.next` resolve to nothing, and so does a
4536/// predecessor's past them. A cut `next` takes its name with it: a
4537/// snapshot whose next has its own name reads itself as `next`, and a
4538/// copy that kept the name would read `prev.next` as `prev`.
4539///
4540/// The `parent` a pushed child keeps ([`Link::Parent`]) keeps the
4541/// pusher's own `child` and `next`, the child itself as it stood when
4542/// pushed, before it had a `next`: no ladder starts there, and
4543/// `parent.child` and `parent.next` read as they do unbounded. The copy
4544/// made before the pusher links the child ([`Link::PusherBefore`]) cuts
4545/// them: its `child` is the pusher's previous child, and a rule that
4546/// pushes again from its close phase would link every child it pushed,
4547/// each through the one before. So `parent.child.parent.child` and the
4548/// like, the pusher as it stood before the push, resolve to nothing
4549/// under a bound, as they do in TypeScript and Go. With no bound the
4550/// push arm links the child again (see [`Rule::relink_child`]), and
4551/// that path reads the child, as the live links of the other two
4552/// runtimes do.
4553///
4554/// What a rule reads through `prev` (counters, values, tokens, node and
4555/// name) and through `parent` (the pusher and its own parents) is kept.
4556/// A copy is a twenty-field struct with eight `Rc` clones, `history` of
4557/// them per link, and `history` is read as 1 to
4558/// [`crate::options::MAX_RULE_HISTORY`], so a link costs a constant.
4559/// `None` links the snapshot untouched.
4560fn bounded_history(
4561    snapshot: Rc<RuleSnapshot>,
4562    history: Option<usize>,
4563    link: Link,
4564) -> Rc<RuleSnapshot> {
4565    let Some(history) = crate::options::effective_rule_history(history) else {
4566        return snapshot;
4567    };
4568    // The links to copy, nearest first: the snapshot and up to
4569    // `history - 1` of its predecessors. Collected and then rebuilt from
4570    // the far end, with no recursion, so that a bound as long as the
4571    // chain costs stack nothing, as `RuleSnapshot`'s `Drop` walks the
4572    // same chain without it.
4573    let mut links: Vec<&Rc<RuleSnapshot>> = Vec::with_capacity(history);
4574    let mut current = Some(&snapshot);
4575    while let Some(snapshot) = current {
4576        if links.len() == history {
4577            break;
4578        }
4579        links.push(snapshot);
4580        current = snapshot.prev_rule.as_ref();
4581    }
4582    let mut prev: Option<Rc<RuleSnapshot>> = None;
4583    // `depth` counts back from the snapshot itself, which is 0.
4584    for (depth, original) in links.into_iter().enumerate().rev() {
4585        let mut copy = (**original).clone();
4586        if depth != 0 || link != Link::Parent {
4587            copy.child_rule = None;
4588            copy.next_rule = None;
4589            copy.next_rule_name = None;
4590        }
4591        copy.prev_rule = prev.take();
4592        prev = Some(Rc::new(copy));
4593    }
4594    prev.expect("at least the snapshot itself is copied")
4595}
4596
4597fn snapshot_condition_exists(rule: &RuleSnapshot, path: &[String]) -> bool {
4598    if path.first().map(String::as_str) == Some("n") && path.len() == 2 {
4599        rule.n.contains_key(&path[1])
4600    } else if path.first().map(String::as_str) == Some("parent") {
4601        rule.parent_rule
4602            .as_deref()
4603            .is_some_and(|parent| snapshot_condition_exists(parent, &path[1..]))
4604    } else if path.first().map(String::as_str) == Some("child") {
4605        rule.child_rule
4606            .as_deref()
4607            .is_some_and(|child| snapshot_condition_exists(child, &path[1..]))
4608    } else if path.first().map(String::as_str) == Some("prev") {
4609        rule.prev_rule
4610            .as_deref()
4611            .is_some_and(|prev| snapshot_condition_exists(prev, &path[1..]))
4612    } else if path.first().map(String::as_str) == Some("next") {
4613        if let Some(next) = rule.next_rule.as_deref() {
4614            snapshot_condition_exists(next, &path[1..])
4615        } else if rule.next_rule_name.as_deref() == Some(&*rule.name) {
4616            snapshot_condition_exists(rule, &path[1..])
4617        } else {
4618            false
4619        }
4620    } else {
4621        resolve_snapshot_path(rule, path).is_some()
4622    }
4623}
4624
4625fn resolve_snapshot_path(rule: &RuleSnapshot, path: &[String]) -> Option<Value> {
4626    let root = path.first()?.as_str();
4627    let rest = &path[1..];
4628    match root {
4629        "n" if rest.len() == 1 => Some(Value::Number(*rule.n.get(&rest[0]).unwrap_or(&0) as f64)),
4630        "u" => map_path(&rule.u, rest),
4631        "k" => map_path(&rule.k, rest),
4632        "d" if rest.is_empty() => Some(Value::Number(rule.d as f64)),
4633        "i" if rest.is_empty() => Some(Value::Number(rule.i as f64)),
4634        "name" if rest.is_empty() => Some(Value::String(rule.name.to_string())),
4635        "state" if rest.is_empty() => Some(Value::String(
4636            match rule.state {
4637                RuleState::Open => "o",
4638                RuleState::Close => "c",
4639            }
4640            .into(),
4641        )),
4642        "node" => value_path(rule.node.borrow().clone(), rest),
4643        "need" if rest.is_empty() => Some(Value::Number(rule.need as f64)),
4644        "oN" if rest.is_empty() => Some(Value::Number(rule.o.len() as f64)),
4645        "cN" if rest.is_empty() => Some(Value::Number(rule.c.len() as f64)),
4646        "o" => token_list_path(&rule.o, rest),
4647        "c" => token_list_path(&rule.c, rest),
4648        "o0" => token_path(rule.o.first(), rest),
4649        "o1" => token_path(rule.o.get(1), rest),
4650        "c0" => token_path(rule.c.first(), rest),
4651        "c1" => token_path(rule.c.get(1), rest),
4652        "parent" => resolve_snapshot_path(rule.parent_rule.as_deref()?, rest),
4653        "child" => resolve_snapshot_path(rule.child_rule.as_deref()?, rest),
4654        "prev" => resolve_snapshot_path(rule.prev_rule.as_deref()?, rest),
4655        "next" => {
4656            if let Some(next) = rule.next_rule.as_deref() {
4657                resolve_snapshot_path(next, rest)
4658            } else if rule.next_rule_name.as_deref() == Some(&*rule.name) {
4659                resolve_snapshot_path(rule, rest)
4660            } else {
4661                None
4662            }
4663        }
4664        "spec" if rest == ["name"] => Some(Value::String(rule.name.to_string())),
4665        _ => None,
4666    }
4667}
4668
4669fn map_path(map: &HashMap<String, Value>, path: &[String]) -> Option<Value> {
4670    let (name, rest) = path.split_first()?;
4671    value_path(map.get(name)?.clone(), rest)
4672}
4673
4674fn value_path(mut value: Value, path: &[String]) -> Option<Value> {
4675    for part in path {
4676        value = match value {
4677            Value::Object(map) => map.get(part)?.clone(),
4678            Value::Array(items) => items.get(part.parse::<usize>().ok()?)?.clone(),
4679            _ => return None,
4680        };
4681    }
4682    Some(value)
4683}
4684
4685fn token_list_path(tokens: &[Token], path: &[String]) -> Option<Value> {
4686    let (index, rest) = path.split_first()?;
4687    token_path(tokens.get(index.parse::<usize>().ok()?), rest)
4688}
4689
4690fn token_path(token: Option<&Token>, path: &[String]) -> Option<Value> {
4691    let token = token?;
4692    if path.is_empty() {
4693        let mut value = IndexMap::new();
4694        value.insert("tin".into(), Value::Number(token.tin as f64));
4695        value.insert("name".into(), Value::String(token.name.to_string()));
4696        value.insert("src".into(), Value::String(token.src.to_string()));
4697        value.insert("val".into(), token.val.clone());
4698        value.insert("why".into(), Value::String(token.why.to_string()));
4699        return Some(Value::object(value));
4700    }
4701    let (field, rest) = path.split_first()?;
4702    let value = match field.as_str() {
4703        "tin" => Value::Number(token.tin as f64),
4704        "name" => Value::String(token.name.to_string()),
4705        "src" => Value::String(token.src.to_string()),
4706        "val" => token.val.clone(),
4707        "why" => Value::String(token.why.to_string()),
4708        _ => return None,
4709    };
4710    value_path(value, rest)
4711}
4712
4713#[cfg(test)]
4714mod tests {
4715    use super::*;
4716
4717    /// `expected_tins`, `names` and `prepared` are derived from `rules` and
4718    /// positional in it, and `add_rule` is the only thing that writes any
4719    /// of the four. That is the whole reason `rules` is private: while it was
4720    /// public, an embedder inserting or replacing a rule straight into it
4721    /// left the derived tables describing the rule that used to be there,
4722    /// and lookahead went on gating the custom matchers on that rule's token
4723    /// identities. This asserts the invariant a second write path would
4724    /// break; making `add_rule` keep an existing entry instead of replacing
4725    /// it fails the second assertion.
4726    #[test]
4727    fn replacing_a_rule_replaces_the_tables_derived_from_it() {
4728        let mut parser = Parser::new(crate::Options::default());
4729
4730        let mut first = RuleSpec::new("val");
4731        first.bo.push("@enter".into());
4732        first.open.push(AltSpec {
4733            s: vec![vec![crate::TIN_NR]],
4734            a: vec!["@act".into()],
4735            ..Default::default()
4736        });
4737        parser.add_rule(first);
4738        let slot = parser
4739            .rules()
4740            .get_index_of("val")
4741            .expect("the rule was just installed");
4742        assert_eq!(parser.expected_tins[slot].at(true, 0), [crate::TIN_NR]);
4743        assert_eq!(&*parser.names[slot], "val");
4744        assert_eq!(parser.prepared[slot].before(true).len(), 1);
4745        assert_eq!(parser.prepared[slot].open[0].actions.len(), 1);
4746
4747        let mut second = RuleSpec::new("val");
4748        second.open.push(AltSpec {
4749            s: vec![vec![crate::TIN_ST]],
4750            ..Default::default()
4751        });
4752        parser.add_rule(second);
4753
4754        assert_eq!(parser.rules().len(), 1, "a replacement, not an addition");
4755        assert_eq!(
4756            parser.expected_tins[slot].at(true, 0),
4757            [crate::TIN_ST],
4758            "lookahead would still expect the replaced rule's tokens"
4759        );
4760        // The name table is positional now, so a replacement has to land on
4761        // the index the map kept for the key rather than append beside it:
4762        // an appended handle would leave `names` describing the wrong rule
4763        // at every index past this one.
4764        assert_eq!(parser.names.len(), 1, "one rule, one shared name");
4765        assert_eq!(parser.rules().get_index_of("val"), Some(slot));
4766        assert_eq!(&*parser.names[slot], "val");
4767        // The prepared table is positional for the same reason, and its
4768        // record has to describe the rule that is installed now: the parse
4769        // loop trusts a record only while its spec is the one the rule in
4770        // hand holds, so a record left pointing at the replaced spec is a
4771        // record the loop would stop using at all.
4772        assert_eq!(parser.prepared.len(), 1);
4773        assert_eq!(&*parser.prepared[slot].name, "val");
4774        assert!(Arc::ptr_eq(
4775            &parser.prepared[slot].spec,
4776            &parser.rules()["val"]
4777        ));
4778        // The action orders are derived from the same spec and go stale the
4779        // same way. The replacement declares neither the lifecycle action
4780        // nor the alternate action the first rule did, and an order left
4781        // over from that rule is one the loop would run for it.
4782        assert!(parser.prepared[slot].before(true).is_empty());
4783        assert!(parser.prepared[slot].open[0].actions.is_empty());
4784        assert!(!parser.prepared[slot].open[0].named);
4785    }
4786
4787    /// The accepted-token table is positional now, and the slot a rule
4788    /// carries is a hint rather than an authority: `name` is public and a
4789    /// callback may have written it between one token fetch and the next.
4790    /// What the rule is called is what decides which row answers, exactly
4791    /// as the name-keyed map this replaced decided it, and a name that is
4792    /// installed nowhere expects nothing rather than whatever happens to
4793    /// sit at its slot.
4794    #[test]
4795    fn expected_match_tins_follows_the_rule_name_when_the_slot_goes_stale() {
4796        fn rule_named(name: &str, tin: Tin) -> RuleSpec {
4797            let mut spec = RuleSpec::new(name);
4798            spec.open.push(AltSpec {
4799                s: vec![vec![tin]],
4800                ..Default::default()
4801            });
4802            spec
4803        }
4804
4805        let mut options = crate::Options::default();
4806        let tin = options.register_token("#QQ");
4807        options.match_tokens.insert(
4808            "#QQ".into(),
4809            crate::options::MatchToken {
4810                name: "#QQ".into(),
4811                tin,
4812                matcher: crate::options::MatchTokenMatcher::Regex(regex::Regex::new("^q").unwrap()),
4813                eager: false,
4814            },
4815        );
4816        let mut parser = Parser::new(options);
4817        parser.add_rule(rule_named("val", crate::TIN_NR));
4818        parser.add_rule(rule_named("other", crate::TIN_ST));
4819
4820        let val_slot = parser.rules().get_index_of("val").expect("installed");
4821        let other_slot = parser.rules().get_index_of("other").expect("installed");
4822        assert_ne!(val_slot, other_slot);
4823
4824        // Bound the way the parse loop binds it: slot, name and spec all
4825        // describe the same installed rule.
4826        let mut rule = Rule::new("val", Value::Undefined);
4827        rule.bind_spec(
4828            &parser.rules()["val"],
4829            parser.names[val_slot].clone(),
4830            val_slot,
4831        );
4832        assert_eq!(&*parser.expected_match_tins(&rule, 0), [crate::TIN_NR]);
4833
4834        // A callback renames the rule to another installed rule. The slot
4835        // still points at `val`, so the slot alone would answer with
4836        // `val`'s row; the name is what makes it `other`'s, which is what
4837        // the lookup by name always gave.
4838        rule.name = parser.names[other_slot].clone();
4839        assert_eq!(
4840            &*parser.expected_match_tins(&rule, 0),
4841            [crate::TIN_ST],
4842            "the rule is called `other` now, so `other`'s row answers"
4843        );
4844
4845        // A callback swaps the spec and leaves the name. Nothing about
4846        // which row answers depends on the spec, because the row is
4847        // derived from the name the rule was installed under.
4848        rule.name = parser.names[val_slot].clone();
4849        rule.spec = Arc::clone(&parser.rules()["other"]);
4850        assert_eq!(
4851            &*parser.expected_match_tins(&rule, 0),
4852            [crate::TIN_NR],
4853            "still called `val`, so still gated by `val`'s row"
4854        );
4855
4856        // A name installed nowhere expects nothing. Its slot is
4857        // `usize::MAX`, so nothing is in range to answer by accident.
4858        let ghost = Rule::new("ghost", Value::Undefined);
4859        assert_eq!(
4860            &*parser.expected_match_tins(&ghost, 0),
4861            &[] as &[Tin],
4862            "an uninstalled name has no row, not another rule's"
4863        );
4864    }
4865
4866    /// `expected_match_tins` exists to gate the custom matchers, so with no
4867    /// custom matcher registered it answers "nothing" without consulting
4868    /// the table -- the table is still built and still says what it said,
4869    /// and the moment a match token appears the same rule at the same slot
4870    /// gets the table's row again. The first assertion is what pins the
4871    /// short-circuit: were it dropped, the empty-matcher parser would
4872    /// return `[TIN_NR]` and the assertion would fail.
4873    #[test]
4874    fn expected_match_tins_is_empty_until_a_match_token_exists() {
4875        fn val_rule() -> RuleSpec {
4876            let mut spec = RuleSpec::new("val");
4877            spec.open.push(AltSpec {
4878                s: vec![vec![crate::TIN_NR]],
4879                ..Default::default()
4880            });
4881            spec
4882        }
4883        let rule = Rule::new("val", Value::Undefined);
4884
4885        let mut without = Parser::new(crate::Options::default());
4886        without.add_rule(val_rule());
4887        assert!(without.options.match_tokens.is_empty());
4888        assert_eq!(without.expected_tins[0].at(true, 0), [crate::TIN_NR]);
4889        assert_eq!(
4890            &*without.expected_match_tins(&rule, 0),
4891            &[] as &[Tin],
4892            "no custom matcher: nothing to gate, so nothing to look up"
4893        );
4894
4895        let mut options = crate::Options::default();
4896        let tin = options.register_token("#QQ");
4897        options.match_tokens.insert(
4898            "#QQ".into(),
4899            crate::options::MatchToken {
4900                name: "#QQ".into(),
4901                tin,
4902                matcher: crate::options::MatchTokenMatcher::Regex(regex::Regex::new("^q").unwrap()),
4903                eager: false,
4904            },
4905        );
4906        let mut with = Parser::new(options);
4907        with.add_rule(val_rule());
4908        assert_eq!(
4909            &*with.expected_match_tins(&rule, 0),
4910            [crate::TIN_NR],
4911            "a custom matcher is present, so the slot's tins gate it"
4912        );
4913        assert_eq!(
4914            &*with.expected_match_tins(&rule, 1),
4915            &[] as &[Tin],
4916            "a slot the rule never fills expects nothing"
4917        );
4918    }
4919}