From 78cfcc816ccc8ed01dfe70c36d857b8155c29b5b Mon Sep 17 00:00:00 2001 From: Florian Cassayre <florian.cassayre@gmail.com> Date: Mon, 21 Feb 2022 08:56:55 +0100 Subject: [PATCH] Introduce the proof judgement ADT --- src/main/scala/lisa/kernel/Printer.scala | 27 +- .../lisa/kernel/proof/SCProofChecker.scala | 230 +++++++++--------- .../proof/SCProofCheckerJudgement.scala | 33 +++ src/test/scala/lisa/kernel/ProofTests.scala | 6 +- .../scala/lisa/proven/SimpleProverTests.scala | 4 +- src/test/scala/test/ProofCheckerSuite.scala | 6 +- 6 files changed, 172 insertions(+), 134 deletions(-) create mode 100644 src/main/scala/lisa/kernel/proof/SCProofCheckerJudgement.scala diff --git a/src/main/scala/lisa/kernel/Printer.scala b/src/main/scala/lisa/kernel/Printer.scala index 6f27c597..cd8c20e2 100644 --- a/src/main/scala/lisa/kernel/Printer.scala +++ b/src/main/scala/lisa/kernel/Printer.scala @@ -1,8 +1,9 @@ package lisa.kernel import lisa.kernel.fol.FOL.* -import lisa.kernel.proof.SequentCalculus._ -import lisa.kernel.proof.SCProof +import lisa.kernel.proof.SequentCalculus.* +import lisa.kernel.proof.SCProofCheckerJudgement.* +import lisa.kernel.proof.{SCProof, SCProofCheckerJudgement} /** * A set of methods to pretty-print kernel trees. @@ -182,10 +183,11 @@ object Printer { /** * Returns a string representation of this proof. * @param proof the proof - * @param showError if set, marks that particular step in the proof (`->`) as an error + * @param judgement optionally provide a proof checking judgement that will mark a particular step in the proof + * (`->`) as an error. The proof is considered to be valid by default * @return a string where each indented line corresponds to a step in the proof */ - def prettySCProof(proof: SCProof, showError: Option[(Seq[Int], String)] = None): String = { + def prettySCProof(proof: SCProof, judgement: SCProofCheckerJudgement = SCValidProof): String = { def computeMaxNumbering(proof: SCProof, level: Int, result: IndexedSeq[Int]): IndexedSeq[Int] = { val resultWithCurrent = result.updated(level, Math.max(proof.steps.size - 1, result(level))) proof.steps.collect { case sp: SCSubproof => sp }.foldLeft(resultWithCurrent)((acc, sp) => computeMaxNumbering(sp.sp, level + 1, if(acc.size <= level + 1) acc :+ 0 else acc)) @@ -205,7 +207,10 @@ object Printer { def prettySCProofRecursive(proof: SCProof, level: Int, tree: IndexedSeq[Int], topMostIndices: IndexedSeq[Int]): Seq[(Boolean, String, String, String)] = { val printedImports = proof.imports.zipWithIndex.reverse.flatMap { case (imp, i) => val currentTree = tree :+ (-i-1) - val showErrorForLine = showError.exists((position, reason) => currentTree.startsWith(position) && currentTree.drop(position.size).forall(_ == 0)) + val showErrorForLine = judgement match { + case SCValidProof => false + case SCInvalidProof(position, _) => currentTree.startsWith(position) && currentTree.drop(position.size).forall(_ == 0) + } val prefix = (Seq.fill(level - topMostIndices.size)(None) ++ Seq.fill(topMostIndices.size)(None) :+ Some(-i-1)) ++ Seq.fill(maxLevel - level)(None) val prefixString = prefix.map(_.map(_.toString).getOrElse("")).zipWithIndex.map { case (v, i1) => leftPadSpaces(v, maxNumberingLengths(i1)) }.mkString(" ") def pretty(stepName: String, topSteps: Int*): (Boolean, String, String, String) = @@ -220,7 +225,10 @@ object Printer { } printedImports ++ proof.steps.zipWithIndex.flatMap { case (step, i) => val currentTree = tree :+ i - val showErrorForLine = showError.exists((position, reason) => currentTree.startsWith(position) && currentTree.drop(position.size).forall(_ == 0)) + val showErrorForLine = judgement match { + case SCValidProof => false + case SCInvalidProof(position, _) => currentTree.startsWith(position) && currentTree.drop(position.size).forall(_ == 0) + } val prefix = (Seq.fill(level - topMostIndices.size)(None) ++ Seq.fill(topMostIndices.size)(None) :+ Some(i)) ++ Seq.fill(maxLevel - level)(None) val prefixString = prefix.map(_.map(_.toString).getOrElse("")).zipWithIndex.map { case (v, i1) => leftPadSpaces(v, maxNumberingLengths(i1)) }.mkString(" ") def pretty(stepName: String, topSteps: Int*): (Boolean, String, String, String) = @@ -276,9 +284,12 @@ object Printer { lines.map { case (isMarked, indices, stepName, sequent) => val suffix = Seq(indices, rightPadSpaces(stepName, maxStepNameLength), sequent) - val full = if(showError.nonEmpty) (if(isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix + val full = if(!judgement.isValid) (if(isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix full.mkString(" ") - }.mkString("\n") + (if (showError.nonEmpty) s"\nProof checker has reported error at line ${showError.get._1}: ${showError.get._2}" else "") + }.mkString("\n") + (judgement match { + case SCValidProof => "" + case SCInvalidProof(path, message) => s"\nProof checker has reported error at line ${path.mkString(".")}: $message" + }) } } diff --git a/src/main/scala/lisa/kernel/proof/SCProofChecker.scala b/src/main/scala/lisa/kernel/proof/SCProofChecker.scala index 11fd361e..6602e1ce 100644 --- a/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -3,6 +3,7 @@ package lisa.kernel.proof import lisa.kernel.Printer import lisa.kernel.fol.FOL.* import lisa.kernel.proof.SequentCalculus.* +import lisa.kernel.proof.SCProofCheckerJudgement.* object SCProofChecker { @@ -21,16 +22,16 @@ object SCProofChecker { * a proof's [[SCProof.getSequent]] function. * @return */ - def checkSingleSCStep(no:Int, step: SCProofStep, references : Int => Sequent, importsSize: Option[Int]=None):(Boolean, List[Int], String) = { + def checkSingleSCStep(no:Int, step: SCProofStep, references : Int => Sequent, importsSize: Option[Int]=None): SCProofCheckerJudgement = { val ref = references val false_premise = step.premises.find(i => i >= no) val false_premise2 = if (importsSize.nonEmpty) step.premises.find(i => i< -importsSize.get) else None - val r: (Boolean, List[Int], String) = + val r: SCProofCheckerJudgement = if (false_premise.nonEmpty) - return (false, Nil, s"Step no $no can't refer to higher number ${false_premise.get} as a premise.") - if (false_premise2.nonEmpty) - return (false, Nil, s"A step can't refer to step ${false_premise2.get}, imports only contains ${importsSize.get} elements.") + SCInvalidProof(Nil, s"Step no $no can't refer to higher number ${false_premise.get} as a premise.") + else if (false_premise2.nonEmpty) + SCInvalidProof(Nil, s"A step can't refer to step ${false_premise2.get}, imports only contains ${importsSize.get} elements.") else step match { /* * Γ |- Δ @@ -38,7 +39,7 @@ object SCProofChecker { * Γ |- Δ */ case Rewrite(s, t1) => - if (isSameSequent(s, ref(t1))) (true, Nil, "") else (false, Nil, s"The premise and the conclusion are not trivially equivalent.") + if (isSameSequent(s, ref(t1))) SCValidProof else SCInvalidProof(Nil, s"The premise and the conclusion are not trivially equivalent.") /* * * -------------- @@ -46,10 +47,10 @@ object SCProofChecker { */ case Hypothesis(Sequent(left, right), phi) => if (contains(left, phi)) - if (contains(right, phi)) (true, Nil, "") + if (contains(right, phi)) SCValidProof - else (false, Nil, s"Right-hand side does not contain formula φ") - else (false, Nil, s"Left-hand side does not contain formula φ") + else SCInvalidProof(Nil, s"Right-hand side does not contain formula φ") + else SCInvalidProof(Nil, s"Left-hand side does not contain formula φ") /* * Γ |- Δ, φ φ, Σ |- Π * ------------------------ @@ -60,11 +61,11 @@ object SCProofChecker { if (isSameSet(b.right + phi, ref(t2).right union ref(t1).right)) if (contains(ref(t2).left, phi)) if (contains(ref(t1).right, phi)) - (true, Nil, "") - else (false, Nil, s"Right-hand side of first premise does not contain φ as claimed.") - else (false, Nil, s"Left-hand side of second premise does not contain φ as claimed.") - else (false, Nil, s"Right-hand side of conclusion + φ is not the union of the right-hand sides of the premises.") - else (false, Nil, s"Left-hand side of conclusion + φ is not the union of the left-hand sides of the premises.") + SCValidProof + else SCInvalidProof(Nil, s"Right-hand side of first premise does not contain φ as claimed.") + else SCInvalidProof(Nil, s"Left-hand side of second premise does not contain φ as claimed.") + else SCInvalidProof(Nil, s"Right-hand side of conclusion + φ is not the union of the right-hand sides of the premises.") + else SCInvalidProof(Nil, s"Left-hand side of conclusion + φ is not the union of the left-hand sides of the premises.") // Left rules /* @@ -78,9 +79,9 @@ object SCProofChecker { if (isSameSet(b.left + phi, ref(t1).left + phiAndPsi) || isSameSet(b.left + psi, ref(t1).left + phiAndPsi) || isSameSet(b.left + phi + psi, ref(t1).left + phiAndPsi)) - (true, Nil, "") - else (false, Nil, "Left-hand side of conclusion + φ∧ψ must be same as left-hand side of premise + either φ, ψ or both.") - else (false, Nil, "Right-hand sides of the premise and the conclusion must be the same.") + SCValidProof + else SCInvalidProof(Nil, "Left-hand side of conclusion + φ∧ψ must be same as left-hand side of premise + either φ, ψ or both.") + else SCInvalidProof(Nil, "Right-hand sides of the premise and the conclusion must be the same.") /* * Γ, φ |- Δ Σ, ψ |- Π * ------------------------ @@ -90,9 +91,9 @@ object SCProofChecker { if (isSameSet(b.right, t.map(ref(_).right).reduce(_ union _)) ) val phiOrPsi = ConnectorFormula(Or, disjuncts) if (isSameSet(disjuncts.foldLeft(b.left)(_ + _), t.map(ref(_).left).reduce(_ union _) + phiOrPsi)) - (true, Nil, "") - else (false, Nil, s"Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") - else (false, Nil, s"Right-hand side of conclusion is not the union of the right-hand sides of the premises.") + SCValidProof + else SCInvalidProof(Nil, s"Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") + else SCInvalidProof(Nil, s"Right-hand side of conclusion is not the union of the right-hand sides of the premises.") /* * Γ |- φ, Δ Σ, ψ |- Π * ------------------------ @@ -102,9 +103,9 @@ object SCProofChecker { val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) if (isSameSet(b.right + phi, ref(t1).right union ref(t2).right)) if (isSameSet(b.left + psi, ref(t1).left union ref(t2).left + phiImpPsi)) - (true, Nil, "") - else (false, Nil, s"Left-hand side of conclusion + ψ must be identical to union of left-hand sides of premisces + φ→ψ.") - else (false, Nil, s"Right-hand side of conclusion + φ must be identical to union of right-hand sides of premisces.") + SCValidProof + else SCInvalidProof(Nil, s"Left-hand side of conclusion + ψ must be identical to union of left-hand sides of premisces + φ→ψ.") + else SCInvalidProof(Nil, s"Right-hand side of conclusion + φ must be identical to union of right-hand sides of premisces.") /* * Γ, φ→ψ |- Δ Γ, φ→ψ, ψ→φ |- Δ * -------------- or --------------- @@ -118,9 +119,9 @@ object SCProofChecker { if (isSameSet(b.left + phiImpPsi , ref(t1).left + phiIffPsi) || isSameSet(b.left + psiImpPhi , ref(t1).left + phiIffPsi) || isSameSet(b.left + phiImpPsi + psiImpPhi , ref(t1).left + phiIffPsi)) - (true, Nil, "") - else (false, Nil, "Left-hand side of conclusion + φ↔ψ must be same as left-hand side of premise + either φ→ψ, ψ→φ or both.") - else (false, Nil, "Right-hand sides of premise and conclusion must be the same.") + SCValidProof + else SCInvalidProof(Nil, "Left-hand side of conclusion + φ↔ψ must be same as left-hand side of premise + either φ→ψ, ψ→φ or both.") + else SCInvalidProof(Nil, "Right-hand sides of premise and conclusion must be the same.") /* * Γ |- φ, Δ @@ -131,9 +132,9 @@ object SCProofChecker { val nPhi = ConnectorFormula(Neg, Seq(phi)) if (isSameSet(b.left, ref(t1).left + nPhi)) if (isSameSet(b.right + phi, ref(t1).right)) - (true, Nil, "") - else (false, Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise") - else (false, Nil, "Left-hand side of conclusion must be the same as left-hand side of premise + ¬φ") + SCValidProof + else SCInvalidProof(Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise") + else SCInvalidProof(Nil, "Left-hand side of conclusion must be the same as left-hand side of premise + ¬φ") /* * Γ, φ[t/x] |- Δ @@ -143,9 +144,9 @@ object SCProofChecker { case LeftForall(b, t1, phi, x, t) => if (isSameSet(b.right, ref(t1).right)) if (isSameSet(b.left + substituteVariable(phi, x, t), ref(t1).left + BinderFormula(Forall, x, phi))) - (true, Nil, "") - else (false, Nil, "Left-hand side of conclusion + φ[t/x] must be the same as left-hand side of premise + ∀x. φ") - else (false, Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") + SCValidProof + else SCInvalidProof(Nil, "Left-hand side of conclusion + φ[t/x] must be the same as left-hand side of premise + ∀x. φ") + else SCInvalidProof(Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") /* * Γ, φ |- Δ @@ -156,10 +157,10 @@ object SCProofChecker { if (isSameSet(b.right, ref(t1).right)) if (isSameSet(b.left + phi, ref(t1).left + BinderFormula(Exists, x, phi))) if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) - (true, Nil, "") - else (false, Nil, "The variable x must not be free in the resulting sequent.") - else (false, Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise + ∃x. φ") - else (false, Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") + SCValidProof + else SCInvalidProof(Nil, "The variable x must not be free in the resulting sequent.") + else SCInvalidProof(Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise + ∃x. φ") + else SCInvalidProof(Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") /* * Γ, ∃y.∀x. (x=y) ↔ φ |- Δ @@ -171,9 +172,9 @@ object SCProofChecker { val temp = BinderFormula(Exists, y, BinderFormula(Forall, x, ConnectorFormula(Iff, List(PredicateFormula(equality, List(VariableTerm(x), VariableTerm(y))), phi)))) if (isSameSet(b.right, ref(t1).right)) if (isSameSet(b.left + temp, ref(t1).left + BinderFormula(ExistsOne, x, phi))) - (true, Nil, "") - else (false, Nil, "Left-hand side of conclusion + ∃y.∀x. (x=y) ↔ φ must be the same as left-hand side of premise + ∃!x. φ") - else (false, Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") + SCValidProof + else SCInvalidProof(Nil, "Left-hand side of conclusion + ∃y.∀x. (x=y) ↔ φ must be the same as left-hand side of premise + ∃!x. φ") + else SCInvalidProof(Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") // Right rules /* @@ -185,9 +186,9 @@ object SCProofChecker { val phiAndPsi = ConnectorFormula(And, cunjuncts) if (isSameSet(b.left, t.map(ref(_).left).reduce(_ union _))) if (isSameSet(cunjuncts.foldLeft(b.right)(_ + _), t.map(ref(_).right).reduce(_ union _) + phiAndPsi)) - (true, Nil, "") - else (false, Nil, s"Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises φ∧ψ.") - else (false, Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + SCValidProof + else SCInvalidProof(Nil, s"Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises φ∧ψ.") + else SCInvalidProof(Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") /* * Γ |- φ, Δ Γ |- φ, ψ, Δ * -------------- or --------------- @@ -199,9 +200,9 @@ object SCProofChecker { if (isSameSet(b.right + phi, ref(t1).right + phiOrPsi) || isSameSet(b.right + psi, ref(t1).right + phiOrPsi) || isSameSet(b.right + phi + psi, ref(t1).right + phiOrPsi)) - (true, Nil, "") - else (false, Nil, "Right-hand side of conclusion + φ∧ψ must be same as right-hand side of premise + either φ, ψ or both.") - else (false, Nil, "Left-hand sides of the premise and the conclusion must be the same.") + SCValidProof + else SCInvalidProof(Nil, "Right-hand side of conclusion + φ∧ψ must be same as right-hand side of premise + either φ, ψ or both.") + else SCInvalidProof(Nil, "Left-hand sides of the premise and the conclusion must be the same.") /* * Γ, φ |- ψ, Δ * -------------- @@ -211,9 +212,9 @@ object SCProofChecker { val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) if (isSameSet(ref(t1).left, b.left + phi)) if (isSameSet(b.right + psi, ref(t1).right + phiImpPsi)) - (true, Nil, "") - else (false, Nil, "Right-hand side of conclusion + ψ must be same as right-hand side of premise + φ→ψ.") - else (false, Nil, "Left-hand side of conclusion + psi must be same as left-hand side of premise.") + SCValidProof + else SCInvalidProof(Nil, "Right-hand side of conclusion + ψ must be same as right-hand side of premise + φ→ψ.") + else SCInvalidProof(Nil, "Left-hand side of conclusion + psi must be same as left-hand side of premise.") /* * Γ |- a→ψ, Δ Σ |- ψ→φ, Π * ---------------------------- @@ -225,9 +226,9 @@ object SCProofChecker { val phiIffPsi = ConnectorFormula(Iff, Seq(phi, psi)) if (isSameSet(b.left, ref(t1).left union ref(t2).left)) if (isSameSet(b.right + phiImpPsi + psiImpPhi, ref(t1).right union ref(t2).right + phiIffPsi)) - (true, Nil, "") - else (false, Nil, s"Right-hand side of conclusion + a→ψ + ψ→φ is not the same as the union of the right-hand sides of the premises φ↔b.") - else (false, Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + SCValidProof + else SCInvalidProof(Nil, s"Right-hand side of conclusion + a→ψ + ψ→φ is not the same as the union of the right-hand sides of the premises φ↔b.") + else SCInvalidProof(Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") /* * Γ, φ |- Δ * -------------- @@ -237,9 +238,9 @@ object SCProofChecker { val nPhi = ConnectorFormula(Neg, Seq(phi)) if (isSameSet(b.right, ref(t1).right + nPhi)) if (isSameSet(b.left + phi, ref(t1).left)) - (true, Nil, "") - else (false, Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise") - else (false, Nil, "Right-hand side of conclusion must be the same as right-hand side of premise + ¬φ") + SCValidProof + else SCInvalidProof(Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise") + else SCInvalidProof(Nil, "Right-hand side of conclusion must be the same as right-hand side of premise + ¬φ") /* * Γ |- φ, Δ * ------------------- if x is not free in the resulting sequent @@ -249,10 +250,10 @@ object SCProofChecker { if (isSameSet(b.left, ref(t1).left)) if (isSameSet(b.right + phi, ref(t1).right + BinderFormula(Forall, x ,phi))) if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) - (true, Nil, "") - else (false, Nil, "The variable x must not be free in the resulting sequent.") - else (false, Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise + ∀x. φ") - else (false, Nil, "Left-hand sides of conclusion and premise must be the same.") + SCValidProof + else SCInvalidProof(Nil, "The variable x must not be free in the resulting sequent.") + else SCInvalidProof(Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise + ∀x. φ") + else SCInvalidProof(Nil, "Left-hand sides of conclusion and premise must be the same.") /* * Γ |- φ[t/x], Δ * ------------------- @@ -261,9 +262,9 @@ object SCProofChecker { case RightExists(b, t1, phi, x, t) => if (isSameSet(b.left, ref(t1).left)) if (isSameSet(b.right + substituteVariable(phi, x, t), ref(t1).right + BinderFormula(Exists, x ,phi))) - (true, Nil, "") - else (false, Nil, "Right-hand side of the conclusion + φ[t/x] must be the same as right-hand side of the premise + ∃x. φ") - else (false, Nil, "Left-hand sides or conclusion and premise must be the same.") + SCValidProof + else SCInvalidProof(Nil, "Right-hand side of the conclusion + φ[t/x] must be the same as right-hand side of the premise + ∃x. φ") + else SCInvalidProof(Nil, "Left-hand sides or conclusion and premise must be the same.") /** * <pre> @@ -277,9 +278,9 @@ object SCProofChecker { val temp = BinderFormula(Exists, y, BinderFormula(Forall, x, ConnectorFormula(Iff, List(PredicateFormula(equality, List(VariableTerm(x), VariableTerm(y))), phi)))) if (isSameSet(b.left, ref(t1).left)) if (isSameSet(b.right + temp, ref(t1).right + BinderFormula(ExistsOne, x, phi))) - (true, Nil, "") - else (false, Nil, "Right-hand side of conclusion + ∃y.∀x. (x=y) ↔ φ must be the same as right-hand side of premise + ∃!x. φ") - else (false, Nil, "Left-hand sides of conclusion and premise must be the same") + SCValidProof + else SCInvalidProof(Nil, "Right-hand side of conclusion + ∃y.∀x. (x=y) ↔ φ must be the same as right-hand side of premise + ∃!x. φ") + else SCInvalidProof(Nil, "Left-hand sides of conclusion and premise must be the same") // Structural rules @@ -291,9 +292,9 @@ object SCProofChecker { case Weakening(b, t1) => if (isSubset(ref(t1).left, b.left)) if (isSubset(ref(t1).right, b.right)) - (true, Nil, "") - else (false, Nil, "Right-hand side of premise must be a subset of right-hand side of conclusion") - else (false, Nil, "Left-hand side of premise must be a subset of left-hand side of conclusion") + SCValidProof + else SCInvalidProof(Nil, "Right-hand side of premise must be a subset of right-hand side of conclusion") + else SCInvalidProof(Nil, "Left-hand side of premise must be a subset of left-hand side of conclusion") // Equality Rules /* @@ -307,11 +308,11 @@ object SCProofChecker { if (isSame(left, right)) if (isSameSet(b.right, ref(t1).right)) if (isSameSet(b.left + phi, ref(t1).left)) - (true, Nil, "") - else (false, Nil, s"Left-hand sides of the conclusion + φ must be the same as left-hand side of the premise.") - else (false, Nil, s"Right-hand sides of the premise and the conclusion aren't the same.") - else (false, Nil, s"φ is not an instance of reflexivity.") - case _ => (false, Nil, "φ is not an equality") + SCValidProof + else SCInvalidProof(Nil, s"Left-hand sides of the conclusion + φ must be the same as left-hand side of the premise.") + else SCInvalidProof(Nil, s"Right-hand sides of the premise and the conclusion aren't the same.") + else SCInvalidProof(Nil, s"φ is not an instance of reflexivity.") + case _ => SCInvalidProof(Nil, "φ is not an equality") } /* @@ -324,10 +325,10 @@ object SCProofChecker { case PredicateFormula(`equality`, Seq(left, right)) => if (isSame(left, right)) if (contains(b.right, phi)) - (true, Nil, "") - else (false, Nil, s"Right-Hand side of conclusion does not contain φ") - else (false, Nil, s"φ is not an instance of reflexivity.") - case _ => (false, Nil, s"φ is not an equality.") + SCValidProof + else SCInvalidProof(Nil, s"Right-Hand side of conclusion does not contain φ") + else SCInvalidProof(Nil, s"φ is not an instance of reflexivity.") + case _ => SCInvalidProof(Nil, s"φ is not an equality.") } /* @@ -343,10 +344,10 @@ object SCProofChecker { if (isSameSet(b.right, ref(t1).right)) if (isSameSet(b.left + phi_t_for_f, ref(t1).left + sEqT + phi_s_for_f) || isSameSet(b.left + phi_s_for_f, ref(t1).left + sEqT + phi_t_for_f)) - (true, Nil, "") - else (false, Nil, "Left-hand sides of the conclusion + φ[s/?f] must be the same as left-hand side of the premise + s=t + φ[t/?f] (or with s and t swapped).") - else (false, Nil, "Right-hand sides of the premise and the conclusion aren't the same.") - else (false, Nil, "Function schema ?f must have arity 0") + SCValidProof + else SCInvalidProof(Nil, "Left-hand sides of the conclusion + φ[s/?f] must be the same as left-hand side of the premise + s=t + φ[t/?f] (or with s and t swapped).") + else SCInvalidProof(Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + else SCInvalidProof(Nil, "Function schema ?f must have arity 0") /* @@ -362,10 +363,10 @@ object SCProofChecker { val phi_t_for_f = instantiateFunctionSchema(phi, f, t, Nil) if (isSameSet(b.right + phi_s_for_f, ref(t1).right + phi_t_for_f) || isSameSet(b.right + phi_t_for_f, ref(t1).right + phi_s_for_f)) - (true, Nil, "") - else (false, Nil, s"Right-hand side of the premise and the conclusion should be the same with each containing one of φ[s/?f] φ[t/?f], but it isn't the case." ) - else (false, Nil, "Left-hand sides of the premise + s=t must be the same as left-hand side of the premise.") - else (false, Nil, "Function schema ?f must have arity 0.") + SCValidProof + else SCInvalidProof(Nil, s"Right-hand side of the premise and the conclusion should be the same with each containing one of φ[s/?f] φ[t/?f], but it isn't the case." ) + else SCInvalidProof(Nil, "Left-hand sides of the premise + s=t must be the same as left-hand side of the premise.") + else SCInvalidProof(Nil, "Function schema ?f must have arity 0.") /* * Γ, φ[ψ/?q] |- Δ * --------------------- @@ -379,10 +380,10 @@ object SCProofChecker { if (isSameSet(b.right, ref(t1).right)) if (isSameSet(ref(t1).left + psiIffTau + phi_tau_for_q, b.left + phi_psi_for_q) || isSameSet(ref(t1).left + psiIffTau + phi_psi_for_q, b.left + phi_tau_for_q)) - (true, Nil, "") - else (false, Nil, "Left-hand sides of the conclusion + φ[ψ/?q] must be the same as left-hand side of the premise + ψ↔τ + φ[τ/?q] (or with ψ and τ swapped).") - else (false, Nil, "Right-hand sides of the premise and the conclusion aren't the same.") - else (false, Nil, "Predicate schema ?q must have arity 0.") + SCValidProof + else SCInvalidProof(Nil, "Left-hand sides of the conclusion + φ[ψ/?q] must be the same as left-hand side of the premise + ψ↔τ + φ[τ/?q] (or with ψ and τ swapped).") + else SCInvalidProof(Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + else SCInvalidProof(Nil, "Predicate schema ?q must have arity 0.") /* * Γ |- φ[ψ/?p], Δ @@ -397,10 +398,10 @@ object SCProofChecker { if (isSameSet(ref(t1).left + psiIffTau, b.left)) if (isSameSet(b.right + phi_tau_for_q, ref(t1).right + phi_psi_for_q) || isSameSet(b.right + phi_psi_for_q, ref(t1).right + phi_tau_for_q)) - (true, Nil, "") - else (false, Nil, s"Right-hand side of the premise and the conclusion should be the same with each containing one of φ[τ/?q] and φ[ψ/?q], but it isn't the case." ) - else (false, Nil, "Left-hand sides of the premise + ψ↔τ must be the same as left-hand side of the premise.") - else (false, Nil, "Predicate schema ?q must have arity 0.") + SCValidProof + else SCInvalidProof(Nil, s"Right-hand side of the premise and the conclusion should be the same with each containing one of φ[τ/?q] and φ[ψ/?q], but it isn't the case." ) + else SCInvalidProof(Nil, "Left-hand sides of the premise + ψ↔τ must be the same as left-hand side of the premise.") + else SCInvalidProof(Nil, "Predicate schema ?q must have arity 0.") /** * <pre> * Γ |- Δ @@ -412,9 +413,9 @@ object SCProofChecker { val expected = (ref(t1).left.map(phi => instantiateFunctionSchema(phi, f, r, a)), ref(t1).right.map(phi => instantiateFunctionSchema(phi, f, r, a))) if (isSameSet(bot.left, expected._1)) if (isSameSet(bot.right, expected._2)) - (true, Nil, "") - else (false, Nil, "Right-hand side of premise instantiated with [?f/r(a)] must be the same as right-hand side of conclusion.") - else (false, Nil, "Left-hand side of premise instantiated with [?f/r(a)] must be the same as left-hand side of conclusion.") + SCValidProof + else SCInvalidProof(Nil, "Right-hand side of premise instantiated with [?f/r(a)] must be the same as right-hand side of conclusion.") + else SCInvalidProof(Nil, "Left-hand side of premise instantiated with [?f/r(a)] must be the same as left-hand side of conclusion.") /** * <pre> @@ -427,45 +428,38 @@ object SCProofChecker { val expected = (ref(t1).left.map(phi => instantiatePredicateSchema(phi, p, psi, a)), ref(t1).right.map(phi => instantiatePredicateSchema(phi, p, psi, a))) if (isSameSet(bot.left, expected._1)) if (isSameSet(bot.right, expected._2)) - (true, Nil, "") - else (false, Nil, "Right-hand side of premise instantiated with [?p/ψ(a)] must be the same as right-hand side of conclusion.") - else (false, Nil, "Left-hand side of premise instantiated with [?p/ψ(a)] must be the same as left-hand side of conclusion.") + SCValidProof + else SCInvalidProof(Nil, "Right-hand side of premise instantiated with [?p/ψ(a)] must be the same as right-hand side of conclusion.") + else SCInvalidProof(Nil, "Left-hand side of premise instantiated with [?p/ψ(a)] must be the same as left-hand side of conclusion.") case SCSubproof(sp, premises, _) => if (premises.size == sp.imports.size){ val invalid = premises.zipWithIndex.find((no, p) => !isSameSequent(ref(no), sp.imports(p)) ) if (invalid.isEmpty){ - val r_subproof = checkSCProof(sp) - if (r_subproof._1) - (true, Nil, "") - else (false, r_subproof._2, r_subproof._3) - } else (false, Nil, s"Premise number ${invalid.get._1} (refering to step ${invalid.get}) is not the same as import number ${invalid.get._1} of the subproof.") - } else (false, Nil, "Number of premises and imports don't match: "+premises.size+" "+sp.imports.size) + checkSCProof(sp) + } else SCInvalidProof(Nil, s"Premise number ${invalid.get._1} (refering to step ${invalid.get}) is not the same as import number ${invalid.get._1} of the subproof.") + } else SCInvalidProof(Nil, "Number of premises and imports don't match: "+premises.size+" "+sp.imports.size) } r } /** - * Verifies if a given pure SequentCalculus is conditionally correct, as the imported sequents are assumed. + * Verifies if a given pure SequentCalculus is conditionally correct, as the imported sequents are assumed. * If the proof is not correct, the functrion will report the faulty line and a brief explanation. * @param proof A SC proof to check - * @return (true, Nil, "") if the proof is correct, else (false, l, s) with l the path to the incorrect - proof step and s an explanation. - * + * @return SCValidProof if the proof is correct, else SCInvalidProof with the path to the incorrect proof step + * and an explanation. */ - def checkSCProof(proof: SCProof) : (Boolean, List[Int], String) = { - - + def checkSCProof(proof: SCProof): SCProofCheckerJudgement = { val possibleError = proof.steps.view.zipWithIndex.map((step, no) => - - val r = checkSingleSCStep(no, step, (i:Int) => proof.getSequent(i), Some(proof.imports.size)) - (r._1, no::r._2, r._3) - ).find(p => !p._1 ) - if (possibleError.isEmpty) (true, Nil, "") + checkSingleSCStep(no, step, (i: Int) => proof.getSequent(i), Some(proof.imports.size)) match { + case SCInvalidProof(path, message) => SCInvalidProof(no +: path, message) + case SCValidProof => SCValidProof + } + ).find(j => !j.isValid) + if (possibleError.isEmpty) SCValidProof else possibleError.get - - } } diff --git a/src/main/scala/lisa/kernel/proof/SCProofCheckerJudgement.scala b/src/main/scala/lisa/kernel/proof/SCProofCheckerJudgement.scala new file mode 100644 index 00000000..bf81d106 --- /dev/null +++ b/src/main/scala/lisa/kernel/proof/SCProofCheckerJudgement.scala @@ -0,0 +1,33 @@ +package lisa.kernel.proof + +/** + * The judgement (or verdict) of a proof checking procedure. + * Typically, see [[SCProofChecker.checkSingleSCStep]] and [[SCProofChecker.checkSCProof]]. + */ +sealed abstract class SCProofCheckerJudgement { + import SCProofCheckerJudgement.* + + /** + * Whether this judgement is positive -- the proof is concluded to be valid; + * or negative -- the proof checker couldn't certify the validity of this proof. + * @return An instance of either [[SCValidProof]] or [[SCInvalidProof]] + */ + def isValid: Boolean = this match { + case SCValidProof => true + case _: SCInvalidProof => false + } +} + +object SCProofCheckerJudgement { + /** + * A positive judgement. + */ + case object SCValidProof extends SCProofCheckerJudgement + + /** + * A negative judgement. + * @param path The path of the error, expressed as indices + * @param message The error message that hints about the first error encountered + */ + case class SCInvalidProof(path: Seq[Int], message: String) extends SCProofCheckerJudgement +} diff --git a/src/test/scala/lisa/kernel/ProofTests.scala b/src/test/scala/lisa/kernel/ProofTests.scala index d2a91582..8389948f 100644 --- a/src/test/scala/lisa/kernel/ProofTests.scala +++ b/src/test/scala/lisa/kernel/ProofTests.scala @@ -31,14 +31,14 @@ class ProofTests extends AnyFunSuite { val s3 = LeftImplies((a ==> b) ==> a |- a, 2, 0, a ==> b, a) val s4 = RightImplies(() |- (a ==> b) ==> a ==> a, 3, (a ==> b) ==> a, a) val ppl: SCProof = SCProof(IndexedSeq(s0, s1, s2, s3, s4)) - assert(predicateVerifier(ppl)._1) + assert(predicateVerifier(ppl).isValid) } test("Verification of substitution") { val t0 = Hypothesis(fp(x)|-fp(x), fp(x)) val t1 = RightSubstEq(Set(fp(x), x === y) |- fp(y), 0, x, y, fp(sT()), sT) val pr = new SCProof(IndexedSeq(t0, t1)) - assert(predicateVerifier(pr)._1) + assert(predicateVerifier(pr).isValid) } test("Commutativity on a random large formula") { @@ -58,6 +58,6 @@ class ProofTests extends AnyFunSuite { val orig = subformulas.next().head val swapped = subformulasSwapped.next().head val prf = SCProof(Vector(Hypothesis(Sequent(Set(orig), Set(orig)), orig), Rewrite(Sequent(Set(orig), Set(swapped)), 0))) - assert(predicateVerifier(prf)._1) + assert(predicateVerifier(prf).isValid) } } diff --git a/src/test/scala/lisa/proven/SimpleProverTests.scala b/src/test/scala/lisa/proven/SimpleProverTests.scala index 8f88cc95..5eb2269e 100644 --- a/src/test/scala/lisa/proven/SimpleProverTests.scala +++ b/src/test/scala/lisa/proven/SimpleProverTests.scala @@ -13,7 +13,7 @@ import proven.tactics.SimplePropositionalSolver as SPS class SimpleProverTests extends AnyFunSuite { - + test("Simple propositional logic proofs") { val problems = getPRPproblems.take(1) @@ -24,7 +24,7 @@ class SimpleProverTests extends AnyFunSuite { val proof = SPS.solveSequent(sq) if (!Seq("Unsatisfiable", "Theorem", "Satisfiable").contains(pr.status)) println("Unknown status: "+pr.status+", "+pr.file) - assert(SCProofChecker.checkSCProof(proof)._1 == (pr.status =="Unsatisfiable" || pr.status == "Theorem")) + assert(SCProofChecker.checkSCProof(proof).isValid == (pr.status =="Unsatisfiable" || pr.status == "Theorem")) }) diff --git a/src/test/scala/test/ProofCheckerSuite.scala b/src/test/scala/test/ProofCheckerSuite.scala index df8786aa..57872adb 100644 --- a/src/test/scala/test/ProofCheckerSuite.scala +++ b/src/test/scala/test/ProofCheckerSuite.scala @@ -26,12 +26,12 @@ abstract class ProofCheckerSuite extends AnyFunSuite { } def checkProof(proof: SCProof, expected: Sequent): Unit = { - val error = checkSCProof(proof) - assert(error._1, "\n"+Printer.prettySCProof(proof, error)) + val judgement = checkSCProof(proof) + assert(judgement.isValid, "\n"+Printer.prettySCProof(proof, judgement)) assert(isSameSequent(proof.conclusion, expected), s"(${Printer.prettySequent(proof.conclusion)} did not equal ${Printer.prettySequent(expected)})") } def checkIncorrectProof(incorrectProof: SCProof): Unit = { - assert(!checkSCProof(incorrectProof)._1, s"(incorrect proof with conclusion '${Printer.prettySequent(incorrectProof.conclusion)}' was accepted by the proof checker)\nSequent: ${incorrectProof.conclusion}") + assert(!checkSCProof(incorrectProof).isValid, s"(incorrect proof with conclusion '${Printer.prettySequent(incorrectProof.conclusion)}' was accepted by the proof checker)\nSequent: ${incorrectProof.conclusion}") } } -- GitLab