Skip to main content

tabnas_expr/
lib.rs

1/* Copyright (c) 2021-2026 Richard Rodger and other contributors, MIT License */
2
3//! Pratt expression-operator plugin for the
4//! [tabnas](https://github.com/tabnas/parser) parser engine, layered on the
5//! [jsonic](https://github.com/tabnas/jsonic) relaxed-JSON base grammar.
6//!
7//! The algorithm is Pratt parsing, and draws heavily from the explanation
8//! written by Aleksey Kladov at
9//! <https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html>.
10//! See [`prattify`] for the core.
11//!
12//! Expressions parse into LISP-style S-expressions: an array whose first
13//! element describes the operator and whose remaining elements are the
14//! operand terms, so `1+2*3` becomes `["+", 1, ["*", 2, 3]]` once the
15//! operator descriptions are reduced to their source text. A caller
16//! supplied evaluator can reduce the tree to a value during the parse.
17//!
18//! TypeScript is canonical: `ts/src/expr.ts` defines the behaviour, the
19//! option names, the defaults and the order of alternates. The shared
20//! fixtures in `test/spec/*.tsv` are the parity contract across
21//! TypeScript, Go and Rust.
22//!
23//! ```
24//! fn main() -> Result<(), Box<dyn std::error::Error>> {
25//!     let value = tabnas_expr::parse("a:1+2*3")?;
26//!     assert_eq!(tabnas_expr::simplify(&value).to_string(),
27//!                r#"{"a":["+",1,["*",2,3]]}"#);
28//!     Ok(())
29//! }
30//! ```
31//!
32//! # Untrusted input
33//!
34//! A parsed expression is data, never instructions. This plugin reads
35//! expression text of unknown provenance and returns S-expressions for a
36//! caller supplied evaluator to reduce, and that hand-off is where hostile
37//! input could become execution. Treat every operand and operator as
38//! untrusted text, and never let a document select which evaluator or
39//! operator implementation runs.
40
41// The engine's error type is large by design (it carries the whole
42// rendered report), and the engine allows this lint at its own crate root
43// for the same reason.
44#![allow(clippy::result_large_err)]
45
46use std::cell::{Cell, RefCell};
47use std::collections::HashMap;
48use std::fmt;
49use std::rc::Rc;
50use std::sync::{Arc, OnceLock};
51
52use indexmap::IndexMap;
53
54use tabnas::{
55    AltMatch, AltSpec, Context, ListRef, Plugin, PluginError, Rule, RuleSnapshot, RuleSpec, Tabnas,
56    TabnasError, Tin, Token, Value, TIN_CA, TIN_CB, TIN_CL, TIN_CS, TIN_NR, TIN_ST, TIN_TX, TIN_VL,
57    TIN_ZZ,
58};
59
60/// VERSION is this crate's version. It MUST equal `ts/package.json`
61/// "version" and the `version` field in `rs/Cargo.toml`: the release
62/// orchestrator rewrites all of them, and `tests/version_test.rs` fails
63/// the build if they drift. Mirrors `VERSION` in `ts/src/expr.ts` and
64/// `const VERSION` in `go/expr.go`.
65pub const VERSION: &str = "0.5.15";
66
67/// The name this plugin registers under.
68pub const PLUGIN_NAME: &str = "Expr";
69
70/// The README's Rust examples run as doctests, so a stale one fails the
71/// gate rather than misleading the reader. Its `toml` and `bash` fences
72/// are skipped; rustdoc runs only the `rust` ones.
73#[cfg(doctest)]
74#[doc = include_str!("../README.md")]
75mod readme_examples {}
76
77/// The error a failed parse produces, re-exported so callers need not
78/// depend on the engine crate directly.
79pub use tabnas::TabnasError as ExprError;
80
81/// JavaScript's `Number.MIN_SAFE_INTEGER`, the binding power an operator
82/// with no declared `left` takes: it binds loosest on the left.
83pub const MIN_SAFE_INTEGER: i64 = -9_007_199_254_740_991;
84
85/// JavaScript's `Number.MAX_SAFE_INTEGER`, the binding power an operator
86/// with no declared `right` takes: it binds tightest on the right.
87pub const MAX_SAFE_INTEGER: i64 = 9_007_199_254_740_991;
88
89// ---------------------------------------------------------------------------
90// Operator model
91// ---------------------------------------------------------------------------
92
93/// Paren preval configuration: whether a value may precede the opening
94/// paren (`foo(1)`, `a[1]`), whether one is required, and which values are
95/// allowed to.
96#[derive(Debug, Clone, Default, PartialEq, Eq)]
97pub struct PrevalDef {
98    pub active: bool,
99    pub required: bool,
100    pub allow: Option<Vec<String>>,
101}
102
103/// One entry of the `op` option map: the definition of an operator.
104///
105/// `src` carries one source string for a prefix, suffix or infix
106/// operator and two for a ternary (`["?", ":"]`); a paren operator uses
107/// `osrc` and `csrc` instead.
108#[derive(Debug, Clone, PartialEq)]
109pub struct OpDef {
110    pub src: Vec<String>,
111    pub osrc: String,
112    pub csrc: String,
113    pub left: Option<i64>,
114    pub right: Option<i64>,
115    pub prefix: bool,
116    pub suffix: bool,
117    pub infix: bool,
118    pub ternary: bool,
119    pub paren: bool,
120    pub preval: Option<PrevalDef>,
121    /// Custom operator data, handed back to an evaluator as `op.use`.
122    pub use_data: Value,
123}
124
125impl Default for OpDef {
126    fn default() -> Self {
127        OpDef {
128            src: Vec::new(),
129            osrc: String::new(),
130            csrc: String::new(),
131            left: None,
132            right: None,
133            prefix: false,
134            suffix: false,
135            infix: false,
136            ternary: false,
137            paren: false,
138            preval: None,
139            use_data: Value::Undefined,
140        }
141    }
142}
143
144impl OpDef {
145    /// An infix operator with the given source and binding powers.
146    pub fn infix(src: impl Into<String>, left: i64, right: i64) -> Self {
147        OpDef {
148            src: vec![src.into()],
149            infix: true,
150            left: Some(left),
151            right: Some(right),
152            ..Default::default()
153        }
154    }
155
156    /// A prefix operator with the given source and right binding power.
157    pub fn prefix(src: impl Into<String>, right: i64) -> Self {
158        OpDef {
159            src: vec![src.into()],
160            prefix: true,
161            right: Some(right),
162            ..Default::default()
163        }
164    }
165
166    /// A suffix operator with the given source and left binding power.
167    pub fn suffix(src: impl Into<String>, left: i64) -> Self {
168        OpDef {
169            src: vec![src.into()],
170            suffix: true,
171            left: Some(left),
172            ..Default::default()
173        }
174    }
175
176    /// A ternary operator with its two source strings.
177    pub fn ternary(first: impl Into<String>, second: impl Into<String>) -> Self {
178        OpDef {
179            src: vec![first.into(), second.into()],
180            ternary: true,
181            ..Default::default()
182        }
183    }
184
185    /// A paren operator with its opening and closing source strings.
186    pub fn paren(osrc: impl Into<String>, csrc: impl Into<String>) -> Self {
187        OpDef {
188            osrc: osrc.into(),
189            csrc: csrc.into(),
190            paren: true,
191            ..Default::default()
192        }
193    }
194
195    /// Give a paren operator a preval configuration.
196    pub fn with_preval(mut self, preval: PrevalDef) -> Self {
197        self.preval = Some(preval);
198        self
199    }
200
201    /// Attach custom operator data, handed back to an evaluator.
202    pub fn with_use(mut self, use_data: Value) -> Self {
203        self.use_data = use_data;
204        self
205    }
206}
207
208/// The full operator description carried by a parsed expression and handed
209/// to an evaluator.
210#[derive(Debug, Clone, PartialEq)]
211pub struct Op {
212    pub name: String,
213    pub src: String,
214    pub left: i64,
215    pub right: i64,
216    pub use_data: Value,
217    pub prefix: bool,
218    pub suffix: bool,
219    pub infix: bool,
220    pub ternary: bool,
221    pub paren: bool,
222    pub terms: usize,
223    pub tkn: String,
224    pub tin: Tin,
225    pub osrc: String,
226    pub csrc: String,
227    pub otkn: String,
228    pub otin: Tin,
229    pub ctkn: String,
230    pub ctin: Tin,
231    pub preval: PrevalDef,
232    /// Which of a ternary's two source strings this operator is, as the
233    /// canonical `use.ternary.opI`. `None` for every other operator.
234    pub ternary_index: Option<usize>,
235}
236
237impl Default for Op {
238    fn default() -> Self {
239        Op {
240            name: String::new(),
241            src: String::new(),
242            left: MIN_SAFE_INTEGER,
243            right: MAX_SAFE_INTEGER,
244            use_data: Value::Undefined,
245            prefix: false,
246            suffix: false,
247            infix: false,
248            ternary: false,
249            paren: false,
250            terms: 0,
251            tkn: String::new(),
252            tin: -1,
253            osrc: String::new(),
254            csrc: String::new(),
255            otkn: String::new(),
256            otin: -1,
257            ctkn: String::new(),
258            ctin: -1,
259            preval: PrevalDef::default(),
260            ternary_index: None,
261        }
262    }
263}
264
265impl Op {
266    /// The serialized operator description, with the canonical lower-case
267    /// key names. `token` is the occurrence's own token, so it is supplied
268    /// separately rather than carried on the shared description.
269    fn to_value(&self, token: Option<&Token>) -> Value {
270        let mut map: IndexMap<String, Value> = IndexMap::new();
271        map.insert("src".into(), Value::String(self.src.clone()));
272        map.insert("left".into(), number(self.left));
273        map.insert("right".into(), number(self.right));
274        map.insert("name".into(), Value::String(self.name.clone()));
275        map.insert("infix".into(), Value::Bool(self.infix));
276        map.insert("prefix".into(), Value::Bool(self.prefix));
277        map.insert("suffix".into(), Value::Bool(self.suffix));
278        map.insert("ternary".into(), Value::Bool(self.ternary));
279        map.insert("tkn".into(), Value::String(self.tkn.clone()));
280        map.insert("tin".into(), Value::Number(f64::from(self.tin)));
281        map.insert("terms".into(), Value::Number(self.terms as f64));
282        map.insert("use".into(), self.use_value());
283        map.insert("paren".into(), Value::Bool(self.paren));
284        map.insert("osrc".into(), Value::String(self.osrc.clone()));
285        map.insert("csrc".into(), Value::String(self.csrc.clone()));
286        map.insert("otkn".into(), Value::String(self.otkn.clone()));
287        map.insert("ctkn".into(), Value::String(self.ctkn.clone()));
288        map.insert("otin".into(), Value::Number(f64::from(self.otin)));
289        map.insert("ctin".into(), Value::Number(f64::from(self.ctin)));
290        map.insert("preval".into(), self.preval_value());
291        map.insert("token".into(), token_value(token));
292        Value::object(map)
293    }
294
295    fn use_value(&self) -> Value {
296        let mut used: IndexMap<String, Value> = match &self.use_data {
297            Value::Object(entries) => (**entries).clone(),
298            _ => IndexMap::new(),
299        };
300        if let Some(index) = self.ternary_index {
301            let mut ternary: IndexMap<String, Value> = IndexMap::new();
302            ternary.insert("opI".into(), Value::Number(index as f64));
303            used.insert("ternary".into(), Value::object(ternary));
304        }
305        Value::object(used)
306    }
307
308    fn preval_value(&self) -> Value {
309        let mut preval: IndexMap<String, Value> = IndexMap::new();
310        preval.insert("active".into(), Value::Bool(self.preval.active));
311        preval.insert("required".into(), Value::Bool(self.preval.required));
312        if let Some(allow) = &self.preval.allow {
313            preval.insert(
314                "allow".into(),
315                Value::array(allow.iter().cloned().map(Value::String).collect()),
316            );
317        }
318        Value::object(preval)
319    }
320}
321
322/// A binding power as a `Value`. The powers are integers on a scale whose
323/// ends are JavaScript's safe-integer bounds, and every one of them is
324/// exactly representable as an `f64`.
325fn number(value: i64) -> Value {
326    Value::Number(value as f64)
327}
328
329/// The occurrence token an operator carries, under the canonical key
330/// names, so that an evaluator can report where a term came from.
331fn token_value(token: Option<&Token>) -> Value {
332    let Some(token) = token else {
333        return Value::object(IndexMap::new());
334    };
335    let mut map: IndexMap<String, Value> = IndexMap::new();
336    map.insert("isToken".into(), Value::Bool(true));
337    map.insert("name".into(), Value::String(token.name.as_str().into()));
338    map.insert("tin".into(), Value::Number(f64::from(token.tin)));
339    map.insert("src".into(), Value::String(token.src.as_str().into()));
340    map.insert("len".into(), Value::Number(token.len as f64));
341    map.insert("sI".into(), Value::Number(token.site.pos as f64));
342    map.insert("rI".into(), Value::Number(token.site.ri as f64));
343    map.insert("cI".into(), Value::Number(token.site.ci as f64));
344    Value::object(map)
345}
346
347// ---------------------------------------------------------------------------
348// Options
349// ---------------------------------------------------------------------------
350
351/// The site an evaluator is called from: the rule the reduction belongs to
352/// and the live parse context, and neither when [`evaluation`] is called on
353/// a finished tree.
354///
355/// The rule is a snapshot rather than the live rule, because the canonical
356/// port hands the evaluator the PARENT of the rule that is closing, which
357/// this engine exposes as a snapshot. Its state, its `u` flags and its node
358/// cell are all reachable through it.
359pub struct EvalSite<'a> {
360    rule: Option<Rc<RuleSnapshot>>,
361    context: Option<&'a mut Context>,
362    token: Option<Token>,
363}
364
365impl<'a> EvalSite<'a> {
366    /// A site with no live parse behind it.
367    pub fn detached() -> Self {
368        EvalSite {
369            rule: None,
370            context: None,
371            token: None,
372        }
373    }
374
375    /// A site inside a parse.
376    pub fn new(rule: Option<Rc<RuleSnapshot>>, context: &'a mut Context) -> Self {
377        EvalSite {
378            rule,
379            context: Some(context),
380            token: None,
381        }
382    }
383
384    /// The rule the reduction belongs to, when there is one.
385    pub fn rule(&self) -> Option<&RuleSnapshot> {
386        self.rule.as_deref()
387    }
388
389    /// The token this OCCURRENCE of the operator was matched from.
390    ///
391    /// The `Op` an evaluator receives is the shared description, one per
392    /// entry in the operator table, so it cannot say which occurrence is
393    /// being reduced. The canonical `makeOp` attaches the token to its
394    /// copy of the description for exactly that reason; this port keeps
395    /// the token beside the node instead, and hands it over here. The
396    /// position is the engine's `Site` triple: `token.site.pos`, `.ri`
397    /// (row) and `.ci` (column).
398    ///
399    /// It is `None` when the node was built without one, which is what
400    /// [`prattify`] called directly produces.
401    pub fn token(&self) -> Option<&Token> {
402        self.token.as_ref()
403    }
404
405    /// The live parse context, when there is one.
406    pub fn context(&mut self) -> Option<&mut Context> {
407        self.context.as_deref_mut()
408    }
409
410    /// Whether the rule carries a `u` flag set to `true`. The
411    /// `paren_preval` flag is what tells an evaluator that a paren
412    /// operator's first term is the preceding value.
413    pub fn flag(&self, name: &str) -> bool {
414        self.rule
415            .as_ref()
416            .is_some_and(|rule| matches!(rule.u.get(name), Some(Value::Bool(true))))
417    }
418
419    /// Reborrow, so a recursive reduction passes the same site down. The
420    /// token is not carried: it belongs to one occurrence, and the nested
421    /// reduction sets the one it is reducing.
422    fn reborrow(&mut self) -> EvalSite<'_> {
423        EvalSite {
424            rule: self.rule.clone(),
425            context: self.context.as_deref_mut(),
426            token: None,
427        }
428    }
429}
430
431/// Resolve the value of an operation. The Rust spelling of the canonical
432/// `Evaluate` callback: it takes the operator and its already reduced
433/// terms, and returns the value the expression stands for.
434pub type Evaluate = Arc<dyn Fn(&mut EvalSite<'_>, &Op, &[Value]) -> Value + Send + Sync>;
435
436/// Options for the plugin.
437///
438/// `op` is an insertion-ordered map, and the order matters: two operators
439/// claiming the same source string resolve to the same token, and the
440/// later entry wins, exactly as it does in the canonical TypeScript. A
441/// `None` entry deletes an operator the defaults provide.
442#[derive(Clone, Default)]
443pub struct ExprOptions {
444    pub op: IndexMap<String, Option<OpDef>>,
445    pub evaluate: Option<Evaluate>,
446}
447
448impl fmt::Debug for ExprOptions {
449    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
450        formatter
451            .debug_struct("ExprOptions")
452            .field("op", &self.op)
453            .field("evaluate", &self.evaluate.as_ref().map(|_| "<function>"))
454            .finish()
455    }
456}
457
458impl ExprOptions {
459    /// Options carrying no operators of their own; the defaults still
460    /// apply.
461    pub fn new() -> Self {
462        Self::default()
463    }
464
465    /// Add or replace one operator definition.
466    pub fn with_op(mut self, name: impl Into<String>, def: OpDef) -> Self {
467        self.op.insert(name.into(), Some(def));
468        self
469    }
470
471    /// Delete a default operator, the Rust spelling of `plain: null`.
472    pub fn without_op(mut self, name: impl Into<String>) -> Self {
473        self.op.insert(name.into(), None);
474        self
475    }
476
477    /// Set the evaluator that reduces each expression as it closes.
478    pub fn with_evaluate(
479        mut self,
480        evaluate: impl Fn(&mut EvalSite<'_>, &Op, &[Value]) -> Value + Send + Sync + 'static,
481    ) -> Self {
482        self.evaluate = Some(Arc::new(evaluate));
483        self
484    }
485
486    /// Read the operator table out of a serialized option bag, as the
487    /// engine hands it to a plugin. Unreadable entries are ignored, the
488    /// way the canonical plugin ignores what it cannot use.
489    pub fn from_value(options: &Value) -> Self {
490        let mut resolved = ExprOptions::new();
491        let Some(op) = object_get(options, "op") else {
492            return resolved;
493        };
494        let Value::Object(entries) = op else {
495            return resolved;
496        };
497        for (name, def) in entries.iter() {
498            match def {
499                Value::Object(_) => {
500                    resolved
501                        .op
502                        .insert(name.clone(), Some(op_def_from_value(def)));
503                }
504                // `plain: null` deletes a default operator.
505                _ => {
506                    resolved.op.insert(name.clone(), None);
507                }
508            }
509        }
510        resolved
511    }
512}
513
514fn object_get<'a>(value: &'a Value, key: &str) -> Option<&'a Value> {
515    match value {
516        Value::Object(map) => map.get(key),
517        Value::MapRef(map) => map.value.get(key),
518        _ => None,
519    }
520}
521
522fn value_bool(value: Option<&Value>) -> bool {
523    matches!(value, Some(Value::Bool(true)))
524}
525
526fn value_i64(value: Option<&Value>) -> Option<i64> {
527    match value {
528        Some(Value::Number(number)) => Some(*number as i64),
529        _ => None,
530    }
531}
532
533fn value_string(value: Option<&Value>) -> String {
534    match value {
535        Some(Value::String(text)) => text.clone(),
536        Some(Value::Text(text)) => text.string.clone(),
537        _ => String::new(),
538    }
539}
540
541fn op_def_from_value(def: &Value) -> OpDef {
542    let mut out = OpDef {
543        osrc: value_string(object_get(def, "osrc")),
544        csrc: value_string(object_get(def, "csrc")),
545        left: value_i64(object_get(def, "left")),
546        right: value_i64(object_get(def, "right")),
547        prefix: value_bool(object_get(def, "prefix")),
548        suffix: value_bool(object_get(def, "suffix")),
549        infix: value_bool(object_get(def, "infix")),
550        ternary: value_bool(object_get(def, "ternary")),
551        paren: value_bool(object_get(def, "paren")),
552        use_data: object_get(def, "use").cloned().unwrap_or(Value::Undefined),
553        ..Default::default()
554    };
555    match object_get(def, "src") {
556        Some(Value::String(src)) => out.src = vec![src.clone()],
557        Some(Value::Array(srcs)) => {
558            out.src = srcs
559                .iter()
560                .map(|src| value_string(Some(src)))
561                .collect::<Vec<_>>()
562        }
563        _ => {}
564    }
565    out.preval = match object_get(def, "preval") {
566        None | Some(Value::Undefined) | Some(Value::Null) => None,
567        Some(Value::Bool(active)) => Some(PrevalDef {
568            active: *active,
569            required: false,
570            allow: None,
571        }),
572        Some(preval) => Some(PrevalDef {
573            // True by default when a preval object is specified at all.
574            active: match object_get(preval, "active") {
575                None | Some(Value::Undefined) | Some(Value::Null) => true,
576                Some(Value::Bool(active)) => *active,
577                Some(_) => true,
578            },
579            required: value_bool(object_get(preval, "required")),
580            allow: match object_get(preval, "allow") {
581                Some(Value::Array(allow)) => Some(
582                    allow
583                        .iter()
584                        .map(|entry| value_string(Some(entry)))
585                        .collect::<Vec<_>>(),
586                ),
587                _ => None,
588            },
589        }),
590    };
591    out
592}
593
594// Default operator precedence: the "binding power" scale.
595//
596// Binding powers are compared only by ORDER, never by magnitude (every
597// comparison in `prattify` is a `<` or `<=`): a higher number binds
598// tighter. Associativity is set by left against right. Left below right is
599// left-associative (`a-b-c` is `(a-b)-c`); left above right is
600// right-associative. An unset left or right falls back to
601// [`MIN_SAFE_INTEGER`] or [`MAX_SAFE_INTEGER`], which is why prefix
602// operators give only `right` and parens give neither.
603//
604// The defaults occupy a compact low block on a base-1000000 ladder,
605// leaving the whole range above the built-ins open for client operators.
606// Tiers are 1000000 apart; the +100000 on `right` is the
607// left-associativity offset, so a tier occupies a 100000-wide band inside
608// its 1000000-wide slot and adjacent tiers can never overlap:
609//
610//    <1000000  looser client operators (assignment, ternary, logical)
611//     1000000  sequence and comma
612//     2000000  addition and subtraction                  built-in
613//     3000000  multiplication, division, remainder       built-in
614//     4000000  unary prefix                              built-in, tightest
615//     5000000+ free for tighter client operators
616/// The default operator table, as the plugin declares it to the engine.
617pub fn defaults() -> Value {
618    let mut ops: IndexMap<String, Value> = IndexMap::new();
619    ops.insert("positive".into(), prefix_value("+", 4_000_000));
620    ops.insert("negative".into(), prefix_value("-", 4_000_000));
621    // NOTE: all of these are left-associative, as left is below right.
622    // Example: 2+3+4 is (2+3)+4.
623    ops.insert("addition".into(), infix_value("+", 2_000_000, 2_100_000));
624    ops.insert("subtraction".into(), infix_value("-", 2_000_000, 2_100_000));
625    ops.insert(
626        "multiplication".into(),
627        infix_value("*", 3_000_000, 3_100_000),
628    );
629    ops.insert("division".into(), infix_value("/", 3_000_000, 3_100_000));
630    ops.insert("remainder".into(), infix_value("%", 3_000_000, 3_100_000));
631    ops.insert("plain".into(), paren_value("(", ")"));
632
633    let mut map: IndexMap<String, Value> = IndexMap::new();
634    map.insert("op".into(), Value::object(ops));
635    Value::object(map)
636}
637
638fn prefix_value(src: &str, right: i64) -> Value {
639    let mut def: IndexMap<String, Value> = IndexMap::new();
640    def.insert("prefix".into(), Value::Bool(true));
641    def.insert("right".into(), number(right));
642    def.insert("src".into(), Value::String(src.into()));
643    Value::object(def)
644}
645
646fn infix_value(src: &str, left: i64, right: i64) -> Value {
647    let mut def: IndexMap<String, Value> = IndexMap::new();
648    def.insert("infix".into(), Value::Bool(true));
649    def.insert("left".into(), number(left));
650    def.insert("right".into(), number(right));
651    def.insert("src".into(), Value::String(src.into()));
652    Value::object(def)
653}
654
655fn paren_value(osrc: &str, csrc: &str) -> Value {
656    let mut def: IndexMap<String, Value> = IndexMap::new();
657    def.insert("paren".into(), Value::Bool(true));
658    def.insert("osrc".into(), Value::String(osrc.into()));
659    def.insert("csrc".into(), Value::String(csrc.into()));
660    Value::object(def)
661}
662
663/// The default operator table as typed options.
664fn default_ops() -> IndexMap<String, Option<OpDef>> {
665    ExprOptions::from_value(&defaults()).op
666}
667
668/// Merge caller options over the defaults with the canonical semantics: a
669/// name the defaults already carry is deep-merged and moves to the end of
670/// the table, a new name is appended, and a `None` entry deletes.
671fn resolve_options(options: &ExprOptions) -> IndexMap<String, Option<OpDef>> {
672    let mut resolved = default_ops();
673    for (name, def) in &options.op {
674        resolved.shift_remove(name);
675        match def {
676            Some(def) => {
677                resolved.insert(name.clone(), Some(def.clone()));
678            }
679            None => {
680                resolved.insert(name.clone(), None);
681            }
682        }
683    }
684    resolved
685}
686
687// ---------------------------------------------------------------------------
688// The expression node arena
689// ---------------------------------------------------------------------------
690//
691// The canonical algorithm rewrites expression nodes IN PLACE, so that every
692// rule holding a node observes the rewrite: that is what keeps the overall
693// AST intact while an expression is still being assembled. JavaScript gets
694// that from array identity and the Go port buys it with a `*ListRef` box.
695//
696// The engine's `Value` is a copy-on-write tree with no interior mutability,
697// so a rewrite through one holder would be invisible to the others. Nodes
698// therefore live in a per-thread arena and the value that travels through
699// the parse is a HANDLE: a `ListRef` carrying the node's identity under
700// `meta.expr`. Cloning a handle shares the identity, which is exactly the
701// property the algorithm needs, and [`realize`] turns a finished tree of
702// handles into plain values at the parse boundary.
703//
704// A handle is a `ListRef` rather than a string because the base grammar's
705// value coalescing asks whether a node is a container, and the canonical
706// expression node -- a JavaScript array -- is one.
707
708/// One expression node: the operator, the token that introduced this
709/// occurrence, and the operand terms.
710///
711/// The operator is optional because the canonical port rewrites a
712/// completed ternary into a one-element LIST in place, keeping the node's
713/// identity so that every holder sees the list. A node with no operator is
714/// exactly that: a plain array of its terms.
715#[derive(Debug, Clone)]
716struct ExprNode {
717    op: Option<Arc<Op>>,
718    token: Option<Token>,
719    terms: Vec<Value>,
720    /// The node at the top of the expression this one belongs to, or this
721    /// node itself.
722    ///
723    /// The Pratt core only ever grows a tree DOWNWARD from its root: a
724    /// looser operator rewrites the root node in place, keeping its
725    /// identity, and a tighter one builds a new node under it. So the
726    /// root a node was created under stays its root for the life of the
727    /// expression, and recording it is what lets a rule holding a
728    /// sub-expression hand the whole expression back.
729    root: u64,
730    /// How many nodes the expression under this root holds, counted only
731    /// on a root. A tree is never deeper than it is large, so this bounds
732    /// its depth: see [`NODE_LIMIT`].
733    size: usize,
734}
735
736#[derive(Default)]
737struct Arena {
738    nodes: HashMap<u64, ExprNode>,
739    next_id: u64,
740    /// Parses currently running on this thread through this crate's own
741    /// entry points. While one is in flight the arena must not be cleared.
742    active: usize,
743}
744
745thread_local! {
746    static ARENA: RefCell<Arena> = RefCell::new(Arena::default());
747    /// Set when an expression outgrows [`NODE_LIMIT`]. The parse budget
748    /// reads it and cancels the parse.
749    static OVER_LIMIT: Cell<bool> = const { Cell::new(false) };
750}
751
752/// The `meta` key a handle carries its node identity under.
753const EXPR_META: &str = "expr";
754
755/// The most nodes one expression may hold.
756///
757/// The engine walks a value with the CALL STACK to display it, convert it
758/// to JSON or drop it, so a deeply nested result aborts the process rather
759/// than failing: a flat sum of a few thousand terms builds a tree as deep
760/// as it is long, and an abort is not something a caller can catch. An
761/// expression past this size is refused with the engine's `cancel` code
762/// instead, which is a recorded divergence (`../DIVERGENCE.md`):
763/// TypeScript raises a JavaScript `RangeError` of its own a few thousand
764/// terms later, and Go grows its stacks and keeps going. The number is
765/// the one the jsonic base grammar already applies to nested containers,
766/// so a document cannot nest deeper than this anyway.
767pub const NODE_LIMIT: usize = 127;
768
769/// The deepest rule stack a parse may reach.
770///
771/// [`NODE_LIMIT`] bounds an expression that is being BUILT, and an
772/// unterminated one builds nothing: `((((` with no closer pushes a rule
773/// frame per level and never reaches the limit, and the engine's cost per
774/// step grows with the stack, so the parse turned quadratic. The base
775/// grammar bounds its own nesting for the same reason, and the deepest
776/// document it accepts, 127 nested containers, measures 378 frames, so
777/// this one sits well above anything it lets through. Past it a parse is
778/// refused with the engine's `cancel` code.
779pub const RULE_LIMIT: usize = 1024;
780
781/// Read a handle's node identity.
782fn node_id(value: &Value) -> Option<u64> {
783    let Value::ListRef(list) = value else {
784        return None;
785    };
786    match list.meta.get(EXPR_META) {
787        Some(Value::Number(id)) => Some(*id as u64),
788        _ => None,
789    }
790}
791
792/// Whether a value is an expression node, the port of the canonical
793/// `isOp`.
794pub fn is_op(value: &Value) -> bool {
795    op_of(value).is_some()
796}
797
798/// The number of elements a node presents, the canonical `node.length`:
799/// the operator plus its terms for an expression, and the terms alone for
800/// a node rewritten into a plain list.
801fn node_length(value: &Value) -> usize {
802    if let Some(length) = with_node(value, |entry| {
803        entry.terms.len() + usize::from(entry.op.is_some())
804    }) {
805        return length;
806    }
807    match value {
808        Value::Array(entries) => entries.len(),
809        Value::ListRef(list) => list.value.len(),
810        _ => 0,
811    }
812}
813
814fn is_op_kind(value: &Value, kind: fn(&Op) -> bool) -> bool {
815    op_of(value).is_some_and(|op| kind(&op))
816}
817
818fn is_paren_op(value: &Value) -> bool {
819    is_op_kind(value, |op| op.paren)
820}
821
822fn is_ternary_op(value: &Value) -> bool {
823    is_op_kind(value, |op| op.ternary)
824}
825
826/// The operator heading an expression node.
827pub fn op_of(value: &Value) -> Option<Arc<Op>> {
828    let id = node_id(value)?;
829    ARENA.with(|arena| {
830        arena
831            .borrow()
832            .nodes
833            .get(&id)
834            .and_then(|node| node.op.clone())
835    })
836}
837
838/// The operand terms of an expression node.
839pub fn terms_of(value: &Value) -> Option<Vec<Value>> {
840    let id = node_id(value)?;
841    ARENA.with(|arena| arena.borrow().nodes.get(&id).map(|node| node.terms.clone()))
842}
843
844/// Allocate a node and return its handle. `under` is the expression the
845/// node belongs to, when it is built inside one.
846fn new_node_under(
847    op: Option<Arc<Op>>,
848    token: Option<Token>,
849    terms: Vec<Value>,
850    under: Option<&Value>,
851) -> Value {
852    let inherited = under.and_then(root_id);
853    let terms_taken = terms.clone();
854    let id = ARENA.with(|arena| {
855        let mut arena = arena.borrow_mut();
856        arena.next_id += 1;
857        let id = arena.next_id;
858        arena.nodes.insert(
859            id,
860            ExprNode {
861                op,
862                token,
863                terms,
864                root: inherited.unwrap_or(id),
865                size: 1,
866            },
867        );
868        id
869    });
870    let handle = handle_for(id);
871    if under.is_some() {
872        grow(&handle, 1);
873    }
874    adopt_all(&handle, &terms_taken);
875    handle
876}
877
878fn new_node(op: Option<Arc<Op>>, token: Option<Token>, terms: Vec<Value>) -> Value {
879    new_node_under(op, token, terms, None)
880}
881
882/// Count `count` more nodes against the expression `node` belongs to, and
883/// record it when the expression has outgrown [`NODE_LIMIT`].
884fn grow(node: &Value, count: usize) {
885    let Some(root) = root_id(node) else {
886        return;
887    };
888    let over = ARENA.with(|arena| {
889        let mut arena = arena.borrow_mut();
890        match arena.nodes.get_mut(&root) {
891            Some(entry) => {
892                entry.size += count;
893                entry.size > NODE_LIMIT
894            }
895            None => false,
896        }
897    });
898    if over {
899        OVER_LIMIT.with(|flag| flag.set(true));
900    }
901}
902
903/// The size of the expression a node belongs to.
904fn expression_size(node: &Value) -> usize {
905    let Some(root) = root_id(node) else {
906        return 0;
907    };
908    ARENA.with(|arena| {
909        arena
910            .borrow()
911            .nodes
912            .get(&root)
913            .map_or(0, |entry| entry.size)
914    })
915}
916
917/// Account for a term taken into an expression. A term that is itself an
918/// expression brings its whole tree with it, unless it already belongs to
919/// this one.
920fn adopt(container: &Value, term: &Value) {
921    let (Some(into), Some(from)) = (root_id(container), root_id(term)) else {
922        return;
923    };
924    if into == from {
925        return;
926    }
927    grow(container, expression_size(term));
928}
929
930fn adopt_all(container: &Value, terms: &[Value]) {
931    for term in terms {
932        adopt(container, term);
933    }
934}
935
936/// The handle for a node identity. Two handles carrying the same identity
937/// are the same node, whichever one a rule is holding.
938fn handle_for(id: u64) -> Value {
939    let mut meta: IndexMap<String, Value> = IndexMap::new();
940    meta.insert(EXPR_META.into(), Value::Number(id as f64));
941    Value::ListRef(Arc::new(ListRef {
942        value: Vec::new(),
943        implicit: false,
944        child: None,
945        meta,
946    }))
947}
948
949/// The identity of the expression a node belongs to.
950fn root_id(value: &Value) -> Option<u64> {
951    let id = node_id(value)?;
952    ARENA.with(|arena| arena.borrow().nodes.get(&id).map(|node| node.root))
953}
954
955/// The whole expression a node belongs to.
956fn root_node(value: &Value) -> Value {
957    match root_id(value) {
958        Some(id) => handle_for(id),
959        None => value.clone(),
960    }
961}
962
963fn with_node<R>(value: &Value, read: impl FnOnce(&ExprNode) -> R) -> Option<R> {
964    let id = node_id(value)?;
965    ARENA.with(|arena| arena.borrow().nodes.get(&id).map(read))
966}
967
968fn with_node_mut<R>(value: &Value, write: impl FnOnce(&mut ExprNode) -> R) -> Option<R> {
969    let id = node_id(value)?;
970    ARENA.with(|arena| arena.borrow_mut().nodes.get_mut(&id).map(write))
971}
972
973/// The port of the canonical `updateExprNode`: rewrite a node in place,
974/// keeping its identity, and truncate it to the supplied terms.
975fn update_expr_node(node: &Value, op: Option<Arc<Op>>, token: Option<Token>, terms: Vec<Value>) {
976    adopt_all(node, &terms);
977    with_node_mut(node, |entry| {
978        entry.op = op;
979        entry.token = token;
980        entry.terms = terms;
981    });
982}
983
984/// The port of the canonical `dupNode`: a shallow copy, sharing the terms
985/// with the original but carrying its own identity.
986fn dup_node(node: &Value) -> Value {
987    let Some((op, token, terms)) = with_node(node, |entry| {
988        (entry.op.clone(), entry.token.clone(), entry.terms.clone())
989    }) else {
990        return node.clone();
991    };
992    new_node_under(op, token, terms, Some(node))
993}
994
995/// `expr[index]` where index 1 is the first term: absent slots read as
996/// `Undefined`, as a sparse JavaScript array does.
997fn term_at(node: &Value, index: usize) -> Value {
998    with_node(node, |entry| {
999        entry
1000            .terms
1001            .get(index.saturating_sub(1))
1002            .cloned()
1003            .unwrap_or(Value::Undefined)
1004    })
1005    .unwrap_or(Value::Undefined)
1006}
1007
1008/// `expr[index] = value`, extending the node when the slot is past its
1009/// end, as assigning past a JavaScript array's length does.
1010fn set_term(node: &Value, index: usize, value: Value) {
1011    adopt(node, &value);
1012    with_node_mut(node, |entry| {
1013        let slot = index.saturating_sub(1);
1014        while entry.terms.len() <= slot {
1015            entry.terms.push(Value::Undefined);
1016        }
1017        entry.terms[slot] = value;
1018    });
1019}
1020
1021/// `expr.push(value)`.
1022fn push_term(node: &Value, value: Value) {
1023    adopt(node, &value);
1024    with_node_mut(node, |entry| entry.terms.push(value));
1025}
1026
1027/// The number of terms a node carries, the canonical `node.length - 1`.
1028fn term_count(node: &Value) -> usize {
1029    with_node(node, |entry| entry.terms.len()).unwrap_or(0)
1030}
1031
1032/// Refuse a handle whose node the arena no longer holds.
1033///
1034/// A node leaves the arena only when a parse releases it wholesale, so a
1035/// handle that names a missing one is a value that outlived the scope its
1036/// nodes were live in: taken out of [`parse_scope`], or read straight off
1037/// [`Tabnas::parse`] and kept across the next parse on this thread. Node
1038/// identities never repeat, so the handle cannot have been captured by a
1039/// later parse either: the expression it headed is simply gone.
1040///
1041/// This is loud on purpose. Reporting the missing node as an empty array
1042/// let a walk finish and hand back a well-formed value that had silently
1043/// lost the whole expression, which is worse than either a correct answer
1044/// or no answer: nothing downstream could tell the two apart.
1045fn released_handle(id: u64) -> ! {
1046    panic!(
1047        "tabnas-expr: expression node {id} has been released. The value \
1048         holding this handle outlived its parse: realize it with \
1049         `tabnas_expr::realize` inside the `parse_scope` closure, or before \
1050         the next parse on this thread, and let the realized value escape \
1051         instead."
1052    )
1053}
1054
1055/// Drop every node this thread's arena holds.
1056///
1057/// The arena is per-thread and is released when the outermost parse
1058/// through this crate's own entry points returns, or at the start of the
1059/// next parse on the thread when a caller drives the engine directly. Its
1060/// high-water mark is therefore one parse's expression nodes.
1061fn release_arena() {
1062    ARENA.with(|arena| {
1063        let mut arena = arena.borrow_mut();
1064        if 0 == arena.active {
1065            arena.nodes.clear();
1066            EVALUATED.with(|set| set.borrow_mut().clear());
1067            OVER_LIMIT.with(|flag| flag.set(false));
1068        }
1069    });
1070}
1071
1072/// Hold the arena open for the duration of one parse, and release it when
1073/// the outermost one returns.
1074struct ArenaGuard;
1075
1076impl ArenaGuard {
1077    fn enter() -> Self {
1078        ARENA.with(|arena| arena.borrow_mut().active += 1);
1079        OVER_LIMIT.with(|flag| flag.set(false));
1080        ArenaGuard
1081    }
1082}
1083
1084impl Drop for ArenaGuard {
1085    fn drop(&mut self) {
1086        ARENA.with(|arena| {
1087            let mut arena = arena.borrow_mut();
1088            arena.active = arena.active.saturating_sub(1);
1089            if 0 == arena.active {
1090                arena.nodes.clear();
1091                EVALUATED.with(|set| set.borrow_mut().clear());
1092                OVER_LIMIT.with(|flag| flag.set(false));
1093            }
1094        });
1095    }
1096}
1097
1098// ---------------------------------------------------------------------------
1099// Realizing and simplifying a parsed tree
1100// ---------------------------------------------------------------------------
1101
1102/// Replace every expression handle in a parsed value with the plain array
1103/// the canonical port returns: the operator description followed by the
1104/// operand terms.
1105///
1106/// A parse through [`parse`], [`parse_with`] or [`parse_simplified`] is
1107/// realized already. A caller driving a [`Tabnas`] instance directly calls
1108/// this on the result.
1109pub fn realize(value: &Value) -> Value {
1110    let mut open: Vec<u64> = Vec::new();
1111    realize_seen(value, &mut open)
1112}
1113
1114fn realize_seen(value: &Value, open: &mut Vec<u64>) -> Value {
1115    match value {
1116        Value::ListRef(list) => {
1117            let Some(id) = node_id(value) else {
1118                let mut out = (**list).clone();
1119                out.value = out
1120                    .value
1121                    .iter()
1122                    .map(|entry| realize_seen(entry, open))
1123                    .collect();
1124                return Value::ListRef(Arc::new(out));
1125            };
1126            // A rewrite can leave a node reachable from itself; the
1127            // canonical simplifier reports the same way rather than
1128            // recursing forever.
1129            if open.contains(&id) {
1130                return Value::String("[CIRCLE]".into());
1131            }
1132            let Some((op, token, terms)) = with_node(value, |entry| {
1133                (entry.op.clone(), entry.token.clone(), entry.terms.clone())
1134            }) else {
1135                released_handle(id)
1136            };
1137            open.push(id);
1138            let mut out = Vec::with_capacity(terms.len() + 1);
1139            if let Some(op) = &op {
1140                out.push(op.to_value(token.as_ref()));
1141            }
1142            for term in &terms {
1143                out.push(realize_seen(term, open));
1144            }
1145            open.pop();
1146            Value::array(out)
1147        }
1148        Value::Array(entries) => Value::array(
1149            entries
1150                .iter()
1151                .map(|entry| realize_seen(entry, open))
1152                .collect(),
1153        ),
1154        Value::Object(entries) => Value::object(
1155            entries
1156                .iter()
1157                .map(|(key, entry)| (key.clone(), realize_seen(entry, open)))
1158                .collect(),
1159        ),
1160        Value::MapRef(map) => {
1161            let mut out = (**map).clone();
1162            out.value = out
1163                .value
1164                .iter()
1165                .map(|(key, entry)| (key.clone(), realize_seen(entry, open)))
1166                .collect();
1167            Value::MapRef(Arc::new(out))
1168        }
1169        other => other.clone(),
1170    }
1171}
1172
1173/// Reduce a parsed value to the S-expression form the shared fixtures
1174/// compare: every operator description becomes its source text, and a
1175/// term that was never filled is dropped.
1176///
1177/// This is the Rust spelling of the canonical `S` helper the TypeScript
1178/// suite uses and of the Go port's `Simplify`.
1179pub fn simplify(value: &Value) -> Value {
1180    let mut open: Vec<u64> = Vec::new();
1181    simplify_seen(value, &mut open)
1182}
1183
1184fn simplify_seen(value: &Value, open: &mut Vec<u64>) -> Value {
1185    match value {
1186        Value::ListRef(list) => {
1187            let Some(id) = node_id(value) else {
1188                return Value::array(
1189                    list.value
1190                        .iter()
1191                        .map(|entry| simplify_seen(entry, open))
1192                        .collect(),
1193                );
1194            };
1195            if open.contains(&id) {
1196                return Value::String("[CIRCLE]".into());
1197            }
1198            let Some((op, terms)) =
1199                with_node(value, |entry| (entry.op.clone(), entry.terms.clone()))
1200            else {
1201                released_handle(id)
1202            };
1203            open.push(id);
1204            let mut out = Vec::with_capacity(terms.len() + 1);
1205            if let Some(op) = &op {
1206                // A paren operator's `src` is its opening source, so one
1207                // field covers both, as it does in the canonical port.
1208                out.push(Value::String(op.src.clone()));
1209            }
1210            for term in &terms {
1211                // An unfilled slot is `undefined` in the canonical port,
1212                // and the canonical simplifier filters those out.
1213                if term.is_undefined() {
1214                    continue;
1215                }
1216                out.push(simplify_seen(term, open));
1217            }
1218            open.pop();
1219            Value::array(out)
1220        }
1221        // A REALIZED expression: the operator description is an object
1222        // carrying its source text, which is what the canonical
1223        // simplifier reads (`x[0].src`).
1224        Value::Array(entries) if realized_source(entries.first()).is_some() => {
1225            let mut out = Vec::with_capacity(entries.len());
1226            out.push(Value::String(
1227                realized_source(entries.first()).unwrap_or_default(),
1228            ));
1229            for entry in entries.iter().skip(1) {
1230                if entry.is_undefined() {
1231                    continue;
1232                }
1233                out.push(simplify_seen(entry, open));
1234            }
1235            Value::array(out)
1236        }
1237        Value::Array(entries) => Value::array(
1238            entries
1239                .iter()
1240                .map(|entry| simplify_seen(entry, open))
1241                .collect(),
1242        ),
1243        Value::Object(entries) => Value::object(
1244            entries
1245                .iter()
1246                .map(|(key, entry)| (key.clone(), simplify_seen(entry, open)))
1247                .collect(),
1248        ),
1249        Value::MapRef(map) => Value::object(
1250            map.value
1251                .iter()
1252                .map(|(key, entry)| (key.clone(), simplify_seen(entry, open)))
1253                .collect(),
1254        ),
1255        Value::Text(text) => Value::String(text.string.clone()),
1256        other => other.clone(),
1257    }
1258}
1259
1260/// The source text of a realized operator description, when a value is
1261/// one.
1262fn realized_source(value: Option<&Value>) -> Option<String> {
1263    match object_get(value?, "src") {
1264        Some(Value::String(src)) if !src.is_empty() => Some(src.clone()),
1265        _ => None,
1266    }
1267}
1268
1269// ---------------------------------------------------------------------------
1270// The Pratt core
1271// ---------------------------------------------------------------------------
1272
1273/// One occurrence of an operator: the shared description and the token
1274/// that introduced it. The canonical `makeOp` builds the same pair by
1275/// copying the description and attaching the token.
1276#[derive(Debug, Clone)]
1277pub struct OpRef {
1278    pub op: Arc<Op>,
1279    pub token: Option<Token>,
1280}
1281
1282impl OpRef {
1283    /// An occurrence with no token behind it, for direct use of
1284    /// [`prattify`].
1285    pub fn new(op: Op) -> Self {
1286        OpRef {
1287            op: Arc::new(opify(op)),
1288            token: None,
1289        }
1290    }
1291
1292    fn at(op: &Arc<Op>, token: Option<Token>) -> Self {
1293        OpRef {
1294            op: op.clone(),
1295            token,
1296        }
1297    }
1298
1299    /// A fresh expression node headed by this operator.
1300    pub fn node(&self, terms: Vec<Value>) -> Value {
1301        new_node(Some(self.op.clone()), self.token.clone(), terms)
1302    }
1303
1304    /// A fresh expression node built inside an existing expression.
1305    fn node_under(&self, terms: Vec<Value>, under: &Value) -> Value {
1306        new_node_under(
1307            Some(self.op.clone()),
1308            self.token.clone(),
1309            terms,
1310            Some(under),
1311        )
1312    }
1313}
1314
1315/// Normalize an operator built by hand, filling the derived term count
1316/// when it is unset, as the parser's own operator builder does. The Rust
1317/// spelling of the canonical `testing.opify`.
1318pub fn opify(mut op: Op) -> Op {
1319    if 0 == op.terms {
1320        op.terms = if op.ternary {
1321            3
1322        } else if op.infix {
1323            2
1324        } else {
1325            1
1326        };
1327    }
1328    op
1329}
1330
1331/// Embed a new operator into an expression tree, in place, according to
1332/// operator precedence.
1333///
1334/// The node keeps its identity, so every rule holding it observes the
1335/// rewrite: that is what preserves the referential integrity of the root
1336/// expression while it is still being assembled. The returned handle is
1337/// the sub-expression the new operator now heads, which is where its next
1338/// term belongs.
1339pub fn prattify(expr: &Value, op: &OpRef) -> Value {
1340    let mut out = expr.clone();
1341    let Some(expr_op) = op_of(expr) else {
1342        return out;
1343    };
1344    let new = &op.op;
1345
1346    if new.infix {
1347        if expr_op.suffix || new.left <= expr_op.right {
1348            // The new operator is looser: it takes the whole expression
1349            // built so far as its first term.
1350            let prior = dup_node(expr);
1351            update_expr_node(expr, Some(new.clone()), op.token.clone(), vec![prior]);
1352        } else {
1353            // The new operator is tighter: it takes only the last term.
1354            let end = expr_op.terms;
1355            let last = term_at(expr, end);
1356            let drill = op_of(&last).is_some_and(|last_op| last_op.right < new.left);
1357            if drill {
1358                out = prattify(&last, op);
1359            } else {
1360                out = op.node_under(vec![last], expr);
1361                set_term(expr, end, out.clone());
1362            }
1363        }
1364    } else if new.prefix {
1365        out = op.node_under(Vec::new(), expr);
1366        set_term(expr, expr_op.terms, out.clone());
1367    } else if new.suffix {
1368        if !expr_op.suffix && expr_op.right <= new.left {
1369            let end = expr_op.terms;
1370            let last = term_at(expr, end);
1371            // NOTE: special case: a higher precedence suffix "drills"
1372            // into lower precedence prefixes, so `@@1!` is `@(@(1!))`
1373            // rather than `@((@1)!)`.
1374            let drill =
1375                op_of(&last).is_some_and(|last_op| last_op.prefix && last_op.right < new.left);
1376            if drill {
1377                prattify(&last, op);
1378            } else {
1379                set_term(expr, end, op.node_under(vec![last], expr));
1380            }
1381        } else {
1382            let prior = dup_node(expr);
1383            update_expr_node(expr, Some(new.clone()), op.token.clone(), vec![prior]);
1384        }
1385    }
1386
1387    out
1388}
1389
1390/// Convert a prior rule's node into the start of a new expression, the
1391/// port of the canonical `prior`.
1392fn prior_expr(rule: &mut Rule, prior: Option<&Rc<RuleSnapshot>>, op: &OpRef) -> Value {
1393    let prior_value = prior.map_or(Value::Undefined, |prior| prior.node.borrow().clone());
1394
1395    let node = if is_op(&prior_value) && !op.op.prefix {
1396        // Infix or suffix: the prior node is the genuine first term to
1397        // wrap (the `(1+2)` of `(1+2)+3`). Duplicate it so the in-place
1398        // rewrite below preserves referential integrity.
1399        let duplicate = dup_node(&prior_value);
1400        update_expr_node(
1401            &prior_value,
1402            Some(op.op.clone()),
1403            op.token.clone(),
1404            vec![duplicate],
1405        );
1406        prior_value
1407    } else {
1408        // A prefix operator never has a prior term, so any expression
1409        // sitting on the prior node here is a foreign parent-seed (a
1410        // ternary or paren wrapper the engine threaded down). Start
1411        // fresh rather than rewriting it.
1412        let terms = if op.op.prefix {
1413            Vec::new()
1414        } else {
1415            vec![prior_value]
1416        };
1417        let fresh = op.node(terms);
1418        if let Some(prior) = prior {
1419            // NOTE: nothing is written when there is no prior rule. The
1420            // canonical ports guard the same write, because a rule with
1421            // no parent would otherwise publish onto the no-rule
1422            // sentinel and leak the expression into every later parse.
1423            *prior.node.borrow_mut() = fresh.clone();
1424        }
1425        fresh
1426    };
1427
1428    // Ensure the first term's rule contains the final expression.
1429    if let Some(prior) = prior {
1430        rule.parent_rule = Some(prior.clone());
1431        rule.parent_node = Some(prior.node.clone());
1432    }
1433
1434    node
1435}
1436
1437// ---------------------------------------------------------------------------
1438// Evaluation
1439// ---------------------------------------------------------------------------
1440
1441/// Values an evaluator has already produced, by identity.
1442#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1443enum EvalKey {
1444    Node(u64),
1445    Shared(usize, usize),
1446}
1447
1448thread_local! {
1449    static EVALUATED: RefCell<Vec<EvalKey>> = const { RefCell::new(Vec::new()) };
1450}
1451
1452fn eval_key(value: &Value) -> Option<EvalKey> {
1453    if let Some(id) = node_id(value) {
1454        return Some(EvalKey::Node(id));
1455    }
1456    match value {
1457        Value::Array(entries) if !entries.is_empty() => Some(EvalKey::Shared(
1458            Arc::as_ptr(entries) as usize,
1459            entries.len(),
1460        )),
1461        Value::Object(entries) if !entries.is_empty() => Some(EvalKey::Shared(
1462            Arc::as_ptr(entries) as usize,
1463            entries.len(),
1464        )),
1465        Value::ListRef(list) => Some(EvalKey::Shared(
1466            Arc::as_ptr(list) as usize,
1467            list.value.len(),
1468        )),
1469        Value::MapRef(map) => Some(EvalKey::Shared(Arc::as_ptr(map) as usize, map.value.len())),
1470        _ => None,
1471    }
1472}
1473
1474fn evaluated_seen(value: &Value) -> bool {
1475    eval_key(value).is_some_and(|key| EVALUATED.with(|set| set.borrow().contains(&key)))
1476}
1477
1478fn evaluated_mark(value: &Value) {
1479    if let Some(key) = eval_key(value) {
1480        EVALUATED.with(|set| {
1481            let mut set = set.borrow_mut();
1482            if !set.contains(&key) {
1483                set.push(key);
1484            }
1485        });
1486    }
1487}
1488
1489/// Reduce an expression tree with an evaluator, innermost first.
1490///
1491/// An implicit list is reduced a member at a time as its members close and
1492/// again as a whole once it is complete, so a value the evaluator has
1493/// already produced is returned untouched rather than reduced twice. That
1494/// record is per-thread and is cleared with the expression arena.
1495pub fn evaluation(site: &mut EvalSite<'_>, value: &Value, evaluate: &Evaluate) -> Value {
1496    if value.is_undefined() || matches!(value, Value::Null) {
1497        return Value::Null;
1498    }
1499    if evaluated_seen(value) {
1500        return value.clone();
1501    }
1502
1503    let out = if is_op(value) {
1504        let Some((Some(op), token, terms)) = with_node(value, |entry| {
1505            (entry.op.clone(), entry.token.clone(), entry.terms.clone())
1506        }) else {
1507            return value.clone();
1508        };
1509        let reduced: Vec<Value> = terms
1510            .iter()
1511            .map(|term| evaluation(&mut site.reborrow(), term, evaluate))
1512            .collect();
1513        // The occurrence token travels on the site, not on the shared
1514        // description: `op` is one entry of the operator table and is the
1515        // same object for every occurrence. Restoring the previous token
1516        // keeps an outer reduction pointing at its own operator.
1517        let previous = std::mem::replace(&mut site.token, token);
1518        let reduction = evaluate(site, &op, &reduced);
1519        site.token = previous;
1520        reduction
1521    } else if let Value::Array(entries) = value {
1522        // An implicit list is a plain array, not an expression node, so
1523        // the branch above does not reach its members. Reduce them into a
1524        // NEW array: this is a documented entry point and the
1525        // parse-once, evaluate-many workflow needs a caller's tree to
1526        // survive a reduction intact.
1527        Value::array(
1528            entries
1529                .iter()
1530                .map(|entry| evaluation(&mut site.reborrow(), entry, evaluate))
1531                .collect(),
1532        )
1533    } else {
1534        value.clone()
1535    };
1536
1537    evaluated_mark(&out);
1538    out
1539}
1540
1541// ---------------------------------------------------------------------------
1542// Grammar construction
1543// ---------------------------------------------------------------------------
1544
1545/// Which of the four token-led operator families a map is built for.
1546#[derive(Clone, Copy, PartialEq, Eq)]
1547enum Anyfix {
1548    Prefix,
1549    Suffix,
1550    Infix,
1551    Ternary,
1552}
1553
1554impl Anyfix {
1555    fn name(self) -> &'static str {
1556        match self {
1557            Anyfix::Prefix => "prefix",
1558            Anyfix::Suffix => "suffix",
1559            Anyfix::Infix => "infix",
1560            Anyfix::Ternary => "ternary",
1561        }
1562    }
1563
1564    fn selects(self, def: &OpDef) -> bool {
1565        match self {
1566            Anyfix::Prefix => def.prefix,
1567            Anyfix::Suffix => def.suffix,
1568            Anyfix::Infix => def.infix,
1569            Anyfix::Ternary => def.ternary,
1570        }
1571    }
1572
1573    fn terms(self) -> usize {
1574        match self {
1575            Anyfix::Ternary => 3,
1576            Anyfix::Infix => 2,
1577            _ => 1,
1578        }
1579    }
1580}
1581
1582/// Resolve the token identity for an operator source, preferring one this
1583/// INSTANCE already binds.
1584///
1585/// The canonical plugin looks a source up with `tabnas.fixed(src)`, an
1586/// instance lookup: a host grammar registers its punctuation as
1587/// instance-level fixed tokens, and binding an operator to a fresh
1588/// `#E<src>` identity instead would leave its alternate waiting on a token
1589/// the host lexer never emits.
1590fn operator_tin(parser: &mut Tabnas, src: &str) -> (Tin, String) {
1591    if let Some(tin) = parser.fixed(src) {
1592        let name = parser.token_name(tin);
1593        return (tin, name);
1594    }
1595    let name = format!("#E{src}");
1596    let tin = parser.token_with_source(name.clone(), src);
1597    (tin, name)
1598}
1599
1600/// An operator definition's binding power, or the fallback when it is
1601/// unset.
1602///
1603/// The canonical `makeOpMap` writes `opdef.left || Number.MIN_SAFE_INTEGER`
1604/// and `opdef.right || Number.MAX_SAFE_INTEGER`. JavaScript's `||` is
1605/// falsy-based, so a power of ZERO is not a power of zero there: it falls
1606/// through to the fallback exactly as an absent one does. Keeping the zero
1607/// changes the tree a zero-power operator builds against one with a
1608/// negative power, which `../test/spec/binding-power-zero.tsv` pins.
1609fn binding_power(power: Option<i64>, unset: i64) -> i64 {
1610    match power {
1611        Some(0) | None => unset,
1612        Some(power) => power,
1613    }
1614}
1615
1616/// Build one token-led operator map, the port of the canonical
1617/// `makeOpMap`.
1618fn make_op_map(
1619    parser: &mut Tabnas,
1620    ops: &IndexMap<String, Option<OpDef>>,
1621    anyfix: Anyfix,
1622) -> IndexMap<Tin, Arc<Op>> {
1623    let mut map: IndexMap<Tin, Arc<Op>> = IndexMap::new();
1624    for (name, def) in ops {
1625        let Some(def) = def else { continue };
1626        if !anyfix.selects(def) {
1627            continue;
1628        }
1629        let src = def.src.first().cloned().unwrap_or_default();
1630        let (tin, tkn) = operator_tin(parser, &src);
1631
1632        let suffix = format!("-{}", anyfix.name());
1633        let op = Op {
1634            src: src.clone(),
1635            left: binding_power(def.left, MIN_SAFE_INTEGER),
1636            right: binding_power(def.right, MAX_SAFE_INTEGER),
1637            name: if name.ends_with(&suffix) {
1638                name.clone()
1639            } else {
1640                format!("{name}{suffix}")
1641            },
1642            infix: Anyfix::Infix == anyfix,
1643            prefix: Anyfix::Prefix == anyfix,
1644            suffix: Anyfix::Suffix == anyfix,
1645            ternary: Anyfix::Ternary == anyfix,
1646            tkn,
1647            tin,
1648            terms: anyfix.terms(),
1649            use_data: def.use_data.clone(),
1650            ..Default::default()
1651        };
1652
1653        if Anyfix::Ternary == anyfix {
1654            // A ternary has two source strings, and both resolve to the
1655            // same description apart from their token and their index.
1656            let second = def.src.get(1).cloned().unwrap_or_default();
1657            let (second_tin, second_tkn) = operator_tin(parser, &second);
1658
1659            let mut first = op.clone();
1660            first.ternary_index = Some(0);
1661            map.insert(tin, Arc::new(first));
1662
1663            let mut last = op;
1664            last.src = second;
1665            last.ternary_index = Some(1);
1666            last.tkn = second_tkn;
1667            last.tin = second_tin;
1668            map.insert(second_tin, Arc::new(last));
1669        } else {
1670            map.insert(tin, Arc::new(op));
1671        }
1672    }
1673    map
1674}
1675
1676/// Build the paren operator map, the port of the canonical
1677/// `makeParenMap`.
1678fn make_paren_map(
1679    parser: &mut Tabnas,
1680    ops: &IndexMap<String, Option<OpDef>>,
1681) -> IndexMap<Tin, Arc<Op>> {
1682    let mut map: IndexMap<Tin, Arc<Op>> = IndexMap::new();
1683    for (name, def) in ops {
1684        let Some(def) = def else { continue };
1685        if !def.paren {
1686            continue;
1687        }
1688        let (otin, otkn) = operator_tin(parser, &def.osrc);
1689        let (ctin, ctkn) = operator_tin(parser, &def.csrc);
1690        let op = Op {
1691            name: format!("{name}-paren"),
1692            osrc: def.osrc.clone(),
1693            csrc: def.csrc.clone(),
1694            otkn,
1695            otin,
1696            ctkn,
1697            ctin,
1698            preval: def.preval.clone().unwrap_or_default(),
1699            paren: true,
1700            src: def.osrc.clone(),
1701            terms: 1,
1702            use_data: def.use_data.clone(),
1703            ..Default::default()
1704        };
1705        map.insert(otin, Arc::new(op));
1706    }
1707    map
1708}
1709
1710/// Everything the rule alternates read: the operator maps, the token
1711/// identities they match on, and the evaluator.
1712struct Grammar {
1713    prefix: IndexMap<Tin, Arc<Op>>,
1714    suffix: IndexMap<Tin, Arc<Op>>,
1715    infix: IndexMap<Tin, Arc<Op>>,
1716    ternary: IndexMap<Tin, Arc<Op>>,
1717    paren_open: IndexMap<Tin, Arc<Op>>,
1718    paren_close: IndexMap<Tin, Arc<Op>>,
1719    tin_prefix: Vec<Tin>,
1720    tin_suffix: Vec<Tin>,
1721    tin_infix: Vec<Tin>,
1722    tin_tern0: Vec<Tin>,
1723    tin_tern1: Vec<Tin>,
1724    tin_open: Vec<Tin>,
1725    tin_close: Vec<Tin>,
1726    evaluate: Option<Evaluate>,
1727}
1728
1729/// The value tokens an expression term can start with, the canonical
1730/// `VAL = [TX, NR, ST, VL]`.
1731const VAL_TINS: [Tin; 4] = [TIN_TX, TIN_NR, TIN_ST, TIN_VL];
1732
1733impl Grammar {
1734    fn has_prefix(&self) -> bool {
1735        !self.tin_prefix.is_empty()
1736    }
1737
1738    fn has_infix(&self) -> bool {
1739        !self.tin_infix.is_empty()
1740    }
1741
1742    fn has_suffix(&self) -> bool {
1743        !self.tin_suffix.is_empty()
1744    }
1745
1746    fn has_ternary(&self) -> bool {
1747        !self.tin_tern0.is_empty() && !self.tin_tern1.is_empty()
1748    }
1749
1750    fn has_paren(&self) -> bool {
1751        !self.tin_open.is_empty() && !self.tin_close.is_empty()
1752    }
1753
1754    /// The operator occurrence a matched token introduces, the port of the
1755    /// canonical `makeOp`.
1756    fn op_use(map: &IndexMap<Tin, Arc<Op>>, token: Option<&Token>) -> Option<OpRef> {
1757        let token = token?;
1758        map.get(&token.tin)
1759            .map(|op| OpRef::at(op, Some(token.clone())))
1760    }
1761}
1762
1763// ---------------------------------------------------------------------------
1764// Rule helpers
1765// ---------------------------------------------------------------------------
1766
1767/// A rule counter, absent reading as zero.
1768fn counter(rule: &Rule, name: &str) -> i32 {
1769    rule.n.get(name).copied().unwrap_or(0)
1770}
1771
1772/// A rule-local number, absent reading as zero.
1773fn local_number(rule: &Rule, name: &str) -> i64 {
1774    match rule.u.get(name) {
1775        Some(Value::Number(number)) => *number as i64,
1776        _ => 0,
1777    }
1778}
1779
1780fn set_local_number(rule: &mut Rule, name: &str, value: i64) {
1781    rule.u_mut()
1782        .insert(name.to_string(), Value::Number(value as f64));
1783}
1784
1785/// Assign a rule's node: the Rust spelling of the canonical `r.node = v`.
1786///
1787/// A pushed or replaced rule SHARES its parent's node cell, so writing
1788/// through `rule.node.borrow_mut()` would overwrite the parent's node too.
1789/// Assigning installs a fresh cell instead.
1790fn set_node(rule: &mut Rule, value: Value) {
1791    rule.node = Rc::new(RefCell::new(value));
1792}
1793
1794fn node_of(rule: &Rule) -> Value {
1795    rule.node.borrow().clone()
1796}
1797
1798/// The `u` key an expr rule keeps its attachment point under, so that a
1799/// later rule reads the same node the canonical port would read off
1800/// `rule.node` even after the after-close has put the root back there.
1801const ATTACH: &str = "expr_attach";
1802
1803/// Publish an expression from an expr rule.
1804///
1805/// `rule.node` takes the ATTACHMENT POINT, where the operator's next term
1806/// belongs, exactly as the canonical port leaves it: the rules pushed from
1807/// here are seeded from it, and a chained prefix nests into it.
1808///
1809/// The ROOT goes to the rule's `u` bag, and the rule's after-close puts it
1810/// back on the node. The canonical port has no need for that, because it
1811/// reads the parse result off the FIRST rule of a replacement chain, which
1812/// the root reaches through `prior_expr`. This engine reads it off the
1813/// LAST, so a rule that kept only its attachment point would hand a
1814/// sub-expression back as the whole parse: `1+2*3` came back as
1815/// `["*",2,3]`.
1816fn publish_expr(rule: &mut Rule, attach: Value) {
1817    set_node(rule, attach.clone());
1818    rule.u_mut().insert(ATTACH.to_string(), attach);
1819}
1820
1821/// Write a value through a rule's shared node cell.
1822fn publish(rule: &mut Rule, value: Value) {
1823    *rule.node.borrow_mut() = value;
1824}
1825
1826/// The root of the expression this rule is building.
1827///
1828/// The rule the first term belongs to holds it: `prior_expr` writes the
1829/// new expression there, and the Pratt core rewrites that same node in
1830/// place however the tree grows, so its identity never moves. A rule with
1831/// no such prior holds the root itself.
1832fn expression_root(rule: &Rule) -> Value {
1833    root_node(&node_of(rule))
1834}
1835
1836/// A linked rule's node as the canonical port would read it.
1837///
1838/// An expr rule keeps its ATTACHMENT POINT on `rule.node` there, and this
1839/// port puts the root back on the node once the rule has closed, so an
1840/// expr rule is read through its attachment point instead: `0!-1!*2!`
1841/// needs the trailing suffix to bind to `2`, which it only does when the
1842/// Pratt core is handed the sub-expression the last operator attached to.
1843fn linked_node(link: Option<&Rc<RuleSnapshot>>) -> Value {
1844    let Some(link) = link else {
1845        return Value::Undefined;
1846    };
1847    if let Some(attach) = link.u.get(ATTACH) {
1848        if is_op(attach) {
1849            return attach.clone();
1850        }
1851    }
1852    link.node.borrow().clone()
1853}
1854
1855fn write_linked_node(link: Option<&Rc<RuleSnapshot>>, value: Value) {
1856    if let Some(link) = link {
1857        *link.node.borrow_mut() = value;
1858    }
1859}
1860
1861fn counters(pairs: &[(&str, i32)]) -> HashMap<String, i32> {
1862    pairs
1863        .iter()
1864        .map(|(name, value)| ((*name).to_string(), *value))
1865        .collect()
1866}
1867
1868fn locals(pairs: &[(&str, Value)]) -> HashMap<String, Value> {
1869    pairs
1870        .iter()
1871        .map(|(name, value)| ((*name).to_string(), value.clone()))
1872        .collect()
1873}
1874
1875/// An alternate matching one token from the given set.
1876fn on(tins: &[Tin]) -> AltSpec {
1877    AltSpec {
1878        s: vec![tins.to_vec()],
1879        ..Default::default()
1880    }
1881}
1882
1883/// An alternate matching two tokens in sequence.
1884fn on2(first: &[Tin], second: &[Tin]) -> AltSpec {
1885    AltSpec {
1886        s: vec![first.to_vec(), second.to_vec()],
1887        ..Default::default()
1888    }
1889}
1890
1891/// An alternate with no token filter at all.
1892fn always() -> AltSpec {
1893    AltSpec::default()
1894}
1895
1896fn group_has(tags: &str, tag: &str) -> bool {
1897    tags.split(',').any(|entry| entry.trim() == tag)
1898}
1899
1900/// Add alternates the way the canonical `RuleSpec.open`/`close` do: the
1901/// whole list goes in FRONT of the alternates already there, keeping its
1902/// own order.
1903fn prepend_open_all(spec: &mut RuleSpec, alts: Vec<AltSpec>) {
1904    for alt in alts.into_iter().rev() {
1905        spec.prepend_open(alt);
1906    }
1907}
1908
1909fn prepend_close_all(spec: &mut RuleSpec, alts: Vec<AltSpec>) {
1910    for alt in alts.into_iter().rev() {
1911        spec.prepend_close(alt);
1912    }
1913}
1914
1915// ---------------------------------------------------------------------------
1916// The plugin
1917// ---------------------------------------------------------------------------
1918
1919/// Install the expression grammar on a parser that already carries the
1920/// jsonic base rules (`val`, `map`, `list`, `pair`, `elem`).
1921///
1922/// This is the plugin body; most callers want [`plugin`], [`apply`] or
1923/// [`make`].
1924pub fn expr(parser: &mut Tabnas, options: &ExprOptions) -> Result<(), PluginError> {
1925    let rules = parser.rule_names();
1926
1927    // A re-run on an instance that already carries the grammar must not
1928    // give it a second copy of every alternate. A derived instance starts
1929    // from an empty rule set and so installs normally.
1930    if rules.iter().any(|name| "expr" == name) {
1931        return Ok(());
1932    }
1933
1934    // The plugin hangs its operator alternates on the base grammar's
1935    // `val` rule. On a bare engine there is nothing to hang them on, and
1936    // the parse would fail later with a message about the document.
1937    if !rules.iter().any(|name| "val" == name) {
1938        return Err(PluginError(
1939            "tabnas-expr: the instance carries no `val` rule. This plugin layers on the \
1940             jsonic base grammar; install that first (tabnas_jsonic::jsonic, or \
1941             tabnas_expr::make)."
1942                .into(),
1943        ));
1944    }
1945
1946    let ops = resolve_options(options);
1947
1948    let prefix = make_op_map(parser, &ops, Anyfix::Prefix);
1949    let suffix = make_op_map(parser, &ops, Anyfix::Suffix);
1950    let infix = make_op_map(parser, &ops, Anyfix::Infix);
1951    let ternary = make_op_map(parser, &ops, Anyfix::Ternary);
1952    let paren_open = make_paren_map(parser, &ops);
1953    let paren_close: IndexMap<Tin, Arc<Op>> = paren_open
1954        .values()
1955        .map(|op| (op.ctin, op.clone()))
1956        .collect();
1957
1958    let grammar = Arc::new(Grammar {
1959        tin_prefix: prefix.keys().copied().collect(),
1960        tin_suffix: suffix.keys().copied().collect(),
1961        tin_infix: infix.keys().copied().collect(),
1962        tin_tern0: ternary
1963            .values()
1964            .filter(|op| Some(0) == op.ternary_index)
1965            .map(|op| op.tin)
1966            .collect(),
1967        tin_tern1: ternary
1968            .values()
1969            .filter(|op| Some(1) == op.ternary_index)
1970            .map(|op| op.tin)
1971            .collect(),
1972        tin_open: paren_open.values().map(|op| op.otin).collect(),
1973        tin_close: paren_close.values().map(|op| op.ctin).collect(),
1974        prefix,
1975        suffix,
1976        infix,
1977        ternary,
1978        paren_open,
1979        paren_close,
1980        evaluate: options.evaluate.clone(),
1981    });
1982
1983    comment_before_fixed(parser, &grammar);
1984
1985    // Release the previous parse's expression nodes when no parse this
1986    // crate drives is in flight. A caller driving the engine directly
1987    // therefore holds its result's nodes until the next parse on the
1988    // thread, which is when to call `realize`.
1989    parser.parse_prepare(|_context: &mut Context| release_arena());
1990
1991    bound_expression_size(parser);
1992
1993    install_val(parser, &grammar);
1994    install_list(parser, &grammar);
1995    install_map(parser, &grammar);
1996    install_elem(parser, &grammar);
1997    install_pair(parser, &grammar);
1998    install_expr(parser, &grammar);
1999    if grammar.has_paren() {
2000        install_paren(parser, &grammar);
2001    }
2002    if grammar.has_ternary() {
2003        install_ternary(parser, &grammar);
2004    }
2005
2006    Ok(())
2007}
2008
2009/// Keep a comment opening marker out of the fixed-token matcher's reach.
2010///
2011/// The canonical plugin moves the comment matcher in front of the fixed
2012/// one, because an operator such as `/` would otherwise cut `//` into two
2013/// operator tokens and a comment would never be recognised. This engine's
2014/// matcher families sit in fixed priority bands, so the same effect comes
2015/// from a `check` on the fixed family: where a comment starts, the fixed
2016/// matcher stands aside and the comment matcher takes the run.
2017///
2018/// It is installed whenever some operator source really is a prefix of a
2019/// comment marker, INCLUDING on a host that already configured
2020/// `options.fixed.check`. Omitting it there left the host's check in place
2021/// and the `/` operator ahead of `//` and `/*`, so a valid comment lexed
2022/// as operators and the document failed; the canonical runs a host check
2023/// and lexes comments correctly at the same time, because reordering the
2024/// matchers means the fixed family, its check included, is never reached
2025/// at a comment opener. Standing aside there is the same behaviour.
2026///
2027/// A host check is DISPLACED rather than chained. `LexCheck` holds its
2028/// callback privately and the engine offers no way to run one from
2029/// outside, so the check installed here cannot call through to the one it
2030/// replaces; everywhere but a contested comment opener it returns
2031/// `Continue`, which is what the fixed family does with no check at all. A
2032/// host that needs both installs its own check AFTER this plugin and skips
2033/// the comment openers itself.
2034fn comment_before_fixed(parser: &mut Tabnas, grammar: &Grammar) {
2035    let options = parser.config();
2036    let markers: Vec<String> = options
2037        .comment
2038        .definitions
2039        .values()
2040        .filter(|def| def.lex)
2041        .map(|def| def.start.clone())
2042        .filter(|start| !start.is_empty())
2043        .collect();
2044    if markers.is_empty() {
2045        return;
2046    }
2047
2048    let sources: Vec<String> = grammar
2049        .prefix
2050        .values()
2051        .chain(grammar.suffix.values())
2052        .chain(grammar.infix.values())
2053        .chain(grammar.ternary.values())
2054        .map(|op| op.src.clone())
2055        .chain(
2056            grammar
2057                .paren_open
2058                .values()
2059                .flat_map(|op| [op.osrc.clone(), op.csrc.clone()]),
2060        )
2061        .filter(|src| !src.is_empty())
2062        .collect();
2063
2064    let contested: Vec<String> = markers
2065        .into_iter()
2066        .filter(|marker| {
2067            sources
2068                .iter()
2069                .any(|src| src.len() < marker.len() && marker.starts_with(src.as_str()))
2070        })
2071        .collect();
2072    if contested.is_empty() {
2073        return;
2074    }
2075
2076    parser.lex_check_ref(COMMENT_FIRST, move |remaining: &str| {
2077        if contested
2078            .iter()
2079            .any(|marker| remaining.starts_with(marker.as_str()))
2080        {
2081            tabnas::LexCheckResult::Skip
2082        } else {
2083            tabnas::LexCheckResult::Continue
2084        }
2085    });
2086    let _ = parser.grammar_json(COMMENT_FIRST_DOCUMENT);
2087}
2088
2089/// Refuse an expression that has outgrown [`NODE_LIMIT`].
2090///
2091/// The check rides on the engine's parse budget, and composes with a
2092/// budget already in force rather than replacing it: the jsonic base
2093/// grammar installs one to bound container nesting, and both limits exist
2094/// for the same reason (see [`NODE_LIMIT`]).
2095fn bound_expression_size(parser: &mut Tabnas) {
2096    let budget = parser.config().parse.budget.clone();
2097    let existing = budget.on_check.clone();
2098    let every = match budget.check_every_n {
2099        0 => 32,
2100        set => set.min(32),
2101    };
2102    parser.parse_budget(every, move |context: &Context| {
2103        if OVER_LIMIT.with(Cell::get) {
2104            return false;
2105        }
2106        if context.rule_stack.len() > RULE_LIMIT {
2107            return false;
2108        }
2109        existing.as_ref().is_none_or(|check| check(context))
2110    });
2111}
2112
2113const COMMENT_FIRST: &str = "@expr-comment-before-fixed";
2114const COMMENT_FIRST_DOCUMENT: &str =
2115    r#"{"options":{"fixed":{"check":"@expr-comment-before-fixed"}}}"#;
2116
2117// ---------------------------------------------------------------------------
2118// val
2119// ---------------------------------------------------------------------------
2120
2121fn install_val(parser: &mut Tabnas, grammar: &Arc<Grammar>) {
2122    let grammar = grammar.clone();
2123    parser.define_rule("val", move |spec| {
2124        // Implicit pair not allowed inside a ternary: the `2:` of `1?2:3`
2125        // is the ternary's second operator, not a pair key.
2126        if grammar.has_ternary() && grammar.tin_tern1.contains(&TIN_CL) {
2127            for alt in spec.open.iter_mut().filter(|alt| group_has(&alt.g, "pair")) {
2128                let previous = alt.c_fn.clone();
2129                alt.c_fn = Some(Arc::new(move |rule: &mut Rule, context: &mut Context| {
2130                    previous
2131                        .as_ref()
2132                        .is_none_or(|condition| condition(rule, context))
2133                        && 0 == counter(rule, "expr_ternary")
2134                }));
2135            }
2136        }
2137
2138        let mut open: Vec<AltSpec> = Vec::new();
2139
2140        // The prefix operator of the first term of an expression.
2141        if grammar.has_prefix() {
2142            let mut alt = on(&grammar.tin_prefix);
2143            alt.b = 1;
2144            alt.n = counters(&[("expr_prefix", 1), ("expr_suffix", 0)]);
2145            alt.p = Some("expr".into());
2146            alt.g = "expr,expr-prefix".into();
2147            alt.add_action(detach_node);
2148            open.push(alt);
2149        }
2150
2151        // A value followed by an opening paren: `foo(1)`, `a[1]`.
2152        if grammar.has_paren() {
2153            let mut alt = on2(&VAL_TINS, &grammar.tin_open);
2154            alt.b = 1;
2155            alt.p = Some("expr".into());
2156            let condition = grammar.clone();
2157            alt.c_fn = Some(Arc::new(move |rule: &mut Rule, context: &mut Context| {
2158                let Some(pdef) = rule
2159                    .o1()
2160                    .and_then(|token| condition.paren_open.get(&token.tin))
2161                    .cloned()
2162                else {
2163                    return false;
2164                };
2165                if !pdef.preval.active {
2166                    return false;
2167                }
2168                let Some(allow) = &pdef.preval.allow else {
2169                    return true;
2170                };
2171                let preval = rule.resolve_open_value(0, context);
2172                allow.contains(&value_string(Some(&preval)))
2173            }));
2174            alt.u = locals(&[("paren_preval", Value::Bool(true))]);
2175            alt.g = "expr,expr-paren,expr-paren-preval".into();
2176            alt.add_action(|rule: &mut Rule, context: &mut Context| {
2177                let preval = rule.resolve_open_value(0, context);
2178                set_node(rule, preval);
2179            });
2180            open.push(alt);
2181        }
2182
2183        // An opening parenthesis. NOTE: this can happen outside an
2184        // expression.
2185        if grammar.has_paren() {
2186            let mut alt = on(&grammar.tin_open);
2187            alt.b = 1;
2188            alt.p = Some("expr".into());
2189            let condition = grammar.clone();
2190            alt.c_fn = Some(Arc::new(move |rule: &mut Rule, _context: &mut Context| {
2191                rule.o0()
2192                    .and_then(|token| condition.paren_open.get(&token.tin))
2193                    .is_some_and(|pdef| !pdef.preval.required)
2194            }));
2195            alt.g = "expr,expr-paren".into();
2196            alt.add_action(detach_node);
2197            open.push(alt);
2198        }
2199
2200        prepend_open_all(spec, open);
2201
2202        let mut close: Vec<AltSpec> = Vec::new();
2203
2204        // Comma-op suppression. When a parent rule (an embedding
2205        // grammar's wrapper rule, say) sets n.no_comma_op, bail at `,`
2206        // without treating it as the comma operator: the parent then
2207        // consumes the `,` itself as a separator. Match by `src` on the
2208        // next infix token, so this works whichever token the embedding
2209        // grammar uses for `,`.
2210        if grammar.has_infix() {
2211            let mut alt = on(&grammar.tin_infix);
2212            alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2213                0 < counter(rule, "no_comma_op")
2214                    && rule.c0().is_some_and(|token| "," == token.src.as_str())
2215            }));
2216            alt.b = 1;
2217            alt.g = "expr,no-comma-op-bail".into();
2218            close.push(alt);
2219        }
2220
2221        if grammar.has_ternary() {
2222            let mut alt = on(&grammar.tin_tern0);
2223            alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2224                0 == counter(rule, "expr")
2225            }));
2226            alt.b = 1;
2227            alt.r = Some("ternary".into());
2228            alt.g = "expr,expr-ternary".into();
2229            close.push(alt);
2230        }
2231
2232        // The infix operator following the first term of an expression.
2233        if grammar.has_infix() {
2234            let mut alt = on(&grammar.tin_infix);
2235            alt.b = 1;
2236            alt.n = counters(&[("expr_prefix", 0), ("expr_suffix", 0)]);
2237            alt.r_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2238                (0 == counter(rule, "expr")).then(|| "expr".to_string())
2239            }));
2240            alt.g = "expr,expr-infix".into();
2241            close.push(alt);
2242        }
2243
2244        // The suffix operator following the first term of an expression.
2245        if grammar.has_suffix() {
2246            let mut alt = on(&grammar.tin_suffix);
2247            alt.b = 1;
2248            alt.n = counters(&[("expr_prefix", 0), ("expr_suffix", 1)]);
2249            alt.r_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2250                (0 == counter(rule, "expr")).then(|| "expr".to_string())
2251            }));
2252            alt.g = "expr,expr-suffix".into();
2253            close.push(alt);
2254        }
2255
2256        // The closing parenthesis of an expression.
2257        if grammar.has_paren() {
2258            let mut alt = on(&grammar.tin_close);
2259            alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2260                0 != counter(rule, "expr_paren")
2261            }));
2262            alt.b = 1;
2263            alt.g = "expr,expr-paren".into();
2264            close.push(alt);
2265        }
2266
2267        // Chain for postfix paren forms. When a val has just produced a
2268        // value (`a[0]`, `f(0)`, or a parenthesised expression like
2269        // `(*p)`) and the next token is another preval-active paren-open,
2270        // push expr, which descends into paren, so the new paren-form
2271        // picks up this val's node as the preval. Pushing rather than
2272        // replacing keeps the current val rule alive, so the chained
2273        // result is still what the parse returns. `paren_preval` is set
2274        // so the paren close finds it on the grandparent and pushes this
2275        // node into the new paren expression. This complements the
2276        // open-time preval alternate above, which only fires on the first
2277        // preval-paren of an expression: chain-time detection is needed
2278        // for later parens, because by then the leading value is a
2279        // produced node rather than a token in the lex buffer.
2280        if grammar.has_paren() {
2281            let mut alt = on(&grammar.tin_open);
2282            alt.b = 1;
2283            let condition = grammar.clone();
2284            alt.c_fn = Some(Arc::new(move |rule: &mut Rule, _context: &mut Context| {
2285                let Some(pdef) = rule
2286                    .c0()
2287                    .and_then(|token| condition.paren_open.get(&token.tin))
2288                    .cloned()
2289                else {
2290                    return false;
2291                };
2292                let node = node_of(rule);
2293                if !pdef.preval.active || node.is_undefined() {
2294                    return false;
2295                }
2296                let Some(allow) = &pdef.preval.allow else {
2297                    return true;
2298                };
2299                allow.contains(&value_string(Some(&node)))
2300            }));
2301            alt.p = Some("expr".into());
2302            alt.u = locals(&[("paren_preval", Value::Bool(true))]);
2303            alt.g = "expr,expr-paren,expr-paren-preval-chain".into();
2304            close.push(alt);
2305        }
2306
2307        if grammar.has_ternary() {
2308            let mut alt = on(&grammar.tin_tern1);
2309            alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2310                0 != counter(rule, "expr_ternary")
2311            }));
2312            alt.b = 1;
2313            alt.g = "expr,expr-ternary".into();
2314            close.push(alt);
2315        }
2316
2317        // Do not create an implicit list inside an expression (comma
2318        // separator).
2319        let mut comma = on(&[TIN_CA]);
2320        comma.c_fn = Some(Arc::new(no_implicit_in_expr));
2321        comma.b = 1;
2322        comma.g = "expr,list,val,imp,comma,top".into();
2323        close.push(comma);
2324
2325        // Do not create an implicit list inside an expression (space
2326        // separator).
2327        let mut space = on(&VAL_TINS);
2328        space.c_fn = Some(Arc::new(no_implicit_in_expr));
2329        space.b = 1;
2330        space.g = "expr,list,val,imp,space,top".into();
2331        close.push(space);
2332
2333        prepend_close_all(spec, close);
2334    });
2335}
2336
2337/// Give this rule its own node cell, keeping the value it was seeded with.
2338///
2339/// A rule pushed by the engine shares its parent's cell, so a later write
2340/// to this rule's node would reach the parent too. The canonical engine
2341/// gives every rule its own `node` field and seeds it by VALUE, and the
2342/// base grammar's own val alternates reach the same state by resetting the
2343/// node outright. The alternates this plugin adds to `val` need the seeded
2344/// value, a partly built expression the operator alternates read back, so
2345/// they detach instead of resetting.
2346fn detach_node(rule: &mut Rule, _context: &mut Context) {
2347    let node = node_of(rule);
2348    set_node(rule, node);
2349}
2350
2351fn no_implicit_in_expr(rule: &mut Rule, _context: &mut Context) -> bool {
2352    (1 == rule.d && (1 <= counter(rule, "expr") || 1 <= counter(rule, "expr_ternary")))
2353        || (1 <= counter(rule, "expr_ternary") && 1 <= counter(rule, "expr_paren"))
2354}
2355
2356// ---------------------------------------------------------------------------
2357// list, map, elem, pair
2358// ---------------------------------------------------------------------------
2359
2360fn clear_expr_counters(rule: &mut Rule) {
2361    let counters = rule.n_mut();
2362    counters.insert("expr".into(), 0);
2363    counters.insert("expr_prefix".into(), 0);
2364    counters.insert("expr_suffix".into(), 0);
2365    counters.insert("expr_paren".into(), 0);
2366    counters.insert("expr_ternary".into(), 0);
2367}
2368
2369fn install_list(parser: &mut Tabnas, grammar: &Arc<Grammar>) {
2370    let grammar = grammar.clone();
2371    parser.define_rule("list", move |spec| {
2372        spec.prepend_bo(|rule: &mut Rule, _context: &mut Context| {
2373            // List elements are new expressions, unless this is an
2374            // implicit list.
2375            let implicit = rule
2376                .prev_rule
2377                .as_ref()
2378                .is_some_and(|prev| matches!(prev.u.get("implist"), Some(Value::Bool(true))));
2379            if !implicit {
2380                clear_expr_counters(rule);
2381            }
2382        });
2383
2384        if grammar.has_paren() {
2385            let mut alt = on(&grammar.tin_close);
2386            // If this is the end of a normal list, consume `]`: it is not
2387            // a close paren.
2388            alt.b_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2389                let square = rule.c0().is_some_and(|token| TIN_CS == token.tin);
2390                if square && 0 == counter(rule, "expr_paren") {
2391                    0
2392                } else {
2393                    1
2394                }
2395            }));
2396            alt.g = "expr".into();
2397            prepend_close_all(spec, vec![alt]);
2398
2399            spec.add_ac(|rule: &mut Rule, _context: &mut Context| {
2400                propagate_to_paren(rule);
2401            });
2402        }
2403    });
2404}
2405
2406fn install_map(parser: &mut Tabnas, grammar: &Arc<Grammar>) {
2407    let grammar = grammar.clone();
2408    parser.define_rule("map", move |spec| {
2409        // Map values are new expressions.
2410        spec.prepend_bo(|rule: &mut Rule, _context: &mut Context| clear_expr_counters(rule));
2411
2412        if grammar.has_paren() {
2413            let mut alt = on(&grammar.tin_close);
2414            // If this is the end of a normal map, consume `}`: it is not
2415            // a close paren.
2416            alt.b_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2417                let brace = rule.c0().is_some_and(|token| TIN_CB == token.tin);
2418                if brace && 0 == counter(rule, "expr_paren") {
2419                    0
2420                } else {
2421                    1
2422                }
2423            }));
2424            alt.g = "expr".into();
2425            prepend_close_all(spec, vec![alt]);
2426        }
2427    });
2428}
2429
2430fn install_elem(parser: &mut Tabnas, grammar: &Arc<Grammar>) {
2431    let grammar = grammar.clone();
2432    parser.define_rule("elem", move |spec| {
2433        if !grammar.has_paren() {
2434            return;
2435        }
2436        let mut close: Vec<AltSpec> = Vec::new();
2437
2438        // Close an implicit list within parens.
2439        let mut alt = on(&grammar.tin_close);
2440        alt.b = 1;
2441        alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2442            0 != counter(rule, "expr_paren")
2443        }));
2444        alt.g = "expr,expr-paren,imp,close,list".into();
2445        close.push(alt);
2446
2447        // The following element is a paren expression.
2448        let mut alt = on(&grammar.tin_open);
2449        alt.b = 1;
2450        alt.r = Some("elem".into());
2451        alt.g = "expr,expr-paren,imp,open,list".into();
2452        close.push(alt);
2453
2454        prepend_close_all(spec, close);
2455
2456        // Propagate the collected list to the enclosing paren. A
2457        // container is copied on write here, so the paren cannot hold the
2458        // same array the `elem` rules are appending to; it takes the
2459        // finished one instead. The Go port does the same, for the same
2460        // reason.
2461        spec.add_ac(|rule: &mut Rule, _context: &mut Context| {
2462            propagate_to_paren(rule);
2463        });
2464    });
2465}
2466
2467/// Write a rule's node onto the paren rule that encloses it.
2468fn propagate_to_paren(rule: &mut Rule) {
2469    if 0 == counter(rule, "expr_paren") {
2470        return;
2471    }
2472    let node = node_of(rule);
2473    let mut cursor = rule.parent_rule.clone();
2474    while let Some(current) = cursor {
2475        if "paren" == current.name.as_ref() {
2476            *current.node.borrow_mut() = node;
2477            return;
2478        }
2479        cursor = current.parent_rule.clone();
2480    }
2481}
2482
2483fn install_pair(parser: &mut Tabnas, grammar: &Arc<Grammar>) {
2484    let grammar = grammar.clone();
2485    parser.define_rule("pair", move |spec| {
2486        if !grammar.has_paren() {
2487            return;
2488        }
2489        // Close an implicit map within parens.
2490        let mut alt = on(&grammar.tin_close);
2491        alt.b = 1;
2492        alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2493            0 != counter(rule, "expr_paren") || 0 < counter(rule, "pk")
2494        }));
2495        alt.g = "expr,expr-paren,imp,map".into();
2496        prepend_close_all(spec, vec![alt]);
2497    });
2498}
2499
2500// ---------------------------------------------------------------------------
2501// expr
2502// ---------------------------------------------------------------------------
2503
2504/// The completed expression a top-level implicit list should start with.
2505///
2506/// The rule that meets the separator is the last one the Pratt core built,
2507/// which for a right-leaning tree is a SUB-expression: `1+2*3` leaves it
2508/// holding `[*,2,3]` while the finished `[+,1,[*,2,3]]` sits on the val
2509/// this expression publishes to, and on the outermost expr in the
2510/// replacement chain. Seeding the list from the rule's own node therefore
2511/// dropped the `1+`, a wrong answer with nothing to signal it.
2512fn expr_root(rule: &Rule) -> Value {
2513    let parent = linked_node(rule.parent_rule.as_ref());
2514    if rule.parent_rule.is_some() && is_op(&parent) {
2515        return parent;
2516    }
2517
2518    // No usable parent. The outermost expr in the chain holds the root;
2519    // later entries are the sub-expressions built under it.
2520    let mut root: Option<Value> = None;
2521    if "expr" == rule.name.as_ref() {
2522        let node = node_of(rule);
2523        if is_op(&node) {
2524            root = Some(node);
2525        }
2526    }
2527    let mut cursor = rule.prev_rule.clone();
2528    while let Some(current) = cursor {
2529        if "expr" == current.name.as_ref() {
2530            let node = current.node.borrow().clone();
2531            if is_op(&node) {
2532                root = Some(node);
2533            }
2534        }
2535        cursor = current.prev_rule.clone();
2536    }
2537
2538    root.unwrap_or_else(|| node_of(rule))
2539}
2540
2541/// Start a top-level implicit list, publishing it to the rule's parent
2542/// unless there is none.
2543///
2544/// A rule seeds its node from its parent, so a list written to a missing
2545/// parent would be handed straight back out as the starting node of the
2546/// rules that follow. The rule's own node is what the parse returns at
2547/// this depth, so skipping the write costs nothing.
2548fn top_list(rule: &mut Rule) -> Value {
2549    let list = Value::array(vec![expr_root(rule)]);
2550
2551    if rule.parent_rule.is_some() {
2552        write_linked_node(rule.parent_rule.as_ref(), list.clone());
2553        return list;
2554    }
2555
2556    // No parent to publish to. The list is still built correctly by the
2557    // `elem` rules that follow, but nothing would ever read it: the
2558    // result would come back as the first member alone. Write it back
2559    // along the replacement chain instead, so whichever rule the parse
2560    // returns is holding this list.
2561    set_node(rule, list.clone());
2562    let mut cursor = rule.prev_rule.clone();
2563    while let Some(current) = cursor {
2564        *current.node.borrow_mut() = list.clone();
2565        cursor = current.prev_rule.clone();
2566    }
2567
2568    list
2569}
2570
2571/// Collect an expression into the implicit list its enclosing paren is
2572/// building, the port of the canonical `implicitList`.
2573fn implicit_list(mut matched: AltMatch, rule: &mut Rule, context: &mut Context) -> AltMatch {
2574    // Find the paren rule that contains this implicit list. A map or list
2575    // rule between the expression and the paren means the expression is
2576    // inside a contained value, not a direct paren child, so no implicit
2577    // list is created.
2578    let mut paren: Option<Rc<RuleSnapshot>> = None;
2579    let mut paren_index = 0usize;
2580    for index in (0..context.rule_stack.len()).rev() {
2581        let frame = &context.rule_stack[index];
2582        let name = frame.name.as_ref();
2583        if "paren" == name {
2584            paren = Some(frame.clone());
2585            paren_index = index;
2586            break;
2587        }
2588        if "map" == name || "list" == name {
2589            return matched;
2590        }
2591    }
2592
2593    let Some(paren) = paren else {
2594        return matched;
2595    };
2596
2597    // The node this expression contributes, captured before the write
2598    // below replaces it with the list. The ROOT, not the attachment
2599    // point: the canonical port takes it off the rule the paren pushed,
2600    // which is where `prior_expr` writes the whole expression, so a
2601    // right-leaning member such as `f(1+2*3, 4)` keeps its `1+`.
2602    let expr_node = node_of(rule);
2603    let expr_node = if is_op(&expr_node) {
2604        expression_root(rule)
2605    } else {
2606        expr_node
2607    };
2608
2609    // Create the list value for the paren rule, unless one is already
2610    // being collected, in which case the `elem` rules do the collecting.
2611    //
2612    // The canonical port reads and writes this through the paren's own
2613    // child rule, because a JavaScript array is shared by reference: the
2614    // rule collecting the list and the paren then hold the same array.
2615    // This engine copies a container on write, so the list is kept on the
2616    // paren itself and the `elem` after-close hook puts the finished one
2617    // back (as the Go port does, for the same reason).
2618    let paren_node = paren.node.borrow().clone();
2619    if matches!(&paren_node, Value::Array(members) if !members.is_empty()) {
2620        // An `elem` rule is already collecting the list, and this
2621        // expression is one of its members: leave the node alone so that
2622        // the member, and not the list, is what the collector reads.
2623        return matched;
2624    }
2625
2626    *paren.node.borrow_mut() = Value::array(vec![expr_node.clone()]);
2627    matched.r = Some("elem".into());
2628    matched.b = 0;
2629
2630    let list = paren.node.borrow().clone();
2631    set_node(rule, list.clone());
2632
2633    // The rule the first term belongs to holds the list too. The
2634    // canonical port gets that for nothing, because the rule it writes
2635    // the list through IS that rule; here the list is kept on the paren,
2636    // so this write is what the expression's own after-close reads when
2637    // it reduces the members with an evaluator.
2638    write_linked_node(rule.parent_rule.as_ref(), list.clone());
2639
2640    // The rule that sees the comma is not always the paren's own child. A
2641    // first argument that nests operators, a prefix whose operand is
2642    // itself a prefix, `f(- -1, 2)`, leaves further rules on the stack
2643    // between the paren and this rule, all holding the same expression
2644    // node. Each writes that node back over the paren's when it closes,
2645    // discarding every element collected after the first. Point them at
2646    // the implicit list so the close-back is a no-op instead of a
2647    // truncation.
2648    //
2649    // Only the frames that carry this expression: the `expr` rules
2650    // themselves, and the `val` rules they were pushed from, which hold
2651    // either nothing yet or the expression node. A rule a host plugin
2652    // stacked in between has a node of its own to close with, and
2653    // overwriting it would destroy a value this plugin knows nothing
2654    // about.
2655    for index in (paren_index + 1)..context.rule_stack.len() {
2656        let frame = context.rule_stack[index].clone();
2657        let name = frame.name.as_ref().to_string();
2658        let node = frame.node.borrow().clone();
2659        let carries = "expr" == name
2660            || ("val" == name
2661                && (node.is_undefined()
2662                    || matches!(node, Value::Null)
2663                    || same_node(&node, &expr_node)
2664                    || is_op(&node)));
2665        if carries {
2666            *frame.node.borrow_mut() = list.clone();
2667        }
2668    }
2669
2670    matched
2671}
2672
2673/// Node identity, the canonical `===` on two expression nodes.
2674fn same_node(left: &Value, right: &Value) -> bool {
2675    match (node_id(left), node_id(right)) {
2676        (Some(left), Some(right)) => left == right,
2677        _ => false,
2678    }
2679}
2680
2681fn install_expr(parser: &mut Tabnas, grammar: &Arc<Grammar>) {
2682    let grammar = grammar.clone();
2683    parser.define_rule("expr", move |spec| {
2684        let mut open: Vec<AltSpec> = Vec::new();
2685
2686        // An opening parenthesis of an expression.
2687        if grammar.has_paren() {
2688            let mut alt = on(&grammar.tin_open);
2689            alt.p = Some("paren".into());
2690            alt.b = 1;
2691            alt.g = "expr,expr-paren,expr-start".into();
2692            open.push(alt);
2693        }
2694
2695        if grammar.has_prefix() {
2696            let mut alt = on(&grammar.tin_prefix);
2697            alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2698                0 != counter(rule, "expr_prefix")
2699            }));
2700            alt.n = counters(&[("expr", 1), ("dlist", 1), ("dmap", 1)]);
2701            alt.p = Some("val".into());
2702            alt.g = "expr,expr-prefix".into();
2703            let ops = grammar.clone();
2704            alt.add_action(move |rule: &mut Rule, _context: &mut Context| {
2705                let Some(op) = Grammar::op_use(&ops.prefix, rule.o0()) else {
2706                    return;
2707                };
2708                let parent = linked_node(rule.parent_rule.as_ref());
2709                // Only fold into the parent's node when it is an
2710                // expression THIS prefix chain is building (the outer `-`
2711                // of `--1`). A ternary or paren node on the parent is a
2712                // foreign seed the engine threaded down, and folding into
2713                // it would corrupt the wrapper with a stray hole and a
2714                // duplicated operand. Start a fresh operand expression
2715                // for those, mirroring the infix guard.
2716                if is_op(&parent) && !is_ternary_op(&parent) && !is_paren_op(&parent) {
2717                    let attach = prattify(&parent, &op);
2718                    publish_expr(rule, attach);
2719                } else {
2720                    let prior = rule.parent_rule.clone();
2721                    let node = prior_expr(rule, prior.as_ref(), &op);
2722                    publish_expr(rule, node);
2723                }
2724            });
2725            open.push(alt);
2726        }
2727
2728        if grammar.has_infix() {
2729            let mut alt = on(&grammar.tin_infix);
2730            alt.p = Some("val".into());
2731            alt.n = counters(&[("expr", 1), ("expr_prefix", 0), ("dlist", 1), ("dmap", 1)]);
2732            alt.g = "expr,expr-infix".into();
2733            let ops = grammar.clone();
2734            alt.add_action(move |rule: &mut Rule, _context: &mut Context| {
2735                let Some(op) = Grammar::op_use(&ops.infix, rule.o0()) else {
2736                    return;
2737                };
2738                let parent = linked_node(rule.parent_rule.as_ref());
2739                let previous = linked_node(rule.prev_rule.as_ref());
2740
2741                // Second and further operators.
2742                if is_op(&parent) && !is_ternary_op(&parent) {
2743                    let attach = prattify(&parent, &op);
2744                    publish_expr(rule, attach);
2745                }
2746                // The first term was a unary expression.
2747                else if is_op(&previous) {
2748                    let attach = prattify(&previous, &op);
2749                    if let Some(prev) = rule.prev_rule.clone() {
2750                        rule.parent_node = Some(prev.node.clone());
2751                        rule.parent_rule = Some(prev);
2752                    }
2753                    publish_expr(rule, attach);
2754                }
2755                // The first term was a plain value or a ternary part.
2756                else {
2757                    let prior = rule.prev_rule.clone();
2758                    let node = prior_expr(rule, prior.as_ref(), &op);
2759                    publish_expr(rule, node);
2760                }
2761            });
2762            open.push(alt);
2763        }
2764
2765        if grammar.has_suffix() {
2766            let mut alt = on(&grammar.tin_suffix);
2767            alt.n = counters(&[("expr", 1), ("expr_prefix", 0), ("dlist", 1), ("dmap", 1)]);
2768            alt.g = "expr,expr-suffix".into();
2769            let ops = grammar.clone();
2770            alt.add_action(move |rule: &mut Rule, _context: &mut Context| {
2771                let Some(op) = Grammar::op_use(&ops.suffix, rule.o0()) else {
2772                    return;
2773                };
2774                let previous = linked_node(rule.prev_rule.as_ref());
2775                if is_op(&previous) {
2776                    let attach = prattify(&previous, &op);
2777                    publish_expr(rule, attach);
2778                } else {
2779                    let prior = rule.prev_rule.clone();
2780                    let node = prior_expr(rule, prior.as_ref(), &op);
2781                    publish_expr(rule, node);
2782                }
2783            });
2784            open.push(alt);
2785        }
2786
2787        prepend_open_all(spec, open);
2788
2789        // Append the final term to the expression.
2790        spec.add_bc(|rule: &mut Rule, _context: &mut Context| {
2791            let node = node_of(rule);
2792            let Some(op) = op_of(&node) else {
2793                return;
2794            };
2795            let child = rule.child_node.clone();
2796            if term_count(&node) < op.terms && !same_node(&node, &child) {
2797                push_term(&node, child);
2798            }
2799        });
2800
2801        let mut close: Vec<AltSpec> = Vec::new();
2802
2803        let mut alt = always();
2804        alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2805            rule.child_rule
2806                .as_ref()
2807                .is_some_and(|child| "paren" == child.name.as_ref())
2808        }));
2809        alt.n = counters(&[("expr", 0)]);
2810        alt.g = "expr,expr-end,expr-paren-end".into();
2811        close.push(alt);
2812
2813        // Comma-op suppression. When n.no_comma_op is set by a parent
2814        // rule, terminate the expression at `,` without treating it as
2815        // the comma operator. Closing the expression frame lets the
2816        // parent rule regain control before any comma-op alternate below
2817        // fires.
2818        if grammar.has_infix() {
2819            let mut alt = on(&grammar.tin_infix);
2820            alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2821                0 < counter(rule, "no_comma_op")
2822                    && rule.c0().is_some_and(|token| "," == token.src.as_str())
2823            }));
2824            alt.b = 1;
2825            alt.n = counters(&[("expr", 0)]);
2826            alt.g = "expr,no-comma-op-bail".into();
2827            close.push(alt);
2828        }
2829
2830        if grammar.has_infix() {
2831            // Complete the prefix first.
2832            let mut alt = on(&grammar.tin_infix);
2833            alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2834                0 == counter(rule, "expr_prefix")
2835            }));
2836            alt.b = 1;
2837            alt.r = Some("expr".into());
2838            alt.g = "expr,expr-infix,expr-prefix".into();
2839            close.push(alt);
2840
2841            let mut alt = on(&grammar.tin_infix);
2842            alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2843                0 != counter(rule, "expr_prefix")
2844            }));
2845            alt.b = 1;
2846            alt.g = "expr,expr-infix".into();
2847            close.push(alt);
2848        }
2849
2850        if grammar.has_suffix() {
2851            let mut alt = on(&grammar.tin_suffix);
2852            alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2853                0 == counter(rule, "expr_prefix")
2854            }));
2855            alt.b = 1;
2856            alt.r = Some("expr".into());
2857            alt.g = "expr,expr-suffix,expr-prefix".into();
2858            close.push(alt);
2859        }
2860
2861        if grammar.has_paren() {
2862            let mut alt = on(&grammar.tin_close);
2863            alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2864                0 != counter(rule, "expr_paren")
2865            }));
2866            alt.b = 1;
2867            alt.g = "expr".into();
2868            close.push(alt);
2869        }
2870
2871        if grammar.has_ternary() {
2872            let mut alt = on(&grammar.tin_tern0);
2873            alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2874                0 == counter(rule, "expr_prefix")
2875            }));
2876            alt.b = 1;
2877            alt.r = Some("ternary".into());
2878            alt.g = "expr,expr-ternary".into();
2879            close.push(alt);
2880        }
2881
2882        // Implicit list at the top level, comma separated.
2883        let mut alt = on(&[TIN_CA]);
2884        alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2885            0 == rule.d
2886        }));
2887        alt.n = counters(&[("expr", 0)]);
2888        alt.r = Some("elem".into());
2889        alt.add_action(|rule: &mut Rule, _context: &mut Context| {
2890            let list = top_list(rule);
2891            set_node(rule, list);
2892        });
2893        alt.g = "expr,comma,list,top".into();
2894        close.push(alt);
2895
2896        // Implicit list at the top level, space separated.
2897        let mut alt = on(&VAL_TINS);
2898        alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2899            0 == rule.d
2900        }));
2901        alt.n = counters(&[("expr", 0)]);
2902        alt.b = 1;
2903        alt.r = Some("elem".into());
2904        alt.add_action(|rule: &mut Rule, _context: &mut Context| {
2905            let list = top_list(rule);
2906            set_node(rule, list);
2907        });
2908        alt.g = "expr,space,list,top".into();
2909        close.push(alt);
2910
2911        // Implicit list indicated by a comma.
2912        let mut alt = on(&[TIN_CA]);
2913        alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
2914            rule.lte("pk", 0)
2915        }));
2916        alt.n = counters(&[("expr", 0)]);
2917        alt.b = 1;
2918        alt.h_match = Some(Arc::new(|matched, rule, context, _next| {
2919            implicit_list(matched, rule, context)
2920        }));
2921        alt.g = "expr,list,val,imp,comma".into();
2922        close.push(alt);
2923
2924        // Implicit list indicated by a space separated value.
2925        //
2926        // This alternate carries no token filter, so it also has to be
2927        // kept off a pending suffix operator. A suffix belongs to the
2928        // expression that just closed, not to a new element: `f(@x!)`
2929        // reaches here on the `!` of a completed prefix, opens `elem`,
2930        // and offers `!` as the start of a value, which nothing matches,
2931        // so a valid expression fails as unexpected. Leaving the token
2932        // alone lets the val-close suffix alternate take it, which is how
2933        // the same expression already parses at the top level.
2934        let mut alt = always();
2935        let suffix_tins = grammar.tin_suffix.clone();
2936        alt.c_fn = Some(Arc::new(move |rule: &mut Rule, context: &mut Context| {
2937            rule.lte("pk", 0)
2938                && rule.lte("expr_suffix", 0)
2939                && !context
2940                    .t0()
2941                    .is_some_and(|token| suffix_tins.contains(&token.tin))
2942        }));
2943        alt.n = counters(&[("expr", 0)]);
2944        alt.h_match = Some(Arc::new(|matched, rule, context, _next| {
2945            implicit_list(matched, rule, context)
2946        }));
2947        alt.g = "expr,list,val,imp,space".into();
2948        close.push(alt);
2949
2950        // The expression ends on a non-expression token.
2951        let mut alt = always();
2952        alt.n = counters(&[("expr", 0)]);
2953        alt.g = "expr,expr-end".into();
2954        close.push(alt);
2955
2956        prepend_close_all(spec, close);
2957
2958        // Put the ROOT of the expression back on the node, so the last
2959        // rule of a replacement chain hands the whole expression to the
2960        // rule that collects it, rather than the sub-expression the last
2961        // operator attached to. Only while the node still IS the
2962        // attachment point: a close alternate that produced an implicit
2963        // list has replaced it deliberately.
2964        spec.add_ac(|rule: &mut Rule, _context: &mut Context| {
2965            if is_op(&node_of(rule)) {
2966                let root = expression_root(rule);
2967                set_node(rule, root);
2968            }
2969        });
2970
2971        // Evaluate at the root of the expression, where n.expr is zero.
2972        if let Some(evaluate) = grammar.evaluate.clone() {
2973            spec.add_ac(move |rule: &mut Rule, context: &mut Context| {
2974                if 0 != counter(rule, "expr") {
2975                    return;
2976                }
2977                let Some(parent) = rule.parent_rule.clone() else {
2978                    return;
2979                };
2980                let parent_node = parent.node.borrow().clone();
2981
2982                // The parent holds an implicit list (`f(1+2, 3)`,
2983                // `1+2, 3`) rather than this expression's own node. A
2984                // list is a plain array, not an expression node, so its
2985                // members would otherwise reach the caller as raw
2986                // S-expressions the evaluator was never called on.
2987                //
2988                // Reduce them in place, keeping the array: the list is
2989                // still being collected, and the paren rule and the expr
2990                // rules between it and here all hold a reference to this
2991                // exact array, so replacing it would strand the members
2992                // still to come. A member already reduced is returned
2993                // untouched rather than reduced again.
2994                if let Value::Array(members) = &parent_node {
2995                    let mut site = EvalSite::new(Some(parent.clone()), context);
2996                    let reduced: Vec<Value> = members
2997                        .iter()
2998                        .map(|member| evaluation(&mut site.reborrow(), member, &evaluate))
2999                        .collect();
3000                    let reduced = Value::array(reduced);
3001                    *parent.node.borrow_mut() = reduced.clone();
3002                    // Through the shared cell, not into a fresh one: the
3003                    // rule that collects the rest of the list was already
3004                    // seeded from this cell, and the canonical port keeps
3005                    // the one array for the same reason.
3006                    publish(rule, reduced);
3007                    return;
3008                }
3009
3010                // The parent node holds the root of the expression tree.
3011                let out = {
3012                    let mut site = EvalSite::new(Some(parent.clone()), context);
3013                    evaluation(&mut site, &parent_node, &evaluate)
3014                };
3015                *parent.node.borrow_mut() = out.clone();
3016
3017                // Also write the evaluated result onto this rule's own
3018                // node. When the expr rule was PUSHED from a still-open
3019                // val (the prefix and paren forms, `+1`, `(a)`), that
3020                // val's close coalescing reads its child's node, which is
3021                // this rule, and would otherwise restore the raw
3022                // S-expression and discard the evaluated value.
3023                publish(rule, out);
3024            });
3025        }
3026    });
3027}
3028
3029// ---------------------------------------------------------------------------
3030// paren
3031// ---------------------------------------------------------------------------
3032
3033fn paren_depth(name: &str) -> String {
3034    format!("expr_paren_depth_{name}")
3035}
3036
3037fn install_paren(parser: &mut Tabnas, grammar: &Arc<Grammar>) {
3038    let grammar = grammar.clone();
3039    parser.define_rule("paren", move |spec| {
3040        // Allow implicits inside parens.
3041        spec.add_bo(|rule: &mut Rule, _context: &mut Context| {
3042            let counters = rule.n_mut();
3043            counters.insert("dmap".into(), 0);
3044            counters.insert("dlist".into(), 0);
3045            counters.insert("pk".into(), 0);
3046        });
3047
3048        let mut open: Vec<AltSpec> = Vec::new();
3049
3050        // Empty parens: `()`.
3051        let mut alt = on2(&grammar.tin_open, &grammar.tin_close);
3052        alt.b = 1;
3053        alt.g = "expr,expr-paren,empty".into();
3054        let condition = grammar.clone();
3055        alt.c_fn = Some(Arc::new(move |rule: &mut Rule, _context: &mut Context| {
3056            let open = rule
3057                .o0()
3058                .and_then(|token| condition.paren_open.get(&token.tin));
3059            let close = rule
3060                .o1()
3061                .and_then(|token| condition.paren_close.get(&token.tin));
3062            match (open, close) {
3063                (Some(open), Some(close)) => open.name == close.name,
3064                _ => false,
3065            }
3066        }));
3067        let ops = grammar.clone();
3068        alt.add_action(move |rule: &mut Rule, _context: &mut Context| {
3069            open_paren(&ops, rule);
3070        });
3071        open.push(alt);
3072
3073        // A normal paren open: consume the opener and push to val.
3074        let mut alt = on(&grammar.tin_open);
3075        alt.p = Some("val".into());
3076        alt.n = counters(&[
3077            ("expr_paren", 1),
3078            ("expr", 0),
3079            ("expr_prefix", 0),
3080            ("expr_suffix", 0),
3081        ]);
3082        alt.g = "expr,expr-paren,open".into();
3083        let ops = grammar.clone();
3084        alt.add_action(move |rule: &mut Rule, _context: &mut Context| {
3085            open_paren(&ops, rule);
3086        });
3087        open.push(alt);
3088
3089        prepend_open_all(spec, open);
3090
3091        let mut alt = on(&grammar.tin_close);
3092        let condition = grammar.clone();
3093        alt.c_fn = Some(Arc::new(move |rule: &mut Rule, _context: &mut Context| {
3094            let Some(pdef) = rule
3095                .c0()
3096                .and_then(|token| condition.paren_close.get(&token.tin))
3097                .cloned()
3098            else {
3099                return false;
3100            };
3101            0 != counter(rule, &paren_depth(&pdef.name))
3102        }));
3103        let ops = grammar.clone();
3104        alt.add_action(move |rule: &mut Rule, _context: &mut Context| {
3105            close_paren(&ops, rule);
3106        });
3107        alt.g = "expr,expr-paren,close".into();
3108        prepend_close_all(spec, vec![alt]);
3109
3110        spec.add_ac(|rule: &mut Rule, _context: &mut Context| {
3111            let node = node_of(rule);
3112            let parent = rule.parent_rule.clone();
3113            write_linked_node(parent.as_ref(), node.clone());
3114            let grandparent = parent.and_then(|parent| parent.parent_rule.clone());
3115            write_linked_node(grandparent.as_ref(), node);
3116        });
3117    });
3118}
3119
3120fn open_paren(grammar: &Grammar, rule: &mut Rule) {
3121    let Some(op) = Grammar::op_use(&grammar.paren_open, rule.o0()) else {
3122        return;
3123    };
3124    let depth = paren_depth(&op.op.name);
3125    rule.u_mut().insert(depth.clone(), Value::Number(1.0));
3126    rule.n_mut().insert(depth, 1);
3127    set_node(rule, Value::Undefined);
3128}
3129
3130fn close_paren(grammar: &Grammar, rule: &mut Rule) {
3131    let child = rule.child_node.clone();
3132    let mut node = node_of(rule);
3133    // The child's value, when this paren holds nothing of its own yet.
3134    //
3135    // The canonical port also takes the child whenever it is an operator,
3136    // and can, because it collects an implicit list IN the child's node
3137    // (`paren.child.node = [...]`): there the child IS the list. This
3138    // engine copies a container on write, so the list is collected on the
3139    // paren itself instead, while the child link stays on the rule the
3140    // paren PUSHED rather than on whichever rule of a replacement chain
3141    // popped last (tabnas/parser a801621, matching TS and Go). That rule
3142    // can still hold the list's first member, an operator. A node already
3143    // on the paren is the collected list, and the child must not overwrite
3144    // it: `(1?2:3 b)` would come back as `(1?2:3)`.
3145    if node.is_undefined() {
3146        node = child;
3147    }
3148
3149    let Some(op) = Grammar::op_use(&grammar.paren_close, rule.c0()) else {
3150        set_node(rule, node);
3151        return;
3152    };
3153    let depth = paren_depth(&op.op.name);
3154
3155    // Construct the completed paren expression.
3156    if local_number(rule, &depth) == i64::from(counter(rule, &depth)) {
3157        let mut terms: Vec<Value> = Vec::new();
3158
3159        let grandparent = rule
3160            .parent_rule
3161            .as_ref()
3162            .and_then(|parent| parent.parent_rule.clone());
3163        if let Some(grandparent) = &grandparent {
3164            let preval = grandparent.node.borrow().clone();
3165            if matches!(grandparent.u.get("paren_preval"), Some(Value::Bool(true)))
3166                && !preval.is_undefined()
3167            {
3168                terms.push(preval);
3169            }
3170        }
3171
3172        if !node.is_undefined() {
3173            terms.push(node);
3174        }
3175
3176        set_node(rule, op.node(terms));
3177    } else {
3178        set_node(rule, node);
3179    }
3180}
3181
3182// ---------------------------------------------------------------------------
3183// ternary
3184// ---------------------------------------------------------------------------
3185
3186fn install_ternary(parser: &mut Tabnas, grammar: &Arc<Grammar>) {
3187    let grammar = grammar.clone();
3188    parser.define_rule("ternary", move |spec| {
3189        let mut open: Vec<AltSpec> = Vec::new();
3190
3191        let mut alt = on(&grammar.tin_tern0);
3192        alt.p = Some("val".into());
3193        alt.n = counters(&[
3194            ("expr_ternary", 1),
3195            ("expr", 0),
3196            ("expr_prefix", 0),
3197            ("expr_suffix", 0),
3198        ]);
3199        alt.u = locals(&[("expr_ternary_step", Value::Number(1.0))]);
3200        alt.g = "expr,expr-ternary,open".into();
3201        let ops = grammar.clone();
3202        alt.add_action(move |rule: &mut Rule, _context: &mut Context| {
3203            let Some(op) = Grammar::op_use(&ops.ternary, rule.o0()) else {
3204                return;
3205            };
3206            rule.u_mut().insert(
3207                "expr_ternary_name".into(),
3208                Value::String(op.op.name.clone()),
3209            );
3210
3211            let previous = linked_node(rule.prev_rule.as_ref());
3212            let node = if is_op(&previous) {
3213                let duplicate = dup_node(&previous);
3214                update_expr_node(
3215                    &previous,
3216                    Some(op.op.clone()),
3217                    op.token.clone(),
3218                    vec![duplicate],
3219                );
3220                previous
3221            } else {
3222                let node = op.node(vec![previous]);
3223                write_linked_node(rule.prev_rule.as_ref(), node.clone());
3224                node
3225            };
3226            set_node(rule, node);
3227
3228            let paren = if 0 != counter(rule, "expr_paren") {
3229                i64::from(counter(rule, "expr_paren"))
3230            } else {
3231                rule.prev_rule
3232                    .as_ref()
3233                    .map_or(0, |prev| snapshot_number(prev, "expr_ternary_paren"))
3234            };
3235            rule.u_mut()
3236                .insert("expr_ternary_paren".into(), Value::Number(paren as f64));
3237            rule.n_mut().insert("expr_paren".into(), 0);
3238        });
3239        open.push(alt);
3240
3241        let mut alt = always();
3242        alt.p = Some("val".into());
3243        alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
3244            rule.prev_rule
3245                .as_ref()
3246                .is_some_and(|prev| 2 == snapshot_number(prev, "expr_ternary_step"))
3247        }));
3248        alt.add_action(|rule: &mut Rule, _context: &mut Context| {
3249            let (step, paren) = rule.prev_rule.as_ref().map_or((0, 0), |prev| {
3250                (
3251                    snapshot_number(prev, "expr_ternary_step"),
3252                    snapshot_number(prev, "expr_ternary_paren"),
3253                )
3254            });
3255            set_local_number(rule, "expr_ternary_step", step);
3256            set_local_number(rule, "expr_ternary_paren", paren);
3257            rule.n_mut().insert("expr_paren".into(), paren as i32);
3258        });
3259        alt.g = "expr,expr-ternary,step".into();
3260        open.push(alt);
3261
3262        prepend_open_all(spec, open);
3263
3264        let mut close: Vec<AltSpec> = Vec::new();
3265
3266        let mut alt = on(&grammar.tin_tern1);
3267        let condition = grammar.clone();
3268        alt.c_fn = Some(Arc::new(move |rule: &mut Rule, _context: &mut Context| {
3269            if 1 != local_number(rule, "expr_ternary_step") {
3270                return false;
3271            }
3272            let Some(op) = rule
3273                .c0()
3274                .and_then(|token| condition.ternary.get(&token.tin))
3275                .cloned()
3276            else {
3277                return false;
3278            };
3279            matches!(rule.u.get("expr_ternary_name"), Some(Value::String(name)) if name == &op.name)
3280        }));
3281        alt.r = Some("ternary".into());
3282        alt.add_action(|rule: &mut Rule, _context: &mut Context| {
3283            let step = local_number(rule, "expr_ternary_step");
3284            set_local_number(rule, "expr_ternary_step", step + 1);
3285            let node = node_of(rule);
3286            push_term(&node, rule.child_node.clone());
3287        });
3288        alt.g = "expr,expr-ternary,step".into();
3289        close.push(alt);
3290
3291        // End of ternary at the top level, implicit list indicated by a
3292        // comma. Handle a ternary as the first item of an implicit list
3293        // inside parens.
3294        let mut tins = vec![TIN_CA];
3295        tins.extend(grammar.tin_close.iter().copied());
3296        let mut alt = on(&tins);
3297        alt.c_fn = Some(Arc::new(implicit_ternary_cond));
3298        let closers = grammar.tin_close.clone();
3299        alt.b_fn = Some(Arc::new(move |_rule: &mut Rule, context: &mut Context| {
3300            usize::from(next_is_close(context, &closers))
3301        }));
3302        let closers = grammar.tin_close.clone();
3303        alt.r_fn = Some(Arc::new(move |rule: &mut Rule, context: &mut Context| {
3304            let to_elem = !next_is_close(context, &closers)
3305                && (0 == rule.d
3306                    || (rule
3307                        .prev_rule
3308                        .as_ref()
3309                        .is_some_and(|prev| 0 != snapshot_number(prev, "expr_ternary_paren"))
3310                        && 0 == node_length(&linked_node(rule.parent_rule.as_ref()))));
3311            to_elem.then(|| "elem".to_string())
3312        }));
3313        alt.add_action_with_match(|rule: &mut Rule, _context: &mut Context, matched| {
3314            implicit_ternary_action(rule, matched);
3315            None
3316        });
3317        alt.g = "expr,expr-ternary,list,val,imp,comma".into();
3318        close.push(alt);
3319
3320        // End of ternary at the top level, implicit list indicated by a
3321        // space separated value.
3322        let mut alt = always();
3323        alt.c_fn = Some(Arc::new(implicit_ternary_cond));
3324        let closers = grammar.tin_close.clone();
3325        alt.r_fn = Some(Arc::new(move |rule: &mut Rule, context: &mut Context| {
3326            let ended = context.t0().is_some_and(|token| TIN_ZZ == token.tin);
3327            let to_elem = (0 == rule.d
3328                || !next_is_close(context, &closers)
3329                || rule
3330                    .prev_rule
3331                    .as_ref()
3332                    .is_some_and(|prev| 0 != snapshot_number(prev, "expr_ternary_paren")))
3333                && 0 == node_length(&linked_node(rule.parent_rule.as_ref()))
3334                && !ended;
3335            to_elem.then(|| "elem".to_string())
3336        }));
3337        alt.add_action_with_match(|rule: &mut Rule, _context: &mut Context, matched| {
3338            implicit_ternary_action(rule, matched);
3339            None
3340        });
3341        alt.g = "expr,expr-ternary,list,val,imp,space".into();
3342        close.push(alt);
3343
3344        // End of ternary.
3345        let mut alt = always();
3346        alt.c_fn = Some(Arc::new(|rule: &mut Rule, _context: &mut Context| {
3347            0 < rule.d && 2 == local_number(rule, "expr_ternary_step")
3348        }));
3349        alt.add_action(|rule: &mut Rule, _context: &mut Context| {
3350            let node = node_of(rule);
3351            push_term(&node, rule.child_node.clone());
3352        });
3353        alt.g = "expr,expr-ternary,close".into();
3354        close.push(alt);
3355
3356        prepend_close_all(spec, close);
3357
3358        // Ensure ternary results get evaluated. Without this, a ternary
3359        // that is not wrapped in an expr leaves its result as a raw
3360        // S-expression. Fire on every ternary instance's after-close, but
3361        // act only when the chain has reached its final step: the node
3362        // has accumulated all three operands and the next rule is not
3363        // another ternary. Write the evaluated value back along the
3364        // replacement chain so whichever node the parse returns reflects
3365        // the evaluated form.
3366        if let Some(evaluate) = grammar.evaluate.clone() {
3367            spec.add_ac(move |rule: &mut Rule, context: &mut Context| {
3368                if rule
3369                    .next_rule_name
3370                    .as_ref()
3371                    .is_some_and(|name| "ternary" == name.as_ref())
3372                {
3373                    return;
3374                }
3375                let node = node_of(rule);
3376                if !is_op(&node) {
3377                    return;
3378                }
3379                // The node is not fully populated until all three
3380                // operands sit under it; earlier steps carry fewer.
3381                if node_length(&node) < 4 {
3382                    return;
3383                }
3384                let out = {
3385                    // The canonical port hands the ternary rule itself to
3386                    // the evaluator here, where the expr rule hands its
3387                    // parent.
3388                    let site_rule = rule.snapshot();
3389                    let mut site = EvalSite::new(Some(site_rule), context);
3390                    evaluation(&mut site, &node, &evaluate)
3391                };
3392                publish(rule, out.clone());
3393                let mut cursor = rule.prev_rule.clone();
3394                while let Some(current) = cursor {
3395                    *current.node.borrow_mut() = out.clone();
3396                    cursor = current.prev_rule.clone();
3397                }
3398                write_linked_node(rule.parent_rule.as_ref(), out);
3399            });
3400        }
3401    });
3402}
3403
3404fn snapshot_number(snapshot: &RuleSnapshot, name: &str) -> i64 {
3405    match snapshot.u.get(name) {
3406        Some(Value::Number(number)) => *number as i64,
3407        _ => 0,
3408    }
3409}
3410
3411fn next_is_close(context: &mut Context, closers: &[Tin]) -> bool {
3412    context
3413        .t0()
3414        .is_some_and(|token| closers.contains(&token.tin))
3415}
3416
3417fn implicit_ternary_cond(rule: &mut Rule, _context: &mut Context) -> bool {
3418    (0 == rule.d || 1 <= counter(rule, "expr_paren"))
3419        && 0 == counter(rule, "pk")
3420        && 2 == local_number(rule, "expr_ternary_step")
3421}
3422
3423fn implicit_ternary_action(rule: &mut Rule, matched: &AltMatch) {
3424    let paren = rule
3425        .prev_rule
3426        .as_ref()
3427        .map_or(0, |prev| snapshot_number(prev, "expr_ternary_paren"));
3428    rule.n_mut().insert("expr_paren".into(), paren as i32);
3429
3430    let node = node_of(rule);
3431    push_term(&node, rule.child_node.clone());
3432
3433    if matched.r.as_deref() != Some("elem") {
3434        return;
3435    }
3436
3437    // The completed ternary becomes the first element of an implicit
3438    // list. The canonical port rewrites the node into that list in place,
3439    // so that every holder sees it; here the list is written through the
3440    // shared node cell instead, which is what the replacing `elem` rule
3441    // is seeded from, and onto the enclosing paren, because a container
3442    // is copied on write and the two cannot share one array.
3443    let list = Value::array(vec![node]);
3444    publish(rule, list.clone());
3445    if 1 <= counter(rule, "expr_paren") {
3446        let mut cursor = rule.parent_rule.clone();
3447        while let Some(current) = cursor {
3448            if "paren" == current.name.as_ref() {
3449                *current.node.borrow_mut() = list;
3450                break;
3451            }
3452            cursor = current.parent_rule.clone();
3453        }
3454    }
3455}
3456
3457// ---------------------------------------------------------------------------
3458// Public entry points
3459// ---------------------------------------------------------------------------
3460
3461/// The plugin, for [`Tabnas::use_plugin`], carrying the default operator
3462/// table as its declared defaults.
3463///
3464/// Call-site options are deep-merged over those defaults by the engine, so
3465/// an entry naming a default operator extends it and a `null` entry
3466/// deletes it, exactly as in the canonical TypeScript. An evaluator cannot
3467/// travel in a serialized option bag: pass one with [`plugin_with`].
3468///
3469/// ```
3470/// use tabnas::Value;
3471///
3472/// fn main() -> Result<(), Box<dyn std::error::Error>> {
3473///     let mut parser = tabnas_jsonic::make();
3474///     parser.use_plugin(tabnas_expr::plugin(), None)?;
3475///     let value = tabnas_expr::realize(&parser.parse("1+2")?);
3476///     assert_eq!(tabnas_expr::simplify(&value).to_string(), "[\"+\",1,2]");
3477///     Ok::<(), Box<dyn std::error::Error>>(())
3478/// }
3479/// ```
3480pub fn plugin() -> Plugin {
3481    Plugin::new(PLUGIN_NAME, |parser: &mut Tabnas, options: &Value| {
3482        expr(parser, &ExprOptions::from_value(options))
3483    })
3484    .with_defaults(defaults())
3485}
3486
3487/// The plugin with typed options, which is how an evaluator and any
3488/// operator data that no `Value` can carry reach the grammar.
3489///
3490/// The typed options resolve the defaults themselves, so the serialized
3491/// option bag is not consulted: an operator given here replaces the
3492/// default of the same name outright rather than extending it.
3493pub fn plugin_with(options: ExprOptions) -> Plugin {
3494    Plugin::new(PLUGIN_NAME, move |parser: &mut Tabnas, _options: &Value| {
3495        expr(parser, &options)
3496    })
3497}
3498
3499/// Install the expression grammar on a parser through
3500/// [`Tabnas::use_plugin`], so that a derived instance rebuilds it.
3501pub fn apply(parser: &mut Tabnas, options: ExprOptions) -> Result<(), PluginError> {
3502    parser.use_plugin(plugin_with(options), None).map(|_| ())
3503}
3504
3505/// Build a parser with the jsonic base grammar and the default operator
3506/// table.
3507///
3508/// ```
3509/// fn main() -> Result<(), Box<dyn std::error::Error>> {
3510///     let parser = tabnas_expr::make();
3511///     let value = tabnas_expr::parse_with(&parser, "(1+2)*3")?;
3512///     assert_eq!(tabnas_expr::simplify(&value).to_string(),
3513///                r#"["*",["(",["+",1,2]],3]"#);
3514///     Ok(())
3515/// }
3516/// ```
3517pub fn make() -> Tabnas {
3518    make_with(ExprOptions::new())
3519}
3520
3521/// Build a parser with the jsonic base grammar and the given options.
3522pub fn make_with(options: ExprOptions) -> Tabnas {
3523    let mut parser = tabnas_jsonic::make();
3524    parser
3525        .use_plugin(plugin_with(options), None)
3526        .expect("the expression grammar installs on the jsonic base");
3527    parser
3528}
3529
3530/// The lazily built instance the no-options [`parse`] reuses.
3531///
3532/// Building the grammar dominates a parse, so a fresh instance per call is
3533/// many times slower (see `tests/perf_test.rs`). Parsing builds a fresh
3534/// context per call and only reads instance state, so one shared instance
3535/// is safe to use from several threads.
3536static DEFAULT_PARSER: OnceLock<Tabnas> = OnceLock::new();
3537
3538/// Parse one document with the default operator table.
3539///
3540/// ```
3541/// fn main() -> Result<(), Box<dyn std::error::Error>> {
3542///     let value = tabnas_expr::parse("a:1+2")?;
3543///     assert_eq!(tabnas_expr::simplify(&value).to_string(),
3544///                r#"{"a":["+",1,2]}"#);
3545///     Ok(())
3546/// }
3547/// ```
3548pub fn parse(src: &str) -> Result<Value, TabnasError> {
3549    parse_with(DEFAULT_PARSER.get_or_init(make), src)
3550}
3551
3552/// Parse one document with a parser this crate configured, and realize the
3553/// expression nodes in the result.
3554///
3555/// A parser driven directly through [`Tabnas::parse`] returns a value whose
3556/// expression nodes are still handles into the per-parse arena; pass that
3557/// value to [`realize`] before the next parse on the same thread.
3558pub fn parse_with(parser: &Tabnas, src: &str) -> Result<Value, TabnasError> {
3559    let _guard = ArenaGuard::enter();
3560    let value = parser.parse(src)?;
3561    Ok(realize(&value))
3562}
3563
3564/// Parse one document and hand the unrealized value to a closure, with
3565/// the expression arena held open for as long as it runs.
3566///
3567/// This is the parse-once, evaluate-many workflow: inside the closure the
3568/// expression nodes are live, so [`evaluation`] can reduce them, and
3569/// whatever the closure returns outlives them.
3570///
3571/// The arena is released when this returns, so the closure must not let an
3572/// expression HANDLE escape in what it returns. Return a reduced value, as
3573/// the example below does, or call [`realize`] on the tree and return
3574/// that; [`parse_with`] is the same parse with the realizing already done.
3575/// A handle that escapes names a node that no longer exists, and
3576/// [`realize`] and [`simplify`] then PANIC rather than report the lost
3577/// expression as an empty array. The closure return type is a type
3578/// parameter, so nothing in the signature can realize it for a caller, and
3579/// nothing in it can stop a handle from being returned.
3580///
3581/// ```
3582/// use tabnas::Value;
3583/// use tabnas_expr::{evaluation, EvalSite, Evaluate, Op};
3584///
3585/// fn main() -> Result<(), Box<dyn std::error::Error>> {
3586///     let parser = tabnas_expr::make();
3587///     let add: Evaluate = std::sync::Arc::new(
3588///         |_site: &mut EvalSite<'_>, _op: &Op, terms: &[Value]| match terms {
3589///             [Value::Number(left), Value::Number(right)] => Value::Number(left + right),
3590///             _ => Value::Null,
3591///         },
3592///     );
3593///     let total = tabnas_expr::parse_scope(&parser, "1+2", |value| {
3594///         evaluation(&mut EvalSite::detached(), &value, &add)
3595///     })?;
3596///     assert_eq!(total, Value::Number(3.0));
3597///     Ok(())
3598/// }
3599/// ```
3600pub fn parse_scope<R>(
3601    parser: &Tabnas,
3602    src: &str,
3603    use_value: impl FnOnce(Value) -> R,
3604) -> Result<R, TabnasError> {
3605    let _guard = ArenaGuard::enter();
3606    let value = parser.parse(src)?;
3607    Ok(use_value(value))
3608}
3609
3610/// Parse one document and reduce it to the S-expression form the shared
3611/// fixtures compare.
3612pub fn parse_simplified(parser: &Tabnas, src: &str) -> Result<Value, TabnasError> {
3613    let _guard = ArenaGuard::enter();
3614    let value = parser.parse(src)?;
3615    Ok(simplify(&value))
3616}