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