Skip to main content

oxilean_kernel/reduction/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::{Expr, Reducer};
6
7use std::collections::HashMap;
8
9/// A non-empty list (at least one element guaranteed).
10#[allow(dead_code)]
11pub struct NonEmptyVec<T> {
12    head: T,
13    tail: Vec<T>,
14}
15#[allow(dead_code)]
16impl<T> NonEmptyVec<T> {
17    /// Creates a non-empty vec with a single element.
18    pub fn singleton(val: T) -> Self {
19        Self {
20            head: val,
21            tail: Vec::new(),
22        }
23    }
24    /// Pushes an element.
25    pub fn push(&mut self, val: T) {
26        self.tail.push(val);
27    }
28    /// Returns a reference to the first element.
29    pub fn first(&self) -> &T {
30        &self.head
31    }
32    /// Returns a reference to the last element.
33    pub fn last(&self) -> &T {
34        self.tail.last().unwrap_or(&self.head)
35    }
36    /// Returns the number of elements.
37    pub fn len(&self) -> usize {
38        1 + self.tail.len()
39    }
40    /// Returns whether the collection is empty.
41    pub fn is_empty(&self) -> bool {
42        self.len() == 0
43    }
44    /// Returns all elements as a Vec.
45    pub fn to_vec(&self) -> Vec<&T> {
46        let mut v = vec![&self.head];
47        v.extend(self.tail.iter());
48        v
49    }
50}
51/// The kind of reducible expression.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum RedexKind {
54    /// Beta-redex: `(λx. e) a`
55    Beta,
56    /// Let-redex: `let x := v in e`
57    Let,
58    /// Projection of a constructor: `(ctor fields).i`
59    Proj,
60    /// Delta-redex: unfoldable constant
61    Delta(crate::Name),
62}
63impl RedexKind {
64    /// Returns a human-readable description.
65    pub fn description(&self) -> String {
66        match self {
67            RedexKind::Beta => "β-redex".to_string(),
68            RedexKind::Let => "let-redex".to_string(),
69            RedexKind::Proj => "proj-redex".to_string(),
70            RedexKind::Delta(name) => format!("δ-redex ({})", name),
71        }
72    }
73}
74/// A generic counter that tracks min/max/sum for statistical summaries.
75#[allow(dead_code)]
76pub struct StatSummary {
77    count: u64,
78    sum: f64,
79    min: f64,
80    max: f64,
81}
82#[allow(dead_code)]
83impl StatSummary {
84    /// Creates an empty summary.
85    pub fn new() -> Self {
86        Self {
87            count: 0,
88            sum: 0.0,
89            min: f64::INFINITY,
90            max: f64::NEG_INFINITY,
91        }
92    }
93    /// Records a sample.
94    pub fn record(&mut self, val: f64) {
95        self.count += 1;
96        self.sum += val;
97        if val < self.min {
98            self.min = val;
99        }
100        if val > self.max {
101            self.max = val;
102        }
103    }
104    /// Returns the mean, or `None` if no samples.
105    pub fn mean(&self) -> Option<f64> {
106        if self.count == 0 {
107            None
108        } else {
109            Some(self.sum / self.count as f64)
110        }
111    }
112    /// Returns the minimum, or `None` if no samples.
113    pub fn min(&self) -> Option<f64> {
114        if self.count == 0 {
115            None
116        } else {
117            Some(self.min)
118        }
119    }
120    /// Returns the maximum, or `None` if no samples.
121    pub fn max(&self) -> Option<f64> {
122        if self.count == 0 {
123            None
124        } else {
125            Some(self.max)
126        }
127    }
128    /// Returns the count of recorded samples.
129    pub fn count(&self) -> u64 {
130        self.count
131    }
132}
133/// A fixed-size sliding window that computes a running sum.
134#[allow(dead_code)]
135pub struct SlidingSum {
136    window: Vec<f64>,
137    capacity: usize,
138    pos: usize,
139    sum: f64,
140    count: usize,
141}
142#[allow(dead_code)]
143impl SlidingSum {
144    /// Creates a sliding sum with the given window size.
145    pub fn new(capacity: usize) -> Self {
146        Self {
147            window: vec![0.0; capacity],
148            capacity,
149            pos: 0,
150            sum: 0.0,
151            count: 0,
152        }
153    }
154    /// Adds a value to the window, removing the oldest if full.
155    pub fn push(&mut self, val: f64) {
156        let oldest = self.window[self.pos];
157        self.sum -= oldest;
158        self.sum += val;
159        self.window[self.pos] = val;
160        self.pos = (self.pos + 1) % self.capacity;
161        if self.count < self.capacity {
162            self.count += 1;
163        }
164    }
165    /// Returns the current window sum.
166    pub fn sum(&self) -> f64 {
167        self.sum
168    }
169    /// Returns the window mean, or `None` if empty.
170    pub fn mean(&self) -> Option<f64> {
171        if self.count == 0 {
172            None
173        } else {
174            Some(self.sum / self.count as f64)
175        }
176    }
177    /// Returns the current window size (number of valid elements).
178    pub fn count(&self) -> usize {
179        self.count
180    }
181}
182/// A pool of reusable string buffers.
183#[allow(dead_code)]
184pub struct StringPool {
185    free: Vec<String>,
186}
187#[allow(dead_code)]
188impl StringPool {
189    /// Creates a new empty string pool.
190    pub fn new() -> Self {
191        Self { free: Vec::new() }
192    }
193    /// Takes a string from the pool (may be empty).
194    pub fn take(&mut self) -> String {
195        self.free.pop().unwrap_or_default()
196    }
197    /// Returns a string to the pool.
198    pub fn give(&mut self, mut s: String) {
199        s.clear();
200        self.free.push(s);
201    }
202    /// Returns the number of free strings in the pool.
203    pub fn free_count(&self) -> usize {
204        self.free.len()
205    }
206}
207/// Represents a rewrite rule `lhs → rhs`.
208#[allow(dead_code)]
209#[allow(missing_docs)]
210pub struct RewriteRule {
211    /// The name of the rule.
212    pub name: String,
213    /// A string representation of the LHS pattern.
214    pub lhs: String,
215    /// A string representation of the RHS.
216    pub rhs: String,
217    /// Whether this is a conditional rule (has side conditions).
218    pub conditional: bool,
219}
220#[allow(dead_code)]
221impl RewriteRule {
222    /// Creates an unconditional rewrite rule.
223    pub fn unconditional(
224        name: impl Into<String>,
225        lhs: impl Into<String>,
226        rhs: impl Into<String>,
227    ) -> Self {
228        Self {
229            name: name.into(),
230            lhs: lhs.into(),
231            rhs: rhs.into(),
232            conditional: false,
233        }
234    }
235    /// Creates a conditional rewrite rule.
236    pub fn conditional(
237        name: impl Into<String>,
238        lhs: impl Into<String>,
239        rhs: impl Into<String>,
240    ) -> Self {
241        Self {
242            name: name.into(),
243            lhs: lhs.into(),
244            rhs: rhs.into(),
245            conditional: true,
246        }
247    }
248    /// Returns a textual representation.
249    pub fn display(&self) -> String {
250        format!("{}: {} → {}", self.name, self.lhs, self.rhs)
251    }
252}
253/// A flat list of substitution pairs `(from, to)`.
254#[allow(dead_code)]
255pub struct FlatSubstitution {
256    pairs: Vec<(String, String)>,
257}
258#[allow(dead_code)]
259impl FlatSubstitution {
260    /// Creates an empty substitution.
261    pub fn new() -> Self {
262        Self { pairs: Vec::new() }
263    }
264    /// Adds a pair.
265    pub fn add(&mut self, from: impl Into<String>, to: impl Into<String>) {
266        self.pairs.push((from.into(), to.into()));
267    }
268    /// Applies all substitutions to `s` (leftmost-first order).
269    pub fn apply(&self, s: &str) -> String {
270        let mut result = s.to_string();
271        for (from, to) in &self.pairs {
272            result = result.replace(from.as_str(), to.as_str());
273        }
274        result
275    }
276    /// Returns the number of pairs.
277    pub fn len(&self) -> usize {
278        self.pairs.len()
279    }
280    /// Returns `true` if empty.
281    pub fn is_empty(&self) -> bool {
282        self.pairs.is_empty()
283    }
284}
285/// A tagged union for representing a simple two-case discriminated union.
286#[allow(dead_code)]
287pub enum Either2<A, B> {
288    /// The first alternative.
289    First(A),
290    /// The second alternative.
291    Second(B),
292}
293#[allow(dead_code)]
294impl<A, B> Either2<A, B> {
295    /// Returns `true` if this is the first alternative.
296    pub fn is_first(&self) -> bool {
297        matches!(self, Either2::First(_))
298    }
299    /// Returns `true` if this is the second alternative.
300    pub fn is_second(&self) -> bool {
301        matches!(self, Either2::Second(_))
302    }
303    /// Returns the first value if present.
304    pub fn first(self) -> Option<A> {
305        match self {
306            Either2::First(a) => Some(a),
307            _ => None,
308        }
309    }
310    /// Returns the second value if present.
311    pub fn second(self) -> Option<B> {
312        match self {
313            Either2::Second(b) => Some(b),
314            _ => None,
315        }
316    }
317    /// Maps over the first alternative.
318    pub fn map_first<C, F: FnOnce(A) -> C>(self, f: F) -> Either2<C, B> {
319        match self {
320            Either2::First(a) => Either2::First(f(a)),
321            Either2::Second(b) => Either2::Second(b),
322        }
323    }
324}
325/// A counter that can measure elapsed time between snapshots.
326#[allow(dead_code)]
327pub struct Stopwatch {
328    start: crate::wall_clock::Instant,
329    splits: Vec<f64>,
330}
331#[allow(dead_code)]
332impl Stopwatch {
333    /// Creates and starts a new stopwatch.
334    pub fn start() -> Self {
335        Self {
336            start: crate::wall_clock::Instant::now(),
337            splits: Vec::new(),
338        }
339    }
340    /// Records a split time (elapsed since start).
341    pub fn split(&mut self) {
342        self.splits.push(self.elapsed_ms());
343    }
344    /// Returns total elapsed milliseconds since start.
345    pub fn elapsed_ms(&self) -> f64 {
346        self.start.elapsed().as_secs_f64() * 1000.0
347    }
348    /// Returns all recorded split times.
349    pub fn splits(&self) -> &[f64] {
350        &self.splits
351    }
352    /// Returns the number of splits.
353    pub fn num_splits(&self) -> usize {
354        self.splits.len()
355    }
356}
357/// A hierarchical configuration tree.
358#[allow(dead_code)]
359pub struct ConfigNode {
360    key: String,
361    value: Option<String>,
362    children: Vec<ConfigNode>,
363}
364#[allow(dead_code)]
365impl ConfigNode {
366    /// Creates a leaf config node with a value.
367    pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
368        Self {
369            key: key.into(),
370            value: Some(value.into()),
371            children: Vec::new(),
372        }
373    }
374    /// Creates a section node with children.
375    pub fn section(key: impl Into<String>) -> Self {
376        Self {
377            key: key.into(),
378            value: None,
379            children: Vec::new(),
380        }
381    }
382    /// Adds a child node.
383    pub fn add_child(&mut self, child: ConfigNode) {
384        self.children.push(child);
385    }
386    /// Returns the key.
387    pub fn key(&self) -> &str {
388        &self.key
389    }
390    /// Returns the value, or `None` for section nodes.
391    pub fn value(&self) -> Option<&str> {
392        self.value.as_deref()
393    }
394    /// Returns the number of children.
395    pub fn num_children(&self) -> usize {
396        self.children.len()
397    }
398    /// Looks up a dot-separated path.
399    pub fn lookup(&self, path: &str) -> Option<&str> {
400        let mut parts = path.splitn(2, '.');
401        let head = parts.next()?;
402        let tail = parts.next();
403        if head != self.key {
404            return None;
405        }
406        match tail {
407            None => self.value.as_deref(),
408            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
409        }
410    }
411    fn lookup_relative(&self, path: &str) -> Option<&str> {
412        let mut parts = path.splitn(2, '.');
413        let head = parts.next()?;
414        let tail = parts.next();
415        if head != self.key {
416            return None;
417        }
418        match tail {
419            None => self.value.as_deref(),
420            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
421        }
422    }
423}
424/// A token bucket rate limiter.
425#[allow(dead_code)]
426pub struct TokenBucket {
427    capacity: u64,
428    tokens: u64,
429    refill_per_ms: u64,
430    last_refill: crate::wall_clock::Instant,
431}
432#[allow(dead_code)]
433impl TokenBucket {
434    /// Creates a new token bucket.
435    pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
436        Self {
437            capacity,
438            tokens: capacity,
439            refill_per_ms,
440            last_refill: crate::wall_clock::Instant::now(),
441        }
442    }
443    /// Attempts to consume `n` tokens.  Returns `true` on success.
444    pub fn try_consume(&mut self, n: u64) -> bool {
445        self.refill();
446        if self.tokens >= n {
447            self.tokens -= n;
448            true
449        } else {
450            false
451        }
452    }
453    fn refill(&mut self) {
454        let now = crate::wall_clock::Instant::now();
455        let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
456        if elapsed_ms > 0 {
457            let new_tokens = elapsed_ms * self.refill_per_ms;
458            self.tokens = (self.tokens + new_tokens).min(self.capacity);
459            self.last_refill = now;
460        }
461    }
462    /// Returns the number of currently available tokens.
463    pub fn available(&self) -> u64 {
464        self.tokens
465    }
466    /// Returns the bucket capacity.
467    pub fn capacity(&self) -> u64 {
468        self.capacity
469    }
470}
471/// A min-heap implemented as a binary heap.
472#[allow(dead_code)]
473pub struct MinHeap<T: Ord> {
474    data: Vec<T>,
475}
476#[allow(dead_code)]
477impl<T: Ord> MinHeap<T> {
478    /// Creates a new empty min-heap.
479    pub fn new() -> Self {
480        Self { data: Vec::new() }
481    }
482    /// Inserts an element.
483    pub fn push(&mut self, val: T) {
484        self.data.push(val);
485        self.sift_up(self.data.len() - 1);
486    }
487    /// Removes and returns the minimum element.
488    pub fn pop(&mut self) -> Option<T> {
489        if self.data.is_empty() {
490            return None;
491        }
492        let n = self.data.len();
493        self.data.swap(0, n - 1);
494        let min = self.data.pop();
495        if !self.data.is_empty() {
496            self.sift_down(0);
497        }
498        min
499    }
500    /// Returns a reference to the minimum element.
501    pub fn peek(&self) -> Option<&T> {
502        self.data.first()
503    }
504    /// Returns the number of elements.
505    pub fn len(&self) -> usize {
506        self.data.len()
507    }
508    /// Returns `true` if empty.
509    pub fn is_empty(&self) -> bool {
510        self.data.is_empty()
511    }
512    fn sift_up(&mut self, mut i: usize) {
513        while i > 0 {
514            let parent = (i - 1) / 2;
515            if self.data[i] < self.data[parent] {
516                self.data.swap(i, parent);
517                i = parent;
518            } else {
519                break;
520            }
521        }
522    }
523    fn sift_down(&mut self, mut i: usize) {
524        let n = self.data.len();
525        loop {
526            let left = 2 * i + 1;
527            let right = 2 * i + 2;
528            let mut smallest = i;
529            if left < n && self.data[left] < self.data[smallest] {
530                smallest = left;
531            }
532            if right < n && self.data[right] < self.data[smallest] {
533                smallest = right;
534            }
535            if smallest == i {
536                break;
537            }
538            self.data.swap(i, smallest);
539            i = smallest;
540        }
541    }
542}
543/// A simple stack-based calculator for arithmetic expressions.
544#[allow(dead_code)]
545pub struct StackCalc {
546    stack: Vec<i64>,
547}
548#[allow(dead_code)]
549impl StackCalc {
550    /// Creates a new empty calculator.
551    pub fn new() -> Self {
552        Self { stack: Vec::new() }
553    }
554    /// Pushes an integer literal.
555    pub fn push(&mut self, n: i64) {
556        self.stack.push(n);
557    }
558    /// Replaces the top two values, `b` on top of `a`, with `op(a, b)` and
559    /// returns it.  Returns `None`, leaving the stack unchanged, if it holds
560    /// fewer than two values.
561    fn apply_binary(&mut self, op: impl FnOnce(i64, i64) -> i64) -> Option<i64> {
562        let (rest_len, a, b) = match self.stack[..] {
563            [ref rest @ .., a, b] => (rest.len(), a, b),
564            _ => return None,
565        };
566        let value = op(a, b);
567        self.stack.truncate(rest_len);
568        self.stack.push(value);
569        Some(value)
570    }
571    /// Adds the top two values and returns the sum, which replaces them.
572    /// Returns `None`, leaving the stack unchanged, if it holds fewer than
573    /// two values.
574    pub fn add(&mut self) -> Option<i64> {
575        self.apply_binary(|a, b| a + b)
576    }
577    /// Subtracts top from second and returns the difference, which replaces
578    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
579    /// than two values.
580    pub fn sub(&mut self) -> Option<i64> {
581        self.apply_binary(|a, b| a - b)
582    }
583    /// Multiplies the top two values and returns the product, which replaces
584    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
585    /// than two values.
586    pub fn mul(&mut self) -> Option<i64> {
587        self.apply_binary(|a, b| a * b)
588    }
589    /// Peeks the top value.
590    pub fn peek(&self) -> Option<i64> {
591        self.stack.last().copied()
592    }
593    /// Returns the stack depth.
594    pub fn depth(&self) -> usize {
595        self.stack.len()
596    }
597}
598/// A window iterator that yields overlapping windows of size `n`.
599#[allow(dead_code)]
600pub struct WindowIterator<'a, T> {
601    pub(super) data: &'a [T],
602    pub(super) pos: usize,
603    pub(super) window: usize,
604}
605#[allow(dead_code)]
606impl<'a, T> WindowIterator<'a, T> {
607    /// Creates a new window iterator.
608    pub fn new(data: &'a [T], window: usize) -> Self {
609        Self {
610            data,
611            pos: 0,
612            window,
613        }
614    }
615}
616/// A write-once cell.
617#[allow(dead_code)]
618pub struct WriteOnce<T> {
619    value: std::cell::Cell<Option<T>>,
620}
621#[allow(dead_code)]
622impl<T: Copy> WriteOnce<T> {
623    /// Creates an empty write-once cell.
624    pub fn new() -> Self {
625        Self {
626            value: std::cell::Cell::new(None),
627        }
628    }
629    /// Writes a value.  Returns `false` if already written.
630    pub fn write(&self, val: T) -> bool {
631        if self.value.get().is_some() {
632            return false;
633        }
634        self.value.set(Some(val));
635        true
636    }
637    /// Returns the value if written.
638    pub fn read(&self) -> Option<T> {
639        self.value.get()
640    }
641    /// Returns `true` if the value has been written.
642    pub fn is_written(&self) -> bool {
643        self.value.get().is_some()
644    }
645}
646/// The result of a single reduction step.
647#[derive(Debug, Clone, PartialEq)]
648pub enum ReductionResult {
649    /// The expression was reduced to a new expression.
650    Reduced(Expr),
651    /// The expression is already in normal form (no reduction possible).
652    Normal(Expr),
653    /// Reduction was aborted (e.g., step limit exceeded).
654    Aborted(Expr),
655}
656impl ReductionResult {
657    /// Get the resulting expression regardless of whether reduction occurred.
658    pub fn expr(&self) -> &Expr {
659        match self {
660            ReductionResult::Reduced(e)
661            | ReductionResult::Normal(e)
662            | ReductionResult::Aborted(e) => e,
663        }
664    }
665    /// Returns `true` if reduction occurred.
666    pub fn was_reduced(&self) -> bool {
667        matches!(self, ReductionResult::Reduced(_))
668    }
669    /// Returns `true` if the expression is in normal form.
670    pub fn is_normal(&self) -> bool {
671        matches!(self, ReductionResult::Normal(_))
672    }
673    /// Consume the result and return the expression.
674    pub fn into_expr(self) -> Expr {
675        match self {
676            ReductionResult::Reduced(e)
677            | ReductionResult::Normal(e)
678            | ReductionResult::Aborted(e) => e,
679        }
680    }
681}
682/// A reusable scratch buffer for path computations.
683#[allow(dead_code)]
684pub struct PathBuf {
685    components: Vec<String>,
686}
687#[allow(dead_code)]
688impl PathBuf {
689    /// Creates a new empty path buffer.
690    pub fn new() -> Self {
691        Self {
692            components: Vec::new(),
693        }
694    }
695    /// Pushes a component.
696    pub fn push(&mut self, comp: impl Into<String>) {
697        self.components.push(comp.into());
698    }
699    /// Pops the last component.
700    pub fn pop(&mut self) {
701        self.components.pop();
702    }
703    /// Returns the current path as a `/`-separated string.
704    pub fn as_str(&self) -> String {
705        self.components.join("/")
706    }
707    /// Returns the depth of the path.
708    pub fn depth(&self) -> usize {
709        self.components.len()
710    }
711    /// Clears the path.
712    pub fn clear(&mut self) {
713        self.components.clear();
714    }
715}
716/// A trie-based prefix counter.
717#[allow(dead_code)]
718pub struct PrefixCounter {
719    children: std::collections::HashMap<char, PrefixCounter>,
720    count: usize,
721}
722#[allow(dead_code)]
723impl PrefixCounter {
724    /// Creates an empty prefix counter.
725    pub fn new() -> Self {
726        Self {
727            children: std::collections::HashMap::new(),
728            count: 0,
729        }
730    }
731    /// Records a string.
732    pub fn record(&mut self, s: &str) {
733        self.count += 1;
734        let mut node = self;
735        for c in s.chars() {
736            node = node.children.entry(c).or_default();
737            node.count += 1;
738        }
739    }
740    /// Returns how many strings have been recorded that start with `prefix`.
741    pub fn count_with_prefix(&self, prefix: &str) -> usize {
742        let mut node = self;
743        for c in prefix.chars() {
744            match node.children.get(&c) {
745                Some(n) => node = n,
746                None => return 0,
747            }
748        }
749        node.count
750    }
751}
752/// A simple decision tree node for rule dispatching.
753#[allow(dead_code)]
754#[allow(missing_docs)]
755pub enum DecisionNode {
756    /// A leaf with an action string.
757    Leaf(String),
758    /// An interior node: check `key` equals `val` → `yes_branch`, else `no_branch`.
759    Branch {
760        key: String,
761        val: String,
762        yes_branch: Box<DecisionNode>,
763        no_branch: Box<DecisionNode>,
764    },
765}
766#[allow(dead_code)]
767impl DecisionNode {
768    /// Evaluates the decision tree with the given context.
769    pub fn evaluate(&self, ctx: &std::collections::HashMap<String, String>) -> &str {
770        match self {
771            DecisionNode::Leaf(action) => action.as_str(),
772            DecisionNode::Branch {
773                key,
774                val,
775                yes_branch,
776                no_branch,
777            } => {
778                let actual = ctx.get(key).map(|s| s.as_str()).unwrap_or("");
779                if actual == val.as_str() {
780                    yes_branch.evaluate(ctx)
781                } else {
782                    no_branch.evaluate(ctx)
783                }
784            }
785        }
786    }
787    /// Returns the depth of the decision tree.
788    pub fn depth(&self) -> usize {
789        match self {
790            DecisionNode::Leaf(_) => 0,
791            DecisionNode::Branch {
792                yes_branch,
793                no_branch,
794                ..
795            } => 1 + yes_branch.depth().max(no_branch.depth()),
796        }
797    }
798}
799/// A dependency closure builder (transitive closure via BFS).
800#[allow(dead_code)]
801pub struct TransitiveClosure {
802    adj: Vec<Vec<usize>>,
803    n: usize,
804}
805#[allow(dead_code)]
806impl TransitiveClosure {
807    /// Creates a transitive closure builder for `n` nodes.
808    pub fn new(n: usize) -> Self {
809        Self {
810            adj: vec![Vec::new(); n],
811            n,
812        }
813    }
814    /// Adds a direct edge.
815    pub fn add_edge(&mut self, from: usize, to: usize) {
816        if from < self.n {
817            self.adj[from].push(to);
818        }
819    }
820    /// Computes all nodes reachable from `start` (including `start`).
821    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
822        let mut visited = vec![false; self.n];
823        let mut queue = std::collections::VecDeque::new();
824        queue.push_back(start);
825        while let Some(node) = queue.pop_front() {
826            if node >= self.n || visited[node] {
827                continue;
828            }
829            visited[node] = true;
830            for &next in &self.adj[node] {
831                queue.push_back(next);
832            }
833        }
834        (0..self.n).filter(|&i| visited[i]).collect()
835    }
836    /// Returns `true` if `from` can transitively reach `to`.
837    pub fn can_reach(&self, from: usize, to: usize) -> bool {
838        self.reachable_from(from).contains(&to)
839    }
840}
841/// A label set for a graph node.
842#[allow(dead_code)]
843pub struct LabelSet {
844    labels: Vec<String>,
845}
846#[allow(dead_code)]
847impl LabelSet {
848    /// Creates a new empty label set.
849    pub fn new() -> Self {
850        Self { labels: Vec::new() }
851    }
852    /// Adds a label (deduplicates).
853    pub fn add(&mut self, label: impl Into<String>) {
854        let s = label.into();
855        if !self.labels.contains(&s) {
856            self.labels.push(s);
857        }
858    }
859    /// Returns `true` if `label` is present.
860    pub fn has(&self, label: &str) -> bool {
861        self.labels.iter().any(|l| l == label)
862    }
863    /// Returns the count of labels.
864    pub fn count(&self) -> usize {
865        self.labels.len()
866    }
867    /// Returns all labels.
868    pub fn all(&self) -> &[String] {
869        &self.labels
870    }
871}
872/// Information about a redex (reducible expression).
873#[derive(Debug, Clone)]
874pub struct RedexInfo {
875    /// The kind of redex.
876    pub kind: RedexKind,
877    /// Depth in the expression tree where the redex occurs.
878    pub depth: usize,
879    /// Size of the redex sub-expression.
880    pub size: usize,
881}
882/// A versioned record that stores a history of values.
883#[allow(dead_code)]
884pub struct VersionedRecord<T: Clone> {
885    /// Every earlier value, oldest first: version `n` is `earlier[n]`.
886    earlier: Vec<T>,
887    /// The latest value (version `earlier.len()`), declared last so it drops last.
888    current: T,
889}
890#[allow(dead_code)]
891impl<T: Clone> VersionedRecord<T> {
892    /// Creates a new record with an initial value.
893    pub fn new(initial: T) -> Self {
894        Self {
895            earlier: Vec::new(),
896            current: initial,
897        }
898    }
899    /// Updates the record with a new version.
900    pub fn update(&mut self, val: T) {
901        let previous = std::mem::replace(&mut self.current, val);
902        self.earlier.push(previous);
903    }
904    /// Returns the current (latest) value.
905    pub fn current(&self) -> &T {
906        &self.current
907    }
908    /// Returns the value at version `n` (0-indexed), or `None`.
909    pub fn at_version(&self, n: usize) -> Option<&T> {
910        match self.earlier.get(n) {
911            Some(value) => Some(value),
912            None if n == self.earlier.len() => Some(&self.current),
913            None => None,
914        }
915    }
916    /// Returns the version number of the current value.
917    pub fn version(&self) -> usize {
918        self.earlier.len()
919    }
920    /// Returns `true` if more than one version exists.
921    pub fn has_history(&self) -> bool {
922        !self.earlier.is_empty()
923    }
924}
925/// A sparse vector: stores only non-default elements.
926#[allow(dead_code)]
927pub struct SparseVec<T: Default + Clone + PartialEq> {
928    entries: std::collections::HashMap<usize, T>,
929    default_: T,
930    logical_len: usize,
931}
932#[allow(dead_code)]
933impl<T: Default + Clone + PartialEq> SparseVec<T> {
934    /// Creates a new sparse vector with logical length `len`.
935    pub fn new(len: usize) -> Self {
936        Self {
937            entries: std::collections::HashMap::new(),
938            default_: T::default(),
939            logical_len: len,
940        }
941    }
942    /// Sets element at `idx`.
943    pub fn set(&mut self, idx: usize, val: T) {
944        if val == self.default_ {
945            self.entries.remove(&idx);
946        } else {
947            self.entries.insert(idx, val);
948        }
949    }
950    /// Gets element at `idx`.
951    pub fn get(&self, idx: usize) -> &T {
952        self.entries.get(&idx).unwrap_or(&self.default_)
953    }
954    /// Returns the logical length.
955    pub fn len(&self) -> usize {
956        self.logical_len
957    }
958    /// Returns whether the collection is empty.
959    pub fn is_empty(&self) -> bool {
960        self.len() == 0
961    }
962    /// Returns the number of non-default elements.
963    pub fn nnz(&self) -> usize {
964        self.entries.len()
965    }
966}
967/// A single step in a reduction trace.
968#[derive(Debug, Clone)]
969pub struct ReductionStep {
970    /// The expression before this step.
971    pub before: Expr,
972    /// The expression after this step.
973    pub after: Expr,
974    /// The rule applied (human-readable description).
975    pub rule: String,
976}
977/// A simple memoization table for reduction results.
978///
979/// Caches `(expr, strategy) → reduced_expr` pairs to avoid
980/// redundant reductions.
981#[derive(Debug, Default)]
982pub struct ReductionMemo {
983    entries: std::collections::HashMap<(ReductionStrategy, String), Expr>,
984    hits: usize,
985    misses: usize,
986}
987impl ReductionMemo {
988    /// Create an empty memo table.
989    pub fn new() -> Self {
990        Self::default()
991    }
992    /// Insert a memo entry.
993    pub fn insert(&mut self, strategy: ReductionStrategy, expr: &Expr, result: Expr) {
994        let key = (strategy, format!("{:?}", expr));
995        self.entries.insert(key, result);
996    }
997    /// Look up a memo entry.
998    pub fn get(&mut self, strategy: ReductionStrategy, expr: &Expr) -> Option<&Expr> {
999        let key = (strategy, format!("{:?}", expr));
1000        if self.entries.contains_key(&key) {
1001            self.hits += 1;
1002            self.entries.get(&key)
1003        } else {
1004            self.misses += 1;
1005            None
1006        }
1007    }
1008    /// Cache hit rate.
1009    pub fn hit_rate(&self) -> f64 {
1010        let total = self.hits + self.misses;
1011        if total == 0 {
1012            0.0
1013        } else {
1014            self.hits as f64 / total as f64
1015        }
1016    }
1017    /// Number of cached entries.
1018    pub fn len(&self) -> usize {
1019        self.entries.len()
1020    }
1021    /// Whether empty.
1022    pub fn is_empty(&self) -> bool {
1023        self.entries.is_empty()
1024    }
1025    /// Clear the memo table.
1026    pub fn clear(&mut self) {
1027        self.entries.clear();
1028        self.hits = 0;
1029        self.misses = 0;
1030    }
1031}
1032/// A simple mutable key-value store for test fixtures.
1033#[allow(dead_code)]
1034pub struct Fixture {
1035    data: std::collections::HashMap<String, String>,
1036}
1037#[allow(dead_code)]
1038impl Fixture {
1039    /// Creates an empty fixture.
1040    pub fn new() -> Self {
1041        Self {
1042            data: std::collections::HashMap::new(),
1043        }
1044    }
1045    /// Sets a key.
1046    pub fn set(&mut self, key: impl Into<String>, val: impl Into<String>) {
1047        self.data.insert(key.into(), val.into());
1048    }
1049    /// Gets a value.
1050    pub fn get(&self, key: &str) -> Option<&str> {
1051        self.data.get(key).map(|s| s.as_str())
1052    }
1053    /// Returns the number of entries.
1054    pub fn len(&self) -> usize {
1055        self.data.len()
1056    }
1057    /// Returns whether the collection is empty.
1058    pub fn is_empty(&self) -> bool {
1059        self.len() == 0
1060    }
1061}
1062/// The head form of an expression.
1063#[derive(Debug, Clone, PartialEq, Eq)]
1064pub enum HeadForm {
1065    /// Expression headed by a bound variable.
1066    BVar(u32),
1067    /// Expression headed by a free variable.
1068    FVar,
1069    /// Expression headed by a constant.
1070    Const(crate::Name),
1071    /// Expression headed by a sort.
1072    Sort,
1073    /// Expression is a lambda abstraction.
1074    Lambda,
1075    /// Expression is a Pi type.
1076    Pi,
1077    /// Expression is a literal.
1078    Lit,
1079    /// Expression is a let binding (reducible).
1080    Let,
1081    /// Expression is a projection.
1082    Proj,
1083    /// Expression is a beta-redex (reducible).
1084    BetaRedex,
1085}
1086impl HeadForm {
1087    /// Returns `true` if this head form indicates the expression is reducible.
1088    pub fn is_reducible(&self) -> bool {
1089        matches!(self, HeadForm::Let | HeadForm::BetaRedex)
1090    }
1091    /// Returns `true` if this is a variable head.
1092    pub fn is_variable(&self) -> bool {
1093        matches!(self, HeadForm::BVar(_) | HeadForm::FVar)
1094    }
1095    /// Returns `true` if this is a canonical/neutral form.
1096    pub fn is_neutral(&self) -> bool {
1097        matches!(
1098            self,
1099            HeadForm::BVar(_) | HeadForm::FVar | HeadForm::Const(_)
1100        )
1101    }
1102}
1103/// A simple key-value store backed by a sorted Vec for small maps.
1104#[allow(dead_code)]
1105pub struct SmallMap<K: Ord + Clone, V: Clone> {
1106    entries: Vec<(K, V)>,
1107}
1108#[allow(dead_code)]
1109impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
1110    /// Creates a new empty small map.
1111    pub fn new() -> Self {
1112        Self {
1113            entries: Vec::new(),
1114        }
1115    }
1116    /// Inserts or replaces the value for `key`.
1117    pub fn insert(&mut self, key: K, val: V) {
1118        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
1119            Ok(i) => self.entries[i].1 = val,
1120            Err(i) => self.entries.insert(i, (key, val)),
1121        }
1122    }
1123    /// Returns the value for `key`, or `None`.
1124    pub fn get(&self, key: &K) -> Option<&V> {
1125        self.entries
1126            .binary_search_by_key(&key, |(k, _)| k)
1127            .ok()
1128            .map(|i| &self.entries[i].1)
1129    }
1130    /// Returns the number of entries.
1131    pub fn len(&self) -> usize {
1132        self.entries.len()
1133    }
1134    /// Returns `true` if empty.
1135    pub fn is_empty(&self) -> bool {
1136        self.entries.is_empty()
1137    }
1138    /// Returns all keys.
1139    pub fn keys(&self) -> Vec<&K> {
1140        self.entries.iter().map(|(k, _)| k).collect()
1141    }
1142    /// Returns all values.
1143    pub fn values(&self) -> Vec<&V> {
1144        self.entries.iter().map(|(_, v)| v).collect()
1145    }
1146}
1147/// A pair of `StatSummary` values tracking before/after a transformation.
1148#[allow(dead_code)]
1149pub struct TransformStat {
1150    before: StatSummary,
1151    after: StatSummary,
1152}
1153#[allow(dead_code)]
1154impl TransformStat {
1155    /// Creates a new transform stat recorder.
1156    pub fn new() -> Self {
1157        Self {
1158            before: StatSummary::new(),
1159            after: StatSummary::new(),
1160        }
1161    }
1162    /// Records a before value.
1163    pub fn record_before(&mut self, v: f64) {
1164        self.before.record(v);
1165    }
1166    /// Records an after value.
1167    pub fn record_after(&mut self, v: f64) {
1168        self.after.record(v);
1169    }
1170    /// Returns the mean reduction ratio (after/before).
1171    pub fn mean_ratio(&self) -> Option<f64> {
1172        let b = self.before.mean()?;
1173        let a = self.after.mean()?;
1174        if b.abs() < f64::EPSILON {
1175            return None;
1176        }
1177        Some(a / b)
1178    }
1179}
1180/// A mutable reference stack for tracking the current "focus" in a tree traversal.
1181#[allow(dead_code)]
1182pub struct FocusStack<T> {
1183    items: Vec<T>,
1184}
1185#[allow(dead_code)]
1186impl<T> FocusStack<T> {
1187    /// Creates an empty focus stack.
1188    pub fn new() -> Self {
1189        Self { items: Vec::new() }
1190    }
1191    /// Focuses on `item`.
1192    pub fn focus(&mut self, item: T) {
1193        self.items.push(item);
1194    }
1195    /// Blurs (pops) the current focus.
1196    pub fn blur(&mut self) -> Option<T> {
1197        self.items.pop()
1198    }
1199    /// Returns the current focus, or `None`.
1200    pub fn current(&self) -> Option<&T> {
1201        self.items.last()
1202    }
1203    /// Returns the focus depth.
1204    pub fn depth(&self) -> usize {
1205        self.items.len()
1206    }
1207    /// Returns `true` if there is no current focus.
1208    pub fn is_empty(&self) -> bool {
1209        self.items.is_empty()
1210    }
1211}
1212/// A full trace of reduction steps.
1213#[derive(Debug, Clone, Default)]
1214pub struct ReductionTrace {
1215    /// The steps in order.
1216    pub steps: Vec<ReductionStep>,
1217    /// Whether the trace reached a normal form.
1218    pub reached_normal: bool,
1219    /// Whether the trace was truncated (too many steps).
1220    pub truncated: bool,
1221}
1222impl ReductionTrace {
1223    /// Create a new empty trace.
1224    pub fn new() -> Self {
1225        Self::default()
1226    }
1227    /// Get the number of steps in the trace.
1228    pub fn len(&self) -> usize {
1229        self.steps.len()
1230    }
1231    /// Returns `true` if the trace has no steps.
1232    pub fn is_empty(&self) -> bool {
1233        self.steps.is_empty()
1234    }
1235    /// Get the final expression (after all reductions).
1236    pub fn final_expr(&self) -> Option<&Expr> {
1237        self.steps.last().map(|s| &s.after)
1238    }
1239}
1240/// A type-erased function pointer with arity tracking.
1241#[allow(dead_code)]
1242pub struct RawFnPtr {
1243    /// The raw function pointer (stored as usize for type erasure).
1244    ptr: usize,
1245    arity: usize,
1246    name: String,
1247}
1248#[allow(dead_code)]
1249impl RawFnPtr {
1250    /// Creates a new raw function pointer descriptor.
1251    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
1252        Self {
1253            ptr,
1254            arity,
1255            name: name.into(),
1256        }
1257    }
1258    /// Returns the arity.
1259    pub fn arity(&self) -> usize {
1260        self.arity
1261    }
1262    /// Returns the name.
1263    pub fn name(&self) -> &str {
1264        &self.name
1265    }
1266    /// Returns the raw pointer value.
1267    pub fn raw(&self) -> usize {
1268        self.ptr
1269    }
1270}
1271/// A set of rewrite rules.
1272#[allow(dead_code)]
1273pub struct RewriteRuleSet {
1274    rules: Vec<RewriteRule>,
1275}
1276#[allow(dead_code)]
1277impl RewriteRuleSet {
1278    /// Creates an empty rule set.
1279    pub fn new() -> Self {
1280        Self { rules: Vec::new() }
1281    }
1282    /// Adds a rule.
1283    pub fn add(&mut self, rule: RewriteRule) {
1284        self.rules.push(rule);
1285    }
1286    /// Returns the number of rules.
1287    pub fn len(&self) -> usize {
1288        self.rules.len()
1289    }
1290    /// Returns `true` if the set is empty.
1291    pub fn is_empty(&self) -> bool {
1292        self.rules.is_empty()
1293    }
1294    /// Returns all conditional rules.
1295    pub fn conditional_rules(&self) -> Vec<&RewriteRule> {
1296        self.rules.iter().filter(|r| r.conditional).collect()
1297    }
1298    /// Returns all unconditional rules.
1299    pub fn unconditional_rules(&self) -> Vec<&RewriteRule> {
1300        self.rules.iter().filter(|r| !r.conditional).collect()
1301    }
1302    /// Looks up a rule by name.
1303    pub fn get(&self, name: &str) -> Option<&RewriteRule> {
1304        self.rules.iter().find(|r| r.name == name)
1305    }
1306}
1307/// Statistics about reduction.
1308#[derive(Debug, Clone, Default)]
1309pub struct ReductionStats {
1310    /// Number of beta reductions performed.
1311    pub beta_steps: usize,
1312    /// Number of delta unfoldings performed.
1313    pub delta_steps: usize,
1314    /// Number of let reductions performed.
1315    pub let_steps: usize,
1316    /// Number of projection reductions performed.
1317    pub proj_steps: usize,
1318    /// Total number of steps.
1319    pub total_steps: usize,
1320    /// Whether the reduction was aborted.
1321    pub aborted: bool,
1322}
1323impl ReductionStats {
1324    /// Create new empty statistics.
1325    pub fn new() -> Self {
1326        Self::default()
1327    }
1328    /// Get the total number of steps.
1329    pub fn total(&self) -> usize {
1330        self.total_steps
1331    }
1332    /// Returns `true` if any reductions were performed.
1333    pub fn any_reductions(&self) -> bool {
1334        self.total_steps > 0
1335    }
1336    /// Returns a formatted summary.
1337    pub fn summary(&self) -> String {
1338        format!(
1339            "β:{} δ:{} let:{} proj:{} total:{}{}",
1340            self.beta_steps,
1341            self.delta_steps,
1342            self.let_steps,
1343            self.proj_steps,
1344            self.total_steps,
1345            if self.aborted { " (aborted)" } else { "" }
1346        )
1347    }
1348}
1349/// A bound on the number of reduction steps.
1350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1351pub enum ReductionBound {
1352    /// No bound (reduce until fixpoint).
1353    Unlimited,
1354    /// Reduce at most `n` steps.
1355    Steps(usize),
1356    /// Reduce until the expression size reaches `n` nodes.
1357    Size(usize),
1358}
1359impl ReductionBound {
1360    /// Returns `true` if the bound has been exceeded.
1361    pub fn exceeded(&self, steps: usize, size: usize) -> bool {
1362        match self {
1363            ReductionBound::Unlimited => false,
1364            ReductionBound::Steps(n) => steps >= *n,
1365            ReductionBound::Size(n) => size >= *n,
1366        }
1367    }
1368}
1369/// Reduction strategy for expression simplification.
1370///
1371/// Different strategies trade off between completeness and efficiency.
1372/// Most use cases should use [`ReductionStrategy::WHNF`] or
1373/// [`ReductionStrategy::NF`].
1374#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1375pub enum ReductionStrategy {
1376    /// Reduce to weak head normal form.
1377    ///
1378    /// Only reduces the outermost redex; sub-expressions may remain unreduced.
1379    /// Efficient for most type-checking purposes.
1380    WHNF,
1381    /// Reduce to normal form.
1382    ///
1383    /// Fully reduces all sub-expressions. More expensive than WHNF.
1384    NF,
1385    /// Reduce exactly one step.
1386    ///
1387    /// Finds and reduces the outermost leftmost redex.
1388    OneStep,
1389    /// Call-by-value reduction.
1390    ///
1391    /// Evaluates arguments before applying functions (strict evaluation).
1392    CBV,
1393    /// Call-by-name reduction.
1394    ///
1395    /// Applies functions before evaluating arguments (lazy evaluation).
1396    CBN,
1397    /// Head reduction only.
1398    ///
1399    /// Like OneStep but restricted to the head position.
1400    HeadOnly,
1401}
1402impl ReductionStrategy {
1403    /// Returns a human-readable name for the strategy.
1404    pub fn name(&self) -> &'static str {
1405        match self {
1406            ReductionStrategy::WHNF => "whnf",
1407            ReductionStrategy::NF => "nf",
1408            ReductionStrategy::OneStep => "one-step",
1409            ReductionStrategy::CBV => "cbv",
1410            ReductionStrategy::CBN => "cbn",
1411            ReductionStrategy::HeadOnly => "head-only",
1412        }
1413    }
1414    /// Returns `true` if this strategy reduces to a complete normal form.
1415    pub fn is_complete(&self) -> bool {
1416        matches!(self, ReductionStrategy::NF)
1417    }
1418    /// Returns `true` if this is a lazy strategy.
1419    pub fn is_lazy(&self) -> bool {
1420        matches!(
1421            self,
1422            ReductionStrategy::WHNF | ReductionStrategy::CBN | ReductionStrategy::HeadOnly
1423        )
1424    }
1425    /// Returns `true` if this is an eager/strict strategy.
1426    pub fn is_eager(&self) -> bool {
1427        matches!(self, ReductionStrategy::CBV)
1428    }
1429}
1430/// A simple directed acyclic graph.
1431#[allow(dead_code)]
1432pub struct SimpleDag {
1433    /// `edges[i]` is the list of direct successors of node `i`.
1434    edges: Vec<Vec<usize>>,
1435}
1436#[allow(dead_code)]
1437impl SimpleDag {
1438    /// Creates a DAG with `n` nodes and no edges.
1439    pub fn new(n: usize) -> Self {
1440        Self {
1441            edges: vec![Vec::new(); n],
1442        }
1443    }
1444    /// Adds an edge from `from` to `to`.
1445    pub fn add_edge(&mut self, from: usize, to: usize) {
1446        if from < self.edges.len() {
1447            self.edges[from].push(to);
1448        }
1449    }
1450    /// Returns the successors of `node`.
1451    pub fn successors(&self, node: usize) -> &[usize] {
1452        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
1453    }
1454    /// Returns `true` if `from` can reach `to` via DFS.
1455    pub fn can_reach(&self, from: usize, to: usize) -> bool {
1456        let mut visited = vec![false; self.edges.len()];
1457        self.dfs(from, to, &mut visited)
1458    }
1459    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
1460        if cur == target {
1461            return true;
1462        }
1463        if cur >= visited.len() || visited[cur] {
1464            return false;
1465        }
1466        visited[cur] = true;
1467        for &next in self.successors(cur) {
1468            if self.dfs(next, target, visited) {
1469                return true;
1470            }
1471        }
1472        false
1473    }
1474    /// Returns the topological order of nodes, or `None` if a cycle is detected.
1475    pub fn topological_sort(&self) -> Option<Vec<usize>> {
1476        let n = self.edges.len();
1477        let mut in_degree = vec![0usize; n];
1478        for succs in &self.edges {
1479            for &s in succs {
1480                if s < n {
1481                    in_degree[s] += 1;
1482                }
1483            }
1484        }
1485        let mut queue: std::collections::VecDeque<usize> =
1486            (0..n).filter(|&i| in_degree[i] == 0).collect();
1487        let mut order = Vec::new();
1488        while let Some(node) = queue.pop_front() {
1489            order.push(node);
1490            for &s in self.successors(node) {
1491                if s < n {
1492                    in_degree[s] -= 1;
1493                    if in_degree[s] == 0 {
1494                        queue.push_back(s);
1495                    }
1496                }
1497            }
1498        }
1499        if order.len() == n {
1500            Some(order)
1501        } else {
1502            None
1503        }
1504    }
1505    /// Returns the number of nodes.
1506    pub fn num_nodes(&self) -> usize {
1507        self.edges.len()
1508    }
1509}