Skip to main content

oxilean_kernel/proof/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::Node;
6use crate::{Expr, LevelView, Name};
7use std::collections::{HashMap, HashSet};
8use std::rc::Rc;
9
10use super::functions::*;
11
12/// Proof term utilities.
13pub struct ProofTerm;
14impl ProofTerm {
15    /// Check if an expression is a proof of a proposition.
16    ///
17    /// In CiC, propositions are types in Prop. A proof is a term
18    /// inhabiting such a type. Without a type checker available,
19    /// we use structural heuristics.
20    pub fn is_proof(term: &Expr, prop: &Expr) -> bool {
21        if Self::could_be_prop(prop) {
22            Self::is_well_formed(term)
23        } else {
24            false
25        }
26    }
27    /// Check if a type expression could be a proposition (in Sort 0).
28    ///
29    /// A type is a Prop if its universe is Sort 0.
30    pub fn could_be_prop(ty: &Expr) -> bool {
31        match ty {
32            Expr::Sort(l) if matches!(l.view(), LevelView::Zero) => true,
33            Expr::Pi(_, _, _, body) => Self::could_be_prop(body),
34            Expr::Const(_, _) => true,
35            Expr::App(_, _) => true,
36            _ => false,
37        }
38    }
39    /// Check if a type is definitely Prop (Sort 0).
40    pub fn is_sort_zero(ty: &Expr) -> bool {
41        matches!(ty, Expr::Sort(l) if matches!(l.view(), LevelView::Zero))
42    }
43    /// Extract the proposition from a proof term's type annotation.
44    ///
45    /// If the term is `(t : P)` where P is a Prop, returns P.
46    pub fn get_proposition(term: &Expr) -> Option<Expr> {
47        match term {
48            Expr::Lam(_, _, ty, _) if Self::could_be_prop(ty) => Some((**ty).clone()),
49            _ => None,
50        }
51    }
52    /// Check if a proof uses only constructive axioms.
53    ///
54    /// A proof is constructive if it doesn't depend on:
55    /// - Classical.choice
56    /// - Classical.em (excluded middle)
57    /// - propext (propositional extensionality, though debatable)
58    pub fn is_constructive(term: &Expr) -> bool {
59        let deps = collect_axiom_deps(term);
60        !deps
61            .iter()
62            .any(|name: &Name| CLASSICAL_AXIOMS.contains(&name.to_string().as_str()))
63    }
64    /// Collect all constant names used in a term.
65    pub fn collect_constants(term: &Expr) -> HashSet<Name> {
66        let mut result = HashSet::new();
67        collect_constants_impl(term, &mut result);
68        result
69    }
70    /// Count the size (number of nodes) of a proof term.
71    pub fn size(term: &Expr) -> usize {
72        match term {
73            Expr::BVar(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Sort(_) | Expr::Lit(_) => 1,
74            Expr::App(f, a) => 1 + Self::size(f) + Self::size(a),
75            Expr::Lam(_, _, ty, body) => 1 + Self::size(ty) + Self::size(body),
76            Expr::Pi(_, _, ty, body) => 1 + Self::size(ty) + Self::size(body),
77            Expr::Let(_, ty, val, body) => 1 + Self::size(ty) + Self::size(val) + Self::size(body),
78            Expr::Proj(_, _, e) => 1 + Self::size(e),
79        }
80    }
81    /// Compute the depth of a proof term.
82    pub fn depth(term: &Expr) -> usize {
83        match term {
84            Expr::BVar(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Sort(_) | Expr::Lit(_) => 0,
85            Expr::App(f, a) => 1 + Self::depth(f).max(Self::depth(a)),
86            Expr::Lam(_, _, ty, body) => 1 + Self::depth(ty).max(Self::depth(body)),
87            Expr::Pi(_, _, ty, body) => 1 + Self::depth(ty).max(Self::depth(body)),
88            Expr::Let(_, ty, val, body) => {
89                1 + Self::depth(ty).max(Self::depth(val)).max(Self::depth(body))
90            }
91            Expr::Proj(_, _, e) => 1 + Self::depth(e),
92        }
93    }
94    /// Check if a term is well-formed (basic structural check).
95    fn is_well_formed(term: &Expr) -> bool {
96        match term {
97            Expr::BVar(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Sort(_) | Expr::Lit(_) => {
98                true
99            }
100            Expr::App(f, a) => Self::is_well_formed(f) && Self::is_well_formed(a),
101            Expr::Lam(_, _, ty, body) => Self::is_well_formed(ty) && Self::is_well_formed(body),
102            Expr::Pi(_, _, ty, body) => Self::is_well_formed(ty) && Self::is_well_formed(body),
103            Expr::Let(_, ty, val, body) => {
104                Self::is_well_formed(ty) && Self::is_well_formed(val) && Self::is_well_formed(body)
105            }
106            Expr::Proj(_, _, e) => Self::is_well_formed(e),
107        }
108    }
109    /// Check if two proofs are of the same proposition structure.
110    ///
111    /// When proof irrelevance is enabled, this can be used to determine
112    /// if two proofs should be considered equal.
113    pub fn same_proposition_structure(p1: &Expr, p2: &Expr) -> bool {
114        match (p1, p2) {
115            (Expr::Sort(l1), Expr::Sort(l2)) => l1 == l2,
116            (Expr::Const(n1, _), Expr::Const(n2, _)) => n1 == n2,
117            (Expr::App(f1, a1), Expr::App(f2, a2)) => {
118                Self::same_proposition_structure(f1, f2) && Self::same_proposition_structure(a1, a2)
119            }
120            (Expr::Pi(_, _, ty1, body1), Expr::Pi(_, _, ty2, body2)) => {
121                Self::same_proposition_structure(ty1, ty2)
122                    && Self::same_proposition_structure(body1, body2)
123            }
124            _ => p1 == p2,
125        }
126    }
127}
128/// A flat list of substitution pairs `(from, to)`.
129#[allow(dead_code)]
130pub struct FlatSubstitution {
131    pairs: Vec<(String, String)>,
132}
133#[allow(dead_code)]
134impl FlatSubstitution {
135    /// Creates an empty substitution.
136    pub fn new() -> Self {
137        Self { pairs: Vec::new() }
138    }
139    /// Adds a pair.
140    pub fn add(&mut self, from: impl Into<String>, to: impl Into<String>) {
141        self.pairs.push((from.into(), to.into()));
142    }
143    /// Applies all substitutions to `s` (leftmost-first order).
144    pub fn apply(&self, s: &str) -> String {
145        let mut result = s.to_string();
146        for (from, to) in &self.pairs {
147            result = result.replace(from.as_str(), to.as_str());
148        }
149        result
150    }
151    /// Returns the number of pairs.
152    pub fn len(&self) -> usize {
153        self.pairs.len()
154    }
155    /// Returns `true` if empty.
156    pub fn is_empty(&self) -> bool {
157        self.pairs.is_empty()
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 trie-based prefix counter.
241#[allow(dead_code)]
242pub struct PrefixCounter {
243    children: std::collections::HashMap<char, PrefixCounter>,
244    count: usize,
245}
246#[allow(dead_code)]
247impl PrefixCounter {
248    /// Creates an empty prefix counter.
249    pub fn new() -> Self {
250        Self {
251            children: std::collections::HashMap::new(),
252            count: 0,
253        }
254    }
255    /// Records a string.
256    pub fn record(&mut self, s: &str) {
257        self.count += 1;
258        let mut node = self;
259        for c in s.chars() {
260            node = node.children.entry(c).or_default();
261            node.count += 1;
262        }
263    }
264    /// Returns how many strings have been recorded that start with `prefix`.
265    pub fn count_with_prefix(&self, prefix: &str) -> usize {
266        let mut node = self;
267        for c in prefix.chars() {
268            match node.children.get(&c) {
269                Some(n) => node = n,
270                None => return 0,
271            }
272        }
273        node.count
274    }
275}
276/// A simple key-value store backed by a sorted Vec for small maps.
277#[allow(dead_code)]
278pub struct SmallMap<K: Ord + Clone, V: Clone> {
279    entries: Vec<(K, V)>,
280}
281#[allow(dead_code)]
282impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
283    /// Creates a new empty small map.
284    pub fn new() -> Self {
285        Self {
286            entries: Vec::new(),
287        }
288    }
289    /// Inserts or replaces the value for `key`.
290    pub fn insert(&mut self, key: K, val: V) {
291        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
292            Ok(i) => self.entries[i].1 = val,
293            Err(i) => self.entries.insert(i, (key, val)),
294        }
295    }
296    /// Returns the value for `key`, or `None`.
297    pub fn get(&self, key: &K) -> Option<&V> {
298        self.entries
299            .binary_search_by_key(&key, |(k, _)| k)
300            .ok()
301            .map(|i| &self.entries[i].1)
302    }
303    /// Returns the number of entries.
304    pub fn len(&self) -> usize {
305        self.entries.len()
306    }
307    /// Returns `true` if empty.
308    pub fn is_empty(&self) -> bool {
309        self.entries.is_empty()
310    }
311    /// Returns all keys.
312    pub fn keys(&self) -> Vec<&K> {
313        self.entries.iter().map(|(k, _)| k).collect()
314    }
315    /// Returns all values.
316    pub fn values(&self) -> Vec<&V> {
317        self.entries.iter().map(|(_, v)| v).collect()
318    }
319}
320/// A window iterator that yields overlapping windows of size `n`.
321#[allow(dead_code)]
322pub struct WindowIterator<'a, T> {
323    pub(super) data: &'a [T],
324    pub(super) pos: usize,
325    pub(super) window: usize,
326}
327#[allow(dead_code)]
328impl<'a, T> WindowIterator<'a, T> {
329    /// Creates a new window iterator.
330    pub fn new(data: &'a [T], window: usize) -> Self {
331        Self {
332            data,
333            pos: 0,
334            window,
335        }
336    }
337}
338/// A generic counter that tracks min/max/sum for statistical summaries.
339#[allow(dead_code)]
340pub struct StatSummary {
341    count: u64,
342    sum: f64,
343    min: f64,
344    max: f64,
345}
346#[allow(dead_code)]
347impl StatSummary {
348    /// Creates an empty summary.
349    pub fn new() -> Self {
350        Self {
351            count: 0,
352            sum: 0.0,
353            min: f64::INFINITY,
354            max: f64::NEG_INFINITY,
355        }
356    }
357    /// Records a sample.
358    pub fn record(&mut self, val: f64) {
359        self.count += 1;
360        self.sum += val;
361        if val < self.min {
362            self.min = val;
363        }
364        if val > self.max {
365            self.max = val;
366        }
367    }
368    /// Returns the mean, or `None` if no samples.
369    pub fn mean(&self) -> Option<f64> {
370        if self.count == 0 {
371            None
372        } else {
373            Some(self.sum / self.count as f64)
374        }
375    }
376    /// Returns the minimum, or `None` if no samples.
377    pub fn min(&self) -> Option<f64> {
378        if self.count == 0 {
379            None
380        } else {
381            Some(self.min)
382        }
383    }
384    /// Returns the maximum, or `None` if no samples.
385    pub fn max(&self) -> Option<f64> {
386        if self.count == 0 {
387            None
388        } else {
389            Some(self.max)
390        }
391    }
392    /// Returns the count of recorded samples.
393    pub fn count(&self) -> u64 {
394        self.count
395    }
396}
397/// A collection of proof obligations, tracking overall proof state.
398#[allow(dead_code)]
399#[derive(Debug, Clone, Default)]
400pub struct ProofState {
401    /// All obligations in this proof attempt.
402    pub obligations: Vec<ProofObligation>,
403}
404impl ProofState {
405    /// Create a fresh proof state with no obligations.
406    #[allow(dead_code)]
407    pub fn new() -> Self {
408        Self::default()
409    }
410    /// Add a new proof obligation.
411    #[allow(dead_code)]
412    pub fn add_obligation(&mut self, label: impl Into<String>, proposition: Expr) {
413        self.obligations
414            .push(ProofObligation::new(label, proposition));
415    }
416    /// Number of obligations that remain undischarged.
417    #[allow(dead_code)]
418    pub fn remaining(&self) -> usize {
419        self.obligations
420            .iter()
421            .filter(|o| !o.is_discharged())
422            .count()
423    }
424    /// Whether all obligations are discharged.
425    #[allow(dead_code)]
426    pub fn is_complete(&self) -> bool {
427        self.remaining() == 0
428    }
429    /// Discharge the first undischarged obligation with the given proof.
430    ///
431    /// Returns `true` if an obligation was discharged, `false` if all were already done.
432    #[allow(dead_code)]
433    pub fn discharge_next(&mut self, proof: Expr) -> bool {
434        for obl in self.obligations.iter_mut() {
435            if !obl.discharged {
436                obl.discharge(proof);
437                return true;
438            }
439        }
440        false
441    }
442    /// Returns references to all undischarged obligations.
443    #[allow(dead_code)]
444    pub fn open_obligations(&self) -> Vec<&ProofObligation> {
445        self.obligations
446            .iter()
447            .filter(|o| !o.is_discharged())
448            .collect()
449    }
450}
451/// A simple directed acyclic graph.
452#[allow(dead_code)]
453pub struct SimpleDag {
454    /// `edges[i]` is the list of direct successors of node `i`.
455    edges: Vec<Vec<usize>>,
456}
457#[allow(dead_code)]
458impl SimpleDag {
459    /// Creates a DAG with `n` nodes and no edges.
460    pub fn new(n: usize) -> Self {
461        Self {
462            edges: vec![Vec::new(); n],
463        }
464    }
465    /// Adds an edge from `from` to `to`.
466    pub fn add_edge(&mut self, from: usize, to: usize) {
467        if from < self.edges.len() {
468            self.edges[from].push(to);
469        }
470    }
471    /// Returns the successors of `node`.
472    pub fn successors(&self, node: usize) -> &[usize] {
473        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
474    }
475    /// Returns `true` if `from` can reach `to` via DFS.
476    pub fn can_reach(&self, from: usize, to: usize) -> bool {
477        let mut visited = vec![false; self.edges.len()];
478        self.dfs(from, to, &mut visited)
479    }
480    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
481        if cur == target {
482            return true;
483        }
484        if cur >= visited.len() || visited[cur] {
485            return false;
486        }
487        visited[cur] = true;
488        for &next in self.successors(cur) {
489            if self.dfs(next, target, visited) {
490                return true;
491            }
492        }
493        false
494    }
495    /// Returns the topological order of nodes, or `None` if a cycle is detected.
496    pub fn topological_sort(&self) -> Option<Vec<usize>> {
497        let n = self.edges.len();
498        let mut in_degree = vec![0usize; n];
499        for succs in &self.edges {
500            for &s in succs {
501                if s < n {
502                    in_degree[s] += 1;
503                }
504            }
505        }
506        let mut queue: std::collections::VecDeque<usize> =
507            (0..n).filter(|&i| in_degree[i] == 0).collect();
508        let mut order = Vec::new();
509        while let Some(node) = queue.pop_front() {
510            order.push(node);
511            for &s in self.successors(node) {
512                if s < n {
513                    in_degree[s] -= 1;
514                    if in_degree[s] == 0 {
515                        queue.push_back(s);
516                    }
517                }
518            }
519        }
520        if order.len() == n {
521            Some(order)
522        } else {
523            None
524        }
525    }
526    /// Returns the number of nodes.
527    pub fn num_nodes(&self) -> usize {
528        self.edges.len()
529    }
530}
531/// A dependency closure builder (transitive closure via BFS).
532#[allow(dead_code)]
533pub struct TransitiveClosure {
534    adj: Vec<Vec<usize>>,
535    n: usize,
536}
537#[allow(dead_code)]
538impl TransitiveClosure {
539    /// Creates a transitive closure builder for `n` nodes.
540    pub fn new(n: usize) -> Self {
541        Self {
542            adj: vec![Vec::new(); n],
543            n,
544        }
545    }
546    /// Adds a direct edge.
547    pub fn add_edge(&mut self, from: usize, to: usize) {
548        if from < self.n {
549            self.adj[from].push(to);
550        }
551    }
552    /// Computes all nodes reachable from `start` (including `start`).
553    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
554        let mut visited = vec![false; self.n];
555        let mut queue = std::collections::VecDeque::new();
556        queue.push_back(start);
557        while let Some(node) = queue.pop_front() {
558            if node >= self.n || visited[node] {
559                continue;
560            }
561            visited[node] = true;
562            for &next in &self.adj[node] {
563                queue.push_back(next);
564            }
565        }
566        (0..self.n).filter(|&i| visited[i]).collect()
567    }
568    /// Returns `true` if `from` can transitively reach `to`.
569    pub fn can_reach(&self, from: usize, to: usize) -> bool {
570        self.reachable_from(from).contains(&to)
571    }
572}
573/// A reusable scratch buffer for path computations.
574#[allow(dead_code)]
575pub struct PathBuf {
576    components: Vec<String>,
577}
578#[allow(dead_code)]
579impl PathBuf {
580    /// Creates a new empty path buffer.
581    pub fn new() -> Self {
582        Self {
583            components: Vec::new(),
584        }
585    }
586    /// Pushes a component.
587    pub fn push(&mut self, comp: impl Into<String>) {
588        self.components.push(comp.into());
589    }
590    /// Pops the last component.
591    pub fn pop(&mut self) {
592        self.components.pop();
593    }
594    /// Returns the current path as a `/`-separated string.
595    pub fn as_str(&self) -> String {
596        self.components.join("/")
597    }
598    /// Returns the depth of the path.
599    pub fn depth(&self) -> usize {
600        self.components.len()
601    }
602    /// Clears the path.
603    pub fn clear(&mut self) {
604        self.components.clear();
605    }
606}
607/// A set of rewrite rules.
608#[allow(dead_code)]
609pub struct RewriteRuleSet {
610    rules: Vec<RewriteRule>,
611}
612#[allow(dead_code)]
613impl RewriteRuleSet {
614    /// Creates an empty rule set.
615    pub fn new() -> Self {
616        Self { rules: Vec::new() }
617    }
618    /// Adds a rule.
619    pub fn add(&mut self, rule: RewriteRule) {
620        self.rules.push(rule);
621    }
622    /// Returns the number of rules.
623    pub fn len(&self) -> usize {
624        self.rules.len()
625    }
626    /// Returns `true` if the set is empty.
627    pub fn is_empty(&self) -> bool {
628        self.rules.is_empty()
629    }
630    /// Returns all conditional rules.
631    pub fn conditional_rules(&self) -> Vec<&RewriteRule> {
632        self.rules.iter().filter(|r| r.conditional).collect()
633    }
634    /// Returns all unconditional rules.
635    pub fn unconditional_rules(&self) -> Vec<&RewriteRule> {
636        self.rules.iter().filter(|r| !r.conditional).collect()
637    }
638    /// Looks up a rule by name.
639    pub fn get(&self, name: &str) -> Option<&RewriteRule> {
640        self.rules.iter().find(|r| r.name == name)
641    }
642}
643/// Describes the complexity class of a proof term.
644#[allow(dead_code)]
645#[derive(Debug, Clone, PartialEq, Eq)]
646pub enum ProofComplexity {
647    /// A single atomic term (constant, BVar, FVar, literal, sort).
648    Atomic,
649    /// A lambda abstraction wrapping a simpler proof.
650    Abstraction,
651    /// An application (function applied to argument).
652    Application,
653    /// A `let`-binding with a subproof body.
654    LetBinding,
655    /// A projection out of a structure.
656    Projection,
657    /// Any composite term beyond the above categories.
658    Composite,
659}
660/// A proof analysis engine.
661pub struct ProofAnalyzer;
662impl ProofAnalyzer {
663    /// Check whether a proof is constructive (no classical axioms).
664    pub fn is_constructive(term: &Expr) -> bool {
665        !Self::uses_classical(term)
666    }
667    /// Check whether a proof uses any classical axioms.
668    pub fn uses_classical(term: &Expr) -> bool {
669        match term {
670            Expr::Const(n, _) => {
671                let s = n.to_string();
672                s == "Classical.choice" || s == "Classical.em" || s == "propext"
673            }
674            Expr::App(f, a) => Self::uses_classical(f) || Self::uses_classical(a),
675            Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
676                Self::uses_classical(ty) || Self::uses_classical(body)
677            }
678            Expr::Let(_, ty, val, body) => {
679                Self::uses_classical(ty) || Self::uses_classical(val) || Self::uses_classical(body)
680            }
681            _ => false,
682        }
683    }
684    /// Count the number of applications in a proof term.
685    pub fn count_applications(term: &Expr) -> usize {
686        match term {
687            Expr::App(f, a) => 1 + Self::count_applications(f) + Self::count_applications(a),
688            Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
689                Self::count_applications(ty) + Self::count_applications(body)
690            }
691            Expr::Let(_, ty, val, body) => {
692                Self::count_applications(ty)
693                    + Self::count_applications(val)
694                    + Self::count_applications(body)
695            }
696            _ => 0,
697        }
698    }
699}
700/// A minimal proof normalizer that applies beta reduction to proof terms.
701///
702/// In the kernel, proof terms are normalised before definitional equality
703/// checks to avoid spurious inequality due to un-reduced redexes.
704#[allow(dead_code)]
705pub struct ProofNormalizer;
706impl ProofNormalizer {
707    /// Beta-reduce a proof term to its normal form.
708    ///
709    /// This is a simple structural pass that eliminates `(fun x => body) arg`
710    /// redexes by substituting `arg` for bound variable `0` in `body`.
711    #[allow(dead_code)]
712    pub fn beta_reduce(term: &Expr) -> Expr {
713        match term {
714            Expr::App(f, arg) => {
715                let f_reduced = Self::beta_reduce(f);
716                let arg_reduced = Self::beta_reduce(arg);
717                if let Expr::Lam(_, _, _, body) = f_reduced {
718                    let substituted = Self::subst_bvar((*body).clone(), 0, &arg_reduced);
719                    Self::beta_reduce(&substituted)
720                } else {
721                    Expr::App(Node::new(f_reduced), Node::new(arg_reduced))
722                }
723            }
724            Expr::Lam(bk, n, ty, body) => {
725                let ty_red = Self::beta_reduce(ty);
726                let body_red = Self::beta_reduce(body);
727                Expr::Lam(*bk, n.clone(), Node::new(ty_red), Node::new(body_red))
728            }
729            Expr::Pi(bk, n, ty, body) => {
730                let ty_red = Self::beta_reduce(ty);
731                let body_red = Self::beta_reduce(body);
732                Expr::Pi(*bk, n.clone(), Node::new(ty_red), Node::new(body_red))
733            }
734            Expr::Let(_n, _ty, val, body) => {
735                let val_red = Self::beta_reduce(val);
736                let body_subst = Self::subst_bvar((**body).clone(), 0, &val_red);
737                Self::beta_reduce(&body_subst)
738            }
739            Expr::Proj(idx, n, e) => Expr::Proj(idx.clone(), *n, Node::new(Self::beta_reduce(e))),
740            other => other.clone(),
741        }
742    }
743    /// Substitute `replacement` for bound variable `depth` in `term`.
744    ///
745    /// This implements the standard capture-avoiding substitution for de Bruijn terms.
746    #[allow(dead_code)]
747    pub fn subst_bvar(term: Expr, depth: u32, replacement: &Expr) -> Expr {
748        match term {
749            Expr::BVar(i) => {
750                if i == depth {
751                    replacement.clone()
752                } else if i > depth {
753                    Expr::BVar(i - 1)
754                } else {
755                    Expr::BVar(i)
756                }
757            }
758            Expr::App(f, a) => Expr::App(
759                Node::new(Self::subst_bvar((*f).clone(), depth, replacement)),
760                Node::new(Self::subst_bvar((*a).clone(), depth, replacement)),
761            ),
762            Expr::Lam(bk, n, ty, body) => Expr::Lam(
763                bk,
764                n,
765                Node::new(Self::subst_bvar((*ty).clone(), depth, replacement)),
766                Node::new(Self::subst_bvar((*body).clone(), depth + 1, replacement)),
767            ),
768            Expr::Pi(bk, n, ty, body) => Expr::Pi(
769                bk,
770                n,
771                Node::new(Self::subst_bvar((*ty).clone(), depth, replacement)),
772                Node::new(Self::subst_bvar((*body).clone(), depth + 1, replacement)),
773            ),
774            Expr::Let(n, ty, val, body) => Expr::Let(
775                n,
776                Node::new(Self::subst_bvar((*ty).clone(), depth, replacement)),
777                Node::new(Self::subst_bvar((*val).clone(), depth, replacement)),
778                Node::new(Self::subst_bvar((*body).clone(), depth + 1, replacement)),
779            ),
780            Expr::Proj(idx, n, e) => Expr::Proj(
781                idx,
782                n,
783                Node::new(Self::subst_bvar((*e).clone(), depth, replacement)),
784            ),
785            other => other,
786        }
787    }
788    /// Count the number of beta-redexes remaining in a term.
789    #[allow(dead_code)]
790    pub fn count_redexes(term: &Expr) -> usize {
791        match term {
792            Expr::App(f, a) => {
793                let in_f = Self::count_redexes(f);
794                let in_a = Self::count_redexes(a);
795                let is_redex = matches!(f.as_ref(), Expr::Lam(_, _, _, _));
796                in_f + in_a + if is_redex { 1 } else { 0 }
797            }
798            Expr::Lam(_, _, ty, body) => Self::count_redexes(ty) + Self::count_redexes(body),
799            Expr::Pi(_, _, ty, body) => Self::count_redexes(ty) + Self::count_redexes(body),
800            Expr::Let(_, ty, val, body) => {
801                Self::count_redexes(ty) + Self::count_redexes(val) + Self::count_redexes(body)
802            }
803            Expr::Proj(_, _, e) => Self::count_redexes(e),
804            _ => 0,
805        }
806    }
807    /// Returns `true` if the term is already in beta-normal form (no redexes).
808    #[allow(dead_code)]
809    pub fn is_beta_normal(term: &Expr) -> bool {
810        Self::count_redexes(term) == 0
811    }
812}
813/// A proof skeleton: a proof term with holes (`sorry`) that need to be filled.
814///
815/// Proof skeletons are used in structured proof construction to track
816/// what remains to be proved.
817#[allow(dead_code)]
818#[derive(Clone, Debug)]
819pub struct ProofSkeleton {
820    /// The proof term, which may contain `sorry` nodes.
821    pub term: Expr,
822    /// The type (proposition) being proved.
823    pub ty: Expr,
824    /// Names of the holes that still need proofs.
825    pub holes: Vec<Name>,
826}
827impl ProofSkeleton {
828    /// Create a new proof skeleton.
829    #[allow(dead_code)]
830    pub fn new(term: Expr, ty: Expr) -> Self {
831        let holes = Self::collect_holes(&term);
832        Self { term, ty, holes }
833    }
834    /// Collect all `sorry` positions from a term.
835    fn collect_holes(term: &Expr) -> Vec<Name> {
836        let mut holes = Vec::new();
837        Self::collect_holes_rec(term, &mut holes);
838        holes
839    }
840    fn collect_holes_rec(term: &Expr, holes: &mut Vec<Name>) {
841        match term {
842            Expr::Const(n, _) if n.to_string().contains("sorry") => {
843                holes.push(n.clone());
844            }
845            Expr::App(f, a) => {
846                Self::collect_holes_rec(f, holes);
847                Self::collect_holes_rec(a, holes);
848            }
849            Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
850                Self::collect_holes_rec(ty, holes);
851                Self::collect_holes_rec(body, holes);
852            }
853            Expr::Let(_, ty, val, body) => {
854                Self::collect_holes_rec(ty, holes);
855                Self::collect_holes_rec(val, holes);
856                Self::collect_holes_rec(body, holes);
857            }
858            _ => {}
859        }
860    }
861    /// Whether the skeleton has no remaining holes.
862    #[allow(dead_code)]
863    pub fn is_complete(&self) -> bool {
864        self.holes.is_empty()
865    }
866    /// Number of remaining holes.
867    #[allow(dead_code)]
868    pub fn num_holes(&self) -> usize {
869        self.holes.len()
870    }
871}
872/// A pair of `StatSummary` values tracking before/after a transformation.
873#[allow(dead_code)]
874pub struct TransformStat {
875    before: StatSummary,
876    after: StatSummary,
877}
878#[allow(dead_code)]
879impl TransformStat {
880    /// Creates a new transform stat recorder.
881    pub fn new() -> Self {
882        Self {
883            before: StatSummary::new(),
884            after: StatSummary::new(),
885        }
886    }
887    /// Records a before value.
888    pub fn record_before(&mut self, v: f64) {
889        self.before.record(v);
890    }
891    /// Records an after value.
892    pub fn record_after(&mut self, v: f64) {
893        self.after.record(v);
894    }
895    /// Returns the mean reduction ratio (after/before).
896    pub fn mean_ratio(&self) -> Option<f64> {
897        let b = self.before.mean()?;
898        let a = self.after.mean()?;
899        if b.abs() < f64::EPSILON {
900            return None;
901        }
902        Some(a / b)
903    }
904}
905/// A non-empty list (at least one element guaranteed).
906#[allow(dead_code)]
907pub struct NonEmptyVec<T> {
908    head: T,
909    tail: Vec<T>,
910}
911#[allow(dead_code)]
912impl<T> NonEmptyVec<T> {
913    /// Creates a non-empty vec with a single element.
914    pub fn singleton(val: T) -> Self {
915        Self {
916            head: val,
917            tail: Vec::new(),
918        }
919    }
920    /// Pushes an element.
921    pub fn push(&mut self, val: T) {
922        self.tail.push(val);
923    }
924    /// Returns a reference to the first element.
925    pub fn first(&self) -> &T {
926        &self.head
927    }
928    /// Returns a reference to the last element.
929    pub fn last(&self) -> &T {
930        self.tail.last().unwrap_or(&self.head)
931    }
932    /// Returns the number of elements.
933    pub fn len(&self) -> usize {
934        1 + self.tail.len()
935    }
936    /// Returns whether the collection is empty.
937    pub fn is_empty(&self) -> bool {
938        self.len() == 0
939    }
940    /// Returns all elements as a Vec.
941    pub fn to_vec(&self) -> Vec<&T> {
942        let mut v = vec![&self.head];
943        v.extend(self.tail.iter());
944        v
945    }
946}
947/// A pool of reusable string buffers.
948#[allow(dead_code)]
949pub struct StringPool {
950    free: Vec<String>,
951}
952#[allow(dead_code)]
953impl StringPool {
954    /// Creates a new empty string pool.
955    pub fn new() -> Self {
956        Self { free: Vec::new() }
957    }
958    /// Takes a string from the pool (may be empty).
959    pub fn take(&mut self) -> String {
960        self.free.pop().unwrap_or_default()
961    }
962    /// Returns a string to the pool.
963    pub fn give(&mut self, mut s: String) {
964        s.clear();
965        self.free.push(s);
966    }
967    /// Returns the number of free strings in the pool.
968    pub fn free_count(&self) -> usize {
969        self.free.len()
970    }
971}
972/// Represents a rewrite rule `lhs → rhs`.
973#[allow(dead_code)]
974#[allow(missing_docs)]
975pub struct RewriteRule {
976    /// The name of the rule.
977    pub name: String,
978    /// A string representation of the LHS pattern.
979    pub lhs: String,
980    /// A string representation of the RHS.
981    pub rhs: String,
982    /// Whether this is a conditional rule (has side conditions).
983    pub conditional: bool,
984}
985#[allow(dead_code)]
986impl RewriteRule {
987    /// Creates an unconditional rewrite rule.
988    pub fn unconditional(
989        name: impl Into<String>,
990        lhs: impl Into<String>,
991        rhs: impl Into<String>,
992    ) -> Self {
993        Self {
994            name: name.into(),
995            lhs: lhs.into(),
996            rhs: rhs.into(),
997            conditional: false,
998        }
999    }
1000    /// Creates a conditional rewrite rule.
1001    pub fn conditional(
1002        name: impl Into<String>,
1003        lhs: impl Into<String>,
1004        rhs: impl Into<String>,
1005    ) -> Self {
1006        Self {
1007            name: name.into(),
1008            lhs: lhs.into(),
1009            rhs: rhs.into(),
1010            conditional: true,
1011        }
1012    }
1013    /// Returns a textual representation.
1014    pub fn display(&self) -> String {
1015        format!("{}: {} → {}", self.name, self.lhs, self.rhs)
1016    }
1017}
1018/// A type-erased function pointer with arity tracking.
1019#[allow(dead_code)]
1020pub struct RawFnPtr {
1021    /// The raw function pointer (stored as usize for type erasure).
1022    ptr: usize,
1023    arity: usize,
1024    name: String,
1025}
1026#[allow(dead_code)]
1027impl RawFnPtr {
1028    /// Creates a new raw function pointer descriptor.
1029    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
1030        Self {
1031            ptr,
1032            arity,
1033            name: name.into(),
1034        }
1035    }
1036    /// Returns the arity.
1037    pub fn arity(&self) -> usize {
1038        self.arity
1039    }
1040    /// Returns the name.
1041    pub fn name(&self) -> &str {
1042        &self.name
1043    }
1044    /// Returns the raw pointer value.
1045    pub fn raw(&self) -> usize {
1046        self.ptr
1047    }
1048}
1049/// A write-once cell.
1050#[allow(dead_code)]
1051pub struct WriteOnce<T> {
1052    value: std::cell::Cell<Option<T>>,
1053}
1054#[allow(dead_code)]
1055impl<T: Copy> WriteOnce<T> {
1056    /// Creates an empty write-once cell.
1057    pub fn new() -> Self {
1058        Self {
1059            value: std::cell::Cell::new(None),
1060        }
1061    }
1062    /// Writes a value.  Returns `false` if already written.
1063    pub fn write(&self, val: T) -> bool {
1064        if self.value.get().is_some() {
1065            return false;
1066        }
1067        self.value.set(Some(val));
1068        true
1069    }
1070    /// Returns the value if written.
1071    pub fn read(&self) -> Option<T> {
1072        self.value.get()
1073    }
1074    /// Returns `true` if the value has been written.
1075    pub fn is_written(&self) -> bool {
1076        self.value.get().is_some()
1077    }
1078}
1079/// A record describing the structure of a proof term for analysis.
1080#[allow(dead_code)]
1081#[derive(Debug, Clone)]
1082pub struct ProofAnalysis {
1083    /// Total AST node count.
1084    pub size: usize,
1085    /// Maximum nesting depth.
1086    pub depth: usize,
1087    /// Number of lambda abstractions.
1088    pub lambda_count: usize,
1089    /// Number of applications.
1090    pub app_count: usize,
1091    /// Number of let-bindings.
1092    pub let_count: usize,
1093    /// Number of free variables referenced.
1094    pub fvar_count: usize,
1095    /// Number of bound variable references.
1096    pub bvar_count: usize,
1097    /// Whether the term contains any classical axiom.
1098    pub uses_classical: bool,
1099    /// Names of all constants referenced.
1100    pub constants: HashSet<Name>,
1101}
1102impl ProofAnalysis {
1103    /// Analyse a proof term and return a `ProofAnalysis`.
1104    #[allow(dead_code)]
1105    pub fn analyse(term: &Expr) -> Self {
1106        let mut analysis = ProofAnalysis {
1107            size: 0,
1108            depth: 0,
1109            lambda_count: 0,
1110            app_count: 0,
1111            let_count: 0,
1112            fvar_count: 0,
1113            bvar_count: 0,
1114            uses_classical: false,
1115            constants: HashSet::new(),
1116        };
1117        analysis.visit(term, 0);
1118        analysis.depth = ProofTerm::depth(term);
1119        analysis.uses_classical = !ProofTerm::is_constructive(term);
1120        analysis
1121    }
1122    fn visit(&mut self, term: &Expr, _depth: usize) {
1123        self.size += 1;
1124        match term {
1125            Expr::BVar(_) => {
1126                self.bvar_count += 1;
1127            }
1128            Expr::FVar(_) => {
1129                self.fvar_count += 1;
1130            }
1131            Expr::Const(name, _) => {
1132                self.constants.insert(name.clone());
1133            }
1134            Expr::Sort(_) | Expr::Lit(_) => {}
1135            Expr::App(f, a) => {
1136                self.app_count += 1;
1137                self.visit(f, _depth + 1);
1138                self.visit(a, _depth + 1);
1139            }
1140            Expr::Lam(_, _, ty, body) => {
1141                self.lambda_count += 1;
1142                self.visit(ty, _depth + 1);
1143                self.visit(body, _depth + 1);
1144            }
1145            Expr::Pi(_, _, ty, body) => {
1146                self.visit(ty, _depth + 1);
1147                self.visit(body, _depth + 1);
1148            }
1149            Expr::Let(_, ty, val, body) => {
1150                self.let_count += 1;
1151                self.visit(ty, _depth + 1);
1152                self.visit(val, _depth + 1);
1153                self.visit(body, _depth + 1);
1154            }
1155            Expr::Proj(_, _, e) => {
1156                self.visit(e, _depth + 1);
1157            }
1158        }
1159    }
1160}
1161/// A simple stack-based calculator for arithmetic expressions.
1162#[allow(dead_code)]
1163pub struct StackCalc {
1164    stack: Vec<i64>,
1165}
1166#[allow(dead_code)]
1167impl StackCalc {
1168    /// Creates a new empty calculator.
1169    pub fn new() -> Self {
1170        Self { stack: Vec::new() }
1171    }
1172    /// Pushes an integer literal.
1173    pub fn push(&mut self, n: i64) {
1174        self.stack.push(n);
1175    }
1176    /// Replaces the top two values, `b` on top of `a`, with `op(a, b)` and
1177    /// returns it.  Returns `None`, leaving the stack unchanged, if it holds
1178    /// fewer than two values.
1179    fn apply_binary(&mut self, op: impl FnOnce(i64, i64) -> i64) -> Option<i64> {
1180        let (rest_len, a, b) = match self.stack[..] {
1181            [ref rest @ .., a, b] => (rest.len(), a, b),
1182            _ => return None,
1183        };
1184        let value = op(a, b);
1185        self.stack.truncate(rest_len);
1186        self.stack.push(value);
1187        Some(value)
1188    }
1189    /// Adds the top two values and returns the sum, which replaces them.
1190    /// Returns `None`, leaving the stack unchanged, if it holds fewer than
1191    /// two values.
1192    pub fn add(&mut self) -> Option<i64> {
1193        self.apply_binary(|a, b| a + b)
1194    }
1195    /// Subtracts top from second and returns the difference, which replaces
1196    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
1197    /// than two values.
1198    pub fn sub(&mut self) -> Option<i64> {
1199        self.apply_binary(|a, b| a - b)
1200    }
1201    /// Multiplies the top two values and returns the product, which replaces
1202    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
1203    /// than two values.
1204    pub fn mul(&mut self) -> Option<i64> {
1205        self.apply_binary(|a, b| a * b)
1206    }
1207    /// Peeks the top value.
1208    pub fn peek(&self) -> Option<i64> {
1209        self.stack.last().copied()
1210    }
1211    /// Returns the stack depth.
1212    pub fn depth(&self) -> usize {
1213        self.stack.len()
1214    }
1215}
1216/// A tagged union for representing a simple two-case discriminated union.
1217#[allow(dead_code)]
1218pub enum Either2<A, B> {
1219    /// The first alternative.
1220    First(A),
1221    /// The second alternative.
1222    Second(B),
1223}
1224#[allow(dead_code)]
1225impl<A, B> Either2<A, B> {
1226    /// Returns `true` if this is the first alternative.
1227    pub fn is_first(&self) -> bool {
1228        matches!(self, Either2::First(_))
1229    }
1230    /// Returns `true` if this is the second alternative.
1231    pub fn is_second(&self) -> bool {
1232        matches!(self, Either2::Second(_))
1233    }
1234    /// Returns the first value if present.
1235    pub fn first(self) -> Option<A> {
1236        match self {
1237            Either2::First(a) => Some(a),
1238            _ => None,
1239        }
1240    }
1241    /// Returns the second value if present.
1242    pub fn second(self) -> Option<B> {
1243        match self {
1244            Either2::Second(b) => Some(b),
1245            _ => None,
1246        }
1247    }
1248    /// Maps over the first alternative.
1249    pub fn map_first<C, F: FnOnce(A) -> C>(self, f: F) -> Either2<C, B> {
1250        match self {
1251            Either2::First(a) => Either2::First(f(a)),
1252            Either2::Second(b) => Either2::Second(b),
1253        }
1254    }
1255}
1256/// A simple mutable key-value store for test fixtures.
1257#[allow(dead_code)]
1258pub struct Fixture {
1259    data: std::collections::HashMap<String, String>,
1260}
1261#[allow(dead_code)]
1262impl Fixture {
1263    /// Creates an empty fixture.
1264    pub fn new() -> Self {
1265        Self {
1266            data: std::collections::HashMap::new(),
1267        }
1268    }
1269    /// Sets a key.
1270    pub fn set(&mut self, key: impl Into<String>, val: impl Into<String>) {
1271        self.data.insert(key.into(), val.into());
1272    }
1273    /// Gets a value.
1274    pub fn get(&self, key: &str) -> Option<&str> {
1275        self.data.get(key).map(|s| s.as_str())
1276    }
1277    /// Returns the number of entries.
1278    pub fn len(&self) -> usize {
1279        self.data.len()
1280    }
1281    /// Returns whether the collection is empty.
1282    pub fn is_empty(&self) -> bool {
1283        self.len() == 0
1284    }
1285}
1286/// A simple decision tree node for rule dispatching.
1287#[allow(dead_code)]
1288#[allow(missing_docs)]
1289pub enum DecisionNode {
1290    /// A leaf with an action string.
1291    Leaf(String),
1292    /// An interior node: check `key` equals `val` → `yes_branch`, else `no_branch`.
1293    Branch {
1294        key: String,
1295        val: String,
1296        yes_branch: Box<DecisionNode>,
1297        no_branch: Box<DecisionNode>,
1298    },
1299}
1300#[allow(dead_code)]
1301impl DecisionNode {
1302    /// Evaluates the decision tree with the given context.
1303    pub fn evaluate(&self, ctx: &std::collections::HashMap<String, String>) -> &str {
1304        match self {
1305            DecisionNode::Leaf(action) => action.as_str(),
1306            DecisionNode::Branch {
1307                key,
1308                val,
1309                yes_branch,
1310                no_branch,
1311            } => {
1312                let actual = ctx.get(key).map(|s| s.as_str()).unwrap_or("");
1313                if actual == val.as_str() {
1314                    yes_branch.evaluate(ctx)
1315                } else {
1316                    no_branch.evaluate(ctx)
1317                }
1318            }
1319        }
1320    }
1321    /// Returns the depth of the decision tree.
1322    pub fn depth(&self) -> usize {
1323        match self {
1324            DecisionNode::Leaf(_) => 0,
1325            DecisionNode::Branch {
1326                yes_branch,
1327                no_branch,
1328                ..
1329            } => 1 + yes_branch.depth().max(no_branch.depth()),
1330        }
1331    }
1332}
1333/// A mutable reference stack for tracking the current "focus" in a tree traversal.
1334#[allow(dead_code)]
1335pub struct FocusStack<T> {
1336    items: Vec<T>,
1337}
1338#[allow(dead_code)]
1339impl<T> FocusStack<T> {
1340    /// Creates an empty focus stack.
1341    pub fn new() -> Self {
1342        Self { items: Vec::new() }
1343    }
1344    /// Focuses on `item`.
1345    pub fn focus(&mut self, item: T) {
1346        self.items.push(item);
1347    }
1348    /// Blurs (pops) the current focus.
1349    pub fn blur(&mut self) -> Option<T> {
1350        self.items.pop()
1351    }
1352    /// Returns the current focus, or `None`.
1353    pub fn current(&self) -> Option<&T> {
1354        self.items.last()
1355    }
1356    /// Returns the focus depth.
1357    pub fn depth(&self) -> usize {
1358        self.items.len()
1359    }
1360    /// Returns `true` if there is no current focus.
1361    pub fn is_empty(&self) -> bool {
1362        self.items.is_empty()
1363    }
1364}
1365/// A counter that can measure elapsed time between snapshots.
1366#[allow(dead_code)]
1367pub struct Stopwatch {
1368    start: crate::wall_clock::Instant,
1369    splits: Vec<f64>,
1370}
1371#[allow(dead_code)]
1372impl Stopwatch {
1373    /// Creates and starts a new stopwatch.
1374    pub fn start() -> Self {
1375        Self {
1376            start: crate::wall_clock::Instant::now(),
1377            splits: Vec::new(),
1378        }
1379    }
1380    /// Records a split time (elapsed since start).
1381    pub fn split(&mut self) {
1382        self.splits.push(self.elapsed_ms());
1383    }
1384    /// Returns total elapsed milliseconds since start.
1385    pub fn elapsed_ms(&self) -> f64 {
1386        self.start.elapsed().as_secs_f64() * 1000.0
1387    }
1388    /// Returns all recorded split times.
1389    pub fn splits(&self) -> &[f64] {
1390        &self.splits
1391    }
1392    /// Returns the number of splits.
1393    pub fn num_splits(&self) -> usize {
1394        self.splits.len()
1395    }
1396}
1397/// A sparse vector: stores only non-default elements.
1398#[allow(dead_code)]
1399pub struct SparseVec<T: Default + Clone + PartialEq> {
1400    entries: std::collections::HashMap<usize, T>,
1401    default_: T,
1402    logical_len: usize,
1403}
1404#[allow(dead_code)]
1405impl<T: Default + Clone + PartialEq> SparseVec<T> {
1406    /// Creates a new sparse vector with logical length `len`.
1407    pub fn new(len: usize) -> Self {
1408        Self {
1409            entries: std::collections::HashMap::new(),
1410            default_: T::default(),
1411            logical_len: len,
1412        }
1413    }
1414    /// Sets element at `idx`.
1415    pub fn set(&mut self, idx: usize, val: T) {
1416        if val == self.default_ {
1417            self.entries.remove(&idx);
1418        } else {
1419            self.entries.insert(idx, val);
1420        }
1421    }
1422    /// Gets element at `idx`.
1423    pub fn get(&self, idx: usize) -> &T {
1424        self.entries.get(&idx).unwrap_or(&self.default_)
1425    }
1426    /// Returns the logical length.
1427    pub fn len(&self) -> usize {
1428        self.logical_len
1429    }
1430    /// Returns whether the collection is empty.
1431    pub fn is_empty(&self) -> bool {
1432        self.len() == 0
1433    }
1434    /// Returns the number of non-default elements.
1435    pub fn nnz(&self) -> usize {
1436        self.entries.len()
1437    }
1438}
1439/// A proof certificate: a self-contained record attesting that a term
1440/// is a valid proof of a proposition.
1441#[allow(dead_code)]
1442#[derive(Clone, Debug)]
1443pub struct ProofCertificate {
1444    /// The proven proposition.
1445    pub proposition: Expr,
1446    /// The proof term.
1447    pub proof_term: Expr,
1448    /// Whether the proof is constructive.
1449    pub is_constructive: bool,
1450    /// Whether the proof contains any `sorry`.
1451    pub has_sorry: bool,
1452}
1453impl ProofCertificate {
1454    /// Create a certificate from a proposition and proof term.
1455    #[allow(dead_code)]
1456    pub fn new(proposition: Expr, proof_term: Expr) -> Self {
1457        let is_constructive = ProofAnalyzer::is_constructive(&proof_term);
1458        let has_sorry = crate::proof::contains_classical_sorry(&proof_term);
1459        Self {
1460            proposition,
1461            proof_term,
1462            is_constructive,
1463            has_sorry,
1464        }
1465    }
1466    /// Whether this certificate is fully trusted (no sorry, constructive).
1467    #[allow(dead_code)]
1468    pub fn is_trusted(&self) -> bool {
1469        !self.has_sorry && self.is_constructive
1470    }
1471}
1472/// A token bucket rate limiter.
1473#[allow(dead_code)]
1474pub struct TokenBucket {
1475    capacity: u64,
1476    tokens: u64,
1477    refill_per_ms: u64,
1478    last_refill: crate::wall_clock::Instant,
1479}
1480#[allow(dead_code)]
1481impl TokenBucket {
1482    /// Creates a new token bucket.
1483    pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
1484        Self {
1485            capacity,
1486            tokens: capacity,
1487            refill_per_ms,
1488            last_refill: crate::wall_clock::Instant::now(),
1489        }
1490    }
1491    /// Attempts to consume `n` tokens.  Returns `true` on success.
1492    pub fn try_consume(&mut self, n: u64) -> bool {
1493        self.refill();
1494        if self.tokens >= n {
1495            self.tokens -= n;
1496            true
1497        } else {
1498            false
1499        }
1500    }
1501    fn refill(&mut self) {
1502        let now = crate::wall_clock::Instant::now();
1503        let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
1504        if elapsed_ms > 0 {
1505            let new_tokens = elapsed_ms * self.refill_per_ms;
1506            self.tokens = (self.tokens + new_tokens).min(self.capacity);
1507            self.last_refill = now;
1508        }
1509    }
1510    /// Returns the number of currently available tokens.
1511    pub fn available(&self) -> u64 {
1512        self.tokens
1513    }
1514    /// Returns the bucket capacity.
1515    pub fn capacity(&self) -> u64 {
1516        self.capacity
1517    }
1518}
1519/// A hierarchical configuration tree.
1520#[allow(dead_code)]
1521pub struct ConfigNode {
1522    key: String,
1523    value: Option<String>,
1524    children: Vec<ConfigNode>,
1525}
1526#[allow(dead_code)]
1527impl ConfigNode {
1528    /// Creates a leaf config node with a value.
1529    pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
1530        Self {
1531            key: key.into(),
1532            value: Some(value.into()),
1533            children: Vec::new(),
1534        }
1535    }
1536    /// Creates a section node with children.
1537    pub fn section(key: impl Into<String>) -> Self {
1538        Self {
1539            key: key.into(),
1540            value: None,
1541            children: Vec::new(),
1542        }
1543    }
1544    /// Adds a child node.
1545    pub fn add_child(&mut self, child: ConfigNode) {
1546        self.children.push(child);
1547    }
1548    /// Returns the key.
1549    pub fn key(&self) -> &str {
1550        &self.key
1551    }
1552    /// Returns the value, or `None` for section nodes.
1553    pub fn value(&self) -> Option<&str> {
1554        self.value.as_deref()
1555    }
1556    /// Returns the number of children.
1557    pub fn num_children(&self) -> usize {
1558        self.children.len()
1559    }
1560    /// Looks up a dot-separated path.
1561    pub fn lookup(&self, path: &str) -> Option<&str> {
1562        let mut parts = path.splitn(2, '.');
1563        let head = parts.next()?;
1564        let tail = parts.next();
1565        if head != self.key {
1566            return None;
1567        }
1568        match tail {
1569            None => self.value.as_deref(),
1570            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
1571        }
1572    }
1573    fn lookup_relative(&self, path: &str) -> Option<&str> {
1574        let mut parts = path.splitn(2, '.');
1575        let head = parts.next()?;
1576        let tail = parts.next();
1577        if head != self.key {
1578            return None;
1579        }
1580        match tail {
1581            None => self.value.as_deref(),
1582            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
1583        }
1584    }
1585}
1586/// A versioned record that stores a history of values.
1587#[allow(dead_code)]
1588pub struct VersionedRecord<T: Clone> {
1589    /// Every earlier value, oldest first: version `n` is `earlier[n]`.
1590    earlier: Vec<T>,
1591    /// The latest value (version `earlier.len()`), declared last so it drops last.
1592    current: T,
1593}
1594#[allow(dead_code)]
1595impl<T: Clone> VersionedRecord<T> {
1596    /// Creates a new record with an initial value.
1597    pub fn new(initial: T) -> Self {
1598        Self {
1599            earlier: Vec::new(),
1600            current: initial,
1601        }
1602    }
1603    /// Updates the record with a new version.
1604    pub fn update(&mut self, val: T) {
1605        let previous = std::mem::replace(&mut self.current, val);
1606        self.earlier.push(previous);
1607    }
1608    /// Returns the current (latest) value.
1609    pub fn current(&self) -> &T {
1610        &self.current
1611    }
1612    /// Returns the value at version `n` (0-indexed), or `None`.
1613    pub fn at_version(&self, n: usize) -> Option<&T> {
1614        match self.earlier.get(n) {
1615            Some(value) => Some(value),
1616            None if n == self.earlier.len() => Some(&self.current),
1617            None => None,
1618        }
1619    }
1620    /// Returns the version number of the current value.
1621    pub fn version(&self) -> usize {
1622        self.earlier.len()
1623    }
1624    /// Returns `true` if more than one version exists.
1625    pub fn has_history(&self) -> bool {
1626        !self.earlier.is_empty()
1627    }
1628}
1629/// A min-heap implemented as a binary heap.
1630#[allow(dead_code)]
1631pub struct MinHeap<T: Ord> {
1632    data: Vec<T>,
1633}
1634#[allow(dead_code)]
1635impl<T: Ord> MinHeap<T> {
1636    /// Creates a new empty min-heap.
1637    pub fn new() -> Self {
1638        Self { data: Vec::new() }
1639    }
1640    /// Inserts an element.
1641    pub fn push(&mut self, val: T) {
1642        self.data.push(val);
1643        self.sift_up(self.data.len() - 1);
1644    }
1645    /// Removes and returns the minimum element.
1646    pub fn pop(&mut self) -> Option<T> {
1647        if self.data.is_empty() {
1648            return None;
1649        }
1650        let n = self.data.len();
1651        self.data.swap(0, n - 1);
1652        let min = self.data.pop();
1653        if !self.data.is_empty() {
1654            self.sift_down(0);
1655        }
1656        min
1657    }
1658    /// Returns a reference to the minimum element.
1659    pub fn peek(&self) -> Option<&T> {
1660        self.data.first()
1661    }
1662    /// Returns the number of elements.
1663    pub fn len(&self) -> usize {
1664        self.data.len()
1665    }
1666    /// Returns `true` if empty.
1667    pub fn is_empty(&self) -> bool {
1668        self.data.is_empty()
1669    }
1670    fn sift_up(&mut self, mut i: usize) {
1671        while i > 0 {
1672            let parent = (i - 1) / 2;
1673            if self.data[i] < self.data[parent] {
1674                self.data.swap(i, parent);
1675                i = parent;
1676            } else {
1677                break;
1678            }
1679        }
1680    }
1681    fn sift_down(&mut self, mut i: usize) {
1682        let n = self.data.len();
1683        loop {
1684            let left = 2 * i + 1;
1685            let right = 2 * i + 2;
1686            let mut smallest = i;
1687            if left < n && self.data[left] < self.data[smallest] {
1688                smallest = left;
1689            }
1690            if right < n && self.data[right] < self.data[smallest] {
1691                smallest = right;
1692            }
1693            if smallest == i {
1694                break;
1695            }
1696            self.data.swap(i, smallest);
1697            i = smallest;
1698        }
1699    }
1700}
1701/// Represents a single obligation in a proof: a goal that must be closed.
1702#[allow(dead_code)]
1703#[derive(Debug, Clone)]
1704pub struct ProofObligation {
1705    /// A human-readable label for this obligation.
1706    pub label: String,
1707    /// The proposition that must be proved.
1708    pub proposition: Expr,
1709    /// Whether this obligation has been discharged.
1710    pub discharged: bool,
1711    /// The proof term supplied, if any.
1712    pub proof_term: Option<Expr>,
1713}
1714impl ProofObligation {
1715    /// Create a new undischarged obligation.
1716    #[allow(dead_code)]
1717    pub fn new(label: impl Into<String>, proposition: Expr) -> Self {
1718        ProofObligation {
1719            label: label.into(),
1720            proposition,
1721            discharged: false,
1722            proof_term: None,
1723        }
1724    }
1725    /// Discharge this obligation with a given proof term.
1726    #[allow(dead_code)]
1727    pub fn discharge(&mut self, proof: Expr) {
1728        self.proof_term = Some(proof);
1729        self.discharged = true;
1730    }
1731    /// Returns true if this obligation has been discharged.
1732    #[allow(dead_code)]
1733    pub fn is_discharged(&self) -> bool {
1734        self.discharged
1735    }
1736}