Skip to main content

oxilean_kernel/expr/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::{Level, Name};
6
7use std::collections::HashMap;
8use std::rc::Rc;
9
10use super::functions::{
11    collect_consts, collect_fvars, count_bvar_occ, has_loose_bvar, max_loose_bvar_impl,
12};
13
14/// A pair of `StatSummary` values tracking before/after a transformation.
15#[allow(dead_code)]
16pub struct TransformStat {
17    before: StatSummary,
18    after: StatSummary,
19}
20#[allow(dead_code)]
21impl TransformStat {
22    /// Creates a new transform stat recorder.
23    pub fn new() -> Self {
24        Self {
25            before: StatSummary::new(),
26            after: StatSummary::new(),
27        }
28    }
29    /// Records a before value.
30    pub fn record_before(&mut self, v: f64) {
31        self.before.record(v);
32    }
33    /// Records an after value.
34    pub fn record_after(&mut self, v: f64) {
35        self.after.record(v);
36    }
37    /// Returns the mean reduction ratio (after/before).
38    pub fn mean_ratio(&self) -> Option<f64> {
39        let b = self.before.mean()?;
40        let a = self.after.mean()?;
41        if b.abs() < f64::EPSILON {
42            return None;
43        }
44        Some(a / b)
45    }
46}
47/// A dependency closure builder (transitive closure via BFS).
48#[allow(dead_code)]
49pub struct TransitiveClosure {
50    adj: Vec<Vec<usize>>,
51    n: usize,
52}
53#[allow(dead_code)]
54impl TransitiveClosure {
55    /// Creates a transitive closure builder for `n` nodes.
56    pub fn new(n: usize) -> Self {
57        Self {
58            adj: vec![Vec::new(); n],
59            n,
60        }
61    }
62    /// Adds a direct edge.
63    pub fn add_edge(&mut self, from: usize, to: usize) {
64        if from < self.n {
65            self.adj[from].push(to);
66        }
67    }
68    /// Computes all nodes reachable from `start` (including `start`).
69    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
70        let mut visited = vec![false; self.n];
71        let mut queue = std::collections::VecDeque::new();
72        queue.push_back(start);
73        while let Some(node) = queue.pop_front() {
74            if node >= self.n || visited[node] {
75                continue;
76            }
77            visited[node] = true;
78            for &next in &self.adj[node] {
79                queue.push_back(next);
80            }
81        }
82        (0..self.n).filter(|&i| visited[i]).collect()
83    }
84    /// Returns `true` if `from` can transitively reach `to`.
85    pub fn can_reach(&self, from: usize, to: usize) -> bool {
86        self.reachable_from(from).contains(&to)
87    }
88}
89/// A tagged union for representing a simple two-case discriminated union.
90#[allow(dead_code)]
91pub enum Either2<A, B> {
92    /// The first alternative.
93    First(A),
94    /// The second alternative.
95    Second(B),
96}
97#[allow(dead_code)]
98impl<A, B> Either2<A, B> {
99    /// Returns `true` if this is the first alternative.
100    pub fn is_first(&self) -> bool {
101        matches!(self, Either2::First(_))
102    }
103    /// Returns `true` if this is the second alternative.
104    pub fn is_second(&self) -> bool {
105        matches!(self, Either2::Second(_))
106    }
107    /// Returns the first value if present.
108    pub fn first(self) -> Option<A> {
109        match self {
110            Either2::First(a) => Some(a),
111            _ => None,
112        }
113    }
114    /// Returns the second value if present.
115    pub fn second(self) -> Option<B> {
116        match self {
117            Either2::Second(b) => Some(b),
118            _ => None,
119        }
120    }
121    /// Maps over the first alternative.
122    pub fn map_first<C, F: FnOnce(A) -> C>(self, f: F) -> Either2<C, B> {
123        match self {
124            Either2::First(a) => Either2::First(f(a)),
125            Either2::Second(b) => Either2::Second(b),
126        }
127    }
128}
129/// A min-heap implemented as a binary heap.
130#[allow(dead_code)]
131pub struct MinHeap<T: Ord> {
132    data: Vec<T>,
133}
134#[allow(dead_code)]
135impl<T: Ord> MinHeap<T> {
136    /// Creates a new empty min-heap.
137    pub fn new() -> Self {
138        Self { data: Vec::new() }
139    }
140    /// Inserts an element.
141    pub fn push(&mut self, val: T) {
142        self.data.push(val);
143        self.sift_up(self.data.len() - 1);
144    }
145    /// Removes and returns the minimum element.
146    pub fn pop(&mut self) -> Option<T> {
147        if self.data.is_empty() {
148            return None;
149        }
150        let n = self.data.len();
151        self.data.swap(0, n - 1);
152        let min = self.data.pop();
153        if !self.data.is_empty() {
154            self.sift_down(0);
155        }
156        min
157    }
158    /// Returns a reference to the minimum element.
159    pub fn peek(&self) -> Option<&T> {
160        self.data.first()
161    }
162    /// Returns the number of elements.
163    pub fn len(&self) -> usize {
164        self.data.len()
165    }
166    /// Returns `true` if empty.
167    pub fn is_empty(&self) -> bool {
168        self.data.is_empty()
169    }
170    fn sift_up(&mut self, mut i: usize) {
171        while i > 0 {
172            let parent = (i - 1) / 2;
173            if self.data[i] < self.data[parent] {
174                self.data.swap(i, parent);
175                i = parent;
176            } else {
177                break;
178            }
179        }
180    }
181    fn sift_down(&mut self, mut i: usize) {
182        let n = self.data.len();
183        loop {
184            let left = 2 * i + 1;
185            let right = 2 * i + 2;
186            let mut smallest = i;
187            if left < n && self.data[left] < self.data[smallest] {
188                smallest = left;
189            }
190            if right < n && self.data[right] < self.data[smallest] {
191                smallest = right;
192            }
193            if smallest == i {
194                break;
195            }
196            self.data.swap(i, smallest);
197            i = smallest;
198        }
199    }
200}
201/// A reusable scratch buffer for path computations.
202#[allow(dead_code)]
203pub struct PathBuf {
204    components: Vec<String>,
205}
206#[allow(dead_code)]
207impl PathBuf {
208    /// Creates a new empty path buffer.
209    pub fn new() -> Self {
210        Self {
211            components: Vec::new(),
212        }
213    }
214    /// Pushes a component.
215    pub fn push(&mut self, comp: impl Into<String>) {
216        self.components.push(comp.into());
217    }
218    /// Pops the last component.
219    pub fn pop(&mut self) {
220        self.components.pop();
221    }
222    /// Returns the current path as a `/`-separated string.
223    pub fn as_str(&self) -> String {
224        self.components.join("/")
225    }
226    /// Returns the depth of the path.
227    pub fn depth(&self) -> usize {
228        self.components.len()
229    }
230    /// Clears the path.
231    pub fn clear(&mut self) {
232        self.components.clear();
233    }
234}
235/// A flat list of substitution pairs `(from, to)`.
236#[allow(dead_code)]
237pub struct FlatSubstitution {
238    pairs: Vec<(String, String)>,
239}
240#[allow(dead_code)]
241impl FlatSubstitution {
242    /// Creates an empty substitution.
243    pub fn new() -> Self {
244        Self { pairs: Vec::new() }
245    }
246    /// Adds a pair.
247    pub fn add(&mut self, from: impl Into<String>, to: impl Into<String>) {
248        self.pairs.push((from.into(), to.into()));
249    }
250    /// Applies all substitutions to `s` (leftmost-first order).
251    pub fn apply(&self, s: &str) -> String {
252        let mut result = s.to_string();
253        for (from, to) in &self.pairs {
254            result = result.replace(from.as_str(), to.as_str());
255        }
256        result
257    }
258    /// Returns the number of pairs.
259    pub fn len(&self) -> usize {
260        self.pairs.len()
261    }
262    /// Returns `true` if empty.
263    pub fn is_empty(&self) -> bool {
264        self.pairs.is_empty()
265    }
266}
267/// A simple directed acyclic graph.
268#[allow(dead_code)]
269pub struct SimpleDag {
270    /// `edges[i]` is the list of direct successors of node `i`.
271    edges: Vec<Vec<usize>>,
272}
273#[allow(dead_code)]
274impl SimpleDag {
275    /// Creates a DAG with `n` nodes and no edges.
276    pub fn new(n: usize) -> Self {
277        Self {
278            edges: vec![Vec::new(); n],
279        }
280    }
281    /// Adds an edge from `from` to `to`.
282    pub fn add_edge(&mut self, from: usize, to: usize) {
283        if from < self.edges.len() {
284            self.edges[from].push(to);
285        }
286    }
287    /// Returns the successors of `node`.
288    pub fn successors(&self, node: usize) -> &[usize] {
289        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
290    }
291    /// Returns `true` if `from` can reach `to` via DFS.
292    pub fn can_reach(&self, from: usize, to: usize) -> bool {
293        let mut visited = vec![false; self.edges.len()];
294        self.dfs(from, to, &mut visited)
295    }
296    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
297        if cur == target {
298            return true;
299        }
300        if cur >= visited.len() || visited[cur] {
301            return false;
302        }
303        visited[cur] = true;
304        for &next in self.successors(cur) {
305            if self.dfs(next, target, visited) {
306                return true;
307            }
308        }
309        false
310    }
311    /// Returns the topological order of nodes, or `None` if a cycle is detected.
312    pub fn topological_sort(&self) -> Option<Vec<usize>> {
313        let n = self.edges.len();
314        let mut in_degree = vec![0usize; n];
315        for succs in &self.edges {
316            for &s in succs {
317                if s < n {
318                    in_degree[s] += 1;
319                }
320            }
321        }
322        let mut queue: std::collections::VecDeque<usize> =
323            (0..n).filter(|&i| in_degree[i] == 0).collect();
324        let mut order = Vec::new();
325        while let Some(node) = queue.pop_front() {
326            order.push(node);
327            for &s in self.successors(node) {
328                if s < n {
329                    in_degree[s] -= 1;
330                    if in_degree[s] == 0 {
331                        queue.push_back(s);
332                    }
333                }
334            }
335        }
336        if order.len() == n {
337            Some(order)
338        } else {
339            None
340        }
341    }
342    /// Returns the number of nodes.
343    pub fn num_nodes(&self) -> usize {
344        self.edges.len()
345    }
346}
347/// A type-erased function pointer with arity tracking.
348#[allow(dead_code)]
349pub struct RawFnPtr {
350    /// The raw function pointer (stored as usize for type erasure).
351    ptr: usize,
352    arity: usize,
353    name: String,
354}
355#[allow(dead_code)]
356impl RawFnPtr {
357    /// Creates a new raw function pointer descriptor.
358    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
359        Self {
360            ptr,
361            arity,
362            name: name.into(),
363        }
364    }
365    /// Returns the arity.
366    pub fn arity(&self) -> usize {
367        self.arity
368    }
369    /// Returns the name.
370    pub fn name(&self) -> &str {
371        &self.name
372    }
373    /// Returns the raw pointer value.
374    pub fn raw(&self) -> usize {
375        self.ptr
376    }
377}
378/// A sparse vector: stores only non-default elements.
379#[allow(dead_code)]
380pub struct SparseVec<T: Default + Clone + PartialEq> {
381    entries: std::collections::HashMap<usize, T>,
382    default_: T,
383    logical_len: usize,
384}
385#[allow(dead_code)]
386impl<T: Default + Clone + PartialEq> SparseVec<T> {
387    /// Creates a new sparse vector with logical length `len`.
388    pub fn new(len: usize) -> Self {
389        Self {
390            entries: std::collections::HashMap::new(),
391            default_: T::default(),
392            logical_len: len,
393        }
394    }
395    /// Sets element at `idx`.
396    pub fn set(&mut self, idx: usize, val: T) {
397        if val == self.default_ {
398            self.entries.remove(&idx);
399        } else {
400            self.entries.insert(idx, val);
401        }
402    }
403    /// Gets element at `idx`.
404    pub fn get(&self, idx: usize) -> &T {
405        self.entries.get(&idx).unwrap_or(&self.default_)
406    }
407    /// Returns the logical length.
408    pub fn len(&self) -> usize {
409        self.logical_len
410    }
411    /// Returns whether the collection is empty.
412    pub fn is_empty(&self) -> bool {
413        self.len() == 0
414    }
415    /// Returns the number of non-default elements.
416    pub fn nnz(&self) -> usize {
417        self.entries.len()
418    }
419}
420/// Binder display annotation.
421///
422/// Determines how arguments are displayed and inferred.
423#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
424pub enum BinderInfo {
425    /// Default explicit argument: (x : α)
426    Default,
427    /// Implicit argument: {x : α}
428    Implicit,
429    /// Strict implicit: ⦃x : α⦄
430    StrictImplicit,
431    /// Instance implicit (type class): [x : α]
432    InstImplicit,
433}
434impl BinderInfo {
435    /// Check if this is an explicit (default) binder.
436    pub fn is_explicit(self) -> bool {
437        matches!(self, BinderInfo::Default)
438    }
439    /// Check if this is an implicit binder.
440    pub fn is_implicit(self) -> bool {
441        matches!(self, BinderInfo::Implicit | BinderInfo::StrictImplicit)
442    }
443    /// Check if this is an instance-implicit binder.
444    pub fn is_inst_implicit(self) -> bool {
445        matches!(self, BinderInfo::InstImplicit)
446    }
447    /// Return the opening delimiter for this binder.
448    pub fn open_delim(self) -> &'static str {
449        match self {
450            BinderInfo::Default => "(",
451            BinderInfo::Implicit => "{",
452            BinderInfo::StrictImplicit => "⦃",
453            BinderInfo::InstImplicit => "[",
454        }
455    }
456    /// Return the closing delimiter for this binder.
457    pub fn close_delim(self) -> &'static str {
458        match self {
459            BinderInfo::Default => ")",
460            BinderInfo::Implicit => "}",
461            BinderInfo::StrictImplicit => "⦄",
462            BinderInfo::InstImplicit => "]",
463        }
464    }
465}
466/// Unique identifier for free variables.
467///
468/// Free variables are used during elaboration and type checking to represent
469/// local hypotheses.
470#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
471pub struct FVarId(pub u64);
472impl FVarId {
473    /// Create a new free variable ID.
474    pub fn new(id: u64) -> Self {
475        FVarId(id)
476    }
477}
478impl FVarId {
479    /// Return the underlying integer ID.
480    pub fn raw(self) -> u64 {
481        self.0
482    }
483}
484/// A label set for a graph node.
485#[allow(dead_code)]
486pub struct LabelSet {
487    labels: Vec<String>,
488}
489#[allow(dead_code)]
490impl LabelSet {
491    /// Creates a new empty label set.
492    pub fn new() -> Self {
493        Self { labels: Vec::new() }
494    }
495    /// Adds a label (deduplicates).
496    pub fn add(&mut self, label: impl Into<String>) {
497        let s = label.into();
498        if !self.labels.contains(&s) {
499            self.labels.push(s);
500        }
501    }
502    /// Returns `true` if `label` is present.
503    pub fn has(&self, label: &str) -> bool {
504        self.labels.iter().any(|l| l == label)
505    }
506    /// Returns the count of labels.
507    pub fn count(&self) -> usize {
508        self.labels.len()
509    }
510    /// Returns all labels.
511    pub fn all(&self) -> &[String] {
512        &self.labels
513    }
514}
515/// A simple decision tree node for rule dispatching.
516#[allow(dead_code)]
517#[allow(missing_docs)]
518pub enum DecisionNode {
519    /// A leaf with an action string.
520    Leaf(String),
521    /// An interior node: check `key` equals `val` → `yes_branch`, else `no_branch`.
522    Branch {
523        key: String,
524        val: String,
525        yes_branch: Box<DecisionNode>,
526        no_branch: Box<DecisionNode>,
527    },
528}
529#[allow(dead_code)]
530impl DecisionNode {
531    /// Evaluates the decision tree with the given context.
532    pub fn evaluate(&self, ctx: &std::collections::HashMap<String, String>) -> &str {
533        match self {
534            DecisionNode::Leaf(action) => action.as_str(),
535            DecisionNode::Branch {
536                key,
537                val,
538                yes_branch,
539                no_branch,
540            } => {
541                let actual = ctx.get(key).map(|s| s.as_str()).unwrap_or("");
542                if actual == val.as_str() {
543                    yes_branch.evaluate(ctx)
544                } else {
545                    no_branch.evaluate(ctx)
546                }
547            }
548        }
549    }
550    /// Returns the depth of the decision tree.
551    pub fn depth(&self) -> usize {
552        match self {
553            DecisionNode::Leaf(_) => 0,
554            DecisionNode::Branch {
555                yes_branch,
556                no_branch,
557                ..
558            } => 1 + yes_branch.depth().max(no_branch.depth()),
559        }
560    }
561}
562/// Represents a rewrite rule `lhs → rhs`.
563#[allow(dead_code)]
564#[allow(missing_docs)]
565pub struct RewriteRule {
566    /// The name of the rule.
567    pub name: String,
568    /// A string representation of the LHS pattern.
569    pub lhs: String,
570    /// A string representation of the RHS.
571    pub rhs: String,
572    /// Whether this is a conditional rule (has side conditions).
573    pub conditional: bool,
574}
575#[allow(dead_code)]
576impl RewriteRule {
577    /// Creates an unconditional rewrite rule.
578    pub fn unconditional(
579        name: impl Into<String>,
580        lhs: impl Into<String>,
581        rhs: impl Into<String>,
582    ) -> Self {
583        Self {
584            name: name.into(),
585            lhs: lhs.into(),
586            rhs: rhs.into(),
587            conditional: false,
588        }
589    }
590    /// Creates a conditional rewrite rule.
591    pub fn conditional(
592        name: impl Into<String>,
593        lhs: impl Into<String>,
594        rhs: impl Into<String>,
595    ) -> Self {
596        Self {
597            name: name.into(),
598            lhs: lhs.into(),
599            rhs: rhs.into(),
600            conditional: true,
601        }
602    }
603    /// Returns a textual representation.
604    pub fn display(&self) -> String {
605        format!("{}: {} → {}", self.name, self.lhs, self.rhs)
606    }
607}
608/// A hierarchical configuration tree.
609#[allow(dead_code)]
610pub struct ConfigNode {
611    key: String,
612    value: Option<String>,
613    children: Vec<ConfigNode>,
614}
615#[allow(dead_code)]
616impl ConfigNode {
617    /// Creates a leaf config node with a value.
618    pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
619        Self {
620            key: key.into(),
621            value: Some(value.into()),
622            children: Vec::new(),
623        }
624    }
625    /// Creates a section node with children.
626    pub fn section(key: impl Into<String>) -> Self {
627        Self {
628            key: key.into(),
629            value: None,
630            children: Vec::new(),
631        }
632    }
633    /// Adds a child node.
634    pub fn add_child(&mut self, child: ConfigNode) {
635        self.children.push(child);
636    }
637    /// Returns the key.
638    pub fn key(&self) -> &str {
639        &self.key
640    }
641    /// Returns the value, or `None` for section nodes.
642    pub fn value(&self) -> Option<&str> {
643        self.value.as_deref()
644    }
645    /// Returns the number of children.
646    pub fn num_children(&self) -> usize {
647        self.children.len()
648    }
649    /// Looks up a dot-separated path.
650    pub fn lookup(&self, path: &str) -> Option<&str> {
651        let mut parts = path.splitn(2, '.');
652        let head = parts.next()?;
653        let tail = parts.next();
654        if head != self.key {
655            return None;
656        }
657        match tail {
658            None => self.value.as_deref(),
659            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
660        }
661    }
662    fn lookup_relative(&self, path: &str) -> Option<&str> {
663        let mut parts = path.splitn(2, '.');
664        let head = parts.next()?;
665        let tail = parts.next();
666        if head != self.key {
667            return None;
668        }
669        match tail {
670            None => self.value.as_deref(),
671            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
672        }
673    }
674}
675/// A set of rewrite rules.
676#[allow(dead_code)]
677pub struct RewriteRuleSet {
678    rules: Vec<RewriteRule>,
679}
680#[allow(dead_code)]
681impl RewriteRuleSet {
682    /// Creates an empty rule set.
683    pub fn new() -> Self {
684        Self { rules: Vec::new() }
685    }
686    /// Adds a rule.
687    pub fn add(&mut self, rule: RewriteRule) {
688        self.rules.push(rule);
689    }
690    /// Returns the number of rules.
691    pub fn len(&self) -> usize {
692        self.rules.len()
693    }
694    /// Returns `true` if the set is empty.
695    pub fn is_empty(&self) -> bool {
696        self.rules.is_empty()
697    }
698    /// Returns all conditional rules.
699    pub fn conditional_rules(&self) -> Vec<&RewriteRule> {
700        self.rules.iter().filter(|r| r.conditional).collect()
701    }
702    /// Returns all unconditional rules.
703    pub fn unconditional_rules(&self) -> Vec<&RewriteRule> {
704        self.rules.iter().filter(|r| !r.conditional).collect()
705    }
706    /// Looks up a rule by name.
707    pub fn get(&self, name: &str) -> Option<&RewriteRule> {
708        self.rules.iter().find(|r| r.name == name)
709    }
710}
711/// A counter that can measure elapsed time between snapshots.
712#[allow(dead_code)]
713pub struct Stopwatch {
714    start: crate::wall_clock::Instant,
715    splits: Vec<f64>,
716}
717#[allow(dead_code)]
718impl Stopwatch {
719    /// Creates and starts a new stopwatch.
720    pub fn start() -> Self {
721        Self {
722            start: crate::wall_clock::Instant::now(),
723            splits: Vec::new(),
724        }
725    }
726    /// Records a split time (elapsed since start).
727    pub fn split(&mut self) {
728        self.splits.push(self.elapsed_ms());
729    }
730    /// Returns total elapsed milliseconds since start.
731    pub fn elapsed_ms(&self) -> f64 {
732        self.start.elapsed().as_secs_f64() * 1000.0
733    }
734    /// Returns all recorded split times.
735    pub fn splits(&self) -> &[f64] {
736        &self.splits
737    }
738    /// Returns the number of splits.
739    pub fn num_splits(&self) -> usize {
740        self.splits.len()
741    }
742}
743/// A write-once cell.
744#[allow(dead_code)]
745pub struct WriteOnce<T> {
746    value: std::cell::Cell<Option<T>>,
747}
748#[allow(dead_code)]
749impl<T: Copy> WriteOnce<T> {
750    /// Creates an empty write-once cell.
751    pub fn new() -> Self {
752        Self {
753            value: std::cell::Cell::new(None),
754        }
755    }
756    /// Writes a value.  Returns `false` if already written.
757    pub fn write(&self, val: T) -> bool {
758        if self.value.get().is_some() {
759            return false;
760        }
761        self.value.set(Some(val));
762        true
763    }
764    /// Returns the value if written.
765    pub fn read(&self) -> Option<T> {
766        self.value.get()
767    }
768    /// Returns `true` if the value has been written.
769    pub fn is_written(&self) -> bool {
770        self.value.get().is_some()
771    }
772}
773/// A generic counter that tracks min/max/sum for statistical summaries.
774#[allow(dead_code)]
775pub struct StatSummary {
776    count: u64,
777    sum: f64,
778    min: f64,
779    max: f64,
780}
781#[allow(dead_code)]
782impl StatSummary {
783    /// Creates an empty summary.
784    pub fn new() -> Self {
785        Self {
786            count: 0,
787            sum: 0.0,
788            min: f64::INFINITY,
789            max: f64::NEG_INFINITY,
790        }
791    }
792    /// Records a sample.
793    pub fn record(&mut self, val: f64) {
794        self.count += 1;
795        self.sum += val;
796        if val < self.min {
797            self.min = val;
798        }
799        if val > self.max {
800            self.max = val;
801        }
802    }
803    /// Returns the mean, or `None` if no samples.
804    pub fn mean(&self) -> Option<f64> {
805        if self.count == 0 {
806            None
807        } else {
808            Some(self.sum / self.count as f64)
809        }
810    }
811    /// Returns the minimum, or `None` if no samples.
812    pub fn min(&self) -> Option<f64> {
813        if self.count == 0 {
814            None
815        } else {
816            Some(self.min)
817        }
818    }
819    /// Returns the maximum, or `None` if no samples.
820    pub fn max(&self) -> Option<f64> {
821        if self.count == 0 {
822            None
823        } else {
824            Some(self.max)
825        }
826    }
827    /// Returns the count of recorded samples.
828    pub fn count(&self) -> u64 {
829        self.count
830    }
831}
832/// Literal values supported natively.
833///
834/// Exactly the two literal kinds of the Lean 4 kernel: arbitrary-precision
835/// natural numbers (`natVal`) and UTF-8 strings (`strVal`). There is
836/// deliberately **no** integer literal — Lean encodes integers as
837/// `Int.ofNat` / `Int.negSucc` over Nat literals, and a signed fixed-width
838/// literal inside the TCB previously carried wrapping arithmetic.
839#[derive(Clone, PartialEq, Eq, Hash, Debug)]
840pub enum Literal {
841    /// Natural number literal (arbitrary precision).
842    Nat(crate::bignat::BigNat),
843    /// String literal.
844    Str(String),
845}
846impl Literal {
847    /// Build a Nat literal from a small value (ergonomic constructor).
848    pub fn nat(n: u64) -> Self {
849        Literal::Nat(crate::bignat::BigNat::from(n))
850    }
851    /// Build a String literal.
852    pub fn string(s: impl Into<String>) -> Self {
853        Literal::Str(s.into())
854    }
855    /// Return true if this is a natural number literal.
856    pub fn is_nat(&self) -> bool {
857        matches!(self, Literal::Nat(_))
858    }
859    /// Return true if this is a string literal.
860    pub fn is_str(&self) -> bool {
861        matches!(self, Literal::Str(_))
862    }
863    /// Extract the natural number value if it fits in `u64` (best effort;
864    /// returns `None` for larger values — use [`Literal::as_bignat`] for the
865    /// exact value).
866    pub fn as_nat(&self) -> Option<u64> {
867        if let Literal::Nat(n) = self {
868            n.to_u64()
869        } else {
870            None
871        }
872    }
873    /// Extract the exact natural number value, or `None`.
874    pub fn as_bignat(&self) -> Option<&crate::bignat::BigNat> {
875        if let Literal::Nat(n) = self {
876            Some(n)
877        } else {
878            None
879        }
880    }
881    /// Extract the string value, or `None`.
882    pub fn as_str(&self) -> Option<&str> {
883        if let Literal::Str(s) = self {
884            Some(s.as_str())
885        } else {
886            None
887        }
888    }
889    /// Return the Lean type name for this literal.
890    pub fn type_name(&self) -> &'static str {
891        match self {
892            Literal::Nat(_) => "Nat",
893            Literal::Str(_) => "String",
894        }
895    }
896}
897impl From<crate::bignat::BigNat> for Literal {
898    fn from(n: crate::bignat::BigNat) -> Self {
899        Literal::Nat(n)
900    }
901}
902impl From<u64> for Literal {
903    fn from(n: u64) -> Self {
904        Literal::nat(n)
905    }
906}
907/// A window iterator that yields overlapping windows of size `n`.
908#[allow(dead_code)]
909pub struct WindowIterator<'a, T> {
910    pub(super) data: &'a [T],
911    pub(super) pos: usize,
912    pub(super) window: usize,
913}
914#[allow(dead_code)]
915impl<'a, T> WindowIterator<'a, T> {
916    /// Creates a new window iterator.
917    pub fn new(data: &'a [T], window: usize) -> Self {
918        Self {
919            data,
920            pos: 0,
921            window,
922        }
923    }
924}
925/// A simple stack-based calculator for arithmetic expressions.
926#[allow(dead_code)]
927pub struct StackCalc {
928    stack: Vec<i64>,
929}
930#[allow(dead_code)]
931impl StackCalc {
932    /// Creates a new empty calculator.
933    pub fn new() -> Self {
934        Self { stack: Vec::new() }
935    }
936    /// Pushes an integer literal.
937    pub fn push(&mut self, n: i64) {
938        self.stack.push(n);
939    }
940    /// Replaces the top two values, `b` on top of `a`, with `op(a, b)` and
941    /// returns it.  Returns `None`, leaving the stack unchanged, if it holds
942    /// fewer than two values.
943    fn apply_binary(&mut self, op: impl FnOnce(i64, i64) -> i64) -> Option<i64> {
944        let (rest_len, a, b) = match self.stack[..] {
945            [ref rest @ .., a, b] => (rest.len(), a, b),
946            _ => return None,
947        };
948        let value = op(a, b);
949        self.stack.truncate(rest_len);
950        self.stack.push(value);
951        Some(value)
952    }
953    /// Adds the top two values and returns the sum, which replaces them.
954    /// Returns `None`, leaving the stack unchanged, if it holds fewer than
955    /// two values.
956    pub fn add(&mut self) -> Option<i64> {
957        self.apply_binary(|a, b| a + b)
958    }
959    /// Subtracts top from second and returns the difference, which replaces
960    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
961    /// than two values.
962    pub fn sub(&mut self) -> Option<i64> {
963        self.apply_binary(|a, b| a - b)
964    }
965    /// Multiplies the top two values and returns the product, which replaces
966    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
967    /// than two values.
968    pub fn mul(&mut self) -> Option<i64> {
969        self.apply_binary(|a, b| a * b)
970    }
971    /// Peeks the top value.
972    pub fn peek(&self) -> Option<i64> {
973        self.stack.last().copied()
974    }
975    /// Returns the stack depth.
976    pub fn depth(&self) -> usize {
977        self.stack.len()
978    }
979}
980/// A pool of reusable string buffers.
981#[allow(dead_code)]
982pub struct StringPool {
983    free: Vec<String>,
984}
985#[allow(dead_code)]
986impl StringPool {
987    /// Creates a new empty string pool.
988    pub fn new() -> Self {
989        Self { free: Vec::new() }
990    }
991    /// Takes a string from the pool (may be empty).
992    pub fn take(&mut self) -> String {
993        self.free.pop().unwrap_or_default()
994    }
995    /// Returns a string to the pool.
996    pub fn give(&mut self, mut s: String) {
997        s.clear();
998        self.free.push(s);
999    }
1000    /// Returns the number of free strings in the pool.
1001    pub fn free_count(&self) -> usize {
1002        self.free.len()
1003    }
1004}
1005/// A child edge in an [`Expr`]: a reference-counted subterm together with
1006/// the facts about it that the kernel needs in O(1) (structural sharing,
1007/// wave5 Stage E; the hash and the three flags added for 0.1.4).
1008///
1009/// * `range` — the subterm's `looseBVarRange`: `1 + the largest loose de Bruijn
1010///   index in the subterm` (0 = closed). It lets a substitution or lift decide
1011///   in O(1) whether a subtree is affected — `range <= depth` means "no loose
1012///   bvar can be touched, share it unchanged" — without traversing the subtree.
1013/// * `cost` — the size of the subterm counted as a TREE (a shared subterm once
1014///   per path to it), saturating at [`REBUILD_COST_MAX`]: one unit per node
1015///   plus one more per leaf, so `cost(leaf) = 2` and
1016///   `cost(internal) = 1 + Σ cost(children)`. The reduction and def-eq caches
1017///   read it to keep giant terms out (`expr_util::expr_size_within`). Through
1018///   0.1.4's first performance pass this was also what a substitution charged
1019///   to the per-declaration budget for a subtree it shared instead of
1020///   rebuilding; sharing a subtree now charges one unit (see [`crate::fuel`]).
1021/// * `hash` — the hash of the subterm's structure (the kernel-private
1022///   `structural_hash` module documents it). `Hash` writes it instead of
1023///   walking the subterm, and `==` uses it as a fast negative test.
1024/// * three flags — whether the subterm contains a free variable, a universe
1025///   parameter, a universe metavariable. They let the operations that only
1026///   concern those (abstracting a free variable, instantiating universe
1027///   parameters, the universe hygiene check of a declaration) pass over
1028///   everything else without visiting it.
1029///
1030/// All of them are computed once, bottom-up, in [`Node::new`] from the
1031/// subterm's own immediate children (also `Node`s), so construction stays
1032/// O(nodes) and none of them ever needs a traversal.
1033///
1034/// # Layout
1035///
1036/// The facts live in the shared allocation, next to the subterm; a `Node` is
1037/// one pointer. (Through 0.1.3 `range` and `cost` sat on the edge beside the
1038/// pointer, and the allocation held the bare `Expr`.) An allocation is the
1039/// two reference counts, 16 bytes of facts and a 40-byte `Expr` — 72 bytes
1040/// where the old layout used 80, with the hash and the flags added (the
1041/// flags share a word with `cost`) — and an `Expr` held by value, as every
1042/// cache key is, is 40 bytes where it was 64. Reading a fact dereferences the
1043/// edge, which costs nothing extra where it matters: a substitution that
1044/// skips a subtree shares it by bumping the reference count stored right
1045/// beside the facts, and one that does not skip it walks into it.
1046///
1047/// `Node` is a transparent wrapper: it `Deref`s to the inner [`Expr`], and its
1048/// `PartialEq`/`Eq`/`Hash` are those of the subterm (so the instances on
1049/// `Expr` keep exactly their structural meaning). The cached facts are
1050/// functions of the subterm and so never change what is equal to what.
1051#[derive(Clone)]
1052pub struct Node {
1053    rc: Rc<Shared>,
1054}
1055
1056/// The shared allocation behind a [`Node`]: a subterm and the facts cached
1057/// about it.
1058///
1059/// Invariant: `range` and `hash` are `expr.range()` and
1060/// `expr.structural_hash()`, and `meta` is `expr.rebuild_cost()` in its low
1061/// 29 bits with `expr.flags()` above them. They are set in [`Node::new`], the
1062/// only place a `Shared` is built, and never changed — a `Node` gives no
1063/// mutable access to its allocation.
1064struct Shared {
1065    range: u32,
1066    /// The tree size (`cost`, at most [`REBUILD_COST_MAX`]) and the three
1067    /// content flags, in one word.
1068    meta: u32,
1069    hash: u64,
1070    /// `expr.depth()`, in a build that tracks the depth of terms
1071    /// ([`crate::fuel::TERM_DEPTH_TRACKED`]).
1072    #[cfg(feature = "term-depth")]
1073    depth: u32,
1074    expr: Expr,
1075}
1076
1077/// The largest value [`Expr::rebuild_cost`] and [`Node::rebuild_cost`]
1078/// report: the count saturates here. (It shares a 32-bit word with the three
1079/// content flags, which take the bits above it — see the `node_facts` module
1080/// of this directory; no cache keeps a term anywhere near this size.)
1081pub const REBUILD_COST_MAX: u32 = (1 << 29) - 1;
1082
1083impl Node {
1084    /// Wrap an [`Expr`] as a child edge, caching its `looseBVarRange`, its
1085    /// tree size, its content flags and the hash of its structure. O(1): each
1086    /// is read off the cached facts of `e`'s immediate children (for a `Sort`
1087    /// or a `Const`, off its universe levels).
1088    ///
1089    /// In a build that tracks the depth of terms
1090    /// ([`crate::fuel::TERM_DEPTH_TRACKED`]) the depth is cached too, and a
1091    /// subterm deeper than the limit of
1092    /// [`crate::fuel::set_term_depth_limit`] spends the budget of the
1093    /// declaration being checked.
1094    #[inline]
1095    pub fn new(e: Expr) -> Node {
1096        #[cfg(feature = "term-depth")]
1097        let depth = {
1098            let depth = e.depth();
1099            crate::fuel::note_term_depth(depth);
1100            depth
1101        };
1102        Node {
1103            rc: Rc::new(Shared {
1104                range: e.range(),
1105                meta: e.rebuild_cost() | e.flags(),
1106                hash: e.structural_hash(),
1107                #[cfg(feature = "term-depth")]
1108                depth,
1109                expr: e,
1110            }),
1111        }
1112    }
1113
1114    /// The cached depth of this subterm (O(1)); see [`Expr::depth`].
1115    #[cfg(feature = "term-depth")]
1116    #[inline]
1117    #[must_use]
1118    pub(crate) fn depth(&self) -> u32 {
1119        self.rc.depth
1120    }
1121
1122    /// The cached `looseBVarRange` of this subterm (O(1)).
1123    #[inline]
1124    #[must_use]
1125    pub fn range(&self) -> u32 {
1126        self.rc.range
1127    }
1128
1129    /// The cached size of this subterm counted as a tree (O(1)); see
1130    /// [`Expr::rebuild_cost`].
1131    #[inline]
1132    #[must_use]
1133    pub fn rebuild_cost(&self) -> u32 {
1134        self.rc.meta & REBUILD_COST_MAX
1135    }
1136
1137    /// The cached content flags of this subterm (O(1)): the bits of the
1138    /// shared word above the tree size.
1139    #[inline]
1140    #[must_use]
1141    pub(super) fn flags(&self) -> u32 {
1142        self.rc.meta & !REBUILD_COST_MAX
1143    }
1144
1145    /// Whether this allocation has more than one owner — another parent in
1146    /// the same term, a cache, another term. A subterm that is reached twice
1147    /// while one term is walked always has; the walks that rebuild a term
1148    /// keep the result for such a subterm and reuse it the second time, so a
1149    /// shared subterm stays shared in what they build.
1150    #[inline]
1151    #[must_use]
1152    pub(crate) fn is_shared(&self) -> bool {
1153        Rc::strong_count(&self.rc) > 1
1154    }
1155
1156    /// The cached hash of this subterm's structure (O(1)): the value
1157    /// [`Expr::structural_hash`] computes for the subterm.
1158    #[inline]
1159    #[must_use]
1160    pub(crate) fn structural_hash(&self) -> u64 {
1161        self.rc.hash
1162    }
1163
1164    /// The address of the shared subterm. Two nodes return the same address
1165    /// exactly when they share one allocation, for as long as either is
1166    /// alive — so the address can key a memo table over a term whose subterms
1167    /// are shared, visiting each shared subterm once.
1168    ///
1169    /// The pointer is for comparison only. (This replaces `rc()`, which
1170    /// exposed the `Rc<Expr>` the edge used to hold.)
1171    #[inline]
1172    #[must_use]
1173    pub fn as_ptr(&self) -> *const Expr {
1174        &self.rc.expr
1175    }
1176
1177    /// Whether two nodes share one allocation (which implies they are equal;
1178    /// equal nodes need not share one).
1179    #[inline]
1180    #[must_use]
1181    pub fn ptr_eq(this: &Node, other: &Node) -> bool {
1182        Rc::ptr_eq(&this.rc, &other.rc)
1183    }
1184}
1185
1186impl std::ops::Deref for Node {
1187    type Target = Expr;
1188    #[inline]
1189    fn deref(&self) -> &Expr {
1190        &self.rc.expr
1191    }
1192}
1193
1194impl PartialEq for Node {
1195    /// Structural equality of the two subterms, decided without comparing
1196    /// them when it can be: one allocation is equal to itself, and two
1197    /// subterms whose cached hashes differ are different (equal terms always
1198    /// hash equal). Only when the hashes agree are the subterms compared.
1199    #[inline]
1200    fn eq(&self, other: &Node) -> bool {
1201        Rc::ptr_eq(&self.rc, &other.rc)
1202            || (self.rc.hash == other.rc.hash && self.rc.expr == other.rc.expr)
1203    }
1204}
1205impl Eq for Node {}
1206impl std::hash::Hash for Node {
1207    /// Writes the cached structural hash: what `Expr`'s `Hash` writes for the
1208    /// subterm (as `Borrow<Expr>` requires), without walking it.
1209    #[inline]
1210    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1211        state.write_u64(self.rc.hash);
1212    }
1213}
1214impl std::fmt::Debug for Node {
1215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1216        std::fmt::Debug::fmt(&self.rc.expr, f)
1217    }
1218}
1219impl std::fmt::Display for Node {
1220    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1221        std::fmt::Display::fmt(&self.rc.expr, f)
1222    }
1223}
1224impl AsRef<Expr> for Node {
1225    #[inline]
1226    fn as_ref(&self) -> &Expr {
1227        &self.rc.expr
1228    }
1229}
1230impl std::borrow::Borrow<Expr> for Node {
1231    #[inline]
1232    fn borrow(&self) -> &Expr {
1233        &self.rc.expr
1234    }
1235}
1236
1237/// Core expression type.
1238///
1239/// This is the heart of the kernel. All terms in the type theory are
1240/// represented as `Expr` values.
1241///
1242/// Sub-expressions are held behind [`Node`]s (structural sharing, wave5): an
1243/// export DAG id referenced by many parents materializes to a single shared
1244/// node, and `clone` is an O(1) refcount bump rather than a deep copy. The
1245/// kernel is single-threaded (the verify pipeline runs on one spawned thread
1246/// and the wasm target is single-threaded), so the sharing is by `Rc` — not
1247/// `Arc`.
1248///
1249/// `Clone` is implemented by hand (not derived) so each clone charges one unit
1250/// of the deterministic per-declaration resource fuel — see [`crate::fuel`].
1251/// A clone copies this one node and shares its children (the sub-`clone`s are
1252/// refcount bumps), so the unit is for the call, not for the size of the
1253/// term; the walks that build new terms charge for the subterms they reach.
1254///
1255/// `Hash` is implemented by hand too: it writes the term's structural hash, one
1256/// 64-bit value computed from the hashes the children cache, where the derived
1257/// implementation walked the whole term. `PartialEq` is derived; the children
1258/// compare through [`Node`]'s equality, which consults the cached hashes
1259/// first.
1260#[derive(PartialEq, Eq, Debug)]
1261pub enum Expr {
1262    /// Sort u — universe.
1263    ///
1264    /// `Prop` is `Sort(Level::Zero)`.
1265    /// `Type` is `Sort(Level::Succ(Level::Zero))`.
1266    Sort(Level),
1267    /// Bound variable (de Bruijn index, 0-based).
1268    ///
1269    /// `BVar(0)` refers to the innermost binder.
1270    /// `BVar(n)` refers to the n-th enclosing binder.
1271    BVar(u32),
1272    /// Free variable (locally nameless).
1273    ///
1274    /// Used during type checking to represent local hypotheses.
1275    FVar(FVarId),
1276    /// Named constant with universe level instantiation.
1277    ///
1278    /// `Const(name, levels)` represents a global constant with its
1279    /// universe parameters instantiated.
1280    Const(Name, Vec<Level>),
1281    /// Function application: `f a`.
1282    ///
1283    /// Application is always binary. `f a b` is `App(App(f, a), b)`.
1284    App(Node, Node),
1285    /// Lambda abstraction: `λ (x : type), body`.
1286    Lam(BinderInfo, Name, Node, Node),
1287    /// Dependent function type: `Π (x : type), body`.
1288    ///
1289    /// Non-dependent arrows `A → B` are represented as
1290    /// `Pi(Default, _, A, B)` where the body doesn't use `BVar(0)`.
1291    Pi(BinderInfo, Name, Node, Node),
1292    /// Let binding: `let x : type := val in body`.
1293    Let(Name, Node, Node, Node),
1294    /// Literal value (Nat or String).
1295    Lit(Literal),
1296    /// Structure projection: `projName.idx struct`.
1297    Proj(Name, u32, Node),
1298}
1299
1300impl Expr {
1301    /// This node's `looseBVarRange` — `1 + the largest loose de Bruijn index`,
1302    /// or 0 if closed. O(1): it reads the immediate children's cached ranges
1303    /// (see [`Node`]) and combines them, accounting for the binders each child
1304    /// sits under.
1305    #[inline]
1306    #[must_use]
1307    pub fn range(&self) -> u32 {
1308        match self {
1309            Expr::BVar(n) => n.saturating_add(1),
1310            Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => 0,
1311            Expr::App(f, a) => f.range().max(a.range()),
1312            Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
1313                ty.range().max(body.range().saturating_sub(1))
1314            }
1315            Expr::Let(_, ty, val, body) => ty
1316                .range()
1317                .max(val.range())
1318                .max(body.range().saturating_sub(1)),
1319            Expr::Proj(_, _, inner) => inner.range(),
1320        }
1321    }
1322
1323    /// The size of this term counted as a TREE — a shared subterm once per
1324    /// path to it — O(1) from the children's cached sizes: one unit per node
1325    /// and one more per leaf, so `cost(leaf) = 2` and
1326    /// `cost(internal) = 1 + Σ cost(children)`, saturating at
1327    /// [`REBUILD_COST_MAX`]. It is what rebuilding the term node by node,
1328    /// with no sharing, would cost; the caches read it to keep giant terms
1329    /// out.
1330    #[inline]
1331    #[must_use]
1332    pub fn rebuild_cost(&self) -> u32 {
1333        // Each child cost is at most `REBUILD_COST_MAX` (2^29 - 1), so the sum
1334        // of up to three of them plus one cannot overflow a `u32`.
1335        let total = match self {
1336            Expr::Sort(_) | Expr::BVar(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => 2,
1337            Expr::App(f, a) => 1 + f.rebuild_cost() + a.rebuild_cost(),
1338            Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
1339                1 + ty.rebuild_cost() + body.rebuild_cost()
1340            }
1341            Expr::Let(_, ty, val, body) => {
1342                1 + ty.rebuild_cost() + val.rebuild_cost() + body.rebuild_cost()
1343            }
1344            Expr::Proj(_, _, inner) => 1 + inner.rebuild_cost(),
1345        };
1346        total.min(REBUILD_COST_MAX)
1347    }
1348
1349    /// The hash of this term's structure (see [`crate::structural_hash`]).
1350    ///
1351    /// O(1) in the size of the term: it combines this node's own fields with
1352    /// the hashes its children cache ([`Node`], `Name` and `Level` each store
1353    /// theirs), so nothing below the immediate children is visited. A literal
1354    /// is the one exception — its limbs or bytes are hashed in full, as there
1355    /// is nothing beneath a literal to cache them on.
1356    ///
1357    /// Every field `==` compares takes part — binder names and binder
1358    /// annotations included, since `Expr` equality distinguishes them — so two
1359    /// equal terms always have equal hashes, and the hash separates what the
1360    /// derived `Hash` separated.
1361    #[must_use]
1362    pub(crate) fn structural_hash(&self) -> u64 {
1363        use crate::structural_hash::{
1364            mix, mix_bytes, mix_words, EXPR_APP, EXPR_BVAR, EXPR_CONST, EXPR_FVAR, EXPR_LAM,
1365            EXPR_LET, EXPR_LIT_NAT, EXPR_LIT_STR, EXPR_PI, EXPR_PROJ, EXPR_SORT,
1366        };
1367        match self {
1368            Expr::Sort(level) => mix(EXPR_SORT, level.structural_hash()),
1369            Expr::BVar(index) => mix(EXPR_BVAR, u64::from(*index)),
1370            Expr::FVar(id) => mix(EXPR_FVAR, id.0),
1371            Expr::Const(name, levels) => levels
1372                .iter()
1373                .fold(mix(EXPR_CONST, name.structural_hash()), |hash, level| {
1374                    mix(hash, level.structural_hash())
1375                }),
1376            Expr::App(f, a) => mix(mix(EXPR_APP, f.structural_hash()), a.structural_hash()),
1377            Expr::Lam(bi, name, ty, body) => mix(
1378                mix(
1379                    mix(mix(EXPR_LAM, binder_info_code(*bi)), name.structural_hash()),
1380                    ty.structural_hash(),
1381                ),
1382                body.structural_hash(),
1383            ),
1384            Expr::Pi(bi, name, ty, body) => mix(
1385                mix(
1386                    mix(mix(EXPR_PI, binder_info_code(*bi)), name.structural_hash()),
1387                    ty.structural_hash(),
1388                ),
1389                body.structural_hash(),
1390            ),
1391            Expr::Let(name, ty, val, body) => mix(
1392                mix(
1393                    mix(mix(EXPR_LET, name.structural_hash()), ty.structural_hash()),
1394                    val.structural_hash(),
1395                ),
1396                body.structural_hash(),
1397            ),
1398            Expr::Lit(Literal::Nat(n)) => mix_words(EXPR_LIT_NAT, n.as_limbs()),
1399            Expr::Lit(Literal::Str(s)) => mix_bytes(EXPR_LIT_STR, s.as_bytes()),
1400            Expr::Proj(name, index, inner) => mix(
1401                mix(mix(EXPR_PROJ, name.structural_hash()), u64::from(*index)),
1402                inner.structural_hash(),
1403            ),
1404        }
1405    }
1406}
1407/// A distinct number for each binder annotation, for
1408/// [`Expr::structural_hash`].
1409const fn binder_info_code(bi: BinderInfo) -> u64 {
1410    match bi {
1411        BinderInfo::Default => 0,
1412        BinderInfo::Implicit => 1,
1413        BinderInfo::StrictImplicit => 2,
1414        BinderInfo::InstImplicit => 3,
1415    }
1416}
1417impl std::hash::Hash for Expr {
1418    /// Writes the term's structural hash — one 64-bit value, O(1) in the size
1419    /// of the term. Consistent with the derived `PartialEq`: the hash covers
1420    /// exactly the fields equality compares.
1421    #[inline]
1422    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1423        state.write_u64(self.structural_hash());
1424    }
1425}
1426impl Clone for Expr {
1427    fn clone(&self) -> Self {
1428        // One unit of resource fuel per clone: this node is copied, its
1429        // children are shared.
1430        crate::fuel::charge(1);
1431        match self {
1432            Expr::Sort(l) => Expr::Sort(l.clone()),
1433            Expr::BVar(n) => Expr::BVar(*n),
1434            Expr::FVar(id) => Expr::FVar(*id),
1435            Expr::Const(name, levels) => Expr::Const(name.clone(), levels.clone()),
1436            Expr::App(f, a) => Expr::App(f.clone(), a.clone()),
1437            Expr::Lam(bi, name, ty, body) => Expr::Lam(*bi, name.clone(), ty.clone(), body.clone()),
1438            Expr::Pi(bi, name, ty, body) => Expr::Pi(*bi, name.clone(), ty.clone(), body.clone()),
1439            Expr::Let(name, ty, val, body) => {
1440                Expr::Let(name.clone(), ty.clone(), val.clone(), body.clone())
1441            }
1442            Expr::Lit(l) => Expr::Lit(l.clone()),
1443            Expr::Proj(name, idx, inner) => Expr::Proj(name.clone(), *idx, inner.clone()),
1444        }
1445    }
1446}
1447impl Expr {
1448    /// Check if this is a Sort.
1449    pub fn is_sort(&self) -> bool {
1450        matches!(self, Expr::Sort(_))
1451    }
1452    /// Check if this is Prop.
1453    pub fn is_prop(&self) -> bool {
1454        matches!(self, Expr::Sort(l) if l.is_zero())
1455    }
1456    /// Check if this is a bound variable.
1457    pub fn is_bvar(&self) -> bool {
1458        matches!(self, Expr::BVar(_))
1459    }
1460    /// Check if this is a free variable.
1461    pub fn is_fvar(&self) -> bool {
1462        matches!(self, Expr::FVar(_))
1463    }
1464    /// Check if this is an application.
1465    pub fn is_app(&self) -> bool {
1466        matches!(self, Expr::App(_, _))
1467    }
1468    /// Check if this is a lambda.
1469    pub fn is_lambda(&self) -> bool {
1470        matches!(self, Expr::Lam(_, _, _, _))
1471    }
1472    /// Check if this is a Pi type.
1473    pub fn is_pi(&self) -> bool {
1474        matches!(self, Expr::Pi(_, _, _, _))
1475    }
1476}
1477impl Expr {
1478    /// Check if this expression is a let binding.
1479    pub fn is_let(&self) -> bool {
1480        matches!(self, Expr::Let(_, _, _, _))
1481    }
1482    /// Check if this expression is a literal.
1483    pub fn is_lit(&self) -> bool {
1484        matches!(self, Expr::Lit(_))
1485    }
1486    /// Check if this expression is a named constant.
1487    pub fn is_const(&self) -> bool {
1488        matches!(self, Expr::Const(_, _))
1489    }
1490    /// Check if this expression is a projection.
1491    pub fn is_proj(&self) -> bool {
1492        matches!(self, Expr::Proj(_, _, _))
1493    }
1494    /// Check if this expression is an "atom" (cannot be further reduced
1495    /// without context): Sort, BVar, FVar, Const, Lit.
1496    pub fn is_atom(&self) -> bool {
1497        matches!(
1498            self,
1499            Expr::Sort(_) | Expr::BVar(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_)
1500        )
1501    }
1502    /// Extract the bound variable index, or `None`.
1503    pub fn as_bvar(&self) -> Option<u32> {
1504        if let Expr::BVar(n) = self {
1505            Some(*n)
1506        } else {
1507            None
1508        }
1509    }
1510    /// Extract the free variable ID, or `None`.
1511    pub fn as_fvar(&self) -> Option<FVarId> {
1512        if let Expr::FVar(id) = self {
1513            Some(*id)
1514        } else {
1515            None
1516        }
1517    }
1518    /// Extract the constant name, or `None`.
1519    pub fn as_const_name(&self) -> Option<&Name> {
1520        if let Expr::Const(n, _) = self {
1521            Some(n)
1522        } else {
1523            None
1524        }
1525    }
1526    /// Extract the sort level, or `None`.
1527    pub fn as_sort_level(&self) -> Option<&Level> {
1528        if let Expr::Sort(l) = self {
1529            Some(l)
1530        } else {
1531            None
1532        }
1533    }
1534    /// Return the head and argument list of an application spine.
1535    ///
1536    /// For `f a b c`, returns `(&f, [&a, &b, &c])`.
1537    pub fn app_head_args(&self) -> (&Expr, Vec<&Expr>) {
1538        let mut args = Vec::new();
1539        let mut cur = self;
1540        while let Expr::App(f, a) = cur {
1541            args.push(a.as_ref());
1542            cur = f;
1543        }
1544        args.reverse();
1545        (cur, args)
1546    }
1547    /// Return the number of arguments in an application spine.
1548    pub fn app_arity(&self) -> usize {
1549        let mut n = 0;
1550        let mut cur = self;
1551        while let Expr::App(f, _) = cur {
1552            n += 1;
1553            cur = f;
1554        }
1555        n
1556    }
1557    /// Count the number of leading Pi binders.
1558    pub fn pi_arity(&self) -> usize {
1559        let mut n = 0;
1560        let mut cur = self;
1561        while let Expr::Pi(_, _, _, body) = cur {
1562            n += 1;
1563            cur = body;
1564        }
1565        n
1566    }
1567    /// Count the number of leading Lambda binders.
1568    pub fn lam_arity(&self) -> usize {
1569        let mut n = 0;
1570        let mut cur = self;
1571        while let Expr::Lam(_, _, _, body) = cur {
1572            n += 1;
1573            cur = body;
1574        }
1575        n
1576    }
1577    /// Compute the AST size (total number of nodes).
1578    pub fn size(&self) -> usize {
1579        match self {
1580            Expr::Sort(_) | Expr::BVar(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => 1,
1581            Expr::App(f, a) => 1 + f.size() + a.size(),
1582            Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => 1 + ty.size() + body.size(),
1583            Expr::Let(_, ty, val, body) => 1 + ty.size() + val.size() + body.size(),
1584            Expr::Proj(_, _, s) => 1 + s.size(),
1585        }
1586    }
1587    /// Compute the AST depth (longest path to a leaf).
1588    pub fn ast_depth(&self) -> usize {
1589        match self {
1590            Expr::Sort(_) | Expr::BVar(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => 0,
1591            Expr::App(f, a) => 1 + f.ast_depth().max(a.ast_depth()),
1592            Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
1593                1 + ty.ast_depth().max(body.ast_depth())
1594            }
1595            Expr::Let(_, ty, val, body) => {
1596                1 + ty.ast_depth().max(val.ast_depth()).max(body.ast_depth())
1597            }
1598            Expr::Proj(_, _, s) => 1 + s.ast_depth(),
1599        }
1600    }
1601    /// Apply this expression to a list of arguments.
1602    ///
1603    /// `e.mk_app_many(&[a, b, c])` returns `((e a) b) c`.
1604    pub fn mk_app_many(self, args: &[Expr]) -> Expr {
1605        args.iter().fold(self, |acc, a| {
1606            Expr::App(Node::new(acc), Node::new(a.clone()))
1607        })
1608    }
1609    /// Check if this expression has any loose bound variables at depth `d`.
1610    pub fn has_loose_bvar_at(&self, d: u32) -> bool {
1611        has_loose_bvar(self, d)
1612    }
1613    /// Count occurrences of bound variable `BVar(idx)` in this expression.
1614    pub fn count_bvar_occurrences(&self, idx: u32) -> usize {
1615        count_bvar_occ(self, idx)
1616    }
1617    /// Check if this expression is closed (no loose bound variables).
1618    pub fn is_closed(&self) -> bool {
1619        !has_loose_bvar(self, 0) && self.max_loose_bvar().is_none()
1620    }
1621    /// Find the maximum loose bound variable index, if any.
1622    fn max_loose_bvar(&self) -> Option<u32> {
1623        max_loose_bvar_impl(self, 0)
1624    }
1625    /// Collect all free variable IDs occurring in this expression.
1626    pub fn free_vars(&self) -> std::collections::HashSet<FVarId> {
1627        let mut set = std::collections::HashSet::new();
1628        collect_fvars(self, &mut set);
1629        set
1630    }
1631    /// Collect all constant names occurring in this expression.
1632    pub fn constants(&self) -> std::collections::HashSet<Name> {
1633        let mut set = std::collections::HashSet::new();
1634        collect_consts(self, &mut set);
1635        set
1636    }
1637}
1638/// A non-empty list (at least one element guaranteed).
1639#[allow(dead_code)]
1640pub struct NonEmptyVec<T> {
1641    head: T,
1642    tail: Vec<T>,
1643}
1644#[allow(dead_code)]
1645impl<T> NonEmptyVec<T> {
1646    /// Creates a non-empty vec with a single element.
1647    pub fn singleton(val: T) -> Self {
1648        Self {
1649            head: val,
1650            tail: Vec::new(),
1651        }
1652    }
1653    /// Pushes an element.
1654    pub fn push(&mut self, val: T) {
1655        self.tail.push(val);
1656    }
1657    /// Returns a reference to the first element.
1658    pub fn first(&self) -> &T {
1659        &self.head
1660    }
1661    /// Returns a reference to the last element.
1662    pub fn last(&self) -> &T {
1663        self.tail.last().unwrap_or(&self.head)
1664    }
1665    /// Returns the number of elements.
1666    pub fn len(&self) -> usize {
1667        1 + self.tail.len()
1668    }
1669    /// Returns whether the collection is empty.
1670    pub fn is_empty(&self) -> bool {
1671        self.len() == 0
1672    }
1673    /// Returns all elements as a Vec.
1674    pub fn to_vec(&self) -> Vec<&T> {
1675        let mut v = vec![&self.head];
1676        v.extend(self.tail.iter());
1677        v
1678    }
1679}
1680/// A simple mutable key-value store for test fixtures.
1681#[allow(dead_code)]
1682pub struct Fixture {
1683    data: std::collections::HashMap<String, String>,
1684}
1685#[allow(dead_code)]
1686impl Fixture {
1687    /// Creates an empty fixture.
1688    pub fn new() -> Self {
1689        Self {
1690            data: std::collections::HashMap::new(),
1691        }
1692    }
1693    /// Sets a key.
1694    pub fn set(&mut self, key: impl Into<String>, val: impl Into<String>) {
1695        self.data.insert(key.into(), val.into());
1696    }
1697    /// Gets a value.
1698    pub fn get(&self, key: &str) -> Option<&str> {
1699        self.data.get(key).map(|s| s.as_str())
1700    }
1701    /// Returns the number of entries.
1702    pub fn len(&self) -> usize {
1703        self.data.len()
1704    }
1705    /// Returns whether the collection is empty.
1706    pub fn is_empty(&self) -> bool {
1707        self.len() == 0
1708    }
1709}
1710/// Generator for fresh free variable IDs.
1711///
1712/// Produces a strictly increasing sequence of unique IDs.
1713#[derive(Debug, Default)]
1714pub struct FVarIdGen {
1715    next: u64,
1716}
1717impl FVarIdGen {
1718    /// Create a new generator starting at 0.
1719    pub fn new() -> Self {
1720        Self::default()
1721    }
1722    /// Produce the next fresh ID.
1723    pub fn fresh(&mut self) -> FVarId {
1724        let id = FVarId::new(self.next);
1725        self.next += 1;
1726        id
1727    }
1728    /// Peek at the next ID without consuming it.
1729    pub fn peek(&self) -> FVarId {
1730        FVarId::new(self.next)
1731    }
1732    /// Reset the generator.
1733    pub fn reset(&mut self) {
1734        self.next = 0;
1735    }
1736    /// Return the current counter value.
1737    pub fn current(&self) -> u64 {
1738        self.next
1739    }
1740}
1741/// A simple key-value store backed by a sorted Vec for small maps.
1742#[allow(dead_code)]
1743pub struct SmallMap<K: Ord + Clone, V: Clone> {
1744    entries: Vec<(K, V)>,
1745}
1746#[allow(dead_code)]
1747impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
1748    /// Creates a new empty small map.
1749    pub fn new() -> Self {
1750        Self {
1751            entries: Vec::new(),
1752        }
1753    }
1754    /// Inserts or replaces the value for `key`.
1755    pub fn insert(&mut self, key: K, val: V) {
1756        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
1757            Ok(i) => self.entries[i].1 = val,
1758            Err(i) => self.entries.insert(i, (key, val)),
1759        }
1760    }
1761    /// Returns the value for `key`, or `None`.
1762    pub fn get(&self, key: &K) -> Option<&V> {
1763        self.entries
1764            .binary_search_by_key(&key, |(k, _)| k)
1765            .ok()
1766            .map(|i| &self.entries[i].1)
1767    }
1768    /// Returns the number of entries.
1769    pub fn len(&self) -> usize {
1770        self.entries.len()
1771    }
1772    /// Returns `true` if empty.
1773    pub fn is_empty(&self) -> bool {
1774        self.entries.is_empty()
1775    }
1776    /// Returns all keys.
1777    pub fn keys(&self) -> Vec<&K> {
1778        self.entries.iter().map(|(k, _)| k).collect()
1779    }
1780    /// Returns all values.
1781    pub fn values(&self) -> Vec<&V> {
1782        self.entries.iter().map(|(_, v)| v).collect()
1783    }
1784}
1785/// A token bucket rate limiter.
1786#[allow(dead_code)]
1787pub struct TokenBucket {
1788    capacity: u64,
1789    tokens: u64,
1790    refill_per_ms: u64,
1791    last_refill: crate::wall_clock::Instant,
1792}
1793#[allow(dead_code)]
1794impl TokenBucket {
1795    /// Creates a new token bucket.
1796    pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
1797        Self {
1798            capacity,
1799            tokens: capacity,
1800            refill_per_ms,
1801            last_refill: crate::wall_clock::Instant::now(),
1802        }
1803    }
1804    /// Attempts to consume `n` tokens.  Returns `true` on success.
1805    pub fn try_consume(&mut self, n: u64) -> bool {
1806        self.refill();
1807        if self.tokens >= n {
1808            self.tokens -= n;
1809            true
1810        } else {
1811            false
1812        }
1813    }
1814    fn refill(&mut self) {
1815        let now = crate::wall_clock::Instant::now();
1816        let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
1817        if elapsed_ms > 0 {
1818            let new_tokens = elapsed_ms * self.refill_per_ms;
1819            self.tokens = (self.tokens + new_tokens).min(self.capacity);
1820            self.last_refill = now;
1821        }
1822    }
1823    /// Returns the number of currently available tokens.
1824    pub fn available(&self) -> u64 {
1825        self.tokens
1826    }
1827    /// Returns the bucket capacity.
1828    pub fn capacity(&self) -> u64 {
1829        self.capacity
1830    }
1831}
1832/// A fixed-size sliding window that computes a running sum.
1833#[allow(dead_code)]
1834pub struct SlidingSum {
1835    window: Vec<f64>,
1836    capacity: usize,
1837    pos: usize,
1838    sum: f64,
1839    count: usize,
1840}
1841#[allow(dead_code)]
1842impl SlidingSum {
1843    /// Creates a sliding sum with the given window size.
1844    pub fn new(capacity: usize) -> Self {
1845        Self {
1846            window: vec![0.0; capacity],
1847            capacity,
1848            pos: 0,
1849            sum: 0.0,
1850            count: 0,
1851        }
1852    }
1853    /// Adds a value to the window, removing the oldest if full.
1854    pub fn push(&mut self, val: f64) {
1855        let oldest = self.window[self.pos];
1856        self.sum -= oldest;
1857        self.sum += val;
1858        self.window[self.pos] = val;
1859        self.pos = (self.pos + 1) % self.capacity;
1860        if self.count < self.capacity {
1861            self.count += 1;
1862        }
1863    }
1864    /// Returns the current window sum.
1865    pub fn sum(&self) -> f64 {
1866        self.sum
1867    }
1868    /// Returns the window mean, or `None` if empty.
1869    pub fn mean(&self) -> Option<f64> {
1870        if self.count == 0 {
1871            None
1872        } else {
1873            Some(self.sum / self.count as f64)
1874        }
1875    }
1876    /// Returns the current window size (number of valid elements).
1877    pub fn count(&self) -> usize {
1878        self.count
1879    }
1880}
1881/// A versioned record that stores a history of values.
1882#[allow(dead_code)]
1883pub struct VersionedRecord<T: Clone> {
1884    /// Every earlier value, oldest first: version `n` is `earlier[n]`.
1885    earlier: Vec<T>,
1886    /// The latest value (version `earlier.len()`), declared last so it drops last.
1887    current: T,
1888}
1889#[allow(dead_code)]
1890impl<T: Clone> VersionedRecord<T> {
1891    /// Creates a new record with an initial value.
1892    pub fn new(initial: T) -> Self {
1893        Self {
1894            earlier: Vec::new(),
1895            current: initial,
1896        }
1897    }
1898    /// Updates the record with a new version.
1899    pub fn update(&mut self, val: T) {
1900        let previous = std::mem::replace(&mut self.current, val);
1901        self.earlier.push(previous);
1902    }
1903    /// Returns the current (latest) value.
1904    pub fn current(&self) -> &T {
1905        &self.current
1906    }
1907    /// Returns the value at version `n` (0-indexed), or `None`.
1908    pub fn at_version(&self, n: usize) -> Option<&T> {
1909        match self.earlier.get(n) {
1910            Some(value) => Some(value),
1911            None if n == self.earlier.len() => Some(&self.current),
1912            None => None,
1913        }
1914    }
1915    /// Returns the version number of the current value.
1916    pub fn version(&self) -> usize {
1917        self.earlier.len()
1918    }
1919    /// Returns `true` if more than one version exists.
1920    pub fn has_history(&self) -> bool {
1921        !self.earlier.is_empty()
1922    }
1923}
1924/// A mutable reference stack for tracking the current "focus" in a tree traversal.
1925#[allow(dead_code)]
1926pub struct FocusStack<T> {
1927    items: Vec<T>,
1928}
1929#[allow(dead_code)]
1930impl<T> FocusStack<T> {
1931    /// Creates an empty focus stack.
1932    pub fn new() -> Self {
1933        Self { items: Vec::new() }
1934    }
1935    /// Focuses on `item`.
1936    pub fn focus(&mut self, item: T) {
1937        self.items.push(item);
1938    }
1939    /// Blurs (pops) the current focus.
1940    pub fn blur(&mut self) -> Option<T> {
1941        self.items.pop()
1942    }
1943    /// Returns the current focus, or `None`.
1944    pub fn current(&self) -> Option<&T> {
1945        self.items.last()
1946    }
1947    /// Returns the focus depth.
1948    pub fn depth(&self) -> usize {
1949        self.items.len()
1950    }
1951    /// Returns `true` if there is no current focus.
1952    pub fn is_empty(&self) -> bool {
1953        self.items.is_empty()
1954    }
1955}
1956/// A trie-based prefix counter.
1957#[allow(dead_code)]
1958pub struct PrefixCounter {
1959    children: std::collections::HashMap<char, PrefixCounter>,
1960    count: usize,
1961}
1962#[allow(dead_code)]
1963impl PrefixCounter {
1964    /// Creates an empty prefix counter.
1965    pub fn new() -> Self {
1966        Self {
1967            children: std::collections::HashMap::new(),
1968            count: 0,
1969        }
1970    }
1971    /// Records a string.
1972    pub fn record(&mut self, s: &str) {
1973        self.count += 1;
1974        let mut node = self;
1975        for c in s.chars() {
1976            node = node.children.entry(c).or_default();
1977            node.count += 1;
1978        }
1979    }
1980    /// Returns how many strings have been recorded that start with `prefix`.
1981    pub fn count_with_prefix(&self, prefix: &str) -> usize {
1982        let mut node = self;
1983        for c in prefix.chars() {
1984            match node.children.get(&c) {
1985                Some(n) => node = n,
1986                None => return 0,
1987            }
1988        }
1989        node.count
1990    }
1991}