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    /// Every earlier value, oldest first: version `n` is `earlier[n]`.
230    earlier: Vec<T>,
231    /// The latest value (version `earlier.len()`), declared last so it drops last.
232    current: T,
233}
234#[allow(dead_code)]
235impl<T: Clone> VersionedRecord<T> {
236    /// Creates a new record with an initial value.
237    pub fn new(initial: T) -> Self {
238        Self {
239            earlier: Vec::new(),
240            current: initial,
241        }
242    }
243    /// Updates the record with a new version.
244    pub fn update(&mut self, val: T) {
245        let previous = std::mem::replace(&mut self.current, val);
246        self.earlier.push(previous);
247    }
248    /// Returns the current (latest) value.
249    pub fn current(&self) -> &T {
250        &self.current
251    }
252    /// Returns the value at version `n` (0-indexed), or `None`.
253    pub fn at_version(&self, n: usize) -> Option<&T> {
254        match self.earlier.get(n) {
255            Some(value) => Some(value),
256            None if n == self.earlier.len() => Some(&self.current),
257            None => None,
258        }
259    }
260    /// Returns the version number of the current value.
261    pub fn version(&self) -> usize {
262        self.earlier.len()
263    }
264    /// Returns `true` if more than one version exists.
265    pub fn has_history(&self) -> bool {
266        !self.earlier.is_empty()
267    }
268}
269/// Represents a pending eta-reduction job.
270#[allow(dead_code)]
271#[allow(missing_docs)]
272pub struct EtaJob {
273    /// Unique job ID.
274    pub id: u64,
275    /// Expression hash to reduce.
276    pub hash: u64,
277    /// Priority (higher = process first).
278    pub prio: u32,
279}
280#[allow(dead_code)]
281impl EtaJob {
282    /// Creates a new eta reduction job.
283    pub fn new(id: u64, hash: u64, prio: u32) -> Self {
284        Self { id, hash, prio }
285    }
286}
287/// Represents a rewrite rule `lhs → rhs`.
288#[allow(dead_code)]
289#[allow(missing_docs)]
290pub struct EtaRewriteRule {
291    /// The left-hand side pattern.
292    pub lhs: String,
293    /// The right-hand side template.
294    pub rhs: String,
295}
296#[allow(dead_code)]
297impl EtaRewriteRule {
298    /// Creates a new rewrite rule.
299    pub fn new(lhs: impl Into<String>, rhs: impl Into<String>) -> Self {
300        Self {
301            lhs: lhs.into(),
302            rhs: rhs.into(),
303        }
304    }
305}
306/// Statistics about an expression's lambda structure.
307#[derive(Debug, Default, Clone)]
308pub struct LambdaStats {
309    /// Total number of lambda binders.
310    pub lambda_count: usize,
311    /// Total number of pi binders.
312    pub pi_count: usize,
313    /// Total number of application nodes.
314    pub app_count: usize,
315    /// Total number of let binders.
316    pub let_count: usize,
317    /// Maximum nesting depth encountered.
318    pub max_depth: usize,
319}
320impl LambdaStats {
321    /// Compute statistics for an expression.
322    pub fn compute(expr: &Expr) -> Self {
323        let mut stats = Self::default();
324        Self::walk(expr, &mut stats, 0);
325        stats
326    }
327    fn walk(expr: &Expr, stats: &mut Self, depth: usize) {
328        if depth > stats.max_depth {
329            stats.max_depth = depth;
330        }
331        match expr {
332            Expr::Lam(_, _, ty, body) => {
333                stats.lambda_count += 1;
334                Self::walk(ty, stats, depth + 1);
335                Self::walk(body, stats, depth + 1);
336            }
337            Expr::Pi(_, _, ty, body) => {
338                stats.pi_count += 1;
339                Self::walk(ty, stats, depth + 1);
340                Self::walk(body, stats, depth + 1);
341            }
342            Expr::App(f, a) => {
343                stats.app_count += 1;
344                Self::walk(f, stats, depth + 1);
345                Self::walk(a, stats, depth + 1);
346            }
347            Expr::Let(_, ty, val, body) => {
348                stats.let_count += 1;
349                Self::walk(ty, stats, depth + 1);
350                Self::walk(val, stats, depth + 1);
351                Self::walk(body, stats, depth + 1);
352            }
353            _ => {}
354        }
355    }
356    /// Total number of binders (lambda + pi + let).
357    pub fn total_binders(&self) -> usize {
358        self.lambda_count + self.pi_count + self.let_count
359    }
360}
361/// A dependency closure builder (transitive closure via BFS).
362#[allow(dead_code)]
363pub struct TransitiveClosure {
364    adj: Vec<Vec<usize>>,
365    n: usize,
366}
367#[allow(dead_code)]
368impl TransitiveClosure {
369    /// Creates a transitive closure builder for `n` nodes.
370    pub fn new(n: usize) -> Self {
371        Self {
372            adj: vec![Vec::new(); n],
373            n,
374        }
375    }
376    /// Adds a direct edge.
377    pub fn add_edge(&mut self, from: usize, to: usize) {
378        if from < self.n {
379            self.adj[from].push(to);
380        }
381    }
382    /// Computes all nodes reachable from `start` (including `start`).
383    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
384        let mut visited = vec![false; self.n];
385        let mut queue = std::collections::VecDeque::new();
386        queue.push_back(start);
387        while let Some(node) = queue.pop_front() {
388            if node >= self.n || visited[node] {
389                continue;
390            }
391            visited[node] = true;
392            for &next in &self.adj[node] {
393                queue.push_back(next);
394            }
395        }
396        (0..self.n).filter(|&i| visited[i]).collect()
397    }
398    /// Returns `true` if `from` can transitively reach `to`.
399    pub fn can_reach(&self, from: usize, to: usize) -> bool {
400        self.reachable_from(from).contains(&to)
401    }
402}
403/// A label set for a graph node.
404#[allow(dead_code)]
405pub struct LabelSet {
406    labels: Vec<String>,
407}
408#[allow(dead_code)]
409impl LabelSet {
410    /// Creates a new empty label set.
411    pub fn new() -> Self {
412        Self { labels: Vec::new() }
413    }
414    /// Adds a label (deduplicates).
415    pub fn add(&mut self, label: impl Into<String>) {
416        let s = label.into();
417        if !self.labels.contains(&s) {
418            self.labels.push(s);
419        }
420    }
421    /// Returns `true` if `label` is present.
422    pub fn has(&self, label: &str) -> bool {
423        self.labels.iter().any(|l| l == label)
424    }
425    /// Returns the count of labels.
426    pub fn count(&self) -> usize {
427        self.labels.len()
428    }
429    /// Returns all labels.
430    pub fn all(&self) -> &[String] {
431        &self.labels
432    }
433}
434/// A cache mapping expression hashes to whether they are eta-normal.
435#[allow(dead_code)]
436pub struct EtaNormalCache {
437    cache: std::collections::HashMap<u64, bool>,
438}
439#[allow(dead_code)]
440impl EtaNormalCache {
441    /// Creates an empty cache.
442    pub fn new() -> Self {
443        Self {
444            cache: std::collections::HashMap::new(),
445        }
446    }
447    /// Records that `hash` maps to `is_normal`.
448    pub fn insert(&mut self, hash: u64, is_normal: bool) {
449        self.cache.insert(hash, is_normal);
450    }
451    /// Returns `Some(is_normal)` if cached, `None` otherwise.
452    pub fn query(&self, hash: u64) -> Option<bool> {
453        self.cache.get(&hash).copied()
454    }
455    /// Invalidates all cached entries.
456    pub fn clear(&mut self) {
457        self.cache.clear();
458    }
459    /// Returns the number of cached entries.
460    pub fn len(&self) -> usize {
461        self.cache.len()
462    }
463    /// Returns whether the collection is empty.
464    pub fn is_empty(&self) -> bool {
465        self.len() == 0
466    }
467}
468/// A pair of `StatSummary` values tracking before/after a transformation.
469#[allow(dead_code)]
470pub struct TransformStat {
471    before: StatSummary,
472    after: StatSummary,
473}
474#[allow(dead_code)]
475impl TransformStat {
476    /// Creates a new transform stat recorder.
477    pub fn new() -> Self {
478        Self {
479            before: StatSummary::new(),
480            after: StatSummary::new(),
481        }
482    }
483    /// Records a before value.
484    pub fn record_before(&mut self, v: f64) {
485        self.before.record(v);
486    }
487    /// Records an after value.
488    pub fn record_after(&mut self, v: f64) {
489        self.after.record(v);
490    }
491    /// Returns the mean reduction ratio (after/before).
492    pub fn mean_ratio(&self) -> Option<f64> {
493        let b = self.before.mean()?;
494        let a = self.after.mean()?;
495        if b.abs() < f64::EPSILON {
496            return None;
497        }
498        Some(a / b)
499    }
500}
501/// A token bucket rate limiter.
502#[allow(dead_code)]
503pub struct TokenBucket {
504    capacity: u64,
505    tokens: u64,
506    refill_per_ms: u64,
507    last_refill: crate::wall_clock::Instant,
508}
509#[allow(dead_code)]
510impl TokenBucket {
511    /// Creates a new token bucket.
512    pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
513        Self {
514            capacity,
515            tokens: capacity,
516            refill_per_ms,
517            last_refill: crate::wall_clock::Instant::now(),
518        }
519    }
520    /// Attempts to consume `n` tokens.  Returns `true` on success.
521    pub fn try_consume(&mut self, n: u64) -> bool {
522        self.refill();
523        if self.tokens >= n {
524            self.tokens -= n;
525            true
526        } else {
527            false
528        }
529    }
530    fn refill(&mut self) {
531        let now = crate::wall_clock::Instant::now();
532        let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
533        if elapsed_ms > 0 {
534            let new_tokens = elapsed_ms * self.refill_per_ms;
535            self.tokens = (self.tokens + new_tokens).min(self.capacity);
536            self.last_refill = now;
537        }
538    }
539    /// Returns the number of currently available tokens.
540    pub fn available(&self) -> u64 {
541        self.tokens
542    }
543    /// Returns the bucket capacity.
544    pub fn capacity(&self) -> u64 {
545        self.capacity
546    }
547}
548/// A non-empty list (at least one element guaranteed).
549#[allow(dead_code)]
550pub struct NonEmptyVec<T> {
551    head: T,
552    tail: Vec<T>,
553}
554#[allow(dead_code)]
555impl<T> NonEmptyVec<T> {
556    /// Creates a non-empty vec with a single element.
557    pub fn singleton(val: T) -> Self {
558        Self {
559            head: val,
560            tail: Vec::new(),
561        }
562    }
563    /// Pushes an element.
564    pub fn push(&mut self, val: T) {
565        self.tail.push(val);
566    }
567    /// Returns a reference to the first element.
568    pub fn first(&self) -> &T {
569        &self.head
570    }
571    /// Returns a reference to the last element.
572    pub fn last(&self) -> &T {
573        self.tail.last().unwrap_or(&self.head)
574    }
575    /// Returns the number of elements.
576    pub fn len(&self) -> usize {
577        1 + self.tail.len()
578    }
579    /// Returns whether the collection is empty.
580    pub fn is_empty(&self) -> bool {
581        self.len() == 0
582    }
583    /// Returns all elements as a Vec.
584    pub fn to_vec(&self) -> Vec<&T> {
585        let mut v = vec![&self.head];
586        v.extend(self.tail.iter());
587        v
588    }
589}
590/// A type-erased function pointer with arity tracking.
591#[allow(dead_code)]
592pub struct RawFnPtr {
593    /// The raw function pointer (stored as usize for type erasure).
594    ptr: usize,
595    pub(super) arity: usize,
596    name: String,
597}
598#[allow(dead_code)]
599impl RawFnPtr {
600    /// Creates a new raw function pointer descriptor.
601    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
602        Self {
603            ptr,
604            arity,
605            name: name.into(),
606        }
607    }
608    /// Returns the arity.
609    pub fn arity(&self) -> usize {
610        self.arity
611    }
612    /// Returns the name.
613    pub fn name(&self) -> &str {
614        &self.name
615    }
616    /// Returns the raw pointer value.
617    pub fn raw(&self) -> usize {
618        self.ptr
619    }
620}
621/// A mutable reference stack for tracking the current "focus" in a tree traversal.
622#[allow(dead_code)]
623pub struct FocusStack<T> {
624    items: Vec<T>,
625}
626#[allow(dead_code)]
627impl<T> FocusStack<T> {
628    /// Creates an empty focus stack.
629    pub fn new() -> Self {
630        Self { items: Vec::new() }
631    }
632    /// Focuses on `item`.
633    pub fn focus(&mut self, item: T) {
634        self.items.push(item);
635    }
636    /// Blurs (pops) the current focus.
637    pub fn blur(&mut self) -> Option<T> {
638        self.items.pop()
639    }
640    /// Returns the current focus, or `None`.
641    pub fn current(&self) -> Option<&T> {
642        self.items.last()
643    }
644    /// Returns the focus depth.
645    pub fn depth(&self) -> usize {
646        self.items.len()
647    }
648    /// Returns `true` if there is no current focus.
649    pub fn is_empty(&self) -> bool {
650        self.items.is_empty()
651    }
652}
653/// Information about the eta-normal form of an expression.
654#[allow(dead_code)]
655#[derive(Debug, Clone)]
656pub struct EtaNormInfo {
657    /// The eta-normalized expression.
658    pub normalized: Expr,
659    /// Number of eta contractions performed.
660    pub contractions: usize,
661    /// Whether the expression was already in eta-normal form.
662    pub already_normal: bool,
663}
664impl EtaNormInfo {
665    /// Create an info record for an expression that was already normal.
666    pub fn already_normal(expr: Expr) -> Self {
667        Self {
668            normalized: expr,
669            contractions: 0,
670            already_normal: true,
671        }
672    }
673    /// Create an info record for an expression that required contractions.
674    pub fn contracted(expr: Expr, n: usize) -> Self {
675        Self {
676            normalized: expr,
677            contractions: n,
678            already_normal: false,
679        }
680    }
681}
682/// A simple directed acyclic graph.
683#[allow(dead_code)]
684pub struct SimpleDag {
685    /// `edges[i]` is the list of direct successors of node `i`.
686    edges: Vec<Vec<usize>>,
687}
688#[allow(dead_code)]
689impl SimpleDag {
690    /// Creates a DAG with `n` nodes and no edges.
691    pub fn new(n: usize) -> Self {
692        Self {
693            edges: vec![Vec::new(); n],
694        }
695    }
696    /// Adds an edge from `from` to `to`.
697    pub fn add_edge(&mut self, from: usize, to: usize) {
698        if from < self.edges.len() {
699            self.edges[from].push(to);
700        }
701    }
702    /// Returns the successors of `node`.
703    pub fn successors(&self, node: usize) -> &[usize] {
704        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
705    }
706    /// Returns `true` if `from` can reach `to` via DFS.
707    pub fn can_reach(&self, from: usize, to: usize) -> bool {
708        let mut visited = vec![false; self.edges.len()];
709        self.dfs(from, to, &mut visited)
710    }
711    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
712        if cur == target {
713            return true;
714        }
715        if cur >= visited.len() || visited[cur] {
716            return false;
717        }
718        visited[cur] = true;
719        for &next in self.successors(cur) {
720            if self.dfs(next, target, visited) {
721                return true;
722            }
723        }
724        false
725    }
726    /// Returns the topological order of nodes, or `None` if a cycle is detected.
727    pub fn topological_sort(&self) -> Option<Vec<usize>> {
728        let n = self.edges.len();
729        let mut in_degree = vec![0usize; n];
730        for succs in &self.edges {
731            for &s in succs {
732                if s < n {
733                    in_degree[s] += 1;
734                }
735            }
736        }
737        let mut queue: std::collections::VecDeque<usize> =
738            (0..n).filter(|&i| in_degree[i] == 0).collect();
739        let mut order = Vec::new();
740        while let Some(node) = queue.pop_front() {
741            order.push(node);
742            for &s in self.successors(node) {
743                if s < n {
744                    in_degree[s] -= 1;
745                    if in_degree[s] == 0 {
746                        queue.push_back(s);
747                    }
748                }
749            }
750        }
751        if order.len() == n {
752            Some(order)
753        } else {
754            None
755        }
756    }
757    /// Returns the number of nodes.
758    pub fn num_nodes(&self) -> usize {
759        self.edges.len()
760    }
761}
762/// A fixed-size sliding window that computes a running sum.
763#[allow(dead_code)]
764pub struct SlidingSum {
765    window: Vec<f64>,
766    capacity: usize,
767    pos: usize,
768    sum: f64,
769    count: usize,
770}
771#[allow(dead_code)]
772impl SlidingSum {
773    /// Creates a sliding sum with the given window size.
774    pub fn new(capacity: usize) -> Self {
775        Self {
776            window: vec![0.0; capacity],
777            capacity,
778            pos: 0,
779            sum: 0.0,
780            count: 0,
781        }
782    }
783    /// Adds a value to the window, removing the oldest if full.
784    pub fn push(&mut self, val: f64) {
785        let oldest = self.window[self.pos];
786        self.sum -= oldest;
787        self.sum += val;
788        self.window[self.pos] = val;
789        self.pos = (self.pos + 1) % self.capacity;
790        if self.count < self.capacity {
791            self.count += 1;
792        }
793    }
794    /// Returns the current window sum.
795    pub fn sum(&self) -> f64 {
796        self.sum
797    }
798    /// Returns the window mean, or `None` if empty.
799    pub fn mean(&self) -> Option<f64> {
800        if self.count == 0 {
801            None
802        } else {
803            Some(self.sum / self.count as f64)
804        }
805    }
806    /// Returns the current window size (number of valid elements).
807    pub fn count(&self) -> usize {
808        self.count
809    }
810}
811/// Statistics tracking eta-reduction performance.
812#[allow(dead_code)]
813#[allow(missing_docs)]
814pub struct EtaReductionStats {
815    /// Number of successful eta-reductions.
816    pub reductions: u64,
817    /// Number of expressions examined.
818    pub examined: u64,
819    /// Number of eta-expanded forms produced.
820    pub expansions: u64,
821    /// Maximum nesting depth encountered.
822    pub max_depth: usize,
823}
824#[allow(dead_code)]
825impl EtaReductionStats {
826    /// Creates zeroed statistics.
827    pub fn new() -> Self {
828        Self {
829            reductions: 0,
830            examined: 0,
831            expansions: 0,
832            max_depth: 0,
833        }
834    }
835    /// Returns the reduction ratio (reductions / examined).
836    pub fn ratio(&self) -> f64 {
837        if self.examined == 0 {
838            return 0.0;
839        }
840        self.reductions as f64 / self.examined as f64
841    }
842}
843/// A reusable scratch buffer for path computations.
844#[allow(dead_code)]
845pub struct PathBuf {
846    components: Vec<String>,
847}
848#[allow(dead_code)]
849impl PathBuf {
850    /// Creates a new empty path buffer.
851    pub fn new() -> Self {
852        Self {
853            components: Vec::new(),
854        }
855    }
856    /// Pushes a component.
857    pub fn push(&mut self, comp: impl Into<String>) {
858        self.components.push(comp.into());
859    }
860    /// Pops the last component.
861    pub fn pop(&mut self) {
862        self.components.pop();
863    }
864    /// Returns the current path as a `/`-separated string.
865    pub fn as_str(&self) -> String {
866        self.components.join("/")
867    }
868    /// Returns the depth of the path.
869    pub fn depth(&self) -> usize {
870        self.components.len()
871    }
872    /// Clears the path.
873    pub fn clear(&mut self) {
874        self.components.clear();
875    }
876}
877/// A priority queue for eta reduction jobs.
878#[allow(dead_code)]
879pub struct EtaJobQueue {
880    jobs: Vec<EtaJob>,
881}
882#[allow(dead_code)]
883impl EtaJobQueue {
884    /// Creates an empty job queue.
885    pub fn new() -> Self {
886        Self { jobs: Vec::new() }
887    }
888    /// Enqueues a job.
889    pub fn enqueue(&mut self, job: EtaJob) {
890        let pos = self.jobs.partition_point(|j| j.prio >= job.prio);
891        self.jobs.insert(pos, job);
892    }
893    /// Dequeues the highest-priority job.
894    pub fn dequeue(&mut self) -> Option<EtaJob> {
895        if self.jobs.is_empty() {
896            None
897        } else {
898            Some(self.jobs.remove(0))
899        }
900    }
901    /// Returns the number of pending jobs.
902    pub fn len(&self) -> usize {
903        self.jobs.len()
904    }
905    /// Returns `true` if empty.
906    pub fn is_empty(&self) -> bool {
907        self.jobs.is_empty()
908    }
909}
910/// A hierarchical configuration tree.
911#[allow(dead_code)]
912pub struct ConfigNode {
913    key: String,
914    value: Option<String>,
915    children: Vec<ConfigNode>,
916}
917#[allow(dead_code)]
918impl ConfigNode {
919    /// Creates a leaf config node with a value.
920    pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
921        Self {
922            key: key.into(),
923            value: Some(value.into()),
924            children: Vec::new(),
925        }
926    }
927    /// Creates a section node with children.
928    pub fn section(key: impl Into<String>) -> Self {
929        Self {
930            key: key.into(),
931            value: None,
932            children: Vec::new(),
933        }
934    }
935    /// Adds a child node.
936    pub fn add_child(&mut self, child: ConfigNode) {
937        self.children.push(child);
938    }
939    /// Returns the key.
940    pub fn key(&self) -> &str {
941        &self.key
942    }
943    /// Returns the value, or `None` for section nodes.
944    pub fn value(&self) -> Option<&str> {
945        self.value.as_deref()
946    }
947    /// Returns the number of children.
948    pub fn num_children(&self) -> usize {
949        self.children.len()
950    }
951    /// Looks up a dot-separated path.
952    pub fn lookup(&self, path: &str) -> Option<&str> {
953        let mut parts = path.splitn(2, '.');
954        let head = parts.next()?;
955        let tail = parts.next();
956        if head != self.key {
957            return None;
958        }
959        match tail {
960            None => self.value.as_deref(),
961            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
962        }
963    }
964    fn lookup_relative(&self, path: &str) -> Option<&str> {
965        let mut parts = path.splitn(2, '.');
966        let head = parts.next()?;
967        let tail = parts.next();
968        if head != self.key {
969            return None;
970        }
971        match tail {
972            None => self.value.as_deref(),
973            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
974        }
975    }
976}
977/// A log of eta-reduction outcomes with timestamps.
978#[allow(dead_code)]
979pub struct EtaLog {
980    entries: Vec<(crate::wall_clock::Instant, EtaOutcome)>,
981}
982#[allow(dead_code)]
983impl EtaLog {
984    /// Creates an empty log.
985    pub fn new() -> Self {
986        Self {
987            entries: Vec::new(),
988        }
989    }
990    /// Records an outcome.
991    pub fn record(&mut self, outcome: EtaOutcome) {
992        self.entries
993            .push((crate::wall_clock::Instant::now(), outcome));
994    }
995    /// Returns the number of recorded outcomes.
996    pub fn len(&self) -> usize {
997        self.entries.len()
998    }
999    /// Returns whether the collection is empty.
1000    pub fn is_empty(&self) -> bool {
1001        self.len() == 0
1002    }
1003    /// Returns the count of a specific outcome.
1004    pub fn count(&self, target: EtaOutcome) -> usize {
1005        self.entries.iter().filter(|(_, o)| *o == target).count()
1006    }
1007    /// Returns the success rate (0.0–1.0).
1008    pub fn success_rate(&self) -> f64 {
1009        if self.entries.is_empty() {
1010            return 0.0;
1011        }
1012        let successes = self.entries.iter().filter(|(_, o)| o.is_success()).count();
1013        successes as f64 / self.entries.len() as f64
1014    }
1015}
1016/// A simple key-value store backed by a sorted Vec for small maps.
1017#[allow(dead_code)]
1018pub struct SmallMap<K: Ord + Clone, V: Clone> {
1019    entries: Vec<(K, V)>,
1020}
1021#[allow(dead_code)]
1022impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
1023    /// Creates a new empty small map.
1024    pub fn new() -> Self {
1025        Self {
1026            entries: Vec::new(),
1027        }
1028    }
1029    /// Inserts or replaces the value for `key`.
1030    pub fn insert(&mut self, key: K, val: V) {
1031        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
1032            Ok(i) => self.entries[i].1 = val,
1033            Err(i) => self.entries.insert(i, (key, val)),
1034        }
1035    }
1036    /// Returns the value for `key`, or `None`.
1037    pub fn get(&self, key: &K) -> Option<&V> {
1038        self.entries
1039            .binary_search_by_key(&key, |(k, _)| k)
1040            .ok()
1041            .map(|i| &self.entries[i].1)
1042    }
1043    /// Returns the number of entries.
1044    pub fn len(&self) -> usize {
1045        self.entries.len()
1046    }
1047    /// Returns `true` if empty.
1048    pub fn is_empty(&self) -> bool {
1049        self.entries.is_empty()
1050    }
1051    /// Returns all keys.
1052    pub fn keys(&self) -> Vec<&K> {
1053        self.entries.iter().map(|(k, _)| k).collect()
1054    }
1055    /// Returns all values.
1056    pub fn values(&self) -> Vec<&V> {
1057        self.entries.iter().map(|(_, v)| v).collect()
1058    }
1059}
1060/// A counter tracking min/max/sum for eta pass statistics.
1061#[allow(dead_code)]
1062pub struct EtaStatCounter {
1063    count: u64,
1064    sum: i64,
1065    min: i64,
1066    max: i64,
1067}
1068#[allow(dead_code)]
1069impl EtaStatCounter {
1070    /// Creates a new counter.
1071    pub fn new() -> Self {
1072        Self {
1073            count: 0,
1074            sum: 0,
1075            min: i64::MAX,
1076            max: i64::MIN,
1077        }
1078    }
1079    /// Records a value.
1080    pub fn record(&mut self, v: i64) {
1081        self.count += 1;
1082        self.sum += v;
1083        if v < self.min {
1084            self.min = v;
1085        }
1086        if v > self.max {
1087            self.max = v;
1088        }
1089    }
1090    /// Returns the mean, or `None` if empty.
1091    pub fn mean(&self) -> Option<f64> {
1092        if self.count == 0 {
1093            None
1094        } else {
1095            Some(self.sum as f64 / self.count as f64)
1096        }
1097    }
1098    /// Returns the count.
1099    pub fn count(&self) -> u64 {
1100        self.count
1101    }
1102}
1103/// A simple counter for tracking eta-related operations.
1104#[allow(dead_code)]
1105pub struct EtaOpCounter {
1106    counts: std::collections::HashMap<String, u64>,
1107}
1108#[allow(dead_code)]
1109impl EtaOpCounter {
1110    /// Creates a new counter.
1111    pub fn new() -> Self {
1112        Self {
1113            counts: std::collections::HashMap::new(),
1114        }
1115    }
1116    /// Increments the counter for `op`.
1117    pub fn inc(&mut self, op: &str) {
1118        *self.counts.entry(op.to_string()).or_insert(0) += 1;
1119    }
1120    /// Returns the count for `op`.
1121    pub fn get(&self, op: &str) -> u64 {
1122        self.counts.get(op).copied().unwrap_or(0)
1123    }
1124    /// Returns the total count.
1125    pub fn total(&self) -> u64 {
1126        self.counts.values().sum()
1127    }
1128}