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