1use crate::alethe::{AletheProof, AletheRule, AletheStep};
27use crate::theory::{TheoryProof, TheoryRule, TheoryStepId};
28use std::collections::HashSet;
29use std::fmt;
30
31#[derive(Debug, Clone)]
33pub enum CheckResult {
34 Valid,
36 Invalid {
38 step: u32,
40 error: CheckError,
42 },
43 MultipleErrors(Vec<(u32, CheckError)>),
45}
46
47impl CheckResult {
48 #[must_use]
50 pub fn is_valid(&self) -> bool {
51 matches!(self, Self::Valid)
52 }
53
54 #[must_use]
56 pub fn error(&self) -> Option<&CheckError> {
57 match self {
58 Self::Invalid { error, .. } => Some(error),
59 _ => None,
60 }
61 }
62}
63
64impl fmt::Display for CheckResult {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 Self::Valid => write!(f, "✓ Proof is valid"),
68 Self::Invalid { step, error } => {
69 writeln!(f, "✗ Proof is invalid")?;
70 writeln!(f, " Step: {}", step)?;
71 writeln!(f, " [{:?}] {}", error.severity(), error)?;
72 if let Some(suggestion) = error.suggestion() {
73 writeln!(f, " Suggestion: {}", suggestion)?;
74 }
75 Ok(())
76 }
77 Self::MultipleErrors(errors) => {
78 writeln!(f, "✗ Proof has {} error(s):", errors.len())?;
79 for (step, error) in errors {
80 writeln!(f, "\n Step {}:", step)?;
81 writeln!(f, " [{:?}] {}", error.severity(), error)?;
82 if let Some(suggestion) = error.suggestion() {
83 writeln!(f, " Suggestion: {}", suggestion)?;
84 }
85 }
86 Ok(())
87 }
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum CheckError {
95 MissingPremise(u32),
97 WrongPremiseCount { expected: usize, got: usize },
99 WrongArgumentCount { expected: usize, got: usize },
101 RuleNotApplicable(String),
103 InvalidConclusion(String),
105 CyclicDependency,
107 EmptyProof,
109 MalformedTerm(String),
111 UnknownRule(String),
113 Custom(String),
115}
116
117impl fmt::Display for CheckError {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 match self {
120 Self::MissingPremise(id) => {
121 write!(
122 f,
123 "Missing premise: step {} does not exist or has not been defined yet. \
124 Premises must be defined before they are referenced.",
125 id
126 )
127 }
128 Self::WrongPremiseCount { expected, got } => {
129 write!(
130 f,
131 "Wrong premise count: rule requires {} premise(s), but {} {} provided. \
132 Check the rule definition for the correct number of premises.",
133 expected,
134 got,
135 if *got == 1 { "was" } else { "were" }
136 )
137 }
138 Self::WrongArgumentCount { expected, got } => {
139 write!(
140 f,
141 "Wrong argument count: rule expects {} argument(s), but {} {} provided. \
142 Ensure all required arguments are supplied.",
143 expected,
144 got,
145 if *got == 1 { "was" } else { "were" }
146 )
147 }
148 Self::RuleNotApplicable(msg) => {
149 write!(
150 f,
151 "Rule not applicable: {}. \
152 Verify that the rule's preconditions are met.",
153 msg
154 )
155 }
156 Self::InvalidConclusion(msg) => {
157 write!(
158 f,
159 "Invalid conclusion: {}. \
160 The conclusion does not follow from the premises using the specified rule.",
161 msg
162 )
163 }
164 Self::CyclicDependency => {
165 write!(
166 f,
167 "Cyclic dependency detected in proof structure. \
168 A proof step cannot depend on itself (directly or indirectly). \
169 Check for circular references in premise chains."
170 )
171 }
172 Self::EmptyProof => {
173 write!(
174 f,
175 "Empty proof: no proof steps provided. \
176 A valid proof must contain at least one step."
177 )
178 }
179 Self::MalformedTerm(msg) => {
180 write!(
181 f,
182 "Malformed term: {}. \
183 Check for syntax errors or invalid term structure.",
184 msg
185 )
186 }
187 Self::UnknownRule(name) => {
188 write!(
189 f,
190 "Unknown rule: '{}'. \
191 This rule is not recognized by the proof checker. \
192 Verify the rule name is spelled correctly.",
193 name
194 )
195 }
196 Self::Custom(msg) => write!(f, "{}", msg),
197 }
198 }
199}
200
201impl std::error::Error for CheckError {}
202
203impl CheckError {
204 #[must_use]
206 pub fn suggestion(&self) -> Option<&str> {
207 match self {
208 Self::MissingPremise(_) => {
209 Some("Ensure all premise steps are added to the proof before referencing them.")
210 }
211 Self::WrongPremiseCount { .. } => {
212 Some("Consult the rule documentation for the correct number of premises.")
213 }
214 Self::WrongArgumentCount { .. } => {
215 Some("Review the rule definition to determine which arguments are required.")
216 }
217 Self::RuleNotApplicable(_) => {
218 Some("Check that the premise types match what the rule expects.")
219 }
220 Self::InvalidConclusion(_) => {
221 Some("Verify that the rule is being applied correctly to the given premises.")
222 }
223 Self::CyclicDependency => {
224 Some("Reorganize proof steps to eliminate circular dependencies.")
225 }
226 Self::EmptyProof => Some("Add at least one axiom or assumption to the proof."),
227 Self::MalformedTerm(_) => Some("Check the term syntax against the expected format."),
228 Self::UnknownRule(_) => {
229 Some("Use a standard proof rule or define a custom rule handler.")
230 }
231 Self::Custom(_) => None,
232 }
233 }
234
235 #[must_use]
237 pub fn severity(&self) -> ErrorSeverity {
238 match self {
239 Self::CyclicDependency | Self::EmptyProof => ErrorSeverity::Critical,
240 Self::MissingPremise(_) | Self::InvalidConclusion(_) | Self::UnknownRule(_) => {
241 ErrorSeverity::Error
242 }
243 Self::WrongPremiseCount { .. }
244 | Self::WrongArgumentCount { .. }
245 | Self::RuleNotApplicable(_)
246 | Self::MalformedTerm(_) => ErrorSeverity::Warning,
247 Self::Custom(_) => ErrorSeverity::Error,
248 }
249 }
250}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
254pub enum ErrorSeverity {
255 Warning,
257 Error,
259 Critical,
261}
262
263impl fmt::Display for ErrorSeverity {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 match self {
266 Self::Warning => write!(f, "WARNING"),
267 Self::Error => write!(f, "ERROR"),
268 Self::Critical => write!(f, "CRITICAL"),
269 }
270 }
271}
272
273#[derive(Debug, Clone, Default)]
275pub struct CheckerConfig {
276 pub continue_on_error: bool,
278 pub verify_conclusions: bool,
280 pub allow_cycles: bool,
282}
283
284#[derive(Debug, Default)]
286pub struct ProofChecker {
287 config: CheckerConfig,
289 errors: Vec<(u32, CheckError)>,
291 validated: HashSet<u32>,
293 in_progress: HashSet<u32>,
295}
296
297impl ProofChecker {
298 #[must_use]
300 pub fn new() -> Self {
301 Self::default()
302 }
303
304 #[must_use]
306 pub fn with_config(config: CheckerConfig) -> Self {
307 Self {
308 config,
309 errors: Vec::new(),
310 validated: HashSet::new(),
311 in_progress: HashSet::new(),
312 }
313 }
314
315 pub fn reset(&mut self) {
317 self.errors.clear();
318 self.validated.clear();
319 self.in_progress.clear();
320 }
321
322 pub fn check_theory_proof(&mut self, proof: &TheoryProof) -> CheckResult {
324 self.reset();
325
326 if proof.is_empty() {
327 return CheckResult::Invalid {
328 step: 0,
329 error: CheckError::EmptyProof,
330 };
331 }
332
333 for step in proof.steps() {
335 if let Err(error) = self.check_theory_step(proof, step.id) {
336 if self.config.continue_on_error {
337 self.errors.push((step.id.0, error));
338 } else {
339 return CheckResult::Invalid {
340 step: step.id.0,
341 error,
342 };
343 }
344 }
345 }
346
347 if self.errors.is_empty() {
348 CheckResult::Valid
349 } else {
350 CheckResult::MultipleErrors(std::mem::take(&mut self.errors))
351 }
352 }
353
354 fn check_theory_step(
356 &mut self,
357 proof: &TheoryProof,
358 step_id: TheoryStepId,
359 ) -> Result<(), CheckError> {
360 if !self.config.allow_cycles {
362 if self.in_progress.contains(&step_id.0) {
363 return Err(CheckError::CyclicDependency);
364 }
365 if self.validated.contains(&step_id.0) {
366 return Ok(());
367 }
368 self.in_progress.insert(step_id.0);
369 }
370
371 let step = proof
372 .get_step(step_id)
373 .ok_or(CheckError::MissingPremise(step_id.0))?;
374
375 for premise_id in &step.premises {
377 if proof.get_step(*premise_id).is_none() {
378 return Err(CheckError::MissingPremise(premise_id.0));
379 }
380
381 if !self.config.allow_cycles {
383 self.check_theory_step(proof, *premise_id)?;
384 }
385 }
386
387 self.check_theory_rule(&step.rule, step.premises.len(), step.args.len())?;
389
390 if !self.config.allow_cycles {
392 self.in_progress.remove(&step_id.0);
393 self.validated.insert(step_id.0);
394 }
395
396 Ok(())
397 }
398
399 fn check_theory_rule(
401 &self,
402 rule: &TheoryRule,
403 premise_count: usize,
404 arg_count: usize,
405 ) -> Result<(), CheckError> {
406 match rule {
407 TheoryRule::Refl if premise_count != 0 => {
409 return Err(CheckError::WrongPremiseCount {
410 expected: 0,
411 got: premise_count,
412 });
413 }
414
415 TheoryRule::Symm if premise_count != 1 => {
417 return Err(CheckError::WrongPremiseCount {
418 expected: 1,
419 got: premise_count,
420 });
421 }
422
423 TheoryRule::Trans if premise_count != 2 => {
425 return Err(CheckError::WrongPremiseCount {
426 expected: 2,
427 got: premise_count,
428 });
429 }
430
431 TheoryRule::Cong => {
433 }
435
436 TheoryRule::LaGeneric if premise_count < 2 => {
438 return Err(CheckError::WrongPremiseCount {
439 expected: 2,
440 got: premise_count,
441 });
442 }
443
444 TheoryRule::ArrReadWrite1 if premise_count != 0 => {
446 return Err(CheckError::WrongPremiseCount {
447 expected: 0,
448 got: premise_count,
449 });
450 }
451
452 TheoryRule::ArrReadWrite2 if premise_count != 1 => {
454 return Err(CheckError::WrongPremiseCount {
455 expected: 1,
456 got: premise_count,
457 });
458 }
459
460 TheoryRule::LaMult if arg_count < 1 => {
462 return Err(CheckError::WrongArgumentCount {
463 expected: 1,
464 got: arg_count,
465 });
466 }
467
468 _ => {}
470 }
471
472 Ok(())
473 }
474
475 pub fn check_alethe_proof(&mut self, proof: &AletheProof) -> CheckResult {
477 self.reset();
478
479 if proof.is_empty() {
480 return CheckResult::Invalid {
481 step: 0,
482 error: CheckError::EmptyProof,
483 };
484 }
485
486 let steps = proof.steps();
487 let mut step_indices: HashSet<u32> = HashSet::new();
488
489 for step in steps {
491 match step {
492 AletheStep::Assume { index, .. } => {
493 step_indices.insert(*index);
494 }
495 AletheStep::Step { index, .. } => {
496 step_indices.insert(*index);
497 }
498 AletheStep::Anchor { step: index, .. } => {
499 step_indices.insert(*index);
500 }
501 AletheStep::DefineFun { .. } => {}
502 }
503 }
504
505 for (idx, step) in steps.iter().enumerate() {
507 if let Err(error) = self.check_alethe_step(step, &step_indices) {
508 if self.config.continue_on_error {
509 self.errors.push((idx as u32, error));
510 } else {
511 return CheckResult::Invalid {
512 step: idx as u32,
513 error,
514 };
515 }
516 }
517 }
518
519 if self.errors.is_empty() {
520 CheckResult::Valid
521 } else {
522 CheckResult::MultipleErrors(std::mem::take(&mut self.errors))
523 }
524 }
525
526 fn check_alethe_step(
528 &self,
529 step: &AletheStep,
530 step_indices: &HashSet<u32>,
531 ) -> Result<(), CheckError> {
532 match step {
533 AletheStep::Assume { .. } => {
534 Ok(())
536 }
537
538 AletheStep::Step { rule, premises, .. } => {
539 for premise in premises {
541 if !step_indices.contains(premise) {
542 return Err(CheckError::MissingPremise(*premise));
543 }
544 }
545
546 self.check_alethe_rule(rule, premises.len())
548 }
549
550 AletheStep::Anchor { .. } => {
551 Ok(())
553 }
554
555 AletheStep::DefineFun { .. } => {
556 Ok(())
558 }
559 }
560 }
561
562 fn check_alethe_rule(&self, rule: &AletheRule, premise_count: usize) -> Result<(), CheckError> {
564 match rule {
565 AletheRule::Resolution if premise_count < 2 => {
567 return Err(CheckError::WrongPremiseCount {
568 expected: 2,
569 got: premise_count,
570 });
571 }
572
573 AletheRule::Refl if premise_count != 0 => {
575 return Err(CheckError::WrongPremiseCount {
576 expected: 0,
577 got: premise_count,
578 });
579 }
580
581 AletheRule::Trans if premise_count < 2 => {
583 return Err(CheckError::WrongPremiseCount {
584 expected: 2,
585 got: premise_count,
586 });
587 }
588
589 _ => {}
591 }
592
593 Ok(())
594 }
595}
596
597pub trait Checkable {
599 fn check(&self) -> CheckResult;
601
602 fn check_with_config(&self, config: CheckerConfig) -> CheckResult;
604}
605
606impl Checkable for TheoryProof {
607 fn check(&self) -> CheckResult {
608 ProofChecker::new().check_theory_proof(self)
609 }
610
611 fn check_with_config(&self, config: CheckerConfig) -> CheckResult {
612 ProofChecker::with_config(config).check_theory_proof(self)
613 }
614}
615
616impl Checkable for AletheProof {
617 fn check(&self) -> CheckResult {
618 ProofChecker::new().check_alethe_proof(self)
619 }
620
621 fn check_with_config(&self, config: CheckerConfig) -> CheckResult {
622 ProofChecker::with_config(config).check_alethe_proof(self)
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629 #[allow(unused_imports)]
630 use crate::theory::ProofTerm;
631
632 #[test]
633 fn test_check_result_is_valid() {
634 assert!(CheckResult::Valid.is_valid());
635 assert!(
636 !CheckResult::Invalid {
637 step: 0,
638 error: CheckError::EmptyProof
639 }
640 .is_valid()
641 );
642 }
643
644 #[test]
645 fn test_check_error_display() {
646 let err = CheckError::MissingPremise(5);
647 let msg = format!("{}", err);
648 assert!(msg.contains("5"));
649 assert!(msg.contains("does not exist"));
650
651 let err = CheckError::WrongPremiseCount {
652 expected: 2,
653 got: 1,
654 };
655 let msg = format!("{}", err);
656 assert!(msg.contains("requires 2"));
657 assert!(msg.contains("1 was provided"));
658 }
659
660 #[test]
661 fn test_theory_proof_empty() {
662 let proof = TheoryProof::new();
663 let result = proof.check();
664 assert!(!result.is_valid());
665 assert!(matches!(result.error(), Some(CheckError::EmptyProof)));
666 }
667
668 #[test]
669 fn test_theory_proof_valid_refl() {
670 let mut proof = TheoryProof::new();
671 proof.refl("x");
672
673 let result = proof.check();
674 assert!(result.is_valid());
675 }
676
677 #[test]
678 fn test_theory_proof_valid_transitivity() {
679 let mut proof = TheoryProof::new();
680 let s1 = proof.add_axiom(TheoryRule::Custom("assert".into()), "(= a b)");
681 let s2 = proof.add_axiom(TheoryRule::Custom("assert".into()), "(= b c)");
682 proof.trans(s1, s2, "a", "c");
683
684 let result = proof.check();
685 assert!(result.is_valid());
686 }
687
688 #[test]
689 fn test_theory_proof_invalid_trans_premises() {
690 let mut proof = TheoryProof::new();
691 let s1 = proof.add_axiom(TheoryRule::Custom("assert".into()), "(= a b)");
692 proof.add_step(TheoryRule::Trans, vec![s1], "(= a c)");
694
695 let result = proof.check();
696 assert!(!result.is_valid());
697 }
698
699 #[test]
700 fn test_theory_proof_missing_premise() {
701 let mut proof = TheoryProof::new();
702 proof.add_step(
704 TheoryRule::Trans,
705 vec![TheoryStepId(99), TheoryStepId(100)],
706 "(= a c)",
707 );
708
709 let result = proof.check();
710 assert!(!result.is_valid());
711 assert!(matches!(
712 result.error(),
713 Some(CheckError::MissingPremise(_))
714 ));
715 }
716
717 #[test]
718 fn test_alethe_proof_empty() {
719 let proof = AletheProof::new();
720 let result = proof.check();
721 assert!(!result.is_valid());
722 }
723
724 #[test]
725 fn test_alethe_proof_valid() {
726 let mut proof = AletheProof::new();
727 proof.assume("p");
728 proof.step_simple(vec![], AletheRule::Refl);
729
730 let result = proof.check();
731 assert!(result.is_valid());
732 }
733
734 #[test]
735 fn test_checker_continue_on_error() {
736 let mut proof = TheoryProof::new();
737 proof.add_step(TheoryRule::Trans, vec![TheoryStepId(99)], "(= a b)");
739 proof.add_step(TheoryRule::Trans, vec![TheoryStepId(100)], "(= c d)");
740
741 let config = CheckerConfig {
742 continue_on_error: true,
743 ..Default::default()
744 };
745
746 let result = proof.check_with_config(config);
747 assert!(matches!(result, CheckResult::MultipleErrors(_)));
748 }
749
750 #[test]
751 fn test_checker_refl_with_premises_fails() {
752 let mut proof = TheoryProof::new();
753 let s1 = proof.add_axiom(TheoryRule::Custom("assert".into()), "(= a b)");
754 proof.add_step(TheoryRule::Refl, vec![s1], "(= x x)");
756
757 let result = proof.check();
758 assert!(!result.is_valid());
759 }
760
761 #[test]
762 fn test_checker_farkas_needs_premises() {
763 let mut proof = TheoryProof::new();
764 let s1 = proof.add_axiom(TheoryRule::Custom("bound".into()), "(>= x 0)");
766 proof.add_step(TheoryRule::LaGeneric, vec![s1], "false");
767
768 let result = proof.check();
769 assert!(!result.is_valid());
770 }
771
772 #[test]
773 fn test_checker_arr_read_write_1_axiom() {
774 let mut proof = TheoryProof::new();
775 proof.add_axiom(TheoryRule::ArrReadWrite1, "(= (select (store a i v) i) v)");
777
778 let result = proof.check();
779 assert!(result.is_valid());
780 }
781
782 #[test]
783 fn test_checker_arr_read_write_2_needs_premise() {
784 let mut proof = TheoryProof::new();
785 proof.add_axiom(
787 TheoryRule::ArrReadWrite2,
788 "(= (select (store a i v) j) (select a j))",
789 );
790
791 let result = proof.check();
792 assert!(!result.is_valid());
793 }
794}