Skip to main content

decl_lang/
semantics.rs

1//! Value model, environment, and type resolution — a port of the
2//! reference implementation's semantics.ts.
3use crate::ast::*;
4use num_bigint::BigInt;
5use num_traits::{ToPrimitive, Zero};
6use regex::Regex;
7use std::cell::{Cell, RefCell};
8use std::collections::{BTreeMap, HashMap, HashSet};
9use std::fmt;
10use std::rc::Rc;
11use std::sync::LazyLock;
12
13// the pattern-interpolation grammar (§3.6): compiled once
14static PATTERN_HOLE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\$\{([^}]*)\}").unwrap());
15static PATTERN_STR_LIT: LazyLock<Regex> =
16    LazyLock::new(|| Regex::new(r#"^"((?:[^"\\]|\\.)*)"$"#).unwrap());
17static PATTERN_INT_RANGE: LazyLock<Regex> =
18    LazyLock::new(|| Regex::new(r"^(-?[0-9]+)\.\.(<?)(-?[0-9]+)$").unwrap());
19static PATTERN_INT_LIT: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^-?[0-9]+$").unwrap());
20static PATTERN_IDENT: LazyLock<Regex> =
21    LazyLock::new(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_.]*$").unwrap());
22
23// ---------------- paths ----------------
24#[derive(Clone, Debug, PartialEq)]
25pub enum Seg {
26    /// a record member by name: dotted when the dot can spell it (§7.2)
27    Name(String),
28    Idx(usize),
29    /// a map key: always bracketed (§7.2)
30    Key(String),
31}
32pub type SegPath = Vec<Seg>;
33pub fn seg_text(s: &Seg) -> String {
34    match s {
35        Seg::Name(n) | Seg::Key(n) => n.clone(),
36        Seg::Idx(i) => i.to_string(),
37    }
38}
39/// dot-spellable (§3.11, §4.3): identifier-shaped and not a literal keyword
40pub fn dot_spellable(name: &str) -> bool {
41    let mut cs = name.chars();
42    let head = matches!(cs.next(), Some(c) if c == '_' || c.is_ascii_alphabetic());
43    head && cs.all(|c| c == '_' || c.is_ascii_alphanumeric())
44        && !matches!(name, "true" | "false" | "null")
45}
46
47// ---------------- values ----------------
48#[derive(Clone)]
49pub enum Value {
50    Int(BigInt),
51    Float(f64),
52    Str(String),
53    Bool(bool),
54    Null,
55    Absent,
56    Undef,
57    Q {
58        dim: String,
59        value: f64,
60    },
61    Ref(Rc<SegPath>),
62    Rec(Rc<RefCell<RecInst>>),
63    Arr(Rc<RefCell<ArrV>>),
64    Map(Rc<RefCell<MapV>>),
65    Range {
66        lo: Box<Value>,
67        hi: Box<Value>,
68        excl: bool,
69    },
70    Clo(Rc<Closure>),
71    Nat(NatFn),
72    Std(Rc<Vec<String>>),
73    NsRef(Rc<NsRefV>),
74    Pat(String),
75    PreObj(Rc<Vec<(String, Value)>>),
76    PreArr(Rc<Vec<(bool, Value)>>),
77    PreVal(Rc<PreValV>),
78    JObj(Rc<Vec<(String, Value)>>),
79    JArr(Rc<Vec<Value>>),
80    Segs(Rc<SegPath>),
81}
82
83impl fmt::Debug for Value {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            Value::Int(i) => write!(f, "{i}"),
87            Value::Float(x) => write!(f, "{x}"),
88            Value::Str(s) => write!(f, "{s:?}"),
89            Value::Bool(b) => write!(f, "{b}"),
90            Value::Null => write!(f, "null"),
91            Value::Absent => write!(f, "ABSENT"),
92            Value::Undef => write!(f, "UNDEF"),
93            Value::Q { dim, value } => write!(f, "{value}<{dim}>"),
94            other => write!(f, "<{}>", other.tag()),
95        }
96    }
97}
98
99impl Value {
100    pub fn tag(&self) -> &'static str {
101        match self {
102            Value::Int(_) => "int",
103            Value::Float(_) => "float",
104            Value::Str(_) => "string",
105            Value::Bool(_) => "bool",
106            Value::Null => "null",
107            Value::Absent => "absent",
108            Value::Undef => "undef",
109            Value::Q { .. } => "quantity",
110            Value::Ref(_) => "ref",
111            Value::Rec(_) => "record",
112            Value::Arr(_) => "array",
113            Value::Map(_) => "map",
114            Value::Range { .. } => "range",
115            Value::Clo(_) => "closure",
116            Value::Nat(_) => "native",
117            Value::Std(_) => "std",
118            Value::NsRef(_) => "namespace",
119            Value::Pat(_) => "pattern",
120            Value::PreObj(_) => "pre-obj",
121            Value::PreArr(_) => "pre-arr",
122            Value::PreVal(_) => "pre-val",
123            Value::JObj(_) => "json-obj",
124            Value::JArr(_) => "json-arr",
125            Value::Segs(_) => "segs",
126        }
127    }
128    pub fn is_undef(&self) -> bool {
129        matches!(self, Value::Undef)
130    }
131    pub fn is_absent(&self) -> bool {
132        matches!(self, Value::Absent)
133    }
134    pub fn place(&self) -> Option<SegPath> {
135        match self {
136            Value::Ref(p) => Some((**p).clone()),
137            Value::Rec(r) => Some(r.borrow().path.clone()),
138            Value::Arr(a) => Some(a.borrow().path.clone()),
139            Value::Map(m) => Some(m.borrow().path.clone()),
140            _ => None,
141        }
142    }
143}
144
145pub type NatFn = Rc<dyn Fn(&[Value]) -> R<Value>>;
146
147pub struct Closure {
148    pub params: Vec<String>,
149    pub body: Rc<Expr>,
150    pub scope: Scope,
151}
152pub struct NsRefV {
153    pub exports: Rc<RefCell<HashMap<String, Export>>>,
154}
155pub struct PreValV {
156    pub expr: Rc<Expr>,
157    pub scope: Scope,
158}
159pub struct ArrV {
160    pub items: Vec<Value>,
161    pub path: SegPath,
162}
163pub struct MapV {
164    pub entries: Vec<(String, Value)>,
165    pub path: SegPath,
166}
167impl MapV {
168    pub fn get(&self, k: &str) -> Option<&Value> {
169        self.entries.iter().find(|(n, _)| n == k).map(|(_, v)| v)
170    }
171    pub fn has(&self, k: &str) -> bool {
172        self.entries.iter().any(|(n, _)| n == k)
173    }
174    pub fn set(&mut self, k: String, v: Value) {
175        if let Some(e) = self.entries.iter_mut().find(|(n, _)| *n == k) {
176            e.1 = v;
177        } else {
178            self.entries.push((k, v));
179        }
180    }
181}
182
183#[derive(Clone, Copy, PartialEq, Debug)]
184pub enum MKind {
185    Req,
186    Opt,
187    Dflt,
188    Der,
189}
190#[derive(Clone, Copy, PartialEq, Debug)]
191pub enum SlotState {
192    Unforced,
193    Forcing,
194    Ok,
195    Invalid,
196    Absent,
197}
198
199#[derive(Clone)]
200pub enum Compute {
201    Check {
202        raw: Value,
203        types: Vec<RT>,
204        name: String,
205        root_name: String,
206        menv: Option<Rc<Env>>,
207    },
208    Default {
209        expr: Rc<Expr>,
210        types: Vec<RT>,
211        name: String,
212        root_name: String,
213        menv: Option<Rc<Env>>,
214    },
215    Derived {
216        expr: Rc<Expr>,
217        ty: Option<RT>,
218        supplied: Option<Value>,
219        name: String,
220        root_name: String,
221        menv: Option<Rc<Env>>,
222    },
223}
224
225pub struct Slot {
226    pub kind: MKind,
227    /// `x$ = e`: computed, never part of the value (D34)
228    pub hidden: bool,
229    pub state: SlotState,
230    pub value: Value,
231    pub compute: Option<Compute>,
232}
233
234pub struct RecInst {
235    pub type_name: Option<String>,
236    pub rt: RT,
237    pub path: SegPath,
238    pub parent: Option<Rc<RefCell<RecInst>>>,
239    // declaration order matters (forcing order drives diagnostic order)
240    pub slots: Vec<(String, Slot)>,
241    pub entry_order: Vec<String>,
242    pub extras: Vec<(String, Value)>,
243    pub menv: Option<Rc<Env>>,
244}
245impl RecInst {
246    pub fn extra(&self, n: &str) -> Option<&Value> {
247        self.extras.iter().find(|(k, _)| k == n).map(|(_, v)| v)
248    }
249    pub fn set_extra(&mut self, n: &str, v: Value) {
250        if let Some(e) = self.extras.iter_mut().find(|(k, _)| k == n) {
251            e.1 = v;
252        } else {
253            self.extras.push((n.to_string(), v));
254        }
255    }
256    pub fn slot(&self, n: &str) -> Option<&Slot> {
257        self.slots.iter().find(|(k, _)| k == n).map(|(_, s)| s)
258    }
259    pub fn slot_mut(&mut self, n: &str) -> Option<&mut Slot> {
260        self.slots.iter_mut().find(|(k, _)| k == n).map(|(_, s)| s)
261    }
262    pub fn has_slot(&self, n: &str) -> bool {
263        self.slots.iter().any(|(k, _)| k == n)
264    }
265}
266
267#[derive(Clone)]
268pub struct Scope {
269    pub inst: Option<Rc<RefCell<RecInst>>>,
270    pub locals: Rc<HashMap<String, Value>>,
271    pub root_name: String,
272    pub menv: Option<Rc<Env>>,
273}
274impl Scope {
275    pub fn new(root_name: &str, menv: Option<Rc<Env>>) -> Scope {
276        Scope {
277            inst: None,
278            locals: Rc::new(HashMap::new()),
279            root_name: root_name.to_string(),
280            menv,
281        }
282    }
283    pub fn with_locals(&self, locals: HashMap<String, Value>) -> Scope {
284        Scope {
285            inst: self.inst.clone(),
286            locals: Rc::new(locals),
287            root_name: self.root_name.clone(),
288            menv: self.menv.clone(),
289        }
290    }
291    pub fn with_inst(&self, inst: Option<Rc<RefCell<RecInst>>>) -> Scope {
292        Scope {
293            inst,
294            locals: self.locals.clone(),
295            root_name: self.root_name.clone(),
296            menv: self.menv.clone(),
297        }
298    }
299    pub fn with_menv(&self, menv: Option<Rc<Env>>) -> Scope {
300        Scope {
301            inst: self.inst.clone(),
302            locals: self.locals.clone(),
303            root_name: self.root_name.clone(),
304            menv,
305        }
306    }
307}
308
309// ---------------- failures & diagnostics ----------------
310pub struct EvalErr {
311    pub msg: String,
312    pub code: Option<String>,
313}
314pub enum Fail {
315    Taint,
316    Defer,
317    Eval(EvalErr),
318}
319pub type R<T> = Result<T, Fail>;
320pub fn err<T>(msg: impl Into<String>) -> R<T> {
321    Err(Fail::Eval(EvalErr {
322        msg: msg.into(),
323        code: None,
324    }))
325}
326pub fn err_code<T>(msg: impl Into<String>, code: &str) -> R<T> {
327    Err(Fail::Eval(EvalErr {
328        msg: msg.into(),
329        code: Some(code.to_string()),
330    }))
331}
332
333#[derive(Clone, Debug)]
334pub struct Diag {
335    pub severity: String,
336    pub id: Option<String>,
337    pub message: String,
338    pub path: String,
339    pub code: Option<String>,
340    /// the source range the checker reported at (the declaration, or the expression under inference)
341    pub loc: Option<Loc>,
342    /// the evaluation step that produced it (a slot, a root, an assert): dependency tracking's tag
343    pub by: Option<String>,
344}
345impl Diag {
346    pub fn error(message: impl Into<String>, path: String, code: Option<&str>) -> Diag {
347        Diag {
348            severity: "error".into(),
349            id: None,
350            message: message.into(),
351            path,
352            code: code.map(|c| c.to_string()),
353            loc: None,
354            by: None,
355        }
356    }
357    pub fn to_json(&self, file: Option<&str>) -> String {
358        let mut parts = Vec::new();
359        // the report's field order (§12.2): file, code, id, severity,
360        // message, path — absent fields omitted (byte-identical across
361        // implementations)
362        if let Some(f) = file {
363            parts.push(format!("\"file\":{}", json_str(f)));
364        }
365        if let Some(c) = &self.code {
366            parts.push(format!("\"code\":{}", json_str(c)));
367        }
368        if let Some(id) = &self.id {
369            parts.push(format!("\"id\":{}", json_str(id)));
370        }
371        parts.push(format!("\"severity\":{}", json_str(&self.severity)));
372        parts.push(format!("\"message\":{}", json_str(&self.message)));
373        parts.push(format!("\"path\":{}", json_str(&self.path)));
374        format!("{{{}}}", parts.join(","))
375    }
376}
377
378// ---------------- resolved types ----------------
379pub type RT = Rc<Ty>;
380
381pub struct Ty {
382    pub k: RTk,
383    pub name: RefCell<Option<String>>,
384    pub tail: RefCell<Option<Tail>>,
385}
386pub fn ty(k: RTk) -> RT {
387    Rc::new(Ty {
388        k,
389        name: RefCell::new(None),
390        tail: RefCell::new(None),
391    })
392}
393
394pub enum RTk {
395    Prim(String),
396    Lit(Value),
397    Range {
398        lo: Value,
399        hi: Value,
400        excl: bool,
401        base: String,
402    },
403    Pattern {
404        src: String,
405        re: Regex,
406    },
407    Arr {
408        elem: RT,
409        lo: Option<i64>,
410        hi: Option<i64>,
411    },
412    Map {
413        key: RT,
414        val: RT,
415    },
416    Union(Vec<RT>),
417    IsectN(Vec<RT>),
418    Rec(RecType),
419    Pred {
420        base: RT,
421        preds: Vec<Rc<Expr>>,
422    },
423    Ref(RT),
424    Quantity(String),
425    Func {
426        params: Vec<RT>,
427        ret: RT,
428    },
429    Any,
430}
431
432pub struct RecType {
433    pub open: Cell<bool>,
434    pub members: RefCell<Vec<Member>>,
435    pub asserts: RefCell<Vec<AssertItem>>,
436    /// `context $parent: ref<T>` declarations (D30), checked at embedding sites
437    pub ctx_decls: RefCell<Vec<(String, RT)>>,
438    /// still being filled; extensions of it wait in `pending` (§3.14)
439    pub filling: Cell<bool>,
440    pub pending: RefCell<Vec<(RT, RT)>>,
441}
442pub fn rec_type(open: bool) -> RecType {
443    RecType {
444        open: Cell::new(open),
445        members: RefCell::new(vec![]),
446        asserts: RefCell::new(vec![]),
447        ctx_decls: RefCell::new(vec![]),
448        filling: Cell::new(false),
449        pending: RefCell::new(vec![]),
450    }
451}
452
453#[derive(Clone)]
454pub struct Member {
455    pub kind: MKind,
456    pub name: String,
457    /// a hidden member (D34): computed, never part of the value
458    pub hidden: bool,
459    pub ty: Option<RT>,
460    pub conj: Option<Vec<RT>>,
461    pub dflt: Option<Rc<Expr>>,
462    pub expr: Option<Rc<Expr>>,
463    pub menv: Option<Rc<Env>>,
464}
465
466#[derive(Clone)]
467pub struct AssertItem {
468    pub when: bool,
469    pub name: String,
470    pub cond: Rc<Expr>,
471    pub tail: Option<Tail>,
472    pub body: Vec<MemberAst>,
473    pub origin: Option<String>,
474    pub menv: Option<Rc<Env>>,
475}
476
477pub fn rec_members(t: &RT) -> Vec<Member> {
478    match &t.k {
479        RTk::Rec(r) => r.members.borrow().clone(),
480        _ => vec![],
481    }
482}
483pub fn is_rec(t: &RT) -> bool {
484    matches!(t.k, RTk::Rec(_))
485}
486
487// ---------------- dimension vectors ----------------
488pub type DimVec = BTreeMap<String, i32>;
489pub fn key_of_vec(v: &DimVec) -> String {
490    v.iter()
491        .filter(|(_, e)| **e != 0)
492        .map(|(n, e)| {
493            if *e == 1 {
494                n.clone()
495            } else {
496                format!("{n}^{e}")
497            }
498        })
499        .collect::<Vec<_>>()
500        .join("*")
501}
502pub fn vec_of_key(key: &str) -> DimVec {
503    let mut v = DimVec::new();
504    if key.is_empty() {
505        return v;
506    }
507    for p in key.split('*') {
508        let (n, e) = match p.split_once('^') {
509            Some((n, e)) => (n.to_string(), e.parse::<i32>().unwrap_or(1)),
510            None => (p.to_string(), 1),
511        };
512        *v.entry(n).or_insert(0) += e;
513    }
514    v
515}
516pub fn vec_combine(a: &DimVec, b: &DimVec, sign: i32) -> DimVec {
517    let mut out = a.clone();
518    for (n, e) in b {
519        *out.entry(n.clone()).or_insert(0) += sign * e;
520    }
521    out
522}
523
524// ---------------- environment ----------------
525pub struct Export {
526    pub env: Rc<Env>,
527    pub name: String,
528}
529impl Clone for Export {
530    fn clone(&self) -> Self {
531        Export {
532            env: self.env.clone(),
533            name: self.name.clone(),
534        }
535    }
536}
537pub struct ConstEntry {
538    pub expr: Rc<Expr>,
539    pub ty: Option<TypeAst>,
540    pub state: Cell<bool>,
541    pub value: RefCell<Value>,
542}
543pub struct FuncEntry {
544    pub params: Vec<Param>,
545    pub ret: Option<TypeAst>,
546    pub body: Rc<Expr>,
547}
548pub struct TypeEntry {
549    pub ast: TypeAst,
550    pub tail: Option<Tail>,
551    pub params: Vec<Param>,
552}
553pub struct DiagDecl {
554    pub params: Vec<Param>,
555    pub severity: String,
556    pub template: Vec<TPart>,
557}
558pub struct UnitDecl {
559    pub dim: Option<String>,
560    pub factor: Option<Rc<Expr>>,
561    pub base: Option<String>,
562}
563pub type ConstEval = Rc<dyn Fn(&str) -> R<Value>>;
564pub type ExprEval = Rc<dyn Fn(&Rc<Expr>) -> R<Value>>;
565
566pub struct Env {
567    pub type_asts: RefCell<HashMap<String, Rc<TypeEntry>>>,
568    pub type_memo: RefCell<HashMap<String, RT>>,
569    // names being spliced into a pattern right now, across nested
570    // resolutions — a mutually recursive pair is a cycle, not a stack overflow
571    pub pattern_visiting: RefCell<Vec<String>>,
572    pub consts: RefCell<HashMap<String, Rc<ConstEntry>>>,
573    pub funcs: RefCell<HashMap<String, Rc<FuncEntry>>>,
574    pub duplicates: RefCell<Vec<String>>,
575    pub outputs: RefCell<Vec<(String, TypeAst, Rc<Expr>)>>,
576    pub inputs: RefCell<HashMap<String, (TypeAst, Option<Rc<Expr>>)>>,
577    pub diags: RefCell<HashMap<String, Rc<DiagDecl>>>,
578    pub registry: RefCell<Rc<RefCell<Vec<Rc<RefCell<RecInst>>>>>>,
579    pub roots: RefCell<Rc<RefCell<Vec<(String, Value)>>>>,
580    pub diagnostics: RefCell<Rc<RefCell<Vec<Diag>>>>,
581    pub const_eval: RefCell<Option<ConstEval>>,
582    pub expr_eval: RefCell<Option<ExprEval>>,
583    pub imports: RefCell<HashMap<String, Export>>,
584    pub namespaces: RefCell<HashMap<String, (Rc<Env>, Rc<RefCell<HashMap<String, Export>>>)>>,
585    const_diag_seen: RefCell<HashSet<String>>,
586    pub dim_decls: RefCell<HashMap<String, Option<Vec<(String, i32)>>>>,
587    pub dim_memo: RefCell<HashMap<String, DimVec>>,
588    pub unit_decls: RefCell<HashMap<String, UnitDecl>>,
589    pub unit_memo: RefCell<HashMap<String, (String, f64)>>,
590    pub base_unit_of: RefCell<HashMap<String, String>>,
591    pub space_diags: RefCell<Vec<Diag>>,
592    /// declaration order (HashMaps do not keep it; diagnostics follow it)
593    pub type_order: RefCell<Vec<String>>,
594    pub unit_order: RefCell<Vec<String>>,
595    /// installed by the checker: constant-evaluation errors go here instead of the report
596    pub const_diag_sink: RefCell<Option<Rc<RefCell<Vec<Diag>>>>>,
597    /// installed by the engine: the evaluation step a report is attributed to
598    pub tagger: RefCell<Option<Rc<dyn Fn() -> Option<String>>>>,
599}
600
601/// §6.7: evaluation- and validation-time diagnostics sort by (path, id), path in canonical order; stable
602pub fn sort_diags(diags: Vec<Diag>) -> Vec<Diag> {
603    let segs_of = |p: &str| -> SegPath {
604        if p.is_empty() {
605            return vec![];
606        }
607        parse_path(p, "").unwrap_or_else(|_| vec![Seg::Name(p.to_string())])
608    };
609    let mut keyed: Vec<(usize, SegPath, Diag)> = diags
610        .into_iter()
611        .enumerate()
612        .map(|(i, d)| (i, segs_of(&d.path), d))
613        .collect();
614    keyed.sort_by(|a, b| {
615        cmp_path(&a.1, &b.1)
616            .then_with(|| {
617                a.2.id
618                    .as_deref()
619                    .unwrap_or("")
620                    .cmp(b.2.id.as_deref().unwrap_or(""))
621            })
622            .then_with(|| a.0.cmp(&b.0))
623    });
624    keyed.into_iter().map(|(_, _, d)| d).collect()
625}
626
627const SI_PREFIXES: [(&str, f64); 20] = [
628    ("y", 1e-24),
629    ("z", 1e-21),
630    ("a", 1e-18),
631    ("f", 1e-15),
632    ("p", 1e-12),
633    ("n", 1e-9),
634    ("u", 1e-6),
635    ("m", 1e-3),
636    ("c", 1e-2),
637    ("d", 1e-1),
638    ("da", 1e1),
639    ("h", 1e2),
640    ("k", 1e3),
641    ("M", 1e6),
642    ("G", 1e9),
643    ("T", 1e12),
644    ("P", 1e15),
645    ("E", 1e18),
646    ("Z", 1e21),
647    ("Y", 1e24),
648];
649
650impl Env {
651    pub fn new() -> Rc<Env> {
652        let env = Env {
653            type_asts: RefCell::new(HashMap::new()),
654            type_memo: RefCell::new(HashMap::new()),
655            pattern_visiting: RefCell::new(vec![]),
656            consts: RefCell::new(HashMap::new()),
657            funcs: RefCell::new(HashMap::new()),
658            duplicates: RefCell::new(vec![]),
659            outputs: RefCell::new(vec![]),
660            inputs: RefCell::new(HashMap::new()),
661            diags: RefCell::new(HashMap::new()),
662            registry: RefCell::new(Rc::new(RefCell::new(vec![]))),
663            roots: RefCell::new(Rc::new(RefCell::new(vec![]))),
664            diagnostics: RefCell::new(Rc::new(RefCell::new(vec![]))),
665            const_eval: RefCell::new(None),
666            expr_eval: RefCell::new(None),
667            imports: RefCell::new(HashMap::new()),
668            namespaces: RefCell::new(HashMap::new()),
669            const_diag_seen: RefCell::new(HashSet::new()),
670            dim_decls: RefCell::new(HashMap::new()),
671            dim_memo: RefCell::new(HashMap::new()),
672            unit_decls: RefCell::new(HashMap::new()),
673            unit_memo: RefCell::new(HashMap::new()),
674            base_unit_of: RefCell::new(HashMap::new()),
675            space_diags: RefCell::new(vec![]),
676            type_order: RefCell::new(vec![]),
677            unit_order: RefCell::new(vec![]),
678            const_diag_sink: RefCell::new(None),
679            tagger: RefCell::new(None),
680        };
681        env.seed_units();
682        Rc::new(env)
683    }
684
685    // std.units — the SI catalog generated from the §13.10 prefix rule (D15)
686    fn seed_units(&self) {
687        let unit = |sym: &str, dim: Option<&str>, factor: f64, base: &str| {
688            let mut m = self.unit_decls.borrow_mut();
689            if m.contains_key(sym) {
690                return;
691            }
692            self.unit_order.borrow_mut().push(sym.to_string());
693            m.insert(
694                sym.to_string(),
695                match dim {
696                    Some(d) => UnitDecl {
697                        dim: Some(d.to_string()),
698                        factor: None,
699                        base: None,
700                    },
701                    None => UnitDecl {
702                        dim: None,
703                        factor: Some(Rc::new(Expr::Lit(Value::Float(factor)))),
704                        base: Some(base.to_string()),
705                    },
706                },
707            );
708        };
709        let bases = [
710            ("Time", "s"),
711            ("Length", "m"),
712            ("Mass", "kg"),
713            ("Current", "A"),
714            ("Temperature", "K"),
715            ("Amount", "mol"),
716            ("LuminousIntensity", "cd"),
717        ];
718        for (d, _) in bases {
719            self.dim_decls.borrow_mut().insert(d.to_string(), None);
720        }
721        let t = |n: &str, e: i32| (n.to_string(), e);
722        let derived: Vec<(&str, Option<Vec<(String, i32)>>, &str)> = vec![
723            ("Frequency", Some(vec![t("Time", -1)]), "Hz"),
724            (
725                "Force",
726                Some(vec![t("Mass", 1), t("Length", 1), t("Time", -2)]),
727                "N",
728            ),
729            (
730                "Pressure",
731                Some(vec![t("Mass", 1), t("Length", -1), t("Time", -2)]),
732                "Pa",
733            ),
734            (
735                "Energy",
736                Some(vec![t("Mass", 1), t("Length", 2), t("Time", -2)]),
737                "J",
738            ),
739            (
740                "Power",
741                Some(vec![t("Mass", 1), t("Length", 2), t("Time", -3)]),
742                "W",
743            ),
744            ("Charge", Some(vec![t("Current", 1), t("Time", 1)]), "C"),
745            (
746                "Voltage",
747                Some(vec![
748                    t("Mass", 1),
749                    t("Length", 2),
750                    t("Time", -3),
751                    t("Current", -1),
752                ]),
753                "V",
754            ),
755            (
756                "Resistance",
757                Some(vec![
758                    t("Mass", 1),
759                    t("Length", 2),
760                    t("Time", -3),
761                    t("Current", -2),
762                ]),
763                "Ohm",
764            ),
765            (
766                "Capacitance",
767                Some(vec![
768                    t("Mass", -1),
769                    t("Length", -2),
770                    t("Time", 4),
771                    t("Current", 2),
772                ]),
773                "F",
774            ),
775            ("DataSize", None, "bit"),
776        ];
777        for (d, terms, _) in &derived {
778            self.dim_decls
779                .borrow_mut()
780                .insert(d.to_string(), terms.clone());
781        }
782        for (d, s) in bases {
783            unit(s, Some(d), 1.0, "");
784        }
785        for (d, _, s) in &derived {
786            unit(s, Some(d), 1.0, "");
787        }
788        unit("B", None, 8.0, "bit");
789        unit("g", None, 1e-3, "kg");
790        let mut prefixable: Vec<&str> = bases
791            .iter()
792            .map(|(_, s)| *s)
793            .filter(|s| *s != "kg")
794            .collect();
795        prefixable.extend(derived.iter().map(|(_, _, s)| *s).filter(|s| *s != "bit"));
796        prefixable.push("g");
797        for u0 in prefixable {
798            for (p, f) in SI_PREFIXES {
799                unit(&format!("{p}{u0}"), None, f, u0);
800            }
801        }
802        for u0 in ["bit", "B"] {
803            for (p, f) in [
804                ("Ki", 1024f64),
805                ("Mi", 1024f64.powi(2)),
806                ("Gi", 1024f64.powi(3)),
807                ("Ti", 1024f64.powi(4)),
808                ("Pi", 1024f64.powi(5)),
809                ("Ei", 1024f64.powi(6)),
810            ] {
811                unit(&format!("{p}{u0}"), None, f, u0);
812            }
813            for (p, f) in SI_PREFIXES {
814                if ["k", "M", "G", "T", "P", "E"].contains(&p) {
815                    unit(&format!("{p}{u0}"), None, f, u0);
816                }
817            }
818        }
819    }
820
821    pub fn load(&self, decls: &[Decl]) {
822        let mut seen: HashSet<String> = HashSet::new();
823        for d in decls {
824            if let Some(n) = d.name() {
825                if !matches!(d.body, DeclBody::Unit { .. } | DeclBody::Dimension { .. })
826                    && !seen.insert(n.to_string())
827                {
828                    self.duplicates.borrow_mut().push(n.to_string());
829                }
830            }
831            match &d.body {
832                DeclBody::Dimension { name, terms } => {
833                    if self.dim_decls.borrow().contains_key(name) {
834                        self.space_diags.borrow_mut().push(Diag::error(
835                            format!("dimension {name} redeclared"),
836                            String::new(),
837                            Some("E3001"),
838                        ));
839                    } else {
840                        self.dim_decls
841                            .borrow_mut()
842                            .insert(name.clone(), terms.clone());
843                    }
844                }
845                DeclBody::Unit {
846                    name,
847                    dim,
848                    factor,
849                    base,
850                } => {
851                    if self.unit_decls.borrow().contains_key(name) {
852                        self.space_diags.borrow_mut().push(Diag::error(
853                            format!("unit {name} redeclared"),
854                            String::new(),
855                            Some("E4073"),
856                        ));
857                    } else {
858                        self.unit_order.borrow_mut().push(name.clone());
859                        self.unit_decls.borrow_mut().insert(
860                            name.clone(),
861                            UnitDecl {
862                                dim: dim.clone(),
863                                factor: factor.clone(),
864                                base: base.clone(),
865                            },
866                        );
867                    }
868                }
869                DeclBody::Type {
870                    name,
871                    params,
872                    ty,
873                    tail,
874                } => {
875                    if !self.type_asts.borrow().contains_key(name) {
876                        self.type_order.borrow_mut().push(name.clone());
877                    }
878                    self.type_asts.borrow_mut().insert(
879                        name.clone(),
880                        Rc::new(TypeEntry {
881                            ast: ty.clone(),
882                            tail: tail.clone(),
883                            params: params.clone(),
884                        }),
885                    );
886                }
887                DeclBody::Const { name, ty, expr } => {
888                    self.consts.borrow_mut().insert(
889                        name.clone(),
890                        Rc::new(ConstEntry {
891                            expr: expr.clone(),
892                            ty: ty.clone(),
893                            state: Cell::new(false),
894                            value: RefCell::new(Value::Null),
895                        }),
896                    );
897                }
898                DeclBody::Func {
899                    name,
900                    params,
901                    ret,
902                    body,
903                } => {
904                    self.funcs.borrow_mut().insert(
905                        name.clone(),
906                        Rc::new(FuncEntry {
907                            params: params.clone(),
908                            ret: ret.clone(),
909                            body: body.clone(),
910                        }),
911                    );
912                }
913                DeclBody::Output { name, ty, expr } => {
914                    self.outputs
915                        .borrow_mut()
916                        .push((name.clone(), ty.clone(), expr.clone()))
917                }
918                DeclBody::Input { name, ty, fallback } => {
919                    self.inputs
920                        .borrow_mut()
921                        .insert(name.clone(), (ty.clone(), fallback.clone()));
922                }
923                DeclBody::Diagnostic {
924                    name,
925                    params,
926                    severity,
927                    template,
928                } => {
929                    self.diags.borrow_mut().insert(
930                        name.clone(),
931                        Rc::new(DiagDecl {
932                            params: params.clone(),
933                            severity: severity.clone(),
934                            template: template.clone(),
935                        }),
936                    );
937                }
938                _ => {}
939            }
940        }
941    }
942
943    pub fn report(&self, d: Diag) {
944        let by = self.tagger.borrow().as_ref().and_then(|t| t());
945        let mut d = d;
946        if by.is_some() {
947            d.by = by;
948        }
949        self.diagnostics.borrow().borrow_mut().push(d);
950    }
951    /// replace every diagnostic (the run entry points sort them, §6.7)
952    pub fn diag_set(&self, diags: Vec<Diag>) {
953        let rc = self.diagnostics.borrow().clone();
954        *rc.borrow_mut() = diags;
955    }
956    pub fn remove_root(&self, name: &str) {
957        let rc = self.roots.borrow().clone();
958        rc.borrow_mut().retain(|(n, _)| n != name);
959    }
960    pub fn root_names(&self) -> Vec<String> {
961        self.roots
962            .borrow()
963            .borrow()
964            .iter()
965            .map(|(n, _)| n.clone())
966            .collect()
967    }
968    pub fn registry_retain(&self, mut pred: impl FnMut(&Rc<RefCell<RecInst>>) -> bool) {
969        let rc = self.registry.borrow().clone();
970        rc.borrow_mut().retain(|i| pred(i));
971    }
972    pub fn diagnostics_vec(&self) -> Vec<Diag> {
973        self.diagnostics.borrow().borrow().clone()
974    }
975    pub fn diag_len(&self) -> usize {
976        self.diagnostics.borrow().borrow().len()
977    }
978    pub fn diag_truncate(&self, n: usize) {
979        self.diagnostics.borrow().borrow_mut().truncate(n);
980    }
981    pub fn root(&self, name: &str) -> Option<Value> {
982        self.roots
983            .borrow()
984            .borrow()
985            .iter()
986            .find(|(n, _)| n == name)
987            .map(|(_, v)| v.clone())
988    }
989    pub fn set_root(&self, name: &str, v: Value) {
990        let rc = self.roots.borrow().clone();
991        let mut roots = rc.borrow_mut();
992        if let Some(e) = roots.iter_mut().find(|(n, _)| n == name) {
993            e.1 = v;
994        } else {
995            roots.push((name.to_string(), v));
996        }
997    }
998    pub fn root_values(&self) -> Vec<Value> {
999        self.roots
1000            .borrow()
1001            .borrow()
1002            .iter()
1003            .map(|(_, v)| v.clone())
1004            .collect()
1005    }
1006    pub fn registry_push(&self, inst: Rc<RefCell<RecInst>>) {
1007        self.registry.borrow().borrow_mut().push(inst);
1008    }
1009    pub fn registry_snapshot(&self) -> Vec<Rc<RefCell<RecInst>>> {
1010        self.registry.borrow().borrow().clone()
1011    }
1012
1013    // §4.13: a named endpoint in a constant position evaluates at elaboration time
1014    pub fn const_num(&self, v: &Value) -> Value {
1015        let name = match v {
1016            Value::Str(s) => s.clone(),
1017            other => return other.clone(),
1018        };
1019        let ce = self.const_eval.borrow().clone();
1020        let Some(ce) = ce else { return v.clone() };
1021        if !self.consts.borrow().contains_key(&name) {
1022            return v.clone();
1023        }
1024        let diag = |code: &str, message: String| {
1025            let key = format!("{name}{code}");
1026            if self.const_diag_seen.borrow().contains(&key) {
1027                return;
1028            }
1029            self.const_diag_seen.borrow_mut().insert(key);
1030            let d = Diag::error(message, String::new(), Some(code));
1031            match &*self.const_diag_sink.borrow() {
1032                Some(sink) => sink.borrow_mut().push(d),
1033                None => self.report(d),
1034            }
1035        };
1036        match ce(&name) {
1037            Ok(Value::Int(i)) => Value::Int(i),
1038            Ok(Value::Float(f)) => Value::Float(f),
1039            Ok(Value::Undef) | Ok(Value::Null) => v.clone(),
1040            Ok(_) => {
1041                diag(
1042                    "E4021",
1043                    format!("constant {name} is not numeric in a constant position"),
1044                );
1045                v.clone()
1046            }
1047            Err(Fail::Eval(e)) => {
1048                let code = if e.msg.contains("zero") {
1049                    "E5001"
1050                } else if e.msg.contains("NaN") || e.msg.contains("Infinity") {
1051                    "E5002"
1052                } else {
1053                    "E5001"
1054                };
1055                diag(code, format!("evaluating constant {name}: {}", e.msg));
1056                v.clone()
1057            }
1058            Err(_) => v.clone(),
1059        }
1060    }
1061
1062    // ---- unit / dimension name spaces ----
1063    pub fn resolve_dim(&self, name: &str, visiting: &mut Vec<String>) -> Result<DimVec, String> {
1064        if let Some(v) = self.dim_memo.borrow().get(name) {
1065            return Ok(v.clone());
1066        }
1067        if visiting.iter().any(|v| v == name) {
1068            return Err(format!("circular dimension {name}"));
1069        }
1070        let decl = self
1071            .dim_decls
1072            .borrow()
1073            .get(name)
1074            .cloned()
1075            .ok_or_else(|| format!("unknown dimension {name}"))?;
1076        let mut vec = DimVec::new();
1077        match decl {
1078            None => {
1079                vec.insert(name.to_string(), 1);
1080            }
1081            Some(terms) => {
1082                visiting.push(name.to_string());
1083                for (tn, te) in terms {
1084                    let sub = self.resolve_dim(&tn, visiting)?;
1085                    for (n, e) in sub {
1086                        *vec.entry(n).or_insert(0) += e * te;
1087                    }
1088                }
1089                visiting.pop();
1090            }
1091        }
1092        self.dim_memo
1093            .borrow_mut()
1094            .insert(name.to_string(), vec.clone());
1095        Ok(vec)
1096    }
1097    pub fn unit_info(&self, sym: &str) -> Result<(String, f64), String> {
1098        self.unit_info_v(sym, &mut vec![])
1099    }
1100    /// §3.16 unit/dimension-space findings for the checker: the load-time
1101    /// redeclarations plus unresolvable units and duplicate base units
1102    pub fn finalize_unit_space(&self) -> Vec<Diag> {
1103        let mut out = self.space_diags.borrow().clone();
1104        let mut base_seen: HashMap<String, String> = HashMap::new();
1105        let syms = self.unit_order.borrow().clone();
1106        for sym in syms {
1107            let has_dim = self
1108                .unit_decls
1109                .borrow()
1110                .get(&sym)
1111                .map(|u| u.dim.is_some())
1112                .unwrap_or(false);
1113            match self.unit_info(&sym) {
1114                Ok((key, _)) => {
1115                    if has_dim {
1116                        if let Some(prev) = base_seen.get(&key) {
1117                            out.push(Diag::error(
1118                                format!(
1119                                    "second base unit {sym} for dimension {key} (base is {prev})"
1120                                ),
1121                                String::new(),
1122                                Some("E4073"),
1123                            ));
1124                        } else {
1125                            base_seen.insert(key, sym.clone());
1126                        }
1127                    }
1128                }
1129                Err(msg) => {
1130                    let code = if msg.contains("unknown dimension")
1131                        || msg.contains("circular dimension")
1132                    {
1133                        "E3003"
1134                    } else {
1135                        "E4073"
1136                    };
1137                    out.push(Diag::error(msg, String::new(), Some(code)));
1138                }
1139            }
1140        }
1141        out
1142    }
1143    fn unit_info_v(&self, sym: &str, visiting: &mut Vec<String>) -> Result<(String, f64), String> {
1144        if let Some(v) = self.unit_memo.borrow().get(sym) {
1145            return Ok(v.clone());
1146        }
1147        if visiting.iter().any(|v| v == sym) {
1148            return Err(format!("circular unit {sym}"));
1149        }
1150        let (dim, factor, base) = {
1151            let m = self.unit_decls.borrow();
1152            let u = m.get(sym).ok_or_else(|| format!("unknown unit {sym}"))?;
1153            (u.dim.clone(), u.factor.clone(), u.base.clone())
1154        };
1155        let info = if let Some(d) = dim {
1156            let key = key_of_vec(&self.resolve_dim(&d, &mut vec![])?);
1157            self.base_unit_of
1158                .borrow_mut()
1159                .entry(key.clone())
1160                .or_insert_with(|| sym.to_string());
1161            (key, 1.0)
1162        } else {
1163            visiting.push(sym.to_string());
1164            let b = self.unit_info_v(base.as_deref().unwrap_or(""), visiting)?;
1165            visiting.pop();
1166            let mut f: Option<f64> = match factor.as_deref() {
1167                Some(Expr::Lit(Value::Float(x))) => Some(*x),
1168                Some(Expr::Lit(Value::Int(i))) => i.to_f64(),
1169                _ => None,
1170            };
1171            if f.is_none() {
1172                if let (Some(fx), Some(ee)) = (factor.clone(), self.expr_eval.borrow().clone()) {
1173                    f = match ee(&fx) {
1174                        Ok(Value::Float(x)) => Some(x),
1175                        Ok(Value::Int(i)) => i.to_f64(),
1176                        _ => None,
1177                    };
1178                }
1179            }
1180            let f = f.ok_or_else(|| format!("unit {sym}: factor is not a numeric constant"))?;
1181            (b.0, f * b.1)
1182        };
1183        self.unit_memo
1184            .borrow_mut()
1185            .insert(sym.to_string(), info.clone());
1186        Ok(info)
1187    }
1188
1189    // ---- type resolution ----
1190    pub fn resolve(self: &Rc<Env>, ast: &TypeAst, name: Option<&str>) -> Result<RT, String> {
1191        Ok(match ast {
1192            TypeAst::Prim { name: n, .. } => ty(RTk::Prim(n.clone())),
1193            TypeAst::Lit { v, .. } => ty(RTk::Lit(v.clone())),
1194            TypeAst::Range { lo, hi, excl, .. } => {
1195                let lo = self.const_num(lo);
1196                let hi = self.const_num(hi);
1197                let is_f = matches!(lo, Value::Float(_)) || matches!(hi, Value::Float(_));
1198                ty(RTk::Range {
1199                    lo,
1200                    hi,
1201                    excl: *excl,
1202                    base: if is_f { "float".into() } else { "int".into() },
1203                })
1204            }
1205            TypeAst::Pattern { re: src, .. } => {
1206                let expanded = self.expand_pattern(src)?;
1207                if let Some(bad) = pattern_error(&expanded) {
1208                    return Err(format!("malformed pattern /{src}/: {bad}"));
1209                }
1210                let re = compile_pattern(&expanded)
1211                    .map_err(|e| format!("malformed pattern /{src}/: {e}"))?;
1212                ty(RTk::Pattern { src: expanded, re })
1213            }
1214            TypeAst::Map { key, val, .. } => ty(RTk::Map {
1215                key: self.resolve(key, None)?,
1216                val: self.resolve(val, None)?,
1217            }),
1218            TypeAst::Array {
1219                elem, lo, hi, excl, ..
1220            } => {
1221                let lo = lo.as_ref().map(|v| self.const_num(v));
1222                let hi0 = hi.as_ref().map(|v| self.const_num(v));
1223                let to_i = |v: &Value| match v {
1224                    Value::Int(i) => i.to_i64(),
1225                    Value::Float(f) => Some(*f as i64),
1226                    _ => None,
1227                };
1228                let lo_i = lo.as_ref().and_then(&to_i);
1229                let hi_i = hi0
1230                    .as_ref()
1231                    .and_then(to_i)
1232                    .map(|h| if *excl { h - 1 } else { h });
1233                ty(RTk::Arr {
1234                    elem: self.resolve(elem, None)?,
1235                    lo: lo_i,
1236                    hi: hi_i,
1237                })
1238            }
1239            TypeAst::Union { arms, .. } => ty(RTk::Union(
1240                arms.iter()
1241                    .map(|a| self.resolve(a, None))
1242                    .collect::<Result<_, _>>()?,
1243            )),
1244            TypeAst::Isect { arms, .. } => {
1245                let arms: Vec<RT> = arms
1246                    .iter()
1247                    .map(|a| self.resolve(a, None))
1248                    .collect::<Result<_, _>>()?;
1249                if arms.iter().all(is_rec) {
1250                    self.merge_isect(&arms, name)
1251                } else {
1252                    ty(RTk::IsectN(arms))
1253                }
1254            }
1255            TypeAst::Record { members, open, .. } => {
1256                let rt = ty(RTk::Rec(rec_type(*open)));
1257                *rt.name.borrow_mut() = name.map(|s| s.to_string());
1258                self.fill_record(&rt, members)?;
1259                rt
1260            }
1261            TypeAst::Func { params, ret, .. } => ty(RTk::Func {
1262                params: params
1263                    .iter()
1264                    .map(|p| self.resolve(p, None))
1265                    .collect::<Result<_, _>>()?,
1266                ret: self.resolve(ret, None)?,
1267            }),
1268            TypeAst::Named {
1269                name: n,
1270                args,
1271                preds,
1272                ext,
1273                ..
1274            } => {
1275                if let Some(preds) = preds {
1276                    if !preds.is_empty() {
1277                        let base = self.resolve(
1278                            &TypeAst::Named {
1279                                name: n.clone(),
1280                                args: args.clone(),
1281                                preds: None,
1282                                ext: ext.clone(),
1283                                loc: None,
1284                            },
1285                            name,
1286                        )?;
1287                        return Ok(ty(RTk::Pred {
1288                            base,
1289                            preds: preds.clone(),
1290                        }));
1291                    }
1292                }
1293                if n == "quantity" {
1294                    let dn = match args.first() {
1295                        Some(TypeAst::Named { name, .. }) | Some(TypeAst::Prim { name, .. }) => {
1296                            name.clone()
1297                        }
1298                        _ => return Err("quantity needs a dimension".into()),
1299                    };
1300                    return Ok(ty(RTk::Quantity(key_of_vec(
1301                        &self.resolve_dim(&dn, &mut vec![])?,
1302                    ))));
1303                }
1304                if n == "map" && args.len() == 2 {
1305                    return Ok(ty(RTk::Map {
1306                        key: self.resolve(&args[0], None)?,
1307                        val: self.resolve(&args[1], None)?,
1308                    }));
1309                }
1310                if n == "ref" {
1311                    return Ok(ty(RTk::Ref(self.resolve(&args[0], None)?)));
1312                }
1313                if ["int", "float", "bool", "string"].contains(&n.as_str())
1314                    && args.is_empty()
1315                    && ext.is_none()
1316                {
1317                    return Ok(ty(RTk::Prim(n.clone())));
1318                }
1319                let decl = self.type_asts.borrow().get(n).cloned();
1320                let Some(decl) = decl else {
1321                    let im = self.imports.borrow().get(n).cloned();
1322                    if let Some(im) = im {
1323                        return im.env.resolve(
1324                            &TypeAst::Named {
1325                                name: im.name.clone(),
1326                                args: args.clone(),
1327                                preds: None,
1328                                ext: ext.clone(),
1329                                loc: None,
1330                            },
1331                            name,
1332                        );
1333                    }
1334                    if let Some((ns, rest)) = n.split_once('.') {
1335                        let ex = self
1336                            .namespaces
1337                            .borrow()
1338                            .get(ns)
1339                            .and_then(|(_, exports)| exports.borrow().get(rest).cloned());
1340                        if let Some(ex) = ex {
1341                            return ex.env.resolve(
1342                                &TypeAst::Named {
1343                                    name: ex.name.clone(),
1344                                    args: args.clone(),
1345                                    preds: None,
1346                                    ext: ext.clone(),
1347                                    loc: None,
1348                                },
1349                                name,
1350                            );
1351                        }
1352                    }
1353                    return Err(format!("unknown type {n}"));
1354                };
1355                let memo = self.type_memo.borrow().get(n).cloned();
1356                let base = if !decl.params.is_empty() {
1357                    self.instantiate(n, args, &decl)?
1358                } else if let Some(b) = memo {
1359                    b
1360                } else {
1361                    match &decl.ast {
1362                        TypeAst::Record { members, open, .. } => {
1363                            let rt = ty(RTk::Rec(rec_type(*open)));
1364                            *rt.name.borrow_mut() = Some(n.clone());
1365                            *rt.tail.borrow_mut() = decl.tail.clone();
1366                            self.type_memo.borrow_mut().insert(n.clone(), rt.clone());
1367                            // a member that fails to resolve must not leave a half-filled
1368                            // record memoized (later lookups would miss its later members)
1369                            if let Err(e) = self.fill_record(&rt, members) {
1370                                self.type_memo.borrow_mut().remove(n);
1371                                return Err(e);
1372                            }
1373                            rt
1374                        }
1375                        TypeAst::Named {
1376                            name: pn,
1377                            args: pa,
1378                            preds: pp,
1379                            ext: Some(body),
1380                            ..
1381                        } => {
1382                            // an extension declaration (§3.14) is memoized before its parent
1383                            // resolves: in a recursive family — `type Base = { kids: { [string]:
1384                            // Kid } }`, `type Kid = Base { … }` — the parent's body names this
1385                            // type, and every reference must share the one final record rather
1386                            // than a snapshot of the parent's members taken mid-fill
1387                            let rt = ty(RTk::Rec(rec_type(false)));
1388                            if let RTk::Rec(r) = &rt.k {
1389                                r.filling.set(true);
1390                            }
1391                            *rt.name.borrow_mut() = Some(n.clone());
1392                            *rt.tail.borrow_mut() = decl.tail.clone();
1393                            self.type_memo.borrow_mut().insert(n.clone(), rt.clone());
1394                            let parent_ast = TypeAst::Named {
1395                                name: pn.clone(),
1396                                args: pa.clone(),
1397                                preds: pp.clone(),
1398                                ext: None,
1399                                loc: None,
1400                            };
1401                            let filled = self.resolve(&parent_ast, None).and_then(|parent| {
1402                                let extr = self.resolve(body, None)?;
1403                                self.extend_into(&rt, &parent, &extr);
1404                                Ok(())
1405                            });
1406                            if let Err(e) = filled {
1407                                self.type_memo.borrow_mut().remove(n);
1408                                return Err(e);
1409                            }
1410                            rt
1411                        }
1412                        other => {
1413                            let rt = self.resolve(other, Some(n))?;
1414                            if matches!(rt.k, RTk::Rec(_) | RTk::Union(_)) {
1415                                *rt.name.borrow_mut() = Some(n.clone());
1416                            }
1417                            if rt.tail.borrow().is_none() {
1418                                *rt.tail.borrow_mut() = decl.tail.clone();
1419                            }
1420                            self.type_memo.borrow_mut().insert(n.clone(), rt.clone());
1421                            rt
1422                        }
1423                    }
1424                };
1425                if let Some(ext) = ext {
1426                    // an inline extension in a type position: anonymous, never memoized
1427                    let extr = self.resolve(ext, None)?;
1428                    if !is_rec(&base) {
1429                        return Ok(base);
1430                    }
1431                    let merged = ty(RTk::Rec(rec_type(false)));
1432                    if let RTk::Rec(r) = &merged.k {
1433                        r.filling.set(true);
1434                    }
1435                    *merged.name.borrow_mut() = base.name.borrow().clone();
1436                    self.extend_into(&merged, &base, &extr);
1437                    return Ok(merged);
1438                }
1439                base
1440            }
1441        })
1442    }
1443
1444    // §3.14: fill `target` as `base` extended by the override body `ext` —
1445    // base members copied, overrides replacing or adding, asserts appended,
1446    // and a context declaration narrowed by the extension replacing the
1447    // inherited one (§7.3). A base still being filled (the recursive-family
1448    // case above) defers the merge until it completes; `target` stays marked
1449    // filling meanwhile, so an extension of an extension waits in turn.
1450    fn extend_into(&self, target: &RT, base: &RT, extr: &RT) {
1451        let (RTk::Rec(tr), RTk::Rec(br), RTk::Rec(er)) = (&target.k, &base.k, &extr.k) else {
1452            if let RTk::Rec(tr) = &target.k {
1453                tr.filling.set(false);
1454            }
1455            return;
1456        };
1457        if br.filling.get() {
1458            br.pending.borrow_mut().push((target.clone(), extr.clone()));
1459            return;
1460        }
1461        tr.open.set(br.open.get());
1462        if let Some(t) = base.tail.borrow().clone() {
1463            *target.tail.borrow_mut() = Some(t);
1464        }
1465        let mut members: Vec<Member> = br.members.borrow().clone();
1466        for om in er.members.borrow().iter() {
1467            if let Some(i) = members.iter().position(|m| m.name == om.name) {
1468                members[i] = om.clone();
1469            } else {
1470                members.push(om.clone());
1471            }
1472        }
1473        let mut asserts = br.asserts.borrow().clone();
1474        asserts.extend(er.asserts.borrow().iter().cloned());
1475        let mut ctx_decls: Vec<(String, RT)> = br.ctx_decls.borrow().clone();
1476        for cd in er.ctx_decls.borrow().iter() {
1477            if let Some(i) = ctx_decls.iter().position(|(v, _)| *v == cd.0) {
1478                ctx_decls[i] = cd.clone();
1479            } else {
1480                ctx_decls.push(cd.clone());
1481            }
1482        }
1483        *tr.members.borrow_mut() = members;
1484        *tr.asserts.borrow_mut() = asserts;
1485        *tr.ctx_decls.borrow_mut() = ctx_decls;
1486        self.complete_record(target);
1487    }
1488
1489    // a record's members are final: extensions that waited on it merge now
1490    fn complete_record(&self, rt: &RT) {
1491        let RTk::Rec(r) = &rt.k else { return };
1492        r.filling.set(false);
1493        let pending: Vec<(RT, RT)> = std::mem::take(&mut *r.pending.borrow_mut());
1494        for (target, extr) in pending {
1495            self.extend_into(&target, rt, &extr);
1496        }
1497    }
1498
1499    // §3.6: `${T}` inside a pattern splices another type — a string-shaped
1500    // T (pattern, string literal, union of those) as its regular language,
1501    // an integer-shaped T (int literal, int range, union) as the decimal
1502    // representations of its members
1503    fn expand_pattern(self: &Rc<Env>, re: &str) -> Result<String, String> {
1504        let hole: &Regex = &PATTERN_HOLE;
1505        let mut out = String::new();
1506        let mut last = 0;
1507        for m in hole.captures_iter(re) {
1508            let whole = m.get(0).unwrap();
1509            out.push_str(&re[last..whole.start()]);
1510            last = whole.end();
1511            let text = m.get(1).unwrap().as_str().trim().to_string();
1512            // the spliced type: a union of string literals, int literals, int
1513            // ranges, and named types — the type-expression subset that fits
1514            // inside a pattern token
1515            let arms: Vec<String> = text.split('|').map(|a| a.trim().to_string()).collect();
1516            let mut frags: Vec<String> = vec![];
1517            let str_lit: &Regex = &PATTERN_STR_LIT;
1518            let int_range: &Regex = &PATTERN_INT_RANGE;
1519            let int_lit: &Regex = &PATTERN_INT_LIT;
1520            let ident: &Regex = &PATTERN_IDENT;
1521            for arm in &arms {
1522                if str_lit.is_match(arm) {
1523                    let v = crate::parse::json_unquote(arm)?;
1524                    frags.push(self.pattern_fragment(&ty(RTk::Lit(Value::Str(v))), &text)?);
1525                    continue;
1526                }
1527                if let Some(c) = int_range.captures(arm) {
1528                    let lo = c[1].parse::<BigInt>().map_err(|e| e.to_string())?;
1529                    let hi = c[3].parse::<BigInt>().map_err(|e| e.to_string())?;
1530                    let rt = ty(RTk::Range {
1531                        lo: Value::Int(lo),
1532                        hi: Value::Int(hi),
1533                        excl: &c[2] == "<",
1534                        base: "int".into(),
1535                    });
1536                    frags.push(self.pattern_fragment(&rt, &text)?);
1537                    continue;
1538                }
1539                if int_lit.is_match(arm) {
1540                    let v = arm.parse::<BigInt>().map_err(|e| e.to_string())?;
1541                    frags.push(self.pattern_fragment(&ty(RTk::Lit(Value::Int(v))), &text)?);
1542                    continue;
1543                }
1544                if !ident.is_match(arm) {
1545                    return Err(format!(
1546                        "pattern interpolation of {text}: not a type (§3.6)"
1547                    ));
1548                }
1549                if self.pattern_visiting.borrow().iter().any(|v| v == arm) {
1550                    return Err(format!("pattern interpolation of {arm} is circular"));
1551                }
1552                self.pattern_visiting.borrow_mut().push(arm.clone());
1553                let resolved = self.resolve(
1554                    &TypeAst::Named {
1555                        name: arm.clone(),
1556                        args: vec![],
1557                        preds: None,
1558                        ext: None,
1559                        loc: None,
1560                    },
1561                    None,
1562                );
1563                self.pattern_visiting.borrow_mut().retain(|v| v != arm);
1564                let rt = match resolved {
1565                    Ok(rt) => rt,
1566                    Err(e) => {
1567                        if e.starts_with("unknown type") {
1568                            return Err(format!("pattern interpolation of {arm}: unknown type"));
1569                        }
1570                        return Err(e);
1571                    }
1572                };
1573                frags.push(self.pattern_fragment(&rt, arm)?);
1574            }
1575            if frags.len() == 1 {
1576                out.push_str(&frags[0]);
1577            } else {
1578                out.push_str(&format!("(?:{})", frags.join("|")));
1579            }
1580        }
1581        out.push_str(&re[last..]);
1582        Ok(out)
1583    }
1584    fn pattern_fragment(self: &Rc<Env>, rt: &RT, name: &str) -> Result<String, String> {
1585        let esc = |s: &str| -> String {
1586            let mut o = String::with_capacity(s.len());
1587            for c in s.chars() {
1588                if ".*+?^${}()|[]\\/".contains(c) {
1589                    o.push('\\');
1590                }
1591                o.push(c);
1592            }
1593            o
1594        };
1595        let bad = || {
1596            Err(format!("pattern interpolation of {name}: type is neither string- nor integer-shaped (§3.6)"))
1597        };
1598        match &rt.k {
1599            RTk::Pattern { src, .. } => Ok(format!("(?:{src})")),
1600            RTk::Lit(Value::Str(s)) => Ok(esc(s)),
1601            RTk::Lit(Value::Int(i)) => Ok(i.to_string()),
1602            RTk::Lit(_) => bad(),
1603            RTk::Range { lo, hi, excl, base } => {
1604                let (Value::Int(lo), Value::Int(hi)) = (lo, hi) else {
1605                    return bad();
1606                };
1607                if base != "int" {
1608                    return bad();
1609                }
1610                let hi = if *excl { hi - 1 } else { hi.clone() };
1611                if &hi - lo >= BigInt::from(65536) {
1612                    return Err(format!(
1613                        "pattern interpolation of {name}: range too large (limit 65536 values)"
1614                    ));
1615                }
1616                let mut alts: Vec<String> = vec![];
1617                let mut v = lo.clone();
1618                while v <= hi {
1619                    alts.push(v.to_string());
1620                    v += 1;
1621                }
1622                Ok(format!("(?:{})", alts.join("|")))
1623            }
1624            RTk::Union(arms) => {
1625                let parts = arms
1626                    .iter()
1627                    .map(|a| self.pattern_fragment(a, name))
1628                    .collect::<Result<Vec<_>, _>>()?;
1629                Ok(format!("(?:{})", parts.join("|")))
1630            }
1631            RTk::Pred { base, .. } => self.pattern_fragment(base, name),
1632            RTk::Prim(n) if n == "string" => Ok(".*".into()),
1633            RTk::Prim(n) if n == "int" => Ok("-?[0-9]+".into()),
1634            _ => bad(),
1635        }
1636    }
1637
1638    // §3.15 generics
1639    fn instantiate(
1640        self: &Rc<Env>,
1641        name: &str,
1642        args: &[TypeAst],
1643        decl: &Rc<TypeEntry>,
1644    ) -> Result<RT, String> {
1645        let ps = &decl.params;
1646        if args.len() != ps.len() {
1647            return Err(format!(
1648                "generic arity: {name} expects {} argument(s), got {}",
1649                ps.len(),
1650                args.len()
1651            ));
1652        }
1653        let mut types: HashMap<String, TypeAst> = HashMap::new();
1654        let mut values: HashMap<String, Value> = HashMap::new();
1655        let mut label = Vec::new();
1656        for (p, a) in ps.iter().zip(args) {
1657            if let Some(pty) = &p.ty {
1658                let v = match a {
1659                    TypeAst::Lit { v, .. } => v.clone(),
1660                    TypeAst::Named {
1661                        name: an,
1662                        args: aa,
1663                        ext: None,
1664                        preds: None,
1665                        ..
1666                    } if aa.is_empty() => {
1667                        let v = self.const_num(&Value::Str(an.clone()));
1668                        if matches!(v, Value::Str(_)) {
1669                            return Err(format!(
1670                                "non-constant value argument {an} for {} of {name}",
1671                                p.name
1672                            ));
1673                        }
1674                        v
1675                    }
1676                    _ => {
1677                        return Err(format!(
1678                            "generic arity: parameter {} of {name} takes a constant value",
1679                            p.name
1680                        ))
1681                    }
1682                };
1683                let bound = self.resolve(&subst_type(pty, &types, &values), None)?;
1684                if !crate::subsume::subsumes(self, &ty(RTk::Lit(v.clone())), &bound) {
1685                    return Err(format!(
1686                        "value argument {v:?} outside parameter {}'s type in {name}",
1687                        p.name
1688                    ));
1689                }
1690                label.push(format!("{v:?}"));
1691                values.insert(p.name.clone(), v);
1692            } else {
1693                label.push(match a {
1694                    TypeAst::Named { name, .. } | TypeAst::Prim { name, .. } => name.clone(),
1695                    _ => "type".into(),
1696                });
1697                types.insert(p.name.clone(), a.clone());
1698            }
1699        }
1700        let key = format!(
1701            "{name}<{}>",
1702            args.iter().map(type_key).collect::<Vec<_>>().join(",")
1703        );
1704        if let Some(rt) = self.type_memo.borrow().get(&key).cloned() {
1705            return Ok(rt);
1706        }
1707        let shown = format!("{name}<{}>", label.join(", "));
1708        let body = subst_type(&decl.ast, &types, &values);
1709        let rt = match &body {
1710            TypeAst::Record { members, open, .. } => {
1711                let rt = ty(RTk::Rec(rec_type(*open)));
1712                *rt.name.borrow_mut() = Some(shown);
1713                *rt.tail.borrow_mut() = decl.tail.clone();
1714                self.type_memo.borrow_mut().insert(key.clone(), rt.clone());
1715                if let Err(e) = self.fill_record(&rt, members) {
1716                    self.type_memo.borrow_mut().remove(&key);
1717                    return Err(e);
1718                }
1719                rt
1720            }
1721            other => {
1722                let rt = self.resolve(other, Some(&shown))?;
1723                if matches!(rt.k, RTk::Rec(_) | RTk::Union(_)) {
1724                    *rt.name.borrow_mut() = Some(shown);
1725                }
1726                if rt.tail.borrow().is_none() {
1727                    *rt.tail.borrow_mut() = decl.tail.clone();
1728                }
1729                self.type_memo.borrow_mut().insert(key, rt.clone());
1730                rt
1731            }
1732        };
1733        Ok(rt)
1734    }
1735
1736    fn fill_record(self: &Rc<Env>, rt: &RT, members: &[MemberAst]) -> Result<(), String> {
1737        let RTk::Rec(r) = &rt.k else { return Ok(()) };
1738        let origin = rt.name.borrow().clone();
1739        r.filling.set(true);
1740        for m in members {
1741            match m {
1742                MemberAst::Value {
1743                    name,
1744                    opt,
1745                    ty: t,
1746                    dflt,
1747                    ..
1748                } => r.members.borrow_mut().push(Member {
1749                    kind: if dflt.is_some() {
1750                        MKind::Dflt
1751                    } else if *opt {
1752                        MKind::Opt
1753                    } else {
1754                        MKind::Req
1755                    },
1756                    name: name.clone(),
1757                    hidden: false,
1758                    ty: Some(self.resolve(t, None)?),
1759                    conj: None,
1760                    dflt: dflt.clone(),
1761                    expr: None,
1762                    menv: Some(self.clone()),
1763                }),
1764                MemberAst::Derived {
1765                    name,
1766                    ty: t,
1767                    expr,
1768                    hidden,
1769                    ..
1770                } => r.members.borrow_mut().push(Member {
1771                    kind: MKind::Der,
1772                    name: name.clone(),
1773                    hidden: *hidden,
1774                    ty: match t {
1775                        Some(t) => Some(self.resolve(t, None)?),
1776                        None => None,
1777                    },
1778                    conj: None,
1779                    dflt: None,
1780                    expr: Some(expr.clone()),
1781                    menv: Some(self.clone()),
1782                }),
1783                MemberAst::Assert {
1784                    name, cond, tail, ..
1785                } => r.asserts.borrow_mut().push(AssertItem {
1786                    when: false,
1787                    name: name.clone(),
1788                    cond: cond.clone(),
1789                    tail: tail.clone(),
1790                    body: vec![],
1791                    origin: origin.clone(),
1792                    menv: Some(self.clone()),
1793                }),
1794                MemberAst::When { cond, body, .. } => r.asserts.borrow_mut().push(AssertItem {
1795                    when: true,
1796                    name: String::new(),
1797                    cond: cond.clone(),
1798                    tail: None,
1799                    body: body.clone(),
1800                    origin: origin.clone(),
1801                    menv: Some(self.clone()),
1802                }),
1803                MemberAst::Context {
1804                    variable, ty: t, ..
1805                } => r
1806                    .ctx_decls
1807                    .borrow_mut()
1808                    .push((variable.clone(), self.resolve(t, None)?)),
1809            }
1810        }
1811        self.complete_record(rt);
1812        Ok(())
1813    }
1814
1815    fn merge_isect(&self, arms: &[RT], name: Option<&str>) -> RT {
1816        let mut members: Vec<Member> = vec![];
1817        let mut asserts: Vec<AssertItem> = vec![];
1818        let mut open = true;
1819        for a in arms {
1820            let RTk::Rec(r) = &a.k else { continue };
1821            open = open && r.open.get();
1822            for m in r.members.borrow().iter() {
1823                if let Some(i) = members.iter().position(|x| x.name == m.name) {
1824                    let prev = members[i].clone();
1825                    let mut conj = prev
1826                        .conj
1827                        .clone()
1828                        .unwrap_or_else(|| prev.ty.iter().cloned().collect());
1829                    if let Some(t) = &m.ty {
1830                        conj.push(t.clone());
1831                    }
1832                    members[i] = Member {
1833                        conj: Some(conj),
1834                        kind: if m.kind == MKind::Req {
1835                            MKind::Req
1836                        } else {
1837                            prev.kind
1838                        },
1839                        ..prev
1840                    };
1841                } else {
1842                    members.push(m.clone());
1843                }
1844            }
1845            asserts.extend(r.asserts.borrow().iter().map(|x| AssertItem {
1846                origin: x.origin.clone().or_else(|| a.name.borrow().clone()),
1847                ..x.clone()
1848            }));
1849        }
1850        let rec = rec_type(open);
1851        *rec.members.borrow_mut() = members;
1852        *rec.asserts.borrow_mut() = asserts;
1853        let rt = ty(RTk::Rec(rec));
1854        *rt.name.borrow_mut() = name.map(|s| s.to_string());
1855        rt
1856    }
1857}
1858
1859fn type_key(t: &TypeAst) -> String {
1860    match t {
1861        TypeAst::Prim { name: n, .. } => format!("p:{n}"),
1862        TypeAst::Lit { v, .. } => format!("l:{v:?}"),
1863        TypeAst::Named { name, args, .. } => format!(
1864            "n:{name}<{}>",
1865            args.iter().map(type_key).collect::<Vec<_>>().join(",")
1866        ),
1867        TypeAst::Range { lo, hi, excl, .. } => format!("r:{lo:?}..{excl}{hi:?}"),
1868        TypeAst::Array { elem, lo, hi, .. } => format!("a:{}[{lo:?},{hi:?}]", type_key(elem)),
1869        TypeAst::Union { arms: a, .. } => {
1870            format!("u:{}", a.iter().map(type_key).collect::<Vec<_>>().join("|"))
1871        }
1872        TypeAst::Isect { arms: a, .. } => {
1873            format!("i:{}", a.iter().map(type_key).collect::<Vec<_>>().join("&"))
1874        }
1875        TypeAst::Map { key, val, .. } => format!("m:{}:{}", type_key(key), type_key(val)),
1876        TypeAst::Pattern { re: p, .. } => format!("pat:{p}"),
1877        TypeAst::Record { members, .. } => format!("rec:{}", members.len()),
1878        TypeAst::Func { params, ret, .. } => format!("f:{}->{}", params.len(), type_key(ret)),
1879    }
1880}
1881
1882// ---------------- generic substitution ----------------
1883pub fn subst_type(
1884    ast: &TypeAst,
1885    types: &HashMap<String, TypeAst>,
1886    values: &HashMap<String, Value>,
1887) -> TypeAst {
1888    let t = |a: &TypeAst| subst_type(a, types, values);
1889    match ast {
1890        TypeAst::Named {
1891            name,
1892            args,
1893            preds,
1894            ext,
1895            loc,
1896        } => {
1897            let plain = args.is_empty() && ext.is_none() && preds.is_none();
1898            if plain {
1899                if let Some(x) = types.get(name) {
1900                    return x.clone();
1901                }
1902                if let Some(v) = values.get(name) {
1903                    return TypeAst::Lit {
1904                        v: v.clone(),
1905                        loc: *loc,
1906                    };
1907                }
1908            }
1909            TypeAst::Named {
1910                name: name.clone(),
1911                args: args.iter().map(t).collect(),
1912                preds: preds
1913                    .as_ref()
1914                    .map(|ps| ps.iter().map(|p| subst_expr(p, values)).collect()),
1915                ext: ext.as_ref().map(|e| Box::new(t(e))),
1916                loc: *loc,
1917            }
1918        }
1919        TypeAst::Range { lo, hi, excl, loc } => {
1920            let sub = |v: &Value| match v {
1921                Value::Str(s) if values.contains_key(s) => values[s].clone(),
1922                other => other.clone(),
1923            };
1924            TypeAst::Range {
1925                lo: sub(lo),
1926                hi: sub(hi),
1927                excl: *excl,
1928                loc: *loc,
1929            }
1930        }
1931        TypeAst::Array {
1932            elem,
1933            lo,
1934            hi,
1935            excl,
1936            loc,
1937        } => {
1938            let sub = |v: &Value| match v {
1939                Value::Str(s) if values.contains_key(s) => values[s].clone(),
1940                other => other.clone(),
1941            };
1942            TypeAst::Array {
1943                elem: Box::new(t(elem)),
1944                lo: lo.as_ref().map(sub),
1945                hi: hi.as_ref().map(sub),
1946                excl: *excl,
1947                loc: *loc,
1948            }
1949        }
1950        TypeAst::Record { members, open, loc } => TypeAst::Record {
1951            members: members
1952                .iter()
1953                .map(|m| subst_member(m, types, values))
1954                .collect(),
1955            open: *open,
1956            loc: *loc,
1957        },
1958        TypeAst::Map { key, val, loc } => TypeAst::Map {
1959            key: Box::new(t(key)),
1960            val: Box::new(t(val)),
1961            loc: *loc,
1962        },
1963        TypeAst::Union { arms: a, loc } => TypeAst::Union {
1964            arms: a.iter().map(t).collect(),
1965            loc: *loc,
1966        },
1967        TypeAst::Isect { arms: a, loc } => TypeAst::Isect {
1968            arms: a.iter().map(t).collect(),
1969            loc: *loc,
1970        },
1971        TypeAst::Func { params, ret, loc } => TypeAst::Func {
1972            params: params.iter().map(t).collect(),
1973            ret: Box::new(t(ret)),
1974            loc: *loc,
1975        },
1976        other => other.clone(),
1977    }
1978}
1979fn subst_member(
1980    m: &MemberAst,
1981    types: &HashMap<String, TypeAst>,
1982    values: &HashMap<String, Value>,
1983) -> MemberAst {
1984    let t = |a: &TypeAst| subst_type(a, types, values);
1985    match m {
1986        MemberAst::Value {
1987            name,
1988            opt,
1989            ty,
1990            dflt,
1991            loc,
1992        } => MemberAst::Value {
1993            name: name.clone(),
1994            opt: *opt,
1995            ty: t(ty),
1996            dflt: dflt.as_ref().map(|d| subst_expr(d, values)),
1997            loc: *loc,
1998        },
1999        MemberAst::Derived {
2000            name,
2001            ty,
2002            expr,
2003            hidden,
2004            loc,
2005        } => MemberAst::Derived {
2006            name: name.clone(),
2007            ty: ty.as_ref().map(t),
2008            expr: subst_expr(expr, values),
2009            hidden: *hidden,
2010            loc: *loc,
2011        },
2012        MemberAst::Context { variable, ty, loc } => MemberAst::Context {
2013            variable: variable.clone(),
2014            ty: t(ty),
2015            loc: *loc,
2016        },
2017        MemberAst::Assert {
2018            name,
2019            cond,
2020            tail,
2021            loc,
2022        } => MemberAst::Assert {
2023            name: name.clone(),
2024            cond: subst_expr(cond, values),
2025            tail: tail.clone(),
2026            loc: *loc,
2027        },
2028        MemberAst::When { cond, body, loc } => MemberAst::When {
2029            cond: subst_expr(cond, values),
2030            body: body
2031                .iter()
2032                .map(|b| subst_member(b, types, values))
2033                .collect(),
2034            loc: *loc,
2035        },
2036    }
2037}
2038pub fn subst_expr(e: &Rc<Expr>, values: &HashMap<String, Value>) -> Rc<Expr> {
2039    if values.is_empty() {
2040        return e.clone();
2041    }
2042    let s = |x: &Rc<Expr>| subst_expr(x, values);
2043    let cls = |c: &ForClause| ForClause {
2044        v: c.v.clone(),
2045        iter: s(&c.iter),
2046        filters: c.filters.iter().map(s).collect(),
2047    };
2048    let out = Rc::new(match &**e {
2049        Expr::Name(n) if values.contains_key(n) => Expr::Lit(values[n].clone()),
2050        Expr::Template(parts) => Expr::Template(
2051            parts
2052                .iter()
2053                .map(|p| match p {
2054                    TPart::Expr(x) => TPart::Expr(s(x)),
2055                    other => other.clone(),
2056                })
2057                .collect(),
2058        ),
2059        Expr::Obj(es) => Expr::Obj(es.iter().map(|(k, v)| (k.clone(), s(v))).collect()),
2060        Expr::Arr(items) => Expr::Arr(items.iter().map(|(sp, v)| (*sp, s(v))).collect()),
2061        Expr::Comp { head, clauses } => Expr::Comp {
2062            head: s(head),
2063            clauses: clauses.iter().map(cls).collect(),
2064        },
2065        Expr::MapComp { key, val, clauses } => Expr::MapComp {
2066            key: s(key),
2067            val: s(val),
2068            clauses: clauses.iter().map(cls).collect(),
2069        },
2070        Expr::Bin { op, l, r } => Expr::Bin {
2071            op: op.clone(),
2072            l: s(l),
2073            r: s(r),
2074        },
2075        Expr::Un { op, x } => Expr::Un {
2076            op: op.clone(),
2077            x: s(x),
2078        },
2079        Expr::Paren(x) => Expr::Paren(s(x)),
2080        Expr::If { c, t, f } => Expr::If {
2081            c: s(c),
2082            t: s(t),
2083            f: s(f),
2084        },
2085        Expr::Lambda { params, body } => Expr::Lambda {
2086            params: params.clone(),
2087            body: s(body),
2088        },
2089        Expr::Call { fun, args } => Expr::Call {
2090            fun: s(fun),
2091            args: args.iter().map(s).collect(),
2092        },
2093        Expr::Member { x, name, safe } => Expr::Member {
2094            x: s(x),
2095            name: name.clone(),
2096            safe: *safe,
2097        },
2098        Expr::Index { x, i } => Expr::Index { x: s(x), i: s(i) },
2099        Expr::With { base, patch } => Expr::With {
2100            base: s(base),
2101            patch: s(patch),
2102        },
2103        Expr::Match { subject, arms } => Expr::Match {
2104            subject: s(subject),
2105            arms: arms
2106                .iter()
2107                .map(|a| MatchArm {
2108                    v: a.v.clone(),
2109                    ty: a.ty.clone(),
2110                    body: s(&a.body),
2111                })
2112                .collect(),
2113        },
2114        other => other.clone(),
2115    });
2116    // a substituted node keeps the source range of the node it replaces (the reference copies `loc`)
2117    if let Some(l) = expr_loc(e) {
2118        set_expr_loc(&out, l);
2119    }
2120    out
2121}
2122
2123// ---------------- helpers ----------------
2124// ---------------- patterns: the portable core (§3.6) ----------------
2125// A pattern body is validated against the specification's regular-
2126// expression core with one fixed set of messages, so every implementation
2127// reports the same text whatever engine runs the accepted patterns.
2128// Returns the reason a body is outside the core, or None when it is inside.
2129const PATTERN_PUNCT: &str = "\\/.*+?()[]{}|^$-";
2130fn pattern_escape(cs: &[char], i: &mut usize) -> Result<i64, String> {
2131    if *i + 1 >= cs.len() {
2132        return Err("trailing backslash".into());
2133    }
2134    let e = cs[*i + 1];
2135    *i += 2;
2136    if "dwsDWS".contains(e) {
2137        return Ok(-1);
2138    }
2139    match e {
2140        'n' => return Ok(10),
2141        't' => return Ok(9),
2142        'r' => return Ok(13),
2143        _ => {}
2144    }
2145    if PATTERN_PUNCT.contains(e) {
2146        return Ok(e as i64);
2147    }
2148    if e.is_ascii_digit() {
2149        return Err(format!("backreference \\{e} is not supported"));
2150    }
2151    Err(format!("unsupported escape \\{e}"))
2152}
2153pub fn pattern_error(src: &str) -> Option<String> {
2154    let cs: Vec<char> = src.chars().collect();
2155    let n = cs.len();
2156    let (mut i, mut depth, mut can_repeat) = (0usize, 0i32, false);
2157    while i < n {
2158        match cs[i] {
2159            '\\' => {
2160                if let Err(r) = pattern_escape(&cs, &mut i) {
2161                    return Some(r);
2162                }
2163                can_repeat = true;
2164            }
2165            '[' => {
2166                i += 1;
2167                if i < n && cs[i] == '^' {
2168                    i += 1;
2169                }
2170                let mut items = 0;
2171                loop {
2172                    if i >= n {
2173                        return Some("unterminated character class".into());
2174                    }
2175                    if cs[i] == ']' {
2176                        i += 1;
2177                        break;
2178                    }
2179                    let lo = if cs[i] == '\\' {
2180                        match pattern_escape(&cs, &mut i) {
2181                            Ok(v) => v,
2182                            Err(r) => return Some(r),
2183                        }
2184                    } else {
2185                        let v = cs[i] as i64;
2186                        i += 1;
2187                        v
2188                    };
2189                    if i < n && cs[i] == '-' && i + 1 < n && cs[i + 1] != ']' {
2190                        i += 1;
2191                        let hi = if cs[i] == '\\' {
2192                            match pattern_escape(&cs, &mut i) {
2193                                Ok(v) => v,
2194                                Err(r) => return Some(r),
2195                            }
2196                        } else {
2197                            let v = cs[i] as i64;
2198                            i += 1;
2199                            v
2200                        };
2201                        if lo < 0 || hi < 0 || lo > hi {
2202                            return Some("invalid range in character class".into());
2203                        }
2204                    }
2205                    items += 1;
2206                }
2207                if items == 0 {
2208                    return Some("empty character class".into());
2209                }
2210                can_repeat = true;
2211            }
2212            ']' => return Some("unbalanced bracket".into()),
2213            '(' => {
2214                i += 1;
2215                if i < n && cs[i] == '?' {
2216                    if i + 1 < n && cs[i + 1] == ':' {
2217                        i += 2;
2218                    } else {
2219                        return Some("unsupported construct (?".into());
2220                    }
2221                }
2222                depth += 1;
2223                can_repeat = false;
2224            }
2225            ')' => {
2226                if depth == 0 {
2227                    return Some("unbalanced parenthesis".into());
2228                }
2229                depth -= 1;
2230                i += 1;
2231                can_repeat = true;
2232            }
2233            '|' => {
2234                i += 1;
2235                can_repeat = false;
2236            }
2237            '*' | '+' | '?' => {
2238                if !can_repeat {
2239                    return Some("nothing to repeat".into());
2240                }
2241                i += 1;
2242                can_repeat = false;
2243            }
2244            '{' => {
2245                if !can_repeat {
2246                    return Some("nothing to repeat".into());
2247                }
2248                let mut j = i + 1;
2249                let start = j;
2250                while j < n && cs[j].is_ascii_digit() {
2251                    j += 1;
2252                }
2253                if j == start {
2254                    return Some("malformed repetition".into());
2255                }
2256                let m: String = cs[start..j].iter().collect();
2257                let mut hi: Option<String> = None;
2258                if j < n && cs[j] == ',' {
2259                    j += 1;
2260                    let s2 = j;
2261                    while j < n && cs[j].is_ascii_digit() {
2262                        j += 1;
2263                    }
2264                    if j > s2 {
2265                        hi = Some(cs[s2..j].iter().collect());
2266                    }
2267                }
2268                if j >= n || cs[j] != '}' {
2269                    return Some("malformed repetition".into());
2270                }
2271                if let Some(h) = hi {
2272                    if h.parse::<BigInt>().unwrap_or_default()
2273                        < m.parse::<BigInt>().unwrap_or_default()
2274                    {
2275                        return Some("malformed repetition".into());
2276                    }
2277                }
2278                i = j + 1;
2279                can_repeat = false;
2280            }
2281            '}' => return Some("malformed repetition".into()),
2282            '^' | '$' => {
2283                i += 1;
2284                can_repeat = false;
2285            }
2286            _ => {
2287                i += 1;
2288                can_repeat = true;
2289            }
2290        }
2291    }
2292    if depth > 0 {
2293        Some("unbalanced parenthesis".into())
2294    } else {
2295        None
2296    }
2297}
2298pub fn compile_pattern(src: &str) -> Result<Regex, String> {
2299    Regex::new(&format!("^(?:{src})$")).map_err(|e| e.to_string())
2300}
2301
2302pub fn path_str(segs: &[Seg], rel_root: Option<&str>) -> String {
2303    let mut out = String::new();
2304    for (i, s) in segs.iter().enumerate() {
2305        match s {
2306            _ if i == 0 => {
2307                let n = seg_text(s);
2308                if rel_root == Some(n.as_str()) {
2309                    out.push('$');
2310                } else {
2311                    out.push_str(&n);
2312                }
2313            }
2314            Seg::Idx(k) => out.push_str(&format!("[{k}]")),
2315            Seg::Key(n) => out.push_str(&format!("[{}]", json_str(n))),
2316            Seg::Name(n) if dot_spellable(n) => {
2317                out.push('.');
2318                out.push_str(n);
2319            }
2320            Seg::Name(n) => out.push_str(&format!("[{}]", json_str(n))),
2321        }
2322    }
2323    out
2324}
2325
2326/// A path string from a document: `.name` is a member, `["…"]` a bracketed
2327/// segment (a map key, or a member the dot cannot spell — the canonical walk,
2328/// §7.5, decides which is legal where), `[n]` an index.
2329pub fn parse_path(s: &str, root_name: &str) -> R<SegPath> {
2330    let id_re = Regex::new(r"^[_A-Za-z][_A-Za-z0-9]*").unwrap();
2331    let mut segs = vec![];
2332    let mut i = if s.starts_with('$') {
2333        segs.push(Seg::Name(root_name.to_string()));
2334        1
2335    } else {
2336        let m = id_re
2337            .find(s)
2338            .ok_or(())
2339            .or_else(|_| err(format!("bad path {s}")))?;
2340        segs.push(Seg::Name(m.as_str().to_string()));
2341        m.end()
2342    };
2343    while i < s.len() {
2344        let rest = &s[i..];
2345        if let Some(r) = rest.strip_prefix('.') {
2346            let m = id_re
2347                .find(r)
2348                .ok_or(())
2349                .or_else(|_| err(format!("bad path {s}")))?;
2350            segs.push(Seg::Name(m.as_str().to_string()));
2351            i += 1 + m.end();
2352        } else if rest.starts_with('[') {
2353            let j = rest
2354                .find(']')
2355                .ok_or(())
2356                .or_else(|_| err(format!("bad path {s}")))?;
2357            let inner = &rest[1..j];
2358            if inner.starts_with('"') {
2359                segs.push(Seg::Key(
2360                    crate::parse::json_unquote(inner).unwrap_or_default(),
2361                ));
2362            } else {
2363                segs.push(Seg::Idx(inner.parse().unwrap_or(0)));
2364            }
2365            i += j + 1;
2366        } else {
2367            return err(format!("bad path {s}"));
2368        }
2369    }
2370    Ok(segs)
2371}
2372
2373/// Canonical path order (§7.2): segment-wise, indices numerically, names and
2374/// keys lexicographically, a prefix first.
2375pub fn cmp_path(a: &[Seg], b: &[Seg]) -> std::cmp::Ordering {
2376    for (x, y) in a.iter().zip(b) {
2377        match (x, y) {
2378            (Seg::Idx(i), Seg::Idx(j)) => {
2379                if i != j {
2380                    return i.cmp(j);
2381                }
2382            }
2383            _ => {
2384                let xs = seg_text(x);
2385                let ys = seg_text(y);
2386                if xs != ys {
2387                    return xs.cmp(&ys);
2388                }
2389            }
2390        }
2391    }
2392    a.len().cmp(&b.len())
2393}
2394
2395pub fn value_eq(a: &Value, b: &Value) -> bool {
2396    let (pa, pb) = (a.place(), b.place());
2397    if let (Some(pa), Some(pb)) = (&pa, &pb) {
2398        if matches!(a, Value::Ref(_)) || matches!(b, Value::Ref(_)) {
2399            return cmp_path(pa, pb) == std::cmp::Ordering::Equal;
2400        }
2401    }
2402    match (a, b) {
2403        (Value::Int(x), Value::Int(y)) => x == y,
2404        (Value::Float(x), Value::Float(y)) => x == y,
2405        (Value::Str(x), Value::Str(y)) => x == y,
2406        (Value::Bool(x), Value::Bool(y)) => x == y,
2407        (Value::Null, Value::Null) => true,
2408        // two unforced slots compare equal, as in the reference (undefined === undefined)
2409        (Value::Undef, Value::Undef) => true,
2410        (Value::Q { dim: d1, value: v1 }, Value::Q { dim: d2, value: v2 }) => d1 == d2 && v1 == v2,
2411        (Value::Arr(x), Value::Arr(y)) => {
2412            let (x, y) = (x.borrow(), y.borrow());
2413            x.items.len() == y.items.len()
2414                && x.items.iter().zip(&y.items).all(|(p, q)| value_eq(p, q))
2415        }
2416        (Value::Map(x), Value::Map(y)) => {
2417            let (x, y) = (x.borrow(), y.borrow());
2418            x.entries.len() == y.entries.len()
2419                && x.entries
2420                    .iter()
2421                    .all(|(k, v)| y.get(k).map(|w| value_eq(v, w)).unwrap_or(false))
2422        }
2423        (Value::Rec(x), Value::Rec(y)) => {
2424            if Rc::ptr_eq(x, y) {
2425                return true;
2426            }
2427            let (x, y) = (x.borrow(), y.borrow());
2428            for (n, s) in &x.slots {
2429                if s.hidden {
2430                    continue; // a hidden member is not part of the value (D34)
2431                }
2432                let v1 = if s.state == SlotState::Absent {
2433                    Value::Absent
2434                } else {
2435                    s.value.clone()
2436                };
2437                let v2 = match y.slot(n) {
2438                    Some(s2) if s2.state != SlotState::Absent => s2.value.clone(),
2439                    _ => Value::Absent,
2440                };
2441                match (&v1, &v2) {
2442                    (Value::Absent, Value::Absent) => continue,
2443                    (Value::Absent, _) | (_, Value::Absent) => return false,
2444                    _ => {
2445                        if !value_eq(&v1, &v2) {
2446                            return false;
2447                        }
2448                    }
2449                }
2450            }
2451            true
2452        }
2453        _ => false,
2454    }
2455}
2456
2457// ---------------- lexical JSON (int/float by lexeme) ----------------
2458pub fn read_json(src: &str) -> R<Value> {
2459    let b = src.as_bytes();
2460    let mut i = 0usize;
2461    fn ws(b: &[u8], i: &mut usize) {
2462        while *i < b.len() && matches!(b[*i], b' ' | b'\t' | b'\r' | b'\n') {
2463            *i += 1;
2464        }
2465    }
2466    fn string(src: &str, b: &[u8], i: &mut usize) -> R<String> {
2467        let mut j = *i + 1;
2468        let mut out = String::new();
2469        while j < b.len() && b[j] != b'"' {
2470            if b[j] == b'\\' {
2471                let e = b[j + 1] as char;
2472                match e {
2473                    'n' => out.push('\n'),
2474                    't' => out.push('\t'),
2475                    'r' => out.push('\r'),
2476                    'b' => out.push('\u{8}'),
2477                    'f' => out.push('\u{c}'),
2478                    'u' => {
2479                        let cp = u32::from_str_radix(&src[j + 2..j + 6], 16).unwrap_or(0xfffd);
2480                        out.push(char::from_u32(cp).unwrap_or('\u{fffd}'));
2481                        j += 4;
2482                    }
2483                    other => out.push(other),
2484                }
2485                j += 2;
2486            } else {
2487                let ch = src[j..].chars().next().unwrap();
2488                out.push(ch);
2489                j += ch.len_utf8();
2490            }
2491        }
2492        *i = j + 1;
2493        Ok(out)
2494    }
2495    fn val(src: &str, b: &[u8], i: &mut usize) -> R<Value> {
2496        ws(b, i);
2497        if *i >= b.len() {
2498            return err("bad JSON: unexpected end");
2499        }
2500        match b[*i] {
2501            b'{' => {
2502                *i += 1;
2503                let mut entries = vec![];
2504                ws(b, i);
2505                if b[*i] == b'}' {
2506                    *i += 1;
2507                    return Ok(Value::JObj(Rc::new(entries)));
2508                }
2509                loop {
2510                    ws(b, i);
2511                    let k = string(src, b, i)?;
2512                    ws(b, i);
2513                    *i += 1;
2514                    let v = val(src, b, i)?;
2515                    entries.push((k, v));
2516                    ws(b, i);
2517                    if b[*i] == b',' {
2518                        *i += 1;
2519                        continue;
2520                    }
2521                    *i += 1;
2522                    return Ok(Value::JObj(Rc::new(entries)));
2523                }
2524            }
2525            b'[' => {
2526                *i += 1;
2527                let mut items = vec![];
2528                ws(b, i);
2529                if b[*i] == b']' {
2530                    *i += 1;
2531                    return Ok(Value::JArr(Rc::new(items)));
2532                }
2533                loop {
2534                    items.push(val(src, b, i)?);
2535                    ws(b, i);
2536                    if b[*i] == b',' {
2537                        *i += 1;
2538                        continue;
2539                    }
2540                    *i += 1;
2541                    return Ok(Value::JArr(Rc::new(items)));
2542                }
2543            }
2544            b'"' => Ok(Value::Str(string(src, b, i)?)),
2545            _ => {
2546                let rest = &src[*i..];
2547                if rest.starts_with("true") {
2548                    *i += 4;
2549                    return Ok(Value::Bool(true));
2550                }
2551                if rest.starts_with("false") {
2552                    *i += 5;
2553                    return Ok(Value::Bool(false));
2554                }
2555                if rest.starts_with("null") {
2556                    *i += 4;
2557                    return Ok(Value::Null);
2558                }
2559                let re = Regex::new(r"^-?(?:0|[1-9][0-9]*)(\.[0-9]+)?([eE][-+]?[0-9]+)?").unwrap();
2560                let m = re
2561                    .captures(rest)
2562                    .ok_or(())
2563                    .or_else(|_| err(format!("bad JSON at {i}")))?;
2564                let whole = m.get(0).unwrap().as_str();
2565                *i += whole.len();
2566                if m.get(1).is_some() || m.get(2).is_some() {
2567                    Ok(Value::Float(whole.parse::<f64>().unwrap_or(0.0)))
2568                } else {
2569                    Ok(Value::Int(
2570                        whole.parse::<BigInt>().unwrap_or_else(|_| BigInt::zero()),
2571                    ))
2572                }
2573            }
2574        }
2575    }
2576    let v = val(src, b, &mut i)?;
2577    ws(b, &mut i);
2578    if i < b.len() {
2579        return err("bad JSON: trailing characters");
2580    }
2581    Ok(v)
2582}
2583
2584// ---------------- JS-compatible number printing ----------------
2585/// ECMAScript Number::toString for finite doubles (shortest round trip)
2586pub fn js_num_str(x: f64) -> String {
2587    if x == 0.0 {
2588        return "0".into();
2589    }
2590    let sci = format!("{:e}", x.abs());
2591    let (mant, exp) = sci.split_once('e').unwrap();
2592    let exp: i32 = exp.parse().unwrap();
2593    let digits: String = mant.chars().filter(|c| *c != '.').collect();
2594    let digits = digits.trim_end_matches('0');
2595    let digits = if digits.is_empty() { "0" } else { digits };
2596    let k = digits.len() as i32;
2597    let n = exp + 1;
2598    let body = if k <= n && n <= 21 {
2599        format!("{digits}{}", "0".repeat((n - k) as usize))
2600    } else if 0 < n && n <= 21 {
2601        format!("{}.{}", &digits[..n as usize], &digits[n as usize..])
2602    } else if -6 < n && n <= 0 {
2603        format!("0.{}{digits}", "0".repeat((-n) as usize))
2604    } else {
2605        let e = n - 1;
2606        let mant = if k > 1 {
2607            format!("{}.{}", &digits[..1], &digits[1..])
2608        } else {
2609            digits.to_string()
2610        };
2611        format!("{mant}e{}{}", if e > 0 { "+" } else { "-" }, e.abs())
2612    };
2613    if x < 0.0 {
2614        format!("-{body}")
2615    } else {
2616        body
2617    }
2618}
2619
2620pub fn json_str(s: &str) -> String {
2621    let mut out = String::with_capacity(s.len() + 2);
2622    out.push('"');
2623    for c in s.chars() {
2624        match c {
2625            '"' => out.push_str("\\\""),
2626            '\\' => out.push_str("\\\\"),
2627            '\n' => out.push_str("\\n"),
2628            '\r' => out.push_str("\\r"),
2629            '\t' => out.push_str("\\t"),
2630            '\u{8}' => out.push_str("\\b"),
2631            '\u{c}' => out.push_str("\\f"),
2632            c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
2633            c => out.push(c),
2634        }
2635    }
2636    out.push('"');
2637    out
2638}