Skip to main content

oxilean_kernel/subst/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::{Expr, FVarId};
6use std::collections::HashMap;
7
8use super::types::{
9    ConfigNode, DecisionNode, Either2, FlatSubstitution, FocusStack, LabelSet, NonEmptyVec,
10    PathBuf, RewriteRule, RewriteRuleSet, SimpleDag, SlidingSum, SmallMap, SparseVec, StackCalc,
11    StatSummary, Stopwatch, StringPool, SubstStats, Substitution, TokenBucket, TransformStat,
12    TransitiveClosure, VersionedRecord, WindowIterator, WriteOnce,
13};
14
15/// Instantiate BVar(0) with arg in body, shifting down all other BVars.
16///
17/// This is the core operation for β-reduction: `(λx.body) arg → body[arg/x]`.
18pub fn instantiate(body: &Expr, arg: &Expr) -> Expr {
19    instantiate_at(body, arg, 0)
20}
21fn instantiate_at(expr: &Expr, arg: &Expr, depth: u32) -> Expr {
22    match expr {
23        Expr::BVar(n) => {
24            if *n == depth {
25                arg.clone()
26            } else if *n > depth {
27                Expr::BVar(*n - 1)
28            } else {
29                expr.clone()
30            }
31        }
32        Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
33        Expr::App(f, a) => {
34            let f_new = instantiate_at(f, arg, depth);
35            let a_new = instantiate_at(a, arg, depth);
36            Expr::App(Box::new(f_new), Box::new(a_new))
37        }
38        Expr::Lam(bi, name, ty, body) => {
39            let ty_new = instantiate_at(ty, arg, depth);
40            let body_new = instantiate_at(body, arg, depth + 1);
41            Expr::Lam(*bi, name.clone(), Box::new(ty_new), Box::new(body_new))
42        }
43        Expr::Pi(bi, name, ty, body) => {
44            let ty_new = instantiate_at(ty, arg, depth);
45            let body_new = instantiate_at(body, arg, depth + 1);
46            Expr::Pi(*bi, name.clone(), Box::new(ty_new), Box::new(body_new))
47        }
48        Expr::Let(name, ty, val, body) => {
49            let ty_new = instantiate_at(ty, arg, depth);
50            let val_new = instantiate_at(val, arg, depth);
51            let body_new = instantiate_at(body, arg, depth + 1);
52            Expr::Let(
53                name.clone(),
54                Box::new(ty_new),
55                Box::new(val_new),
56                Box::new(body_new),
57            )
58        }
59        Expr::Proj(name, idx, e) => {
60            let e_new = instantiate_at(e, arg, depth);
61            Expr::Proj(name.clone(), *idx, Box::new(e_new))
62        }
63    }
64}
65/// Replace FVar with BVar(0), shifting up all existing BVars.
66///
67/// This is the inverse of instantiation, used when forming binders.
68pub fn abstract_expr(expr: &Expr, fvar: FVarId) -> Expr {
69    abstract_at(expr, fvar, 0)
70}
71fn abstract_at(expr: &Expr, fvar: FVarId, depth: u32) -> Expr {
72    match expr {
73        Expr::FVar(id) if *id == fvar => Expr::BVar(depth),
74        Expr::BVar(n) => Expr::BVar(*n + 1),
75        Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
76        Expr::App(f, a) => {
77            let f_new = abstract_at(f, fvar, depth);
78            let a_new = abstract_at(a, fvar, depth);
79            Expr::App(Box::new(f_new), Box::new(a_new))
80        }
81        Expr::Lam(bi, name, ty, body) => {
82            let ty_new = abstract_at(ty, fvar, depth);
83            let body_new = abstract_at(body, fvar, depth + 1);
84            Expr::Lam(*bi, name.clone(), Box::new(ty_new), Box::new(body_new))
85        }
86        Expr::Pi(bi, name, ty, body) => {
87            let ty_new = abstract_at(ty, fvar, depth);
88            let body_new = abstract_at(body, fvar, depth + 1);
89            Expr::Pi(*bi, name.clone(), Box::new(ty_new), Box::new(body_new))
90        }
91        Expr::Let(name, ty, val, body) => {
92            let ty_new = abstract_at(ty, fvar, depth);
93            let val_new = abstract_at(val, fvar, depth);
94            let body_new = abstract_at(body, fvar, depth + 1);
95            Expr::Let(
96                name.clone(),
97                Box::new(ty_new),
98                Box::new(val_new),
99                Box::new(body_new),
100            )
101        }
102        Expr::Proj(name, idx, e) => {
103            let e_new = abstract_at(e, fvar, depth);
104            Expr::Proj(name.clone(), *idx, Box::new(e_new))
105        }
106    }
107}
108#[cfg(test)]
109mod tests {
110    use super::*;
111    #[test]
112    fn test_instantiate_simple() {
113        let bvar0 = Expr::BVar(0);
114        let fvar = Expr::FVar(FVarId(1));
115        let result = instantiate(&bvar0, &fvar);
116        assert_eq!(result, fvar);
117    }
118    #[test]
119    fn test_instantiate_shift_down() {
120        let bvar1 = Expr::BVar(1);
121        let arg = Expr::FVar(FVarId(1));
122        let result = instantiate(&bvar1, &arg);
123        assert_eq!(result, Expr::BVar(0));
124    }
125    #[test]
126    fn test_abstract_roundtrip() {
127        let fvar_id = FVarId(42);
128        let fvar = Expr::FVar(fvar_id);
129        let abstracted = abstract_expr(&fvar, fvar_id);
130        let back = instantiate(&abstracted, &fvar);
131        assert_eq!(back, fvar);
132    }
133    #[test]
134    fn test_instantiate_app() {
135        let app = Expr::App(Box::new(Expr::BVar(0)), Box::new(Expr::BVar(1)));
136        let arg = Expr::FVar(FVarId(99));
137        let result = instantiate(&app, &arg);
138        match result {
139            Expr::App(f, a) => {
140                assert_eq!(*f, arg);
141                assert_eq!(*a, Expr::BVar(0));
142            }
143            _ => panic!("Expected App"),
144        }
145    }
146}
147/// A finite mapping from free-variable identifiers to expressions.
148pub type SubstMap = HashMap<FVarId, Expr>;
149/// Simultaneously substitute `BVar(0)..BVar(k-1)` with `args[0]..args[k-1]`.
150pub fn instantiate_many(expr: &Expr, args: &[Expr]) -> Expr {
151    let k = args.len() as u32;
152    if k == 0 {
153        return expr.clone();
154    }
155    instantiate_many_at(expr, args, k, 0)
156}
157fn instantiate_many_at(expr: &Expr, args: &[Expr], k: u32, offset: u32) -> Expr {
158    match expr {
159        Expr::BVar(n) => {
160            let idx = *n;
161            if idx >= offset && idx < offset + k {
162                args[(idx - offset) as usize].clone()
163            } else if idx >= offset + k {
164                Expr::BVar(idx - k)
165            } else {
166                expr.clone()
167            }
168        }
169        Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
170        Expr::App(f, a) => Expr::App(
171            Box::new(instantiate_many_at(f, args, k, offset)),
172            Box::new(instantiate_many_at(a, args, k, offset)),
173        ),
174        Expr::Lam(bi, name, ty, body) => Expr::Lam(
175            *bi,
176            name.clone(),
177            Box::new(instantiate_many_at(ty, args, k, offset)),
178            Box::new(instantiate_many_at(body, args, k, offset + 1)),
179        ),
180        Expr::Pi(bi, name, ty, body) => Expr::Pi(
181            *bi,
182            name.clone(),
183            Box::new(instantiate_many_at(ty, args, k, offset)),
184            Box::new(instantiate_many_at(body, args, k, offset + 1)),
185        ),
186        Expr::Let(name, ty, val, body) => Expr::Let(
187            name.clone(),
188            Box::new(instantiate_many_at(ty, args, k, offset)),
189            Box::new(instantiate_many_at(val, args, k, offset)),
190            Box::new(instantiate_many_at(body, args, k, offset + 1)),
191        ),
192        Expr::Proj(name, idx, e) => Expr::Proj(
193            name.clone(),
194            *idx,
195            Box::new(instantiate_many_at(e, args, k, offset)),
196        ),
197    }
198}
199/// Apply a parallel substitution: replace each `FVar(id)` with the corresponding
200/// expression in `map`.
201pub fn parallel_subst(expr: &Expr, map: &SubstMap) -> Expr {
202    if map.is_empty() {
203        return expr.clone();
204    }
205    parallel_subst_impl(expr, map)
206}
207fn parallel_subst_impl(expr: &Expr, map: &SubstMap) -> Expr {
208    match expr {
209        Expr::FVar(id) => {
210            if let Some(replacement) = map.get(id) {
211                replacement.clone()
212            } else {
213                expr.clone()
214            }
215        }
216        Expr::BVar(_) | Expr::Sort(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
217        Expr::App(f, a) => Expr::App(
218            Box::new(parallel_subst_impl(f, map)),
219            Box::new(parallel_subst_impl(a, map)),
220        ),
221        Expr::Lam(bi, name, ty, body) => Expr::Lam(
222            *bi,
223            name.clone(),
224            Box::new(parallel_subst_impl(ty, map)),
225            Box::new(parallel_subst_impl(body, map)),
226        ),
227        Expr::Pi(bi, name, ty, body) => Expr::Pi(
228            *bi,
229            name.clone(),
230            Box::new(parallel_subst_impl(ty, map)),
231            Box::new(parallel_subst_impl(body, map)),
232        ),
233        Expr::Let(name, ty, val, body) => Expr::Let(
234            name.clone(),
235            Box::new(parallel_subst_impl(ty, map)),
236            Box::new(parallel_subst_impl(val, map)),
237            Box::new(parallel_subst_impl(body, map)),
238        ),
239        Expr::Proj(name, idx, e) => {
240            Expr::Proj(name.clone(), *idx, Box::new(parallel_subst_impl(e, map)))
241        }
242    }
243}
244/// Shift all `BVar(i)` with `i >= cutoff` up by `amount`.
245pub fn shift_bvars(expr: &Expr, amount: u32, cutoff: u32) -> Expr {
246    match expr {
247        Expr::BVar(i) => {
248            if *i >= cutoff {
249                Expr::BVar(*i + amount)
250            } else {
251                expr.clone()
252            }
253        }
254        Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
255        Expr::App(f, a) => Expr::App(
256            Box::new(shift_bvars(f, amount, cutoff)),
257            Box::new(shift_bvars(a, amount, cutoff)),
258        ),
259        Expr::Lam(bi, name, ty, body) => Expr::Lam(
260            *bi,
261            name.clone(),
262            Box::new(shift_bvars(ty, amount, cutoff)),
263            Box::new(shift_bvars(body, amount, cutoff + 1)),
264        ),
265        Expr::Pi(bi, name, ty, body) => Expr::Pi(
266            *bi,
267            name.clone(),
268            Box::new(shift_bvars(ty, amount, cutoff)),
269            Box::new(shift_bvars(body, amount, cutoff + 1)),
270        ),
271        Expr::Let(name, ty, val, body) => Expr::Let(
272            name.clone(),
273            Box::new(shift_bvars(ty, amount, cutoff)),
274            Box::new(shift_bvars(val, amount, cutoff)),
275            Box::new(shift_bvars(body, amount, cutoff + 1)),
276        ),
277        Expr::Proj(name, idx, e) => {
278            Expr::Proj(name.clone(), *idx, Box::new(shift_bvars(e, amount, cutoff)))
279        }
280    }
281}
282/// Collect the set of free `FVarId`s occurring in `expr`.
283pub fn free_vars(expr: &Expr) -> std::collections::HashSet<FVarId> {
284    let mut set = std::collections::HashSet::new();
285    free_vars_impl(expr, &mut set);
286    set
287}
288fn free_vars_impl(expr: &Expr, set: &mut std::collections::HashSet<FVarId>) {
289    match expr {
290        Expr::FVar(id) => {
291            set.insert(*id);
292        }
293        Expr::App(f, a) => {
294            free_vars_impl(f, set);
295            free_vars_impl(a, set);
296        }
297        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
298            free_vars_impl(ty, set);
299            free_vars_impl(body, set);
300        }
301        Expr::Let(_, ty, val, body) => {
302            free_vars_impl(ty, set);
303            free_vars_impl(val, set);
304            free_vars_impl(body, set);
305        }
306        Expr::Proj(_, _, e) => free_vars_impl(e, set),
307        _ => {}
308    }
309}
310/// Return `true` if `fvar` occurs free in `expr`.
311pub fn occurs_free(expr: &Expr, fvar: FVarId) -> bool {
312    match expr {
313        Expr::FVar(id) => *id == fvar,
314        Expr::App(f, a) => occurs_free(f, fvar) || occurs_free(a, fvar),
315        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
316            occurs_free(ty, fvar) || occurs_free(body, fvar)
317        }
318        Expr::Let(_, ty, val, body) => {
319            occurs_free(ty, fvar) || occurs_free(val, fvar) || occurs_free(body, fvar)
320        }
321        Expr::Proj(_, _, e) => occurs_free(e, fvar),
322        _ => false,
323    }
324}
325/// Count how many times `fvar` occurs free in `expr`.
326pub fn count_free_occurrences(expr: &Expr, fvar: FVarId) -> usize {
327    match expr {
328        Expr::FVar(id) => usize::from(*id == fvar),
329        Expr::App(f, a) => count_free_occurrences(f, fvar) + count_free_occurrences(a, fvar),
330        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
331            count_free_occurrences(ty, fvar) + count_free_occurrences(body, fvar)
332        }
333        Expr::Let(_, ty, val, body) => {
334            count_free_occurrences(ty, fvar)
335                + count_free_occurrences(val, fvar)
336                + count_free_occurrences(body, fvar)
337        }
338        Expr::Proj(_, _, e) => count_free_occurrences(e, fvar),
339        _ => 0,
340    }
341}
342/// Like [`instantiate`] but also fills in [`SubstStats`].
343pub fn instantiate_tracked(body: &Expr, arg: &Expr, stats: &mut SubstStats) -> Expr {
344    instantiate_tracked_at(body, arg, 0, stats)
345}
346fn instantiate_tracked_at(expr: &Expr, arg: &Expr, depth: u32, stats: &mut SubstStats) -> Expr {
347    stats.nodes_visited += 1;
348    match expr {
349        Expr::BVar(n) => {
350            if *n == depth {
351                stats.bvar_hits += 1;
352                arg.clone()
353            } else if *n > depth {
354                stats.bvar_misses += 1;
355                Expr::BVar(*n - 1)
356            } else {
357                expr.clone()
358            }
359        }
360        Expr::Sort(_) | Expr::FVar(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
361        Expr::App(f, a) => Expr::App(
362            Box::new(instantiate_tracked_at(f, arg, depth, stats)),
363            Box::new(instantiate_tracked_at(a, arg, depth, stats)),
364        ),
365        Expr::Lam(bi, name, ty, body) => Expr::Lam(
366            *bi,
367            name.clone(),
368            Box::new(instantiate_tracked_at(ty, arg, depth, stats)),
369            Box::new(instantiate_tracked_at(body, arg, depth + 1, stats)),
370        ),
371        Expr::Pi(bi, name, ty, body) => Expr::Pi(
372            *bi,
373            name.clone(),
374            Box::new(instantiate_tracked_at(ty, arg, depth, stats)),
375            Box::new(instantiate_tracked_at(body, arg, depth + 1, stats)),
376        ),
377        Expr::Let(name, ty, val, body) => Expr::Let(
378            name.clone(),
379            Box::new(instantiate_tracked_at(ty, arg, depth, stats)),
380            Box::new(instantiate_tracked_at(val, arg, depth, stats)),
381            Box::new(instantiate_tracked_at(body, arg, depth + 1, stats)),
382        ),
383        Expr::Proj(name, idx, e) => Expr::Proj(
384            name.clone(),
385            *idx,
386            Box::new(instantiate_tracked_at(e, arg, depth, stats)),
387        ),
388    }
389}
390/// Build a `SubstMap` from parallel slices of ids and replacements.
391pub fn build_subst_map(fvars: &[FVarId], replacements: &[Expr]) -> SubstMap {
392    assert_eq!(fvars.len(), replacements.len());
393    fvars
394        .iter()
395        .zip(replacements.iter())
396        .map(|(id, expr)| (*id, expr.clone()))
397        .collect()
398}
399/// Apply beta-reduction once at the top level if possible.
400pub fn try_beta_reduce(expr: &Expr) -> Option<Expr> {
401    match expr {
402        Expr::App(f, a) => {
403            if let Expr::Lam(_, _, _, body) = f.as_ref() {
404                Some(instantiate(body, a))
405            } else {
406                None
407            }
408        }
409        _ => None,
410    }
411}
412/// Repeatedly apply top-level beta reduction until no more is possible.
413pub fn beta_reduce_head(expr: Expr) -> Expr {
414    let mut curr = expr;
415    while let Some(reduced) = try_beta_reduce(&curr) {
416        curr = reduced;
417    }
418    curr
419}
420/// Substitute a single free variable with an expression.
421pub fn subst_fvar(expr: &Expr, fvar: FVarId, replacement: &Expr) -> Expr {
422    let mut map = SubstMap::new();
423    map.insert(fvar, replacement.clone());
424    parallel_subst(expr, &map)
425}
426/// Check whether `expr` is in weak head normal form with respect to beta.
427pub fn is_whnf_beta(expr: &Expr) -> bool {
428    match expr {
429        Expr::App(f, _) => match f.as_ref() {
430            Expr::Lam(_, _, _, _) => false,
431            other => is_whnf_beta(other),
432        },
433        _ => true,
434    }
435}
436/// Collect the indices of all loose bound variables.
437pub fn collect_loose_bvar_indices(expr: &Expr) -> Vec<u32> {
438    let mut indices = Vec::new();
439    collect_loose_bvar_impl(expr, 0, &mut indices);
440    indices.sort();
441    indices.dedup();
442    indices
443}
444fn collect_loose_bvar_impl(expr: &Expr, depth: u32, result: &mut Vec<u32>) {
445    match expr {
446        Expr::BVar(n) => {
447            if *n >= depth {
448                result.push(*n - depth);
449            }
450        }
451        Expr::App(f, a) => {
452            collect_loose_bvar_impl(f, depth, result);
453            collect_loose_bvar_impl(a, depth, result);
454        }
455        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
456            collect_loose_bvar_impl(ty, depth, result);
457            collect_loose_bvar_impl(body, depth + 1, result);
458        }
459        Expr::Let(_, ty, val, body) => {
460            collect_loose_bvar_impl(ty, depth, result);
461            collect_loose_bvar_impl(val, depth, result);
462            collect_loose_bvar_impl(body, depth + 1, result);
463        }
464        Expr::Proj(_, _, e) => collect_loose_bvar_impl(e, depth, result),
465        _ => {}
466    }
467}
468/// Apply a parallel substitution and collect profiling statistics.
469pub fn parallel_subst_tracked(expr: &Expr, map: &SubstMap, stats: &mut SubstStats) -> Expr {
470    if map.is_empty() {
471        return expr.clone();
472    }
473    parallel_subst_tracked_impl(expr, map, stats)
474}
475fn parallel_subst_tracked_impl(expr: &Expr, map: &SubstMap, stats: &mut SubstStats) -> Expr {
476    stats.nodes_visited += 1;
477    match expr {
478        Expr::FVar(id) => {
479            if let Some(r) = map.get(id) {
480                stats.fvar_hits += 1;
481                r.clone()
482            } else {
483                expr.clone()
484            }
485        }
486        Expr::BVar(_) | Expr::Sort(_) | Expr::Const(_, _) | Expr::Lit(_) => expr.clone(),
487        Expr::App(f, a) => Expr::App(
488            Box::new(parallel_subst_tracked_impl(f, map, stats)),
489            Box::new(parallel_subst_tracked_impl(a, map, stats)),
490        ),
491        Expr::Lam(bi, name, ty, body) => Expr::Lam(
492            *bi,
493            name.clone(),
494            Box::new(parallel_subst_tracked_impl(ty, map, stats)),
495            Box::new(parallel_subst_tracked_impl(body, map, stats)),
496        ),
497        Expr::Pi(bi, name, ty, body) => Expr::Pi(
498            *bi,
499            name.clone(),
500            Box::new(parallel_subst_tracked_impl(ty, map, stats)),
501            Box::new(parallel_subst_tracked_impl(body, map, stats)),
502        ),
503        Expr::Let(name, ty, val, body) => Expr::Let(
504            name.clone(),
505            Box::new(parallel_subst_tracked_impl(ty, map, stats)),
506            Box::new(parallel_subst_tracked_impl(val, map, stats)),
507            Box::new(parallel_subst_tracked_impl(body, map, stats)),
508        ),
509        Expr::Proj(name, idx, e) => Expr::Proj(
510            name.clone(),
511            *idx,
512            Box::new(parallel_subst_tracked_impl(e, map, stats)),
513        ),
514    }
515}
516#[cfg(test)]
517mod extended_tests {
518    use super::*;
519    use crate::{BinderInfo, Level, Literal, Name};
520    fn fvar(id: u64) -> Expr {
521        Expr::FVar(FVarId(id))
522    }
523    fn lit(n: u64) -> Expr {
524        Expr::Lit(Literal::Nat(n))
525    }
526    fn sort0() -> Expr {
527        Expr::Sort(Level::zero())
528    }
529    #[test]
530    fn test_instantiate_many_two() {
531        let app = Expr::App(Box::new(Expr::BVar(0)), Box::new(Expr::BVar(1)));
532        let result = instantiate_many(&app, &[lit(10), lit(20)]);
533        match result {
534            Expr::App(f, a) => {
535                assert_eq!(*f, lit(10));
536                assert_eq!(*a, lit(20));
537            }
538            _ => panic!("Expected App"),
539        }
540    }
541    #[test]
542    fn test_parallel_subst_single() {
543        let mut map = SubstMap::new();
544        map.insert(FVarId(1), lit(99));
545        let expr = fvar(1);
546        assert_eq!(parallel_subst(&expr, &map), lit(99));
547    }
548    #[test]
549    fn test_parallel_subst_empty() {
550        let expr = fvar(5);
551        let map = SubstMap::new();
552        assert_eq!(parallel_subst(&expr, &map), expr);
553    }
554    #[test]
555    fn test_shift_bvars_above_cutoff() {
556        let e = Expr::BVar(2);
557        let shifted = shift_bvars(&e, 3, 1);
558        assert_eq!(shifted, Expr::BVar(5));
559    }
560    #[test]
561    fn test_shift_bvars_below_cutoff() {
562        let e = Expr::BVar(0);
563        let shifted = shift_bvars(&e, 2, 1);
564        assert_eq!(shifted, Expr::BVar(0));
565    }
566    #[test]
567    fn test_free_vars_single() {
568        let set = free_vars(&fvar(7));
569        assert_eq!(set.len(), 1);
570        assert!(set.contains(&FVarId(7)));
571    }
572    #[test]
573    fn test_occurs_free_true() {
574        let expr = Expr::App(Box::new(fvar(1)), Box::new(fvar(2)));
575        assert!(occurs_free(&expr, FVarId(1)));
576        assert!(!occurs_free(&expr, FVarId(3)));
577    }
578    #[test]
579    fn test_count_free_occurrences() {
580        let expr = Expr::App(Box::new(fvar(1)), Box::new(fvar(1)));
581        assert_eq!(count_free_occurrences(&expr, FVarId(1)), 2);
582    }
583    #[test]
584    fn test_subst_stats_merge() {
585        let mut a = SubstStats {
586            bvar_hits: 3,
587            bvar_misses: 1,
588            fvar_hits: 2,
589            nodes_visited: 10,
590        };
591        let b = SubstStats {
592            bvar_hits: 1,
593            bvar_misses: 0,
594            fvar_hits: 1,
595            nodes_visited: 5,
596        };
597        a.merge(&b);
598        assert_eq!(a.bvar_hits, 4);
599        assert_eq!(a.total_substs(), 7);
600    }
601    #[test]
602    fn test_instantiate_tracked() {
603        let body = Expr::BVar(0);
604        let arg = lit(42);
605        let mut stats = SubstStats::default();
606        let result = instantiate_tracked(&body, &arg, &mut stats);
607        assert_eq!(result, lit(42));
608        assert_eq!(stats.bvar_hits, 1);
609    }
610    #[test]
611    fn test_build_subst_map() {
612        let fvars = vec![FVarId(1), FVarId(2)];
613        let replacements = vec![lit(10), lit(20)];
614        let map = build_subst_map(&fvars, &replacements);
615        assert_eq!(map.get(&FVarId(1)), Some(&lit(10)));
616    }
617    #[test]
618    fn test_try_beta_reduce_success() {
619        let lam = Expr::Lam(
620            BinderInfo::Default,
621            Name::str("x"),
622            Box::new(sort0()),
623            Box::new(Expr::BVar(0)),
624        );
625        let app = Expr::App(Box::new(lam), Box::new(lit(42)));
626        let result = try_beta_reduce(&app).expect("result should be present");
627        assert_eq!(result, lit(42));
628    }
629    #[test]
630    fn test_try_beta_reduce_fail() {
631        let app = Expr::App(Box::new(fvar(1)), Box::new(lit(42)));
632        assert!(try_beta_reduce(&app).is_none());
633    }
634    #[test]
635    fn test_beta_reduce_head() {
636        let lam = Expr::Lam(
637            BinderInfo::Default,
638            Name::str("x"),
639            Box::new(sort0()),
640            Box::new(Expr::BVar(0)),
641        );
642        let app = Expr::App(Box::new(lam), Box::new(lit(7)));
643        let result = beta_reduce_head(app);
644        assert_eq!(result, lit(7));
645    }
646    #[test]
647    fn test_subst_fvar() {
648        let expr = fvar(1);
649        let result = subst_fvar(&expr, FVarId(1), &lit(99));
650        assert_eq!(result, lit(99));
651    }
652    #[test]
653    fn test_is_whnf_beta_lam() {
654        let lam = Expr::Lam(
655            BinderInfo::Default,
656            Name::str("x"),
657            Box::new(sort0()),
658            Box::new(Expr::BVar(0)),
659        );
660        assert!(is_whnf_beta(&lam));
661    }
662    #[test]
663    fn test_is_whnf_beta_app_not_whnf() {
664        let lam = Expr::Lam(
665            BinderInfo::Default,
666            Name::str("x"),
667            Box::new(sort0()),
668            Box::new(Expr::BVar(0)),
669        );
670        let app = Expr::App(Box::new(lam), Box::new(lit(1)));
671        assert!(!is_whnf_beta(&app));
672    }
673    #[test]
674    fn test_collect_loose_bvar_indices() {
675        let e = Expr::App(Box::new(Expr::BVar(0)), Box::new(Expr::BVar(2)));
676        let indices = collect_loose_bvar_indices(&e);
677        assert!(indices.contains(&0));
678        assert!(indices.contains(&2));
679    }
680    #[test]
681    fn test_parallel_subst_tracked() {
682        let expr = Expr::App(Box::new(fvar(1)), Box::new(fvar(2)));
683        let mut map = SubstMap::new();
684        map.insert(FVarId(1), lit(10));
685        map.insert(FVarId(2), lit(20));
686        let mut stats = SubstStats::default();
687        let result = parallel_subst_tracked(&expr, &map, &mut stats);
688        assert_eq!(stats.fvar_hits, 2);
689        match result {
690            Expr::App(f, a) => {
691                assert_eq!(*f, lit(10));
692                assert_eq!(*a, lit(20));
693            }
694            _ => panic!("Expected App"),
695        }
696    }
697}
698/// Apply a list of arguments to a function expression via beta reduction.
699///
700/// Given `f` and `[a1, a2, ..., an]`, returns `((...((f a1) a2) ...) an)`
701/// after instantiating each lambda body with the corresponding argument.
702pub fn apply_args(f: &Expr, args: &[Expr]) -> Expr {
703    let mut result = f.clone();
704    for arg in args {
705        if let Expr::Lam(_, _, _, body) = result {
706            result = instantiate(&body, arg);
707        } else {
708            result = Expr::App(Box::new(result), Box::new(arg.clone()));
709        }
710    }
711    result
712}
713/// Check if an expression is a lambda abstraction.
714#[inline]
715pub fn is_lambda(e: &Expr) -> bool {
716    matches!(e, Expr::Lam(_, _, _, _))
717}
718/// Peel off `n` leading lambda binders, collecting binder info.
719///
720/// Returns `(binders, body)` where `binders` is a vector of `(name, binder_info, type)`
721/// tuples and `body` is the inner expression with `n` fewer lambdas.
722#[allow(clippy::type_complexity)]
723pub fn peel_lambdas(
724    e: &Expr,
725    n: usize,
726) -> (Vec<(crate::Name, crate::BinderInfo, Box<Expr>)>, &Expr) {
727    let mut binders = Vec::new();
728    let mut cur = e;
729    for _ in 0..n {
730        if let Expr::Lam(bi, _, ty, body) = cur {
731            binders.push((crate::Name::Anonymous, *bi, ty.clone()));
732            cur = body;
733        } else {
734            break;
735        }
736    }
737    (binders, cur)
738}
739/// Count the number of leading lambda binders.
740pub fn count_lambdas(e: &Expr) -> usize {
741    let mut n = 0;
742    let mut cur = e;
743    while let Expr::Lam(_, _, _, body) = cur {
744        n += 1;
745        cur = body;
746    }
747    n
748}
749/// Count the number of leading pi binders.
750pub fn count_pis(e: &Expr) -> usize {
751    let mut n = 0;
752    let mut cur = e;
753    while let Expr::Pi(_, _, _, body) = cur {
754        n += 1;
755        cur = body;
756    }
757    n
758}
759#[cfg(test)]
760mod extended2_tests {
761    use super::*;
762    use crate::{BinderInfo, Expr, FVarId, Literal, Name};
763    fn lit(n: u64) -> Expr {
764        Expr::Lit(Literal::Nat(n))
765    }
766    fn fvar(id: u64) -> Expr {
767        Expr::FVar(FVarId(id))
768    }
769    #[test]
770    fn test_apply_args_lam() {
771        let body = Expr::BVar(0);
772        let lam = Expr::Lam(
773            BinderInfo::Default,
774            Name::str("_"),
775            Box::new(lit(0)),
776            Box::new(body),
777        );
778        let result = apply_args(&lam, &[lit(42)]);
779        assert_eq!(result, lit(42));
780    }
781    #[test]
782    fn test_apply_args_non_lam() {
783        let f = fvar(1);
784        let result = apply_args(&f, &[lit(1), lit(2)]);
785        match &result {
786            Expr::App(outer, arg2) => {
787                assert_eq!(**arg2, lit(2));
788                match outer.as_ref() {
789                    Expr::App(inner_f, arg1) => {
790                        assert_eq!(**inner_f, fvar(1));
791                        assert_eq!(**arg1, lit(1));
792                    }
793                    _ => panic!("Expected App"),
794                }
795            }
796            _ => panic!("Expected App"),
797        }
798    }
799    #[test]
800    fn test_is_lambda() {
801        let lam = Expr::Lam(
802            BinderInfo::Default,
803            Name::str("_"),
804            Box::new(lit(0)),
805            Box::new(lit(1)),
806        );
807        assert!(is_lambda(&lam));
808        assert!(!is_lambda(&lit(0)));
809    }
810    #[test]
811    fn test_count_lambdas() {
812        let inner = lit(0);
813        let lam1 = Expr::Lam(
814            BinderInfo::Default,
815            Name::str("_"),
816            Box::new(lit(0)),
817            Box::new(inner),
818        );
819        let lam2 = Expr::Lam(
820            BinderInfo::Default,
821            Name::str("_"),
822            Box::new(lit(0)),
823            Box::new(lam1),
824        );
825        assert_eq!(count_lambdas(&lam2), 2);
826        assert_eq!(count_lambdas(&lit(0)), 0);
827    }
828    #[test]
829    fn test_count_pis() {
830        let inner = lit(0);
831        let pi1 = Expr::Pi(
832            BinderInfo::Default,
833            Name::str("_"),
834            Box::new(lit(0)),
835            Box::new(inner),
836        );
837        let pi2 = Expr::Pi(
838            BinderInfo::Default,
839            Name::str("_"),
840            Box::new(lit(0)),
841            Box::new(pi1),
842        );
843        assert_eq!(count_pis(&pi2), 2);
844        assert_eq!(count_pis(&lit(0)), 0);
845    }
846    #[test]
847    fn test_peel_lambdas_two() {
848        let body = Expr::BVar(0);
849        let lam1 = Expr::Lam(
850            BinderInfo::Default,
851            Name::str("_"),
852            Box::new(lit(0)),
853            Box::new(body),
854        );
855        let lam2 = Expr::Lam(
856            BinderInfo::Implicit,
857            Name::str("_"),
858            Box::new(lit(0)),
859            Box::new(lam1),
860        );
861        let (binders, inner) = peel_lambdas(&lam2, 2);
862        assert_eq!(binders.len(), 2);
863        assert_eq!(binders[0].1, BinderInfo::Implicit);
864        assert_eq!(binders[1].1, BinderInfo::Default);
865        assert_eq!(*inner, Expr::BVar(0));
866    }
867    #[test]
868    fn test_apply_args_empty() {
869        let e = lit(5);
870        let result = apply_args(&e, &[]);
871        assert_eq!(result, e);
872    }
873}
874/// Apply a substitution list (as parallel arrays) to an expression.
875#[allow(dead_code)]
876pub fn substitute_fvars(expr: &Expr, fvars: &[FVarId], replacements: &[Expr]) -> Expr {
877    assert_eq!(fvars.len(), replacements.len());
878    let mut subst = Substitution::new();
879    for (fvar, rep) in fvars.iter().zip(replacements.iter()) {
880        subst.insert(*fvar, rep.clone());
881    }
882    subst.apply(expr)
883}
884/// Check if an expression contains any free variable in the given set.
885#[allow(dead_code)]
886pub fn expr_contains_fvar(expr: &Expr, fvars: &[FVarId]) -> bool {
887    match expr {
888        Expr::FVar(id) => fvars.contains(id),
889        Expr::App(f, a) => expr_contains_fvar(f, fvars) || expr_contains_fvar(a, fvars),
890        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
891            expr_contains_fvar(ty, fvars) || expr_contains_fvar(body, fvars)
892        }
893        Expr::Let(_, ty, val, body) => {
894            expr_contains_fvar(ty, fvars)
895                || expr_contains_fvar(val, fvars)
896                || expr_contains_fvar(body, fvars)
897        }
898        Expr::Proj(_, _, inner) => expr_contains_fvar(inner, fvars),
899        _ => false,
900    }
901}
902/// Replace the outermost `BVar(0)` of a body with `FVar(id)` (open a binder).
903///
904/// This is the "open" operation in locally nameless representation.
905#[allow(dead_code)]
906pub fn open_binder(body: &Expr, id: FVarId) -> Expr {
907    open_binder_at(body, &Expr::FVar(id), 0)
908}
909fn open_binder_at(expr: &Expr, fvar_expr: &Expr, depth: u32) -> Expr {
910    match expr {
911        Expr::BVar(n) => {
912            if *n == depth {
913                fvar_expr.clone()
914            } else {
915                expr.clone()
916            }
917        }
918        Expr::App(f, a) => {
919            let f2 = open_binder_at(f, fvar_expr, depth);
920            let a2 = open_binder_at(a, fvar_expr, depth);
921            Expr::App(Box::new(f2), Box::new(a2))
922        }
923        Expr::Lam(bi, name, ty, body) => {
924            let ty2 = open_binder_at(ty, fvar_expr, depth);
925            let body2 = open_binder_at(body, fvar_expr, depth + 1);
926            Expr::Lam(*bi, name.clone(), Box::new(ty2), Box::new(body2))
927        }
928        Expr::Pi(bi, name, ty, body) => {
929            let ty2 = open_binder_at(ty, fvar_expr, depth);
930            let body2 = open_binder_at(body, fvar_expr, depth + 1);
931            Expr::Pi(*bi, name.clone(), Box::new(ty2), Box::new(body2))
932        }
933        Expr::Let(name, ty, val, body) => {
934            let ty2 = open_binder_at(ty, fvar_expr, depth);
935            let val2 = open_binder_at(val, fvar_expr, depth);
936            let body2 = open_binder_at(body, fvar_expr, depth + 1);
937            Expr::Let(name.clone(), Box::new(ty2), Box::new(val2), Box::new(body2))
938        }
939        Expr::Proj(name, idx, inner) => {
940            let inner2 = open_binder_at(inner, fvar_expr, depth);
941            Expr::Proj(name.clone(), *idx, Box::new(inner2))
942        }
943        _ => expr.clone(),
944    }
945}
946#[cfg(test)]
947mod extended3_subst_tests {
948    use super::*;
949    use crate::{BinderInfo, Expr, FVarId, Literal, Name};
950    fn lit(n: u64) -> Expr {
951        Expr::Lit(Literal::Nat(n))
952    }
953    fn fvar(id: u64) -> Expr {
954        Expr::FVar(FVarId(id))
955    }
956    #[test]
957    fn test_substitution_insert_and_get() {
958        let mut s = Substitution::new();
959        s.insert(FVarId(0), lit(42));
960        assert_eq!(s.get(FVarId(0)), Some(&lit(42)));
961        assert_eq!(s.get(FVarId(1)), None);
962    }
963    #[test]
964    fn test_substitution_len() {
965        let mut s = Substitution::new();
966        assert_eq!(s.len(), 0);
967        s.insert(FVarId(0), lit(1));
968        assert_eq!(s.len(), 1);
969    }
970    #[test]
971    fn test_substitution_apply_fvar() {
972        let mut s = Substitution::new();
973        s.insert(FVarId(1), lit(99));
974        let expr = fvar(1);
975        assert_eq!(s.apply(&expr), lit(99));
976    }
977    #[test]
978    fn test_substitution_apply_no_match() {
979        let s = Substitution::new();
980        let expr = fvar(5);
981        assert_eq!(s.apply(&expr), fvar(5));
982    }
983    #[test]
984    fn test_substitution_remove() {
985        let mut s = Substitution::new();
986        s.insert(FVarId(0), lit(1));
987        s.remove(FVarId(0));
988        assert_eq!(s.len(), 0);
989    }
990    #[test]
991    fn test_substitution_restrict() {
992        let mut s = Substitution::new();
993        s.insert(FVarId(0), lit(1));
994        s.insert(FVarId(1), lit(2));
995        let r = s.restrict(&[FVarId(0)]);
996        assert_eq!(r.len(), 1);
997        assert!(r.get(FVarId(0)).is_some());
998        assert!(r.get(FVarId(1)).is_none());
999    }
1000    #[test]
1001    fn test_substitute_fvars_parallel() {
1002        let expr = Expr::App(Box::new(fvar(0)), Box::new(fvar(1)));
1003        let result = substitute_fvars(&expr, &[FVarId(0), FVarId(1)], &[lit(10), lit(20)]);
1004        match result {
1005            Expr::App(f, a) => {
1006                assert_eq!(*f, lit(10));
1007                assert_eq!(*a, lit(20));
1008            }
1009            _ => panic!("Expected App"),
1010        }
1011    }
1012    #[test]
1013    fn test_expr_contains_fvar_true() {
1014        let expr = Expr::App(Box::new(fvar(3)), Box::new(lit(0)));
1015        assert!(expr_contains_fvar(&expr, &[FVarId(3)]));
1016    }
1017    #[test]
1018    fn test_expr_contains_fvar_false() {
1019        let expr = lit(5);
1020        assert!(!expr_contains_fvar(&expr, &[FVarId(0)]));
1021    }
1022    #[test]
1023    fn test_open_binder_simple() {
1024        let body = Expr::BVar(0);
1025        let result = open_binder(&body, FVarId(7));
1026        assert_eq!(result, fvar(7));
1027    }
1028    #[test]
1029    fn test_open_binder_nested() {
1030        let inner_body = Expr::BVar(0);
1031        let inner_lam = Expr::Lam(
1032            BinderInfo::Default,
1033            Name::str("_"),
1034            Box::new(Expr::Const(Name::str("Nat"), vec![])),
1035            Box::new(inner_body),
1036        );
1037        let outer_body = Expr::App(Box::new(inner_lam), Box::new(Expr::BVar(0)));
1038        let opened = open_binder(&outer_body, FVarId(99));
1039        match opened {
1040            Expr::App(f, arg) => {
1041                assert!(matches!(*f, Expr::Lam(_, _, _, _)));
1042                assert_eq!(*arg, fvar(99));
1043            }
1044            _ => panic!("Expected App"),
1045        }
1046    }
1047    #[test]
1048    fn test_substitution_compose() {
1049        let mut s1 = Substitution::new();
1050        s1.insert(FVarId(0), lit(10));
1051        let mut s2 = Substitution::new();
1052        s2.insert(FVarId(1), lit(20));
1053        let composed = s1.compose(&s2);
1054        assert_eq!(composed.get(FVarId(0)), Some(&lit(10)));
1055        assert_eq!(composed.get(FVarId(1)), Some(&lit(20)));
1056    }
1057    #[test]
1058    fn test_substitution_insert_replace() {
1059        let mut s = Substitution::new();
1060        s.insert(FVarId(0), lit(1));
1061        s.insert(FVarId(0), lit(2));
1062        assert_eq!(s.len(), 1);
1063        assert_eq!(s.get(FVarId(0)), Some(&lit(2)));
1064    }
1065}
1066#[cfg(test)]
1067mod tests_padding_infra {
1068    use super::*;
1069    #[test]
1070    fn test_stat_summary() {
1071        let mut ss = StatSummary::new();
1072        ss.record(10.0);
1073        ss.record(20.0);
1074        ss.record(30.0);
1075        assert_eq!(ss.count(), 3);
1076        assert!((ss.mean().expect("mean should succeed") - 20.0).abs() < 1e-9);
1077        assert_eq!(ss.min().expect("min should succeed") as i64, 10);
1078        assert_eq!(ss.max().expect("max should succeed") as i64, 30);
1079    }
1080    #[test]
1081    fn test_transform_stat() {
1082        let mut ts = TransformStat::new();
1083        ts.record_before(100.0);
1084        ts.record_after(80.0);
1085        let ratio = ts.mean_ratio().expect("ratio should be present");
1086        assert!((ratio - 0.8).abs() < 1e-9);
1087    }
1088    #[test]
1089    fn test_small_map() {
1090        let mut m: SmallMap<u32, &str> = SmallMap::new();
1091        m.insert(3, "three");
1092        m.insert(1, "one");
1093        m.insert(2, "two");
1094        assert_eq!(m.get(&2), Some(&"two"));
1095        assert_eq!(m.len(), 3);
1096        let keys = m.keys();
1097        assert_eq!(*keys[0], 1);
1098        assert_eq!(*keys[2], 3);
1099    }
1100    #[test]
1101    fn test_label_set() {
1102        let mut ls = LabelSet::new();
1103        ls.add("foo");
1104        ls.add("bar");
1105        ls.add("foo");
1106        assert_eq!(ls.count(), 2);
1107        assert!(ls.has("bar"));
1108        assert!(!ls.has("baz"));
1109    }
1110    #[test]
1111    fn test_config_node() {
1112        let mut root = ConfigNode::section("root");
1113        let child = ConfigNode::leaf("key", "value");
1114        root.add_child(child);
1115        assert_eq!(root.num_children(), 1);
1116    }
1117    #[test]
1118    fn test_versioned_record() {
1119        let mut vr = VersionedRecord::new(0u32);
1120        vr.update(1);
1121        vr.update(2);
1122        assert_eq!(*vr.current(), 2);
1123        assert_eq!(vr.version(), 2);
1124        assert!(vr.has_history());
1125        assert_eq!(*vr.at_version(0).expect("value should be present"), 0);
1126    }
1127    #[test]
1128    fn test_simple_dag() {
1129        let mut dag = SimpleDag::new(4);
1130        dag.add_edge(0, 1);
1131        dag.add_edge(1, 2);
1132        dag.add_edge(2, 3);
1133        assert!(dag.can_reach(0, 3));
1134        assert!(!dag.can_reach(3, 0));
1135        let order = dag.topological_sort().expect("order should be present");
1136        assert_eq!(order, vec![0, 1, 2, 3]);
1137    }
1138    #[test]
1139    fn test_focus_stack() {
1140        let mut fs: FocusStack<&str> = FocusStack::new();
1141        fs.focus("a");
1142        fs.focus("b");
1143        assert_eq!(fs.current(), Some(&"b"));
1144        assert_eq!(fs.depth(), 2);
1145        fs.blur();
1146        assert_eq!(fs.current(), Some(&"a"));
1147    }
1148}
1149#[cfg(test)]
1150mod tests_extra_iterators {
1151    use super::*;
1152    #[test]
1153    fn test_window_iterator() {
1154        let data = vec![1u32, 2, 3, 4, 5];
1155        let windows: Vec<_> = WindowIterator::new(&data, 3).collect();
1156        assert_eq!(windows.len(), 3);
1157        assert_eq!(windows[0], &[1, 2, 3]);
1158        assert_eq!(windows[2], &[3, 4, 5]);
1159    }
1160    #[test]
1161    fn test_non_empty_vec() {
1162        let mut nev = NonEmptyVec::singleton(10u32);
1163        nev.push(20);
1164        nev.push(30);
1165        assert_eq!(nev.len(), 3);
1166        assert_eq!(*nev.first(), 10);
1167        assert_eq!(*nev.last(), 30);
1168    }
1169}
1170#[cfg(test)]
1171mod tests_padding2 {
1172    use super::*;
1173    #[test]
1174    fn test_sliding_sum() {
1175        let mut ss = SlidingSum::new(3);
1176        ss.push(1.0);
1177        ss.push(2.0);
1178        ss.push(3.0);
1179        assert!((ss.sum() - 6.0).abs() < 1e-9);
1180        ss.push(4.0);
1181        assert!((ss.sum() - 9.0).abs() < 1e-9);
1182        assert_eq!(ss.count(), 3);
1183    }
1184    #[test]
1185    fn test_path_buf() {
1186        let mut pb = PathBuf::new();
1187        pb.push("src");
1188        pb.push("main");
1189        assert_eq!(pb.as_str(), "src/main");
1190        assert_eq!(pb.depth(), 2);
1191        pb.pop();
1192        assert_eq!(pb.as_str(), "src");
1193    }
1194    #[test]
1195    fn test_string_pool() {
1196        let mut pool = StringPool::new();
1197        let s = pool.take();
1198        assert!(s.is_empty());
1199        pool.give("hello".to_string());
1200        let s2 = pool.take();
1201        assert!(s2.is_empty());
1202        assert_eq!(pool.free_count(), 0);
1203    }
1204    #[test]
1205    fn test_transitive_closure() {
1206        let mut tc = TransitiveClosure::new(4);
1207        tc.add_edge(0, 1);
1208        tc.add_edge(1, 2);
1209        tc.add_edge(2, 3);
1210        assert!(tc.can_reach(0, 3));
1211        assert!(!tc.can_reach(3, 0));
1212        let r = tc.reachable_from(0);
1213        assert_eq!(r.len(), 4);
1214    }
1215    #[test]
1216    fn test_token_bucket() {
1217        let mut tb = TokenBucket::new(100, 10);
1218        assert_eq!(tb.available(), 100);
1219        assert!(tb.try_consume(50));
1220        assert_eq!(tb.available(), 50);
1221        assert!(!tb.try_consume(60));
1222        assert_eq!(tb.capacity(), 100);
1223    }
1224    #[test]
1225    fn test_rewrite_rule_set() {
1226        let mut rrs = RewriteRuleSet::new();
1227        rrs.add(RewriteRule::unconditional(
1228            "beta",
1229            "App(Lam(x, b), v)",
1230            "b[x:=v]",
1231        ));
1232        rrs.add(RewriteRule::conditional("comm", "a + b", "b + a"));
1233        assert_eq!(rrs.len(), 2);
1234        assert_eq!(rrs.unconditional_rules().len(), 1);
1235        assert_eq!(rrs.conditional_rules().len(), 1);
1236        assert!(rrs.get("beta").is_some());
1237        let disp = rrs
1238            .get("beta")
1239            .expect("element at \'beta\' should exist")
1240            .display();
1241        assert!(disp.contains("→"));
1242    }
1243}
1244#[cfg(test)]
1245mod tests_padding3 {
1246    use super::*;
1247    #[test]
1248    fn test_decision_node() {
1249        let tree = DecisionNode::Branch {
1250            key: "x".into(),
1251            val: "1".into(),
1252            yes_branch: Box::new(DecisionNode::Leaf("yes".into())),
1253            no_branch: Box::new(DecisionNode::Leaf("no".into())),
1254        };
1255        let mut ctx = std::collections::HashMap::new();
1256        ctx.insert("x".into(), "1".into());
1257        assert_eq!(tree.evaluate(&ctx), "yes");
1258        ctx.insert("x".into(), "2".into());
1259        assert_eq!(tree.evaluate(&ctx), "no");
1260        assert_eq!(tree.depth(), 1);
1261    }
1262    #[test]
1263    fn test_flat_substitution() {
1264        let mut sub = FlatSubstitution::new();
1265        sub.add("foo", "bar");
1266        sub.add("baz", "qux");
1267        assert_eq!(sub.apply("foo and baz"), "bar and qux");
1268        assert_eq!(sub.len(), 2);
1269    }
1270    #[test]
1271    fn test_stopwatch() {
1272        let mut sw = Stopwatch::start();
1273        sw.split();
1274        sw.split();
1275        assert_eq!(sw.num_splits(), 2);
1276        assert!(sw.elapsed_ms() >= 0.0);
1277        for &s in sw.splits() {
1278            assert!(s >= 0.0);
1279        }
1280    }
1281    #[test]
1282    fn test_either2() {
1283        let e: Either2<i32, &str> = Either2::First(42);
1284        assert!(e.is_first());
1285        let mapped = e.map_first(|x| x * 2);
1286        assert_eq!(mapped.first(), Some(84));
1287        let e2: Either2<i32, &str> = Either2::Second("hello");
1288        assert!(e2.is_second());
1289        assert_eq!(e2.second(), Some("hello"));
1290    }
1291    #[test]
1292    fn test_write_once() {
1293        let wo: WriteOnce<u32> = WriteOnce::new();
1294        assert!(!wo.is_written());
1295        assert!(wo.write(42));
1296        assert!(!wo.write(99));
1297        assert_eq!(wo.read(), Some(42));
1298    }
1299    #[test]
1300    fn test_sparse_vec() {
1301        let mut sv: SparseVec<i32> = SparseVec::new(100);
1302        sv.set(5, 10);
1303        sv.set(50, 20);
1304        assert_eq!(*sv.get(5), 10);
1305        assert_eq!(*sv.get(50), 20);
1306        assert_eq!(*sv.get(0), 0);
1307        assert_eq!(sv.nnz(), 2);
1308        sv.set(5, 0);
1309        assert_eq!(sv.nnz(), 1);
1310    }
1311    #[test]
1312    fn test_stack_calc() {
1313        let mut calc = StackCalc::new();
1314        calc.push(3);
1315        calc.push(4);
1316        calc.add();
1317        assert_eq!(calc.peek(), Some(7));
1318        calc.push(2);
1319        calc.mul();
1320        assert_eq!(calc.peek(), Some(14));
1321    }
1322}