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                    let after = self.run_after_actions(
3396                        spec,
3397                        prepared,
3398                        is_open,
3399                        &mut current_rule,
3400                        &mut context,
3401                        ParseSite {
3402                            source: src,
3403                            stack: &stack,
3404                            alts,
3405                        },
3406                    );
3407                    update_partial(mode, &root_node, &current_rule, &stack);
3408                    if self.recover_after_actions(
3409                        after,
3410                        if is_open {
3411                            RuleState::Open
3412                        } else {
3413                            RuleState::Close
3414                        },
3415                        done_alt.clone(),
3416                        src,
3417                        &mut current_rule,
3418                        &mut stack,
3419                        &mut context,
3420                        &mut lexer,
3421                        mode,
3422                    )? {
3423                        continue 'parse;
3424                    }
3425                    if is_open {
3426                        current_rule.state = RuleState::Close;
3427                    }
3428                    child.parent_rule = Some(bounded_history(
3429                        current_rule.snapshot(),
3430                        self.options.rule.history,
3431                        Link::Parent,
3432                    ));
3433                    completed_rule = self.rule_done_copy(&current_rule);
3434                    // The child about to run shares this rule's node cell,
3435                    // and `child_node` may be a second handle on the very
3436                    // container it will write into. Let go of it for the
3437                    // duration -- see `Rule::park_child_node`. Taken after
3438                    // `rule_done_copy`, so a ruleDone subscriber still sees
3439                    // the rule exactly as it stood, and only when every pop
3440                    // that can resume this rule overwrites the field first
3441                    // (see `park_child_node` and `attempt_recover`).
3442                    if park_child_nodes {
3443                        current_rule.park_child_node();
3444                    }
3445                    stack.push(current_rule);
3446                    current_rule = child;
3447                } else if replace_name.is_some() {
3448                    let (replace_slot, replace_shared, replace_spec) =
3449                        route.take().expect("a replace route was resolved above");
3450                    let mut next = Rule::bound(
3451                        replace_shared.clone(),
3452                        current_rule.node.clone(),
3453                        Some(replace_spec),
3454                        replace_slot,
3455                    );
3456                    next.i = next_rule_id;
3457                    next_rule_id += 1;
3458                    next.d = current_rule.d;
3459                    next.parent_node = current_rule.parent_node.clone();
3460                    next.parent_rule = current_rule.parent_rule.clone();
3461                    next.n = Rc::clone(&current_rule.n);
3462                    next.k = Rc::clone(&current_rule.k);
3463                    current_rule.next_rule_name = Some(replace_shared);
3464                    current_rule.next_rule = Some(next.snapshot());
3465                    let after = self.run_after_actions(
3466                        spec,
3467                        prepared,
3468                        is_open,
3469                        &mut current_rule,
3470                        &mut context,
3471                        ParseSite {
3472                            source: src,
3473                            stack: &stack,
3474                            alts,
3475                        },
3476                    );
3477                    update_partial(mode, &root_node, &current_rule, &stack);
3478                    if self.recover_after_actions(
3479                        after,
3480                        if is_open {
3481                            RuleState::Open
3482                        } else {
3483                            RuleState::Close
3484                        },
3485                        done_alt.clone(),
3486                        src,
3487                        &mut current_rule,
3488                        &mut stack,
3489                        &mut context,
3490                        &mut lexer,
3491                        mode,
3492                    )? {
3493                        continue 'parse;
3494                    }
3495                    if is_open {
3496                        current_rule.state = RuleState::Close;
3497                    }
3498                    next.prev_rule = Some(bounded_history(
3499                        current_rule.snapshot(),
3500                        self.options.rule.history,
3501                        Link::Prev,
3502                    ));
3503                    // The rule being replaced stops existing here. If it is
3504                    // the one its parent PUSHED, the parent's `child` link
3505                    // stays on it -- TypeScript never relinks `rule.child`
3506                    // (rules.ts:665) and neither does Go (rule.go:1280) --
3507                    // so freeze the node cell and the record it ended on
3508                    // before it goes.
3509                    if let Some(parent) = stack.last_mut() {
3510                        parent.freeze_child(&current_rule);
3511                    }
3512                    // Moved rather than cloned: this arm hands the
3513                    // finished rule over instead of copying it, so the
3514                    // gate above has nothing to save here.
3515                    completed_rule = Some(current_rule);
3516                    current_rule = next;
3517                } else if is_open {
3518                    current_rule.next_rule_name = Some(current_rule.name.clone());
3519                    current_rule.next_rule = Some(current_rule.snapshot());
3520                    let after = self.run_after_actions(
3521                        spec,
3522                        prepared,
3523                        true,
3524                        &mut current_rule,
3525                        &mut context,
3526                        ParseSite {
3527                            source: src,
3528                            stack: &stack,
3529                            alts,
3530                        },
3531                    );
3532                    update_partial(mode, &root_node, &current_rule, &stack);
3533                    if self.recover_after_actions(
3534                        after,
3535                        RuleState::Open,
3536                        done_alt.clone(),
3537                        src,
3538                        &mut current_rule,
3539                        &mut stack,
3540                        &mut context,
3541                        &mut lexer,
3542                        mode,
3543                    )? {
3544                        continue 'parse;
3545                    }
3546                    current_rule.state = RuleState::Close;
3547                    completed_rule = self.rule_done_copy(&current_rule);
3548                } else {
3549                    // Close phase pop
3550                    current_rule.next_rule_name = stack.last().map(|rule| rule.name.clone());
3551                    current_rule.next_rule = stack.last().map(Rule::snapshot);
3552                    let after = self.run_after_actions(
3553                        spec,
3554                        prepared,
3555                        false,
3556                        &mut current_rule,
3557                        &mut context,
3558                        ParseSite {
3559                            source: src,
3560                            stack: &stack,
3561                            alts,
3562                        },
3563                    );
3564                    update_partial(mode, &root_node, &current_rule, &stack);
3565                    if self.recover_after_actions(
3566                        after,
3567                        RuleState::Close,
3568                        done_alt.clone(),
3569                        src,
3570                        &mut current_rule,
3571                        &mut stack,
3572                        &mut context,
3573                        &mut lexer,
3574                        mode,
3575                    )? {
3576                        continue 'parse;
3577                    }
3578                    let parent = stack.pop();
3579                    completed_rule = self.rule_done_copy(&current_rule);
3580                    if let Some(mut parent) = parent {
3581                        parent.accept_child(&current_rule);
3582                        current_rule = parent;
3583                    } else {
3584                        // Root rule popped! Done.
3585                        completed_value = Some(current_rule.node.borrow().clone());
3586                    }
3587                }
3588                if let Some(completed_rule) = &completed_rule {
3589                    self.notify_rule_done(
3590                        completed_rule,
3591                        &context,
3592                        if is_open {
3593                            RuleState::Open
3594                        } else {
3595                            RuleState::Close
3596                        },
3597                        done_alt,
3598                        src,
3599                        &stack,
3600                    )?;
3601                }
3602                if let Some(value) = completed_value {
3603                    final_value = Some(value);
3604                    break;
3605                }
3606                update_partial(mode, &root_node, &current_rule, &stack);
3607            } else if alts.is_empty() {
3608                // A state with no alternatives performs an implicit empty
3609                // pass. It still resolves next and runs lifecycle after-actions.
3610                let completed_rule: Option<Rule>;
3611                let mut completed_value = None;
3612                if is_open {
3613                    current_rule.next_rule_name = Some(current_rule.name.clone());
3614                    current_rule.next_rule = Some(current_rule.snapshot());
3615                    let after = self.run_after_actions(
3616                        spec,
3617                        prepared,
3618                        true,
3619                        &mut current_rule,
3620                        &mut context,
3621                        ParseSite {
3622                            source: src,
3623                            stack: &stack,
3624                            alts,
3625                        },
3626                    );
3627                    update_partial(mode, &root_node, &current_rule, &stack);
3628                    if self.recover_after_actions(
3629                        after,
3630                        RuleState::Open,
3631                        None,
3632                        src,
3633                        &mut current_rule,
3634                        &mut stack,
3635                        &mut context,
3636                        &mut lexer,
3637                        mode,
3638                    )? {
3639                        continue 'parse;
3640                    }
3641                    current_rule.state = RuleState::Close;
3642                    completed_rule = self.rule_done_copy(&current_rule);
3643                } else {
3644                    current_rule.next_rule_name = stack.last().map(|rule| rule.name.clone());
3645                    current_rule.next_rule = stack.last().map(Rule::snapshot);
3646                    let after = self.run_after_actions(
3647                        spec,
3648                        prepared,
3649                        false,
3650                        &mut current_rule,
3651                        &mut context,
3652                        ParseSite {
3653                            source: src,
3654                            stack: &stack,
3655                            alts,
3656                        },
3657                    );
3658                    update_partial(mode, &root_node, &current_rule, &stack);
3659                    if self.recover_after_actions(
3660                        after,
3661                        RuleState::Close,
3662                        None,
3663                        src,
3664                        &mut current_rule,
3665                        &mut stack,
3666                        &mut context,
3667                        &mut lexer,
3668                        mode,
3669                    )? {
3670                        continue 'parse;
3671                    }
3672                    let parent = stack.pop();
3673                    completed_rule = self.rule_done_copy(&current_rule);
3674                    if let Some(mut parent) = parent {
3675                        parent.accept_child(&current_rule);
3676                        current_rule = parent;
3677                    } else {
3678                        completed_value = Some(current_rule.node.borrow().clone());
3679                    }
3680                }
3681                if let Some(completed_rule) = &completed_rule {
3682                    self.notify_rule_done(
3683                        completed_rule,
3684                        &context,
3685                        if is_open {
3686                            RuleState::Open
3687                        } else {
3688                            RuleState::Close
3689                        },
3690                        None,
3691                        src,
3692                        &stack,
3693                    )?;
3694                }
3695                if let Some(value) = completed_value {
3696                    final_value = Some(value);
3697                    break;
3698                }
3699                update_partial(mode, &root_node, &current_rule, &stack);
3700            } else {
3701                // Declared alternatives exist, but none matched.
3702                if is_open {
3703                    if let Err(error) = self.ensure_lookahead(
3704                        &mut lexer,
3705                        &mut context,
3706                        &mut current_rule,
3707                        1,
3708                        mode,
3709                        ParseSite {
3710                            source: src,
3711                            stack: &stack,
3712                            alts,
3713                        },
3714                        Fetch::Rule,
3715                    ) {
3716                        return Err(self.attach_error(error, &current_rule, &stack, alts, None));
3717                    }
3718                    if let Some(capture) = mode.continuation.as_deref_mut() {
3719                        let base = failed_alt_tins(&context, alts, &self.options);
3720                        capture.failure = continuation_tins(
3721                            &context,
3722                            &current_rule,
3723                            &stack,
3724                            &self.rules,
3725                            &self.options,
3726                            0,
3727                            Some(&base),
3728                        );
3729                    }
3730                    let t0 = context.t.first().cloned();
3731                    let (src_token, si, ri, ci) = if let Some(t) = t0.as_ref() {
3732                        (t.src.to_string(), t.site.pos, t.site.ri, t.site.ci)
3733                    } else {
3734                        (String::new(), src.len(), 1, 1)
3735                    };
3736                    let code = t0.as_ref().map_or("unexpected", deferred_error_code);
3737                    let error = TabnasError::new(code, src_token, src, si, ri, ci);
3738                    let done_alt = (!alts.is_empty() && !self.rule_done_subscribers.is_empty())
3739                        .then(|| RuleDoneAlt {
3740                            b: 0,
3741                            g: Vec::new(),
3742                            p: String::new(),
3743                            r: String::new(),
3744                            err: t0.clone(),
3745                        });
3746                    let error = self.attach_error(error, &current_rule, &stack, alts, t0.as_ref());
3747                    self.recover_error_pass(
3748                        error,
3749                        RuleState::Open,
3750                        done_alt,
3751                        false,
3752                        src,
3753                        &mut current_rule,
3754                        &mut stack,
3755                        &mut context,
3756                        &mut lexer,
3757                        mode,
3758                    )?;
3759                    continue;
3760                } else {
3761                    if let Err(error) = self.ensure_lookahead(
3762                        &mut lexer,
3763                        &mut context,
3764                        &mut current_rule,
3765                        1,
3766                        mode,
3767                        ParseSite {
3768                            source: src,
3769                            stack: &stack,
3770                            alts,
3771                        },
3772                        Fetch::Rule,
3773                    ) {
3774                        return Err(self.attach_error(error, &current_rule, &stack, alts, None));
3775                    }
3776                    if let Some(capture) = mode.continuation.as_deref_mut() {
3777                        let base = failed_alt_tins(&context, alts, &self.options);
3778                        capture.failure = continuation_tins(
3779                            &context,
3780                            &current_rule,
3781                            &stack,
3782                            &self.rules,
3783                            &self.options,
3784                            0,
3785                            Some(&base),
3786                        );
3787                    }
3788                    let token = context.t.first().cloned();
3789                    let (source, pos, row, col) = token.as_ref().map_or_else(
3790                        || (String::new(), src.chars().count(), 1, 1),
3791                        |value| {
3792                            (
3793                                value.src.to_string(),
3794                                value.site.pos,
3795                                value.site.ri,
3796                                value.site.ci,
3797                            )
3798                        },
3799                    );
3800                    let code = token.as_ref().map_or("unexpected", deferred_error_code);
3801                    let error = TabnasError::new(code, source, src, pos, row, col);
3802                    let done_alt = Some(RuleDoneAlt {
3803                        b: 0,
3804                        g: Vec::new(),
3805                        p: String::new(),
3806                        r: String::new(),
3807                        err: token.clone(),
3808                    });
3809                    let error =
3810                        self.attach_error(error, &current_rule, &stack, alts, token.as_ref());
3811                    self.recover_error_pass(
3812                        error,
3813                        RuleState::Close,
3814                        done_alt,
3815                        false,
3816                        src,
3817                        &mut current_rule,
3818                        &mut stack,
3819                        &mut context,
3820                        &mut lexer,
3821                        mode,
3822                    )?;
3823                    continue;
3824                }
3825            }
3826        }
3827
3828        let res = final_value.unwrap_or(Value::Null).unwrap_undefined();
3829        if mode.recovering {
3830            mode.partial = Some(PartialValue::Complete(res.clone()));
3831        }
3832
3833        // Post-loop check: ensure no unexpected trailing tokens. Recovery
3834        // keeps the completed value and reports the trailing fault.
3835        if let Err(error) = self.ensure_lookahead(
3836            &mut lexer,
3837            &mut context,
3838            &mut current_rule,
3839            1,
3840            mode,
3841            ParseSite {
3842                source: src,
3843                stack: &stack,
3844                alts: &[],
3845            },
3846            Fetch::Trailing,
3847        ) {
3848            let error = self.attach_error(error, &current_rule, &stack, &[], None);
3849            if mode.recovering {
3850                mode.errors.push(error.clone());
3851                context.errs.push(error);
3852                return Ok(res);
3853            }
3854            return Err(error);
3855        }
3856        if let Some(t0) = context.t.first() {
3857            if t0.tin != TIN_ZZ {
3858                let code = if t0.tin == TIN_BD && !t0.why.is_empty() {
3859                    t0.why.as_str()
3860                } else {
3861                    "unexpected"
3862                };
3863                let error =
3864                    TabnasError::new(code, &*t0.src, src, t0.site.pos, t0.site.ri, t0.site.ci);
3865                let error = self.attach_error(
3866                    error,
3867                    &current_rule,
3868                    &stack,
3869                    &[AltSpec {
3870                        s: vec![vec![TIN_ZZ]],
3871                        ..Default::default()
3872                    }],
3873                    Some(t0),
3874                );
3875                if mode.recovering {
3876                    mode.errors.push(error.clone());
3877                    context.errs.push(error);
3878                    return Ok(res);
3879                }
3880                return Err(error);
3881            }
3882        }
3883        if self
3884            .options
3885            .result
3886            .fail
3887            .iter()
3888            .any(|failed| failed.deep_equal(&res))
3889        {
3890            let token = context.t.first();
3891            let error = token.map_or_else(
3892                || TabnasError::new("unexpected", "", src, 0, 1, 1),
3893                |token| {
3894                    TabnasError::new(
3895                        "unexpected",
3896                        &*token.src,
3897                        src,
3898                        token.site.pos,
3899                        token.site.ri,
3900                        token.site.ci,
3901                    )
3902                },
3903            );
3904            if mode.recovering {
3905                mode.errors.push(error.clone());
3906                context.errs.push(error);
3907                return Ok(res);
3908            }
3909            return Err(error);
3910        }
3911        Ok(res)
3912    }
3913}
3914
3915/// Keep a handle to the best partial node the recovery path would return.
3916///
3917/// Fifteen sites in the parse loop call this, twelve times per input
3918/// construct on the benchmark grammars, and outside recovery every one of them is a
3919/// load and a branch wrapped in a call. The guard is inline so the call
3920/// goes away. Inside recovery this deliberately clones only an `Rc` node
3921/// handle, never the `Value` being built: a `Value` snapshot keeps its
3922/// container shared and turns the next mutation into a copy of the entire
3923/// accumulated prefix.
3924///
3925/// The value read at the end is the one the last site would have taken:
3926/// the node is chosen by the same test, in the same order, and it keeps
3927/// the node even when recovery then pops the rules that held it. Only a
3928/// write to that node between the last site and the failure can tell the
3929/// two apart -- an action or callback that changes the node and then
3930/// fails, or is followed by a matched action whose error recovery gives up
3931/// on -- and the value is then the node as the failure left it, which is
3932/// what TypeScript returns: it reads the node in its `catch`
3933/// (`ts/src/parser.ts`), not before the step that failed.
3934#[inline]
3935fn update_partial(
3936    mode: &mut ParseMode<'_>,
3937    root_node: &Rc<RefCell<Value>>,
3938    current_rule: &Rule,
3939    stack: &[Rule],
3940) {
3941    if mode.recovering {
3942        mode.partial = best_partial_node(root_node, current_rule, stack).map(PartialValue::Node);
3943    }
3944}
3945
3946#[inline(never)]
3947fn best_partial_node(
3948    root_node: &Rc<RefCell<Value>>,
3949    current_rule: &Rule,
3950    stack: &[Rule],
3951) -> Option<Rc<RefCell<Value>>> {
3952    let usable = |node: &Rc<RefCell<Value>>| {
3953        (!matches!(*node.borrow(), Value::Undefined | Value::Null)).then(|| Rc::clone(node))
3954    };
3955
3956    usable(root_node)
3957        .or_else(|| stack.iter().find_map(|rule| usable(&rule.node)))
3958        .or_else(|| usable(&current_rule.node))
3959}
3960
3961fn error_token(error: &TabnasError) -> Token {
3962    let byte_position = error
3963        .full_source
3964        .char_indices()
3965        .nth(error.pos)
3966        .map_or(error.full_source.len(), |(index, _)| index);
3967    let mut token = Token::new(
3968        "#BD",
3969        TIN_BD,
3970        Value::Undefined,
3971        error.src.clone(),
3972        crate::Point {
3973            len: error.len,
3974            site: crate::Site {
3975                si: byte_position,
3976                pos: error.pos,
3977                ri: error.row,
3978                ci: error.col,
3979            },
3980        },
3981    );
3982    token.err = crate::TokenCode::from(error.code.as_str());
3983    token.why = token.err.clone();
3984    token
3985}
3986
3987fn deferred_error_code(token: &Token) -> &str {
3988    if token.tin != TIN_BD {
3989        "unexpected"
3990    } else if !token.why.is_empty() {
3991        &token.why
3992    } else if !token.err.is_empty() {
3993        &token.err
3994    } else {
3995        "unexpected"
3996    }
3997}
3998
3999fn raised_error_code(token: &Token) -> &str {
4000    if !token.err.is_empty() {
4001        &token.err
4002    } else if !token.why.is_empty() {
4003        &token.why
4004    } else {
4005        "unexpected"
4006    }
4007}
4008
4009fn mid_construct(code: &str) -> bool {
4010    matches!(code, "unprintable" | "invalid_unicode" | "invalid_ascii")
4011}
4012
4013/// Record a bad token met at a rule's fetch under recovery, in the order
4014/// TypeScript's `parse_alts` does it: a token inside the run the last one
4015/// began grows that run's error rather than being listed again; a new run
4016/// inside the suppress window of the recovery before it is a cascade of
4017/// that fault and is dropped; anything else is recorded. Returns whether
4018/// the fetch goes on past the token. It does not when the run outgrows
4019/// `maxSkip`, or when the list has outgrown `maxRecoveries`, a cap read
4020/// after recording, so the error that stops the parse is listed.
4021fn absorb_lex_error(
4022    error: &TabnasError,
4023    context: &mut Context,
4024    options: &Options,
4025    errors: &mut Vec<TabnasError>,
4026) -> bool {
4027    let recover = &options.parse.recover;
4028    let run = context
4029        .bad_error
4030        .filter(|index| *index < errors.len())
4031        .filter(|_| context.bad_to.is_some_and(|bad_to| error.pos <= bad_to));
4032    if let Some(index) = run {
4033        let previous = &mut errors[index];
4034        let recovered = previous.recovered.get_or_insert(crate::RecoveredAt {
4035            skipped: 0,
4036            sync: None,
4037            bad: true,
4038        });
4039        recovered.skipped = recovered.skipped.saturating_add(1);
4040        let skipped = recovered.skipped;
4041        if let Some(context_previous) = context.errs.get_mut(index) {
4042            *context_previous = previous.clone();
4043        }
4044        if recover.max_skip < skipped {
4045            return false;
4046        }
4047    } else if context
4048        .recover_at
4049        .is_some_and(|at| context.v_abs.saturating_sub(at) < recover.suppress)
4050    {
4051        context.bad_error = None;
4052    } else {
4053        let mut recorded = error.clone();
4054        recorded.recovered = Some(crate::RecoveredAt {
4055            skipped: 1,
4056            sync: None,
4057            bad: true,
4058        });
4059        errors.push(recorded.clone());
4060        context.errs.push(recorded);
4061        context.bad_error = Some(errors.len() - 1);
4062        context.recover_at = Some(context.v_abs);
4063    }
4064    context.bad_to = Some(error.pos.saturating_add(error.len.max(1)));
4065    errors.len() <= recover.max_recoveries
4066}
4067
4068fn slot_matches(slot: &[Tin], tin: Tin) -> bool {
4069    tin != TIN_BD && (slot.is_empty() || slot.contains(&tin) || slot.contains(&TIN_AA))
4070}
4071
4072fn alt_match_depth(alt: &AltSpec, context: &Context) -> usize {
4073    let mut depth = 0;
4074    while depth < alt.s.len() {
4075        let Some(token) = context.t.get(depth) else {
4076            break;
4077        };
4078        if !slot_matches(&alt.s[depth], token.tin) {
4079            break;
4080        }
4081        depth += 1;
4082    }
4083    depth
4084}
4085
4086fn completion_tins(slot: &[Tin]) -> impl Iterator<Item = Tin> + '_ {
4087    slot.iter()
4088        .copied()
4089        .chain(slot.is_empty().then_some(TIN_AA))
4090}
4091
4092fn failed_alt_tins(context: &Context, alts: &[AltSpec], options: &Options) -> Vec<Tin> {
4093    let mut out = BTreeSet::new();
4094    for alt in alts {
4095        if !groups_enabled(alt, options) {
4096            continue;
4097        }
4098        let depth = alt_match_depth(alt, context);
4099        if let Some(slot) = alt.s.get(depth) {
4100            out.extend(completion_tins(slot));
4101        }
4102    }
4103    out.into_iter().collect()
4104}
4105
4106fn lead_tins(alts: &[AltSpec], options: &Options, out: &mut BTreeSet<Tin>) {
4107    for alt in alts {
4108        if !groups_enabled(alt, options) {
4109            continue;
4110        }
4111        if let Some(slot) = alt.s.first() {
4112            out.extend(completion_tins(slot));
4113        }
4114    }
4115}
4116
4117fn has_empty_close(spec: &RuleSpec, options: &Options) -> bool {
4118    spec.close
4119        .iter()
4120        .any(|alt| groups_enabled(alt, options) && alt.s.is_empty())
4121}
4122
4123fn alt_has_sync_group(alt: &AltSpec, sync_groups: &[String]) -> bool {
4124    alt.g
4125        .split(',')
4126        .map(str::trim)
4127        .any(|tag| sync_groups.iter().any(|wanted| wanted == tag))
4128}
4129
4130fn add_close_tins(
4131    rule: &Rule,
4132    rules: &IndexMap<String, Arc<RuleSpec>>,
4133    options: &Options,
4134    tagged_only: bool,
4135    out: &mut BTreeSet<Tin>,
4136) {
4137    let Some(spec) = rules.get(&*rule.name) else {
4138        return;
4139    };
4140    for alt in &spec.close {
4141        if !groups_enabled(alt, options)
4142            || (tagged_only && !alt_has_sync_group(alt, &options.parse.recover.sync_groups))
4143        {
4144            continue;
4145        }
4146        if let Some(slot) = alt.s.first() {
4147            out.extend(completion_tins(slot));
4148        }
4149    }
4150}
4151
4152fn compute_sync_tins(
4153    rule: &Rule,
4154    stack: &[Rule],
4155    rules: &IndexMap<String, Arc<RuleSpec>>,
4156    options: &Options,
4157) -> BTreeSet<Tin> {
4158    let mut out = BTreeSet::new();
4159    add_close_tins(rule, rules, options, true, &mut out);
4160    for parent in stack.iter().rev() {
4161        add_close_tins(parent, rules, options, true, &mut out);
4162    }
4163    if out.is_empty() {
4164        add_close_tins(rule, rules, options, false, &mut out);
4165        for parent in stack.iter().rev() {
4166            add_close_tins(parent, rules, options, false, &mut out);
4167        }
4168    }
4169    for name in &options.parse.recover.sync_tokens {
4170        if let Some(tin) = options.token(name) {
4171            out.insert(tin);
4172        }
4173        if let Some(tins) = options.token_set.get(name.trim_start_matches('#')) {
4174            out.extend(tins.iter().copied());
4175        }
4176    }
4177    out
4178}
4179
4180fn accepts_close(
4181    rule: &Rule,
4182    tin: Tin,
4183    rules: &IndexMap<String, Arc<RuleSpec>>,
4184    options: &Options,
4185) -> bool {
4186    rules.get(&*rule.name).is_some_and(|spec| {
4187        spec.close.iter().any(|alt| {
4188            groups_enabled(alt, options)
4189                && (alt.s.is_empty() || alt.s.first().is_some_and(|slot| slot_matches(slot, tin)))
4190        })
4191    })
4192}
4193
4194fn add_openers(
4195    name: &str,
4196    rules: &IndexMap<String, Arc<RuleSpec>>,
4197    options: &Options,
4198    opened: &mut BTreeSet<String>,
4199    out: &mut BTreeSet<Tin>,
4200) {
4201    if name.is_empty() || !opened.insert(name.to_owned()) {
4202        return;
4203    }
4204    let Some(spec) = rules.get(name) else {
4205        return;
4206    };
4207    lead_tins(&spec.open, options, out);
4208    for alt in &spec.open {
4209        if !groups_enabled(alt, options) || !alt.s.is_empty() {
4210            continue;
4211        }
4212        if let Some(push) = alt.p.as_deref() {
4213            add_openers(push, rules, options, opened, out);
4214        }
4215        if let Some(replace) = alt.r.as_deref() {
4216            add_openers(replace, rules, options, opened, out);
4217        }
4218    }
4219}
4220
4221fn continuation_tins(
4222    context: &Context,
4223    rule: &Rule,
4224    stack: &[Rule],
4225    rules: &IndexMap<String, Arc<RuleSpec>>,
4226    options: &Options,
4227    query_pos: usize,
4228    failed: Option<&[Tin]>,
4229) -> Vec<Tin> {
4230    let Some(spec) = rules.get(&*rule.name) else {
4231        return Vec::new();
4232    };
4233    let state_alts = if rule.state == RuleState::Open {
4234        &spec.open
4235    } else {
4236        &spec.close
4237    };
4238    let mut out = BTreeSet::new();
4239
4240    if let Some(failed) = failed.filter(|tins| !tins.is_empty()) {
4241        out.extend(failed.iter().copied());
4242    } else {
4243        for alt in state_alts {
4244            if !groups_enabled(alt, options) {
4245                continue;
4246            }
4247            let depth = alt_match_depth(alt, context);
4248            if depth == query_pos {
4249                if let Some(slot) = alt.s.get(depth) {
4250                    out.extend(completion_tins(slot));
4251                }
4252            }
4253        }
4254    }
4255
4256    // If the current rule can close without consuming a token, closing
4257    // tokens accepted by each parent are legal at the same point too.
4258    let mut close_rule = rule;
4259    let mut parent_index = stack.len();
4260    while let Some(close_spec) = rules.get(&*close_rule.name) {
4261        if !has_empty_close(close_spec, options) || parent_index == 0 {
4262            break;
4263        }
4264        parent_index -= 1;
4265        let parent = &stack[parent_index];
4266        if let Some(parent_spec) = rules.get(&*parent.name) {
4267            lead_tins(&parent_spec.close, options, &mut out);
4268        }
4269        close_rule = parent;
4270    }
4271
4272    // A fully matched alternate can immediately hand control to a pushed or
4273    // replacement rule. Follow empty opening hand-offs transitively.
4274    let mut opened = BTreeSet::new();
4275    for alt in state_alts {
4276        if !groups_enabled(alt, options) || alt_match_depth(alt, context) != alt.s.len() {
4277            continue;
4278        }
4279        // A callback backtrack is only knowable while executing the match.
4280        // Do not speculate that its static default is the handover point.
4281        if alt.b_fn.is_some() {
4282            continue;
4283        }
4284        if alt.s.len().checked_sub(alt.b) != Some(query_pos) {
4285            continue;
4286        }
4287        if let Some(push) = alt.p.as_deref() {
4288            add_openers(push, rules, options, &mut opened, &mut out);
4289        }
4290        if let Some(replace) = alt.r.as_deref() {
4291            add_openers(replace, rules, options, &mut opened, &mut out);
4292        }
4293    }
4294
4295    out.into_iter().collect()
4296}
4297
4298/// The group tags a comma-separated group list declares.
4299///
4300/// One definition, because two places ask: the include/exclude filter in
4301/// `groups_enabled`, and the list the engine publishes into `matched.g`.
4302/// They have to agree on what a tag is -- trimmed, and never empty.
4303fn listed(list: &str) -> impl Iterator<Item = &str> {
4304    list.split(',')
4305        .map(str::trim)
4306        .filter(|entry| !entry.is_empty())
4307}
4308
4309pub(crate) fn groups_enabled(alt: &AltSpec, options: &Options) -> bool {
4310    // With neither an include nor an exclude list there is nothing to
4311    // test against, so every alternate is enabled whatever groups it
4312    // declares. That is the usual case, and it is asked once per
4313    // alternate per iteration.
4314    let include = options.rule.include.as_str();
4315    let exclude = options.rule.exclude.as_str();
4316    if include.is_empty() && exclude.is_empty() {
4317        return true;
4318    }
4319    // Iterators rather than three collected `Vec<&str>`. The question is
4320    // the same one and the answer is the same answer; the three heap
4321    // allocations per call were not part of either. The shipped JSON
4322    // preset sets `rule.include` to "json", so the early return above
4323    // never fires for it and every alternate of every rule step paid
4324    // them.
4325    let declares = |wanted: &str| listed(&alt.g).any(|group| group == wanted);
4326    let included = listed(include).next().is_none() || listed(include).any(&declares);
4327    included && !listed(exclude).any(&declares)
4328}
4329
4330fn builtin_condition_matches(reference: Option<&str>, rule: &Rule) -> bool {
4331    let phase = match rule.k.get("pd_phase") {
4332        Some(Value::Number(value)) => *value as i32,
4333        _ => 0,
4334    };
4335    match reference {
4336        None => true,
4337        Some("@probePhase0$") => phase == 0,
4338        Some("@probePhase1$") => phase == 1,
4339        Some("@probePhase2$") => phase == 2,
4340        Some(_) => false,
4341    }
4342}
4343
4344fn conditions_match(conditions: &[Condition], rule: &Rule, ancestors: &[Rule]) -> bool {
4345    conditions.iter().all(|condition| {
4346        let resolved = resolve_condition_path(rule, ancestors, &condition.path);
4347        if condition.op == CompareOp::Exist {
4348            let exists = condition_exists(rule, ancestors, &condition.path);
4349            let wanted = matches!(condition.value, Value::Bool(true));
4350            return exists == wanted;
4351        }
4352        let Some(actual) = resolved else {
4353            return !matches!(condition.op, CompareOp::Eq);
4354        };
4355        match condition.op {
4356            CompareOp::Eq => actual.deep_equal(&condition.value),
4357            CompareOp::Ne => !actual.deep_equal(&condition.value),
4358            CompareOp::Lt => ordered(&actual, &condition.value).is_none_or(|value| value < 0),
4359            CompareOp::Lte => ordered(&actual, &condition.value).is_none_or(|value| value <= 0),
4360            CompareOp::Gt => ordered(&actual, &condition.value).is_none_or(|value| value > 0),
4361            CompareOp::Gte => ordered(&actual, &condition.value).is_none_or(|value| value >= 0),
4362            CompareOp::Exist => unreachable!("handled above"),
4363        }
4364    })
4365}
4366
4367fn ordered(left: &Value, right: &Value) -> Option<i8> {
4368    match (left, right) {
4369        (Value::Number(left), Value::Number(right)) => Some(if left < right {
4370            -1
4371        } else if left > right {
4372            1
4373        } else {
4374            0
4375        }),
4376        (Value::String(left), Value::String(right)) => Some(match left.cmp(right) {
4377            std::cmp::Ordering::Less => -1,
4378            std::cmp::Ordering::Equal => 0,
4379            std::cmp::Ordering::Greater => 1,
4380        }),
4381        _ => None,
4382    }
4383}
4384
4385fn condition_exists(rule: &Rule, ancestors: &[Rule], path: &[String]) -> bool {
4386    if path.first().map(String::as_str) == Some("n") && path.len() == 2 {
4387        rule.n.contains_key(&path[1])
4388    } else if path.first().map(String::as_str) == Some("parent") {
4389        ancestors
4390            .split_last()
4391            .is_some_and(|(parent, parent_ancestors)| {
4392                condition_exists(parent, parent_ancestors, &path[1..])
4393            })
4394    } else if path.first().map(String::as_str) == Some("child") {
4395        rule.child_rule
4396            .as_deref()
4397            .is_some_and(|child| snapshot_condition_exists(child, &path[1..]))
4398    } else if path.first().map(String::as_str) == Some("prev") {
4399        rule.prev_rule
4400            .as_deref()
4401            .is_some_and(|prev| snapshot_condition_exists(prev, &path[1..]))
4402    } else if path.first().map(String::as_str) == Some("next") {
4403        if let Some(next) = rule.next_rule.as_deref() {
4404            snapshot_condition_exists(next, &path[1..])
4405        } else if rule.next_rule_name.as_deref() == Some(&*rule.name) {
4406            condition_exists(rule, ancestors, &path[1..])
4407        } else {
4408            false
4409        }
4410    } else {
4411        resolve_condition_path(rule, ancestors, path).is_some()
4412    }
4413}
4414
4415fn resolve_condition_path(rule: &Rule, ancestors: &[Rule], path: &[String]) -> Option<Value> {
4416    let root = path.first()?.as_str();
4417    let rest = &path[1..];
4418    match root {
4419        "n" if rest.len() == 1 => Some(Value::Number(*rule.n.get(&rest[0]).unwrap_or(&0) as f64)),
4420        "u" => map_path(&rule.u, rest),
4421        "k" => map_path(&rule.k, rest),
4422        "d" if rest.is_empty() => Some(Value::Number(rule.d as f64)),
4423        "i" if rest.is_empty() => Some(Value::Number(rule.i as f64)),
4424        "name" if rest.is_empty() => Some(Value::String(rule.name.to_string())),
4425        "state" if rest.is_empty() => Some(Value::String(
4426            match rule.state {
4427                RuleState::Open => "o",
4428                RuleState::Close => "c",
4429            }
4430            .into(),
4431        )),
4432        "node" => value_path(rule.node.borrow().clone(), rest),
4433        "need" if rest.is_empty() => Some(Value::Number(rule.need as f64)),
4434        "oN" if rest.is_empty() => Some(Value::Number(rule.o.len() as f64)),
4435        "cN" if rest.is_empty() => Some(Value::Number(rule.c.len() as f64)),
4436        "o" => token_list_path(&rule.o, rest),
4437        "c" => token_list_path(&rule.c, rest),
4438        "o0" => token_path(rule.o.first(), rest),
4439        "o1" => token_path(rule.o.get(1), rest),
4440        "c0" => token_path(rule.c.first(), rest),
4441        "c1" => token_path(rule.c.get(1), rest),
4442        "parent" => {
4443            let (parent, parent_ancestors) = ancestors.split_last()?;
4444            resolve_condition_path(parent, parent_ancestors, rest)
4445        }
4446        "child" => resolve_snapshot_path(rule.child_rule.as_deref()?, rest),
4447        "prev" => resolve_snapshot_path(rule.prev_rule.as_deref()?, rest),
4448        "next" => {
4449            if let Some(next) = rule.next_rule.as_deref() {
4450                resolve_snapshot_path(next, rest)
4451            } else if rule.next_rule_name.as_deref() == Some(&*rule.name) {
4452                resolve_condition_path(rule, ancestors, rest)
4453            } else {
4454                None
4455            }
4456        }
4457        "spec" if rest == ["name"] => Some(Value::String(rule.name.to_string())),
4458        _ => None,
4459    }
4460}
4461
4462/// Which link a bounded snapshot becomes (see [`bounded_history`]).
4463#[derive(Clone, Copy, PartialEq, Eq)]
4464enum Link {
4465    /// The `parent_rule` a pushed child keeps: the pusher once it has
4466    /// linked the child, made at the end of the push arm.
4467    Parent,
4468    /// The pusher before it links the child, made at the start of the
4469    /// push arm. It survives as the `parent_rule` of the child's own
4470    /// snapshot, the one the pusher links as `child`.
4471    PusherBefore,
4472    /// A replacement's `prev_rule`: the rule it replaces.
4473    Prev,
4474}
4475
4476/// A snapshot to link, bounded by `options.rule.history`
4477/// (`doc/rule-history-bound.md`): a copy that keeps `history - 1`
4478/// predecessors, each a copy the same way, with no `child` or `next`
4479/// links. Those links are what defeat a cut of the predecessors alone:
4480/// a rule's `child` is its finished child, whose `next` leads back to
4481/// the rule's own record, whose `prev` is a copy of its predecessor,
4482/// whose `child` does the same, a ladder through the whole sequence. So
4483/// `prev.child` and `prev.next` resolve to nothing, and so does a
4484/// predecessor's past them. A cut `next` takes its name with it: a
4485/// snapshot whose next has its own name reads itself as `next`, and a
4486/// copy that kept the name would read `prev.next` as `prev`.
4487///
4488/// The `parent` a pushed child keeps ([`Link::Parent`]) keeps the
4489/// pusher's own `child` and `next`, the child itself as it stood when
4490/// pushed, before it had a `next`: no ladder starts there, and
4491/// `parent.child` and `parent.next` read as they do unbounded. The copy
4492/// made before the pusher links the child ([`Link::PusherBefore`]) cuts
4493/// them: its `child` is the pusher's previous child, and a rule that
4494/// pushes again from its close phase would link every child it pushed,
4495/// each through the one before. So `parent.child.parent.child` and the
4496/// like, the pusher as it stood before the push, resolve to nothing.
4497///
4498/// What a rule reads through `prev` (counters, values, tokens, node and
4499/// name) and through `parent` (the pusher and its own parents) is kept.
4500/// A copy is a twenty-field struct with eight `Rc` clones, `history` of
4501/// them per link, and `history` is read as 1 to
4502/// [`crate::options::MAX_RULE_HISTORY`], so a link costs a constant.
4503/// `None` links the snapshot untouched.
4504fn bounded_history(
4505    snapshot: Rc<RuleSnapshot>,
4506    history: Option<usize>,
4507    link: Link,
4508) -> Rc<RuleSnapshot> {
4509    let Some(history) = crate::options::effective_rule_history(history) else {
4510        return snapshot;
4511    };
4512    // The links to copy, nearest first: the snapshot and up to
4513    // `history - 1` of its predecessors. Collected and then rebuilt from
4514    // the far end, with no recursion, so that a bound as long as the
4515    // chain costs stack nothing, as `RuleSnapshot`'s `Drop` walks the
4516    // same chain without it.
4517    let mut links: Vec<&Rc<RuleSnapshot>> = Vec::with_capacity(history);
4518    let mut current = Some(&snapshot);
4519    while let Some(snapshot) = current {
4520        if links.len() == history {
4521            break;
4522        }
4523        links.push(snapshot);
4524        current = snapshot.prev_rule.as_ref();
4525    }
4526    let mut prev: Option<Rc<RuleSnapshot>> = None;
4527    // `depth` counts back from the snapshot itself, which is 0.
4528    for (depth, original) in links.into_iter().enumerate().rev() {
4529        let mut copy = (**original).clone();
4530        if depth != 0 || link != Link::Parent {
4531            copy.child_rule = None;
4532            copy.next_rule = None;
4533            copy.next_rule_name = None;
4534        }
4535        copy.prev_rule = prev.take();
4536        prev = Some(Rc::new(copy));
4537    }
4538    prev.expect("at least the snapshot itself is copied")
4539}
4540
4541fn snapshot_condition_exists(rule: &RuleSnapshot, path: &[String]) -> bool {
4542    if path.first().map(String::as_str) == Some("n") && path.len() == 2 {
4543        rule.n.contains_key(&path[1])
4544    } else if path.first().map(String::as_str) == Some("parent") {
4545        rule.parent_rule
4546            .as_deref()
4547            .is_some_and(|parent| snapshot_condition_exists(parent, &path[1..]))
4548    } else if path.first().map(String::as_str) == Some("child") {
4549        rule.child_rule
4550            .as_deref()
4551            .is_some_and(|child| snapshot_condition_exists(child, &path[1..]))
4552    } else if path.first().map(String::as_str) == Some("prev") {
4553        rule.prev_rule
4554            .as_deref()
4555            .is_some_and(|prev| snapshot_condition_exists(prev, &path[1..]))
4556    } else if path.first().map(String::as_str) == Some("next") {
4557        if let Some(next) = rule.next_rule.as_deref() {
4558            snapshot_condition_exists(next, &path[1..])
4559        } else if rule.next_rule_name.as_deref() == Some(&*rule.name) {
4560            snapshot_condition_exists(rule, &path[1..])
4561        } else {
4562            false
4563        }
4564    } else {
4565        resolve_snapshot_path(rule, path).is_some()
4566    }
4567}
4568
4569fn resolve_snapshot_path(rule: &RuleSnapshot, path: &[String]) -> Option<Value> {
4570    let root = path.first()?.as_str();
4571    let rest = &path[1..];
4572    match root {
4573        "n" if rest.len() == 1 => Some(Value::Number(*rule.n.get(&rest[0]).unwrap_or(&0) as f64)),
4574        "u" => map_path(&rule.u, rest),
4575        "k" => map_path(&rule.k, rest),
4576        "d" if rest.is_empty() => Some(Value::Number(rule.d as f64)),
4577        "i" if rest.is_empty() => Some(Value::Number(rule.i as f64)),
4578        "name" if rest.is_empty() => Some(Value::String(rule.name.to_string())),
4579        "state" if rest.is_empty() => Some(Value::String(
4580            match rule.state {
4581                RuleState::Open => "o",
4582                RuleState::Close => "c",
4583            }
4584            .into(),
4585        )),
4586        "node" => value_path(rule.node.borrow().clone(), rest),
4587        "need" if rest.is_empty() => Some(Value::Number(rule.need as f64)),
4588        "oN" if rest.is_empty() => Some(Value::Number(rule.o.len() as f64)),
4589        "cN" if rest.is_empty() => Some(Value::Number(rule.c.len() as f64)),
4590        "o" => token_list_path(&rule.o, rest),
4591        "c" => token_list_path(&rule.c, rest),
4592        "o0" => token_path(rule.o.first(), rest),
4593        "o1" => token_path(rule.o.get(1), rest),
4594        "c0" => token_path(rule.c.first(), rest),
4595        "c1" => token_path(rule.c.get(1), rest),
4596        "parent" => resolve_snapshot_path(rule.parent_rule.as_deref()?, rest),
4597        "child" => resolve_snapshot_path(rule.child_rule.as_deref()?, rest),
4598        "prev" => resolve_snapshot_path(rule.prev_rule.as_deref()?, rest),
4599        "next" => {
4600            if let Some(next) = rule.next_rule.as_deref() {
4601                resolve_snapshot_path(next, rest)
4602            } else if rule.next_rule_name.as_deref() == Some(&*rule.name) {
4603                resolve_snapshot_path(rule, rest)
4604            } else {
4605                None
4606            }
4607        }
4608        "spec" if rest == ["name"] => Some(Value::String(rule.name.to_string())),
4609        _ => None,
4610    }
4611}
4612
4613fn map_path(map: &HashMap<String, Value>, path: &[String]) -> Option<Value> {
4614    let (name, rest) = path.split_first()?;
4615    value_path(map.get(name)?.clone(), rest)
4616}
4617
4618fn value_path(mut value: Value, path: &[String]) -> Option<Value> {
4619    for part in path {
4620        value = match value {
4621            Value::Object(map) => map.get(part)?.clone(),
4622            Value::Array(items) => items.get(part.parse::<usize>().ok()?)?.clone(),
4623            _ => return None,
4624        };
4625    }
4626    Some(value)
4627}
4628
4629fn token_list_path(tokens: &[Token], path: &[String]) -> Option<Value> {
4630    let (index, rest) = path.split_first()?;
4631    token_path(tokens.get(index.parse::<usize>().ok()?), rest)
4632}
4633
4634fn token_path(token: Option<&Token>, path: &[String]) -> Option<Value> {
4635    let token = token?;
4636    if path.is_empty() {
4637        let mut value = IndexMap::new();
4638        value.insert("tin".into(), Value::Number(token.tin as f64));
4639        value.insert("name".into(), Value::String(token.name.to_string()));
4640        value.insert("src".into(), Value::String(token.src.to_string()));
4641        value.insert("val".into(), token.val.clone());
4642        value.insert("why".into(), Value::String(token.why.to_string()));
4643        return Some(Value::object(value));
4644    }
4645    let (field, rest) = path.split_first()?;
4646    let value = match field.as_str() {
4647        "tin" => Value::Number(token.tin as f64),
4648        "name" => Value::String(token.name.to_string()),
4649        "src" => Value::String(token.src.to_string()),
4650        "val" => token.val.clone(),
4651        "why" => Value::String(token.why.to_string()),
4652        _ => return None,
4653    };
4654    value_path(value, rest)
4655}
4656
4657#[cfg(test)]
4658mod tests {
4659    use super::*;
4660
4661    /// `expected_tins`, `names` and `prepared` are derived from `rules` and
4662    /// positional in it, and `add_rule` is the only thing that writes any
4663    /// of the four. That is the whole reason `rules` is private: while it was
4664    /// public, an embedder inserting or replacing a rule straight into it
4665    /// left the derived tables describing the rule that used to be there,
4666    /// and lookahead went on gating the custom matchers on that rule's token
4667    /// identities. This asserts the invariant a second write path would
4668    /// break; making `add_rule` keep an existing entry instead of replacing
4669    /// it fails the second assertion.
4670    #[test]
4671    fn replacing_a_rule_replaces_the_tables_derived_from_it() {
4672        let mut parser = Parser::new(crate::Options::default());
4673
4674        let mut first = RuleSpec::new("val");
4675        first.bo.push("@enter".into());
4676        first.open.push(AltSpec {
4677            s: vec![vec![crate::TIN_NR]],
4678            a: vec!["@act".into()],
4679            ..Default::default()
4680        });
4681        parser.add_rule(first);
4682        let slot = parser
4683            .rules()
4684            .get_index_of("val")
4685            .expect("the rule was just installed");
4686        assert_eq!(parser.expected_tins[slot].at(true, 0), [crate::TIN_NR]);
4687        assert_eq!(&*parser.names[slot], "val");
4688        assert_eq!(parser.prepared[slot].before(true).len(), 1);
4689        assert_eq!(parser.prepared[slot].open[0].actions.len(), 1);
4690
4691        let mut second = RuleSpec::new("val");
4692        second.open.push(AltSpec {
4693            s: vec![vec![crate::TIN_ST]],
4694            ..Default::default()
4695        });
4696        parser.add_rule(second);
4697
4698        assert_eq!(parser.rules().len(), 1, "a replacement, not an addition");
4699        assert_eq!(
4700            parser.expected_tins[slot].at(true, 0),
4701            [crate::TIN_ST],
4702            "lookahead would still expect the replaced rule's tokens"
4703        );
4704        // The name table is positional now, so a replacement has to land on
4705        // the index the map kept for the key rather than append beside it:
4706        // an appended handle would leave `names` describing the wrong rule
4707        // at every index past this one.
4708        assert_eq!(parser.names.len(), 1, "one rule, one shared name");
4709        assert_eq!(parser.rules().get_index_of("val"), Some(slot));
4710        assert_eq!(&*parser.names[slot], "val");
4711        // The prepared table is positional for the same reason, and its
4712        // record has to describe the rule that is installed now: the parse
4713        // loop trusts a record only while its spec is the one the rule in
4714        // hand holds, so a record left pointing at the replaced spec is a
4715        // record the loop would stop using at all.
4716        assert_eq!(parser.prepared.len(), 1);
4717        assert_eq!(&*parser.prepared[slot].name, "val");
4718        assert!(Arc::ptr_eq(
4719            &parser.prepared[slot].spec,
4720            &parser.rules()["val"]
4721        ));
4722        // The action orders are derived from the same spec and go stale the
4723        // same way. The replacement declares neither the lifecycle action
4724        // nor the alternate action the first rule did, and an order left
4725        // over from that rule is one the loop would run for it.
4726        assert!(parser.prepared[slot].before(true).is_empty());
4727        assert!(parser.prepared[slot].open[0].actions.is_empty());
4728        assert!(!parser.prepared[slot].open[0].named);
4729    }
4730
4731    /// The accepted-token table is positional now, and the slot a rule
4732    /// carries is a hint rather than an authority: `name` is public and a
4733    /// callback may have written it between one token fetch and the next.
4734    /// What the rule is called is what decides which row answers, exactly
4735    /// as the name-keyed map this replaced decided it, and a name that is
4736    /// installed nowhere expects nothing rather than whatever happens to
4737    /// sit at its slot.
4738    #[test]
4739    fn expected_match_tins_follows_the_rule_name_when_the_slot_goes_stale() {
4740        fn rule_named(name: &str, tin: Tin) -> RuleSpec {
4741            let mut spec = RuleSpec::new(name);
4742            spec.open.push(AltSpec {
4743                s: vec![vec![tin]],
4744                ..Default::default()
4745            });
4746            spec
4747        }
4748
4749        let mut options = crate::Options::default();
4750        let tin = options.register_token("#QQ");
4751        options.match_tokens.insert(
4752            "#QQ".into(),
4753            crate::options::MatchToken {
4754                name: "#QQ".into(),
4755                tin,
4756                matcher: crate::options::MatchTokenMatcher::Regex(regex::Regex::new("^q").unwrap()),
4757                eager: false,
4758            },
4759        );
4760        let mut parser = Parser::new(options);
4761        parser.add_rule(rule_named("val", crate::TIN_NR));
4762        parser.add_rule(rule_named("other", crate::TIN_ST));
4763
4764        let val_slot = parser.rules().get_index_of("val").expect("installed");
4765        let other_slot = parser.rules().get_index_of("other").expect("installed");
4766        assert_ne!(val_slot, other_slot);
4767
4768        // Bound the way the parse loop binds it: slot, name and spec all
4769        // describe the same installed rule.
4770        let mut rule = Rule::new("val", Value::Undefined);
4771        rule.bind_spec(
4772            &parser.rules()["val"],
4773            parser.names[val_slot].clone(),
4774            val_slot,
4775        );
4776        assert_eq!(&*parser.expected_match_tins(&rule, 0), [crate::TIN_NR]);
4777
4778        // A callback renames the rule to another installed rule. The slot
4779        // still points at `val`, so the slot alone would answer with
4780        // `val`'s row; the name is what makes it `other`'s, which is what
4781        // the lookup by name always gave.
4782        rule.name = parser.names[other_slot].clone();
4783        assert_eq!(
4784            &*parser.expected_match_tins(&rule, 0),
4785            [crate::TIN_ST],
4786            "the rule is called `other` now, so `other`'s row answers"
4787        );
4788
4789        // A callback swaps the spec and leaves the name. Nothing about
4790        // which row answers depends on the spec, because the row is
4791        // derived from the name the rule was installed under.
4792        rule.name = parser.names[val_slot].clone();
4793        rule.spec = Arc::clone(&parser.rules()["other"]);
4794        assert_eq!(
4795            &*parser.expected_match_tins(&rule, 0),
4796            [crate::TIN_NR],
4797            "still called `val`, so still gated by `val`'s row"
4798        );
4799
4800        // A name installed nowhere expects nothing. Its slot is
4801        // `usize::MAX`, so nothing is in range to answer by accident.
4802        let ghost = Rule::new("ghost", Value::Undefined);
4803        assert_eq!(
4804            &*parser.expected_match_tins(&ghost, 0),
4805            &[] as &[Tin],
4806            "an uninstalled name has no row, not another rule's"
4807        );
4808    }
4809
4810    /// `expected_match_tins` exists to gate the custom matchers, so with no
4811    /// custom matcher registered it answers "nothing" without consulting
4812    /// the table -- the table is still built and still says what it said,
4813    /// and the moment a match token appears the same rule at the same slot
4814    /// gets the table's row again. The first assertion is what pins the
4815    /// short-circuit: were it dropped, the empty-matcher parser would
4816    /// return `[TIN_NR]` and the assertion would fail.
4817    #[test]
4818    fn expected_match_tins_is_empty_until_a_match_token_exists() {
4819        fn val_rule() -> RuleSpec {
4820            let mut spec = RuleSpec::new("val");
4821            spec.open.push(AltSpec {
4822                s: vec![vec![crate::TIN_NR]],
4823                ..Default::default()
4824            });
4825            spec
4826        }
4827        let rule = Rule::new("val", Value::Undefined);
4828
4829        let mut without = Parser::new(crate::Options::default());
4830        without.add_rule(val_rule());
4831        assert!(without.options.match_tokens.is_empty());
4832        assert_eq!(without.expected_tins[0].at(true, 0), [crate::TIN_NR]);
4833        assert_eq!(
4834            &*without.expected_match_tins(&rule, 0),
4835            &[] as &[Tin],
4836            "no custom matcher: nothing to gate, so nothing to look up"
4837        );
4838
4839        let mut options = crate::Options::default();
4840        let tin = options.register_token("#QQ");
4841        options.match_tokens.insert(
4842            "#QQ".into(),
4843            crate::options::MatchToken {
4844                name: "#QQ".into(),
4845                tin,
4846                matcher: crate::options::MatchTokenMatcher::Regex(regex::Regex::new("^q").unwrap()),
4847                eager: false,
4848            },
4849        );
4850        let mut with = Parser::new(options);
4851        with.add_rule(val_rule());
4852        assert_eq!(
4853            &*with.expected_match_tins(&rule, 0),
4854            [crate::TIN_NR],
4855            "a custom matcher is present, so the slot's tins gate it"
4856        );
4857        assert_eq!(
4858            &*with.expected_match_tins(&rule, 1),
4859            &[] as &[Tin],
4860            "a slot the rule never fills expects nothing"
4861        );
4862    }
4863}