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