Skip to main content

oxilean_kernel/reduce/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use crate::expr_util::{get_app_arg_nodes, get_app_args, get_app_fn, mk_app, mk_app_nodes};
7use crate::instantiate::instantiate_type_lparams;
8use crate::subst::{instantiate, instantiate_rev_nodes};
9use crate::Node;
10use crate::{Environment, Expr, Literal, Name};
11use std::collections::HashMap;
12use std::rc::Rc;
13
14/// A tagged union for representing a simple two-case discriminated union.
15#[allow(dead_code)]
16pub enum Either2<A, B> {
17    /// The first alternative.
18    First(A),
19    /// The second alternative.
20    Second(B),
21}
22#[allow(dead_code)]
23impl<A, B> Either2<A, B> {
24    /// Returns `true` if this is the first alternative.
25    pub fn is_first(&self) -> bool {
26        matches!(self, Either2::First(_))
27    }
28    /// Returns `true` if this is the second alternative.
29    pub fn is_second(&self) -> bool {
30        matches!(self, Either2::Second(_))
31    }
32    /// Returns the first value if present.
33    pub fn first(self) -> Option<A> {
34        match self {
35            Either2::First(a) => Some(a),
36            _ => None,
37        }
38    }
39    /// Returns the second value if present.
40    pub fn second(self) -> Option<B> {
41        match self {
42            Either2::Second(b) => Some(b),
43            _ => None,
44        }
45    }
46    /// Maps over the first alternative.
47    pub fn map_first<C, F: FnOnce(A) -> C>(self, f: F) -> Either2<C, B> {
48        match self {
49            Either2::First(a) => Either2::First(f(a)),
50            Either2::Second(b) => Either2::Second(b),
51        }
52    }
53}
54/// A generic counter that tracks min/max/sum for statistical summaries.
55#[allow(dead_code)]
56pub struct StatSummary {
57    count: u64,
58    sum: f64,
59    min: f64,
60    max: f64,
61}
62#[allow(dead_code)]
63impl StatSummary {
64    /// Creates an empty summary.
65    pub fn new() -> Self {
66        Self {
67            count: 0,
68            sum: 0.0,
69            min: f64::INFINITY,
70            max: f64::NEG_INFINITY,
71        }
72    }
73    /// Records a sample.
74    pub fn record(&mut self, val: f64) {
75        self.count += 1;
76        self.sum += val;
77        if val < self.min {
78            self.min = val;
79        }
80        if val > self.max {
81            self.max = val;
82        }
83    }
84    /// Returns the mean, or `None` if no samples.
85    pub fn mean(&self) -> Option<f64> {
86        if self.count == 0 {
87            None
88        } else {
89            Some(self.sum / self.count as f64)
90        }
91    }
92    /// Returns the minimum, or `None` if no samples.
93    pub fn min(&self) -> Option<f64> {
94        if self.count == 0 {
95            None
96        } else {
97            Some(self.min)
98        }
99    }
100    /// Returns the maximum, or `None` if no samples.
101    pub fn max(&self) -> Option<f64> {
102        if self.count == 0 {
103            None
104        } else {
105            Some(self.max)
106        }
107    }
108    /// Returns the count of recorded samples.
109    pub fn count(&self) -> u64 {
110        self.count
111    }
112}
113/// A token bucket rate limiter.
114#[allow(dead_code)]
115pub struct TokenBucket {
116    capacity: u64,
117    tokens: u64,
118    refill_per_ms: u64,
119    last_refill: crate::wall_clock::Instant,
120}
121#[allow(dead_code)]
122impl TokenBucket {
123    /// Creates a new token bucket.
124    pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
125        Self {
126            capacity,
127            tokens: capacity,
128            refill_per_ms,
129            last_refill: crate::wall_clock::Instant::now(),
130        }
131    }
132    /// Attempts to consume `n` tokens.  Returns `true` on success.
133    pub fn try_consume(&mut self, n: u64) -> bool {
134        self.refill();
135        if self.tokens >= n {
136            self.tokens -= n;
137            true
138        } else {
139            false
140        }
141    }
142    fn refill(&mut self) {
143        let now = crate::wall_clock::Instant::now();
144        let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
145        if elapsed_ms > 0 {
146            let new_tokens = elapsed_ms * self.refill_per_ms;
147            self.tokens = (self.tokens + new_tokens).min(self.capacity);
148            self.last_refill = now;
149        }
150    }
151    /// Returns the number of currently available tokens.
152    pub fn available(&self) -> u64 {
153        self.tokens
154    }
155    /// Returns the bucket capacity.
156    pub fn capacity(&self) -> u64 {
157        self.capacity
158    }
159}
160/// A label set for a graph node.
161#[allow(dead_code)]
162pub struct LabelSet {
163    labels: Vec<String>,
164}
165#[allow(dead_code)]
166impl LabelSet {
167    /// Creates a new empty label set.
168    pub fn new() -> Self {
169        Self { labels: Vec::new() }
170    }
171    /// Adds a label (deduplicates).
172    pub fn add(&mut self, label: impl Into<String>) {
173        let s = label.into();
174        if !self.labels.contains(&s) {
175            self.labels.push(s);
176        }
177    }
178    /// Returns `true` if `label` is present.
179    pub fn has(&self, label: &str) -> bool {
180        self.labels.iter().any(|l| l == label)
181    }
182    /// Returns the count of labels.
183    pub fn count(&self) -> usize {
184        self.labels.len()
185    }
186    /// Returns all labels.
187    pub fn all(&self) -> &[String] {
188        &self.labels
189    }
190}
191/// A fixed-size sliding window that computes a running sum.
192#[allow(dead_code)]
193pub struct SlidingSum {
194    window: Vec<f64>,
195    capacity: usize,
196    pos: usize,
197    sum: f64,
198    count: usize,
199}
200#[allow(dead_code)]
201impl SlidingSum {
202    /// Creates a sliding sum with the given window size.
203    pub fn new(capacity: usize) -> Self {
204        Self {
205            window: vec![0.0; capacity],
206            capacity,
207            pos: 0,
208            sum: 0.0,
209            count: 0,
210        }
211    }
212    /// Adds a value to the window, removing the oldest if full.
213    pub fn push(&mut self, val: f64) {
214        let oldest = self.window[self.pos];
215        self.sum -= oldest;
216        self.sum += val;
217        self.window[self.pos] = val;
218        self.pos = (self.pos + 1) % self.capacity;
219        if self.count < self.capacity {
220            self.count += 1;
221        }
222    }
223    /// Returns the current window sum.
224    pub fn sum(&self) -> f64 {
225        self.sum
226    }
227    /// Returns the window mean, or `None` if empty.
228    pub fn mean(&self) -> Option<f64> {
229        if self.count == 0 {
230            None
231        } else {
232            Some(self.sum / self.count as f64)
233        }
234    }
235    /// Returns the current window size (number of valid elements).
236    pub fn count(&self) -> usize {
237        self.count
238    }
239}
240/// A simple stack-based calculator for arithmetic expressions.
241#[allow(dead_code)]
242pub struct StackCalc {
243    stack: Vec<i64>,
244}
245#[allow(dead_code)]
246impl StackCalc {
247    /// Creates a new empty calculator.
248    pub fn new() -> Self {
249        Self { stack: Vec::new() }
250    }
251    /// Pushes an integer literal.
252    pub fn push(&mut self, n: i64) {
253        self.stack.push(n);
254    }
255    /// Replaces the top two values, `b` on top of `a`, with `op(a, b)` and
256    /// returns it.  Returns `None`, leaving the stack unchanged, if it holds
257    /// fewer than two values.
258    fn apply_binary(&mut self, op: impl FnOnce(i64, i64) -> i64) -> Option<i64> {
259        let (rest_len, a, b) = match self.stack[..] {
260            [ref rest @ .., a, b] => (rest.len(), a, b),
261            _ => return None,
262        };
263        let value = op(a, b);
264        self.stack.truncate(rest_len);
265        self.stack.push(value);
266        Some(value)
267    }
268    /// Adds the top two values and returns the sum, which replaces them.
269    /// Returns `None`, leaving the stack unchanged, if it holds fewer than
270    /// two values.
271    pub fn add(&mut self) -> Option<i64> {
272        self.apply_binary(|a, b| a + b)
273    }
274    /// Subtracts top from second and returns the difference, which replaces
275    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
276    /// than two values.
277    pub fn sub(&mut self) -> Option<i64> {
278        self.apply_binary(|a, b| a - b)
279    }
280    /// Multiplies the top two values and returns the product, which replaces
281    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
282    /// than two values.
283    pub fn mul(&mut self) -> Option<i64> {
284        self.apply_binary(|a, b| a * b)
285    }
286    /// Peeks the top value.
287    pub fn peek(&self) -> Option<i64> {
288        self.stack.last().copied()
289    }
290    /// Returns the stack depth.
291    pub fn depth(&self) -> usize {
292        self.stack.len()
293    }
294}
295/// A simple decision tree node for rule dispatching.
296#[allow(dead_code)]
297#[allow(missing_docs)]
298pub enum DecisionNode {
299    /// A leaf with an action string.
300    Leaf(String),
301    /// An interior node: check `key` equals `val` → `yes_branch`, else `no_branch`.
302    Branch {
303        key: String,
304        val: String,
305        yes_branch: Box<DecisionNode>,
306        no_branch: Box<DecisionNode>,
307    },
308}
309#[allow(dead_code)]
310impl DecisionNode {
311    /// Evaluates the decision tree with the given context.
312    pub fn evaluate(&self, ctx: &std::collections::HashMap<String, String>) -> &str {
313        match self {
314            DecisionNode::Leaf(action) => action.as_str(),
315            DecisionNode::Branch {
316                key,
317                val,
318                yes_branch,
319                no_branch,
320            } => {
321                let actual = ctx.get(key).map(|s| s.as_str()).unwrap_or("");
322                if actual == val.as_str() {
323                    yes_branch.evaluate(ctx)
324                } else {
325                    no_branch.evaluate(ctx)
326                }
327            }
328        }
329    }
330    /// Returns the depth of the decision tree.
331    pub fn depth(&self) -> usize {
332        match self {
333            DecisionNode::Leaf(_) => 0,
334            DecisionNode::Branch {
335                yes_branch,
336                no_branch,
337                ..
338            } => 1 + yes_branch.depth().max(no_branch.depth()),
339        }
340    }
341}
342/// A simple key-value store backed by a sorted Vec for small maps.
343#[allow(dead_code)]
344pub struct SmallMap<K: Ord + Clone, V: Clone> {
345    entries: Vec<(K, V)>,
346}
347#[allow(dead_code)]
348impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
349    /// Creates a new empty small map.
350    pub fn new() -> Self {
351        Self {
352            entries: Vec::new(),
353        }
354    }
355    /// Inserts or replaces the value for `key`.
356    pub fn insert(&mut self, key: K, val: V) {
357        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
358            Ok(i) => self.entries[i].1 = val,
359            Err(i) => self.entries.insert(i, (key, val)),
360        }
361    }
362    /// Returns the value for `key`, or `None`.
363    pub fn get(&self, key: &K) -> Option<&V> {
364        self.entries
365            .binary_search_by_key(&key, |(k, _)| k)
366            .ok()
367            .map(|i| &self.entries[i].1)
368    }
369    /// Returns the number of entries.
370    pub fn len(&self) -> usize {
371        self.entries.len()
372    }
373    /// Returns `true` if empty.
374    pub fn is_empty(&self) -> bool {
375        self.entries.is_empty()
376    }
377    /// Returns all keys.
378    pub fn keys(&self) -> Vec<&K> {
379        self.entries.iter().map(|(k, _)| k).collect()
380    }
381    /// Returns all values.
382    pub fn values(&self) -> Vec<&V> {
383        self.entries.iter().map(|(_, v)| v).collect()
384    }
385}
386/// Named reduction rules for tracing.
387#[allow(dead_code)]
388#[derive(Debug, Clone, PartialEq, Eq)]
389pub enum ReductionRule {
390    /// Beta reduction: (λx.body) arg → body[arg/x].
391    Beta,
392    /// Delta reduction: unfold a definition.
393    Delta,
394    /// Zeta reduction: let x := v in body → body[v/x].
395    Zeta,
396    /// Iota reduction: recursor application.
397    Iota,
398    /// Projection reduction.
399    Proj,
400    /// Quotient reduction.
401    Quot,
402    /// Nat literal operation.
403    NatLit,
404    /// String literal operation.
405    StrLit,
406    /// No reduction was possible.
407    None,
408}
409/// A window iterator that yields overlapping windows of size `n`.
410#[allow(dead_code)]
411pub struct WindowIterator<'a, T> {
412    pub(super) data: &'a [T],
413    pub(super) pos: usize,
414    pub(super) window: usize,
415}
416#[allow(dead_code)]
417impl<'a, T> WindowIterator<'a, T> {
418    /// Creates a new window iterator.
419    pub fn new(data: &'a [T], window: usize) -> Self {
420        Self {
421            data,
422            pos: 0,
423            window,
424        }
425    }
426}
427/// A sparse vector: stores only non-default elements.
428#[allow(dead_code)]
429pub struct SparseVec<T: Default + Clone + PartialEq> {
430    entries: std::collections::HashMap<usize, T>,
431    default_: T,
432    logical_len: usize,
433}
434#[allow(dead_code)]
435impl<T: Default + Clone + PartialEq> SparseVec<T> {
436    /// Creates a new sparse vector with logical length `len`.
437    pub fn new(len: usize) -> Self {
438        Self {
439            entries: std::collections::HashMap::new(),
440            default_: T::default(),
441            logical_len: len,
442        }
443    }
444    /// Sets element at `idx`.
445    pub fn set(&mut self, idx: usize, val: T) {
446        if val == self.default_ {
447            self.entries.remove(&idx);
448        } else {
449            self.entries.insert(idx, val);
450        }
451    }
452    /// Gets element at `idx`.
453    pub fn get(&self, idx: usize) -> &T {
454        self.entries.get(&idx).unwrap_or(&self.default_)
455    }
456    /// Returns the logical length.
457    pub fn len(&self) -> usize {
458        self.logical_len
459    }
460    /// Returns whether the collection is empty.
461    pub fn is_empty(&self) -> bool {
462        self.len() == 0
463    }
464    /// Returns the number of non-default elements.
465    pub fn nnz(&self) -> usize {
466        self.entries.len()
467    }
468}
469/// A simple directed acyclic graph.
470#[allow(dead_code)]
471pub struct SimpleDag {
472    /// `edges[i]` is the list of direct successors of node `i`.
473    edges: Vec<Vec<usize>>,
474}
475#[allow(dead_code)]
476impl SimpleDag {
477    /// Creates a DAG with `n` nodes and no edges.
478    pub fn new(n: usize) -> Self {
479        Self {
480            edges: vec![Vec::new(); n],
481        }
482    }
483    /// Adds an edge from `from` to `to`.
484    pub fn add_edge(&mut self, from: usize, to: usize) {
485        if from < self.edges.len() {
486            self.edges[from].push(to);
487        }
488    }
489    /// Returns the successors of `node`.
490    pub fn successors(&self, node: usize) -> &[usize] {
491        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
492    }
493    /// Returns `true` if `from` can reach `to` via DFS.
494    pub fn can_reach(&self, from: usize, to: usize) -> bool {
495        let mut visited = vec![false; self.edges.len()];
496        self.dfs(from, to, &mut visited)
497    }
498    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
499        if cur == target {
500            return true;
501        }
502        if cur >= visited.len() || visited[cur] {
503            return false;
504        }
505        visited[cur] = true;
506        for &next in self.successors(cur) {
507            if self.dfs(next, target, visited) {
508                return true;
509            }
510        }
511        false
512    }
513    /// Returns the topological order of nodes, or `None` if a cycle is detected.
514    pub fn topological_sort(&self) -> Option<Vec<usize>> {
515        let n = self.edges.len();
516        let mut in_degree = vec![0usize; n];
517        for succs in &self.edges {
518            for &s in succs {
519                if s < n {
520                    in_degree[s] += 1;
521                }
522            }
523        }
524        let mut queue: std::collections::VecDeque<usize> =
525            (0..n).filter(|&i| in_degree[i] == 0).collect();
526        let mut order = Vec::new();
527        while let Some(node) = queue.pop_front() {
528            order.push(node);
529            for &s in self.successors(node) {
530                if s < n {
531                    in_degree[s] -= 1;
532                    if in_degree[s] == 0 {
533                        queue.push_back(s);
534                    }
535                }
536            }
537        }
538        if order.len() == n {
539            Some(order)
540        } else {
541            None
542        }
543    }
544    /// Returns the number of nodes.
545    pub fn num_nodes(&self) -> usize {
546        self.edges.len()
547    }
548}
549/// A mutable reference stack for tracking the current "focus" in a tree traversal.
550#[allow(dead_code)]
551pub struct FocusStack<T> {
552    items: Vec<T>,
553}
554#[allow(dead_code)]
555impl<T> FocusStack<T> {
556    /// Creates an empty focus stack.
557    pub fn new() -> Self {
558        Self { items: Vec::new() }
559    }
560    /// Focuses on `item`.
561    pub fn focus(&mut self, item: T) {
562        self.items.push(item);
563    }
564    /// Blurs (pops) the current focus.
565    pub fn blur(&mut self) -> Option<T> {
566        self.items.pop()
567    }
568    /// Returns the current focus, or `None`.
569    pub fn current(&self) -> Option<&T> {
570        self.items.last()
571    }
572    /// Returns the focus depth.
573    pub fn depth(&self) -> usize {
574        self.items.len()
575    }
576    /// Returns `true` if there is no current focus.
577    pub fn is_empty(&self) -> bool {
578        self.items.is_empty()
579    }
580}
581/// A versioned record that stores a history of values.
582#[allow(dead_code)]
583pub struct VersionedRecord<T: Clone> {
584    /// Every earlier value, oldest first: version `n` is `earlier[n]`.
585    earlier: Vec<T>,
586    /// The latest value (version `earlier.len()`), declared last so it drops last.
587    current: T,
588}
589#[allow(dead_code)]
590impl<T: Clone> VersionedRecord<T> {
591    /// Creates a new record with an initial value.
592    pub fn new(initial: T) -> Self {
593        Self {
594            earlier: Vec::new(),
595            current: initial,
596        }
597    }
598    /// Updates the record with a new version.
599    pub fn update(&mut self, val: T) {
600        let previous = std::mem::replace(&mut self.current, val);
601        self.earlier.push(previous);
602    }
603    /// Returns the current (latest) value.
604    pub fn current(&self) -> &T {
605        &self.current
606    }
607    /// Returns the value at version `n` (0-indexed), or `None`.
608    pub fn at_version(&self, n: usize) -> Option<&T> {
609        match self.earlier.get(n) {
610            Some(value) => Some(value),
611            None if n == self.earlier.len() => Some(&self.current),
612            None => None,
613        }
614    }
615    /// Returns the version number of the current value.
616    pub fn version(&self) -> usize {
617        self.earlier.len()
618    }
619    /// Returns `true` if more than one version exists.
620    pub fn has_history(&self) -> bool {
621        !self.earlier.is_empty()
622    }
623}
624/// Represents a rewrite rule `lhs → rhs`.
625#[allow(dead_code)]
626#[allow(missing_docs)]
627pub struct RewriteRule {
628    /// The name of the rule.
629    pub name: String,
630    /// A string representation of the LHS pattern.
631    pub lhs: String,
632    /// A string representation of the RHS.
633    pub rhs: String,
634    /// Whether this is a conditional rule (has side conditions).
635    pub conditional: bool,
636}
637#[allow(dead_code)]
638impl RewriteRule {
639    /// Creates an unconditional rewrite rule.
640    pub fn unconditional(
641        name: impl Into<String>,
642        lhs: impl Into<String>,
643        rhs: impl Into<String>,
644    ) -> Self {
645        Self {
646            name: name.into(),
647            lhs: lhs.into(),
648            rhs: rhs.into(),
649            conditional: false,
650        }
651    }
652    /// Creates a conditional rewrite rule.
653    pub fn conditional(
654        name: impl Into<String>,
655        lhs: impl Into<String>,
656        rhs: impl Into<String>,
657    ) -> Self {
658        Self {
659            name: name.into(),
660            lhs: lhs.into(),
661            rhs: rhs.into(),
662            conditional: true,
663        }
664    }
665    /// Returns a textual representation.
666    pub fn display(&self) -> String {
667        format!("{}: {} → {}", self.name, self.lhs, self.rhs)
668    }
669}
670/// A dependency closure builder (transitive closure via BFS).
671#[allow(dead_code)]
672pub struct TransitiveClosure {
673    adj: Vec<Vec<usize>>,
674    n: usize,
675}
676#[allow(dead_code)]
677impl TransitiveClosure {
678    /// Creates a transitive closure builder for `n` nodes.
679    pub fn new(n: usize) -> Self {
680        Self {
681            adj: vec![Vec::new(); n],
682            n,
683        }
684    }
685    /// Adds a direct edge.
686    pub fn add_edge(&mut self, from: usize, to: usize) {
687        if from < self.n {
688            self.adj[from].push(to);
689        }
690    }
691    /// Computes all nodes reachable from `start` (including `start`).
692    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
693        let mut visited = vec![false; self.n];
694        let mut queue = std::collections::VecDeque::new();
695        queue.push_back(start);
696        while let Some(node) = queue.pop_front() {
697            if node >= self.n || visited[node] {
698                continue;
699            }
700            visited[node] = true;
701            for &next in &self.adj[node] {
702                queue.push_back(next);
703            }
704        }
705        (0..self.n).filter(|&i| visited[i]).collect()
706    }
707    /// Returns `true` if `from` can transitively reach `to`.
708    pub fn can_reach(&self, from: usize, to: usize) -> bool {
709        self.reachable_from(from).contains(&to)
710    }
711}
712/// Statistics about reduction performance.
713#[allow(dead_code)]
714#[derive(Debug, Default, Clone)]
715pub struct ReducerStats {
716    /// Number of WHNF calls.
717    pub whnf_calls: u64,
718    /// Number of cache hits.
719    pub cache_hits: u64,
720    /// Number of beta reductions performed.
721    pub beta_count: u64,
722    /// Number of delta reductions performed.
723    pub delta_count: u64,
724    /// Number of iota reductions performed.
725    pub iota_count: u64,
726    /// Number of zeta reductions performed.
727    pub zeta_count: u64,
728    /// Number of nat literal reductions.
729    pub nat_lit_count: u64,
730}
731impl ReducerStats {
732    /// Compute the total reduction count.
733    #[allow(dead_code)]
734    pub fn total_reductions(&self) -> u64 {
735        self.beta_count + self.delta_count + self.iota_count + self.zeta_count + self.nat_lit_count
736    }
737    /// Cache hit rate as a fraction.
738    #[allow(dead_code)]
739    pub fn cache_hit_rate(&self) -> f64 {
740        if self.whnf_calls == 0 {
741            0.0
742        } else {
743            self.cache_hits as f64 / self.whnf_calls as f64
744        }
745    }
746    /// Pretty-print the stats.
747    #[allow(dead_code)]
748    pub fn display(&self) -> String {
749        format!(
750            "whnf:{} hits:{} β:{} δ:{} ι:{} ζ:{} lit:{}",
751            self.whnf_calls,
752            self.cache_hits,
753            self.beta_count,
754            self.delta_count,
755            self.iota_count,
756            self.zeta_count,
757            self.nat_lit_count,
758        )
759    }
760}
761/// A reusable scratch buffer for path computations.
762#[allow(dead_code)]
763pub struct PathBuf {
764    components: Vec<String>,
765}
766#[allow(dead_code)]
767impl PathBuf {
768    /// Creates a new empty path buffer.
769    pub fn new() -> Self {
770        Self {
771            components: Vec::new(),
772        }
773    }
774    /// Pushes a component.
775    pub fn push(&mut self, comp: impl Into<String>) {
776        self.components.push(comp.into());
777    }
778    /// Pops the last component.
779    pub fn pop(&mut self) {
780        self.components.pop();
781    }
782    /// Returns the current path as a `/`-separated string.
783    pub fn as_str(&self) -> String {
784        self.components.join("/")
785    }
786    /// Returns the depth of the path.
787    pub fn depth(&self) -> usize {
788        self.components.len()
789    }
790    /// Clears the path.
791    pub fn clear(&mut self) {
792        self.components.clear();
793    }
794}
795/// A hierarchical configuration tree.
796#[allow(dead_code)]
797pub struct ConfigNode {
798    key: String,
799    value: Option<String>,
800    children: Vec<ConfigNode>,
801}
802#[allow(dead_code)]
803impl ConfigNode {
804    /// Creates a leaf config node with a value.
805    pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
806        Self {
807            key: key.into(),
808            value: Some(value.into()),
809            children: Vec::new(),
810        }
811    }
812    /// Creates a section node with children.
813    pub fn section(key: impl Into<String>) -> Self {
814        Self {
815            key: key.into(),
816            value: None,
817            children: Vec::new(),
818        }
819    }
820    /// Adds a child node.
821    pub fn add_child(&mut self, child: ConfigNode) {
822        self.children.push(child);
823    }
824    /// Returns the key.
825    pub fn key(&self) -> &str {
826        &self.key
827    }
828    /// Returns the value, or `None` for section nodes.
829    pub fn value(&self) -> Option<&str> {
830        self.value.as_deref()
831    }
832    /// Returns the number of children.
833    pub fn num_children(&self) -> usize {
834        self.children.len()
835    }
836    /// Looks up a dot-separated path.
837    pub fn lookup(&self, path: &str) -> Option<&str> {
838        let mut parts = path.splitn(2, '.');
839        let head = parts.next()?;
840        let tail = parts.next();
841        if head != self.key {
842            return None;
843        }
844        match tail {
845            None => self.value.as_deref(),
846            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
847        }
848    }
849    fn lookup_relative(&self, path: &str) -> Option<&str> {
850        let mut parts = path.splitn(2, '.');
851        let head = parts.next()?;
852        let tail = parts.next();
853        if head != self.key {
854            return None;
855        }
856        match tail {
857            None => self.value.as_deref(),
858            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
859        }
860    }
861}
862/// A non-empty list (at least one element guaranteed).
863#[allow(dead_code)]
864pub struct NonEmptyVec<T> {
865    head: T,
866    tail: Vec<T>,
867}
868#[allow(dead_code)]
869impl<T> NonEmptyVec<T> {
870    /// Creates a non-empty vec with a single element.
871    pub fn singleton(val: T) -> Self {
872        Self {
873            head: val,
874            tail: Vec::new(),
875        }
876    }
877    /// Pushes an element.
878    pub fn push(&mut self, val: T) {
879        self.tail.push(val);
880    }
881    /// Returns a reference to the first element.
882    pub fn first(&self) -> &T {
883        &self.head
884    }
885    /// Returns a reference to the last element.
886    pub fn last(&self) -> &T {
887        self.tail.last().unwrap_or(&self.head)
888    }
889    /// Returns the number of elements.
890    pub fn len(&self) -> usize {
891        1 + self.tail.len()
892    }
893    /// Returns whether the collection is empty.
894    pub fn is_empty(&self) -> bool {
895        self.len() == 0
896    }
897    /// Returns all elements as a Vec.
898    pub fn to_vec(&self) -> Vec<&T> {
899        let mut v = vec![&self.head];
900        v.extend(self.tail.iter());
901        v
902    }
903}
904/// A pool of reusable string buffers.
905#[allow(dead_code)]
906pub struct StringPool {
907    free: Vec<String>,
908}
909#[allow(dead_code)]
910impl StringPool {
911    /// Creates a new empty string pool.
912    pub fn new() -> Self {
913        Self { free: Vec::new() }
914    }
915    /// Takes a string from the pool (may be empty).
916    pub fn take(&mut self) -> String {
917        self.free.pop().unwrap_or_default()
918    }
919    /// Returns a string to the pool.
920    pub fn give(&mut self, mut s: String) {
921        s.clear();
922        self.free.push(s);
923    }
924    /// Returns the number of free strings in the pool.
925    pub fn free_count(&self) -> usize {
926        self.free.len()
927    }
928}
929/// The outcome of one head-reduction step (see `Reducer::whnf_step`).
930enum StepOutcome {
931    /// The term is in weak head normal form.
932    Done(Expr),
933    /// One step of progress; the caller's loop continues on this term.
934    Continue(Expr),
935}
936/// Reduction context with caching.
937pub struct Reducer {
938    /// WHNF cache: expr -> whnf(expr).
939    ///
940    /// Every result is kept, whatever the size of the term as a tree: the
941    /// terms that most need the cache are the heavily shared ones, a few
942    /// hundred allocations standing for millions of tree nodes, and through
943    /// 0.1.4's first performance pass those were exactly the ones a
944    /// tree-size limit kept out — each visit reduced them again. A kept
945    /// entry holds the term and its result by reference; what that retains
946    /// beyond the term being checked is nodes some reduction built, each of
947    /// which was charged to the per-declaration budget.
948    pub(crate) cache: crate::term_cache::TermCache<Expr>,
949    /// The values of universe-polymorphic constants with their universe
950    /// parameters instantiated, by the constant as it occurs
951    /// (`Const(name, levels)`). A reduction unfolds the same constant at the
952    /// same universes over and over — tens of thousands of times in one
953    /// `omega` certificate — and instantiating rebuilds every part of the
954    /// value that mentions a universe. Keeping the result also means every
955    /// unfolding of a constant is ONE allocation, shared by all of them.
956    /// A function of the environment alone; dropped with `cache`.
957    unfolded: crate::term_cache::TermCache<Expr>,
958    /// Limit on the NESTING depth of reduction (a stack guard, always
959    /// applied) and on the LENGTH of one head-reduction chain (applied
960    /// unless lifted — see [`crate::fuel::set_reduction_cap`]). A reduction
961    /// either limit cuts short is recorded with
962    /// [`crate::fuel::note_reduction_limit`].
963    max_depth: u32,
964    /// Current transparency mode.
965    transparency: TransparencyMode,
966    /// Types of the free variables of the surrounding checker's local
967    /// context that are in scope (mirrored via
968    /// [`Reducer::record_fvar_type`], removed via
969    /// [`Reducer::forget_fvar_type`] when their binder closes). K-like iota
970    /// reduction must *type* the recursor's major premise; a major that
971    /// mentions local FVars (e.g. `Eq.symm … (h : ctorIdx t = 0)` inside
972    /// `Std.IterStep.noConfusionType`) is only typeable with this context —
973    /// Lean's kernel reads the same information from its local context.
974    local_fvar_types: crate::local_types::LocalTypes,
975}
976impl Reducer {
977    /// Create a new reducer with default settings.
978    pub fn new() -> Self {
979        Self {
980            cache: crate::term_cache::TermCache::new(),
981            unfolded: crate::term_cache::TermCache::new(),
982            max_depth: 10000,
983            transparency: TransparencyMode::Default,
984            local_fvar_types: crate::local_types::LocalTypes::new(),
985        }
986    }
987    /// Create a reducer with custom max depth.
988    pub fn with_max_depth(max_depth: u32) -> Self {
989        Self {
990            cache: crate::term_cache::TermCache::new(),
991            unfolded: crate::term_cache::TermCache::new(),
992            max_depth,
993            transparency: TransparencyMode::Default,
994            local_fvar_types: crate::local_types::LocalTypes::new(),
995        }
996    }
997    /// Record the type of a free variable opened by the surrounding checker
998    /// so K-like iota reduction can type majors that mention it.
999    ///
1000    /// `FVarId`s are dispensed monotonically and never reused, so recorded
1001    /// types are stable and cached WHNF results stay sound. Defensively, a
1002    /// re-bind that could make them unsound invalidates the cache: an id in
1003    /// scope given a different type, or an id whose binder was closed
1004    /// ([`Self::forget_fvar_type`]) bound again.
1005    pub fn record_fvar_type(&mut self, id: crate::FVarId, ty: Expr) {
1006        let _stale = self.bind_fvar_type(id, ty);
1007    }
1008    /// [`Self::record_fvar_type`], reporting whether the binding made
1009    /// earlier results stale (see `LocalTypes::bind`): `true` when this
1010    /// reducer dropped its cache, so that an owner keeping caches of its own
1011    /// over the same local context can drop them too.
1012    pub(crate) fn bind_fvar_type(&mut self, id: crate::FVarId, ty: Expr) -> bool {
1013        let stale = self.local_fvar_types.bind(id, ty);
1014        if stale {
1015            self.cache.clear();
1016        }
1017        stale
1018    }
1019    /// The binder of a free variable recorded with
1020    /// [`Self::record_fvar_type`] has been closed: forget its type.
1021    ///
1022    /// Nothing reduced from here on mentions the variable, so nothing needs
1023    /// its type; cached WHNF results that mention it are never looked up
1024    /// again, since its id is not dispensed again (and if a caller does bind
1025    /// it again, [`Self::record_fvar_type`] invalidates the cache). Without
1026    /// this the mirror grows by one type per binder opened, for as long as
1027    /// the reducer lives.
1028    pub fn forget_fvar_type(&mut self, id: crate::FVarId) {
1029        self.local_fvar_types.unbind(id);
1030    }
1031    /// This reducer's mirror of the local context (for tests).
1032    #[cfg(test)]
1033    pub(crate) fn local_types(&self) -> &crate::local_types::LocalTypes {
1034        &self.local_fvar_types
1035    }
1036    /// Set the transparency mode.
1037    pub fn set_transparency(&mut self, mode: TransparencyMode) {
1038        if self.transparency != mode {
1039            self.transparency = mode;
1040            self.cache.clear();
1041        }
1042    }
1043    /// Clear the cache (call between declarations).
1044    pub fn clear_cache(&mut self) {
1045        self.cache.clear();
1046        self.unfolded.clear();
1047    }
1048    /// The value of the constant `head` (`Const(name, levels)`) with its
1049    /// universe parameters instantiated — from the table of unfoldings when
1050    /// it has been asked for before.
1051    fn unfold_value(
1052        &mut self,
1053        head: &Expr,
1054        value: &Expr,
1055        level_params: &[Name],
1056        levels: &[crate::Level],
1057    ) -> Expr {
1058        if level_params.is_empty() || levels.is_empty() {
1059            return value.clone();
1060        }
1061        if let Some(unfolded) = self.unfolded.get(head) {
1062            return unfolded.clone();
1063        }
1064        let unfolded = instantiate_type_lparams(value, level_params, levels);
1065        self.unfolded.insert(head, unfolded.clone());
1066        unfolded
1067    }
1068    /// Reduce an expression to Weak Head Normal Form.
1069    ///
1070    /// This implements all reduction rules except delta (which needs environment).
1071    pub fn whnf(&mut self, expr: &Expr) -> Expr {
1072        self.whnf_with_depth(expr, 0)
1073    }
1074    fn whnf_with_depth(&mut self, expr: &Expr, depth: u32) -> Expr {
1075        if depth > self.max_depth {
1076            return expr.clone();
1077        }
1078        if let Some(cached) = self.cache.get(expr) {
1079            return cached.clone();
1080        }
1081        let result = match expr {
1082            Expr::Sort(_)
1083            | Expr::BVar(_)
1084            | Expr::FVar(_)
1085            | Expr::Const(_, _)
1086            | Expr::Lam(_, _, _, _)
1087            | Expr::Pi(_, _, _, _)
1088            | Expr::Lit(_) => expr.clone(),
1089            Expr::App(f, a) => {
1090                let f_whnf = self.whnf_with_depth(f, depth + 1);
1091                match f_whnf {
1092                    Expr::Lam(_, _, _, body) => {
1093                        let reduced = instantiate(&body, a);
1094                        self.whnf_with_depth(&reduced, depth + 1)
1095                    }
1096                    _ => Expr::App(Node::new(f_whnf), a.clone()),
1097                }
1098            }
1099            Expr::Let(_, _, val, body) => {
1100                let reduced = instantiate(body, val);
1101                self.whnf_with_depth(&reduced, depth + 1)
1102            }
1103            Expr::Proj(struct_name, idx, struct_expr) => {
1104                let struct_whnf = self.whnf_with_depth(struct_expr, depth + 1);
1105                Expr::Proj(struct_name.clone(), *idx, Node::new(struct_whnf))
1106            }
1107        };
1108        self.cache.insert(expr, result.clone());
1109        result
1110    }
1111    /// Reduce to WHNF with delta-reduction (constant unfolding).
1112    ///
1113    /// This requires environment access to look up definitions.
1114    pub fn whnf_delta<F>(&mut self, expr: &Expr, lookup: &F) -> Expr
1115    where
1116        F: Fn(&crate::Name) -> Option<(Expr, ReducibilityHint)>,
1117    {
1118        self.whnf_delta_with_depth(expr, lookup, 0)
1119    }
1120    fn whnf_delta_with_depth<F>(&mut self, expr: &Expr, lookup: &F, depth: u32) -> Expr
1121    where
1122        F: Fn(&crate::Name) -> Option<(Expr, ReducibilityHint)>,
1123    {
1124        if depth > self.max_depth {
1125            return expr.clone();
1126        }
1127        match expr {
1128            Expr::Sort(_)
1129            | Expr::BVar(_)
1130            | Expr::FVar(_)
1131            | Expr::Lam(_, _, _, _)
1132            | Expr::Pi(_, _, _, _)
1133            | Expr::Lit(_) => expr.clone(),
1134            Expr::Const(name, _levels) => {
1135                if let Some((defn, hint)) = lookup(name) {
1136                    if self.should_unfold_hint(hint) {
1137                        return self.whnf_delta_with_depth(&defn, lookup, depth + 1);
1138                    }
1139                }
1140                expr.clone()
1141            }
1142            Expr::App(f, a) => {
1143                let f_whnf = self.whnf_delta_with_depth(f, lookup, depth + 1);
1144                match f_whnf {
1145                    Expr::Lam(_, _, _, body) => {
1146                        let reduced = instantiate(&body, a);
1147                        self.whnf_delta_with_depth(&reduced, lookup, depth + 1)
1148                    }
1149                    _ => Expr::App(Node::new(f_whnf), a.clone()),
1150                }
1151            }
1152            Expr::Let(_, _, val, body) => {
1153                let reduced = instantiate(body, val);
1154                self.whnf_delta_with_depth(&reduced, lookup, depth + 1)
1155            }
1156            Expr::Proj(struct_name, idx, struct_expr) => {
1157                let struct_whnf = self.whnf_delta_with_depth(struct_expr, lookup, depth + 1);
1158                Expr::Proj(struct_name.clone(), *idx, Node::new(struct_whnf))
1159            }
1160        }
1161    }
1162    /// Check if a hint should be unfolded under current transparency.
1163    pub(crate) fn should_unfold_hint(&self, hint: ReducibilityHint) -> bool {
1164        match self.transparency {
1165            TransparencyMode::All => true,
1166            TransparencyMode::Default => hint.should_unfold(),
1167            TransparencyMode::Reducible => matches!(hint, ReducibilityHint::Abbrev),
1168            TransparencyMode::Instances => false,
1169            TransparencyMode::None => false,
1170        }
1171    }
1172    /// Full WHNF with environment-aware reductions.
1173    ///
1174    /// Performs: beta, zeta, delta, iota (recursor), projection, nat/string ops.
1175    pub fn whnf_env(&mut self, expr: &Expr, env: &Environment) -> Expr {
1176        self.whnf_env_depth(expr, env, 0)
1177    }
1178    fn whnf_env_depth(&mut self, expr: &Expr, env: &Environment, depth: u32) -> Expr {
1179        // The thread's own limit on nested reductions, where its owner has
1180        // set one (`fuel::set_reduction_nesting_limit`: a small stack): a
1181        // reduction that would nest deeper is not started. As with the guard
1182        // below, the term returned is sound but not a normal form, and the
1183        // event is recorded.
1184        let Some(_nested) = crate::fuel::NestedReduction::enter() else {
1185            crate::fuel::note_reduction_limit();
1186            return expr.clone();
1187        };
1188        self.whnf_env_nested(expr, env, depth)
1189    }
1190    /// [`Self::whnf_env_depth`] once the nested reduction has been entered.
1191    fn whnf_env_nested(&mut self, expr: &Expr, env: &Environment, depth: u32) -> Expr {
1192        // Nesting-depth guard: `depth` counts NESTED whnf calls (an
1193        // application head, a recursor major, a projection's structure),
1194        // each of which holds a native stack frame. Past the limit the term
1195        // is returned as it is — sound (it is definitionally equal to
1196        // itself) but not a normal form, so the event is recorded: a failure
1197        // that follows is a resource limit, not a verdict.
1198        if depth > self.max_depth {
1199            crate::fuel::note_reduction_limit();
1200            return expr.clone();
1201        }
1202        // Resource-fuel degradation (C16): once the per-declaration budget
1203        // is exhausted, reduction stops making progress — a stuck term is
1204        // always sound, and it halts the term growth that exhausted the
1205        // budget. The caller reports the failure as a named resource limit.
1206        if crate::fuel::is_exhausted() {
1207            return expr.clone();
1208        }
1209        if let Some(cached) = self.cache.get(expr) {
1210            return cached.clone();
1211        }
1212        // The head-reduction chain runs as a LOOP, not recursion: each step
1213        // *moves* ownership of the intermediate term into the next step, so
1214        // exactly one intermediate is alive at a time. The old tail
1215        // recursion kept every intermediate of a long delta/iota chain
1216        // alive on the call stack simultaneously — the multi-GiB peak of
1217        // Lean core's `Int.add_mul_ediv_right` (C16) and a large share of
1218        // the deep-stack requirement (C17).
1219        //
1220        // What bounds the LENGTH of the chain. By default the reducer's own
1221        // cap does: the chain's steps are counted from the nesting depth of
1222        // this call, on the same counter and against the same `max_depth`
1223        // (so each step's nested reductions run at the depth the chain has
1224        // reached). Besides ending a runaway chain, the cap is what lets
1225        // definitional equality abandon a reduction that is going nowhere
1226        // and try another route; the verdicts on Lean core and Mathlib
1227        // depend on it, so this accounting is kept exactly. A chain the cap
1228        // cuts short is recorded — the term returned is sound but not a
1229        // normal form, and a failure that follows is not a verdict.
1230        //
1231        // A caller repeating a check that failed that way lifts the cap
1232        // (`fuel::set_reduction_cap(false)`, honoured only under a budget).
1233        // The budget then bounds the chain: every step clones or rebuilds at
1234        // least one node, which charges fuel, and `is_exhausted` polls the
1235        // wall-clock deadline. Steps are no longer charged against the
1236        // nesting depth, and every step runs at the depth of this call — so
1237        // a long but cheap chain (`Nat.rec` iterating tens of thousands of
1238        // times over a small term, as in a `decide` proof) runs to its
1239        // normal form instead of failing a valid proof.
1240        let capped = crate::fuel::reduction_cap_applies();
1241        let mut current: Option<Expr> = None;
1242        let mut steps = depth;
1243        let result = loop {
1244            let cur: &Expr = current.as_ref().unwrap_or(expr);
1245            if crate::fuel::is_exhausted() {
1246                break cur.clone();
1247            }
1248            if capped && steps > self.max_depth {
1249                crate::fuel::note_reduction_limit();
1250                break cur.clone();
1251            }
1252            let step_depth = if capped { steps } else { depth };
1253            match self.whnf_step(cur, env, step_depth) {
1254                StepOutcome::Done(done) => break done,
1255                StepOutcome::Continue(next) => {
1256                    steps = steps.saturating_add(1);
1257                    // Drop the previous intermediate before the next step.
1258                    current = Some(next);
1259                }
1260            }
1261        };
1262        // Only the result of the whole chain is kept, not its intermediate
1263        // terms (see the cache's own documentation for what keeping it
1264        // retains).
1265        self.cache.insert(expr, result.clone());
1266        result
1267    }
1268    /// Perform ONE head-reduction step (zeta, delta, beta, literal folding,
1269    /// iota, quotient iota, projection). `StepOutcome::Continue` hands the
1270    /// reduct back to the caller's loop ([`Self::whnf_env_depth`]);
1271    /// `StepOutcome::Done` is the WHNF. Sub-terms (application heads,
1272    /// recursor majors, projection structs) are still reduced by recursive
1273    /// calls — only the head-reduction *chain* is iterative.
1274    fn whnf_step(&mut self, expr: &Expr, env: &Environment, depth: u32) -> StepOutcome {
1275        match expr {
1276            Expr::Sort(_)
1277            | Expr::BVar(_)
1278            | Expr::FVar(_)
1279            | Expr::Lam(_, _, _, _)
1280            | Expr::Pi(_, _, _, _)
1281            | Expr::Lit(_) => StepOutcome::Done(expr.clone()),
1282            Expr::Let(_, _, val, body) => StepOutcome::Continue(instantiate(body, val)),
1283            Expr::Const(name, levels) => {
1284                if let Some(ci) = env.find(name) {
1285                    if let Some(val) = ci.value() {
1286                        let hint = ci.reducibility_hint();
1287                        if self.should_unfold_hint(hint) {
1288                            let unfolded = self.unfold_value(expr, val, ci.level_params(), levels);
1289                            return StepOutcome::Continue(unfolded);
1290                        }
1291                    }
1292                }
1293                StepOutcome::Done(expr.clone())
1294            }
1295            Expr::App(_, _) => {
1296                // Keep the spine arguments BORROWED until a reduction rule
1297                // actually needs owned copies: cloning every argument up
1298                // front (and again in the final `mk_app` rebuild) doubled
1299                // the clone volume of every whnf step on large stuck spines
1300                // (C16, Lean core's `Int.add_mul_ediv_right`).
1301                // The arguments are held as the allocations the spine holds
1302                // them in, so every rebuilt spine and every substitution
1303                // below shares them instead of copying their roots.
1304                let head = get_app_fn(expr);
1305                let args: Vec<&Node> = get_app_arg_nodes(expr);
1306                // The head is reduced ONE STEP AT A TIME while the
1307                // application context is kept, exactly like Lean's whnf loop
1308                // (whnf_core, then the literal extension, then
1309                // `unfold_definition` — which unfolds the head constant of
1310                // the WHOLE application and re-wraps the arguments). Every
1311                // iteration therefore re-checks the Nat/String literal
1312                // extension and iota on the current head, so a definition
1313                // chain that lands on `Nat.mod lit lit` (e.g. through
1314                // `HMod.hMod → instHMod → Mod.mod → Nat.instMod`) folds via
1315                // the extension instead of delta-unfolding `Nat.mod` into
1316                // its structural-recursion body. Reducing the bare head in
1317                // isolation (the old code) unfolded accelerated operations
1318                // before the extension could see the arguments — the
1319                // wrong-REJECT class of `UInt64.toUInt32_mul`,
1320                // `Int64.toInt_minValue` and the Omega constraint lemmas on
1321                // the Init corpus. The extension itself fires only at the
1322                // operation's exact arity, so an over-applied op is left
1323                // stuck and no argument is ever dropped; real exports
1324                // declare `Nat.ble`/`Nat.add`/… as ordinary definitions
1325                // (structural recursion over `Nat`), and the extension
1326                // computes exactly what those definitions compute.
1327                //
1328                // The head loop is bounded like the chain loop of
1329                // `whnf_env_depth`: by the reducer's cap, counted from
1330                // `depth` and recorded when it cuts the loop short, unless
1331                // the cap is lifted — then by the budget (each iteration
1332                // that continues clones an unfolding or steps the head,
1333                // both of which charge fuel).
1334                let capped = crate::fuel::reduction_cap_applies();
1335                let mut head_owned: Option<Expr> = None;
1336                let mut head_steps = depth;
1337                loop {
1338                    if crate::fuel::is_exhausted() {
1339                        break;
1340                    }
1341                    if capped && head_steps > self.max_depth {
1342                        crate::fuel::note_reduction_limit();
1343                        break;
1344                    }
1345                    head_steps = head_steps.saturating_add(1);
1346                    let head_ref: &Expr = head_owned.as_ref().unwrap_or(head);
1347                    match head_ref {
1348                        Expr::Lam(_, _, _, _) => {
1349                            // β: every leading lambda that has an argument,
1350                            // in ONE substitution (one walk of the body
1351                            // where consuming them one at a time walked it
1352                            // once per argument); the arguments left over
1353                            // are re-applied. If what the substitution
1354                            // yields is itself a lambda, the caller's loop
1355                            // comes back here for the next redex.
1356                            let mut body: &Expr = head_ref;
1357                            let mut taken = 0;
1358                            while taken < args.len() {
1359                                match body {
1360                                    Expr::Lam(_, _, _, inner) => {
1361                                        body = inner;
1362                                        taken += 1;
1363                                    }
1364                                    _ => break,
1365                                }
1366                            }
1367                            let reduct = instantiate_rev_nodes(body, &args[..taken]);
1368                            return StepOutcome::Continue(mk_app_nodes(reduct, &args[taken..]));
1369                        }
1370                        Expr::App(_, _) => {
1371                            // The head STEPPED TO AN APPLICATION: a constant
1372                            // whose value is a partial application
1373                            // (`def f : Quot r → β := Quot.lift g h`), a
1374                            // projection that yields one (an instance field
1375                            // such as `⟨Quot.map g h⟩`), or a `let` body.
1376                            // Its own spine and the outer arguments are ONE
1377                            // application, so re-attach the outer arguments
1378                            // and hand the whole term back to the caller's
1379                            // loop, which re-flattens the spine. Lean's
1380                            // `whnf_core` does the same when the head
1381                            // changed (`whnf_core(mk_rev_app(f, args))`).
1382                            //
1383                            // Stepping the bare head in isolation (the old
1384                            // code) saw a recursor / `Quot.lift` / `Quot.ind`
1385                            // short of its major premise — the major is
1386                            // among the OUTER arguments — reported it stuck,
1387                            // and returned the application unreduced: the
1388                            // wrong-REJECT class of Mathlib's
1389                            // `AddCon.addGroup._proof_1`. It also unfolded a
1390                            // partially applied accelerated operation
1391                            // (`Nat.add 2` awaiting an outer `3`) before the
1392                            // literal extension could see both arguments.
1393                            //
1394                            // `get_app_fn` never returns an application, so
1395                            // this arm is only reached after at least one
1396                            // head step: it always makes progress.
1397                            return StepOutcome::Continue(mk_app_nodes(head_ref.clone(), &args));
1398                        }
1399                        Expr::Const(name, levels) => {
1400                            // 1. Literal folding (Nat/String kernel
1401                            //    extension), before delta — the Lean order.
1402                            if let Some(arity) = lit_op_arity(&name.to_string()) {
1403                                if args.len() == arity {
1404                                    let args_whnf: Vec<Expr> = args
1405                                        .iter()
1406                                        .map(|a| self.whnf_env_depth(a, env, depth + 1))
1407                                        .collect();
1408                                    if let Some(reduced) = try_reduce_nat_app(head_ref, &args_whnf)
1409                                    {
1410                                        let reduced = respell_bool_const_for_env(reduced, env);
1411                                        return StepOutcome::Continue(reduced);
1412                                    }
1413                                }
1414                            }
1415                            // 2. Iota (recursor) and quotient reduction.
1416                            if let Some(reduced) =
1417                                self.try_reduce_recursor(name, levels, &args, env, depth)
1418                            {
1419                                return StepOutcome::Continue(reduced);
1420                            }
1421                            if let Some(reduced) = self.try_reduce_quot(name, &args, env, depth) {
1422                                return StepOutcome::Continue(reduced);
1423                            }
1424                            // 3. Delta: unfold the head IN CONTEXT and keep
1425                            //    stepping (the next iteration β-reduces).
1426                            if let Some(ci) = env.find(name) {
1427                                if let Some(val) = ci.value() {
1428                                    if self.should_unfold_hint(ci.reducibility_hint()) {
1429                                        let unfolded = self.unfold_value(
1430                                            head_ref,
1431                                            val,
1432                                            ci.level_params(),
1433                                            levels,
1434                                        );
1435                                        head_owned = Some(unfolded);
1436                                        continue;
1437                                    }
1438                                }
1439                            }
1440                            break; // stuck constant head
1441                        }
1442                        // Let / Proj / FVar / Sort / Pi / Lit heads: step the
1443                        // head itself; `Done` means the head is in WHNF and
1444                        // the application is stuck.
1445                        _ => match self.whnf_step(head_ref, env, depth + 1) {
1446                            StepOutcome::Continue(h2) => {
1447                                head_owned = Some(h2);
1448                            }
1449                            StepOutcome::Done(h2) => {
1450                                head_owned = Some(h2);
1451                                break;
1452                            }
1453                        },
1454                    }
1455                }
1456                match head_owned {
1457                    Some(h) => StepOutcome::Done(mk_app_nodes(h, &args)),
1458                    None => StepOutcome::Done(expr.clone()),
1459                }
1460            }
1461            Expr::Proj(struct_name, idx, struct_expr) => {
1462                let mut struct_whnf = self.whnf_env_depth(struct_expr, env, depth + 1);
1463                // Lean v4.32 `reduce_proj_core`: a string-literal struct is
1464                // expanded (`String.ofList l`, then WHNF'd into constructor
1465                // form) before the field is projected — e.g.
1466                // `Proj String 0 (StrLit s)` must reduce, or Lean core's
1467                // `String.toByteArray ""` lemmas go stuck and are wrongly
1468                // rejected.
1469                if let Expr::Lit(Literal::Str(s)) = &struct_whnf {
1470                    if let Some(e) = super::iota::str_lit_expansion(s, env) {
1471                        struct_whnf = self.whnf_env_depth(&e, env, depth + 1);
1472                    }
1473                }
1474                if let Some(reduced) = try_reduce_proj(struct_name, *idx, &struct_whnf, env) {
1475                    return StepOutcome::Continue(reduced);
1476                }
1477                StepOutcome::Done(Expr::Proj(
1478                    struct_name.clone(),
1479                    *idx,
1480                    Node::new(struct_whnf),
1481                ))
1482            }
1483        }
1484    }
1485    /// Try to reduce a recursor application (iota-reduction), following the
1486    /// Lean 4 kernel: K-like reduction first (when the recursor carries the
1487    /// K flag), then WHNF of the major premise, then `Nat`/`String` literal
1488    /// expansion, and finally rule application. Rule right-hand sides are
1489    /// closed lambdas applied to `params ++ motives ++ minors ++ fields`;
1490    /// arguments beyond the major premise are re-applied (over-application
1491    /// never drops arguments). See [`super::iota`].
1492    fn try_reduce_recursor(
1493        &mut self,
1494        rec_name: &Name,
1495        rec_levels: &[crate::Level],
1496        args: &[&Node],
1497        env: &Environment,
1498        depth: u32,
1499    ) -> Option<Expr> {
1500        let rec_val = env.get_recursor_val(rec_name)?;
1501        let major_idx = rec_val.get_major_idx() as usize;
1502        if args.len() <= major_idx {
1503            return None;
1504        }
1505        // K-like reduction: replace the major premise by the canonical
1506        // constructor when its *type* has the right shape (Lean does this
1507        // before reducing the major itself).
1508        let major_arg: &Expr = args[major_idx];
1509        let mut major = if rec_val.k {
1510            super::iota::to_ctor_when_k(rec_val, major_arg, env, &self.local_fvar_types)
1511                .unwrap_or_else(|| major_arg.clone())
1512        } else {
1513            major_arg.clone()
1514        };
1515        major = self.whnf_env_depth(&major, env, depth + 1);
1516        // Literal-to-constructor expansion (one layer at a time for Nat), and
1517        // structure-eta expansion of stuck majors of structure-like inductives
1518        // (Lean's `toCtorWhenStruct`) on the fallthrough arm — same order as
1519        // lean4lean's `inductiveReduceRec`.
1520        match &major {
1521            Expr::Lit(Literal::Nat(n)) => {
1522                if let Some(e) = super::iota::nat_lit_to_ctor(n, rec_val) {
1523                    major = e;
1524                }
1525            }
1526            Expr::Lit(Literal::Str(s)) => {
1527                // Lean v4.32: `major = whnf(string_lit_to_constructor(major))`
1528                // — the expansion goes through the `String.ofList` *function*
1529                // (the constructor is `ofByteArray` since the UTF-8 String
1530                // refactor), so it must be WHNF'd into constructor form.
1531                if let Some(e) = super::iota::str_lit_expansion(s, env) {
1532                    major = self.whnf_env_depth(&e, env, depth + 1);
1533                }
1534            }
1535            _ => {
1536                if let Some(e) =
1537                    super::iota::to_ctor_when_struct(rec_val, &major, env, &self.local_fvar_types)
1538                {
1539                    major = e;
1540                }
1541            }
1542        }
1543        super::iota::apply_recursor_rule(rec_val, rec_levels, args, major_idx, &major, env)
1544    }
1545    /// Try to reduce a `Quot.lift` / `Quot.ind` application (quotient iota).
1546    ///
1547    /// This mirrors Lean's `quot.cpp` `quot_reduce_rec`:
1548    ///
1549    /// - `Quot.lift {α} {r} {β} (f : α → β) (h) (q : Quot r)` has `mk_pos = 5`
1550    ///   with the applied function `f` at position `3`. When the major premise
1551    ///   `q` reduces to `Quot.mk α r a`, the result is `f a`.
1552    /// - `Quot.ind {α} {r} {β} (m : ∀ a, β (Quot.mk r a)) (q : Quot r)` has
1553    ///   `mk_pos = 4` with the minor premise `m` at position `3`. When `q`
1554    ///   reduces to `Quot.mk α r a`, the result is `m a`.
1555    ///
1556    /// The quotiented element is always `mk_args[2]` of the fully-applied
1557    /// `Quot.mk` (`Quot.mk {α} (r) (a)` — three explicit-position arguments).
1558    ///
1559    /// Unlike a naive implementation, this:
1560    /// 1. WHNFs the major premise before inspecting its head, so a `q` that
1561    ///    only reduces to `Quot.mk` (e.g. `id (Quot.mk r a)`) still fires; and
1562    /// 2. re-applies any over-application arguments `args[mk_pos + 1 ..]` to
1563    ///    the result, so no argument is ever silently dropped (which would be
1564    ///    unsound on the def-eq path).
1565    fn try_reduce_quot(
1566        &mut self,
1567        name: &Name,
1568        args: &[&Node],
1569        env: &Environment,
1570        depth: u32,
1571    ) -> Option<Expr> {
1572        use crate::declaration::QuotKind;
1573        let qv = env.get_quotient_val(name)?;
1574        // (arg_pos, mk_pos): position of the applied function / minor premise,
1575        // and position of the major premise (the `Quot.mk` value).
1576        let (arg_pos, mk_pos) = match qv.kind {
1577            QuotKind::Lift => (3usize, 5usize),
1578            QuotKind::Ind => (3usize, 4usize),
1579            QuotKind::Type | QuotKind::Mk => return None,
1580        };
1581        // The major premise must be present.
1582        if args.len() <= mk_pos {
1583            return None;
1584        }
1585        // WHNF the major premise so terms that only *reduce* to `Quot.mk`
1586        // still fire the computation rule.
1587        let major_whnf = self.whnf_env_depth(args[mk_pos], env, depth + 1);
1588        let mk_head = get_app_fn(&major_whnf);
1589        let mk_name = if let Expr::Const(n, _) = mk_head {
1590            n
1591        } else {
1592            return None;
1593        };
1594        // The head must be a registered `Quot.mk`.
1595        let mk_qv = env.get_quotient_val(mk_name)?;
1596        if mk_qv.kind != QuotKind::Mk {
1597            return None;
1598        }
1599        // `Quot.mk {α} (r) (a)` — the quotiented element is `mk_args[2]`.
1600        let mk_args = get_app_arg_nodes(&major_whnf);
1601        if mk_args.len() < 3 {
1602            return None;
1603        }
1604        let a = mk_args[2];
1605        let f = args[arg_pos];
1606        let reduced = Expr::App(f.clone(), a.clone());
1607        // Re-apply any over-application arguments so they are never dropped.
1608        Some(mk_app_nodes(reduced, &args[mk_pos + 1..]))
1609    }
1610    /// Check if two expressions are alpha-equivalent (syntactically equal).
1611    pub fn is_alpha_equiv(e1: &Expr, e2: &Expr) -> bool {
1612        e1 == e2
1613    }
1614}
1615/// A set of rewrite rules.
1616#[allow(dead_code)]
1617pub struct RewriteRuleSet {
1618    rules: Vec<RewriteRule>,
1619}
1620#[allow(dead_code)]
1621impl RewriteRuleSet {
1622    /// Creates an empty rule set.
1623    pub fn new() -> Self {
1624        Self { rules: Vec::new() }
1625    }
1626    /// Adds a rule.
1627    pub fn add(&mut self, rule: RewriteRule) {
1628        self.rules.push(rule);
1629    }
1630    /// Returns the number of rules.
1631    pub fn len(&self) -> usize {
1632        self.rules.len()
1633    }
1634    /// Returns `true` if the set is empty.
1635    pub fn is_empty(&self) -> bool {
1636        self.rules.is_empty()
1637    }
1638    /// Returns all conditional rules.
1639    pub fn conditional_rules(&self) -> Vec<&RewriteRule> {
1640        self.rules.iter().filter(|r| r.conditional).collect()
1641    }
1642    /// Returns all unconditional rules.
1643    pub fn unconditional_rules(&self) -> Vec<&RewriteRule> {
1644        self.rules.iter().filter(|r| !r.conditional).collect()
1645    }
1646    /// Looks up a rule by name.
1647    pub fn get(&self, name: &str) -> Option<&RewriteRule> {
1648        self.rules.iter().find(|r| r.name == name)
1649    }
1650}
1651/// A reduction trace, collecting applied reduction steps.
1652#[allow(dead_code)]
1653#[derive(Debug, Default)]
1654pub struct ReductionTrace {
1655    steps: Vec<ReductionStep>,
1656    enabled: bool,
1657}
1658impl ReductionTrace {
1659    /// Create an enabled trace.
1660    #[allow(dead_code)]
1661    pub fn enabled() -> Self {
1662        Self {
1663            steps: Vec::new(),
1664            enabled: true,
1665        }
1666    }
1667    /// Create a disabled (no-op) trace.
1668    #[allow(dead_code)]
1669    pub fn disabled() -> Self {
1670        Self {
1671            steps: Vec::new(),
1672            enabled: false,
1673        }
1674    }
1675    /// Record a step if tracing is enabled.
1676    #[allow(dead_code)]
1677    pub fn record(&mut self, rule: ReductionRule, before: Expr, after: Expr) {
1678        if self.enabled {
1679            self.steps.push(ReductionStep {
1680                rule,
1681                before,
1682                after,
1683            });
1684        }
1685    }
1686    /// Return the number of recorded steps.
1687    #[allow(dead_code)]
1688    pub fn step_count(&self) -> usize {
1689        self.steps.len()
1690    }
1691    /// Return all recorded steps.
1692    #[allow(dead_code)]
1693    pub fn steps(&self) -> &[ReductionStep] {
1694        &self.steps
1695    }
1696    /// Clear all recorded steps.
1697    #[allow(dead_code)]
1698    pub fn clear(&mut self) {
1699        self.steps.clear();
1700    }
1701    /// Count steps by rule kind.
1702    #[allow(dead_code)]
1703    pub fn count_rule(&self, rule: &ReductionRule) -> usize {
1704        self.steps.iter().filter(|s| &s.rule == rule).count()
1705    }
1706}
1707/// A counter that can measure elapsed time between snapshots.
1708#[allow(dead_code)]
1709pub struct Stopwatch {
1710    start: crate::wall_clock::Instant,
1711    splits: Vec<f64>,
1712}
1713#[allow(dead_code)]
1714impl Stopwatch {
1715    /// Creates and starts a new stopwatch.
1716    pub fn start() -> Self {
1717        Self {
1718            start: crate::wall_clock::Instant::now(),
1719            splits: Vec::new(),
1720        }
1721    }
1722    /// Records a split time (elapsed since start).
1723    pub fn split(&mut self) {
1724        self.splits.push(self.elapsed_ms());
1725    }
1726    /// Returns total elapsed milliseconds since start.
1727    pub fn elapsed_ms(&self) -> f64 {
1728        self.start.elapsed().as_secs_f64() * 1000.0
1729    }
1730    /// Returns all recorded split times.
1731    pub fn splits(&self) -> &[f64] {
1732        &self.splits
1733    }
1734    /// Returns the number of splits.
1735    pub fn num_splits(&self) -> usize {
1736        self.splits.len()
1737    }
1738}
1739/// Transparency mode controls which definitions can be unfolded.
1740#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1741pub enum TransparencyMode {
1742    /// All definitions can be unfolded.
1743    All,
1744    /// Default: unfold non-opaque definitions.
1745    Default,
1746    /// Only unfold definitions marked as reducible/abbrev.
1747    Reducible,
1748    /// Only unfold instance definitions.
1749    Instances,
1750    /// Never unfold anything.
1751    None,
1752}
1753/// A flat list of substitution pairs `(from, to)`.
1754#[allow(dead_code)]
1755pub struct FlatSubstitution {
1756    pairs: Vec<(String, String)>,
1757}
1758#[allow(dead_code)]
1759impl FlatSubstitution {
1760    /// Creates an empty substitution.
1761    pub fn new() -> Self {
1762        Self { pairs: Vec::new() }
1763    }
1764    /// Adds a pair.
1765    pub fn add(&mut self, from: impl Into<String>, to: impl Into<String>) {
1766        self.pairs.push((from.into(), to.into()));
1767    }
1768    /// Applies all substitutions to `s` (leftmost-first order).
1769    pub fn apply(&self, s: &str) -> String {
1770        let mut result = s.to_string();
1771        for (from, to) in &self.pairs {
1772            result = result.replace(from.as_str(), to.as_str());
1773        }
1774        result
1775    }
1776    /// Returns the number of pairs.
1777    pub fn len(&self) -> usize {
1778        self.pairs.len()
1779    }
1780    /// Returns `true` if empty.
1781    pub fn is_empty(&self) -> bool {
1782        self.pairs.is_empty()
1783    }
1784}
1785/// A write-once cell.
1786#[allow(dead_code)]
1787pub struct WriteOnce<T> {
1788    value: std::cell::Cell<Option<T>>,
1789}
1790#[allow(dead_code)]
1791impl<T: Copy> WriteOnce<T> {
1792    /// Creates an empty write-once cell.
1793    pub fn new() -> Self {
1794        Self {
1795            value: std::cell::Cell::new(None),
1796        }
1797    }
1798    /// Writes a value.  Returns `false` if already written.
1799    pub fn write(&self, val: T) -> bool {
1800        if self.value.get().is_some() {
1801            return false;
1802        }
1803        self.value.set(Some(val));
1804        true
1805    }
1806    /// Returns the value if written.
1807    pub fn read(&self) -> Option<T> {
1808        self.value.get()
1809    }
1810    /// Returns `true` if the value has been written.
1811    pub fn is_written(&self) -> bool {
1812        self.value.get().is_some()
1813    }
1814}
1815/// A pair of `StatSummary` values tracking before/after a transformation.
1816#[allow(dead_code)]
1817pub struct TransformStat {
1818    before: StatSummary,
1819    after: StatSummary,
1820}
1821#[allow(dead_code)]
1822impl TransformStat {
1823    /// Creates a new transform stat recorder.
1824    pub fn new() -> Self {
1825        Self {
1826            before: StatSummary::new(),
1827            after: StatSummary::new(),
1828        }
1829    }
1830    /// Records a before value.
1831    pub fn record_before(&mut self, v: f64) {
1832        self.before.record(v);
1833    }
1834    /// Records an after value.
1835    pub fn record_after(&mut self, v: f64) {
1836        self.after.record(v);
1837    }
1838    /// Returns the mean reduction ratio (after/before).
1839    pub fn mean_ratio(&self) -> Option<f64> {
1840        let b = self.before.mean()?;
1841        let a = self.after.mean()?;
1842        if b.abs() < f64::EPSILON {
1843            return None;
1844        }
1845        Some(a / b)
1846    }
1847}
1848/// A reduction step record used for debugging and tracing.
1849#[allow(dead_code)]
1850#[derive(Debug, Clone)]
1851pub struct ReductionStep {
1852    /// Which reduction rule was applied.
1853    pub rule: ReductionRule,
1854    /// The expression before reduction.
1855    pub before: Expr,
1856    /// The expression after reduction.
1857    pub after: Expr,
1858}
1859/// A type-erased function pointer with arity tracking.
1860#[allow(dead_code)]
1861pub struct RawFnPtr {
1862    /// The raw function pointer (stored as usize for type erasure).
1863    ptr: usize,
1864    arity: usize,
1865    name: String,
1866}
1867#[allow(dead_code)]
1868impl RawFnPtr {
1869    /// Creates a new raw function pointer descriptor.
1870    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
1871        Self {
1872            ptr,
1873            arity,
1874            name: name.into(),
1875        }
1876    }
1877    /// Returns the arity.
1878    pub fn arity(&self) -> usize {
1879        self.arity
1880    }
1881    /// Returns the name.
1882    pub fn name(&self) -> &str {
1883        &self.name
1884    }
1885    /// Returns the raw pointer value.
1886    pub fn raw(&self) -> usize {
1887        self.ptr
1888    }
1889}
1890/// Reducibility hint controls when definitions are unfolded.
1891#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1892pub enum ReducibilityHint {
1893    /// Never unfold (like theorem proofs)
1894    Opaque,
1895    /// Always unfold first (like notation)
1896    Abbrev,
1897    /// Unfold based on height (lower = unfold first)
1898    Regular(u32),
1899}
1900impl ReducibilityHint {
1901    /// Get the unfolding height (lower = unfold first)
1902    pub fn height(&self) -> u32 {
1903        match self {
1904            ReducibilityHint::Opaque => u32::MAX,
1905            ReducibilityHint::Abbrev => 0,
1906            ReducibilityHint::Regular(h) => *h,
1907        }
1908    }
1909    /// Should this definition be unfolded?
1910    pub fn should_unfold(&self) -> bool {
1911        !matches!(self, ReducibilityHint::Opaque)
1912    }
1913}