Skip to main content

oxilean_kernel/eta/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::{BinderInfo, Expr, Name};
6
7use std::collections::HashMap;
8
9/// Records the outcome of an eta-reduction attempt.
10#[allow(dead_code)]
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum EtaOutcome {
13    /// The expression was already in eta-normal form.
14    AlreadyNormal,
15    /// The expression was reduced successfully.
16    Reduced,
17    /// The expression could not be reduced (not an eta-redex).
18    NotApplicable,
19    /// Reduction was aborted due to exceeding depth limit.
20    DepthLimitExceeded,
21}
22#[allow(dead_code)]
23impl EtaOutcome {
24    /// Returns `true` if this represents a successful reduction.
25    pub fn is_success(self) -> bool {
26        matches!(self, EtaOutcome::Reduced | EtaOutcome::AlreadyNormal)
27    }
28    /// Returns a short human-readable string.
29    pub fn label(self) -> &'static str {
30        match self {
31            EtaOutcome::AlreadyNormal => "already_normal",
32            EtaOutcome::Reduced => "reduced",
33            EtaOutcome::NotApplicable => "not_applicable",
34            EtaOutcome::DepthLimitExceeded => "depth_limit",
35        }
36    }
37}
38/// Describes the structure of an expression for eta-expansion purposes.
39#[allow(dead_code)]
40#[derive(Clone, Debug, PartialEq, Eq)]
41pub enum EtaStructure {
42    /// An expression that is not a lambda or application.
43    Atomic,
44    /// A lambda abstraction with arity `n`.
45    Lambda(usize),
46    /// An application spine with `n` arguments.
47    App(usize),
48    /// An expression suitable for eta-expansion.
49    EtaExpandable,
50}
51#[allow(dead_code)]
52impl EtaStructure {
53    /// Returns the arity if this is a lambda or app.
54    pub fn arity(&self) -> usize {
55        match self {
56            EtaStructure::Lambda(n) | EtaStructure::App(n) => *n,
57            _ => 0,
58        }
59    }
60}
61/// A window iterator that yields overlapping windows of size `n`.
62#[allow(dead_code)]
63pub struct WindowIterator<'a, T> {
64    pub(super) data: &'a [T],
65    pub(super) pos: usize,
66    pub(super) window: usize,
67}
68#[allow(dead_code)]
69impl<'a, T> WindowIterator<'a, T> {
70    /// Creates a new window iterator.
71    pub fn new(data: &'a [T], window: usize) -> Self {
72        Self {
73            data,
74            pos: 0,
75            window,
76        }
77    }
78}
79/// A pool of reusable string buffers.
80#[allow(dead_code)]
81pub struct StringPool {
82    free: Vec<String>,
83}
84#[allow(dead_code)]
85impl StringPool {
86    /// Creates a new empty string pool.
87    pub fn new() -> Self {
88        Self { free: Vec::new() }
89    }
90    /// Takes a string from the pool (may be empty).
91    pub fn take(&mut self) -> String {
92        self.free.pop().unwrap_or_default()
93    }
94    /// Returns a string to the pool.
95    pub fn give(&mut self, mut s: String) {
96        s.clear();
97        self.free.push(s);
98    }
99    /// Returns the number of free strings in the pool.
100    pub fn free_count(&self) -> usize {
101        self.free.len()
102    }
103}
104/// A rewrite rule: `lhs → rhs` (both closed expressions).
105#[derive(Clone, Debug)]
106pub struct RewriteRule {
107    /// Left-hand side pattern.
108    pub lhs: Expr,
109    /// Right-hand side replacement.
110    pub rhs: Expr,
111    /// Human-readable name for the rule.
112    pub name: String,
113}
114impl RewriteRule {
115    /// Create a new rewrite rule.
116    pub fn new(name: impl Into<String>, lhs: Expr, rhs: Expr) -> Self {
117        Self {
118            lhs,
119            rhs,
120            name: name.into(),
121        }
122    }
123    /// Apply the rule to `expr` if the top-level matches.
124    pub fn apply_top(&self, expr: &Expr) -> Option<Expr> {
125        if expr == &self.lhs {
126            Some(self.rhs.clone())
127        } else {
128            None
129        }
130    }
131}
132/// A generic counter that tracks min/max/sum for statistical summaries.
133#[allow(dead_code)]
134pub struct StatSummary {
135    count: u64,
136    sum: f64,
137    min: f64,
138    max: f64,
139}
140#[allow(dead_code)]
141impl StatSummary {
142    /// Creates an empty summary.
143    pub fn new() -> Self {
144        Self {
145            count: 0,
146            sum: 0.0,
147            min: f64::INFINITY,
148            max: f64::NEG_INFINITY,
149        }
150    }
151    /// Records a sample.
152    pub fn record(&mut self, val: f64) {
153        self.count += 1;
154        self.sum += val;
155        if val < self.min {
156            self.min = val;
157        }
158        if val > self.max {
159            self.max = val;
160        }
161    }
162    /// Returns the mean, or `None` if no samples.
163    pub fn mean(&self) -> Option<f64> {
164        if self.count == 0 {
165            None
166        } else {
167            Some(self.sum / self.count as f64)
168        }
169    }
170    /// Returns the minimum, or `None` if no samples.
171    pub fn min(&self) -> Option<f64> {
172        if self.count == 0 {
173            None
174        } else {
175            Some(self.min)
176        }
177    }
178    /// Returns the maximum, or `None` if no samples.
179    pub fn max(&self) -> Option<f64> {
180        if self.count == 0 {
181            None
182        } else {
183            Some(self.max)
184        }
185    }
186    /// Returns the count of recorded samples.
187    pub fn count(&self) -> u64 {
188        self.count
189    }
190}
191/// A simple memoised eta-normal-form checker.
192#[allow(dead_code)]
193pub struct EtaChecker {
194    cache: EtaNormalCache,
195    log: EtaLog,
196}
197#[allow(dead_code)]
198impl EtaChecker {
199    /// Creates a new eta checker.
200    pub fn new() -> Self {
201        Self {
202            cache: EtaNormalCache::new(),
203            log: EtaLog::new(),
204        }
205    }
206    /// Checks whether `hash` is eta-normal, using the cache.
207    pub fn is_eta_normal(&mut self, hash: u64) -> EtaOutcome {
208        if let Some(is_normal) = self.cache.query(hash) {
209            let outcome = if is_normal {
210                EtaOutcome::AlreadyNormal
211            } else {
212                EtaOutcome::NotApplicable
213            };
214            self.log.record(outcome);
215            return outcome;
216        }
217        self.cache.insert(hash, false);
218        self.log.record(EtaOutcome::NotApplicable);
219        EtaOutcome::NotApplicable
220    }
221    /// Returns the success rate from the log.
222    pub fn success_rate(&self) -> f64 {
223        self.log.success_rate()
224    }
225}
226/// A versioned record that stores a history of values.
227#[allow(dead_code)]
228pub struct VersionedRecord<T: Clone> {
229    history: Vec<T>,
230}
231#[allow(dead_code)]
232impl<T: Clone> VersionedRecord<T> {
233    /// Creates a new record with an initial value.
234    pub fn new(initial: T) -> Self {
235        Self {
236            history: vec![initial],
237        }
238    }
239    /// Updates the record with a new version.
240    pub fn update(&mut self, val: T) {
241        self.history.push(val);
242    }
243    /// Returns the current (latest) value.
244    pub fn current(&self) -> &T {
245        self.history
246            .last()
247            .expect("VersionedRecord history is always non-empty after construction")
248    }
249    /// Returns the value at version `n` (0-indexed), or `None`.
250    pub fn at_version(&self, n: usize) -> Option<&T> {
251        self.history.get(n)
252    }
253    /// Returns the version number of the current value.
254    pub fn version(&self) -> usize {
255        self.history.len() - 1
256    }
257    /// Returns `true` if more than one version exists.
258    pub fn has_history(&self) -> bool {
259        self.history.len() > 1
260    }
261}
262/// Represents a pending eta-reduction job.
263#[allow(dead_code)]
264#[allow(missing_docs)]
265pub struct EtaJob {
266    /// Unique job ID.
267    pub id: u64,
268    /// Expression hash to reduce.
269    pub hash: u64,
270    /// Priority (higher = process first).
271    pub prio: u32,
272}
273#[allow(dead_code)]
274impl EtaJob {
275    /// Creates a new eta reduction job.
276    pub fn new(id: u64, hash: u64, prio: u32) -> Self {
277        Self { id, hash, prio }
278    }
279}
280/// Represents a rewrite rule `lhs → rhs`.
281#[allow(dead_code)]
282#[allow(missing_docs)]
283pub struct EtaRewriteRule {
284    /// The left-hand side pattern.
285    pub lhs: String,
286    /// The right-hand side template.
287    pub rhs: String,
288}
289#[allow(dead_code)]
290impl EtaRewriteRule {
291    /// Creates a new rewrite rule.
292    pub fn new(lhs: impl Into<String>, rhs: impl Into<String>) -> Self {
293        Self {
294            lhs: lhs.into(),
295            rhs: rhs.into(),
296        }
297    }
298}
299/// Statistics about an expression's lambda structure.
300#[derive(Debug, Default, Clone)]
301pub struct LambdaStats {
302    /// Total number of lambda binders.
303    pub lambda_count: usize,
304    /// Total number of pi binders.
305    pub pi_count: usize,
306    /// Total number of application nodes.
307    pub app_count: usize,
308    /// Total number of let binders.
309    pub let_count: usize,
310    /// Maximum nesting depth encountered.
311    pub max_depth: usize,
312}
313impl LambdaStats {
314    /// Compute statistics for an expression.
315    pub fn compute(expr: &Expr) -> Self {
316        let mut stats = Self::default();
317        Self::walk(expr, &mut stats, 0);
318        stats
319    }
320    fn walk(expr: &Expr, stats: &mut Self, depth: usize) {
321        if depth > stats.max_depth {
322            stats.max_depth = depth;
323        }
324        match expr {
325            Expr::Lam(_, _, ty, body) => {
326                stats.lambda_count += 1;
327                Self::walk(ty, stats, depth + 1);
328                Self::walk(body, stats, depth + 1);
329            }
330            Expr::Pi(_, _, ty, body) => {
331                stats.pi_count += 1;
332                Self::walk(ty, stats, depth + 1);
333                Self::walk(body, stats, depth + 1);
334            }
335            Expr::App(f, a) => {
336                stats.app_count += 1;
337                Self::walk(f, stats, depth + 1);
338                Self::walk(a, stats, depth + 1);
339            }
340            Expr::Let(_, ty, val, body) => {
341                stats.let_count += 1;
342                Self::walk(ty, stats, depth + 1);
343                Self::walk(val, stats, depth + 1);
344                Self::walk(body, stats, depth + 1);
345            }
346            _ => {}
347        }
348    }
349    /// Total number of binders (lambda + pi + let).
350    pub fn total_binders(&self) -> usize {
351        self.lambda_count + self.pi_count + self.let_count
352    }
353}
354/// A dependency closure builder (transitive closure via BFS).
355#[allow(dead_code)]
356pub struct TransitiveClosure {
357    adj: Vec<Vec<usize>>,
358    n: usize,
359}
360#[allow(dead_code)]
361impl TransitiveClosure {
362    /// Creates a transitive closure builder for `n` nodes.
363    pub fn new(n: usize) -> Self {
364        Self {
365            adj: vec![Vec::new(); n],
366            n,
367        }
368    }
369    /// Adds a direct edge.
370    pub fn add_edge(&mut self, from: usize, to: usize) {
371        if from < self.n {
372            self.adj[from].push(to);
373        }
374    }
375    /// Computes all nodes reachable from `start` (including `start`).
376    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
377        let mut visited = vec![false; self.n];
378        let mut queue = std::collections::VecDeque::new();
379        queue.push_back(start);
380        while let Some(node) = queue.pop_front() {
381            if node >= self.n || visited[node] {
382                continue;
383            }
384            visited[node] = true;
385            for &next in &self.adj[node] {
386                queue.push_back(next);
387            }
388        }
389        (0..self.n).filter(|&i| visited[i]).collect()
390    }
391    /// Returns `true` if `from` can transitively reach `to`.
392    pub fn can_reach(&self, from: usize, to: usize) -> bool {
393        self.reachable_from(from).contains(&to)
394    }
395}
396/// A label set for a graph node.
397#[allow(dead_code)]
398pub struct LabelSet {
399    labels: Vec<String>,
400}
401#[allow(dead_code)]
402impl LabelSet {
403    /// Creates a new empty label set.
404    pub fn new() -> Self {
405        Self { labels: Vec::new() }
406    }
407    /// Adds a label (deduplicates).
408    pub fn add(&mut self, label: impl Into<String>) {
409        let s = label.into();
410        if !self.labels.contains(&s) {
411            self.labels.push(s);
412        }
413    }
414    /// Returns `true` if `label` is present.
415    pub fn has(&self, label: &str) -> bool {
416        self.labels.iter().any(|l| l == label)
417    }
418    /// Returns the count of labels.
419    pub fn count(&self) -> usize {
420        self.labels.len()
421    }
422    /// Returns all labels.
423    pub fn all(&self) -> &[String] {
424        &self.labels
425    }
426}
427/// A cache mapping expression hashes to whether they are eta-normal.
428#[allow(dead_code)]
429pub struct EtaNormalCache {
430    cache: std::collections::HashMap<u64, bool>,
431}
432#[allow(dead_code)]
433impl EtaNormalCache {
434    /// Creates an empty cache.
435    pub fn new() -> Self {
436        Self {
437            cache: std::collections::HashMap::new(),
438        }
439    }
440    /// Records that `hash` maps to `is_normal`.
441    pub fn insert(&mut self, hash: u64, is_normal: bool) {
442        self.cache.insert(hash, is_normal);
443    }
444    /// Returns `Some(is_normal)` if cached, `None` otherwise.
445    pub fn query(&self, hash: u64) -> Option<bool> {
446        self.cache.get(&hash).copied()
447    }
448    /// Invalidates all cached entries.
449    pub fn clear(&mut self) {
450        self.cache.clear();
451    }
452    /// Returns the number of cached entries.
453    pub fn len(&self) -> usize {
454        self.cache.len()
455    }
456    /// Returns whether the collection is empty.
457    pub fn is_empty(&self) -> bool {
458        self.len() == 0
459    }
460}
461/// A pair of `StatSummary` values tracking before/after a transformation.
462#[allow(dead_code)]
463pub struct TransformStat {
464    before: StatSummary,
465    after: StatSummary,
466}
467#[allow(dead_code)]
468impl TransformStat {
469    /// Creates a new transform stat recorder.
470    pub fn new() -> Self {
471        Self {
472            before: StatSummary::new(),
473            after: StatSummary::new(),
474        }
475    }
476    /// Records a before value.
477    pub fn record_before(&mut self, v: f64) {
478        self.before.record(v);
479    }
480    /// Records an after value.
481    pub fn record_after(&mut self, v: f64) {
482        self.after.record(v);
483    }
484    /// Returns the mean reduction ratio (after/before).
485    pub fn mean_ratio(&self) -> Option<f64> {
486        let b = self.before.mean()?;
487        let a = self.after.mean()?;
488        if b.abs() < f64::EPSILON {
489            return None;
490        }
491        Some(a / b)
492    }
493}
494/// A token bucket rate limiter.
495#[allow(dead_code)]
496pub struct TokenBucket {
497    capacity: u64,
498    tokens: u64,
499    refill_per_ms: u64,
500    last_refill: std::time::Instant,
501}
502#[allow(dead_code)]
503impl TokenBucket {
504    /// Creates a new token bucket.
505    pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
506        Self {
507            capacity,
508            tokens: capacity,
509            refill_per_ms,
510            last_refill: std::time::Instant::now(),
511        }
512    }
513    /// Attempts to consume `n` tokens.  Returns `true` on success.
514    pub fn try_consume(&mut self, n: u64) -> bool {
515        self.refill();
516        if self.tokens >= n {
517            self.tokens -= n;
518            true
519        } else {
520            false
521        }
522    }
523    fn refill(&mut self) {
524        let now = std::time::Instant::now();
525        let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
526        if elapsed_ms > 0 {
527            let new_tokens = elapsed_ms * self.refill_per_ms;
528            self.tokens = (self.tokens + new_tokens).min(self.capacity);
529            self.last_refill = now;
530        }
531    }
532    /// Returns the number of currently available tokens.
533    pub fn available(&self) -> u64 {
534        self.tokens
535    }
536    /// Returns the bucket capacity.
537    pub fn capacity(&self) -> u64 {
538        self.capacity
539    }
540}
541/// A non-empty list (at least one element guaranteed).
542#[allow(dead_code)]
543pub struct NonEmptyVec<T> {
544    head: T,
545    tail: Vec<T>,
546}
547#[allow(dead_code)]
548impl<T> NonEmptyVec<T> {
549    /// Creates a non-empty vec with a single element.
550    pub fn singleton(val: T) -> Self {
551        Self {
552            head: val,
553            tail: Vec::new(),
554        }
555    }
556    /// Pushes an element.
557    pub fn push(&mut self, val: T) {
558        self.tail.push(val);
559    }
560    /// Returns a reference to the first element.
561    pub fn first(&self) -> &T {
562        &self.head
563    }
564    /// Returns a reference to the last element.
565    pub fn last(&self) -> &T {
566        self.tail.last().unwrap_or(&self.head)
567    }
568    /// Returns the number of elements.
569    pub fn len(&self) -> usize {
570        1 + self.tail.len()
571    }
572    /// Returns whether the collection is empty.
573    pub fn is_empty(&self) -> bool {
574        self.len() == 0
575    }
576    /// Returns all elements as a Vec.
577    pub fn to_vec(&self) -> Vec<&T> {
578        let mut v = vec![&self.head];
579        v.extend(self.tail.iter());
580        v
581    }
582}
583/// A type-erased function pointer with arity tracking.
584#[allow(dead_code)]
585pub struct RawFnPtr {
586    /// The raw function pointer (stored as usize for type erasure).
587    ptr: usize,
588    pub(super) arity: usize,
589    name: String,
590}
591#[allow(dead_code)]
592impl RawFnPtr {
593    /// Creates a new raw function pointer descriptor.
594    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
595        Self {
596            ptr,
597            arity,
598            name: name.into(),
599        }
600    }
601    /// Returns the arity.
602    pub fn arity(&self) -> usize {
603        self.arity
604    }
605    /// Returns the name.
606    pub fn name(&self) -> &str {
607        &self.name
608    }
609    /// Returns the raw pointer value.
610    pub fn raw(&self) -> usize {
611        self.ptr
612    }
613}
614/// A mutable reference stack for tracking the current "focus" in a tree traversal.
615#[allow(dead_code)]
616pub struct FocusStack<T> {
617    items: Vec<T>,
618}
619#[allow(dead_code)]
620impl<T> FocusStack<T> {
621    /// Creates an empty focus stack.
622    pub fn new() -> Self {
623        Self { items: Vec::new() }
624    }
625    /// Focuses on `item`.
626    pub fn focus(&mut self, item: T) {
627        self.items.push(item);
628    }
629    /// Blurs (pops) the current focus.
630    pub fn blur(&mut self) -> Option<T> {
631        self.items.pop()
632    }
633    /// Returns the current focus, or `None`.
634    pub fn current(&self) -> Option<&T> {
635        self.items.last()
636    }
637    /// Returns the focus depth.
638    pub fn depth(&self) -> usize {
639        self.items.len()
640    }
641    /// Returns `true` if there is no current focus.
642    pub fn is_empty(&self) -> bool {
643        self.items.is_empty()
644    }
645}
646/// Information about the eta-normal form of an expression.
647#[allow(dead_code)]
648#[derive(Debug, Clone)]
649pub struct EtaNormInfo {
650    /// The eta-normalized expression.
651    pub normalized: Expr,
652    /// Number of eta contractions performed.
653    pub contractions: usize,
654    /// Whether the expression was already in eta-normal form.
655    pub already_normal: bool,
656}
657impl EtaNormInfo {
658    /// Create an info record for an expression that was already normal.
659    pub fn already_normal(expr: Expr) -> Self {
660        Self {
661            normalized: expr,
662            contractions: 0,
663            already_normal: true,
664        }
665    }
666    /// Create an info record for an expression that required contractions.
667    pub fn contracted(expr: Expr, n: usize) -> Self {
668        Self {
669            normalized: expr,
670            contractions: n,
671            already_normal: false,
672        }
673    }
674}
675/// A simple directed acyclic graph.
676#[allow(dead_code)]
677pub struct SimpleDag {
678    /// `edges[i]` is the list of direct successors of node `i`.
679    edges: Vec<Vec<usize>>,
680}
681#[allow(dead_code)]
682impl SimpleDag {
683    /// Creates a DAG with `n` nodes and no edges.
684    pub fn new(n: usize) -> Self {
685        Self {
686            edges: vec![Vec::new(); n],
687        }
688    }
689    /// Adds an edge from `from` to `to`.
690    pub fn add_edge(&mut self, from: usize, to: usize) {
691        if from < self.edges.len() {
692            self.edges[from].push(to);
693        }
694    }
695    /// Returns the successors of `node`.
696    pub fn successors(&self, node: usize) -> &[usize] {
697        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
698    }
699    /// Returns `true` if `from` can reach `to` via DFS.
700    pub fn can_reach(&self, from: usize, to: usize) -> bool {
701        let mut visited = vec![false; self.edges.len()];
702        self.dfs(from, to, &mut visited)
703    }
704    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
705        if cur == target {
706            return true;
707        }
708        if cur >= visited.len() || visited[cur] {
709            return false;
710        }
711        visited[cur] = true;
712        for &next in self.successors(cur) {
713            if self.dfs(next, target, visited) {
714                return true;
715            }
716        }
717        false
718    }
719    /// Returns the topological order of nodes, or `None` if a cycle is detected.
720    pub fn topological_sort(&self) -> Option<Vec<usize>> {
721        let n = self.edges.len();
722        let mut in_degree = vec![0usize; n];
723        for succs in &self.edges {
724            for &s in succs {
725                if s < n {
726                    in_degree[s] += 1;
727                }
728            }
729        }
730        let mut queue: std::collections::VecDeque<usize> =
731            (0..n).filter(|&i| in_degree[i] == 0).collect();
732        let mut order = Vec::new();
733        while let Some(node) = queue.pop_front() {
734            order.push(node);
735            for &s in self.successors(node) {
736                if s < n {
737                    in_degree[s] -= 1;
738                    if in_degree[s] == 0 {
739                        queue.push_back(s);
740                    }
741                }
742            }
743        }
744        if order.len() == n {
745            Some(order)
746        } else {
747            None
748        }
749    }
750    /// Returns the number of nodes.
751    pub fn num_nodes(&self) -> usize {
752        self.edges.len()
753    }
754}
755/// A fixed-size sliding window that computes a running sum.
756#[allow(dead_code)]
757pub struct SlidingSum {
758    window: Vec<f64>,
759    capacity: usize,
760    pos: usize,
761    sum: f64,
762    count: usize,
763}
764#[allow(dead_code)]
765impl SlidingSum {
766    /// Creates a sliding sum with the given window size.
767    pub fn new(capacity: usize) -> Self {
768        Self {
769            window: vec![0.0; capacity],
770            capacity,
771            pos: 0,
772            sum: 0.0,
773            count: 0,
774        }
775    }
776    /// Adds a value to the window, removing the oldest if full.
777    pub fn push(&mut self, val: f64) {
778        let oldest = self.window[self.pos];
779        self.sum -= oldest;
780        self.sum += val;
781        self.window[self.pos] = val;
782        self.pos = (self.pos + 1) % self.capacity;
783        if self.count < self.capacity {
784            self.count += 1;
785        }
786    }
787    /// Returns the current window sum.
788    pub fn sum(&self) -> f64 {
789        self.sum
790    }
791    /// Returns the window mean, or `None` if empty.
792    pub fn mean(&self) -> Option<f64> {
793        if self.count == 0 {
794            None
795        } else {
796            Some(self.sum / self.count as f64)
797        }
798    }
799    /// Returns the current window size (number of valid elements).
800    pub fn count(&self) -> usize {
801        self.count
802    }
803}
804/// Statistics tracking eta-reduction performance.
805#[allow(dead_code)]
806#[allow(missing_docs)]
807pub struct EtaReductionStats {
808    /// Number of successful eta-reductions.
809    pub reductions: u64,
810    /// Number of expressions examined.
811    pub examined: u64,
812    /// Number of eta-expanded forms produced.
813    pub expansions: u64,
814    /// Maximum nesting depth encountered.
815    pub max_depth: usize,
816}
817#[allow(dead_code)]
818impl EtaReductionStats {
819    /// Creates zeroed statistics.
820    pub fn new() -> Self {
821        Self {
822            reductions: 0,
823            examined: 0,
824            expansions: 0,
825            max_depth: 0,
826        }
827    }
828    /// Returns the reduction ratio (reductions / examined).
829    pub fn ratio(&self) -> f64 {
830        if self.examined == 0 {
831            return 0.0;
832        }
833        self.reductions as f64 / self.examined as f64
834    }
835}
836/// A reusable scratch buffer for path computations.
837#[allow(dead_code)]
838pub struct PathBuf {
839    components: Vec<String>,
840}
841#[allow(dead_code)]
842impl PathBuf {
843    /// Creates a new empty path buffer.
844    pub fn new() -> Self {
845        Self {
846            components: Vec::new(),
847        }
848    }
849    /// Pushes a component.
850    pub fn push(&mut self, comp: impl Into<String>) {
851        self.components.push(comp.into());
852    }
853    /// Pops the last component.
854    pub fn pop(&mut self) {
855        self.components.pop();
856    }
857    /// Returns the current path as a `/`-separated string.
858    pub fn as_str(&self) -> String {
859        self.components.join("/")
860    }
861    /// Returns the depth of the path.
862    pub fn depth(&self) -> usize {
863        self.components.len()
864    }
865    /// Clears the path.
866    pub fn clear(&mut self) {
867        self.components.clear();
868    }
869}
870/// A priority queue for eta reduction jobs.
871#[allow(dead_code)]
872pub struct EtaJobQueue {
873    jobs: Vec<EtaJob>,
874}
875#[allow(dead_code)]
876impl EtaJobQueue {
877    /// Creates an empty job queue.
878    pub fn new() -> Self {
879        Self { jobs: Vec::new() }
880    }
881    /// Enqueues a job.
882    pub fn enqueue(&mut self, job: EtaJob) {
883        let pos = self.jobs.partition_point(|j| j.prio >= job.prio);
884        self.jobs.insert(pos, job);
885    }
886    /// Dequeues the highest-priority job.
887    pub fn dequeue(&mut self) -> Option<EtaJob> {
888        if self.jobs.is_empty() {
889            None
890        } else {
891            Some(self.jobs.remove(0))
892        }
893    }
894    /// Returns the number of pending jobs.
895    pub fn len(&self) -> usize {
896        self.jobs.len()
897    }
898    /// Returns `true` if empty.
899    pub fn is_empty(&self) -> bool {
900        self.jobs.is_empty()
901    }
902}
903/// A hierarchical configuration tree.
904#[allow(dead_code)]
905pub struct ConfigNode {
906    key: String,
907    value: Option<String>,
908    children: Vec<ConfigNode>,
909}
910#[allow(dead_code)]
911impl ConfigNode {
912    /// Creates a leaf config node with a value.
913    pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
914        Self {
915            key: key.into(),
916            value: Some(value.into()),
917            children: Vec::new(),
918        }
919    }
920    /// Creates a section node with children.
921    pub fn section(key: impl Into<String>) -> Self {
922        Self {
923            key: key.into(),
924            value: None,
925            children: Vec::new(),
926        }
927    }
928    /// Adds a child node.
929    pub fn add_child(&mut self, child: ConfigNode) {
930        self.children.push(child);
931    }
932    /// Returns the key.
933    pub fn key(&self) -> &str {
934        &self.key
935    }
936    /// Returns the value, or `None` for section nodes.
937    pub fn value(&self) -> Option<&str> {
938        self.value.as_deref()
939    }
940    /// Returns the number of children.
941    pub fn num_children(&self) -> usize {
942        self.children.len()
943    }
944    /// Looks up a dot-separated path.
945    pub fn lookup(&self, path: &str) -> Option<&str> {
946        let mut parts = path.splitn(2, '.');
947        let head = parts.next()?;
948        let tail = parts.next();
949        if head != self.key {
950            return None;
951        }
952        match tail {
953            None => self.value.as_deref(),
954            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
955        }
956    }
957    fn lookup_relative(&self, path: &str) -> Option<&str> {
958        let mut parts = path.splitn(2, '.');
959        let head = parts.next()?;
960        let tail = parts.next();
961        if head != self.key {
962            return None;
963        }
964        match tail {
965            None => self.value.as_deref(),
966            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
967        }
968    }
969}
970/// A log of eta-reduction outcomes with timestamps.
971#[allow(dead_code)]
972pub struct EtaLog {
973    entries: Vec<(std::time::Instant, EtaOutcome)>,
974}
975#[allow(dead_code)]
976impl EtaLog {
977    /// Creates an empty log.
978    pub fn new() -> Self {
979        Self {
980            entries: Vec::new(),
981        }
982    }
983    /// Records an outcome.
984    pub fn record(&mut self, outcome: EtaOutcome) {
985        self.entries.push((std::time::Instant::now(), outcome));
986    }
987    /// Returns the number of recorded outcomes.
988    pub fn len(&self) -> usize {
989        self.entries.len()
990    }
991    /// Returns whether the collection is empty.
992    pub fn is_empty(&self) -> bool {
993        self.len() == 0
994    }
995    /// Returns the count of a specific outcome.
996    pub fn count(&self, target: EtaOutcome) -> usize {
997        self.entries.iter().filter(|(_, o)| *o == target).count()
998    }
999    /// Returns the success rate (0.0–1.0).
1000    pub fn success_rate(&self) -> f64 {
1001        if self.entries.is_empty() {
1002            return 0.0;
1003        }
1004        let successes = self.entries.iter().filter(|(_, o)| o.is_success()).count();
1005        successes as f64 / self.entries.len() as f64
1006    }
1007}
1008/// A simple key-value store backed by a sorted Vec for small maps.
1009#[allow(dead_code)]
1010pub struct SmallMap<K: Ord + Clone, V: Clone> {
1011    entries: Vec<(K, V)>,
1012}
1013#[allow(dead_code)]
1014impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
1015    /// Creates a new empty small map.
1016    pub fn new() -> Self {
1017        Self {
1018            entries: Vec::new(),
1019        }
1020    }
1021    /// Inserts or replaces the value for `key`.
1022    pub fn insert(&mut self, key: K, val: V) {
1023        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
1024            Ok(i) => self.entries[i].1 = val,
1025            Err(i) => self.entries.insert(i, (key, val)),
1026        }
1027    }
1028    /// Returns the value for `key`, or `None`.
1029    pub fn get(&self, key: &K) -> Option<&V> {
1030        self.entries
1031            .binary_search_by_key(&key, |(k, _)| k)
1032            .ok()
1033            .map(|i| &self.entries[i].1)
1034    }
1035    /// Returns the number of entries.
1036    pub fn len(&self) -> usize {
1037        self.entries.len()
1038    }
1039    /// Returns `true` if empty.
1040    pub fn is_empty(&self) -> bool {
1041        self.entries.is_empty()
1042    }
1043    /// Returns all keys.
1044    pub fn keys(&self) -> Vec<&K> {
1045        self.entries.iter().map(|(k, _)| k).collect()
1046    }
1047    /// Returns all values.
1048    pub fn values(&self) -> Vec<&V> {
1049        self.entries.iter().map(|(_, v)| v).collect()
1050    }
1051}
1052/// A counter tracking min/max/sum for eta pass statistics.
1053#[allow(dead_code)]
1054pub struct EtaStatCounter {
1055    count: u64,
1056    sum: i64,
1057    min: i64,
1058    max: i64,
1059}
1060#[allow(dead_code)]
1061impl EtaStatCounter {
1062    /// Creates a new counter.
1063    pub fn new() -> Self {
1064        Self {
1065            count: 0,
1066            sum: 0,
1067            min: i64::MAX,
1068            max: i64::MIN,
1069        }
1070    }
1071    /// Records a value.
1072    pub fn record(&mut self, v: i64) {
1073        self.count += 1;
1074        self.sum += v;
1075        if v < self.min {
1076            self.min = v;
1077        }
1078        if v > self.max {
1079            self.max = v;
1080        }
1081    }
1082    /// Returns the mean, or `None` if empty.
1083    pub fn mean(&self) -> Option<f64> {
1084        if self.count == 0 {
1085            None
1086        } else {
1087            Some(self.sum as f64 / self.count as f64)
1088        }
1089    }
1090    /// Returns the count.
1091    pub fn count(&self) -> u64 {
1092        self.count
1093    }
1094}
1095/// A simple counter for tracking eta-related operations.
1096#[allow(dead_code)]
1097pub struct EtaOpCounter {
1098    counts: std::collections::HashMap<String, u64>,
1099}
1100#[allow(dead_code)]
1101impl EtaOpCounter {
1102    /// Creates a new counter.
1103    pub fn new() -> Self {
1104        Self {
1105            counts: std::collections::HashMap::new(),
1106        }
1107    }
1108    /// Increments the counter for `op`.
1109    pub fn inc(&mut self, op: &str) {
1110        *self.counts.entry(op.to_string()).or_insert(0) += 1;
1111    }
1112    /// Returns the count for `op`.
1113    pub fn get(&self, op: &str) -> u64 {
1114        self.counts.get(op).copied().unwrap_or(0)
1115    }
1116    /// Returns the total count.
1117    pub fn total(&self) -> u64 {
1118        self.counts.values().sum()
1119    }
1120}