Skip to main content

tabnas/
rule.rs

1// Copyright (c) 2013-2026 Richard Rodger, MIT License
2
3use crate::token::{Tin, Token};
4use crate::value::Value;
5use crate::Lexer;
6use crate::{ActionError, Context, ContextAction};
7use std::cell::RefCell;
8use std::collections::HashMap;
9use std::fmt;
10use std::rc::Rc;
11use std::sync::Arc;
12
13/// Function-valued alternate condition. The candidate's matched tokens have
14/// already been copied onto `rule` when this callback runs.
15pub type AltCondition = Arc<dyn Fn(&mut Rule, &mut Context) -> bool + Send + Sync>;
16
17/// Canonical condition shape, including the effective match record being
18/// assembled for this candidate.
19pub type AltConditionWithMatch =
20    Arc<dyn Fn(&mut Rule, &mut Context, &mut AltMatch) -> bool + Send + Sync>;
21
22/// Condition with access to the live lexer. This is the imperative form used
23/// by plugins that perform controlled lookahead from a condition.
24pub type AltConditionWithLexer =
25    Arc<dyn for<'source> Fn(&mut Rule, &mut Context, &mut Lexer<'source>) -> bool + Send + Sync>;
26
27/// Complete canonical condition shape: the live effective match plus access
28/// to the lexer for bounded, plugin-controlled lookahead.
29pub type AltConditionWithLexerAndMatch = Arc<
30    dyn for<'source> Fn(&mut Rule, &mut Context, &mut AltMatch, &mut Lexer<'source>) -> bool
31        + Send
32        + Sync,
33>;
34
35/// Function-valued push/replace route.
36pub type AltNext = Arc<dyn Fn(&mut Rule, &mut Context) -> Option<String> + Send + Sync>;
37pub type AltNextWithMatch =
38    Arc<dyn Fn(&mut Rule, &mut Context, &mut AltMatch) -> Option<String> + Send + Sync>;
39
40/// Function-valued token backtrack count.
41pub type AltBack = Arc<dyn Fn(&mut Rule, &mut Context) -> usize + Send + Sync>;
42pub type AltBackWithMatch =
43    Arc<dyn Fn(&mut Rule, &mut Context, &mut AltMatch) -> usize + Send + Sync>;
44
45/// Function-valued alternate error. Returning a token rejects the alternate
46/// at its match site and uses the token's `err`/`why` field as the error code.
47pub type AltError = Arc<dyn Fn(&mut Rule, &mut Context) -> Option<Token> + Send + Sync>;
48pub type AltErrorWithMatch =
49    Arc<dyn Fn(&mut Rule, &mut Context, &mut AltMatch) -> Option<Token> + Send + Sync>;
50
51/// Post-match alternate modifier. It takes and returns the effective match so
52/// replacement is explicit rather than mutating shared grammar state.
53pub type AltModifier = Arc<dyn Fn(AltSpec, &mut Rule, &mut Context) -> AltSpec + Send + Sync>;
54
55/// Full canonical alternate modifier. `next` is the pre-routing next-rule
56/// view (`Some(current)` during open, `None` during close). The returned
57/// match is the one used for erroring, state mutation, action, and routing.
58pub type AltModifierWithMatch =
59    Arc<dyn Fn(AltMatch, &mut Rule, &mut Context, Option<&RuleSnapshot>) -> AltMatch + Send + Sync>;
60
61/// Full canonical alternate action. A returned error token aborts the pass;
62/// other returned tokens are ignored by the mature engine and should be
63/// expressed as `None` here.
64pub type AltAction = Arc<
65    dyn Fn(&mut Rule, &mut Context, &mut AltMatch) -> Result<Option<Token>, ActionError>
66        + Send
67        + Sync,
68>;
69
70#[derive(Clone)]
71pub enum AltActionBinding {
72    Named(String),
73    Context(ContextAction),
74    Matched(AltAction),
75}
76
77/// One executable action in its exact declaration position. The mature
78/// engines allow named function references and direct callbacks to be mixed;
79/// retaining this sequence avoids losing prepend/append order while keeping
80/// the serialized `a`/`bo`/`ao`/`bc`/`ac` lists available for inspection.
81#[derive(Clone)]
82pub enum ActionBinding {
83    Named(String),
84    Callback(ContextAction),
85    State(StateAction),
86}
87
88/// Canonical rule lifecycle callback (`bo`/`ao`/`bc`/`ac`). `next` is the
89/// rule selected for the following pass, or `None` for the no-rule sentinel;
90/// `out` is the previous handler's token result in the same phase.
91pub type StateAction = Arc<
92    dyn Fn(
93            &mut Rule,
94            &mut Context,
95            Option<&RuleSnapshot>,
96            Option<Token>,
97        ) -> Result<Option<Token>, ActionError>
98        + Send
99        + Sync,
100>;
101
102/// Effective result of matching one alternate. Dynamic error/routing/backtrack
103/// callbacks are resolved into this record before the modifier runs, matching
104/// TypeScript's `AltMatch` contract.
105#[derive(Clone, Default)]
106pub struct AltMatch {
107    pub p: Option<String>,
108    pub r: Option<String>,
109    pub b: usize,
110    pub n: HashMap<String, i32>,
111    pub u: HashMap<String, Value>,
112    pub k: HashMap<String, Value>,
113    pub g: Vec<String>,
114    /// The alternate's error token, boxed. A `Token` is 248 bytes and this
115    /// record is moved twice per rule step, so carrying one inline made
116    /// `AltMatch` 560 bytes of which 248 were an error almost no alternate
117    /// raises. The box costs an allocation only on the error path, which
118    /// is already the expensive one.
119    pub e: Option<Box<Token>>,
120    /// Canonical post-match modifier attached to the selected alternate.
121    /// It remains visible to the modifier itself and later actions even
122    /// though changing it after selection does not rerun the phase.
123    pub h: Option<AltModifierWithMatch>,
124    pub actions: Vec<AltActionBinding>,
125    pub action_configs: HashMap<String, Value>,
126}
127
128impl AltMatch {
129    /// Return the record to the state `AltMatch::default()` would give it,
130    /// without giving up the buffers it has already allocated.
131    ///
132    /// The parse loop keeps one record for the whole parse and resets it at
133    /// the head of each rule step, the shape TypeScript's `ctx._palt` has
134    /// (ts/src/rules.ts): building a fresh 320-byte record twice per step
135    /// and moving it twice more cost more than the nine fields are worth.
136    /// Every field an earlier step or a rejected alternate can leave behind
137    /// has to be cleared here -- a step can leave by the error path with
138    /// `e`, `p`, `r`, `b` and `g` still set -- and the maps and vectors are
139    /// cleared rather than replaced so the next step writes into the
140    /// capacity the last one left.
141    pub(crate) fn reset(&mut self) {
142        self.p = None;
143        self.r = None;
144        self.b = 0;
145        self.e = None;
146        self.h = None;
147        if !self.n.is_empty() {
148            self.n.clear();
149        }
150        if !self.u.is_empty() {
151            self.u.clear();
152        }
153        if !self.k.is_empty() {
154            self.k.clear();
155        }
156        if !self.g.is_empty() {
157            self.g.clear();
158        }
159        if !self.actions.is_empty() {
160            self.actions.clear();
161        }
162        if !self.action_configs.is_empty() {
163            self.action_configs.clear();
164        }
165    }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum RuleState {
170    Open,
171    Close,
172}
173
174#[derive(Debug, Clone, PartialEq)]
175pub struct RuleDoneAlt {
176    pub b: usize,
177    pub g: Vec<String>,
178    pub p: String,
179    pub r: String,
180    pub err: Option<Token>,
181}
182
183#[derive(Debug, Clone, PartialEq)]
184pub struct RuleDone {
185    /// Rule state before the completed pass.
186    pub state: RuleState,
187    /// `None` only when that state declared no alternatives.
188    pub alt: Option<RuleDoneAlt>,
189    /// True only for a close synthesized by recovery.
190    pub forced: bool,
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub enum CompareOp {
195    Eq,
196    Ne,
197    Lt,
198    Lte,
199    Gt,
200    Gte,
201    Exist,
202}
203
204#[derive(Debug, Clone, PartialEq)]
205pub struct Condition {
206    pub path: Vec<String>,
207    pub op: CompareOp,
208    pub value: Value,
209}
210
211#[derive(Clone, Default)]
212pub struct AltSpec {
213    pub s: Vec<Vec<Tin>>,
214    /// The token names each `s` slot was declared with, when the
215    /// alternate came from a serialized grammar; empty for an alternate
216    /// built from tins directly. A slot naming a token set (`#KEY`,
217    /// `#VAL`, a custom set) is resolved again against the instance's
218    /// options whenever the parser is rebuilt, so a set overridden after
219    /// the alternate was installed still reaches it, as in TypeScript and
220    /// Go (tabnas/parser#217).
221    pub s_names: Vec<Vec<String>>,
222    /// What `s_names` last resolved to, slot by slot. A slot whose `s`
223    /// no longer equals this was set by hand after the alternate was
224    /// installed, and that edit wins: the names are not applied over it.
225    pub s_bound: Vec<Vec<Tin>>,
226    pub p: Option<String>,
227    pub p_fn: Option<AltNext>,
228    pub p_match: Option<AltNextWithMatch>,
229    pub r: Option<String>,
230    pub r_fn: Option<AltNext>,
231    pub r_match: Option<AltNextWithMatch>,
232    pub b: usize,
233    pub b_fn: Option<AltBack>,
234    pub b_match: Option<AltBackWithMatch>,
235    pub a: Vec<String>,
236    /// Imperative actions installed directly by a native Rust plugin.
237    /// Named actions in `a` remain the serialized grammar representation;
238    /// `action_order` retains their exact interleaving with direct callbacks.
239    pub action_fns: Vec<ContextAction>,
240    pub action_order: Vec<AltActionBinding>,
241    pub matched_action_fns: Vec<AltAction>,
242    pub action_configs: HashMap<String, Value>,
243    pub c: Vec<Condition>,
244    pub c_ref: Option<String>,
245    pub c_fn: Option<AltCondition>,
246    pub c_match: Option<AltConditionWithMatch>,
247    pub c_lex: Option<AltConditionWithLexer>,
248    pub c_lex_match: Option<AltConditionWithLexerAndMatch>,
249    pub n: HashMap<String, i32>,
250    pub u: HashMap<String, Value>,
251    pub k: HashMap<String, Value>,
252    pub g: String,
253    pub h: Option<AltModifier>,
254    pub h_match: Option<AltModifierWithMatch>,
255    pub e: Option<AltError>,
256    pub e_match: Option<AltErrorWithMatch>,
257}
258
259impl AltSpec {
260    pub fn new() -> Self {
261        Self::default()
262    }
263}
264
265#[derive(Clone)]
266pub struct RuleSpec {
267    pub name: String,
268    pub open: Vec<AltSpec>,
269    pub close: Vec<AltSpec>,
270    pub bo: Vec<String>,
271    pub ao: Vec<String>,
272    pub bc: Vec<String>,
273    pub ac: Vec<String>,
274    /// Imperative lifecycle callbacks, parallel to the named serialized
275    /// callback lists above.
276    pub bo_fns: Vec<ContextAction>,
277    pub ao_fns: Vec<ContextAction>,
278    pub bc_fns: Vec<ContextAction>,
279    pub ac_fns: Vec<ContextAction>,
280    pub bo_state_fns: Vec<StateAction>,
281    pub ao_state_fns: Vec<StateAction>,
282    pub bc_state_fns: Vec<StateAction>,
283    pub ac_state_fns: Vec<StateAction>,
284    pub bo_order: Vec<ActionBinding>,
285    pub ao_order: Vec<ActionBinding>,
286    pub bc_order: Vec<ActionBinding>,
287    pub ac_order: Vec<ActionBinding>,
288}
289
290impl fmt::Debug for RuleSpec {
291    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
292        formatter
293            .debug_struct("RuleSpec")
294            .field("name", &self.name)
295            .field("open", &self.open.len())
296            .field("close", &self.close.len())
297            .field("bo", &self.bo.len())
298            .field("ao", &self.ao.len())
299            .field("bc", &self.bc.len())
300            .field("ac", &self.ac.len())
301            .finish_non_exhaustive()
302    }
303}
304
305impl RuleSpec {
306    pub fn new(name: impl Into<String>) -> Self {
307        RuleSpec {
308            name: name.into(),
309            open: Vec::new(),
310            close: Vec::new(),
311            bo: Vec::new(),
312            ao: Vec::new(),
313            bc: Vec::new(),
314            ac: Vec::new(),
315            bo_fns: Vec::new(),
316            ao_fns: Vec::new(),
317            bc_fns: Vec::new(),
318            ac_fns: Vec::new(),
319            bo_state_fns: Vec::new(),
320            ao_state_fns: Vec::new(),
321            bc_state_fns: Vec::new(),
322            ac_state_fns: Vec::new(),
323            bo_order: Vec::new(),
324            ao_order: Vec::new(),
325            bc_order: Vec::new(),
326            ac_order: Vec::new(),
327        }
328    }
329
330    /// Remove all alternates and lifecycle actions from this rule.
331    pub fn clear(&mut self) -> &mut Self {
332        self.open.clear();
333        self.close.clear();
334        self.clear_actions(&[]);
335        self
336    }
337
338    pub fn add_open(&mut self, alt: AltSpec) -> &mut Self {
339        self.open.push(alt);
340        self
341    }
342
343    pub fn prepend_open(&mut self, alt: AltSpec) -> &mut Self {
344        self.open.insert(0, alt);
345        self
346    }
347
348    pub fn add_close(&mut self, alt: AltSpec) -> &mut Self {
349        self.close.push(alt);
350        self
351    }
352
353    pub fn prepend_close(&mut self, alt: AltSpec) -> &mut Self {
354        self.close.insert(0, alt);
355        self
356    }
357
358    pub fn clear_open(&mut self) -> &mut Self {
359        self.open.clear();
360        self
361    }
362
363    pub fn clear_close(&mut self) -> &mut Self {
364        self.close.clear();
365        self
366    }
367
368    /// Delete then move entries using the same signed-index rules as the
369    /// serialized grammar `inject` object.
370    pub fn modify_open(&mut self, mods: &crate::utility::ListMods<AltSpec>) -> &mut Self {
371        self.open = crate::utility::modlist(std::mem::take(&mut self.open), Some(mods));
372        self
373    }
374
375    pub fn modify_close(&mut self, mods: &crate::utility::ListMods<AltSpec>) -> &mut Self {
376        self.close = crate::utility::modlist(std::mem::take(&mut self.close), Some(mods));
377        self
378    }
379
380    pub fn add_bo(
381        &mut self,
382        action: impl Fn(&mut Rule, &mut Context) + Send + Sync + 'static,
383    ) -> &mut Self {
384        prepare_order(
385            &self.bo,
386            &self.bo_fns,
387            &self.bo_state_fns,
388            &mut self.bo_order,
389        );
390        let action = infallible_action(action);
391        self.bo_fns.push(action.clone());
392        self.bo_order.push(ActionBinding::Callback(action));
393        self
394    }
395
396    pub fn prepend_bo(
397        &mut self,
398        action: impl Fn(&mut Rule, &mut Context) + Send + Sync + 'static,
399    ) -> &mut Self {
400        prepare_order(
401            &self.bo,
402            &self.bo_fns,
403            &self.bo_state_fns,
404            &mut self.bo_order,
405        );
406        let action = infallible_action(action);
407        self.bo_fns.insert(0, action.clone());
408        self.bo_order.insert(0, ActionBinding::Callback(action));
409        self
410    }
411
412    pub fn add_ao(
413        &mut self,
414        action: impl Fn(&mut Rule, &mut Context) + Send + Sync + 'static,
415    ) -> &mut Self {
416        prepare_order(
417            &self.ao,
418            &self.ao_fns,
419            &self.ao_state_fns,
420            &mut self.ao_order,
421        );
422        let action = infallible_action(action);
423        self.ao_fns.push(action.clone());
424        self.ao_order.push(ActionBinding::Callback(action));
425        self
426    }
427
428    pub fn prepend_ao(
429        &mut self,
430        action: impl Fn(&mut Rule, &mut Context) + Send + Sync + 'static,
431    ) -> &mut Self {
432        prepare_order(
433            &self.ao,
434            &self.ao_fns,
435            &self.ao_state_fns,
436            &mut self.ao_order,
437        );
438        let action = infallible_action(action);
439        self.ao_fns.insert(0, action.clone());
440        self.ao_order.insert(0, ActionBinding::Callback(action));
441        self
442    }
443
444    pub fn add_bc(
445        &mut self,
446        action: impl Fn(&mut Rule, &mut Context) + Send + Sync + 'static,
447    ) -> &mut Self {
448        prepare_order(
449            &self.bc,
450            &self.bc_fns,
451            &self.bc_state_fns,
452            &mut self.bc_order,
453        );
454        let action = infallible_action(action);
455        self.bc_fns.push(action.clone());
456        self.bc_order.push(ActionBinding::Callback(action));
457        self
458    }
459
460    pub fn prepend_bc(
461        &mut self,
462        action: impl Fn(&mut Rule, &mut Context) + Send + Sync + 'static,
463    ) -> &mut Self {
464        prepare_order(
465            &self.bc,
466            &self.bc_fns,
467            &self.bc_state_fns,
468            &mut self.bc_order,
469        );
470        let action = infallible_action(action);
471        self.bc_fns.insert(0, action.clone());
472        self.bc_order.insert(0, ActionBinding::Callback(action));
473        self
474    }
475
476    pub fn add_ac(
477        &mut self,
478        action: impl Fn(&mut Rule, &mut Context) + Send + Sync + 'static,
479    ) -> &mut Self {
480        prepare_order(
481            &self.ac,
482            &self.ac_fns,
483            &self.ac_state_fns,
484            &mut self.ac_order,
485        );
486        let action = infallible_action(action);
487        self.ac_fns.push(action.clone());
488        self.ac_order.push(ActionBinding::Callback(action));
489        self
490    }
491
492    pub fn prepend_ac(
493        &mut self,
494        action: impl Fn(&mut Rule, &mut Context) + Send + Sync + 'static,
495    ) -> &mut Self {
496        prepare_order(
497            &self.ac,
498            &self.ac_fns,
499            &self.ac_state_fns,
500            &mut self.ac_order,
501        );
502        let action = infallible_action(action);
503        self.ac_fns.insert(0, action.clone());
504        self.ac_order.insert(0, ActionBinding::Callback(action));
505        self
506    }
507
508    pub fn add_bo_result(&mut self, action: ContextAction) -> &mut Self {
509        prepare_order(
510            &self.bo,
511            &self.bo_fns,
512            &self.bo_state_fns,
513            &mut self.bo_order,
514        );
515        self.bo_fns.push(action.clone());
516        self.bo_order.push(ActionBinding::Callback(action));
517        self
518    }
519
520    pub fn add_ao_result(&mut self, action: ContextAction) -> &mut Self {
521        prepare_order(
522            &self.ao,
523            &self.ao_fns,
524            &self.ao_state_fns,
525            &mut self.ao_order,
526        );
527        self.ao_fns.push(action.clone());
528        self.ao_order.push(ActionBinding::Callback(action));
529        self
530    }
531
532    pub fn add_bc_result(&mut self, action: ContextAction) -> &mut Self {
533        prepare_order(
534            &self.bc,
535            &self.bc_fns,
536            &self.bc_state_fns,
537            &mut self.bc_order,
538        );
539        self.bc_fns.push(action.clone());
540        self.bc_order.push(ActionBinding::Callback(action));
541        self
542    }
543
544    pub fn add_ac_result(&mut self, action: ContextAction) -> &mut Self {
545        prepare_order(
546            &self.ac,
547            &self.ac_fns,
548            &self.ac_state_fns,
549            &mut self.ac_order,
550        );
551        self.ac_fns.push(action.clone());
552        self.ac_order.push(ActionBinding::Callback(action));
553        self
554    }
555
556    pub fn add_bo_with_state(&mut self, action: StateAction) -> &mut Self {
557        prepare_order(
558            &self.bo,
559            &self.bo_fns,
560            &self.bo_state_fns,
561            &mut self.bo_order,
562        );
563        self.bo_state_fns.push(action.clone());
564        self.bo_order.push(ActionBinding::State(action));
565        self
566    }
567
568    pub fn prepend_bo_with_state(&mut self, action: StateAction) -> &mut Self {
569        prepare_order(
570            &self.bo,
571            &self.bo_fns,
572            &self.bo_state_fns,
573            &mut self.bo_order,
574        );
575        self.bo_state_fns.insert(0, action.clone());
576        self.bo_order.insert(0, ActionBinding::State(action));
577        self
578    }
579
580    pub fn add_ao_with_state(&mut self, action: StateAction) -> &mut Self {
581        prepare_order(
582            &self.ao,
583            &self.ao_fns,
584            &self.ao_state_fns,
585            &mut self.ao_order,
586        );
587        self.ao_state_fns.push(action.clone());
588        self.ao_order.push(ActionBinding::State(action));
589        self
590    }
591
592    pub fn prepend_ao_with_state(&mut self, action: StateAction) -> &mut Self {
593        prepare_order(
594            &self.ao,
595            &self.ao_fns,
596            &self.ao_state_fns,
597            &mut self.ao_order,
598        );
599        self.ao_state_fns.insert(0, action.clone());
600        self.ao_order.insert(0, ActionBinding::State(action));
601        self
602    }
603
604    pub fn add_bc_with_state(&mut self, action: StateAction) -> &mut Self {
605        prepare_order(
606            &self.bc,
607            &self.bc_fns,
608            &self.bc_state_fns,
609            &mut self.bc_order,
610        );
611        self.bc_state_fns.push(action.clone());
612        self.bc_order.push(ActionBinding::State(action));
613        self
614    }
615
616    pub fn prepend_bc_with_state(&mut self, action: StateAction) -> &mut Self {
617        prepare_order(
618            &self.bc,
619            &self.bc_fns,
620            &self.bc_state_fns,
621            &mut self.bc_order,
622        );
623        self.bc_state_fns.insert(0, action.clone());
624        self.bc_order.insert(0, ActionBinding::State(action));
625        self
626    }
627
628    pub fn add_ac_with_state(&mut self, action: StateAction) -> &mut Self {
629        prepare_order(
630            &self.ac,
631            &self.ac_fns,
632            &self.ac_state_fns,
633            &mut self.ac_order,
634        );
635        self.ac_state_fns.push(action.clone());
636        self.ac_order.push(ActionBinding::State(action));
637        self
638    }
639
640    pub fn prepend_ac_with_state(&mut self, action: StateAction) -> &mut Self {
641        prepare_order(
642            &self.ac,
643            &self.ac_fns,
644            &self.ac_state_fns,
645            &mut self.ac_order,
646        );
647        self.ac_state_fns.insert(0, action.clone());
648        self.ac_order.insert(0, ActionBinding::State(action));
649        self
650    }
651
652    pub fn add_bo_ref(&mut self, action: impl Into<String>) -> &mut Self {
653        add_named(
654            &mut self.bo,
655            &mut self.bo_fns,
656            &mut self.bo_state_fns,
657            &mut self.bo_order,
658            action.into(),
659            false,
660        );
661        self
662    }
663
664    pub fn prepend_bo_ref(&mut self, action: impl Into<String>) -> &mut Self {
665        add_named(
666            &mut self.bo,
667            &mut self.bo_fns,
668            &mut self.bo_state_fns,
669            &mut self.bo_order,
670            action.into(),
671            true,
672        );
673        self
674    }
675
676    pub fn add_ao_ref(&mut self, action: impl Into<String>) -> &mut Self {
677        add_named(
678            &mut self.ao,
679            &mut self.ao_fns,
680            &mut self.ao_state_fns,
681            &mut self.ao_order,
682            action.into(),
683            false,
684        );
685        self
686    }
687
688    pub fn prepend_ao_ref(&mut self, action: impl Into<String>) -> &mut Self {
689        add_named(
690            &mut self.ao,
691            &mut self.ao_fns,
692            &mut self.ao_state_fns,
693            &mut self.ao_order,
694            action.into(),
695            true,
696        );
697        self
698    }
699
700    pub fn add_bc_ref(&mut self, action: impl Into<String>) -> &mut Self {
701        add_named(
702            &mut self.bc,
703            &mut self.bc_fns,
704            &mut self.bc_state_fns,
705            &mut self.bc_order,
706            action.into(),
707            false,
708        );
709        self
710    }
711
712    pub fn prepend_bc_ref(&mut self, action: impl Into<String>) -> &mut Self {
713        add_named(
714            &mut self.bc,
715            &mut self.bc_fns,
716            &mut self.bc_state_fns,
717            &mut self.bc_order,
718            action.into(),
719            true,
720        );
721        self
722    }
723
724    pub fn add_ac_ref(&mut self, action: impl Into<String>) -> &mut Self {
725        add_named(
726            &mut self.ac,
727            &mut self.ac_fns,
728            &mut self.ac_state_fns,
729            &mut self.ac_order,
730            action.into(),
731            false,
732        );
733        self
734    }
735
736    pub fn prepend_ac_ref(&mut self, action: impl Into<String>) -> &mut Self {
737        add_named(
738            &mut self.ac,
739            &mut self.ac_fns,
740            &mut self.ac_state_fns,
741            &mut self.ac_order,
742            action.into(),
743            true,
744        );
745        self
746    }
747
748    /// Clear named and imperative lifecycle actions. An empty phase slice
749    /// clears all four; otherwise accepted phase names are `bo`, `ao`, `bc`,
750    /// and `ac`.
751    pub fn clear_actions(&mut self, phases: &[&str]) -> &mut Self {
752        let clear_all = phases.is_empty();
753        for phase in ["bo", "ao", "bc", "ac"] {
754            if clear_all || phases.contains(&phase) {
755                match phase {
756                    "bo" => {
757                        self.bo.clear();
758                        self.bo_fns.clear();
759                        self.bo_state_fns.clear();
760                        self.bo_order.clear();
761                    }
762                    "ao" => {
763                        self.ao.clear();
764                        self.ao_fns.clear();
765                        self.ao_state_fns.clear();
766                        self.ao_order.clear();
767                    }
768                    "bc" => {
769                        self.bc.clear();
770                        self.bc_fns.clear();
771                        self.bc_state_fns.clear();
772                        self.bc_order.clear();
773                    }
774                    "ac" => {
775                        self.ac.clear();
776                        self.ac_fns.clear();
777                        self.ac_state_fns.clear();
778                        self.ac_order.clear();
779                    }
780                    _ => unreachable!(),
781                }
782            }
783        }
784        self
785    }
786}
787
788fn infallible_action(
789    action: impl Fn(&mut Rule, &mut Context) + Send + Sync + 'static,
790) -> ContextAction {
791    Arc::new(move |rule, context| {
792        action(rule, context);
793        Ok::<(), ActionError>(())
794    })
795}
796
797impl AltSpec {
798    /// Append an imperative Rust action to this alternate.
799    pub fn add_action(
800        &mut self,
801        action: impl Fn(&mut Rule, &mut Context) + Send + Sync + 'static,
802    ) -> &mut Self {
803        prepare_alt_order(
804            &self.a,
805            &self.action_fns,
806            &self.matched_action_fns,
807            &mut self.action_order,
808        );
809        let action = infallible_action(action);
810        self.action_fns.push(action.clone());
811        self.action_order.push(AltActionBinding::Context(action));
812        self
813    }
814
815    /// Prepend an imperative Rust action ahead of existing named or direct
816    /// actions.
817    pub fn prepend_action(
818        &mut self,
819        action: impl Fn(&mut Rule, &mut Context) + Send + Sync + 'static,
820    ) -> &mut Self {
821        prepare_alt_order(
822            &self.a,
823            &self.action_fns,
824            &self.matched_action_fns,
825            &mut self.action_order,
826        );
827        let action = infallible_action(action);
828        self.action_fns.insert(0, action.clone());
829        self.action_order
830            .insert(0, AltActionBinding::Context(action));
831        self
832    }
833
834    /// Append a fallible imperative Rust action to this alternate.
835    pub fn add_action_result(&mut self, action: ContextAction) -> &mut Self {
836        prepare_alt_order(
837            &self.a,
838            &self.action_fns,
839            &self.matched_action_fns,
840            &mut self.action_order,
841        );
842        self.action_fns.push(action.clone());
843        self.action_order.push(AltActionBinding::Context(action));
844        self
845    }
846
847    pub fn prepend_action_result(&mut self, action: ContextAction) -> &mut Self {
848        prepare_alt_order(
849            &self.a,
850            &self.action_fns,
851            &self.matched_action_fns,
852            &mut self.action_order,
853        );
854        self.action_fns.insert(0, action.clone());
855        self.action_order
856            .insert(0, AltActionBinding::Context(action));
857        self
858    }
859
860    /// Append an action with the complete matched-alternate argument.
861    pub fn add_action_with_match(
862        &mut self,
863        action: impl Fn(&mut Rule, &mut Context, &AltMatch) -> Option<Token> + Send + Sync + 'static,
864    ) -> &mut Self {
865        self.add_action_with_match_result(Arc::new(move |rule, context, matched| {
866            Ok(action(rule, context, matched))
867        }))
868    }
869
870    pub fn prepend_action_with_match(
871        &mut self,
872        action: impl Fn(&mut Rule, &mut Context, &AltMatch) -> Option<Token> + Send + Sync + 'static,
873    ) -> &mut Self {
874        self.prepend_action_with_match_result(Arc::new(move |rule, context, matched| {
875            Ok(action(rule, context, matched))
876        }))
877    }
878
879    pub fn add_action_with_match_result(&mut self, action: AltAction) -> &mut Self {
880        prepare_alt_order(
881            &self.a,
882            &self.action_fns,
883            &self.matched_action_fns,
884            &mut self.action_order,
885        );
886        self.matched_action_fns.push(action.clone());
887        self.action_order.push(AltActionBinding::Matched(action));
888        self
889    }
890
891    pub fn prepend_action_with_match_result(&mut self, action: AltAction) -> &mut Self {
892        prepare_alt_order(
893            &self.a,
894            &self.action_fns,
895            &self.matched_action_fns,
896            &mut self.action_order,
897        );
898        self.matched_action_fns.insert(0, action.clone());
899        self.action_order
900            .insert(0, AltActionBinding::Matched(action));
901        self
902    }
903
904    pub fn add_action_ref(&mut self, action: impl Into<String>) -> &mut Self {
905        add_alt_named(
906            &mut self.a,
907            &mut self.action_fns,
908            &mut self.matched_action_fns,
909            &mut self.action_order,
910            action.into(),
911            false,
912        );
913        self
914    }
915
916    pub fn prepend_action_ref(&mut self, action: impl Into<String>) -> &mut Self {
917        add_alt_named(
918            &mut self.a,
919            &mut self.action_fns,
920            &mut self.matched_action_fns,
921            &mut self.action_order,
922            action.into(),
923            true,
924        );
925        self
926    }
927}
928
929fn alt_order_matches(
930    named: &[String],
931    callbacks: &[ContextAction],
932    matched: &[AltAction],
933    order: &[AltActionBinding],
934) -> bool {
935    if order.len() != named.len() + callbacks.len() + matched.len() {
936        return false;
937    }
938    // One pass. Written as three filtered views compared against their
939    // lists, this walked `order` five times over -- once per view plus a
940    // second walk of two of them to count -- and the parse loop asks this
941    // question once per rule step. Taking the next expected entry from
942    // whichever list a binding names says the same thing in one walk.
943    let (mut next_name, mut next_callback, mut next_matched) = (0, 0, 0);
944    for binding in order {
945        match binding {
946            AltActionBinding::Named(name) => {
947                if named.get(next_name) != Some(name) {
948                    return false;
949                }
950                next_name += 1;
951            }
952            AltActionBinding::Context(callback) => {
953                if !callbacks
954                    .get(next_callback)
955                    .is_some_and(|expected| Arc::ptr_eq(expected, callback))
956                {
957                    return false;
958                }
959                next_callback += 1;
960            }
961            AltActionBinding::Matched(callback) => {
962                if !matched
963                    .get(next_matched)
964                    .is_some_and(|expected| Arc::ptr_eq(expected, callback))
965                {
966                    return false;
967                }
968                next_matched += 1;
969            }
970        }
971    }
972    next_name == named.len() && next_callback == callbacks.len() && next_matched == matched.len()
973}
974
975fn prepare_alt_order(
976    named: &[String],
977    callbacks: &[ContextAction],
978    matched: &[AltAction],
979    order: &mut Vec<AltActionBinding>,
980) {
981    if !alt_order_matches(named, callbacks, matched, order) {
982        *order = named
983            .iter()
984            .cloned()
985            .map(AltActionBinding::Named)
986            .chain(callbacks.iter().cloned().map(AltActionBinding::Context))
987            .chain(matched.iter().cloned().map(AltActionBinding::Matched))
988            .collect();
989    }
990}
991
992fn add_alt_named(
993    named: &mut Vec<String>,
994    callbacks: &mut Vec<ContextAction>,
995    matched: &mut Vec<AltAction>,
996    order: &mut Vec<AltActionBinding>,
997    action: String,
998    prepend: bool,
999) {
1000    prepare_alt_order(named, callbacks, matched, order);
1001    if prepend {
1002        named.insert(0, action.clone());
1003        order.insert(0, AltActionBinding::Named(action));
1004    } else {
1005        named.push(action.clone());
1006        order.push(AltActionBinding::Named(action));
1007    }
1008}
1009
1010pub(crate) fn resolved_alt_action_order(
1011    named: &[String],
1012    callbacks: &[ContextAction],
1013    matched: &[AltAction],
1014    order: &[AltActionBinding],
1015) -> Vec<AltActionBinding> {
1016    if alt_order_matches(named, callbacks, matched, order) {
1017        order.to_vec()
1018    } else {
1019        named
1020            .iter()
1021            .cloned()
1022            .map(AltActionBinding::Named)
1023            .chain(callbacks.iter().cloned().map(AltActionBinding::Context))
1024            .chain(matched.iter().cloned().map(AltActionBinding::Matched))
1025            .collect()
1026    }
1027}
1028
1029fn order_matches(
1030    named: &[String],
1031    callbacks: &[ContextAction],
1032    states: &[StateAction],
1033    order: &[ActionBinding],
1034) -> bool {
1035    if order.len() != named.len() + callbacks.len() + states.len() {
1036        return false;
1037    }
1038    // One pass, for the reason given on `alt_order_matches`.
1039    let (mut next_name, mut next_callback, mut next_state) = (0, 0, 0);
1040    for binding in order {
1041        match binding {
1042            ActionBinding::Named(name) => {
1043                if named.get(next_name) != Some(name) {
1044                    return false;
1045                }
1046                next_name += 1;
1047            }
1048            ActionBinding::Callback(callback) => {
1049                if !callbacks
1050                    .get(next_callback)
1051                    .is_some_and(|expected| Arc::ptr_eq(expected, callback))
1052                {
1053                    return false;
1054                }
1055                next_callback += 1;
1056            }
1057            ActionBinding::State(callback) => {
1058                if !states
1059                    .get(next_state)
1060                    .is_some_and(|expected| Arc::ptr_eq(expected, callback))
1061                {
1062                    return false;
1063                }
1064                next_state += 1;
1065            }
1066        }
1067    }
1068    next_name == named.len() && next_callback == callbacks.len() && next_state == states.len()
1069}
1070
1071fn prepare_order(
1072    named: &[String],
1073    callbacks: &[ContextAction],
1074    states: &[StateAction],
1075    order: &mut Vec<ActionBinding>,
1076) {
1077    if !order_matches(named, callbacks, states, order) {
1078        *order = named
1079            .iter()
1080            .cloned()
1081            .map(ActionBinding::Named)
1082            .chain(callbacks.iter().cloned().map(ActionBinding::Callback))
1083            .chain(states.iter().cloned().map(ActionBinding::State))
1084            .collect();
1085    }
1086}
1087
1088fn add_named(
1089    named: &mut Vec<String>,
1090    callbacks: &mut Vec<ContextAction>,
1091    states: &mut Vec<StateAction>,
1092    order: &mut Vec<ActionBinding>,
1093    action: String,
1094    prepend: bool,
1095) {
1096    prepare_order(named, callbacks, states, order);
1097    if prepend {
1098        named.insert(0, action.clone());
1099        order.insert(0, ActionBinding::Named(action));
1100    } else {
1101        named.push(action.clone());
1102        order.push(ActionBinding::Named(action));
1103    }
1104}
1105
1106pub(crate) fn resolved_action_order(
1107    named: &[String],
1108    callbacks: &[ContextAction],
1109    states: &[StateAction],
1110    order: &[ActionBinding],
1111) -> Vec<ActionBinding> {
1112    if order_matches(named, callbacks, states, order) {
1113        order.to_vec()
1114    } else {
1115        named
1116            .iter()
1117            .cloned()
1118            .map(ActionBinding::Named)
1119            .chain(callbacks.iter().cloned().map(ActionBinding::Callback))
1120            .chain(states.iter().cloned().map(ActionBinding::State))
1121            .collect()
1122    }
1123}
1124
1125/// A rule's name.
1126///
1127/// Rules are pushed and popped for every construct in a parse, so the
1128/// name is shared between a rule, its snapshots and whatever the next
1129/// rule records, rather than being copied at each step. It still
1130/// behaves like the `String` it replaced: compare it with a literal,
1131/// print it, or take a `&str` from it.
1132#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1133pub struct RuleName(Arc<str>);
1134
1135impl RuleName {
1136    pub fn as_str(&self) -> &str {
1137        &self.0
1138    }
1139}
1140
1141impl std::ops::Deref for RuleName {
1142    type Target = str;
1143
1144    fn deref(&self) -> &str {
1145        &self.0
1146    }
1147}
1148
1149impl AsRef<str> for RuleName {
1150    fn as_ref(&self) -> &str {
1151        &self.0
1152    }
1153}
1154
1155/// Keyed lookups borrow the name as a `str`, so `Hash` and `Eq` have to
1156/// agree with `str`'s. Both reach `str` through the `Arc`, so they do.
1157impl std::borrow::Borrow<str> for RuleName {
1158    fn borrow(&self) -> &str {
1159        &self.0
1160    }
1161}
1162
1163/// Printed as the bare name, so a `{:?}` of a rule or a snapshot reads
1164/// the way it did when this was a `String`.
1165impl fmt::Debug for RuleName {
1166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1167        fmt::Debug::fmt(&*self.0, f)
1168    }
1169}
1170
1171impl fmt::Display for RuleName {
1172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1173        f.write_str(&self.0)
1174    }
1175}
1176
1177impl PartialEq<str> for RuleName {
1178    fn eq(&self, other: &str) -> bool {
1179        &*self.0 == other
1180    }
1181}
1182
1183impl PartialEq<&str> for RuleName {
1184    fn eq(&self, other: &&str) -> bool {
1185        &*self.0 == *other
1186    }
1187}
1188
1189impl PartialEq<String> for RuleName {
1190    fn eq(&self, other: &String) -> bool {
1191        &*self.0 == other.as_str()
1192    }
1193}
1194
1195impl PartialEq<RuleName> for str {
1196    fn eq(&self, other: &RuleName) -> bool {
1197        self == &*other.0
1198    }
1199}
1200
1201impl PartialEq<RuleName> for &str {
1202    fn eq(&self, other: &RuleName) -> bool {
1203        *self == &*other.0
1204    }
1205}
1206
1207impl PartialEq<RuleName> for String {
1208    fn eq(&self, other: &RuleName) -> bool {
1209        self.as_str() == &*other.0
1210    }
1211}
1212
1213impl From<&str> for RuleName {
1214    fn from(name: &str) -> Self {
1215        RuleName(Arc::from(name))
1216    }
1217}
1218
1219impl From<String> for RuleName {
1220    fn from(name: String) -> Self {
1221        RuleName(Arc::from(name.as_str()))
1222    }
1223}
1224
1225impl From<&String> for RuleName {
1226    fn from(name: &String) -> Self {
1227        RuleName(Arc::from(name.as_str()))
1228    }
1229}
1230
1231impl From<Arc<str>> for RuleName {
1232    fn from(name: Arc<str>) -> Self {
1233        RuleName(name)
1234    }
1235}
1236
1237impl From<RuleName> for String {
1238    fn from(name: RuleName) -> Self {
1239        name.0.to_string()
1240    }
1241}
1242
1243/// A rule as the parse loop sees it.
1244///
1245/// Its state lives behind an `Rc` that it shares with every snapshot
1246/// taken of it, so `snapshot()` is a pointer copy. The copy happens
1247/// instead on the next write, and only while a snapshot is still
1248/// holding the current value — so a rule that is snapshotted several
1249/// times between writes pays for one copy, not several, and a rule
1250/// nobody snapshotted pays for none. Reads and writes both go through
1251/// `Deref`, so `rule.state` and `rule.state = ..` are unchanged at
1252/// every call site.
1253#[derive(Clone)]
1254pub struct Rule {
1255    shared: Rc<RuleSnapshot>,
1256    /// Not shared with snapshots, which do not carry it.
1257    pub parent_node: Option<Rc<RefCell<Value>>>,
1258    /// The completed child's node, likewise not shared. A `Value` is 72
1259    /// bytes, which was a third of a `RuleSnapshot`, and copy-on-write
1260    /// copied all of it on the next write to any field. Nothing reads
1261    /// this through a snapshot: every use in the engine and in the
1262    /// plugin repos is `rule.child_node` on a live rule.
1263    ///
1264    /// `Undefined` when the child shared this rule's node cell, so that
1265    /// this field is never a second handle on this rule's own
1266    /// accumulator (#195); [`Rule::child_value`] answers from the node
1267    /// in that case.
1268    pub child_node: Value,
1269    pub(crate) skip_befores: bool,
1270    pub(crate) child_node_is_self: bool,
1271    /// Whether the child link below still names the rule this one PUSHED,
1272    /// and that rule is still running.
1273    ///
1274    /// The link is frozen ONCE, by the first `freeze_child` after the push,
1275    /// and this says whether that has happened yet. It is deliberately not
1276    /// an identity carried on the child: a rule is handed to callbacks as
1277    /// `&mut Rule`, so an imperative action can assign `*rule = ...` and
1278    /// replace the whole value, resetting any field on it. Anything the
1279    /// engine needs to trust therefore cannot live there. This flag lives
1280    /// on the PARENT, which is buried on the parse stack for the whole of
1281    /// the child's life and is never handed to a callback, so no grammar
1282    /// can reach it.
1283    ///
1284    /// Getting this wrong is not a stale-looking value but a wrong one:
1285    /// `@node$`, `@value$`, `@object$`, `@fold$` and `@bubble$` all REPLACE
1286    /// `rule.node` with a fresh cell rather than writing through it
1287    /// (`rs/src/builtins.rs`), so a link left at its push-time cell holds
1288    /// the value from before the child did any work.
1289    ///
1290    /// TypeScript links `rule.child` at the push (`ts/src/rules.ts:665`)
1291    /// and never relinks it; Go does the same (`go/rule.go:1280`). A child
1292    /// that REPLACES itself therefore leaves the parent reading the first
1293    /// instance of the chain, which is why `@fold$` exists at all (see its
1294    /// doc comment in `ts/src/builtins.ts`). Closing the link on the first
1295    /// freeze is how that survives here, where the replaced rule is dropped
1296    /// rather than kept alive by a reference.
1297    pub(crate) child_link_open: bool,
1298    pub(crate) child_cell: Option<Rc<RefCell<Value>>>,
1299    /// The pushed rule's record, frozen at the moment it stopped being the
1300    /// current rule. Deliberately NOT a [`RuleSnapshot`] field: the parse
1301    /// loop writes it on a rule that is still buried on the stack, and
1302    /// `Context::sync_rule_stack` requires a buried frame's snapshot to
1303    /// keep describing it. It reaches `child_rule` only when the rule is
1304    /// resumed, which is the moment TypeScript's live reference would
1305    /// first be read from that rule again.
1306    pub(crate) child_record: Option<Rc<RuleSnapshot>>,
1307    /// Where this rule's prepared state sits in the parser's table, or
1308    /// `usize::MAX` for a rule the parser did not bind.
1309    ///
1310    /// A plain `usize` rather than a handle on the prepared record itself:
1311    /// the parse loop clones a `Rule` once per close and snapshots it
1312    /// several times per step, and an `Arc` here would make every one of
1313    /// those a pair of atomics. The table is reached through the parser's
1314    /// `&self` instead, and this only says where to look. It is a hint,
1315    /// not a fact -- a callback can write `spec` or `name` out from under
1316    /// it -- so the loop checks the record it finds against both before
1317    /// trusting it.
1318    pub(crate) slot: usize,
1319}
1320
1321impl Drop for RuleSnapshot {
1322    /// Unlink iteratively, because the derived drop recurses and the links
1323    /// below form a chain as long as the input.
1324    ///
1325    /// `parent_rule`, `child_rule`, `prev_rule` and `next_rule` each own an
1326    /// `Rc<RuleSnapshot>`, so the generated glue walks a chain with the call
1327    /// stack: `drop_in_place<RuleSnapshot>` calls `Rc::drop_slow` calls
1328    /// `drop_in_place<RuleSnapshot>` again, one frame per link. A grammar
1329    /// that pushes or replaces a rule per input element builds one link per
1330    /// element, so a flat JSON array of 150,000 numbers -- nesting depth
1331    /// ONE, nothing recursive about the document -- overflowed the default
1332    /// 8 MiB main-thread stack and aborted the process.
1333    ///
1334    /// Fat LTO makes it worse rather than better: inlining the cycle into
1335    /// itself multiplies the per-link frame, so a default release build
1336    /// survived an input that a build in the configuration rs/README.md
1337    /// documents for shipping did not. That is the wrong way round, and it
1338    /// is why this is a `Drop` impl rather than advice about stack size.
1339    ///
1340    /// The scratch is a local rather than a thread-local. A thread-local
1341    /// is faster and was wrong twice over: its key can be destroyed before
1342    /// another thread-local holding a snapshot is, and touching a
1343    /// destroyed key panics from inside a destructor; and re-entering this
1344    /// function while its `RefCell` is borrowed panics too. A local is
1345    /// immune to both, and the early return below means most drops never
1346    /// reach it.
1347    fn drop(&mut self) {
1348        // The overwhelmingly common case, and the one on the parse loop's
1349        // hot path: nothing is linked, so there is nothing to walk. Four
1350        // loads and a branch, no scratch, no allocation.
1351        if self.parent_rule.is_none()
1352            && self.child_rule.is_none()
1353            && self.prev_rule.is_none()
1354            && self.next_rule.is_none()
1355        {
1356            return;
1357        }
1358
1359        /// Move a snapshot's links out, preferring the single-slot
1360        /// `cursor` so that a pure chain -- one link per snapshot, which
1361        /// is what a rule replaced once per input element builds -- is
1362        /// walked without allocating anything at all. `pending` is only
1363        /// reached for a snapshot holding more than one link, and a `Vec`
1364        /// that is never pushed to never allocates.
1365        fn unlink(
1366            snapshot: &mut RuleSnapshot,
1367            cursor: &mut Option<Rc<RuleSnapshot>>,
1368            pending: &mut Vec<Rc<RuleSnapshot>>,
1369        ) {
1370            let mut hold = |held: Option<Rc<RuleSnapshot>>| {
1371                if let Some(held) = held {
1372                    if cursor.is_none() {
1373                        *cursor = Some(held);
1374                    } else {
1375                        pending.push(held);
1376                    }
1377                }
1378            };
1379            hold(snapshot.parent_rule.take());
1380            hold(snapshot.child_rule.take());
1381            hold(snapshot.prev_rule.take());
1382            hold(snapshot.next_rule.take());
1383        }
1384
1385        let mut cursor: Option<Rc<RuleSnapshot>> = None;
1386        let mut pending: Vec<Rc<RuleSnapshot>> = Vec::new();
1387        unlink(self, &mut cursor, &mut pending);
1388        while let Some(mut link) = cursor.take().or_else(|| pending.pop()) {
1389            if Rc::weak_count(&link) == 0 {
1390                // No weak observers, so `get_mut` answers "am I the last
1391                // handle" without moving anything. `RuleSnapshot` is a
1392                // twenty-field struct carrying eight reference-counted
1393                // handles, and this is the path the parse loop takes.
1394                if let Some(owned) = Rc::get_mut(&mut link) {
1395                    unlink(owned, &mut cursor, &mut pending);
1396                }
1397            } else if let Ok(mut owned) = Rc::try_unwrap(link) {
1398                // `Rule::snapshot` is public and `Context` hands out
1399                // `Rc<RuleSnapshot>`, so an embedder can hold a `Weak` to
1400                // one. `get_mut` refuses while any weak observer exists,
1401                // even when we ARE the last strong owner and dropping will
1402                // run the destructor; `try_unwrap` is the one that
1403                // distinguishes those. Getting this wrong left the links
1404                // in place and recursed after all.
1405                unlink(&mut owned, &mut cursor, &mut pending);
1406            }
1407            // Anything reached here drops with its links already taken, so
1408            // its own `drop` returns at the check above.
1409        }
1410    }
1411}
1412
1413impl std::ops::Deref for Rule {
1414    type Target = RuleSnapshot;
1415
1416    fn deref(&self) -> &RuleSnapshot {
1417        &self.shared
1418    }
1419}
1420
1421/// Every write to a rule's shared state goes through here, which is
1422/// what makes the copy happen on write rather than on snapshot.
1423///
1424/// The obvious next step — stop copying at all, so a snapshot aliases
1425/// the live rule the way TypeScript's and Go's rule handles do — was
1426/// measured here by making this return an aliasing pointer. It is
1427/// faster where it works: a 512-character palindrome drops from 31.7M
1428/// to 24.6M instructions, and a 16K-term adder from 72.7 ms to 57.8 ms.
1429/// But `next_rule` and the parent and child links then point at rules
1430/// that point back, and an `Rc` cycle is never freed: peak memory for
1431/// the benchmark set goes from 71 MB to 1214 MB, and a 32K-character
1432/// palindrome slows from 111 ms to 270 ms once the leak outweighs the
1433/// saving. Doing it properly needs `Weak` on every back-reference and
1434/// an upgrade on every traversal, which spends some of the same 1.3x
1435/// it is chasing. Worth knowing before anyone tries it again.
1436impl std::ops::DerefMut for Rule {
1437    fn deref_mut(&mut self) -> &mut RuleSnapshot {
1438        Rc::make_mut(&mut self.shared)
1439    }
1440}
1441
1442#[derive(Debug, Clone)]
1443pub struct RuleSnapshot {
1444    pub i: usize,
1445    pub d: usize,
1446    pub name: RuleName,
1447    pub spec: Arc<RuleSpec>,
1448    pub state: RuleState,
1449    pub bo: bool,
1450    pub ao: bool,
1451    pub bc: bool,
1452    pub ac: bool,
1453    pub need: i32,
1454    pub node: Rc<RefCell<Value>>,
1455    pub parent_rule: Option<Rc<RuleSnapshot>>,
1456    pub child_rule: Option<Rc<RuleSnapshot>>,
1457    pub prev_rule: Option<Rc<RuleSnapshot>>,
1458    pub next_rule: Option<Rc<RuleSnapshot>>,
1459    pub next_rule_name: Option<RuleName>,
1460    pub n: Rc<HashMap<String, i32>>,
1461    pub u: Rc<HashMap<String, Value>>,
1462    pub k: Rc<HashMap<String, Value>>,
1463    /// Matched open and close tokens. Shared rather than owned: the parse
1464    /// loop only ever replaces these wholesale, and a snapshot that copied
1465    /// them copied every `Token`'s name and source text with them.
1466    pub o: Rc<Vec<Token>>,
1467    pub c: Rc<Vec<Token>>,
1468}
1469
1470/// One shared empty map per thread, so a rule that never writes to `n`,
1471/// `u` or `k` costs no allocation for them. `Rc::make_mut` copies on the
1472/// first write, which for an empty map is close to free.
1473fn empty_counters() -> Rc<HashMap<String, i32>> {
1474    thread_local! {
1475        static EMPTY: Rc<HashMap<String, i32>> = Rc::new(HashMap::new());
1476    }
1477    EMPTY.with(Rc::clone)
1478}
1479
1480fn empty_values() -> Rc<HashMap<String, Value>> {
1481    thread_local! {
1482        static EMPTY: Rc<HashMap<String, Value>> = Rc::new(HashMap::new());
1483    }
1484    EMPTY.with(Rc::clone)
1485}
1486
1487/// The matched-token lists start empty and are replaced wholesale when an
1488/// alternate matches, so every rule created allocated two `Rc` boxes for two
1489/// vectors that never grew. Shared like the counter and value bags above.
1490fn empty_tokens() -> Rc<Vec<Token>> {
1491    thread_local! {
1492        static EMPTY: Rc<Vec<Token>> = Rc::new(Vec::new());
1493    }
1494    EMPTY.with(Rc::clone)
1495}
1496
1497impl Rule {
1498    /// Mutable access to the per-rule counters and state. Copies only when
1499    /// a snapshot is still holding the current value.
1500    pub fn n_mut(&mut self) -> &mut HashMap<String, i32> {
1501        Rc::make_mut(&mut self.n)
1502    }
1503
1504    pub fn u_mut(&mut self) -> &mut HashMap<String, Value> {
1505        Rc::make_mut(&mut self.u)
1506    }
1507
1508    pub fn k_mut(&mut self) -> &mut HashMap<String, Value> {
1509        Rc::make_mut(&mut self.k)
1510    }
1511
1512    pub fn new(name: impl Into<RuleName>, initial_node: Value) -> Self {
1513        let name: RuleName = name.into();
1514        let spec = Arc::new(RuleSpec::new(name.as_str()));
1515        Rule {
1516            shared: Rc::new(RuleSnapshot {
1517                i: 0,
1518                d: 0,
1519                name,
1520                spec,
1521                state: RuleState::Open,
1522                bo: true,
1523                ao: true,
1524                bc: true,
1525                ac: true,
1526                need: 0,
1527                node: Rc::new(RefCell::new(initial_node)),
1528                parent_rule: None,
1529                child_rule: None,
1530                prev_rule: None,
1531                next_rule: None,
1532                next_rule_name: None,
1533                n: empty_counters(),
1534                u: empty_values(),
1535                k: empty_values(),
1536                o: empty_tokens(),
1537                c: empty_tokens(),
1538            }),
1539            parent_node: None,
1540            child_node: Value::Undefined,
1541            skip_befores: false,
1542            child_node_is_self: false,
1543            child_link_open: false,
1544            child_cell: None,
1545            child_record: None,
1546            slot: usize::MAX,
1547        }
1548    }
1549
1550    pub fn with_shared_node(name: impl Into<RuleName>, node: Rc<RefCell<Value>>) -> Self {
1551        Self::bound(name.into(), node, None, usize::MAX)
1552    }
1553
1554    /// Build a rule already bound to its installed spec.
1555    ///
1556    /// An unbound rule reports its own name through `spec.name`, so building
1557    /// one with no spec has to invent a placeholder `RuleSpec` carrying that
1558    /// name: a `String` and an `Arc` box. Every push and replace in the parse
1559    /// loop then bound the installed spec straight over the placeholder, so
1560    /// both were allocated and freed once per rule step for nothing. The
1561    /// placeholder is still built for a name that names no installed rule,
1562    /// which is the case it exists for.
1563    pub(crate) fn bound(
1564        name: RuleName,
1565        node: Rc<RefCell<Value>>,
1566        installed: Option<&Arc<RuleSpec>>,
1567        slot: usize,
1568    ) -> Self {
1569        let spec = match installed {
1570            Some(spec) => Arc::clone(spec),
1571            None => Arc::new(RuleSpec::new(name.as_str())),
1572        };
1573        Rule {
1574            shared: Rc::new(RuleSnapshot {
1575                i: 0,
1576                d: 0,
1577                name,
1578                spec,
1579                state: RuleState::Open,
1580                bo: true,
1581                ao: true,
1582                bc: true,
1583                ac: true,
1584                need: 0,
1585                node,
1586                parent_rule: None,
1587                child_rule: None,
1588                prev_rule: None,
1589                next_rule: None,
1590                next_rule_name: None,
1591                n: empty_counters(),
1592                u: empty_values(),
1593                k: empty_values(),
1594                o: empty_tokens(),
1595                c: empty_tokens(),
1596            }),
1597            parent_node: None,
1598            child_node: Value::Undefined,
1599            skip_befores: false,
1600            child_node_is_self: false,
1601            child_link_open: false,
1602            child_cell: None,
1603            child_record: None,
1604            slot,
1605        }
1606    }
1607
1608    /// `name` arrives already shared: the parser interns one handle per
1609    /// installed rule, so binding copies a pointer rather than the text.
1610    ///
1611    /// `slot` is the rule's position in the parser's prepared table, which
1612    /// the same lookup that found the spec already returned.
1613    pub(crate) fn bind_spec(&mut self, spec: &Arc<RuleSpec>, name: RuleName, slot: usize) {
1614        self.name = name;
1615        self.spec = Arc::clone(spec);
1616        self.slot = slot;
1617        // Rust RuleSpec lifecycle lists are always present (possibly empty),
1618        // matching the canonical normalized definition's non-null defaults.
1619        self.bo = true;
1620        self.ao = true;
1621        self.bc = true;
1622        self.ac = true;
1623    }
1624
1625    pub fn o0(&self) -> Option<&Token> {
1626        self.o.first()
1627    }
1628
1629    pub fn o1(&self) -> Option<&Token> {
1630        self.o.get(1)
1631    }
1632
1633    pub fn c0(&self) -> Option<&Token> {
1634        self.c.first()
1635    }
1636
1637    pub fn c1(&self) -> Option<&Token> {
1638        self.c.get(1)
1639    }
1640
1641    pub fn os(&self) -> usize {
1642        self.o.len()
1643    }
1644
1645    pub fn cs(&self) -> usize {
1646        self.c.len()
1647    }
1648
1649    /// Resolve a matched opening token's eager or lazy semantic value without
1650    /// exposing the temporary token clone needed by Rust's borrow rules.
1651    pub fn resolve_open_value(&mut self, index: usize, context: &mut Context) -> Value {
1652        self.o
1653            .get(index)
1654            .cloned()
1655            .map_or(Value::Undefined, |token| token.resolve_val(self, context))
1656    }
1657
1658    /// Resolve a matched closing token's eager or lazy semantic value.
1659    pub fn resolve_close_value(&mut self, index: usize, context: &mut Context) -> Value {
1660        self.c
1661            .get(index)
1662            .cloned()
1663            .map_or(Value::Undefined, |token| token.resolve_val(self, context))
1664    }
1665
1666    /// Counter comparisons use zero for an unset counter, matching the
1667    /// canonical engine. `exist` distinguishes unset from explicitly zero.
1668    pub fn eq(&self, counter: &str, limit: i32) -> bool {
1669        self.n.get(counter).copied().unwrap_or(0) == limit
1670    }
1671
1672    pub fn lt(&self, counter: &str, limit: i32) -> bool {
1673        self.n.get(counter).copied().unwrap_or(0) < limit
1674    }
1675
1676    pub fn gt(&self, counter: &str, limit: i32) -> bool {
1677        self.n.get(counter).copied().unwrap_or(0) > limit
1678    }
1679
1680    pub fn lte(&self, counter: &str, limit: i32) -> bool {
1681        self.n.get(counter).copied().unwrap_or(0) <= limit
1682    }
1683
1684    pub fn gte(&self, counter: &str, limit: i32) -> bool {
1685        self.n.get(counter).copied().unwrap_or(0) >= limit
1686    }
1687
1688    pub fn exist(&self, counter: &str) -> bool {
1689        self.n.contains_key(counter)
1690    }
1691
1692    /// The rule's state as it stands, shared rather than copied. The
1693    /// copy, if one is still needed, happens on the rule's next write.
1694    pub fn snapshot(&self) -> Rc<RuleSnapshot> {
1695        Rc::clone(&self.shared)
1696    }
1697
1698    /// Remember which rule this one pushed, and where its node cell was at
1699    /// the push. Called by the parse loop's push arm.
1700    pub(crate) fn note_child_push(&mut self, child: &Rule) {
1701        self.child_link_open = true;
1702        self.child_cell = Some(Rc::clone(&child.node));
1703        self.child_record = Some(child.snapshot());
1704    }
1705
1706    /// The pushed child is about to stop being the current rule, either
1707    /// because it is being replaced or because it is popping. Freeze both
1708    /// halves of the link: the cell its `node` field ended on, and its
1709    /// record as it stands. That pair is what TypeScript would go on
1710    /// reading through the live `rule.child` reference.
1711    ///
1712    /// Only the FIRST call after the push freezes; the link is closed
1713    /// afterwards. A later link of a replacement chain would otherwise
1714    /// overwrite it and report a `rule.child` no canonical runtime can
1715    /// produce -- the pushed rule's node under the chain tail's name.
1716    /// Closing the link rather than comparing an identity on the child is
1717    /// deliberate: see `Rule::child_link_open`.
1718    /// Holding the cell costs a refcount and no copy-on-write: the `Value`
1719    /// itself is read out only when this rule is resumed. The record is
1720    /// `Rc<RuleSnapshot>`, which the replaced rule is about to stop
1721    /// writing to in any case.
1722    pub(crate) fn freeze_child(&mut self, child: &Rule) {
1723        if self.child_link_open {
1724            self.child_cell = Some(Rc::clone(&child.node));
1725            self.child_record = Some(child.snapshot());
1726            self.child_link_open = false;
1727        }
1728    }
1729
1730    pub(crate) fn accept_child_node(&mut self, child: &Rule) {
1731        self.freeze_child(child);
1732        let cell = self
1733            .child_cell
1734            .clone()
1735            .unwrap_or_else(|| Rc::clone(&child.node));
1736        self.child_node_is_self = Rc::ptr_eq(&self.node, &cell);
1737        // A child that shared this rule's node cell wrote into this
1738        // rule's own container, so the value it hands back IS this
1739        // rule's node. Holding a clone of it here would be a second
1740        // `Arc` handle on the accumulator this rule is about to write
1741        // into again, and `Arc::make_mut` then copies the whole
1742        // container once per element: one rule with N elements cost
1743        // O(N^2), 46 seconds for 800 fields where TypeScript took a
1744        // tenth of a second (#195). The field stays `Undefined` in that
1745        // case and `child_value()` answers from the node instead, which
1746        // is what TypeScript's `rule.child.node` (the same object as
1747        // `rule.node` then) gives.
1748        self.child_node = if self.child_node_is_self {
1749            Value::Undefined
1750        } else {
1751            cell.borrow().clone()
1752        };
1753    }
1754
1755    /// The completed child's value, as TypeScript's `rule.child.node`
1756    /// reads: `child_node` when the child had a node of its own, and
1757    /// this rule's own node when the child shared this rule's cell (see
1758    /// `Rule::accept_child_node`, which is crate-private, so this is a
1759    /// name and not a link). Read this rather than `child_node`
1760    /// wherever the shared case must be seen as a value.
1761    pub fn child_value(&self) -> Value {
1762        if self.child_node_is_self {
1763            self.node.borrow().clone()
1764        } else {
1765            self.child_node.clone()
1766        }
1767    }
1768
1769    /// Whether [`Rule::child_value`] is defined.
1770    pub fn has_child_value(&self) -> bool {
1771        if self.child_node_is_self {
1772            !self.node.borrow().is_undefined()
1773        } else {
1774            !self.child_node.is_undefined()
1775        }
1776    }
1777
1778    /// Resume this rule with the child that has just stopped running: its
1779    /// node, and the `rule.child` / `rule.next` links a grammar reads from
1780    /// the resumed rule.
1781    ///
1782    /// Both links name the rule this one PUSHED, never whichever link of a
1783    /// replacement chain happened to pop. TypeScript assigns `rule.child`
1784    /// and `rule.next` once, in the push arm (`ts/src/rules.ts:665`,
1785    /// `:720`), and relinks neither when a descendant pops; Go does the
1786    /// same (`go/rule.go:1280`, `:1343`). The chain's later links are
1787    /// separate rule objects there, so the parent goes on reading the
1788    /// first. Here that rule has been dropped, so [`Rule::freeze_child`]
1789    /// captured it at its replace and this call leaves that capture alone.
1790    pub(crate) fn accept_child(&mut self, child: &Rule) {
1791        self.accept_child_node(child);
1792        // `child_record` is `None` only for a rule resumed by a child it
1793        // did not push. No parse-loop path reaches that today -- the one
1794        // `stack.push` is the push arm, which records the child first --
1795        // and falling back to the popping rule keeps the link honest
1796        // rather than leaving a stale one behind.
1797        self.child_rule = self.child_record.clone().or_else(|| Some(child.snapshot()));
1798        self.next_rule = self.child_rule.clone();
1799    }
1800
1801    /// Let go of `child_node` while this rule sits on the parse stack, in
1802    /// the one case where holding it is not free.
1803    ///
1804    /// A pushed child starts out on its PARENT's node cell, and a child
1805    /// that never installs a cell of its own still has it when it closes
1806    /// -- `child_node_is_self`. [`Rule::accept_child_node`] then leaves
1807    /// `child_node` holding a second `Value` handle on the very container
1808    /// the next child will write into. Containers are copy-on-write
1809    /// (`value.rs`), so that second handle turns the next `push` or
1810    /// `insert` into a copy of the whole container, and a rule with one
1811    /// such child per element copies its accumulated node once per
1812    /// element: quadratic in the number of elements inside ONE rule,
1813    /// where TypeScript and Go, whose nodes are plain references, stay
1814    /// linear.
1815    ///
1816    /// Parking is invisible because every pop that resumes a parked rule
1817    /// OVERWRITES the field before anything reads that rule. There are
1818    /// four pops in `parser.rs`: the two close-phase pops and the
1819    /// forced-close loop hand the popped rule to `accept_child`, which
1820    /// writes both `child_node` and `child_node_is_self` outright,
1821    /// and nothing touches the rule in between. The fourth, the
1822    /// fixed-depth recovery pop in `attempt_recover`, does not, and that
1823    /// pop is taken only when `recover.pop_until_valid` is off -- which
1824    /// is why `parser.rs` parks only when that option is on.
1825    /// `pop_until_valid` is read through `&self` and cannot change
1826    /// during a parse, so a rule parks exactly when the pop that will
1827    /// resume it is one of the three that overwrite.
1828    ///
1829    /// The field itself is only ever read on the CURRENT rule in any
1830    /// case: by the built-ins, by grammar actions and by the plugin
1831    /// repos, all of which are handed the rule the loop is working on.
1832    /// Snapshots do not carry it (see its declaration) and
1833    /// `Context::rule_stack` is snapshots, so no callback can reach a
1834    /// stacked rule's copy either. That is the second line of defence,
1835    /// not the argument: the argument is the overwrite.
1836    pub(crate) fn park_child_node(&mut self) {
1837        if self.child_node_is_self {
1838            self.child_node = Value::Undefined;
1839        }
1840    }
1841}
1842
1843impl fmt::Display for Rule {
1844    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1845        write!(formatter, "[Rule {}~{}]", self.name, self.i)
1846    }
1847}
1848
1849#[cfg(test)]
1850mod tests {
1851    use super::*;
1852
1853    fn cell(text: &str) -> Rc<RefCell<Value>> {
1854        Rc::new(RefCell::new(Value::String(text.into())))
1855    }
1856
1857    /// The first freeze after the push is the one that lands.
1858    ///
1859    /// `@node$` and its relatives REPLACE `rule.node` with a fresh cell
1860    /// rather than writing through it, so a link left at the push-time cell
1861    /// holds the value from before the child ran. Freezing at the moment the
1862    /// child stops being current is what makes the parent read what the
1863    /// child actually produced.
1864    #[test]
1865    fn freeze_child_takes_the_cell_the_child_ended_on() {
1866        let mut parent = Rule::new("parent", Value::Undefined);
1867        let mut child = Rule::new("child", Value::Undefined);
1868        parent.note_child_push(&child);
1869
1870        child.node = cell("done");
1871        parent.freeze_child(&child);
1872
1873        assert_eq!(
1874            *parent
1875                .child_cell
1876                .as_ref()
1877                .expect("a frozen child cell")
1878                .borrow(),
1879            Value::String("done".into()),
1880            "the freeze kept the push-time cell instead of the one the child ended on"
1881        );
1882    }
1883
1884    /// A later link of a replacement chain must not overwrite the link.
1885    ///
1886    /// TypeScript and Go both keep `rule.child` on the rule the parent
1887    /// PUSHED, so the parent must go on reading the first link of the chain
1888    /// and not its tail.
1889    #[test]
1890    fn freeze_child_ignores_every_link_after_the_first() {
1891        let mut parent = Rule::new("parent", Value::Undefined);
1892        let mut pushed = Rule::new("pushed", Value::Undefined);
1893        parent.note_child_push(&pushed);
1894
1895        pushed.node = cell("pushed rule");
1896        parent.freeze_child(&pushed);
1897
1898        // `pushed` is replaced; the chain runs on and each link stops being
1899        // current in turn.
1900        let mut tail = Rule::new("tail", Value::Undefined);
1901        tail.node = cell("chain tail");
1902        parent.freeze_child(&tail);
1903
1904        assert_eq!(
1905            *parent
1906                .child_cell
1907                .as_ref()
1908                .expect("a frozen child cell")
1909                .borrow(),
1910            Value::String("pushed rule".into()),
1911            "a later link of the replacement chain overwrote the child link"
1912        );
1913    }
1914
1915    /// A callback cannot break the link by replacing the whole rule.
1916    ///
1917    /// An imperative action holds `&mut Rule`, so `*rule = Rule::new(..)` is
1918    /// legal and resets every field on the value, `i` included. While the
1919    /// link was keyed on an identity carried by the child, that assignment
1920    /// silently cost the parent its link and left it on the push-time cell.
1921    /// The flag lives on the parent, which is buried on the parse stack for
1922    /// the whole of the child's life and is never handed to a callback, so
1923    /// the freeze still lands and the parent sees what the replacement
1924    /// holds.
1925    #[test]
1926    fn a_callback_replacing_the_whole_child_rule_does_not_break_the_link() {
1927        let mut parent = Rule::new("parent", Value::Undefined);
1928        let mut child = Rule::new("child", Value::Undefined);
1929        parent.note_child_push(&child);
1930
1931        // What an imperative action is able to do to the rule it is given.
1932        child = Rule::new("child", Value::Undefined);
1933        child.node = cell("done");
1934
1935        parent.freeze_child(&child);
1936
1937        assert_eq!(
1938            *parent
1939                .child_cell
1940                .as_ref()
1941                .expect("a frozen child cell")
1942                .borrow(),
1943            Value::String("done".into()),
1944            "replacing the rule value cost the parent its child link"
1945        );
1946    }
1947}