1#![allow(dead_code)]
2#![allow(missing_docs)]
3#![allow(unused_imports)]
4#![allow(private_interfaces)]
5#![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
325pub mod basic;
327pub mod def_eq;
328pub mod discr_tree;
329pub mod infer_type;
330pub mod level_def_eq;
331pub mod whnf;
332
333pub mod app_builder;
335pub mod congr_theorems;
336pub mod match_basic;
337pub mod match_dectree;
338pub mod match_exhaust;
339pub mod synth_instance;
340
341pub 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
351pub 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
359pub mod tactic;
361
362pub mod util;
364
365pub mod proof_replay;
367pub mod prop_test;
368pub mod simp_engine;
369
370pub mod ast_cache;
371pub mod convenience;
372pub mod meta_debug;
373
374pub use tactic::rewrite::RewriteDirection;
376pub use tactic::{GoalView, TacticError, TacticResult, TacticState};
377
378pub 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
387pub const META_VERSION: &str = "0.9.0-alpha";
393
394pub fn meta_version_str() -> &'static str {
396 META_VERSION
397}
398
399pub fn mk_test_ctx() -> MetaContext {
401 MetaContext::new(oxilean_kernel::Environment::new())
402}
403
404#[derive(Debug, Default)]
406pub struct TacticRegistry {
407 pub entries: std::collections::HashMap<String, usize>,
408 pub names: Vec<String>,
409}
410
411impl TacticRegistry {
412 pub fn new() -> Self {
414 Self::default()
415 }
416
417 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 pub fn lookup(&self, name: &str) -> Option<usize> {
431 self.entries.get(name).copied()
432 }
433
434 pub fn name_of(&self, idx: usize) -> Option<&str> {
436 self.names.get(idx).map(String::as_str)
437 }
438
439 pub fn len(&self) -> usize {
441 self.names.len()
442 }
443
444 pub fn is_empty(&self) -> bool {
446 self.names.is_empty()
447 }
448
449 pub fn all_names(&self) -> &[String] {
451 &self.names
452 }
453}
454
455pub 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
508pub 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
546pub 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
564pub 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#[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 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 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 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 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 pub fn len(&self) -> usize {
641 self.entries.len()
642 }
643
644 pub fn is_empty(&self) -> bool {
646 self.entries.is_empty()
647 }
648
649 pub fn clear(&mut self) {
651 self.entries.clear();
652 self.hits = 0;
653 self.misses = 0;
654 }
655}
656
657#[derive(Debug, Clone, Default)]
659pub struct MetaStats {
660 pub num_expr_mvars: usize,
662 pub num_assigned_expr: usize,
664 pub num_level_mvars: usize,
666 pub num_assigned_levels: usize,
668 pub num_postponed: usize,
670}
671
672pub fn collect_meta_stats(ctx: &MetaContext) -> MetaStats {
674 MetaStats {
675 num_expr_mvars: ctx.num_mvars(),
676 num_assigned_expr: 0, num_level_mvars: 0, num_assigned_levels: 0, num_postponed: ctx.num_postponed(),
680 }
681}
682
683#[derive(Debug, Clone)]
685pub struct ProofStateReport {
686 pub open_goals: usize,
688 pub is_complete: bool,
690}
691
692impl ProofStateReport {
693 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#[derive(Debug, Clone)]
704pub struct ScoredCandidate<T> {
705 pub candidate: T,
707 pub score: i64,
709}
710
711impl<T> ScoredCandidate<T> {
712 pub fn new(candidate: T, score: i64) -> Self {
714 Self { candidate, score }
715 }
716}
717
718pub 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#[allow(dead_code)]
860pub struct PerfStats {
861 pub elab_attempts: u64,
863 pub elab_successes: u64,
865 pub unif_attempts: u64,
867 pub unif_successes: u64,
869 pub elapsed_us: u64,
871}
872
873#[allow(dead_code)]
874impl PerfStats {
875 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 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 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 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#[derive(Clone, Debug)]
993pub struct MetaFeatures {
994 pub discr_tree: bool,
996 pub whnf_cache: bool,
998 pub proof_recording: bool,
1000 pub instance_synth: bool,
1002 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 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 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 pub fn any_caching(&self) -> bool {
1043 self.whnf_cache || self.proof_recording
1044 }
1045}
1046
1047#[derive(Clone, Debug)]
1049pub struct TacticGroup {
1050 pub name: String,
1052 pub members: Vec<String>,
1054 pub description: String,
1056}
1057
1058impl TacticGroup {
1059 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 #[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 pub fn contains(&self, tactic: &str) -> bool {
1077 self.members.iter().any(|m| m == tactic)
1078 }
1079}
1080
1081pub 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
1115pub 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#[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#[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#[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#[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#[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#[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#[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#[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); 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()); }
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#[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#[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#[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#[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#[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#[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#[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)); assert_eq!(cfg.get_bool("key"), Some(true)); 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"); 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#[allow(dead_code)]
2416#[derive(Debug, Clone)]
2417pub enum LibExtResult1300 {
2418 Ok(String),
2420 Err(String),
2422 Partial { done: usize, total: usize },
2424 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}