Skip to main content

oxilean_kernel/
lib.rs

1#![allow(unused_imports)]
2
3//! # OxiLean Kernel — Trusted Computing Base
4//!
5//! The minimal, auditable kernel implementing the **Calculus of Inductive Constructions (CiC)**
6//! with universe polymorphism. This crate is the **trusted core** of OxiLean.
7//!
8//! ## Philosophy
9//!
10//! - **Minimal TCB**: Only ~3,500 SLOC need to be trusted for soundness
11//! - **Zero dependencies**: Uses only `std` (and is `no_std`-compatible where feasible)
12//! - **No unsafe**: `#![forbid(unsafe_code)]` ensures memory safety via Rust's guarantees
13//! - **Layered trust**: Bugs in the parser or elaborator cannot produce unsound proofs
14//! - **Arena-based memory**: All expressions allocated in typed arenas for O(1) equality
15//! - **WASM-first**: Compiles to `wasm32-unknown-unknown` with no system calls or FFI
16//!
17//! ## Quick Start
18//!
19//! ### Creating a Type
20//!
21//! ```ignore
22//! use oxilean_kernel::{Expr, Environment, Level};
23//!
24//! let env = Environment::new();
25//! // Nat : Type 0
26//! let nat_type = Expr::Sort(Level::Zero);
27//! ```
28//!
29//! ### Type Checking
30//!
31//! ```ignore
32//! use oxilean_kernel::{TypeChecker, KernelError};
33//!
34//! let mut checker = TypeChecker::new(&env);
35//! // Type-check an expression
36//! let result = checker.infer(expr)?;
37//! ```
38//!
39//! ## Architecture Overview
40//!
41//! The kernel is organized into three layers:
42//!
43//! ### Layer 1: Core Data Structures
44//!
45//! - **`arena`** — Typed arena allocator (`Arena<T>`, `Idx<T>`)
46//! - **`name`** — Hierarchical identifiers (`Nat.add.comm`)
47//! - **`level`** — Universe levels (`Zero`, `Succ`, `Max`, `IMax`, `Param`)
48//! - **`expr`** — Core AST (11 node types: `BVar`, `FVar`, `Sort`, `Const`, `App`, `Lam`, `Pi`, `Let`, `Lit`, `Proj`, `Rec`)
49//!
50//! ### Layer 2: Environment & Declarations
51//!
52//! - **`env`** — Global environment holding axioms, definitions, inductives, etc.
53//! - **`declaration`** — Declaration types: `Axiom`, `Def`, `Theorem`, `Inductive`, `Quotient`, `Recursor`, `Opaque`, `Constructor`
54//! - **`context`** — Local variable contexts and context snapshots
55//!
56//! ### Layer 3: Type Theory Engine
57//!
58//! - **`infer`** — Type inference: `infer_type(expr) → Type`
59//! - **`def_eq`** — Definitional equality: `is_def_eq(t1, t2) → bool`
60//! - **`whnf`** — Weak head normal form: `whnf(expr) → Expr`
61//! - **`check`** — Declaration checking and environment validation
62//!
63//! ### Layer 4: Reduction & Normalization
64//!
65//! - **`beta`** — β-reduction (function application)
66//! - **`eta`** — η-reduction (function extensionality)
67//! - **`simp`** — Simplification using equations
68//! - **`reduce`** — Generic reducer trait with reducibility hints
69//! - **`normalize`** — Full normal form computation
70//!
71//! ### Layer 5: Inductive Types & Recursion
72//!
73//! - **`inductive`** — Inductive type validation and recursor synthesis
74//! - **`match_compile`** — Pattern matching compilation to decision trees
75//! - **`quotient`** — Quotient type operations
76//! - **`termination`** — Structural recursion validation
77//!
78//! ## Key Concepts & Terminology
79//!
80//! ### Calculus of Inductive Constructions (CiC)
81//!
82//! OxiLean implements a variant of CiC featuring:
83//! - **Dependent types**: `Π (x : A), B x` (types can depend on values)
84//! - **Universe polymorphism**: Definitions can abstract over type universe levels
85//! - **Inductive types**: Type families with auto-generated recursion principles
86//! - **Impredicativity**: `Prop : Prop` (proofs of propositions are proof-irrelevant)
87//! - **Quotient types**: Extensional equality for abstract types\
88//!
89//! ### Trust Boundary
90//!
91//! Only the kernel is trusted. External code paths:
92//! 1. **Parser** → produces AST (can contain errors/malice)
93//! 2. **Elaborator** → converts AST to kernel terms (can produce invalid proofs)
94//! 3. **Kernel** → validates every declaration independently (soundness gate)
95//!
96//! If parser or elaborator is compromised, users get proof failures, never unsoundness.
97//!
98//! ### Soundness Properties
99//!
100//! - Every type-checkable declaration is sound by construction
101//! - No `unsafe` code means memory safety is Rust's responsibility
102//! - Axiom tracking prevents unsound axioms from polluting proofs
103//! - Universe polymorphism respects level constraints
104//!
105//! ### Memory Layout
106//!
107//! Expressions live in **typed arenas**:
108//! ```text
109//! Environment
110//!   ├─ expr_arena: Arena<Expr>     // All Expr nodes
111//!   ├─ level_arena: Arena<Level>   // All universe levels
112//!   └─ name_arena: Arena<Name>     // All identifiers
113//!
114//! Idx<Expr> = u32 (not a pointer, just an index)
115//! → O(1) equality via index comparison
116//! → Cache-friendly dense layout
117//! → Deterministic, no GC pauses
118//! ```
119//!
120//! ### Reduction Strategy
121//!
122//! - **WHNF (Weak Head Normal Form)**: Reduces until top-level constructor
123//!   - `λ x. t` stays as lambda
124//!   - `App (λ x. t) a` reduces to `t[a/x]`
125//!   - Inductive pattern match reduces to branch
126//! - **Full Normal Form**: WHNF + reduce inside binders
127//! - **Eta-expansion**: Insert `λ x. f x` for functions
128//!
129//! ### Locally Nameless Representation
130//!
131//! - **Bound variables** (inside binders): de Bruijn indices (`BVar(0)` = innermost binder)
132//! - **Free variables** (global/local): unique IDs (`FVar(id)`)
133//! - **Converting between**: `abstract` (close over binder), `instantiate` (substitute FVar)\
134//!
135//! ## Module Organization
136//!
137//! ### Foundational Modules\
138//!
139//! | Module | SLOC | Purpose |
140//! |--------|------|---------|
141//! | `arena` | ~150 | Typed arena allocator |
142//! | `name` | ~200 | Hierarchical names |
143//! | `level` | ~300 | Universe level computation |
144//! | `expr` | ~400 | Expression AST definition |
145//! | `subst` | ~150 | Substitution/instantiation |
146//! | `context` | ~100 | Local context management |
147//!
148//! ### Type Checking Core
149//!
150//! | Module | SLOC | Purpose |
151//! |--------|------|---------|
152//! | `infer` | ~500 | Type inference |
153//! | `def_eq` | ~400 | Definitional equality |
154//! | `whnf` | ~300 | Weak head normal form |
155//! | `check` | ~250 | Declaration validation |
156//!
157//! ### Reduction & Normalization
158//!
159//! | Module | SLOC | Purpose |
160//! |--------|------|---------|
161//! | `beta` | ~200 | Beta reduction |
162//! | `eta` | ~150 | Eta reduction |
163//! | `reduce` | ~200 | Generic reduction infrastructure |
164//! | `simp` | ~300 | Simplification engine |
165//! | `normalize` | ~150 | Full normalization |
166//!
167//! ### Type Families
168//!
169//! | Module | SLOC | Purpose |
170//! |--------|------|---------|
171//! | `inductive` | ~500 | Inductive types and recursors |
172//! | `quotient` | ~300 | Quotient types |
173//! | `match_compile` | ~400 | Pattern match compilation |
174//! | `termination` | ~350 | Recursion validation |
175//!
176//! ### Utilities
177//!
178//! | Module | SLOC | Purpose |
179//! |--------|------|---------|
180//! | `alpha` | ~200 | Alpha equivalence |
181//! | `axiom` | ~250 | Axiom tracking and safety |
182//! | `congruence` | ~300 | Congruence closure |
183//! | `export` | ~400 | Module serialization |
184//! | `prettyprint` | ~350 | Expression printing |
185//! | `trace` | ~200 | Debug tracing |
186//!
187//! ## Usage Examples
188//!
189//! ### Example 1: Check if Two Terms Are Definitionally Equal
190//!
191//! ```ignore
192//! use oxilean_kernel::{DefEqChecker, Expr};
193//!
194//! let checker = DefEqChecker::new(&env);
195//! let eq = checker.is_def_eq(expr1, expr2)?;
196//! assert!(eq);
197//! ```
198//!
199//! ### Example 2: Normalize an Expression
200//!
201//! ```ignore
202//! use oxilean_kernel::normalize;
203//!
204//! let normal = normalize(&env, expr)?;
205//! // normal is now in full normal form\
206//! ```
207//!
208//! ### Example 3: Work with Inductive Types
209//!
210//! ```ignore
211//! use oxilean_kernel::{InductiveType, check_inductive};
212//!
213//! let nat_ind = InductiveType::new(/* ... */);
214//! check_inductive(&env, &nat_ind)?;
215//! // nat_ind is validated and recursors are synthesized
216//! ```
217//!
218//! ## Integration with Other Crates
219//!
220//! ### With `oxilean-meta`
221//!
222//! The meta layer (`oxilean-meta`) extends the kernel with:
223//! - **Metavariable support**: Holes `?m` for unification
224//! - **Meta WHNF**: WHNF aware of unsolved metavars
225//! - **Meta DefEq**: Unification that assigns metavars
226//! - **App builder**: Helpers for constructing proof terms
227//!
228//! ### With `oxilean-elab`
229//!
230//! The elaborator (`oxilean-elab`) uses the kernel for:
231//! - **Type checking**: Validates elaborated proofs via `TypeChecker`
232//! - **Definitional equality**: Checks `is_def_eq` during elaboration
233//! - **Declaration checking**: Ensures all definitions are sound
234//!
235//! ### With `oxilean-parse`
236//!
237//! The parser provides surface syntax (`Expr` types) that the elaborator converts to kernel `Expr`.
238//!
239//! ## Soundness Guarantees
240//!
241//! OxiLean provides the following soundness invariants:
242//!
243//! 1. **Type Safety**: No expression can be assigned a type that doesn't logically follow
244//! 2. **Axiom Tracking**: All uses of axioms are recorded; proofs can be reviewed for axiomatic assumptions
245//! 3. **Termination**: Recursive definitions are proven terminating before acceptance
246//! 4. **Universe Consistency**: No universe cycles or level violations
247//! 5. **Proof Irrelevance**: All proofs of the same `Prop` are definitionally equal
248//!
249//! ## Performance Characteristics
250//!
251//! - **Expression comparison**: O(1) via arena indexing
252//! - **WHNF reduction**: O(depth × reduction_steps) for typical programs
253//! - **Environment lookup**: O(log n) (indexed environment)
254//! - **Memory**: Dense arena layout, no pointer chasing
255//! - **GC**: None needed (Rust ownership handles deallocation)\
256//!
257//! ## Further Reading
258//!
259//! - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System architecture
260//! - [BLUEPRINT.md](../../BLUEPRINT.md) — Formal CiC specification
261//! - Module documentation for specific subcomponents
262
263#![forbid(unsafe_code)]
264#![allow(missing_docs)]
265#![warn(clippy::all)]
266
267pub mod arena;
268pub mod expr;
269pub mod level;
270pub mod name;
271
272// Re-exports for convenience
273pub use arena::{Arena, Idx};
274pub use expr::{BinderInfo, Expr, FVarId, Literal};
275pub use level::{Level, LevelMVarId};
276pub use name::Name;
277pub mod subst;
278
279pub use subst::{abstract_expr, instantiate};
280pub mod env;
281pub mod reduce;
282
283pub use env::{Declaration, EnvError, Environment};
284pub use reduce::{reduce_nat_op, Reducer, ReducibilityHint};
285pub mod error;
286pub mod infer;
287
288// Phase 3.1: Kernel Foundation modules
289pub mod r#abstract;
290pub mod declaration;
291pub mod equiv_manager;
292pub mod expr_cache;
293pub mod expr_util;
294pub mod instantiate;
295
296pub use declaration::{
297    instantiate_level_params, AxiomVal, ConstantInfo, ConstantVal, ConstructorVal,
298    DefinitionSafety, DefinitionVal, InductiveVal, OpaqueVal, QuotKind, QuotVal, RecursorRule,
299    RecursorVal, TheoremVal,
300};
301pub use equiv_manager::EquivManager;
302
303pub use error::KernelError;
304pub use infer::{LocalDecl, TypeChecker};
305pub mod abstract_interp;
306pub mod alpha;
307pub mod axiom;
308pub mod beta;
309pub mod builtin;
310pub mod check;
311pub mod congruence;
312pub mod context;
313pub mod conversion;
314pub mod def_eq;
315pub mod eta;
316pub mod export;
317pub mod inductive;
318pub mod match_compile;
319pub mod normalize;
320pub mod prettyprint;
321pub mod proof;
322pub mod quotient;
323pub mod reduction;
324pub mod serial;
325pub mod simp;
326pub mod struct_eta;
327pub mod substitution;
328pub mod termination;
329pub mod trace;
330pub mod type_erasure;
331pub mod typeclasses;
332pub mod universe;
333pub mod whnf;
334
335/// Benchmarking support utilities for the OxiLean kernel.
336pub mod bench_support;
337/// Caching infrastructure for performance optimization.
338pub mod cache;
339/// Foreign function interface support.
340pub mod ffi;
341/// No-std compatibility layer for constrained and WASM environments.
342pub mod no_std_compat;
343
344pub use alpha::{alpha_equiv, canonicalize};
345pub use axiom::{
346    classify_axiom, extract_axioms, has_unsafe_dependencies, transitive_axiom_deps, AxiomSafety,
347    AxiomValidator,
348};
349pub use beta::{beta_normalize, beta_step, beta_under_binder, is_beta_normal, mk_beta_redex};
350pub use builtin::{init_builtin_env, is_nat_op, is_string_op};
351pub use check::{check_constant_info, check_constant_infos, check_declaration, check_declarations};
352pub use congruence::{
353    mk_congr_theorem, mk_congr_theorem_with_fixed, CongrArgKind, CongruenceClosure,
354    CongruenceTheorem,
355};
356pub use context::{ContextSnapshot, NameGenerator};
357pub use conversion::ConversionChecker;
358pub use def_eq::{is_def_eq_simple, DefEqChecker};
359pub use eta::{eta_contract, eta_expand, is_eta_expandable};
360pub use export::{
361    deserialize_module_header, export_environment, import_module, serialize_module, ExportedModule,
362    ModuleCache,
363};
364pub use inductive::{check_inductive, reduce_recursor, InductiveEnv, InductiveType, IntroRule};
365pub use match_compile::{
366    CompileResult, ConstructorInfo as MatchConstructorInfo, DecisionTree, MatchArm, MatchCompiler,
367    Pattern,
368};
369pub use normalize::{alpha_eq_env, evaluate, is_normal_form, normalize_env, normalize_whnf};
370pub use prettyprint::{print_expr, print_expr_ascii, ExprPrinter};
371pub use proof::ProofTerm;
372pub use quotient::{
373    check_equivalence_relation, check_quot_usage, is_quot_type_expr, quot_eq, reduce_quot_lift,
374    QuotUsageKind, QuotientType,
375};
376pub use simp::{alpha_eq, normalize, simplify};
377pub use termination::{ParamInfo, RecCallInfo, TerminationChecker, TerminationResult};
378pub use trace::{TraceEvent, TraceLevel, Tracer};
379pub use typeclasses::{
380    is_class_constraint, Instance as KernelInstance, Method as KernelMethod,
381    TypeClass as KernelTypeClass, TypeClassRegistry as KernelTypeClassRegistry,
382};
383pub use universe::{UnivChecker, UnivConstraint};
384pub use whnf::{is_whnf, whnf, whnf_is_lambda, whnf_is_pi, whnf_is_sort};
385
386// ============================================================
387// Kernel utility helpers
388// ============================================================
389
390/// Version string for the OxiLean kernel.
391pub const KERNEL_VERSION: &str = env!("CARGO_PKG_VERSION");
392
393/// Return the kernel version as a `(major, minor, patch)` tuple.
394#[allow(dead_code)]
395pub fn kernel_version() -> (u32, u32, u32) {
396    let v = KERNEL_VERSION;
397    let parts: Vec<u32> = v.split('.').filter_map(|s| s.parse().ok()).collect();
398    match parts.as_slice() {
399        [major, minor, patch, ..] => (*major, *minor, *patch),
400        [major, minor] => (*major, *minor, 0),
401        [major] => (*major, 0, 0),
402        [] => (0, 0, 0),
403    }
404}
405
406/// Convenience: make a `Prop` expression (Sort 0).
407#[allow(dead_code)]
408pub fn mk_prop() -> Expr {
409    Expr::Sort(Level::zero())
410}
411
412/// Convenience: make `Type 0` (Sort 1).
413#[allow(dead_code)]
414pub fn mk_type0() -> Expr {
415    Expr::Sort(Level::succ(Level::zero()))
416}
417
418/// Convenience: make `Type 1` (Sort 2).
419#[allow(dead_code)]
420pub fn mk_type1() -> Expr {
421    Expr::Sort(Level::succ(Level::succ(Level::zero())))
422}
423
424/// Convenience: make `Sort u` for an arbitrary level.
425#[allow(dead_code)]
426pub fn mk_sort(level: Level) -> Expr {
427    Expr::Sort(level)
428}
429
430/// Convenience: make a `Nat` literal expression.
431#[allow(dead_code)]
432pub fn mk_nat_lit(n: u64) -> Expr {
433    Expr::Lit(Literal::Nat(n))
434}
435
436/// Convenience: make a `String` literal expression.
437#[allow(dead_code)]
438pub fn mk_string_lit(s: &str) -> Expr {
439    Expr::Lit(Literal::Str(s.to_string()))
440}
441
442/// Convenience: make `App(f, a)`.
443#[allow(dead_code)]
444pub fn mk_app(f: Expr, a: Expr) -> Expr {
445    Expr::App(Box::new(f), Box::new(a))
446}
447
448/// Build `f a1 a2 ... an` from a head `f` and argument list.
449#[allow(dead_code)]
450pub fn mk_app_spine(f: Expr, args: Vec<Expr>) -> Expr {
451    args.into_iter().fold(f, mk_app)
452}
453
454/// Convenience: make a Pi-type `(x : dom) -> cod`.
455#[allow(dead_code)]
456pub fn mk_pi(name: Name, dom: Expr, cod: Expr) -> Expr {
457    Expr::Pi(BinderInfo::Default, name, Box::new(dom), Box::new(cod))
458}
459
460/// Build a chain of Pi-types from a list of `(name, type)` binders and a result type.
461#[allow(dead_code)]
462pub fn mk_pi_chain(binders: Vec<(Name, Expr)>, ret: Expr) -> Expr {
463    binders
464        .into_iter()
465        .rev()
466        .fold(ret, |acc, (n, ty)| mk_pi(n, ty, acc))
467}
468
469/// Convenience: make a lambda `fun x : dom => body`.
470#[allow(dead_code)]
471pub fn mk_lam(name: Name, dom: Expr, body: Expr) -> Expr {
472    Expr::Lam(BinderInfo::Default, name, Box::new(dom), Box::new(body))
473}
474
475/// Build a chain of lambdas from a list of `(name, type)` binders and a body.
476#[allow(dead_code)]
477pub fn mk_lam_chain(binders: Vec<(Name, Expr)>, body: Expr) -> Expr {
478    binders
479        .into_iter()
480        .rev()
481        .fold(body, |acc, (n, ty)| mk_lam(n, ty, acc))
482}
483
484/// Unfold the head and arguments of an `App` spine.
485///
486/// `f a b c` → `(f, [a, b, c])`
487#[allow(dead_code)]
488pub fn unfold_app(expr: &Expr) -> (&Expr, Vec<&Expr>) {
489    let mut args = Vec::new();
490    let mut cur = expr;
491    while let Expr::App(f, a) = cur {
492        args.push(a.as_ref());
493        cur = f;
494    }
495    args.reverse();
496    (cur, args)
497}
498
499/// Count the number of arguments an expression is applied to.
500#[allow(dead_code)]
501pub fn app_arity(expr: &Expr) -> usize {
502    match expr {
503        Expr::App(f, _) => 1 + app_arity(f),
504        _ => 0,
505    }
506}
507
508/// Count Pi binders in a Pi-chain.
509#[allow(dead_code)]
510pub fn count_pi_binders(expr: &Expr) -> usize {
511    match expr {
512        Expr::Pi(_, _, _, body) => 1 + count_pi_binders(body),
513        _ => 0,
514    }
515}
516
517/// Count Lam binders in a Lambda-chain.
518#[allow(dead_code)]
519pub fn count_lam_binders(expr: &Expr) -> usize {
520    match expr {
521        Expr::Lam(_, _, _, body) => 1 + count_lam_binders(body),
522        _ => 0,
523    }
524}
525
526/// Check whether an expression is closed (contains no `BVar` with index ≥ depth).
527#[allow(dead_code)]
528pub fn is_closed(expr: &Expr) -> bool {
529    is_closed_at(expr, 0)
530}
531
532fn is_closed_at(expr: &Expr, depth: u32) -> bool {
533    match expr {
534        Expr::BVar(i) => *i < depth,
535        Expr::FVar(_) | Expr::Sort(_) | Expr::Const(_, _) | Expr::Lit(_) => true,
536        Expr::App(f, a) => is_closed_at(f, depth) && is_closed_at(a, depth),
537        Expr::Lam(_, _, dom, body) | Expr::Pi(_, _, dom, body) => {
538            is_closed_at(dom, depth) && is_closed_at(body, depth + 1)
539        }
540        Expr::Let(_, ty, val, body) => {
541            is_closed_at(ty, depth) && is_closed_at(val, depth) && is_closed_at(body, depth + 1)
542        }
543        Expr::Proj(_, _, e) => is_closed_at(e, depth),
544    }
545}
546
547/// Check whether an expression contains no `FVar`, `MVar`, or `BVar` nodes.
548#[allow(dead_code)]
549pub fn is_ground(expr: &Expr) -> bool {
550    match expr {
551        Expr::BVar(_) | Expr::FVar(_) => false,
552        Expr::Sort(_) | Expr::Lit(_) => true,
553        Expr::Const(_, _) => true,
554        Expr::App(f, a) => is_ground(f) && is_ground(a),
555        Expr::Lam(_, _, dom, body) | Expr::Pi(_, _, dom, body) => is_ground(dom) && is_ground(body),
556        Expr::Let(_, ty, val, body) => is_ground(ty) && is_ground(val) && is_ground(body),
557        Expr::Proj(_, _, e) => is_ground(e),
558    }
559}
560
561/// Compute an approximate "size" of an expression (node count).
562#[allow(dead_code)]
563pub fn expr_size(expr: &Expr) -> usize {
564    match expr {
565        Expr::BVar(_) | Expr::FVar(_) | Expr::Sort(_) | Expr::Lit(_) | Expr::Const(_, _) => 1,
566        Expr::App(f, a) => 1 + expr_size(f) + expr_size(a),
567        Expr::Lam(_, _, dom, body) | Expr::Pi(_, _, dom, body) => {
568            1 + expr_size(dom) + expr_size(body)
569        }
570        Expr::Let(_, ty, val, body) => 1 + expr_size(ty) + expr_size(val) + expr_size(body),
571        Expr::Proj(_, _, e) => 1 + expr_size(e),
572    }
573}
574
575/// Check whether an expression contains any metavariable (`MVar`).
576#[allow(dead_code)]
577pub fn has_metavars(expr: &Expr) -> bool {
578    match expr {
579        Expr::App(f, a) => has_metavars(f) || has_metavars(a),
580        Expr::Lam(_, _, dom, body) | Expr::Pi(_, _, dom, body) => {
581            has_metavars(dom) || has_metavars(body)
582        }
583        Expr::Let(_, ty, val, body) => has_metavars(ty) || has_metavars(val) || has_metavars(body),
584        Expr::Proj(_, _, e) => has_metavars(e),
585        _ => false,
586    }
587}
588
589/// Collect all `Const` names referenced in an expression.
590#[allow(dead_code)]
591pub fn collect_const_names(expr: &Expr) -> Vec<Name> {
592    let mut names = Vec::new();
593    collect_const_names_rec(expr, &mut names);
594    names
595}
596
597fn collect_const_names_rec(expr: &Expr, acc: &mut Vec<Name>) {
598    match expr {
599        Expr::Const(n, _) => acc.push(n.clone()),
600        Expr::App(f, a) => {
601            collect_const_names_rec(f, acc);
602            collect_const_names_rec(a, acc);
603        }
604        Expr::Lam(_, _, dom, body) | Expr::Pi(_, _, dom, body) => {
605            collect_const_names_rec(dom, acc);
606            collect_const_names_rec(body, acc);
607        }
608        Expr::Let(_, ty, val, body) => {
609            collect_const_names_rec(ty, acc);
610            collect_const_names_rec(val, acc);
611            collect_const_names_rec(body, acc);
612        }
613        Expr::Proj(_, _, e) => collect_const_names_rec(e, acc),
614        _ => {}
615    }
616}
617
618/// Collect all `FVar` ids referenced in an expression.
619#[allow(dead_code)]
620pub fn collect_fvars(expr: &Expr) -> Vec<FVarId> {
621    let mut fvars = Vec::new();
622    collect_fvars_rec(expr, &mut fvars);
623    fvars
624}
625
626fn collect_fvars_rec(expr: &Expr, acc: &mut Vec<FVarId>) {
627    match expr {
628        Expr::FVar(id) => acc.push(*id),
629        Expr::App(f, a) => {
630            collect_fvars_rec(f, acc);
631            collect_fvars_rec(a, acc);
632        }
633        Expr::Lam(_, _, dom, body) | Expr::Pi(_, _, dom, body) => {
634            collect_fvars_rec(dom, acc);
635            collect_fvars_rec(body, acc);
636        }
637        Expr::Let(_, ty, val, body) => {
638            collect_fvars_rec(ty, acc);
639            collect_fvars_rec(val, acc);
640            collect_fvars_rec(body, acc);
641        }
642        Expr::Proj(_, _, e) => collect_fvars_rec(e, acc),
643        _ => {}
644    }
645}
646
647/// Return the "head" of an expression (strip App arguments).
648#[allow(dead_code)]
649pub fn expr_head(expr: &Expr) -> &Expr {
650    match expr {
651        Expr::App(f, _) => expr_head(f),
652        _ => expr,
653    }
654}
655
656/// Check whether an expression is an `App` whose head is `Const(name)`.
657#[allow(dead_code)]
658pub fn is_app_of(expr: &Expr, name: &Name) -> bool {
659    matches!(expr_head(expr), Expr::Const(n, _) if n == name)
660}
661
662/// Return the maximum de Bruijn index that appears free (not under enough binders).
663///
664/// Returns `None` if no `BVar` nodes occur.
665#[allow(dead_code)]
666pub fn max_bvar_index(expr: &Expr) -> Option<u32> {
667    max_bvar_index_at(expr, 0)
668}
669
670fn max_bvar_index_at(expr: &Expr, depth: u32) -> Option<u32> {
671    match expr {
672        Expr::BVar(i) => {
673            if *i >= depth {
674                Some(*i - depth)
675            } else {
676                None
677            }
678        }
679        Expr::App(f, a) => {
680            let l = max_bvar_index_at(f, depth);
681            let r = max_bvar_index_at(a, depth);
682            match (l, r) {
683                (Some(a), Some(b)) => Some(a.max(b)),
684                (x, None) | (None, x) => x,
685            }
686        }
687        Expr::Lam(_, _, dom, body) | Expr::Pi(_, _, dom, body) => {
688            let l = max_bvar_index_at(dom, depth);
689            let r = max_bvar_index_at(body, depth + 1);
690            match (l, r) {
691                (Some(a), Some(b)) => Some(a.max(b)),
692                (x, None) | (None, x) => x,
693            }
694        }
695        Expr::Let(_, ty, val, body) => {
696            let t = max_bvar_index_at(ty, depth);
697            let v = max_bvar_index_at(val, depth);
698            let b = max_bvar_index_at(body, depth + 1);
699            [t, v, b].into_iter().flatten().reduce(|a, b| a.max(b))
700        }
701        Expr::Proj(_, _, e) => max_bvar_index_at(e, depth),
702        _ => None,
703    }
704}
705
706#[cfg(test)]
707mod kernel_util_tests {
708    use super::*;
709
710    #[test]
711    fn test_kernel_version_parses() {
712        let (major, minor, patch) = kernel_version();
713        let _ = (major, minor, patch); // just ensure it doesn't panic
714    }
715
716    #[test]
717    fn test_mk_prop() {
718        assert!(matches!(mk_prop(), Expr::Sort(l) if l == Level::zero()));
719    }
720
721    #[test]
722    fn test_mk_type0() {
723        assert!(matches!(mk_type0(), Expr::Sort(l) if l == Level::succ(Level::zero())));
724    }
725
726    #[test]
727    fn test_mk_nat_lit() {
728        assert!(matches!(mk_nat_lit(42), Expr::Lit(Literal::Nat(_))));
729    }
730
731    #[test]
732    fn test_mk_string_lit() {
733        assert!(matches!(mk_string_lit("hello"), Expr::Lit(Literal::Str(_))));
734    }
735
736    #[test]
737    fn test_mk_app_spine_empty() {
738        let f = Expr::Const(Name::str("f"), vec![]);
739        let result = mk_app_spine(f.clone(), vec![]);
740        assert_eq!(result, f);
741    }
742
743    #[test]
744    fn test_mk_app_spine() {
745        let f = Expr::Const(Name::str("f"), vec![]);
746        let a = Expr::Const(Name::str("a"), vec![]);
747        let b = Expr::Const(Name::str("b"), vec![]);
748        let result = mk_app_spine(f, vec![a, b]);
749        assert!(matches!(result, Expr::App(_, _)));
750        assert_eq!(app_arity(&result), 2);
751    }
752
753    #[test]
754    fn test_unfold_app() {
755        let f = Expr::Const(Name::str("f"), vec![]);
756        let a = Expr::Const(Name::str("a"), vec![]);
757        let b = Expr::Const(Name::str("b"), vec![]);
758        let e = mk_app_spine(f, vec![a, b]);
759        let (head, args) = unfold_app(&e);
760        assert!(matches!(head, Expr::Const(n, _) if n == &Name::str("f")));
761        assert_eq!(args.len(), 2);
762    }
763
764    #[test]
765    fn test_is_closed_bvar_0() {
766        // BVar(0) is closed at depth=1
767        let e = Expr::BVar(0);
768        assert!(!is_closed(&e)); // depth=0, so BVar(0) is NOT closed
769    }
770
771    #[test]
772    fn test_is_closed_const() {
773        let e = Expr::Const(Name::str("Nat"), vec![]);
774        assert!(is_closed(&e));
775    }
776
777    #[test]
778    fn test_is_ground_const() {
779        let e = Expr::Const(Name::str("Nat"), vec![]);
780        assert!(is_ground(&e));
781    }
782
783    #[test]
784    fn test_is_ground_fvar() {
785        let e = Expr::FVar(FVarId(0));
786        assert!(!is_ground(&e));
787    }
788
789    #[test]
790    fn test_expr_size_atom() {
791        assert_eq!(expr_size(&Expr::BVar(0)), 1);
792        assert_eq!(expr_size(&Expr::Sort(Level::zero())), 1);
793    }
794
795    #[test]
796    fn test_expr_size_app() {
797        let e = mk_app(Expr::BVar(0), Expr::BVar(1));
798        assert_eq!(expr_size(&e), 3); // App(BVar, BVar) = 1 + 1 + 1
799    }
800
801    #[test]
802    fn test_has_metavars_false() {
803        let e = Expr::Const(Name::str("f"), vec![]);
804        assert!(!has_metavars(&e));
805    }
806
807    #[test]
808    fn test_has_metavars_true() {
809        // Metavariables are represented as FVar in OxiLean kernel; has_metavars is always false for plain exprs
810        let e = Expr::FVar(FVarId(0));
811        assert!(!has_metavars(&e));
812    }
813
814    #[test]
815    fn test_collect_const_names() {
816        let e = mk_app(
817            Expr::Const(Name::str("f"), vec![]),
818            Expr::Const(Name::str("a"), vec![]),
819        );
820        let names = collect_const_names(&e);
821        assert!(names.contains(&Name::str("f")));
822        assert!(names.contains(&Name::str("a")));
823    }
824
825    #[test]
826    fn test_is_app_of() {
827        let e = mk_app(
828            Expr::Const(Name::str("List"), vec![]),
829            Expr::Const(Name::str("Nat"), vec![]),
830        );
831        assert!(is_app_of(&e, &Name::str("List")));
832        assert!(!is_app_of(&e, &Name::str("Nat")));
833    }
834
835    #[test]
836    fn test_count_pi_binders() {
837        let p = mk_pi(Name::str("x"), mk_prop(), mk_prop());
838        assert_eq!(count_pi_binders(&p), 1);
839    }
840
841    #[test]
842    fn test_count_lam_binders() {
843        let l = mk_lam(Name::str("x"), mk_prop(), Expr::BVar(0));
844        assert_eq!(count_lam_binders(&l), 1);
845    }
846}
847
848// ── Additional kernel utilities ───────────────────────────────────────────────
849
850/// Returns true if an expression contains any `Let` binders.
851#[allow(dead_code)]
852pub fn has_let_binders(expr: &Expr) -> bool {
853    match expr {
854        Expr::Let(_, ty, val, body) => {
855            let _ = (
856                has_let_binders(ty),
857                has_let_binders(val),
858                has_let_binders(body),
859            );
860            true
861        }
862        Expr::App(f, a) => has_let_binders(f) || has_let_binders(a),
863        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
864            has_let_binders(ty) || has_let_binders(body)
865        }
866        Expr::Proj(_, _, e) => has_let_binders(e),
867        _ => false,
868    }
869}
870
871/// Returns true if an expression contains any projections.
872#[allow(dead_code)]
873pub fn has_projections(expr: &Expr) -> bool {
874    match expr {
875        Expr::Proj(_, _, _) => true,
876        Expr::App(f, a) => has_projections(f) || has_projections(a),
877        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
878            has_projections(ty) || has_projections(body)
879        }
880        Expr::Let(_, ty, val, body) => {
881            has_projections(ty) || has_projections(val) || has_projections(body)
882        }
883        _ => false,
884    }
885}
886
887/// Count the number of `App` nodes in an expression.
888#[allow(dead_code)]
889pub fn count_apps(expr: &Expr) -> usize {
890    match expr {
891        Expr::App(f, a) => 1 + count_apps(f) + count_apps(a),
892        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => count_apps(ty) + count_apps(body),
893        Expr::Let(_, ty, val, body) => count_apps(ty) + count_apps(val) + count_apps(body),
894        Expr::Proj(_, _, e) => count_apps(e),
895        _ => 0,
896    }
897}
898
899/// Count all sort occurrences in an expression.
900#[allow(dead_code)]
901pub fn count_sorts(expr: &Expr) -> usize {
902    match expr {
903        Expr::Sort(_) => 1,
904        Expr::App(f, a) => count_sorts(f) + count_sorts(a),
905        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => count_sorts(ty) + count_sorts(body),
906        Expr::Let(_, ty, val, body) => count_sorts(ty) + count_sorts(val) + count_sorts(body),
907        Expr::Proj(_, _, e) => count_sorts(e),
908        _ => 0,
909    }
910}
911
912/// Check whether an expression is a literal.
913#[allow(dead_code)]
914pub fn is_literal(expr: &Expr) -> bool {
915    matches!(expr, Expr::Lit(_))
916}
917
918/// Check whether an expression is a sort.
919#[allow(dead_code)]
920pub fn is_sort(expr: &Expr) -> bool {
921    matches!(expr, Expr::Sort(_))
922}
923
924/// Check whether an expression is a Pi-type (possibly nested).
925#[allow(dead_code)]
926pub fn is_pi(expr: &Expr) -> bool {
927    matches!(expr, Expr::Pi(_, _, _, _))
928}
929
930/// Check whether an expression is a lambda.
931#[allow(dead_code)]
932pub fn is_lam(expr: &Expr) -> bool {
933    matches!(expr, Expr::Lam(_, _, _, _))
934}
935
936/// Check whether an expression is an application.
937#[allow(dead_code)]
938pub fn is_app(expr: &Expr) -> bool {
939    matches!(expr, Expr::App(_, _))
940}
941
942/// Check whether an expression is a constant.
943#[allow(dead_code)]
944pub fn is_const(expr: &Expr) -> bool {
945    matches!(expr, Expr::Const(_, _))
946}
947
948/// Get the name of a constant, or None if not a constant.
949#[allow(dead_code)]
950pub fn const_name(expr: &Expr) -> Option<&Name> {
951    match expr {
952        Expr::Const(n, _) => Some(n),
953        _ => None,
954    }
955}
956
957/// Strip outer Pi binders, collecting binder info.
958///
959/// Returns `(binders, inner_type)` where `binders` is a list of `(BinderInfo, Name, domain_type)`.
960#[allow(dead_code)]
961pub fn strip_pi_binders(expr: &Expr) -> (Vec<(BinderInfo, Name, Expr)>, &Expr) {
962    let mut binders = Vec::new();
963    let mut current = expr;
964    while let Expr::Pi(bi, n, ty, body) = current {
965        binders.push((*bi, n.clone(), ty.as_ref().clone()));
966        current = body;
967    }
968    (binders, current)
969}
970
971/// Strip outer lambda binders, collecting binder info.
972///
973/// Returns `(binders, body)` where `binders` is a list of `(BinderInfo, Name, domain_type)`.
974#[allow(dead_code)]
975pub fn strip_lam_binders(expr: &Expr) -> (Vec<(BinderInfo, Name, Expr)>, &Expr) {
976    let mut binders = Vec::new();
977    let mut current = expr;
978    while let Expr::Lam(bi, n, ty, body) = current {
979        binders.push((*bi, n.clone(), ty.as_ref().clone()));
980        current = body;
981    }
982    (binders, current)
983}
984
985/// Build a Pi type from a list of binders and an inner type.
986#[allow(dead_code)]
987pub fn build_pi_from_binders(binders: &[(BinderInfo, Name, Expr)], inner: Expr) -> Expr {
988    binders.iter().rev().fold(inner, |acc, (bi, n, ty)| {
989        Expr::Pi(*bi, n.clone(), Box::new(ty.clone()), Box::new(acc))
990    })
991}
992
993/// Build a lambda from a list of binders and a body.
994#[allow(dead_code)]
995pub fn build_lam_from_binders(binders: &[(BinderInfo, Name, Expr)], body: Expr) -> Expr {
996    binders.iter().rev().fold(body, |acc, (bi, n, ty)| {
997        Expr::Lam(*bi, n.clone(), Box::new(ty.clone()), Box::new(acc))
998    })
999}
1000
1001/// Replace all occurrences of a constant by another expression.
1002///
1003/// Traverses the expression and substitutes `replacement` for every
1004/// `Const(name, _)` node.
1005#[allow(dead_code)]
1006pub fn replace_const(expr: &Expr, name: &Name, replacement: &Expr) -> Expr {
1007    match expr {
1008        Expr::Const(n, _) if n == name => replacement.clone(),
1009        Expr::App(f, a) => Expr::App(
1010            Box::new(replace_const(f, name, replacement)),
1011            Box::new(replace_const(a, name, replacement)),
1012        ),
1013        Expr::Lam(bi, n, ty, body) => Expr::Lam(
1014            *bi,
1015            n.clone(),
1016            Box::new(replace_const(ty, name, replacement)),
1017            Box::new(replace_const(body, name, replacement)),
1018        ),
1019        Expr::Pi(bi, n, ty, body) => Expr::Pi(
1020            *bi,
1021            n.clone(),
1022            Box::new(replace_const(ty, name, replacement)),
1023            Box::new(replace_const(body, name, replacement)),
1024        ),
1025        Expr::Let(n, ty, val, body) => Expr::Let(
1026            n.clone(),
1027            Box::new(replace_const(ty, name, replacement)),
1028            Box::new(replace_const(val, name, replacement)),
1029            Box::new(replace_const(body, name, replacement)),
1030        ),
1031        Expr::Proj(n, i, s) => {
1032            Expr::Proj(n.clone(), *i, Box::new(replace_const(s, name, replacement)))
1033        }
1034        e => e.clone(),
1035    }
1036}
1037
1038/// Check if an expression is eta-reducible at the top level.
1039///
1040/// An expression `λ x. f x` is eta-reducible to `f` when `x` does not occur free in `f`.
1041#[allow(dead_code)]
1042pub fn is_eta_reducible(expr: &Expr) -> bool {
1043    match expr {
1044        Expr::Lam(_, _, _, body) => {
1045            if let Expr::App(f, a) = body.as_ref() {
1046                if matches!(a.as_ref(), Expr::BVar(0)) {
1047                    // Check that BVar(0) doesn't appear free in f
1048                    return !contains_bvar(f, 0, 0);
1049                }
1050            }
1051            false
1052        }
1053        _ => false,
1054    }
1055}
1056
1057/// Check if `BVar(idx + depth)` occurs in `expr` at the given depth.
1058#[allow(dead_code)]
1059pub fn contains_bvar(expr: &Expr, idx: u32, depth: u32) -> bool {
1060    match expr {
1061        Expr::BVar(i) => *i == idx + depth,
1062        Expr::App(f, a) => contains_bvar(f, idx, depth) || contains_bvar(a, idx, depth),
1063        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
1064            contains_bvar(ty, idx, depth) || contains_bvar(body, idx, depth + 1)
1065        }
1066        Expr::Let(_, ty, val, body) => {
1067            contains_bvar(ty, idx, depth)
1068                || contains_bvar(val, idx, depth)
1069                || contains_bvar(body, idx, depth + 1)
1070        }
1071        Expr::Proj(_, _, s) => contains_bvar(s, idx, depth),
1072        _ => false,
1073    }
1074}
1075
1076/// Check if two expressions are syntactically equal (no alpha equivalence, just `==`).
1077#[allow(dead_code)]
1078pub fn syntactically_equal(e1: &Expr, e2: &Expr) -> bool {
1079    e1 == e2
1080}
1081
1082/// Collect all literals occurring in an expression.
1083#[allow(dead_code)]
1084pub fn collect_literals(expr: &Expr) -> Vec<Literal> {
1085    let mut lits = Vec::new();
1086    collect_lits_rec(expr, &mut lits);
1087    lits
1088}
1089
1090fn collect_lits_rec(expr: &Expr, acc: &mut Vec<Literal>) {
1091    match expr {
1092        Expr::Lit(l) => acc.push(l.clone()),
1093        Expr::App(f, a) => {
1094            collect_lits_rec(f, acc);
1095            collect_lits_rec(a, acc);
1096        }
1097        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
1098            collect_lits_rec(ty, acc);
1099            collect_lits_rec(body, acc);
1100        }
1101        Expr::Let(_, ty, val, body) => {
1102            collect_lits_rec(ty, acc);
1103            collect_lits_rec(val, acc);
1104            collect_lits_rec(body, acc);
1105        }
1106        Expr::Proj(_, _, e) => collect_lits_rec(e, acc),
1107        _ => {}
1108    }
1109}
1110
1111/// Return the depth of the deepest nested binder.
1112#[allow(dead_code)]
1113pub fn max_binder_depth(expr: &Expr) -> u32 {
1114    max_binder_depth_impl(expr, 0)
1115}
1116
1117fn max_binder_depth_impl(expr: &Expr, depth: u32) -> u32 {
1118    match expr {
1119        Expr::Lam(_, _, ty, body) | Expr::Pi(_, _, ty, body) => {
1120            let ty_d = max_binder_depth_impl(ty, depth);
1121            let body_d = max_binder_depth_impl(body, depth + 1);
1122            ty_d.max(body_d).max(depth + 1)
1123        }
1124        Expr::Let(_, ty, val, body) => {
1125            let ty_d = max_binder_depth_impl(ty, depth);
1126            let val_d = max_binder_depth_impl(val, depth);
1127            let body_d = max_binder_depth_impl(body, depth + 1);
1128            ty_d.max(val_d).max(body_d)
1129        }
1130        Expr::App(f, a) => max_binder_depth_impl(f, depth).max(max_binder_depth_impl(a, depth)),
1131        Expr::Proj(_, _, e) => max_binder_depth_impl(e, depth),
1132        _ => depth,
1133    }
1134}
1135
1136#[cfg(test)]
1137mod kernel_extra_tests {
1138    use super::*;
1139
1140    fn nat() -> Expr {
1141        Expr::Const(Name::str("Nat"), vec![])
1142    }
1143    fn prop() -> Expr {
1144        Expr::Sort(Level::zero())
1145    }
1146
1147    #[test]
1148    fn test_is_literal_true() {
1149        assert!(is_literal(&Expr::Lit(Literal::Nat(42))));
1150    }
1151
1152    #[test]
1153    fn test_is_literal_false() {
1154        assert!(!is_literal(&nat()));
1155    }
1156
1157    #[test]
1158    fn test_is_sort() {
1159        assert!(is_sort(&prop()));
1160        assert!(!is_sort(&nat()));
1161    }
1162
1163    #[test]
1164    fn test_is_pi() {
1165        let p = mk_pi(Name::str("x"), prop(), prop());
1166        assert!(is_pi(&p));
1167        assert!(!is_pi(&nat()));
1168    }
1169
1170    #[test]
1171    fn test_is_lam() {
1172        let l = mk_lam(Name::str("x"), prop(), Expr::BVar(0));
1173        assert!(is_lam(&l));
1174        assert!(!is_lam(&nat()));
1175    }
1176
1177    #[test]
1178    fn test_is_app() {
1179        let e = mk_app(nat(), nat());
1180        assert!(is_app(&e));
1181        assert!(!is_app(&nat()));
1182    }
1183
1184    #[test]
1185    fn test_is_const() {
1186        assert!(is_const(&nat()));
1187        assert!(!is_const(&Expr::BVar(0)));
1188    }
1189
1190    #[test]
1191    fn test_const_name() {
1192        assert_eq!(const_name(&nat()), Some(&Name::str("Nat")));
1193        assert!(const_name(&Expr::BVar(0)).is_none());
1194    }
1195
1196    #[test]
1197    fn test_strip_pi_binders_none() {
1198        let nat_expr = nat();
1199        let (binders, inner) = strip_pi_binders(&nat_expr);
1200        assert!(binders.is_empty());
1201        assert_eq!(*inner, nat());
1202    }
1203
1204    #[test]
1205    fn test_strip_pi_binders_one() {
1206        let p = mk_pi(Name::str("x"), prop(), prop());
1207        let (binders, _inner) = strip_pi_binders(&p);
1208        assert_eq!(binders.len(), 1);
1209    }
1210
1211    #[test]
1212    fn test_strip_lam_binders_one() {
1213        let l = mk_lam(Name::str("x"), prop(), Expr::BVar(0));
1214        let (binders, _body) = strip_lam_binders(&l);
1215        assert_eq!(binders.len(), 1);
1216    }
1217
1218    #[test]
1219    fn test_build_pi_from_binders() {
1220        let binders = vec![(BinderInfo::Default, Name::str("x"), prop())];
1221        let ty = build_pi_from_binders(&binders, prop());
1222        assert!(is_pi(&ty));
1223    }
1224
1225    #[test]
1226    fn test_build_lam_from_binders() {
1227        let binders = vec![(BinderInfo::Default, Name::str("x"), prop())];
1228        let l = build_lam_from_binders(&binders, Expr::BVar(0));
1229        assert!(is_lam(&l));
1230    }
1231
1232    #[test]
1233    fn test_replace_const() {
1234        let e = nat();
1235        let result = replace_const(&e, &Name::str("Nat"), &prop());
1236        assert_eq!(result, prop());
1237    }
1238
1239    #[test]
1240    fn test_replace_const_in_app() {
1241        let e = mk_app(nat(), nat());
1242        let result = replace_const(&e, &Name::str("Nat"), &prop());
1243        if let Expr::App(f, a) = &result {
1244            assert_eq!(**f, prop());
1245            assert_eq!(**a, prop());
1246        }
1247    }
1248
1249    #[test]
1250    fn test_count_apps_zero() {
1251        assert_eq!(count_apps(&nat()), 0);
1252    }
1253
1254    #[test]
1255    fn test_count_apps_one() {
1256        let e = mk_app(nat(), nat());
1257        assert_eq!(count_apps(&e), 1);
1258    }
1259
1260    #[test]
1261    fn test_count_sorts_one() {
1262        assert_eq!(count_sorts(&prop()), 1);
1263    }
1264
1265    #[test]
1266    fn test_count_sorts_zero() {
1267        assert_eq!(count_sorts(&nat()), 0);
1268    }
1269
1270    #[test]
1271    fn test_contains_bvar_true() {
1272        assert!(contains_bvar(&Expr::BVar(0), 0, 0));
1273    }
1274
1275    #[test]
1276    fn test_contains_bvar_false() {
1277        assert!(!contains_bvar(&Expr::BVar(1), 0, 0));
1278    }
1279
1280    #[test]
1281    fn test_syntactically_equal() {
1282        assert!(syntactically_equal(&nat(), &nat()));
1283        assert!(!syntactically_equal(&nat(), &prop()));
1284    }
1285
1286    #[test]
1287    fn test_collect_literals() {
1288        let e = mk_app(Expr::Lit(Literal::Nat(1)), Expr::Lit(Literal::Nat(2)));
1289        let lits = collect_literals(&e);
1290        assert_eq!(lits.len(), 2);
1291    }
1292
1293    #[test]
1294    fn test_max_binder_depth_zero() {
1295        assert_eq!(max_binder_depth(&nat()), 0);
1296    }
1297
1298    #[test]
1299    fn test_max_binder_depth_one() {
1300        let l = mk_lam(Name::str("x"), prop(), Expr::BVar(0));
1301        assert_eq!(max_binder_depth(&l), 1);
1302    }
1303
1304    #[test]
1305    fn test_is_eta_reducible_false() {
1306        assert!(!is_eta_reducible(&nat()));
1307        // λ x. f x  where x = BVar(0)
1308        let not_eta = Expr::Lam(
1309            BinderInfo::Default,
1310            Name::str("x"),
1311            Box::new(prop()),
1312            Box::new(Expr::App(
1313                Box::new(Expr::BVar(0)), // f = BVar(0) (x itself, not a function)
1314                Box::new(Expr::BVar(0)),
1315            )),
1316        );
1317        assert!(!is_eta_reducible(&not_eta));
1318    }
1319
1320    #[test]
1321    fn test_has_let_binders_false() {
1322        assert!(!has_let_binders(&nat()));
1323        assert!(!has_let_binders(&mk_pi(Name::str("x"), prop(), prop())));
1324    }
1325
1326    #[test]
1327    fn test_has_projections_false() {
1328        assert!(!has_projections(&nat()));
1329        assert!(!has_projections(&Expr::BVar(0)));
1330    }
1331}
1332
1333// ─── Padding infrastructure ──────────────────────────────────────────────────
1334
1335/// A generic counter that tracks min/max/sum for statistical summaries.
1336#[allow(dead_code)]
1337pub struct StatSummary {
1338    count: u64,
1339    sum: f64,
1340    min: f64,
1341    max: f64,
1342}
1343
1344#[allow(dead_code)]
1345impl StatSummary {
1346    /// Creates an empty summary.
1347    pub fn new() -> Self {
1348        Self {
1349            count: 0,
1350            sum: 0.0,
1351            min: f64::INFINITY,
1352            max: f64::NEG_INFINITY,
1353        }
1354    }
1355
1356    /// Records a sample.
1357    pub fn record(&mut self, val: f64) {
1358        self.count += 1;
1359        self.sum += val;
1360        if val < self.min {
1361            self.min = val;
1362        }
1363        if val > self.max {
1364            self.max = val;
1365        }
1366    }
1367
1368    /// Returns the mean, or `None` if no samples.
1369    pub fn mean(&self) -> Option<f64> {
1370        if self.count == 0 {
1371            None
1372        } else {
1373            Some(self.sum / self.count as f64)
1374        }
1375    }
1376
1377    /// Returns the minimum, or `None` if no samples.
1378    pub fn min(&self) -> Option<f64> {
1379        if self.count == 0 {
1380            None
1381        } else {
1382            Some(self.min)
1383        }
1384    }
1385
1386    /// Returns the maximum, or `None` if no samples.
1387    pub fn max(&self) -> Option<f64> {
1388        if self.count == 0 {
1389            None
1390        } else {
1391            Some(self.max)
1392        }
1393    }
1394
1395    /// Returns the count of recorded samples.
1396    pub fn count(&self) -> u64 {
1397        self.count
1398    }
1399}
1400
1401impl Default for StatSummary {
1402    fn default() -> Self {
1403        Self::new()
1404    }
1405}
1406
1407/// A pair of `StatSummary` values tracking before/after a transformation.
1408#[allow(dead_code)]
1409pub struct TransformStat {
1410    before: StatSummary,
1411    after: StatSummary,
1412}
1413
1414#[allow(dead_code)]
1415impl TransformStat {
1416    /// Creates a new transform stat recorder.
1417    pub fn new() -> Self {
1418        Self {
1419            before: StatSummary::new(),
1420            after: StatSummary::new(),
1421        }
1422    }
1423
1424    /// Records a before value.
1425    pub fn record_before(&mut self, v: f64) {
1426        self.before.record(v);
1427    }
1428
1429    /// Records an after value.
1430    pub fn record_after(&mut self, v: f64) {
1431        self.after.record(v);
1432    }
1433
1434    /// Returns the mean reduction ratio (after/before).
1435    pub fn mean_ratio(&self) -> Option<f64> {
1436        let b = self.before.mean()?;
1437        let a = self.after.mean()?;
1438        if b.abs() < f64::EPSILON {
1439            return None;
1440        }
1441        Some(a / b)
1442    }
1443}
1444
1445impl Default for TransformStat {
1446    fn default() -> Self {
1447        Self::new()
1448    }
1449}
1450
1451/// A simple key-value store backed by a sorted Vec for small maps.
1452#[allow(dead_code)]
1453pub struct SmallMap<K: Ord + Clone, V: Clone> {
1454    entries: Vec<(K, V)>,
1455}
1456
1457#[allow(dead_code)]
1458impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
1459    /// Creates a new empty small map.
1460    pub fn new() -> Self {
1461        Self {
1462            entries: Vec::new(),
1463        }
1464    }
1465
1466    /// Inserts or replaces the value for `key`.
1467    pub fn insert(&mut self, key: K, val: V) {
1468        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
1469            Ok(i) => self.entries[i].1 = val,
1470            Err(i) => self.entries.insert(i, (key, val)),
1471        }
1472    }
1473
1474    /// Returns the value for `key`, or `None`.
1475    pub fn get(&self, key: &K) -> Option<&V> {
1476        self.entries
1477            .binary_search_by_key(&key, |(k, _)| k)
1478            .ok()
1479            .map(|i| &self.entries[i].1)
1480    }
1481
1482    /// Returns the number of entries.
1483    pub fn len(&self) -> usize {
1484        self.entries.len()
1485    }
1486
1487    /// Returns `true` if empty.
1488    pub fn is_empty(&self) -> bool {
1489        self.entries.is_empty()
1490    }
1491
1492    /// Returns all keys.
1493    pub fn keys(&self) -> Vec<&K> {
1494        self.entries.iter().map(|(k, _)| k).collect()
1495    }
1496
1497    /// Returns all values.
1498    pub fn values(&self) -> Vec<&V> {
1499        self.entries.iter().map(|(_, v)| v).collect()
1500    }
1501}
1502
1503impl<K: Ord + Clone, V: Clone> Default for SmallMap<K, V> {
1504    fn default() -> Self {
1505        Self::new()
1506    }
1507}
1508
1509/// A label set for a graph node.
1510#[allow(dead_code)]
1511pub struct LabelSet {
1512    labels: Vec<String>,
1513}
1514
1515#[allow(dead_code)]
1516impl LabelSet {
1517    /// Creates a new empty label set.
1518    pub fn new() -> Self {
1519        Self { labels: Vec::new() }
1520    }
1521
1522    /// Adds a label (deduplicates).
1523    pub fn add(&mut self, label: impl Into<String>) {
1524        let s = label.into();
1525        if !self.labels.contains(&s) {
1526            self.labels.push(s);
1527        }
1528    }
1529
1530    /// Returns `true` if `label` is present.
1531    pub fn has(&self, label: &str) -> bool {
1532        self.labels.iter().any(|l| l == label)
1533    }
1534
1535    /// Returns the count of labels.
1536    pub fn count(&self) -> usize {
1537        self.labels.len()
1538    }
1539
1540    /// Returns all labels.
1541    pub fn all(&self) -> &[String] {
1542        &self.labels
1543    }
1544}
1545
1546impl Default for LabelSet {
1547    fn default() -> Self {
1548        Self::new()
1549    }
1550}
1551
1552/// A hierarchical configuration tree.
1553#[allow(dead_code)]
1554pub struct ConfigNode {
1555    key: String,
1556    value: Option<String>,
1557    children: Vec<ConfigNode>,
1558}
1559
1560#[allow(dead_code)]
1561impl ConfigNode {
1562    /// Creates a leaf config node with a value.
1563    pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
1564        Self {
1565            key: key.into(),
1566            value: Some(value.into()),
1567            children: Vec::new(),
1568        }
1569    }
1570
1571    /// Creates a section node with children.
1572    pub fn section(key: impl Into<String>) -> Self {
1573        Self {
1574            key: key.into(),
1575            value: None,
1576            children: Vec::new(),
1577        }
1578    }
1579
1580    /// Adds a child node.
1581    pub fn add_child(&mut self, child: ConfigNode) {
1582        self.children.push(child);
1583    }
1584
1585    /// Returns the key.
1586    pub fn key(&self) -> &str {
1587        &self.key
1588    }
1589
1590    /// Returns the value, or `None` for section nodes.
1591    pub fn value(&self) -> Option<&str> {
1592        self.value.as_deref()
1593    }
1594
1595    /// Returns the number of children.
1596    pub fn num_children(&self) -> usize {
1597        self.children.len()
1598    }
1599
1600    /// Looks up a dot-separated path.
1601    pub fn lookup(&self, path: &str) -> Option<&str> {
1602        let mut parts = path.splitn(2, '.');
1603        let head = parts.next()?;
1604        let tail = parts.next();
1605        if head != self.key {
1606            return None;
1607        }
1608        match tail {
1609            None => self.value.as_deref(),
1610            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
1611        }
1612    }
1613
1614    fn lookup_relative(&self, path: &str) -> Option<&str> {
1615        let mut parts = path.splitn(2, '.');
1616        let head = parts.next()?;
1617        let tail = parts.next();
1618        if head != self.key {
1619            return None;
1620        }
1621        match tail {
1622            None => self.value.as_deref(),
1623            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
1624        }
1625    }
1626}
1627
1628/// A versioned record that stores a history of values.
1629#[allow(dead_code)]
1630pub struct VersionedRecord<T: Clone> {
1631    history: Vec<T>,
1632}
1633
1634#[allow(dead_code)]
1635impl<T: Clone> VersionedRecord<T> {
1636    /// Creates a new record with an initial value.
1637    pub fn new(initial: T) -> Self {
1638        Self {
1639            history: vec![initial],
1640        }
1641    }
1642
1643    /// Updates the record with a new version.
1644    pub fn update(&mut self, val: T) {
1645        self.history.push(val);
1646    }
1647
1648    /// Returns the current (latest) value.
1649    pub fn current(&self) -> &T {
1650        self.history
1651            .last()
1652            .expect("VersionedRecord history is always non-empty after construction")
1653    }
1654
1655    /// Returns the value at version `n` (0-indexed), or `None`.
1656    pub fn at_version(&self, n: usize) -> Option<&T> {
1657        self.history.get(n)
1658    }
1659
1660    /// Returns the version number of the current value.
1661    pub fn version(&self) -> usize {
1662        self.history.len() - 1
1663    }
1664
1665    /// Returns `true` if more than one version exists.
1666    pub fn has_history(&self) -> bool {
1667        self.history.len() > 1
1668    }
1669}
1670
1671/// A simple directed acyclic graph.
1672#[allow(dead_code)]
1673pub struct SimpleDag {
1674    /// `edges[i]` is the list of direct successors of node `i`.
1675    edges: Vec<Vec<usize>>,
1676}
1677
1678#[allow(dead_code)]
1679impl SimpleDag {
1680    /// Creates a DAG with `n` nodes and no edges.
1681    pub fn new(n: usize) -> Self {
1682        Self {
1683            edges: vec![Vec::new(); n],
1684        }
1685    }
1686
1687    /// Adds an edge from `from` to `to`.
1688    pub fn add_edge(&mut self, from: usize, to: usize) {
1689        if from < self.edges.len() {
1690            self.edges[from].push(to);
1691        }
1692    }
1693
1694    /// Returns the successors of `node`.
1695    pub fn successors(&self, node: usize) -> &[usize] {
1696        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
1697    }
1698
1699    /// Returns `true` if `from` can reach `to` via DFS.
1700    pub fn can_reach(&self, from: usize, to: usize) -> bool {
1701        let mut visited = vec![false; self.edges.len()];
1702        self.dfs(from, to, &mut visited)
1703    }
1704
1705    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
1706        if cur == target {
1707            return true;
1708        }
1709        if cur >= visited.len() || visited[cur] {
1710            return false;
1711        }
1712        visited[cur] = true;
1713        for &next in self.successors(cur) {
1714            if self.dfs(next, target, visited) {
1715                return true;
1716            }
1717        }
1718        false
1719    }
1720
1721    /// Returns the topological order of nodes, or `None` if a cycle is detected.
1722    pub fn topological_sort(&self) -> Option<Vec<usize>> {
1723        let n = self.edges.len();
1724        let mut in_degree = vec![0usize; n];
1725        for succs in &self.edges {
1726            for &s in succs {
1727                if s < n {
1728                    in_degree[s] += 1;
1729                }
1730            }
1731        }
1732        let mut queue: std::collections::VecDeque<usize> =
1733            (0..n).filter(|&i| in_degree[i] == 0).collect();
1734        let mut order = Vec::new();
1735        while let Some(node) = queue.pop_front() {
1736            order.push(node);
1737            for &s in self.successors(node) {
1738                if s < n {
1739                    in_degree[s] -= 1;
1740                    if in_degree[s] == 0 {
1741                        queue.push_back(s);
1742                    }
1743                }
1744            }
1745        }
1746        if order.len() == n {
1747            Some(order)
1748        } else {
1749            None
1750        }
1751    }
1752
1753    /// Returns the number of nodes.
1754    pub fn num_nodes(&self) -> usize {
1755        self.edges.len()
1756    }
1757}
1758
1759/// A mutable reference stack for tracking the current "focus" in a tree traversal.
1760#[allow(dead_code)]
1761pub struct FocusStack<T> {
1762    items: Vec<T>,
1763}
1764
1765#[allow(dead_code)]
1766impl<T> FocusStack<T> {
1767    /// Creates an empty focus stack.
1768    pub fn new() -> Self {
1769        Self { items: Vec::new() }
1770    }
1771
1772    /// Focuses on `item`.
1773    pub fn focus(&mut self, item: T) {
1774        self.items.push(item);
1775    }
1776
1777    /// Blurs (pops) the current focus.
1778    pub fn blur(&mut self) -> Option<T> {
1779        self.items.pop()
1780    }
1781
1782    /// Returns the current focus, or `None`.
1783    pub fn current(&self) -> Option<&T> {
1784        self.items.last()
1785    }
1786
1787    /// Returns the focus depth.
1788    pub fn depth(&self) -> usize {
1789        self.items.len()
1790    }
1791
1792    /// Returns `true` if there is no current focus.
1793    pub fn is_empty(&self) -> bool {
1794        self.items.is_empty()
1795    }
1796}
1797
1798impl<T> Default for FocusStack<T> {
1799    fn default() -> Self {
1800        Self::new()
1801    }
1802}
1803
1804#[cfg(test)]
1805mod tests_padding_infra {
1806    use super::*;
1807
1808    #[test]
1809    fn test_stat_summary() {
1810        let mut ss = StatSummary::new();
1811        ss.record(10.0);
1812        ss.record(20.0);
1813        ss.record(30.0);
1814        assert_eq!(ss.count(), 3);
1815        assert!((ss.mean().expect("mean should succeed") - 20.0).abs() < 1e-9);
1816        assert_eq!(ss.min().expect("min should succeed") as i64, 10);
1817        assert_eq!(ss.max().expect("max should succeed") as i64, 30);
1818    }
1819
1820    #[test]
1821    fn test_transform_stat() {
1822        let mut ts = TransformStat::new();
1823        ts.record_before(100.0);
1824        ts.record_after(80.0);
1825        let ratio = ts.mean_ratio().expect("ratio should be present");
1826        assert!((ratio - 0.8).abs() < 1e-9);
1827    }
1828
1829    #[test]
1830    fn test_small_map() {
1831        let mut m: SmallMap<u32, &str> = SmallMap::new();
1832        m.insert(3, "three");
1833        m.insert(1, "one");
1834        m.insert(2, "two");
1835        assert_eq!(m.get(&2), Some(&"two"));
1836        assert_eq!(m.len(), 3);
1837        // Keys should be sorted
1838        let keys = m.keys();
1839        assert_eq!(*keys[0], 1);
1840        assert_eq!(*keys[2], 3);
1841    }
1842
1843    #[test]
1844    fn test_label_set() {
1845        let mut ls = LabelSet::new();
1846        ls.add("foo");
1847        ls.add("bar");
1848        ls.add("foo"); // duplicate
1849        assert_eq!(ls.count(), 2);
1850        assert!(ls.has("bar"));
1851        assert!(!ls.has("baz"));
1852    }
1853
1854    #[test]
1855    fn test_config_node() {
1856        let mut root = ConfigNode::section("root");
1857        let child = ConfigNode::leaf("key", "value");
1858        root.add_child(child);
1859        assert_eq!(root.num_children(), 1);
1860    }
1861
1862    #[test]
1863    fn test_versioned_record() {
1864        let mut vr = VersionedRecord::new(0u32);
1865        vr.update(1);
1866        vr.update(2);
1867        assert_eq!(*vr.current(), 2);
1868        assert_eq!(vr.version(), 2);
1869        assert!(vr.has_history());
1870        assert_eq!(*vr.at_version(0).expect("value should be present"), 0);
1871    }
1872
1873    #[test]
1874    fn test_simple_dag() {
1875        let mut dag = SimpleDag::new(4);
1876        dag.add_edge(0, 1);
1877        dag.add_edge(1, 2);
1878        dag.add_edge(2, 3);
1879        assert!(dag.can_reach(0, 3));
1880        assert!(!dag.can_reach(3, 0));
1881        let order = dag.topological_sort().expect("order should be present");
1882        assert_eq!(order, vec![0, 1, 2, 3]);
1883    }
1884
1885    #[test]
1886    fn test_focus_stack() {
1887        let mut fs: FocusStack<&str> = FocusStack::new();
1888        fs.focus("a");
1889        fs.focus("b");
1890        assert_eq!(fs.current(), Some(&"b"));
1891        assert_eq!(fs.depth(), 2);
1892        fs.blur();
1893        assert_eq!(fs.current(), Some(&"a"));
1894    }
1895}
1896
1897/// A window iterator that yields overlapping windows of size `n`.
1898#[allow(dead_code)]
1899pub struct WindowIterator<'a, T> {
1900    data: &'a [T],
1901    pos: usize,
1902    window: usize,
1903}
1904
1905#[allow(dead_code)]
1906impl<'a, T> WindowIterator<'a, T> {
1907    /// Creates a new window iterator.
1908    pub fn new(data: &'a [T], window: usize) -> Self {
1909        Self {
1910            data,
1911            pos: 0,
1912            window,
1913        }
1914    }
1915}
1916
1917impl<'a, T> Iterator for WindowIterator<'a, T> {
1918    type Item = &'a [T];
1919
1920    fn next(&mut self) -> Option<Self::Item> {
1921        if self.pos + self.window > self.data.len() {
1922            return None;
1923        }
1924        let slice = &self.data[self.pos..self.pos + self.window];
1925        self.pos += 1;
1926        Some(slice)
1927    }
1928}
1929
1930/// A non-empty list (at least one element guaranteed).
1931#[allow(dead_code)]
1932pub struct NonEmptyVec<T> {
1933    head: T,
1934    tail: Vec<T>,
1935}
1936
1937#[allow(dead_code)]
1938impl<T> NonEmptyVec<T> {
1939    /// Creates a non-empty vec with a single element.
1940    pub fn singleton(val: T) -> Self {
1941        Self {
1942            head: val,
1943            tail: Vec::new(),
1944        }
1945    }
1946
1947    /// Pushes an element.
1948    pub fn push(&mut self, val: T) {
1949        self.tail.push(val);
1950    }
1951
1952    /// Returns a reference to the first element.
1953    pub fn first(&self) -> &T {
1954        &self.head
1955    }
1956
1957    /// Returns a reference to the last element.
1958    pub fn last(&self) -> &T {
1959        self.tail.last().unwrap_or(&self.head)
1960    }
1961
1962    /// Returns the number of elements.
1963    pub fn len(&self) -> usize {
1964        1 + self.tail.len()
1965    }
1966
1967    /// Returns whether the collection is empty.
1968    pub fn is_empty(&self) -> bool {
1969        self.len() == 0
1970    }
1971
1972    /// Returns all elements as a Vec.
1973    pub fn to_vec(&self) -> Vec<&T> {
1974        let mut v = vec![&self.head];
1975        v.extend(self.tail.iter());
1976        v
1977    }
1978}
1979
1980#[cfg(test)]
1981mod tests_extra_iterators {
1982    use super::*;
1983
1984    #[test]
1985    fn test_window_iterator() {
1986        let data = vec![1u32, 2, 3, 4, 5];
1987        let windows: Vec<_> = WindowIterator::new(&data, 3).collect();
1988        assert_eq!(windows.len(), 3);
1989        assert_eq!(windows[0], &[1, 2, 3]);
1990        assert_eq!(windows[2], &[3, 4, 5]);
1991    }
1992
1993    #[test]
1994    fn test_non_empty_vec() {
1995        let mut nev = NonEmptyVec::singleton(10u32);
1996        nev.push(20);
1997        nev.push(30);
1998        assert_eq!(nev.len(), 3);
1999        assert_eq!(*nev.first(), 10);
2000        assert_eq!(*nev.last(), 30);
2001    }
2002}
2003
2004// ─── Second padding block ─────────────────────────────────────────────────────
2005
2006/// A fixed-size sliding window that computes a running sum.
2007#[allow(dead_code)]
2008pub struct SlidingSum {
2009    window: Vec<f64>,
2010    capacity: usize,
2011    pos: usize,
2012    sum: f64,
2013    count: usize,
2014}
2015
2016#[allow(dead_code)]
2017impl SlidingSum {
2018    /// Creates a sliding sum with the given window size.
2019    pub fn new(capacity: usize) -> Self {
2020        Self {
2021            window: vec![0.0; capacity],
2022            capacity,
2023            pos: 0,
2024            sum: 0.0,
2025            count: 0,
2026        }
2027    }
2028
2029    /// Adds a value to the window, removing the oldest if full.
2030    pub fn push(&mut self, val: f64) {
2031        let oldest = self.window[self.pos];
2032        self.sum -= oldest;
2033        self.sum += val;
2034        self.window[self.pos] = val;
2035        self.pos = (self.pos + 1) % self.capacity;
2036        if self.count < self.capacity {
2037            self.count += 1;
2038        }
2039    }
2040
2041    /// Returns the current window sum.
2042    pub fn sum(&self) -> f64 {
2043        self.sum
2044    }
2045
2046    /// Returns the window mean, or `None` if empty.
2047    pub fn mean(&self) -> Option<f64> {
2048        if self.count == 0 {
2049            None
2050        } else {
2051            Some(self.sum / self.count as f64)
2052        }
2053    }
2054
2055    /// Returns the current window size (number of valid elements).
2056    pub fn count(&self) -> usize {
2057        self.count
2058    }
2059}
2060
2061/// A reusable scratch buffer for path computations.
2062#[allow(dead_code)]
2063pub struct PathBuf {
2064    components: Vec<String>,
2065}
2066
2067#[allow(dead_code)]
2068impl PathBuf {
2069    /// Creates a new empty path buffer.
2070    pub fn new() -> Self {
2071        Self {
2072            components: Vec::new(),
2073        }
2074    }
2075
2076    /// Pushes a component.
2077    pub fn push(&mut self, comp: impl Into<String>) {
2078        self.components.push(comp.into());
2079    }
2080
2081    /// Pops the last component.
2082    pub fn pop(&mut self) {
2083        self.components.pop();
2084    }
2085
2086    /// Returns the current path as a `/`-separated string.
2087    pub fn as_str(&self) -> String {
2088        self.components.join("/")
2089    }
2090
2091    /// Returns the depth of the path.
2092    pub fn depth(&self) -> usize {
2093        self.components.len()
2094    }
2095
2096    /// Clears the path.
2097    pub fn clear(&mut self) {
2098        self.components.clear();
2099    }
2100}
2101
2102impl Default for PathBuf {
2103    fn default() -> Self {
2104        Self::new()
2105    }
2106}
2107
2108/// A type-erased function pointer with arity tracking.
2109#[allow(dead_code)]
2110pub struct RawFnPtr {
2111    /// The raw function pointer (stored as usize for type erasure).
2112    ptr: usize,
2113    arity: usize,
2114    name: String,
2115}
2116
2117#[allow(dead_code)]
2118impl RawFnPtr {
2119    /// Creates a new raw function pointer descriptor.
2120    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
2121        Self {
2122            ptr,
2123            arity,
2124            name: name.into(),
2125        }
2126    }
2127
2128    /// Returns the arity.
2129    pub fn arity(&self) -> usize {
2130        self.arity
2131    }
2132
2133    /// Returns the name.
2134    pub fn name(&self) -> &str {
2135        &self.name
2136    }
2137
2138    /// Returns the raw pointer value.
2139    pub fn raw(&self) -> usize {
2140        self.ptr
2141    }
2142}
2143
2144/// A pool of reusable string buffers.
2145#[allow(dead_code)]
2146pub struct StringPool {
2147    free: Vec<String>,
2148}
2149
2150#[allow(dead_code)]
2151impl StringPool {
2152    /// Creates a new empty string pool.
2153    pub fn new() -> Self {
2154        Self { free: Vec::new() }
2155    }
2156
2157    /// Takes a string from the pool (may be empty).
2158    pub fn take(&mut self) -> String {
2159        self.free.pop().unwrap_or_default()
2160    }
2161
2162    /// Returns a string to the pool.
2163    pub fn give(&mut self, mut s: String) {
2164        s.clear();
2165        self.free.push(s);
2166    }
2167
2168    /// Returns the number of free strings in the pool.
2169    pub fn free_count(&self) -> usize {
2170        self.free.len()
2171    }
2172}
2173
2174impl Default for StringPool {
2175    fn default() -> Self {
2176        Self::new()
2177    }
2178}
2179
2180/// A dependency closure builder (transitive closure via BFS).
2181#[allow(dead_code)]
2182pub struct TransitiveClosure {
2183    adj: Vec<Vec<usize>>,
2184    n: usize,
2185}
2186
2187#[allow(dead_code)]
2188impl TransitiveClosure {
2189    /// Creates a transitive closure builder for `n` nodes.
2190    pub fn new(n: usize) -> Self {
2191        Self {
2192            adj: vec![Vec::new(); n],
2193            n,
2194        }
2195    }
2196
2197    /// Adds a direct edge.
2198    pub fn add_edge(&mut self, from: usize, to: usize) {
2199        if from < self.n {
2200            self.adj[from].push(to);
2201        }
2202    }
2203
2204    /// Computes all nodes reachable from `start` (including `start`).
2205    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
2206        let mut visited = vec![false; self.n];
2207        let mut queue = std::collections::VecDeque::new();
2208        queue.push_back(start);
2209        while let Some(node) = queue.pop_front() {
2210            if node >= self.n || visited[node] {
2211                continue;
2212            }
2213            visited[node] = true;
2214            for &next in &self.adj[node] {
2215                queue.push_back(next);
2216            }
2217        }
2218        (0..self.n).filter(|&i| visited[i]).collect()
2219    }
2220
2221    /// Returns `true` if `from` can transitively reach `to`.
2222    pub fn can_reach(&self, from: usize, to: usize) -> bool {
2223        self.reachable_from(from).contains(&to)
2224    }
2225}
2226
2227/// A token bucket rate limiter.
2228#[allow(dead_code)]
2229pub struct TokenBucket {
2230    capacity: u64,
2231    tokens: u64,
2232    refill_per_ms: u64,
2233    last_refill: std::time::Instant,
2234}
2235
2236#[allow(dead_code)]
2237impl TokenBucket {
2238    /// Creates a new token bucket.
2239    pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
2240        Self {
2241            capacity,
2242            tokens: capacity,
2243            refill_per_ms,
2244            last_refill: std::time::Instant::now(),
2245        }
2246    }
2247
2248    /// Attempts to consume `n` tokens.  Returns `true` on success.
2249    pub fn try_consume(&mut self, n: u64) -> bool {
2250        self.refill();
2251        if self.tokens >= n {
2252            self.tokens -= n;
2253            true
2254        } else {
2255            false
2256        }
2257    }
2258
2259    fn refill(&mut self) {
2260        let now = std::time::Instant::now();
2261        let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
2262        if elapsed_ms > 0 {
2263            let new_tokens = elapsed_ms * self.refill_per_ms;
2264            self.tokens = (self.tokens + new_tokens).min(self.capacity);
2265            self.last_refill = now;
2266        }
2267    }
2268
2269    /// Returns the number of currently available tokens.
2270    pub fn available(&self) -> u64 {
2271        self.tokens
2272    }
2273
2274    /// Returns the bucket capacity.
2275    pub fn capacity(&self) -> u64 {
2276        self.capacity
2277    }
2278}
2279
2280/// Represents a rewrite rule `lhs → rhs`.
2281#[allow(dead_code)]
2282#[allow(missing_docs)]
2283pub struct RewriteRule {
2284    /// The name of the rule.
2285    pub name: String,
2286    /// A string representation of the LHS pattern.
2287    pub lhs: String,
2288    /// A string representation of the RHS.
2289    pub rhs: String,
2290    /// Whether this is a conditional rule (has side conditions).
2291    pub conditional: bool,
2292}
2293
2294#[allow(dead_code)]
2295impl RewriteRule {
2296    /// Creates an unconditional rewrite rule.
2297    pub fn unconditional(
2298        name: impl Into<String>,
2299        lhs: impl Into<String>,
2300        rhs: impl Into<String>,
2301    ) -> Self {
2302        Self {
2303            name: name.into(),
2304            lhs: lhs.into(),
2305            rhs: rhs.into(),
2306            conditional: false,
2307        }
2308    }
2309
2310    /// Creates a conditional rewrite rule.
2311    pub fn conditional(
2312        name: impl Into<String>,
2313        lhs: impl Into<String>,
2314        rhs: impl Into<String>,
2315    ) -> Self {
2316        Self {
2317            name: name.into(),
2318            lhs: lhs.into(),
2319            rhs: rhs.into(),
2320            conditional: true,
2321        }
2322    }
2323
2324    /// Returns a textual representation.
2325    pub fn display(&self) -> String {
2326        format!("{}: {} → {}", self.name, self.lhs, self.rhs)
2327    }
2328}
2329
2330/// A set of rewrite rules.
2331#[allow(dead_code)]
2332pub struct RewriteRuleSet {
2333    rules: Vec<RewriteRule>,
2334}
2335
2336#[allow(dead_code)]
2337impl RewriteRuleSet {
2338    /// Creates an empty rule set.
2339    pub fn new() -> Self {
2340        Self { rules: Vec::new() }
2341    }
2342
2343    /// Adds a rule.
2344    pub fn add(&mut self, rule: RewriteRule) {
2345        self.rules.push(rule);
2346    }
2347
2348    /// Returns the number of rules.
2349    pub fn len(&self) -> usize {
2350        self.rules.len()
2351    }
2352
2353    /// Returns `true` if the set is empty.
2354    pub fn is_empty(&self) -> bool {
2355        self.rules.is_empty()
2356    }
2357
2358    /// Returns all conditional rules.
2359    pub fn conditional_rules(&self) -> Vec<&RewriteRule> {
2360        self.rules.iter().filter(|r| r.conditional).collect()
2361    }
2362
2363    /// Returns all unconditional rules.
2364    pub fn unconditional_rules(&self) -> Vec<&RewriteRule> {
2365        self.rules.iter().filter(|r| !r.conditional).collect()
2366    }
2367
2368    /// Looks up a rule by name.
2369    pub fn get(&self, name: &str) -> Option<&RewriteRule> {
2370        self.rules.iter().find(|r| r.name == name)
2371    }
2372}
2373
2374impl Default for RewriteRuleSet {
2375    fn default() -> Self {
2376        Self::new()
2377    }
2378}
2379
2380#[cfg(test)]
2381mod tests_padding2 {
2382    use super::*;
2383
2384    #[test]
2385    fn test_sliding_sum() {
2386        let mut ss = SlidingSum::new(3);
2387        ss.push(1.0);
2388        ss.push(2.0);
2389        ss.push(3.0);
2390        assert!((ss.sum() - 6.0).abs() < 1e-9);
2391        ss.push(4.0); // slides out 1.0
2392        assert!((ss.sum() - 9.0).abs() < 1e-9);
2393        assert_eq!(ss.count(), 3);
2394    }
2395
2396    #[test]
2397    fn test_path_buf() {
2398        let mut pb = PathBuf::new();
2399        pb.push("src");
2400        pb.push("main");
2401        assert_eq!(pb.as_str(), "src/main");
2402        assert_eq!(pb.depth(), 2);
2403        pb.pop();
2404        assert_eq!(pb.as_str(), "src");
2405    }
2406
2407    #[test]
2408    fn test_string_pool() {
2409        let mut pool = StringPool::new();
2410        let s = pool.take();
2411        assert!(s.is_empty());
2412        pool.give("hello".to_string());
2413        let s2 = pool.take();
2414        assert!(s2.is_empty()); // cleared on give
2415        assert_eq!(pool.free_count(), 0);
2416    }
2417
2418    #[test]
2419    fn test_transitive_closure() {
2420        let mut tc = TransitiveClosure::new(4);
2421        tc.add_edge(0, 1);
2422        tc.add_edge(1, 2);
2423        tc.add_edge(2, 3);
2424        assert!(tc.can_reach(0, 3));
2425        assert!(!tc.can_reach(3, 0));
2426        let r = tc.reachable_from(0);
2427        assert_eq!(r.len(), 4);
2428    }
2429
2430    #[test]
2431    fn test_token_bucket() {
2432        let mut tb = TokenBucket::new(100, 10);
2433        assert_eq!(tb.available(), 100);
2434        assert!(tb.try_consume(50));
2435        assert_eq!(tb.available(), 50);
2436        assert!(!tb.try_consume(60)); // over remaining
2437        assert_eq!(tb.capacity(), 100);
2438    }
2439
2440    #[test]
2441    fn test_rewrite_rule_set() {
2442        let mut rrs = RewriteRuleSet::new();
2443        rrs.add(RewriteRule::unconditional(
2444            "beta",
2445            "App(Lam(x, b), v)",
2446            "b[x:=v]",
2447        ));
2448        rrs.add(RewriteRule::conditional("comm", "a + b", "b + a"));
2449        assert_eq!(rrs.len(), 2);
2450        assert_eq!(rrs.unconditional_rules().len(), 1);
2451        assert_eq!(rrs.conditional_rules().len(), 1);
2452        assert!(rrs.get("beta").is_some());
2453        let disp = rrs
2454            .get("beta")
2455            .expect("element at \'beta\' should exist")
2456            .display();
2457        assert!(disp.contains("→"));
2458    }
2459}
2460
2461// ─── Third padding block ─────────────────────────────────────────────────────
2462
2463/// A simple decision tree node for rule dispatching.
2464#[allow(dead_code)]
2465#[allow(missing_docs)]
2466pub enum DecisionNode {
2467    /// A leaf with an action string.
2468    Leaf(String),
2469    /// An interior node: check `key` equals `val` → `yes_branch`, else `no_branch`.
2470    Branch {
2471        key: String,
2472        val: String,
2473        yes_branch: Box<DecisionNode>,
2474        no_branch: Box<DecisionNode>,
2475    },
2476}
2477
2478#[allow(dead_code)]
2479impl DecisionNode {
2480    /// Evaluates the decision tree with the given context.
2481    pub fn evaluate(&self, ctx: &std::collections::HashMap<String, String>) -> &str {
2482        match self {
2483            DecisionNode::Leaf(action) => action.as_str(),
2484            DecisionNode::Branch {
2485                key,
2486                val,
2487                yes_branch,
2488                no_branch,
2489            } => {
2490                let actual = ctx.get(key).map(|s| s.as_str()).unwrap_or("");
2491                if actual == val.as_str() {
2492                    yes_branch.evaluate(ctx)
2493                } else {
2494                    no_branch.evaluate(ctx)
2495                }
2496            }
2497        }
2498    }
2499
2500    /// Returns the depth of the decision tree.
2501    pub fn depth(&self) -> usize {
2502        match self {
2503            DecisionNode::Leaf(_) => 0,
2504            DecisionNode::Branch {
2505                yes_branch,
2506                no_branch,
2507                ..
2508            } => 1 + yes_branch.depth().max(no_branch.depth()),
2509        }
2510    }
2511}
2512
2513/// A flat list of substitution pairs `(from, to)`.
2514#[allow(dead_code)]
2515pub struct FlatSubstitution {
2516    pairs: Vec<(String, String)>,
2517}
2518
2519#[allow(dead_code)]
2520impl FlatSubstitution {
2521    /// Creates an empty substitution.
2522    pub fn new() -> Self {
2523        Self { pairs: Vec::new() }
2524    }
2525
2526    /// Adds a pair.
2527    pub fn add(&mut self, from: impl Into<String>, to: impl Into<String>) {
2528        self.pairs.push((from.into(), to.into()));
2529    }
2530
2531    /// Applies all substitutions to `s` (leftmost-first order).
2532    pub fn apply(&self, s: &str) -> String {
2533        let mut result = s.to_string();
2534        for (from, to) in &self.pairs {
2535            result = result.replace(from.as_str(), to.as_str());
2536        }
2537        result
2538    }
2539
2540    /// Returns the number of pairs.
2541    pub fn len(&self) -> usize {
2542        self.pairs.len()
2543    }
2544
2545    /// Returns `true` if empty.
2546    pub fn is_empty(&self) -> bool {
2547        self.pairs.is_empty()
2548    }
2549}
2550
2551impl Default for FlatSubstitution {
2552    fn default() -> Self {
2553        Self::new()
2554    }
2555}
2556
2557/// A counter that can measure elapsed time between snapshots.
2558#[allow(dead_code)]
2559pub struct Stopwatch {
2560    start: std::time::Instant,
2561    splits: Vec<f64>,
2562}
2563
2564#[allow(dead_code)]
2565impl Stopwatch {
2566    /// Creates and starts a new stopwatch.
2567    pub fn start() -> Self {
2568        Self {
2569            start: std::time::Instant::now(),
2570            splits: Vec::new(),
2571        }
2572    }
2573
2574    /// Records a split time (elapsed since start).
2575    pub fn split(&mut self) {
2576        self.splits.push(self.elapsed_ms());
2577    }
2578
2579    /// Returns total elapsed milliseconds since start.
2580    pub fn elapsed_ms(&self) -> f64 {
2581        self.start.elapsed().as_secs_f64() * 1000.0
2582    }
2583
2584    /// Returns all recorded split times.
2585    pub fn splits(&self) -> &[f64] {
2586        &self.splits
2587    }
2588
2589    /// Returns the number of splits.
2590    pub fn num_splits(&self) -> usize {
2591        self.splits.len()
2592    }
2593}
2594
2595/// A tagged union for representing a simple two-case discriminated union.
2596#[allow(dead_code)]
2597pub enum Either2<A, B> {
2598    /// The first alternative.
2599    First(A),
2600    /// The second alternative.
2601    Second(B),
2602}
2603
2604#[allow(dead_code)]
2605impl<A, B> Either2<A, B> {
2606    /// Returns `true` if this is the first alternative.
2607    pub fn is_first(&self) -> bool {
2608        matches!(self, Either2::First(_))
2609    }
2610
2611    /// Returns `true` if this is the second alternative.
2612    pub fn is_second(&self) -> bool {
2613        matches!(self, Either2::Second(_))
2614    }
2615
2616    /// Returns the first value if present.
2617    pub fn first(self) -> Option<A> {
2618        match self {
2619            Either2::First(a) => Some(a),
2620            _ => None,
2621        }
2622    }
2623
2624    /// Returns the second value if present.
2625    pub fn second(self) -> Option<B> {
2626        match self {
2627            Either2::Second(b) => Some(b),
2628            _ => None,
2629        }
2630    }
2631
2632    /// Maps over the first alternative.
2633    pub fn map_first<C, F: FnOnce(A) -> C>(self, f: F) -> Either2<C, B> {
2634        match self {
2635            Either2::First(a) => Either2::First(f(a)),
2636            Either2::Second(b) => Either2::Second(b),
2637        }
2638    }
2639}
2640
2641/// A write-once cell.
2642#[allow(dead_code)]
2643pub struct WriteOnce<T> {
2644    value: std::cell::Cell<Option<T>>,
2645}
2646
2647#[allow(dead_code)]
2648impl<T: Copy> WriteOnce<T> {
2649    /// Creates an empty write-once cell.
2650    pub fn new() -> Self {
2651        Self {
2652            value: std::cell::Cell::new(None),
2653        }
2654    }
2655
2656    /// Writes a value.  Returns `false` if already written.
2657    pub fn write(&self, val: T) -> bool {
2658        if self.value.get().is_some() {
2659            return false;
2660        }
2661        self.value.set(Some(val));
2662        true
2663    }
2664
2665    /// Returns the value if written.
2666    pub fn read(&self) -> Option<T> {
2667        self.value.get()
2668    }
2669
2670    /// Returns `true` if the value has been written.
2671    pub fn is_written(&self) -> bool {
2672        self.value.get().is_some()
2673    }
2674}
2675
2676impl<T: Copy> Default for WriteOnce<T> {
2677    fn default() -> Self {
2678        Self::new()
2679    }
2680}
2681
2682/// A sparse vector: stores only non-default elements.
2683#[allow(dead_code)]
2684pub struct SparseVec<T: Default + Clone + PartialEq> {
2685    entries: std::collections::HashMap<usize, T>,
2686    default_: T,
2687    logical_len: usize,
2688}
2689
2690#[allow(dead_code)]
2691impl<T: Default + Clone + PartialEq> SparseVec<T> {
2692    /// Creates a new sparse vector with logical length `len`.
2693    pub fn new(len: usize) -> Self {
2694        Self {
2695            entries: std::collections::HashMap::new(),
2696            default_: T::default(),
2697            logical_len: len,
2698        }
2699    }
2700
2701    /// Sets element at `idx`.
2702    pub fn set(&mut self, idx: usize, val: T) {
2703        if val == self.default_ {
2704            self.entries.remove(&idx);
2705        } else {
2706            self.entries.insert(idx, val);
2707        }
2708    }
2709
2710    /// Gets element at `idx`.
2711    pub fn get(&self, idx: usize) -> &T {
2712        self.entries.get(&idx).unwrap_or(&self.default_)
2713    }
2714
2715    /// Returns the logical length.
2716    pub fn len(&self) -> usize {
2717        self.logical_len
2718    }
2719
2720    /// Returns whether the collection is empty.
2721    pub fn is_empty(&self) -> bool {
2722        self.len() == 0
2723    }
2724
2725    /// Returns the number of non-default elements.
2726    pub fn nnz(&self) -> usize {
2727        self.entries.len()
2728    }
2729}
2730
2731/// A simple stack-based calculator for arithmetic expressions.
2732#[allow(dead_code)]
2733pub struct StackCalc {
2734    stack: Vec<i64>,
2735}
2736
2737#[allow(dead_code)]
2738impl StackCalc {
2739    /// Creates a new empty calculator.
2740    pub fn new() -> Self {
2741        Self { stack: Vec::new() }
2742    }
2743
2744    /// Pushes an integer literal.
2745    pub fn push(&mut self, n: i64) {
2746        self.stack.push(n);
2747    }
2748
2749    /// Adds the top two values.  Panics if fewer than two values.
2750    pub fn add(&mut self) {
2751        let b = self
2752            .stack
2753            .pop()
2754            .expect("stack must have at least two values for add");
2755        let a = self
2756            .stack
2757            .pop()
2758            .expect("stack must have at least two values for add");
2759        self.stack.push(a + b);
2760    }
2761
2762    /// Subtracts top from second.
2763    pub fn sub(&mut self) {
2764        let b = self
2765            .stack
2766            .pop()
2767            .expect("stack must have at least two values for sub");
2768        let a = self
2769            .stack
2770            .pop()
2771            .expect("stack must have at least two values for sub");
2772        self.stack.push(a - b);
2773    }
2774
2775    /// Multiplies the top two values.
2776    pub fn mul(&mut self) {
2777        let b = self
2778            .stack
2779            .pop()
2780            .expect("stack must have at least two values for mul");
2781        let a = self
2782            .stack
2783            .pop()
2784            .expect("stack must have at least two values for mul");
2785        self.stack.push(a * b);
2786    }
2787
2788    /// Peeks the top value.
2789    pub fn peek(&self) -> Option<i64> {
2790        self.stack.last().copied()
2791    }
2792
2793    /// Returns the stack depth.
2794    pub fn depth(&self) -> usize {
2795        self.stack.len()
2796    }
2797}
2798
2799impl Default for StackCalc {
2800    fn default() -> Self {
2801        Self::new()
2802    }
2803}
2804
2805#[cfg(test)]
2806mod tests_padding3 {
2807    use super::*;
2808
2809    #[test]
2810    fn test_decision_node() {
2811        let tree = DecisionNode::Branch {
2812            key: "x".into(),
2813            val: "1".into(),
2814            yes_branch: Box::new(DecisionNode::Leaf("yes".into())),
2815            no_branch: Box::new(DecisionNode::Leaf("no".into())),
2816        };
2817        let mut ctx = std::collections::HashMap::new();
2818        ctx.insert("x".into(), "1".into());
2819        assert_eq!(tree.evaluate(&ctx), "yes");
2820        ctx.insert("x".into(), "2".into());
2821        assert_eq!(tree.evaluate(&ctx), "no");
2822        assert_eq!(tree.depth(), 1);
2823    }
2824
2825    #[test]
2826    fn test_flat_substitution() {
2827        let mut sub = FlatSubstitution::new();
2828        sub.add("foo", "bar");
2829        sub.add("baz", "qux");
2830        assert_eq!(sub.apply("foo and baz"), "bar and qux");
2831        assert_eq!(sub.len(), 2);
2832    }
2833
2834    #[test]
2835    fn test_stopwatch() {
2836        let mut sw = Stopwatch::start();
2837        sw.split();
2838        sw.split();
2839        assert_eq!(sw.num_splits(), 2);
2840        assert!(sw.elapsed_ms() >= 0.0);
2841        for &s in sw.splits() {
2842            assert!(s >= 0.0);
2843        }
2844    }
2845
2846    #[test]
2847    fn test_either2() {
2848        let e: Either2<i32, &str> = Either2::First(42);
2849        assert!(e.is_first());
2850        let mapped = e.map_first(|x| x * 2);
2851        assert_eq!(mapped.first(), Some(84));
2852
2853        let e2: Either2<i32, &str> = Either2::Second("hello");
2854        assert!(e2.is_second());
2855        assert_eq!(e2.second(), Some("hello"));
2856    }
2857
2858    #[test]
2859    fn test_write_once() {
2860        let wo: WriteOnce<u32> = WriteOnce::new();
2861        assert!(!wo.is_written());
2862        assert!(wo.write(42));
2863        assert!(!wo.write(99)); // already written
2864        assert_eq!(wo.read(), Some(42));
2865    }
2866
2867    #[test]
2868    fn test_sparse_vec() {
2869        let mut sv: SparseVec<i32> = SparseVec::new(100);
2870        sv.set(5, 10);
2871        sv.set(50, 20);
2872        assert_eq!(*sv.get(5), 10);
2873        assert_eq!(*sv.get(50), 20);
2874        assert_eq!(*sv.get(0), 0); // default
2875        assert_eq!(sv.nnz(), 2);
2876        sv.set(5, 0); // reset to default
2877        assert_eq!(sv.nnz(), 1);
2878    }
2879
2880    #[test]
2881    fn test_stack_calc() {
2882        let mut calc = StackCalc::new();
2883        calc.push(3);
2884        calc.push(4);
2885        calc.add();
2886        assert_eq!(calc.peek(), Some(7));
2887        calc.push(2);
2888        calc.mul();
2889        assert_eq!(calc.peek(), Some(14));
2890    }
2891}