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