Skip to main content

oxilean_kernel/eta/
functions.rs

1//! Auto-generated module
2//!
3//! 馃 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::{BinderInfo, Expr, Name};
6
7use super::types::{
8    ConfigNode, EtaChecker, EtaJob, EtaJobQueue, EtaLog, EtaNormInfo, EtaNormalCache, EtaOpCounter,
9    EtaOutcome, EtaReductionStats, EtaStatCounter, EtaStructure, FocusStack, LabelSet, LambdaStats,
10    NonEmptyVec, RewriteRule, SimpleDag, SmallMap, StatSummary, TransformStat, VersionedRecord,
11    WindowIterator,
12};
13
14/// Perform eta expansion: given f, produce 位x. f x.
15pub fn eta_expand(expr: &Expr, arg_name: Name, arg_ty: Expr) -> Expr {
16    Expr::Lam(
17        BinderInfo::Default,
18        arg_name,
19        Box::new(arg_ty),
20        Box::new(Expr::App(Box::new(expr.clone()), Box::new(Expr::BVar(0)))),
21    )
22}
23/// Perform eta expansion with an implicit binder.
24pub fn eta_expand_implicit(expr: &Expr, arg_name: Name, arg_ty: Expr) -> Expr {
25    Expr::Lam(
26        BinderInfo::Implicit,
27        arg_name,
28        Box::new(arg_ty),
29        Box::new(Expr::App(Box::new(expr.clone()), Box::new(Expr::BVar(0)))),
30    )
31}
32/// Check whether an expression can be eta-expanded (not already a lambda).
33pub fn is_eta_expandable(expr: &Expr) -> bool {
34    !matches!(expr, Expr::Lam(..))
35}
36/// Perform one step of eta contraction.
37///
38/// Contracts (位x. f x) to f when x is not free in f.
39pub fn eta_contract(expr: &Expr) -> Option<Expr> {
40    if let Expr::Lam(_, _, _, body) = expr {
41        if let Expr::App(f, a) = &**body {
42            if let Expr::BVar(0) = **a {
43                if !contains_bvar(f, 0) {
44                    return Some(shift_down(f, 1));
45                }
46            }
47        }
48    }
49    None
50}
51/// Fully eta-contract an expression.
52pub fn eta_contract_full(expr: &Expr) -> Expr {
53    if let Some(c) = eta_contract(expr) {
54        return eta_contract_full(&c);
55    }
56    match expr {
57        Expr::App(func, arg) => Expr::App(
58            Box::new(eta_contract_full(func)),
59            Box::new(eta_contract_full(arg)),
60        ),
61        Expr::Lam(i, n, ty, body) => Expr::Lam(
62            *i,
63            n.clone(),
64            Box::new(eta_contract_full(ty)),
65            Box::new(eta_contract_full(body)),
66        ),
67        Expr::Pi(i, n, ty, body) => Expr::Pi(
68            *i,
69            n.clone(),
70            Box::new(eta_contract_full(ty)),
71            Box::new(eta_contract_full(body)),
72        ),
73        Expr::Let(n, ty, val, body) => Expr::Let(
74            n.clone(),
75            Box::new(eta_contract_full(ty)),
76            Box::new(eta_contract_full(val)),
77            Box::new(eta_contract_full(body)),
78        ),
79        _ => expr.clone(),
80    }
81}
82/// Check eta equivalence (normalize then compare).
83pub fn eta_equiv(e1: &Expr, e2: &Expr) -> bool {
84    eta_normalize(e1) == eta_normalize(e2)
85}
86/// Eta-normalize an expression (recursively eta-contracting).
87pub fn eta_normalize(expr: &Expr) -> Expr {
88    let inner = eta_normalize_inner(expr);
89    match eta_contract(&inner) {
90        Some(c) => eta_normalize(&c),
91        None => inner,
92    }
93}
94fn eta_normalize_inner(expr: &Expr) -> Expr {
95    match expr {
96        Expr::App(f, a) => Expr::App(Box::new(eta_normalize(f)), Box::new(eta_normalize(a))),
97        Expr::Lam(i, n, ty, body) => Expr::Lam(
98            *i,
99            n.clone(),
100            Box::new(eta_normalize(ty)),
101            Box::new(eta_normalize(body)),
102        ),
103        Expr::Pi(i, n, ty, body) => Expr::Pi(
104            *i,
105            n.clone(),
106            Box::new(eta_normalize(ty)),
107            Box::new(eta_normalize(body)),
108        ),
109        Expr::Let(n, ty, val, body) => Expr::Let(
110            n.clone(),
111            Box::new(eta_normalize(ty)),
112            Box::new(eta_normalize(val)),
113            Box::new(eta_normalize(body)),
114        ),
115        _ => expr.clone(),
116    }
117}
118/// Check if an expression contains a specific bound variable at the given De Bruijn index.
119pub fn contains_bvar(expr: &Expr, idx: u32) -> bool {
120    match expr {
121        Expr::BVar(i) => *i == idx,
122        Expr::App(f, a) => contains_bvar(f, idx) || contains_bvar(a, idx),
123        Expr::Lam(_, _, ty, body) => contains_bvar(ty, idx) || contains_bvar(body, idx + 1),
124        Expr::Pi(_, _, ty, body) => contains_bvar(ty, idx) || contains_bvar(body, idx + 1),
125        Expr::Let(_, ty, val, body) => {
126            contains_bvar(ty, idx) || contains_bvar(val, idx) || contains_bvar(body, idx + 1)
127        }
128        _ => false,
129    }
130}
131/// Count occurrences of a bound variable in an expression.
132pub fn count_bvar(expr: &Expr, idx: u32) -> usize {
133    match expr {
134        Expr::BVar(i) if *i == idx => 1,
135        Expr::BVar(_) => 0,
136        Expr::App(f, a) => count_bvar(f, idx) + count_bvar(a, idx),
137        Expr::Lam(_, _, ty, body) => count_bvar(ty, idx) + count_bvar(body, idx + 1),
138        Expr::Pi(_, _, ty, body) => count_bvar(ty, idx) + count_bvar(body, idx + 1),
139        Expr::Let(_, ty, val, body) => {
140            count_bvar(ty, idx) + count_bvar(val, idx) + count_bvar(body, idx + 1)
141        }
142        _ => 0,
143    }
144}
145/// Shift down bound variables (for removing a binder).
146pub fn shift_down(expr: &Expr, amount: u32) -> Expr {
147    shift_helper(expr, amount, 0)
148}
149/// Shift up bound variables (for adding a binder).
150pub fn shift_up(expr: &Expr, amount: u32) -> Expr {
151    shift_up_helper(expr, amount, 0)
152}
153fn shift_helper(expr: &Expr, amount: u32, cutoff: u32) -> Expr {
154    match expr {
155        Expr::BVar(i) => {
156            if *i >= cutoff {
157                Expr::BVar(i.saturating_sub(amount))
158            } else {
159                Expr::BVar(*i)
160            }
161        }
162        Expr::App(f, a) => Expr::App(
163            Box::new(shift_helper(f, amount, cutoff)),
164            Box::new(shift_helper(a, amount, cutoff)),
165        ),
166        Expr::Lam(i, n, ty, body) => Expr::Lam(
167            *i,
168            n.clone(),
169            Box::new(shift_helper(ty, amount, cutoff)),
170            Box::new(shift_helper(body, amount, cutoff + 1)),
171        ),
172        Expr::Pi(i, n, ty, body) => Expr::Pi(
173            *i,
174            n.clone(),
175            Box::new(shift_helper(ty, amount, cutoff)),
176            Box::new(shift_helper(body, amount, cutoff + 1)),
177        ),
178        Expr::Let(n, ty, val, body) => Expr::Let(
179            n.clone(),
180            Box::new(shift_helper(ty, amount, cutoff)),
181            Box::new(shift_helper(val, amount, cutoff)),
182            Box::new(shift_helper(body, amount, cutoff + 1)),
183        ),
184        _ => expr.clone(),
185    }
186}
187fn shift_up_helper(expr: &Expr, amount: u32, cutoff: u32) -> Expr {
188    match expr {
189        Expr::BVar(i) => {
190            if *i >= cutoff {
191                Expr::BVar(*i + amount)
192            } else {
193                Expr::BVar(*i)
194            }
195        }
196        Expr::App(f, a) => Expr::App(
197            Box::new(shift_up_helper(f, amount, cutoff)),
198            Box::new(shift_up_helper(a, amount, cutoff)),
199        ),
200        Expr::Lam(i, n, ty, body) => Expr::Lam(
201            *i,
202            n.clone(),
203            Box::new(shift_up_helper(ty, amount, cutoff)),
204            Box::new(shift_up_helper(body, amount, cutoff + 1)),
205        ),
206        Expr::Pi(i, n, ty, body) => Expr::Pi(
207            *i,
208            n.clone(),
209            Box::new(shift_up_helper(ty, amount, cutoff)),
210            Box::new(shift_up_helper(body, amount, cutoff + 1)),
211        ),
212        Expr::Let(n, ty, val, body) => Expr::Let(
213            n.clone(),
214            Box::new(shift_up_helper(ty, amount, cutoff)),
215            Box::new(shift_up_helper(val, amount, cutoff)),
216            Box::new(shift_up_helper(body, amount, cutoff + 1)),
217        ),
218        _ => expr.clone(),
219    }
220}
221/// Eta-expand a term with an explicit Pi binder.
222pub fn eta_expand_pi(expr: &Expr, binder_info: BinderInfo, arg_name: Name, arg_ty: Expr) -> Expr {
223    Expr::Lam(
224        binder_info,
225        arg_name,
226        Box::new(arg_ty),
227        Box::new(Expr::App(
228            Box::new(shift_up(expr, 1)),
229            Box::new(Expr::BVar(0)),
230        )),
231    )
232}
233/// Check whether an expression is in eta-long normal form.
234pub fn is_eta_long(expr: &Expr) -> bool {
235    match expr {
236        Expr::Lam(_, _, _, body) => is_eta_long(body),
237        Expr::App(f, a) => is_eta_long(f) && is_eta_long(a),
238        Expr::Pi(_, _, ty, body) => is_eta_long(ty) && is_eta_long(body),
239        Expr::Let(_, ty, val, body) => is_eta_long(ty) && is_eta_long(val) && is_eta_long(body),
240        _ => true,
241    }
242}
243/// Count the number of leading lambda binders.
244pub fn binder_depth(expr: &Expr) -> usize {
245    match expr {
246        Expr::Lam(_, _, _, body) => 1 + binder_depth(body),
247        _ => 0,
248    }
249}
250/// Peel off all leading lambdas, returning binders and body.
251pub fn peel_lambdas(expr: &Expr) -> (Vec<(BinderInfo, Name, Expr)>, &Expr) {
252    let mut binders = Vec::new();
253    let mut current = expr;
254    while let Expr::Lam(info, name, ty, body) = current {
255        binders.push((*info, name.clone(), *ty.clone()));
256        current = body;
257    }
258    (binders, current)
259}
260/// Substitute replacement for BVar(depth) in expr.
261pub fn subst_bvar(expr: &Expr, depth: u32, replacement: &Expr) -> Expr {
262    match expr {
263        Expr::BVar(i) => {
264            if *i == depth {
265                replacement.clone()
266            } else if *i > depth {
267                Expr::BVar(*i - 1)
268            } else {
269                Expr::BVar(*i)
270            }
271        }
272        Expr::App(f, a) => Expr::App(
273            Box::new(subst_bvar(f, depth, replacement)),
274            Box::new(subst_bvar(a, depth, replacement)),
275        ),
276        Expr::Lam(i, n, ty, body) => Expr::Lam(
277            *i,
278            n.clone(),
279            Box::new(subst_bvar(ty, depth, replacement)),
280            Box::new(subst_bvar(body, depth + 1, replacement)),
281        ),
282        Expr::Pi(i, n, ty, body) => Expr::Pi(
283            *i,
284            n.clone(),
285            Box::new(subst_bvar(ty, depth, replacement)),
286            Box::new(subst_bvar(body, depth + 1, replacement)),
287        ),
288        Expr::Let(n, ty, val, body) => Expr::Let(
289            n.clone(),
290            Box::new(subst_bvar(ty, depth, replacement)),
291            Box::new(subst_bvar(val, depth, replacement)),
292            Box::new(subst_bvar(body, depth + 1, replacement)),
293        ),
294        _ => expr.clone(),
295    }
296}
297/// Check if a lambda body is the identity (lambda x. x).
298pub fn is_identity_lambda(expr: &Expr) -> bool {
299    if let Expr::Lam(_, _, _, body) = expr {
300        matches!(body.as_ref(), Expr::BVar(0))
301    } else {
302        false
303    }
304}
305/// Count the nesting depth of applications.
306pub fn app_depth(expr: &Expr) -> usize {
307    match expr {
308        Expr::App(f, _) => 1 + app_depth(f),
309        _ => 0,
310    }
311}
312/// Collect the spine of an application: (head, [arg1, arg2, ...]).
313pub fn collect_app_spine(expr: &Expr) -> (&Expr, Vec<&Expr>) {
314    let mut args = Vec::new();
315    let mut current = expr;
316    while let Expr::App(f, a) = current {
317        args.push(a.as_ref());
318        current = f;
319    }
320    args.reverse();
321    (current, args)
322}
323#[cfg(test)]
324mod tests {
325    use super::*;
326    use crate::Level;
327    fn sort() -> Expr {
328        Expr::Sort(Level::zero())
329    }
330    fn mk_const(s: &str) -> Expr {
331        Expr::Const(Name::str(s), vec![])
332    }
333    fn mk_lam(name: &str, ty: Expr, body: Expr) -> Expr {
334        Expr::Lam(
335            BinderInfo::Default,
336            Name::str(name),
337            Box::new(ty),
338            Box::new(body),
339        )
340    }
341    fn mk_app(f: Expr, a: Expr) -> Expr {
342        Expr::App(Box::new(f), Box::new(a))
343    }
344    #[test]
345    fn test_eta_expand() {
346        let f = mk_const("f");
347        let expanded = eta_expand(&f, Name::str("x"), sort());
348        if let Expr::Lam(_, name, _, body) = expanded {
349            assert_eq!(name, Name::str("x"));
350            if let Expr::App(fn_expr, arg_expr) = *body {
351                assert_eq!(*fn_expr, f);
352                assert_eq!(*arg_expr, Expr::BVar(0));
353            } else {
354                panic!("Expected App in body");
355            }
356        } else {
357            panic!("Expected Lam");
358        }
359    }
360    #[test]
361    fn test_is_eta_expandable() {
362        assert!(is_eta_expandable(&mk_const("f")));
363        assert!(!is_eta_expandable(&mk_lam("x", sort(), Expr::BVar(0))));
364    }
365    #[test]
366    fn test_eta_contract() {
367        let f = mk_const("f");
368        let lam = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
369        let contracted = eta_contract(&lam);
370        assert!(contracted.is_some());
371        assert_eq!(contracted.expect("contracted should be valid"), f);
372    }
373    #[test]
374    fn test_eta_contract_none_identity() {
375        let lam = mk_lam("x", sort(), Expr::BVar(0));
376        assert!(eta_contract(&lam).is_none());
377    }
378    #[test]
379    fn test_eta_contract_full_recurses_into_app() {
380        let f = mk_const("f");
381        let g = mk_const("g");
382        let inner_lam = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
383        let expr = mk_app(inner_lam, g.clone());
384        let contracted = eta_contract_full(&expr);
385        assert_eq!(contracted, mk_app(f, g));
386    }
387    #[test]
388    fn test_eta_contract_full_recurses_into_lam_body() {
389        let f = mk_const("f");
390        let inner = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
391        let outer = mk_lam("y", sort(), inner);
392        let contracted = eta_contract_full(&outer);
393        assert_eq!(contracted, mk_lam("y", sort(), f));
394    }
395    #[test]
396    fn test_contains_bvar() {
397        let expr = mk_app(Expr::BVar(2), Expr::BVar(0));
398        assert!(contains_bvar(&expr, 2));
399        assert!(contains_bvar(&expr, 0));
400        assert!(!contains_bvar(&expr, 1));
401    }
402    #[test]
403    fn test_count_bvar() {
404        let expr = mk_app(Expr::BVar(0), Expr::BVar(0));
405        assert_eq!(count_bvar(&expr, 0), 2);
406        assert_eq!(count_bvar(&expr, 1), 0);
407    }
408    #[test]
409    fn test_shift_down() {
410        assert_eq!(shift_down(&Expr::BVar(3), 1), Expr::BVar(2));
411    }
412    #[test]
413    fn test_shift_up() {
414        assert_eq!(shift_up(&Expr::BVar(1), 2), Expr::BVar(3));
415    }
416    #[test]
417    fn test_binder_depth() {
418        let lam = mk_lam("x", sort(), mk_lam("y", sort(), Expr::BVar(0)));
419        assert_eq!(binder_depth(&lam), 2);
420        assert_eq!(binder_depth(&mk_const("f")), 0);
421    }
422    #[test]
423    fn test_peel_lambdas() {
424        let inner = mk_const("body");
425        let lam = mk_lam("x", sort(), mk_lam("y", sort(), inner.clone()));
426        let (binders, body) = peel_lambdas(&lam);
427        assert_eq!(binders.len(), 2);
428        assert_eq!(*body, inner);
429    }
430    #[test]
431    fn test_eta_normalize_idempotent() {
432        let f = mk_const("f");
433        let lam = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
434        let normalized = eta_normalize(&lam);
435        assert_eq!(normalized, f);
436        assert_eq!(eta_normalize(&normalized), normalized);
437    }
438    #[test]
439    fn test_eta_expand_pi() {
440        let f = mk_const("f");
441        let expanded = eta_expand_pi(&f, BinderInfo::Default, Name::str("x"), sort());
442        assert!(matches!(expanded, Expr::Lam(BinderInfo::Default, _, _, _)));
443    }
444    #[test]
445    fn test_is_identity_lambda() {
446        assert!(is_identity_lambda(&mk_lam("x", sort(), Expr::BVar(0))));
447        assert!(!is_identity_lambda(&mk_lam("x", sort(), mk_const("y"))));
448    }
449    #[test]
450    fn test_collect_app_spine() {
451        let f = mk_const("f");
452        let a = mk_const("a");
453        let b = mk_const("b");
454        let expr = mk_app(mk_app(f.clone(), a.clone()), b.clone());
455        let (head, args) = collect_app_spine(&expr);
456        assert_eq!(*head, f);
457        assert_eq!(args.len(), 2);
458    }
459}
460/// Check whether two expressions are eta-equivalent up to the given depth.
461pub fn eta_equiv_depth(e1: &Expr, e2: &Expr, max_depth: u32) -> bool {
462    if max_depth == 0 {
463        return e1 == e2;
464    }
465    let n1 = eta_normalize(e1);
466    let n2 = eta_normalize(e2);
467    n1 == n2
468}
469/// Eta-expand an expression to match a target number of lambda binders.
470///
471/// If `expr` already has `n` or more binders, it is returned unchanged.
472pub fn eta_expand_to(expr: &Expr, n: usize) -> Expr {
473    let current = binder_depth(expr);
474    if current >= n {
475        return expr.clone();
476    }
477    let extra = n - current;
478    let mut result = expr.clone();
479    for i in 0..extra {
480        let arg_name = Name::str(format!("_x{}", i));
481        result = eta_expand_pi(&result, BinderInfo::Default, arg_name, Expr::BVar(0));
482    }
483    result
484}
485/// Apply a sequence of arguments to a head expression.
486pub fn mk_app_spine(head: &Expr, args: &[Expr]) -> Expr {
487    args.iter().fold(head.clone(), |acc, arg| {
488        Expr::App(Box::new(acc), Box::new(arg.clone()))
489    })
490}
491/// Rebuild a lambda from peeled binders and a body.
492pub fn rebuild_lam(binders: &[(BinderInfo, Name, Expr)], body: Expr) -> Expr {
493    binders.iter().rev().fold(body, |acc, (info, name, ty)| {
494        Expr::Lam(*info, name.clone(), Box::new(ty.clone()), Box::new(acc))
495    })
496}
497/// Rebuild a Pi from peeled binders and a body.
498pub fn rebuild_pi(binders: &[(BinderInfo, Name, Expr)], body: Expr) -> Expr {
499    binders.iter().rev().fold(body, |acc, (info, name, ty)| {
500        Expr::Pi(*info, name.clone(), Box::new(ty.clone()), Box::new(acc))
501    })
502}
503/// Collect the Pi binders of a type, returning (binders, return_type).
504pub fn peel_pis(expr: &Expr) -> (Vec<(BinderInfo, Name, Expr)>, &Expr) {
505    let mut binders = Vec::new();
506    let mut current = expr;
507    while let Expr::Pi(info, name, ty, body) = current {
508        binders.push((*info, name.clone(), *ty.clone()));
509        current = body;
510    }
511    (binders, current)
512}
513/// Count the number of leading Pi binders.
514pub fn pi_depth(expr: &Expr) -> usize {
515    match expr {
516        Expr::Pi(_, _, _, body) => 1 + pi_depth(body),
517        _ => 0,
518    }
519}
520/// Attempt to compute the arity of an expression by counting Pi binders.
521pub fn arity(expr: &Expr) -> usize {
522    pi_depth(expr)
523}
524/// Check whether an expression is a "simple" application (head is a constant).
525pub fn is_simple_app(expr: &Expr) -> bool {
526    let (head, _) = collect_app_spine(expr);
527    matches!(head, Expr::Const(..))
528}
529/// Check whether an expression is closed (no free BVars at depth >= `cutoff`).
530pub fn is_closed_at(expr: &Expr, cutoff: u32) -> bool {
531    !contains_bvar(expr, cutoff)
532}
533/// Check whether an expression is completely closed (no BVars and no FVars).
534pub fn is_closed(expr: &Expr) -> bool {
535    match expr {
536        Expr::BVar(_) => false,
537        Expr::FVar(_) => false,
538        Expr::App(f, a) => is_closed(f) && is_closed(a),
539        Expr::Lam(_, _, ty, body) => is_closed(ty) && is_closed(body),
540        Expr::Pi(_, _, ty, body) => is_closed(ty) && is_closed(body),
541        Expr::Let(_, ty, val, body) => is_closed(ty) && is_closed(val) && is_closed(body),
542        Expr::Proj(_, _, inner) => is_closed(inner),
543        _ => true,
544    }
545}
546/// Replace all lambda binders in `expr` with pi binders (unsafe structural change).
547pub fn lam_to_pi(expr: &Expr) -> Expr {
548    match expr {
549        Expr::Lam(info, name, ty, body) => Expr::Pi(
550            *info,
551            name.clone(),
552            Box::new(lam_to_pi(ty)),
553            Box::new(lam_to_pi(body)),
554        ),
555        Expr::App(f, a) => Expr::App(Box::new(lam_to_pi(f)), Box::new(lam_to_pi(a))),
556        _ => expr.clone(),
557    }
558}
559/// Substitute `replacement` for the outermost bound variable (BVar(0)).
560pub fn beta_reduce_one(lam: &Expr, arg: &Expr) -> Option<Expr> {
561    if let Expr::Lam(_, _, _, body) = lam {
562        Some(subst_bvar(body, 0, arg))
563    } else {
564        None
565    }
566}
567/// Apply beta reduction repeatedly until no redex exists at the top level.
568pub fn beta_reduce_full(expr: &Expr) -> Expr {
569    match expr {
570        Expr::App(f, a) => {
571            let f2 = beta_reduce_full(f);
572            let a2 = beta_reduce_full(a);
573            match beta_reduce_one(&f2, &a2) {
574                Some(reduced) => beta_reduce_full(&reduced),
575                None => Expr::App(Box::new(f2), Box::new(a2)),
576            }
577        }
578        Expr::Lam(i, n, ty, body) => Expr::Lam(
579            *i,
580            n.clone(),
581            Box::new(beta_reduce_full(ty)),
582            Box::new(beta_reduce_full(body)),
583        ),
584        Expr::Pi(i, n, ty, body) => Expr::Pi(
585            *i,
586            n.clone(),
587            Box::new(beta_reduce_full(ty)),
588            Box::new(beta_reduce_full(body)),
589        ),
590        Expr::Let(_n, _ty, val, body) => {
591            let val2 = beta_reduce_full(val);
592            let body2 = subst_bvar(body, 0, &val2);
593            beta_reduce_full(&body2)
594        }
595        _ => expr.clone(),
596    }
597}
598/// Normalize by both eta-contracting and beta-reducing.
599pub fn normalize_full(expr: &Expr) -> Expr {
600    let beta = beta_reduce_full(expr);
601    eta_normalize(&beta)
602}
603/// Lift a substitution under `n` binders (shift the replacement up by `n`).
604pub fn subst_bvar_shifted(expr: &Expr, depth: u32, replacement: &Expr, shift: u32) -> Expr {
605    let shifted = shift_up(replacement, shift);
606    subst_bvar(expr, depth, &shifted)
607}
608/// Check structural equality after full normalization.
609pub fn normal_eq(e1: &Expr, e2: &Expr) -> bool {
610    normalize_full(e1) == normalize_full(e2)
611}
612#[cfg(test)]
613mod eta_extended_tests {
614    use super::*;
615    use crate::Level;
616    fn sort() -> Expr {
617        Expr::Sort(Level::zero())
618    }
619    fn mk_const(s: &str) -> Expr {
620        Expr::Const(Name::str(s), vec![])
621    }
622    fn mk_lam(name: &str, ty: Expr, body: Expr) -> Expr {
623        Expr::Lam(
624            BinderInfo::Default,
625            Name::str(name),
626            Box::new(ty),
627            Box::new(body),
628        )
629    }
630    fn mk_app(f: Expr, a: Expr) -> Expr {
631        Expr::App(Box::new(f), Box::new(a))
632    }
633    fn mk_pi(name: &str, ty: Expr, body: Expr) -> Expr {
634        Expr::Pi(
635            BinderInfo::Default,
636            Name::str(name),
637            Box::new(ty),
638            Box::new(body),
639        )
640    }
641    #[test]
642    fn test_lambda_stats() {
643        let expr = mk_lam(
644            "x",
645            sort(),
646            mk_lam("y", sort(), mk_app(Expr::BVar(1), Expr::BVar(0))),
647        );
648        let stats = LambdaStats::compute(&expr);
649        assert_eq!(stats.lambda_count, 2);
650        assert_eq!(stats.app_count, 1);
651    }
652    #[test]
653    fn test_peel_pis() {
654        let body = mk_const("Nat");
655        let pi_expr = mk_pi("x", sort(), mk_pi("y", sort(), body.clone()));
656        let (binders, ret) = peel_pis(&pi_expr);
657        assert_eq!(binders.len(), 2);
658        assert_eq!(*ret, body);
659    }
660    #[test]
661    fn test_pi_depth() {
662        let expr = mk_pi("x", sort(), mk_pi("y", sort(), mk_const("Nat")));
663        assert_eq!(pi_depth(&expr), 2);
664        assert_eq!(pi_depth(&mk_const("Nat")), 0);
665    }
666    #[test]
667    fn test_rebuild_lam() {
668        let binders = vec![(BinderInfo::Default, Name::str("x"), sort())];
669        let body = Expr::BVar(0);
670        let result = rebuild_lam(&binders, body);
671        assert!(matches!(result, Expr::Lam(..)));
672    }
673    #[test]
674    fn test_rebuild_pi() {
675        let binders = vec![(BinderInfo::Default, Name::str("x"), sort())];
676        let body = Expr::BVar(0);
677        let result = rebuild_pi(&binders, body);
678        assert!(matches!(result, Expr::Pi(..)));
679    }
680    #[test]
681    fn test_mk_app_spine() {
682        let f = mk_const("f");
683        let args = vec![mk_const("a"), mk_const("b")];
684        let result = mk_app_spine(&f, &args);
685        let (head, spine) = collect_app_spine(&result);
686        assert_eq!(*head, f);
687        assert_eq!(spine.len(), 2);
688    }
689    #[test]
690    fn test_is_closed() {
691        assert!(is_closed(&mk_const("f")));
692        assert!(!is_closed(&Expr::BVar(0)));
693        assert!(!is_closed(&mk_lam("x", sort(), Expr::BVar(0))));
694        assert!(is_closed(&mk_lam("x", sort(), mk_const("a"))));
695    }
696    #[test]
697    fn test_beta_reduce_one() {
698        let lam = mk_lam("x", sort(), Expr::BVar(0));
699        let arg = mk_const("a");
700        let result = beta_reduce_one(&lam, &arg);
701        assert_eq!(result, Some(arg));
702    }
703    #[test]
704    fn test_beta_reduce_full() {
705        let lam = mk_lam("x", sort(), Expr::BVar(0));
706        let arg = mk_const("a");
707        let app = mk_app(lam, arg.clone());
708        assert_eq!(beta_reduce_full(&app), arg);
709    }
710    #[test]
711    fn test_normal_eq() {
712        let f = mk_const("f");
713        let lam = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
714        assert!(normal_eq(&lam, &f));
715    }
716    #[test]
717    fn test_is_simple_app() {
718        let expr = mk_app(mk_const("f"), mk_const("a"));
719        assert!(is_simple_app(&expr));
720        let lam_app = mk_app(mk_lam("x", sort(), Expr::BVar(0)), mk_const("a"));
721        assert!(!is_simple_app(&lam_app));
722    }
723    #[test]
724    fn test_lam_to_pi() {
725        let lam = mk_lam("x", sort(), Expr::BVar(0));
726        let pi = lam_to_pi(&lam);
727        assert!(matches!(pi, Expr::Pi(..)));
728    }
729    #[test]
730    fn test_arity() {
731        let ty = mk_pi("a", sort(), mk_pi("b", sort(), mk_const("Nat")));
732        assert_eq!(arity(&ty), 2);
733    }
734}
735/// Apply a list of rewrite rules to an expression, innermost-first.
736pub fn rewrite_with_rules(expr: &Expr, rules: &[RewriteRule]) -> Expr {
737    let inner = match expr {
738        Expr::App(f, a) => Expr::App(
739            Box::new(rewrite_with_rules(f, rules)),
740            Box::new(rewrite_with_rules(a, rules)),
741        ),
742        Expr::Lam(i, n, ty, body) => Expr::Lam(
743            *i,
744            n.clone(),
745            Box::new(rewrite_with_rules(ty, rules)),
746            Box::new(rewrite_with_rules(body, rules)),
747        ),
748        Expr::Pi(i, n, ty, body) => Expr::Pi(
749            *i,
750            n.clone(),
751            Box::new(rewrite_with_rules(ty, rules)),
752            Box::new(rewrite_with_rules(body, rules)),
753        ),
754        Expr::Let(n, ty, val, body) => Expr::Let(
755            n.clone(),
756            Box::new(rewrite_with_rules(ty, rules)),
757            Box::new(rewrite_with_rules(val, rules)),
758            Box::new(rewrite_with_rules(body, rules)),
759        ),
760        _ => expr.clone(),
761    };
762    for rule in rules {
763        if let Some(rewritten) = rule.apply_top(&inner) {
764            return rewritten;
765        }
766    }
767    inner
768}
769/// Check whether an expression is a beta-redex (lambda applied to an argument).
770pub fn is_beta_redex(expr: &Expr) -> bool {
771    matches!(expr, Expr::App(f, _) if matches!(f.as_ref(), Expr::Lam(..)))
772}
773/// Check whether an expression is an eta-redex (位x. f x with x not free in f).
774pub fn is_eta_redex(expr: &Expr) -> bool {
775    eta_contract(expr).is_some()
776}
777/// Count the total number of nodes in an expression tree.
778pub fn node_count(expr: &Expr) -> usize {
779    match expr {
780        Expr::App(f, a) => 1 + node_count(f) + node_count(a),
781        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
782            1 + node_count(ty) + node_count(body)
783        }
784        Expr::Let(_, ty, val, body) => 1 + node_count(ty) + node_count(val) + node_count(body),
785        _ => 1,
786    }
787}
788/// Check whether an expression has no lambdas anywhere in it.
789pub fn is_lambda_free(expr: &Expr) -> bool {
790    match expr {
791        Expr::Lam(..) => false,
792        Expr::App(f, a) => is_lambda_free(f) && is_lambda_free(a),
793        Expr::Pi(_, _, ty, body) => is_lambda_free(ty) && is_lambda_free(body),
794        Expr::Let(_, ty, val, body) => {
795            is_lambda_free(ty) && is_lambda_free(val) && is_lambda_free(body)
796        }
797        _ => true,
798    }
799}
800/// Compute a simple structural hash of an expression.
801pub fn structural_hash(expr: &Expr) -> u64 {
802    use std::collections::hash_map::DefaultHasher;
803    use std::hash::{Hash, Hasher};
804    let mut hasher = DefaultHasher::new();
805    format!("{:?}", expr).hash(&mut hasher);
806    hasher.finish()
807}
808/// Wrap an expression in `n` identity lambdas: `位_0. 位_1. ... expr`.
809pub fn wrap_in_lambdas(expr: &Expr, n: usize) -> Expr {
810    (0..n).fold(expr.clone(), |acc, i| {
811        Expr::Lam(
812            BinderInfo::Default,
813            Name::str(format!("_w{}", i)),
814            Box::new(Expr::Sort(crate::Level::zero())),
815            Box::new(shift_up(&acc, 1)),
816        )
817    })
818}
819/// Apply `n` BVar arguments to an expression: `expr BVar(0) BVar(1) ... BVar(n-1)`.
820pub fn apply_bvars(expr: &Expr, n: u32) -> Expr {
821    (0..n).fold(expr.clone(), |acc, i| {
822        Expr::App(Box::new(acc), Box::new(Expr::BVar(i)))
823    })
824}
825/// Reduce `let x := v in body` to `body[x := v]`.
826pub fn reduce_let(expr: &Expr) -> Option<Expr> {
827    if let Expr::Let(_, _, val, body) = expr {
828        Some(subst_bvar(body, 0, val))
829    } else {
830        None
831    }
832}
833/// Fully reduce all let bindings in an expression.
834pub fn reduce_lets(expr: &Expr) -> Expr {
835    match expr {
836        Expr::Let(_, _, val, body) => {
837            let val2 = reduce_lets(val);
838            let body2 = reduce_lets(body);
839            reduce_lets(&subst_bvar(&body2, 0, &val2))
840        }
841        Expr::App(f, a) => Expr::App(Box::new(reduce_lets(f)), Box::new(reduce_lets(a))),
842        Expr::Lam(i, n, ty, body) => Expr::Lam(
843            *i,
844            n.clone(),
845            Box::new(reduce_lets(ty)),
846            Box::new(reduce_lets(body)),
847        ),
848        Expr::Pi(i, n, ty, body) => Expr::Pi(
849            *i,
850            n.clone(),
851            Box::new(reduce_lets(ty)),
852            Box::new(reduce_lets(body)),
853        ),
854        _ => expr.clone(),
855    }
856}
857#[cfg(test)]
858mod eta_further_tests {
859    use super::*;
860    use crate::Level;
861    fn sort() -> Expr {
862        Expr::Sort(Level::zero())
863    }
864    fn mk_const(s: &str) -> Expr {
865        Expr::Const(Name::str(s), vec![])
866    }
867    fn mk_lam(name: &str, ty: Expr, body: Expr) -> Expr {
868        Expr::Lam(
869            BinderInfo::Default,
870            Name::str(name),
871            Box::new(ty),
872            Box::new(body),
873        )
874    }
875    fn mk_app(f: Expr, a: Expr) -> Expr {
876        Expr::App(Box::new(f), Box::new(a))
877    }
878    #[test]
879    fn test_is_beta_redex() {
880        let lam = mk_lam("x", sort(), Expr::BVar(0));
881        let app = mk_app(lam, mk_const("a"));
882        assert!(is_beta_redex(&app));
883        assert!(!is_beta_redex(&mk_const("f")));
884    }
885    #[test]
886    fn test_is_eta_redex() {
887        let f = mk_const("f");
888        let lam = mk_lam("x", sort(), mk_app(f.clone(), Expr::BVar(0)));
889        assert!(is_eta_redex(&lam));
890        assert!(!is_eta_redex(&f));
891    }
892    #[test]
893    fn test_node_count() {
894        let expr = mk_app(mk_const("f"), mk_const("a"));
895        assert_eq!(node_count(&expr), 3);
896        assert_eq!(node_count(&mk_const("f")), 1);
897    }
898    #[test]
899    fn test_is_lambda_free() {
900        assert!(is_lambda_free(&mk_const("f")));
901        assert!(!is_lambda_free(&mk_lam("x", sort(), Expr::BVar(0))));
902    }
903    #[test]
904    fn test_rewrite_rule_apply() {
905        let rule = RewriteRule::new("id", mk_const("x"), mk_const("y"));
906        assert_eq!(rule.apply_top(&mk_const("x")), Some(mk_const("y")));
907        assert_eq!(rule.apply_top(&mk_const("z")), None);
908    }
909    #[test]
910    fn test_rewrite_with_rules() {
911        let rules = vec![RewriteRule::new("r", mk_const("a"), mk_const("b"))];
912        let expr = mk_app(mk_const("f"), mk_const("a"));
913        let result = rewrite_with_rules(&expr, &rules);
914        assert_eq!(result, mk_app(mk_const("f"), mk_const("b")));
915    }
916    #[test]
917    fn test_reduce_let() {
918        let body = Expr::BVar(0);
919        let val = mk_const("v");
920        let let_expr = Expr::Let(
921            Name::str("x"),
922            Box::new(sort()),
923            Box::new(val.clone()),
924            Box::new(body),
925        );
926        assert_eq!(reduce_let(&let_expr), Some(val));
927    }
928    #[test]
929    fn test_wrap_in_lambdas() {
930        let body = mk_const("f");
931        let wrapped = wrap_in_lambdas(&body, 2);
932        assert_eq!(binder_depth(&wrapped), 2);
933    }
934    #[test]
935    fn test_apply_bvars() {
936        let f = mk_const("f");
937        let result = apply_bvars(&f, 2);
938        let (_, args) = collect_app_spine(&result);
939        assert_eq!(args.len(), 2);
940    }
941    #[test]
942    fn test_structural_hash_stable() {
943        let e = mk_const("foo");
944        assert_eq!(structural_hash(&e), structural_hash(&e));
945    }
946}
947/// Eta-normalize and track how many contractions were performed.
948#[allow(dead_code)]
949pub fn eta_normalize_tracked(expr: &Expr) -> EtaNormInfo {
950    let mut count = 0;
951    let mut current = expr.clone();
952    while let Some(c) = eta_contract(&current) {
953        count += 1;
954        current = c;
955    }
956    if count == 0 {
957        EtaNormInfo::already_normal(current)
958    } else {
959        EtaNormInfo::contracted(current, count)
960    }
961}
962/// Determine if two expressions are definitionally equal under eta.
963///
964/// Eta-normalizes both then checks structural equality.
965#[allow(dead_code)]
966pub fn eta_def_eq(e1: &Expr, e2: &Expr) -> bool {
967    eta_normalize(e1) == eta_normalize(e2)
968}
969/// Return the "eta-head" of an expression.
970///
971/// The eta-head is the result of stripping all outer lambda binders that
972/// form eta-redexes. For `位x. f x` this returns `f` (if x not free in f).
973#[allow(dead_code)]
974pub fn eta_head(expr: &Expr) -> Expr {
975    let contracted = eta_contract_full(expr);
976    if contracted == *expr {
977        expr.clone()
978    } else {
979        eta_head(&contracted)
980    }
981}
982/// Count the maximum lambda depth (outermost consecutive lambdas).
983#[allow(dead_code)]
984pub fn outer_lambda_depth(expr: &Expr) -> usize {
985    match expr {
986        Expr::Lam(_, _, _, body) => 1 + outer_lambda_depth(body),
987        _ => 0,
988    }
989}
990/// Expand an expression to exactly `n` lambda binders using eta expansion.
991///
992/// If the expression already has >= n outer lambdas, it is returned unchanged.
993#[allow(dead_code)]
994pub fn eta_expand_with_ty(expr: &Expr, n: usize, arg_ty: &Expr) -> Expr {
995    let current_depth = outer_lambda_depth(expr);
996    let mut result = expr.clone();
997    for _ in current_depth..n {
998        result = eta_expand(&result, Name::str("_x"), arg_ty.clone());
999    }
1000    result
1001}
1002/// Check if an expression has no free variables using a recursive walk.
1003///
1004/// This is an alias for [`is_closed`] provided for symmetry with `is_open`.
1005#[allow(dead_code)]
1006pub fn expr_is_closed(expr: &Expr) -> bool {
1007    is_closed(expr)
1008}
1009/// Produce a one-line human-readable description of an expression's structure.
1010#[allow(dead_code)]
1011pub fn expr_structure_desc(expr: &Expr) -> &'static str {
1012    match expr {
1013        Expr::BVar(_) => "BVar",
1014        Expr::FVar(_) => "FVar",
1015        Expr::Const(_, _) => "Const",
1016        Expr::Sort(_) => "Sort",
1017        Expr::Lam(_, _, _, _) => "Lam",
1018        Expr::Pi(_, _, _, _) => "Pi",
1019        Expr::App(_, _) => "App",
1020        Expr::Let(_, _, _, _) => "Let",
1021        Expr::Lit(_) => "Lit",
1022        Expr::Proj(_, _, _) => "Proj",
1023    }
1024}
1025#[cfg(test)]
1026mod extra_eta_tests {
1027    use super::*;
1028    fn mk_sort_expr() -> Expr {
1029        Expr::Sort(crate::Level::zero())
1030    }
1031    fn mk_const_e(s: &str) -> Expr {
1032        Expr::Const(Name::str(s), vec![])
1033    }
1034    fn mk_app_e(f: Expr, a: Expr) -> Expr {
1035        Expr::App(Box::new(f), Box::new(a))
1036    }
1037    fn mk_lam_e(body: Expr) -> Expr {
1038        Expr::Lam(
1039            crate::BinderInfo::Default,
1040            Name::str("x"),
1041            Box::new(mk_sort_expr()),
1042            Box::new(body),
1043        )
1044    }
1045    #[test]
1046    fn test_eta_normalize_tracked_already_normal() {
1047        let e = mk_const_e("f");
1048        let info = eta_normalize_tracked(&e);
1049        assert!(info.already_normal);
1050        assert_eq!(info.contractions, 0);
1051    }
1052    #[test]
1053    fn test_eta_normalize_tracked_contracts_one() {
1054        let f = mk_const_e("f");
1055        let e = mk_lam_e(mk_app_e(f.clone(), Expr::BVar(0)));
1056        let info = eta_normalize_tracked(&e);
1057        assert!(!info.already_normal);
1058        assert_eq!(info.contractions, 1);
1059        assert_eq!(info.normalized, f);
1060    }
1061    #[test]
1062    fn test_eta_def_eq_same() {
1063        let e = mk_const_e("foo");
1064        assert!(eta_def_eq(&e, &e));
1065    }
1066    #[test]
1067    fn test_eta_def_eq_eta_variants() {
1068        let f = mk_const_e("f");
1069        let e = mk_lam_e(mk_app_e(f.clone(), Expr::BVar(0)));
1070        assert!(eta_def_eq(&e, &f));
1071    }
1072    #[test]
1073    fn test_outer_lambda_depth_zero() {
1074        assert_eq!(outer_lambda_depth(&mk_const_e("x")), 0);
1075    }
1076    #[test]
1077    fn test_outer_lambda_depth_one() {
1078        let e = mk_lam_e(Expr::BVar(0));
1079        assert_eq!(outer_lambda_depth(&e), 1);
1080    }
1081    #[test]
1082    fn test_outer_lambda_depth_two() {
1083        let e = mk_lam_e(mk_lam_e(Expr::BVar(0)));
1084        assert_eq!(outer_lambda_depth(&e), 2);
1085    }
1086    #[test]
1087    fn test_eta_expand_to_already_deep_enough() {
1088        let e = mk_lam_e(Expr::BVar(0));
1089        let expanded = eta_expand_with_ty(&e, 1, &mk_sort_expr());
1090        assert_eq!(outer_lambda_depth(&expanded), 1);
1091    }
1092    #[test]
1093    fn test_is_closed_const() {
1094        assert!(expr_is_closed(&mk_const_e("Nat")));
1095    }
1096    #[test]
1097    fn test_is_closed_fvar() {
1098        let e = Expr::FVar(crate::FVarId::new(1));
1099        assert!(!expr_is_closed(&e));
1100    }
1101    #[test]
1102    fn test_is_closed_app_with_fvar() {
1103        let f = mk_const_e("f");
1104        let a = Expr::FVar(crate::FVarId::new(99));
1105        let e = mk_app_e(f, a);
1106        assert!(!expr_is_closed(&e));
1107    }
1108    #[test]
1109    fn test_is_closed_nested() {
1110        let e = mk_app_e(mk_const_e("f"), mk_const_e("a"));
1111        assert!(expr_is_closed(&e));
1112    }
1113    #[test]
1114    fn test_expr_structure_desc() {
1115        assert_eq!(expr_structure_desc(&mk_const_e("x")), "Const");
1116        assert_eq!(expr_structure_desc(&mk_lam_e(Expr::BVar(0))), "Lam");
1117        assert_eq!(expr_structure_desc(&Expr::BVar(0)), "BVar");
1118    }
1119    #[test]
1120    fn test_eta_head_already_contracted() {
1121        let f = mk_const_e("f");
1122        assert_eq!(eta_head(&f), f);
1123    }
1124    #[test]
1125    fn test_eta_info_contracted_fields() {
1126        let info = EtaNormInfo::contracted(mk_const_e("x"), 3);
1127        assert!(!info.already_normal);
1128        assert_eq!(info.contractions, 3);
1129    }
1130}
1131#[cfg(test)]
1132mod tests_padding_infra {
1133    use super::*;
1134    #[test]
1135    fn test_stat_summary() {
1136        let mut ss = StatSummary::new();
1137        ss.record(10.0);
1138        ss.record(20.0);
1139        ss.record(30.0);
1140        assert_eq!(ss.count(), 3);
1141        assert!((ss.mean().expect("mean should succeed") - 20.0).abs() < 1e-9);
1142        assert_eq!(ss.min().expect("min should succeed") as i64, 10);
1143        assert_eq!(ss.max().expect("max should succeed") as i64, 30);
1144    }
1145    #[test]
1146    fn test_transform_stat() {
1147        let mut ts = TransformStat::new();
1148        ts.record_before(100.0);
1149        ts.record_after(80.0);
1150        let ratio = ts.mean_ratio().expect("ratio should be present");
1151        assert!((ratio - 0.8).abs() < 1e-9);
1152    }
1153    #[test]
1154    fn test_small_map() {
1155        let mut m: SmallMap<u32, &str> = SmallMap::new();
1156        m.insert(3, "three");
1157        m.insert(1, "one");
1158        m.insert(2, "two");
1159        assert_eq!(m.get(&2), Some(&"two"));
1160        assert_eq!(m.len(), 3);
1161        let keys = m.keys();
1162        assert_eq!(*keys[0], 1);
1163        assert_eq!(*keys[2], 3);
1164    }
1165    #[test]
1166    fn test_label_set() {
1167        let mut ls = LabelSet::new();
1168        ls.add("foo");
1169        ls.add("bar");
1170        ls.add("foo");
1171        assert_eq!(ls.count(), 2);
1172        assert!(ls.has("bar"));
1173        assert!(!ls.has("baz"));
1174    }
1175    #[test]
1176    fn test_config_node() {
1177        let mut root = ConfigNode::section("root");
1178        let child = ConfigNode::leaf("key", "value");
1179        root.add_child(child);
1180        assert_eq!(root.num_children(), 1);
1181    }
1182    #[test]
1183    fn test_versioned_record() {
1184        let mut vr = VersionedRecord::new(0u32);
1185        vr.update(1);
1186        vr.update(2);
1187        assert_eq!(*vr.current(), 2);
1188        assert_eq!(vr.version(), 2);
1189        assert!(vr.has_history());
1190        assert_eq!(*vr.at_version(0).expect("value should be present"), 0);
1191    }
1192    #[test]
1193    fn test_simple_dag() {
1194        let mut dag = SimpleDag::new(4);
1195        dag.add_edge(0, 1);
1196        dag.add_edge(1, 2);
1197        dag.add_edge(2, 3);
1198        assert!(dag.can_reach(0, 3));
1199        assert!(!dag.can_reach(3, 0));
1200        let order = dag.topological_sort().expect("order should be present");
1201        assert_eq!(order, vec![0, 1, 2, 3]);
1202    }
1203    #[test]
1204    fn test_focus_stack() {
1205        let mut fs: FocusStack<&str> = FocusStack::new();
1206        fs.focus("a");
1207        fs.focus("b");
1208        assert_eq!(fs.current(), Some(&"b"));
1209        assert_eq!(fs.depth(), 2);
1210        fs.blur();
1211        assert_eq!(fs.current(), Some(&"a"));
1212    }
1213}
1214#[cfg(test)]
1215mod tests_extra_iterators {
1216    use super::*;
1217    #[test]
1218    fn test_window_iterator() {
1219        let data = vec![1u32, 2, 3, 4, 5];
1220        let windows: Vec<_> = WindowIterator::new(&data, 3).collect();
1221        assert_eq!(windows.len(), 3);
1222        assert_eq!(windows[0], &[1, 2, 3]);
1223        assert_eq!(windows[2], &[3, 4, 5]);
1224    }
1225    #[test]
1226    fn test_non_empty_vec() {
1227        let mut nev = NonEmptyVec::singleton(10u32);
1228        nev.push(20);
1229        nev.push(30);
1230        assert_eq!(nev.len(), 3);
1231        assert_eq!(*nev.first(), 10);
1232        assert_eq!(*nev.last(), 30);
1233    }
1234}
1235#[cfg(test)]
1236mod tests_eta_padding {
1237    use super::*;
1238    #[test]
1239    fn test_eta_reduction_stats() {
1240        let mut s = EtaReductionStats::new();
1241        s.examined = 10;
1242        s.reductions = 7;
1243        assert!((s.ratio() - 0.7).abs() < 1e-9);
1244    }
1245    #[test]
1246    fn test_eta_op_counter() {
1247        let mut c = EtaOpCounter::new();
1248        c.inc("check");
1249        c.inc("check");
1250        c.inc("reduce");
1251        assert_eq!(c.get("check"), 2);
1252        assert_eq!(c.total(), 3);
1253    }
1254    #[test]
1255    fn test_eta_normal_cache() {
1256        let mut cache = EtaNormalCache::new();
1257        cache.insert(42, true);
1258        assert_eq!(cache.query(42), Some(true));
1259        assert_eq!(cache.query(99), None);
1260        cache.clear();
1261        assert_eq!(cache.len(), 0);
1262    }
1263    #[test]
1264    fn test_eta_job_queue() {
1265        let mut q = EtaJobQueue::new();
1266        q.enqueue(EtaJob::new(1, 100, 5));
1267        q.enqueue(EtaJob::new(2, 200, 10));
1268        q.enqueue(EtaJob::new(3, 300, 1));
1269        assert_eq!(q.len(), 3);
1270        let job = q.dequeue().expect("job should be present");
1271        assert_eq!(job.prio, 10);
1272    }
1273    #[test]
1274    fn test_eta_outcome() {
1275        assert!(EtaOutcome::Reduced.is_success());
1276        assert!(EtaOutcome::AlreadyNormal.is_success());
1277        assert!(!EtaOutcome::NotApplicable.is_success());
1278        assert_eq!(EtaOutcome::Reduced.label(), "reduced");
1279    }
1280    #[test]
1281    fn test_eta_log() {
1282        let mut log = EtaLog::new();
1283        log.record(EtaOutcome::Reduced);
1284        log.record(EtaOutcome::AlreadyNormal);
1285        log.record(EtaOutcome::NotApplicable);
1286        assert_eq!(log.count(EtaOutcome::Reduced), 1);
1287        assert!((log.success_rate() - 2.0 / 3.0).abs() < 1e-9);
1288    }
1289    #[test]
1290    fn test_eta_structure() {
1291        let s = EtaStructure::Lambda(3);
1292        assert_eq!(s.arity(), 3);
1293        let a = EtaStructure::Atomic;
1294        assert_eq!(a.arity(), 0);
1295    }
1296    #[test]
1297    fn test_eta_checker() {
1298        let mut ec = EtaChecker::new();
1299        let outcome = ec.is_eta_normal(42);
1300        assert!(!outcome.is_success());
1301    }
1302}
1303#[cfg(test)]
1304mod tests_eta_stat_counter {
1305    use super::*;
1306    #[test]
1307    fn test_eta_stat_counter() {
1308        let mut c = EtaStatCounter::new();
1309        c.record(10);
1310        c.record(20);
1311        c.record(30);
1312        assert!((c.mean().expect("mean should succeed") - 20.0).abs() < 1e-9);
1313        assert_eq!(c.count(), 3);
1314    }
1315}