Skip to main content

oxilean_meta/
lib.rs

1#![allow(dead_code)]
2#![allow(missing_docs)]
3#![allow(unused_imports)]
4#![allow(private_interfaces)]
5//! # OxiLean Meta Layer — Metavar-Aware Operations & Tactics
6//!
7//! The meta layer extends the kernel with metavariable support, providing all the infrastructure
8//! for elaboration, unification, and interactive tactic proving. It mirrors LEAN 4's `Lean.Meta`
9//! namespace and sits logically between the elaborator and the trusted kernel.
10//!
11//! ## Quick Start
12//!
13//! ### Creating a Meta Context
14//!
15//! ```ignore
16//! use oxilean_meta::{MetaContext, MetaConfig};
17//! use oxilean_kernel::Environment;
18//!
19//! let env = Environment::new();
20//! let config = MetaConfig::default();
21//! let mut meta_ctx = MetaContext::new(&env, config);
22//! ```
23//!
24//! ### Creating and Solving Metavariables
25//!
26//! ```ignore
27//! use oxilean_meta::MVarId;
28//!
29//! let m1 = meta_ctx.mk_metavar(ty)?;
30//! // ... unification happens ...
31//! let solution = meta_ctx.get_assignment(m1)?;
32//! ```
33//!
34//! ### Running a Tactic
35//!
36//! ```ignore
37//! use oxilean_meta::TacticState;
38//!
39//! let mut state = TacticState::new(&meta_ctx, goal)?;
40//! // ... execute tactics ...
41//! let proof = state.close()?;
42//! ```
43//!
44//! ## Architecture Overview
45//!
46//! The meta layer is organized into three functional areas:
47//!
48//! ```text
49//! ┌──────────────────────────────────────────────────────┐
50//! │              Meta Layer (oxilean-meta)               │
51//! ├────────────────────────────────────────────────────────┤
52//! │                                                         │
53//! │  ┌─────────────────┐  ┌──────────────────┐             │
54//! │  │  Core Meta Ops  │  │  Advanced Features│             │
55//! │  ├─────────────────┤  ├──────────────────┤             │
56//! │  │- MetaContext    │  │- Instance Synth  │             │
57//! │  │- MetaWhnf       │  │- Discrimination  │             │
58//! │  │- MetaDefEq      │  │  Trees           │             │
59//! │  │- MetaInferType  │  │- App Builder     │             │
60//! │  │- Level DefEq    │  │- Congr Theorems  │             │
61//! │  └─────────────────┘  └──────────────────┘             │
62//! │                                                         │
63//! │  ┌──────────────────────────────────────┐              │
64//! │  │     Tactic System                    │              │
65//! │  ├──────────────────────────────────────┤              │
66//! │  │- intro, apply, exact, rw, simp, ... │              │
67//! │  │- Goal & TacticState management      │              │
68//! │  │- Calc proofs, Omega solver, etc.    │              │
69//! │  └──────────────────────────────────────┘              │
70//! │                                                         │
71//! └──────────────────────────────────────────────────────────┘
72//!                          │
73//!                          │ uses (doesn't modify)
74//!                          ▼
75//! ┌──────────────────────────────────────────────────────┐
76//! │              OxiLean Kernel (TCB)                    │
77//! │         (Expression, Type Checking, WHNF, ...)      │
78//! └──────────────────────────────────────────────────────┘
79//! ```
80//!
81//! ## Key Concepts & Terminology
82//!
83//! ### MetaContext vs MetaState
84//!
85//! - **MetaContext**: Global state during a proof session
86//!   - All metavariables and their assignments
87//!   - Configuration (recursion depth, timeouts, etc.)
88//!   - Doesn't change during individual tactics
89//!
90//! - **MetaState**: Local goal-solving state
91//!   - Current goal and subgoals
92//!   - Local context (variables and hypotheses)
93//!   - Changes as tactics are applied
94//!
95//! ### Goal
96//!
97//! A proof goal represented as:
98//! ```text
99//! x : Nat
100//! h : P x
101//! ⊢ Q x
102//! ```
103//!
104//! Components:
105//! - **Local context**: Variables `x : Nat`, hypotheses `h : P x`
106//! - **Type** (target): What to prove `Q x`
107//!
108//! Goals are solved when type is inhabited (proof term provided).
109//!
110//! ### Tactic
111//!
112//! A **tactic** transforms goals:
113//! ```text
114//! Tactic: Goal → (Proof ∪ NewGoals ∪ Error)
115//! ```
116//!
117//! Examples:
118//! - `intro`: Transform `⊢ P → Q` to `p : P ⊢ Q`
119//! - `exact t`: If `t : P`, close goal `⊢ P`
120//! - `rw [eq]`: Transform using equality
121//!
122//! ### Metavariable Assignment
123//!
124//! Unification solves `?m =?= expr` by assigning:
125//! ```text
126//! ?m := expr
127//! ```
128//!
129//! Subsequent references to `?m` automatically get the value.
130//!
131//! ### Discrimination Tree
132//!
133//! Fast indexing structure for term-based lookup (e.g., for simp lemmas):
134//! - Index by **discriminator**: First argument, head function, etc.
135//! - Retrieve candidates in O(log n) instead of O(n)
136//!
137//! ### Instance Synthesis
138//!
139//! Automated search for typeclass instances:
140//! - Given `[C x]`, find value of type `C x`
141//! - Uses tabled resolution (memoization)
142//! - Prevents infinite loops
143//!
144//! ## Module Organization
145//!
146//! ### Batch 4.1: Core Meta Infrastructure
147//!
148//! | Module | Purpose |
149//! |--------|---------|
150//! | `basic` | `MetaContext`, `MetaState`, `MVarId`, metavariable creation |
151//! | `whnf` | Weak head normal form with metavar support |
152//! | `def_eq` | Definitional equality and unification |
153//! | `infer_type` | Type synthesis and checking (metavar-aware) |
154//! | `level_def_eq` | Universe level unification |
155//! | `discr_tree` | Discrimination tree indexing for fast lookup |
156//!
157//! ### Batch 4.2: Advanced Features
158//!
159//! | Module | Purpose |
160//! |--------|---------|
161//! | `app_builder` | Helpers for building common proof terms (eq, symm, etc.) |
162//! | `congr_theorems` | Automatic congruence lemma generation |
163//! | `match_basic` | Basic pattern matching representation |
164//! | `match_dectree` | Decision tree compilation for patterns |
165//! | `match_exhaust` | Exhaustiveness checking |
166//! | `synth_instance` | Typeclass instance synthesis |
167//!
168//! ### Batch 4.3: Core Tactics
169//!
170//! | Module | Purpose |
171//! |--------|---------|
172//! | `tactic` | Tactic engine and state management |
173//! | `tactic::intro` | `intro` (introduce binders) |
174//! | `tactic::apply` | `apply` (apply theorems) |
175//! | `tactic::rewrite` | `rw` (rewrite by equality) |
176//! | `tactic::simp` | `simp` (simplification with lemmas) |
177//! | `tactic::omega` | `omega` (linear arithmetic) |
178//! | `tactic::calc` | `calc` (calculational proofs) |
179//!
180//! ### Batch 4.4: Utilities
181//!
182//! | Module | Purpose |
183//! |--------|---------|
184//! | `util` | Utilities: `collect_mvars`, `collect_fvars`, `FunInfo` |
185//! | `proof_replay` | Proof term caching and memoization |
186//! | `simp_engine` | Simplification engine and simp lemma management |
187//!
188//! ## Meta Operations Pipeline
189//!
190//! ### Type Inference (Meta Version)
191//!
192//! ```text
193//! MetaInferType::infer_synth(expr)
194//!   │
195//!   ├─ Recursively infer subexpressions
196//!   ├─ Create metavars for unknown types
197//!   ├─ Collect unification constraints
198//!   └─ Return: (expr_with_metavars, type)
199//! ```
200//!
201//! ### Unification Pipeline
202//!
203//! ```text
204//! MetaDefEq::unify(goal_expr, expr)
205//!   │
206//!   ├─ Reduce both to WHNF (with metavar support)
207//!   ├─ Check if structurally equal
208//!   ├─ If not, try:
209//!   │  ├─ Variable unification: ?m := expr
210//!   │  ├─ Application: f a =? g b
211//!   │  │  └─ Unify: f =? g, a =? b
212//!   │  ├─ Lambda: λ x. t =? λ y. u
213//!   │  │  └─ Unify: t[x/?a] =? u[y/?a] (fresh ?a)
214//!   │  └─ Higher-order cases
215//!   ├─ Occurs check: Prevent ?m := f ?m
216//!   └─ Return: Success or failure
217//! ```
218//!
219//! ### Tactic Execution
220//!
221//! ```text
222//! TacticState::execute(tactic)
223//!   │
224//!   ├─ For each goal:
225//!   │  ├─ Apply tactic to goal
226//!   │  ├─ If success: Add new subgoals to queue
227//!   │  ├─ If exact: Mark goal closed
228//!   │  └─ If failure: Backtrack or error
229//!   │
230//!   ├─ Recursively solve subgoals
231//!   │
232//!   └─ When all goals closed: Construct proof term
233//! ```
234//!
235//! ## Usage Examples
236//!
237//! ### Example 1: Type Inference with Metavars
238//!
239//! ```ignore
240//! use oxilean_meta::MetaInferType;
241//!
242//! let mut infer = MetaInferType::new(&meta_ctx);
243//! let (expr_with_metas, ty) = infer.infer_synth(surface_expr)?;
244//! // expr_with_metas may contain unsolved metavars
245//! ```
246//!
247//! ### Example 2: Unification
248//!
249//! ```ignore
250//! use oxilean_meta::MetaDefEq;
251//!
252//! let mut def_eq = MetaDefEq::new(&meta_ctx);
253//! def_eq.unify(goal, candidate)?;
254//! // If successful, metavars are assigned
255//! ```
256//!
257//! ### Example 3: Tactic Execution
258//!
259//! ```ignore
260//! use oxilean_meta::TacticState;
261//!
262//! let mut state = TacticState::new(&meta_ctx, initial_goal)?;
263//! state.intro()?;  // Run intro tactic
264//! state.apply(theorem)?;  // Run apply tactic
265//! let proof = state.close()?;  // Get proof term
266//! ```
267//!
268//! ### Example 4: Instance Synthesis
269//!
270//! ```ignore
271//! use oxilean_meta::InstanceSynthesizer;
272//!
273//! let synth = InstanceSynthesizer::new(&meta_ctx);
274//! let instance = synth.synth_instance(class_type)?;
275//! // Returns proof that satisfies typeclass constraint
276//! ```
277//!
278//! ## Tactic Language
279//!
280//! Common tactics:
281//! - **Proof construction**: `intro`, `apply`, `exact`, `refine`
282//! - **Rewriting**: `rw [h]`, `simp`, `simp only`
283//! - **Analysis**: `cases x`, `induction x on y`, `split`
284//! - **Automation**: `omega` (linear arith), `decide` (decidable)
285//! - **Control**: `;` (then), `<|>` (or), `repeat`, `try`
286//!
287//! ## Integration with Other Crates
288//!
289//! ### With oxilean-kernel
290//!
291//! - Uses kernel `Expr`, `Level`, `Environment`
292//! - Reads kernel operations: WHNF, type checking
293//! - **Does not** modify kernel (immutable reference)
294//! - Proof terms eventually passed to kernel for validation
295//!
296//! ### With oxilean-elab
297//!
298//! - Elaborator uses MetaContext for managing elaboration state
299//! - MetaDefEq/MetaWhnf for type checking during elaboration
300//! - Instance synthesis for implicit arguments
301//! - Tactic execution during proof elaboration
302//!
303//! ## Performance Optimizations
304//!
305//! - **Memoization**: Cache WHNF results for repeated reductions
306//! - **Discrimination trees**: O(log n) lookup for simp lemmas
307//! - **Proof replay**: Avoid re-executing identical tactics
308//! - **Lazy normalization**: Only normalize when needed
309//! - **Constraint postponement**: Defer hard constraints
310//!
311//! ## Further Reading
312//!
313//! - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System architecture
314//! - Module documentation for specific subcomponents
315
316#![forbid(unsafe_code)]
317#![warn(clippy::all)]
318#![allow(clippy::result_large_err)]
319#![allow(clippy::collapsible_if)]
320#![allow(clippy::single_match)]
321#![allow(clippy::module_inception)]
322#![allow(clippy::redundant_closure)]
323#![allow(clippy::needless_ifs)]
324
325// --- Batch 4.1: Meta Core ---
326pub mod basic;
327pub mod def_eq;
328pub mod discr_tree;
329pub mod infer_type;
330pub mod level_def_eq;
331pub mod whnf;
332
333// --- Batch 4.2: Meta Features ---
334pub mod app_builder;
335pub mod congr_theorems;
336pub mod match_basic;
337pub mod match_dectree;
338pub mod match_exhaust;
339pub mod synth_instance;
340
341// --- Re-exports: Batch 4.1 ---
342pub use basic::{
343    MVarId, MetaConfig, MetaContext, MetaState, MetavarDecl, MetavarKind, PostponedConstraint,
344};
345pub use def_eq::{MetaDefEq, UnificationResult};
346pub use discr_tree::{DiscrTree, DiscrTreeKey};
347pub use infer_type::MetaInferType;
348pub use level_def_eq::LevelDefEq;
349pub use whnf::MetaWhnf;
350
351// --- Re-exports: Batch 4.2 ---
352pub use app_builder::{mk_eq, mk_eq_refl, mk_eq_symm, mk_eq_trans};
353pub use congr_theorems::{CongrArgKind, MetaCongrTheorem};
354pub use match_basic::{MetaMatchArm, MetaMatchExpr, MetaPattern};
355pub use match_dectree::{DecisionBranch, DecisionTree, MatchEquation};
356pub use match_exhaust::{ConstructorSpec, ExhaustivenessResult};
357pub use synth_instance::{InstanceEntry, InstanceSynthesizer, SynthResult};
358
359// --- Batch 4.3: Core Tactics ---
360pub mod tactic;
361
362// --- Batch 4.4: Meta Utilities ---
363pub mod util;
364
365// --- Phase 9.1: Performance modules ---
366pub mod proof_replay;
367pub mod prop_test;
368pub mod simp_engine;
369
370pub mod ast_cache;
371pub mod convenience;
372pub mod meta_debug;
373
374// --- Re-exports: Batch 4.3 ---
375pub use tactic::rewrite::RewriteDirection;
376pub use tactic::{GoalView, TacticError, TacticResult, TacticState};
377
378// --- Re-exports: Batch 4.4 ---
379pub use tactic::calc::{CalcProof, CalcStep, ConvSide};
380pub use tactic::omega::{LinearConstraint, LinearExpr, OmegaResult};
381pub use tactic::simp::discharge::DischargeStrategy;
382pub use tactic::simp::types::{
383    default_simp_lemmas, SimpConfig, SimpLemma, SimpResult, SimpTheorems,
384};
385pub use util::{collect_fvars, collect_mvars, FunInfo};
386
387// ---------------------------------------------------------------------------
388// Section 2: Meta layer facade, convenience API, and registry utilities
389// ---------------------------------------------------------------------------
390
391/// Version of the meta layer.
392pub const META_VERSION: &str = "0.9.0-alpha";
393
394/// Return the meta layer version string.
395pub fn meta_version_str() -> &'static str {
396    META_VERSION
397}
398
399/// Helper to create a minimal `MetaContext` for testing.
400pub fn mk_test_ctx() -> MetaContext {
401    MetaContext::new(oxilean_kernel::Environment::new())
402}
403
404/// A named tactic registry.
405#[derive(Debug, Default)]
406pub struct TacticRegistry {
407    pub entries: std::collections::HashMap<String, usize>,
408    pub names: Vec<String>,
409}
410
411impl TacticRegistry {
412    /// Create a new empty registry.
413    pub fn new() -> Self {
414        Self::default()
415    }
416
417    /// Register a tactic name.
418    pub fn register(&mut self, name: impl Into<String>) -> usize {
419        let name = name.into();
420        if let Some(&idx) = self.entries.get(&name) {
421            return idx;
422        }
423        let idx = self.names.len();
424        self.entries.insert(name.clone(), idx);
425        self.names.push(name);
426        idx
427    }
428
429    /// Look up a tactic index by name.
430    pub fn lookup(&self, name: &str) -> Option<usize> {
431        self.entries.get(name).copied()
432    }
433
434    /// Get the name for an index.
435    pub fn name_of(&self, idx: usize) -> Option<&str> {
436        self.names.get(idx).map(String::as_str)
437    }
438
439    /// Number of registered tactics.
440    pub fn len(&self) -> usize {
441        self.names.len()
442    }
443
444    /// Whether the registry is empty.
445    pub fn is_empty(&self) -> bool {
446        self.names.is_empty()
447    }
448
449    /// Get all registered names.
450    pub fn all_names(&self) -> &[String] {
451        &self.names
452    }
453}
454
455/// Build a default tactic registry.
456pub fn default_tactic_registry() -> TacticRegistry {
457    let mut reg = TacticRegistry::new();
458    for tac in [
459        "intro",
460        "intros",
461        "exact",
462        "assumption",
463        "refl",
464        "trivial",
465        "sorry",
466        "apply",
467        "cases",
468        "induction",
469        "rw",
470        "rewrite",
471        "simp",
472        "simp_only",
473        "have",
474        "show",
475        "obtain",
476        "use",
477        "exists",
478        "constructor",
479        "left",
480        "right",
481        "split",
482        "exfalso",
483        "clear",
484        "revert",
485        "subst",
486        "rename",
487        "ring",
488        "linarith",
489        "omega",
490        "norm_num",
491        "push_neg",
492        "by_contra",
493        "by_contradiction",
494        "contrapose",
495        "field_simp",
496        "simp_all",
497        "rfl",
498        "all_goals",
499        "first",
500        "repeat",
501        "try",
502    ] {
503        reg.register(tac);
504    }
505    reg
506}
507
508/// Check if a tactic name is a core tactic.
509pub fn is_core_tactic(name: &str) -> bool {
510    matches!(
511        name,
512        "intro"
513            | "intros"
514            | "exact"
515            | "assumption"
516            | "refl"
517            | "trivial"
518            | "sorry"
519            | "apply"
520            | "cases"
521            | "induction"
522            | "rw"
523            | "rewrite"
524            | "simp"
525            | "have"
526            | "show"
527            | "obtain"
528            | "use"
529            | "constructor"
530            | "left"
531            | "right"
532            | "split"
533            | "exfalso"
534            | "clear"
535            | "revert"
536            | "subst"
537            | "rename"
538            | "ring"
539            | "linarith"
540            | "omega"
541            | "push_neg"
542            | "by_contra"
543    )
544}
545
546/// Check if a tactic is an automation tactic.
547pub fn is_automation_tactic(name: &str) -> bool {
548    matches!(
549        name,
550        "simp"
551            | "simp_all"
552            | "omega"
553            | "linarith"
554            | "ring"
555            | "norm_num"
556            | "decide"
557            | "trivial"
558            | "tauto"
559            | "aesop"
560            | "field_simp"
561    )
562}
563
564/// Describe a tactic's purpose.
565pub fn tactic_description(name: &str) -> Option<&'static str> {
566    match name {
567        "intro" | "intros" => Some("Introduce binders from the goal"),
568        "exact" => Some("Close the goal with a proof term"),
569        "apply" => Some("Apply a lemma to the goal"),
570        "assumption" => Some("Close goal using a hypothesis"),
571        "refl" | "rfl" => Some("Close a reflexivity goal"),
572        "cases" => Some("Case-split on an inductive type"),
573        "induction" => Some("Induct on an inductive type"),
574        "rw" | "rewrite" => Some("Rewrite the goal using an equation"),
575        "simp" => Some("Simplify using simp lemmas"),
576        "have" => Some("Introduce a local lemma"),
577        "split" => Some("Split a conjunction or iff goal"),
578        "sorry" => Some("Close goal with sorry (unsound)"),
579        _ => None,
580    }
581}
582
583/// A simple cache for memoizing meta computations.
584#[derive(Debug)]
585pub struct MetaCache<K, V> {
586    pub entries: std::collections::HashMap<K, V>,
587    pub capacity: usize,
588    pub hits: u64,
589    pub misses: u64,
590}
591
592impl<K: std::hash::Hash + Eq + Clone, V> MetaCache<K, V> {
593    /// Create a cache with a given capacity.
594    pub fn with_capacity(capacity: usize) -> Self {
595        Self {
596            entries: std::collections::HashMap::with_capacity(capacity),
597            capacity,
598            hits: 0,
599            misses: 0,
600        }
601    }
602
603    /// Insert a value.
604    pub fn insert(&mut self, key: K, value: V) {
605        if self.entries.len() >= self.capacity {
606            let len = self.entries.len();
607            if len > 0 {
608                let to_remove = len / 2;
609                let keys: Vec<K> = self.entries.keys().take(to_remove).cloned().collect();
610                for k in keys {
611                    self.entries.remove(&k);
612                }
613            }
614        }
615        self.entries.insert(key, value);
616    }
617
618    /// Look up a value.
619    pub fn get(&mut self, key: &K) -> Option<&V> {
620        if self.entries.contains_key(key) {
621            self.hits += 1;
622            self.entries.get(key)
623        } else {
624            self.misses += 1;
625            None
626        }
627    }
628
629    /// Cache hit rate.
630    pub fn hit_rate(&self) -> f64 {
631        let total = self.hits + self.misses;
632        if total == 0 {
633            0.0
634        } else {
635            self.hits as f64 / total as f64
636        }
637    }
638
639    /// Number of entries.
640    pub fn len(&self) -> usize {
641        self.entries.len()
642    }
643
644    /// Whether empty.
645    pub fn is_empty(&self) -> bool {
646        self.entries.is_empty()
647    }
648
649    /// Clear the cache.
650    pub fn clear(&mut self) {
651        self.entries.clear();
652        self.hits = 0;
653        self.misses = 0;
654    }
655}
656
657/// Summary statistics about a `MetaContext`.
658#[derive(Debug, Clone, Default)]
659pub struct MetaStats {
660    /// Number of expression metavariables.
661    pub num_expr_mvars: usize,
662    /// Number of assigned expression metavariables.
663    pub num_assigned_expr: usize,
664    /// Number of level metavariables.
665    pub num_level_mvars: usize,
666    /// Number of assigned level metavariables.
667    pub num_assigned_levels: usize,
668    /// Number of postponed constraints.
669    pub num_postponed: usize,
670}
671
672/// Collect statistics from a `MetaContext`.
673pub fn collect_meta_stats(ctx: &MetaContext) -> MetaStats {
674    MetaStats {
675        num_expr_mvars: ctx.num_mvars(),
676        num_assigned_expr: 0, // private field; use num_mvars() as approximation
677        num_level_mvars: 0,   // private field; not exposed via public API
678        num_assigned_levels: 0, // private field; not exposed via public API
679        num_postponed: ctx.num_postponed(),
680    }
681}
682
683/// Report of proof state completeness.
684#[derive(Debug, Clone)]
685pub struct ProofStateReport {
686    /// Number of open goals.
687    pub open_goals: usize,
688    /// Whether the proof is complete.
689    pub is_complete: bool,
690}
691
692impl ProofStateReport {
693    /// Create from a tactic state.
694    pub fn from_state(state: &TacticState) -> Self {
695        ProofStateReport {
696            open_goals: state.num_goals(),
697            is_complete: state.is_done(),
698        }
699    }
700}
701
702/// A scored candidate.
703#[derive(Debug, Clone)]
704pub struct ScoredCandidate<T> {
705    /// The candidate.
706    pub candidate: T,
707    /// Score.
708    pub score: i64,
709}
710
711impl<T> ScoredCandidate<T> {
712    /// Create a new scored candidate.
713    pub fn new(candidate: T, score: i64) -> Self {
714        Self { candidate, score }
715    }
716}
717
718/// Sort candidates by descending score.
719pub fn sort_candidates<T: Clone>(candidates: &mut [ScoredCandidate<T>]) {
720    candidates.sort_by(|a, b| b.score.cmp(&a.score));
721}
722
723#[cfg(test)]
724mod meta_lib_tests {
725    use super::*;
726
727    #[test]
728    fn test_meta_version_str() {
729        assert!(!meta_version_str().is_empty());
730    }
731
732    #[test]
733    fn test_tactic_registry_register() {
734        let mut reg = TacticRegistry::new();
735        let idx = reg.register("intro");
736        assert_eq!(reg.lookup("intro"), Some(idx));
737        assert_eq!(reg.lookup("nonexistent"), None);
738    }
739
740    #[test]
741    fn test_tactic_registry_idempotent() {
742        let mut reg = TacticRegistry::new();
743        assert_eq!(reg.register("intro"), reg.register("intro"));
744    }
745
746    #[test]
747    fn test_tactic_registry_name_of() {
748        let mut reg = TacticRegistry::new();
749        let idx = reg.register("apply");
750        assert_eq!(reg.name_of(idx), Some("apply"));
751        assert_eq!(reg.name_of(999), None);
752    }
753
754    #[test]
755    fn test_default_tactic_registry() {
756        let reg = default_tactic_registry();
757        assert!(reg.len() > 10);
758        assert!(reg.lookup("intro").is_some());
759    }
760
761    #[test]
762    fn test_is_core_tactic() {
763        assert!(is_core_tactic("intro"));
764        assert!(!is_core_tactic("nonexistent"));
765    }
766
767    #[test]
768    fn test_is_automation_tactic() {
769        assert!(is_automation_tactic("simp"));
770        assert!(!is_automation_tactic("intro"));
771    }
772
773    #[test]
774    fn test_tactic_description() {
775        assert!(tactic_description("intro").is_some());
776        assert_eq!(tactic_description("nonexistent_xyz"), None);
777    }
778
779    #[test]
780    fn test_meta_cache_basic() {
781        let mut cache: MetaCache<String, i32> = MetaCache::with_capacity(10);
782        cache.insert("key".into(), 42);
783        assert_eq!(cache.get(&"key".to_string()), Some(&42));
784        assert_eq!(cache.get(&"missing".to_string()), None);
785    }
786
787    #[test]
788    fn test_meta_cache_hit_rate() {
789        let mut cache: MetaCache<String, i32> = MetaCache::with_capacity(10);
790        cache.insert("key".into(), 1);
791        let _ = cache.get(&"key".to_string());
792        let _ = cache.get(&"miss".to_string());
793        assert!((cache.hit_rate() - 0.5).abs() < 1e-9);
794    }
795
796    #[test]
797    fn test_meta_cache_clear() {
798        let mut cache: MetaCache<String, i32> = MetaCache::with_capacity(10);
799        cache.insert("a".into(), 1);
800        cache.clear();
801        assert!(cache.is_empty());
802    }
803
804    #[test]
805    fn test_scored_candidate() {
806        let c = ScoredCandidate::new("lemma", 100i64);
807        assert_eq!(c.candidate, "lemma");
808    }
809
810    #[test]
811    fn test_sort_candidates() {
812        let mut v = vec![
813            ScoredCandidate::new("a", 1i64),
814            ScoredCandidate::new("b", 3i64),
815            ScoredCandidate::new("c", 2i64),
816        ];
817        sort_candidates(&mut v);
818        assert_eq!(v[0].candidate, "b");
819    }
820
821    #[test]
822    fn test_collect_meta_stats() {
823        let ctx = mk_test_ctx();
824        let stats = collect_meta_stats(&ctx);
825        assert_eq!(stats.num_expr_mvars, 0);
826    }
827
828    #[test]
829    fn test_proof_state_report() {
830        let mut ctx = mk_test_ctx();
831        let goal_ty = oxilean_kernel::Expr::Const(oxilean_kernel::Name::str("P"), vec![]);
832        let (mvar_id, _) = ctx.mk_fresh_expr_mvar(goal_ty, crate::basic::MetavarKind::Natural);
833        let state = TacticState::single(mvar_id);
834        let report = ProofStateReport::from_state(&state);
835        assert_eq!(report.open_goals, 1);
836        assert!(!report.is_complete);
837    }
838
839    #[test]
840    fn test_tactic_registry_all_names() {
841        let mut reg = TacticRegistry::new();
842        reg.register("a");
843        reg.register("b");
844        assert_eq!(reg.all_names().len(), 2);
845    }
846
847    #[test]
848    fn test_mk_test_ctx() {
849        let ctx = mk_test_ctx();
850        assert_eq!(ctx.num_mvars(), 0);
851    }
852}
853
854// ============================================================
855// Additional meta-layer utilities
856// ============================================================
857
858/// Simple accumulator for meta-layer performance statistics.
859#[allow(dead_code)]
860pub struct PerfStats {
861    /// Total number of elaboration attempts.
862    pub elab_attempts: u64,
863    /// Number of successful elaborations.
864    pub elab_successes: u64,
865    /// Total unification attempts.
866    pub unif_attempts: u64,
867    /// Number of successful unifications.
868    pub unif_successes: u64,
869    /// Total elapsed time in microseconds.
870    pub elapsed_us: u64,
871}
872
873#[allow(dead_code)]
874impl PerfStats {
875    /// Create an empty stats record.
876    pub fn new() -> Self {
877        PerfStats {
878            elab_attempts: 0,
879            elab_successes: 0,
880            unif_attempts: 0,
881            unif_successes: 0,
882            elapsed_us: 0,
883        }
884    }
885
886    /// Return the elaboration success rate as a fraction in [0, 1].
887    pub fn elab_success_rate(&self) -> f64 {
888        if self.elab_attempts == 0 {
889            return 0.0;
890        }
891        self.elab_successes as f64 / self.elab_attempts as f64
892    }
893
894    /// Return the unification success rate as a fraction in [0, 1].
895    pub fn unif_success_rate(&self) -> f64 {
896        if self.unif_attempts == 0 {
897            return 0.0;
898        }
899        self.unif_successes as f64 / self.unif_attempts as f64
900    }
901
902    /// Merge another `PerfStats` into this one.
903    pub fn merge(&mut self, other: &PerfStats) {
904        self.elab_attempts += other.elab_attempts;
905        self.elab_successes += other.elab_successes;
906        self.unif_attempts += other.unif_attempts;
907        self.unif_successes += other.unif_successes;
908        self.elapsed_us += other.elapsed_us;
909    }
910}
911
912impl Default for PerfStats {
913    fn default() -> Self {
914        Self::new()
915    }
916}
917
918#[cfg(test)]
919mod perf_stats_tests {
920    use super::*;
921
922    #[test]
923    fn test_perf_stats_empty() {
924        let s = PerfStats::new();
925        assert_eq!(s.elab_attempts, 0);
926        assert!((s.elab_success_rate() - 0.0).abs() < 1e-9);
927    }
928
929    #[test]
930    fn test_perf_stats_success_rate() {
931        let mut s = PerfStats::new();
932        s.elab_attempts = 10;
933        s.elab_successes = 7;
934        assert!((s.elab_success_rate() - 0.7).abs() < 1e-9);
935    }
936
937    #[test]
938    fn test_perf_stats_merge() {
939        let mut a = PerfStats::new();
940        a.elab_attempts = 5;
941        let b = PerfStats::new();
942        a.merge(&b);
943        assert_eq!(a.elab_attempts, 5);
944    }
945
946    #[test]
947    fn test_perf_stats_unif_success_rate_no_attempts() {
948        let s = PerfStats::new();
949        assert!((s.unif_success_rate() - 0.0).abs() < 1e-9);
950    }
951
952    #[test]
953    fn test_perf_stats_merge_sums() {
954        let mut a = PerfStats::new();
955        a.elab_attempts = 5;
956        a.elab_successes = 3;
957        let mut b = PerfStats::new();
958        b.elab_attempts = 3;
959        b.elab_successes = 2;
960        a.merge(&b);
961        assert_eq!(a.elab_attempts, 8);
962        assert_eq!(a.elab_successes, 5);
963    }
964
965    #[test]
966    fn test_perf_stats_elapsed() {
967        let mut s = PerfStats::new();
968        s.elapsed_us = 1000;
969        assert_eq!(s.elapsed_us, 1000);
970    }
971
972    #[test]
973    fn test_perf_stats_default() {
974        let s = PerfStats::default();
975        assert_eq!(s.elab_attempts, 0);
976    }
977
978    #[test]
979    fn test_perf_stats_unif() {
980        let mut s = PerfStats::new();
981        s.unif_attempts = 4;
982        s.unif_successes = 3;
983        assert!((s.unif_success_rate() - 0.75).abs() < 1e-9);
984    }
985}
986
987// ============================================================
988// MetaLayer configuration and feature flags
989// ============================================================
990
991/// Feature flags for the meta layer.
992#[derive(Clone, Debug)]
993pub struct MetaFeatures {
994    /// Enable discrimination tree indexing for simp lemmas.
995    pub discr_tree: bool,
996    /// Enable memoization of WHNF results.
997    pub whnf_cache: bool,
998    /// Enable proof term recording.
999    pub proof_recording: bool,
1000    /// Enable instance synthesis.
1001    pub instance_synth: bool,
1002    /// Enable congr-lemma automation.
1003    pub congr_lemmas: bool,
1004}
1005
1006impl Default for MetaFeatures {
1007    fn default() -> Self {
1008        Self {
1009            discr_tree: true,
1010            whnf_cache: true,
1011            proof_recording: false,
1012            instance_synth: true,
1013            congr_lemmas: true,
1014        }
1015    }
1016}
1017
1018impl MetaFeatures {
1019    /// All features enabled.
1020    pub fn all_enabled() -> Self {
1021        Self {
1022            discr_tree: true,
1023            whnf_cache: true,
1024            proof_recording: true,
1025            instance_synth: true,
1026            congr_lemmas: true,
1027        }
1028    }
1029
1030    /// Minimal features (fast, less complete).
1031    pub fn minimal() -> Self {
1032        Self {
1033            discr_tree: false,
1034            whnf_cache: false,
1035            proof_recording: false,
1036            instance_synth: false,
1037            congr_lemmas: false,
1038        }
1039    }
1040
1041    /// Whether at least one caching feature is enabled.
1042    pub fn any_caching(&self) -> bool {
1043        self.whnf_cache || self.proof_recording
1044    }
1045}
1046
1047/// A named group of related tactics.
1048#[derive(Clone, Debug)]
1049pub struct TacticGroup {
1050    /// Group name.
1051    pub name: String,
1052    /// Tactic names in this group.
1053    pub members: Vec<String>,
1054    /// Short description of the group.
1055    pub description: String,
1056}
1057
1058impl TacticGroup {
1059    /// Create a tactic group.
1060    pub fn new(name: &str, description: &str) -> Self {
1061        Self {
1062            name: name.to_string(),
1063            members: Vec::new(),
1064            description: description.to_string(),
1065        }
1066    }
1067
1068    /// Add a member tactic.
1069    #[allow(clippy::should_implement_trait)]
1070    pub fn add(mut self, tactic: &str) -> Self {
1071        self.members.push(tactic.to_string());
1072        self
1073    }
1074
1075    /// Whether a tactic is in this group.
1076    pub fn contains(&self, tactic: &str) -> bool {
1077        self.members.iter().any(|m| m == tactic)
1078    }
1079}
1080
1081/// Return the standard tactic groups.
1082pub fn standard_tactic_groups() -> Vec<TacticGroup> {
1083    vec![
1084        TacticGroup::new("introduction", "Tactics that introduce hypotheses")
1085            .add("intro")
1086            .add("intros"),
1087        TacticGroup::new("closing", "Tactics that close goals")
1088            .add("exact")
1089            .add("assumption")
1090            .add("refl")
1091            .add("rfl")
1092            .add("trivial")
1093            .add("sorry"),
1094        TacticGroup::new("rewriting", "Tactics that rewrite the goal")
1095            .add("rw")
1096            .add("rewrite")
1097            .add("simp")
1098            .add("simp_all"),
1099        TacticGroup::new("structural", "Tactics that split/analyze goals")
1100            .add("cases")
1101            .add("induction")
1102            .add("split")
1103            .add("constructor")
1104            .add("left")
1105            .add("right"),
1106        TacticGroup::new("automation", "Automated solving tactics")
1107            .add("omega")
1108            .add("linarith")
1109            .add("ring")
1110            .add("norm_num")
1111            .add("decide"),
1112    ]
1113}
1114
1115/// Find the group that a tactic belongs to.
1116pub fn tactic_group_for(tactic: &str) -> Option<&'static str> {
1117    match tactic {
1118        "intro" | "intros" => Some("introduction"),
1119        "exact" | "assumption" | "refl" | "rfl" | "trivial" | "sorry" => Some("closing"),
1120        "rw" | "rewrite" | "simp" | "simp_all" => Some("rewriting"),
1121        "cases" | "induction" | "split" | "constructor" | "left" | "right" => Some("structural"),
1122        "omega" | "linarith" | "ring" | "norm_num" | "decide" => Some("automation"),
1123        _ => None,
1124    }
1125}
1126
1127#[cfg(test)]
1128mod meta_features_tests {
1129    use super::*;
1130
1131    #[test]
1132    fn test_meta_features_default() {
1133        let f = MetaFeatures::default();
1134        assert!(f.discr_tree);
1135        assert!(f.instance_synth);
1136        assert!(!f.proof_recording);
1137    }
1138
1139    #[test]
1140    fn test_meta_features_all_enabled() {
1141        let f = MetaFeatures::all_enabled();
1142        assert!(f.proof_recording);
1143        assert!(f.whnf_cache);
1144    }
1145
1146    #[test]
1147    fn test_meta_features_minimal() {
1148        let f = MetaFeatures::minimal();
1149        assert!(!f.discr_tree);
1150        assert!(!f.instance_synth);
1151    }
1152
1153    #[test]
1154    fn test_meta_features_any_caching_default() {
1155        let f = MetaFeatures::default();
1156        assert!(f.any_caching());
1157    }
1158
1159    #[test]
1160    fn test_meta_features_any_caching_minimal() {
1161        let f = MetaFeatures::minimal();
1162        assert!(!f.any_caching());
1163    }
1164
1165    #[test]
1166    fn test_tactic_group_contains() {
1167        let g = TacticGroup::new("test", "desc").add("intro").add("intros");
1168        assert!(g.contains("intro"));
1169        assert!(!g.contains("exact"));
1170    }
1171
1172    #[test]
1173    fn test_standard_tactic_groups_nonempty() {
1174        let groups = standard_tactic_groups();
1175        assert!(!groups.is_empty());
1176    }
1177
1178    #[test]
1179    fn test_tactic_group_for_intro() {
1180        assert_eq!(tactic_group_for("intro"), Some("introduction"));
1181    }
1182
1183    #[test]
1184    fn test_tactic_group_for_exact() {
1185        assert_eq!(tactic_group_for("exact"), Some("closing"));
1186    }
1187
1188    #[test]
1189    fn test_tactic_group_for_unknown() {
1190        assert_eq!(tactic_group_for("foobar_nonexistent"), None);
1191    }
1192
1193    #[test]
1194    fn test_tactic_group_for_omega() {
1195        assert_eq!(tactic_group_for("omega"), Some("automation"));
1196    }
1197}
1198
1199// ============================================================
1200// Extended: MetaLib Utilities (Part 2)
1201// ============================================================
1202
1203/// An extended utility type for MetaLib.
1204#[allow(dead_code)]
1205#[derive(Debug, Clone, Default)]
1206pub struct MetaLibExtUtil {
1207    pub key: String,
1208    pub data: Vec<i64>,
1209    pub active: bool,
1210    pub flags: u32,
1211}
1212
1213#[allow(dead_code)]
1214impl MetaLibExtUtil {
1215    pub fn new(key: &str) -> Self {
1216        MetaLibExtUtil {
1217            key: key.to_string(),
1218            data: Vec::new(),
1219            active: true,
1220            flags: 0,
1221        }
1222    }
1223
1224    pub fn push(&mut self, v: i64) {
1225        self.data.push(v);
1226    }
1227    pub fn pop(&mut self) -> Option<i64> {
1228        self.data.pop()
1229    }
1230    pub fn sum(&self) -> i64 {
1231        self.data.iter().sum()
1232    }
1233    pub fn min_val(&self) -> Option<i64> {
1234        self.data.iter().copied().reduce(i64::min)
1235    }
1236    pub fn max_val(&self) -> Option<i64> {
1237        self.data.iter().copied().reduce(i64::max)
1238    }
1239    pub fn len(&self) -> usize {
1240        self.data.len()
1241    }
1242    pub fn is_empty(&self) -> bool {
1243        self.data.is_empty()
1244    }
1245    pub fn clear(&mut self) {
1246        self.data.clear();
1247    }
1248    pub fn set_flag(&mut self, bit: u32) {
1249        self.flags |= 1 << bit;
1250    }
1251    pub fn has_flag(&self, bit: u32) -> bool {
1252        self.flags & (1 << bit) != 0
1253    }
1254    pub fn deactivate(&mut self) {
1255        self.active = false;
1256    }
1257    pub fn activate(&mut self) {
1258        self.active = true;
1259    }
1260}
1261
1262/// An extended map for MetaLib keys to values.
1263#[allow(dead_code)]
1264pub struct MetaLibExtMap<V> {
1265    pub data: std::collections::HashMap<String, V>,
1266    pub default_key: Option<String>,
1267}
1268
1269#[allow(dead_code)]
1270impl<V: Clone + Default> MetaLibExtMap<V> {
1271    pub fn new() -> Self {
1272        MetaLibExtMap {
1273            data: std::collections::HashMap::new(),
1274            default_key: None,
1275        }
1276    }
1277
1278    pub fn insert(&mut self, key: &str, value: V) {
1279        self.data.insert(key.to_string(), value);
1280    }
1281
1282    pub fn get(&self, key: &str) -> Option<&V> {
1283        self.data.get(key)
1284    }
1285
1286    pub fn get_or_default(&self, key: &str) -> V {
1287        self.data.get(key).cloned().unwrap_or_default()
1288    }
1289
1290    pub fn contains(&self, key: &str) -> bool {
1291        self.data.contains_key(key)
1292    }
1293    pub fn remove(&mut self, key: &str) -> Option<V> {
1294        self.data.remove(key)
1295    }
1296    pub fn size(&self) -> usize {
1297        self.data.len()
1298    }
1299    pub fn is_empty(&self) -> bool {
1300        self.data.is_empty()
1301    }
1302
1303    pub fn set_default(&mut self, key: &str) {
1304        self.default_key = Some(key.to_string());
1305    }
1306
1307    pub fn keys_sorted(&self) -> Vec<&String> {
1308        let mut keys: Vec<&String> = self.data.keys().collect();
1309        keys.sort();
1310        keys
1311    }
1312}
1313
1314impl<V: Clone + Default> Default for MetaLibExtMap<V> {
1315    fn default() -> Self {
1316        Self::new()
1317    }
1318}
1319
1320/// A sliding window accumulator for MetaLib.
1321#[allow(dead_code)]
1322pub struct MetaLibWindow {
1323    pub buffer: std::collections::VecDeque<f64>,
1324    pub capacity: usize,
1325    pub running_sum: f64,
1326}
1327
1328#[allow(dead_code)]
1329impl MetaLibWindow {
1330    pub fn new(capacity: usize) -> Self {
1331        MetaLibWindow {
1332            buffer: std::collections::VecDeque::new(),
1333            capacity,
1334            running_sum: 0.0,
1335        }
1336    }
1337
1338    pub fn push(&mut self, v: f64) {
1339        if self.buffer.len() >= self.capacity {
1340            if let Some(old) = self.buffer.pop_front() {
1341                self.running_sum -= old;
1342            }
1343        }
1344        self.buffer.push_back(v);
1345        self.running_sum += v;
1346    }
1347
1348    pub fn mean(&self) -> f64 {
1349        if self.buffer.is_empty() {
1350            0.0
1351        } else {
1352            self.running_sum / self.buffer.len() as f64
1353        }
1354    }
1355
1356    pub fn variance(&self) -> f64 {
1357        if self.buffer.len() < 2 {
1358            return 0.0;
1359        }
1360        let m = self.mean();
1361        self.buffer.iter().map(|&x| (x - m).powi(2)).sum::<f64>() / self.buffer.len() as f64
1362    }
1363
1364    pub fn std_dev(&self) -> f64 {
1365        self.variance().sqrt()
1366    }
1367    pub fn len(&self) -> usize {
1368        self.buffer.len()
1369    }
1370    pub fn is_full(&self) -> bool {
1371        self.buffer.len() >= self.capacity
1372    }
1373    pub fn is_empty(&self) -> bool {
1374        self.buffer.is_empty()
1375    }
1376}
1377
1378/// A builder pattern for MetaLib.
1379#[allow(dead_code)]
1380pub struct MetaLibBuilder {
1381    pub name: String,
1382    pub items: Vec<String>,
1383    pub config: std::collections::HashMap<String, String>,
1384}
1385
1386#[allow(dead_code)]
1387impl MetaLibBuilder {
1388    pub fn new(name: &str) -> Self {
1389        MetaLibBuilder {
1390            name: name.to_string(),
1391            items: Vec::new(),
1392            config: std::collections::HashMap::new(),
1393        }
1394    }
1395
1396    pub fn add_item(mut self, item: &str) -> Self {
1397        self.items.push(item.to_string());
1398        self
1399    }
1400
1401    pub fn set_config(mut self, key: &str, value: &str) -> Self {
1402        self.config.insert(key.to_string(), value.to_string());
1403        self
1404    }
1405
1406    pub fn item_count(&self) -> usize {
1407        self.items.len()
1408    }
1409    pub fn has_config(&self, key: &str) -> bool {
1410        self.config.contains_key(key)
1411    }
1412    pub fn get_config(&self, key: &str) -> Option<&str> {
1413        self.config.get(key).map(|s| s.as_str())
1414    }
1415
1416    pub fn build_summary(&self) -> String {
1417        format!(
1418            "{}: {} items, {} config keys",
1419            self.name,
1420            self.items.len(),
1421            self.config.len()
1422        )
1423    }
1424}
1425
1426/// A state machine for MetaLib.
1427#[allow(dead_code)]
1428#[derive(Debug, Clone, PartialEq)]
1429pub enum MetaLibState {
1430    Initial,
1431    Running,
1432    Paused,
1433    Complete,
1434    Failed(String),
1435}
1436
1437#[allow(dead_code)]
1438impl MetaLibState {
1439    pub fn is_terminal(&self) -> bool {
1440        matches!(self, MetaLibState::Complete | MetaLibState::Failed(_))
1441    }
1442
1443    pub fn can_run(&self) -> bool {
1444        matches!(self, MetaLibState::Initial | MetaLibState::Paused)
1445    }
1446    pub fn is_running(&self) -> bool {
1447        matches!(self, MetaLibState::Running)
1448    }
1449    pub fn error_msg(&self) -> Option<&str> {
1450        match self {
1451            MetaLibState::Failed(s) => Some(s),
1452            _ => None,
1453        }
1454    }
1455}
1456
1457/// A state machine controller for MetaLib.
1458#[allow(dead_code)]
1459pub struct MetaLibStateMachine {
1460    pub state: MetaLibState,
1461    pub transitions: usize,
1462    pub history: Vec<String>,
1463}
1464
1465#[allow(dead_code)]
1466impl MetaLibStateMachine {
1467    pub fn new() -> Self {
1468        MetaLibStateMachine {
1469            state: MetaLibState::Initial,
1470            transitions: 0,
1471            history: Vec::new(),
1472        }
1473    }
1474
1475    pub fn transition_to(&mut self, new_state: MetaLibState) -> bool {
1476        if self.state.is_terminal() {
1477            return false;
1478        }
1479        let desc = format!("{:?} -> {:?}", self.state, new_state);
1480        self.state = new_state;
1481        self.transitions += 1;
1482        self.history.push(desc);
1483        true
1484    }
1485
1486    pub fn start(&mut self) -> bool {
1487        self.transition_to(MetaLibState::Running)
1488    }
1489    pub fn pause(&mut self) -> bool {
1490        self.transition_to(MetaLibState::Paused)
1491    }
1492    pub fn complete(&mut self) -> bool {
1493        self.transition_to(MetaLibState::Complete)
1494    }
1495    pub fn fail(&mut self, msg: &str) -> bool {
1496        self.transition_to(MetaLibState::Failed(msg.to_string()))
1497    }
1498    pub fn num_transitions(&self) -> usize {
1499        self.transitions
1500    }
1501}
1502
1503impl Default for MetaLibStateMachine {
1504    fn default() -> Self {
1505        Self::new()
1506    }
1507}
1508
1509/// A work queue for MetaLib items.
1510#[allow(dead_code)]
1511pub struct MetaLibWorkQueue {
1512    pub pending: std::collections::VecDeque<String>,
1513    pub processed: Vec<String>,
1514    pub capacity: usize,
1515}
1516
1517#[allow(dead_code)]
1518impl MetaLibWorkQueue {
1519    pub fn new(capacity: usize) -> Self {
1520        MetaLibWorkQueue {
1521            pending: std::collections::VecDeque::new(),
1522            processed: Vec::new(),
1523            capacity,
1524        }
1525    }
1526
1527    pub fn enqueue(&mut self, item: String) -> bool {
1528        if self.pending.len() >= self.capacity {
1529            return false;
1530        }
1531        self.pending.push_back(item);
1532        true
1533    }
1534
1535    pub fn dequeue(&mut self) -> Option<String> {
1536        let item = self.pending.pop_front()?;
1537        self.processed.push(item.clone());
1538        Some(item)
1539    }
1540
1541    pub fn pending_count(&self) -> usize {
1542        self.pending.len()
1543    }
1544    pub fn processed_count(&self) -> usize {
1545        self.processed.len()
1546    }
1547    pub fn is_empty(&self) -> bool {
1548        self.pending.is_empty()
1549    }
1550    pub fn is_full(&self) -> bool {
1551        self.pending.len() >= self.capacity
1552    }
1553    pub fn total_processed(&self) -> usize {
1554        self.processed.len()
1555    }
1556}
1557
1558/// A counter map for MetaLib frequency analysis.
1559#[allow(dead_code)]
1560pub struct MetaLibCounterMap {
1561    pub counts: std::collections::HashMap<String, usize>,
1562    pub total: usize,
1563}
1564
1565#[allow(dead_code)]
1566impl MetaLibCounterMap {
1567    pub fn new() -> Self {
1568        MetaLibCounterMap {
1569            counts: std::collections::HashMap::new(),
1570            total: 0,
1571        }
1572    }
1573
1574    pub fn increment(&mut self, key: &str) {
1575        *self.counts.entry(key.to_string()).or_insert(0) += 1;
1576        self.total += 1;
1577    }
1578
1579    pub fn count(&self, key: &str) -> usize {
1580        *self.counts.get(key).unwrap_or(&0)
1581    }
1582
1583    pub fn frequency(&self, key: &str) -> f64 {
1584        if self.total == 0 {
1585            0.0
1586        } else {
1587            self.count(key) as f64 / self.total as f64
1588        }
1589    }
1590
1591    pub fn most_common(&self) -> Option<(&String, usize)> {
1592        self.counts
1593            .iter()
1594            .max_by_key(|(_, &v)| v)
1595            .map(|(k, &v)| (k, v))
1596    }
1597
1598    pub fn num_unique(&self) -> usize {
1599        self.counts.len()
1600    }
1601    pub fn is_empty(&self) -> bool {
1602        self.counts.is_empty()
1603    }
1604}
1605
1606impl Default for MetaLibCounterMap {
1607    fn default() -> Self {
1608        Self::new()
1609    }
1610}
1611
1612#[cfg(test)]
1613mod metalib_ext2_tests {
1614    use super::*;
1615
1616    #[test]
1617    fn test_metalib_ext_util_basic() {
1618        let mut u = MetaLibExtUtil::new("test");
1619        u.push(10);
1620        u.push(20);
1621        assert_eq!(u.sum(), 30);
1622        assert_eq!(u.len(), 2);
1623    }
1624
1625    #[test]
1626    fn test_metalib_ext_util_min_max() {
1627        let mut u = MetaLibExtUtil::new("mm");
1628        u.push(5);
1629        u.push(1);
1630        u.push(9);
1631        assert_eq!(u.min_val(), Some(1));
1632        assert_eq!(u.max_val(), Some(9));
1633    }
1634
1635    #[test]
1636    fn test_metalib_ext_util_flags() {
1637        let mut u = MetaLibExtUtil::new("flags");
1638        u.set_flag(3);
1639        assert!(u.has_flag(3));
1640        assert!(!u.has_flag(2));
1641    }
1642
1643    #[test]
1644    fn test_metalib_ext_util_pop() {
1645        let mut u = MetaLibExtUtil::new("pop");
1646        u.push(42);
1647        assert_eq!(u.pop(), Some(42));
1648        assert!(u.is_empty());
1649    }
1650
1651    #[test]
1652    fn test_metalib_ext_map_basic() {
1653        let mut m: MetaLibExtMap<i32> = MetaLibExtMap::new();
1654        m.insert("key", 42);
1655        assert_eq!(m.get("key"), Some(&42));
1656        assert!(m.contains("key"));
1657        assert!(!m.contains("other"));
1658    }
1659
1660    #[test]
1661    fn test_metalib_ext_map_get_or_default() {
1662        let mut m: MetaLibExtMap<i32> = MetaLibExtMap::new();
1663        m.insert("k", 5);
1664        assert_eq!(m.get_or_default("k"), 5);
1665        assert_eq!(m.get_or_default("missing"), 0);
1666    }
1667
1668    #[test]
1669    fn test_metalib_ext_map_keys_sorted() {
1670        let mut m: MetaLibExtMap<i32> = MetaLibExtMap::new();
1671        m.insert("z", 1);
1672        m.insert("a", 2);
1673        m.insert("m", 3);
1674        let keys = m.keys_sorted();
1675        assert_eq!(keys[0].as_str(), "a");
1676        assert_eq!(keys[2].as_str(), "z");
1677    }
1678
1679    #[test]
1680    fn test_metalib_window_mean() {
1681        let mut w = MetaLibWindow::new(3);
1682        w.push(1.0);
1683        w.push(2.0);
1684        w.push(3.0);
1685        assert!((w.mean() - 2.0).abs() < 1e-10);
1686    }
1687
1688    #[test]
1689    fn test_metalib_window_evict() {
1690        let mut w = MetaLibWindow::new(2);
1691        w.push(10.0);
1692        w.push(20.0);
1693        w.push(30.0); // evicts 10.0
1694        assert_eq!(w.len(), 2);
1695        assert!((w.mean() - 25.0).abs() < 1e-10);
1696    }
1697
1698    #[test]
1699    fn test_metalib_window_std_dev() {
1700        let mut w = MetaLibWindow::new(10);
1701        for i in 0..10 {
1702            w.push(i as f64);
1703        }
1704        assert!(w.std_dev() > 0.0);
1705    }
1706
1707    #[test]
1708    fn test_metalib_builder_basic() {
1709        let b = MetaLibBuilder::new("test")
1710            .add_item("a")
1711            .add_item("b")
1712            .set_config("key", "val");
1713        assert_eq!(b.item_count(), 2);
1714        assert!(b.has_config("key"));
1715        assert_eq!(b.get_config("key"), Some("val"));
1716    }
1717
1718    #[test]
1719    fn test_metalib_builder_summary() {
1720        let b = MetaLibBuilder::new("suite").add_item("x");
1721        let s = b.build_summary();
1722        assert!(s.contains("suite"));
1723    }
1724
1725    #[test]
1726    fn test_metalib_state_machine_start() {
1727        let mut sm = MetaLibStateMachine::new();
1728        assert!(sm.start());
1729        assert!(sm.state.is_running());
1730    }
1731
1732    #[test]
1733    fn test_metalib_state_machine_complete() {
1734        let mut sm = MetaLibStateMachine::new();
1735        sm.start();
1736        sm.complete();
1737        assert!(sm.state.is_terminal());
1738    }
1739
1740    #[test]
1741    fn test_metalib_state_machine_fail() {
1742        let mut sm = MetaLibStateMachine::new();
1743        sm.fail("oops");
1744        assert!(sm.state.is_terminal());
1745        assert_eq!(sm.state.error_msg(), Some("oops"));
1746    }
1747
1748    #[test]
1749    fn test_metalib_state_machine_no_transition_after_terminal() {
1750        let mut sm = MetaLibStateMachine::new();
1751        sm.complete();
1752        assert!(!sm.start()); // Already terminal
1753    }
1754
1755    #[test]
1756    fn test_metalib_work_queue_basic() {
1757        let mut wq = MetaLibWorkQueue::new(10);
1758        wq.enqueue("task1".to_string());
1759        wq.enqueue("task2".to_string());
1760        assert_eq!(wq.pending_count(), 2);
1761        let t = wq.dequeue();
1762        assert_eq!(t, Some("task1".to_string()));
1763        assert_eq!(wq.processed_count(), 1);
1764    }
1765
1766    #[test]
1767    fn test_metalib_work_queue_capacity() {
1768        let mut wq = MetaLibWorkQueue::new(2);
1769        wq.enqueue("a".to_string());
1770        wq.enqueue("b".to_string());
1771        assert!(wq.is_full());
1772        assert!(!wq.enqueue("c".to_string()));
1773    }
1774
1775    #[test]
1776    fn test_metalib_counter_map_basic() {
1777        let mut cm = MetaLibCounterMap::new();
1778        cm.increment("apple");
1779        cm.increment("apple");
1780        cm.increment("banana");
1781        assert_eq!(cm.count("apple"), 2);
1782        assert_eq!(cm.count("banana"), 1);
1783        assert_eq!(cm.num_unique(), 2);
1784    }
1785
1786    #[test]
1787    fn test_metalib_counter_map_frequency() {
1788        let mut cm = MetaLibCounterMap::new();
1789        cm.increment("a");
1790        cm.increment("a");
1791        cm.increment("b");
1792        assert!((cm.frequency("a") - 2.0 / 3.0).abs() < 1e-9);
1793    }
1794
1795    #[test]
1796    fn test_metalib_counter_map_most_common() {
1797        let mut cm = MetaLibCounterMap::new();
1798        cm.increment("x");
1799        cm.increment("y");
1800        cm.increment("x");
1801        let (k, v) = cm
1802            .most_common()
1803            .expect("most_common should return a value after increments");
1804        assert_eq!(k.as_str(), "x");
1805        assert_eq!(v, 2);
1806    }
1807}
1808
1809// ============================================================
1810// Extended: Lib Analysis Infrastructure
1811// ============================================================
1812
1813/// A result type for Lib analysis.
1814#[allow(dead_code)]
1815#[derive(Debug, Clone, PartialEq)]
1816pub enum LibResult {
1817    Ok(String),
1818    Err(String),
1819    Partial { done: usize, total: usize },
1820    Skipped,
1821}
1822
1823#[allow(dead_code)]
1824impl LibResult {
1825    pub fn is_ok(&self) -> bool {
1826        matches!(self, LibResult::Ok(_))
1827    }
1828    pub fn is_err(&self) -> bool {
1829        matches!(self, LibResult::Err(_))
1830    }
1831    pub fn is_partial(&self) -> bool {
1832        matches!(self, LibResult::Partial { .. })
1833    }
1834    pub fn is_skipped(&self) -> bool {
1835        matches!(self, LibResult::Skipped)
1836    }
1837    pub fn ok_msg(&self) -> Option<&str> {
1838        match self {
1839            LibResult::Ok(s) => Some(s),
1840            _ => None,
1841        }
1842    }
1843    pub fn err_msg(&self) -> Option<&str> {
1844        match self {
1845            LibResult::Err(s) => Some(s),
1846            _ => None,
1847        }
1848    }
1849    pub fn progress(&self) -> f64 {
1850        match self {
1851            LibResult::Ok(_) => 1.0,
1852            LibResult::Err(_) => 0.0,
1853            LibResult::Skipped => 0.0,
1854            LibResult::Partial { done, total } => {
1855                if *total == 0 {
1856                    0.0
1857                } else {
1858                    *done as f64 / *total as f64
1859                }
1860            }
1861        }
1862    }
1863}
1864
1865/// An analysis pass for Lib.
1866#[allow(dead_code)]
1867pub struct LibAnalysisPass {
1868    pub name: String,
1869    pub enabled: bool,
1870    pub results: Vec<LibResult>,
1871    pub total_runs: usize,
1872}
1873
1874#[allow(dead_code)]
1875impl LibAnalysisPass {
1876    pub fn new(name: &str) -> Self {
1877        LibAnalysisPass {
1878            name: name.to_string(),
1879            enabled: true,
1880            results: Vec::new(),
1881            total_runs: 0,
1882        }
1883    }
1884
1885    pub fn run(&mut self, input: &str) -> LibResult {
1886        self.total_runs += 1;
1887        let result = if input.is_empty() {
1888            LibResult::Err("empty input".to_string())
1889        } else {
1890            LibResult::Ok(format!("processed: {}", input))
1891        };
1892        self.results.push(result.clone());
1893        result
1894    }
1895
1896    pub fn success_count(&self) -> usize {
1897        self.results.iter().filter(|r| r.is_ok()).count()
1898    }
1899
1900    pub fn error_count(&self) -> usize {
1901        self.results.iter().filter(|r| r.is_err()).count()
1902    }
1903
1904    pub fn success_rate(&self) -> f64 {
1905        if self.total_runs == 0 {
1906            0.0
1907        } else {
1908            self.success_count() as f64 / self.total_runs as f64
1909        }
1910    }
1911
1912    pub fn disable(&mut self) {
1913        self.enabled = false;
1914    }
1915    pub fn enable(&mut self) {
1916        self.enabled = true;
1917    }
1918    pub fn clear_results(&mut self) {
1919        self.results.clear();
1920    }
1921}
1922
1923/// A pipeline of Lib analysis passes.
1924#[allow(dead_code)]
1925pub struct LibPipeline {
1926    pub passes: Vec<LibAnalysisPass>,
1927    pub name: String,
1928    pub total_inputs_processed: usize,
1929}
1930
1931#[allow(dead_code)]
1932impl LibPipeline {
1933    pub fn new(name: &str) -> Self {
1934        LibPipeline {
1935            passes: Vec::new(),
1936            name: name.to_string(),
1937            total_inputs_processed: 0,
1938        }
1939    }
1940
1941    pub fn add_pass(&mut self, pass: LibAnalysisPass) {
1942        self.passes.push(pass);
1943    }
1944
1945    pub fn run_all(&mut self, input: &str) -> Vec<LibResult> {
1946        self.total_inputs_processed += 1;
1947        self.passes
1948            .iter_mut()
1949            .filter(|p| p.enabled)
1950            .map(|p| p.run(input))
1951            .collect()
1952    }
1953
1954    pub fn num_passes(&self) -> usize {
1955        self.passes.len()
1956    }
1957    pub fn num_enabled_passes(&self) -> usize {
1958        self.passes.iter().filter(|p| p.enabled).count()
1959    }
1960    pub fn total_success_rate(&self) -> f64 {
1961        if self.passes.is_empty() {
1962            0.0
1963        } else {
1964            let total_rate: f64 = self.passes.iter().map(|p| p.success_rate()).sum();
1965            total_rate / self.passes.len() as f64
1966        }
1967    }
1968}
1969
1970/// A diff for Lib analysis results.
1971#[allow(dead_code)]
1972#[derive(Debug, Clone)]
1973pub struct LibDiff {
1974    pub added: Vec<String>,
1975    pub removed: Vec<String>,
1976    pub unchanged: Vec<String>,
1977}
1978
1979#[allow(dead_code)]
1980impl LibDiff {
1981    pub fn new() -> Self {
1982        LibDiff {
1983            added: Vec::new(),
1984            removed: Vec::new(),
1985            unchanged: Vec::new(),
1986        }
1987    }
1988
1989    pub fn add(&mut self, s: &str) {
1990        self.added.push(s.to_string());
1991    }
1992    pub fn remove(&mut self, s: &str) {
1993        self.removed.push(s.to_string());
1994    }
1995    pub fn keep(&mut self, s: &str) {
1996        self.unchanged.push(s.to_string());
1997    }
1998
1999    pub fn is_empty(&self) -> bool {
2000        self.added.is_empty() && self.removed.is_empty()
2001    }
2002
2003    pub fn total_changes(&self) -> usize {
2004        self.added.len() + self.removed.len()
2005    }
2006    pub fn net_additions(&self) -> i64 {
2007        self.added.len() as i64 - self.removed.len() as i64
2008    }
2009
2010    pub fn summary(&self) -> String {
2011        format!(
2012            "+{} -{} =={}",
2013            self.added.len(),
2014            self.removed.len(),
2015            self.unchanged.len()
2016        )
2017    }
2018}
2019
2020impl Default for LibDiff {
2021    fn default() -> Self {
2022        Self::new()
2023    }
2024}
2025
2026/// A typed slot for Lib configuration.
2027#[allow(dead_code)]
2028#[derive(Debug, Clone)]
2029pub enum LibConfigValue {
2030    Bool(bool),
2031    Int(i64),
2032    Float(f64),
2033    Str(String),
2034    List(Vec<String>),
2035}
2036
2037#[allow(dead_code)]
2038impl LibConfigValue {
2039    pub fn as_bool(&self) -> Option<bool> {
2040        match self {
2041            LibConfigValue::Bool(b) => Some(*b),
2042            _ => None,
2043        }
2044    }
2045    pub fn as_int(&self) -> Option<i64> {
2046        match self {
2047            LibConfigValue::Int(i) => Some(*i),
2048            _ => None,
2049        }
2050    }
2051    pub fn as_float(&self) -> Option<f64> {
2052        match self {
2053            LibConfigValue::Float(f) => Some(*f),
2054            _ => None,
2055        }
2056    }
2057    pub fn as_str(&self) -> Option<&str> {
2058        match self {
2059            LibConfigValue::Str(s) => Some(s),
2060            _ => None,
2061        }
2062    }
2063    pub fn as_list(&self) -> Option<&[String]> {
2064        match self {
2065            LibConfigValue::List(v) => Some(v),
2066            _ => None,
2067        }
2068    }
2069    pub fn type_name(&self) -> &'static str {
2070        match self {
2071            LibConfigValue::Bool(_) => "bool",
2072            LibConfigValue::Int(_) => "int",
2073            LibConfigValue::Float(_) => "float",
2074            LibConfigValue::Str(_) => "str",
2075            LibConfigValue::List(_) => "list",
2076        }
2077    }
2078}
2079
2080/// A configuration store for Lib.
2081#[allow(dead_code)]
2082pub struct LibConfig {
2083    pub values: std::collections::HashMap<String, LibConfigValue>,
2084    pub read_only: bool,
2085}
2086
2087#[allow(dead_code)]
2088impl LibConfig {
2089    pub fn new() -> Self {
2090        LibConfig {
2091            values: std::collections::HashMap::new(),
2092            read_only: false,
2093        }
2094    }
2095
2096    pub fn set(&mut self, key: &str, value: LibConfigValue) -> bool {
2097        if self.read_only {
2098            return false;
2099        }
2100        self.values.insert(key.to_string(), value);
2101        true
2102    }
2103
2104    pub fn get(&self, key: &str) -> Option<&LibConfigValue> {
2105        self.values.get(key)
2106    }
2107
2108    pub fn get_bool(&self, key: &str) -> Option<bool> {
2109        self.get(key)?.as_bool()
2110    }
2111    pub fn get_int(&self, key: &str) -> Option<i64> {
2112        self.get(key)?.as_int()
2113    }
2114    pub fn get_str(&self, key: &str) -> Option<&str> {
2115        self.get(key)?.as_str()
2116    }
2117
2118    pub fn set_bool(&mut self, key: &str, v: bool) -> bool {
2119        self.set(key, LibConfigValue::Bool(v))
2120    }
2121    pub fn set_int(&mut self, key: &str, v: i64) -> bool {
2122        self.set(key, LibConfigValue::Int(v))
2123    }
2124    pub fn set_str(&mut self, key: &str, v: &str) -> bool {
2125        self.set(key, LibConfigValue::Str(v.to_string()))
2126    }
2127
2128    pub fn lock(&mut self) {
2129        self.read_only = true;
2130    }
2131    pub fn unlock(&mut self) {
2132        self.read_only = false;
2133    }
2134    pub fn size(&self) -> usize {
2135        self.values.len()
2136    }
2137    pub fn has(&self, key: &str) -> bool {
2138        self.values.contains_key(key)
2139    }
2140    pub fn remove(&mut self, key: &str) -> bool {
2141        self.values.remove(key).is_some()
2142    }
2143}
2144
2145impl Default for LibConfig {
2146    fn default() -> Self {
2147        Self::new()
2148    }
2149}
2150
2151/// A diagnostic reporter for Lib.
2152#[allow(dead_code)]
2153pub struct LibDiagnostics {
2154    pub errors: Vec<String>,
2155    pub warnings: Vec<String>,
2156    pub notes: Vec<String>,
2157    pub max_errors: usize,
2158}
2159
2160#[allow(dead_code)]
2161impl LibDiagnostics {
2162    pub fn new(max_errors: usize) -> Self {
2163        LibDiagnostics {
2164            errors: Vec::new(),
2165            warnings: Vec::new(),
2166            notes: Vec::new(),
2167            max_errors,
2168        }
2169    }
2170
2171    pub fn error(&mut self, msg: &str) {
2172        if self.errors.len() < self.max_errors {
2173            self.errors.push(msg.to_string());
2174        }
2175    }
2176
2177    pub fn warning(&mut self, msg: &str) {
2178        self.warnings.push(msg.to_string());
2179    }
2180    pub fn note(&mut self, msg: &str) {
2181        self.notes.push(msg.to_string());
2182    }
2183
2184    pub fn has_errors(&self) -> bool {
2185        !self.errors.is_empty()
2186    }
2187    pub fn num_errors(&self) -> usize {
2188        self.errors.len()
2189    }
2190    pub fn num_warnings(&self) -> usize {
2191        self.warnings.len()
2192    }
2193    pub fn is_clean(&self) -> bool {
2194        self.errors.is_empty() && self.warnings.is_empty()
2195    }
2196    pub fn at_error_limit(&self) -> bool {
2197        self.errors.len() >= self.max_errors
2198    }
2199
2200    pub fn clear(&mut self) {
2201        self.errors.clear();
2202        self.warnings.clear();
2203        self.notes.clear();
2204    }
2205
2206    pub fn summary(&self) -> String {
2207        format!(
2208            "{} error(s), {} warning(s)",
2209            self.errors.len(),
2210            self.warnings.len()
2211        )
2212    }
2213}
2214
2215#[cfg(test)]
2216mod lib_analysis_tests {
2217    use super::*;
2218
2219    #[test]
2220    fn test_lib_result_ok() {
2221        let r = LibResult::Ok("success".to_string());
2222        assert!(r.is_ok());
2223        assert!(!r.is_err());
2224        assert_eq!(r.ok_msg(), Some("success"));
2225        assert!((r.progress() - 1.0).abs() < 1e-10);
2226    }
2227
2228    #[test]
2229    fn test_lib_result_err() {
2230        let r = LibResult::Err("failure".to_string());
2231        assert!(r.is_err());
2232        assert_eq!(r.err_msg(), Some("failure"));
2233        assert!((r.progress() - 0.0).abs() < 1e-10);
2234    }
2235
2236    #[test]
2237    fn test_lib_result_partial() {
2238        let r = LibResult::Partial { done: 3, total: 10 };
2239        assert!(r.is_partial());
2240        assert!((r.progress() - 0.3).abs() < 1e-10);
2241    }
2242
2243    #[test]
2244    fn test_lib_result_skipped() {
2245        let r = LibResult::Skipped;
2246        assert!(r.is_skipped());
2247    }
2248
2249    #[test]
2250    fn test_lib_analysis_pass_run() {
2251        let mut p = LibAnalysisPass::new("test_pass");
2252        let r = p.run("hello");
2253        assert!(r.is_ok());
2254        assert_eq!(p.total_runs, 1);
2255        assert_eq!(p.success_count(), 1);
2256    }
2257
2258    #[test]
2259    fn test_lib_analysis_pass_empty_input() {
2260        let mut p = LibAnalysisPass::new("empty_test");
2261        let r = p.run("");
2262        assert!(r.is_err());
2263        assert_eq!(p.error_count(), 1);
2264    }
2265
2266    #[test]
2267    fn test_lib_analysis_pass_success_rate() {
2268        let mut p = LibAnalysisPass::new("rate_test");
2269        p.run("a");
2270        p.run("b");
2271        p.run("");
2272        assert!((p.success_rate() - 2.0 / 3.0).abs() < 1e-9);
2273    }
2274
2275    #[test]
2276    fn test_lib_analysis_pass_disable() {
2277        let mut p = LibAnalysisPass::new("disable_test");
2278        p.disable();
2279        assert!(!p.enabled);
2280        p.enable();
2281        assert!(p.enabled);
2282    }
2283
2284    #[test]
2285    fn test_lib_pipeline_basic() {
2286        let mut pipeline = LibPipeline::new("main_pipeline");
2287        pipeline.add_pass(LibAnalysisPass::new("pass1"));
2288        pipeline.add_pass(LibAnalysisPass::new("pass2"));
2289        assert_eq!(pipeline.num_passes(), 2);
2290        let results = pipeline.run_all("test_input");
2291        assert_eq!(results.len(), 2);
2292    }
2293
2294    #[test]
2295    fn test_lib_pipeline_disabled_pass() {
2296        let mut pipeline = LibPipeline::new("partial");
2297        let mut p = LibAnalysisPass::new("disabled");
2298        p.disable();
2299        pipeline.add_pass(p);
2300        pipeline.add_pass(LibAnalysisPass::new("enabled"));
2301        assert_eq!(pipeline.num_enabled_passes(), 1);
2302        let results = pipeline.run_all("input");
2303        assert_eq!(results.len(), 1);
2304    }
2305
2306    #[test]
2307    fn test_lib_diff_basic() {
2308        let mut d = LibDiff::new();
2309        d.add("new_item");
2310        d.remove("old_item");
2311        d.keep("same_item");
2312        assert!(!d.is_empty());
2313        assert_eq!(d.total_changes(), 2);
2314        assert_eq!(d.net_additions(), 0);
2315    }
2316
2317    #[test]
2318    fn test_lib_diff_summary() {
2319        let mut d = LibDiff::new();
2320        d.add("x");
2321        d.add("y");
2322        d.remove("z");
2323        let s = d.summary();
2324        assert!(s.contains("+2"));
2325    }
2326
2327    #[test]
2328    fn test_lib_config_set_get() {
2329        let mut cfg = LibConfig::new();
2330        cfg.set_bool("debug", true);
2331        cfg.set_int("max_iter", 100);
2332        cfg.set_str("name", "test");
2333        assert_eq!(cfg.get_bool("debug"), Some(true));
2334        assert_eq!(cfg.get_int("max_iter"), Some(100));
2335        assert_eq!(cfg.get_str("name"), Some("test"));
2336    }
2337
2338    #[test]
2339    fn test_lib_config_read_only() {
2340        let mut cfg = LibConfig::new();
2341        cfg.set_bool("key", true);
2342        cfg.lock();
2343        assert!(!cfg.set_bool("key", false)); // should fail
2344        assert_eq!(cfg.get_bool("key"), Some(true)); // unchanged
2345        cfg.unlock();
2346        assert!(cfg.set_bool("key", false));
2347    }
2348
2349    #[test]
2350    fn test_lib_config_remove() {
2351        let mut cfg = LibConfig::new();
2352        cfg.set_int("x", 42);
2353        assert!(cfg.has("x"));
2354        cfg.remove("x");
2355        assert!(!cfg.has("x"));
2356    }
2357
2358    #[test]
2359    fn test_lib_diagnostics_basic() {
2360        let mut diag = LibDiagnostics::new(10);
2361        diag.error("something went wrong");
2362        diag.warning("maybe check this");
2363        diag.note("fyi");
2364        assert!(diag.has_errors());
2365        assert!(!diag.is_clean());
2366        assert_eq!(diag.num_errors(), 1);
2367        assert_eq!(diag.num_warnings(), 1);
2368    }
2369
2370    #[test]
2371    fn test_lib_diagnostics_max_errors() {
2372        let mut diag = LibDiagnostics::new(2);
2373        diag.error("e1");
2374        diag.error("e2");
2375        diag.error("e3"); // e3 dropped
2376        assert_eq!(diag.num_errors(), 2);
2377        assert!(diag.at_error_limit());
2378    }
2379
2380    #[test]
2381    fn test_lib_diagnostics_clear() {
2382        let mut diag = LibDiagnostics::new(10);
2383        diag.error("e1");
2384        diag.clear();
2385        assert!(diag.is_clean());
2386    }
2387
2388    #[test]
2389    fn test_lib_config_value_types() {
2390        let b = LibConfigValue::Bool(true);
2391        assert_eq!(b.type_name(), "bool");
2392        assert_eq!(b.as_bool(), Some(true));
2393        assert_eq!(b.as_int(), None);
2394
2395        let i = LibConfigValue::Int(42);
2396        assert_eq!(i.type_name(), "int");
2397        assert_eq!(i.as_int(), Some(42));
2398
2399        let f = LibConfigValue::Float(2.5);
2400        assert_eq!(f.type_name(), "float");
2401        assert!((f.as_float().expect("Float variant should return as_float") - 2.5).abs() < 1e-10);
2402
2403        let s = LibConfigValue::Str("hello".to_string());
2404        assert_eq!(s.type_name(), "str");
2405        assert_eq!(s.as_str(), Some("hello"));
2406
2407        let l = LibConfigValue::List(vec!["a".to_string(), "b".to_string()]);
2408        assert_eq!(l.type_name(), "list");
2409        assert_eq!(l.as_list().map(|v| v.len()), Some(2));
2410    }
2411}
2412
2413// --- Extended analysis infrastructure for lib ---
2414
2415#[allow(dead_code)]
2416#[derive(Debug, Clone)]
2417pub enum LibExtResult1300 {
2418    /// Operation completed successfully.
2419    Ok(String),
2420    /// Operation encountered an error.
2421    Err(String),
2422    /// Operation partially completed.
2423    Partial { done: usize, total: usize },
2424    /// Operation was skipped.
2425    Skipped,
2426}
2427
2428impl LibExtResult1300 {
2429    #[allow(dead_code)]
2430    pub fn is_ok(&self) -> bool {
2431        matches!(self, LibExtResult1300::Ok(_))
2432    }
2433    #[allow(dead_code)]
2434    pub fn is_err(&self) -> bool {
2435        matches!(self, LibExtResult1300::Err(_))
2436    }
2437    #[allow(dead_code)]
2438    pub fn is_partial(&self) -> bool {
2439        matches!(self, LibExtResult1300::Partial { .. })
2440    }
2441    #[allow(dead_code)]
2442    pub fn is_skipped(&self) -> bool {
2443        matches!(self, LibExtResult1300::Skipped)
2444    }
2445    #[allow(dead_code)]
2446    pub fn ok_msg(&self) -> Option<&str> {
2447        if let LibExtResult1300::Ok(s) = self {
2448            Some(s)
2449        } else {
2450            None
2451        }
2452    }
2453    #[allow(dead_code)]
2454    pub fn err_msg(&self) -> Option<&str> {
2455        if let LibExtResult1300::Err(s) = self {
2456            Some(s)
2457        } else {
2458            None
2459        }
2460    }
2461    #[allow(dead_code)]
2462    pub fn progress(&self) -> f64 {
2463        match self {
2464            LibExtResult1300::Ok(_) => 1.0,
2465            LibExtResult1300::Err(_) => 0.0,
2466            LibExtResult1300::Partial { done, total } => {
2467                if *total == 0 {
2468                    0.0
2469                } else {
2470                    *done as f64 / *total as f64
2471                }
2472            }
2473            LibExtResult1300::Skipped => 0.5,
2474        }
2475    }
2476}
2477
2478#[allow(dead_code)]
2479pub struct LibExtPass1300 {
2480    pub name: String,
2481    pub total_runs: usize,
2482    pub successes: usize,
2483    pub errors: usize,
2484    pub enabled: bool,
2485    pub results: Vec<LibExtResult1300>,
2486}
2487
2488impl LibExtPass1300 {
2489    #[allow(dead_code)]
2490    pub fn new(name: &str) -> Self {
2491        Self {
2492            name: name.to_string(),
2493            total_runs: 0,
2494            successes: 0,
2495            errors: 0,
2496            enabled: true,
2497            results: Vec::new(),
2498        }
2499    }
2500    #[allow(dead_code)]
2501    pub fn run(&mut self, input: &str) -> LibExtResult1300 {
2502        if !self.enabled {
2503            return LibExtResult1300::Skipped;
2504        }
2505        self.total_runs += 1;
2506        let result = if input.is_empty() {
2507            self.errors += 1;
2508            LibExtResult1300::Err(format!("empty input in pass '{}'", self.name))
2509        } else {
2510            self.successes += 1;
2511            LibExtResult1300::Ok(format!(
2512                "processed {} chars in pass '{}'",
2513                input.len(),
2514                self.name
2515            ))
2516        };
2517        self.results.push(result.clone());
2518        result
2519    }
2520    #[allow(dead_code)]
2521    pub fn success_count(&self) -> usize {
2522        self.successes
2523    }
2524    #[allow(dead_code)]
2525    pub fn error_count(&self) -> usize {
2526        self.errors
2527    }
2528    #[allow(dead_code)]
2529    pub fn success_rate(&self) -> f64 {
2530        if self.total_runs == 0 {
2531            0.0
2532        } else {
2533            self.successes as f64 / self.total_runs as f64
2534        }
2535    }
2536    #[allow(dead_code)]
2537    pub fn disable(&mut self) {
2538        self.enabled = false;
2539    }
2540    #[allow(dead_code)]
2541    pub fn enable(&mut self) {
2542        self.enabled = true;
2543    }
2544    #[allow(dead_code)]
2545    pub fn clear_results(&mut self) {
2546        self.results.clear();
2547    }
2548}
2549
2550#[allow(dead_code)]
2551pub struct LibExtPipeline1300 {
2552    pub name: String,
2553    pub passes: Vec<LibExtPass1300>,
2554    pub run_count: usize,
2555}
2556
2557impl LibExtPipeline1300 {
2558    #[allow(dead_code)]
2559    pub fn new(name: &str) -> Self {
2560        Self {
2561            name: name.to_string(),
2562            passes: Vec::new(),
2563            run_count: 0,
2564        }
2565    }
2566    #[allow(dead_code)]
2567    pub fn add_pass(&mut self, pass: LibExtPass1300) {
2568        self.passes.push(pass);
2569    }
2570    #[allow(dead_code)]
2571    pub fn run_all(&mut self, input: &str) -> Vec<LibExtResult1300> {
2572        self.run_count += 1;
2573        self.passes
2574            .iter_mut()
2575            .filter(|p| p.enabled)
2576            .map(|p| p.run(input))
2577            .collect()
2578    }
2579    #[allow(dead_code)]
2580    pub fn num_passes(&self) -> usize {
2581        self.passes.len()
2582    }
2583    #[allow(dead_code)]
2584    pub fn num_enabled_passes(&self) -> usize {
2585        self.passes.iter().filter(|p| p.enabled).count()
2586    }
2587    #[allow(dead_code)]
2588    pub fn total_success_rate(&self) -> f64 {
2589        let total: usize = self.passes.iter().map(|p| p.total_runs).sum();
2590        let ok: usize = self.passes.iter().map(|p| p.successes).sum();
2591        if total == 0 {
2592            0.0
2593        } else {
2594            ok as f64 / total as f64
2595        }
2596    }
2597}
2598
2599#[allow(dead_code)]
2600pub struct LibExtDiff1300 {
2601    pub added: Vec<String>,
2602    pub removed: Vec<String>,
2603    pub unchanged: Vec<String>,
2604}
2605
2606impl LibExtDiff1300 {
2607    #[allow(dead_code)]
2608    pub fn new() -> Self {
2609        Self {
2610            added: Vec::new(),
2611            removed: Vec::new(),
2612            unchanged: Vec::new(),
2613        }
2614    }
2615    #[allow(dead_code)]
2616    pub fn add(&mut self, s: &str) {
2617        self.added.push(s.to_string());
2618    }
2619    #[allow(dead_code)]
2620    pub fn remove(&mut self, s: &str) {
2621        self.removed.push(s.to_string());
2622    }
2623    #[allow(dead_code)]
2624    pub fn keep(&mut self, s: &str) {
2625        self.unchanged.push(s.to_string());
2626    }
2627    #[allow(dead_code)]
2628    pub fn is_empty(&self) -> bool {
2629        self.added.is_empty() && self.removed.is_empty()
2630    }
2631    #[allow(dead_code)]
2632    pub fn total_changes(&self) -> usize {
2633        self.added.len() + self.removed.len()
2634    }
2635    #[allow(dead_code)]
2636    pub fn net_additions(&self) -> i64 {
2637        self.added.len() as i64 - self.removed.len() as i64
2638    }
2639    #[allow(dead_code)]
2640    pub fn summary(&self) -> String {
2641        format!(
2642            "+{} -{} =={}",
2643            self.added.len(),
2644            self.removed.len(),
2645            self.unchanged.len()
2646        )
2647    }
2648}
2649
2650impl Default for LibExtDiff1300 {
2651    fn default() -> Self {
2652        Self::new()
2653    }
2654}
2655
2656#[allow(dead_code)]
2657#[derive(Debug, Clone)]
2658pub enum LibExtConfigVal1300 {
2659    Bool(bool),
2660    Int(i64),
2661    Float(f64),
2662    Str(String),
2663    List(Vec<String>),
2664}
2665
2666impl LibExtConfigVal1300 {
2667    #[allow(dead_code)]
2668    pub fn as_bool(&self) -> Option<bool> {
2669        if let LibExtConfigVal1300::Bool(b) = self {
2670            Some(*b)
2671        } else {
2672            None
2673        }
2674    }
2675    #[allow(dead_code)]
2676    pub fn as_int(&self) -> Option<i64> {
2677        if let LibExtConfigVal1300::Int(i) = self {
2678            Some(*i)
2679        } else {
2680            None
2681        }
2682    }
2683    #[allow(dead_code)]
2684    pub fn as_float(&self) -> Option<f64> {
2685        if let LibExtConfigVal1300::Float(f) = self {
2686            Some(*f)
2687        } else {
2688            None
2689        }
2690    }
2691    #[allow(dead_code)]
2692    pub fn as_str(&self) -> Option<&str> {
2693        if let LibExtConfigVal1300::Str(s) = self {
2694            Some(s)
2695        } else {
2696            None
2697        }
2698    }
2699    #[allow(dead_code)]
2700    pub fn as_list(&self) -> Option<&[String]> {
2701        if let LibExtConfigVal1300::List(l) = self {
2702            Some(l)
2703        } else {
2704            None
2705        }
2706    }
2707    #[allow(dead_code)]
2708    pub fn type_name(&self) -> &'static str {
2709        match self {
2710            LibExtConfigVal1300::Bool(_) => "bool",
2711            LibExtConfigVal1300::Int(_) => "int",
2712            LibExtConfigVal1300::Float(_) => "float",
2713            LibExtConfigVal1300::Str(_) => "str",
2714            LibExtConfigVal1300::List(_) => "list",
2715        }
2716    }
2717}
2718
2719#[allow(dead_code)]
2720pub struct LibExtConfig1300 {
2721    pub values: std::collections::HashMap<String, LibExtConfigVal1300>,
2722    pub read_only: bool,
2723    pub name: String,
2724}
2725
2726impl LibExtConfig1300 {
2727    #[allow(dead_code)]
2728    pub fn new() -> Self {
2729        Self {
2730            values: std::collections::HashMap::new(),
2731            read_only: false,
2732            name: String::new(),
2733        }
2734    }
2735    #[allow(dead_code)]
2736    pub fn named(name: &str) -> Self {
2737        Self {
2738            values: std::collections::HashMap::new(),
2739            read_only: false,
2740            name: name.to_string(),
2741        }
2742    }
2743    #[allow(dead_code)]
2744    pub fn set(&mut self, key: &str, value: LibExtConfigVal1300) -> bool {
2745        if self.read_only {
2746            return false;
2747        }
2748        self.values.insert(key.to_string(), value);
2749        true
2750    }
2751    #[allow(dead_code)]
2752    pub fn get(&self, key: &str) -> Option<&LibExtConfigVal1300> {
2753        self.values.get(key)
2754    }
2755    #[allow(dead_code)]
2756    pub fn get_bool(&self, key: &str) -> Option<bool> {
2757        self.get(key)?.as_bool()
2758    }
2759    #[allow(dead_code)]
2760    pub fn get_int(&self, key: &str) -> Option<i64> {
2761        self.get(key)?.as_int()
2762    }
2763    #[allow(dead_code)]
2764    pub fn get_str(&self, key: &str) -> Option<&str> {
2765        self.get(key)?.as_str()
2766    }
2767    #[allow(dead_code)]
2768    pub fn set_bool(&mut self, key: &str, v: bool) -> bool {
2769        self.set(key, LibExtConfigVal1300::Bool(v))
2770    }
2771    #[allow(dead_code)]
2772    pub fn set_int(&mut self, key: &str, v: i64) -> bool {
2773        self.set(key, LibExtConfigVal1300::Int(v))
2774    }
2775    #[allow(dead_code)]
2776    pub fn set_str(&mut self, key: &str, v: &str) -> bool {
2777        self.set(key, LibExtConfigVal1300::Str(v.to_string()))
2778    }
2779    #[allow(dead_code)]
2780    pub fn lock(&mut self) {
2781        self.read_only = true;
2782    }
2783    #[allow(dead_code)]
2784    pub fn unlock(&mut self) {
2785        self.read_only = false;
2786    }
2787    #[allow(dead_code)]
2788    pub fn size(&self) -> usize {
2789        self.values.len()
2790    }
2791    #[allow(dead_code)]
2792    pub fn has(&self, key: &str) -> bool {
2793        self.values.contains_key(key)
2794    }
2795    #[allow(dead_code)]
2796    pub fn remove(&mut self, key: &str) -> bool {
2797        self.values.remove(key).is_some()
2798    }
2799}
2800
2801impl Default for LibExtConfig1300 {
2802    fn default() -> Self {
2803        Self::new()
2804    }
2805}
2806
2807#[allow(dead_code)]
2808pub struct LibExtDiag1300 {
2809    pub errors: Vec<String>,
2810    pub warnings: Vec<String>,
2811    pub notes: Vec<String>,
2812    pub max_errors: usize,
2813}
2814
2815impl LibExtDiag1300 {
2816    #[allow(dead_code)]
2817    pub fn new(max_errors: usize) -> Self {
2818        Self {
2819            errors: Vec::new(),
2820            warnings: Vec::new(),
2821            notes: Vec::new(),
2822            max_errors,
2823        }
2824    }
2825    #[allow(dead_code)]
2826    pub fn error(&mut self, msg: &str) {
2827        if self.errors.len() < self.max_errors {
2828            self.errors.push(msg.to_string());
2829        }
2830    }
2831    #[allow(dead_code)]
2832    pub fn warning(&mut self, msg: &str) {
2833        self.warnings.push(msg.to_string());
2834    }
2835    #[allow(dead_code)]
2836    pub fn note(&mut self, msg: &str) {
2837        self.notes.push(msg.to_string());
2838    }
2839    #[allow(dead_code)]
2840    pub fn has_errors(&self) -> bool {
2841        !self.errors.is_empty()
2842    }
2843    #[allow(dead_code)]
2844    pub fn num_errors(&self) -> usize {
2845        self.errors.len()
2846    }
2847    #[allow(dead_code)]
2848    pub fn num_warnings(&self) -> usize {
2849        self.warnings.len()
2850    }
2851    #[allow(dead_code)]
2852    pub fn is_clean(&self) -> bool {
2853        self.errors.is_empty() && self.warnings.is_empty()
2854    }
2855    #[allow(dead_code)]
2856    pub fn at_error_limit(&self) -> bool {
2857        self.errors.len() >= self.max_errors
2858    }
2859    #[allow(dead_code)]
2860    pub fn clear(&mut self) {
2861        self.errors.clear();
2862        self.warnings.clear();
2863        self.notes.clear();
2864    }
2865    #[allow(dead_code)]
2866    pub fn summary(&self) -> String {
2867        format!(
2868            "{} error(s), {} warning(s)",
2869            self.errors.len(),
2870            self.warnings.len()
2871        )
2872    }
2873}
2874
2875#[cfg(test)]
2876mod lib_ext_tests_1300 {
2877    use super::*;
2878
2879    #[test]
2880    fn test_lib_ext_result_ok_1300() {
2881        let r = LibExtResult1300::Ok("success".to_string());
2882        assert!(r.is_ok());
2883        assert!(!r.is_err());
2884        assert_eq!(r.ok_msg(), Some("success"));
2885        assert!((r.progress() - 1.0).abs() < 1e-10);
2886    }
2887
2888    #[test]
2889    fn test_lib_ext_result_err_1300() {
2890        let r = LibExtResult1300::Err("failure".to_string());
2891        assert!(r.is_err());
2892        assert_eq!(r.err_msg(), Some("failure"));
2893        assert!((r.progress() - 0.0).abs() < 1e-10);
2894    }
2895
2896    #[test]
2897    fn test_lib_ext_result_partial_1300() {
2898        let r = LibExtResult1300::Partial { done: 3, total: 10 };
2899        assert!(r.is_partial());
2900        assert!((r.progress() - 0.3).abs() < 1e-10);
2901    }
2902
2903    #[test]
2904    fn test_lib_ext_result_skipped_1300() {
2905        let r = LibExtResult1300::Skipped;
2906        assert!(r.is_skipped());
2907    }
2908
2909    #[test]
2910    fn test_lib_ext_pass_run_1300() {
2911        let mut p = LibExtPass1300::new("test_pass");
2912        let r = p.run("hello");
2913        assert!(r.is_ok());
2914        assert_eq!(p.total_runs, 1);
2915        assert_eq!(p.success_count(), 1);
2916    }
2917
2918    #[test]
2919    fn test_lib_ext_pass_empty_1300() {
2920        let mut p = LibExtPass1300::new("empty_test");
2921        let r = p.run("");
2922        assert!(r.is_err());
2923        assert_eq!(p.error_count(), 1);
2924    }
2925
2926    #[test]
2927    fn test_lib_ext_pass_rate_1300() {
2928        let mut p = LibExtPass1300::new("rate_test");
2929        p.run("a");
2930        p.run("b");
2931        p.run("");
2932        assert!((p.success_rate() - 2.0 / 3.0).abs() < 1e-9);
2933    }
2934
2935    #[test]
2936    fn test_lib_ext_pass_disable_1300() {
2937        let mut p = LibExtPass1300::new("disable_test");
2938        p.disable();
2939        assert!(!p.enabled);
2940        p.enable();
2941        assert!(p.enabled);
2942    }
2943
2944    #[test]
2945    fn test_lib_ext_pipeline_basic_1300() {
2946        let mut pipeline = LibExtPipeline1300::new("main_pipeline");
2947        pipeline.add_pass(LibExtPass1300::new("pass1"));
2948        pipeline.add_pass(LibExtPass1300::new("pass2"));
2949        assert_eq!(pipeline.num_passes(), 2);
2950        let results = pipeline.run_all("test_input");
2951        assert_eq!(results.len(), 2);
2952    }
2953
2954    #[test]
2955    fn test_lib_ext_pipeline_disabled_1300() {
2956        let mut pipeline = LibExtPipeline1300::new("partial");
2957        let mut p = LibExtPass1300::new("disabled");
2958        p.disable();
2959        pipeline.add_pass(p);
2960        pipeline.add_pass(LibExtPass1300::new("enabled"));
2961        assert_eq!(pipeline.num_enabled_passes(), 1);
2962        let results = pipeline.run_all("input");
2963        assert_eq!(results.len(), 1);
2964    }
2965
2966    #[test]
2967    fn test_lib_ext_diff_basic_1300() {
2968        let mut d = LibExtDiff1300::new();
2969        d.add("new_item");
2970        d.remove("old_item");
2971        d.keep("same_item");
2972        assert!(!d.is_empty());
2973        assert_eq!(d.total_changes(), 2);
2974        assert_eq!(d.net_additions(), 0);
2975    }
2976
2977    #[test]
2978    fn test_lib_ext_config_set_get_1300() {
2979        let mut cfg = LibExtConfig1300::new();
2980        cfg.set_bool("debug", true);
2981        cfg.set_int("max_iter", 100);
2982        cfg.set_str("name", "test");
2983        assert_eq!(cfg.get_bool("debug"), Some(true));
2984        assert_eq!(cfg.get_int("max_iter"), Some(100));
2985        assert_eq!(cfg.get_str("name"), Some("test"));
2986    }
2987
2988    #[test]
2989    fn test_lib_ext_config_read_only_1300() {
2990        let mut cfg = LibExtConfig1300::new();
2991        cfg.set_bool("key", true);
2992        cfg.lock();
2993        assert!(!cfg.set_bool("key", false));
2994        assert_eq!(cfg.get_bool("key"), Some(true));
2995        cfg.unlock();
2996        assert!(cfg.set_bool("key", false));
2997    }
2998
2999    #[test]
3000    fn test_lib_ext_config_remove_1300() {
3001        let mut cfg = LibExtConfig1300::new();
3002        cfg.set_int("x", 42);
3003        assert!(cfg.has("x"));
3004        cfg.remove("x");
3005        assert!(!cfg.has("x"));
3006    }
3007
3008    #[test]
3009    fn test_lib_ext_diagnostics_basic_1300() {
3010        let mut diag = LibExtDiag1300::new(10);
3011        diag.error("something went wrong");
3012        diag.warning("maybe check this");
3013        diag.note("fyi");
3014        assert!(diag.has_errors());
3015        assert!(!diag.is_clean());
3016        assert_eq!(diag.num_errors(), 1);
3017        assert_eq!(diag.num_warnings(), 1);
3018    }
3019
3020    #[test]
3021    fn test_lib_ext_diagnostics_max_errors_1300() {
3022        let mut diag = LibExtDiag1300::new(2);
3023        diag.error("e1");
3024        diag.error("e2");
3025        diag.error("e3");
3026        assert_eq!(diag.num_errors(), 2);
3027        assert!(diag.at_error_limit());
3028    }
3029
3030    #[test]
3031    fn test_lib_ext_diagnostics_clear_1300() {
3032        let mut diag = LibExtDiag1300::new(10);
3033        diag.error("e1");
3034        diag.clear();
3035        assert!(diag.is_clean());
3036    }
3037
3038    #[test]
3039    fn test_lib_ext_config_value_types_1300() {
3040        let b = LibExtConfigVal1300::Bool(true);
3041        assert_eq!(b.type_name(), "bool");
3042        assert_eq!(b.as_bool(), Some(true));
3043        assert_eq!(b.as_int(), None);
3044
3045        let i = LibExtConfigVal1300::Int(42);
3046        assert_eq!(i.type_name(), "int");
3047        assert_eq!(i.as_int(), Some(42));
3048
3049        let f = LibExtConfigVal1300::Float(2.5);
3050        assert_eq!(f.type_name(), "float");
3051        assert!((f.as_float().expect("Float variant should return as_float") - 2.5).abs() < 1e-10);
3052
3053        let s = LibExtConfigVal1300::Str("hello".to_string());
3054        assert_eq!(s.type_name(), "str");
3055        assert_eq!(s.as_str(), Some("hello"));
3056
3057        let l = LibExtConfigVal1300::List(vec!["a".to_string(), "b".to_string()]);
3058        assert_eq!(l.type_name(), "list");
3059        assert_eq!(l.as_list().map(|v| v.len()), Some(2));
3060    }
3061}