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: std::time::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: std::time::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: std::time::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: std::time::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 = std::time::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    /// Adds the top two values.  Panics if fewer than two values.
559    pub fn add(&mut self) {
560        let b = self
561            .stack
562            .pop()
563            .expect("stack must have at least two values for add");
564        let a = self
565            .stack
566            .pop()
567            .expect("stack must have at least two values for add");
568        self.stack.push(a + b);
569    }
570    /// Subtracts top from second.
571    pub fn sub(&mut self) {
572        let b = self
573            .stack
574            .pop()
575            .expect("stack must have at least two values for sub");
576        let a = self
577            .stack
578            .pop()
579            .expect("stack must have at least two values for sub");
580        self.stack.push(a - b);
581    }
582    /// Multiplies the top two values.
583    pub fn mul(&mut self) {
584        let b = self
585            .stack
586            .pop()
587            .expect("stack must have at least two values for mul");
588        let a = self
589            .stack
590            .pop()
591            .expect("stack must have at least two values for mul");
592        self.stack.push(a * b);
593    }
594    /// Peeks the top value.
595    pub fn peek(&self) -> Option<i64> {
596        self.stack.last().copied()
597    }
598    /// Returns the stack depth.
599    pub fn depth(&self) -> usize {
600        self.stack.len()
601    }
602}
603/// A window iterator that yields overlapping windows of size `n`.
604#[allow(dead_code)]
605pub struct WindowIterator<'a, T> {
606    pub(super) data: &'a [T],
607    pub(super) pos: usize,
608    pub(super) window: usize,
609}
610#[allow(dead_code)]
611impl<'a, T> WindowIterator<'a, T> {
612    /// Creates a new window iterator.
613    pub fn new(data: &'a [T], window: usize) -> Self {
614        Self {
615            data,
616            pos: 0,
617            window,
618        }
619    }
620}
621/// A write-once cell.
622#[allow(dead_code)]
623pub struct WriteOnce<T> {
624    value: std::cell::Cell<Option<T>>,
625}
626#[allow(dead_code)]
627impl<T: Copy> WriteOnce<T> {
628    /// Creates an empty write-once cell.
629    pub fn new() -> Self {
630        Self {
631            value: std::cell::Cell::new(None),
632        }
633    }
634    /// Writes a value.  Returns `false` if already written.
635    pub fn write(&self, val: T) -> bool {
636        if self.value.get().is_some() {
637            return false;
638        }
639        self.value.set(Some(val));
640        true
641    }
642    /// Returns the value if written.
643    pub fn read(&self) -> Option<T> {
644        self.value.get()
645    }
646    /// Returns `true` if the value has been written.
647    pub fn is_written(&self) -> bool {
648        self.value.get().is_some()
649    }
650}
651/// The result of a single reduction step.
652#[derive(Debug, Clone, PartialEq)]
653pub enum ReductionResult {
654    /// The expression was reduced to a new expression.
655    Reduced(Expr),
656    /// The expression is already in normal form (no reduction possible).
657    Normal(Expr),
658    /// Reduction was aborted (e.g., step limit exceeded).
659    Aborted(Expr),
660}
661impl ReductionResult {
662    /// Get the resulting expression regardless of whether reduction occurred.
663    pub fn expr(&self) -> &Expr {
664        match self {
665            ReductionResult::Reduced(e)
666            | ReductionResult::Normal(e)
667            | ReductionResult::Aborted(e) => e,
668        }
669    }
670    /// Returns `true` if reduction occurred.
671    pub fn was_reduced(&self) -> bool {
672        matches!(self, ReductionResult::Reduced(_))
673    }
674    /// Returns `true` if the expression is in normal form.
675    pub fn is_normal(&self) -> bool {
676        matches!(self, ReductionResult::Normal(_))
677    }
678    /// Consume the result and return the expression.
679    pub fn into_expr(self) -> Expr {
680        match self {
681            ReductionResult::Reduced(e)
682            | ReductionResult::Normal(e)
683            | ReductionResult::Aborted(e) => e,
684        }
685    }
686}
687/// A reusable scratch buffer for path computations.
688#[allow(dead_code)]
689pub struct PathBuf {
690    components: Vec<String>,
691}
692#[allow(dead_code)]
693impl PathBuf {
694    /// Creates a new empty path buffer.
695    pub fn new() -> Self {
696        Self {
697            components: Vec::new(),
698        }
699    }
700    /// Pushes a component.
701    pub fn push(&mut self, comp: impl Into<String>) {
702        self.components.push(comp.into());
703    }
704    /// Pops the last component.
705    pub fn pop(&mut self) {
706        self.components.pop();
707    }
708    /// Returns the current path as a `/`-separated string.
709    pub fn as_str(&self) -> String {
710        self.components.join("/")
711    }
712    /// Returns the depth of the path.
713    pub fn depth(&self) -> usize {
714        self.components.len()
715    }
716    /// Clears the path.
717    pub fn clear(&mut self) {
718        self.components.clear();
719    }
720}
721/// A trie-based prefix counter.
722#[allow(dead_code)]
723pub struct PrefixCounter {
724    children: std::collections::HashMap<char, PrefixCounter>,
725    count: usize,
726}
727#[allow(dead_code)]
728impl PrefixCounter {
729    /// Creates an empty prefix counter.
730    pub fn new() -> Self {
731        Self {
732            children: std::collections::HashMap::new(),
733            count: 0,
734        }
735    }
736    /// Records a string.
737    pub fn record(&mut self, s: &str) {
738        self.count += 1;
739        let mut node = self;
740        for c in s.chars() {
741            node = node.children.entry(c).or_default();
742            node.count += 1;
743        }
744    }
745    /// Returns how many strings have been recorded that start with `prefix`.
746    pub fn count_with_prefix(&self, prefix: &str) -> usize {
747        let mut node = self;
748        for c in prefix.chars() {
749            match node.children.get(&c) {
750                Some(n) => node = n,
751                None => return 0,
752            }
753        }
754        node.count
755    }
756}
757/// A simple decision tree node for rule dispatching.
758#[allow(dead_code)]
759#[allow(missing_docs)]
760pub enum DecisionNode {
761    /// A leaf with an action string.
762    Leaf(String),
763    /// An interior node: check `key` equals `val` → `yes_branch`, else `no_branch`.
764    Branch {
765        key: String,
766        val: String,
767        yes_branch: Box<DecisionNode>,
768        no_branch: Box<DecisionNode>,
769    },
770}
771#[allow(dead_code)]
772impl DecisionNode {
773    /// Evaluates the decision tree with the given context.
774    pub fn evaluate(&self, ctx: &std::collections::HashMap<String, String>) -> &str {
775        match self {
776            DecisionNode::Leaf(action) => action.as_str(),
777            DecisionNode::Branch {
778                key,
779                val,
780                yes_branch,
781                no_branch,
782            } => {
783                let actual = ctx.get(key).map(|s| s.as_str()).unwrap_or("");
784                if actual == val.as_str() {
785                    yes_branch.evaluate(ctx)
786                } else {
787                    no_branch.evaluate(ctx)
788                }
789            }
790        }
791    }
792    /// Returns the depth of the decision tree.
793    pub fn depth(&self) -> usize {
794        match self {
795            DecisionNode::Leaf(_) => 0,
796            DecisionNode::Branch {
797                yes_branch,
798                no_branch,
799                ..
800            } => 1 + yes_branch.depth().max(no_branch.depth()),
801        }
802    }
803}
804/// A dependency closure builder (transitive closure via BFS).
805#[allow(dead_code)]
806pub struct TransitiveClosure {
807    adj: Vec<Vec<usize>>,
808    n: usize,
809}
810#[allow(dead_code)]
811impl TransitiveClosure {
812    /// Creates a transitive closure builder for `n` nodes.
813    pub fn new(n: usize) -> Self {
814        Self {
815            adj: vec![Vec::new(); n],
816            n,
817        }
818    }
819    /// Adds a direct edge.
820    pub fn add_edge(&mut self, from: usize, to: usize) {
821        if from < self.n {
822            self.adj[from].push(to);
823        }
824    }
825    /// Computes all nodes reachable from `start` (including `start`).
826    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
827        let mut visited = vec![false; self.n];
828        let mut queue = std::collections::VecDeque::new();
829        queue.push_back(start);
830        while let Some(node) = queue.pop_front() {
831            if node >= self.n || visited[node] {
832                continue;
833            }
834            visited[node] = true;
835            for &next in &self.adj[node] {
836                queue.push_back(next);
837            }
838        }
839        (0..self.n).filter(|&i| visited[i]).collect()
840    }
841    /// Returns `true` if `from` can transitively reach `to`.
842    pub fn can_reach(&self, from: usize, to: usize) -> bool {
843        self.reachable_from(from).contains(&to)
844    }
845}
846/// A label set for a graph node.
847#[allow(dead_code)]
848pub struct LabelSet {
849    labels: Vec<String>,
850}
851#[allow(dead_code)]
852impl LabelSet {
853    /// Creates a new empty label set.
854    pub fn new() -> Self {
855        Self { labels: Vec::new() }
856    }
857    /// Adds a label (deduplicates).
858    pub fn add(&mut self, label: impl Into<String>) {
859        let s = label.into();
860        if !self.labels.contains(&s) {
861            self.labels.push(s);
862        }
863    }
864    /// Returns `true` if `label` is present.
865    pub fn has(&self, label: &str) -> bool {
866        self.labels.iter().any(|l| l == label)
867    }
868    /// Returns the count of labels.
869    pub fn count(&self) -> usize {
870        self.labels.len()
871    }
872    /// Returns all labels.
873    pub fn all(&self) -> &[String] {
874        &self.labels
875    }
876}
877/// Information about a redex (reducible expression).
878#[derive(Debug, Clone)]
879pub struct RedexInfo {
880    /// The kind of redex.
881    pub kind: RedexKind,
882    /// Depth in the expression tree where the redex occurs.
883    pub depth: usize,
884    /// Size of the redex sub-expression.
885    pub size: usize,
886}
887/// A versioned record that stores a history of values.
888#[allow(dead_code)]
889pub struct VersionedRecord<T: Clone> {
890    history: Vec<T>,
891}
892#[allow(dead_code)]
893impl<T: Clone> VersionedRecord<T> {
894    /// Creates a new record with an initial value.
895    pub fn new(initial: T) -> Self {
896        Self {
897            history: vec![initial],
898        }
899    }
900    /// Updates the record with a new version.
901    pub fn update(&mut self, val: T) {
902        self.history.push(val);
903    }
904    /// Returns the current (latest) value.
905    pub fn current(&self) -> &T {
906        self.history
907            .last()
908            .expect("VersionedRecord history is always non-empty after construction")
909    }
910    /// Returns the value at version `n` (0-indexed), or `None`.
911    pub fn at_version(&self, n: usize) -> Option<&T> {
912        self.history.get(n)
913    }
914    /// Returns the version number of the current value.
915    pub fn version(&self) -> usize {
916        self.history.len() - 1
917    }
918    /// Returns `true` if more than one version exists.
919    pub fn has_history(&self) -> bool {
920        self.history.len() > 1
921    }
922}
923/// A sparse vector: stores only non-default elements.
924#[allow(dead_code)]
925pub struct SparseVec<T: Default + Clone + PartialEq> {
926    entries: std::collections::HashMap<usize, T>,
927    default_: T,
928    logical_len: usize,
929}
930#[allow(dead_code)]
931impl<T: Default + Clone + PartialEq> SparseVec<T> {
932    /// Creates a new sparse vector with logical length `len`.
933    pub fn new(len: usize) -> Self {
934        Self {
935            entries: std::collections::HashMap::new(),
936            default_: T::default(),
937            logical_len: len,
938        }
939    }
940    /// Sets element at `idx`.
941    pub fn set(&mut self, idx: usize, val: T) {
942        if val == self.default_ {
943            self.entries.remove(&idx);
944        } else {
945            self.entries.insert(idx, val);
946        }
947    }
948    /// Gets element at `idx`.
949    pub fn get(&self, idx: usize) -> &T {
950        self.entries.get(&idx).unwrap_or(&self.default_)
951    }
952    /// Returns the logical length.
953    pub fn len(&self) -> usize {
954        self.logical_len
955    }
956    /// Returns whether the collection is empty.
957    pub fn is_empty(&self) -> bool {
958        self.len() == 0
959    }
960    /// Returns the number of non-default elements.
961    pub fn nnz(&self) -> usize {
962        self.entries.len()
963    }
964}
965/// A single step in a reduction trace.
966#[derive(Debug, Clone)]
967pub struct ReductionStep {
968    /// The expression before this step.
969    pub before: Expr,
970    /// The expression after this step.
971    pub after: Expr,
972    /// The rule applied (human-readable description).
973    pub rule: String,
974}
975/// A simple memoization table for reduction results.
976///
977/// Caches `(expr, strategy) → reduced_expr` pairs to avoid
978/// redundant reductions.
979#[derive(Debug, Default)]
980pub struct ReductionMemo {
981    entries: std::collections::HashMap<(ReductionStrategy, String), Expr>,
982    hits: usize,
983    misses: usize,
984}
985impl ReductionMemo {
986    /// Create an empty memo table.
987    pub fn new() -> Self {
988        Self::default()
989    }
990    /// Insert a memo entry.
991    pub fn insert(&mut self, strategy: ReductionStrategy, expr: &Expr, result: Expr) {
992        let key = (strategy, format!("{:?}", expr));
993        self.entries.insert(key, result);
994    }
995    /// Look up a memo entry.
996    pub fn get(&mut self, strategy: ReductionStrategy, expr: &Expr) -> Option<&Expr> {
997        let key = (strategy, format!("{:?}", expr));
998        if self.entries.contains_key(&key) {
999            self.hits += 1;
1000            self.entries.get(&key)
1001        } else {
1002            self.misses += 1;
1003            None
1004        }
1005    }
1006    /// Cache hit rate.
1007    pub fn hit_rate(&self) -> f64 {
1008        let total = self.hits + self.misses;
1009        if total == 0 {
1010            0.0
1011        } else {
1012            self.hits as f64 / total as f64
1013        }
1014    }
1015    /// Number of cached entries.
1016    pub fn len(&self) -> usize {
1017        self.entries.len()
1018    }
1019    /// Whether empty.
1020    pub fn is_empty(&self) -> bool {
1021        self.entries.is_empty()
1022    }
1023    /// Clear the memo table.
1024    pub fn clear(&mut self) {
1025        self.entries.clear();
1026        self.hits = 0;
1027        self.misses = 0;
1028    }
1029}
1030/// A simple mutable key-value store for test fixtures.
1031#[allow(dead_code)]
1032pub struct Fixture {
1033    data: std::collections::HashMap<String, String>,
1034}
1035#[allow(dead_code)]
1036impl Fixture {
1037    /// Creates an empty fixture.
1038    pub fn new() -> Self {
1039        Self {
1040            data: std::collections::HashMap::new(),
1041        }
1042    }
1043    /// Sets a key.
1044    pub fn set(&mut self, key: impl Into<String>, val: impl Into<String>) {
1045        self.data.insert(key.into(), val.into());
1046    }
1047    /// Gets a value.
1048    pub fn get(&self, key: &str) -> Option<&str> {
1049        self.data.get(key).map(|s| s.as_str())
1050    }
1051    /// Returns the number of entries.
1052    pub fn len(&self) -> usize {
1053        self.data.len()
1054    }
1055    /// Returns whether the collection is empty.
1056    pub fn is_empty(&self) -> bool {
1057        self.len() == 0
1058    }
1059}
1060/// The head form of an expression.
1061#[derive(Debug, Clone, PartialEq, Eq)]
1062pub enum HeadForm {
1063    /// Expression headed by a bound variable.
1064    BVar(u32),
1065    /// Expression headed by a free variable.
1066    FVar,
1067    /// Expression headed by a constant.
1068    Const(crate::Name),
1069    /// Expression headed by a sort.
1070    Sort,
1071    /// Expression is a lambda abstraction.
1072    Lambda,
1073    /// Expression is a Pi type.
1074    Pi,
1075    /// Expression is a literal.
1076    Lit,
1077    /// Expression is a let binding (reducible).
1078    Let,
1079    /// Expression is a projection.
1080    Proj,
1081    /// Expression is a beta-redex (reducible).
1082    BetaRedex,
1083}
1084impl HeadForm {
1085    /// Returns `true` if this head form indicates the expression is reducible.
1086    pub fn is_reducible(&self) -> bool {
1087        matches!(self, HeadForm::Let | HeadForm::BetaRedex)
1088    }
1089    /// Returns `true` if this is a variable head.
1090    pub fn is_variable(&self) -> bool {
1091        matches!(self, HeadForm::BVar(_) | HeadForm::FVar)
1092    }
1093    /// Returns `true` if this is a canonical/neutral form.
1094    pub fn is_neutral(&self) -> bool {
1095        matches!(
1096            self,
1097            HeadForm::BVar(_) | HeadForm::FVar | HeadForm::Const(_)
1098        )
1099    }
1100}
1101/// A simple key-value store backed by a sorted Vec for small maps.
1102#[allow(dead_code)]
1103pub struct SmallMap<K: Ord + Clone, V: Clone> {
1104    entries: Vec<(K, V)>,
1105}
1106#[allow(dead_code)]
1107impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
1108    /// Creates a new empty small map.
1109    pub fn new() -> Self {
1110        Self {
1111            entries: Vec::new(),
1112        }
1113    }
1114    /// Inserts or replaces the value for `key`.
1115    pub fn insert(&mut self, key: K, val: V) {
1116        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
1117            Ok(i) => self.entries[i].1 = val,
1118            Err(i) => self.entries.insert(i, (key, val)),
1119        }
1120    }
1121    /// Returns the value for `key`, or `None`.
1122    pub fn get(&self, key: &K) -> Option<&V> {
1123        self.entries
1124            .binary_search_by_key(&key, |(k, _)| k)
1125            .ok()
1126            .map(|i| &self.entries[i].1)
1127    }
1128    /// Returns the number of entries.
1129    pub fn len(&self) -> usize {
1130        self.entries.len()
1131    }
1132    /// Returns `true` if empty.
1133    pub fn is_empty(&self) -> bool {
1134        self.entries.is_empty()
1135    }
1136    /// Returns all keys.
1137    pub fn keys(&self) -> Vec<&K> {
1138        self.entries.iter().map(|(k, _)| k).collect()
1139    }
1140    /// Returns all values.
1141    pub fn values(&self) -> Vec<&V> {
1142        self.entries.iter().map(|(_, v)| v).collect()
1143    }
1144}
1145/// A pair of `StatSummary` values tracking before/after a transformation.
1146#[allow(dead_code)]
1147pub struct TransformStat {
1148    before: StatSummary,
1149    after: StatSummary,
1150}
1151#[allow(dead_code)]
1152impl TransformStat {
1153    /// Creates a new transform stat recorder.
1154    pub fn new() -> Self {
1155        Self {
1156            before: StatSummary::new(),
1157            after: StatSummary::new(),
1158        }
1159    }
1160    /// Records a before value.
1161    pub fn record_before(&mut self, v: f64) {
1162        self.before.record(v);
1163    }
1164    /// Records an after value.
1165    pub fn record_after(&mut self, v: f64) {
1166        self.after.record(v);
1167    }
1168    /// Returns the mean reduction ratio (after/before).
1169    pub fn mean_ratio(&self) -> Option<f64> {
1170        let b = self.before.mean()?;
1171        let a = self.after.mean()?;
1172        if b.abs() < f64::EPSILON {
1173            return None;
1174        }
1175        Some(a / b)
1176    }
1177}
1178/// A mutable reference stack for tracking the current "focus" in a tree traversal.
1179#[allow(dead_code)]
1180pub struct FocusStack<T> {
1181    items: Vec<T>,
1182}
1183#[allow(dead_code)]
1184impl<T> FocusStack<T> {
1185    /// Creates an empty focus stack.
1186    pub fn new() -> Self {
1187        Self { items: Vec::new() }
1188    }
1189    /// Focuses on `item`.
1190    pub fn focus(&mut self, item: T) {
1191        self.items.push(item);
1192    }
1193    /// Blurs (pops) the current focus.
1194    pub fn blur(&mut self) -> Option<T> {
1195        self.items.pop()
1196    }
1197    /// Returns the current focus, or `None`.
1198    pub fn current(&self) -> Option<&T> {
1199        self.items.last()
1200    }
1201    /// Returns the focus depth.
1202    pub fn depth(&self) -> usize {
1203        self.items.len()
1204    }
1205    /// Returns `true` if there is no current focus.
1206    pub fn is_empty(&self) -> bool {
1207        self.items.is_empty()
1208    }
1209}
1210/// A full trace of reduction steps.
1211#[derive(Debug, Clone, Default)]
1212pub struct ReductionTrace {
1213    /// The steps in order.
1214    pub steps: Vec<ReductionStep>,
1215    /// Whether the trace reached a normal form.
1216    pub reached_normal: bool,
1217    /// Whether the trace was truncated (too many steps).
1218    pub truncated: bool,
1219}
1220impl ReductionTrace {
1221    /// Create a new empty trace.
1222    pub fn new() -> Self {
1223        Self::default()
1224    }
1225    /// Get the number of steps in the trace.
1226    pub fn len(&self) -> usize {
1227        self.steps.len()
1228    }
1229    /// Returns `true` if the trace has no steps.
1230    pub fn is_empty(&self) -> bool {
1231        self.steps.is_empty()
1232    }
1233    /// Get the final expression (after all reductions).
1234    pub fn final_expr(&self) -> Option<&Expr> {
1235        self.steps.last().map(|s| &s.after)
1236    }
1237}
1238/// A type-erased function pointer with arity tracking.
1239#[allow(dead_code)]
1240pub struct RawFnPtr {
1241    /// The raw function pointer (stored as usize for type erasure).
1242    ptr: usize,
1243    arity: usize,
1244    name: String,
1245}
1246#[allow(dead_code)]
1247impl RawFnPtr {
1248    /// Creates a new raw function pointer descriptor.
1249    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
1250        Self {
1251            ptr,
1252            arity,
1253            name: name.into(),
1254        }
1255    }
1256    /// Returns the arity.
1257    pub fn arity(&self) -> usize {
1258        self.arity
1259    }
1260    /// Returns the name.
1261    pub fn name(&self) -> &str {
1262        &self.name
1263    }
1264    /// Returns the raw pointer value.
1265    pub fn raw(&self) -> usize {
1266        self.ptr
1267    }
1268}
1269/// A set of rewrite rules.
1270#[allow(dead_code)]
1271pub struct RewriteRuleSet {
1272    rules: Vec<RewriteRule>,
1273}
1274#[allow(dead_code)]
1275impl RewriteRuleSet {
1276    /// Creates an empty rule set.
1277    pub fn new() -> Self {
1278        Self { rules: Vec::new() }
1279    }
1280    /// Adds a rule.
1281    pub fn add(&mut self, rule: RewriteRule) {
1282        self.rules.push(rule);
1283    }
1284    /// Returns the number of rules.
1285    pub fn len(&self) -> usize {
1286        self.rules.len()
1287    }
1288    /// Returns `true` if the set is empty.
1289    pub fn is_empty(&self) -> bool {
1290        self.rules.is_empty()
1291    }
1292    /// Returns all conditional rules.
1293    pub fn conditional_rules(&self) -> Vec<&RewriteRule> {
1294        self.rules.iter().filter(|r| r.conditional).collect()
1295    }
1296    /// Returns all unconditional rules.
1297    pub fn unconditional_rules(&self) -> Vec<&RewriteRule> {
1298        self.rules.iter().filter(|r| !r.conditional).collect()
1299    }
1300    /// Looks up a rule by name.
1301    pub fn get(&self, name: &str) -> Option<&RewriteRule> {
1302        self.rules.iter().find(|r| r.name == name)
1303    }
1304}
1305/// Statistics about reduction.
1306#[derive(Debug, Clone, Default)]
1307pub struct ReductionStats {
1308    /// Number of beta reductions performed.
1309    pub beta_steps: usize,
1310    /// Number of delta unfoldings performed.
1311    pub delta_steps: usize,
1312    /// Number of let reductions performed.
1313    pub let_steps: usize,
1314    /// Number of projection reductions performed.
1315    pub proj_steps: usize,
1316    /// Total number of steps.
1317    pub total_steps: usize,
1318    /// Whether the reduction was aborted.
1319    pub aborted: bool,
1320}
1321impl ReductionStats {
1322    /// Create new empty statistics.
1323    pub fn new() -> Self {
1324        Self::default()
1325    }
1326    /// Get the total number of steps.
1327    pub fn total(&self) -> usize {
1328        self.total_steps
1329    }
1330    /// Returns `true` if any reductions were performed.
1331    pub fn any_reductions(&self) -> bool {
1332        self.total_steps > 0
1333    }
1334    /// Returns a formatted summary.
1335    pub fn summary(&self) -> String {
1336        format!(
1337            "β:{} δ:{} let:{} proj:{} total:{}{}",
1338            self.beta_steps,
1339            self.delta_steps,
1340            self.let_steps,
1341            self.proj_steps,
1342            self.total_steps,
1343            if self.aborted { " (aborted)" } else { "" }
1344        )
1345    }
1346}
1347/// A bound on the number of reduction steps.
1348#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1349pub enum ReductionBound {
1350    /// No bound (reduce until fixpoint).
1351    Unlimited,
1352    /// Reduce at most `n` steps.
1353    Steps(usize),
1354    /// Reduce until the expression size reaches `n` nodes.
1355    Size(usize),
1356}
1357impl ReductionBound {
1358    /// Returns `true` if the bound has been exceeded.
1359    pub fn exceeded(&self, steps: usize, size: usize) -> bool {
1360        match self {
1361            ReductionBound::Unlimited => false,
1362            ReductionBound::Steps(n) => steps >= *n,
1363            ReductionBound::Size(n) => size >= *n,
1364        }
1365    }
1366}
1367/// Reduction strategy for expression simplification.
1368///
1369/// Different strategies trade off between completeness and efficiency.
1370/// Most use cases should use [`ReductionStrategy::WHNF`] or
1371/// [`ReductionStrategy::NF`].
1372#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1373pub enum ReductionStrategy {
1374    /// Reduce to weak head normal form.
1375    ///
1376    /// Only reduces the outermost redex; sub-expressions may remain unreduced.
1377    /// Efficient for most type-checking purposes.
1378    WHNF,
1379    /// Reduce to normal form.
1380    ///
1381    /// Fully reduces all sub-expressions. More expensive than WHNF.
1382    NF,
1383    /// Reduce exactly one step.
1384    ///
1385    /// Finds and reduces the outermost leftmost redex.
1386    OneStep,
1387    /// Call-by-value reduction.
1388    ///
1389    /// Evaluates arguments before applying functions (strict evaluation).
1390    CBV,
1391    /// Call-by-name reduction.
1392    ///
1393    /// Applies functions before evaluating arguments (lazy evaluation).
1394    CBN,
1395    /// Head reduction only.
1396    ///
1397    /// Like OneStep but restricted to the head position.
1398    HeadOnly,
1399}
1400impl ReductionStrategy {
1401    /// Returns a human-readable name for the strategy.
1402    pub fn name(&self) -> &'static str {
1403        match self {
1404            ReductionStrategy::WHNF => "whnf",
1405            ReductionStrategy::NF => "nf",
1406            ReductionStrategy::OneStep => "one-step",
1407            ReductionStrategy::CBV => "cbv",
1408            ReductionStrategy::CBN => "cbn",
1409            ReductionStrategy::HeadOnly => "head-only",
1410        }
1411    }
1412    /// Returns `true` if this strategy reduces to a complete normal form.
1413    pub fn is_complete(&self) -> bool {
1414        matches!(self, ReductionStrategy::NF)
1415    }
1416    /// Returns `true` if this is a lazy strategy.
1417    pub fn is_lazy(&self) -> bool {
1418        matches!(
1419            self,
1420            ReductionStrategy::WHNF | ReductionStrategy::CBN | ReductionStrategy::HeadOnly
1421        )
1422    }
1423    /// Returns `true` if this is an eager/strict strategy.
1424    pub fn is_eager(&self) -> bool {
1425        matches!(self, ReductionStrategy::CBV)
1426    }
1427}
1428/// A simple directed acyclic graph.
1429#[allow(dead_code)]
1430pub struct SimpleDag {
1431    /// `edges[i]` is the list of direct successors of node `i`.
1432    edges: Vec<Vec<usize>>,
1433}
1434#[allow(dead_code)]
1435impl SimpleDag {
1436    /// Creates a DAG with `n` nodes and no edges.
1437    pub fn new(n: usize) -> Self {
1438        Self {
1439            edges: vec![Vec::new(); n],
1440        }
1441    }
1442    /// Adds an edge from `from` to `to`.
1443    pub fn add_edge(&mut self, from: usize, to: usize) {
1444        if from < self.edges.len() {
1445            self.edges[from].push(to);
1446        }
1447    }
1448    /// Returns the successors of `node`.
1449    pub fn successors(&self, node: usize) -> &[usize] {
1450        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
1451    }
1452    /// Returns `true` if `from` can reach `to` via DFS.
1453    pub fn can_reach(&self, from: usize, to: usize) -> bool {
1454        let mut visited = vec![false; self.edges.len()];
1455        self.dfs(from, to, &mut visited)
1456    }
1457    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
1458        if cur == target {
1459            return true;
1460        }
1461        if cur >= visited.len() || visited[cur] {
1462            return false;
1463        }
1464        visited[cur] = true;
1465        for &next in self.successors(cur) {
1466            if self.dfs(next, target, visited) {
1467                return true;
1468            }
1469        }
1470        false
1471    }
1472    /// Returns the topological order of nodes, or `None` if a cycle is detected.
1473    pub fn topological_sort(&self) -> Option<Vec<usize>> {
1474        let n = self.edges.len();
1475        let mut in_degree = vec![0usize; n];
1476        for succs in &self.edges {
1477            for &s in succs {
1478                if s < n {
1479                    in_degree[s] += 1;
1480                }
1481            }
1482        }
1483        let mut queue: std::collections::VecDeque<usize> =
1484            (0..n).filter(|&i| in_degree[i] == 0).collect();
1485        let mut order = Vec::new();
1486        while let Some(node) = queue.pop_front() {
1487            order.push(node);
1488            for &s in self.successors(node) {
1489                if s < n {
1490                    in_degree[s] -= 1;
1491                    if in_degree[s] == 0 {
1492                        queue.push_back(s);
1493                    }
1494                }
1495            }
1496        }
1497        if order.len() == n {
1498            Some(order)
1499        } else {
1500            None
1501        }
1502    }
1503    /// Returns the number of nodes.
1504    pub fn num_nodes(&self) -> usize {
1505        self.edges.len()
1506    }
1507}