1use crate::alethe::{AletheProof, AletheRule, AletheStep};
8use crate::drat::{DratProof, DratStep};
9use crate::lfsc::{LfscProof, LfscSort, LfscTerm};
10use std::fmt;
11
12pub type ConversionResult<T> = Result<T, ConversionError>;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum ConversionError {
18 UnsupportedConversion { from: String, to: String },
20 InformationLoss { reason: String },
22 InvalidSource { reason: String },
24 ConversionFailed { reason: String },
26}
27
28impl fmt::Display for ConversionError {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 ConversionError::UnsupportedConversion { from, to } => {
32 write!(f, "Unsupported conversion from {} to {}", from, to)
33 }
34 ConversionError::InformationLoss { reason } => {
35 write!(f, "Information loss during conversion: {}", reason)
36 }
37 ConversionError::InvalidSource { reason } => {
38 write!(f, "Invalid source format: {}", reason)
39 }
40 ConversionError::ConversionFailed { reason } => {
41 write!(f, "Conversion failed: {}", reason)
42 }
43 }
44 }
45}
46
47impl std::error::Error for ConversionError {}
48
49pub struct FormatConverter {
54 strict_mode: bool,
56 add_comments: bool,
58}
59
60impl Default for FormatConverter {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl FormatConverter {
67 pub fn new() -> Self {
69 Self {
70 strict_mode: false,
71 add_comments: true,
72 }
73 }
74
75 pub fn strict_mode(mut self, strict: bool) -> Self {
77 self.strict_mode = strict;
78 self
79 }
80
81 pub fn add_comments(mut self, add: bool) -> Self {
83 self.add_comments = add;
84 self
85 }
86
87 pub fn drat_to_alethe(&self, drat: &DratProof) -> ConversionResult<AletheProof> {
115 let mut alethe = AletheProof::new();
116
117 let mut step_indices = Vec::new();
119
120 for (i, step) in drat.steps().iter().enumerate() {
121 match step {
122 DratStep::Add(clause) => {
123 let alethe_clause: Vec<String> = clause
125 .iter()
126 .map(|&lit| {
127 if lit == 0 {
128 return String::new();
130 }
131 if lit > 0 {
132 format!("p{}", lit)
133 } else {
134 format!("(not p{})", -lit)
135 }
136 })
137 .filter(|s| !s.is_empty())
138 .collect();
139
140 let rule = if i < 5 {
142 AletheRule::Input
144 } else {
145 AletheRule::Resolution
147 };
148
149 let premises = if rule == AletheRule::Resolution && !step_indices.is_empty() {
151 let len = step_indices.len();
153 if len >= 2 {
154 vec![step_indices[len - 2], step_indices[len - 1]]
155 } else {
156 vec![step_indices[len - 1]]
157 }
158 } else {
159 Vec::new()
160 };
161
162 let idx = alethe.step(alethe_clause, rule, premises, Vec::new());
163 step_indices.push(idx);
164 }
165 DratStep::Delete(_clause) => {
166 if self.strict_mode {
169 return Err(ConversionError::InformationLoss {
170 reason:
171 "DRAT deletions cannot be represented in Alethe (monotonic format)"
172 .to_string(),
173 });
174 }
175 }
177 }
178 }
179
180 Ok(alethe)
181 }
182
183 pub fn alethe_to_lfsc(&self, alethe: &AletheProof) -> ConversionResult<LfscProof> {
209 let mut lfsc = LfscProof::new();
210
211 self.add_boolean_signature(&mut lfsc);
213
214 for step in alethe.steps() {
216 match step {
217 AletheStep::Assume { index, term } => {
218 let var_name = format!("t{}", index);
220 let formula = Self::term_to_lfsc_term(term)?;
221
222 lfsc.declare_const(&var_name, LfscSort::Named("proof".to_string()));
224
225 if self.add_comments {
226 lfsc.define(
228 format!("{}_formula", var_name),
229 LfscSort::Named("formula".to_string()),
230 formula,
231 );
232 }
233 }
234 AletheStep::Step {
235 index,
236 clause,
237 rule,
238 premises,
239 args: _args,
240 } => {
241 let var_name = format!("t{}", index);
242
243 let proof_term = self.alethe_rule_to_lfsc(*rule, clause, premises)?;
245
246 lfsc.define(var_name, LfscSort::Named("proof".to_string()), proof_term);
248 }
249 AletheStep::Anchor { step, args } => {
250 let var_name = format!("anchor{}", step);
253
254 let mut body = LfscTerm::Var(var_name.clone());
256 for (arg_name, arg_sort) in args.iter().rev() {
257 let sort = Self::parse_sort(arg_sort)?;
258 body = LfscTerm::Lambda(arg_name.clone(), Box::new(sort), Box::new(body));
259 }
260
261 lfsc.define(var_name, LfscSort::Named("proof".to_string()), body);
262 }
263 AletheStep::DefineFun {
264 name,
265 args,
266 return_sort,
267 body,
268 } => {
269 let lfsc_sort = Self::parse_sort(return_sort)?;
271 let lfsc_body = Self::term_to_lfsc_term(body)?;
272
273 let mut def_body = lfsc_body;
275 for (arg_name, arg_sort) in args.iter().rev() {
276 let sort = Self::parse_sort(arg_sort)?;
277 def_body =
278 LfscTerm::Lambda(arg_name.clone(), Box::new(sort), Box::new(def_body));
279 }
280
281 lfsc.define(name.clone(), lfsc_sort, def_body);
282 }
283 }
284 }
285
286 Ok(lfsc)
287 }
288
289 fn add_boolean_signature(&self, lfsc: &mut LfscProof) {
291 lfsc.declare_sort("formula", 0);
293
294 lfsc.declare_sort("proof", 0);
296
297 lfsc.declare_const(
299 "holds",
300 LfscSort::Arrow(
301 Box::new(LfscSort::Named("formula".to_string())),
302 Box::new(LfscSort::Type),
303 ),
304 );
305
306 lfsc.declare_const("true", LfscSort::Named("formula".to_string()));
308 lfsc.declare_const("false", LfscSort::Named("formula".to_string()));
309
310 lfsc.declare_const(
311 "not",
312 LfscSort::Arrow(
313 Box::new(LfscSort::Named("formula".to_string())),
314 Box::new(LfscSort::Named("formula".to_string())),
315 ),
316 );
317
318 lfsc.declare_const(
319 "and",
320 LfscSort::Arrow(
321 Box::new(LfscSort::Named("formula".to_string())),
322 Box::new(LfscSort::Arrow(
323 Box::new(LfscSort::Named("formula".to_string())),
324 Box::new(LfscSort::Named("formula".to_string())),
325 )),
326 ),
327 );
328
329 lfsc.declare_const(
330 "or",
331 LfscSort::Arrow(
332 Box::new(LfscSort::Named("formula".to_string())),
333 Box::new(LfscSort::Arrow(
334 Box::new(LfscSort::Named("formula".to_string())),
335 Box::new(LfscSort::Named("formula".to_string())),
336 )),
337 ),
338 );
339 }
340
341 fn alethe_rule_to_lfsc(
343 &self,
344 rule: AletheRule,
345 _clause: &[String],
346 premises: &[u32],
347 ) -> ConversionResult<LfscTerm> {
348 let rule_name = match rule {
350 AletheRule::Resolution => "resolution",
351 AletheRule::Trans => "trans",
352 AletheRule::Cong => "cong",
353 AletheRule::Refl => "refl",
354 AletheRule::Symm => "symm",
355 AletheRule::EqRefl => "eq_refl",
356 AletheRule::EqSymm => "eq_symm",
357 AletheRule::EqTrans => "eq_trans",
358 AletheRule::EqCong => "eq_cong",
359 AletheRule::Input => "input",
360 _ => {
361 return Err(ConversionError::UnsupportedConversion {
362 from: format!("Alethe rule {:?}", rule),
363 to: "LFSC".to_string(),
364 });
365 }
366 };
367
368 let premise_terms: Vec<LfscTerm> = premises
370 .iter()
371 .map(|&idx| LfscTerm::Var(format!("t{}", idx)))
372 .collect();
373
374 Ok(LfscTerm::App(rule_name.to_string(), premise_terms))
375 }
376
377 fn term_to_lfsc_term(term: &str) -> ConversionResult<LfscTerm> {
385 let trimmed = term.trim();
386
387 if trimmed == "true" {
389 return Ok(LfscTerm::True);
390 } else if trimmed == "false" {
391 return Ok(LfscTerm::False);
392 }
393
394 if trimmed.starts_with('(') && trimmed.ends_with(')') {
396 return Self::parse_sexpr(trimmed);
397 }
398
399 if let Some(term) = Self::try_parse_number(trimmed) {
401 return Ok(term);
402 }
403
404 Ok(LfscTerm::Var(trimmed.to_string()))
406 }
407
408 fn try_parse_number(s: &str) -> Option<LfscTerm> {
410 if let Some(slash_pos) = s.find('/') {
412 let num_str = &s[..slash_pos];
413 let den_str = &s[slash_pos + 1..];
414
415 if let (Ok(num), Ok(den)) = (num_str.parse::<i64>(), den_str.parse::<i64>()) {
416 return Some(LfscTerm::RatLit(num, den));
417 }
418 }
419
420 if let Ok(n) = s.parse::<i64>() {
422 return Some(LfscTerm::IntLit(n));
423 }
424
425 None
426 }
427
428 fn parse_sexpr(s: &str) -> ConversionResult<LfscTerm> {
430 let inner = &s[1..s.len() - 1].trim();
431
432 if inner.is_empty() {
433 return Err(ConversionError::InvalidSource {
434 reason: "Empty s-expression".to_string(),
435 });
436 }
437
438 let tokens = Self::tokenize_sexpr(inner)?;
440
441 if tokens.is_empty() {
442 return Err(ConversionError::InvalidSource {
443 reason: "Empty s-expression".to_string(),
444 });
445 }
446
447 let func = &tokens[0];
448 let args: ConversionResult<Vec<LfscTerm>> = tokens[1..]
449 .iter()
450 .map(|arg| Self::term_to_lfsc_term(arg))
451 .collect();
452
453 Ok(LfscTerm::App(func.to_string(), args?))
454 }
455
456 fn tokenize_sexpr(s: &str) -> ConversionResult<Vec<String>> {
458 let mut tokens = Vec::new();
459 let mut current = String::new();
460 let mut depth = 0;
461 let mut in_string = false;
462
463 for ch in s.chars() {
464 match ch {
465 '"' => {
466 in_string = !in_string;
467 current.push(ch);
468 }
469 '(' if !in_string => {
470 if depth == 0 && !current.trim().is_empty() {
471 tokens.push(current.trim().to_string());
472 current.clear();
473 }
474 depth += 1;
475 current.push(ch);
476 }
477 ')' if !in_string => {
478 depth -= 1;
479 current.push(ch);
480 if depth == 0 {
481 tokens.push(current.trim().to_string());
482 current.clear();
483 }
484 }
485 ' ' | '\t' | '\n' if !in_string && depth == 0 => {
486 if !current.trim().is_empty() {
487 tokens.push(current.trim().to_string());
488 current.clear();
489 }
490 }
491 _ => {
492 current.push(ch);
493 }
494 }
495 }
496
497 if in_string {
498 return Err(ConversionError::InvalidSource {
499 reason: "Unterminated string literal".to_string(),
500 });
501 }
502
503 if depth != 0 {
504 return Err(ConversionError::InvalidSource {
505 reason: "Unbalanced parentheses in s-expression".to_string(),
506 });
507 }
508
509 if !current.trim().is_empty() {
510 tokens.push(current.trim().to_string());
511 }
512
513 Ok(tokens)
514 }
515
516 fn parse_sort(sort: &str) -> ConversionResult<LfscSort> {
518 match sort.trim() {
519 "Bool" => Ok(LfscSort::Bool),
520 "Int" => Ok(LfscSort::Int),
521 "Real" => Ok(LfscSort::Real),
522 s if s.starts_with("(_ BitVec ") => {
523 let width_str = s.trim_start_matches("(_ BitVec ").trim_end_matches(')');
525 let width =
526 width_str
527 .parse::<u32>()
528 .map_err(|_| ConversionError::InvalidSource {
529 reason: format!("Invalid bitvector width: {}", width_str),
530 })?;
531 Ok(LfscSort::BitVec(width))
532 }
533 other => Ok(LfscSort::Named(other.to_string())),
534 }
535 }
536}
537
538#[cfg(test)]
539mod tests {
540 use super::*;
541 use proptest::prelude::*;
542
543 #[test]
544 fn test_converter_new() {
545 let converter = FormatConverter::new();
546 assert!(!converter.strict_mode);
547 assert!(converter.add_comments);
548 }
549
550 #[test]
551 fn test_converter_with_settings() {
552 let converter = FormatConverter::new().strict_mode(true).add_comments(false);
553 assert!(converter.strict_mode);
554 assert!(!converter.add_comments);
555 }
556
557 #[test]
558 fn test_conversion_error_display() {
559 let err = ConversionError::UnsupportedConversion {
560 from: "DRAT".to_string(),
561 to: "Coq".to_string(),
562 };
563 assert!(err.to_string().contains("Unsupported conversion"));
564 }
565
566 #[test]
569 fn test_drat_to_alethe_empty() {
570 let converter = FormatConverter::new();
571 let drat = DratProof::new();
572 let alethe = converter.drat_to_alethe(&drat).unwrap();
573
574 assert!(alethe.is_empty());
575 }
576
577 #[test]
578 fn test_drat_to_alethe_single_clause() {
579 let converter = FormatConverter::new();
580 let mut drat = DratProof::new();
581 drat.add_clause(vec![1, 2, -3]);
582
583 let alethe = converter.drat_to_alethe(&drat).unwrap();
584
585 assert_eq!(alethe.len(), 1);
586 let output = alethe.to_string();
587 assert!(output.contains("p1"));
588 assert!(output.contains("p2"));
589 assert!(output.contains("(not p3)"));
590 }
591
592 #[test]
593 fn test_drat_to_alethe_multiple_clauses() {
594 let converter = FormatConverter::new();
595 let mut drat = DratProof::new();
596 drat.add_clause(vec![1, 2]);
597 drat.add_clause(vec![-1, 3]);
598 drat.add_clause(vec![-2, 4]);
599 drat.add_clause(vec![2, -3]);
600
601 let alethe = converter.drat_to_alethe(&drat).unwrap();
602
603 assert_eq!(alethe.len(), 4);
604 let output = alethe.to_string();
606 assert!(output.contains(":rule input"));
607 }
608
609 #[test]
610 fn test_drat_to_alethe_with_resolution() {
611 let converter = FormatConverter::new();
612 let mut drat = DratProof::new();
613
614 for i in 1..=6 {
616 drat.add_clause(vec![i, -i]);
617 }
618
619 let alethe = converter.drat_to_alethe(&drat).unwrap();
620
621 assert_eq!(alethe.len(), 6);
622 let output = alethe.to_string();
623 assert!(output.contains(":rule resolution"));
625 }
626
627 #[test]
628 fn test_drat_to_alethe_with_deletion_non_strict() {
629 let converter = FormatConverter::new();
630 let mut drat = DratProof::new();
631 drat.add_clause(vec![1, 2]);
632 drat.delete_clause(vec![1, 2]);
633 drat.add_clause(vec![3, 4]);
634
635 let alethe = converter.drat_to_alethe(&drat).unwrap();
637
638 assert_eq!(alethe.len(), 2);
640 }
641
642 #[test]
643 fn test_drat_to_alethe_with_deletion_strict() {
644 let converter = FormatConverter::new().strict_mode(true);
645 let mut drat = DratProof::new();
646 drat.add_clause(vec![1, 2]);
647 drat.delete_clause(vec![1, 2]);
648
649 let result = converter.drat_to_alethe(&drat);
651 assert!(result.is_err());
652
653 if let Err(ConversionError::InformationLoss { reason }) = result {
654 assert!(reason.contains("monotonic"));
655 } else {
656 panic!("Expected InformationLoss error");
657 }
658 }
659
660 #[test]
661 fn test_drat_to_alethe_empty_clause() {
662 let converter = FormatConverter::new();
663 let mut drat = DratProof::new();
664 drat.add_clause(vec![1]);
665 drat.add_clause(vec![-1]);
666 drat.add_clause(vec![]); let alethe = converter.drat_to_alethe(&drat).unwrap();
669
670 assert_eq!(alethe.len(), 3);
671 let output = alethe.to_string();
672 assert!(output.contains("(cl)"));
674 }
675
676 #[test]
679 fn test_alethe_to_lfsc_empty() {
680 let converter = FormatConverter::new();
681 let alethe = AletheProof::new();
682 let lfsc = converter.alethe_to_lfsc(&alethe).unwrap();
683
684 assert!(!lfsc.is_empty());
686 let output = lfsc.to_string();
687 assert!(output.contains("formula"));
688 assert!(output.contains("proof"));
689 }
690
691 #[test]
692 fn test_alethe_to_lfsc_assumption() {
693 let converter = FormatConverter::new();
694 let mut alethe = AletheProof::new();
695 alethe.assume("(= x 5)");
696
697 let lfsc = converter.alethe_to_lfsc(&alethe).unwrap();
698
699 let output = lfsc.to_string();
700 assert!(output.contains("t1"));
701 assert!(output.contains("proof"));
702 }
703
704 #[test]
705 fn test_alethe_to_lfsc_resolution() {
706 let converter = FormatConverter::new();
707 let mut alethe = AletheProof::new();
708 let a1 = alethe.assume("(or p q)");
709 let a2 = alethe.assume("(not p)");
710 alethe.resolution(vec!["q".to_string()], vec![a1, a2]);
711
712 let lfsc = converter.alethe_to_lfsc(&alethe).unwrap();
713
714 let output = lfsc.to_string();
715 assert!(output.contains("resolution"));
716 assert!(output.contains("t1"));
717 assert!(output.contains("t2"));
718 assert!(output.contains("t3"));
719 }
720
721 #[test]
722 fn test_alethe_to_lfsc_equality_rules() {
723 let converter = FormatConverter::new();
724 let mut alethe = AletheProof::new();
725
726 alethe.step_simple(vec!["(= x x)".to_string()], AletheRule::EqRefl);
727 alethe.step_simple(vec!["(= y x)".to_string()], AletheRule::EqSymm);
728
729 let lfsc = converter.alethe_to_lfsc(&alethe).unwrap();
730
731 let output = lfsc.to_string();
732 assert!(output.contains("eq_refl"));
733 assert!(output.contains("eq_symm"));
734 }
735
736 #[test]
737 fn test_alethe_to_lfsc_anchor() {
738 let converter = FormatConverter::new();
739 let mut alethe = AletheProof::new();
740
741 alethe.anchor(vec![("x".to_string(), "Int".to_string())]);
742
743 let lfsc = converter.alethe_to_lfsc(&alethe).unwrap();
744
745 let output = lfsc.to_string();
746 assert!(output.contains("anchor1"));
747 }
748
749 #[test]
750 fn test_alethe_to_lfsc_define_fun() {
751 let converter = FormatConverter::new();
752 let mut alethe = AletheProof::new();
753
754 alethe.define_fun(
755 "f",
756 vec![("x".to_string(), "Int".to_string())],
757 "Int",
758 "(+ x 1)",
759 );
760
761 let lfsc = converter.alethe_to_lfsc(&alethe).unwrap();
762
763 let output = lfsc.to_string();
764 assert!(output.contains("(define f"));
765 }
766
767 #[test]
768 fn test_alethe_to_lfsc_with_comments() {
769 let converter = FormatConverter::new().add_comments(true);
770 let mut alethe = AletheProof::new();
771 alethe.assume("(= x 5)");
772
773 let lfsc = converter.alethe_to_lfsc(&alethe).unwrap();
774
775 let output = lfsc.to_string();
776 assert!(output.contains("t1_formula"));
777 }
778
779 #[test]
780 fn test_alethe_to_lfsc_without_comments() {
781 let converter = FormatConverter::new().add_comments(false);
782 let mut alethe = AletheProof::new();
783 alethe.assume("(= x 5)");
784
785 let lfsc = converter.alethe_to_lfsc(&alethe).unwrap();
786
787 let output = lfsc.to_string();
788 assert!(!output.contains("t1_formula"));
789 }
790
791 #[test]
792 fn test_alethe_to_lfsc_unsupported_rule() {
793 let converter = FormatConverter::new();
794 let mut alethe = AletheProof::new();
795
796 alethe.step_simple(vec!["p".to_string()], AletheRule::Skolem);
798
799 let result = converter.alethe_to_lfsc(&alethe);
800 assert!(result.is_err());
801
802 if let Err(ConversionError::UnsupportedConversion { from, to }) = result {
803 assert!(from.contains("Skolem"));
804 assert_eq!(to, "LFSC");
805 } else {
806 panic!("Expected UnsupportedConversion error");
807 }
808 }
809
810 #[test]
813 fn test_term_to_lfsc_term_literals() {
814 assert!(matches!(
815 FormatConverter::term_to_lfsc_term("true"),
816 Ok(LfscTerm::True)
817 ));
818 assert!(matches!(
819 FormatConverter::term_to_lfsc_term("false"),
820 Ok(LfscTerm::False)
821 ));
822 assert!(matches!(
823 FormatConverter::term_to_lfsc_term("42"),
824 Ok(LfscTerm::IntLit(42))
825 ));
826 }
827
828 #[test]
829 fn test_term_to_lfsc_term_variable() {
830 if let Ok(LfscTerm::Var(name)) = FormatConverter::term_to_lfsc_term("x") {
831 assert_eq!(name, "x");
832 } else {
833 panic!("Expected Var term");
834 }
835 }
836
837 #[test]
838 fn test_term_to_lfsc_term_application() {
839 if let Ok(LfscTerm::App(func, args)) = FormatConverter::term_to_lfsc_term("(+ x y)") {
840 assert_eq!(func, "+");
841 assert_eq!(args.len(), 2);
842 } else {
843 panic!("Expected App term");
844 }
845 }
846
847 #[test]
848 fn test_term_to_lfsc_term_nested() {
849 if let Ok(LfscTerm::App(func, args)) = FormatConverter::term_to_lfsc_term("(+ (* 2 x) y)") {
851 assert_eq!(func, "+");
852 assert_eq!(args.len(), 2);
853
854 if let LfscTerm::App(inner_func, inner_args) = &args[0] {
856 assert_eq!(inner_func, "*");
857 assert_eq!(inner_args.len(), 2);
858 } else {
859 panic!("Expected nested App term");
860 }
861 } else {
862 panic!("Expected App term");
863 }
864 }
865
866 #[test]
867 fn test_term_to_lfsc_term_negative_number() {
868 if let Ok(LfscTerm::IntLit(n)) = FormatConverter::term_to_lfsc_term("-42") {
869 assert_eq!(n, -42);
870 } else {
871 panic!("Expected IntLit term");
872 }
873 }
874
875 #[test]
876 fn test_term_to_lfsc_term_rational() {
877 if let Ok(LfscTerm::RatLit(num, den)) = FormatConverter::term_to_lfsc_term("3/4") {
878 assert_eq!(num, 3);
879 assert_eq!(den, 4);
880 } else {
881 panic!("Expected RatLit term");
882 }
883 }
884
885 #[test]
886 fn test_term_to_lfsc_term_negative_rational() {
887 if let Ok(LfscTerm::RatLit(num, den)) = FormatConverter::term_to_lfsc_term("-5/7") {
888 assert_eq!(num, -5);
889 assert_eq!(den, 7);
890 } else {
891 panic!("Expected RatLit term");
892 }
893 }
894
895 #[test]
896 fn test_term_to_lfsc_term_complex_nested() {
897 let term = "(= (+ x (* 3 y)) z)";
899 let result = FormatConverter::term_to_lfsc_term(term);
900
901 assert!(result.is_ok());
902 if let Ok(LfscTerm::App(func, args)) = result {
903 assert_eq!(func, "=");
904 assert_eq!(args.len(), 2);
905 } else {
906 panic!("Expected App term");
907 }
908 }
909
910 #[test]
911 fn test_tokenize_sexpr_simple() {
912 let tokens = FormatConverter::tokenize_sexpr("+ x y").unwrap();
913 assert_eq!(tokens, vec!["+", "x", "y"]);
914 }
915
916 #[test]
917 fn test_tokenize_sexpr_nested() {
918 let tokens = FormatConverter::tokenize_sexpr("+ (- x y) z").unwrap();
919 assert_eq!(tokens, vec!["+", "(- x y)", "z"]);
920 }
921
922 #[test]
923 fn test_tokenize_sexpr_deeply_nested() {
924 let tokens = FormatConverter::tokenize_sexpr("f (g (h x)) y").unwrap();
925 assert_eq!(tokens, vec!["f", "(g (h x))", "y"]);
926 }
927
928 #[test]
929 fn test_tokenize_sexpr_unbalanced() {
930 let result = FormatConverter::tokenize_sexpr("+ (x y z");
931 assert!(result.is_err());
932
933 if let Err(ConversionError::InvalidSource { reason }) = result {
934 assert!(reason.contains("Unbalanced parentheses"));
935 } else {
936 panic!("Expected InvalidSource error");
937 }
938 }
939
940 #[test]
941 fn test_parse_sort_basic() {
942 assert!(matches!(
943 FormatConverter::parse_sort("Bool"),
944 Ok(LfscSort::Bool)
945 ));
946 assert!(matches!(
947 FormatConverter::parse_sort("Int"),
948 Ok(LfscSort::Int)
949 ));
950 assert!(matches!(
951 FormatConverter::parse_sort("Real"),
952 Ok(LfscSort::Real)
953 ));
954 }
955
956 #[test]
957 fn test_parse_sort_bitvector() {
958 if let Ok(LfscSort::BitVec(width)) = FormatConverter::parse_sort("(_ BitVec 32)") {
959 assert_eq!(width, 32);
960 } else {
961 panic!("Expected BitVec sort");
962 }
963 }
964
965 #[test]
966 fn test_parse_sort_named() {
967 if let Ok(LfscSort::Named(name)) = FormatConverter::parse_sort("MySort") {
968 assert_eq!(name, "MySort");
969 } else {
970 panic!("Expected Named sort");
971 }
972 }
973
974 #[test]
975 fn test_information_loss_error_display() {
976 let err = ConversionError::InformationLoss {
977 reason: "test reason".to_string(),
978 };
979 assert!(err.to_string().contains("Information loss"));
980 assert!(err.to_string().contains("test reason"));
981 }
982
983 #[test]
984 fn test_invalid_source_error_display() {
985 let err = ConversionError::InvalidSource {
986 reason: "bad input".to_string(),
987 };
988 assert!(err.to_string().contains("Invalid source"));
989 assert!(err.to_string().contains("bad input"));
990 }
991
992 #[test]
993 fn test_conversion_failed_error_display() {
994 let err = ConversionError::ConversionFailed {
995 reason: "conversion issue".to_string(),
996 };
997 assert!(err.to_string().contains("Conversion failed"));
998 assert!(err.to_string().contains("conversion issue"));
999 }
1000
1001 proptest! {
1004 #[test]
1006 fn prop_drat_to_alethe_preserves_additions(
1007 clauses in prop::collection::vec(
1008 prop::collection::vec(-100i32..100i32, 0..5),
1009 0..20
1010 )
1011 ) {
1012 let converter = FormatConverter::new();
1013 let mut drat = DratProof::new();
1014
1015 let mut add_count = 0;
1016 for clause in clauses {
1017 drat.add_clause(clause);
1018 add_count += 1;
1019 }
1020
1021 let alethe = converter.drat_to_alethe(&drat).unwrap();
1022
1023 prop_assert_eq!(alethe.len(), add_count);
1025 }
1026
1027 #[test]
1029 fn prop_drat_deletion_strict_mode_fails(
1030 clauses in prop::collection::vec(-100i32..100i32, 1..5)
1031 ) {
1032 let converter = FormatConverter::new().strict_mode(true);
1033 let mut drat = DratProof::new();
1034
1035 drat.add_clause(clauses.clone());
1036 drat.delete_clause(clauses);
1037
1038 let result = converter.drat_to_alethe(&drat);
1039 prop_assert!(result.is_err());
1040 }
1041
1042 #[test]
1044 fn prop_alethe_to_lfsc_nonempty(
1045 num_assumptions in 0usize..10
1046 ) {
1047 let converter = FormatConverter::new();
1048 let mut alethe = AletheProof::new();
1049
1050 for i in 0..num_assumptions {
1051 alethe.assume(format!("p{}", i));
1052 }
1053
1054 let lfsc = converter.alethe_to_lfsc(&alethe).unwrap();
1055
1056 prop_assert!(!lfsc.is_empty());
1058 }
1059
1060 #[test]
1062 fn prop_term_parsing_deterministic(
1063 term in "[a-z][a-z0-9]{0,5}"
1064 ) {
1065 let result1 = FormatConverter::term_to_lfsc_term(&term);
1066 let result2 = FormatConverter::term_to_lfsc_term(&term);
1067
1068 prop_assert_eq!(
1069 result1.is_ok(),
1070 result2.is_ok(),
1071 "Parsing determinism violated"
1072 );
1073 }
1074
1075 #[test]
1077 fn prop_sort_parsing_basic(
1078 sort in prop::sample::select(vec!["Bool", "Int", "Real"])
1079 ) {
1080 let result = FormatConverter::parse_sort(sort);
1081 prop_assert!(result.is_ok());
1082 }
1083
1084 #[test]
1086 fn prop_bitvec_parsing(
1087 width in 1u32..256
1088 ) {
1089 let sort_str = format!("(_ BitVec {})", width);
1090 let result = FormatConverter::parse_sort(&sort_str);
1091
1092 prop_assert!(result.is_ok());
1093 if let Ok(LfscSort::BitVec(w)) = result {
1094 prop_assert_eq!(w, width);
1095 }
1096 }
1097
1098 #[test]
1100 fn prop_conversion_errors_nonempty(
1101 reason in "[a-zA-Z ]{1,50}"
1102 ) {
1103 let errors = vec![
1104 ConversionError::InformationLoss { reason: reason.clone() },
1105 ConversionError::InvalidSource { reason: reason.clone() },
1106 ConversionError::ConversionFailed { reason },
1107 ];
1108
1109 for err in errors {
1110 let msg = err.to_string();
1111 prop_assert!(!msg.is_empty());
1112 }
1113 }
1114
1115 #[test]
1117 fn prop_drat_empty_clause_handling(
1118 prefix_clauses in prop::collection::vec(
1119 prop::collection::vec(-10i32..10i32, 1..3),
1120 0..5
1121 )
1122 ) {
1123 let converter = FormatConverter::new();
1124 let mut drat = DratProof::new();
1125
1126 for clause in prefix_clauses {
1128 drat.add_clause(clause);
1129 }
1130
1131 drat.add_clause(vec![]);
1133
1134 let alethe = converter.drat_to_alethe(&drat).unwrap();
1135
1136 prop_assert!(!alethe.is_empty());
1138 }
1139
1140 #[test]
1142 fn prop_alethe_resolution_chain(
1143 chain_length in 1usize..10
1144 ) {
1145 let converter = FormatConverter::new();
1146 let mut alethe = AletheProof::new();
1147
1148 let mut indices = Vec::new();
1150 for i in 0..chain_length {
1151 let idx = alethe.assume(format!("p{}", i));
1152 indices.push(idx);
1153 }
1154
1155 if indices.len() >= 2 {
1157 alethe.resolution(
1158 vec!["q".to_string()],
1159 vec![indices[0], indices[1]]
1160 );
1161 }
1162
1163 let lfsc = converter.alethe_to_lfsc(&alethe).unwrap();
1164
1165 prop_assert!(!lfsc.is_empty());
1167 }
1168
1169 #[test]
1171 fn prop_converter_settings_preserved(
1172 strict in prop::bool::ANY,
1173 comments in prop::bool::ANY
1174 ) {
1175 let converter = FormatConverter::new()
1176 .strict_mode(strict)
1177 .add_comments(comments);
1178
1179 prop_assert_eq!(converter.strict_mode, strict);
1180 prop_assert_eq!(converter.add_comments, comments);
1181 }
1182 }
1183}