diff --git a/lisa-utils/src/main/scala/lisa/prooflib/BasicMain.scala b/backup/backup2/prooflib/BasicMain.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/BasicMain.scala rename to backup/backup2/prooflib/BasicMain.scala diff --git a/backup/backup2/prooflib/BasicStepTactic.scala b/backup/backup2/prooflib/BasicStepTactic.scala new file mode 100644 index 00000000..fdda8257 --- /dev/null +++ b/backup/backup2/prooflib/BasicStepTactic.scala @@ -0,0 +1,1481 @@ +package lisa.prooflib +import lisa.fol.FOL as F +import lisa.prooflib.ProofTacticLib.{_, given} +import lisa.prooflib.* +import lisa.utils.K +import lisa.utils.KernelHelpers.{|- => `K|-`, _} +import lisa.utils.UserLisaException +import lisa.utils.unification.UnificationUtils + +object BasicStepTactic { +/* + def unwrapTactic(using lib: Library, proof: lib.Proof)(using tactic: ProofTactic)(judgement: proof.ProofTacticJudgement)(message: String): proof.ProofTacticJudgement = { + judgement match { + case j: proof.ValidProofTactic => proof.ValidProofTactic(j.bot, j.scps, j.imports) + case j: proof.InvalidProofTactic => proof.InvalidProofTactic(s"Internal tactic call failed! $message\n${j.message}") + } + } + + object Hypothesis extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + val intersectedPivot = botK.left.intersect(botK.right) + + if (intersectedPivot.isEmpty) + proof.InvalidProofTactic("A formula for input to Hypothesis could not be inferred from left and right side of the sequent.") + else + proof.ValidProofTactic(bot, Seq(K.Hypothesis(botK, intersectedPivot.head)), Seq()) + } + } + + object Rewrite extends ProofTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + if (!K.isSameSequent(botK, proof.getSequent(premise).underlying)) + proof.InvalidProofTactic("The premise and the conclusion are not trivially equivalent.") + else + proof.ValidProofTactic(bot, Seq(K.Restate(botK, -1)), Seq(premise)) + } + } + + object RewriteTrue extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + if (!K.isSameSequent(botK, () `K|-` K.top)) + proof.InvalidProofTactic("The desired conclusion is not a trivial tautology.") + else + proof.ValidProofTactic(bot, Seq(K.RestateTrue(botK)), Seq()) + } + } + + /** + *
+   *  Γ |- Δ, φ    φ, Σ |- Π
+   * ------------------------
+   *       Γ, Σ |- Δ, Π
+   * 
+ */ + object Cut extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + val botK = bot.underlying + val phiK = phi.underlying + + if (!K.contains(leftSequent.right, phiK)) + proof.InvalidProofTactic("Right-hand side of first premise does not contain φ as claimed.") + else if (!K.contains(rightSequent.left, phiK)) + proof.InvalidProofTactic("Left-hand side of second premise does not contain φ as claimed.") + else if (!K.isSameSet(botK.left + phiK, leftSequent.left ++ rightSequent.left) || (leftSequent.left.contains(phiK) && !botK.left.contains(phiK))) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the union of the left-hand sides of the premises.") + else if (!K.isSameSet(botK.right + phiK, leftSequent.right ++ rightSequent.right) || (rightSequent.right.contains(phiK) && !botK.right.contains(phiK))) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the union of the right-hand sides of the premises.") + else + proof.ValidProofTactic(bot, Seq(K.Cut(botK, -1, -2, phiK)), Seq(prem1, prem2)) + } + + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1) + lazy val rightSequent = proof.getSequent(prem2) + + lazy val cutSet = (((rightSequent --? bot).right |- ())).left + lazy val intersectedCutSet = rightSequent.left intersect leftSequent.right + + if (!cutSet.isEmpty) + if (cutSet.tail.isEmpty) + Cut.withParameters(cutSet.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("Inferred cut pivot is not a singleton set.") + else if (!intersectedCutSet.isEmpty && intersectedCutSet.tail.isEmpty) + // can still find a pivot + Cut.withParameters(intersectedCutSet.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("A consistent cut pivot cannot be inferred from the premises. Possibly a missing or extraneous clause.") + } + } + + // Left rules + /** + *
+   *   Γ, φ |- Δ                Γ, φ, ψ |- Δ
+   * --------------     or     --------------
+   *  Γ, φ∧ψ |- Δ               Γ, φ∧ψ |- Δ
+   * 
+ */ + object LeftAnd extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + lazy val phiAndPsi = phiK /\ psiK + + if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of the conclusion is not the same as the right-hand side of the premise.") + else if ( + !K.isSameSet(botK.left + phiK, premiseSequent.left + phiAndPsi) && + !K.isSameSet(botK.left + psiK, premiseSequent.left + phiAndPsi) && + !K.isSameSet(botK.left + phiK + psiK, premiseSequent.left + phiAndPsi) + ) + proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") + else + proof.ValidProofTactic(bot, Seq(K.LeftAnd(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + pivot.head match { + case F.App(F.App(F.and, phi), psi) => + if (premiseSequent.left.contains(phi)) + LeftAnd.withParameters(phi, psi)(premise)(bot) + else + LeftAnd.withParameters(phi, psi)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a conjunction as pivot from premise and conclusion.") + } + else + // try a rewrite, if it works, go ahead with it, otherwise malformed + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial LeftAnd failed.") + else + proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") + } + } + + /** + *
+   *  Γ, φ |- Δ    Σ, ψ |- Π    ...
+   * --------------------------------
+   *    Γ, Σ, φ∨ψ∨... |- Δ, Π
+   * 
+ */ + object LeftOr extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(disjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) + val botK = bot.underlying + val disjunctsK = disjuncts.map(_.underlying) + lazy val disjunction = K.multior(disjunctsK) + + if (premises.length == 0) + proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (premises.length != disjuncts.length) + proof.InvalidProofTactic(s"Premises and disjuncts expected to be equal in number, but ${premises.length} premises and ${disjuncts.length} disjuncts received.") + else if (!K.isSameSet(botK.right, premiseSequents.map(_.right).reduce(_ union _))) + proof.InvalidProofTactic("Right-hand side of conclusion is not the union of the right-hand sides of the premises.") + else if ( + premiseSequents.zip(disjunctsK).forall((sequent, disjunct) => K.isSubset(sequent.left, botK.left + disjunct)) // \forall i. premise_i.left \subset bot.left + phi_i + && !K.isSubset(botK.left, premiseSequents.map(_.left).reduce(_ union _) + disjunction) // bot.left \subseteq \bigcup premise_i.left + ) + proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftOr(botK, Range(-1, -premises.length - 1, -1), disjunctsK)), premises.toSeq) + } + + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_)) + lazy val pivots = premiseSequents.map(_.left.diff(bot.left)) + + if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (pivots.exists(_.isEmpty)) { + val emptyIndex = pivots.indexWhere(_.isEmpty) + if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) + unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for LeftOr failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the one of the premises.") + } else if (pivots.forall(_.tail.isEmpty)) + LeftOr.withParameters(pivots.map(_.head)*)(premises*)(bot) + else + // some extraneous formulae + proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") + } + } + + /** + *
+   *  Γ |- φ, Δ    Σ, ψ |- Π
+   * ------------------------
+   *    Γ, Σ, φ⇒ψ |- Δ, Π
+   * 
+ */ + object LeftImplies extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + lazy val implication = (phiK ==> psiK) + + if (!K.isSameSet(botK.right + phiK, leftSequent.right union rightSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the union of right-hand sides of premises.") + else if (!K.isSameSet(botK.left + psiK, leftSequent.left union rightSequent.left + implication)) + proof.InvalidProofTactic("Left-hand side of conclusion + ψ is not the union of left-hand sides of premises + φ⇒ψ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftImplies(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) + } + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1) + lazy val rightSequent = proof.getSequent(prem2) + lazy val pivotLeft = leftSequent.right.diff(bot.right) + lazy val pivotRight = rightSequent.left.diff(bot.left) + + if (pivotLeft.isEmpty) + if (F.isSubset(leftSequent.left, bot.left)) + unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial left premise for LeftImplies failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the first premises.") + else if (pivotRight.isEmpty) + if (F.isSubset(rightSequent.right, bot.right)) + unwrapTactic(Weakening(prem2)(bot))("Attempted weakening on trivial right premise for LeftImplies failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the second premises.") + else if (pivotLeft.tail.isEmpty && pivotRight.tail.isEmpty) + LeftImplies.withParameters(pivotLeft.head, pivotRight.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("Could not infer an implication as a pivot from the premises and conclusion, possible extraneous formulae in premises.") + } + } + + /** + *
+   *  Γ, φ⇒ψ |- Δ               Γ, φ⇒ψ, ψ⇒φ |- Δ
+   * --------------    or     --------------------
+   *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
+   * 
+ */ + object LeftIff extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + lazy val implication = phiK <=> psiK + lazy val impLeft = phiK ==> psiK + lazy val impRight = psiK ==> phiK + + if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of premise is not the same as right-hand side of conclusion.") + else if ( + !K.isSameSet(botK.left + impLeft, premiseSequent.left + implication) && + !K.isSameSet(botK.left + impRight, premiseSequent.left + implication) && + !K.isSameSet(botK.left + impLeft + impRight, premiseSequent.left + implication) + ) + proof.InvalidProofTactic("Left-hand side of premise + φ⇔ψ is not the same as left-hand side of conclusion + either φ⇒ψ, ψ⇒φ or both.") + else + proof.ValidProofTactic(bot, Seq(K.LeftIff(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftIff failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else + pivot.head match { + case F.App(F.App(F.implies, phi), psi) => LeftIff.withParameters(phi, psi)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a pivot implication from premise.") + } + } + } + + /** + *
+   *   Γ |- φ, Δ
+   * --------------
+   *   Γ, ¬φ |- Δ
+   * 
+ */ + object LeftNot extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + lazy val negation = !phiK + + if (!K.isSameSet(botK.right + phiK, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise.") + else if (!K.isSameSet(botK.left, premiseSequent.left + negation)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise + ¬φ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftNot(botK, -1, phiK)), Seq(premise)) + } + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftNot failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (!pivot.isEmpty && pivot.tail.isEmpty) + LeftNot.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise.") + + } + } + + /** + *
+   *   Γ, φ[t/x] |- Δ
+   * -------------------
+   *   Γ, ∀x. φ |- Δ
+   *
+   * 
+ */ + object LeftForall extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T], t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val tK = t.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val quantified = K.forall(xK, phiK) + lazy val instantiated = K.substituteVariables(phiK, Map(xK -> tK)) + + if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise") + else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ") + else + proof.ValidProofTactic(bot, Seq(K.LeftForall(botK, -1, phiK, xK, tK)), Seq(premise)) + } + + def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left // .diff(botK.left) + + if (!pivot.isEmpty) + if (pivot.tail.isEmpty) + pivot.head match { + case F.forall(x, phi) => LeftForall.withParameters(phi, x, t)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a universally quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") + else if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.left.find(f => + f match { + case g @ F.forall(v, e) => F.isSame(e.substitute(v := t), in) + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.forall(x, phi)) => LeftForall.withParameters(phi, x, t)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") + } + } else proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val prepivot = bot.left.diff(premiseSequent.left) + lazy val pivot = if (prepivot.isEmpty) bot.left else prepivot + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = pivot.find(f => + f match { + case g @ F.forall(x, phi) => UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.forall(x, phi)) => + LeftForall.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).get._2.getOrElse(x, x)*/)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") + } + } else proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") + } + } + + /** + *
+   *    Γ, φ |- Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ, ∃x φ|- Δ
+   *
+   * 
+ */ + object LeftExists extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val quantified = K.exists(xK, phiK) + + if ((botK.left union botK.right).exists(_.freeVariables.contains(xK))) + proof.InvalidProofTactic("The variable x must not be free in the resulting sequent.") + else if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise") + else if (!K.isSameSet(botK.left + phiK, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ") + else + proof.ValidProofTactic(bot, Seq(K.LeftExists(botK, -1, phiK, xK)), Seq(premise)) + } + + var debug = false + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExists failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.left.find(f => + f match { + case F.exists(_, g) => F.isSame(g, in) + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.exists(x, phi)) => LeftExists.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existensially quantified pivot from premise and conclusion.") + } + } else proof.InvalidProofTactic("Ambigous application of LeftExists, multiple pivots corresponding to the unquantified formula found.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.exists(x, phi) => LeftExists.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Ambigous application of LeftExists, multiple pivots corresponding to the quantified formula found.") + } + } + + /* + /** + *
+   *  Γ, ∃y.∀x. (x=y) ⇔ φ |-  Δ
+   * ---------------------------- if y is not free in φ
+   *      Γ, ∃!x. φ |- Δ
+   * 
+ */ + object LeftExistsOne extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val y = K.Variable(lisa.utils.KernelHelpers.freshId(phiK.freeVariables.map(_.id), x.id), K.Term) + lazy val instantiated = K.exists(y, K.forall(xK, (xK === y) <=> phiK )) + lazy val quantified = K.ExistsOne(xK, phiK) + + if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise.") + else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as left-hand side of premise + ∃!x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftExistsOne(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExistsOne failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + instantiatedPivot.head match { + // ∃_. ∀x. _ ⇔ φ == extract ==> x, phi + case F.exists(_, F.forall(x, F.AppliedConnector(F.Iff, Seq(_, phi)))) => LeftExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + } else + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.BinderFormula(F.ExistsOne, x, phi) => LeftExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") + } + } + + */ + + // Right rules + /** + *
+   *  Γ |- φ, Δ    Σ |- ψ, Π     ...
+   * ------------------------------------
+   *    Γ, Σ |- φ∧ψ∧..., Π, Δ
+   * 
+ */ + object RightAnd extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(conjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) + lazy val botK = bot.underlying + lazy val conjunctsK = conjuncts.map(_.underlying) + lazy val conjunction = K.multiand(conjunctsK) + + if (premises.length == 0) + proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (premises.length != conjuncts.length) + proof.InvalidProofTactic(s"Premises and conjuncts expected to be equal in number, but ${premises.length} premises and ${conjuncts.length} conjuncts received.") + else if (!K.isSameSet(botK.left, premiseSequents.map(_.left).reduce(_ union _))) + proof.InvalidProofTactic("Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + else if ( + premiseSequents.zip(conjunctsK).forall((sequent, conjunct) => K.isSubset(sequent.right, botK.right + conjunct)) // \forall i. premise_i.right \subset bot.right + phi_i + && !K.isSubset(botK.right, premiseSequents.map(_.right).reduce(_ union _) + conjunction) // bot.right \subseteq \bigcup premise_i.right + ) + proof.InvalidProofTactic("Right-hand side of conclusion + conjuncts is not the same as the union of the right-hand sides of the premises + φ∧ψ....") + else + proof.ValidProofTactic(bot, Seq(K.RightAnd(botK, Range(-1, -premises.length - 1, -1), conjunctsK)), premises) + } + + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_)) + lazy val pivots = premiseSequents.map(_.right.diff(bot.right)) + + if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (pivots.exists(_.isEmpty)) { + val emptyIndex = pivots.indexWhere(_.isEmpty) + if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) + unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for RightAnd failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the one of the premises.") + } else if (pivots.forall(_.tail.isEmpty)) + RightAnd.withParameters(pivots.map(_.head)*)(premises*)(bot) + else + // some extraneous formulae + proof.InvalidProofTactic("Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises +φ∧ψ.") + } + } + + /** + *
+   *   Γ |- φ, Δ               Γ |- φ, ψ, Δ
+   * --------------    or    ---------------
+   *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
+   * 
+ */ + object RightOr extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + lazy val phiAndPsi = phiK \/ psiK + + if (!K.isSameSet(botK.left, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of the premise is not the same as the left-hand side of the conclusion.") + else if ( + !K.isSameSet(botK.right + phiK, premiseSequent.right + phiAndPsi) && + !K.isSameSet(botK.right + psiK, premiseSequent.right + phiAndPsi) && + !K.isSameSet(botK.right + phiK + psiK, premiseSequent.right + phiAndPsi) + ) + proof.InvalidProofTactic("Right-hand side of premise + φ∧ψ is not the same as right-hand side of conclusion + either φ, ψ or both.") + else + proof.ValidProofTactic(bot, Seq(K.RightOr(botK, -1, phiK, psiK)), Seq(premise)) + } + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + pivot.head match { + case F.App(F.App(F.or, phi), psi) => + if (premiseSequent.left.contains(phi)) + RightOr.withParameters(phi, psi)(premise)(bot) + else + RightOr.withParameters(psi, phi)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a disjunction as pivot from premise and conclusion.") + } + else + // try a rewrite, if it works, go ahead with it, otherwise malformed + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightOr failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ∧ψ is not the same as right-hand side of premise + either φ, ψ or both.") + } + } + + /** + *
+   *  Γ, φ |- ψ, Δ
+   * --------------
+   *  Γ |- φ⇒ψ, Δ
+   * 
+ */ + object RightImplies extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + lazy val implication = phiK ==> psiK + + if (!K.isSameSet(botK.left + phiK, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") + else if (!K.isSameSet(botK.right + psiK, premiseSequent.right + implication)) + proof.InvalidProofTactic("Right-hand side of conclusion + ψ is not the same as right-hand side of premise + φ⇒ψ.") + else + proof.ValidProofTactic(bot, Seq(K.RightImplies(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val leftPivot = premiseSequent.left.diff(bot.left) + lazy val rightPivot = premiseSequent.right.diff(bot.right) + + if ( + !leftPivot.isEmpty && leftPivot.tail.isEmpty && + !rightPivot.isEmpty && rightPivot.tail.isEmpty + ) + RightImplies.withParameters(leftPivot.head, rightPivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Could not infer an implication as pivot from premise and conclusion.") + } + } + + /** + *
+   *  Γ |- φ⇒ψ, Δ    Σ |- ψ⇒φ, Π
+   * ----------------------------
+   *      Γ, Σ |- φ⇔ψ, Π, Δ
+   * 
+ */ + object RightIff extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + lazy val implication = phiK <=> psiK + lazy val impLeft = phiK ==> psiK + lazy val impRight = psiK ==> phiK + + if (!K.isSameSet(botK.left, leftSequent.left union rightSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + else if (!K.isSubset(leftSequent.right, botK.right + impLeft)) + proof.InvalidProofTactic( + "Conclusion is missing the following formulas from the left premise: " + (leftSequent.right -- botK.right).map(f => s"[${f.repr}]").reduce(_ ++ ", " ++ _) + ) + else if (!K.isSubset(rightSequent.right, botK.right + impRight)) + proof.InvalidProofTactic( + "Conclusion is missing the following formulas from the right premise: " + (rightSequent.right -- botK.right).map(f => s"[${f.repr}]").reduce(_ ++ ", " ++ _) + ) + else if (!K.isSubset(botK.right, leftSequent.right union rightSequent.right + implication)) + proof.InvalidProofTactic( + "Conclusion has extraneous formulas apart from premises and implication: " ++ (botK.right + .removedAll(leftSequent.right union rightSequent.right + implication)) + .map(f => s"[${f.repr}]") + .reduce(_ ++ ", " ++ _) + ) + else + proof.ValidProofTactic(bot, Seq(K.RightIff(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) + } + + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(prem1) + lazy val pivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial premise for RightIff failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.App(F.App(F.implies, phi), psi) => RightIff.withParameters(phi, psi)(prem1, prem2)(bot) + case _ => proof.InvalidProofTactic("Could not infer an implication as pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ⇒ψ + ψ⇒φ is not the same as the union of the right-hand sides of the premises φ⇔ψ.") + } + } + + /** + *
+   *  Γ, φ |- Δ
+   * --------------
+   *   Γ |- ¬φ, Δ
+   * 
+ */ + object RightNot extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val negation = !phiK + + if (!K.isSameSet(botK.left + phiK, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") + else if (!K.isSameSet(botK.right, premiseSequent.right + negation)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise + ¬φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightNot(botK, -1, phiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightIff failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (pivot.tail.isEmpty) + RightNot.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") + + } + } + + /** + *
+   *    Γ |- φ, Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ |- ∀x. φ, Δ
+   * 
+ */ + object RightForall extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val quantified = K.forall(xK, phiK) + + if ((botK.left union botK.right).exists(_.freeVariables.contains(xK))) + proof.InvalidProofTactic("The variable x is free in the resulting sequent.") + else if (!K.isSameSet(botK.left, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise.") + else if (!K.isSameSet(botK.right + phiK, premiseSequent.right + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∀x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightForall(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightForall failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from the premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.right.find(f => + f match { + case F.forall(_, g) => F.isSame(g, in) + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.forall(x, phi)) => RightForall.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a universally quantified pivot from premise and conclusion.") + } + } else proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∃x. φ.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.forall(x, phi) => RightForall.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a universally quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∃x. φ.") + } + } + + /** + *
+   *   Γ |- φ[t/x], Δ
+   * -------------------
+   *  Γ |- ∃x. φ, Δ
+   *
+   * (ln-x stands for locally nameless x)
+   * 
+ */ + object RightExists extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T], t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val tK = t.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val quantified = K.exists(xK, phiK) + lazy val instantiated = K.substituteVariables(phiK, Map(xK -> tK)) + + if (!K.isSameSet(botK.left, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise") + else if (!K.isSameSet(botK.right + instantiated, premiseSequent.right + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ") + else + proof.ValidProofTactic(bot, Seq(K.RightExists(botK, -1, phiK, xK, tK)), Seq(premise)) + } + + def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (!pivot.isEmpty) + if (pivot.tail.isEmpty) + pivot.head match { + case F.exists(x, phi) => RightExists.withParameters(phi, x, t)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") + else if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightExists failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.right.find(f => + f match { + case g @ F.exists(v, e) => F.isSame(e.substitute(v := t), in) + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.exists(x, phi)) => RightExists.withParameters(phi, x, t)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") + } + } else proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val prepivot = bot.right.diff(premiseSequent.right) + lazy val pivot = if (prepivot.isEmpty) bot.right else prepivot + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightForall failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + + val quantifiedPhi: Option[F.Formula] = pivot.find(f => + f match { + case g @ F.exists(x, phi) => + UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.exists(x, phi)) => + RightExists.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).get._2.getOrElse(x, x)*/)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") + } + } else proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") + } + } + + /* + /** + *
+   *  Γ |- ∃y.∀x. (x=y) ⇔ φ, Δ
+   * ---------------------------- if y is not free in φ
+   *      Γ|- ∃!x. φ,  Δ
+   * 
+ */ + object RightExistsOne extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val y = K.Variable(lisa.utils.KernelHelpers.freshId(phiK.freeVariables.map(_.id), x.id)) + lazy val instantiated = K.BinderFormula( + K.Exists, + y, + K.BinderFormula( + K.Forall, + xK, + K.ConnectorFormula(K.Iff, List(K.AtomicFormula(K.equality, List(K.VariableTerm(xK), K.VariableTerm(y))), phiK)) + ) + ) + lazy val quantified = K.BinderFormula(K.ExistsOne, xK, phiK) + + if (!K.isSameSet(botK.left, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise.") + else if (!K.isSameSet(botK.right + instantiated, premiseSequent.right + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as right-hand side of premise + ∃!x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightExistsOne(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightExistsOne failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + instantiatedPivot.head match { + // ∃_. ∀x. _ ⇔ φ == extract ==> x, phi + case F.exists(_, F.forall(x, F.AppliedConnector(F.Iff, Seq(_, phi)))) => + RightExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + } else + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∃x. φ.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.BinderFormula(F.ExistsOne, x, phi) => RightExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∃x. φ.") + } + } + + */ + + object RightBeta extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof) + (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val lambdaK = lambda.underlying + lazy val tK = t.underlying + lazy val xK = x.underlying + lazy val botK = bot.underlying + lazy val y = lambda.v.underlying + lazy val e = lambda.body.underlying + if (phi.sort != K.Formula) + return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) + else if (e.sort != x.sort) + return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) + else if (K.isSameSet(botK.left, premiseSequent.left)) { + val redex = lambdaK(tK) + val normalized = K.substituteVariables(e, Map(y -> tK)) + val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) + val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) + if (K.isSameSet(botK.right + phi_redex, premiseSequent.right + phi_normalized) || K.isSameSet(botK.right + phi_normalized, premiseSequent.right + phi_redex)) + return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) + else + return proof.InvalidProofTactic("Right-hand side of the conclusion + φ[λy.e]t/x must be the same as right-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else + return proof.InvalidProofTactic("Left-hand side of the conclusion must be the same as the left-hand side of the premise") + } + } + + object LeftBeta extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof) + (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val lambdaK = lambda.underlying + lazy val tK = t.underlying + lazy val xK = x.underlying + lazy val botK = bot.underlying + lazy val y = lambda.v.underlying + lazy val e = lambda.body.underlying + if (phi.sort != K.Formula) + return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) + else if (e.sort != x.sort) + return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) + else if (K.isSameSet(botK.right, premiseSequent.right)) { + val redex = lambdaK(tK) + val normalized = K.substituteVariables(e, Map(y -> tK)) + val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) + val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) + if (K.isSameSet(botK.left + phi_redex, premiseSequent.left + phi_normalized) || K.isSameSet(botK.left + phi_normalized, premiseSequent.left + phi_redex)) + return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) + else + return proof.InvalidProofTactic("Left-hand side of the conclusion + φ[λy.e]t/x must be the same as left-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else + return proof.InvalidProofTactic("Right-hand side of the conclusion must be the same as the right-hand side of the premise") + } + } + + // Structural rules + /** + *
+   *     Γ |- Δ
+   * --------------
+   *   Γ, Σ |- Δ, Π
+   * 
+ */ + object Weakening extends ProofTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + + if (!F.isImplyingSequent(premiseSequent, bot)) + proof.InvalidProofTactic("Conclusion cannot be trivially derived from premise.") + else + proof.ValidProofTactic(bot, Seq(K.Weakening(bot.underlying, -1)), Seq(premise)) + } + } + + // Equality Rules + /** + *
+   *  Γ, s=s |- Δ
+   * --------------
+   *     Γ |- Δ
+   * 
+ */ + object LeftRefl extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val faK = fa.underlying + lazy val botK = bot.underlying + + if (!K.isSameSet(botK.left + faK, premiseSequent.left) || !premiseSequent.left.exists(_ == faK) || botK.left.exists(_ == faK)) + proof.InvalidProofTactic("Left-hand sides of the conclusion + φ is not the same as left-hand side of the premise.") + else if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of the premise is not the same as the right-hand side of the conclusion.") + else + faK match { + case K.Application(K.Application(K.equality, left), right) => + if (K.isSame(left, right)) + proof.ValidProofTactic(bot, Seq(K.LeftRefl(botK, -1, faK)), Seq(premise)) + else + proof.InvalidProofTactic("φ is not an instance of reflexivity.") + case _ => proof.InvalidProofTactic("φ is not an equality.") + } + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + LeftRefl.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Could not infer an equality as pivot from premise and conclusion.") + } + } + + /** + *
+   *
+   * --------------
+   *     |- s=s
+   * 
+ */ + object RightRefl extends ProofTactic with ProofSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val faK = fa.underlying + lazy val botK = bot.underlying + if (!botK.right.exists(_ == faK)) + proof.InvalidProofTactic("Right-hand side of conclusion does not contain φ.") + else + faK match { + case K.Application(K.Application(K.equality, left), right) => + if (K.isSame(left, right)) + proof.ValidProofTactic(bot, Seq(K.RightRefl(botK, faK)), Seq()) + else + proof.InvalidProofTactic("φ is not an instance of reflexivity.") + case _ => proof.InvalidProofTactic("φ is not an equality.") + } + } + + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + if (bot.right.isEmpty) proof.InvalidProofTactic("Right-hand side of conclusion does not contain an instance of reflexivity.") + else { + // go through conclusion to see if you can find an reflexive formula + val pivot: Option[F.Formula] = bot.right.find(f => + val Eq = F.equality // (F.equality: (F.|->[F.**[F.Term, 2], F.Formula])) + f match { + case F.App(F.App(e, l), r) => + (F.equality) == (e) && l == r // termequality + case _ => false + } + ) + + pivot match { + case Some(phi) => RightRefl.withParameters(phi)(bot) + case _ => proof.InvalidProofTactic("Could not infer an equality as pivot from conclusion.") + } + + } + + } + } + + /** + *
+   *   Γ, φ(s) |- Δ     Σ |- s=t, Π     
+   * --------------------------------
+   *        Γ, Σ φ(t) |- Δ, Π
+   * 
+ */ + object LeftSubstEq extends ProofTactic { + + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + } + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.equality(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + + + } + } + + /** + *
+   *  Γ |- φ(s), Δ     Σ |- s=t, Π
+   * ---------------------------------
+   *         Γ, Σ |- φ(t), Δ, Π
+   * 
+ */ + object RightSubstEq extends ProofTactic { + def withParametersSimple[T1](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + } + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Formula) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.equality(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.RightSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + } + + } + + /** + *
+   *           Γ, φ(a1,...an) |- Δ
+   * ----------------------------------------
+   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
+   * 
+ */ + object LeftSubstIff extends ProofTactic { + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + /*{ + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.Iff(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + */ + + } + + /** + *
+   *           Γ |- φ(a1,...an), Δ
+   * ----------------------------------------
+   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
+   * 
+ */ + object RightSubstIff extends ProofTactic { + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + /* + def withParametersSimple(using lib: Library, proof: lib.Proof)( + equals: List[(F.Formula, F.Formula)], + lambdaPhi: F.LambdaExpression[F.Formula, F.Formula, ?] + )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + withParameters(equals.map { case (a, b) => (F.lambda(Seq(), a), F.lambda(Seq(), b)) }, (lambdaPhi.bounds.asInstanceOf[Seq[F.SchematicAtomicLabel[?]]], lambdaPhi.body))(premise)(bot) + } + + def withParameters(using lib: Library, proof: lib.Proof)( + equals: List[(F.LambdaExpression[F.Term, F.Formula, ?], F.LambdaExpression[F.Term, F.Formula, ?])], + lambdaPhi: (Seq[F.SchematicAtomicLabel[?]], F.Formula) + )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val botK = bot.underlying + lazy val equalsK = equals.map(p => (p._1.underlyingLTF, p._2.underlyingLTF)) + lazy val lambdaPhiK = (lambdaPhi._1.map(_.underlying), lambdaPhi._2.underlying) + + val (psi_s, tau_s) = equalsK.unzip + val (phi_args, phi_body) = lambdaPhiK + if (phi_args.size != psi_s.size) + return proof.InvalidProofTactic("The number of arguments of φ must be the same as the number of equalities.") + else if (equalsK.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) + return proof.InvalidProofTactic("The arities of symbols in φ must be the same as the arities of equalities.") + + val phi_psi = K.instantiatePredicateSchemas(phi_body, (phi_args zip psi_s).toMap) + val phi_tau = K.instantiatePredicateSchemas(phi_body, (phi_args zip tau_s).toMap) + val psiIffTau = equalsK map { case (s, t) => + assert(s.vars.size == t.vars.size) + val base = K.ConnectorFormula(K.Iff, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(K.VariableTerm)))) + (s.vars).foldLeft(base: K.Formula) { case (acc, s_arg) => K.forall(s_arg, acc) } + } + + if (!K.isSameSet(botK.left, premiseSequent.left ++ psiIffTau)) { + proof.InvalidProofTactic("Left-hand side of the conclusion is not the same as the left-hand side of the premise + (ψ ⇔ τ)_.") + } else if ( + !K.isSameSet(botK.right + phi_psi, premiseSequent.right + phi_tau) && + !K.isSameSet(botK.right + phi_tau, premiseSequent.right + phi_psi) + ) + proof.InvalidProofTactic("Right-hand side of the conclusion + φ(ψ_) is not the same as right-hand side of the premise + φ(τ_) (or with ψ_ and τ_ swapped).") + else + proof.ValidProofTactic(bot, Seq(K.RightSubstIff(botK, -1, equalsK, lambdaPhiK)), Seq(premise)) + } + */ + } + + /** + *
+   *           Γ |- Δ
+   * --------------------------
+   *  Γ[r(a)/?f] |- Δ[r(a)/?f]
+   * 
+ */ + object InstSchema extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof + )(map: Map[F.Variable[?], F.Expr[?]])(premise: proof.Fact): proof.ProofTacticJudgement = { + val premiseSequent = proof.getSequent(premise).underlying + val mapK = map.map((v, e) => (v.underlying, e.underlying)) + val botK = K.substituteVariablesInSequent(premiseSequent, mapK) + val res = proof.getSequent(premise).substituteUnsafe(map) + proof.ValidProofTactic(res, Seq(K.InstSchema(botK, -1, mapK)), Seq(premise)) + } + } + object Subproof extends ProofTactic { + def apply(using proof: Library#Proof)(statement: Option[F.Sequent])(iProof: proof.InnerProof) = { + val bot: Option[F.Sequent] = statement + val botK: Option[K.Sequent] = statement map (_.underlying) + if (iProof.length == 0) throw (new UnimplementedProof(proof.owningTheorem)) + val scproof: K.SCProof = iProof.toSCProof + val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) + val judgement: proof.ProofTacticJudgement = { + if (botK.isEmpty) + proof.ValidProofTactic(iProof.mostRecentStep.bot, scproof.steps, premises) + else if (!K.isSameSequent(botK.get, scproof.conclusion)) + proof.InvalidProofTactic( + s"The subproof does not prove the desired conclusion.\n\tExpected: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}" + ) + else + proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) + } + judgement + } + } +*/ + class SUBPROOF(using val proof: Library#Proof)(statement: Option[F.Sequent])(val iProof: proof.InnerProof) extends ProofTactic { + val bot: Option[F.Sequent] = statement + val botK: Option[K.Sequent] = statement map (_.underlying) + if (iProof.length == 0) + throw (new UnimplementedProof(proof.owningTheorem)) + val scproof: K.SCProof = iProof.toSCProof + + val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) + def judgement: proof.ProofTacticJudgement = { + if (botK.isEmpty) + proof.ValidProofTactic(iProof.mostRecentStep.bot, scproof.steps, premises) + else if (!K.isSameSequent(botK.get, scproof.conclusion)) + proof.InvalidProofTactic(s"The subproof does not prove the desired conclusion.\n\tExpected: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}") + else + proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) + } + } + + // TODO make specific support for subproofs written inside tactics.kkkkkkk + + inline def TacticSubproof(using proof: Library#Proof)(inline computeProof: proof.InnerProof ?=> Unit): proof.ProofTacticJudgement = + val iProof: proof.InnerProof = new proof.InnerProof(None) + computeProof(using iProof) + SUBPROOF(using proof)(None)(iProof).judgement.asInstanceOf[proof.ProofTacticJudgement] + + object Sorry extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + proof.ValidProofTactic(bot, Seq(K.Sorry(bot.underlying)), Seq()) + } + } + +} diff --git a/lisa-utils/src/main/scala/lisa/prooflib/Exports.scala b/backup/backup2/prooflib/Exports.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/Exports.scala rename to backup/backup2/prooflib/Exports.scala diff --git a/backup/backup2/prooflib/Library.scala b/backup/backup2/prooflib/Library.scala new file mode 100644 index 00000000..049b82bf --- /dev/null +++ b/backup/backup2/prooflib/Library.scala @@ -0,0 +1,106 @@ +package lisa.prooflib + +import lisa.kernel.proof.RunningTheory +import lisa.kernel.proof.SCProofChecker +import lisa.kernel.proof.SCProofCheckerJudgement +import lisa.kernel.proof.SequentCalculus +import lisa.prooflib.ProofTacticLib.ProofTactic +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.{_, given} + +import scala.collection.mutable.Stack as stack + +/** + * A class abstracting a [[lisa.kernel.proof.RunningTheory]] providing utility functions and a convenient syntax + * to write and use Theorems and Definitions. + * @param theory The inner RunningTheory + */ +abstract class Library extends lisa.prooflib.WithTheorems with lisa.prooflib.ProofsHelpers { + + val theory: RunningTheory + given library: this.type = this + given RunningTheory = theory + + export lisa.kernel.proof.SCProof + + val K = lisa.utils.K + val SC: SequentCalculus.type = K.SC + private[prooflib] val F = lisa.fol.FOL + import F.{given} + + var last: Option[JUSTIFICATION] = None + + // Options for files + private[prooflib] var _withCache: Boolean = false + def withCache(using file: sourcecode.File, om: OutputManager)(): Unit = + if last.nonEmpty then om.output(OutputManager.WARNING("Warning: withCache option should be used before the first definition or theorem.")) + else _withCache = true + + private[prooflib] var _draft: Option[sourcecode.File] = None + def draft(using file: sourcecode.File, om: OutputManager)(): Unit = + if last.nonEmpty then om.output(OutputManager.WARNING("Warning: draft option should be used before the first definition or theorem.")) + else _draft = Some(file) + def isDraft = _draft.nonEmpty + + val knownDefs: scala.collection.mutable.Map[F.Constant[?], Option[JUSTIFICATION]] = scala.collection.mutable.Map.empty + val shortDefs: scala.collection.mutable.Map[F.Constant[?], Option[JUSTIFICATION]] = scala.collection.mutable.Map.empty + + def addSymbol(s: F.Constant[?]): Unit = + theory.addSymbol(s.underlying) + knownDefs.update(s, None) + + def getDefinition(label: F.Constant[?]): Option[JUSTIFICATION] = knownDefs.get(label) match { + case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) + case Some(value) => value + } + def getShortDefinition(label: F.Constant[?]): Option[JUSTIFICATION] = shortDefs.get(label) match { + case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) + case Some(value) => value + } + + /** + * An alias to create a Theorem + */ + def makeTheorem(name: String, statement: K.Sequent, proof: K.SCProof, justifications: Seq[theory.Justification]): K.Judgement[theory.Theorem] = + theory.theorem(name, statement, proof, justifications) + + // DEFINITION Syntax + + /* + /** + * Allows to create a definition by shortcut of a function symbol: + */ + def makeSimpleFunctionDefinition(symbol: String, expression: K.LambdaTermTerm): K.Judgement[theory.FunctionDefinition] = { + import K.* + val LambdaTermTerm(vars, body) = expression + + val out: VariableLabel = VariableLabel(freshId((vars.map(_.id) ++ body.schematicTermLabels.map(_.id)).toSet, "y")) + val proof: SCProof = simpleFunctionDefinition(expression, out) + theory.functionDefinition(symbol, LambdaTermFormula(vars, out === body), out, proof, out === body, Nil) + } + */ + + /** + * Allows to create a definition by shortcut of a predicate symbol: + */ + def makeSimpleDefinition(symbol: String, expression: K.Expression): K.Judgement[theory.Definition] = + theory.definition(symbol, expression) + + + /** + * Prints a short representation of the given theorem or definition + */ + def show(using om: OutputManager)(thm: JUSTIFICATION) = { + if (thm.withSorry) om.output(thm.repr, Console.YELLOW) + else om.output(thm.repr, Console.GREEN) + } + + /** + * Prints a short representation of the last theorem or definition introduced + */ + def show(using om: OutputManager): Unit = last match { + case Some(value) => show(value) + case None => throw new NoSuchElementException("There is nothing to show: No theorem or definition has been proved yet.") + } + +} diff --git a/backup/backup2/prooflib/OutputManager.scala b/backup/backup2/prooflib/OutputManager.scala new file mode 100644 index 00000000..bc7442ef --- /dev/null +++ b/backup/backup2/prooflib/OutputManager.scala @@ -0,0 +1,52 @@ +package lisa.prooflib + +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.{_, given} + +import java.io.PrintWriter +import java.io.StringWriter + +abstract class OutputManager { + + given OutputManager = this + + def output(s: String): Unit = stringWriter.write(s + "\n") + def output(s: String, color: String): Unit = stringWriter.write(Console.RESET + color + s + "\n" + Console.RESET) + val stringWriter: StringWriter + + def finishOutput(exception: Exception): Nothing + + def lisaThrow(le: LisaException): Nothing = + le match { + case ule: UserLisaException => + ule.fixTrace() + output(ule.showError) + finishOutput(ule) + + case e: LisaException.InvalidKernelJustificationComputation => + e.proof match { + case Some(value) => output(lisa.prooflib.ProofPrinter.prettyProof(value)) + case None => () + } + output(e.underlying.repr) + finishOutput(e) + + } + + def log(e: Exception): Unit = { + stringWriter.write("\n[" + Console.RED + "Error" + Console.RESET + "] ") + e.printStackTrace(PrintWriter(stringWriter)) + output(Console.RESET) + } + +} +object OutputManager { + def RED(s: String): String = Console.RED + s + Console.RESET + def GREEN(s: String): String = Console.GREEN + s + Console.RESET + def BLUE(s: String): String = Console.BLUE + s + Console.RESET + def YELLOW(s: String): String = Console.YELLOW + s + Console.RESET + def CYAN(s: String): String = Console.CYAN + s + Console.RESET + def MAGENTA(s: String): String = Console.MAGENTA + s + Console.RESET + + def WARNING(s: String): String = Console.YELLOW + "⚠ " + s + Console.RESET +} diff --git a/lisa-utils/src/main/scala/lisa/utils/parsing/ProofPrinter.scala b/backup/backup2/prooflib/ProofPrinter.scala similarity index 98% rename from lisa-utils/src/main/scala/lisa/utils/parsing/ProofPrinter.scala rename to backup/backup2/prooflib/ProofPrinter.scala index ea2fe2ff..96c95137 100644 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/ProofPrinter.scala +++ b/backup/backup2/prooflib/ProofPrinter.scala @@ -1,4 +1,4 @@ -package lisa.utils.parsing +package lisa.prooflib import lisa.kernel.proof.SCProofCheckerJudgement import lisa.prooflib.BasicStepTactic.SUBPROOF @@ -6,9 +6,7 @@ import lisa.prooflib.Library import lisa.prooflib.* import lisa.utils.* -//temporary - get merged wit regular printer in time object ProofPrinter { - private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" diff --git a/backup/backup2/prooflib/ProofTacticLib.scala b/backup/backup2/prooflib/ProofTacticLib.scala new file mode 100644 index 00000000..c107e724 --- /dev/null +++ b/backup/backup2/prooflib/ProofTacticLib.scala @@ -0,0 +1,66 @@ +package lisa.prooflib + +import lisa.fol.FOL as F +import lisa.prooflib.* +import lisa.utils.K +import lisa.utils.UserLisaException +import lisa.prooflib.ProofPrinter + +object ProofTacticLib { + type Arity = Int & Singleton + + /** + * A ProofTactic is an object that relies on a step of premises and which can be translated into pure Sequent Calculus. + */ + trait ProofTactic { + val name: String = this.getClass.getName.split('$').last + given ProofTactic = this + + } + + trait OnlyProofTactic { + def apply(using lib: Library, proof: lib.Proof): proof.ProofTacticJudgement + } + + trait ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement + } + + trait ProofFactTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact): proof.ProofTacticJudgement + } + trait ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement + } + + class UnapplicableProofTactic(val tactic: ProofTactic, proof: Library#Proof, errorMessage: String)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { + override def fixTrace(): Unit = { + val start = getStackTrace.indexWhere(elem => { + !elem.getClassName.contains(tactic.name) + }) + 1 + setStackTrace(getStackTrace.take(start)) + } + + def showError: String = { + val source = scala.io.Source.fromFile(file.value) + val textline = source.getLines().drop(line.value - 1).next().dropWhile(c => c.isWhitespace) + source.close() + Console.RED + proof.owningTheorem.prettyGoal + Console.RESET + "\n" + + ProofPrinter.prettyProof(proof, 2) + "\n" + + " " * (1 + proof.depth) + Console.RED + textline + Console.RESET + "\n\n" + + s" Proof tactic ${tactic.name} used in.(${file.value.split("/").last.split("\\\\").last}:${line.value}) did not succeed:\n" + + " " + errorMessage + } + } + + class UnimplementedProof(val theorem: Library#THM)(using sourcecode.Line, sourcecode.File) extends UserLisaException("Unimplemented Theorem") { + def showError: String = s"Theorem ${theorem.name}" + } + case class UnexpectedProofTacticFailureException(failure: Library#Proof#InvalidProofTactic, errorMessage: String)(using sourcecode.Line, sourcecode.File) + extends lisa.utils.LisaException(errorMessage) { + def showError: String = "A proof tactic used in another proof tactic returned an unexpected error. This may indicate an implementation error in either of the two tactics.\n" + + "Status of the proof at time of the error is:" + + ProofPrinter.prettyProof(failure.proof) + } + +} diff --git a/backup/backup2/prooflib/ProofsHelpers.scala b/backup/backup2/prooflib/ProofsHelpers.scala new file mode 100644 index 00000000..46d75d7f --- /dev/null +++ b/backup/backup2/prooflib/ProofsHelpers.scala @@ -0,0 +1,356 @@ +package lisa.prooflib + +import lisa.kernel.proof.SCProofChecker.checkSCProof +import lisa.prooflib.BasicStepTactic.* +import lisa.prooflib.ProofTacticLib.* +import lisa.prooflib.SimpleDeducedSteps.* +import lisa.prooflib.* +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.K.Identifier +import lisa.utils.LisaException +import lisa.utils.UserLisaException +import lisa.utils.{_, given} + +import scala.annotation.targetName + +trait ProofsHelpers { + library: Library & WithTheorems => + + import lisa.fol.FOL.{given, *} + + class HaveSequent(val bot: Sequent) { + + inline infix def by(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): By { val _proof: proof.type } = By(proof, line, file).asInstanceOf + + class By(val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { + + val bot = HaveSequent.this.bot ++ (F.iterable_to_set(_proof.getAssumptions) |- ()) + inline infix def apply(tactic: Sequent => _proof.ProofTacticJudgement): _proof.ProofStep & _proof.Fact = { + tactic(bot).validate(line, file) + } + inline infix def apply(tactic: ProofSequentTactic): _proof.ProofStep = { + tactic(using library, _proof)(bot).validate(line, file) + } + } + + infix def subproof(using proof: Library#Proof, line: sourcecode.Line, file: sourcecode.File)(computeProof: proof.InnerProof ?=> Unit): proof.ProofStep = { + val botWithAssumptions = HaveSequent.this.bot ++ (proof.getAssumptions |- ()) + val iProof: proof.InnerProof = new proof.InnerProof(Some(botWithAssumptions)) + computeProof(using iProof) + (new BasicStepTactic.SUBPROOF(using proof)(Some(botWithAssumptions))(iProof)).judgement.validate(line, file).asInstanceOf[proof.ProofStep] + } + + } + + class AndThenSequent private[ProofsHelpers] (val bot: Sequent) { + + inline infix def by(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): By { val _proof: proof.type } = + By(proof, line, file).asInstanceOf[By { val _proof: proof.type }] + + class By(val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { + private val bot = AndThenSequent.this.bot ++ (_proof.getAssumptions |- ()) + inline infix def apply(tactic: _proof.Fact => Sequent => _proof.ProofTacticJudgement): _proof.ProofStep = { + tactic(_proof.mostRecentStep)(bot).validate(line, file) + } + + inline infix def apply(tactic: ProofFactSequentTactic): _proof.ProofStep = { + tactic(using library, _proof)(_proof.mostRecentStep)(bot).validate(line, file) + } + + } + } + + /** + * Claim the given Sequent as a ProofTactic, which may require a justification by a proof tactic and premises. + */ + def have(using proof: library.Proof)(res: Sequent): HaveSequent = HaveSequent(res) + + def have(using line: sourcecode.Line, file: sourcecode.File)(using proof: library.Proof)(v: proof.Fact | proof.ProofTacticJudgement) = v match { + case judg: proof.ProofTacticJudgement => judg.validate(line, file) + case fact: proof.Fact @unchecked => HaveSequent(proof.sequentOfFact(fact)).by(using proof, line, file)(Rewrite(using library, proof)(fact)) + } + + /** + * Claim the given Sequent as a ProofTactic directly following the previously proven tactic, + * which may require a justification by a proof tactic. + */ + def thenHave(using proof: library.Proof)(res: Sequent): AndThenSequent = AndThenSequent(res) + + infix def andThen(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): AndThen { val _proof: proof.type } = AndThen(proof, line, file).asInstanceOf + + class AndThen private[ProofsHelpers] (val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { + inline infix def apply(tactic: _proof.Fact => _proof.ProofTacticJudgement): _proof.ProofStep = { + tactic(_proof.mostRecentStep).validate(line, file) + } + inline infix def apply(tactic: ProofFactTactic): _proof.ProofStep = { + tactic(using library, _proof)(_proof.mostRecentStep).validate(line, file) + } + } + + + /* + /** + * Assume the given formula in all future left hand-side of claimed sequents. + */ + def assume(using proof: library.Proof)(f: Formula): proof.ProofStep = { + proof.addAssumption(f) + have(() |- f) by BasicStepTactic.Hypothesis + } + */ + /** + * Assume the given formulas in all future left hand-side of claimed sequents. + */ + def assume(using proof: library.Proof)(fs: Formula*): proof.ProofStep = { + fs.foreach(f => proof.addAssumption(f)) + have(() |- fs.toSet) by BasicStepTactic.Hypothesis + } + + def thesis(using proof: library.Proof): Sequent = proof.possibleGoal.get + def goal(using proof: library.Proof): Sequent = proof.possibleGoal.get + + def lastStep(using proof: library.Proof): proof.ProofStep = proof.mostRecentStep + + def sorry(using proof: library.Proof): proof.ProofStep = have(thesis) by Sorry + + def showCurrentProof(using om: OutputManager, _proof: library.Proof)(): Unit = { + om.output("Current proof of " + _proof.owningTheorem.prettyGoal + ": ") + om.output( + ProofPrinter.prettyProof(_proof, 2) + ) + } + + extension (using proof: library.Proof)(fact: proof.Fact) { + infix def of(insts: (F.SubstPair | F.Term)*): proof.InstantiatedFact = { + proof.InstantiatedFact(fact, insts) + } + def statement: F.Sequent = proof.sequentOfFact(fact) + } + + def currentProof(using p: library.Proof): Library#Proof = p + +/* + + //////////////////////////////////////// + // DSL for definitions and theorems // + //////////////////////////////////////// + + class UserInvalidDefinitionException(val symbol: String, errorMessage: String)(using line: sourcecode.Line, file: sourcecode.File) extends UserLisaException(errorMessage) { // TODO refine + val showError: String = { + val source = scala.io.Source.fromFile(file.value) + val textline = source.getLines().drop(line.value - 1).next().dropWhile(c => c.isWhitespace) + source.close() + s" Definition of $symbol at.(${file.value.split("/").last.split("\\\\").last}:${line.value}) is invalid:\n" + + " " + Console.RED + textline + Console.RESET + "\n\n" + + " " + errorMessage + } + } + + + def leadingVarsAndBody(e: Expr[?]): (Seq[Variable[?]], Expr[?]) = + def inner(e: Expr[?]): (Seq[Variable[?]], Expr[?]) = e match + case Abs(v, body) => + val (vars, bodyR) = inner(body) + (v +: vars, bodyR) + case _ => (Seq(), e) + val r = inner(e) + (r._1.reverse, r._2) + + def DEF[S: Sort](using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File) + (e: Expr[S]): Constant[S] = + val (vars, body) = leadingVarsAndBody(e) + if vars.size == e.sort.depth then + DirectDefinition[S](name.value, line.value, file.value)(e, vars).cst + else + val maxV: Int = vars.maxBy(_.id.no).id.no + val maxB: Int = body.freeVars.maxBy(_.id.no).id.no + var no = List(maxV, maxB).max + val newvars = K.flatTypeParameters(body.sort).map(i =>{no+=1; Variable.unsafe(K.Identifier("x", no), i)}) + val totvars = vars ++ newvars + DirectDefinition[S](name.value, line.value, file.value)(e, totvars)(using F.unsafeSortEvidence(e.sort)).cst + + def EpsilonDEF[S: Sort](using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File) + (e: Expr[S], j: JUSTIFICATION): Constant[S] = + val (vars, body) = leadingVarsAndBody(e) + if vars.size == e.sort.depth then + body match + case epsilon(x, inner) => + EpsilonDefinition[S](name.value, line.value, file.value)(e, vars, j).cst + case _ => om.lisaThrow(UserInvalidDefinitionException(name.value, "The given expression is not an epsilon term.")) + else om.lisaThrow(UserInvalidDefinitionException(name.value, "The given expression is not an epsilon term.")) + + + + class DirectDefinition[S : Sort](using om: OutputManager)(val fullName: String, line: Int, file: String)(val expr: Expr[S], val vars: Seq[Variable[?]]) extends DEFINITION(line, file) { + + val arity = vars.size + + lazy val cst: Constant[S] = F.Constant(name) + + + val appliedCst: Expr[?] = cst#@@(vars) + + + val innerJustification: theory.Definition = { + import lisa.utils.K.{findUndefinedSymbols} + val uexpr = expr.underlying + val ucst = K.Constant(name, cst.sort) + val uvars = vars.map(_.underlying) + val judgement = theory.makeDefinition(ucst, uexpr, uvars) + judgement match { + case K.ValidJustification(just) => + just + case wrongJudgement: K.InvalidJustification[?] => + if (!theory.belongsToTheory(uexpr)) { + om.lisaThrow( + UserInvalidDefinitionException( + name, + s"All symbols in the definition must belong to the theory. The symbols ${theory.findUndefinedSymbols(uexpr)} are unknown and you need to define them first." + ) + ) + } + if !theory.isAvailable(ucst) then + om.lisaThrow(UserInvalidDefinitionException(name, s"The symbol ${name} has already been defined and can't be redefined.")) + if !uexpr.freeVariables.nonEmpty then + om.lisaThrow( + UserInvalidDefinitionException( + name, + s"The definition is not allowed to contain schematic symbols or free variables. " + + s"The variables {${(uexpr.freeVariables).mkString(", ")}} are free in the expression ${uexpr}." + ) + ) + if !theory.isAvailable(ucst) then + om.lisaThrow(UserInvalidDefinitionException(name, s"The symbol ${name} has already been defined and can't be redefined.")) + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or an error in LISA.", + wrongJudgement, + None + ) + ) + } + } + val right = expr#@@(vars) + val statement = + if appliedCst.sort == K.Term then + () |- iff.#@(appliedCst).#@(right).asInstanceOf[Formula] + else + () |- equality.#@(appliedCst).#@(right).asInstanceOf[Formula] + library.last = Some(this) + } + + def dropAllLambdas(s: Expr[?]): Expr[?] = s match { + case Abs(v, body) => dropAllLambdas(body) + case _ => s + } + + + /** + * For a list of sequence of variables x, y, z, creates the term with lambdas: + * λx.(λy.(λz. base)) + */ + def abstractVars(v: Seq[Variable[?]], body: Expr[?]): Expr[?] = + def inner(v: Seq[Variable[?]], body: Expr[?]) = v match + case Seq() => body + case x +: xs => Abs.unsafe(x, abstractVars(xs, body)) + inner(v.reverse, body) + + /** + * For a list of sequence of variables x, y, z, creates the term with lambdas: + * λx.(λy.(λz. base)) + */ + def applyVars(v: Seq[Variable[?]], body: Expr[?]): Expr[?] = v match + case Seq() => body + case x +: xs => applyVars(xs, body#@(x)) + + /** + * For a list of sequence of variables x, y, z, creates the term with lambdas: + * ((((λx.(λy.(λz. base))) x) y) z) + */ + def betaExpand(base: Expr[?], vars: Seq[Variable[?]]): Expr[?] = + applyVars(vars, abstractVars(vars.reverse, base)) + + + + /** + * Allows to make definitions "by unique existance" of a symbol. May need debugging + */ + class EpsilonDefinition[S: Sort](using om: OutputManager)(fullName: String, line: Int, file: String)( + expr: Expr[S], + vars: Seq[Variable[?]], + val j: JUSTIFICATION + ) extends DirectDefinition(fullName, line, file)(expr, vars) { + + val body: Term = dropAllLambdas(expr).asInstanceOf[Term] + override val appliedCst : Term = (cst#@@(vars)).asInstanceOf[Term] + val (epsilonVar, inner) = body match + case epsilon(x, inner) => (x, inner) + case _ => om.lisaThrow(UserInvalidDefinitionException(name, "The given expression is not an epsilon term.")) + + private val propCst = inner.substitute(epsilonVar := appliedCst) + private val propEpsilon = inner.substitute(epsilonVar := body) + val definingProp = Theorem(propCst) { + val fresh = freshId(vars, "x") + have(this) + + def loop(expr: Expr[?], leading: List[Variable[?]]) : Unit = + expr match { + case App(lam @ Abs(vAbs, body1: Term), _) => + val freshX = Variable.unsafe(fresh, body1.sort) + val right: Term = applyVars(leading.reverse, freshX).asInstanceOf[Term] + var instRight: Term = applyVars(leading.reverse, body1).asInstanceOf[Term] + thenHave(appliedCst === instRight) by RightBeta.withParameters(appliedCst === right, lam, vAbs, freshX) + case App(f, a: Variable[?]) => loop(expr, a :: leading) + case _ => throw new Exception("Unreachable") + } + while lastStep.bot.right.head match {case App(epsilon, _) => false; case _ => true} do + loop(lastStep.bot.right.head, List()) + val eqStep = lastStep // appliedCst === body + // j is exists(x, prop(x)) + val existsStep = ??? //have(propEpsilon) by // prop(body) + val s3 = have(propCst) by BasicStepTactic.RightSubstEq.withParametersSimple[T](appliedCst, body, Seq(), (epsilonVar, inner))(j, lastStep) + ??? + } + + override def repr: String = + s" ${if (withSorry) " Sorry" else ""} Definition of symbol ${appliedCst} such that ${definingProp.statement})\n" + + } + + + + ///////////////////////// + // Local Definitions // + ///////////////////////// + + import lisa.utils.K.prettySCProof + import lisa.utils.KernelHelpers.apply + + /** + * A term with a definition, local to a proof. + * + * @param proof + * @param id + */ + abstract class LocalyDefinedVariable[S:Sort](val proof: library.Proof, id: Identifier) extends Variable(id) { + + val definition: proof.Fact + lazy val definingFormula = proof.sequentOfFact(definition).right.head + + // proof.addDefinition(this, defin(this), fact) + // val definition: proof.Fact = proof.getDefinition(this) + } + + /** + * Check correctness of the proof, using LISA's logical kernel, to the current point. + */ + def sanityProofCheck(using p: Proof)(message: String): Unit = { + val csc = p.toSCProof + if checkSCProof(csc).isValid then + println("Proof is valid. " + message) + Thread.sleep(100) + else + checkProof(csc) + throw Exception("Proof is not valid: " + message) + } +*/ +} diff --git a/backup/backup2/prooflib/SimpleDeducedSteps.scala b/backup/backup2/prooflib/SimpleDeducedSteps.scala new file mode 100644 index 00000000..2ce8f574 --- /dev/null +++ b/backup/backup2/prooflib/SimpleDeducedSteps.scala @@ -0,0 +1,331 @@ +package lisa.prooflib + +import lisa.fol.FOL as F +import lisa.prooflib.BasicStepTactic.* +import lisa.prooflib.ProofTacticLib.{_, given} +import lisa.prooflib.* +import lisa.utils.K +import lisa.utils.KernelHelpers.{_, given} + +object SimpleDeducedSteps { +/* + + object Restate extends ProofTactic with ProofSequentTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = + unwrapTactic(RewriteTrue(bot))("Attempted true rewrite during tactic Restate failed.") + + // (proof.ProofStep | proof.OutsideFact | Int) is definitionally equal to proof.Fact, but for some reason + // scala compiler doesn't resolve the overload with a type alias, dependant type and implicit parameter + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") + + def from(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") + + } + + object Discharge extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(premise: proof.Fact): proof.ProofTacticJudgement = { + val ss = premises zip (premises map (e => proof.getSequent(e))) + val seqs = ss.map(_._2) + if (!seqs.forall(_.right.size == 1)) + return proof.InvalidProofTactic("When discharging this way, the discharged sequent must have only a single formula on the right handside.") + val seqAny = ss.find((_, s) => premise.statement.left.exists(f2 => F.isSame(s.right.head, f2))) + if (seqAny.isEmpty) + Restate.from(premise)(premise.statement) + else + TacticSubproof: ip ?=> + ss.foldLeft(premise: ip.Fact)((prem, discharge) => + val seq = discharge._2 + if prem.statement.left.exists(f => F.isSame(f, seq.right.head)) then + val goal = prem.statement - + * Γ ⊢ ∀x.ψ, Δ + * ------------------------- + * Γ |- ψ[t/x], Δ + * + * + * + * Returns a subproof containing the instantiation steps + */ + object InstantiateForall extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: F.Formula, t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + val phiK = phi.underlying + val tK = t map (_.underlying) + val premiseSequent = proof.getSequent(premise) + val premiseSequentK = premiseSequent.underlying + if (!premiseSequent.right.contains(phi)) { + proof.InvalidProofTactic("Input formula was not found in the RHS of the premise sequent.") + } else { + val emptyProof = K.SCProof(IndexedSeq(), IndexedSeq(premiseSequentK)) + val j = proof.ValidProofTactic(bot, Seq(K.Restate(premiseSequentK, -1)), Seq(premise)) + val res = tK.foldLeft((emptyProof, phiK, j: proof.ProofTacticJudgement)) { case ((p, f, j), t) => + j match { + case proof.InvalidProofTactic(_) => (p, f, j) // propagate error + case proof.ValidProofTactic(_, _, _) => + // good state, continue instantiating + // by construction the premise is well-formed + // verify the formula structure and instantiate + f match { + case psi @ K.Forall(K.Lambda(x, inner)) => + val tempVar = K.Variable(K.freshId(psi.freeVariables.map(_.id), x.id), K.Term) + // instantiate the formula with input + val in = K.substituteVariables(inner, Map(x -> t)) + val con = p.conclusion ->> f +>> in + // construct proof + val p0 = K.Hypothesis(in |- in, in) + val p1 = K.LeftForall(f |- in, 0, K.substituteVariables(inner, Map(x -> tempVar)) , tempVar, t) + val p2 = K.Cut(con, -1, 1, f) + + /** + * in = ψ[t/x] + * + * s1 = Γ ⊢ ∀x.ψ, Δ Premise + * con = Γ ⊢ ψ[t/x], Δ Result + * + * p0 = ψ[t/x] ⊢ ψ[t/x] Hypothesis + * p1 = ∀x.ψ ⊢ ψ[t/x] LeftForall p0 + * p2 = Γ ⊢ ψ[t/x], Δ Cut s1, p1 + */ + val newStep = K.SCSubproof(K.SCProof(IndexedSeq(p0, p1, p2), IndexedSeq(p.conclusion)), Seq(p.length - 1)) + ( + p withNewSteps IndexedSeq(newStep), + in, + j + ) + case _ => + (p, f, proof.InvalidProofTactic("Input formula is not universally quantified")) + } + } + } + + res._3 match { + case proof.InvalidProofTactic(_) => res._3 + case proof.ValidProofTactic(_, _, _) => { + if (K.isImplyingSequent(res._1.conclusion, botK)) + proof.ValidProofTactic(bot, Seq(K.SCSubproof(res._1.withNewSteps(IndexedSeq(K.Weakening(botK, res._1.length - 1))), Seq(-1))), Seq(premise)) + else + proof.InvalidProofTactic(s"InstantiateForall proved \n\t${res._1.conclusion.repr}\ninstead of input sequent\n\t${botK.repr}") + } + } + } + } + + def apply(using lib: Library, proof: lib.Proof)(t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val prem = proof.getSequent(premise) + if (prem.right.tail.isEmpty) { + // well formed + apply(using lib, proof)(prem.right.head, t*)(premise)(bot): proof.ProofTacticJudgement + } else proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") + } + + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + try { + val sp = TacticSubproof { + // lazy val premiseSequent = proof.getSequent(premise) + val s1 = lib.have(bot +<< bot.right.head) by Restate + lib.have(bot) by LeftForall(s1) + } + BasicStepTactic.unwrapTactic(sp)("Subproof substitution fail.") + } catch { + case e: Exception => proof.InvalidProofTactic("Impossible to justify desired step with instantiation.") + } + + } + + } + + */ + + + /* + /** + * Performs a cut when the formula to be used as pivot for the cut is + * inside a conjunction, preserving the conjunction structure + * + *
+   *
+   * PartialCut(ϕ, ϕ ∧ ψ)(left, right) :
+   *
+   *     left: Γ ⊢ ϕ ∧ ψ, Δ      right: ϕ, Σ ⊢ γ1 , γ2, …, γn
+   * -----------------------------------------------------------
+   *            Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn
+   *
+   * 
+ */ + object PartialCut extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, conjunction: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val leftSequent = proof.getSequent(prem1) + val rightSequent = proof.getSequent(prem2) + + if (leftSequent.right.contains(conjunction)) { + + if (rightSequent.left.contains(phi)) { + // check conjunction matches with phi + conjunction match { + case K.ConnectorFormula(K.And, s: Seq[K.Formula]) => { + if (s.contains(phi)) { + // construct proof + + val psi: Seq[K.Formula] = s.filterNot(_ == phi) + val newConclusions: Set[K.Formula] = rightSequent.right.map((f: K.Formula) => K.ConnectorFormula(K.And, f +: psi)) + + val Sigma: Set[K.Formula] = rightSequent.left - phi + + val p0 = K.Weakening(rightSequent ++<< (psi |- ()), -2) + val p1 = K.RestateTrue(psi |- psi) + + // TODO: can be abstracted into a RightAndAll step + val emptyProof = SCProof(IndexedSeq(), IndexedSeq(p0.bot, p1.bot)) + val proofRightAndAll = rightSequent.right.foldLeft(emptyProof) { case (p, gamma) => + p withNewSteps IndexedSeq(K.RightAnd(p.conclusion ->> gamma +>> K.ConnectorFormula(K.And, gamma +: psi), Seq(p.length - 1, -2), gamma +: psi)) + } + + val p2 = K.SCSubproof(proofRightAndAll, Seq(0, 1)) + val p3 = K.Restate(Sigma + conjunction |- newConclusions, 2) // sanity check and correct form + val p4 = K.Cut(bot, -1, 3, conjunction) + + /** + * newConclusions = ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn + * + * left = Γ ⊢ ϕ ∧ ψ, Δ Premise + * right = ϕ, Σ ⊢ γ1 , γ2, …, γn Premise + * + * p0 = ϕ, Σ, ψ ⊢ γ1 , γ2, …, γn Weakening on right + * p1 = ψ ⊢ ψ Hypothesis + * p2 = Subproof: + * 2.1 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , γ2, …, γn RightAnd on p0 and p1 with ψ ∧ γ1 + * 2.2 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , ψ ∧ γ2, …, γn RightAnd on 2.1 and p1 ψ ∧ γ2 + * ... + * 2.n = ϕ, Σ, ψ ⊢ ψ ∧ γ1, ψ ∧ γ2, …, ψ ∧ γn RightAnd on 2.(n-1) and p1 with ψ ∧ γn + * + * p3 = ϕ ∧ ψ, Σ ⊢ ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Rewrite on p2 (just to have a cleaner form) + * p2 = Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Cut on left, p1 with ϕ ∧ ψ + * + * p2 is the result + */ + + proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3, p4), Seq(prem1, prem2)) + } else { + proof.InvalidProofTactic("Input conjunction does not contain the pivot.") + } + } + case _ => proof.InvalidProofTactic("Input not a conjunction.") + } + } else { + proof.InvalidProofTactic("Input pivot formula not found in right premise.") + } + } else { + proof.InvalidProofTactic("Input conjunction not found in first premise.") + } + } + } + + object destructRightAnd extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val conc = proof.getSequent(prem) + val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) + val p1 = K.LeftAnd(emptySeq +<< (a /\ b) +>> a, 0, a, b) + val p2 = K.Cut(conc ->> (a /\ b) ->> (b /\ a) +>> a, -1, 1, a /\ b) + proof.ValidProofTactic(IndexedSeq(p0, p1, p2), Seq(prem)) + } + } + object destructRightOr extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val conc = proof.getSequent(prem) + val mat = conc.right.find(f => K.isSame(f, a \/ b)) + if (mat.nonEmpty) { + + val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) + val p1 = K.Hypothesis(emptySeq +<< b +>> b, b) + + val p2 = K.LeftOr(emptySeq +<< (a \/ b) +>> a +>> b, Seq(0, 1), Seq(a, b)) + val p3 = K.Cut(conc ->> mat.get +>> a +>> b, -1, 2, a \/ b) + proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3), Seq(prem)) + } else { + proof.InvalidProofTactic("Premise does not contain the union of the given formulas") + } + + } + } + + object GeneralizeToForall extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val sequent = proof.getSequent(prem) + if (sequent.right.contains(phi)) { + val emptyProof = SCProof(IndexedSeq(), IndexedSeq(sequent)) + val j = proof.ValidProofTactic(IndexedSeq(K.Restate(sequent, proof.length - 1)), Seq[proof.Fact]()) + + val res = t.foldRight(emptyProof: SCProof, phi: K.Formula, j: proof.ProofTacticJudgement) { case (x1, (p1: SCProof, phi1, j1)) => + j1 match { + case proof.InvalidProofTactic(_) => (p1, phi1, j1) + case proof.ValidProofTactic(_, _) => { + if (!p1.conclusion.right.contains(phi1)) + (p1, phi1, proof.InvalidProofTactic("Formula is not present in the lass sequent")) + + val proofStep = K.RightForall(p1.conclusion ->> phi1 +>> forall(x1, phi1), p1.length - 1, phi1, x1) + ( + p1 appended proofStep, + forall(x1, phi1), + j1 + ) + } + } + } + + res._3 match { + case proof.InvalidProofTactic(_) => res._3 + case proof.ValidProofTactic(_, _) => proof.ValidProofTactic((res._1.steps appended K.Restate(bot, res._1.length - 1)), Seq(prem)) + } + + } else proof.InvalidProofTactic("RHS of premise sequent contains not phi") + + } + } + + object GeneralizeToForallNoForm extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + if (proof.getSequent(prem).right.tail.isEmpty) + GeneralizeToForall.apply(using lib, proof)(proof.getSequent(prem).right.head, t*)(prem)(bot): proof.ProofTacticJudgement + else + proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") + } + + } + + object ByCase extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val nphi = !phi + + val pa = proof.getSequent(prem1) + val pb = proof.getSequent(prem2) + val (leftAphi, leftBnphi) = (pa.left.find(K.isSame(_, phi)), pb.left.find(K.isSame(_, nphi))) + if (leftAphi.nonEmpty && leftBnphi.nonEmpty) { + val p2 = K.RightNot(pa -<< leftAphi.get +>> nphi, -1, phi) + val p3 = K.Cut(pa -<< leftAphi.get ++ (pb -<< leftBnphi.get), 0, -2, nphi) + val p4 = K.Restate(bot, 1) + proof.ValidProofTactic(IndexedSeq(p2, p3, p4), IndexedSeq(prem1, prem2)) // TODO: Check pa/pb orDer + + } else { + proof.InvalidProofTactic("Premises have not the right syntax") + } + } + } + */ +} diff --git a/backup/backup2/prooflib/WithTheorems.scala b/backup/backup2/prooflib/WithTheorems.scala new file mode 100644 index 00000000..d8f3e930 --- /dev/null +++ b/backup/backup2/prooflib/WithTheorems.scala @@ -0,0 +1,649 @@ +package lisa.prooflib + +import lisa.kernel.proof.RunningTheory +import lisa.prooflib.ProofTacticLib.ProofTactic +import lisa.prooflib.ProofTacticLib.UnimplementedProof +import lisa.prooflib.* +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.LisaException +import lisa.utils.UserLisaException +import lisa.utils.UserLisaException.* + +import scala.annotation.nowarn +import scala.collection.mutable.Buffer as mBuf +import scala.collection.mutable.Map as mMap +import scala.collection.mutable.Stack as stack + +trait WithTheorems { + library: Library => + + /** + * The main builder for proofs. It is a mutable object that can be used to build a proof step by step. + * It is used either to construct a theorem/lemma ([[BaseProof]]) or to construct a subproof ([[InnerProof]]). + * We can add proof tactics to it producing intermediate results. In the end, obtain a [[K.SCProof]] from it. + * + * @param assump list of starting assumptions, usually propagated from outer proofs. + */ + sealed abstract class Proof(assump: List[F.Formula]) { + val possibleGoal: Option[F.Sequent] + type SelfType = this.type + type OutsideFact >: JUSTIFICATION + type Fact = ProofStep | InstantiatedFact | OutsideFact | Int + + /** + * A proven fact (from a previously proven step, a theorem or a definition) with specific instantiations of free variables. + * + * @param fact The base fact + * @param insts The instantiation of free variables + */ + case class InstantiatedFact( + fact: Fact, + insts: Seq[F.SubstPair | F.Term] + ) { + val baseFormula: F.Sequent = sequentOfFact(fact) + val (result, proof) = { + val (terms, substPairs) = insts.partitionMap { + case t: F.Term => Left(t) + case sp: F.SubstPair => Right(sp) + } + + val (s1, p1) = if substPairs.isEmpty then (baseFormula, Seq()) else baseFormula.instantiateWithProof(substPairs.map(sp => (sp._1, sp._2)).toMap, -1) + val (s2, p2) = if terms.isEmpty then (s1, p1) else s1.instantiateForallWithProof(terms, p1.length - 1) + (s2, p1 ++ p2) + } + + } + + val library: WithTheorems.this.type = WithTheorems.this + + private var steps: List[ProofStep] = Nil + private var imports: List[(OutsideFact, F.Sequent)] = Nil + private var instantiatedFacts: List[(InstantiatedFact, Int)] = Nil + private var assumptions: List[F.Formula] = assump + private var eliminations: List[(F.Formula, (Int, F.Sequent) => List[K.SCProofStep])] = Nil + + def cleanAssumptions: Unit = assumptions = Nil + + /** + * the theorem that is being proved (paritally, if subproof) by this proof. + * + * @return The theorem + */ + def owningTheorem: THM + + /** + * A proof step, containing a high level ProofTactic and the corresponding K.SCProofStep. If the tactic produce more than one + * step, they must be encapsulated in a subproof. Usually constructed with [[ValidProofTactic.validate]] + * + * @param judgement The result of the tactic + * @param scps The corresponding [[K.SCProofStep]] + * @param position The position of the step in the proof + */ + case class ProofStep private (judgement: ValidProofTactic, scps: K.SCProofStep, position: Int) { + val bot: F.Sequent = judgement.bot + def innerBot: K.Sequent = scps.bot + val host: Proof.this.type = Proof.this + + def tactic: ProofTactic = judgement.tactic + + } + private object ProofStep { // TODO + def newProofStep(judgement: ValidProofTactic): ProofStep = { + val ps = ProofStep( + judgement, + SC.SCSubproof( + K.SCProof(judgement.scps.toIndexedSeq, judgement.imports.map(f => sequentOfFact(f).underlying).toIndexedSeq), + judgement.imports.map(sequentAndIntOfFact(_)._2) + ), + steps.length + ) + addStep(ps) + ps + + } + } + + /** + * A proof step can be constructed from a succesfully executed tactic + */ + def newProofStep(judgement: ValidProofTactic): ProofStep = + ProofStep.newProofStep(judgement) + + private def addStep(ds: ProofStep): Unit = steps = ds :: steps + private def addImport(imp: OutsideFact, seq: F.Sequent): Unit = { + imports = (imp, seq) :: imports + } + + private def addInstantiatedFact(instFact: InstantiatedFact): Unit = { + val step = ValidProofTactic(instFact.result, instFact.proof, Seq(instFact.fact))(using F.SequentInstantiationRule) + newProofStep(step) + instantiatedFacts = (instFact, steps.length - 1) :: instantiatedFacts + } + + /** + * Add an assumption the the proof, i.e. a formula that is automatically on the left side of the sequent. + * + * @param f + */ + def addAssumption(f: F.Formula): Unit = { + if (!assumptions.contains(f)) assumptions = f :: assumptions + } + + def addElimination(f: F.Formula, elim: (Int, F.Sequent) => List[K.SCProofStep]): Unit = { + eliminations = (f, elim) :: eliminations + } + + def addDischarge(ji: Fact): Unit = { + val (s1, t1) = sequentAndIntOfFact(ji) + val f = s1.right.head + val fu = f.underlying + addElimination( + f, + (i, sequent) => + List( + SC.Cut((sequent.underlying -<< fu) ++ (s1.underlying ->> fu), t1, i, fu) + ) + ) + } + /* + def addDefinition(v: LocalyDefinedVariable, defin: F.Formula): Unit = { + if localdefs.contains(v) then + throw new UserInvalidDefinitionException("v", "Variable already defined with" + v.definition + " in current proof") + else { + localdefs(v) = defin + addAssumption(defin) + } + } + def getDefinition(v: LocalyDefinedVariable): Fact = localdefs(v)._2 + */ + + // Getters + + /** + * Favour using getSequent when applicable. + * @return The list of ValidatedSteps (containing a high level ProofTactic and the corresponding K.SCProofStep). + */ + def getSteps: List[ProofStep] = steps.reverse + + /** + * Favour using getSequent when applicable. + * @return The list of Imports validated in the formula, with their original justification. + */ + def getImports: List[(OutsideFact, F.Sequent)] = imports.reverse + + /** + * @return The list of formulas that are assumed for the reminder of the proof. + */ + def getAssumptions: List[F.Formula] = assumptions + + /** + * Produce the low level [[K.SCProof]] corresponding to the proof. Automatically eliminates any formula in the discharges that is still left of the sequent. + * + * @return + */ + def toSCProof: K.SCProof = { + import lisa.utils.KernelHelpers.{-<<, ->>} + val finalSteps = eliminations.foldLeft[(List[SC.SCProofStep], F.Sequent)]((steps.map(_.scps), steps.head.bot)) { (cumul_bot, f_elim) => + val (cumul, bot) = cumul_bot + val (f, elim) = f_elim + val i = cumul.size + val elimSteps = elim(i - 1, bot) + (elimSteps.foldLeft(cumul)((cumul2, step) => step :: cumul2), bot -<< f) + } + + val r = K.SCProof(finalSteps._1.reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) + r + } + + def currentSCProof: K.SCProof = K.SCProof(steps.map(_.scps).reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) + + /** + * For a fact, returns the sequent that the fact proove and the position of the fact in the proof. + * + * @param fact Any fact, possibly instantiated, belonging to the proof + * @return its proven sequent and position + */ + def sequentAndIntOfFact(fact: Fact): (F.Sequent, Int) = fact match { + case i: Int => + ( + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(steps.length - i - 1).bot + else { + val i2 = -(i + 1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(imports.length + i)._2 + }, + i + ) + case ds: ProofStep => (ds.bot, ds.position) + case instFact: InstantiatedFact => + val r = instantiatedFacts.find(instFact == _._1) + r match { + case Some(value) => (instFact.result, value._2) + case None => + addInstantiatedFact(instFact) + (instFact.result, steps.length - 1) + } + case of: OutsideFact @unchecked => + val r = imports.indexWhere(of == _._1) + if (r != -1) { + (imports(r)._2, r - imports.length) + } else { + val r2 = sequentOfOutsideFact(of) + addImport(of, r2) + (r2, -imports.length) + } + } + + def sequentOfFact(fact: Fact): F.Sequent = fact match { + case i: Int => + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(steps.length - i - 1).bot + else { + val i2 = -(i + 1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(imports.length + i)._2 + } + case ds: ProofStep => ds.bot + case instfact: InstantiatedFact => instfact.result + case of: OutsideFact @unchecked => + val r = imports.find(of == _._1) + if (r.nonEmpty) { + r.get._2 + } else { + sequentOfOutsideFact(of) + } + } + + def sequentOfOutsideFact(of: OutsideFact): F.Sequent + + def getSequent(f: Fact): F.Sequent = sequentOfFact(f) + def mostRecentStep: ProofStep = steps.head + + /** + * The number of steps in the proof. This is not the same as the number of steps in the corresponding [[K.SCProof]]. + * This also does not count the number of steps in the subproof. + * + * @return + */ + def length: Int = steps.length + + /** + * The set of symbols that can't be instantiated because they are free in an assumption. + */ + def lockedSymbols: Set[F.Variable[?]] = assumptions.toSet.flatMap(f => f.freeVars.toSet) + + /** + * Used to "lift" the type of a justification when the compiler can't infer it. + */ + def asOutsideFact(j: JUSTIFICATION): OutsideFact + + def depth: Int = + (this: @unchecked) match { + case p: Proof#InnerProof => 1 + p.parent.depth + case _: BaseProof => 0 + } + + /** + * Create a subproof inside the current proof. The subproof will have the same assumptions as the current proof. + * Can have a goal known in advance (usually for a user-written subproof) or not (usually for a tactic-generated subproof). + */ + def newInnerProof(possibleGoal: Option[F.Sequent]) = new InnerProof(possibleGoal) + final class InnerProof(val possibleGoal: Option[F.Sequent]) extends Proof(this.getAssumptions) { + val parent: Proof.this.type = Proof.this + val owningTheorem: THM = parent.owningTheorem + type OutsideFact = parent.Fact + override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = parent.asOutsideFact(j) + + override def sequentOfOutsideFact(of: parent.Fact): F.Sequent = of match { + case j: JUSTIFICATION => j.statement + case ds: Proof#ProofStep => ds.bot + case _ => parent.sequentOfFact(of) + } + } + + /** + * Contains the result of a tactic computing a K.SCProofTactic. + * Can be successful or unsuccessful. + */ + sealed abstract class ProofTacticJudgement { + val tactic: ProofTactic + val proof: Proof = Proof.this + + /** + * Returns true if and only if the judgement is valid. + */ + def isValid: Boolean = this match { + case ValidProofTactic(_, _, _) => true + case InvalidProofTactic(_) => false + } + + def validate(line: sourcecode.Line, file: sourcecode.File): ProofStep = { + this match { + case vpt: ValidProofTactic => newProofStep(vpt) + case ipt: InvalidProofTactic => + val e = lisa.prooflib.ProofTacticLib.UnapplicableProofTactic(ipt.tactic, ipt.proof, ipt.message)(using line, file) + e.setStackTrace(ipt.stack) + throw e + } + } + } + + /** + * A Kernel Sequent Calculus proof step that has been correctly produced. + */ + case class ValidProofTactic(bot: lisa.fol.FOL.Sequent, scps: Seq[K.SCProofStep], imports: Seq[Fact])(using val tactic: ProofTactic) extends ProofTacticJudgement {} + + /** + * A proof step which led to an error when computing the corresponding K.Sequent Calculus proof step. + */ + case class InvalidProofTactic(message: String)(using val tactic: ProofTactic) extends ProofTacticJudgement { + private val nstack = Throwable() + val stack: Array[StackTraceElement] = nstack.getStackTrace.drop(2) + } + } + + /** + * Top-level instance of [[Proof]] directly proving a theorem + */ + sealed class BaseProof(val owningTheorem: THMFromProof) extends Proof(Nil) { + val goal: F.Sequent = owningTheorem.goal + val possibleGoal: Option[F.Sequent] = Some(goal) + type OutsideFact = JUSTIFICATION + override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = j + + override def sequentOfOutsideFact(j: JUSTIFICATION): F.Sequent = j.statement + + def justifications: List[JUSTIFICATION] = getImports.map(_._1) + } + + /** + * Abstract class representing theorems, axioms and different kinds of definitions. Corresponds to a [[theory.Justification]]. + */ + sealed abstract class JUSTIFICATION { + + /** + * A pretty representation of the justification + */ + def repr: String + + /** + * The inner kernel justification + */ + def innerJustification: theory.Justification + + /** + * The sequent that the justification proves + */ + def statement: F.Sequent + + /** + * The complete name of the justification. Two justifications should never have the same full name. Typically, path is used to disambiguate. + */ + def fullName: String + + /** + * The short name of the justification (without the path). + */ + val name: String = fullName.split("\\.").last + + /** + * The "owning" object of the justification. Typically, the package/object in which it is defined. + */ + val owner = fullName.split("\\.").dropRight(1).mkString(".") + + /** + * Returns if the statement is unconditionaly proven or if it depends on some sorry step (including in the other justifications it relies on) + */ + def withSorry: Boolean = innerJustification match { + case thm: theory.Theorem => thm.withSorry + case d: theory.Definition => false + case ax: theory.Axiom => false + } + } + + /** + * A Justification, corresponding to [[K.Axiom]] + */ + class AXIOM(innerAxiom: theory.Axiom, val axiom: F.Formula, val fullName: String) extends JUSTIFICATION { + def innerJustification: theory.Axiom = innerAxiom + val statement: F.Sequent = F.Sequent(Set(), Set(axiom)) + if (statement.underlying != theory.sequentFromJustification(innerAxiom)) { + throw new InvalidAxiomException("The provided kernel axiom and desired statement don't match.", name, axiom, library) + } + def repr: String = s" Axiom $name := $axiom" + } + + /** + * Introduces a new axiom in the theory. + * + * @param fullName The name of the axiom, including the path. Usually fetched automatically by the compiler. + * @param axiom The axiomatized formula. + * @return + */ + def Axiom(using fullName: sourcecode.FullName)(axiom: F.Formula): AXIOM = { + val ax: Option[theory.Axiom] = theory.addAxiom(fullName.value, axiom.underlying) + ax match { + case None => throw new InvalidAxiomException("Not all symbols belong to the theory", fullName.value, axiom, library) + case Some(value) => AXIOM(value, axiom, fullName.value) + } + } + + /** + * A Justification, corresponding to [[K.FunctionDefinition]] or [[K.PredicateDefinition]] + */ + abstract class DEFINITION(line: Int, file: String) extends JUSTIFICATION { + val fullName: String + def repr: String = innerJustification.repr + + def cst: F.Constant[?] + knownDefs.update(cst, Some(this)) + + } + + /** + * A proven, reusable statement. A justification corresponding to [[K.Theorem]]. + */ + sealed abstract class THM extends JUSTIFICATION { + def repr: String = + s" Theorem ${name} := ${statement}${if (withSorry) " (!! Relies on Sorry)" else ""}" + + /** + * The underlying Kernel proof [[K.SCProof]], if it is still available. Proofs are not kept in memory for efficiency. + */ + def kernelProof: Option[K.SCProof] + + /** + * The high level [[Proof]], if one was used to obtain the theorem. If the theorem was not produced by such high level proof but directly by a low level one, this is None. + */ + def highProof: Option[BaseProof] + val innerJustification: theory.Theorem + + /** + * A pretty representation of the goal of the theorem + */ + def prettyGoal: String = statement.underlying.repr + } + object THM { + + /** + * Standard way to construct a theorem using a high level proof. + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path. Usually fetched automatically by the compiler. + * @param line The line at which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting + * @param file The file in which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param computeProof The proof computation. The proof is built by adding proof steps to the proof object. The proof object is an impicit argument of computeProof, + * @see Context Functions in Scala + * @return + */ + def apply(using om: OutputManager)(statement: F.Sequent, fullName: String, line: Int, file: String, kind: TheoremKind)(computeProof: Proof ?=> Unit) = + THMFromProof(statement, fullName, line, file, kind)(computeProof) + + /** + * Constructs a "high level" theorem from an existing theorem in the + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path/package. + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param innerThm The inner theorem, coming from the kernel + * @param getProof If available, a way to compute the Kernel proof again. + */ + def fromKernel(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) = + THMFromKernel(statement, fullName, kind, innerThm, getProof) + + /** + * Construct a theorem (both in the kernel and high level) from a proof. + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path/package. + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param getProof The kernel proof. + * @param justifs low level justifications used to justify the proof's imports + * @return + */ + def fromSCProof(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, getProof: () => K.SCProof, justifs: Seq[theory.Justification]): THM = + val proof = getProof() + theory.theorem(fullName, statement.underlying, proof, justifs) match { + case K.Judgement.ValidJustification(just) => + fromKernel(statement, fullName, kind, just.asInstanceOf, () => Some(getProof())) + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The proof was rejected by LISA's logical kernel. ", + wrongJudgement, + None + ) + ) + } + + } + + /** + * A theorem that was produced from a kernel theorem and not from a high level proof. See [[THM.fromKernel]]. + * Those are typically theorems imported from another tool, or from serialization. + */ + class THMFromKernel(using om: OutputManager)(val statement: F.Sequent, val fullName: String, val kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) extends THM { + + val innerJustification: theory.Theorem = innerThm + assert(innerThm.name == fullName) + def kernelProof: Option[K.SCProof] = getProof() + def highProof: Option[BaseProof] = None + + val goal: F.Sequent = statement + + } + + /** + * A theorem that was produced from a high level proof. See [[THM.apply]]. + * Typical way to construct a theorem in the library, but serialization for example will produce a [[THMFromKernel]]. + */ + class THMFromProof(using om: OutputManager)(val statement: F.Sequent, val fullName: String, line: Int, file: String, val kind: TheoremKind)(computeProof: Proof ?=> Unit) extends THM { + + val goal: F.Sequent = statement + + val proof: BaseProof = new BaseProof(this) + def kernelProof: Option[K.SCProof] = Some(proof.toSCProof) + def highProof: Option[BaseProof] = Some(proof) + + import lisa.utils.Serialization.* + val innerJustification: theory.Theorem = + if library._draft.nonEmpty && library._draft.get.value != file + then // if the draft option is activated, and the theorem is not in the file where the draft option is given, then we replace the proof by sorry + theory.theorem(name, goal.underlying, SCProof(SC.Sorry(goal.underlying)), IndexedSeq.empty) match { + case K.Judgement.ValidJustification(just) => + just + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", + wrongJudgement, + Some(proof) + ) + ) + } + else if library._withCache then + oneThmFromFile("cache/" + name, library.theory) match { + case Some(thm) => thm // try to get the theorem from file + + case None => + val (thm, scp, justifs) = prove(computeProof) // if fail, prove it + thmsToFile("cache/" + name, theory, List((name, scp, justifs))) // and save it to the file + thm + } + else prove(computeProof)._1 + + library.last = Some(this) + + /** + * Construct the kernel theorem from the high level proof + */ + private def prove(computeProof: Proof ?=> Unit): (theory.Theorem, SCProof, List[(String, theory.Justification)]) = { + try { + computeProof(using proof) + } catch { + case e: UserLisaException => + om.lisaThrow(e) + } + + if (proof.length == 0) + om.lisaThrow(new UnimplementedProof(this)) + + val scp = proof.toSCProof + val justifs = proof.getImports.map(e => (e._1.owner, e._1.innerJustification)) + theory.theorem(name, goal.underlying, scp, justifs.map(_._2)) match { + case K.Judgement.ValidJustification(just) => + (just, scp, justifs) + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", + wrongJudgement, + Some(proof) + ) + ) + } + } + + } + + given thmConv: Conversion[library.THM, theory.Theorem] = _.innerJustification + + trait TheoremKind { + val kind2: String + + def apply(using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(statement: F.Sequent)(computeProof: Proof ?=> Unit): THM = { + val thm = THM(statement, name.value, line.value, file.value, this)(computeProof) + if this == Theorem then show(thm) + thm + } + + } + + /** + * A "Theorem" kind of theorem, by opposition with a lemma or corollary. The difference is that theorem are always printed when a file defining one is run. + */ + object Theorem extends TheoremKind { val kind2: String = "Theorem" } + + /** + * Lemmas are like theorems, but are conceptually less importants and are not printed when a file defining one is run. + */ + object Lemma extends TheoremKind { val kind2: String = "Lemma" } + + /** + * Corollaries are like theorems, but are conceptually less importants and are not printed when a file defining one is run. + */ + object Corollary extends TheoremKind { val kind2: String = "Corollary" } + + /** + * Internal statements are internally produced theorems, for example as intermediate step in definitions. + */ + object InternalStatement extends TheoremKind { val kind2: String = "Internal, automatically produced" } + +} diff --git a/lisa-utils/src/main/scala/lisa/fol/Common.scala b/backup/fol/Common.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/Common.scala rename to backup/fol/Common.scala diff --git a/lisa-utils/src/main/scala/lisa/fol/FOL.scala b/backup/fol/FOL.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/FOL.scala rename to backup/fol/FOL.scala diff --git a/lisa-utils/src/main/scala/lisa/fol/FOLHelpers.scala b/backup/fol/FOLHelpers.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/FOLHelpers.scala rename to backup/fol/FOLHelpers.scala diff --git a/lisa-utils/src/main/scala/lisa/fol/Lambdas.scala b/backup/fol/Lambdas.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/Lambdas.scala rename to backup/fol/Lambdas.scala diff --git a/lisa-utils/src/main/scala/lisa/fol/Predef.scala b/backup/fol/Predef.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/Predef.scala rename to backup/fol/Predef.scala diff --git a/lisa-utils/src/main/scala/lisa/fol/Sequents.scala b/backup/fol/Sequents.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/Sequents.scala rename to backup/fol/Sequents.scala diff --git a/backup/prooflib/BasicMain.scala b/backup/prooflib/BasicMain.scala new file mode 100644 index 00000000..748f58d5 --- /dev/null +++ b/backup/prooflib/BasicMain.scala @@ -0,0 +1,29 @@ +package lisa.prooflib + +import lisa.utils.Serialization.* + +trait BasicMain { + val library: Library + + private val realOutput: String => Unit = println + + val om: OutputManager = new OutputManager { + def finishOutput(exception: Exception): Nothing = { + log(exception) + main(Array[String]()) + sys.exit + } + val stringWriter: java.io.StringWriter = new java.io.StringWriter() + } + export om.output + + /** + * This specific implementation make sure that what is "shown" in theory files is only printed for the one we run, and not for the whole library. + */ + def main(args: Array[String]): Unit = { + realOutput(om.stringWriter.toString) + } + + given om.type = om + +} diff --git a/lisa-utils/src/main/scala/lisa/prooflib/BasicStepTactic.scala b/backup/prooflib/BasicStepTactic.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/BasicStepTactic.scala rename to backup/prooflib/BasicStepTactic.scala diff --git a/backup/prooflib/Exports.scala b/backup/prooflib/Exports.scala new file mode 100644 index 00000000..836d1cac --- /dev/null +++ b/backup/prooflib/Exports.scala @@ -0,0 +1,6 @@ +package lisa.prooflib + +object Exports { + export BasicStepTactic.* + export lisa.prooflib.SimpleDeducedSteps.* +} diff --git a/lisa-utils/src/main/scala/lisa/prooflib/Library.scala b/backup/prooflib/Library.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/Library.scala rename to backup/prooflib/Library.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/OutputManager.scala b/backup/prooflib/OutputManager.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/OutputManager.scala rename to backup/prooflib/OutputManager.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/ProofTacticLib.scala b/backup/prooflib/ProofTacticLib.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/ProofTacticLib.scala rename to backup/prooflib/ProofTacticLib.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala b/backup/prooflib/ProofsHelpers.scala similarity index 99% rename from lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala rename to backup/prooflib/ProofsHelpers.scala index f6f030f8..77ef9744 100644 --- a/lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala +++ b/backup/prooflib/ProofsHelpers.scala @@ -115,7 +115,7 @@ trait ProofsHelpers { def showCurrentProof(using om: OutputManager, _proof: library.Proof)(): Unit = { om.output("Current proof of " + _proof.owningTheorem.prettyGoal + ": ") om.output( - lisa.utils.parsing.ProofPrinter.prettyProof(_proof, 2) + lisa.utils.ProofPrinter.prettyProof(_proof, 2) ) } diff --git a/lisa-utils/src/main/scala/lisa/prooflib/SimpleDeducedSteps.scala b/backup/prooflib/SimpleDeducedSteps.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/SimpleDeducedSteps.scala rename to backup/prooflib/SimpleDeducedSteps.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/WithTheorems.scala b/backup/prooflib/WithTheorems.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/WithTheorems.scala rename to backup/prooflib/WithTheorems.scala diff --git a/backup/unification/UnificationUtils.scala b/backup/unification/UnificationUtils.scala new file mode 100644 index 00000000..dcc762fa --- /dev/null +++ b/backup/unification/UnificationUtils.scala @@ -0,0 +1,619 @@ +package lisa.utils.unification + +import lisa.fol.FOL.{_, given} +//import lisa.fol.FOLHelpers.* + +//import lisa.kernel.fol.FOL.* +//import lisa.utils.KernelHelpers.{_, given} + +/** + * General utilities for unification, substitution, and rewriting + */ +object UnificationUtils { + + extension [A](seq: Seq[A]) { + + /** + * Seq.collectFirst, but for a function returning an Option. Evaluates the + * function only once per argument. Returns when the first non-`None` value + * is found. + * + * @param T output type under option + * @param f the function to evaluate + */ + def getFirst[T](f: A => Option[T]): Option[T] = { + var res: Option[T] = None + val iter = seq.iterator + while (res.isEmpty && iter.hasNext) { + res = f(iter.next()) + } + res + } + + } + + /** + * All the information required for performing rewrites. + */ + case class RewriteContext( + freeFormulaRules: Seq[(Formula, Formula)] = Seq.empty, + freeTermRules: Seq[(Term, Term)] = Seq.empty, + confinedFormulaRules: Seq[(Formula, Formula)] = Seq.empty, + confinedTermRules: Seq[(Term, Term)] = Seq.empty, + takenFormulaVars: Set[VariableFormula] = Set.empty, + takenTermVars: Set[Variable] = Set.empty + ) { + private var lastID: Identifier = freshId((takenFormulaVars ++ takenTermVars).map(_.id), "@@rewriteVar@@") + + /** + * Generates a fresh identifier with an internal label `__rewriteVar__`. + * Mutates state. + * + * @return fresh identifier + */ + def freshIdentifier = { + lastID = freshId(Seq(lastID), "@@rewriteVar@@") + lastID + } + + def isFreeVariable(v: Variable) = !takenTermVars.contains(v) + def isFreeVariable(v: VariableFormula) = !takenFormulaVars.contains(v) + + /** + * Update the last generated fresh ID to that of another context if it is + * larger, otherwise retain the previous value. Mutates state. + * + * @param other another context + */ + def updateTo(other: RewriteContext) = + lastID = if (other.lastID.no > lastID.no) other.lastID else lastID + } + + object RewriteContext { + def empty = RewriteContext() + } + + // substitutions + + type TermSubstitution = Map[Variable, Term] + val TermSubstitution = Map // don't abuse pls O.o + + type FormulaSubstitution = Map[VariableFormula, Formula] + val FormulaSubstitution = Map + + /** + * Performs first-order matching for two terms. Returns a (most-general) + * substitution from variables to terms such that `first` substituted is equal TODO: Fix `first`and `second` + * to `second`, if one exists. Uses [[matchTermRecursive]] as the actual + * implementation. + * + * @param reference the reference term + * @param template the term to match + * @param takenVariables any variables in the template which cannot be + * substituted, i.e., treated as constant + * @return substitution (Option) from variables to terms. `None` iff a + * substitution does not exist. + */ + def matchTerm(reference: Term, template: Term, takenVariables: Iterable[Variable] = Iterable.empty): Option[TermSubstitution] = { + val context = RewriteContext(takenTermVars = takenVariables.toSet) + matchTermRecursive(using context)(reference, template, TermSubstitution.empty) + } + + /** + * Implementation for matching terms. See [[matchTerm]] for the interface. + * + * @param context all information about restricted variables and fresh name + * generation state + * @param reference the reference terms + * @param template the terms to match + * @param substitution currently accumulated susbtitutions to variables + * @return substitution (Option) from variables to terms. `None` if a + * substitution does not exist. + */ + private def matchTermRecursive(using context: RewriteContext)(reference: Term, template: Term, substitution: TermSubstitution): Option[TermSubstitution] = + if (reference == template) + Some(substitution) + else + template match { + case v @ Variable(id) if context.isFreeVariable(v) => + // different label but substitutable or already correctly set + if (reference != template && reference == substitution.getOrElse(v, reference)) Some(substitution + (v -> reference)) + // same and not already substituted to something else + else if (reference == template && reference == substitution.getOrElse(v, reference)) Some(substitution) + // unsat + else None + // {Constant, Schematic} FunctionLabel + case _ => + if (reference.label != template.label) None + else + (reference.args zip template.args).foldLeft(Option(substitution)) { + case (Some(subs), (r, t)) => matchTermRecursive(r, t, subs) + case (None, _) => None + } + } + + /** + * Performs first-order matching for two formulas. Returns a (most-general) + * substitution from variables to terms such that `first` substituted is equal + * to `second`, if one exists. Uses [[matchFormulaRecursive]] as the actual + * implementation. + * + * @param reference the reference formula + * @param template the formula to match + * @param takenTermVariables any variables in the template which cannot be + * substituted, i.e., treated as constant + * @param takenFormulaVariables any formula variables in the template which + * cannot be substituted, i.e., treated as constant + * @return substitution pair (Option) from formula variables to formulas, and + * variables to terms. `None` if a substitution does not exist. + */ + def matchFormula( + reference: Formula, + template: Formula, + takenTermVariables: Iterable[Variable] = Iterable.empty, + takenFormulaVariables: Iterable[VariableFormula] = Iterable.empty + ): Option[(FormulaSubstitution, TermSubstitution)] = { + val context = RewriteContext( + takenTermVars = takenTermVariables.toSet, + takenFormulaVars = takenFormulaVariables.toSet + ) + matchFormulaRecursive(using context)(reference, template, FormulaSubstitution.empty, TermSubstitution.empty) + } + + /** + * Implementation for matching formulas. See [[matchFormula]] for the + * interface. + * + * @param context all information about restricted variables and fresh name generation state + * @param reference the reference formula + * @param template the formula to match + * @param formulaSubstitution currently accumulated susbtitutions to formula variables + * @param termSubstitution currently accumulated susbtitutions to term variables + * @return substitution pair (Option) from formula variables to formulas, and + * variables to terms. `None` if a substitution does not exist. + */ + private def matchFormulaRecursive(using + context: RewriteContext + )(reference: Formula, template: Formula, formulaSubstitution: FormulaSubstitution, termSubstitution: TermSubstitution): Option[(FormulaSubstitution, TermSubstitution)] = { + if (isSame(reference, template)) + Some((formulaSubstitution, termSubstitution)) + else + (reference, template) match { + case (BinderFormula(labelR, boundR, innerR), BinderFormula(labelT, boundT, innerT)) if labelR == labelT => { + val freshVar = Variable(context.freshIdentifier) + + // add a safety substitution to make sure bound variable isn't substituted, and check instantiated bodies + val innerSubst = matchFormulaRecursive( + innerR.substitute(boundR := freshVar), + innerT.substitute(boundT := freshVar), + formulaSubstitution, + termSubstitution + (freshVar -> freshVar) // dummy substitution to make sure we don't attempt to match this as a variable + ) + + innerSubst match { + case None => innerSubst + case Some((sf, st)) => { + val cleanSubst = (sf, st - freshVar) // remove the dummy substitution we added + + // were any formula substitutions involving the bound variable required? + // if yes, not matchable + if (cleanSubst._1.exists((k, v) => v.freeVariables.contains(freshVar))) None + else Some(cleanSubst) + } + } + } + + case (AppliedConnector(labelR, argsR), AppliedConnector(labelT, argsT)) if labelR == labelT => + if (argsR.length != argsT.length) + // quick discard + None + else { + // recursively check inner formulas + val newSubstitution = (argsR zip argsT).foldLeft(Option(formulaSubstitution, termSubstitution)) { + case (Some(substs), (ref, temp)) => matchFormulaRecursive(ref, temp, substs._1, substs._2) + case (None, _) => None + } + newSubstitution + } + + case (_, template: VariableFormula) => + // can this variable be matched with the reference based on previously known or new substitutions? + if (reference == formulaSubstitution.getOrElse(template, reference)) Some(formulaSubstitution + (template -> reference), termSubstitution) + else if (template == reference && reference == formulaSubstitution.getOrElse(template, reference)) Some(formulaSubstitution, termSubstitution) + else None + + case (AppliedPredicate(labelR, argsR), AppliedPredicate(labelT, argsT)) if labelR == labelT => + if (argsR.length != argsT.length) + // quick discard + None + else { + // our arguments are terms, match them recursively + val newTermSubstitution = (argsR zip argsT).foldLeft(Option(termSubstitution)) { + case (Some(tSubst), (ref, temp)) => matchTermRecursive(ref, temp, tSubst) + case (None, _) => None + } + if (newTermSubstitution.isEmpty) None + else Some(formulaSubstitution, newTermSubstitution.get) + } + case _ => None + } + } + // rewrites + + /** + * A term rewrite rule (`l -> r`) with an accompanying instantiation, given + * by a term substitution. + * + * @example A rule without any instantiation would be `((l -> r), + * TermSubstitution.empty)`. + * @example Commutativity of a function with instantiation can be `((f(x, y) + * -> f(y, x)), Map(x -> pair(a, b), y -> c))` + */ + type TermRule = ((Term, Term), TermSubstitution) + + /** + * A formula rewrite rule (`l -> r`) with an accompanying instantiation, + * given by a formula and a term substitution. + * + * @example A rule without any instantiation would be `((l -> r), + * FormulaSubstitution.empty)`. + * @example `((P(x) \/ Q -> Q /\ R(x)), Map(Q -> A \/ B, x -> f(t)))` + */ + type FormulaRule = ((Formula, Formula), (FormulaSubstitution, TermSubstitution)) + + /** + * A lambda representing a term, with inputs as terms. Carries extra + * information about rewrite rules used in its construction for proof + * genration later. + * + * @param termVars variables in the body to be treated as parameters closed + * under this function + * @param termRules mapping to the rules (with instantiations) used to + * construct this function; used for proof construction + * @param body the body of the function + */ + case class TermRewriteLambda( + termVars: Seq[Variable] = Seq.empty, + termRules: Seq[(Variable, TermRule)] = Seq.empty, + body: Term + ) {} + + /** + * A lambda representing a formula, with inputs as terms or formulas. Carries + * extra information about rewrite rules used in its construction for proof + * geenration later. + * + * @param termVars variables in the body to be treated as parameters closed + * under this function + * @param formulaVars formula variables in the body to be treated as + * parameters closed under this function + * @param termRules mapping to the term rewrite rules (with instantiations) + * used to construct this function; used for proof construction + * @param formulaRules mapping to the formula rewrite rules (with + * instantiations) used to construct this function; used for proof + * construction + * @param body the body of the function + */ + case class FormulaRewriteLambda( + termRules: Seq[(Variable, TermRule)] = Seq.empty, + formulaRules: Seq[(VariableFormula, FormulaRule)] = Seq.empty, + body: Formula + ) { + + /** + * **Unsafe** conversion to a term lambda, discarding rule and formula information + * + * Use if **know that only term rewrites were applied**. + */ + def toLambdaTF: LambdaExpression[Term, Formula, ?] = LambdaExpression(termRules.map(_._1), body, termRules.size) + + /** + * **Unsafe** conversion to a formula lambda, discarding rule and term information + * + * Use if **know that only formula rewrites were applied**. + */ + def toLambdaFF: LambdaExpression[Formula, Formula, ?] = LambdaExpression(formulaRules.map(_._1), body, formulaRules.size) + } + + /** + * Dummy connector used to combine formulas for convenience during rewriting + */ + val formulaRewriteConnector = SchematicConnectorLabel(Identifier("@@rewritesTo@@"), 2) + + /** + * Dummy function symbol used to combine terms for convenience during rewriting + */ + val termRewriteConnector = ConstantFunctionLabel(Identifier("@@rewritesTo@@"), 2) + + /** + * Decides whether a term `first` be rewritten into `second` at the top level + * using the provided rewrite rule (with instantiation). + * + * Reduces to matching using [[matchTermRecursive]]. + */ + private def canRewrite(using context: RewriteContext)(first: Term, second: Term, rule: (Term, Term)): Option[TermSubstitution] = + matchTermRecursive(termRewriteConnector(first, second), termRewriteConnector(rule._1, rule._2), TermSubstitution.empty) + + /** + * Decides whether a formula `first` be rewritten into `second` at the top + * level using the provided rewrite rule (with instantiation). Produces the + * instantiation as output, if one exists. + * + * Reduces to matching using [[matchFormulaRecursive]]. + */ + private def canRewrite(using context: RewriteContext)(first: Formula, second: Formula, rule: (Formula, Formula)): Option[(FormulaSubstitution, TermSubstitution)] = + matchFormulaRecursive(formulaRewriteConnector(first, second), formulaRewriteConnector(rule._1, rule._2), FormulaSubstitution.empty, TermSubstitution.empty) + + /** + * Decides whether a term `first` can be rewritten into another term `second` + * under the given rewrite rules and restrictions. + * + * Calls [[getContextRecursive]] as its actual implementation. + * + * @param first source term + * @param second destination term + * @param freeTermRules rewrite rules with unrestricted instantiations + * @param confinedTermRules rewrite rules with restricted instantiations wrt takenTermVariables + * @param takenTermVariables variables to *not* instantiate, i.e., treat as constant, for confined rules + */ + def getContextTerm( + first: Term, + second: Term, + freeTermRules: Seq[(Term, Term)], + confinedTermRules: Seq[(Term, Term)] = Seq.empty, + takenTermVariables: Set[Variable] = Set.empty + ): Option[TermRewriteLambda] = { + val context = RewriteContext( + takenTermVars = takenTermVariables, + freeTermRules = freeTermRules, + confinedTermRules = confinedTermRules + ) + getContextRecursive(using context)(first, second) + } + + /** + * Inner implementation for [[getContextTerm]]. + * + * @param context all information about rewrite rules and allowed instantiations + * @param first source term + * @param second destination term + */ + private def getContextRecursive(using context: RewriteContext)(first: Term, second: Term): Option[TermRewriteLambda] = { + // check if there exists a substitution + lazy val validSubstitution = + context.confinedTermRules + .getFirst { case (l, r) => + val subst = canRewrite(using context)(first, second, (l, r)) + subst.map(s => ((l, r), s)) + } + .orElse { + // free all variables for substitution + // matchTermRecursive does not generate any free variables + // so it cannot affect global state, so this is safe to do + val freeContext = context.copy(takenTermVars = Set.empty) + freeContext.freeTermRules.getFirst { case (l, r) => + val subst = canRewrite(using freeContext)(first, second, (l, r)) + subst.map(s => ((l, r), s)) + } + } + + if (first == second) Some(TermRewriteLambda(body = first)) + else if (validSubstitution.isDefined) { + val newVar = Variable(context.freshIdentifier) + val body = newVar // newVar() + Some( + TermRewriteLambda( + Seq(newVar), + Seq(newVar -> validSubstitution.get), + body + ) + ) + } else if (first.label != second.label || first.args.length != second.args.length) None + else { + // recurse + // known: first.label == second.label + // first.args.length == second.args.length + // and first cannot be rewritten into second + val innerSubstitutions = (first.args zip second.args).map(arg => getContextRecursive(using context)(arg._1, arg._2)) + + if (innerSubstitutions.exists(_.isEmpty)) None + else { + val retrieved = innerSubstitutions.map(_.get) + val body = first.label.applySeq(retrieved.map(_.body)) + val lambda = + retrieved.foldLeft(TermRewriteLambda(body = body)) { case (currentLambda, nextLambda) => + TermRewriteLambda( + currentLambda.termVars ++ nextLambda.termVars, + currentLambda.termRules ++ nextLambda.termRules, + currentLambda.body + ) + } + Some(lambda) + } + } + } + + /** + * Decides whether a formula `first` can be rewritten into another formula + * `second` under the given rewrite rules and restrictions. + * + * Calls [[getContextRecursive]] as its actual implementation. + * + * @param first source formula + * @param second destination formula + * @param freeTermRules term rewrite rules with unrestricted instantiations + * @param freeFormulaRules formula rewrite rules with unrestricted + * instantiations + * @param confinedTermRules term rewrite rules with restricted instantiations + * wrt takenTermVariables + * @param confinedTermRules formula rewrite rules with restricted + * instantiations wrt takenTermVariables + * @param takenTermVariables term variables to *not* instantiate, i.e., treat + * as constant, for confined rules + * @param takenFormulaVariables formula variables to *not* instantiate, i.e., + * treat as constant, for confined rules + */ + def getContextFormula( + first: Formula, + second: Formula, + freeTermRules: Seq[(Term, Term)] = Seq.empty, + freeFormulaRules: Seq[(Formula, Formula)] = Seq.empty, + confinedTermRules: Seq[(Term, Term)] = Seq.empty, + takenTermVariables: Set[Variable] = Set.empty, + confinedFormulaRules: Seq[(Formula, Formula)] = Seq.empty, + takenFormulaVariables: Set[VariableFormula] = Set.empty + ): Option[FormulaRewriteLambda] = { + val context = RewriteContext( + takenTermVars = takenTermVariables, + takenFormulaVars = takenFormulaVariables, + freeTermRules = freeTermRules, + confinedTermRules = confinedTermRules, + freeFormulaRules = freeFormulaRules, + confinedFormulaRules = confinedFormulaRules + ) + getContextRecursive(using context)(first, second) + } + + def getContextFormulaSet( + first: Seq[Formula], + second: Seq[Formula], + freeTermRules: Seq[(Term, Term)], + freeFormulaRules: Seq[(Formula, Formula)], + confinedTermRules: Seq[(Term, Term)] = Seq.empty, + takenTermVariables: Set[Variable] = Set.empty, + confinedFormulaRules: Seq[(Formula, Formula)] = Seq.empty, + takenFormulaVariables: Set[VariableFormula] = Set.empty + ): Option[Seq[FormulaRewriteLambda]] = { + val context = RewriteContext( + takenTermVars = takenTermVariables, + takenFormulaVars = takenFormulaVariables, + freeTermRules = freeTermRules, + confinedTermRules = confinedTermRules, + freeFormulaRules = freeFormulaRules, + confinedFormulaRules = confinedFormulaRules + ) + + val substSeq = first.map { f => + second.getFirst { s => + val newContext = context.copy() + val subst = getContextRecursive(using newContext)(f, s) + subst.foreach { _ => context.updateTo(newContext) } + subst + } + } + + // Seq[Option[_]] -> Option[Seq[_]] + substSeq.foldLeft(Option(Seq.empty[FormulaRewriteLambda]))((f, s) => f.flatMap(f1 => s.map(s1 => f1 :+ s1))) + } + + /** + * Inner implementation for [[getContextFormula]]. + * + * @param context all information about rewrite rules and allowed instantiations + * @param first source formula + * @param second destination formula + */ + private def getContextRecursive(using context: RewriteContext)(first: Formula, second: Formula): Option[FormulaRewriteLambda] = { + // check if there exists a substitution + lazy val validSubstitution = + context.confinedFormulaRules + .getFirst { (l: Formula, r: Formula) => + val subst = canRewrite(using context)(first, second, (l, r)) + subst.map(s => ((l, r), s)) + } + .orElse { + // free all variables for substitution + // matchFormulaRecursive generates but does not expose any new variables + // It cannot affect global state, so this is safe to do + val freeContext = context.copy(takenTermVars = Set.empty) + freeContext.freeFormulaRules.getFirst { case (l, r) => + val subst = canRewrite(using freeContext)(first, second, (l, r)) + subst.map(s => ((l, r), s)) + } + } + + if (isSame(first, second)) Some(FormulaRewriteLambda(body = first)) + else if (validSubstitution.isDefined) { + val newVar = VariableFormula(context.freshIdentifier) + val body = newVar // newVar() + Some( + FormulaRewriteLambda( + Seq(), + Seq(newVar -> validSubstitution.get), + body + ) + ) + } // else if (first.label != second.label) None //Should not pass the next match anyway + else { + // recurse + // known: first.label == second.label + // and first cannot be rewritten into second + (first, second) match { + case (BinderFormula(labelF, boundF, innerF), BinderFormula(labelS, boundS, innerS)) => { + val freshVar = Variable(context.freshIdentifier) + val freeContext = context.copy(takenTermVars = context.takenTermVars + freshVar) + + // add a safety substitution to make sure bound variable isn't substituted, and check instantiated bodies + val innerSubst = getContextRecursive(using freeContext)( + innerF.substitute(boundF := freshVar), + innerS.substitute(boundS := freshVar) + ) + + context.updateTo(freeContext) + + innerSubst.map(s => s.copy(body = BinderFormula(labelF, freshVar, s.body))) + } + + case (AppliedConnector(labelF, argsF), AppliedConnector(labelS, argsS)) => + if (argsF.length != argsS.length) + // quick discard + None + else { + // recursively check inner formulas + val innerSubstitutions = (argsF zip argsS).map(arg => getContextRecursive(using context)(arg._1, arg._2)) + + if (innerSubstitutions.exists(_.isEmpty)) None + else { + val retrieved = innerSubstitutions.map(_.get) + val body = AppliedConnector(labelF, retrieved.map(_.body)) + val lambda = + retrieved.foldLeft(FormulaRewriteLambda(body = body)) { case (currentLambda, nextLambda) => + FormulaRewriteLambda( + currentLambda.termRules ++ nextLambda.termRules, + currentLambda.formulaRules ++ nextLambda.formulaRules, + currentLambda.body + ) + } + Some(lambda) + } + } + + case (AppliedPredicate(labelF, argsF), AppliedPredicate(labelS, argsS)) => + if (argsF.length != argsS.length) + // quick discard + None + else { + // our arguments are terms, get contexts from them recursively + val innerSubstitutions = (argsF zip argsS).map(arg => getContextRecursive(using context)(arg._1, arg._2)) + + if (innerSubstitutions.exists(_.isEmpty)) None + else { + val retrieved = innerSubstitutions.map(_.get) + val body = AppliedPredicate(labelF, retrieved.map(_.body)) + val lambda = + retrieved.foldLeft(FormulaRewriteLambda(body = body)) { case (currentLambda, nextLambda) => + FormulaRewriteLambda( + currentLambda.termRules ++ nextLambda.termRules, + currentLambda.formulaRules, + currentLambda.body + ) + } + Some(lambda) + } + } + case _ => None + } + } + } + +} diff --git a/build.sbt b/build.sbt index 4a3136c1..ceadfb1c 100644 --- a/build.sbt +++ b/build.sbt @@ -32,7 +32,8 @@ val commonSettings3 = commonSettings ++ Seq( scalacOptions ++= Seq( "-language:implicitConversions", //"-rewrite", "-source", "3.4-migration", - "-Wconf:msg=.*will never be selected.*:silent" + "-Wconf:msg=.*will never be selected.*:silent", + "-language:experimental.modularity" ), javaOptions += "-Xmx10G", diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/CommonDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/CommonDefinitions.scala similarity index 96% rename from lisa-kernel/src/main/scala/lisa/kernel/fol/CommonDefinitions.scala rename to lisa-kernel/src/main/scala/lisa/kernel/exfol/CommonDefinitions.scala index 6f65ffdf..679e7ec4 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/CommonDefinitions.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/CommonDefinitions.scala @@ -1,9 +1,9 @@ -package lisa.kernel.fol +package lisa.kernel.exfol /** * Definitions that are common to terms and formulas. */ -private[fol] trait CommonDefinitions { +private[exfol] trait CommonDefinitions { val MaxArity: Int = 1000000 /** diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/EquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/EquivalenceChecker.scala similarity index 99% rename from lisa-kernel/src/main/scala/lisa/kernel/fol/EquivalenceChecker.scala rename to lisa-kernel/src/main/scala/lisa/kernel/exfol/EquivalenceChecker.scala index d323f621..20d80c24 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/EquivalenceChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/EquivalenceChecker.scala @@ -1,4 +1,4 @@ -package lisa.kernel.fol +package lisa.kernel.exfol import scala.collection.mutable import scala.math.Numeric.IntIsIntegral @@ -13,7 +13,7 @@ import scala.math.Numeric.IntIsIntegral * of equality and alpha-equivalence. * See https://github.com/epfl-lara/OCBSL for more informations */ -private[fol] trait EquivalenceChecker extends FormulaDefinitions { +private[exfol] trait EquivalenceChecker extends FormulaDefinitions { def reducedForm(formula: Formula): Formula = { val p = simplify(formula) diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exfol/FOL.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FOL.scala new file mode 100644 index 00000000..7a0cc33c --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FOL.scala @@ -0,0 +1,10 @@ +package lisa.kernel.exfol + +/** + * The concrete implementation of first order logic. + * All its content can be imported using a single statement: + *
+ * import lisa.fol.FOL._
+ * 
+ */ +object FOL extends FormulaDefinitions with EquivalenceChecker with Substitutions {} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaDefinitions.scala similarity index 97% rename from lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaDefinitions.scala rename to lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaDefinitions.scala index 192460ce..07c48a0d 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaDefinitions.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaDefinitions.scala @@ -1,10 +1,10 @@ -package lisa.kernel.fol +package lisa.kernel.exfol /** * Definitions of formulas; analogous to [[TermDefinitions]]. * Depends on [[FormulaLabelDefinitions]] and [[TermDefinitions]]. */ -private[fol] trait FormulaDefinitions extends FormulaLabelDefinitions with TermDefinitions { +private[exfol] trait FormulaDefinitions extends FormulaLabelDefinitions with TermDefinitions { type SimpleFormula def reducedForm(formula: Formula): Formula @@ -22,7 +22,7 @@ private[fol] trait FormulaDefinitions extends FormulaLabelDefinitions with TermD */ sealed trait Formula extends TreeWithLabel[FormulaLabel] { val uniqueNumber: Long = Formula.getNewId - private[fol] var polarFormula: Option[SimpleFormula] = None + private[exfol] var polarFormula: Option[SimpleFormula] = None val arity: Int = label.arity override def constantTermLabels: Set[ConstantFunctionLabel] diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaLabelDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaLabelDefinitions.scala similarity index 97% rename from lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaLabelDefinitions.scala rename to lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaLabelDefinitions.scala index 61e75077..59b38c2f 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaLabelDefinitions.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaLabelDefinitions.scala @@ -1,9 +1,9 @@ -package lisa.kernel.fol +package lisa.kernel.exfol /** * Definitions of formula labels. Analogous to [[TermLabelDefinitions]]. */ -private[fol] trait FormulaLabelDefinitions extends CommonDefinitions { +private[exfol] trait FormulaLabelDefinitions extends CommonDefinitions { /** * The parent class of formula labels. diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/Substitutions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/Substitutions.scala similarity index 99% rename from lisa-kernel/src/main/scala/lisa/kernel/fol/Substitutions.scala rename to lisa-kernel/src/main/scala/lisa/kernel/exfol/Substitutions.scala index 51bfb465..278682ab 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/Substitutions.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/Substitutions.scala @@ -1,4 +1,4 @@ -package lisa.kernel.fol +package lisa.kernel.exfol trait Substitutions extends FormulaDefinitions { diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/TermDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermDefinitions.scala similarity index 97% rename from lisa-kernel/src/main/scala/lisa/kernel/fol/TermDefinitions.scala rename to lisa-kernel/src/main/scala/lisa/kernel/exfol/TermDefinitions.scala index 6e1b9b5c..bf25f09f 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/TermDefinitions.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermDefinitions.scala @@ -1,9 +1,9 @@ -package lisa.kernel.fol +package lisa.kernel.exfol /** * Definitions of terms; depends on [[TermLabelDefinitions]]. */ -private[fol] trait TermDefinitions extends TermLabelDefinitions { +private[exfol] trait TermDefinitions extends TermLabelDefinitions { protected trait TreeWithLabel[A] { val label: A diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/TermLabelDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermLabelDefinitions.scala similarity index 95% rename from lisa-kernel/src/main/scala/lisa/kernel/fol/TermLabelDefinitions.scala rename to lisa-kernel/src/main/scala/lisa/kernel/exfol/TermLabelDefinitions.scala index 610ffe8a..209820ce 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/TermLabelDefinitions.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermLabelDefinitions.scala @@ -1,9 +1,9 @@ -package lisa.kernel.fol +package lisa.kernel.exfol /** * Definitions of term labels. */ -private[fol] trait TermLabelDefinitions extends CommonDefinitions { +private[exfol] trait TermLabelDefinitions extends CommonDefinitions { /** * The parent class of term labels. diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/Judgement.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/Judgement.scala new file mode 100644 index 00000000..f7e774c6 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exproof/Judgement.scala @@ -0,0 +1,76 @@ +package lisa.kernel.exproof + +import lisa.kernel.exproof.RunningTheory + +/** + * The judgement (or verdict) of a proof checking procedure. + * Typically, see [[SCProofChecker.checkSingleSCStep]] and [[SCProofChecker.checkSCProof]]. + */ +sealed abstract class SCProofCheckerJudgement { + import SCProofCheckerJudgement._ + val proof: SCProof + + /** + * 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 class SCValidProof(proof: SCProof, val usesSorry: Boolean = false) 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(proof: SCProof, path: Seq[Int], message: String) extends SCProofCheckerJudgement +} + +/** + * The judgement (or verdict) of a running theory. + */ +sealed abstract class RunningTheoryJudgement[+J <: RunningTheory#Justification] { + import RunningTheoryJudgement._ + + /** + * Whether this judgement is positive -- the justification could be imported into the running theory; + * or negative -- the justification is not suitable to be imported in the theory. + * @return An instance of either [[ValidJustification]] or [[InvalidJustification]] + */ + def isValid: Boolean = this match { + case _: ValidJustification[_] => true + case _: InvalidJustification[_] => false + } + def get: J = this match { + case ValidJustification(just) => just + case InvalidJustification(message, error) => + throw InvalidJustificationException(message, error) + } +} + +object RunningTheoryJudgement { + + /** + * A positive judgement. + */ + case class ValidJustification[J <: RunningTheory#Justification](just: J) extends RunningTheoryJudgement[J] + + /** + * A negative judgement. + * @param error If the justification is rejected because the proof is wrong, will contain the error in the proof. + * @param message The error message that hints about the first error encountered + */ + case class InvalidJustification[J <: RunningTheory#Justification](message: String, error: Option[SCProofCheckerJudgement.SCInvalidProof]) extends RunningTheoryJudgement[J] + + case class InvalidJustificationException(message: String, error: Option[SCProofCheckerJudgement.SCInvalidProof]) extends Exception(message) +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/RunningTheory.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/RunningTheory.scala new file mode 100644 index 00000000..dc35bfaf --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exproof/RunningTheory.scala @@ -0,0 +1,379 @@ +package lisa.kernel.exproof + +import lisa.kernel.exfol.FOL._ +import lisa.kernel.exproof.RunningTheoryJudgement._ +import lisa.kernel.exproof.SequentCalculus._ + +import scala.collection.immutable.Set +import scala.collection.mutable.{Map => mMap} + +/** + * This class describes the theory, i.e. the context and language, in which theorems are proven. + * A theory is built from scratch by introducing axioms and symbols first, then by definitional extensions. + * The structure is one-way mutable: Once an axiom or definition has been introduced, it can't be removed. + * On the other hand, theorems proven before the theory is extended will still hold. + * A theorem only holds true within a specific theory. + * A theory is responsible to make sure that a symbol already defined or present in the language can't + * be redefined. If a theory needs to be extanded in two different ways, or if a theory and its extension need + * to coexist independently, they should be different instances of this class. + */ +class RunningTheory { + + /** + * A Justification is either a Theorem, an Axiom or a Definition + */ + sealed abstract class Justification + + /** + * A theorem encapsulate a sequent and assert that this sequent has been correctly proven and may be used safely in further proofs. + */ + sealed case class Theorem private[RunningTheory] (name: String, proposition: Sequent, withSorry: Boolean) extends Justification + + /** + * An axiom is any formula that is assumed and considered true within the theory. It can freely be used later in any proof. + */ + sealed case class Axiom private[RunningTheory] (name: String, ax: Formula) extends Justification + + /** + * A definition can be either a PredicateDefinition or a FunctionDefinition. + */ + sealed abstract class Definition extends Justification + + /** + * Define a predicate symbol as a shortcut for a formula. Example : P(x,y) := ∃!z. (x=y+z) + * + * @param label The name and arity of the new symbol + * @param expression The formula, depending on terms, that define the symbol. + */ + sealed case class PredicateDefinition private[RunningTheory] (label: ConstantAtomicLabel, expression: LambdaTermFormula) extends Definition + + /** + * Define a function symbol as the unique element that has some property. The existence and uniqueness + * of that elements must have been proven before obtaining such a definition. Example + * f(x,y) := the "z" s.t. x=y+z + * + * @param label The name and arity of the new symbol + * @param out The variable representing the result of the function in phi + * @param expression The formula, with term parameters, defining the function. + * @param withSorry Stores if Sorry was used to in the proof used to define the symbol, or one of its ancestor. + */ + sealed case class FunctionDefinition private[RunningTheory] (label: ConstantFunctionLabel, out: VariableLabel, expression: LambdaTermFormula, withSorry: Boolean) extends Definition + + private[exproof] val theoryAxioms: mMap[String, Axiom] = mMap.empty + private[exproof] val theorems: mMap[String, Theorem] = mMap.empty + + private[exproof] val funDefinitions: mMap[ConstantFunctionLabel, Option[FunctionDefinition]] = mMap.empty + private[exproof] val predDefinitions: mMap[ConstantAtomicLabel, Option[PredicateDefinition]] = mMap(equality -> None, top -> None, bot -> None) + + private[exproof] val knownSymbols: mMap[Identifier, ConstantLabel] = mMap(equality.id -> equality) + + /** + * From a given proof, if it is true in the Running theory, add that theorem to the theory and returns it. + * The proof's imports must be justified by the list of justification, and the conclusion of the theorem + * can't contain symbols that do not belong to the theory. + * + * @param justifications The list of justifications of the proof's imports. + * @param proof The proof of the desired Theorem. + * @return A Theorem if the proof is correct, None else + */ + def makeTheorem(name: String, statement: Sequent, proof: SCProof, justifications: Seq[Justification]): RunningTheoryJudgement[this.Theorem] = { + if (proof.conclusion == statement) proofToTheorem(name, proof, justifications) + else InvalidJustification("The proof does not prove the claimed statement", None) + } + + private def proofToTheorem(name: String, proof: SCProof, justifications: Seq[Justification]): RunningTheoryJudgement[this.Theorem] = + if (proof.imports.forall(i => justifications.exists(j => isSameSequent(i, sequentFromJustification(j))))) + if (belongsToTheory(proof.conclusion)) { + val r = SCProofChecker.checkSCProof(proof) + r match { + case SCProofCheckerJudgement.SCValidProof(_, sorry) => + val usesSorry = sorry || justifications.exists(_ match { + case Theorem(name, proposition, withSorry) => withSorry + case Axiom(name, ax) => false + case d: Definition => + d match { + case PredicateDefinition(label, expression) => false + case FunctionDefinition(label, out, expression, withSorry) => withSorry + } + }) + val thm = Theorem(name, proof.conclusion, usesSorry) + theorems.update(name, thm) + ValidJustification(thm) + case r @ SCProofCheckerJudgement.SCInvalidProof(_, _, message) => + InvalidJustification("The given proof is incorrect: " + message, Some(r)) + } + } else InvalidJustification("All symbols in the conclusion of the proof must belong to the theory. You need to add missing symbols to the theory.", None) + else InvalidJustification("Not all imports of the proof are correctly justified.", None) + + /** + * Introduce a new definition of a predicate in the theory. The symbol must not already exist in the theory + * and the formula can't contain symbols that are not in the theory. + * + * @param label The desired label. + * @param expression The functional formula defining the predicate. + * @return A definition object if the parameters are correct, + */ + def makePredicateDefinition(label: ConstantAtomicLabel, expression: LambdaTermFormula): RunningTheoryJudgement[this.PredicateDefinition] = { + val LambdaTermFormula(vars, body) = expression + if (belongsToTheory(body)) + if (isAvailable(label)) + if (body.freeSchematicTermLabels.subsetOf(vars.toSet) && body.schematicAtomicLabels.isEmpty) { + val newDef = PredicateDefinition(label, expression) + predDefinitions.update(label, Some(newDef)) + knownSymbols.update(label.id, label) + RunningTheoryJudgement.ValidJustification(newDef) + } else InvalidJustification("The definition is not allowed to contain schematic symbols or free variables.", None) + else InvalidJustification("The specified symbol id is already part of the theory and can't be redefined.", None) + else InvalidJustification("All symbols in the conclusion of the proof must belong to the theory. You need to add missing symbols to the theory.", None) + } + + /** + * Introduce a new definition of a function in the theory. The symbol must not already exist in the theory + * and the formula can't contain symbols that are not in the theory. The existence and uniqueness of an element + * satisfying the definition's formula must first be proven. This is easy if the formula behaves as a shortcut, + * for example f(x,y) = 3x+2y + * but is much more general. The proof's conclusion must be of the form: |- ∀args. ∃!out. phi + * + * @param proof The proof of existence and uniqueness + * @param justifications The justifications of the proof. + * @param label The desired label. + * @param expression The functional term defining the function symbol. + * @param out The variable representing the function's result in the formula + * @param proven A formula possibly stronger than `expression` that the proof proves. It is always correct if it is the same as "expression", but + * if `expression` is less strong, this allows to make underspecified definitions. + * @return A definition object if the parameters are correct, + */ + def makeFunctionDefinition( + proof: SCProof, + justifications: Seq[Justification], + label: ConstantFunctionLabel, + out: VariableLabel, + expression: LambdaTermFormula, + proven: Formula + ): RunningTheoryJudgement[this.FunctionDefinition] = { + val LambdaTermFormula(vars, body) = expression + if (vars.length == label.arity) { + if (belongsToTheory(body)) { + if (isAvailable(label)) { + if (body.freeSchematicTermLabels.subsetOf((vars appended out).toSet) && body.schematicFormulaLabels.isEmpty) { + if (proof.imports.forall(i => justifications.exists(j => isSameSequent(i, sequentFromJustification(j))))) { + val r = SCProofChecker.checkSCProof(proof) + r match { + case SCProofCheckerJudgement.SCValidProof(_, sorry) => + proof.conclusion match { + case Sequent(l, r) if l.isEmpty && r.size == 1 => + if (isImplying(proven, body)) { + val subst = BinderFormula(ExistsOne, out, proven) + if (isSame(r.head, subst)) { + val usesSorry = sorry || justifications.exists(_ match { + case Theorem(name, proposition, withSorry) => withSorry + case Axiom(name, ax) => false + case d: Definition => + d match { + case PredicateDefinition(label, expression) => false + case FunctionDefinition(label, out, expression, withSorry) => withSorry + } + }) + val newDef = FunctionDefinition(label, out, expression, usesSorry) + funDefinitions.update(label, Some(newDef)) + knownSymbols.update(label.id, label) + RunningTheoryJudgement.ValidJustification(newDef) + } else InvalidJustification("The proof is correct but its conclusion does not correspond to the claimed proven property.", None) + } else InvalidJustification("The proven property must be at least as strong as the desired definition, and it is not.", None) + + case _ => InvalidJustification("The conclusion of the proof must have an empty left hand side, and a single formula on the right hand side.", None) + } + case r @ SCProofCheckerJudgement.SCInvalidProof(_, path, message) => InvalidJustification("The given proof is incorrect: " + message, Some(r)) + } + } else InvalidJustification("Not all imports of the proof are correctly justified.", None) + } else InvalidJustification("The definition is not allowed to contain schematic symbols or free variables.", None) + } else InvalidJustification("The specified symbol id is already part of the theory and can't be redefined.", None) + } else InvalidJustification("All symbols in the conclusion of the proof must belong to the theory. You need to add missing symbols to the theory.", None) + } else InvalidJustification("The arity of the label must be equal to the number of parameters in the definition.", None) + } + + def sequentFromJustification(j: Justification): Sequent = j match { + case Theorem(name, proposition, _) => proposition + case Axiom(name, ax) => Sequent(Set.empty, Set(ax)) + case PredicateDefinition(label, LambdaTermFormula(vars, body)) => + val inner = ConnectorFormula(Iff, Seq(AtomicFormula(label, vars.map(VariableTerm.apply)), body)) + Sequent(Set(), Set(inner)) + case FunctionDefinition(label, out, LambdaTermFormula(vars, body), _) => + val inner = BinderFormula( + Forall, + out, + ConnectorFormula( + Iff, + Seq( + AtomicFormula(equality, Seq(Term(label, vars.map(VariableTerm.apply)), VariableTerm(out))), + body + ) + ) + ) + Sequent(Set(), Set(inner)) + + } + + /** + * Add a new axiom to the Theory. For example, if the theory contains the language and theorems + * of Zermelo-Fraenkel Set Theory, this function may add the axiom of choice to it. + * If the axiom belongs to the language of the theory, adds it and return true. Else, returns false. + * + * @param f the new axiom to be added. + * @return true if the axiom was added to the theory, false else. + */ + def addAxiom(name: String, f: Formula): Option[Axiom] = { + if (belongsToTheory(f)) { + val ax = Axiom(name, f) + theoryAxioms.update(name, ax) + Some(ax) + } else None + } + + /** + * Add a new symbol to the theory, without providing a definition. An ad-hoc definition can be + * added via an axiom, typically if the desired object is not derivable in the base theory itself. + * For example, This function can add the empty set symbol to a theory, and then an axiom asserting + * that it is empty can be introduced as well. + */ + + def addSymbol(s: ConstantLabel): Unit = { + if (isAvailable(s)) { + knownSymbols.update(s.id, s) + s match { + case c: ConstantFunctionLabel => funDefinitions.update(c, None) + case c: ConstantAtomicLabel => predDefinitions.update(c, None) + } + } else {} + } + + /** + * Add all constant symbols in the sequent. Note that this can't be reversed and will prevent from giving them a definition later. + */ + def makeFormulaBelongToTheory(phi: Formula): Unit = { + phi.constantAtomicLabels.foreach(addSymbol) + phi.constantTermLabels.foreach(addSymbol) + } + + /** + * Add all constant symbols in the sequent. Note that this can't be reversed and will prevent from giving them a definition later. + */ + def makeSequentBelongToTheory(s: Sequent): Unit = { + s.left.foreach(makeFormulaBelongToTheory) + s.right.foreach(makeFormulaBelongToTheory) + } + + /** + * Verify if a given formula belongs to some language + * + * @param phi The formula to check + * @return Weather phi belongs to the specified language + */ + def belongsToTheory(phi: Formula): Boolean = phi match { + case AtomicFormula(label, args) => + label match { + case l: ConstantAtomicLabel => isSymbol(l) && args.forall(belongsToTheory) + case _ => args.forall(belongsToTheory) + } + case ConnectorFormula(label, args) => args.forall(belongsToTheory) + case BinderFormula(label, bound, inner) => belongsToTheory(inner) + } + + /** + * Verify if a given term belongs to the language of the theory. + * + * @param t The term to check + * @return Weather t belongs to the specified language. + */ + def belongsToTheory(t: Term): Boolean = t match { + case Term(label, args) => + label match { + case l: ConstantFunctionLabel => isSymbol(l) && args.forall(belongsToTheory) + case _: SchematicTermLabel => args.forall(belongsToTheory) + } + + } + + /** + * Verify if a given sequent belongs to the language of the theory. + * + * @param s The sequent to check + * @return Weather s belongs to the specified language + */ + def belongsToTheory(s: Sequent): Boolean = + s.left.forall(belongsToTheory) && s.right.forall(belongsToTheory) + + /** + * Public accessor to the set of symbol currently in the theory's language. + * + * @return the set of symbol currently in the theory's language. + */ + def language(): List[(ConstantLabel, Option[Definition])] = funDefinitions.toList ++ predDefinitions.toList + + /** + * Check if a label is a symbol of the theory. + */ + def isSymbol(label: ConstantLabel): Boolean = label match { + case c: ConstantFunctionLabel => funDefinitions.contains(c) + case c: ConstantAtomicLabel => predDefinitions.contains(c) + } + + /** + * Check if a label is not already used in the theory. + * @return + */ + def isAvailable(label: ConstantLabel): Boolean = !knownSymbols.contains(label.id) + + /** + * Public accessor to the current set of axioms of the theory + * + * @return the current set of axioms of the theory + */ + def axiomsList(): Iterable[Axiom] = theoryAxioms.values + + /** + * Verify if a given formula is an axiom of the theory + */ + def isAxiom(f: Formula): Boolean = theoryAxioms.exists(a => isSame(a._2.ax, f)) + + /** + * Get the Axiom that is the same as the given formula, if it exists in the theory. + */ + def getAxiom(f: Formula): Option[Axiom] = theoryAxioms.find(a => isSame(a._2.ax, f)).map(_._2) + + /** + * Get the definition of the given label, if it is defined in the theory. + */ + def getDefinition(label: ConstantAtomicLabel): Option[PredicateDefinition] = predDefinitions.get(label).flatten + + /** + * Get the definition of the given label, if it is defined in the theory. + */ + def getDefinition(label: ConstantFunctionLabel): Option[FunctionDefinition] = funDefinitions.get(label).flatten + + /** + * Get the Axiom with the given name, if it exists in the theory. + */ + def getAxiom(name: String): Option[Axiom] = theoryAxioms.get(name) + + /** + * Get the Theorem with the given name, if it exists in the theory. + */ + def getTheorem(name: String): Option[Theorem] = theorems.get(name) + + /** + * Get the definition for the given identifier, if it is defined in the theory. + */ + def getDefinition(name: Identifier): Option[Definition] = knownSymbols.get(name).flatMap { + case f: ConstantAtomicLabel => getDefinition(f) + case f: ConstantFunctionLabel => getDefinition(f) + } + +} +object RunningTheory { + + /** + * An empty theory suitable to reason about first order logic. + */ + def PredicateLogic: RunningTheory = new RunningTheory() +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProof.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProof.scala new file mode 100644 index 00000000..21846db3 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProof.scala @@ -0,0 +1,97 @@ +package lisa.kernel.exproof + +import lisa.kernel.exproof.SequentCalculus._ + +/** + * A SCPRoof (for Sequent Calculus Proof) is a (dependant) proof. While technically a proof is an Directed Acyclic Graph, + * here proofs are linearized and represented as a list of proof steps. + * Moreover, a proof can depend on some assumed, unproved, sequents specified in the second argument + * @param steps A list of Proof Steps that should form a valid proof. Each individual step should only refer to earlier + * proof steps as premisces. + * @param imports A list of assumed sequents that further steps may refer to. Imports are refered to using negative integers + * To refer to the first sequent of imports, use integer -1. + */ +case class SCProof(steps: IndexedSeq[SCProofStep], imports: IndexedSeq[Sequent] = IndexedSeq.empty) { + def numberedSteps: Seq[(SCProofStep, Int)] = steps.zipWithIndex + + /** + * Fetches the ith step of the proof. + * @param i the index + * @return a step + */ + def apply(i: Int): SCProofStep = { + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(i) + else throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + } + + /** + * Get the ith sequent of the proof. If the index is positive, give the bottom sequent of proof step number i. + * If the index is negative, return the (-i-1)th imported sequent. + * + * @param i The reference number of a sequent in the proof + * @return A sequent, either imported or reached during the proof. + */ + def getSequent(i: Int): Sequent = { + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(i).bot + else { + val i2 = -(i + 1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(i2) + } + } + + /** + * The length of the proof in terms of top-level steps, without including the imports. + */ + def length: Int = steps.length + + /** + * The total length of the proof in terms of proof-step, including steps in subproof, but excluding the imports. + */ + def totalLength: Int = steps.foldLeft(0)((i, s) => + i + (s match { + case s: SCSubproof => s.sp.totalLength + 1 + case _ => 1 + }) + ) + + /** + * The conclusion of the proof, namely the bottom sequent of the last proof step. + * Can be undefined if the proof is empty. + */ + def conclusion: Sequent = { + if (steps.isEmpty && imports.isEmpty) throw new NoSuchElementException("conclusion of an empty proof") + this.getSequent(length - 1) + } + + /** + * A helper method that creates a new proof with a new step appended at the end. + * @param newStep the new step to be added + * @return a new proof + */ + def appended(newStep: SCProofStep): SCProof = copy(steps = steps appended newStep) + + /** + * A helper method that creates a new proof with a sequence of new steps appended at the end. + * @param newSteps the sequence of steps to be added + * @return a new proof + */ + def withNewSteps(newSteps: IndexedSeq[SCProofStep]): SCProof = copy(steps = steps ++ newSteps) +} + +object SCProof { + + /** + * Instantiates a proof from an indexed list of proof steps. + * @param steps the steps of the proof + * @return the corresponding proof + */ + def apply(steps: SCProofStep*): SCProof = { + SCProof(steps.toIndexedSeq) + } + +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProofChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProofChecker.scala new file mode 100644 index 00000000..1dcb768a --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProofChecker.scala @@ -0,0 +1,567 @@ +package lisa.kernel.exproof + +import lisa.kernel.exfol.FOL._ +import lisa.kernel.exproof.SCProofCheckerJudgement._ +import lisa.kernel.exproof.SequentCalculus._ + +object SCProofChecker { + + /** + * This function verifies that a single SCProofStep is correctly applied. It verifies that the step only refers to sequents with a lower number, + * and that the type, premises and parameters of the proof step correspond to the claimed conclusion. + * + * @param no The number of the given proof step. Needed to vewrify that the proof step doesn't refer to posterior sequents. + * @param step The proof step whose correctness needs to be checked + * @param references A function that associates sequents to a range of positive and negative integers that the proof step may refer to. Typically, + * a proof's [[SCProof.getSequent]] function. + * @return A Judgement about the correctness of the proof step. + */ + def checkSingleSCStep(no: Int, step: SCProofStep, references: Int => Sequent, importsSize: Int): SCProofCheckerJudgement = { + val ref = references + val false_premise = step.premises.find(i => i >= no) + val false_premise2 = step.premises.find(i => i < -importsSize) + + val r: SCProofCheckerJudgement = + if (false_premise.nonEmpty) + SCInvalidProof(SCProof(step), Nil, s"Step no $no can't refer to higher number ${false_premise.get} as a premise.") + else if (false_premise2.nonEmpty) + SCInvalidProof(SCProof(step), Nil, s"A step can't refer to step ${false_premise2.get}, imports only contains ${importsSize} elements.") + else + step match { + /* + * Γ |- Δ + * ------------ + * Γ |- Δ + */ + case Restate(s, t1) => + if (isSameSequent(ref(t1), s)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"The premise does not trivially imply the conclusion.") + + /* + * + * ------------ + * Γ |- Γ + */ + case RestateTrue(s) => + val truth = Sequent(Set(), Set(AtomicFormula(top, Nil))) + if (isSameSequent(s, truth)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"The desired conclusion is not a trivial tautology") + /* + * + * -------------- + * Γ, φ |- φ, Δ + */ + case Hypothesis(Sequent(left, right), phi) => + if (contains(left, phi)) + if (contains(right, phi)) SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side does not contain formula φ") + else SCInvalidProof(SCProof(step), Nil, s"Left-hand side does not contain formula φ") + /* + * Γ |- Δ, φ φ, Σ |- Π + * ------------------------ + * Γ, Σ |- Δ, Π + */ + case Cut(b, t1, t2, phi) => + if (isSameSet(b.left + phi, ref(t1).left union ref(t2).left) && (!contains(ref(t1).left, phi) || contains(b.left, phi))) + if (isSameSet(b.right + phi, ref(t2).right union ref(t1).right) && (!contains(ref(t2).right, phi) || contains(b.right, phi))) + if (contains(ref(t2).left, phi)) + if (contains(ref(t1).right, phi)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of first premise does not contain φ as claimed.") + else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of second premise does not contain φ as claimed.") + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ is not the union of the right-hand sides of the premises.") + else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion + φ is not the union of the left-hand sides of the premises.") + + // Left rules + /* + * Γ, φ |- Δ Γ, φ, ψ |- Δ + * -------------- or ------------- + * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ + */ + case LeftAnd(b, t1, phi, psi) => + if (isSameSet(ref(t1).right, b.right)) { + val phiAndPsi = ConnectorFormula(And, Seq(phi, psi)) + 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) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ∧ψ must be same as left-hand side of premise + either φ, ψ or both.") + } else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion must be the same.") + /* + * Γ, φ |- Δ Σ, ψ |- Π + * ------------------------ + * Γ, Σ, φ∨ψ |- Δ, Π + */ + case LeftOr(b, t, disjuncts) => + if (isSameSet(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _))) { + val phiOrPsi = ConnectorFormula(Or, disjuncts) + if ( + t.zip(disjuncts).forall { case (s, phi) => isSubset(ref(s).left, b.left + phi) } && + isSubset(b.left, t.map(ref(_).left).fold(Set.empty)(_ union _) + phiOrPsi) + ) + + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), 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(SCProof(step), Nil, s"Right-hand side of conclusion is not the union of the right-hand sides of the premises.") + /* + * Γ |- φ, Δ Σ, ψ |- Π + * ------------------------ + * Γ, Σ, φ⇒ψ |- Δ, Π + */ + case LeftImplies(b, t1, t2, phi, psi) => + 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)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion + ψ must be identical to union of left-hand sides of premisces + φ⇒ψ.") + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ must be identical to union of right-hand sides of premisces.") + /* + * Γ, φ⇒ψ |- Δ Γ, φ⇒ψ, ψ⇒φ |- Δ + * -------------- or --------------- + * Γ, φ⇔ψ |- Δ Γ, φ⇔ψ |- Δ + */ + case LeftIff(b, t1, phi, psi) => + val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) + val psiImpPhi = ConnectorFormula(Implies, Seq(psi, phi)) + val phiIffPsi = ConnectorFormula(Iff, Seq(phi, psi)) + if (isSameSet(ref(t1).right, b.right)) + 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) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ⇔ψ must be same as left-hand side of premise + either φ⇒ψ, ψ⇒φ or both.") + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of premise and conclusion must be the same.") + + /* + * Γ |- φ, Δ + * -------------- + * Γ, ¬φ |- Δ + */ + case LeftNot(b, t1, phi) => + val nPhi = ConnectorFormula(Neg, Seq(phi)) + if (isSameSet(b.left, ref(t1).left + nPhi)) + if (isSameSet(b.right + phi, ref(t1).right)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise") + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion must be the same as left-hand side of premise + ¬φ") + + /* + * Γ, φ[t/x] |- Δ + * ------------------- + * Γ, ∀x. φ |- Δ + */ + case LeftForall(b, t1, phi, x, t) => + if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + substituteVariablesInFormula(phi, Map(x -> t)), ref(t1).left + BinderFormula(Forall, x, phi))) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ[t/x] must be the same as left-hand side of premise + ∀x. φ") + else SCInvalidProof(SCProof(step), 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 + * Γ, ∃x. φ|- Δ + */ + case LeftExists(b, t1, phi, x) => + 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))) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "The variable x must not be free in the resulting sequent.") + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise + ∃x. φ") + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") + + /* + * Γ, ∃y.∀x. (x=y) ⇔ φ |- Δ + * ---------------------------- if y is not free in φ + * Γ, ∃!x. φ |- Δ + */ + case LeftExistsOne(b, t1, phi, x) => + val y = VariableLabel(freshId(phi.freeVariables.map(_.id), x.id)) + val temp = BinderFormula(Exists, y, BinderFormula(Forall, x, ConnectorFormula(Iff, List(AtomicFormula(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))) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ must be the same as left-hand side of premise + ∃!x. φ") + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") + + // Right rules + /* + * Γ |- φ, Δ Σ |- ψ, Π + * ------------------------ + * Γ, Σ |- φ∧ψ, Π, Δ + */ + case RightAnd(b, t, cunjuncts) => + val phiAndPsi = ConnectorFormula(And, cunjuncts) + if (isSameSet(b.left, t.map(ref(_).left).fold(Set.empty)(_ union _))) + if ( + t.zip(cunjuncts).forall { case (s, phi) => isSubset(ref(s).right, b.right + phi) } && + isSubset(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _) + phiAndPsi) + //isSameSet(cunjuncts.foldLeft(b.right)(_ + _), t.map(ref(_).right).fold(Set.empty)(_ union _) + phiAndPsi) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises φ∧ψ.") + else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + /* + * Γ |- φ, Δ Γ |- φ, ψ, Δ + * -------------- or --------------- + * Γ |- φ∨ψ, Δ Γ |- φ∨ψ, Δ + */ + case RightOr(b, t1, phi, psi) => + val phiOrPsi = ConnectorFormula(Or, Seq(phi, psi)) + if (isSameSet(ref(t1).left, b.left)) + 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) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ∧ψ must be same as right-hand side of premise + either φ, ψ or both.") + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the premise and the conclusion must be the same.") + /* + * Γ, φ |- ψ, Δ + * -------------- + * Γ |- φ⇒ψ, Δ + */ + case RightImplies(b, t1, phi, psi) => + val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) + if (isSameSet(ref(t1).left, b.left + phi)) + if (isSameSet(b.right + psi, ref(t1).right + phiImpPsi)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + ψ must be same as right-hand side of premise + φ⇒ψ.") + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + psi must be same as left-hand side of premise.") + /* + * Γ |- φ⇒ψ, Δ Σ |- ψ⇒φ, Π + * ---------------------------- + * Γ, Σ |- φ⇔ψ, Π, Δ + */ + case RightIff(b, t1, t2, phi, psi) => + val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) + val psiImpPhi = ConnectorFormula(Implies, Seq(psi, phi)) + val phiIffPsi = ConnectorFormula(Iff, Seq(phi, psi)) + if (isSameSet(b.left, ref(t1).left union ref(t2).left)) + if ( + isSubset(ref(t1).right, b.right + phiImpPsi) && + isSubset(ref(t2).right, b.right + psiImpPhi) && + isSubset(b.right, ref(t1).right union ref(t2).right + phiIffPsi) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), 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(SCProof(step), Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + /* + * Γ, φ |- Δ + * -------------- + * Γ |- ¬φ, Δ + */ + case RightNot(b, t1, phi) => + val nPhi = ConnectorFormula(Neg, Seq(phi)) + if (isSameSet(b.right, ref(t1).right + nPhi)) + if (isSameSet(b.left + phi, ref(t1).left)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise") + else SCInvalidProof(SCProof(step), 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 + * Γ |- ∀x. φ, Δ + */ + case RightForall(b, t1, phi, x) => + 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))) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "The variable x must not be free in the resulting sequent.") + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise + ∀x. φ") + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of conclusion and premise must be the same.") + /* + * Γ |- φ[t/x], Δ + * ------------------- + * Γ |- ∃x. φ, Δ + */ + case RightExists(b, t1, phi, x, t) => + if (isSameSet(b.left, ref(t1).left)) + if (isSameSet(b.right + substituteVariablesInFormula(phi, Map(x -> t)), ref(t1).right + BinderFormula(Exists, x, phi))) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of the conclusion + φ[t/x] must be the same as right-hand side of the premise + ∃x. φ") + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides or conclusion and premise must be the same.") + + /** + *
+           * Γ |- ∃y.∀x. (x=y) ⇔ φ, Δ
+           * ---------------------------- if y is not free in φ
+           * Γ|- ∃!x. φ,  Δ
+           * 
+ */ + case RightExistsOne(b, t1, phi, x) => + val y = VariableLabel(freshId(phi.freeVariables.map(_.id), x.id)) + val temp = BinderFormula(Exists, y, BinderFormula(Forall, x, ConnectorFormula(Iff, List(AtomicFormula(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))) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ must be the same as right-hand side of premise + ∃!x. φ") + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of conclusion and premise must be the same") + + // Structural rules + /* + * Γ |- Δ + * -------------- + * Γ, Σ |- Δ + */ + case Weakening(b, t1) => + if (isImplyingSequent(ref(t1), b)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Conclusion cannot be trivially derived from premise.") + + // Equality Rules + /* + * Γ, s=s |- Δ + * -------------- + * Γ |- Δ + */ + case LeftRefl(b, t1, phi) => + phi match { + case AtomicFormula(`equality`, Seq(left, right)) => + if (isSameTerm(left, right)) + if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + phi, ref(t1).left)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Left-hand sides of the conclusion + φ must be the same as left-hand side of the premise.") + else SCInvalidProof(SCProof(step), Nil, s"Right-hand sides of the premise and the conclusion aren't the same.") + else SCInvalidProof(SCProof(step), Nil, s"φ is not an instance of reflexivity.") + case _ => SCInvalidProof(SCProof(step), Nil, "φ is not an equality") + } + + /* + * + * -------------- + * |- s=s + */ + case RightRefl(b, phi) => + phi match { + case AtomicFormula(`equality`, Seq(left, right)) => + if (isSameTerm(left, right)) + if (contains(b.right, phi)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Right-Hand side of conclusion does not contain φ") + else SCInvalidProof(SCProof(step), Nil, s"φ is not an instance of reflexivity.") + case _ => SCInvalidProof(SCProof(step), Nil, s"φ is not an equality.") + } + + /* + * Γ, φ(s_) |- Δ + * --------------------- + * Γ, (s=t)_, φ(t_)|- Δ + */ + case LeftSubstEq(b, t1, equals, lambdaPhi) => + val (s_es, t_es) = equals.unzip + val (phi_args, phi_body) = lambdaPhi + if (phi_args.size != s_es.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. + SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") + else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) + SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") + else { + val phi_s_for_f = instantiateTermSchemas(phi_body, (phi_args zip s_es).toMap) + val phi_t_for_f = instantiateTermSchemas(phi_body, (phi_args zip t_es).toMap) + val sEqT_es = equals map { + case (s, t) => + assert(s.vars.size == t.vars.size) + val base = AtomicFormula(equality, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) + (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } + } + + if (isSameSet(b.right, ref(t1).right)) + if ( + isSameSet(b.left + phi_t_for_f, ref(t1).left ++ sEqT_es + phi_s_for_f) || + isSameSet(b.left + phi_s_for_f, ref(t1).left ++ sEqT_es + phi_t_for_f) + ) + SCValidProof(SCProof(step)) + else + SCInvalidProof( + SCProof(step), + Nil, + "Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_) (or with s_ and t_ swapped)." + ) + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + } + + /* + * Γ |- φ(s_), Δ + * --------------------- + * Γ, (s=t)_ |- φ(t_), Δ + */ + case RightSubstEq(b, t1, equals, lambdaPhi) => + val (s_es, t_es) = equals.unzip + val (phi_args, phi_body) = lambdaPhi + if (phi_args.size != equals.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. + SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") + else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) + SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") + else { + val phi_s_for_f = instantiateTermSchemas(phi_body, (phi_args zip s_es).toMap) + val phi_t_for_f = instantiateTermSchemas(phi_body, (phi_args zip t_es).toMap) + val sEqT_es = equals map { + case (s, t) => + assert(s.vars.size == t.vars.size) + val base = AtomicFormula(equality, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) + (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } + } + + if (isSameSet(ref(t1).left ++ sEqT_es, b.left)) + 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) + ) + SCValidProof(SCProof(step)) + else + SCInvalidProof( + SCProof(step), + Nil, + "Right-hand side of the premise and the conclusion should be the same with each containing one of φ(s_) φ(t_), but it isn't the case." + ) + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the premise + (s=t)_ must be the same as left-hand side of the premise.") + } + + /* + * Γ, φ(ψ_) |- Δ + * --------------------- + * Γ, ψ⇔τ, φ(τ) |- Δ + */ + case LeftSubstIff(b, t1, equals, lambdaPhi) => + val (phi_s, tau_s) = equals.unzip + val (phi_args, phi_body) = lambdaPhi + if (phi_args.size != phi_s.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. + SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") + else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) + SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") + else { + val phi_psi_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip phi_s).toMap) + val phi_tau_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip tau_s).toMap) + val psiIffTau = equals map { + case (s, t) => + assert(s.vars.size == t.vars.size) + val base = ConnectorFormula(Iff, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) + (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } + } + + if (isSameSet(b.right, ref(t1).right)) + if ( + isSameSet(b.left + phi_tau_for_q, ref(t1).left ++ psiIffTau + phi_psi_for_q) || + isSameSet(b.left + phi_psi_for_q, ref(t1).left ++ psiIffTau + phi_tau_for_q) + ) + SCValidProof(SCProof(step)) + else + SCInvalidProof( + SCProof(step), + Nil, + "Left-hand sides of the conclusion + φ(ψ_) must be the same as left-hand side of the premise + (ψ⇔τ)_ + φ(τ_) (or with ψ and τ swapped)." + ) + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + } + + /* + * Γ |- φ[ψ/?p], Δ + * --------------------- + * Γ, ψ⇔τ |- φ[τ/?p], Δ + */ + case RightSubstIff(b, t1, equals, lambdaPhi) => + val (psi_s, tau_s) = equals.unzip + val (phi_args, phi_body) = lambdaPhi + if (phi_args.size != psi_s.size) + SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") + else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) + SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") + else { + val phi_psi_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip psi_s).toMap) + val phi_tau_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip tau_s).toMap) + val psiIffTau = equals map { + case (s, t) => + assert(s.vars.size == t.vars.size) + val base = ConnectorFormula(Iff, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) + (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } + } + + 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) + ) + SCValidProof(SCProof(step)) + else + SCInvalidProof( + SCProof(step), + Nil, + "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(SCProof(step), Nil, "Left-hand sides of the premise + ψ⇔τ must be the same as left-hand side of the premise.") + } + + + + /** + *
+           * Γ |- Δ
+           * --------------------------
+           * Γ[ψ/?p] |- Δ[ψ/?p]
+           * 
+ */ + case InstSchema(bot, t1, mCon, mPred, mTerm) => + val expected = + (ref(t1).left.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm)), ref(t1).right.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm))) + if (isSameSet(bot.left, expected._1)) + if (isSameSet(bot.right, expected._2)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of premise instantiated with the given maps must be the same as right-hand side of conclusion.") + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of premise instantiated with the given maps 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 { case (no, p) => !isSameSequent(ref(no), sp.imports(p)) } + if (invalid.isEmpty) { + checkSCProof(sp) + } else + SCInvalidProof( + SCProof(step), + 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(SCProof(step), Nil, "Number of premises and imports don't match: " + premises.size + " " + sp.imports.size) + + /* + * + * -------------- + * |- s=s + */ + case Sorry(b) => + SCValidProof(SCProof(step), usesSorry = true) + + } + r + } + + /** + * Verifies if a given pure SequentCalculus is conditionally correct, as the imported sequents are assumed. + * If the proof is not correct, the function will report the faulty line and a brief explanation. + * + * @param proof A SC proof to check + * @return SCValidProof(SCProof(step)) if the proof is correct, else SCInvalidProof with the path to the incorrect proof step + * and an explanation. + */ + def checkSCProof(proof: SCProof): SCProofCheckerJudgement = { + var isSorry = false + val possibleError = proof.steps.view.zipWithIndex + .map { case (step, no) => + checkSingleSCStep(no, step, (i: Int) => proof.getSequent(i), proof.imports.size) match { + case SCInvalidProof(_, path, message) => SCInvalidProof(proof, no +: path, message) + case SCValidProof(_, sorry) => + isSorry = isSorry || sorry + SCValidProof(proof, sorry) + } + } + .find(j => !j.isValid) + if (possibleError.isEmpty) SCValidProof(proof, isSorry) + else possibleError.get + } + +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SequentCalculus.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SequentCalculus.scala new file mode 100644 index 00000000..ef9cbd9b --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SequentCalculus.scala @@ -0,0 +1,348 @@ +package lisa.kernel.exproof + +import lisa.kernel.exfol.FOL._ + +/** + * The concrete implementation of sequent calculus (with equality). + * This file specifies the sequents and the allowed operations on them, the deduction rules of sequent calculus. + * It contains typical sequent calculus rules for FOL with equality as can be found in a text book, as well as a couple more for + * non-elementary symbols (⇔, ∃!) and rules for substituting equal terms or equivalent formulas. I also contains two structural rules, + * subproof and a dummy rewrite step. + * Further mathematical steps, such as introducing or using definitions, axioms or theorems are not part of the basic sequent calculus. + */ +object SequentCalculus { + + /** + * A sequent is an object that can contain two sets of formulas, [[left]] and [[right]]. + * The intended semantic is for the [[left]] formulas to be interpreted as a conjunction, while the [[right]] ones as a disjunction. + * Traditionally, sequents are represented by two lists of formulas. + * Since sequent calculus includes rules for permuting and weakening, it is in essence equivalent to sets. + * Seqs make verifying proof steps much easier, but proof construction much more verbose and proofs longer. + * @param left the left side of the sequent + * @param right the right side of the sequent + */ + case class Sequent(left: Set[Formula], right: Set[Formula]) + + /** + * Simple method that transforms a sequent to a logically equivalent formula. + */ + def sequentToFormula(s: Sequent): Formula = ConnectorFormula(Implies, List(ConnectorFormula(And, s.left.toSeq), ConnectorFormula(Or, s.right.toSeq))) + + /** + * Checks whether two sequents are equivalent, with respect to [[isSameTerm]]. + * + * @param l the first sequent + * @param r the second sequent + * @return see [[isSameTerm]] + */ + def isSameSequent(l: Sequent, r: Sequent): Boolean = isSame(sequentToFormula(l), sequentToFormula(r)) + + /** + * Checks whether a given sequent implies another, with respect to [[latticeLEQ]]. + * + * @param l the first sequent + * @param r the second sequent + * @return see [[latticeLEQ]] + */ + def isImplyingSequent(l: Sequent, r: Sequent): Boolean = isImplying(sequentToFormula(l), sequentToFormula(r)) + + /** + * The parent of all proof steps types. + * A proof step is a deduction rule of sequent calculus, with the sequents forming the prerequisite and conclusion. + * For easier linearisation of the proof, the prerequisite are represented with numbers showing the place in the proof of the sequent used. + */ + + /** + * The parent of all sequent calculus rules. + */ + sealed trait SCProofStep { + val bot: Sequent + val premises: Seq[Int] + } + + /** + *
+   *    Γ |- Δ
+   * ------------
+   *    Γ |- Δ  (OL rewrite)
+   * 
+ */ + case class Restate(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *
+   * ------------
+   *    Γ |- Γ  (OL tautology)
+   * 
+ */ + case class RestateTrue(bot: Sequent) extends SCProofStep { val premises = Seq() } + + /** + *
+   *
+   * --------------
+   *   Γ, φ |- φ, Δ
+   * 
+ */ + case class Hypothesis(bot: Sequent, phi: Formula) extends SCProofStep { val premises = Seq() } + + /** + *
+   *  Γ |- Δ, φ    φ, Σ |- Π
+   * ------------------------
+   *       Γ, Σ |-Δ, Π
+   * 
+ */ + case class Cut(bot: Sequent, t1: Int, t2: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + + // Left rules + /** + *
+   *   Γ, φ |- Δ                Γ, φ, ψ |- Δ
+   * --------------     or     --------------
+   *  Γ, φ∧ψ |- Δ               Γ, φ∧ψ |- Δ
+   * 
+ */ + case class LeftAnd(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *  Γ, φ |- Δ    Σ, ψ |- Π    ...
+   * --------------------------------
+   *    Γ, Σ, φ∨ψ∨... |- Δ, Π
+   * 
+ */ + case class LeftOr(bot: Sequent, t: Seq[Int], disjuncts: Seq[Formula]) extends SCProofStep { val premises = t } + + /** + *
+   *  Γ |- φ, Δ    Σ, ψ |- Π
+   * ------------------------
+   *    Γ, Σ, φ⇒ψ |- Δ, Π
+   * 
+ */ + case class LeftImplies(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + + /** + *
+   *  Γ, φ⇒ψ |- Δ               Γ, φ⇒ψ, ψ⇒φ |- Δ
+   * --------------    or     --------------------
+   *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
+   * 
+ */ + case class LeftIff(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *   Γ |- φ, Δ
+   * --------------
+   *   Γ, ¬φ |- Δ
+   * 
+ */ + case class LeftNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *   Γ, φ[t/x] |- Δ
+   * -------------------
+   *  Γ, ∀ φ |- Δ
+   *
+   * 
+ */ + case class LeftForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel, t: Term) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ, φ |- Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ, ∃x φ|- Δ
+   *
+   * 
+ */ + case class LeftExists(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *  Γ, ∃y.∀x. (x=y) ⇔ φ |-  Δ
+   * ---------------------------- if y is not free in φ
+   *      Γ, ∃!x. φ |- Δ
+   * 
+ */ + case class LeftExistsOne(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + + // Right rules + /** + *
+   *  Γ |- φ, Δ    Σ |- ψ, Π     ...
+   * ------------------------------------
+   *    Γ, Σ |- φ∧ψ∧..., Π, Δ
+   * 
+ */ + case class RightAnd(bot: Sequent, t: Seq[Int], cunjuncts: Seq[Formula]) extends SCProofStep { val premises = t } + + /** + *
+   *   Γ |- φ, Δ                Γ |- φ, ψ, Δ
+   * --------------    or    ---------------
+   *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
+   * 
+ */ + case class RightOr(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *  Γ, φ |- ψ, Δ
+   * --------------
+   *  Γ |- φ⇒ψ, Δ
+   * 
+ */ + case class RightImplies(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *  Γ |- a⇒ψ, Δ    Σ |- ψ⇒φ, Π
+   * ----------------------------
+   *      Γ, Σ |- φ⇔ψ, Π, Δ
+   * 
+ */ + case class RightIff(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + + /** + *
+   *  Γ, φ |- Δ
+   * --------------
+   *   Γ |- ¬φ, Δ
+   * 
+ */ + case class RightNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ |- φ, Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ |- ∀x. φ, Δ
+   * 
+ */ + case class RightForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *   Γ |- φ[t/x], Δ
+   * -------------------
+   *  Γ |- ∃x. φ, Δ
+   *
+   * (ln-x stands for locally nameless x)
+   * 
+ */ + case class RightExists(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel, t: Term) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *  Γ |- ∃y.∀x. (x=y) ⇔ φ, Δ
+   * ---------------------------- if y is not free in φ
+   *      Γ|- ∃!x. φ,  Δ
+   * 
+ */ + case class RightExistsOne(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + + // Structural rule + /** + *
+   *     Γ |- Δ
+   * --------------
+   *   Γ, Σ |- Δ, Π
+   * 
+ */ + case class Weakening(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } + + // Equality Rules + /** + *
+   *  Γ, s=s |- Δ
+   * --------------
+   *     Γ |- Δ
+   * 
+ */ + case class LeftRefl(bot: Sequent, t1: Int, fa: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *
+   * --------------
+   *     |- s=s
+   * 
+ */ + case class RightRefl(bot: Sequent, fa: Formula) extends SCProofStep { val premises = Seq() } + + /** + *
+   *    Γ, φ(s1,...,sn) |- Δ
+   * ---------------------
+   *  Γ, s1=t1, ..., sn=tn, φ(t1,...tn) |- Δ
+   * 
+ */ + case class LeftSubstEq(bot: Sequent, t1: Int, equals: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ |- φ(s1,...,sn), Δ
+   * ---------------------
+   *  Γ, s1=t1, ..., sn=tn |- φ(t1,...tn), Δ
+   * 
+ */ + case class RightSubstEq(bot: Sequent, t1: Int, equals: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ, φ(a1,...an) |- Δ
+   * ---------------------
+   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
+   * 
+ */ + case class LeftSubstIff(bot: Sequent, t1: Int, equals: List[(LambdaTermFormula, LambdaTermFormula)], lambdaPhi: (Seq[SchematicAtomicLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ |- φ(a1,...an), Δ
+   * ---------------------
+   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
+   * 
+ */ + + case class RightSubstIff(bot: Sequent, t1: Int, equals: List[(LambdaTermFormula, LambdaTermFormula)], lambdaPhi: (Seq[SchematicAtomicLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + + // Rule for schemas + + case class InstSchema( + bot: Sequent, + t1: Int, + mCon: Map[SchematicConnectorLabel, LambdaFormulaFormula], + mPred: Map[SchematicAtomicLabel, LambdaTermFormula], + mTerm: Map[SchematicTermLabel, LambdaTermTerm] + ) extends SCProofStep { val premises = Seq(t1) } + + // Proof Organisation rules + + /** + * Encapsulate a proof into a single step. The imports of the subproof correspond to the premisces of the step. + * @param sp The encapsulated subproof. + * @param premises The indices of steps on the outside proof that are equivalent to the import of the subproof. + * @param display A boolean value indicating whether the subproof needs to be expanded when printed. Should probably go and + * be replaced by encapsulation. + */ + case class SCSubproof(sp: SCProof, premises: Seq[Int] = Seq.empty) extends SCProofStep { + // premises is a list of ints similar to t1, t2... that verifies that imports of the subproof sp are justified by previous steps. + val bot: Sequent = sp.conclusion + } + + /** + *
+   *
+   * --------------
+   *   Γ  |- Δ
+   * 
+ */ + case class Sorry(bot: Sequent) extends SCProofStep { val premises = Seq() } + +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/FOL.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/FOL.scala index 24f895f3..30e863d3 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/FOL.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/FOL.scala @@ -1,4 +1,5 @@ package lisa.kernel.fol +import lisa.kernel.fol.OLEquivalenceChecker /** * The concrete implementation of first order logic. @@ -7,4 +8,4 @@ package lisa.kernel.fol * import lisa.fol.FOL._ * */ -object FOL extends FormulaDefinitions with EquivalenceChecker with Substitutions {} +object FOL extends OLEquivalenceChecker {} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala new file mode 100644 index 00000000..6f876973 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala @@ -0,0 +1,485 @@ +package lisa.kernel.fol + +import scala.collection.mutable +import lisa.kernel.fol.Syntax + +private[fol] trait OLEquivalenceChecker extends Syntax { + + + def reducedForm(expr: Expression): Expression = { + val p = simplify(expr) + val nf = computeNormalForm(p) + val fln = fromLocallyNameless(nf, Map.empty, 0) + val res = toExpressionAIG(fln) + res + } + + def reducedNNFForm(formula: Expression): Expression = { + val p = simplify(formula) + val nf = computeNormalForm(p) + val fln = fromLocallyNameless(nf, Map.empty, 0) + val res = toExpressionNNF(fln, true) + res + } + def reduceSet(s: Set[Expression]): Set[Expression] = { + var res: List[Expression] = Nil + s.map(reducedForm) + .foreach({ f => + if (!res.exists(isSame(f, _))) res = f :: res + }) + res.toSet + } + + @deprecated("Use isSame instead", "0.8") + def isSameTerm(term1: Expression, term2: Expression): Boolean = isSame(term1, term2) + def isSame(e1: Expression, e2: Expression): Boolean = { + if (e1.containsFormulas != e2.containsFormulas) false + else if (!e1.containsFormulas) e1 == e2 + else { + val nf1 = computeNormalForm(simplify(e1)) + val nf2 = computeNormalForm(simplify(e2)) + latticesEQ(nf1, nf2) + } + } + + /** + * returns true if the first argument implies the second by the laws of ortholattices. + */ + def isImplying(e1: Expression, e2: Expression): Boolean = { + require(e1.sort == Formula && e2.sort == Formula) + val nf1 = computeNormalForm(simplify(e1)) + val nf2 = computeNormalForm(simplify(e2)) + latticesLEQ(nf1, nf2) + } + + def isSubset(s1: Set[Expression], s2: Set[Expression]): Boolean = { + s1.forall(e1 => s2.exists(e2 => isSame(e1, e2))) + } + def isSameSet(s1: Set[Expression], s2: Set[Expression]): Boolean = + isSubset(s1, s2) && isSubset(s2, s1) + + def isSameSetL(s1: Set[Expression], s2: Set[Expression]): Boolean = + isSame(s1.reduceLeft(and(_)(_)), s2.reduceLeft(and(_)(_))) + + def isSameSetR(s1: Set[Expression], s2: Set[Expression]): Boolean = + isSame(s1.reduceLeft(or(_)(_)), s2.reduceLeft(or(_)(_))) + + def contains(s: Set[Expression], f: Expression): Boolean = { + s.exists(g => isSame(f, g)) + } + + + private var totSimpleExpr = 0 + sealed abstract class SimpleExpression { + val sort: Sort + val containsFormulas : Boolean + + val uniqueKey = totSimpleExpr + totSimpleExpr += 1 + val size : Int //number of subterms which are actual concrete formulas + private[OLEquivalenceChecker] var inverse : Option[SimpleExpression] = None + def getInverse = inverse + private[OLEquivalenceChecker] var NNF_pos: Option[Expression] = None + def getNNF_pos = NNF_pos + private[OLEquivalenceChecker] var NNF_neg: Option[Expression] = None + def getNNF_neg = NNF_neg + private[OLEquivalenceChecker] var formulaAIG: Option[Expression] = None + def getFormulaAIG = formulaAIG + private[OLEquivalenceChecker] var normalForm: Option[SimpleExpression] = if (containsFormulas) None else Some(this) + def getNormalForm = normalForm + + // caching for lessThan + private val lessThanBitSet: mutable.Set[Long] = new mutable.HashSet() + setLessThanCache(this, true) + + def lessThanCached(other: SimpleExpression): Option[Boolean] = { + val otherIx = 2 * other.uniqueKey + if (lessThanBitSet.contains(otherIx)) Some(lessThanBitSet.contains(otherIx + 1)) + else None + } + + def setLessThanCache(other: SimpleExpression, value: Boolean): Unit = { + val otherIx = 2 * other.uniqueKey + lessThanBitSet.contains(otherIx) + if (value) lessThanBitSet.update(otherIx + 1, true) + } + } + + case class SimpleVariable(id: Identifier, sort:Sort, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = sort == Formula + val size = 1 + } + case class SimpleBoundVariable(no: Int, sort: Sort, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = sort == Formula + val size = 1 + } + case class SimpleConstant(id: Identifier, sort: Sort, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = sort == Formula + val size = 1 + } + case class SimpleApplication(f: SimpleExpression, arg: SimpleExpression, polarity: Boolean) extends SimpleExpression { + private val legalapp = legalApplication(f.sort, arg.sort) // Optimize after debugging + val sort = legalapp.get + val containsFormulas: Boolean = sort == Formula || f.containsFormulas || arg.containsFormulas + val size = f.size + arg.size + } + case class SimpleLambda(v: Variable, body: SimpleExpression) extends SimpleExpression { + val containsFormulas: Boolean = body.containsFormulas + val sort = (v.sort -> body.sort) + val size = body.size + } + case class SimpleAnd(children: Seq[SimpleExpression], polarity: Boolean) extends SimpleExpression{ + val containsFormulas: Boolean = true + val sort = Formula + val size = children.map(_.size).sum+1 + } + case class SimpleForall(id: Identifier, body: SimpleExpression, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val sort = Formula + val size = body.size +1 + } + case class SimpleLiteral(polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val sort = Formula + val size = 1 + } + case class SimpleEquality(left: SimpleExpression, right: SimpleExpression, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val sort = Formula + val size = left.size + right.size + 1 + } + + + def getInversePolar(e: SimpleExpression): SimpleExpression = e.inverse match { + case Some(inverse) => inverse + case None => + val inverse = e match { + case e: SimpleAnd => e.copy(polarity = !e.polarity) + case e: SimpleForall => e.copy(polarity = !e.polarity) + case e: SimpleLiteral => e.copy(polarity = !e.polarity) + case e: SimpleEquality => e.copy(polarity = !e.polarity) + case e: SimpleVariable if e.sort == Formula => e.copy(polarity = !e.polarity) + case e: SimpleBoundVariable if e.sort == Formula => e.copy(polarity = !e.polarity) + case e: SimpleConstant if e.sort == Formula => e.copy(polarity = !e.polarity) + case e: SimpleApplication if e.sort == Formula => e.copy(polarity = !e.polarity) + case _ => throw new Exception("Cannot invert expression that is not a formula") + } + e.inverse = Some(inverse) + inverse + } + + + def toExpressionAIG(e:SimpleExpression): Expression = + if (e.formulaAIG.isDefined) e.formulaAIG.get + else { + val r: Expression = e match { + case SimpleAnd(children, polarity) => + val f = children.map(toExpressionAIG).reduceLeft(and(_)(_)) + if (polarity) f else neg(f) + case SimpleForall(x, body, polarity) => + val f = forall(Lambda(Variable(x, Term), toExpressionAIG(body))) + if (polarity) f else neg(f) + case SimpleEquality(left, right, polarity) => + val f = equality(toExpressionAIG(left))(toExpressionAIG(right)) + if (polarity) f else neg(f) + case SimpleLiteral(polarity) => if (polarity) top else bot + case SimpleVariable(id, sort, polarity) => if (polarity) Variable(id, sort) else neg(Variable(id, sort)) + case SimpleBoundVariable(no, sort, polarity) => throw new Exception("This case should be unreachable. Can't call toFormulaAIG on a bound variable") + case SimpleConstant(id, sort, polarity) => if (polarity) Constant(id, sort) else neg(Constant(id, sort)) + case SimpleApplication(f, arg, polarity) => + val g = Application(toExpressionAIG(f), toExpressionAIG(arg)) + if (polarity) + g else + neg(g) + case SimpleLambda(v, body) => Lambda(v, toExpressionAIG(body)) + } + e.formulaAIG = Some(r) + r + } + + def toExpressionNNF(e: SimpleExpression, positive: Boolean): Expression = { + if (positive){ + if (e.NNF_pos.isDefined) return e.NNF_pos.get + if (e.inverse.isDefined && e.inverse.get.NNF_neg.isDefined) return e.inverse.get.NNF_neg.get + } + else if (!positive) { + if (e.NNF_neg.isDefined) return e.NNF_neg.get + if (e.inverse.isDefined && e.inverse.get.NNF_pos.isDefined) return e.inverse.get.NNF_pos.get + } + val r = e match { + case SimpleAnd(children, polarity) => + if (positive == polarity) + children.map(toExpressionNNF(_, true)).reduceLeft(and(_)(_)) + else + children.map(toExpressionNNF(_, false)).reduceLeft(or(_)(_)) + case SimpleForall(x, body, polarity) => + if (positive == polarity) + forall(Lambda(Variable(x, Term), toExpressionNNF(body, true))) //rebuilding variable not ideal + else + exists(Lambda(Variable(x, Term), toExpressionNNF(body, false))) + case SimpleEquality(left, right, polarity) => + if (positive == polarity) + equality(toExpressionNNF(left, true))(toExpressionNNF(right, true)) + else + neg(equality(toExpressionNNF(left, true))(toExpressionNNF(right, true))) + case SimpleLiteral(polarity) => + if (positive == polarity) top + else bot + case SimpleVariable(id, sort, polarity) => + if (polarity == positive) Variable(id, sort) + else neg(Variable(id, sort)) + case SimpleBoundVariable(no, sort, polarity) => throw new Exception("This case should be unreachable. Can't call toExpressionNNF on a bound variable") + case SimpleConstant(id, sort, polarity) => + if (polarity == positive) Constant(id, sort) + else neg(Constant(id, sort)) + case SimpleApplication(f, arg, polarity) => + if (polarity == positive) + Application(toExpressionNNF(f, true), toExpressionNNF(arg, true)) + else + neg(Application(toExpressionNNF(f, true), toExpressionNNF(arg, true))) + case SimpleLambda(v, body) => Lambda(v, toExpressionNNF(body, true)) + } + if (positive) e.NNF_pos = Some(r) + else e.NNF_neg = Some(r) + r + } + + + + def polarize(e: Expression, polarity:Boolean): SimpleExpression = e match { + case neg(arg) => + polarize(arg, !polarity) + case implies(arg1, arg2) => + SimpleAnd(Seq(polarize(arg1, true), polarize(arg2, false)), !polarity) + case iff(arg1, arg2) => + val l1 = polarize(arg1, true) + val r1 = polarize(arg2, true) + SimpleAnd( + Seq( + SimpleAnd(Seq(l1, getInversePolar(r1)), false), + SimpleAnd(Seq(getInversePolar(l1), r1), false) + ), polarity) + case and(arg1, arg2) => + SimpleAnd(Seq(polarize(arg1, true), polarize(arg2, true)), polarity) + case or(arg1, arg2) => + SimpleAnd(Seq(polarize(arg1, false), polarize(arg2, false)), !polarity) + case forall(Lambda(v, body)) => + SimpleForall(v.id, polarize(body, true), polarity) + case exists(Lambda(v, body)) => + SimpleForall(v.id, polarize(body, false), !polarity) + case equality(arg1, arg2) => + SimpleEquality(polarize(arg1, true), polarize(arg2, true), polarity) + case Application(f, arg) => + SimpleApplication(polarize(f, true), polarize(arg, true), polarity) + case Lambda(v, body) => SimpleLambda(v, polarize(body, true)) + case Constant(`top`, Formula) => SimpleLiteral(true) + case Constant(`bot`, Formula) => SimpleLiteral(false) + case Constant(id, sort) => SimpleConstant(id, sort, polarity) + case Variable(id, sort) => SimpleVariable(id, sort, polarity) + } + + def toLocallyNameless(e: SimpleExpression, subst: Map[(Identifier, Sort), Int], i: Int): SimpleExpression = e match { + case SimpleAnd(children, polarity) => SimpleAnd(children.map(toLocallyNameless(_, subst, i)), polarity) + case SimpleForall(x, inner, polarity) => SimpleForall(x, toLocallyNameless(inner, subst + ((x, Term) -> i), i + 1), polarity) + case e: SimpleLiteral => e + case SimpleEquality(left, right, polarity) => SimpleEquality(toLocallyNameless(left, subst, i), toLocallyNameless(right, subst, i), polarity) + case v: SimpleVariable => + if (subst.contains((v.id, v.sort))) SimpleBoundVariable(i - subst((v.id, v.sort)), v.sort, v.polarity) + else v + case s: SimpleBoundVariable => throw new Exception("This case should be unreachable. Can't call toLocallyNameless on a bound variable") + case e: SimpleConstant => e + case SimpleApplication(arg1, arg2, polarity) => SimpleApplication(toLocallyNameless(arg1, subst, i), toLocallyNameless(arg2, subst, i), polarity) + case SimpleLambda(x, inner) => SimpleLambda(x, toLocallyNameless(inner, subst + ((x.id, x.sort) -> i), i + 1)) + } + + def fromLocallyNameless(e: SimpleExpression, subst: Map[Int, (Identifier, Sort)], i: Int): SimpleExpression = e match { + case SimpleAnd(children, polarity) => SimpleAnd(children.map(fromLocallyNameless(_, subst, i)), polarity) + case SimpleForall(x, inner, polarity) => SimpleForall(x, fromLocallyNameless(inner, subst + (i -> (x, Term)), i + 1), polarity) + case e: SimpleLiteral => e + case SimpleEquality(left, right, polarity) => SimpleEquality(fromLocallyNameless(left, subst, i), fromLocallyNameless(right, subst, i), polarity) + case SimpleBoundVariable(no, sort, polarity) => + val dist = i - no + if (subst.contains(dist)) {val (id, sort) = subst(dist); SimpleVariable(id, sort, polarity)} + else throw new Exception("This case should be unreachable, error") + case v: SimpleVariable => v + case e: SimpleConstant => e + case SimpleApplication(arg1, arg2, polarity) => SimpleApplication(fromLocallyNameless(arg1, subst, i), fromLocallyNameless(arg2, subst, i), polarity) + case SimpleLambda(x, inner) => SimpleLambda(x, fromLocallyNameless(inner, subst + (i -> (x.id, x.sort)), i + 1)) + } + + def simplify(e: Expression): SimpleExpression = toLocallyNameless(polarize(e, true), Map.empty, 0) + + + ////////////////////// + //// OL Algorithm //// + ////////////////////// + + def computeNormalForm(e: SimpleExpression): SimpleExpression = { + e.normalForm match { + case Some(value) => + value + case None => + val r: SimpleExpression = e match { + case SimpleAnd(children, polarity) => + val newChildren = children map computeNormalForm + val simp = reduce(newChildren, polarity) + simp match { + case conj: SimpleAnd if checkForContradiction(conj) => SimpleLiteral(!polarity) + case _ => simp + } + + case SimpleApplication(f, arg, true) => SimpleApplication(computeNormalForm(f), computeNormalForm(arg), true) + + case SimpleBoundVariable(no, sort, true) => e + + case SimpleVariable(id, sort, true) => e + + case SimpleConstant(id, sort, true) => e + + case SimpleEquality(left, right, true) => + val l = computeNormalForm(left) + val r = computeNormalForm(right) + if (l == r) SimpleLiteral(true) + else if (l.uniqueKey >= r.uniqueKey) SimpleEquality(l, r, true) + else SimpleEquality(r, l, true) + + case SimpleForall(id, body, true) => SimpleForall(id, computeNormalForm(body), true) + + case SimpleLambda(v, body) => SimpleLambda(v, computeNormalForm(body)) + + case SimpleLiteral(polarity) => e + + case _ => getInversePolar(computeNormalForm(getInversePolar(e))) + + } + e.normalForm = Some(r) + r + } + } + + def checkForContradiction(f: SimpleAnd): Boolean = { + f match { + case SimpleAnd(children, false) => + children.exists(cc => latticesLEQ(cc, f)) + case SimpleAnd(children, true) => + val shadowChildren = children map getInversePolar + shadowChildren.exists(sc => latticesLEQ(f, sc)) + } + } + + def reduceList(children: Seq[SimpleExpression], polarity: Boolean): List[SimpleExpression] = { + val nonSimplified = SimpleAnd(children, polarity) + var remaining : Seq[SimpleExpression] = Nil + def treatChild(i: SimpleExpression): Seq[SimpleExpression] = { + val r: Seq[SimpleExpression] = i match { + case SimpleAnd(ch, true) => ch + case SimpleAnd(ch, false) => + if (polarity) { + val trCh = ch map getInversePolar + trCh.find(f => latticesLEQ(nonSimplified, f)) match { + case Some(value) => treatChild(value) + case None => List(i) + } + } else { + val trCH = ch + trCH.find(f => latticesLEQ(f, nonSimplified)) match { + case Some(value) => treatChild(getInversePolar(value)) + case None => List(i) + } + } + case _ => List(i) + } + r + } + children.foreach(i => { + val r = treatChild(i) + remaining = r ++ remaining + }) + + var accepted: List[SimpleExpression] = Nil + while (remaining.nonEmpty) { + val current = remaining.head + remaining = remaining.tail + if (!latticesLEQ(SimpleAnd(remaining ++ accepted, true), current)) { + accepted = current :: accepted + } + } + accepted + } + + + def reduce(children: Seq[SimpleExpression], polarity: Boolean): SimpleExpression = { + val accepted: List[SimpleExpression] = reduceList(children, polarity) + if (accepted.isEmpty) SimpleLiteral(polarity) + else if (accepted.size == 1) + if (polarity) accepted.head + else getInversePolar(accepted.head) + else SimpleAnd(accepted, polarity) + } + + + def latticesLEQ(e1: SimpleExpression, e2: SimpleExpression): Boolean = { + require(e1.sort == Formula && e2.sort == Formula) + if (e1.uniqueKey == e2.uniqueKey) true + else + e1.lessThanCached(e2) match { + case Some(value) => value + case None => + val r = (e1, e2) match { + case (SimpleLiteral(false), _) => true + + case (_, SimpleLiteral(true)) => true + + case (SimpleEquality(l1, r1, pol1), SimpleEquality(l2, r2, pol2)) => + pol1 == pol2 && latticesEQ(l1, l2) && latticesEQ(r1, r2) + + case (SimpleForall(x1, body1, polarity1), SimpleForall(x2, body2, polarity2)) => + polarity1 == polarity2 && (if (polarity1) latticesLEQ(body1, body2) else latticesLEQ(body2, body1)) + + // Usual lattice conjunction/disjunction cases + case (_, SimpleAnd(children, true)) => + children.forall(c => latticesLEQ(e1, c)) + case (SimpleAnd(children, false), _) => + children.forall(c => latticesLEQ(getInversePolar(c), e2)) + case (SimpleAnd(children1, true), SimpleAnd(children2, false)) => + children1.exists(c => latticesLEQ(c, e2)) || children2.exists(c => latticesLEQ(e1, getInversePolar(c))) + case (_, SimpleAnd(children, false)) => + children.exists(c => latticesLEQ(e1, getInversePolar(c))) + case (SimpleAnd(children, true), _) => + children.exists(c => latticesLEQ(c, e2)) + + + case (s1: SimpleBoundVariable, s2: SimpleBoundVariable) => s1 == s2 + + case (s1: SimpleVariable, s2: SimpleVariable) => s1 == s2 + + case (s1: SimpleConstant, s2: SimpleConstant) => s1 == s2 + + case (SimpleApplication(f1, arg1, polarity1), SimpleApplication(f2, arg2, polarity2)) => + polarity1 == polarity2 && latticesEQ(f1, f2) && latticesEQ(arg1, arg2) + + case (_, _) => false + } + e1.setLessThanCache(e2, r) + r + } + + + } + + def latticesEQ(e1: SimpleExpression, e2: SimpleExpression): Boolean = + if (e1.uniqueKey == e2.uniqueKey) true + else if (e1.containsFormulas && e2.containsFormulas) { + if (e1.sort == Formula) latticesLEQ(e1, e2) && latticesLEQ(e2, e1) + else (e1, e2) match { + case (s1: SimpleBoundVariable, s2: SimpleBoundVariable) => s1 == s2 + case (s1: SimpleVariable, s2: SimpleVariable) => s1 == s2 + case (s1: SimpleConstant, s2: SimpleConstant) => s1 == s2 + case (SimpleApplication(f1, arg1, polarity1), SimpleApplication(f2, arg2, polarity2)) => + polarity1 == polarity2 && latticesEQ(f1, f2) && latticesEQ(arg1, arg2) + case (SimpleLambda(x1, body1), SimpleLambda(x2, body2)) => + latticesEQ(body1, body2) + case (_, _) => false + } + } else e1 == e2 +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala new file mode 100644 index 00000000..578acbfb --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala @@ -0,0 +1,240 @@ +package lisa.kernel.fol + +private[fol] trait Syntax { + + + sealed case class Identifier(val name: String, val no: Int) { + require(no >= 0, "Variable index must be positive") + require(Identifier.isValidIdentifier(name), "Variable name " + name + "is not valid.") + override def toString: String = if (no == 0) name else name + Identifier.counterSeparator + no + } + + object Identifier { + def unapply(i: Identifier): Option[(String, Int)] = Some((i.name, i.no)) + def apply(name: String): Identifier = new Identifier(name, 0) + def apply(name: String, no: Int): Identifier = new Identifier(name, no) + + val counterSeparator: Char = '_' + val delimiter: Char = '`' + val forbiddenChars: Set[Char] = ("()[]{}?,;" + delimiter + counterSeparator).toSet + def isValidIdentifier(s: String): Boolean = s.forall(c => !forbiddenChars.contains(c) && !c.isWhitespace) + } + + private[kernel] def freshId(taken: Iterable[Identifier], base: Identifier): Identifier = { + new Identifier( + base.name, + (Iterable(base.no) ++ taken.collect({ case Identifier(base.name, no) => + no + })).max + 1 + ) + } + + + + + sealed trait Sort { + def ->(to: Sort): Arrow = Arrow(this, to) + val isFunctional: Boolean + def isPredicate: Boolean = !isFunctional + val depth: Int + } + case object Term extends Sort { + val isFunctional = true + val depth = 0 + } + case object Formula extends Sort { + val isFunctional = false + val depth = 0 + } + sealed case class Arrow(from: Sort, to: Sort) extends Sort { + val isFunctional = to.isFunctional + val depth = 1+to.depth + } + + def depth(t:Sort): Int = t match { + case Arrow(a, b) => 1 + depth(b) + case _ => 0 + } + + + def legalApplication(typ1: Sort, typ2: Sort): Option[Sort] = { + typ1 match { + case Arrow(`typ2`, to) => Some(to) + case _ => None + } + } + + private object ExpressionCounters { + var totalNumberOfExpressions: Long = 0 + def getNewId: Long = { + totalNumberOfExpressions += 1 + totalNumberOfExpressions + } + } + + sealed trait Expression { + val sort: Sort + val uniqueNumber: Long = ExpressionCounters.getNewId + val containsFormulas : Boolean + def apply(arg: Expression): Application = Application(this, arg) + def unapplySeq(arg: Expression): Option[Seq[Expression]] = arg match { + case Application(f, arg) if f == this => Some(arg :: Nil) + case Application(f, arg) => unapplySeq(f).map(fargs => fargs :+ arg) + case _ => None + } + + /** + * @return The list of free variables in the tree. + */ + def freeVariables: Set[Variable] + + /** + * @return The list of constant symbols. + */ + def constants: Set[Constant] + + /** + * @return The list of variables in the tree. + */ + def allVariables: Set[Variable] + + } + + case class Variable(id: Identifier, sort:Sort) extends Expression { + val containsFormulas = sort == Formula + def freeVariables: Set[Variable] = Set(this) + def constants: Set[Constant] = Set() + def allVariables: Set[Variable] = Set(this) + } + case class Constant(id: Identifier, sort: Sort) extends Expression { + val containsFormulas = sort == Formula + def freeVariables: Set[Variable] = Set() + def constants: Set[Constant] = Set(this) + def allVariables: Set[Variable] = Set() + } + case class Application(f: Expression, arg: Expression) extends Expression { + private val legalapp = legalApplication(f.sort, arg.sort) + require(legalapp.isDefined, s"Application of $f to $arg is not legal") + val sort = legalapp.get + val containsFormulas = sort == Formula || f.containsFormulas || arg.containsFormulas + + def freeVariables: Set[Variable] = f.freeVariables union arg.freeVariables + def constants: Set[Constant] = f.constants union arg.constants + def allVariables: Set[Variable] = f.allVariables union arg.allVariables + } + + case class Lambda(v: Variable, body: Expression) extends Expression { + val containsFormulas = body.containsFormulas + val sort = (v.sort -> body.sort) + + def freeVariables: Set[Variable] = body.freeVariables - v + def constants: Set[Constant] = body.constants + def allVariables: Set[Variable] = body.allVariables + } + + /* + object Equality { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`equality`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(arg1: Expression, arg2: Expression): Expression = equality(arg1)(arg2) + } + + object Neg { + def unapply (e: Expression): Option[Expression] = e match { + case Application(`neg`, arg) => Some(arg) + case _ => None + } + def apply(arg: Expression): Expression = neg(arg) + } + object Implies { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`implies`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(arg1: Expression, arg2: Expression): Expression = implies(arg1)(arg2) + } + object Iff { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`iff`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(arg1: Expression, arg2: Expression): Expression = iff(arg1)(arg2) + } + object And { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`and`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(args: Iterable[Expression]): Expression = args.reduceLeft(and(_)(_)) + } + object Or { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`or`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(args: Iterable[Expression]): Expression = args.reduceLeft(and(_)(_)) + } + object Forall { + def unapply (e: Expression): Option[(Variable, Expression)] = e match { + case Application(`forall`, Lambda(v, body)) => Some((v, body)) + case _ => None + } + def apply(v: Variable, body: Expression): Expression = forall(Lambda(v, body)) + } + object Exists { + def unapply (e: Expression): Option[(Variable, Expression)] = e match { + case Application(`exists`, Lambda(v, body)) => Some((v, body)) + case _ => None + } + def apply(v: Variable, body: Expression): Expression = exists(Lambda(v, body)) + } + object Epsilon { + def unapply (e: Expression): Option[(Variable, Expression)] = e match { + case Application(`epsilon`, Lambda(v, body)) => Some((v, body)) + case _ => None + } + def apply(v: Variable, body: Expression): Expression = epsilon(Lambda(v, body)) + } +*/ + + val equality = Constant(Identifier("="), Term -> (Term -> Formula)) + val top = Constant(Identifier("⊤"), Formula) + val bot = Constant(Identifier("⊥"), Formula) + val neg = Constant(Identifier("¬"), Formula -> Formula) + val implies = Constant(Identifier("⇒"), Formula -> (Formula -> Formula)) + val iff = Constant(Identifier("⇔"), Formula -> (Formula -> Formula)) + val and = Constant(Identifier("∧"), Formula -> (Formula -> Formula)) + val or = Constant(Identifier("∨"), Formula -> (Formula -> Formula)) + val forall = Constant(Identifier("∀"), (Term -> Formula) -> Formula) + val exists = Constant(Identifier("∃"), (Term -> Formula) -> Formula) + val epsilon = Constant(Identifier("ε"), (Term -> Formula) -> Term) + + + /** + * Performs simultaneous substitution of multiple variables by multiple terms in a term. + * @param t The base term + * @param m A map from variables to terms. + * @return t[m] + */ + def substituteVariables(e: Expression, m: Map[Variable, Expression]): Expression = e match { + case v: Variable => + m.get(v) match { + case Some(r) => + if (r.sort == v.sort) r + else throw new IllegalArgumentException("Sort mismatch in substitution: " + v + " -> " + r) + case None => v + } + case c: Constant => c + case Application(f, arg) => Application(substituteVariables(f, m), substituteVariables(arg, m)) + case Lambda(v, t) => + Lambda(v, substituteVariables(t, m - v)) + } + + def flatTypeParameters(t: Sort): List[Sort] = t match { + case Arrow(a, b) => a :: flatTypeParameters(b) + case _ => List() + } + +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala b/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala index 8167d9f7..f8e1bbf4 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala @@ -32,40 +32,21 @@ class RunningTheory { /** * An axiom is any formula that is assumed and considered true within the theory. It can freely be used later in any proof. */ - sealed case class Axiom private[RunningTheory] (name: String, ax: Formula) extends Justification + sealed case class Axiom private[RunningTheory] (name: String, ax: Expression) extends Justification /** - * A definition can be either a PredicateDefinition or a FunctionDefinition. + * A definition of a new symbol. */ - sealed abstract class Definition extends Justification - - /** - * Define a predicate symbol as a shortcut for a formula. Example : P(x,y) := ∃!z. (x=y+z) - * - * @param label The name and arity of the new symbol - * @param expression The formula, depending on terms, that define the symbol. - */ - sealed case class PredicateDefinition private[RunningTheory] (label: ConstantAtomicLabel, expression: LambdaTermFormula) extends Definition - - /** - * Define a function symbol as the unique element that has some property. The existence and uniqueness - * of that elements must have been proven before obtaining such a definition. Example - * f(x,y) := the "z" s.t. x=y+z - * - * @param label The name and arity of the new symbol - * @param out The variable representing the result of the function in phi - * @param expression The formula, with term parameters, defining the function. - * @param withSorry Stores if Sorry was used to in the proof used to define the symbol, or one of its ancestor. - */ - sealed case class FunctionDefinition private[RunningTheory] (label: ConstantFunctionLabel, out: VariableLabel, expression: LambdaTermFormula, withSorry: Boolean) extends Definition + sealed case class Definition private[RunningTheory] (cst: Constant, expression: Expression, vars: Seq[Variable]) extends Justification private[proof] val theoryAxioms: mMap[String, Axiom] = mMap.empty private[proof] val theorems: mMap[String, Theorem] = mMap.empty - private[proof] val funDefinitions: mMap[ConstantFunctionLabel, Option[FunctionDefinition]] = mMap.empty - private[proof] val predDefinitions: mMap[ConstantAtomicLabel, Option[PredicateDefinition]] = mMap(equality -> None, top -> None, bot -> None) + private[proof] val definitions: mMap[Constant, Option[Definition]] = + mMap(equality -> None, top -> None, bot -> None, and -> None, or -> None, neg -> None, implies -> None, iff -> None, forall -> None, exists -> None, epsilon -> None) - private[proof] val knownSymbols: mMap[Identifier, ConstantLabel] = mMap(equality.id -> equality) + private[proof] val knownSymbols: mMap[Identifier, Constant] = + mMap(equality.id -> equality, top.id -> top, bot.id -> bot, and.id -> and, or.id -> or, neg.id -> neg, implies.id -> implies, iff.id -> iff, forall.id -> forall, exists.id -> exists, epsilon.id -> epsilon) /** * From a given proof, if it is true in the Running theory, add that theorem to the theory and returns it. @@ -90,11 +71,7 @@ class RunningTheory { val usesSorry = sorry || justifications.exists(_ match { case Theorem(name, proposition, withSorry) => withSorry case Axiom(name, ax) => false - case d: Definition => - d match { - case PredicateDefinition(label, expression) => false - case FunctionDefinition(label, out, expression, withSorry) => withSorry - } + case d: Definition => false }) val thm = Theorem(name, proof.conclusion, usesSorry) theorems.update(name, thm) @@ -105,28 +82,28 @@ class RunningTheory { } else InvalidJustification("All symbols in the conclusion of the proof must belong to the theory. You need to add missing symbols to the theory.", None) else InvalidJustification("Not all imports of the proof are correctly justified.", None) - /** - * Introduce a new definition of a predicate in the theory. The symbol must not already exist in the theory - * and the formula can't contain symbols that are not in the theory. - * - * @param label The desired label. - * @param expression The functional formula defining the predicate. - * @return A definition object if the parameters are correct, - */ - def makePredicateDefinition(label: ConstantAtomicLabel, expression: LambdaTermFormula): RunningTheoryJudgement[this.PredicateDefinition] = { - val LambdaTermFormula(vars, body) = expression - if (belongsToTheory(body)) - if (isAvailable(label)) - if (body.freeSchematicTermLabels.subsetOf(vars.toSet) && body.schematicAtomicLabels.isEmpty) { - val newDef = PredicateDefinition(label, expression) - predDefinitions.update(label, Some(newDef)) - knownSymbols.update(label.id, label) - RunningTheoryJudgement.ValidJustification(newDef) - } else InvalidJustification("The definition is not allowed to contain schematic symbols or free variables.", None) - else InvalidJustification("The specified symbol id is already part of the theory and can't be redefined.", None) - else InvalidJustification("All symbols in the conclusion of the proof must belong to the theory. You need to add missing symbols to the theory.", None) + + def makeDefinition(cst: Constant, expression: Expression, vars: Seq[Variable]): RunningTheoryJudgement[this.Definition] = { + if (cst.sort.depth == vars.length) + if (flatTypeParameters(cst.sort) zip vars.map(_.sort) forall { case (a, b) => a == b }) + if (cst.sort == expression.sort) + if (belongsToTheory(expression)) + if (isAvailable(cst)) + if (expression.freeVariables.isEmpty) { + val newDef = Definition(cst, expression, vars) + definitions.update(cst, Some(newDef)) + knownSymbols.update(cst.id , cst) + RunningTheoryJudgement.ValidJustification(newDef) + } else InvalidJustification("The definition is not allowed to contain schematic symbols or free variables.", None) + else InvalidJustification("The specified symbol id is already part of the theory and can't be redefined.", None) + else InvalidJustification("All symbols in the definition must belong to the theory. You need to add missing symbols to the theory.", None) + else InvalidJustification("The type of the constant and the type of the expression must be the same.", None) + else InvalidJustification("The types of the variables must match the type of the constant.", None) + else InvalidJustification("The arity of the label must be equal to the number of parameters in the definition.", None) } + /* + /** * Introduce a new definition of a function in the theory. The symbol must not already exist in the theory * and the formula can't contain symbols that are not in the theory. The existence and uniqueness of an element @@ -191,27 +168,20 @@ class RunningTheory { } else InvalidJustification("All symbols in the conclusion of the proof must belong to the theory. You need to add missing symbols to the theory.", None) } else InvalidJustification("The arity of the label must be equal to the number of parameters in the definition.", None) } +*/ + def sequentFromJustification(j: Justification): Sequent = j match { case Theorem(name, proposition, _) => proposition case Axiom(name, ax) => Sequent(Set.empty, Set(ax)) - case PredicateDefinition(label, LambdaTermFormula(vars, body)) => - val inner = ConnectorFormula(Iff, Seq(AtomicFormula(label, vars.map(VariableTerm.apply)), body)) - Sequent(Set(), Set(inner)) - case FunctionDefinition(label, out, LambdaTermFormula(vars, body), _) => - val inner = BinderFormula( - Forall, - out, - ConnectorFormula( - Iff, - Seq( - AtomicFormula(equality, Seq(Term(label, vars.map(VariableTerm.apply)), VariableTerm(out))), - body - ) - ) - ) - Sequent(Set(), Set(inner)) - + case Definition(cst, e, vars) => + if (cst.sort.isPredicate){ + val inner = iff(vars.foldLeft(cst: Expression)(_(_)))(vars.foldLeft(e)(_(_))) + Sequent(Set(), Set(inner)) + } else { + val inner = equality(vars.foldLeft(cst: Expression)(_(_)))(vars.foldLeft(e)(_(_))) + Sequent(Set(), Set(inner)) + } } /** @@ -222,8 +192,8 @@ class RunningTheory { * @param f the new axiom to be added. * @return true if the axiom was added to the theory, false else. */ - def addAxiom(name: String, f: Formula): Option[Axiom] = { - if (belongsToTheory(f)) { + def addAxiom(name: String, f: Expression): Option[Axiom] = { + if (f.sort == Formula && belongsToTheory(f)) { val ax = Axiom(name, f) theoryAxioms.update(name, ax) Some(ax) @@ -237,22 +207,18 @@ class RunningTheory { * that it is empty can be introduced as well. */ - def addSymbol(s: ConstantLabel): Unit = { - if (isAvailable(s)) { - knownSymbols.update(s.id, s) - s match { - case c: ConstantFunctionLabel => funDefinitions.update(c, None) - case c: ConstantAtomicLabel => predDefinitions.update(c, None) - } + def addSymbol(c: Constant): Unit = { + if (isAvailable(c)) { + knownSymbols.update(c.id, c) + definitions.update(c, None) } else {} } /** * Add all constant symbols in the sequent. Note that this can't be reversed and will prevent from giving them a definition later. */ - def makeFormulaBelongToTheory(phi: Formula): Unit = { - phi.constantAtomicLabels.foreach(addSymbol) - phi.constantTermLabels.foreach(addSymbol) + def makeFormulaBelongToTheory(e: Expression): Unit = { + e.constants.foreach(addSymbol) } /** @@ -264,34 +230,16 @@ class RunningTheory { } /** - * Verify if a given formula belongs to some language - * - * @param phi The formula to check - * @return Weather phi belongs to the specified language - */ - def belongsToTheory(phi: Formula): Boolean = phi match { - case AtomicFormula(label, args) => - label match { - case l: ConstantAtomicLabel => isSymbol(l) && args.forall(belongsToTheory) - case _ => args.forall(belongsToTheory) - } - case ConnectorFormula(label, args) => args.forall(belongsToTheory) - case BinderFormula(label, bound, inner) => belongsToTheory(inner) - } - - /** - * Verify if a given term belongs to the language of the theory. + * Verify if a given expression belongs to the language of the theory. * - * @param t The term to check + * @param e The expression to check * @return Weather t belongs to the specified language. */ - def belongsToTheory(t: Term): Boolean = t match { - case Term(label, args) => - label match { - case l: ConstantFunctionLabel => isSymbol(l) && args.forall(belongsToTheory) - case _: SchematicTermLabel => args.forall(belongsToTheory) - } - + def belongsToTheory(e: Expression): Boolean = e match { + case v: Variable => true + case c: Constant => isSymbol(c) + case Application(f, arg) => belongsToTheory(f) && belongsToTheory(arg) + case Lambda(v, t) => belongsToTheory(t) } /** @@ -308,21 +256,18 @@ class RunningTheory { * * @return the set of symbol currently in the theory's language. */ - def language(): List[(ConstantLabel, Option[Definition])] = funDefinitions.toList ++ predDefinitions.toList + def language(): List[(Constant, Option[Definition])] = definitions.toList /** * Check if a label is a symbol of the theory. */ - def isSymbol(label: ConstantLabel): Boolean = label match { - case c: ConstantFunctionLabel => funDefinitions.contains(c) - case c: ConstantAtomicLabel => predDefinitions.contains(c) - } + def isSymbol(cst: Constant): Boolean = definitions.contains(cst) /** * Check if a label is not already used in the theory. * @return */ - def isAvailable(label: ConstantLabel): Boolean = !knownSymbols.contains(label.id) + def isAvailable(label: Constant): Boolean = !knownSymbols.contains(label.id) /** * Public accessor to the current set of axioms of the theory @@ -334,22 +279,17 @@ class RunningTheory { /** * Verify if a given formula is an axiom of the theory */ - def isAxiom(f: Formula): Boolean = theoryAxioms.exists(a => isSame(a._2.ax, f)) + def isAxiom(f: Expression): Boolean = f.sort == Formula && theoryAxioms.exists(a => isSame(a._2.ax, f)) /** * Get the Axiom that is the same as the given formula, if it exists in the theory. */ - def getAxiom(f: Formula): Option[Axiom] = theoryAxioms.find(a => isSame(a._2.ax, f)).map(_._2) - - /** - * Get the definition of the given label, if it is defined in the theory. - */ - def getDefinition(label: ConstantAtomicLabel): Option[PredicateDefinition] = predDefinitions.get(label).flatten + def getAxiom(f: Expression): Option[Axiom] = if (f.sort == Formula) theoryAxioms.find(a => isSame(a._2.ax, f)).map(_._2) else None /** * Get the definition of the given label, if it is defined in the theory. */ - def getDefinition(label: ConstantFunctionLabel): Option[FunctionDefinition] = funDefinitions.get(label).flatten + def getDefinition(label: Constant): Option[Definition] = definitions.get(label).flatten /** * Get the Axiom with the given name, if it exists in the theory. @@ -364,10 +304,7 @@ class RunningTheory { /** * Get the definition for the given identifier, if it is defined in the theory. */ - def getDefinition(name: Identifier): Option[Definition] = knownSymbols.get(name).flatMap { - case f: ConstantAtomicLabel => getDefinition(f) - case f: ConstantFunctionLabel => getDefinition(f) - } + def getDefinition(name: Identifier): Option[Definition] = knownSymbols.get(name).flatMap(getDefinition) } object RunningTheory { diff --git a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProof.scala b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProof.scala index fb4e5c79..fb283d89 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProof.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProof.scala @@ -94,4 +94,4 @@ object SCProof { SCProof(steps.toIndexedSeq) } -} +} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala index bf7c7114..f41ae994 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -4,6 +4,7 @@ import lisa.kernel.fol.FOL._ import lisa.kernel.proof.SCProofCheckerJudgement._ import lisa.kernel.proof.SequentCalculus._ + object SCProofChecker { /** @@ -42,7 +43,7 @@ object SCProofChecker { * Γ |- Γ */ case RestateTrue(s) => - val truth = Sequent(Set(), Set(AtomicFormula(top, Nil))) + val truth = Sequent(Set(), Set(top)) if (isSameSequent(s, truth)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"The desired conclusion is not a trivial tautology") /* * @@ -50,17 +51,22 @@ object SCProofChecker { * Γ, φ |- φ, Δ */ case Hypothesis(Sequent(left, right), phi) => - if (contains(left, phi)) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (contains(left, phi)) if (contains(right, phi)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"Right-hand side does not contain formula φ") else SCInvalidProof(SCProof(step), Nil, s"Left-hand side does not contain formula φ") + /* * Γ |- Δ, φ φ, Σ |- Π * ------------------------ * Γ, Σ |- Δ, Π */ case Cut(b, t1, t2, phi) => - if (isSameSet(b.left + phi, ref(t1).left union ref(t2).left) && (!contains(ref(t1).left, phi) || contains(b.left, phi))) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (isSameSet(b.left + phi, ref(t1).left union ref(t2).left) && (!contains(ref(t1).left, phi) || contains(b.left, phi))) if (isSameSet(b.right + phi, ref(t2).right union ref(t1).right) && (!contains(ref(t2).right, phi) || contains(b.right, phi))) if (contains(ref(t2).left, phi)) if (contains(ref(t1).right, phi)) @@ -77,8 +83,12 @@ object SCProofChecker { * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ */ case LeftAnd(b, t1, phi, psi) => - if (isSameSet(ref(t1).right, b.right)) { - val phiAndPsi = ConnectorFormula(And, Seq(phi, psi)) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else if (isSameSet(ref(t1).right, b.right)) { + val phiAndPsi = and(phi)(psi) if ( isSameSet(b.left + phi, ref(t1).left + phiAndPsi) || isSameSet(b.left + psi, ref(t1).left + phiAndPsi) || @@ -93,13 +103,15 @@ object SCProofChecker { * Γ, Σ, φ∨ψ |- Δ, Π */ case LeftOr(b, t, disjuncts) => - if (isSameSet(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _))) { - val phiOrPsi = ConnectorFormula(Or, disjuncts) + if (disjuncts.exists(phi => phi.sort != Formula)){ + val culprit = disjuncts.find(phi => phi.sort != Formula).get + SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.sort) + } else if (isSameSet(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _))) { + val phiOrPsi = disjuncts.reduceLeft(or(_)(_)) if ( t.zip(disjuncts).forall { case (s, phi) => isSubset(ref(s).left, b.left + phi) } && isSubset(b.left, t.map(ref(_).left).fold(Set.empty)(_ union _) + phiOrPsi) ) - SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), 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(SCProof(step), Nil, s"Right-hand side of conclusion is not the union of the right-hand sides of the premises.") @@ -109,30 +121,42 @@ object SCProofChecker { * Γ, Σ, φ⇒ψ |- Δ, Π */ case LeftImplies(b, t1, t2, phi, psi) => - 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)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion + ψ must be identical to union of left-hand sides of premisces + φ⇒ψ.") - else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ must be identical to union of right-hand sides of premisces.") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else { + val phiImpPsi = implies(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)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion + ψ must be identical to union of left-hand sides of premisces + φ⇒ψ.") + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ must be identical to union of right-hand sides of premisces.") + } /* * Γ, φ⇒ψ |- Δ Γ, φ⇒ψ, ψ⇒φ |- Δ * -------------- or --------------- * Γ, φ⇔ψ |- Δ Γ, φ⇔ψ |- Δ */ case LeftIff(b, t1, phi, psi) => - val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) - val psiImpPhi = ConnectorFormula(Implies, Seq(psi, phi)) - val phiIffPsi = ConnectorFormula(Iff, Seq(phi, psi)) - if (isSameSet(ref(t1).right, b.right)) - 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) - ) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ⇔ψ must be same as left-hand side of premise + either φ⇒ψ, ψ⇒φ or both.") - else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of premise and conclusion must be the same.") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else { + val phiImpPsi = implies(phi)(psi) + val psiImpPhi = implies(psi)(phi) + val phiIffPsi = iff(phi)(psi) + if (isSameSet(ref(t1).right, b.right)) + 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) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ⇔ψ must be same as left-hand side of premise + either φ⇒ψ, ψ⇒φ or both.") + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of premise and conclusion must be the same.") + } /* * Γ |- φ, Δ @@ -140,12 +164,16 @@ object SCProofChecker { * Γ, ¬φ |- Δ */ case LeftNot(b, t1, phi) => - val nPhi = ConnectorFormula(Neg, Seq(phi)) - if (isSameSet(b.left, ref(t1).left + nPhi)) - if (isSameSet(b.right + phi, ref(t1).right)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise") - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion must be the same as left-hand side of premise + ¬φ") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else { + val nPhi = neg(phi) + if (isSameSet(b.left, ref(t1).left + nPhi)) + if (isSameSet(b.right + phi, ref(t1).right)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise") + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion must be the same as left-hand side of premise + ¬φ") + } /* * Γ, φ[t/x] |- Δ @@ -153,8 +181,14 @@ object SCProofChecker { * Γ, ∀x. φ |- Δ */ case LeftForall(b, t1, phi, x, t) => - if (isSameSet(b.right, ref(t1).right)) - if (isSameSet(b.left + substituteVariablesInFormula(phi, Map(x -> t)), ref(t1).left + BinderFormula(Forall, x, phi))) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (x.sort != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.sort) + else if (t.sort != Term) + SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.sort) + else if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + substituteVariables(phi, Map(x -> t)), ref(t1).left + forall(Lambda(x, phi)))) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ[t/x] must be the same as left-hand side of premise + ∀x. φ") else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") @@ -165,28 +199,18 @@ object SCProofChecker { * Γ, ∃x. φ|- Δ */ case LeftExists(b, t1, phi, x) => - if (isSameSet(b.right, ref(t1).right)) - if (isSameSet(b.left + phi, ref(t1).left + BinderFormula(Exists, x, phi))) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (x.sort != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.sort) + else if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + phi, ref(t1).left + exists(Lambda(x, phi)))) if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, "The variable x must not be free in the resulting sequent.") else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise + ∃x. φ") else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") - /* - * Γ, ∃y.∀x. (x=y) ⇔ φ |- Δ - * ---------------------------- if y is not free in φ - * Γ, ∃!x. φ |- Δ - */ - case LeftExistsOne(b, t1, phi, x) => - val y = VariableLabel(freshId(phi.freeVariables.map(_.id), x.id)) - val temp = BinderFormula(Exists, y, BinderFormula(Forall, x, ConnectorFormula(Iff, List(AtomicFormula(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))) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ must be the same as left-hand side of premise + ∃!x. φ") - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") - // Right rules /* * Γ |- φ, Δ Σ |- ψ, Π @@ -194,82 +218,113 @@ object SCProofChecker { * Γ, Σ |- φ∧ψ, Π, Δ */ case RightAnd(b, t, cunjuncts) => - val phiAndPsi = ConnectorFormula(And, cunjuncts) - if (isSameSet(b.left, t.map(ref(_).left).fold(Set.empty)(_ union _))) - if ( - t.zip(cunjuncts).forall { case (s, phi) => isSubset(ref(s).right, b.right + phi) } && - isSubset(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _) + phiAndPsi) - //isSameSet(cunjuncts.foldLeft(b.right)(_ + _), t.map(ref(_).right).fold(Set.empty)(_ union _) + phiAndPsi) - ) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises φ∧ψ.") - else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + if (cunjuncts.exists(phi => phi.sort != Formula)){ + val culprit = cunjuncts.find(phi => phi.sort != Formula).get + SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.sort) + } else { + val phiAndPsi = cunjuncts.reduce(and(_)(_)) + if (isSameSet(b.left, t.map(ref(_).left).fold(Set.empty)(_ union _))) + if ( + t.zip(cunjuncts).forall { case (s, phi) => isSubset(ref(s).right, b.right + phi) } && + isSubset(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _) + phiAndPsi) + //isSameSet(cunjuncts.foldLeft(b.right)(_ + _), t.map(ref(_).right).fold(Set.empty)(_ union _) + phiAndPsi) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises φ∧ψ.") + else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + } /* * Γ |- φ, Δ Γ |- φ, ψ, Δ * -------------- or --------------- * Γ |- φ∨ψ, Δ Γ |- φ∨ψ, Δ */ case RightOr(b, t1, phi, psi) => - val phiOrPsi = ConnectorFormula(Or, Seq(phi, psi)) - if (isSameSet(ref(t1).left, b.left)) - 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) - ) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ∧ψ must be same as right-hand side of premise + either φ, ψ or both.") - else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the premise and the conclusion must be the same.") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else { + val phiOrPsi = or(phi)(psi) + if (isSameSet(ref(t1).left, b.left)) + 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) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ∧ψ must be same as right-hand side of premise + either φ, ψ or both.") + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the premise and the conclusion must be the same.") + } /* * Γ, φ |- ψ, Δ * -------------- * Γ |- φ⇒ψ, Δ */ case RightImplies(b, t1, phi, psi) => - val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) - if (isSameSet(ref(t1).left, b.left + phi)) - if (isSameSet(b.right + psi, ref(t1).right + phiImpPsi)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + ψ must be same as right-hand side of premise + φ⇒ψ.") - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + psi must be same as left-hand side of premise.") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else { + val phiImpPsi = implies(phi)(psi) + if (isSameSet(ref(t1).left, b.left + phi)) + if (isSameSet(b.right + psi, ref(t1).right + phiImpPsi)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + ψ must be same as right-hand side of premise + φ⇒ψ.") + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + psi must be same as left-hand side of premise.") + } /* * Γ |- φ⇒ψ, Δ Σ |- ψ⇒φ, Π * ---------------------------- * Γ, Σ |- φ⇔ψ, Π, Δ */ case RightIff(b, t1, t2, phi, psi) => - val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) - val psiImpPhi = ConnectorFormula(Implies, Seq(psi, phi)) - val phiIffPsi = ConnectorFormula(Iff, Seq(phi, psi)) - if (isSameSet(b.left, ref(t1).left union ref(t2).left)) - if ( - isSubset(ref(t1).right, b.right + phiImpPsi) && - isSubset(ref(t2).right, b.right + psiImpPhi) && - isSubset(b.right, ref(t1).right union ref(t2).right + phiIffPsi) - ) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), 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(SCProof(step), Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else { + val phiImpPsi = implies(phi)(psi) + val psiImpPhi = implies(psi)(phi) + val phiIffPsi = iff(phi)(psi) + if (isSameSet(b.left, ref(t1).left union ref(t2).left)) + if ( + isSubset(ref(t1).right, b.right + phiImpPsi) && + isSubset(ref(t2).right, b.right + psiImpPhi) && + isSubset(b.right, ref(t1).right union ref(t2).right + phiIffPsi) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), 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(SCProof(step), Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + } /* * Γ, φ |- Δ * -------------- * Γ |- ¬φ, Δ */ case RightNot(b, t1, phi) => - val nPhi = ConnectorFormula(Neg, Seq(phi)) - if (isSameSet(b.right, ref(t1).right + nPhi)) - if (isSameSet(b.left + phi, ref(t1).left)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise") - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion must be the same as right-hand side of premise + ¬φ") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else { + val nPhi = neg(phi) + if (isSameSet(b.right, ref(t1).right + nPhi)) + if (isSameSet(b.left + phi, ref(t1).left)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise") + else SCInvalidProof(SCProof(step), 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 * Γ |- ∀x. φ, Δ */ case RightForall(b, t1, phi, x) => - if (isSameSet(b.left, ref(t1).left)) - if (isSameSet(b.right + phi, ref(t1).right + BinderFormula(Forall, x, phi))) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (x.sort != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.sort) + else if (isSameSet(b.left, ref(t1).left)) + if (isSameSet(b.right + phi, ref(t1).right + forall(Lambda(x, phi)))) if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, "The variable x must not be free in the resulting sequent.") @@ -281,27 +336,39 @@ object SCProofChecker { * Γ |- ∃x. φ, Δ */ case RightExists(b, t1, phi, x, t) => - if (isSameSet(b.left, ref(t1).left)) - if (isSameSet(b.right + substituteVariablesInFormula(phi, Map(x -> t)), ref(t1).right + BinderFormula(Exists, x, phi))) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (x.sort != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.sort) + else if (t.sort != Term) + SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.sort) + else if (isSameSet(b.left, ref(t1).left)) + if (isSameSet(b.right + substituteVariables(phi, Map(x -> t)), ref(t1).right + exists(Lambda(x, phi)))) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, "Right-hand side of the conclusion + φ[t/x] must be the same as right-hand side of the premise + ∃x. φ") else SCInvalidProof(SCProof(step), Nil, "Left-hand sides or conclusion and premise must be the same.") /** *
-           * Γ |- ∃y.∀x. (x=y) ⇔ φ, Δ
-           * ---------------------------- if y is not free in φ
-           * Γ|- ∃!x. φ,  Δ
+           *       Γ |- φ[t/x], Δ
+           * -------------------------- if y is not free in φ
+           *     Γ|- φ[(εx. φ)/x], Δ
            * 
*/ - case RightExistsOne(b, t1, phi, x) => - val y = VariableLabel(freshId(phi.freeVariables.map(_.id), x.id)) - val temp = BinderFormula(Exists, y, BinderFormula(Forall, x, ConnectorFormula(Iff, List(AtomicFormula(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))) + case RightEpsilon(b, t1, phi, x, t) => + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (x.sort != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.sort) + else if (t.sort != Term) + SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.sort) + else if (isSameSet(b.left, ref(t1).left)) { + val expected_top = substituteVariables(phi, Map(x -> t)) + val expected_bot = substituteVariables(phi, Map(x -> epsilon(Lambda(x, phi)))) + if (isSameSet(b.right + expected_top, ref(t1).right + expected_bot)) SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ must be the same as right-hand side of premise + ∃!x. φ") - else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of conclusion and premise must be the same") + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of the conclusion + φ[t/x] must be the same as right-hand side of the premise + ∃x. φ") + } else SCInvalidProof(SCProof(step), Nil, "Left-hand sides or conclusion and premise must be the same.") // Structural rules /* @@ -314,6 +381,59 @@ object SCProofChecker { SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, "Conclusion cannot be trivially derived from premise.") + + /** + *
+           *    Γ |- φ[(λy. e)t/x], Δ
+           * ---------------------------
+           *     Γ |- φ[e[t/y]/x], Δ
+           * 
+ */ + case RightBeta(b, t1, phi, lambda, t, x) => + val Lambda(y, e) = lambda + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + SCInvalidProof(SCProof(step), Nil, "t must have the same type as y, but they are " + t.sort + " and " + y.sort) + else if (e.sort != x.sort) + SCInvalidProof(SCProof(step), Nil, "e must have the same type as x, but they are " + e.sort + " and " + x.sort) + else if (isSameSet(b.left, ref(t1).left)) { + val redex = lambda(t) + val normalized = substituteVariables(e, Map(y -> t)) + val phi_redex = substituteVariables(phi, Map(x -> redex)) + val phi_normalized = substituteVariables(phi, Map(x -> normalized)) + if (isSameSet(b.right + phi_redex, ref(t1).right + phi_normalized) || isSameSet(b.right + phi_normalized, ref(t1).right + phi_redex)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of the conclusion + φ[(λy. e)t/x] must be the same as right-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else SCInvalidProof(SCProof(step), Nil, "Left-hand sides or conclusion and premise must be the same.") + + + /** + *
+           *    Γ, φ[(λy. e)t/x] |- Δ
+           * ---------------------------
+           *     Γ, φ[e[t/y]/x] |- Δ
+           * 
+ */ + case LeftBeta(b, t1, phi, lambda, t, x) => + val Lambda(y, e) = lambda + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + SCInvalidProof(SCProof(step), Nil, "t must have the same type as y, but they are " + t.sort + " and " + y.sort) + else if (e.sort != x.sort) + SCInvalidProof(SCProof(step), Nil, "e must have the same type as x, but they are " + e.sort + " and " + x.sort) + else if (isSameSet(b.right, ref(t1).right)) { + val redex = lambda(t) + val normalized = substituteVariables(e, Map(y -> t)) + val phi_redex = substituteVariables(phi, Map(x -> redex)) + val phi_normalized = substituteVariables(phi, Map(x -> normalized)) + if (isSameSet(b.left + phi_redex, ref(t1).left + phi_normalized) || isSameSet(b.left + phi_normalized, ref(t1).left + phi_redex)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of the conclusion + φ[(λy. e)t/x] must be the same as left-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else SCInvalidProof(SCProof(step), Nil, "Right-hand sides or conclusion and premise must be the same.") + + // Equality Rules /* * Γ, s=s |- Δ @@ -322,8 +442,8 @@ object SCProofChecker { */ case LeftRefl(b, t1, phi) => phi match { - case AtomicFormula(`equality`, Seq(left, right)) => - if (isSameTerm(left, right)) + case equality(left, right) => + if (isSame(left, right)) if (isSameSet(b.right, ref(t1).right)) if (isSameSet(b.left + phi, ref(t1).left)) SCValidProof(SCProof(step)) @@ -340,8 +460,8 @@ object SCProofChecker { */ case RightRefl(b, phi) => phi match { - case AtomicFormula(`equality`, Seq(left, right)) => - if (isSameTerm(left, right)) + case equality(left, right) => + if (isSame(left, right)) if (contains(b.right, phi)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"Right-Hand side of conclusion does not contain φ") @@ -350,151 +470,170 @@ object SCProofChecker { } /* - * Γ, φ(s_) |- Δ - * --------------------- - * Γ, (s=t)_, φ(t_)|- Δ + * Γ, φ(s) |- Δ Σ |- s=t, Π + * -------------------------------- + * Γ, Σ φ(t) |- Δ, Π */ - case LeftSubstEq(b, t1, equals, lambdaPhi) => - val (s_es, t_es) = equals.unzip - val (phi_args, phi_body) = lambdaPhi - if (phi_args.size != s_es.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. - SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") - else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) - SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") - else { - val phi_s_for_f = instantiateTermSchemas(phi_body, (phi_args zip s_es).toMap) - val phi_t_for_f = instantiateTermSchemas(phi_body, (phi_args zip t_es).toMap) - val sEqT_es = equals map { - case (s, t) => - assert(s.vars.size == t.vars.size) - val base = AtomicFormula(equality, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) - (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } - } + case LeftSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") + else /*if (!s.sort.isFunctional) + SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + else */{ + val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) + val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) + + val inner1 = vars.foldLeft(s)(_(_)) + val inner2 = vars.foldLeft(t)(_(_)) + val sEqt = + if (s.sort.isFunctional) + equality(inner1)(inner2) + else + iff(inner1)(inner2) + val varss = vars.toSet - if (isSameSet(b.right, ref(t1).right)) + if ( + isSubset(ref(t1).right, b.right) && + isSubset(ref(t2).right, b.right + sEqt) && + isSubset(b.right, ref(t1).right union ref(t2).right) + ) { if ( - isSameSet(b.left + phi_t_for_f, ref(t1).left ++ sEqT_es + phi_s_for_f) || - isSameSet(b.left + phi_s_for_f, ref(t1).left ++ sEqT_es + phi_t_for_f) - ) - SCValidProof(SCProof(step)) - else - SCInvalidProof( - SCProof(step), - Nil, - "Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_) (or with s_ and t_ swapped)." - ) + isSubset(ref(t1).left, b.left + phi_s_for_f) && + isSubset(ref(t2).left, b.left) && + isSubset(b.left, ref(t1).left union ref(t2).left + phi_t_for_f) + ) { + if ( + ref(t2).left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + ref(t2).right.exists(f => !isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + SCInvalidProof(SCProof(step), Nil, "The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else SCValidProof(SCProof(step)) + } + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") } - /* - * Γ |- φ(s_), Δ - * --------------------- - * Γ, (s=t)_ |- φ(t_), Δ + /* + * Γ, φ(ψ) |- Δ Σ |- a⇔b, Π + * -------------------------------- + * Γ, Σ φ(b) |- Δ, Π */ - case RightSubstEq(b, t1, equals, lambdaPhi) => - val (s_es, t_es) = equals.unzip - val (phi_args, phi_body) = lambdaPhi - if (phi_args.size != equals.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. - SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") - else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) - SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") + case LeftSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (psi.sort != phi_arg.sort || tau.sort != phi_arg.sort) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of ψ and τ.") + else if (!psi.sort.isPredicate) + SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> Term -> Formula)") else { - val phi_s_for_f = instantiateTermSchemas(phi_body, (phi_args zip s_es).toMap) - val phi_t_for_f = instantiateTermSchemas(phi_body, (phi_args zip t_es).toMap) - val sEqT_es = equals map { - case (s, t) => - assert(s.vars.size == t.vars.size) - val base = AtomicFormula(equality, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) - (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } - } + val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> psi)) + val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> tau)) + + val inner1 = vars.foldLeft(psi)(_(_)) + val inner2 = vars.foldLeft(tau)(_(_)) + val sEqt = iff(inner1)(inner2) + val varss = vars.toSet - if (isSameSet(ref(t1).left ++ sEqT_es, b.left)) + if ( + isSubset(ref(t1).right, b.right) && + isSubset(ref(t2).right, b.right + sEqt) && + isSubset(b.right, ref(t1).right union ref(t2).right) + ) { 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) - ) - SCValidProof(SCProof(step)) - else - SCInvalidProof( - SCProof(step), - Nil, - "Right-hand side of the premise and the conclusion should be the same with each containing one of φ(s_) φ(t_), but it isn't the case." - ) - else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the premise + (s=t)_ must be the same as left-hand side of the premise.") + isSubset(ref(t1).left, b.left + phi_s_for_f) && + isSubset(ref(t2).left, b.left) && + isSubset(b.left, ref(t1).left union ref(t2).left + phi_t_for_f) + ) { + if ( + ref(t2).left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + ref(t2).right.exists(f => !isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + SCInvalidProof(SCProof(step), Nil, "The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else SCValidProof(SCProof(step)) + } + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") } + /* - * Γ, φ(ψ_) |- Δ - * --------------------- - * Γ, ψ⇔τ, φ(τ) |- Δ + * Γ |- φ(s), Δ Σ |- s=t, Π + * --------------------------------- + * Γ, Σ |- φ(t), Δ, Π */ - case LeftSubstIff(b, t1, equals, lambdaPhi) => - val (phi_s, tau_s) = equals.unzip - val (phi_args, phi_body) = lambdaPhi - if (phi_args.size != phi_s.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. - SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") - else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) - SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") + case RightSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") else { - val phi_psi_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip phi_s).toMap) - val phi_tau_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip tau_s).toMap) - val psiIffTau = equals map { - case (s, t) => - assert(s.vars.size == t.vars.size) - val base = ConnectorFormula(Iff, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) - (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } - } + val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) + val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) - if (isSameSet(b.right, ref(t1).right)) - if ( - isSameSet(b.left + phi_tau_for_q, ref(t1).left ++ psiIffTau + phi_psi_for_q) || - isSameSet(b.left + phi_psi_for_q, ref(t1).left ++ psiIffTau + phi_tau_for_q) - ) - SCValidProof(SCProof(step)) - else - SCInvalidProof( - SCProof(step), - Nil, - "Left-hand sides of the conclusion + φ(ψ_) must be the same as left-hand side of the premise + (ψ⇔τ)_ + φ(τ_) (or with ψ and τ swapped)." - ) + val inner1 = vars.foldLeft(s)(_(_)) + val inner2 = vars.foldLeft(t)(_(_)) + val sEqt = equality(inner1)(inner2) + val varss = vars.toSet + + if ( + isSubset(ref(t1).right, b.right + phi_s_for_f) && + isSubset(ref(t2).right, b.right + sEqt) && + isSubset(b.right, ref(t1).right union ref(t2).right + phi_t_for_f) + ) { + if (isSameSet(b.left, ref(t1).left union ref(t2).left)) { + if ( + ref(t2).left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + ref(t2).right.exists(f => !isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + SCInvalidProof(SCProof(step), Nil, "The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else SCValidProof(SCProof(step)) + } + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") } + + /* * Γ |- φ[ψ/?p], Δ * --------------------- * Γ, ψ⇔τ |- φ[τ/?p], Δ */ - case RightSubstIff(b, t1, equals, lambdaPhi) => - val (psi_s, tau_s) = equals.unzip - val (phi_args, phi_body) = lambdaPhi - if (phi_args.size != psi_s.size) - SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") - else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) - SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") + case RightSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (psi.sort != phi_arg.sort || tau.sort != phi_arg.sort) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of ψ and τ.") + else if (!psi.sort.isPredicate) + SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> Term -> Formula)") else { - val phi_psi_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip psi_s).toMap) - val phi_tau_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip tau_s).toMap) - val psiIffTau = equals map { - case (s, t) => - assert(s.vars.size == t.vars.size) - val base = ConnectorFormula(Iff, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) - (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } - } + val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> psi)) + val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> tau)) - 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) - ) - SCValidProof(SCProof(step)) - else - SCInvalidProof( - SCProof(step), - Nil, - "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(SCProof(step), Nil, "Left-hand sides of the premise + ψ⇔τ must be the same as left-hand side of the premise.") + val inner1 = vars.foldLeft(psi)(_(_)) + val inner2 = vars.foldLeft(tau)(_(_)) + val sEqt = iff(inner1)(inner2) + val varss = vars.toSet + + if ( + isSubset(ref(t1).right, b.right + phi_s_for_f) && + isSubset(ref(t2).right, b.right + sEqt) && + isSubset(b.right, ref(t1).right union ref(t2).right + phi_t_for_f) + ) { + if (isSameSet(b.left, ref(t1).left union ref(t2).left)) { + if ( + ref(t2).left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + ref(t2).right.exists(f => !isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + SCInvalidProof(SCProof(step), Nil, "The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else SCValidProof(SCProof(step)) + } + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") } @@ -506,9 +645,9 @@ object SCProofChecker { * Γ[ψ/?p] |- Δ[ψ/?p] * */ - case InstSchema(bot, t1, mCon, mPred, mTerm) => + case InstSchema(bot, t1, subst) => val expected = - (ref(t1).left.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm)), ref(t1).right.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm))) + (ref(t1).left.map(phi => substituteVariables(phi, subst)), ref(t1).right.map(phi => substituteVariables(phi, subst))) if (isSameSet(bot.left, expected._1)) if (isSameSet(bot.right, expected._2)) SCValidProof(SCProof(step)) @@ -564,4 +703,4 @@ object SCProofChecker { else possibleError.get } -} +} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala b/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala index b84d84b4..bf679c6b 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala @@ -21,12 +21,14 @@ object SequentCalculus { * @param left the left side of the sequent * @param right the right side of the sequent */ - case class Sequent(left: Set[Formula], right: Set[Formula]) + case class Sequent(left: Set[Expression], right: Set[Expression]){ + require(left.forall(_.sort == Formula) && right.forall(_.sort == Formula), "Sequent can only contain formulas") + } /** * Simple method that transforms a sequent to a logically equivalent formula. */ - def sequentToFormula(s: Sequent): Formula = ConnectorFormula(Implies, List(ConnectorFormula(And, s.left.toSeq), ConnectorFormula(Or, s.right.toSeq))) + def sequentToFormula(s: Sequent): Expression = implies(s.left.reduce(and(_)(_)))(s.right.reduce(or(_)(_))) /** * Checks whether two sequents are equivalent, with respect to [[isSameTerm]]. @@ -85,7 +87,7 @@ object SequentCalculus { * Γ, φ |- φ, Δ * */ - case class Hypothesis(bot: Sequent, phi: Formula) extends SCProofStep { val premises = Seq() } + case class Hypothesis(bot: Sequent, phi: Expression) extends SCProofStep { val premises = Seq() } /** *
@@ -94,7 +96,7 @@ object SequentCalculus {
    *       Γ, Σ |-Δ, Π
    * 
*/ - case class Cut(bot: Sequent, t1: Int, t2: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + case class Cut(bot: Sequent, t1: Int, t2: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } // Left rules /** @@ -104,7 +106,7 @@ object SequentCalculus { * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ * */ - case class LeftAnd(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class LeftAnd(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -113,7 +115,7 @@ object SequentCalculus {
    *    Γ, Σ, φ∨ψ∨... |- Δ, Π
    * 
*/ - case class LeftOr(bot: Sequent, t: Seq[Int], disjuncts: Seq[Formula]) extends SCProofStep { val premises = t } + case class LeftOr(bot: Sequent, t: Seq[Int], disjuncts: Seq[Expression]) extends SCProofStep { val premises = t } /** *
@@ -122,7 +124,7 @@ object SequentCalculus {
    *    Γ, Σ, φ⇒ψ |- Δ, Π
    * 
*/ - case class LeftImplies(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + case class LeftImplies(bot: Sequent, t1: Int, t2: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } /** *
@@ -131,7 +133,7 @@ object SequentCalculus {
    *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
    * 
*/ - case class LeftIff(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class LeftIff(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -140,7 +142,7 @@ object SequentCalculus {
    *   Γ, ¬φ |- Δ
    * 
*/ - case class LeftNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class LeftNot(bot: Sequent, t1: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -150,7 +152,7 @@ object SequentCalculus {
    *
    * 
*/ - case class LeftForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel, t: Term) extends SCProofStep { val premises = Seq(t1) } + case class LeftForall(bot: Sequent, t1: Int, phi: Expression, x: Variable, t: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -160,16 +162,7 @@ object SequentCalculus {
    *
    * 
*/ - case class LeftExists(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *  Γ, ∃y.∀x. (x=y) ⇔ φ |-  Δ
-   * ---------------------------- if y is not free in φ
-   *      Γ, ∃!x. φ |- Δ
-   * 
- */ - case class LeftExistsOne(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + case class LeftExists(bot: Sequent, t1: Int, phi: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } // Right rules /** @@ -179,7 +172,7 @@ object SequentCalculus { * Γ, Σ |- φ∧ψ∧..., Π, Δ * */ - case class RightAnd(bot: Sequent, t: Seq[Int], cunjuncts: Seq[Formula]) extends SCProofStep { val premises = t } + case class RightAnd(bot: Sequent, t: Seq[Int], cunjuncts: Seq[Expression]) extends SCProofStep { val premises = t } /** *
@@ -188,7 +181,7 @@ object SequentCalculus {
    *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
    * 
*/ - case class RightOr(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class RightOr(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -197,7 +190,7 @@ object SequentCalculus {
    *  Γ |- φ⇒ψ, Δ
    * 
*/ - case class RightImplies(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class RightImplies(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -206,7 +199,7 @@ object SequentCalculus {
    *      Γ, Σ |- φ⇔ψ, Π, Δ
    * 
*/ - case class RightIff(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + case class RightIff(bot: Sequent, t1: Int, t2: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } /** *
@@ -215,7 +208,7 @@ object SequentCalculus {
    *   Γ |- ¬φ, Δ
    * 
*/ - case class RightNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class RightNot(bot: Sequent, t1: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -224,7 +217,7 @@ object SequentCalculus {
    *  Γ |- ∀x. φ, Δ
    * 
*/ - case class RightForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + case class RightForall(bot: Sequent, t1: Int, phi: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -235,16 +228,16 @@ object SequentCalculus {
    * (ln-x stands for locally nameless x)
    * 
*/ - case class RightExists(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel, t: Term) extends SCProofStep { val premises = Seq(t1) } + case class RightExists(bot: Sequent, t1: Int, phi: Expression, x: Variable, t: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
-   *  Γ |- ∃y.∀x. (x=y) ⇔ φ, Δ
-   * ---------------------------- if y is not free in φ
-   *      Γ|- ∃!x. φ,  Δ
+   *       Γ |- φ[t/x], Δ
+   * -------------------------- if y is not free in φ
+   *    Γ|- φ[(εx. φ)/x],  Δ
    * 
*/ - case class RightExistsOne(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + case class RightEpsilon(bot: Sequent, t1: Int, phi: Expression, x: Variable, t: Expression) extends SCProofStep { val premises = Seq(t1) } // Structural rule /** @@ -256,6 +249,26 @@ object SequentCalculus { */ case class Weakening(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ |- φ[(λy. e)t/x], Δ
+   * ---------------------------
+   *     Γ |- φ[e[t/y]/x], Δ
+   * 
+ */ + case class LeftBeta(bot: Sequent, t1: Int, phi: Expression, lambda: Lambda, t: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } + + + /** + *
+   *    Γ, φ[(λy. e)t/x] |- Δ
+   * ---------------------------
+   *     Γ, φ[e[t/y]/x] |- Δ
+   * 
+ */ + case class RightBeta(bot: Sequent, t1: Int, phi: Expression, lambda: Lambda, t: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } + // Equality Rules /** *
@@ -264,7 +277,7 @@ object SequentCalculus {
    *     Γ |- Δ
    * 
*/ - case class LeftRefl(bot: Sequent, t1: Int, fa: Formula) extends SCProofStep { val premises = Seq(t1) } + case class LeftRefl(bot: Sequent, t1: Int, fa: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -273,53 +286,55 @@ object SequentCalculus {
    *     |- s=s
    * 
*/ - case class RightRefl(bot: Sequent, fa: Formula) extends SCProofStep { val premises = Seq() } + case class RightRefl(bot: Sequent, fa: Expression) extends SCProofStep { val premises = Seq() } /** *
-   *    Γ, φ(s1,...,sn) |- Δ
-   * ---------------------
-   *  Γ, s1=t1, ..., sn=tn, φ(t1,...tn) |- Δ
+   *  Γ, φ(s) |- Δ     Σ1 |- s=t, Π     
+   * ----------------------------------------
+   *             Γ, Σ φ(t) |- Δ, Π
    * 
+ * equals elements must have type ... -> ... -> Term */ - case class LeftSubstEq(bot: Sequent, t1: Int, equals: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + //case class LeftSubstEq(bot: Sequent, t1: Int, equals: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + case class LeftSubstEq(bot: Sequent, t1: Int, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } /** *
-   *    Γ |- φ(s1,...,sn), Δ
-   * ---------------------
-   *  Γ, s1=t1, ..., sn=tn |- φ(t1,...tn), Δ
+   *  Γ |- φ(s), Δ     Σ1 |- s=t, Π
+   * ---------------------------------
+   *         Γ, Σ |- φ(t), Δ, Π
    * 
+ * equals elements must have type ... -> ... -> Term */ - case class RightSubstEq(bot: Sequent, t1: Int, equals: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + case class RightSubstEq(bot: Sequent, t1: Int, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } /** *
-   *    Γ, φ(a1,...an) |- Δ
-   * ---------------------
-   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
+   *   Γ, φ(ψ) |- Δ     Σ |- ψ⇔τ, Π     
+   * --------------------------------
+   *        Γ, Σ φ(τ) |- Δ, Π
    * 
+ * equals elements must have type ... -> ... -> Formula */ - case class LeftSubstIff(bot: Sequent, t1: Int, equals: List[(LambdaTermFormula, LambdaTermFormula)], lambdaPhi: (Seq[SchematicAtomicLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + case class LeftSubstIff(bot: Sequent, t1: Int, t2: Int, psi: Expression, tau: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } /** *
-   *    Γ |- φ(a1,...an), Δ
-   * ---------------------
-   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
+   *   Γ |- φ(ψ), Δ     Σ |- ψ⇔τ, Π     
+   * --------------------------------
+   *        Γ, Σ |- φ(τ), Δ, Π
    * 
+ * equals elements must have type ... -> ... -> Formula */ - - case class RightSubstIff(bot: Sequent, t1: Int, equals: List[(LambdaTermFormula, LambdaTermFormula)], lambdaPhi: (Seq[SchematicAtomicLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + case class RightSubstIff(bot: Sequent, t1: Int, t2: Int, psi: Expression, tau: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } // Rule for schemas case class InstSchema( bot: Sequent, t1: Int, - mCon: Map[SchematicConnectorLabel, LambdaFormulaFormula], - mPred: Map[SchematicAtomicLabel, LambdaTermFormula], - mTerm: Map[SchematicTermLabel, LambdaTermTerm] + subst: Map[Variable, Expression] ) extends SCProofStep { val premises = Seq(t1) } // Proof Organisation rules @@ -345,4 +360,4 @@ object SequentCalculus { */ case class Sorry(bot: Sequent) extends SCProofStep { val premises = Seq() } -} +} \ No newline at end of file diff --git a/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala b/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala new file mode 100644 index 00000000..b6438378 --- /dev/null +++ b/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala @@ -0,0 +1,172 @@ +package lisa.automation +import lisa.fol.FOL.{*, given} +import lisa.prooflib.BasicStepTactic.* +import lisa.prooflib.ProofTacticLib.* +import lisa.prooflib.SimpleDeducedSteps.* +import lisa.prooflib.* +import lisa.utils.parsing.UnreachableException + + + + +/////////////////////////////// +///////// E-graph ///////////// +/////////////////////////////// + +import scala.collection.mutable + + + + + +class EGraphTermsSimp() { + + val termParents = mutable.Map[Term, mutable.Set[AppliedFunctional]]() + val termUF = new UnionFind[Term]() + val termProofMap = mutable.Map[(Term, Term), Boolean]() + + + + def find(id: Term): Term = termUF.find(id) + + def add(node: Term): Term = + termUF.add(node) + termParents(node) = mutable.Set() + node match + case node @ AppliedFunctional(_, args) => + + args.foreach(child => + add(child) + termParents(child).add(node) + ) + node + case _ => node + + + def merge(id1: Term, id2: Term): Unit = { + mergeWithStep(id1, id2, true) + } + + type Sig = (TermLabel[?]|Term, List[Int]) + val termSigs = mutable.Map[Sig, Term]() + val codes = mutable.Map[Term, Int]() + + protected def mergeWithStep(id1: Term, id2: Term, isExternal: Boolean): Unit = { + if find(id1) == find(id2) then return () + termProofMap((id1, id2)) = isExternal + val (small, big ) = if termParents(find(id1)).size < termParents(find(id2)).size then + (id1, id2) else (id2, id1) + codes(find(small)) = codes(find(big)) + termUF.union(id1, id2) + val newId = find(id1) + var worklist = List[(Term, Term, Boolean)]() + + termParents(small).foreach { pTerm => + val canonicalPTerm = canonicalize(pTerm) + if termSigs.contains(canonicalPTerm) then + val qTerm = termSigs(canonicalPTerm) + mergeWithStep(pTerm, qTerm, false) + else + termSigs(canonicalPTerm) = pTerm + } + termParents(newId) = termParents(big) + termParents(newId).addAll(termParents(small)) + } + + def canonicalize(node: Term): Sig = node match + case AppliedFunctional(label, args) => + (label, args.map(a => codes(find(a))).toList) + case _ => (node, List()) + + + + + // Explain + + + + + def explain(id1: Term, id2: Term): Option[List[(Term, Term, Boolean)]] = { + val steps = termUF.explain(id1, id2) + steps.map(_.map { a => (a._1, a._2, termProofMap(a)) + + }) + } + + + + + + + + + + + + // Proofs Lisa + + def proveTerm(using lib: Library, proof: lib.Proof)(id1: Term, id2:Term, base: Sequent): proof.ProofTacticJudgement = + TacticSubproof { proveInnerTerm(id1, id2, base) } + + def proveInnerTerm(using lib: Library, proof: lib.Proof)(id1: Term, id2:Term, base: Sequent): Unit = { + import lib.* + val steps = explain(id1, id2) + steps match { + case None => throw new Exception("No proof found in the egraph") + case Some(steps) => // External + have(base.left |- (base.right + (id1 === id2))) by Restate + var current = id1 + steps.foreach { + case (l, r, true) => + current = if current == l then r else l + val goalSequent = base.left |- (base.right + (id1 === r)) + val x = freshVariable(id1) + //thenHave(id1 === current) by Transitivity(l === r) + have(goalSequent) by RightSubstEq.withParametersSimple(List((l, r)), lambda(x, id1 === x))(lastStep) + case (l, r, false) => // Congruence + val prev = lastStep + val leqr = have(base.left |- (base.right + (l === r))) subproof { sp ?=> + (l, r) match + case (AppliedFunctional(labell, argsl), AppliedFunctional(labelr, argsr)) if labell == labelr && argsl.size == argsr.size => + var freshn = freshId((l.freeVariables ++ r.freeVariables).map(_.id), "n").no + val ziped = (argsl zip argsr) + var zip = List[(Term, Term)]() + var children = List[Term]() + var vars = List[Variable]() + var steps = List[(Formula, sp.ProofStep)]() + ziped.reverse.foreach { (al, ar) => + if al == ar then children = al :: children + else { + val x = Variable(Identifier("n", freshn)) + freshn = freshn + 1 + children = x :: children + vars = x :: vars + steps = (al === ar, have(proveTerm(al, ar, base))) :: steps + zip = (al, ar) :: zip + } + } + have(base.left |- (base.right + (l === l))) by Restate + val eqs = zip.map((l, r) => l === r) + val goal = have((base.left ++ eqs) |- (base.right + (l === r))).by.bot + have((base.left ++ eqs) |- (base.right + (l === r))) by RightSubstEq.withParametersSimple(zip, lambda(vars, l === labelr.applyUnsafe(children)))(lastStep) + steps.foreach { s => + have( + if s._2.bot.left.contains(s._1) then lastStep.bot else lastStep.bot -<< s._1 + ) by Cut(s._2, lastStep) + } + case _ => + println(s"l: $l") + println(s"r: $r") + throw UnreachableException + + } + val goalSequent = base.left |- (base.right + (id1 === r)) + val x = freshVariable(id1) + have(goalSequent +<< (l === r)) by RightSubstEq.withParametersSimple(List((l, r)), lambda(x, id1 === x))(prev) + have(goalSequent) by Cut(leqr, lastStep) + } + } + } + + +} \ No newline at end of file diff --git a/lisa-utils/src/main/scala/lisa/utils/K.scala b/lisa-utils/src/main/scala/lisa/utils/K.scala index 2269cfed..1980bc0f 100644 --- a/lisa-utils/src/main/scala/lisa/utils/K.scala +++ b/lisa-utils/src/main/scala/lisa/utils/K.scala @@ -11,6 +11,5 @@ object K { export lisa.kernel.proof.RunningTheoryJudgement as Judgement export lisa.kernel.proof.RunningTheoryJudgement.* export lisa.utils.KernelHelpers.{*, given} - export lisa.utils.parsing.FOLPrinter.* } diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 70d40452..bc6a0582 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -3,110 +3,152 @@ package lisa.utils import lisa.kernel.fol.FOL.* import lisa.kernel.proof.RunningTheoryJudgement.InvalidJustification import lisa.kernel.proof.SCProofCheckerJudgement.SCInvalidProof +import lisa.kernel.proof.SCProofCheckerJudgement.SCValidProof import lisa.kernel.proof.SequentCalculus.* import lisa.kernel.proof.* -import lisa.utils.FOLParser import scala.annotation.targetName - /** * A helper file that provides various syntactic sugars for LISA's FOL and proofs at the Kernel level. */ object KernelHelpers { + def predicateType(arity: Int) = Range(0, arity).foldLeft(Formula: Sort)((acc, _) => Term -> acc) + def functionType(arity: Int) = Range(0, arity).foldLeft(Term: Sort)((acc, _) => Term -> acc) + ///////////////// // FOL helpers // ///////////////// /* Prefix syntax */ + val Equality = equality val === = equality - val ⊤ : Formula = top() - val ⊥ : Formula = bot() - val True: Formula = top() - val False: Formula = bot() + val ⊤ : Expression = top + val ⊥ : Expression = bot + val True: Expression = top + val False: Expression = bot - val neg = Neg + val Neg = neg val ¬ = neg val ! = neg - val and = And - val /\ = And - val or = Or - val \/ = Or - val implies = Implies - val ==> = Implies - val iff = Iff - val <=> = Iff - val forall = Forall + val And = and + val /\ = and + val Or = or + val \/ = or + val Implies = implies + val ==> = implies + val Iff = iff + val <=> = iff + val Forall = forall val ∀ = forall - val exists = Exists + val Exists = exists val ∃ = exists - val existsOne = ExistsOne - val ∃! = existsOne - - extension [L <: TermLabel](label: L) { - def apply(args: Term*): Term = Term(label, args) - @targetName("applySeq") - def apply(args: Seq[Term]): Term = Term(label, args) - def unapply(f: Formula): Option[Seq[Formula]] = f match { - case ConnectorFormula(`label`, args) => Some(args) - case _ => None - } - } + val Epsilon = epsilon + val ε = epsilon + - extension [L <: AtomicLabel](label: L) { - def apply(args: Term*): Formula = AtomicFormula(label, args) - @targetName("applySeq") - def apply(args: Seq[Term]): Formula = AtomicFormula(label, args) - def unapply(f: Formula): Option[Seq[Term]] = f match { - case AtomicFormula(`label`, args) => Some(args) + extension (binder: forall.type) { + @targetName("forallApply") + def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) + @targetName("forallUnapply") + def unapply(e: Expression): Option[(Variable, Expression)] = e match { + case Application(forall, Lambda(x, inner)) => Some((x, inner)) case _ => None } } - - extension [L <: ConnectorLabel](label: L) { - def apply(args: Formula*): Formula = ConnectorFormula(label, args) - @targetName("applySeq") - def apply(args: Seq[Formula]): Formula = ConnectorFormula(label, args) - def unapply(f: Formula): Option[Seq[Formula]] = f match { - case ConnectorFormula(`label`, args) => Some(args) + extension (binder: exists.type) { + @targetName("existsApply") + def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) + @targetName("existsUnapply") + def unapply(e: Expression): Option[(Variable, Expression)] = e match { + case Application(exists, Lambda(x, inner)) => Some((x, inner)) case _ => None } } - - extension [L <: BinderLabel](label: L) { - def apply(bound: VariableLabel, inner: Formula): Formula = BinderFormula(label, bound, inner) - def unapply(f: Formula): Option[(VariableLabel, Formula)] = f match { - case BinderFormula(`label`, x, inner) => Some((x, inner)) + extension (binder: epsilon.type) { + @targetName("epsilonApply") + def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) + @targetName("epsilonUnapply") + def unapply(e: Expression): Option[(Variable, Expression)] = e match { + case Application(epsilon, Lambda(x, inner)) => Some((x, inner)) case _ => None } } + def multiand(args: Seq[Expression]): Expression = args.reduceLeft(and(_)(_)) + def multior(args: Seq[Expression]): Expression = args.reduceLeft(or(_)(_)) + def multiapply(f: Expression)(args: Seq[Expression]): Expression = args.foldLeft(f)(_(_)) + /* Infix syntax */ - extension (f: Formula) { - def unary_! = ConnectorFormula(Neg, Seq(f)) - infix inline def ==>(g: Formula): Formula = ConnectorFormula(Implies, Seq(f, g)) - infix inline def <=>(g: Formula): Formula = ConnectorFormula(Iff, Seq(f, g)) - infix inline def /\(g: Formula): Formula = ConnectorFormula(And, Seq(f, g)) - infix inline def \/(g: Formula): Formula = ConnectorFormula(Or, Seq(f, g)) + object Multiapp { + def unapply(e: Expression): Option[(Expression, Seq[Expression])] = + def inner(e: Expression): Option[List[Expression]] = e match + case Application(f, arg) => inner(f) map (l => arg :: l) + case _ => None + inner(e).map(l => {val rev = l.reverse; (rev.head, rev.tail)}) + } - extension (t: Term) { - infix def ===(u: Term): Formula = AtomicFormula(equality, Seq(t, u)) - infix def =(u: Term): Formula = AtomicFormula(equality, Seq(t, u)) + extension (f: Expression) { + def unary_! = neg(f) + infix inline def ==>(g: Expression): Expression = implies(f)(g) + infix inline def <=>(g: Expression): Expression = iff(f)(g) + infix inline def /\(g: Expression): Expression = and(f)(g) + infix inline def \/(g: Expression): Expression = or(f)(g) + infix def ===(g: Expression): Expression = equality(f)(g) + infix def =(g: Expression): Expression = equality(f)(g) + + def maxVarId(): Int = f match { + case Variable(id, _) => id.no+1 + case Constant(_, _) => 0 + case Application(f, arg) => f.maxVarId() max arg.maxVarId() + case Lambda(v, inner) => v.id.no max inner.maxVarId() + } + + def leadingVars(): List[Variable] = + def recurse(e:Expression) : List[Variable] = e match { + case Lambda(v, inner) => v :: recurse(inner) + case _ => Nil + } + recurse(f).reverse + + def repr: String = f match + case equality(a, b) => s"${a.repr} === ${b.repr}" + case neg(a) => s"!${a.repr}" + case and(a, b) => s"(${a.repr} /\\ ${b.repr})" + case or(a, b) => s"(${a.repr} \\/ ${b.repr})" + case implies(a, b) => s"(${a.repr} ==> ${b.repr})" + case iff(a, b) => s"(${a.repr} <=> ${b.repr})" + case forall(v, inner) => s"(forall(${v.repr}, ${inner.repr})" + case exists(v, inner) => s"(exists(${v.repr}, ${inner.repr})" + case epsilon(v, inner) => s"(epsilon(${v.repr}, ${inner.repr})" + + case Application(f, arg) => s"${f.repr}(${arg.repr})" + case Constant(id, sort) => id.toString + case Lambda(v, body) => s"lambda(${v.repr}, ${body.repr})" + case Variable(id, sort) => id.toString + + def fullRepr: String = f match + case Application(f, arg) => s"${f.fullRepr}(${arg.fullRepr})" + case Constant(id, sort) => s"cst(${id},${sort})" + case Lambda(v, body) => s"λ${v.fullRepr}.${body.fullRepr}" + case Variable(id, sort) => s"v(${id},${sort})" } /* Conversions */ - given Conversion[TermLabel, Term] = Term(_, Seq()) - given Conversion[Term, TermLabel] = _.label + /* + given Conversion[TermLabel, Expression] = Expression(_, Seq()) + given Conversion[Expression, TermLabel] = _.label given Conversion[AtomicLabel, AtomicFormula] = AtomicFormula(_, Seq()) given Conversion[AtomicFormula, AtomicLabel] = _.label given Conversion[VariableFormulaLabel, AtomicFormula] = AtomicFormula(_, Seq()) given Conversion[(Boolean, List[Int], String), Option[(List[Int], String)]] = tr => if (tr._1) None else Some(tr._2, tr._3) - given Conversion[Formula, Sequent] = () |- _ +*/ + given Conversion[Expression, Sequent] = () |- _ /* Sequents */ @@ -114,10 +156,10 @@ object KernelHelpers { extension (s: Sequent) { // non OL-based / naive Sequent manipulation - infix def +<<(f: Formula): Sequent = s.copy(left = s.left + f) - infix def -<<(f: Formula): Sequent = s.copy(left = s.left - f) - infix def +>>(f: Formula): Sequent = s.copy(right = s.right + f) - infix def ->>(f: Formula): Sequent = s.copy(right = s.right - f) + infix def +<<(f: Expression): Sequent = s.copy(left = s.left + f) + infix def -<<(f: Expression): Sequent = s.copy(left = s.left - f) + infix def +>>(f: Expression): Sequent = s.copy(right = s.right + f) + infix def ->>(f: Expression): Sequent = s.copy(right = s.right - f) infix def ++<<(s1: Sequent): Sequent = s.copy(left = s.left ++ s1.left) infix def --<<(s1: Sequent): Sequent = s.copy(left = s.left -- s1.left) infix def ++>>(s1: Sequent): Sequent = s.copy(right = s.right ++ s1.right) @@ -126,31 +168,35 @@ object KernelHelpers { infix def --(s1: Sequent): Sequent = s.copy(left = s.left -- s1.left, right = s.right -- s1.right) // OL-based Sequent manipulation - infix def removeLeft(f: Formula): Sequent = s.copy(left = s.left.filterNot(isSame(_, f))) - infix def removeRight(f: Formula): Sequent = s.copy(right = s.right.filterNot(isSame(_, f))) + infix def removeLeft(f: Expression): Sequent = s.copy(left = s.left.filterNot(isSame(_, f))) + infix def removeRight(f: Expression): Sequent = s.copy(right = s.right.filterNot(isSame(_, f))) infix def removeAllLeft(s1: Sequent): Sequent = s.copy(left = s.left.filterNot(e1 => s1.left.exists(e2 => isSame(e1, e2)))) - infix def removeAllLeft(s1: Set[Formula]): Sequent = s.copy(left = s.left.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) + infix def removeAllLeft(s1: Set[Expression]): Sequent = s.copy(left = s.left.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) infix def removeAllRight(s1: Sequent): Sequent = s.copy(right = s.right.filterNot(e1 => s1.right.exists(e2 => isSame(e1, e2)))) - infix def removeAllRight(s1: Set[Formula]): Sequent = s.copy(right = s.right.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) + infix def removeAllRight(s1: Set[Expression]): Sequent = s.copy(right = s.right.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) infix def removeAll(s1: Sequent): Sequent = s.copy(left = s.left.filterNot(e1 => s1.left.exists(e2 => isSame(e1, e2))), right = s.right.filterNot(e1 => s1.right.exists(e2 => isSame(e1, e2)))) - infix def addLeftIfNotExists(f: Formula): Sequent = if (s.left.exists(isSame(_, f))) s else (s +<< f) - infix def addRightIfNotExists(f: Formula): Sequent = if (s.right.exists(isSame(_, f))) s else (s +>> f) + infix def addLeftIfNotExists(f: Expression): Sequent = if (s.left.exists(isSame(_, f))) s else (s +<< f) + infix def addRightIfNotExists(f: Expression): Sequent = if (s.right.exists(isSame(_, f))) s else (s +>> f) infix def addAllLeftIfNotExists(s1: Sequent): Sequent = s ++<< s1.copy(left = s1.left.filterNot(e1 => s.left.exists(isSame(_, e1)))) infix def addAllRightIfNotExists(s1: Sequent): Sequent = s ++>> s1.copy(right = s1.right.filterNot(e1 => s.right.exists(isSame(_, e1)))) infix def addAllIfNotExists(s1: Sequent): Sequent = s ++ s1.copy(left = s1.left.filterNot(e1 => s.left.exists(isSame(_, e1))), right = s1.right.filterNot(e1 => s.right.exists(isSame(_, e1)))) // OL shorthands - infix def +?(f: Formula): Sequent = s addRightIfNotExists f - infix def ->?(f: Formula): Sequent = s removeRight f + infix def +?(f: Expression): Sequent = s addRightIfNotExists f + infix def ->?(f: Expression): Sequent = s removeRight f infix def ++?(s1: Sequent): Sequent = s addAllRightIfNotExists s1 infix def -->?(s1: Sequent): Sequent = s removeAllRight s1 infix def --?(s1: Sequent): Sequent = s removeAll s1 infix def ++?(s1: Sequent): Sequent = s addAllIfNotExists s1 + + def repr: String = s"${s.left.map(_.repr).mkString(", ")} |- ${s.right.map(_.repr).mkString(", ")}" + + def fullRepr: String = s"${s.left.map(_.fullRepr).mkString(", ")} |- ${s.right.map(_.fullRepr).mkString(", ")}" } // TODO: Should make less generic @@ -160,34 +206,30 @@ object KernelHelpers { * @tparam T The type to convert from */ protected trait FormulaSetConverter[T] { - def apply(t: T): Set[Formula] + def apply(t: T): Set[Expression] } given FormulaSetConverter[Unit] with { - override def apply(u: Unit): Set[Formula] = Set.empty + override def apply(u: Unit): Set[Expression] = Set.empty } given FormulaSetConverter[EmptyTuple] with { - override def apply(t: EmptyTuple): Set[Formula] = Set.empty - } - - given [H <: Formula, T <: Tuple](using c: FormulaSetConverter[T]): FormulaSetConverter[H *: T] with { - override def apply(t: H *: T): Set[Formula] = c.apply(t.tail) + t.head + override def apply(t: EmptyTuple): Set[Expression] = Set.empty } - given formula_to_set[T <: Formula]: FormulaSetConverter[T] with { - override def apply(f: T): Set[Formula] = Set(f) + given [H <: Expression, T <: Tuple](using c: FormulaSetConverter[T]): FormulaSetConverter[H *: T] with { + override def apply(t: H *: T): Set[Expression] = c.apply(t.tail) + t.head } - given [T <: Formula, I <: Iterable[T]]: FormulaSetConverter[I] with { - override def apply(s: I): Set[Formula] = s.toSet + given formula_to_set[T <: Expression]: FormulaSetConverter[T] with { + override def apply(f: T): Set[Expression] = Set(f) } - given FormulaSetConverter[VariableFormulaLabel] with { - override def apply(s: VariableFormulaLabel): Set[Formula] = Set(s()) + given [T <: Expression, I <: Iterable[T]]: FormulaSetConverter[I] with { + override def apply(s: I): Set[Expression] = s.toSet } - private def any2set[A, T <: A](any: T)(using c: FormulaSetConverter[T]): Set[Formula] = c.apply(any) + private def any2set[A, T <: A](any: T)(using c: FormulaSetConverter[T]): Set[Expression] = c.apply(any) extension [A, T1 <: A](left: T1)(using FormulaSetConverter[T1]) { infix def |-[B, T2 <: B](right: T2)(using FormulaSetConverter[T2]): Sequent = Sequent(any2set(left), any2set(right)) @@ -196,6 +238,11 @@ object KernelHelpers { // Instatiation functions for formulas lifted to sequents. + def substituteVariablesInSequent(s: Sequent, m: Map[Variable, Expression]): Sequent = { + s.left.map(phi => substituteVariables(phi, m)) |- s.right.map(phi => substituteVariables(phi, m)) + } + + /* def instantiatePredicateSchemaInSequent(s: Sequent, m: Map[SchematicAtomicLabel, LambdaTermFormula]): Sequent = { s.left.map(phi => instantiatePredicateSchemas(phi, m)) |- s.right.map(phi => instantiatePredicateSchemas(phi, m)) } @@ -212,6 +259,7 @@ object KernelHelpers { ): Sequent = { s.left.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm)) |- s.right.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm)) } + */ ////////////////////// // SCProofs helpers // @@ -240,50 +288,50 @@ object KernelHelpers { def followPath(path: Seq[Int]): SCProofStep = SCSubproof(p, p.imports.indices).followPath(path) } - // Conversions from String to datatypes - // given Conversion[String, Sequent] = FOLParser.parseSequent(_) - // given Conversion[String, Formula] = FOLParser.parseFormula(_) - // given Conversion[String, Term] = FOLParser.parseTerm(_) - // given Conversion[String, VariableLabel] = s => VariableLabel(if (s.head == '?') s.tail else s) - +/* // Conversion from pairs (e.g. x -> f(x)) to lambdas - given Conversion[Term, LambdaTermTerm] = LambdaTermTerm(Seq(), _) - given Conversion[VariableLabel, LambdaTermTerm] = a => LambdaTermTerm(Seq(), a: Term) - given Conversion[(VariableLabel, Term), LambdaTermTerm] = a => LambdaTermTerm(Seq(a._1), a._2) - given Conversion[(Seq[VariableLabel], Term), LambdaTermTerm] = a => LambdaTermTerm(a._1, a._2) + given Conversion[Expression, LambdaTermTerm] = LambdaTermTerm(Seq(), _) + given Conversion[VariableLabel, LambdaTermTerm] = a => LambdaTermTerm(Seq(), a: Expression) + given Conversion[(VariableLabel, Expression), LambdaTermTerm] = a => LambdaTermTerm(Seq(a._1), a._2) + given Conversion[(Seq[VariableLabel], Expression), LambdaTermTerm] = a => LambdaTermTerm(a._1, a._2) - given Conversion[Formula, LambdaTermFormula] = LambdaTermFormula(Seq(), _) - given Conversion[(VariableLabel, Formula), LambdaTermFormula] = a => LambdaTermFormula(Seq(a._1), a._2) - given Conversion[(Seq[VariableLabel], Formula), LambdaTermFormula] = a => LambdaTermFormula(a._1, a._2) + given Conversion[Expression, LambdaTermFormula] = LambdaTermFormula(Seq(), _) + given Conversion[(VariableLabel, Expression), LambdaTermFormula] = a => LambdaTermFormula(Seq(a._1), a._2) + given Conversion[(Seq[VariableLabel], Expression), LambdaTermFormula] = a => LambdaTermFormula(a._1, a._2) - given Conversion[Formula, LambdaFormulaFormula] = LambdaFormulaFormula(Seq(), _) - given Conversion[(VariableFormulaLabel, Formula), LambdaFormulaFormula] = a => LambdaFormulaFormula(Seq(a._1), a._2) - given Conversion[(Seq[VariableFormulaLabel], Formula), LambdaFormulaFormula] = a => LambdaFormulaFormula(a._1, a._2) + given Conversion[Expression, LambdaFormulaFormula] = LambdaFormulaFormula(Seq(), _) + given Conversion[(VariableFormulaLabel, Expression), LambdaFormulaFormula] = a => LambdaFormulaFormula(Seq(a._1), a._2) + given Conversion[(Seq[VariableFormulaLabel], Expression), LambdaFormulaFormula] = a => LambdaFormulaFormula(a._1, a._2) + def // Shortcut for LambdaTermTerm, LambdaTermFormula and LambdaFormulaFormula construction - def lambda(x: VariableLabel, t: Term): LambdaTermTerm = LambdaTermTerm(Seq(x), t) - def lambda(xs: Seq[VariableLabel], t: Term): LambdaTermTerm = LambdaTermTerm(xs, t) + def lambda(x: VariableLabel, t: Expression): LambdaTermTerm = LambdaTermTerm(Seq(x), t) + def lambda(xs: Seq[VariableLabel], t: Expression): LambdaTermTerm = LambdaTermTerm(xs, t) def lambda(x: VariableLabel, l: LambdaTermTerm): LambdaTermTerm = LambdaTermTerm(Seq(x) ++ l.vars, l.body) def lambda(xs: Seq[VariableLabel], l: LambdaTermTerm): LambdaTermTerm = LambdaTermTerm(xs ++ l.vars, l.body) - def lambda(x: VariableLabel, phi: Formula): LambdaTermFormula = LambdaTermFormula(Seq(x), phi) - def lambda(xs: Seq[VariableLabel], phi: Formula): LambdaTermFormula = LambdaTermFormula(xs, phi) + def lambda(x: VariableLabel, phi: Expression): LambdaTermFormula = LambdaTermFormula(Seq(x), phi) + def lambda(xs: Seq[VariableLabel], phi: Expression): LambdaTermFormula = LambdaTermFormula(xs, phi) def lambda(x: VariableLabel, l: LambdaTermFormula): LambdaTermFormula = LambdaTermFormula(Seq(x) ++ l.vars, l.body) def lambda(xs: Seq[VariableLabel], l: LambdaTermFormula): LambdaTermFormula = LambdaTermFormula(xs ++ l.vars, l.body) - def lambda(X: VariableFormulaLabel, phi: Formula): LambdaFormulaFormula = LambdaFormulaFormula(Seq(X), phi) - def lambda(Xs: Seq[VariableFormulaLabel], phi: Formula): LambdaFormulaFormula = LambdaFormulaFormula(Xs, phi) + def lambda(X: VariableFormulaLabel, phi: Expression): LambdaFormulaFormula = LambdaFormulaFormula(Seq(X), phi) + def lambda(Xs: Seq[VariableFormulaLabel], phi: Expression): LambdaFormulaFormula = LambdaFormulaFormula(Xs, phi) def lambda(X: VariableFormulaLabel, l: LambdaFormulaFormula): LambdaFormulaFormula = LambdaFormulaFormula(Seq(X) ++ l.vars, l.body) def lambda(Xs: Seq[VariableFormulaLabel], l: LambdaFormulaFormula): LambdaFormulaFormula = LambdaFormulaFormula(Xs ++ l.vars, l.body) - - def instantiateBinder(f: BinderFormula, t: Term): Formula = substituteVariablesInFormula(f.inner, Map(f.bound -> t)) +*/ + def lambda(xs: Seq[Variable], t: Expression): Expression = xs.foldRight(t)((x, t) => Lambda(x, t)) + def reduceLambda(f: Lambda, t: Expression): Expression = substituteVariables(f.body, Map(f.v -> t)) // declare symbols easily: "val x = variable;" - def variable(using name: sourcecode.Name): VariableLabel = VariableLabel(name.value) - def function(arity: Integer)(using name: sourcecode.Name): SchematicFunctionLabel = SchematicFunctionLabel(name.value, arity) - def formulaVariable(using name: sourcecode.Name): VariableFormulaLabel = VariableFormulaLabel(name.value) - def predicate(arity: Integer)(using name: sourcecode.Name): SchematicPredicateLabel = SchematicPredicateLabel(name.value, arity) - def connector(arity: Integer)(using name: sourcecode.Name): SchematicConnectorLabel = SchematicConnectorLabel(name.value, arity) + def HOvariable(using name: sourcecode.Name)(sort: Sort): Variable = Variable(name.value, sort) + def variable(using name: sourcecode.Name): Variable = Variable(name.value, Term) + def v(id: Identifier, sort:Sort): Variable = Variable(id, sort) + def function(arity: Integer)(using name: sourcecode.Name): Variable = Variable(name.value, Range(0, arity).foldLeft(Term: Sort)((acc, _)=> Term -> acc)) + def formulaVariable(using name: sourcecode.Name): Variable = Variable(name.value, Formula) + def predicate(arity: Integer)(using name: sourcecode.Name): Variable = Variable(name.value, Range(0, arity).foldLeft(Formula: Sort)((acc, _)=> Term -> acc)) + def connector(arity: Integer)(using name: sourcecode.Name): Variable = Variable(name.value, Range(0, arity).foldLeft(Formula: Sort)((acc, _)=> Formula -> acc)) + def cst(id: Identifier, sort:Sort): Constant = Constant(id, sort) // Conversions from String to Identifier class InvalidIdentifierException(identifier: String, errorMessage: String) extends LisaException(errorMessage) { @@ -336,9 +384,9 @@ object KernelHelpers { ///////////////////////////// extension (theory: RunningTheory) { - def makeAxiom(using name: sourcecode.Name)(formula: Formula): theory.Axiom = theory.addAxiom(name.value, formula) match { + def makeAxiom(using name: sourcecode.Name)(formula: Expression): theory.Axiom = theory.addAxiom(name.value, formula) match { case Some(value) => value - case None => throw new LisaException.InvalidKernelAxiomException("Axiom contains undefined symbols", name.value, formula, theory) + case None => throw new Exception("Axiom contains undefined symbols " + name.value + formula + theory) } /** @@ -348,32 +396,22 @@ object KernelHelpers { def theorem(name: String, statement: Sequent, proof: SCProof, justifications: Seq[theory.Justification]): RunningTheoryJudgement[theory.Theorem] = { if (statement == proof.conclusion) theory.makeTheorem(name, statement, proof, justifications) else if (isSameSequent(statement, proof.conclusion)) theory.makeTheorem(name, statement, proof.appended(Restate(statement, proof.length - 1)), justifications) - else InvalidJustification(s"The proof proves \n ${FOLPrinter.prettySequent(proof.conclusion)}\ninstead of claimed \n ${FOLPrinter.prettySequent(statement)}", None) - } - - /** - * Make a function definition in the theory, but only ask for the identifier of the new symbol; Arity is inferred - * of the theorem to have more explicit writing and for sanity check. See [[lisa.kernel.proof.RunningTheory.makeFunctionDefinition]] - */ - def functionDefinition( - symbol: String, - expression: LambdaTermFormula, - out: VariableLabel, - proof: SCProof, - proven: Formula, - justifications: Seq[theory.Justification] - ): RunningTheoryJudgement[theory.FunctionDefinition] = { - val label = ConstantFunctionLabel(symbol, expression.vars.size) - theory.makeFunctionDefinition(proof, justifications, label, out, expression, proven) + else InvalidJustification(s"The proof proves \n ${proof.conclusion.repr}\ninstead of claimed \n ${statement.repr}", None) } /** * Make a predicate definition in the theory, but only ask for the identifier of the new symbol; Arity is inferred * of the theorem to have more explicit writing and for sanity check. See also [[lisa.kernel.proof.RunningTheory.makePredicateDefinition]] */ - def predicateDefinition(symbol: String, expression: LambdaTermFormula): RunningTheoryJudgement[theory.PredicateDefinition] = { - val label = ConstantAtomicLabel(symbol, expression.vars.size) - theory.makePredicateDefinition(label, expression) + def definition(symbol: String, expression: Expression): RunningTheoryJudgement[theory.Definition] = { + val label = Constant(symbol, expression.sort) + val vars = expression.leadingVars() + if (vars.length == expression.sort.depth) then + theory.makeDefinition(label, expression, vars) + else + var maxid = expression.maxVarId()-1 + val newvars = flatTypeParameters(expression.sort).drop(vars.length).map(t => {maxid+=1;Variable(Identifier("x", maxid), t)}) + theory.makeDefinition(label, expression, vars ++ newvars) } /** @@ -388,29 +426,11 @@ object KernelHelpers { * @param phi The formula to check * @return The List of undefined symols */ - def findUndefinedSymbols(phi: Formula): Set[ConstantLabel] = phi match { - case AtomicFormula(label, args) => - label match { - case l: ConstantAtomicLabel => ((if (theory.isSymbol(l)) Nil else List(l)) ++ args.flatMap(findUndefinedSymbols)).toSet - case _ => args.flatMap(findUndefinedSymbols).toSet - } - case ConnectorFormula(label, args) => args.flatMap(findUndefinedSymbols).toSet - case BinderFormula(label, bound, inner) => findUndefinedSymbols(inner) - } - - /** - * Verify if a given term belongs to the language of the theory. - * - * @param t The term to check - * @return The List of undefined symols - */ - def findUndefinedSymbols(t: Term): Set[ConstantLabel] = t match { - case Term(label, args) => - label match { - case l: ConstantFunctionLabel => ((if (theory.isSymbol(l)) Nil else List(l)) ++ args.flatMap(findUndefinedSymbols)).toSet - case _: SchematicTermLabel => args.flatMap(findUndefinedSymbols).toSet - } - + def findUndefinedSymbols(phi: Expression): Set[Constant] = phi match { + case Variable(id, sort) => Set.empty + case cst: Constant => if (theory.isSymbol(cst)) Set.empty else Set(cst) + case Lambda(v, inner) => findUndefinedSymbols(inner) + case Application(f, arg) => findUndefinedSymbols(f) ++ findUndefinedSymbols(arg) } /** @@ -419,23 +439,18 @@ object KernelHelpers { * @param s The sequent to check * @return The List of undefined symols */ - def findUndefinedSymbols(s: Sequent): Set[ConstantLabel] = + def findUndefinedSymbols(s: Sequent): Set[Constant] = s.left.flatMap(findUndefinedSymbols) ++ s.right.flatMap(findUndefinedSymbols) } extension (just: RunningTheory#Justification) { def repr: String = just match { - case thm: RunningTheory#Theorem => s" Theorem ${thm.name} := ${FOLPrinter.prettySequent(thm.proposition)}${if (thm.withSorry) " (!! Relies on Sorry)" else ""}\n" - case axiom: RunningTheory#Axiom => s" Axiom ${axiom.name} := ${FOLPrinter.prettyFormula(axiom.ax)}\n" + case thm: RunningTheory#Theorem => s" Theorem ${thm.name} := ${thm.proposition.repr}${if (thm.withSorry) " (!! Relies on Sorry)" else ""}\n" + case axiom: RunningTheory#Axiom => s" Axiom ${axiom.name} := ${axiom.ax.repr}\n" case d: RunningTheory#Definition => - d match { - case pd: RunningTheory#PredicateDefinition => - s" Definition of predicate symbol ${pd.label.id} := ${FOLPrinter.prettyFormula(pd.label(pd.expression.vars.map(VariableTerm.apply)*) <=> pd.expression.body)}\n" - case fd: RunningTheory#FunctionDefinition => - s" Definition of function symbol ${FOLPrinter.prettyTerm(fd.label(fd.expression.vars.map(VariableTerm.apply)*))} := the ${fd.out.id} such that ${FOLPrinter - .prettyFormula((fd.out === fd.label(fd.expression.vars.map(VariableTerm.apply)*)) <=> fd.expression.body)})${if (fd.withSorry) " (!! Relies on Sorry)" else ""}\n" - } + s" Definition of symbol ${d.cst.id} : ${d.cst.sort} := ${d.expression}\n" + } } @@ -451,7 +466,7 @@ object KernelHelpers { just.repr case InvalidJustification(message, error) => s"$message\n${error match { - case Some(judgement) => FOLPrinter.prettySCProof(judgement) + case Some(judgement) => prettySCProof(judgement) case None => "" }}" } @@ -467,6 +482,140 @@ object KernelHelpers { if pl > 100 then output("...") output(s"Proof is too long to be displayed [$pl steps]") - else output(FOLPrinter.prettySCProof(judgement)) + else output(prettySCProof(judgement)) } + + private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " + + private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" + + /** + * Returns a string representation of this proof. + * + * @param proof the proof + * @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(judgement: SCProofCheckerJudgement, forceDisplaySubproofs: Boolean = false): String = { + val proof = judgement.proof + def computeMaxNumberingLengths(proof: SCProof, level: Int, result: IndexedSeq[Int]): IndexedSeq[Int] = { + val resultWithCurrent = result.updated( + level, + (Seq((proof.steps.size - 1).toString.length, result(level)) ++ (if (proof.imports.nonEmpty) Seq((-proof.imports.size).toString.length) else Seq.empty)).max + ) + proof.steps.collect { case sp: SCSubproof => sp }.foldLeft(resultWithCurrent)((acc, sp) => computeMaxNumberingLengths(sp.sp, level + 1, if (acc.size <= level + 1) acc :+ 0 else acc)) + } + + val maxNumberingLengths = computeMaxNumberingLengths(proof, 0, IndexedSeq(0)) // The maximum value for each number column + val maxLevel = maxNumberingLengths.size - 1 + + def leftPadSpaces(v: Any, n: Int): String = { + val s = String.valueOf(v) + if (s.length < n) (" " * (n - s.length)) + s else s + } + + def rightPadSpaces(v: Any, n: Int): String = { + val s = String.valueOf(v) + if (s.length < n) s + (" " * (n - s.length)) else s + } + + 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 = judgement match { + case SCValidProof(_, _) => false + case SCInvalidProof(proof, 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) = + ( + showErrorForLine, + prefixString, + Seq(stepName, topSteps.mkString(commaSeparator(compact = false))).filter(_.nonEmpty).mkString(" "), + imp.repr + ) + + Seq(pretty("Import", 0)) + } + printedImports ++ proof.steps.zipWithIndex.flatMap { case (step, i) => + val currentTree = tree :+ i + val showErrorForLine = judgement match { + case SCValidProof(_, _) => false + case SCInvalidProof(proof, 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) = + ( + showErrorForLine, + prefixString, + Seq(stepName, topSteps.mkString(commaSeparator(compact = false))).filter(_.nonEmpty).mkString(" "), + step.bot.repr + ) + + step match { + case sp @ SCSubproof(_, _) => + pretty("Subproof", sp.premises*) +: prettySCProofRecursive(sp.sp, level + 1, currentTree, (if (i == 0) topMostIndices else IndexedSeq.empty) :+ i) + case other => + val line = other match { + case Restate(_, t1) => pretty("Rewrite", t1) + case RestateTrue(_) => pretty("RewriteTrue") + case Hypothesis(_, _) => pretty("Hypo.") + case Cut(_, t1, t2, _) => pretty("Cut", t1, t2) + case LeftAnd(_, t1, _, _) => pretty("Left ∧", t1) + case LeftNot(_, t1, _) => pretty("Left ¬", t1) + case RightOr(_, t1, _, _) => pretty("Right ∨", t1) + case RightNot(_, t1, _) => pretty("Right ¬", t1) + case LeftExists(_, t1, _, _) => pretty("Left ∃", t1) + case LeftForall(_, t1, _, _, _) => pretty("Left ∀", t1) + case LeftOr(_, l, _) => pretty("Left ∨", l*) + case RightExists(_, t1, _, _, _) => pretty("Right ∃", t1) + case RightForall(_, t1, _, _) => pretty("Right ∀", t1) + case RightEpsilon(_, t1, _, _, _) => pretty("Right ε", t1) + case RightAnd(_, l, _) => pretty("Right ∧", l*) + case RightIff(_, t1, t2, _, _) => pretty("Right ⇔", t1, t2) + case RightImplies(_, t1, _, _) => pretty("Right ⇒", t1) + case LeftImplies(_, t1, t2, _, _) => pretty("Left ⇒", t1, t2) + case LeftIff(_, t1, _, _) => pretty("Left ⇔", t1) + case Weakening(_, t1) => pretty("Weakening", t1) + case LeftBeta(_, t1, _, _, _, _) => pretty("Left β", t1) + case RightBeta(_, t1, _, _, _, _) => pretty("Right β", t1) + case LeftRefl(_, t1, _) => pretty("L. Refl", t1) + case RightRefl(_, _) => pretty("R. Refl") + case LeftSubstEq(_, t1, t2, _, _, _, _) => pretty("L. SubstEq", t1, t2) + case RightSubstEq(_, t1, t2, _, _, _, _) => pretty("R. SubstEq", t1, t2) + case LeftSubstIff(_, t1, t2, _, _, _, _) => pretty("L. SubstIff", t1, t2) + case RightSubstIff(_, t1, t2, _, _, _, _) => pretty("R. SubstIff", t1, t2) + case InstSchema(_, t1, _) => pretty("Schema Instantiation", t1) + case Sorry(_) => pretty("Sorry") + case SCSubproof(_, _) => throw new Exception("Should not happen") + } + Seq(line) + } + } + } + + val marker = "->" + + val lines = prettySCProofRecursive(proof, 0, IndexedSeq.empty, IndexedSeq.empty) + val maxStepNameLength = lines.map { case (_, _, stepName, _) => stepName.length }.maxOption.getOrElse(0) + lines + .map { case (isMarked, indices, stepName, sequent) => + val suffix = Seq(indices, rightPadSpaces(stepName, maxStepNameLength), sequent) + val full = if (!judgement.isValid) (if (isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix + full.mkString(" ") + } + .mkString("\n") + (judgement match { + case SCValidProof(_, _) => "" + case SCInvalidProof(proof, path, message) => s"\nProof checker has reported an error at line ${path.mkString(".")}: $message" + }) + } + + def prettySCProof(proof: SCProof): String = prettySCProof(SCValidProof(proof), false) + + } diff --git a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala index 6d141d39..126397ff 100644 --- a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala +++ b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala @@ -6,8 +6,9 @@ import lisa.kernel.proof.RunningTheoryJudgement import lisa.kernel.proof.RunningTheoryJudgement.InvalidJustification import lisa.kernel.proof.SCProof import lisa.prooflib.Library -import lisa.prooflib.ProofTacticLib.ProofTactic +// import lisa.prooflib.ProofTacticLib.ProofTactic import lisa.utils.KernelHelpers.repr +import lisa.utils.KernelHelpers.prettySCProof abstract class LisaException(errorMessage: String)(using val line: sourcecode.Line, val file: sourcecode.File) extends Exception(errorMessage) { def showError: String @@ -15,8 +16,10 @@ abstract class LisaException(errorMessage: String)(using val line: sourcecode.Li import lisa.utils.KernelHelpers.{_, given} + import java.io.File object LisaException { + case class InvalidKernelJustificationComputation(errorMessage: String, underlying: RunningTheoryJudgement.InvalidJustification[?], proof: Option[Library#Proof])(using sourcecode.Line, sourcecode.File @@ -24,12 +27,12 @@ object LisaException { def showError: String = "Construction of proof succedded, but the resulting proof or definition has been reported to be faulty. This may be due to an internal bug.\n" + "The resulting faulty event is:\n" + s"$underlying.message\n${underlying.error match { - case Some(judgement) => FOLPrinter.prettySCProof(judgement) + case Some(judgement) => prettySCProof(judgement) case None => "" }}" } - class InvalidKernelAxiomException(errorMessage: String, name: String, formula: lisa.kernel.fol.FOL.Formula, theory: lisa.kernel.proof.RunningTheory)(using sourcecode.Line, sourcecode.File) + class InvalidKernelAxiomException(errorMessage: String, name: String, formula: lisa.kernel.fol.FOL.Expression, theory: lisa.kernel.proof.RunningTheory)(using sourcecode.Line, sourcecode.File) extends LisaException(errorMessage) { def showError: String = s"The desired axiom \"$name\" contains symbol that are not part of the theory.\n" + s"The symbols {${theory.findUndefinedSymbols(formula)}} are undefined." @@ -37,12 +40,15 @@ object LisaException { } + + /** * Error made by the user, should be "explained" */ abstract class UserLisaException(var errorMessage: String)(using line: sourcecode.Line, file: sourcecode.File) extends LisaException(errorMessage) { def fixTrace(): Unit = () } + object UserLisaException { class InvalidProofFromFileException(errorMessage: String, file: String)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { def showError: String = errorMessage @@ -58,7 +64,7 @@ object UserLisaException { def showError: String = "" } - class UndefinedSymbolException(errorMessage: String, symbol: F.ConstantLabel[?], library: lisa.prooflib.Library)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { + class UndefinedSymbolException(errorMessage: String, symbol: F.Constant[?], library: lisa.prooflib.Library)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { def showError: String = s"The desired symbol \"$symbol\" is unknown and has not been defined.\n" } diff --git a/lisa-utils/src/main/scala/lisa/utils/ProofsShrink.scala b/lisa-utils/src/main/scala/lisa/utils/ProofsShrink.scala index 5f620aa8..4d0d3b94 100644 --- a/lisa-utils/src/main/scala/lisa/utils/ProofsShrink.scala +++ b/lisa-utils/src/main/scala/lisa/utils/ProofsShrink.scala @@ -10,7 +10,7 @@ import lisa.kernel.proof.SequentCalculus.* * If the provided proofs are valid, then the resulting proofs will also be valid. */ object ProofsShrink { - +/* /** * Computes the size of a proof. Size corresponds to the number of proof steps. * Subproofs are count as one plus the size of their body. @@ -300,4 +300,5 @@ object ProofsShrink { } def minimizeProofOnce(proof: SCProof): SCProof = deadStepsElimination(factorProof(simplifyProof(flattenProof(proof)))) + */ } diff --git a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala index 3b934506..883b02a9 100644 --- a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala +++ b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala @@ -21,7 +21,6 @@ object Serialization { inline def leftNot: Byte = 8 inline def leftForall: Byte = 9 inline def leftExists: Byte = 10 - inline def leftExistsOne: Byte = 11 inline def rightAnd: Byte = 12 inline def rightOr: Byte = 13 inline def rightImplies: Byte = 14 @@ -29,55 +28,44 @@ object Serialization { inline def rightNot: Byte = 16 inline def rightForall: Byte = 17 inline def rightExists: Byte = 18 - inline def rightExistsOne: Byte = 19 + inline def rightEpsilon: Byte = 19 inline def weakening: Byte = 20 - inline def leftRefl: Byte = 21 - inline def rightRefl: Byte = 22 - inline def leftSubstEq: Byte = 23 - inline def rightSubstEq: Byte = 24 - inline def leftSubstIff: Byte = 25 - inline def rightSubstIff: Byte = 26 - inline def instSchema: Byte = 27 - inline def scSubproof: Byte = 28 - inline def sorry: Byte = 29 + inline def leftBeta: Byte = 21 + inline def rightBeta: Byte = 22 + inline def leftRefl: Byte = 23 + inline def rightRefl: Byte = 24 + inline def leftSubstEq: Byte = 25 + inline def rightSubstEq: Byte = 26 + inline def leftSubstIff: Byte = 27 + inline def rightSubstIff: Byte = 28 + inline def instSchema: Byte = 29 + inline def scSubproof: Byte = 30 + inline def sorry: Byte = 31 type Line = Int - // Injectively represent a TermLabel as a string - def termLabelToString(label: TermLabel): String = - label match - case l: ConstantFunctionLabel => "cfl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: SchematicFunctionLabel => "sfl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: VariableLabel => "vl_" + l.id.name + "_" + l.id.no - - // Injectively represent a FormulaLabel as a string. - def formulaLabelToString(label: FormulaLabel): String = - label match - case l: ConstantAtomicLabel => "cpl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: SchematicPredicateLabel => "spl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: ConstantConnectorLabel => "ccl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: SchematicConnectorLabel => "scl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: VariableFormulaLabel => "vfl_" + l.id.name + "_" + l.id.no - case l: BinderLabel => "bl_" + l.id.name + "_" + l.id.no - - // write a term label to an OutputStream - def termLabelToDOS(label: TermLabel, dos: DataOutputStream): Unit = - label match - case l: ConstantFunctionLabel => - dos.writeByte(0) - dos.writeUTF(l.id.name) - dos.writeInt(l.id.no) - dos.writeInt(l.arity) - case l: SchematicFunctionLabel => - dos.writeByte(1) - dos.writeUTF(l.id.name) - dos.writeInt(l.id.no) - dos.writeInt(l.arity) - case l: VariableLabel => - dos.writeByte(2) - dos.writeUTF(l.id.name) - dos.writeInt(l.id.no) - // write a formula label to an OutputStream + def typeToString(t: Sort): String = + t match + case Term => "T" + case Formula => "F" + case Arrow(from, to) => s">${typeToString(from)}${typeToString(to)}" + + def constantToSting(c: Constant): String = "cst_" + c.id.name + "_" + c.id.no + "_" + typeToString(c.sort) + def variableToSting(v: Variable): String = "var_" + v.id.name + "_" + v.id.no + "_" + typeToString(v.sort) + + def constantToDos(c: Constant, dos: DataOutputStream): Unit = + dos.writeByte(0) + dos.writeUTF(c.id.name) + dos.writeInt(c.id.no) + dos.writeUTF(typeToString(c.sort)) + + def variableToDOS(v: Variable, dos: DataOutputStream): Unit = + dos.writeByte(1) + dos.writeUTF(v.id.name) + dos.writeInt(v.id.no) + dos.writeUTF(typeToString(v.sort)) + + /* def formulaLabelToDOS(label: FormulaLabel, dos: DataOutputStream): Unit = label match case l: ConstantAtomicLabel => @@ -105,77 +93,56 @@ object Serialization { case l: BinderLabel => dos.writeByte(8) dos.writeUTF(l.id.name) + */ /** * Main function that, when given a proof, will serialize it to a file. It will also serialize all the formulas appearing in it to another file. */ def proofsToDataStream(treesDOS: DataOutputStream, proofDOS: DataOutputStream, theorems: Seq[(String, SCProof, List[String])]): Unit = { - val termMap = MutMap[Long, Line]() - val formulaMap = MutMap[Long, Line]() + val exprMap = MutMap[Long, Line]() var line = -1 - // Compute the line of a term. If it is not in the map, add it to the map and write it to the tree file - def lineOfTerm(term: Term): Line = - termMap.get(term.uniqueNumber) match - case Some(line) => line - case None => - val na = term.args.map(t => lineOfTerm(t)) - termLabelToDOS(term.label, treesDOS) - na.foreach(t => treesDOS.writeInt(t)) - line = line + 1 - termMap(term.uniqueNumber) = line - line - // Compute the line of a formula. If it is not in the map, add it to the map and write it to the tree file - def lineOfFormula(formula: Formula): Line = - formulaMap.get(formula.uniqueNumber) match + def lineOfExpr(e: Expression): Line = + exprMap.get(e.uniqueNumber) match case Some(line) => line case None => - val nextLine = formula match - case AtomicFormula(label, args) => - val na = args.map(t => lineOfTerm(t)) - formulaLabelToDOS(label, treesDOS) - na.foreach(t => treesDOS.writeInt(t)) - case ConnectorFormula(label, args) => - val na = args.map(t => lineOfFormula(t)) - formulaLabelToDOS(label, treesDOS) - treesDOS.writeShort(na.size) - na.foreach(t => treesDOS.writeInt(t)) - case BinderFormula(label, bound, inner) => - val ni = lineOfFormula(inner) - formulaLabelToDOS(label, treesDOS) - termLabelToDOS(bound, treesDOS) + val nextLine = e match + case v: Variable => + treesDOS.writeByte(0) + treesDOS.writeUTF(v.id.name) + treesDOS.writeInt(v.id.no) + treesDOS.writeUTF(typeToString(v.sort)) + case c: Constant => + treesDOS.writeByte(1) + treesDOS.writeUTF(c.id.name) + treesDOS.writeInt(c.id.no) + treesDOS.writeUTF(typeToString(c.sort)) + case Lambda(v, inner) => + treesDOS.writeByte(2) + val vi = lineOfExpr(v) + val ni = lineOfExpr(inner) + treesDOS.writeInt(vi) treesDOS.writeInt(ni) + case Application(f, arg) => + treesDOS.writeByte(3) + val a1 = lineOfExpr(f) + val a2 = lineOfExpr(arg) + treesDOS.writeInt(a1) + treesDOS.writeInt(a2) line = line + 1 - formulaMap(formula.uniqueNumber) = line + exprMap(e.uniqueNumber) = line line // Write a sequent to the proof file. def sequentToProofDOS(sequent: Sequent): Unit = proofDOS.writeShort(sequent.left.size) - sequent.left.foreach(f => proofDOS.writeInt(lineOfFormula(f))) + sequent.left.foreach(f => proofDOS.writeInt(lineOfExpr(f))) proofDOS.writeShort(sequent.right.size) - sequent.right.foreach(f => proofDOS.writeInt(lineOfFormula(f))) - - def lttToProofDOS(ltt: LambdaTermTerm): Unit = - val body = lineOfTerm(ltt.body) - proofDOS.writeShort(ltt.vars.size) - ltt.vars.foreach(v => termLabelToDOS(v, proofDOS)) - proofDOS.writeInt(body) - - def ltfToProofDOS(ltf: LambdaTermFormula): Unit = - val body = lineOfFormula(ltf.body) - proofDOS.writeShort(ltf.vars.size) - ltf.vars.foreach(v => termLabelToDOS(v, proofDOS)) - proofDOS.writeInt(body) - - def lffToProofDOS(lff: LambdaFormulaFormula): Unit = - val body = lineOfFormula(lff.body) - proofDOS.writeShort(lff.vars.size) - lff.vars.foreach(v => formulaLabelToDOS(v, proofDOS)) - proofDOS.writeInt(body) + sequent.right.foreach(f => proofDOS.writeInt(lineOfExpr(f))) + /** * Write a proof step to the proof file. @@ -196,192 +163,189 @@ object Serialization { case Hypothesis(bot, phi) => proofDOS.writeByte(hypothesis) sequentToProofDOS(bot) - proofDOS.writeInt(lineOfFormula(phi)) + proofDOS.writeInt(lineOfExpr(phi)) case Cut(bot, t1, t2, phi) => proofDOS.writeByte(cut) sequentToProofDOS(bot) proofDOS.writeInt(t1) proofDOS.writeInt(t2) - proofDOS.writeInt(lineOfFormula(phi)) + proofDOS.writeInt(lineOfExpr(phi)) case LeftAnd(bot, t1, phi, psi) => proofDOS.writeByte(leftAnd) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case LeftOr(bot, t, disjuncts) => proofDOS.writeByte(leftOr) sequentToProofDOS(bot) proofDOS.writeShort(t.size) t.foreach(proofDOS.writeInt) proofDOS.writeShort(disjuncts.size) - disjuncts.foreach(f => proofDOS.writeInt(lineOfFormula(f))) + disjuncts.foreach(f => proofDOS.writeInt(lineOfExpr(f))) case LeftImplies(bot, t1, t2, phi, psi) => proofDOS.writeByte(leftImplies) sequentToProofDOS(bot) proofDOS.writeInt(t1) proofDOS.writeInt(t2) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case LeftIff(bot, t1, phi, psi) => proofDOS.writeByte(leftIff) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case LeftNot(bot, t1, phi) => proofDOS.writeByte(leftNot) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) + proofDOS.writeInt(lineOfExpr(phi)) case LeftForall(bot, t1, phi, x, t) => proofDOS.writeByte(leftForall) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) - proofDOS.writeInt(lineOfTerm(t)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(x)) + proofDOS.writeInt(lineOfExpr(t)) case LeftExists(bot, t1, phi, x) => proofDOS.writeByte(leftExists) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) - case LeftExistsOne(bot, t1, phi, x) => - proofDOS.writeByte(leftExistsOne) - sequentToProofDOS(bot) - proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(x)) case RightAnd(bot, t, conjuncts) => proofDOS.writeByte(rightAnd) sequentToProofDOS(bot) proofDOS.writeShort(t.size) t.foreach(proofDOS.writeInt) proofDOS.writeShort(conjuncts.size) - conjuncts.foreach(f => proofDOS.writeInt(lineOfFormula(f))) + conjuncts.foreach(f => proofDOS.writeInt(lineOfExpr(f))) case RightOr(bot, t1, phi, psi) => proofDOS.writeByte(rightOr) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case RightImplies(bot, t1, phi, psi) => proofDOS.writeByte(rightImplies) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case RightIff(bot, t1, t2, phi, psi) => proofDOS.writeByte(rightIff) sequentToProofDOS(bot) proofDOS.writeInt(t1) proofDOS.writeInt(t2) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case RightNot(bot, t1, phi) => proofDOS.writeByte(rightNot) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) + proofDOS.writeInt(lineOfExpr(phi)) case RightForall(bot, t1, phi, x) => proofDOS.writeByte(rightForall) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(x)) case RightExists(bot, t1, phi, x, t) => proofDOS.writeByte(rightExists) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) - proofDOS.writeInt(lineOfTerm(t)) - case RightExistsOne(bot, t1, phi, x) => - proofDOS.writeByte(rightExistsOne) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(x)) + proofDOS.writeInt(lineOfExpr(t)) + case RightEpsilon(bot, t1, phi, x, t) => + proofDOS.writeByte(rightEpsilon) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(x)) + proofDOS.writeInt(lineOfExpr(t)) case Weakening(bot, t1) => proofDOS.writeByte(weakening) sequentToProofDOS(bot) proofDOS.writeInt(t1) + case LeftBeta(bot, t1, phi, lambda, t, x) => + proofDOS.writeByte(leftBeta) + sequentToProofDOS(bot) + proofDOS.writeInt(t1) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(lambda)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeInt(lineOfExpr(x)) + case RightBeta(bot, t1, phi, lambda, t, x) => + proofDOS.writeByte(rightBeta) + sequentToProofDOS(bot) + proofDOS.writeInt(t1) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(lambda)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeInt(lineOfExpr(x)) case LeftRefl(bot, t1, fa) => proofDOS.writeByte(leftRefl) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(fa)) + proofDOS.writeInt(lineOfExpr(fa)) case RightRefl(bot, fa) => proofDOS.writeByte(rightRefl) sequentToProofDOS(bot) - proofDOS.writeInt(lineOfFormula(fa)) - case LeftSubstEq(bot, t1, equals, lambdaPhi) => + proofDOS.writeInt(lineOfExpr(fa)) + case LeftSubstEq(bot, t1, t2, s, t, vars, lambdaPhi) => proofDOS.writeByte(leftSubstEq) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeShort(equals.size) - equals.foreach(ltts => - lttToProofDOS(ltts._1) - lttToProofDOS(ltts._2) - ) - proofDOS.writeShort(lambdaPhi._1.size) - lambdaPhi._1.foreach(stl => termLabelToDOS(stl, proofDOS)) - proofDOS.writeInt(lineOfFormula(lambdaPhi._2)) - case RightSubstEq(bot, t1, equals, lambdaPhi) => + proofDOS.writeInt(t2) + proofDOS.writeInt(lineOfExpr(s)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeShort(vars.size) + vars.foreach(v => proofDOS.writeInt(lineOfExpr(v))) + proofDOS.writeInt(lineOfExpr(lambdaPhi._1)) + proofDOS.writeInt(lineOfExpr(lambdaPhi._2)) + case RightSubstEq(bot, t1, t2, s, t, vars, lambdaPhi) => proofDOS.writeByte(rightSubstEq) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeShort(equals.size) - equals.foreach(ltts => - lttToProofDOS(ltts._1) - lttToProofDOS(ltts._2) - ) - proofDOS.writeShort(lambdaPhi._1.size) - lambdaPhi._1.foreach(stl => termLabelToDOS(stl, proofDOS)) - proofDOS.writeInt(lineOfFormula(lambdaPhi._2)) - case LeftSubstIff(bot, t1, equals, lambdaPhi) => + proofDOS.writeInt(t2) + proofDOS.writeInt(lineOfExpr(s)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeShort(vars.size) + vars.foreach(v => proofDOS.writeInt(lineOfExpr(v))) + proofDOS.writeInt(lineOfExpr(lambdaPhi._1)) + proofDOS.writeInt(lineOfExpr(lambdaPhi._2)) + case LeftSubstIff(bot, t1, t2, s, t, vars, lambdaPhi) => proofDOS.writeByte(leftSubstIff) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeShort(equals.size) - equals.foreach(ltts => - ltfToProofDOS(ltts._1) - ltfToProofDOS(ltts._2) - ) - proofDOS.writeShort(lambdaPhi._1.size) - lambdaPhi._1.foreach(stl => formulaLabelToDOS(stl, proofDOS)) - proofDOS.writeInt(lineOfFormula(lambdaPhi._2)) - case RightSubstIff(bot, t1, equals, lambdaPhi) => + proofDOS.writeInt(t2) + proofDOS.writeInt(lineOfExpr(s)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeShort(vars.size) + vars.foreach(v => proofDOS.writeInt(lineOfExpr(v))) + proofDOS.writeInt(lineOfExpr(lambdaPhi._1)) + proofDOS.writeInt(lineOfExpr(lambdaPhi._2)) + case RightSubstIff(bot, t1, t2, s, t, vars, lambdaPhi) => proofDOS.writeByte(rightSubstIff) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeShort(equals.size) - equals.foreach(ltts => - ltfToProofDOS(ltts._1) - ltfToProofDOS(ltts._2) - ) - proofDOS.writeShort(lambdaPhi._1.size) - lambdaPhi._1.foreach(stl => formulaLabelToDOS(stl, proofDOS)) - proofDOS.writeInt(lineOfFormula(lambdaPhi._2)) - case InstSchema(bot, t1, mCon, mPred, mTerm) => + proofDOS.writeInt(t2) + proofDOS.writeInt(lineOfExpr(s)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeShort(vars.size) + vars.foreach(v => proofDOS.writeInt(lineOfExpr(v))) + proofDOS.writeInt(lineOfExpr(lambdaPhi._1)) + proofDOS.writeInt(lineOfExpr(lambdaPhi._2)) + case InstSchema(bot, t1, m) => proofDOS.writeByte(instSchema) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeShort(mCon.size) - mCon.foreach(t => - formulaLabelToDOS(t._1, proofDOS) - lffToProofDOS(t._2) - ) - proofDOS.writeShort(mPred.size) - mPred.foreach(t => - formulaLabelToDOS(t._1, proofDOS) - ltfToProofDOS(t._2) - ) - proofDOS.writeShort(mTerm.size) - mTerm.foreach(t => - termLabelToDOS(t._1, proofDOS) - lttToProofDOS(t._2) + proofDOS.writeShort(m.size) + m.foreach(t => + proofDOS.writeInt(lineOfExpr(t._1)) + proofDOS.writeInt(lineOfExpr(t._2)) ) case SCSubproof(sp, premises) => throw new Exception("Cannot support subproofs, flatten the proof first.") case Sorry(bot) => @@ -403,6 +367,15 @@ object Serialization { } + def typeFromString(s: String): (Sort, String) = + if s(0) == 'T' then (Term, s.drop(1)) + else if s(0) == 'F' then (Formula, s.drop(1)) + else if s(0) == '>' then + val (from, reminder) = typeFromString(s.drop(1)) + val (to, r) = typeFromString(reminder) + (Arrow(from, to), r) + else throw new Exception("Unknown type: " + s) + /** * This functions reverses the effect of proofToDataStream * @@ -410,110 +383,44 @@ object Serialization { */ def proofsFromDataStream(treesDIS: DataInputStream, proofDIS: DataInputStream): Seq[(String, SCProof, List[String])] = { - val termMap = MutMap[Line, Term]() - val formulaMap = MutMap[Line, Formula]() - - // Read a label from the tree file, reversing the effect of termLabelToDOS and formulaLabelToDOS. - def labelFromInputStream(dis: DataInputStream): Label = { - val labelType = dis.readByte() - labelType match - case 0 => - val name = dis.readUTF() - val no = dis.readInt() - val arity = dis.readInt() - ConstantFunctionLabel(Identifier(name, no), arity) - case 1 => - val name = dis.readUTF() - val no = dis.readInt() - val arity = dis.readInt() - SchematicFunctionLabel(Identifier(name, no), arity) - case 2 => - val name = dis.readUTF() - val no = dis.readInt() - VariableLabel(Identifier(name, no)) - case 3 => - val name = dis.readUTF() - val no = dis.readInt() - val arity = dis.readInt() - ConstantAtomicLabel(Identifier(name, no), arity) - case 4 => - val name = dis.readUTF() - val no = dis.readInt() - val arity = dis.readInt() - SchematicPredicateLabel(Identifier(name, no), arity) - case 5 => - val name = dis.readUTF() - name match - case And.id.name => And - case Or.id.name => Or - case Implies.id.name => Implies - case Iff.id.name => Iff - case Neg.id.name => Neg - case 6 => - val name = dis.readUTF() - val no = dis.readInt() - val arity = dis.readInt() - SchematicConnectorLabel(Identifier(name, no), arity) - case 7 => - val name = dis.readUTF() - val no = dis.readInt() - VariableFormulaLabel(Identifier(name, no)) - case 8 => - dis.readUTF() match - case Forall.id.name => Forall - case Exists.id.name => Exists - case ExistsOne.id.name => ExistsOne - - } + val exprMap = MutMap[Line, Expression]() // Read and reconstruct all the terms and formulas in the tree file. Fill the table with it. var lineNo = -1 try { while true do lineNo = lineNo + 1 - val label = labelFromInputStream(treesDIS) - label match - case l: TermLabel => - val args = (1 to l.arity).map(_ => termMap(treesDIS.readInt())).toSeq - termMap(lineNo) = Term(l, args) - case l: FormulaLabel => - val formula = label match - case l: AtomicLabel => - val args = (1 to l.arity).map(_ => termMap(treesDIS.readInt())).toSeq - AtomicFormula(l, args) - case l: ConnectorLabel => - val ar = treesDIS.readShort() - val args = (1 to ar).map(_ => formulaMap(treesDIS.readInt())).toSeq - ConnectorFormula(l, args) - case l: BinderLabel => - BinderFormula(l, labelFromInputStream(treesDIS).asInstanceOf[VariableLabel], formulaMap(treesDIS.readInt())) - formulaMap(lineNo) = formula + treesDIS.readByte() match + case 0 => + val name = treesDIS.readUTF() + val no = treesDIS.readInt() + val sort = treesDIS.readUTF() + Variable(Identifier(name, no), typeFromString(sort)._1) + case 1 => + val name = treesDIS.readUTF() + val no = treesDIS.readInt() + val sort = treesDIS.readUTF() + Constant(Identifier(name, no), typeFromString(sort)._1) + case 2 => + val v = exprMap(treesDIS.readInt()) + val body = exprMap(treesDIS.readInt()) + Lambda(v.asInstanceOf[Variable], body) + case 3 => + val f = exprMap(treesDIS.readInt()) + val arg = exprMap(treesDIS.readInt()) + Application(f, arg) } catch case _: EOFException => () // Terms and Formulas finished, deal with the proof now. - def lttFromProofDIS(): LambdaTermTerm = - val vars = (1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]).toSeq - val body = termMap(proofDIS.readInt()) - LambdaTermTerm(vars, body) - - def ltfFromProofDIS(): LambdaTermFormula = - val vars = (1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]).toSeq - val body = formulaMap(proofDIS.readInt()) - LambdaTermFormula(vars, body) - - def lffFromProofDIS(): LambdaFormulaFormula = - val vars = (1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[VariableFormulaLabel]).toSeq - val body = formulaMap(proofDIS.readInt()) - LambdaFormulaFormula(vars, body) def sequentFromProofDIS(): Sequent = val leftSize = proofDIS.readShort() - val left = (1 to leftSize).map(_ => formulaMap(proofDIS.readInt())).toSet + val left = (1 to leftSize).map(_ => exprMap(proofDIS.readInt())).toSet val rightSize = proofDIS.readShort() - val right = (1 to rightSize).map(_ => formulaMap(proofDIS.readInt())).toSet + val right = (1 to rightSize).map(_ => exprMap(proofDIS.readInt())).toSet Sequent(left, right) // Read a proof step from the proof file. Inverse of proofStepToProofDOS @@ -521,86 +428,118 @@ object Serialization { val psType = proofDIS.readByte() if (psType == restate) Restate(sequentFromProofDIS(), proofDIS.readInt()) else if (psType == restateTrue) RestateTrue(sequentFromProofDIS()) - else if (psType == hypothesis) Hypothesis(sequentFromProofDIS(), formulaMap(proofDIS.readInt())) - else if (psType == cut) Cut(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), formulaMap(proofDIS.readInt())) - else if (psType == leftAnd) LeftAnd(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) + else if (psType == hypothesis) Hypothesis(sequentFromProofDIS(), exprMap(proofDIS.readInt())) + else if (psType == cut) Cut(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), exprMap(proofDIS.readInt())) + else if (psType == leftAnd) LeftAnd(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) else if (psType == leftOr) LeftOr( sequentFromProofDIS(), (1 to proofDIS.readShort()).map(_ => proofDIS.readInt()).toSeq, - (1 to proofDIS.readShort()).map(_ => formulaMap(proofDIS.readInt())).toSeq + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt())).toSeq ) - else if (psType == leftImplies) LeftImplies(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) - else if (psType == leftIff) LeftIff(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) - else if (psType == leftNot) LeftNot(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt())) + else if (psType == leftImplies) LeftImplies(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) + else if (psType == leftIff) LeftIff(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) + else if (psType == leftNot) LeftNot(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt())) else if (psType == leftForall) LeftForall( sequentFromProofDIS(), proofDIS.readInt(), - formulaMap(proofDIS.readInt()), - labelFromInputStream(proofDIS).asInstanceOf[VariableLabel], - termMap(proofDIS.readInt()) + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Variable], + exprMap(proofDIS.readInt()) ) - else if (psType == leftExists) LeftExists(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]) - else if (psType == leftExistsOne) LeftExistsOne(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]) + else if (psType == leftExists) LeftExists(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt()).asInstanceOf[Variable]) else if (psType == rightAnd) RightAnd( sequentFromProofDIS(), (1 to proofDIS.readShort()).map(_ => proofDIS.readInt()).toSeq, - (1 to proofDIS.readShort()).map(_ => formulaMap(proofDIS.readInt())).toSeq + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt())).toSeq ) - else if (psType == rightOr) RightOr(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) - else if (psType == rightImplies) RightImplies(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) - else if (psType == rightIff) RightIff(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) - else if (psType == rightNot) RightNot(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt())) - else if (psType == rightForall) RightForall(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]) + else if (psType == rightOr) RightOr(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) + else if (psType == rightImplies) RightImplies(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) + else if (psType == rightIff) RightIff(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) + else if (psType == rightNot) RightNot(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt())) + else if (psType == rightForall) RightForall(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt()).asInstanceOf[Variable]) else if (psType == rightExists) RightExists( sequentFromProofDIS(), proofDIS.readInt(), - formulaMap(proofDIS.readInt()), - labelFromInputStream(proofDIS).asInstanceOf[VariableLabel], - termMap(proofDIS.readInt()) + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Variable], + exprMap(proofDIS.readInt()) + ) + else if (psType == rightEpsilon) + RightEpsilon( + sequentFromProofDIS(), + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Variable], + exprMap(proofDIS.readInt()) ) - else if (psType == rightExistsOne) RightExistsOne(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]) else if (psType == weakening) Weakening(sequentFromProofDIS(), proofDIS.readInt()) - else if (psType == leftRefl) LeftRefl(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt())) - else if (psType == rightRefl) RightRefl(sequentFromProofDIS(), formulaMap(proofDIS.readInt())) + else if (psType == leftBeta) + LeftBeta(sequentFromProofDIS(), + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Lambda], + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Variable] + ) + else if (psType == rightBeta) + RightBeta(sequentFromProofDIS(), + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Lambda], + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Variable] + ) + else if (psType == leftRefl) LeftRefl(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt())) + else if (psType == rightRefl) RightRefl(sequentFromProofDIS(), exprMap(proofDIS.readInt())) else if (psType == leftSubstEq) LeftSubstEq( sequentFromProofDIS(), proofDIS.readInt(), - (1 to proofDIS.readShort()).map(_ => (lttFromProofDIS(), lttFromProofDIS())).toList, - ((1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[SchematicTermLabel]).toList, formulaMap(proofDIS.readInt())) + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()), + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList, + (exprMap(proofDIS.readInt()).asInstanceOf[Variable], exprMap(proofDIS.readInt())) ) else if (psType == rightSubstEq) RightSubstEq( sequentFromProofDIS(), proofDIS.readInt(), - (1 to proofDIS.readShort()).map(_ => (lttFromProofDIS(), lttFromProofDIS())).toList, - ((1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[SchematicTermLabel]).toList, formulaMap(proofDIS.readInt())) + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()), + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList, + (exprMap(proofDIS.readInt()).asInstanceOf[Variable], exprMap(proofDIS.readInt())) ) else if (psType == leftSubstIff) LeftSubstIff( sequentFromProofDIS(), proofDIS.readInt(), - (1 to proofDIS.readShort()).map(_ => (ltfFromProofDIS(), ltfFromProofDIS())).toList, - ((1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[SchematicAtomicLabel]).toList, formulaMap(proofDIS.readInt())) + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()), + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList, + (exprMap(proofDIS.readInt()).asInstanceOf[Variable], exprMap(proofDIS.readInt())) ) else if (psType == rightSubstIff) RightSubstIff( sequentFromProofDIS(), proofDIS.readInt(), - (1 to proofDIS.readShort()).map(_ => (ltfFromProofDIS(), ltfFromProofDIS())).toList, - ((1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[SchematicAtomicLabel]).toList, formulaMap(proofDIS.readInt())) + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()), + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList, + (exprMap(proofDIS.readInt()).asInstanceOf[Variable], exprMap(proofDIS.readInt())) ) else if (psType == instSchema) InstSchema( sequentFromProofDIS(), proofDIS.readInt(), - (1 to proofDIS.readShort()).map(_ => (labelFromInputStream(proofDIS).asInstanceOf[SchematicConnectorLabel], lffFromProofDIS())).toMap, - (1 to proofDIS.readShort()).map(_ => (labelFromInputStream(proofDIS).asInstanceOf[SchematicAtomicLabel], ltfFromProofDIS())).toMap, - (1 to proofDIS.readShort()).map(_ => (labelFromInputStream(proofDIS).asInstanceOf[SchematicTermLabel], lttFromProofDIS())).toMap + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable] -> exprMap(proofDIS.readInt())).toMap ) else if (psType == sorry) Sorry(sequentFromProofDIS()) else throw new Exception("Unknown proof step type: " + psType) @@ -636,10 +575,12 @@ object Serialization { val justNames = justs.map { case (obj, theory.Axiom(name, ax)) => "a" + obj + "$" + name case (obj, theory.Theorem(name, proposition, withSorry)) => "t" + obj + "$" + name - case (obj, theory.FunctionDefinition(label, out, expression, withSorry)) => "f" + obj + "$" + label.id.name + "_" + label.id.no + "_" + label.arity - case (obj, theory.PredicateDefinition(label, expression)) => "p" + obj + "$" + label.id.name + "_" + label.id.no + "_" + label.arity + case (obj, theory.Definition(label, expression, vars)) => + "d" + obj + "$" + label.id.name + "_" + label.id.no + "_" + typeToString(label.sort) //+ "__" + + //vars.size + vars.map(v => v.id.name + "_" + v.id.no + "_" + typeToString(v.sort)).mkString("__") } - (name, minimizeProofOnce(proof), justNames) + //(name, minimizeProofOnce(proof), justNames) + (name, proof, justNames) ) ) } @@ -663,12 +604,10 @@ object Serialization { case 'a' => theory.getAxiom(name).get case 't' => theory.getTheorem(name).get - case 'f' => - name.split("_") match - case Array(name, no, arity) => theory.getDefinition(ConstantFunctionLabel(Identifier(name, no.toInt), arity.toInt)).get - case 'p' => - name.split("_") match - case Array(name, no, arity) => theory.getDefinition(ConstantAtomicLabel(Identifier(name, no.toInt), arity.toInt)).get + case 'd' => + val Array(id, no, sort) = name.split("_") + val cst = Constant(Identifier(id, no.toInt), typeFromString(sort)._1) + theory.getDefinition(cst).get } if debug then // To avoid conflicts where a theorem already exists, for example in test suits. diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala b/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala new file mode 100644 index 00000000..84802045 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala @@ -0,0 +1,6 @@ +package lisa.fol + +object FOL extends Sequents { + export lisa.utils.K + export K.Identifier +} diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala new file mode 100644 index 00000000..aa7f76ab --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -0,0 +1,134 @@ +package lisa.fol + +import lisa.utils.K +import K.given + +trait Predef extends Syntax { + + + def variable[S](using IsSort[SortOf[S]])(id: K.Identifier): Variable[SortOf[S]] = new Variable(id) + def constant[S](using IsSort[SortOf[S]])(id: K.Identifier): Constant[SortOf[S]] = new Constant(id) + def binder[S1, S2, S3](using IsSort[SortOf[S1]], IsSort[SortOf[S2]], IsSort[SortOf[S3]]) + (id: K.Identifier): Binder[SortOf[S1], SortOf[S2], SortOf[S3]] = new Binder(id) + + def variable[S](using name: sourcecode.Name, is: IsSort[SortOf[S]]): Variable[SortOf[S]] = new Variable(name.value) + def constant[S](using name: sourcecode.Name, is: IsSort[SortOf[S]]): Constant[SortOf[S]] = new Constant(name.value) + def binder[S1, S2, S3](using name: sourcecode.Name) + (using IsSort[SortOf[S1]], IsSort[SortOf[S2]], IsSort[SortOf[S3]]): Binder[SortOf[S1], SortOf[S2], SortOf[S3]] = new Binder(name.value) + + val equality = constant[Term >>: Term >>: Formula]("===") + val === = equality + val = = equality + + extension (t: Term) { + infix def ===(u: Term): Formula = equality(t)(u) + infix def =(u: Term): Formula = equality(t)(u) + } + + val top = constant[Formula]("⊤") + val ⊤ : top.type = top + val True: top.type = top + + val bot = constant[Formula]("⊥") + val ⊥ : bot.type = bot + val False: bot.type = bot + + val neg = constant[Formula >>: Formula]("¬") + val ¬ : neg.type = neg + val ! : neg.type = neg + + val and = constant[Formula >>: Formula >>: Formula]("∧") + val /\ : and.type = and + val ∧ : and.type = and + + val or = constant[Formula >>: Formula >>: Formula]("∨") + val \/ : or.type = or + val ∨ : or.type = or + + val implies = constant[Formula >>: Formula >>: Formula]("⇒") + val ==> : implies.type = implies + + val iff = constant[Formula >>: Formula >>: Formula]("⇔") + val <=> : iff.type = iff + val ⇔ : iff.type = iff + + val forall = binder[Term, Formula, Formula]("∀") + val ∀ : forall.type = forall + + val exists = binder[Term, Formula, Formula]("∃") + val ∃ : exists.type = exists + + val epsilon = binder[Term, Formula, Term]("ε") + val ε : epsilon.type = epsilon + + val existsOne = binder[Term, Formula, Formula]("∃!") + val ∃! : existsOne.type = existsOne + + + + extension (f: Formula) { + def unary_! = neg(f) + infix inline def ==>(g: Formula): Formula = implies(f)(g) + infix inline def <=>(g: Formula): Formula = iff(f)(g) + infix inline def /\(g: Formula): Formula = and(f)(g) + infix inline def ∧(g: Formula): Formula = and(f)(g) + infix inline def \/(g: Formula): Formula = or(f)(g) + infix inline def ∨(g: Formula): Formula = or(f)(g) + } + + def asFrontExpression(e: K.Expression): Expr[?] = e match + case c: K.Constant => asFrontConstant(c) + case v: K.Variable => asFrontVariable(v) + case a: K.Application => asFrontApplication(a) + case l: K.Lambda => asFrontLambda(l) + + def asFrontConstant(c: K.Constant): Constant[?] = + new Constant[T](c.id)(using new Sort { type Self = T; val underlying = c.sort }) + + def asFrontVariable(v: K.Variable): Variable[?] = + new Variable[T](v.id)(using new Sort { type Self = T; val underlying = v.sort }) + + def asFrontApplication(a: K.Application): App[?, ?] = + new App[T, T](asFrontExpression(a.f).asInstanceOf, asFrontExpression(a.arg).asInstanceOf)( + using new Sort { type Self = T; val underlying = a.sort }) + + def asFrontLambda(l: K.Lambda): Abs[?, ?] = + new Abs[T, T](asFrontVariable(l.v).asInstanceOf, asFrontExpression(l.body).asInstanceOf)( + using new Sort { type Self = T; val underlying = l.sort }) + + def greatestId(exprs: Seq[K.Expression | Expr[?] | K.Identifier ]): Int = + exprs.view.flatMap({ + case e: K.Expression => e.freeVariables.map(_.id) + case e: Expr[?] => e.freeVars.map(_.id) + case id: K.Identifier => Seq(id) + }).map(_.no).max + + def freshId(exprs: Seq[K.Expression | Expr[?] | K.Identifier ], base: String): K.Identifier = { + val i = exprs.view.flatMap({ + case e: K.Expression => e.freeVariables.map(_.id) + case e: Expr[?] => e.freeVars.map(_.id) + case id: K.Identifier => Seq(id) + }).filter(_.name == base).map(_.no).max + K.Identifier(base, i + 1) + } + + def nFreshIds(n: Int, exprs: Seq[K.Expression | Expr[?] | K.Identifier ], base: String): Seq[K.Identifier] = { + val i = exprs.view.flatMap({ + case e: K.Expression => e.freeVariables.map(_.id) + case e: Expr[?] => e.freeVars.map(_.id) + case id: K.Identifier => Seq(id) + }).filter(_.name == base).map(_.no).max + (i + 1 to i + n).map(K.Identifier(base, _)) + } + + + + + val f = variable[Formula >>: Term]("f") + val x: Expr[?] = variable[Term]("x") + val y: Expr[F] = variable[Formula]("x") + val g: Expr[Arrow[F, T]] = variable[Formula >>: Term]("g") + val h: Expr[?] = variable[Formula >>: Term]("g") + + +} \ No newline at end of file diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala new file mode 100644 index 00000000..b09ed3b5 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala @@ -0,0 +1,202 @@ +package lisa.fol + +import lisa.prooflib.BasicStepTactic +import lisa.prooflib.Library +import lisa.prooflib.ProofTacticLib.ProofTactic + +import lisa.utils.K + +import scala.annotation.showAsInfix + +trait Sequents extends Predef { + + + object SequentInstantiationRule extends ProofTactic + given ProofTactic = SequentInstantiationRule + + case class Sequent(left: Set[Formula], right: Set[Formula]) extends LisaObject{ + def underlying: lisa.kernel.proof.SequentCalculus.Sequent = K.Sequent(left.map(_.underlying), right.map(_.underlying)) + + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): Sequent = Sequent(left.map(_.substituteUnsafe(m)), right.map(_.substituteUnsafe(m))) + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): Sequent = + super.substituteWithCheck(m).asInstanceOf[Sequent] + override def substitute(pairs: SubstPair*): Sequent = + super.substitute(pairs*).asInstanceOf[Sequent] + + def freeVars: Set[Variable[?]] = left.flatMap(_.freeVars) ++ right.flatMap(_.freeVars) + def freeTermVars: Set[Variable[T]] = left.flatMap(_.freeTermVars) ++ right.flatMap(_.freeTermVars) + def constants: Set[Constant[?]] = left.flatMap(_.constants) ++ right.flatMap(_.constants) + + + + + + /*Ok for now but what when we have more*/ + /** + * Substitute schematic symbols inside this, and produces a kernel proof. + * Namely, if "that" is the result of the substitution, the proof should conclude with "that.underlying", + * using the assumption "this.underlying" at step index -1. + * + * @param map + * @return + */ + def instantiateWithProof(map: Map[Variable[?], Expr[?]], index: Int): (Sequent, Seq[K.SCProofStep]) = { + (substituteUnsafe(map), instantiateWithProofLikeKernel(map, index)) + + } + + def instantiateForallWithProof(args: Seq[Term], index: Int): (Sequent, Seq[K.SCProofStep]) = { + if this.right.size != 1 then throw new IllegalArgumentException("Right side of sequent must be a single universally quantified formula") + this.right.head match { + case r @ forall(x, f) => + val t = args.head + val newf: Formula = f.substitute(x := t) + val s0 = K.Hypothesis((newf |- newf).underlying, newf.underlying) + val s1 = K.LeftForall((r |- newf).underlying, index + 1, f.underlying, x.underlying, t.underlying) + val s2 = K.Cut((this.left |- newf).underlying, index, index + 2, r.underlying) + if args.tail.isEmpty then (this.left |- newf, Seq(s0, s1, s2)) + else + (this.left |- newf).instantiateForallWithProof(args.tail, index + 3) match { + case (s, p) => (s, Seq(s0, s1, s2) ++ p) + } + + case _ => throw new IllegalArgumentException("Right side of sequent must be a single universally quantified formula") + } + + } + + /** + * Given 3 substitution maps like the kernel accepts, i.e. Substitution of Predicate Connector and Term schemas, do the substitution + * and produce the (one-step) kernel proof that the result is provable from the original sequent + * + * @param mCon The substitution of connector schemas + * @param mPred The substitution of predicate schemas + * @param mTerm The substitution of function schemas + * @return + */ + def instantiateWithProofLikeKernel( + map: Map[Variable[?], Expr[?]], + index: Int + ): Seq[K.SCProofStep] = { + val premiseSequent = this.underlying + val mapK = map.map((v, e) => (v.underlying, e.underlying)) + val botK = lisa.utils.KernelHelpers.substituteVariablesInSequent(premiseSequent, mapK) + Seq(K.InstSchema(botK, index, mapK)) + } + + + infix def +<<(f: Formula): Sequent = this.copy(left = this.left + f) + infix def -<<(f: Formula): Sequent = this.copy(left = this.left - f) + infix def +>>(f: Formula): Sequent = this.copy(right = this.right + f) + infix def ->>(f: Formula): Sequent = this.copy(right = this.right - f) + infix def ++<<(s1: Sequent): Sequent = this.copy(left = this.left ++ s1.left) + infix def --<<(s1: Sequent): Sequent = this.copy(left = this.left -- s1.left) + infix def ++>>(s1: Sequent): Sequent = this.copy(right = this.right ++ s1.right) + infix def -->>(s1: Sequent): Sequent = this.copy(right = this.right -- s1.right) + infix def ++(s1: Sequent): Sequent = this.copy(left = this.left ++ s1.left, right = this.right ++ s1.right) + infix def --(s1: Sequent): Sequent = this.copy(left = this.left -- s1.left, right = this.right -- s1.right) + + infix def removeLeft(f: Formula): Sequent = this.copy(left = this.left.filterNot(isSame(_, f))) + infix def removeRight(f: Formula): Sequent = this.copy(right = this.right.filterNot(isSame(_, f))) + infix def removeAllLeft(s1: Sequent): Sequent = this.copy(left = this.left.filterNot(e1 => s1.left.exists(e2 => isSame(e1, e2)))) + infix def removeAllLeft(s1: Set[Formula]): Sequent = this.copy(left = this.left.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) + infix def removeAllRight(s1: Sequent): Sequent = this.copy(right = this.right.filterNot(e1 => s1.right.exists(e2 => isSame(e1, e2)))) + infix def removeAllRight(s1: Set[Formula]): Sequent = this.copy(right = this.right.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) + infix def removeAll(s1: Sequent): Sequent = + this.copy(left = this.left.filterNot(e1 => s1.left.exists(e2 => isSame(e1, e2))), right = this.right.filterNot(e1 => s1.right.exists(e2 => isSame(e1, e2)))) + + infix def addLeftIfNotExists(f: Formula): Sequent = if (this.left.exists(isSame(_, f))) this else (this +<< f) + infix def addRightIfNotExists(f: Formula): Sequent = if (this.right.exists(isSame(_, f))) this else (this +>> f) + infix def addAllLeftIfNotExists(s1: Sequent): Sequent = this ++<< s1.copy(left = s1.left.filterNot(e1 => this.left.exists(isSame(_, e1)))) + infix def addAllRightIfNotExists(s1: Sequent): Sequent = this ++>> s1.copy(right = s1.right.filterNot(e1 => this.right.exists(isSame(_, e1)))) + infix def addAllIfNotExists(s1: Sequent): Sequent = + this ++ s1.copy(left = s1.left.filterNot(e1 => this.left.exists(isSame(_, e1))), right = s1.right.filterNot(e1 => this.right.exists(isSame(_, e1)))) + + // OL shorthands + infix def +?(f: Formula): Sequent = this addRightIfNotExists f + infix def ->?(f: Formula): Sequent = this removeRight f + infix def ++?(s1: Sequent): Sequent = this addAllRightIfNotExists s1 + infix def -->?(s1: Sequent): Sequent = this removeAllRight s1 + infix def --?(s1: Sequent): Sequent = this removeAll s1 + infix def ++?(s1: Sequent): Sequent = this addAllIfNotExists s1 + + override def toString = + (if left.size == 0 then "" else if left.size == 1 then left.head.toString else "( " + left.mkString(", ") + " )") + + " ⊢ " + + (if right.size == 0 then "" else if right.size == 1 then right.head.toString else "( " + right.mkString(", ") + " )") + + } + + val emptySeq: Sequent = Sequent(Set.empty, Set.empty) + + given Conversion[Formula, Sequent] = f => Sequent(Set.empty, Set(f)) + + def isSame(e1: Expr[?], e2: Expr[?]): Boolean = { + e1.sort == e2.sort && K.isSame(e1.underlying, e2.underlying) + } + + def isSameSequent(sequent1: Sequent, sequent2: Sequent): Boolean = { + K.isSameSequent(sequent1.underlying, sequent2.underlying) + } + + /** + * returns true if the first argument implies the second by the laws of ortholattices. + */ + def isImplying[S: Sort](e1: Formula, e2: Formula): Boolean = { + K.isImplying(e1.underlying, e2.underlying) + } + def isImplyingSequent(sequent1: Sequent, sequent2: Sequent): Boolean = { + K.isImplyingSequent(sequent1.underlying, sequent2.underlying) + } + + def isSubset[A, B](s1: Set[Expr[A]], s2: Set[Expr[B]]): Boolean = { + K.isSubset(s1.map(_.underlying), s2.map(_.underlying)) + } + def isSameSet[A, B](s1: Set[Expr[A]], s2: Set[Expr[B]]): Boolean = + K.isSameSet(s1.map(_.underlying), s2.map(_.underlying)) + + def contains[A, B](s: Set[Expr[A]], f: Expr[B]): Boolean = { + K.contains(s.map(_.underlying), f.underlying) + } + + /** + * Represents a converter of some object into a set. + * @tparam S The type of elements in that set + * @tparam T The type to convert from + */ + trait FormulaSetConverter[T] { + def apply(t: T): Set[Formula] + } + + given FormulaSetConverter[Unit] with { + override def apply(u: Unit): Set[Formula] = Set.empty + } + + given FormulaSetConverter[EmptyTuple] with { + override def apply(t: EmptyTuple): Set[Formula] = Set.empty + } + + given [H <: Formula, T <: Tuple](using c: FormulaSetConverter[T]): FormulaSetConverter[H *: T] with { + override def apply(t: H *: T): Set[Formula] = c.apply(t.tail) + t.head + } + + given formula_to_set[T <: Formula]: FormulaSetConverter[T] with { + override def apply(f: T): Set[Formula] = Set(f) + } + + given iterable_to_set[T <: Formula, I <: Iterable[T]]: FormulaSetConverter[I] with { + override def apply(s: I): Set[Formula] = s.toSet + } + + private def any2set[A, T <: A](any: T)(using c: FormulaSetConverter[T]): Set[Formula] = c.apply(any) + + extension [A, T1 <: A](left: T1)(using FormulaSetConverter[T1]) { + infix def |-[B, T2 <: B](right: T2)(using FormulaSetConverter[T2]): Sequent = Sequent(any2set(left), any2set(right)) + infix def ⊢[B, T2 <: B](right: T2)(using FormulaSetConverter[T2]): Sequent = Sequent(any2set(left), any2set(right)) + } + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala new file mode 100644 index 00000000..63ad3933 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -0,0 +1,274 @@ +package lisa.fol + +import lisa.utils.K +import lisa.utils.K.Identifier +import lisa.utils.K.given_Conversion_String_Identifier + + +import scala.annotation.nowarn +import scala.annotation.showAsInfix +import scala.annotation.targetName + +trait Syntax { + + type IsSort[T] = Sort{type Self = T} + + trait T + trait F + trait Arrow[A: Sort, B: Sort] + + type Formula = Expr[F] + type Term = Expr[T] + @showAsInfix + infix type >>:[I, O] = (I, O) match { + case (Expr[a], Expr[b]) => Expr[Arrow[a, b]] + } + + type SortOf[T] = T match { + case Expr[t] => t + } + + trait Sort { + type Self + val underlying: K.Sort + } + given given_TermType: IsSort[T] with + val underlying = K.Term + given given_FormulaType: IsSort[F] with + val underlying = K.Formula + given given_ArrowType[A : Sort as ta, B : Sort as tb]: (IsSort[Arrow[A, B]]) with + val underlying = K.Arrow(ta.underlying, tb.underlying) + + sealed trait SubstPair extends Product { + type S + val _1: Variable[S] + val _2: Expr[S] + } + private case class ConcreteSubstPair[S1] (_1: Variable[S1], _2: Expr[S1]) extends SubstPair {type S = S1} + object SubstPair { + def apply[S1 : Sort](_1: Variable[S1], _2: Expr[S1]): SubstPair {type S = S1} = new ConcreteSubstPair[S1](_1, _2) + def unapply[S1](s: SubstPair{type S = S1}): SubstPair{type S = S1} = s + } + + def unsafeSortEvidence[S](sort: K.Sort) : IsSort[S] = new Sort { type Self = S; val underlying = sort } + + given [T: Sort]: Conversion[(Variable[T], Expr[T]), SubstPair{type S = T}] = s => SubstPair(s._1, s._2) + + + trait LisaObject { + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): LisaObject + def substituteWithCheck(m: Map[Variable[?], Expr[?]]): LisaObject = { + if m.forall((k, v) => k.sort == v.sort) then + substituteUnsafe(m) + else + val culprit = m.find((k, v) => k.sort != v.sort).get + throw new IllegalArgumentException("Sort mismatch in substitution: " + culprit._1 + " -> " + culprit._2) + } + def substitute(pairs: SubstPair*): LisaObject = + substituteWithCheck(pairs.view.map(s => (s._1, s._2)).toMap) + + def freeVars: Set[Variable[?]] + def freeTermVars: Set[Variable[T]] + def constants: Set[Constant[?]] + } + trait Expr[S: Sort] extends LisaObject { + val sort: K.Sort = summon[IsSort[S]].underlying + private val arity = K.flatTypeParameters(sort).size + def underlying: K.Expression + + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): Expr[S] + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): Expr[S] = + super.substituteWithCheck(m).asInstanceOf[Expr[S]] + override def substitute(pairs: SubstPair*): Expr[S] = + super.substitute(pairs*).asInstanceOf[Expr[S]] + + + def unapply[T1, T2](e: Expr[Arrow[T1, T2]]): Option[Expr[T1]] = (e: @unchecked) match { + case App[T1, T2](f, arg) if f == this => Some(arg) + case _ => None + } + final def defaultMkString(args: Seq[Expr[?]]): String = s"$this(${args.map(a => s"(${a})")})" + final def defaultMkStringSeparated(args: Seq[Expr[?]]): String = s"(${defaultMkString(args)})" + var mkString: Seq[Expr[?]] => String = defaultMkString + var mkStringSeparated: Seq[Expr[?]] => String = defaultMkStringSeparated + + + def #@(arg: Expr[?]): RetExpr[S] = + App.unsafe(this, arg).asInstanceOf + + def #@@(args: Seq[Expr[?]]): Expr[?] = + Multiapp.unsafe(this, args) + } + + extension [T1, T2](f: Expr[Arrow[T1, T2]]) + def apply(using IsSort[T1], IsSort[T2])(arg: Expr[T1]): Expr[T2] = App(f, arg) + +/* + type RetExpr[T] <: Expr[?] = T match + case Arrow[a, b] => Expr[b] + case _ => Expr[?] + */ + + type RetExpr[T] = Expr[?] + + class Multiapp(f: Expr[?]): + def unapply (e: Expr[?]): Option[Seq[Expr[?]]] = + def inner(e: Expr[?]): Option[List[Expr[?]]] = e match + case App(f2, arg) if f == f2 => Some(List(arg)) + case App(f2, arg) => inner(f2).map(arg :: _) + case _ => None + inner(e).map(_.reverse) + + object Multiapp: + def unsafe(f: Expr[?], args: Seq[Expr[?]]): Expr[?] = + args.foldLeft(f)((f, arg) => App.unsafe(f, arg)) + + + + def unfoldAllApp(e:Expr[?]): (Expr[?], List[Expr[?]]) = e match + case App(f, arg) => + val (f1, args) = unfoldAllApp(f) + (f1, arg :: args ) + case _ => (e, Nil) + + + + + case class Variable[S : Sort](id: K.Identifier) extends Expr[S] { + val underlying: K.Variable = K.Variable(id, sort) + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): Expr[S] = m.getOrElse(this, this).asInstanceOf[Expr[S]] + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): Expr[S] = + super.substituteWithCheck(m).asInstanceOf[Expr[S]] + override def substitute(pairs: SubstPair*): Expr[S] = + super.substitute(pairs*).asInstanceOf[Expr[S]] + def freeVars: Set[Variable[?]] = Set(this) + def freeTermVars: Set[Variable[T]] = if sort == K.Term then Set(this.asInstanceOf) else Set.empty + def constants: Set[Constant[?]] = Set.empty + def rename(newId: K.Identifier): Variable[S] = Variable(newId) + def freshRename(existing: Iterable[Expr[?]]): Variable[S] = { + val newId = K.freshId(existing.flatMap(_.freeVars.map(_.id)), id) + Variable(newId) + } + override def toString(): String = id.toString + def :=(replacement: Expr[S]) = SubstPair(this, replacement) + } + + object Variable { + def unsafe(id: String, sort: K.Sort): Variable[?] = Variable(id)(using unsafeSortEvidence(sort)) + } + + + case class Constant[S : Sort](id: K.Identifier) extends Expr[S] { + private var infix: Boolean = false + def setInfix(): Unit = infix = true + val underlying: K.Constant = K.Constant(id, sort) + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): Constant[S] = this + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): Expr[S] = + super.substituteWithCheck(m).asInstanceOf[Constant[S]] + override def substitute(pairs: SubstPair*): Constant[S] = + super.substitute(pairs*).asInstanceOf[Constant[S]] + def freeVars: Set[Variable[?]] = Set.empty + def freeTermVars: Set[Variable[T]] = Set.empty + def constants: Set[Constant[?]] = Set(this) + def rename(newId: K.Identifier): Constant[S] = Constant(newId) + override def toString(): String = id.toString + mkString = (args: Seq[Expr[?]]) => + if infix && args.size == 2 then + s"${args(0)} $this ${args(1)}" + else if infix & args.size > 2 then + s"(${args(0)} $this ${args(1)})${args.drop(2).map(_.mkStringSeparated).mkString})" + else + defaultMkString(args) + mkStringSeparated = (args: Seq[Expr[?]]) => + if infix && args.size == 2 then + s"${args(0)} $this ${args(1)}" + else if infix & args.size > 2 then + s"(${args(0)} $this ${args(1)})${args.drop(2).map(_.mkStringSeparated).mkString})" + else + defaultMkStringSeparated(args) + } + + object Constant { + def unsafe(id: String, sort: K.Sort): Constant[?] = Constant(id)(using unsafeSortEvidence(sort)) + } + + class Binder[T1: Sort, T2: Sort, T3: Sort](id: K.Identifier) extends Constant[Arrow[Arrow[T1, T2], T3]](id) { + def apply(v1: Variable[T1], e: Expr[T2]): App[Arrow[T1, T2], T3] = App(this, Abs(v1, e)) + @targetName("unapplyBinder") + def unapply(e: Expr[?]): Option[(Variable[T1], Expr[T2])] = e match { + case App(f:Expr[Arrow[Arrow[T1, T2], T3]], Abs(v, e)) if f == this => Some((v, e)) + case _ => None + } + mkString = (args: Seq[Expr[?]]) => + if args.size == 0 then toString + else args(0) match { + case Abs(v, e) => s"$id($v, $e)${args.drop(1).map(_.mkStringSeparated).mkString}" + case _ => defaultMkString(args) + } + mkStringSeparated = (args: Seq[Expr[?]]) => + args match { + case Seq(Abs(v, e)) => s"($id($v, $e))" + case _ => defaultMkStringSeparated(args) + } + } + + + case class App[T1 : Sort, T2 : Sort](f: Expr[Arrow[T1, T2]], arg: Expr[T1]) extends Expr[T2] { + val underlying: K.Application = K.Application(f.underlying, arg.underlying) + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): App[T1, T2] = App[T1, T2](f.substituteUnsafe(m), arg.substituteUnsafe(m)) + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): App[T1, T2] = + super.substituteWithCheck(m).asInstanceOf[App[T1, T2]] + override def substitute(pairs: SubstPair*): App[T1, T2] = + super.substitute(pairs*).asInstanceOf[App[T1, T2]] + def freeVars: Set[Variable[?]] = f.freeVars ++ arg.freeVars + def freeTermVars: Set[Variable[T]] = f.freeTermVars ++ arg.freeTermVars + def constants: Set[Constant[?]] = f.constants ++ arg.constants + override def toString(): String = + val (f, args) = unfoldAllApp(this) + f.mkString(args) + } + + object App { + def unsafe(f: Expr[?], arg: Expr[?]): Expr[?] = + val rsort = K.legalApplication(f.sort, arg.sort) + rsort match + case Some(to) => + App(f.asInstanceOf, arg.asInstanceOf)(using unsafeSortEvidence(to), unsafeSortEvidence(arg.sort)) + case None => throw new IllegalArgumentException(s"Cannot apply $f of sort ${f.sort} to $arg of sort ${arg.sort}") + } + + + case class Abs[T1 : Sort, T2 : Sort](v: Variable[T1], body: Expr[T2]) extends Expr[Arrow[T1, T2]] { + val underlying: K.Lambda = K.Lambda(v.underlying, body.underlying) + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): Abs[T1, T2] = Abs(v, body.substituteUnsafe(m - v)) + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): Abs[T1, T2] = + super.substituteWithCheck(m).asInstanceOf[Abs[T1, T2]] + override def substitute(pairs: SubstPair*): Abs[T1, T2] = + super.substitute(pairs*).asInstanceOf[Abs[T1, T2]] + def freeVars: Set[Variable[?]] = body.freeVars - v + def freeTermVars: Set[Variable[T]] = body.freeTermVars.filterNot(_ == v) + def constants: Set[Constant[?]] = body.constants + override def toString(): String = s"Abs($v, $body)" + } + + object Abs { + def unsafe(v: Variable[?], body: Expr[?]): Expr[?] = + Abs(v.asInstanceOf, body.asInstanceOf)(using unsafeSortEvidence(v.sort), unsafeSortEvidence(body.sort)) + } + + + + + + private val x = Variable[T]("x") + private val y = Variable[F]("x") + private val z: Expr[Arrow[T, F]] = Variable("x") + z(x) + + + +} + + + + diff --git a/lisa-utils/src/main/scala/lisa/utils/package.scala b/lisa-utils/src/main/scala/lisa/utils/package.scala deleted file mode 100644 index 0dd12e8f..00000000 --- a/lisa-utils/src/main/scala/lisa/utils/package.scala +++ /dev/null @@ -1,4 +0,0 @@ -package lisa.utils - -export lisa.utils.parsing.{FOLParser, FOLPrinter, Parser, Printer, ProofPrinter} -//export lisa.utils.KernelHelpers.{*, given} diff --git a/lisa-utils/src/main/scala/lisa/utils/parsing/Parser.scala b/lisa-utils/src/main/scala/lisa/utils/parsing/Parser.scala deleted file mode 100644 index 806caf3d..00000000 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/Parser.scala +++ /dev/null @@ -1,689 +0,0 @@ -package lisa.utils.parsing - -import lisa.kernel.fol.FOL -import lisa.kernel.fol.FOL.* -import lisa.kernel.fol.FOL.equality -import lisa.kernel.proof.SequentCalculus.* -import lisa.utils.KernelHelpers.False -import lisa.utils.KernelHelpers.given_Conversion_Identifier_String -import lisa.utils.KernelHelpers.given_Conversion_String_Identifier -import lisa.utils.parsing.ParsingUtils -import scallion.* -import scallion.util.Unfolds.unfoldRight -import silex.* - -import scala.collection.mutable - -val FOLParser = Parser(SynonymInfo.empty, "=" :: Nil, Nil) - -enum Associativity { - case Left, Right, None -} - -//TODO: Deal with errors in parsing. -class ParsingException(parsedString: String, errorMessage: String) extends lisa.utils.LisaException(errorMessage) { - def showError: String = "" -} - -abstract class ParserException(msg: String) extends Exception(msg) - -class UnexpectedInputException(input: String, position: (Int, Int), expected: String) - extends ParserException( - s""" - |$input - |${" " * position._1 + "^" * (position._2 - position._1)} - |Unexpected input: expected $expected - |""".stripMargin - ) - -object UnexpectedEndOfInputException extends Exception("Unexpected end of input") - -object UnreachableException extends ParserException("Internal error: expected unreachable") - -class PrintFailedException(inp: Sequent | Formula | Term) extends ParserException(s"Printing of $inp failed unexpectedly") - -/** - * @param synonymToCanonical information about synonyms that correspond to the same FunctionLabel / AtomicLabel. - * Can be constructed with [[lisa.utils.SynonymInfoBuilder]] - * @param infixPredicates list of infix predicates' names - * @param infixFunctions list of infix functions and their associativity in the decreasing order of priority - */ -class Parser( - synonymToCanonical: SynonymInfo, - infixPredicates: List[String], - infixFunctions: List[(String, Associativity)] -) { - private val infixPredicateSet = infixPredicates.toSet - private val infixFunctionSet = infixFunctions.map(_._1).toSet - private val infixSet = infixPredicateSet ++ infixFunctionSet - - /** - * Parses a sequent from a string. A sequent consists of the left and right side, separated by `⊢` or `|-`. - * Left and right sides consist of formulas, separated by `;`. - * - * @see Parser#parseFormula - * @param s string representation of the sequent - * @return parsed sequent on success, throws an exception when unexpected input or end of input. - */ - def parseSequent(s: String): Sequent = - try { - extractParseResult(s, SequentParser.parseTermulaSequent(SequentLexer(s.iterator))).toSequent - } catch { - case e: ExpectedFormulaGotTerm => throw new UnexpectedInputException(s, e.range, "formula") - case e: ExpectedTermGotFormula => throw new UnexpectedInputException(s, e.range, "term") - } - - /** - * Parses a formula from a string. A formula can be: - *

- a bound formula: `∀'x. f`, `∃'x. f`, `∃!'x. f`. A binder binds the entire formula until the end of the scope (a closing parenthesis or the end of string). - *

- two formulas, connected by `⇔` or `⇒`. Iff / implies bind less tight than and / or. - *

- a conjunction or disjunction of arbitrary number of formulas. `∧` binds tighter than `∨`. - *

- negated formula. - *

- schematic connector formula: `?c(f1, f2, f3)`. - *

- equality of two formulas: `f1 = f2`. - *

- a constant `p(a)` or schematic `'p(a)` predicate application to arbitrary number of term arguments. - *

- boolean constant: `⊤` or `⊥`. - * - * @param s string representation of the formula - * @return parsed formula on success, throws an exception when unexpected input or end of input. - */ - def parseFormula(s: String): Formula = - try { - extractParseResult(s, SequentParser.parseTermula(SequentLexer(s.iterator))).toFormula - } catch { - case e: ExpectedFormulaGotTerm => throw new UnexpectedInputException(s, e.range, "formula") - case e: ExpectedTermGotFormula => throw new UnexpectedInputException(s, e.range, "term") - } - - /** - * Parses a term from a string. A term is a constant `c`, a schematic variable `'x` or an application of a constant `f(a)` - * or a schematic `'f(a)` function to other terms. - * - * @param s string representation of the term - * @return parsed term on success, throws an exception when unexpected input or end of input. - */ - def parseTerm(s: String): Term = - try { - extractParseResult(s, SequentParser.parseTermula(SequentLexer(s.iterator))).toTerm - } catch { - case e: ExpectedFormulaGotTerm => throw new UnexpectedInputException(s, e.range, "formula") - case e: ExpectedTermGotFormula => throw new UnexpectedInputException(s, e.range, "term") - } - - private def extractParseResult[T](input: String, r: SequentParser.ParseResult[T]): T = r match { - case SequentParser.Parsed(value, _) => value - case SequentParser.UnexpectedToken(token, rest) => throw UnexpectedInputException(input, token.range, "one of " + rest.first.mkString(", ")) - case SequentParser.UnexpectedEnd(_) => throw UnexpectedEndOfInputException - } - - /** - * Returns a string representation of the sequent. Empty set of formulas on the left side is not printed. - * Empty set of formulas on the right side is represented as `⊥` (false). - * - * @param s sequent to print - * @return string representation of the sequent, or the smallest component thereof, on which printing failed - */ - def printSequent(s: Sequent): String = SequentParser - .printTermulaSequent(s.toTermulaSequent) - .getOrElse({ - // attempt to print individual formulas. It might throw a more detailed PrintFailedException - s.left.foreach(printFormula) - s.right.foreach(printFormula) - throw PrintFailedException(s) - }) - - /** - * @param f formula to print - * @return string representation of the formula, or the smallest component thereof, on which printing failed - */ - def printFormula(f: Formula): String = SequentParser - .printTermula(f.toTermula) - .getOrElse({ - f match { - case AtomicFormula(_, args) => args.foreach(printTerm) - case ConnectorFormula(_, args) => args.foreach(printFormula) - case BinderFormula(_, _, inner) => printFormula(inner) - } - throw PrintFailedException(f) - }) - - /** - * @param t term to print - * @return string representation of the term, or the smallest component thereof, on which printing failed - */ - def printTerm(t: Term): String = SequentParser - .printTermula(t.toTermula) - .getOrElse({ - t match { - case Term(_, args) => args.foreach(printTerm) - case VariableTerm(_) => () - } - throw PrintFailedException(t) - }) - - private val UNKNOWN_RANGE = (-1, -1) - - private[Parser] object SequentLexer extends Lexers with CharLexers { - sealed abstract class FormulaToken(stringRepr: String) { - def printString: String = stringRepr - val range: (Int, Int) - } - - case class ForallToken(range: (Int, Int)) extends FormulaToken(Forall.id) - - case class ExistsOneToken(range: (Int, Int)) extends FormulaToken(ExistsOne.id) - - case class ExistsToken(range: (Int, Int)) extends FormulaToken(Exists.id) - - case class DotToken(range: (Int, Int)) extends FormulaToken(".") - - case class AndToken(range: (Int, Int), prefix: Boolean) extends FormulaToken(And.id) - - case class OrToken(range: (Int, Int), prefix: Boolean) extends FormulaToken(Or.id) - - case class ImpliesToken(range: (Int, Int)) extends FormulaToken(Implies.id) - - case class IffToken(range: (Int, Int)) extends FormulaToken(Iff.id) - - case class NegationToken(range: (Int, Int)) extends FormulaToken(Neg.id) - - case class TrueToken(range: (Int, Int)) extends FormulaToken("⊤") - - case class FalseToken(range: (Int, Int)) extends FormulaToken("⊥") - - // Constant functions and predicates - case class ConstantToken(id: String, range: (Int, Int)) extends FormulaToken(id) - - // Variables, schematic functions and predicates - case class SchematicToken(id: String, range: (Int, Int)) extends FormulaToken(schematicSymbol + id) - - // This token is not required for parsing, but is needed to print spaces around infix operations - case class InfixToken(id: String, range: (Int, Int)) extends FormulaToken(id) - - // Schematic connector (prefix notation is expected) - case class SchematicConnectorToken(id: String, range: (Int, Int)) extends FormulaToken(schematicConnectorSymbol + id) - - case class ParenthesisToken(isOpen: Boolean, range: (Int, Int)) extends FormulaToken(if (isOpen) "(" else ")") - - case class CommaToken(range: (Int, Int)) extends FormulaToken(",") - - case class SemicolonToken(range: (Int, Int)) extends FormulaToken(";") - - case class SequentToken(range: (Int, Int)) extends FormulaToken("⊢") - - case class SpaceToken(range: (Int, Int)) extends FormulaToken(" ") - - case class UnknownToken(str: String, range: (Int, Int)) extends FormulaToken(str) - - type Token = FormulaToken - type Position = Int - - val escapeChar = '`' - val pathSeparator = '$' - private val schematicSymbol = "'" - private val schematicConnectorSymbol = "?" - - private val letter = elem(_.isLetter) - private val variableLike = letter ~ many(elem(c => c.isLetterOrDigit || c == '_')) - private val number = many1(elem(_.isDigit)) - private val escaped = elem(escapeChar) ~ many1(elem(_ != escapeChar)) ~ elem(escapeChar) - private val arbitrarySymbol = elem(!_.isWhitespace) - private val symbolSequence = many1(oneOf("*/+-^:<>#%&@")) - private val path = many1(many1(letter) ~ elem(pathSeparator)) - - private val lexer = Lexer( - elem('∀') |> { (_, r) => ForallToken(r) }, - word("∃!") |> { (_, r) => ExistsOneToken(r) }, - elem('∃') |> { (_, r) => ExistsToken(r) }, - elem('.') |> { (_, r) => DotToken(r) }, - elem('∧') | word("/\\") |> { (_, r) => AndToken(r, false) }, - elem('∨') | word("\\/") |> { (_, r) => OrToken(r, false) }, - word(Implies.id.name) | word("=>") | word("==>") | elem('⇒') |> { (_, r) => ImpliesToken(r) }, - word(Iff.id.name) | word("<=>") | word("<==>") | elem('⟷') | elem('⇔') |> { (_, r) => IffToken(r) }, - elem('⊤') | elem('T') | word("True") | word("true") |> { (_, r) => TrueToken(r) }, - elem('⊥') | elem('F') | word("False") | word("false") |> { (_, r) => FalseToken(r) }, - elem('¬') | elem('!') |> { (_, r) => NegationToken(r) }, - elem('(') |> { (_, r) => ParenthesisToken(true, r) }, - elem(')') |> { (_, r) => ParenthesisToken(false, r) }, - elem(',') |> { (_, r) => CommaToken(r) }, - elem(';') |> { (_, r) => SemicolonToken(r) }, - elem('⊢') | word("|-") |> { (_, r) => SequentToken(r) }, - many1(whiteSpace) |> { (_, r) => SpaceToken(r) }, - word(schematicSymbol) ~ variableLike |> { (cs, r) => - // drop the ' - SchematicToken(cs.drop(1).mkString, r) - }, - word(schematicConnectorSymbol) ~ variableLike |> { (cs, r) => - SchematicConnectorToken(cs.drop(1).mkString, r) - }, - // Currently the path is merged into the id on the lexer level. When qualified ids are supported, this should be - // lifted into the parser. - opt(path) ~ (variableLike | number | arbitrarySymbol | symbolSequence | escaped) |> { (cs, r) => ConstantToken(cs.filter(_ != escapeChar).mkString, r) } - ) onError { (cs, r) => - UnknownToken(cs.mkString, r) - } - - def apply(it: Iterator[Char]): Iterator[Token] = { - val source = Source.fromIterator(it, IndexPositioner) - lexer - .spawn(source) - .map({ - case ConstantToken(id, r) if infixSet.contains(id) => InfixToken(id, r) - case t => t - }) - .filter(!_.isInstanceOf[SpaceToken]) - } - - def unapply(tokens: Iterator[Token]): String = { - val space = " " - tokens - .foldLeft(Nil: List[String]) { - // Sequent token is the only separator that can have an empty left side; in this case, omit the space before - case (Nil, s: SequentToken) => s.printString :: space :: Nil - case (l, t) => - t match { - // do not require spaces - - case _: ForallToken | _: ExistsToken | _: ExistsOneToken | _: NegationToken | _: ConstantToken | _: SchematicToken | _: SchematicConnectorToken | _: TrueToken | _: FalseToken | - _: ParenthesisToken | _: SpaceToken | AndToken(_, true) | OrToken(_, true) => - l :+ t.printString - // space after: separators - case _: DotToken | _: CommaToken | _: SemicolonToken => l :+ t.printString :+ space - // space before and after: infix, connectors, sequent symbol - - case _: InfixToken | _: AndToken | _: OrToken | _: ImpliesToken | _: IffToken | _: SequentToken => l :+ space :+ t.printString :+ space - case _: UnknownToken => throw UnreachableException - } - } - .mkString - } - } - - case class RangedLabel(folLabel: FOL.FormulaLabel, range: (Int, Int)) - - abstract class TermulaConversionError(range: (Int, Int)) extends Exception - case class ExpectedTermGotFormula(range: (Int, Int)) extends TermulaConversionError(range) - case class ExpectedFormulaGotTerm(range: (Int, Int)) extends TermulaConversionError(range) - - /** - * Termula represents parser-level union of terms and formulas. - * After parsing, the termula can be converted either to a term or to a formula: - *

- to be converted to a term, termula must consist only of function applications; - *

- to be converted to a formula, `args` of the termula are interpreted as formulas until a predicate application is observed; - * `args` of the predicate application are terms. - * - *

Convention: since the difference between `TermLabel`s and `AtomicLabel`s is purely semantic and Termula needs - * FormulaLabels (because of connector and binder labels), all TermLabels are translated to the corresponding - * PredicateLabels (see [[toTermula]]). - * - * @param label `AtomicLabel` for predicates and functions, `ConnectorLabel` or `BinderLabel` - * @param args Predicate / function arguments for `AtomicLabel`, connected formulas for `ConnectorLabel`, - * `Seq(VariableFormulaLabel(bound), inner)` for `BinderLabel` - */ - case class Termula(label: RangedLabel, args: Seq[Termula], range: (Int, Int)) { - def toTerm: Term = label.folLabel match { - case _: BinderLabel | _: ConnectorLabel => throw ExpectedTermGotFormula(range) - case t: ConstantAtomicLabel => Term(ConstantFunctionLabel(t.id, t.arity), args.map(_.toTerm)) - case t: SchematicPredicateLabel => Term(SchematicFunctionLabel(t.id, t.arity), args.map(_.toTerm)) - case v: VariableFormulaLabel => Term(VariableLabel(v.id), Seq()) - } - - def toFormula: Formula = label.folLabel match { - case p: AtomicLabel => AtomicFormula(p, args.map(_.toTerm)) - case c: ConnectorLabel => ConnectorFormula(c, args.map(_.toFormula)) - case b: BinderLabel => - args match { - case Seq(Termula(RangedLabel(v: VariableFormulaLabel, _), Seq(), _), f) => BinderFormula(b, VariableLabel(v.id), f.toFormula) - // wrong binder format. Termulas can only be constructed within parser and they are expected to always be constructed according - // to the format above - case _ => throw UnreachableException - } - } - } - - extension (f: Formula) { - - def toTermula: Termula = { - given Conversion[FOL.FormulaLabel, RangedLabel] with { - def apply(f: FOL.FormulaLabel): RangedLabel = RangedLabel(f, UNKNOWN_RANGE) - } - - f match { - case AtomicFormula(label, args) => Termula(label, args.map(_.toTermula), UNKNOWN_RANGE) - // case ConnectorFormula(And | Or, Seq(singleArg)) => singleArg.toTermula - case ConnectorFormula(label, args) => Termula(label, args.map(_.toTermula), UNKNOWN_RANGE) - case BinderFormula(label, bound, inner) => Termula(label, Seq(Termula(VariableFormulaLabel(bound.id), Seq(), UNKNOWN_RANGE), inner.toTermula), UNKNOWN_RANGE) - } - } - } - - extension (t: Term) { - def toTermula: Termula = { - given Conversion[FOL.FormulaLabel, RangedLabel] with { - def apply(f: FOL.FormulaLabel): RangedLabel = RangedLabel(f, UNKNOWN_RANGE) - } - val newLabel = t.label match { - case ConstantFunctionLabel(id, arity) => ConstantAtomicLabel(id, arity) - case SchematicFunctionLabel(id, arity) => SchematicPredicateLabel(id, arity) - case VariableLabel(id) => VariableFormulaLabel(id) - } - Termula(newLabel, t.args.map(_.toTermula), UNKNOWN_RANGE) - } - } - - case class TermulaSequent(left: Set[Termula], right: Set[Termula]) { - def toSequent: Sequent = Sequent(left.map(_.toFormula), right.map(_.toFormula)) - } - - extension (s: Sequent) { - def toTermulaSequent: TermulaSequent = TermulaSequent(s.left.map(_.toTermula), s.right.map(_.toTermula)) - } - - private[Parser] object SequentParser extends Parsers with ParsingUtils { - sealed abstract class TokenKind(debugString: String) { - override def toString: Mark = debugString - } - // and, or are both (left) associative and bind tighter than implies, iff - case object AndKind extends TokenKind(And.id) - case object OrKind extends TokenKind(Or.id) - // implies, iff are both non-associative and are of equal priority, lower than and, or - case object TopLevelConnectorKind extends TokenKind(s"${Implies.id} or ${Iff.id}") - case object NegationKind extends TokenKind(Neg.id) - case object BooleanConstantKind extends TokenKind("boolean constant") - case object IdKind extends TokenKind("constant or schematic id") - case class InfixKind(id: String) extends TokenKind(s"infix operation $id") - case object CommaKind extends TokenKind(",") - case class ParenthesisKind(isOpen: Boolean) extends TokenKind(if (isOpen) "(" else ")") - case object BinderKind extends TokenKind("binder") - case object DotKind extends TokenKind(".") - case object SemicolonKind extends TokenKind(";") - case object SequentSymbolKind extends TokenKind("⊢") - case object OtherKind extends TokenKind("") - - import SequentLexer._ - type Token = FormulaToken - type Kind = TokenKind - - // can safely skip tokens which were mapped to unit with elem(kind).unit(token) - import SafeImplicits._ - - def getKind(t: Token): Kind = t match { - case _: AndToken => AndKind - case _: OrToken => OrKind - case _: IffToken | _: ImpliesToken => TopLevelConnectorKind - case _: NegationToken => NegationKind - case _: TrueToken | _: FalseToken => BooleanConstantKind - case InfixToken(id, _) => InfixKind(id) - case _: ConstantToken | _: SchematicToken | _: SchematicConnectorToken => IdKind - case _: CommaToken => CommaKind - case ParenthesisToken(isOpen, _) => ParenthesisKind(isOpen) - case _: ExistsToken | _: ExistsOneToken | _: ForallToken => BinderKind - case _: DotToken => DotKind - case _: SemicolonToken => SemicolonKind - case _: SequentToken => SequentSymbolKind - case _: SpaceToken | _: UnknownToken => OtherKind - } - - /////////////////////// SEPARATORS //////////////////////////////// - def parens(isOpen: Boolean): Syntax[Unit] = - elem(ParenthesisKind(isOpen)).unit(ParenthesisToken(isOpen, UNKNOWN_RANGE)) - - val open: Syntax[Unit] = parens(true) - - val closed: Syntax[Unit] = parens(false) - - val comma: Syntax[Unit] = elem(CommaKind).unit(CommaToken(UNKNOWN_RANGE)) - - val dot: Syntax[Unit] = elem(DotKind).unit(DotToken(UNKNOWN_RANGE)) - - val semicolon: Syntax[Unit] = elem(SemicolonKind).unit(SemicolonToken(UNKNOWN_RANGE)) - - val sequentSymbol: Syntax[Unit] = elem(SequentSymbolKind).unit(SequentToken(UNKNOWN_RANGE)) - /////////////////////////////////////////////////////////////////// - - //////////////////////// LABELS /////////////////////////////////// - val toplevelConnector: Syntax[RangedLabel] = accept(TopLevelConnectorKind)( - { - case ImpliesToken(r) => RangedLabel(Implies, r) - case IffToken(r) => RangedLabel(Iff, r) - }, - { - case RangedLabel(Implies, r) => Seq(ImpliesToken(r)) - case RangedLabel(Iff, r) => Seq(IffToken(r)) - case _ => throw UnreachableException - } - ) - - val negation: Syntax[RangedLabel] = accept(NegationKind)( - { case NegationToken(r) => RangedLabel(Neg, r) }, - { - case RangedLabel(Neg, r) => Seq(NegationToken(r)) - case _ => throw UnreachableException - } - ) - - val INFIX_ARITY = 2 - /////////////////////////////////////////////////////////////////// - - //////////////////////// TERMULAS ///////////////////////////////// - // can appear without parentheses - lazy val simpleTermula: Syntax[Termula] = prefixApplication | negated | bool - lazy val subtermula: Syntax[Termula] = simpleTermula | open.skip ~ termula ~ closed.skip - - val bool: Syntax[Termula] = accept(BooleanConstantKind)( - { - case TrueToken(r) => Termula(RangedLabel(top, r), Seq(), r) - case FalseToken(r) => Termula(RangedLabel(bot, r), Seq(), r) - }, - { - case Termula(RangedLabel(And, _), Seq(), r) => Seq(TrueToken(r)) - case Termula(RangedLabel(Or, _), Seq(), r) => Seq(FalseToken(r)) - case _ => throw UnreachableException - } - ) - - case class RangedTermulaSeq(ts: Seq[Termula], range: (Int, Int)) - lazy val args: Syntax[RangedTermulaSeq] = recursive( - (elem(ParenthesisKind(true)) ~ repsep(termula, comma) ~ elem(ParenthesisKind(false))).map( - { case start ~ ts ~ end => - RangedTermulaSeq(ts, (start.range._1, end.range._2)) - }, - { case RangedTermulaSeq(ts, (start, end)) => - Seq(ParenthesisToken(true, (start, start)) ~ ts ~ ParenthesisToken(false, (end, end))) - } - ) - ) - - def reconstructPrefixApplication(t: Termula): Token ~ Option[RangedTermulaSeq] = { - val argsRange = (t.label.range._2 + 1, t.range._2) - t.label.folLabel match { - case VariableFormulaLabel(id) => SchematicToken(id, t.label.range) ~ None - case SchematicPredicateLabel(id, _) => SchematicToken(id, t.range) ~ Some(RangedTermulaSeq(t.args, argsRange)) - case ConstantAtomicLabel(id, arity) => - ConstantToken(synonymToCanonical.getPrintName(id), t.label.range) ~ - (if (arity == 0) None else Some(RangedTermulaSeq(t.args, argsRange))) - case _ => throw UnreachableException - } - } - - // schematic connector, function, or predicate; constant function or predicate - val prefixApplication: Syntax[Termula] = ((elem(IdKind) | elem(OrKind) | elem(AndKind)) ~ opt(args)).map( - { case p ~ optArgs => - val args: Seq[Termula] = optArgs.map(_.ts).getOrElse(Seq()) - val l = p match { - - case ConstantToken(id, _) => ConstantAtomicLabel(synonymToCanonical.getInternalName(id), args.size) - case SchematicToken(id, _) => - if (args.isEmpty) VariableFormulaLabel(id) else SchematicPredicateLabel(id, args.size) - case SchematicConnectorToken(id, _) => SchematicConnectorLabel(id, args.size) - case AndToken(_, _) => And - case OrToken(_, _) => Or - case _ => throw UnreachableException - } - Termula(RangedLabel(l, p.range), args, (p.range._1, optArgs.map(_.range._2).getOrElse(p.range._2))) - }, - { - case t @ Termula(RangedLabel(_: AtomicLabel, _), _, _) => Seq(reconstructPrefixApplication(t)) - case t @ Termula(RangedLabel(SchematicConnectorLabel(id, _), r), args, _) => - val argsRange = (t.label.range._2 + 1, t.range._2) - Seq(SchematicConnectorToken(id, r) ~ Some(RangedTermulaSeq(args, argsRange))) - case t @ Termula(RangedLabel(And, r), args, _) => - val argsRange = (t.label.range._2 + 1, t.range._2) - Seq(AndToken(r, true) ~ Some(RangedTermulaSeq(args, argsRange))) - case t @ Termula(RangedLabel(Or, r), args, _) => - val argsRange = (t.label.range._2 + 1, t.range._2) - Seq(OrToken(r, true) ~ Some(RangedTermulaSeq(args, argsRange))) - - case _ => throw UnreachableException - } - ) - - val infixFunctionLabels: List[PrecedenceLevel[RangedLabel]] = infixFunctions.map({ case (l, assoc) => - elem(InfixKind(l)).map[RangedLabel]( - { - case InfixToken(`l`, range) => RangedLabel(ConstantAtomicLabel(synonymToCanonical.getInternalName(l), INFIX_ARITY), range) - case _ => throw UnreachableException - }, - { - case RangedLabel(ConstantAtomicLabel(id, INFIX_ARITY), range) => Seq(InfixToken(synonymToCanonical.getPrintName(id), range)) - case _ => throw UnreachableException - } - ) has assoc - }) - - val infixPredicateLabels: List[PrecedenceLevel[RangedLabel]] = infixPredicates.map(l => - elem(InfixKind(l)).map[RangedLabel]( - { - case InfixToken(`l`, range) => RangedLabel(ConstantAtomicLabel(synonymToCanonical.getInternalName(l), INFIX_ARITY), range) - case _ => throw UnreachableException - }, - { - case RangedLabel(ConstantAtomicLabel(id, INFIX_ARITY), range) => Seq(InfixToken(synonymToCanonical.getPrintName(id), range)) - case _ => throw UnreachableException - } - ) has Associativity.None - ) - - val negated: Syntax[Termula] = recursive { - (negation ~ subtermula).map( - { case n ~ f => - Termula(n, Seq(f), (n.range._1, f.range._2)) - }, - { - case Termula(l @ RangedLabel(Neg, _), Seq(f), _) => Seq(l ~ f) - case _ => throw UnreachableException - } - ) - } - - val and: Syntax[RangedLabel] = elem(AndKind).map[RangedLabel]( - { - - case AndToken(r, _) => RangedLabel(And, r) - case _ => throw UnreachableException - }, - { - case RangedLabel(And, r) => Seq(AndToken(r, false)) - case _ => throw UnreachableException - } - ) - - val or: Syntax[RangedLabel] = elem(OrKind).map[RangedLabel]( - { - case OrToken(r, _) => RangedLabel(Or, r) - case _ => throw UnreachableException - }, - { - case RangedLabel(Or, r) => Seq(OrToken(r, false)) - case _ => throw UnreachableException - } - ) - - // 'and' has higher priority than 'or' - val connectorTermula: Syntax[Termula] = infixOperators[RangedLabel, Termula](subtermula)( - infixFunctionLabels ++ - infixPredicateLabels ++ - ((and has Associativity.Left) :: - (or has Associativity.Left) :: - (toplevelConnector has Associativity.None) :: Nil)* - )( - (l, conn, r) => Termula(conn, Seq(l, r), (l.range._1, r.range._2)), - { - case Termula(pred @ RangedLabel(ConstantAtomicLabel(_, INFIX_ARITY), _), Seq(l, r), _) => (l, pred, r) - case Termula(conn @ RangedLabel(_: ConnectorLabel, _), Seq(l, r), _) => - (l, conn, r) - case Termula(conn @ RangedLabel(_: ConnectorLabel, _), l +: rest, _) if rest.nonEmpty => - val last = rest.last - val leftSide = rest.dropRight(1) - // parser only knows about connector formulas of two arguments, so we unfold the formula of many arguments to - // multiple nested formulas of two arguments - (leftSide.foldLeft(l)((f1, f2) => Termula(conn, Seq(f1, f2), UNKNOWN_RANGE)), conn, last) - } - ) - - val binderLabel: Syntax[RangedLabel] = accept(BinderKind)( - { - case ExistsToken(r) => RangedLabel(Exists, r) - case ExistsOneToken(r) => RangedLabel(ExistsOne, r) - case ForallToken(r) => RangedLabel(Forall, r) - }, - { - case RangedLabel(Exists, r) => Seq(ExistsToken(r)) - case RangedLabel(ExistsOne, r) => Seq(ExistsOneToken(r)) - case RangedLabel(Forall, r) => Seq(ForallToken(r)) - case _ => throw UnreachableException - } - ) - - val boundVariable: Syntax[RangedLabel] = accept(IdKind)( - t => { - val (id, r) = t match { - case ConstantToken(id, r) => (id, r) - case SchematicToken(id, r) => (id, r) - case _ => throw UnreachableException - } - RangedLabel(VariableFormulaLabel(id), r) - }, - { - case RangedLabel(VariableFormulaLabel(id), r) => Seq(SchematicToken(id, r)) - case _ => throw UnreachableException - } - ) - - val binder: Syntax[RangedLabel ~ RangedLabel] = binderLabel ~ boundVariable ~ dot.skip - - lazy val termula: Syntax[Termula] = recursive { - prefixes(binder, connectorTermula)( - { case (label ~ variable, f) => Termula(label, Seq(Termula(variable, Seq(), variable.range), f), (label.range._1, f.range._2)) }, - { case Termula(label @ RangedLabel(_: BinderLabel, _), Seq(Termula(variable @ RangedLabel(_: VariableFormulaLabel, _), Seq(), _), f), _) => - (label ~ variable, f) - } - ) - } - /////////////////////////////////////////////////////////////////// - - val sequent: Syntax[TermulaSequent] = (repsep(termula, semicolon) ~ opt(sequentSymbol.skip ~ repsep(termula, semicolon))).map[TermulaSequent]( - { - case left ~ Some(right) => TermulaSequent(left.toSet, right.toSet) - case right ~ None => TermulaSequent(Set(), right.toSet) - }, - { - case TermulaSequent(Seq(), right) => Seq(right.toSeq ~ None) - case TermulaSequent(left, Seq()) => Seq(left.toSeq ~ Some(Seq(False.toTermula))) - case TermulaSequent(left, right) => Seq(left.toSeq ~ Some(right.toSeq)) - } - ) - - val parser: Parser[TermulaSequent] = Parser(sequent) - val printer: PrettyPrinter[TermulaSequent] = PrettyPrinter(sequent) - - val termulaParser: SequentParser.Parser[Termula] = Parser(termula) - val termulaPrinter: SequentParser.PrettyPrinter[Termula] = PrettyPrinter(termula) - - def parseTermulaSequent(it: Iterator[Token]): ParseResult[TermulaSequent] = parser(it) - def printTermulaSequent(s: TermulaSequent): Option[String] = printer(s).map(SequentLexer.unapply) - - def parseTermula(it: Iterator[Token]): ParseResult[Termula] = termulaParser(it) - def printTermula(t: Termula): Option[String] = termulaPrinter(t).map(SequentLexer.unapply) - } -} diff --git a/lisa-utils/src/main/scala/lisa/utils/parsing/ParsingUtils.scala b/lisa-utils/src/main/scala/lisa/utils/parsing/ParsingUtils.scala deleted file mode 100644 index 858f6abf..00000000 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/ParsingUtils.scala +++ /dev/null @@ -1,41 +0,0 @@ -package lisa.utils.parsing - -import lisa.utils.parsing.ParserException -import scallion.* -import scallion.util.Unfolds.unfoldLeft - -trait ParsingUtils extends Operators { self: Parsers => - case class PrecedenceLevel[Op](operator: Syntax[Op], associativity: lisa.utils.parsing.Associativity) - - implicit class PrecedenceLevelDecorator[Op](operator: Syntax[Op]) { - - /** - * Indicates the associativity of the operator. - */ - infix def has(associativity: lisa.utils.parsing.Associativity): PrecedenceLevel[Op] = PrecedenceLevel(operator, associativity) - } - - def singleInfix[Op, A](elem: Syntax[A], op: Syntax[Op])(function: (A, Op, A) => A, inverse: PartialFunction[A, (A, Op, A)] = PartialFunction.empty): Syntax[A] = - (elem ~ opt(op ~ elem)).map( - { - case first ~ None => first - case first ~ Some(op ~ second) => function(first, op, second) - }, - v => - inverse.lift(v) match { - // see the usage of singleInfix in infixOperators: the inverse function is the same for all ops, so it might - // or might not be correct to unwrap the current element with the inverse function. Hence, provide both options. - case Some(l, op, r) => Seq(l ~ Some(op ~ r), v ~ None) - case None => Seq(v ~ None) - } - ) - - def infixOperators[Op, A](elem: Syntax[A])(levels: PrecedenceLevel[Op]*)(function: (A, Op, A) => A, inverse: PartialFunction[A, (A, Op, A)] = PartialFunction.empty): Syntax[A] = - levels.foldLeft(elem) { case (currSyntax, PrecedenceLevel(op, assoc)) => - assoc match { - case Associativity.Left => infixLeft(currSyntax, op)(function, inverse) - case Associativity.Right => infixRight(currSyntax, op)(function, inverse) - case Associativity.None => singleInfix(currSyntax, op)(function, inverse) - } - } -} diff --git a/lisa-utils/src/main/scala/lisa/utils/parsing/Printer.scala b/lisa-utils/src/main/scala/lisa/utils/parsing/Printer.scala deleted file mode 100644 index 6b33007d..00000000 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/Printer.scala +++ /dev/null @@ -1,185 +0,0 @@ -package lisa.utils.parsing - -import lisa.kernel.fol.FOL.* -import lisa.kernel.proof.SCProof -import lisa.kernel.proof.SCProofCheckerJudgement -import lisa.kernel.proof.SCProofCheckerJudgement.* -import lisa.kernel.proof.SequentCalculus.* - -val FOLPrinter = Printer(FOLParser) - -/** - * A set of methods to pretty-print kernel trees. - */ -class Printer(parser: Parser) { - - private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " - - private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" - - /** - * Returns a string representation of this formula. See also [[prettyTerm]]. - * Example output: - *

-   * ∀x, y. (∀z. (z ∈ x) ⇔ (z ∈ y)) ⇔ (x = y)
-   * 
- * - * @param formula the formula - * @param compact whether spaces should be omitted between tokens - * @return the string representation of this formula - */ - def prettyFormula(formula: Formula, compact: Boolean = false): String = parser.printFormula(formula) - - /** - * Returns a string representation of this term. See also [[prettyFormula]]. - * Example output: - *
-   * f({w, (x, y)}, z)
-   * 
- * - * @param term the term - * @param compact whether spaces should be omitted between tokens - * @return the string representation of this term - */ - def prettyTerm(term: Term, compact: Boolean = false): String = parser.printTerm(term) - - /** - * Returns a string representation of this sequent. - * Example output: - *
-   * ⊢ ∀x, y. (∀z. (z ∈ x) ⇔ (z ∈ y)) ⇔ (x = y)
-   * 
- * - * @param sequent the sequent - * @param compact whether spaces should be omitted between tokens - * @return the string representation of this sequent - */ - def prettySequent(sequent: Sequent, compact: Boolean = false): String = parser.printSequent(sequent) - - /** - * Returns a string representation of this proof. - * - * @param proof the proof - * @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(judgement: SCProofCheckerJudgement, forceDisplaySubproofs: Boolean = false): String = { - val proof = judgement.proof - def computeMaxNumberingLengths(proof: SCProof, level: Int, result: IndexedSeq[Int]): IndexedSeq[Int] = { - val resultWithCurrent = result.updated( - level, - (Seq((proof.steps.size - 1).toString.length, result(level)) ++ (if (proof.imports.nonEmpty) Seq((-proof.imports.size).toString.length) else Seq.empty)).max - ) - proof.steps.collect { case sp: SCSubproof => sp }.foldLeft(resultWithCurrent)((acc, sp) => computeMaxNumberingLengths(sp.sp, level + 1, if (acc.size <= level + 1) acc :+ 0 else acc)) - } - - val maxNumberingLengths = computeMaxNumberingLengths(proof, 0, IndexedSeq(0)) // The maximum value for each number column - val maxLevel = maxNumberingLengths.size - 1 - - def leftPadSpaces(v: Any, n: Int): String = { - val s = String.valueOf(v) - if (s.length < n) (" " * (n - s.length)) + s else s - } - - def rightPadSpaces(v: Any, n: Int): String = { - val s = String.valueOf(v) - if (s.length < n) s + (" " * (n - s.length)) else s - } - - 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 = judgement match { - case SCValidProof(_, _) => false - case SCInvalidProof(proof, 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) = - ( - showErrorForLine, - prefixString, - Seq(stepName, topSteps.mkString(commaSeparator(compact = false))).filter(_.nonEmpty).mkString(" "), - prettySequent(imp) - ) - - Seq(pretty("Import", 0)) - } - printedImports ++ proof.steps.zipWithIndex.flatMap { case (step, i) => - val currentTree = tree :+ i - val showErrorForLine = judgement match { - case SCValidProof(_, _) => false - case SCInvalidProof(proof, 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) = - ( - showErrorForLine, - prefixString, - Seq(stepName, topSteps.mkString(commaSeparator(compact = false))).filter(_.nonEmpty).mkString(" "), - prettySequent(step.bot) - ) - - step match { - case sp @ SCSubproof(_, _) => - pretty("Subproof", sp.premises*) +: prettySCProofRecursive(sp.sp, level + 1, currentTree, (if (i == 0) topMostIndices else IndexedSeq.empty) :+ i) - case other => - val line = other match { - case Restate(_, t1) => pretty("Rewrite", t1) - case RestateTrue(_) => pretty("RewriteTrue") - case Hypothesis(_, _) => pretty("Hypo.") - case Cut(_, t1, t2, _) => pretty("Cut", t1, t2) - case LeftAnd(_, t1, _, _) => pretty("Left ∧", t1) - case LeftNot(_, t1, _) => pretty("Left ¬", t1) - case RightOr(_, t1, _, _) => pretty("Right ∨", t1) - case RightNot(_, t1, _) => pretty("Right ¬", t1) - case LeftExists(_, t1, _, _) => pretty("Left ∃", t1) - case LeftForall(_, t1, _, _, _) => pretty("Left ∀", t1) - case LeftExistsOne(_, t1, _, _) => pretty("Left ∃!", t1) - case LeftOr(_, l, _) => pretty("Left ∨", l*) - case RightExists(_, t1, _, _, _) => pretty("Right ∃", t1) - case RightForall(_, t1, _, _) => pretty("Right ∀", t1) - case RightExistsOne(_, t1, _, _) => pretty("Right ∃!", t1) - case RightAnd(_, l, _) => pretty("Right ∧", l*) - case RightIff(_, t1, t2, _, _) => pretty("Right ⇔", t1, t2) - case RightImplies(_, t1, _, _) => pretty("Right ⇒", t1) - case Weakening(_, t1) => pretty("Weakening", t1) - case LeftImplies(_, t1, t2, _, _) => pretty("Left ⇒", t1, t2) - case LeftIff(_, t1, _, _) => pretty("Left ⇔", t1) - case LeftRefl(_, t1, _) => pretty("L. Refl", t1) - case RightRefl(_, _) => pretty("R. Refl") - case LeftSubstEq(_, t1, _, _) => pretty("L. SubstEq", t1) - case RightSubstEq(_, t1, _, _) => pretty("R. SubstEq", t1) - case LeftSubstIff(_, t1, _, _) => pretty("L. SubstIff", t1) - case RightSubstIff(_, t1, _, _) => pretty("R. SubstIff", t1) - case InstSchema(_, t1, _, _, _) => pretty("Schema Instantiation", t1) - case Sorry(_) => pretty("Sorry") - case SCSubproof(_, _) => throw UnreachableException - } - Seq(line) - } - } - } - - val marker = "->" - - val lines = prettySCProofRecursive(proof, 0, IndexedSeq.empty, IndexedSeq.empty) - val maxStepNameLength = lines.map { case (_, _, stepName, _) => stepName.length }.maxOption.getOrElse(0) - lines - .map { case (isMarked, indices, stepName, sequent) => - val suffix = Seq(indices, rightPadSpaces(stepName, maxStepNameLength), sequent) - val full = if (!judgement.isValid) (if (isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix - full.mkString(" ") - } - .mkString("\n") + (judgement match { - case SCValidProof(_, _) => "" - case SCInvalidProof(proof, path, message) => s"\nProof checker has reported an error at line ${path.mkString(".")}: $message" - }) - } - - def prettySCProof(proof: SCProof): String = prettySCProof(SCValidProof(proof), false) -} diff --git a/lisa-utils/src/main/scala/lisa/utils/parsing/SynonymInfo.scala b/lisa-utils/src/main/scala/lisa/utils/parsing/SynonymInfo.scala deleted file mode 100644 index 2ab84429..00000000 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/SynonymInfo.scala +++ /dev/null @@ -1,34 +0,0 @@ -package lisa.utils.parsing - -import scala.collection.mutable - -case class CanonicalId(internal: String, print: String) - -case class SynonymInfo(private val synonymToCanonical: Map[String, CanonicalId]) { - - /** - * @return the preferred way to output this id, if available, otherwise the id itself. - */ - def getPrintName(id: String): String = synonymToCanonical.get(id).map(_.print).getOrElse(id) - - /** - * @return the synonym of `id` that is used to construct the corresponding `ConstantAtomicLabel` or - * `ConstantFunctionLabel`. If not available, `id` has no known synonyms, so return `id` itself. - */ - def getInternalName(id: String): String = synonymToCanonical.get(id).map(_.internal).getOrElse(id) -} - -object SynonymInfo { - val empty: SynonymInfo = SynonymInfo(Map.empty[String, CanonicalId]) -} - -class SynonymInfoBuilder { - private val mapping: mutable.Map[String, CanonicalId] = mutable.Map() - - def addSynonyms(internal: String, print: String, equivalentLabels: List[String] = Nil): SynonymInfoBuilder = { - (print :: internal :: equivalentLabels).foreach(mapping(_) = CanonicalId(internal, print)) - this - } - - def build: SynonymInfo = SynonymInfo(mapping.toMap) -} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicMain.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicMain.scala new file mode 100644 index 00000000..748f58d5 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicMain.scala @@ -0,0 +1,29 @@ +package lisa.prooflib + +import lisa.utils.Serialization.* + +trait BasicMain { + val library: Library + + private val realOutput: String => Unit = println + + val om: OutputManager = new OutputManager { + def finishOutput(exception: Exception): Nothing = { + log(exception) + main(Array[String]()) + sys.exit + } + val stringWriter: java.io.StringWriter = new java.io.StringWriter() + } + export om.output + + /** + * This specific implementation make sure that what is "shown" in theory files is only printed for the one we run, and not for the whole library. + */ + def main(args: Array[String]): Unit = { + realOutput(om.stringWriter.toString) + } + + given om.type = om + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala new file mode 100644 index 00000000..81204620 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -0,0 +1,1481 @@ +package lisa.prooflib +import lisa.fol.FOL as F +import lisa.prooflib.ProofTacticLib.{_, given} +import lisa.prooflib.* +import lisa.utils.K +import lisa.utils.KernelHelpers.{|- => `K|-`, _} +//import lisa.utils.UserLisaException +import lisa.utils.unification.UnificationUtils + +object BasicStepTactic { + + def unwrapTactic(using lib: Library, proof: lib.Proof)(using tactic: ProofTactic)(judgement: proof.ProofTacticJudgement)(message: String): proof.ProofTacticJudgement = { + judgement match { + case j: proof.ValidProofTactic => proof.ValidProofTactic(j.bot, j.scps, j.imports) + case j: proof.InvalidProofTactic => proof.InvalidProofTactic(s"Internal tactic call failed! $message\n${j.message}") + } + } + + object Hypothesis extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + val intersectedPivot = botK.left.intersect(botK.right) + + if (intersectedPivot.isEmpty) + proof.InvalidProofTactic("A formula for input to Hypothesis could not be inferred from left and right side of the sequent.") + else + proof.ValidProofTactic(bot, Seq(K.Hypothesis(botK, intersectedPivot.head)), Seq()) + } + } + + object Rewrite extends ProofTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + if (!K.isSameSequent(botK, proof.getSequent(premise).underlying)) + proof.InvalidProofTactic("The premise and the conclusion are not trivially equivalent.") + else + proof.ValidProofTactic(bot, Seq(K.Restate(botK, -1)), Seq(premise)) + } + } + + object RewriteTrue extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + if (!K.isSameSequent(botK, () `K|-` K.top)) + proof.InvalidProofTactic("The desired conclusion is not a trivial tautology.") + else + proof.ValidProofTactic(bot, Seq(K.RestateTrue(botK)), Seq()) + } + } + + /** + *
+   *  Γ |- Δ, φ    φ, Σ |- Π
+   * ------------------------
+   *       Γ, Σ |- Δ, Π
+   * 
+ */ + object Cut extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + val botK = bot.underlying + val phiK = phi.underlying + + if (!K.contains(leftSequent.right, phiK)) + proof.InvalidProofTactic("Right-hand side of first premise does not contain φ as claimed.") + else if (!K.contains(rightSequent.left, phiK)) + proof.InvalidProofTactic("Left-hand side of second premise does not contain φ as claimed.") + else if (!K.isSameSet(botK.left + phiK, leftSequent.left ++ rightSequent.left) || (leftSequent.left.contains(phiK) && !botK.left.contains(phiK))) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the union of the left-hand sides of the premises.") + else if (!K.isSameSet(botK.right + phiK, leftSequent.right ++ rightSequent.right) || (rightSequent.right.contains(phiK) && !botK.right.contains(phiK))) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the union of the right-hand sides of the premises.") + else + proof.ValidProofTactic(bot, Seq(K.Cut(botK, -1, -2, phiK)), Seq(prem1, prem2)) + } + + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1) + lazy val rightSequent = proof.getSequent(prem2) + + lazy val cutSet = (((rightSequent --? bot).right |- ())).left + lazy val intersectedCutSet = rightSequent.left intersect leftSequent.right + + if (!cutSet.isEmpty) + if (cutSet.tail.isEmpty) + Cut.withParameters(cutSet.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("Inferred cut pivot is not a singleton set.") + else if (!intersectedCutSet.isEmpty && intersectedCutSet.tail.isEmpty) + // can still find a pivot + Cut.withParameters(intersectedCutSet.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("A consistent cut pivot cannot be inferred from the premises. Possibly a missing or extraneous clause.") + } + } + + // Left rules + /** + *
+   *   Γ, φ |- Δ                Γ, φ, ψ |- Δ
+   * --------------     or     --------------
+   *  Γ, φ∧ψ |- Δ               Γ, φ∧ψ |- Δ
+   * 
+ */ + object LeftAnd extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + lazy val phiAndPsi = phiK /\ psiK + + if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of the conclusion is not the same as the right-hand side of the premise.") + else if ( + !K.isSameSet(botK.left + phiK, premiseSequent.left + phiAndPsi) && + !K.isSameSet(botK.left + psiK, premiseSequent.left + phiAndPsi) && + !K.isSameSet(botK.left + phiK + psiK, premiseSequent.left + phiAndPsi) + ) + proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") + else + proof.ValidProofTactic(bot, Seq(K.LeftAnd(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + pivot.head match { + case F.App(F.App(F.and, phi), psi) => + if (premiseSequent.left.contains(phi)) + LeftAnd.withParameters(phi, psi)(premise)(bot) + else + LeftAnd.withParameters(phi, psi)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a conjunction as pivot from premise and conclusion.") + } + else + // try a rewrite, if it works, go ahead with it, otherwise malformed + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial LeftAnd failed.") + else + proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") + } + } + + /** + *
+   *  Γ, φ |- Δ    Σ, ψ |- Π    ...
+   * --------------------------------
+   *    Γ, Σ, φ∨ψ∨... |- Δ, Π
+   * 
+ */ + object LeftOr extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(disjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) + val botK = bot.underlying + val disjunctsK = disjuncts.map(_.underlying) + lazy val disjunction = K.multior(disjunctsK) + + if (premises.length == 0) + proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (premises.length != disjuncts.length) + proof.InvalidProofTactic(s"Premises and disjuncts expected to be equal in number, but ${premises.length} premises and ${disjuncts.length} disjuncts received.") + else if (!K.isSameSet(botK.right, premiseSequents.map(_.right).reduce(_ union _))) + proof.InvalidProofTactic("Right-hand side of conclusion is not the union of the right-hand sides of the premises.") + else if ( + premiseSequents.zip(disjunctsK).forall((sequent, disjunct) => K.isSubset(sequent.left, botK.left + disjunct)) // \forall i. premise_i.left \subset bot.left + phi_i + && !K.isSubset(botK.left, premiseSequents.map(_.left).reduce(_ union _) + disjunction) // bot.left \subseteq \bigcup premise_i.left + ) + proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftOr(botK, Range(-1, -premises.length - 1, -1), disjunctsK)), premises.toSeq) + } + + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_)) + lazy val pivots = premiseSequents.map(_.left.diff(bot.left)) + + if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (pivots.exists(_.isEmpty)) { + val emptyIndex = pivots.indexWhere(_.isEmpty) + if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) + unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for LeftOr failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the one of the premises.") + } else if (pivots.forall(_.tail.isEmpty)) + LeftOr.withParameters(pivots.map(_.head)*)(premises*)(bot) + else + // some extraneous formulae + proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") + } + } + + /** + *
+   *  Γ |- φ, Δ    Σ, ψ |- Π
+   * ------------------------
+   *    Γ, Σ, φ⇒ψ |- Δ, Π
+   * 
+ */ + object LeftImplies extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + lazy val implication = (phiK ==> psiK) + + if (!K.isSameSet(botK.right + phiK, leftSequent.right union rightSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the union of right-hand sides of premises.") + else if (!K.isSameSet(botK.left + psiK, leftSequent.left union rightSequent.left + implication)) + proof.InvalidProofTactic("Left-hand side of conclusion + ψ is not the union of left-hand sides of premises + φ⇒ψ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftImplies(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) + } + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1) + lazy val rightSequent = proof.getSequent(prem2) + lazy val pivotLeft = leftSequent.right.diff(bot.right) + lazy val pivotRight = rightSequent.left.diff(bot.left) + + if (pivotLeft.isEmpty) + if (F.isSubset(leftSequent.left, bot.left)) + unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial left premise for LeftImplies failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the first premises.") + else if (pivotRight.isEmpty) + if (F.isSubset(rightSequent.right, bot.right)) + unwrapTactic(Weakening(prem2)(bot))("Attempted weakening on trivial right premise for LeftImplies failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the second premises.") + else if (pivotLeft.tail.isEmpty && pivotRight.tail.isEmpty) + LeftImplies.withParameters(pivotLeft.head, pivotRight.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("Could not infer an implication as a pivot from the premises and conclusion, possible extraneous formulae in premises.") + } + } + + /** + *
+   *  Γ, φ⇒ψ |- Δ               Γ, φ⇒ψ, ψ⇒φ |- Δ
+   * --------------    or     --------------------
+   *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
+   * 
+ */ + object LeftIff extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + lazy val implication = phiK <=> psiK + lazy val impLeft = phiK ==> psiK + lazy val impRight = psiK ==> phiK + + if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of premise is not the same as right-hand side of conclusion.") + else if ( + !K.isSameSet(botK.left + impLeft, premiseSequent.left + implication) && + !K.isSameSet(botK.left + impRight, premiseSequent.left + implication) && + !K.isSameSet(botK.left + impLeft + impRight, premiseSequent.left + implication) + ) + proof.InvalidProofTactic("Left-hand side of premise + φ⇔ψ is not the same as left-hand side of conclusion + either φ⇒ψ, ψ⇒φ or both.") + else + proof.ValidProofTactic(bot, Seq(K.LeftIff(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftIff failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else + pivot.head match { + case F.App(F.App(F.implies, phi), psi) => LeftIff.withParameters(phi, psi)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a pivot implication from premise.") + } + } + } + + /** + *
+   *   Γ |- φ, Δ
+   * --------------
+   *   Γ, ¬φ |- Δ
+   * 
+ */ + object LeftNot extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + lazy val negation = !phiK + + if (!K.isSameSet(botK.right + phiK, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise.") + else if (!K.isSameSet(botK.left, premiseSequent.left + negation)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise + ¬φ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftNot(botK, -1, phiK)), Seq(premise)) + } + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftNot failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (!pivot.isEmpty && pivot.tail.isEmpty) + LeftNot.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise.") + + } + } + + /** + *
+   *   Γ, φ[t/x] |- Δ
+   * -------------------
+   *   Γ, ∀x. φ |- Δ
+   *
+   * 
+ */ + object LeftForall extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T], t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val tK = t.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val quantified = K.forall(xK, phiK) + lazy val instantiated = K.substituteVariables(phiK, Map(xK -> tK)) + + if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise") + else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ") + else + proof.ValidProofTactic(bot, Seq(K.LeftForall(botK, -1, phiK, xK, tK)), Seq(premise)) + } + + def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left // .diff(botK.left) + + if (!pivot.isEmpty) + if (pivot.tail.isEmpty) + pivot.head match { + case F.forall(x, phi) => LeftForall.withParameters(phi, x, t)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a universally quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") + else if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.left.find(f => + f match { + case g @ F.forall(v, e) => F.isSame(e.substitute(v := t), in) + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.forall(x, phi)) => LeftForall.withParameters(phi, x, t)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") + } + } else proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val prepivot = bot.left.diff(premiseSequent.left) + lazy val pivot = if (prepivot.isEmpty) bot.left else prepivot + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = pivot.find(f => + f match { + case g @ F.forall(x, phi) => ??? // TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.forall(x, phi)) => + LeftForall.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).get._2.getOrElse(x, x)*/)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") + } + } else proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") + } + } + + /** + *
+   *    Γ, φ |- Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ, ∃x φ|- Δ
+   *
+   * 
+ */ + object LeftExists extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val quantified = K.exists(xK, phiK) + + if ((botK.left union botK.right).exists(_.freeVariables.contains(xK))) + proof.InvalidProofTactic("The variable x must not be free in the resulting sequent.") + else if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise") + else if (!K.isSameSet(botK.left + phiK, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ") + else + proof.ValidProofTactic(bot, Seq(K.LeftExists(botK, -1, phiK, xK)), Seq(premise)) + } + + var debug = false + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExists failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.left.find(f => + f match { + case F.exists(_, g) => F.isSame(g, in) + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.exists(x, phi)) => LeftExists.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existensially quantified pivot from premise and conclusion.") + } + } else proof.InvalidProofTactic("Ambigous application of LeftExists, multiple pivots corresponding to the unquantified formula found.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.exists(x, phi) => LeftExists.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Ambigous application of LeftExists, multiple pivots corresponding to the quantified formula found.") + } + } + + /* + /** + *
+   *  Γ, ∃y.∀x. (x=y) ⇔ φ |-  Δ
+   * ---------------------------- if y is not free in φ
+   *      Γ, ∃!x. φ |- Δ
+   * 
+ */ + object LeftExistsOne extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val y = K.Variable(lisa.utils.KernelHelpers.freshId(phiK.freeVariables.map(_.id), x.id), K.Term) + lazy val instantiated = K.exists(y, K.forall(xK, (xK === y) <=> phiK )) + lazy val quantified = K.ExistsOne(xK, phiK) + + if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise.") + else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as left-hand side of premise + ∃!x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftExistsOne(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExistsOne failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + instantiatedPivot.head match { + // ∃_. ∀x. _ ⇔ φ == extract ==> x, phi + case F.exists(_, F.forall(x, F.AppliedConnector(F.Iff, Seq(_, phi)))) => LeftExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + } else + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.BinderFormula(F.ExistsOne, x, phi) => LeftExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") + } + } + + */ + + // Right rules + /** + *
+   *  Γ |- φ, Δ    Σ |- ψ, Π     ...
+   * ------------------------------------
+   *    Γ, Σ |- φ∧ψ∧..., Π, Δ
+   * 
+ */ + object RightAnd extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(conjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) + lazy val botK = bot.underlying + lazy val conjunctsK = conjuncts.map(_.underlying) + lazy val conjunction = K.multiand(conjunctsK) + + if (premises.length == 0) + proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (premises.length != conjuncts.length) + proof.InvalidProofTactic(s"Premises and conjuncts expected to be equal in number, but ${premises.length} premises and ${conjuncts.length} conjuncts received.") + else if (!K.isSameSet(botK.left, premiseSequents.map(_.left).reduce(_ union _))) + proof.InvalidProofTactic("Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + else if ( + premiseSequents.zip(conjunctsK).forall((sequent, conjunct) => K.isSubset(sequent.right, botK.right + conjunct)) // \forall i. premise_i.right \subset bot.right + phi_i + && !K.isSubset(botK.right, premiseSequents.map(_.right).reduce(_ union _) + conjunction) // bot.right \subseteq \bigcup premise_i.right + ) + proof.InvalidProofTactic("Right-hand side of conclusion + conjuncts is not the same as the union of the right-hand sides of the premises + φ∧ψ....") + else + proof.ValidProofTactic(bot, Seq(K.RightAnd(botK, Range(-1, -premises.length - 1, -1), conjunctsK)), premises) + } + + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_)) + lazy val pivots = premiseSequents.map(_.right.diff(bot.right)) + + if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (pivots.exists(_.isEmpty)) { + val emptyIndex = pivots.indexWhere(_.isEmpty) + if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) + unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for RightAnd failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the one of the premises.") + } else if (pivots.forall(_.tail.isEmpty)) + RightAnd.withParameters(pivots.map(_.head)*)(premises*)(bot) + else + // some extraneous formulae + proof.InvalidProofTactic("Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises +φ∧ψ.") + } + } + + /** + *
+   *   Γ |- φ, Δ               Γ |- φ, ψ, Δ
+   * --------------    or    ---------------
+   *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
+   * 
+ */ + object RightOr extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + lazy val phiAndPsi = phiK \/ psiK + + if (!K.isSameSet(botK.left, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of the premise is not the same as the left-hand side of the conclusion.") + else if ( + !K.isSameSet(botK.right + phiK, premiseSequent.right + phiAndPsi) && + !K.isSameSet(botK.right + psiK, premiseSequent.right + phiAndPsi) && + !K.isSameSet(botK.right + phiK + psiK, premiseSequent.right + phiAndPsi) + ) + proof.InvalidProofTactic("Right-hand side of premise + φ∧ψ is not the same as right-hand side of conclusion + either φ, ψ or both.") + else + proof.ValidProofTactic(bot, Seq(K.RightOr(botK, -1, phiK, psiK)), Seq(premise)) + } + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + pivot.head match { + case F.App(F.App(F.or, phi), psi) => + if (premiseSequent.left.contains(phi)) + RightOr.withParameters(phi, psi)(premise)(bot) + else + RightOr.withParameters(psi, phi)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a disjunction as pivot from premise and conclusion.") + } + else + // try a rewrite, if it works, go ahead with it, otherwise malformed + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightOr failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ∧ψ is not the same as right-hand side of premise + either φ, ψ or both.") + } + } + + /** + *
+   *  Γ, φ |- ψ, Δ
+   * --------------
+   *  Γ |- φ⇒ψ, Δ
+   * 
+ */ + object RightImplies extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + lazy val implication = phiK ==> psiK + + if (!K.isSameSet(botK.left + phiK, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") + else if (!K.isSameSet(botK.right + psiK, premiseSequent.right + implication)) + proof.InvalidProofTactic("Right-hand side of conclusion + ψ is not the same as right-hand side of premise + φ⇒ψ.") + else + proof.ValidProofTactic(bot, Seq(K.RightImplies(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val leftPivot = premiseSequent.left.diff(bot.left) + lazy val rightPivot = premiseSequent.right.diff(bot.right) + + if ( + !leftPivot.isEmpty && leftPivot.tail.isEmpty && + !rightPivot.isEmpty && rightPivot.tail.isEmpty + ) + RightImplies.withParameters(leftPivot.head, rightPivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Could not infer an implication as pivot from premise and conclusion.") + } + } + + /** + *
+   *  Γ |- φ⇒ψ, Δ    Σ |- ψ⇒φ, Π
+   * ----------------------------
+   *      Γ, Σ |- φ⇔ψ, Π, Δ
+   * 
+ */ + object RightIff extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + lazy val implication = phiK <=> psiK + lazy val impLeft = phiK ==> psiK + lazy val impRight = psiK ==> phiK + + if (!K.isSameSet(botK.left, leftSequent.left union rightSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + else if (!K.isSubset(leftSequent.right, botK.right + impLeft)) + proof.InvalidProofTactic( + "Conclusion is missing the following formulas from the left premise: " + (leftSequent.right -- botK.right).map(f => s"[${f.repr}]").reduce(_ ++ ", " ++ _) + ) + else if (!K.isSubset(rightSequent.right, botK.right + impRight)) + proof.InvalidProofTactic( + "Conclusion is missing the following formulas from the right premise: " + (rightSequent.right -- botK.right).map(f => s"[${f.repr}]").reduce(_ ++ ", " ++ _) + ) + else if (!K.isSubset(botK.right, leftSequent.right union rightSequent.right + implication)) + proof.InvalidProofTactic( + "Conclusion has extraneous formulas apart from premises and implication: " ++ (botK.right + .removedAll(leftSequent.right union rightSequent.right + implication)) + .map(f => s"[${f.repr}]") + .reduce(_ ++ ", " ++ _) + ) + else + proof.ValidProofTactic(bot, Seq(K.RightIff(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) + } + + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(prem1) + lazy val pivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial premise for RightIff failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.App(F.App(F.implies, phi), psi) => RightIff.withParameters(phi, psi)(prem1, prem2)(bot) + case _ => proof.InvalidProofTactic("Could not infer an implication as pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ⇒ψ + ψ⇒φ is not the same as the union of the right-hand sides of the premises φ⇔ψ.") + } + } + + /** + *
+   *  Γ, φ |- Δ
+   * --------------
+   *   Γ |- ¬φ, Δ
+   * 
+ */ + object RightNot extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val negation = !phiK + + if (!K.isSameSet(botK.left + phiK, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") + else if (!K.isSameSet(botK.right, premiseSequent.right + negation)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise + ¬φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightNot(botK, -1, phiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightIff failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (pivot.tail.isEmpty) + RightNot.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") + + } + } + + /** + *
+   *    Γ |- φ, Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ |- ∀x. φ, Δ
+   * 
+ */ + object RightForall extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val quantified = K.forall(xK, phiK) + + if ((botK.left union botK.right).exists(_.freeVariables.contains(xK))) + proof.InvalidProofTactic("The variable x is free in the resulting sequent.") + else if (!K.isSameSet(botK.left, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise.") + else if (!K.isSameSet(botK.right + phiK, premiseSequent.right + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∀x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightForall(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightForall failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from the premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.right.find(f => + f match { + case F.forall(_, g) => F.isSame(g, in) + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.forall(x, phi)) => RightForall.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a universally quantified pivot from premise and conclusion.") + } + } else proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∃x. φ.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.forall(x, phi) => RightForall.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a universally quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∃x. φ.") + } + } + + /** + *
+   *   Γ |- φ[t/x], Δ
+   * -------------------
+   *  Γ |- ∃x. φ, Δ
+   *
+   * (ln-x stands for locally nameless x)
+   * 
+ */ + object RightExists extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T], t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val tK = t.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val quantified = K.exists(xK, phiK) + lazy val instantiated = K.substituteVariables(phiK, Map(xK -> tK)) + + if (!K.isSameSet(botK.left, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise") + else if (!K.isSameSet(botK.right + instantiated, premiseSequent.right + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ") + else + proof.ValidProofTactic(bot, Seq(K.RightExists(botK, -1, phiK, xK, tK)), Seq(premise)) + } + + def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (!pivot.isEmpty) + if (pivot.tail.isEmpty) + pivot.head match { + case F.exists(x, phi) => RightExists.withParameters(phi, x, t)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") + else if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightExists failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.right.find(f => + f match { + case g @ F.exists(v, e) => F.isSame(e.substitute(v := t), in) + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.exists(x, phi)) => RightExists.withParameters(phi, x, t)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") + } + } else proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val prepivot = bot.right.diff(premiseSequent.right) + lazy val pivot = if (prepivot.isEmpty) bot.right else prepivot + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightForall failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + + val quantifiedPhi: Option[F.Formula] = pivot.find(f => + f match { + case g @ F.exists(x, phi) => + ??? // TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.exists(x, phi)) => + RightExists.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).get._2.getOrElse(x, x)*/)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") + } + } else proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") + } + } + + /* + /** + *
+   *  Γ |- ∃y.∀x. (x=y) ⇔ φ, Δ
+   * ---------------------------- if y is not free in φ
+   *      Γ|- ∃!x. φ,  Δ
+   * 
+ */ + object RightExistsOne extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val y = K.Variable(lisa.utils.KernelHelpers.freshId(phiK.freeVariables.map(_.id), x.id)) + lazy val instantiated = K.BinderFormula( + K.Exists, + y, + K.BinderFormula( + K.Forall, + xK, + K.ConnectorFormula(K.Iff, List(K.AtomicFormula(K.equality, List(K.VariableTerm(xK), K.VariableTerm(y))), phiK)) + ) + ) + lazy val quantified = K.BinderFormula(K.ExistsOne, xK, phiK) + + if (!K.isSameSet(botK.left, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise.") + else if (!K.isSameSet(botK.right + instantiated, premiseSequent.right + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as right-hand side of premise + ∃!x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightExistsOne(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightExistsOne failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + instantiatedPivot.head match { + // ∃_. ∀x. _ ⇔ φ == extract ==> x, phi + case F.exists(_, F.forall(x, F.AppliedConnector(F.Iff, Seq(_, phi)))) => + RightExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + } else + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∃x. φ.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.BinderFormula(F.ExistsOne, x, phi) => RightExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∃x. φ.") + } + } + + */ + + object RightBeta extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof) + (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val lambdaK = lambda.underlying + lazy val tK = t.underlying + lazy val xK = x.underlying + lazy val botK = bot.underlying + lazy val y = lambda.v.underlying + lazy val e = lambda.body.underlying + if (phi.sort != K.Formula) + return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) + else if (e.sort != x.sort) + return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) + else if (K.isSameSet(botK.left, premiseSequent.left)) { + val redex = lambdaK(tK) + val normalized = K.substituteVariables(e, Map(y -> tK)) + val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) + val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) + if (K.isSameSet(botK.right + phi_redex, premiseSequent.right + phi_normalized) || K.isSameSet(botK.right + phi_normalized, premiseSequent.right + phi_redex)) + return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) + else + return proof.InvalidProofTactic("Right-hand side of the conclusion + φ[λy.e]t/x must be the same as right-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else + return proof.InvalidProofTactic("Left-hand side of the conclusion must be the same as the left-hand side of the premise") + } + } + + object LeftBeta extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof) + (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val lambdaK = lambda.underlying + lazy val tK = t.underlying + lazy val xK = x.underlying + lazy val botK = bot.underlying + lazy val y = lambda.v.underlying + lazy val e = lambda.body.underlying + if (phi.sort != K.Formula) + return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) + else if (e.sort != x.sort) + return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) + else if (K.isSameSet(botK.right, premiseSequent.right)) { + val redex = lambdaK(tK) + val normalized = K.substituteVariables(e, Map(y -> tK)) + val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) + val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) + if (K.isSameSet(botK.left + phi_redex, premiseSequent.left + phi_normalized) || K.isSameSet(botK.left + phi_normalized, premiseSequent.left + phi_redex)) + return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) + else + return proof.InvalidProofTactic("Left-hand side of the conclusion + φ[λy.e]t/x must be the same as left-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else + return proof.InvalidProofTactic("Right-hand side of the conclusion must be the same as the right-hand side of the premise") + } + } + + // Structural rules + /** + *
+   *     Γ |- Δ
+   * --------------
+   *   Γ, Σ |- Δ, Π
+   * 
+ */ + object Weakening extends ProofTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + + if (!F.isImplyingSequent(premiseSequent, bot)) + proof.InvalidProofTactic("Conclusion cannot be trivially derived from premise.") + else + proof.ValidProofTactic(bot, Seq(K.Weakening(bot.underlying, -1)), Seq(premise)) + } + } + + // Equality Rules + /** + *
+   *  Γ, s=s |- Δ
+   * --------------
+   *     Γ |- Δ
+   * 
+ */ + object LeftRefl extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val faK = fa.underlying + lazy val botK = bot.underlying + + if (!K.isSameSet(botK.left + faK, premiseSequent.left) || !premiseSequent.left.exists(_ == faK) || botK.left.exists(_ == faK)) + proof.InvalidProofTactic("Left-hand sides of the conclusion + φ is not the same as left-hand side of the premise.") + else if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of the premise is not the same as the right-hand side of the conclusion.") + else + faK match { + case K.Application(K.Application(K.equality, left), right) => + if (K.isSame(left, right)) + proof.ValidProofTactic(bot, Seq(K.LeftRefl(botK, -1, faK)), Seq(premise)) + else + proof.InvalidProofTactic("φ is not an instance of reflexivity.") + case _ => proof.InvalidProofTactic("φ is not an equality.") + } + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + LeftRefl.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Could not infer an equality as pivot from premise and conclusion.") + } + } + + /** + *
+   *
+   * --------------
+   *     |- s=s
+   * 
+ */ + object RightRefl extends ProofTactic with ProofSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val faK = fa.underlying + lazy val botK = bot.underlying + if (!botK.right.exists(_ == faK)) + proof.InvalidProofTactic("Right-hand side of conclusion does not contain φ.") + else + faK match { + case K.Application(K.Application(K.equality, left), right) => + if (K.isSame(left, right)) + proof.ValidProofTactic(bot, Seq(K.RightRefl(botK, faK)), Seq()) + else + proof.InvalidProofTactic("φ is not an instance of reflexivity.") + case _ => proof.InvalidProofTactic("φ is not an equality.") + } + } + + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + if (bot.right.isEmpty) proof.InvalidProofTactic("Right-hand side of conclusion does not contain an instance of reflexivity.") + else { + // go through conclusion to see if you can find an reflexive formula + val pivot: Option[F.Formula] = bot.right.find(f => + val Eq = F.equality // (F.equality: (F.|->[F.**[F.Term, 2], F.Formula])) + f match { + case F.App(F.App(e, l), r) => + (F.equality) == (e) && l == r // termequality + case _ => false + } + ) + + pivot match { + case Some(phi) => RightRefl.withParameters(phi)(bot) + case _ => proof.InvalidProofTactic("Could not infer an equality as pivot from conclusion.") + } + + } + + } + } + + /** + *
+   *   Γ, φ(s) |- Δ     Σ |- s=t, Π     
+   * --------------------------------
+   *        Γ, Σ φ(t) |- Δ, Π
+   * 
+ */ + object LeftSubstEq extends ProofTactic { + + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + } + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.equality(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + + + } + } + + /** + *
+   *  Γ |- φ(s), Δ     Σ |- s=t, Π
+   * ---------------------------------
+   *         Γ, Σ |- φ(t), Δ, Π
+   * 
+ */ + object RightSubstEq extends ProofTactic { + def withParametersSimple[T1](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + } + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Formula) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.equality(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.RightSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + } + + } + + /** + *
+   *           Γ, φ(a1,...an) |- Δ
+   * ----------------------------------------
+   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
+   * 
+ */ + object LeftSubstIff extends ProofTactic { + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + /*{ + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.Iff(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + */ + + } + + /** + *
+   *           Γ |- φ(a1,...an), Δ
+   * ----------------------------------------
+   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
+   * 
+ */ + object RightSubstIff extends ProofTactic { + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + /* + def withParametersSimple(using lib: Library, proof: lib.Proof)( + equals: List[(F.Formula, F.Formula)], + lambdaPhi: F.LambdaExpression[F.Formula, F.Formula, ?] + )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + withParameters(equals.map { case (a, b) => (F.lambda(Seq(), a), F.lambda(Seq(), b)) }, (lambdaPhi.bounds.asInstanceOf[Seq[F.SchematicAtomicLabel[?]]], lambdaPhi.body))(premise)(bot) + } + + def withParameters(using lib: Library, proof: lib.Proof)( + equals: List[(F.LambdaExpression[F.Term, F.Formula, ?], F.LambdaExpression[F.Term, F.Formula, ?])], + lambdaPhi: (Seq[F.SchematicAtomicLabel[?]], F.Formula) + )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val botK = bot.underlying + lazy val equalsK = equals.map(p => (p._1.underlyingLTF, p._2.underlyingLTF)) + lazy val lambdaPhiK = (lambdaPhi._1.map(_.underlying), lambdaPhi._2.underlying) + + val (psi_s, tau_s) = equalsK.unzip + val (phi_args, phi_body) = lambdaPhiK + if (phi_args.size != psi_s.size) + return proof.InvalidProofTactic("The number of arguments of φ must be the same as the number of equalities.") + else if (equalsK.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) + return proof.InvalidProofTactic("The arities of symbols in φ must be the same as the arities of equalities.") + + val phi_psi = K.instantiatePredicateSchemas(phi_body, (phi_args zip psi_s).toMap) + val phi_tau = K.instantiatePredicateSchemas(phi_body, (phi_args zip tau_s).toMap) + val psiIffTau = equalsK map { case (s, t) => + assert(s.vars.size == t.vars.size) + val base = K.ConnectorFormula(K.Iff, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(K.VariableTerm)))) + (s.vars).foldLeft(base: K.Formula) { case (acc, s_arg) => K.forall(s_arg, acc) } + } + + if (!K.isSameSet(botK.left, premiseSequent.left ++ psiIffTau)) { + proof.InvalidProofTactic("Left-hand side of the conclusion is not the same as the left-hand side of the premise + (ψ ⇔ τ)_.") + } else if ( + !K.isSameSet(botK.right + phi_psi, premiseSequent.right + phi_tau) && + !K.isSameSet(botK.right + phi_tau, premiseSequent.right + phi_psi) + ) + proof.InvalidProofTactic("Right-hand side of the conclusion + φ(ψ_) is not the same as right-hand side of the premise + φ(τ_) (or with ψ_ and τ_ swapped).") + else + proof.ValidProofTactic(bot, Seq(K.RightSubstIff(botK, -1, equalsK, lambdaPhiK)), Seq(premise)) + } + */ + } + + /** + *
+   *           Γ |- Δ
+   * --------------------------
+   *  Γ[r(a)/?f] |- Δ[r(a)/?f]
+   * 
+ */ + object InstSchema extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof + )(map: Map[F.Variable[?], F.Expr[?]])(premise: proof.Fact): proof.ProofTacticJudgement = { + val premiseSequent = proof.getSequent(premise).underlying + val mapK = map.map((v, e) => (v.underlying, e.underlying)) + val botK = K.substituteVariablesInSequent(premiseSequent, mapK) + val res = proof.getSequent(premise).substituteUnsafe(map) + proof.ValidProofTactic(res, Seq(K.InstSchema(botK, -1, mapK)), Seq(premise)) + } + } + object Subproof extends ProofTactic { + def apply(using proof: Library#Proof)(statement: Option[F.Sequent])(iProof: proof.InnerProof) = { + val bot: Option[F.Sequent] = statement + val botK: Option[K.Sequent] = statement map (_.underlying) + if (iProof.length == 0) throw (new UnimplementedProof(proof.owningTheorem)) + val scproof: K.SCProof = iProof.toSCProof + val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) + val judgement: proof.ProofTacticJudgement = { + if (botK.isEmpty) + proof.ValidProofTactic(iProof.mostRecentStep.bot, scproof.steps, premises) + else if (!K.isSameSequent(botK.get, scproof.conclusion)) + proof.InvalidProofTactic( + s"The subproof does not prove the desired conclusion.\n\tExpected: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}" + ) + else + proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) + } + judgement + } + } + class SUBPROOF(using val proof: Library#Proof)(statement: Option[F.Sequent])(val iProof: proof.InnerProof) extends ProofTactic { + val bot: Option[F.Sequent] = statement + val botK: Option[K.Sequent] = statement map (_.underlying) + if (iProof.length == 0) + throw (new UnimplementedProof(proof.owningTheorem)) + val scproof: K.SCProof = iProof.toSCProof + + val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) + def judgement: proof.ProofTacticJudgement = { + if (botK.isEmpty) + proof.ValidProofTactic(iProof.mostRecentStep.bot, scproof.steps, premises) + else if (!K.isSameSequent(botK.get, scproof.conclusion)) + proof.InvalidProofTactic(s"The subproof does not prove the desired conclusion.\n\tExpected: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}") + else + proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) + } + } + + // TODO make specific support for subproofs written inside tactics.kkkkkkk + + inline def TacticSubproof(using proof: Library#Proof)(inline computeProof: proof.InnerProof ?=> Unit): proof.ProofTacticJudgement = + val iProof: proof.InnerProof = new proof.InnerProof(None) + computeProof(using iProof) + SUBPROOF(using proof)(None)(iProof).judgement.asInstanceOf[proof.ProofTacticJudgement] + + object Sorry extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + proof.ValidProofTactic(bot, Seq(K.Sorry(bot.underlying)), Seq()) + } + } + + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/Exports.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/Exports.scala new file mode 100644 index 00000000..836d1cac --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/Exports.scala @@ -0,0 +1,6 @@ +package lisa.prooflib + +object Exports { + export BasicStepTactic.* + export lisa.prooflib.SimpleDeducedSteps.* +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala new file mode 100644 index 00000000..0a677f30 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala @@ -0,0 +1,92 @@ +package lisa.prooflib + +import lisa.kernel.proof.RunningTheory +import lisa.kernel.proof.SCProofChecker +import lisa.kernel.proof.SCProofCheckerJudgement +import lisa.kernel.proof.SequentCalculus +//import lisa.prooflib.ProofTacticLib.ProofTactic +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.{_, given} + +import scala.collection.mutable.Stack as stack + +/** + * A class abstracting a [[lisa.kernel.proof.RunningTheory]] providing utility functions and a convenient syntax + * to write and use Theorems and Definitions. + * @param theory The inner RunningTheory + */ +abstract class Library extends lisa.prooflib.WithTheorems with lisa.prooflib.ProofsHelpers { + + val theory: RunningTheory + given library: this.type = this + given RunningTheory = theory + + export lisa.kernel.proof.SCProof + + val K = lisa.utils.K + val SC: SequentCalculus.type = K.SC + private[prooflib] val F = lisa.fol.FOL + import F.{given} + + var last: Option[JUSTIFICATION] = None + + // Options for files + private[prooflib] var _withCache: Boolean = false + def withCache(using file: sourcecode.File, om: OutputManager)(): Unit = + if last.nonEmpty then om.output(OutputManager.WARNING("Warning: withCache option should be used before the first definition or theorem.")) + else _withCache = true + + private[prooflib] var _draft: Option[sourcecode.File] = None + def draft(using file: sourcecode.File, om: OutputManager)(): Unit = + if last.nonEmpty then om.output(OutputManager.WARNING("Warning: draft option should be used before the first definition or theorem.")) + else _draft = Some(file) + def isDraft = _draft.nonEmpty + + val knownDefs: scala.collection.mutable.Map[F.Constant[?], Option[JUSTIFICATION]] = scala.collection.mutable.Map.empty + val shortDefs: scala.collection.mutable.Map[F.Constant[?], Option[JUSTIFICATION]] = scala.collection.mutable.Map.empty + + def addSymbol(s: F.Constant[?]): Unit = + theory.addSymbol(s.underlying) + knownDefs.update(s, None) + + def getDefinition(label: F.Constant[?]): Option[JUSTIFICATION] = knownDefs.get(label) match { + case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) + case Some(value) => value + } + def getShortDefinition(label: F.Constant[?]): Option[JUSTIFICATION] = shortDefs.get(label) match { + case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) + case Some(value) => value + } + + /** + * An alias to create a Theorem + */ + def makeTheorem(name: String, statement: K.Sequent, proof: K.SCProof, justifications: Seq[theory.Justification]): K.Judgement[theory.Theorem] = + theory.theorem(name, statement, proof, justifications) + + // DEFINITION Syntax + + /** + * Allows to create a definition by shortcut of a predicate symbol: + */ + def makeSimpleDefinition(symbol: String, expression: K.Expression): K.Judgement[theory.Definition] = + theory.definition(symbol, expression) + + + /** + * Prints a short representation of the given theorem or definition + */ + def show(using om: OutputManager)(thm: JUSTIFICATION) = { + if (thm.withSorry) om.output(thm.repr, Console.YELLOW) + else om.output(thm.repr, Console.GREEN) + } + + /** + * Prints a short representation of the last theorem or definition introduced + */ + def show(using om: OutputManager): Unit = last match { + case Some(value) => show(value) + case None => throw new NoSuchElementException("There is nothing to show: No theorem or definition has been proved yet.") + } + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala new file mode 100644 index 00000000..bc7442ef --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala @@ -0,0 +1,52 @@ +package lisa.prooflib + +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.{_, given} + +import java.io.PrintWriter +import java.io.StringWriter + +abstract class OutputManager { + + given OutputManager = this + + def output(s: String): Unit = stringWriter.write(s + "\n") + def output(s: String, color: String): Unit = stringWriter.write(Console.RESET + color + s + "\n" + Console.RESET) + val stringWriter: StringWriter + + def finishOutput(exception: Exception): Nothing + + def lisaThrow(le: LisaException): Nothing = + le match { + case ule: UserLisaException => + ule.fixTrace() + output(ule.showError) + finishOutput(ule) + + case e: LisaException.InvalidKernelJustificationComputation => + e.proof match { + case Some(value) => output(lisa.prooflib.ProofPrinter.prettyProof(value)) + case None => () + } + output(e.underlying.repr) + finishOutput(e) + + } + + def log(e: Exception): Unit = { + stringWriter.write("\n[" + Console.RED + "Error" + Console.RESET + "] ") + e.printStackTrace(PrintWriter(stringWriter)) + output(Console.RESET) + } + +} +object OutputManager { + def RED(s: String): String = Console.RED + s + Console.RESET + def GREEN(s: String): String = Console.GREEN + s + Console.RESET + def BLUE(s: String): String = Console.BLUE + s + Console.RESET + def YELLOW(s: String): String = Console.YELLOW + s + Console.RESET + def CYAN(s: String): String = Console.CYAN + s + Console.RESET + def MAGENTA(s: String): String = Console.MAGENTA + s + Console.RESET + + def WARNING(s: String): String = Console.YELLOW + "⚠ " + s + Console.RESET +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala new file mode 100644 index 00000000..09cc6d8d --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala @@ -0,0 +1,128 @@ +package lisa.prooflib + +import lisa.kernel.proof.SCProofCheckerJudgement +import lisa.prooflib.BasicStepTactic.SUBPROOF +import lisa.prooflib.Library +import lisa.prooflib.* +import lisa.utils.* + +object ProofPrinter { + + private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " + + private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" + + private def prettyProofLines(printedProof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): Seq[String] = { + def computeMaxNumberingLengths(proof: Library#Proof, level: Int, result: IndexedSeq[Int]): IndexedSeq[Int] = { + val resultWithCurrent = result.updated( + level, + (Seq((proof.getSteps.size - 1).toString.length, result(level)) ++ (if (proof.getImports.nonEmpty) Seq((-proof.getImports.size).toString.length) else Seq.empty)).max + ) + proof.getSteps + .collect { case ps: proof.ProofStep if ps.tactic.isInstanceOf[SUBPROOF] => ps.tactic.asInstanceOf[SUBPROOF] } + .foldLeft(resultWithCurrent)((acc, sp) => computeMaxNumberingLengths(sp.iProof, level + 1, if (acc.size <= level + 1) acc :+ 0 else acc)) + } + + val maxNumberingLengths = computeMaxNumberingLengths(printedProof, 0, IndexedSeq(0)) // The maximum value for each number column + val maxLevel = maxNumberingLengths.size - 1 + + def leftPadSpaces(v: Any, n: Int): String = { + val s = String.valueOf(v) + if (s.length < n) (" " * (n - s.length)) + s else s + } + + def rightPadSpaces(v: Any, n: Int): String = { + val s = String.valueOf(v) + if (s.length < n) s + (" " * (n - s.length)) else s + } + + def prettyProofRecursive(printedProof: Library#Proof, level: Int, tree: IndexedSeq[Int], topMostIndices: IndexedSeq[Int]): Seq[(Boolean, String, String, String)] = { + val imports = printedProof.getImports + val steps = printedProof.getSteps + val printedImports = imports.zipWithIndex.reverse.flatMap { case (imp, i) => + val currentTree = tree :+ (-i - 1) + + val showErrorForLine: Boolean = error match { + case None => false + case Some((position, message)) => 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) = + ( + showErrorForLine, + prefixString, + Seq(stepName, topSteps.mkString(commaSeparator(compact = false))).filter(_.nonEmpty).mkString(" "), + imp._2.toString() + ) + + Seq(pretty("Import", 0)) + } + printedImports ++ steps.zipWithIndex.flatMap { case (step, i) => + val currentTree = tree :+ i + val showErrorForLine: Boolean = error match { + case None => false + case Some((position, message)) => 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) = + ( + showErrorForLine, + prefixString, + Seq(stepName, topSteps.mkString(commaSeparator(compact = false))).filter(_.nonEmpty).mkString(" "), + step.bot.toString() + ) + + step.tactic match { + case sp: SUBPROOF => + val topSteps: Seq[Int] = sp.premises.map((f: sp.proof.Fact) => sp.proof.sequentAndIntOfFact(f)._2) + pretty("Subproof", topSteps*) +: prettyProofRecursive(sp.iProof, level + 1, currentTree, (if (i == 0) topMostIndices else IndexedSeq.empty) :+ i) + case other => + val line = pretty(other.name) + Seq(line) + } + } + } + + val marker = "->" + + val lines = prettyProofRecursive(printedProof, 0, IndexedSeq.empty, IndexedSeq.empty) + val maxStepNameLength = lines.map { case (_, _, stepName, _) => stepName.length }.maxOption.getOrElse(0) + lines + .map { case (isMarked, indices, stepName, sequent) => + val suffix = Seq(indices, rightPadSpaces(stepName, maxStepNameLength), sequent) + val full = if (error.isDefined) (if (isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix + full.mkString(" ") + } + ++ (error match { + case None => Nil + case Some((path, message)) => List(s"\nProof checker has reported an error at line ${path.mkString(".")}: $message") + }) + } + + def prettyFullProofLines(printedProof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): Seq[String] = { + printedProof match { + case proof: Library#Proof#InnerProof => + prettyFullProofLines(proof.parent, None) ++ prettyProofLines(proof, error).map(" " + _) + case proof: Library#BaseProof => + prettyProofLines(proof, None) + } + } + + def prettyProof(proof: Library#Proof): String = prettyFullProofLines(proof, None).mkString("\n") + def prettyProof(proof: Library#Proof, indent: Int): String = (" " * indent) + prettyFullProofLines(proof, None).mkString("\n" + (" " * indent)) + + def prettyProof(proof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): String = prettyFullProofLines(proof, error).mkString("\n") + def prettyProof(proof: Library#Proof, indent: Int, error: Option[(IndexedSeq[Int], String)]): String = prettyFullProofLines(proof, None).mkString("\n" + " " * indent) + + def prettySimpleProof(proof: Library#Proof): String = prettyProofLines(proof, None).mkString("\n") + def prettySimpleProof(proof: Library#Proof, indent: Int): String = (" " * indent) + prettyProofLines(proof, None).mkString("\n" + (" " * indent)) + + def prettySimpleProof(proof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): String = prettyProofLines(proof, error).mkString("\n") + def prettySimpleProof(proof: Library#Proof, indent: Int, error: Option[(IndexedSeq[Int], String)]): String = prettyProofLines(proof, None).mkString("\n" + " " * indent) + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala new file mode 100644 index 00000000..c398c880 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala @@ -0,0 +1,67 @@ +package lisa.prooflib + +import lisa.fol.FOL as F +import lisa.prooflib.* +import lisa.utils.K +import lisa.utils.UserLisaException +import lisa.prooflib.ProofPrinter + +object ProofTacticLib { + type Arity = Int & Singleton + + + /** + * A ProofTactic is an object that relies on a step of premises and which can be translated into pure Sequent Calculus. + */ + trait ProofTactic { + val name: String = this.getClass.getName.split('$').last + given ProofTactic = this + + } + + trait OnlyProofTactic { + def apply(using lib: Library, proof: lib.Proof): proof.ProofTacticJudgement + } + + trait ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement + } + + trait ProofFactTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact): proof.ProofTacticJudgement + } + trait ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement + } + + class UnapplicableProofTactic(val tactic: ProofTactic, proof: Library#Proof, errorMessage: String)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { + override def fixTrace(): Unit = { + val start = getStackTrace.indexWhere(elem => { + !elem.getClassName.contains(tactic.name) + }) + 1 + setStackTrace(getStackTrace.take(start)) + } + + def showError: String = { + val source = scala.io.Source.fromFile(file.value) + val textline = source.getLines().drop(line.value - 1).next().dropWhile(c => c.isWhitespace) + source.close() + Console.RED + proof.owningTheorem.prettyGoal + Console.RESET + "\n" + + ProofPrinter.prettyProof(proof, 2) + "\n" + + " " * (1 + proof.depth) + Console.RED + textline + Console.RESET + "\n\n" + + s" Proof tactic ${tactic.name} used in.(${file.value.split("/").last.split("\\\\").last}:${line.value}) did not succeed:\n" + + " " + errorMessage + } + } + + class UnimplementedProof(val theorem: Library#THM)(using sourcecode.Line, sourcecode.File) extends UserLisaException("Unimplemented Theorem") { + def showError: String = s"Theorem ${theorem.name}" + } + case class UnexpectedProofTacticFailureException(failure: Library#Proof#InvalidProofTactic, errorMessage: String)(using sourcecode.Line, sourcecode.File) + extends lisa.utils.LisaException(errorMessage) { + def showError: String = "A proof tactic used in another proof tactic returned an unexpected error. This may indicate an implementation error in either of the two tactics.\n" + + "Status of the proof at time of the error is:" + + ProofPrinter.prettyProof(failure.proof) + } + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala new file mode 100644 index 00000000..9e499d7d --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala @@ -0,0 +1,355 @@ +package lisa.prooflib + +import lisa.kernel.proof.SCProofChecker.checkSCProof +import lisa.prooflib.BasicStepTactic.* +import lisa.prooflib.ProofTacticLib.* +import lisa.prooflib.SimpleDeducedSteps.* +import lisa.prooflib.* +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.K.Identifier +import lisa.utils.LisaException +import lisa.utils.UserLisaException +import lisa.utils.{_, given} + +import scala.annotation.targetName + +trait ProofsHelpers { + library: Library & WithTheorems => + + import lisa.fol.FOL.{given, *} + + class HaveSequent(val bot: Sequent) { + + inline infix def by(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): By { val _proof: proof.type } = By(proof, line, file).asInstanceOf + + class By(val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { + + val bot = HaveSequent.this.bot ++ (F.iterable_to_set(_proof.getAssumptions) |- ()) + inline infix def apply(tactic: Sequent => _proof.ProofTacticJudgement): _proof.ProofStep & _proof.Fact = { + tactic(bot).validate(line, file) + } + inline infix def apply(tactic: ProofSequentTactic): _proof.ProofStep = { + tactic(using library, _proof)(bot).validate(line, file) + } + } + + infix def subproof(using proof: Library#Proof, line: sourcecode.Line, file: sourcecode.File)(computeProof: proof.InnerProof ?=> Unit): proof.ProofStep = { + val botWithAssumptions = HaveSequent.this.bot ++ (proof.getAssumptions |- ()) + val iProof: proof.InnerProof = new proof.InnerProof(Some(botWithAssumptions)) + computeProof(using iProof) + (new BasicStepTactic.SUBPROOF(using proof)(Some(botWithAssumptions))(iProof)).judgement.validate(line, file).asInstanceOf[proof.ProofStep] + } + + } + + class AndThenSequent private[ProofsHelpers] (val bot: Sequent) { + + inline infix def by(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): By { val _proof: proof.type } = + By(proof, line, file).asInstanceOf[By { val _proof: proof.type }] + + class By(val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { + private val bot = AndThenSequent.this.bot ++ (_proof.getAssumptions |- ()) + inline infix def apply(tactic: _proof.Fact => Sequent => _proof.ProofTacticJudgement): _proof.ProofStep = { + tactic(_proof.mostRecentStep)(bot).validate(line, file) + } + + inline infix def apply(tactic: ProofFactSequentTactic): _proof.ProofStep = { + tactic(using library, _proof)(_proof.mostRecentStep)(bot).validate(line, file) + } + + } + } + + /** + * Claim the given Sequent as a ProofTactic, which may require a justification by a proof tactic and premises. + */ + def have(using proof: library.Proof)(res: Sequent): HaveSequent = HaveSequent(res) + + def have(using line: sourcecode.Line, file: sourcecode.File)(using proof: library.Proof)(v: proof.Fact | proof.ProofTacticJudgement) = v match { + case judg: proof.ProofTacticJudgement => judg.validate(line, file) + case fact: proof.Fact @unchecked => HaveSequent(proof.sequentOfFact(fact)).by(using proof, line, file)(Rewrite(using library, proof)(fact)) + } + + /** + * Claim the given Sequent as a ProofTactic directly following the previously proven tactic, + * which may require a justification by a proof tactic. + */ + def thenHave(using proof: library.Proof)(res: Sequent): AndThenSequent = AndThenSequent(res) + + infix def andThen(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): AndThen { val _proof: proof.type } = AndThen(proof, line, file).asInstanceOf + + class AndThen private[ProofsHelpers] (val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { + inline infix def apply(tactic: _proof.Fact => _proof.ProofTacticJudgement): _proof.ProofStep = { + tactic(_proof.mostRecentStep).validate(line, file) + } + inline infix def apply(tactic: ProofFactTactic): _proof.ProofStep = { + tactic(using library, _proof)(_proof.mostRecentStep).validate(line, file) + } + } + + + /* + /** + * Assume the given formula in all future left hand-side of claimed sequents. + */ + def assume(using proof: library.Proof)(f: Formula): proof.ProofStep = { + proof.addAssumption(f) + have(() |- f) by BasicStepTactic.Hypothesis + } + */ + /** + * Assume the given formulas in all future left hand-side of claimed sequents. + */ + def assume(using proof: library.Proof)(fs: Formula*): proof.ProofStep = { + fs.foreach(f => proof.addAssumption(f)) + have(() |- fs.toSet) by BasicStepTactic.Hypothesis + } + + def thesis(using proof: library.Proof): Sequent = proof.possibleGoal.get + def goal(using proof: library.Proof): Sequent = proof.possibleGoal.get + + def lastStep(using proof: library.Proof): proof.ProofStep = proof.mostRecentStep + + def sorry(using proof: library.Proof): proof.ProofStep = have(thesis) by Sorry + + def showCurrentProof(using om: OutputManager, _proof: library.Proof)(): Unit = { + om.output("Current proof of " + _proof.owningTheorem.prettyGoal + ": ") + om.output( + ProofPrinter.prettyProof(_proof, 2) + ) + } + + extension (using proof: library.Proof)(fact: proof.Fact) { + infix def of(insts: (F.SubstPair | F.Term)*): proof.InstantiatedFact = { + proof.InstantiatedFact(fact, insts) + } + def statement: F.Sequent = proof.sequentOfFact(fact) + } + + def currentProof(using p: library.Proof): Library#Proof = p + + + //////////////////////////////////////// + // DSL for definitions and theorems // + //////////////////////////////////////// + + class UserInvalidDefinitionException(val symbol: String, errorMessage: String)(using line: sourcecode.Line, file: sourcecode.File) extends UserLisaException(errorMessage) { // TODO refine + val showError: String = { + val source = scala.io.Source.fromFile(file.value) + val textline = source.getLines().drop(line.value - 1).next().dropWhile(c => c.isWhitespace) + source.close() + s" Definition of $symbol at.(${file.value.split("/").last.split("\\\\").last}:${line.value}) is invalid:\n" + + " " + Console.RED + textline + Console.RESET + "\n\n" + + " " + errorMessage + } + } + + + def leadingVarsAndBody(e: Expr[?]): (Seq[Variable[?]], Expr[?]) = + def inner(e: Expr[?]): (Seq[Variable[?]], Expr[?]) = e match + case Abs(v, body) => + val (vars, bodyR) = inner(body) + (v +: vars, bodyR) + case _ => (Seq(), e) + val r = inner(e) + (r._1.reverse, r._2) + + def DEF[S: Sort](using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File) + (e: Expr[S]): Constant[S] = + val (vars, body) = leadingVarsAndBody(e) + if vars.size == e.sort.depth then + DirectDefinition[S](name.value, line.value, file.value)(e, vars).cst + else + val maxV: Int = vars.maxBy(_.id.no).id.no + val maxB: Int = body.freeVars.maxBy(_.id.no).id.no + var no = List(maxV, maxB).max + val newvars = K.flatTypeParameters(body.sort).map(i =>{no+=1; Variable.unsafe(K.Identifier("x", no), i)}) + val totvars = vars ++ newvars + DirectDefinition[S](name.value, line.value, file.value)(e, totvars)(using F.unsafeSortEvidence(e.sort)).cst + + def EpsilonDEF[S: Sort](using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File) + (e: Expr[S], j: JUSTIFICATION): Constant[S] = + val (vars, body) = leadingVarsAndBody(e) + if vars.size == e.sort.depth then + body match + case epsilon(x, inner) => + EpsilonDefinition[S](name.value, line.value, file.value)(e, vars, j).cst + case _ => om.lisaThrow(UserInvalidDefinitionException(name.value, "The given expression is not an epsilon term.")) + else om.lisaThrow(UserInvalidDefinitionException(name.value, "The given expression is not an epsilon term.")) + + + + class DirectDefinition[S : Sort](using om: OutputManager)(val fullName: String, line: Int, file: String)(val expr: Expr[S], val vars: Seq[Variable[?]]) extends DEFINITION(line, file) { + + val arity = vars.size + + lazy val cst: Constant[S] = F.Constant(name) + + + val appliedCst: Expr[?] = cst#@@(vars) + + + val innerJustification: theory.Definition = { + import lisa.utils.K.{findUndefinedSymbols} + val uexpr = expr.underlying + val ucst = K.Constant(name, cst.sort) + val uvars = vars.map(_.underlying) + val judgement = theory.makeDefinition(ucst, uexpr, uvars) + judgement match { + case K.ValidJustification(just) => + just + case wrongJudgement: K.InvalidJustification[?] => + if (!theory.belongsToTheory(uexpr)) { + om.lisaThrow( + UserInvalidDefinitionException( + name, + s"All symbols in the definition must belong to the theory. The symbols ${theory.findUndefinedSymbols(uexpr)} are unknown and you need to define them first." + ) + ) + } + if !theory.isAvailable(ucst) then + om.lisaThrow(UserInvalidDefinitionException(name, s"The symbol ${name} has already been defined and can't be redefined.")) + if !uexpr.freeVariables.nonEmpty then + om.lisaThrow( + UserInvalidDefinitionException( + name, + s"The definition is not allowed to contain schematic symbols or free variables. " + + s"The variables {${(uexpr.freeVariables).mkString(", ")}} are free in the expression ${uexpr}." + ) + ) + if !theory.isAvailable(ucst) then + om.lisaThrow(UserInvalidDefinitionException(name, s"The symbol ${name} has already been defined and can't be redefined.")) + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or an error in LISA.", + wrongJudgement, + None + ) + ) + } + } + val right = expr#@@(vars) + val statement = + if appliedCst.sort == K.Term then + () |- iff.#@(appliedCst).#@(right).asInstanceOf[Formula] + else + () |- equality.#@(appliedCst).#@(right).asInstanceOf[Formula] + library.last = Some(this) + } + + def dropAllLambdas(s: Expr[?]): Expr[?] = s match { + case Abs(v, body) => dropAllLambdas(body) + case _ => s + } + + + /** + * For a list of sequence of variables x, y, z, creates the term with lambdas: + * λx.(λy.(λz. base)) + */ + def abstractVars(v: Seq[Variable[?]], body: Expr[?]): Expr[?] = + def inner(v: Seq[Variable[?]], body: Expr[?]) = v match + case Seq() => body + case x +: xs => Abs.unsafe(x, abstractVars(xs, body)) + inner(v.reverse, body) + + /** + * For a list of sequence of variables x, y, z, creates the term with lambdas: + * λx.(λy.(λz. base)) + */ + def applyVars(v: Seq[Variable[?]], body: Expr[?]): Expr[?] = v match + case Seq() => body + case x +: xs => applyVars(xs, body#@(x)) + + /** + * For a list of sequence of variables x, y, z, creates the term with lambdas: + * ((((λx.(λy.(λz. base))) x) y) z) + */ + def betaExpand(base: Expr[?], vars: Seq[Variable[?]]): Expr[?] = + applyVars(vars, abstractVars(vars.reverse, base)) + + + + /** + * Allows to make definitions "by unique existance" of a symbol. May need debugging + */ + class EpsilonDefinition[S: Sort](using om: OutputManager)(fullName: String, line: Int, file: String)( + expr: Expr[S], + vars: Seq[Variable[?]], + val j: JUSTIFICATION + ) extends DirectDefinition(fullName, line, file)(expr, vars) { + + val body: Term = dropAllLambdas(expr).asInstanceOf[Term] + override val appliedCst : Term = (cst#@@(vars)).asInstanceOf[Term] + val (epsilonVar, inner) = body match + case epsilon(x, inner) => (x, inner) + case _ => om.lisaThrow(UserInvalidDefinitionException(name, "The given expression is not an epsilon term.")) + + private val propCst = inner.substitute(epsilonVar := appliedCst) + private val propEpsilon = inner.substitute(epsilonVar := body) + val definingProp = Theorem(propCst) { + val fresh = freshId(vars, "x") + have(this) + + def loop(expr: Expr[?], leading: List[Variable[?]]) : Unit = + expr match { + case App(lam @ Abs(vAbs, body1: Term), _) => + val freshX = Variable.unsafe(fresh, body1.sort) + val right: Term = applyVars(leading.reverse, freshX).asInstanceOf[Term] + var instRight: Term = applyVars(leading.reverse, body1).asInstanceOf[Term] + thenHave(appliedCst === instRight) by RightBeta.withParameters(appliedCst === right, lam, vAbs, freshX) + case App(f, a: Variable[?]) => loop(expr, a :: leading) + case _ => throw new Exception("Unreachable") + } + while lastStep.bot.right.head match {case App(epsilon, _) => false; case _ => true} do + loop(lastStep.bot.right.head, List()) + val eqStep = lastStep // appliedCst === body + // j is exists(x, prop(x)) + val existsStep = ??? // have(propEpsilon) by // prop(body) + val s3 = have(propCst) by BasicStepTactic.RightSubstEq.withParametersSimple[T](appliedCst, body, Seq(), (epsilonVar, inner))(j, lastStep) + ??? + } + + override def repr: String = + s" ${if (withSorry) " Sorry" else ""} Definition of symbol ${appliedCst} such that ${definingProp.statement})\n" + + } + + + + ///////////////////////// + // Local Definitions // + ///////////////////////// + + import lisa.utils.K.prettySCProof + import lisa.utils.KernelHelpers.apply + + /** + * A term with a definition, local to a proof. + * + * @param proof + * @param id + */ + abstract class LocalyDefinedVariable[S:Sort](val proof: library.Proof, id: Identifier) extends Variable(id) { + + val definition: proof.Fact + lazy val definingFormula = proof.sequentOfFact(definition).right.head + + // proof.addDefinition(this, defin(this), fact) + // val definition: proof.Fact = proof.getDefinition(this) + } + + /** + * Check correctness of the proof, using LISA's logical kernel, to the current point. + */ + def sanityProofCheck(using p: Proof)(message: String): Unit = { + val csc = p.toSCProof + if checkSCProof(csc).isValid then + println("Proof is valid. " + message) + Thread.sleep(100) + else + checkProof(csc) + throw Exception("Proof is not valid: " + message) + } + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala new file mode 100644 index 00000000..2ce8f574 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala @@ -0,0 +1,331 @@ +package lisa.prooflib + +import lisa.fol.FOL as F +import lisa.prooflib.BasicStepTactic.* +import lisa.prooflib.ProofTacticLib.{_, given} +import lisa.prooflib.* +import lisa.utils.K +import lisa.utils.KernelHelpers.{_, given} + +object SimpleDeducedSteps { +/* + + object Restate extends ProofTactic with ProofSequentTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = + unwrapTactic(RewriteTrue(bot))("Attempted true rewrite during tactic Restate failed.") + + // (proof.ProofStep | proof.OutsideFact | Int) is definitionally equal to proof.Fact, but for some reason + // scala compiler doesn't resolve the overload with a type alias, dependant type and implicit parameter + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") + + def from(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") + + } + + object Discharge extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(premise: proof.Fact): proof.ProofTacticJudgement = { + val ss = premises zip (premises map (e => proof.getSequent(e))) + val seqs = ss.map(_._2) + if (!seqs.forall(_.right.size == 1)) + return proof.InvalidProofTactic("When discharging this way, the discharged sequent must have only a single formula on the right handside.") + val seqAny = ss.find((_, s) => premise.statement.left.exists(f2 => F.isSame(s.right.head, f2))) + if (seqAny.isEmpty) + Restate.from(premise)(premise.statement) + else + TacticSubproof: ip ?=> + ss.foldLeft(premise: ip.Fact)((prem, discharge) => + val seq = discharge._2 + if prem.statement.left.exists(f => F.isSame(f, seq.right.head)) then + val goal = prem.statement - + * Γ ⊢ ∀x.ψ, Δ + * ------------------------- + * Γ |- ψ[t/x], Δ + * + * + * + * Returns a subproof containing the instantiation steps + */ + object InstantiateForall extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: F.Formula, t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + val phiK = phi.underlying + val tK = t map (_.underlying) + val premiseSequent = proof.getSequent(premise) + val premiseSequentK = premiseSequent.underlying + if (!premiseSequent.right.contains(phi)) { + proof.InvalidProofTactic("Input formula was not found in the RHS of the premise sequent.") + } else { + val emptyProof = K.SCProof(IndexedSeq(), IndexedSeq(premiseSequentK)) + val j = proof.ValidProofTactic(bot, Seq(K.Restate(premiseSequentK, -1)), Seq(premise)) + val res = tK.foldLeft((emptyProof, phiK, j: proof.ProofTacticJudgement)) { case ((p, f, j), t) => + j match { + case proof.InvalidProofTactic(_) => (p, f, j) // propagate error + case proof.ValidProofTactic(_, _, _) => + // good state, continue instantiating + // by construction the premise is well-formed + // verify the formula structure and instantiate + f match { + case psi @ K.Forall(K.Lambda(x, inner)) => + val tempVar = K.Variable(K.freshId(psi.freeVariables.map(_.id), x.id), K.Term) + // instantiate the formula with input + val in = K.substituteVariables(inner, Map(x -> t)) + val con = p.conclusion ->> f +>> in + // construct proof + val p0 = K.Hypothesis(in |- in, in) + val p1 = K.LeftForall(f |- in, 0, K.substituteVariables(inner, Map(x -> tempVar)) , tempVar, t) + val p2 = K.Cut(con, -1, 1, f) + + /** + * in = ψ[t/x] + * + * s1 = Γ ⊢ ∀x.ψ, Δ Premise + * con = Γ ⊢ ψ[t/x], Δ Result + * + * p0 = ψ[t/x] ⊢ ψ[t/x] Hypothesis + * p1 = ∀x.ψ ⊢ ψ[t/x] LeftForall p0 + * p2 = Γ ⊢ ψ[t/x], Δ Cut s1, p1 + */ + val newStep = K.SCSubproof(K.SCProof(IndexedSeq(p0, p1, p2), IndexedSeq(p.conclusion)), Seq(p.length - 1)) + ( + p withNewSteps IndexedSeq(newStep), + in, + j + ) + case _ => + (p, f, proof.InvalidProofTactic("Input formula is not universally quantified")) + } + } + } + + res._3 match { + case proof.InvalidProofTactic(_) => res._3 + case proof.ValidProofTactic(_, _, _) => { + if (K.isImplyingSequent(res._1.conclusion, botK)) + proof.ValidProofTactic(bot, Seq(K.SCSubproof(res._1.withNewSteps(IndexedSeq(K.Weakening(botK, res._1.length - 1))), Seq(-1))), Seq(premise)) + else + proof.InvalidProofTactic(s"InstantiateForall proved \n\t${res._1.conclusion.repr}\ninstead of input sequent\n\t${botK.repr}") + } + } + } + } + + def apply(using lib: Library, proof: lib.Proof)(t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val prem = proof.getSequent(premise) + if (prem.right.tail.isEmpty) { + // well formed + apply(using lib, proof)(prem.right.head, t*)(premise)(bot): proof.ProofTacticJudgement + } else proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") + } + + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + try { + val sp = TacticSubproof { + // lazy val premiseSequent = proof.getSequent(premise) + val s1 = lib.have(bot +<< bot.right.head) by Restate + lib.have(bot) by LeftForall(s1) + } + BasicStepTactic.unwrapTactic(sp)("Subproof substitution fail.") + } catch { + case e: Exception => proof.InvalidProofTactic("Impossible to justify desired step with instantiation.") + } + + } + + } + + */ + + + /* + /** + * Performs a cut when the formula to be used as pivot for the cut is + * inside a conjunction, preserving the conjunction structure + * + *
+   *
+   * PartialCut(ϕ, ϕ ∧ ψ)(left, right) :
+   *
+   *     left: Γ ⊢ ϕ ∧ ψ, Δ      right: ϕ, Σ ⊢ γ1 , γ2, …, γn
+   * -----------------------------------------------------------
+   *            Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn
+   *
+   * 
+ */ + object PartialCut extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, conjunction: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val leftSequent = proof.getSequent(prem1) + val rightSequent = proof.getSequent(prem2) + + if (leftSequent.right.contains(conjunction)) { + + if (rightSequent.left.contains(phi)) { + // check conjunction matches with phi + conjunction match { + case K.ConnectorFormula(K.And, s: Seq[K.Formula]) => { + if (s.contains(phi)) { + // construct proof + + val psi: Seq[K.Formula] = s.filterNot(_ == phi) + val newConclusions: Set[K.Formula] = rightSequent.right.map((f: K.Formula) => K.ConnectorFormula(K.And, f +: psi)) + + val Sigma: Set[K.Formula] = rightSequent.left - phi + + val p0 = K.Weakening(rightSequent ++<< (psi |- ()), -2) + val p1 = K.RestateTrue(psi |- psi) + + // TODO: can be abstracted into a RightAndAll step + val emptyProof = SCProof(IndexedSeq(), IndexedSeq(p0.bot, p1.bot)) + val proofRightAndAll = rightSequent.right.foldLeft(emptyProof) { case (p, gamma) => + p withNewSteps IndexedSeq(K.RightAnd(p.conclusion ->> gamma +>> K.ConnectorFormula(K.And, gamma +: psi), Seq(p.length - 1, -2), gamma +: psi)) + } + + val p2 = K.SCSubproof(proofRightAndAll, Seq(0, 1)) + val p3 = K.Restate(Sigma + conjunction |- newConclusions, 2) // sanity check and correct form + val p4 = K.Cut(bot, -1, 3, conjunction) + + /** + * newConclusions = ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn + * + * left = Γ ⊢ ϕ ∧ ψ, Δ Premise + * right = ϕ, Σ ⊢ γ1 , γ2, …, γn Premise + * + * p0 = ϕ, Σ, ψ ⊢ γ1 , γ2, …, γn Weakening on right + * p1 = ψ ⊢ ψ Hypothesis + * p2 = Subproof: + * 2.1 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , γ2, …, γn RightAnd on p0 and p1 with ψ ∧ γ1 + * 2.2 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , ψ ∧ γ2, …, γn RightAnd on 2.1 and p1 ψ ∧ γ2 + * ... + * 2.n = ϕ, Σ, ψ ⊢ ψ ∧ γ1, ψ ∧ γ2, …, ψ ∧ γn RightAnd on 2.(n-1) and p1 with ψ ∧ γn + * + * p3 = ϕ ∧ ψ, Σ ⊢ ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Rewrite on p2 (just to have a cleaner form) + * p2 = Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Cut on left, p1 with ϕ ∧ ψ + * + * p2 is the result + */ + + proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3, p4), Seq(prem1, prem2)) + } else { + proof.InvalidProofTactic("Input conjunction does not contain the pivot.") + } + } + case _ => proof.InvalidProofTactic("Input not a conjunction.") + } + } else { + proof.InvalidProofTactic("Input pivot formula not found in right premise.") + } + } else { + proof.InvalidProofTactic("Input conjunction not found in first premise.") + } + } + } + + object destructRightAnd extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val conc = proof.getSequent(prem) + val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) + val p1 = K.LeftAnd(emptySeq +<< (a /\ b) +>> a, 0, a, b) + val p2 = K.Cut(conc ->> (a /\ b) ->> (b /\ a) +>> a, -1, 1, a /\ b) + proof.ValidProofTactic(IndexedSeq(p0, p1, p2), Seq(prem)) + } + } + object destructRightOr extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val conc = proof.getSequent(prem) + val mat = conc.right.find(f => K.isSame(f, a \/ b)) + if (mat.nonEmpty) { + + val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) + val p1 = K.Hypothesis(emptySeq +<< b +>> b, b) + + val p2 = K.LeftOr(emptySeq +<< (a \/ b) +>> a +>> b, Seq(0, 1), Seq(a, b)) + val p3 = K.Cut(conc ->> mat.get +>> a +>> b, -1, 2, a \/ b) + proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3), Seq(prem)) + } else { + proof.InvalidProofTactic("Premise does not contain the union of the given formulas") + } + + } + } + + object GeneralizeToForall extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val sequent = proof.getSequent(prem) + if (sequent.right.contains(phi)) { + val emptyProof = SCProof(IndexedSeq(), IndexedSeq(sequent)) + val j = proof.ValidProofTactic(IndexedSeq(K.Restate(sequent, proof.length - 1)), Seq[proof.Fact]()) + + val res = t.foldRight(emptyProof: SCProof, phi: K.Formula, j: proof.ProofTacticJudgement) { case (x1, (p1: SCProof, phi1, j1)) => + j1 match { + case proof.InvalidProofTactic(_) => (p1, phi1, j1) + case proof.ValidProofTactic(_, _) => { + if (!p1.conclusion.right.contains(phi1)) + (p1, phi1, proof.InvalidProofTactic("Formula is not present in the lass sequent")) + + val proofStep = K.RightForall(p1.conclusion ->> phi1 +>> forall(x1, phi1), p1.length - 1, phi1, x1) + ( + p1 appended proofStep, + forall(x1, phi1), + j1 + ) + } + } + } + + res._3 match { + case proof.InvalidProofTactic(_) => res._3 + case proof.ValidProofTactic(_, _) => proof.ValidProofTactic((res._1.steps appended K.Restate(bot, res._1.length - 1)), Seq(prem)) + } + + } else proof.InvalidProofTactic("RHS of premise sequent contains not phi") + + } + } + + object GeneralizeToForallNoForm extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + if (proof.getSequent(prem).right.tail.isEmpty) + GeneralizeToForall.apply(using lib, proof)(proof.getSequent(prem).right.head, t*)(prem)(bot): proof.ProofTacticJudgement + else + proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") + } + + } + + object ByCase extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val nphi = !phi + + val pa = proof.getSequent(prem1) + val pb = proof.getSequent(prem2) + val (leftAphi, leftBnphi) = (pa.left.find(K.isSame(_, phi)), pb.left.find(K.isSame(_, nphi))) + if (leftAphi.nonEmpty && leftBnphi.nonEmpty) { + val p2 = K.RightNot(pa -<< leftAphi.get +>> nphi, -1, phi) + val p3 = K.Cut(pa -<< leftAphi.get ++ (pb -<< leftBnphi.get), 0, -2, nphi) + val p4 = K.Restate(bot, 1) + proof.ValidProofTactic(IndexedSeq(p2, p3, p4), IndexedSeq(prem1, prem2)) // TODO: Check pa/pb orDer + + } else { + proof.InvalidProofTactic("Premises have not the right syntax") + } + } + } + */ +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala new file mode 100644 index 00000000..bf0fed20 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala @@ -0,0 +1,643 @@ +package lisa.prooflib + +import lisa.kernel.proof.RunningTheory +import lisa.prooflib.ProofTacticLib.ProofTactic +import lisa.prooflib.ProofTacticLib.UnimplementedProof +import lisa.prooflib.* +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.LisaException +import lisa.utils.UserLisaException +import lisa.utils.UserLisaException.* + +import scala.annotation.nowarn +import scala.collection.mutable.Buffer as mBuf +import scala.collection.mutable.Map as mMap +import scala.collection.mutable.Stack as stack + +trait WithTheorems { + library: Library => + + /** + * The main builder for proofs. It is a mutable object that can be used to build a proof step by step. + * It is used either to construct a theorem/lemma ([[BaseProof]]) or to construct a subproof ([[InnerProof]]). + * We can add proof tactics to it producing intermediate results. In the end, obtain a [[K.SCProof]] from it. + * + * @param assump list of starting assumptions, usually propagated from outer proofs. + */ + sealed abstract class Proof(assump: List[F.Formula]) { + + val possibleGoal: Option[F.Sequent] + type SelfType = this.type + type OutsideFact >: JUSTIFICATION + type Fact = ProofStep | InstantiatedFact | OutsideFact | Int + + /** + * A proven fact (from a previously proven step, a theorem or a definition) with specific instantiations of free variables. + * + * @param fact The base fact + * @param insts The instantiation of free variables + */ + case class InstantiatedFact( + fact: Fact, + insts: Seq[F.SubstPair | F.Term] + ) { + val baseFormula: F.Sequent = sequentOfFact(fact) + val (result, proof) = { + val (terms, substPairs) = insts.partitionMap {e => + if e.isInstanceOf[F.Expr[?]] then Left(e.asInstanceOf[F.Term]) + else Right(e.asInstanceOf[F.SubstPair]) + } + + val (s1, p1) = if substPairs.isEmpty then (baseFormula, Seq()) else baseFormula.instantiateWithProof(substPairs.map(sp => (sp._1, sp._2)).toMap, -1) + val (s2, p2) = if terms.isEmpty then (s1, p1) else s1.instantiateForallWithProof(terms, p1.length - 1) + (s2, p1 ++ p2) + } + + } + + val library: WithTheorems.this.type = WithTheorems.this + + private var steps: List[ProofStep] = Nil + private var imports: List[(OutsideFact, F.Sequent)] = Nil + private var instantiatedFacts: List[(InstantiatedFact, Int)] = Nil + private var assumptions: List[F.Formula] = assump + private var eliminations: List[(F.Formula, (Int, F.Sequent) => List[K.SCProofStep])] = Nil + + def cleanAssumptions: Unit = assumptions = Nil + + /** + * the theorem that is being proved (paritally, if subproof) by this proof. + * + * @return The theorem + */ + def owningTheorem: THM + + /** + * A proof step, containing a high level ProofTactic and the corresponding K.SCProofStep. If the tactic produce more than one + * step, they must be encapsulated in a subproof. Usually constructed with [[ValidProofTactic.validate]] + * + * @param judgement The result of the tactic + * @param scps The corresponding [[K.SCProofStep]] + * @param position The position of the step in the proof + */ + case class ProofStep private (judgement: ValidProofTactic, scps: K.SCProofStep, position: Int) { + val bot: F.Sequent = judgement.bot + def innerBot: K.Sequent = scps.bot + val host: Proof.this.type = Proof.this + + def tactic: ProofTactic = judgement.tactic + + } + private object ProofStep { // TODO + def newProofStep(judgement: ValidProofTactic): ProofStep = { + val ps = ProofStep( + judgement, + SC.SCSubproof( + K.SCProof(judgement.scps.toIndexedSeq, judgement.imports.map(f => sequentOfFact(f).underlying).toIndexedSeq), + judgement.imports.map(sequentAndIntOfFact(_)._2) + ), + steps.length + ) + addStep(ps) + ps + + } + } + + /** + * A proof step can be constructed from a succesfully executed tactic + */ + def newProofStep(judgement: ValidProofTactic): ProofStep = + ProofStep.newProofStep(judgement) + + private def addStep(ds: ProofStep): Unit = steps = ds :: steps + private def addImport(imp: OutsideFact, seq: F.Sequent): Unit = { + imports = (imp, seq) :: imports + } + + private def addInstantiatedFact(instFact: InstantiatedFact): Unit = { + val step = ValidProofTactic(instFact.result, instFact.proof, Seq(instFact.fact))(using F.SequentInstantiationRule) + newProofStep(step) + instantiatedFacts = (instFact, steps.length - 1) :: instantiatedFacts + } + + /** + * Add an assumption the the proof, i.e. a formula that is automatically on the left side of the sequent. + * + * @param f + */ + def addAssumption(f: F.Formula): Unit = { + if (!assumptions.contains(f)) assumptions = f :: assumptions + } + + def addElimination(f: F.Formula, elim: (Int, F.Sequent) => List[K.SCProofStep]): Unit = { + eliminations = (f, elim) :: eliminations + } + + def addDischarge(ji: Fact): Unit = { + val (s1, t1) = sequentAndIntOfFact(ji) + val f = s1.right.head + val fu = f.underlying + addElimination( + f, + (i, sequent) => + List( + SC.Cut((sequent.underlying -<< fu) ++ (s1.underlying ->> fu), t1, i, fu) + ) + ) + } + + // Getters + + /** + * Favour using getSequent when applicable. + * @return The list of ValidatedSteps (containing a high level ProofTactic and the corresponding K.SCProofStep). + */ + def getSteps: List[ProofStep] = steps.reverse + + /** + * Favour using getSequent when applicable. + * @return The list of Imports validated in the formula, with their original justification. + */ + def getImports: List[(OutsideFact, F.Sequent)] = imports.reverse + + /** + * @return The list of formulas that are assumed for the reminder of the proof. + */ + def getAssumptions: List[F.Formula] = assumptions + + /** + * Produce the low level [[K.SCProof]] corresponding to the proof. Automatically eliminates any formula in the discharges that is still left of the sequent. + * + * @return + */ + def toSCProof: K.SCProof = { + import lisa.utils.KernelHelpers.{-<<, ->>} + val finalSteps = eliminations.foldLeft[(List[SC.SCProofStep], F.Sequent)]((steps.map(_.scps), steps.head.bot)) { (cumul_bot, f_elim) => + val (cumul, bot) = cumul_bot + val (f, elim) = f_elim + val i = cumul.size + val elimSteps = elim(i - 1, bot) + (elimSteps.foldLeft(cumul)((cumul2, step) => step :: cumul2), bot -<< f) + } + + val r = K.SCProof(finalSteps._1.reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) + r + } + + def currentSCProof: K.SCProof = K.SCProof(steps.map(_.scps).reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) + + /** + * For a fact, returns the sequent that the fact proove and the position of the fact in the proof. + * + * @param fact Any fact, possibly instantiated, belonging to the proof + * @return its proven sequent and position + */ + def sequentAndIntOfFact(fact: Fact): (F.Sequent, Int) = fact match { + case i: Int => + ( + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(steps.length - i - 1).bot + else { + val i2 = -(i + 1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(imports.length + i)._2 + }, + i + ) + case ds: ProofStep => (ds.bot, ds.position) + case instFact: InstantiatedFact => + val r = instantiatedFacts.find(instFact == _._1) + r match { + case Some(value) => (instFact.result, value._2) + case None => + addInstantiatedFact(instFact) + (instFact.result, steps.length - 1) + } + case of: OutsideFact @unchecked => + val r = imports.indexWhere(of == _._1) + if (r != -1) { + (imports(r)._2, r - imports.length) + } else { + val r2 = sequentOfOutsideFact(of) + addImport(of, r2) + (r2, -imports.length) + } + } + + def sequentOfFact(fact: Fact): F.Sequent = fact match { + case i: Int => + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(steps.length - i - 1).bot + else { + val i2 = -(i + 1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(imports.length + i)._2 + } + case ds: ProofStep => ds.bot + case instfact: InstantiatedFact => instfact.result + case of: OutsideFact @unchecked => + val r = imports.find(of == _._1) + if (r.nonEmpty) { + r.get._2 + } else { + sequentOfOutsideFact(of) + } + } + + def sequentOfOutsideFact(of: OutsideFact): F.Sequent + + def getSequent(f: Fact): F.Sequent = sequentOfFact(f) + def mostRecentStep: ProofStep = steps.head + + /** + * The number of steps in the proof. This is not the same as the number of steps in the corresponding [[K.SCProof]]. + * This also does not count the number of steps in the subproof. + * + * @return + */ + def length: Int = steps.length + + /** + * The set of symbols that can't be instantiated because they are free in an assumption. + */ + def lockedSymbols: Set[F.Variable[?]] = assumptions.toSet.flatMap(f => f.freeVars.toSet) + + /** + * Used to "lift" the type of a justification when the compiler can't infer it. + */ + def asOutsideFact(j: JUSTIFICATION): OutsideFact + + def depth: Int = + (this: @unchecked) match { + case p: Proof#InnerProof => 1 + p.parent.depth + case _: BaseProof => 0 + } + + /** + * Create a subproof inside the current proof. The subproof will have the same assumptions as the current proof. + * Can have a goal known in advance (usually for a user-written subproof) or not (usually for a tactic-generated subproof). + */ + def newInnerProof(possibleGoal: Option[F.Sequent]) = new InnerProof(possibleGoal) + final class InnerProof(val possibleGoal: Option[F.Sequent]) extends Proof(this.getAssumptions) { + val parent: Proof.this.type = Proof.this + val owningTheorem: THM = parent.owningTheorem + type OutsideFact = parent.Fact + override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = parent.asOutsideFact(j) + + override def sequentOfOutsideFact(of: parent.Fact): F.Sequent = of match { + case j: JUSTIFICATION => j.statement + case ds: Proof#ProofStep => ds.bot + case _ => parent.sequentOfFact(of) + } + } + + /** + * Contains the result of a tactic computing a K.SCProofTactic. + * Can be successful or unsuccessful. + */ + sealed abstract class ProofTacticJudgement { + val tactic: ProofTactic + val proof: Proof = Proof.this + + /** + * Returns true if and only if the judgement is valid. + */ + def isValid: Boolean = this match { + case ValidProofTactic(_, _, _) => true + case InvalidProofTactic(_) => false + } + + def validate(line: sourcecode.Line, file: sourcecode.File): ProofStep = { + this match { + case vpt: ValidProofTactic => newProofStep(vpt) + case ipt: InvalidProofTactic => + val e = lisa.prooflib.ProofTacticLib.UnapplicableProofTactic(ipt.tactic, ipt.proof, ipt.message)(using line, file) + e.setStackTrace(ipt.stack) + throw e + } + } + } + + /** + * A Kernel Sequent Calculus proof step that has been correctly produced. + */ + case class ValidProofTactic(bot: lisa.fol.FOL.Sequent, scps: Seq[K.SCProofStep], imports: Seq[Fact])(using val tactic: ProofTactic) extends ProofTacticJudgement {} + + /** + * A proof step which led to an error when computing the corresponding K.Sequent Calculus proof step. + */ + case class InvalidProofTactic(message: String)(using val tactic: ProofTactic) extends ProofTacticJudgement { + private val nstack = Throwable() + val stack: Array[StackTraceElement] = nstack.getStackTrace.drop(2) + } + + } + + /** + * Top-level instance of [[Proof]] directly proving a theorem + */ + sealed class BaseProof(val owningTheorem: THMFromProof) extends Proof(Nil) { + + val goal: F.Sequent = owningTheorem.goal + val possibleGoal: Option[F.Sequent] = Some(goal) + type OutsideFact = JUSTIFICATION + override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = j + + override def sequentOfOutsideFact(j: JUSTIFICATION): F.Sequent = j.statement + + def justifications: List[JUSTIFICATION] = getImports.map(_._1) + + } + + /** + * Abstract class representing theorems, axioms and different kinds of definitions. Corresponds to a [[theory.Justification]]. + */ + sealed abstract class JUSTIFICATION { + + /** + * A pretty representation of the justification + */ + def repr: String + + /** + * The inner kernel justification + */ + def innerJustification: theory.Justification + + /** + * The sequent that the justification proves + */ + def statement: F.Sequent + + /** + * The complete name of the justification. Two justifications should never have the same full name. Typically, path is used to disambiguate. + */ + def fullName: String + + /** + * The short name of the justification (without the path). + */ + val name: String = fullName.split("\\.").last + + /** + * The "owning" object of the justification. Typically, the package/object in which it is defined. + */ + val owner = fullName.split("\\.").dropRight(1).mkString(".") + + /** + * Returns if the statement is unconditionaly proven or if it depends on some sorry step (including in the other justifications it relies on) + */ + def withSorry: Boolean = innerJustification match { + case thm: theory.Theorem => thm.withSorry + case d: theory.Definition => false + case ax: theory.Axiom => false + } + } + + /** + * A Justification, corresponding to [[K.Axiom]] + */ + class AXIOM(innerAxiom: theory.Axiom, val axiom: F.Formula, val fullName: String) extends JUSTIFICATION { + def innerJustification: theory.Axiom = innerAxiom + val statement: F.Sequent = F.Sequent(Set(), Set(axiom)) + if (statement.underlying != theory.sequentFromJustification(innerAxiom)) { + throw new InvalidAxiomException("The provided kernel axiom and desired statement don't match.", name, axiom, library) + } + def repr: String = s" Axiom $name := $axiom" + } + + /** + * Introduces a new axiom in the theory. + * + * @param fullName The name of the axiom, including the path. Usually fetched automatically by the compiler. + * @param axiom The axiomatized formula. + * @return + */ + def Axiom(using fullName: sourcecode.FullName)(axiom: F.Formula): AXIOM = { + val ax: Option[theory.Axiom] = theory.addAxiom(fullName.value, axiom.underlying) + ax match { + case None => throw new InvalidAxiomException("Not all symbols belong to the theory", fullName.value, axiom, library) + case Some(value) => AXIOM(value, axiom, fullName.value) + } + } + + /** + * A Justification, corresponding to [[K.FunctionDefinition]] or [[K.PredicateDefinition]] + */ + abstract class DEFINITION(line: Int, file: String) extends JUSTIFICATION { + val fullName: String + def repr: String = innerJustification.repr + + def cst: F.Constant[?] + knownDefs.update(cst, Some(this)) + + } + + /** + * A proven, reusable statement. A justification corresponding to [[K.Theorem]]. + */ + sealed abstract class THM extends JUSTIFICATION { + def repr: String = + s" Theorem ${name} := ${statement}${if (withSorry) " (!! Relies on Sorry)" else ""}" + + /** + * The underlying Kernel proof [[K.SCProof]], if it is still available. Proofs are not kept in memory for efficiency. + */ + def kernelProof: Option[K.SCProof] + + /** + * The high level [[Proof]], if one was used to obtain the theorem. If the theorem was not produced by such high level proof but directly by a low level one, this is None. + */ + def highProof: Option[BaseProof] + val innerJustification: theory.Theorem + + /** + * A pretty representation of the goal of the theorem + */ + def prettyGoal: String = statement.underlying.repr + } + object THM { + + /** + * Standard way to construct a theorem using a high level proof. + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path. Usually fetched automatically by the compiler. + * @param line The line at which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting + * @param file The file in which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param computeProof The proof computation. The proof is built by adding proof steps to the proof object. The proof object is an impicit argument of computeProof, + * @see Context Functions in Scala + * @return + */ + def apply(using om: OutputManager)(statement: F.Sequent, fullName: String, line: Int, file: String, kind: TheoremKind)(computeProof: Proof ?=> Unit) = + THMFromProof(statement, fullName, line, file, kind)(computeProof) + + /** + * Constructs a "high level" theorem from an existing theorem in the + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path/package. + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param innerThm The inner theorem, coming from the kernel + * @param getProof If available, a way to compute the Kernel proof again. + */ + def fromKernel(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) = + THMFromKernel(statement, fullName, kind, innerThm, getProof) + + /** + * Construct a theorem (both in the kernel and high level) from a proof. + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path/package. + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param getProof The kernel proof. + * @param justifs low level justifications used to justify the proof's imports + * @return + */ + def fromSCProof(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, getProof: () => K.SCProof, justifs: Seq[theory.Justification]): THM = + val proof = getProof() + theory.theorem(fullName, statement.underlying, proof, justifs) match { + case K.Judgement.ValidJustification(just) => + fromKernel(statement, fullName, kind, just.asInstanceOf, () => Some(getProof())) + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The proof was rejected by LISA's logical kernel. ", + wrongJudgement, + None + ) + ) + } + + } + + /** + * A theorem that was produced from a kernel theorem and not from a high level proof. See [[THM.fromKernel]]. + * Those are typically theorems imported from another tool, or from serialization. + */ + class THMFromKernel(using om: OutputManager)(val statement: F.Sequent, val fullName: String, val kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) extends THM { + + val innerJustification: theory.Theorem = innerThm + assert(innerThm.name == fullName) + def kernelProof: Option[K.SCProof] = getProof() + def highProof: Option[BaseProof] = None + + val goal: F.Sequent = statement + + } + + /** + * A theorem that was produced from a high level proof. See [[THM.apply]]. + * Typical way to construct a theorem in the library, but serialization for example will produce a [[THMFromKernel]]. + */ + class THMFromProof(using om: OutputManager)(val statement: F.Sequent, val fullName: String, line: Int, file: String, val kind: TheoremKind)(computeProof: Proof ?=> Unit) extends THM { + + val goal: F.Sequent = statement + + val proof: BaseProof = new BaseProof(this) + def kernelProof: Option[K.SCProof] = Some(proof.toSCProof) + def highProof: Option[BaseProof] = Some(proof) + + import lisa.utils.Serialization.* + val innerJustification: theory.Theorem = + if library._draft.nonEmpty && library._draft.get.value != file + then // if the draft option is activated, and the theorem is not in the file where the draft option is given, then we replace the proof by sorry + theory.theorem(name, goal.underlying, SCProof(SC.Sorry(goal.underlying)), IndexedSeq.empty) match { + case K.Judgement.ValidJustification(just) => + just + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", + wrongJudgement, + Some(proof) + ) + ) + } + else if library._withCache then + oneThmFromFile("cache/" + name, library.theory) match { + case Some(thm) => thm // try to get the theorem from file + + case None => + val (thm, scp, justifs) = prove(computeProof) // if fail, prove it + thmsToFile("cache/" + name, theory, List((name, scp, justifs))) // and save it to the file + thm + } + else prove(computeProof)._1 + + library.last = Some(this) + + /** + * Construct the kernel theorem from the high level proof + */ + private def prove(computeProof: Proof ?=> Unit): (theory.Theorem, SCProof, List[(String, theory.Justification)]) = { + try { + computeProof(using proof) + } catch { + case e: UserLisaException => + om.lisaThrow(e) + } + + if (proof.length == 0) + then om.lisaThrow(new UnimplementedProof(this)) + + val scp = proof.toSCProof + val justifs = proof.getImports.map(e => (e._1.owner, e._1.innerJustification)) + theory.theorem(name, goal.underlying, scp, justifs.map(_._2)) match { + case K.Judgement.ValidJustification(just) => + (just, scp, justifs) + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", + wrongJudgement, + Some(proof) + ) + ) + } + + } + + } + + given thmConv: Conversion[library.THM, theory.Theorem] = _.innerJustification + + trait TheoremKind { + val kind2: String + + def apply(using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(statement: F.Sequent)(computeProof: Proof ?=> Unit): THM = { + val thm = THM(statement, name.value, line.value, file.value, this)(computeProof) + if this == Theorem then show(thm) + thm + } + + } + + /** + * A "Theorem" kind of theorem, by opposition with a lemma or corollary. The difference is that theorem are always printed when a file defining one is run. + */ + object Theorem extends TheoremKind { val kind2: String = "Theorem" } + + /** + * Lemmas are like theorems, but are conceptually less importants and are not printed when a file defining one is run. + */ + object Lemma extends TheoremKind { val kind2: String = "Lemma" } + + /** + * Corollaries are like theorems, but are conceptually less importants and are not printed when a file defining one is run. + */ + object Corollary extends TheoremKind { val kind2: String = "Corollary" } + + /** + * Internal statements are internally produced theorems, for example as intermediate step in definitions. + */ + object InternalStatement extends TheoremKind { val kind2: String = "Internal, automatically produced" } + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/tptp/Example.scala b/lisa-utils/src/main/scala/lisa/utils/tptp/Example.scala index 4c834ae9..ded5883e 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/Example.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/Example.scala @@ -1,17 +1,15 @@ package lisa.utils.tptp -import lisa.utils.parsing.FOLParser.* import lisa.utils.tptp.KernelParser.annotatedStatementToKernel import lisa.utils.tptp.KernelParser.parseToKernel import lisa.utils.tptp.KernelParser.problemToSequent import lisa.utils.tptp.ProblemGatherer.getPRPproblems +import lisa.utils.K.{repr, given} import KernelParser.{mapAtom, mapTerm, mapVariable} object Example { - val prettyFormula = lisa.utils.parsing.FOLParser.printFormula - val prettySequent = lisa.utils.parsing.FOLParser.printSequent def tptpExample(): Unit = { val axioms = List( "( ~ ( ? [X] : ( big_s(X) & big_q(X) ) ) )", @@ -29,8 +27,8 @@ object Example { ) println("\n---Individual Fetched Formulas---") - axioms.foreach(a => println(prettyFormula(parseToKernel(a)(using mapAtom, mapTerm, mapVariable)))) - println(prettyFormula(parseToKernel(conjecture)(using mapAtom, mapTerm, mapVariable))) + axioms.foreach(a => println(parseToKernel(a)(using mapAtom, mapTerm, mapVariable).repr)) + println(parseToKernel(conjecture)(using mapAtom, mapTerm, mapVariable).repr) println("\n---Annotated Formulas---") anStatements.map(annotatedStatementToKernel(_)(using mapAtom, mapTerm, mapVariable)).foreach(f => printAnnotatedStatement(f)) @@ -48,7 +46,7 @@ object Example { val seq = problemToSequent(probs.head) printProblem(probs.head) println("\n---Sequent---") - println(prettySequent(seq)) + println(seq.repr) } } catch { case error: NullPointerException => println("You can download the tptp library at http://www.tptp.org/ and put it in main/resources") @@ -59,8 +57,8 @@ object Example { // Utility def printAnnotatedStatement(a: AnnotatedStatement): Unit = { val prettyStatement = a match { - case f: AnnotatedFormula => prettyFormula(f.formula) - case s: AnnotatedSequent => prettySequent(s.sequent) + case f: AnnotatedFormula => f.formula.repr + case s: AnnotatedSequent => s.sequent.repr } if (a.role == "axiom") println("Given " + a.name + ": " + prettyStatement) else if (a.role == "conjecture") println("Prove " + a.name + ": " + prettyStatement) diff --git a/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala b/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala index 1911e479..5d4f1d31 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala @@ -23,25 +23,28 @@ object KernelParser { * @param formula A formula in the tptp language * @return the corresponding LISA formula */ - def parseToKernel(formula: String)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Formula = convertToKernel( + def parseToKernel(formula: String)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Expression = convertToKernel( Parser.fof(formula) - ) + )(using mapAtom, mapTerm, mapVariable) /** * @param formula a tptp formula in leo parser * @return the same formula in LISA */ - def convertToKernel(formula: FOF.Formula)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Formula = { + def convertToKernel(formula: FOF.Formula)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Expression = { + val (mapAtom, mapTerm, mapVariable) = maps formula match { case FOF.AtomicFormula(f, args) => - if f == "$true" then K.top() - else if f == "$false" then K.bot() - else K.AtomicFormula(mapAtom(f, args.size), args map convertTermToKernel) + if f == "$true" then K.top + else if f == "$false" then K.bot + else args.foldLeft(mapAtom(f, args.size): K.Expression)((acc, arg) => acc(convertTermToKernel(arg))) + // else throw new Exception("Unknown atomic formula kind: " + kind +" in " + f) case FOF.QuantifiedFormula(quantifier, variableList, body) => quantifier match { - case FOF.! => variableList.foldRight(convertToKernel(body))((s, f) => K.Forall(mapVariable(s), f)) - case FOF.? => variableList.foldRight(convertToKernel(body))((s, f) => K.Exists(mapVariable(s), f)) + case FOF.! => + variableList.foldRight(convertToKernel(body))((s, f) => K.forall(mapVariable(s), f)) + case FOF.? => variableList.foldRight(convertToKernel(body))((s, f) => K.exists(mapVariable(s), f)) } case FOF.UnaryFormula(connective, body) => connective match { @@ -58,24 +61,22 @@ object KernelParser { case FOF.| => convertToKernel(left) \/ convertToKernel(right) case FOF.& => convertToKernel(left) /\ convertToKernel(right) } - case FOF.Equality(left, right) => K.equality(convertTermToKernel(left), convertTermToKernel(right)) - case FOF.Inequality(left, right) => !K.equality(convertTermToKernel(left), convertTermToKernel(right)) + case FOF.Equality(left, right) => K.equality(convertTermToKernel(left))(convertTermToKernel(right)) + case FOF.Inequality(left, right) => !K.equality(convertTermToKernel(left))(convertTermToKernel(right)) } } - def convertToKernel(sequent: FOF.Sequent)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Sequent = { + def convertToKernel(sequent: FOF.Sequent)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Sequent = { K.Sequent(sequent.lhs.map(convertToKernel).toSet, sequent.rhs.map(convertToKernel).toSet) } - def convertToKernel(formula: CNF.Formula)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Formula = { - - K.ConnectorFormula( - K.Or, + def convertToKernel(formula: CNF.Formula)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Expression = { + K.multior( formula.map { - case CNF.PositiveAtomic(formula) => K.AtomicFormula(mapAtom(formula.f, formula.args.size), formula.args.map(convertTermToKernel).toList) - case CNF.NegativeAtomic(formula) => !K.AtomicFormula(mapAtom(formula.f, formula.args.size), formula.args.map(convertTermToKernel).toList) - case CNF.Equality(left, right) => K.equality(convertTermToKernel(left), convertTermToKernel(right)) - case CNF.Inequality(left, right) => !K.equality(convertTermToKernel(left), convertTermToKernel(right)) + case CNF.PositiveAtomic(formula) => multiapply(mapAtom(formula.f, formula.args.size))(formula.args.map(convertTermToKernel).toList) + case CNF.NegativeAtomic(formula) => !multiapply(mapAtom(formula.f, formula.args.size))(formula.args.map(convertTermToKernel).toList) + case CNF.Equality(left, right) => K.equality(convertTermToKernel(left))(convertTermToKernel(right)) + case CNF.Inequality(left, right) => !K.equality(convertTermToKernel(left))(convertTermToKernel(right)) } ) } @@ -84,31 +85,33 @@ object KernelParser { * @param term a tptp term in leo parser * @return the same term in LISA */ - def convertTermToKernel(term: CNF.Term)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Term = term match { - case CNF.AtomicTerm(f, args) => K.Term(mapTerm(f, args.size), args map convertTermToKernel) - case CNF.Variable(name) => K.VariableTerm(mapVariable(name)) - case CNF.DistinctObject(name) => ??? - } + def convertTermToKernel(term: CNF.Term)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Expression = + val (mapAtom, mapTerm, mapVariable) = maps + term match { + case CNF.AtomicTerm(f, args) => K.multiapply(mapTerm(f, args.size))(args map convertTermToKernel) + case CNF.Variable(name) => mapVariable(name) + case CNF.DistinctObject(name) => ??? + } /** * @param term a tptp term in leo parser * @return the same term in LISA */ - def convertTermToKernel(term: FOF.Term)(using mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Term = term match { - - case FOF.AtomicTerm(f, args) => - K.Term(mapTerm(f, args.size), args map convertTermToKernel) - case FOF.Variable(name) => K.VariableTerm(mapVariable(name)) - case FOF.DistinctObject(name) => ??? - case FOF.NumberTerm(value) => ??? - + def convertTermToKernel(term: FOF.Term)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Expression = + val (mapAtom, mapTerm, mapVariable) = maps + term match { + case FOF.AtomicTerm(f, args) => + K.multiapply(mapTerm(f, args.size))(args map convertTermToKernel) + case FOF.Variable(name) => mapVariable(name) + case FOF.DistinctObject(name) => ??? + case FOF.NumberTerm(value) => ??? } /** * @param formula an annotated tptp statement * @return the corresponding LISA formula augmented with name and role. */ - def annotatedStatementToKernel(formula: String)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): AnnotatedStatement = { + def annotatedStatementToKernel(formula: String)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): AnnotatedStatement = { val i = Parser.annotatedFOF(formula) i match case TPTP.FOFAnnotated(name, role, formula, annotations) => @@ -120,11 +123,8 @@ object KernelParser { } - private def problemToKernel(problemFile: File, md: ProblemMetadata)(using - mapAtom: (String, Int) => K.AtomicLabel, - mapTerm: (String, Int) => K.TermLabel, - mapVariable: String => K.VariableLabel - ): Problem = { + private def problemToKernel(problemFile: File, md: ProblemMetadata)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): Problem = { + val (mapAtom, mapTerm, mapVariable) = maps val file = io.Source.fromFile(problemFile) val pattern = "SPC\\s*:\\s*[A-z]{3}(_[A-z]{3})*".r val g = file.getLines() @@ -152,7 +152,7 @@ object KernelParser { * @param problemFile a file containning a tptp problem * @return a Problem object containing the data of the tptp problem in LISA representation */ - def problemToKernel(problemFile: File)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Problem = { + def problemToKernel(problemFile: File)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): Problem = { problemToKernel(problemFile, getProblemInfos(problemFile)) } @@ -160,7 +160,7 @@ object KernelParser { * @param problemFile a path to a file containing a tptp problem * @return a Problem object containing the data of the tptp problem in LISA representation */ - def problemToKernel(problemFile: String)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Problem = { + def problemToKernel(problemFile: String)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): Problem = { problemToKernel(File(problemFile)) } @@ -188,9 +188,9 @@ object KernelParser { if last.nonEmpty && last.forall(_.isDigit) && last.head != '0' then lead.mkString("$u") + "_" + last else pieces.mkString("$u") - val mapAtom: ((String, Int) => K.AtomicLabel) = (f, n) => K.ConstantAtomicLabel(sanitize(f), n) - val mapTerm: ((String, Int) => K.TermLabel) = (f, n) => K.ConstantFunctionLabel(sanitize(f), n) - val mapVariable: (String => K.VariableLabel) = f => K.VariableLabel(sanitize(f)) + val mapAtom: ((String, Int) => K.Constant) = (f, n) => K.Constant(sanitize(f), predicateType(n)) + val mapTerm: ((String, Int) => K.Constant) = (f, n) => K.Constant(sanitize(f), functionType(n)) + val mapVariable: (String => K.Variable) = f => K.Variable(sanitize(f), K.Term) /** * Given a folder containing folders containing problem (typical organisation of TPTP library) and a list of spc, diff --git a/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala b/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala index 162313eb..852c99ef 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala @@ -16,24 +16,26 @@ object ProofParser { val TPTPversion = "TPTP v8.0.0" val rand = scala.util.Random() - given mapAtom: ((String, Int) => K.AtomicLabel) = (f, n) => + type MapTriplet = ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable) + + val mapAtom: ((String, Int) => K.Expression) = (f, n) => val kind = f.head val id = f.tail if kind == 's' then - if n == 0 then K.VariableFormulaLabel(sanitize(id)) - else K.SchematicPredicateLabel(sanitize(id), n) - else if kind == 'c' then K.ConstantAtomicLabel(sanitize(id), n) + K.Variable(sanitize(id), K.predicateType(n)) + else if kind == 'c' then K.Constant(sanitize(id), K.predicateType(n)) else throw new Exception(s"Unknown kind of atomic label: $f") - given mapTerm: ((String, Int) => K.TermLabel) = (f, n) => + val mapTerm: ((String, Int) => K.Expression) = (f, n) => val kind = f.head val id = f.tail - if kind == 's' then K.SchematicFunctionLabel(sanitize(id), n) - else if kind == 'c' then K.ConstantFunctionLabel(sanitize(id), n) - else if n == 0 then K.VariableLabel(sanitize(f)) - else K.SchematicFunctionLabel(sanitize(f), n) - given mapVariable: (String => K.VariableLabel) = f => - if f.head == 'X' then K.VariableLabel(f.tail) - else K.VariableLabel(f) + if kind == 's' then K.Variable(sanitize(id), K.functionType(n)) + else if kind == 'c' then K.Constant(sanitize(id), K.functionType(n)) + else throw new Exception(s"Unknown kind of term label: $f") + val mapVariable: (String => K.Variable) = f => + if f.head == 'X' then K.Variable(f.tail, K.Term) + else K.Variable(f, K.Term) + + given maps: MapTriplet = (mapAtom, mapTerm, mapVariable) def problemToFile(fileDirectory: String, fileName: String, name: String, axioms: Seq[K.Sequent], conjecture: K.Sequent, source: String): File = { // case class Problem(file: String, domain: String, name: String, status: String, spc: Seq[String], formulas: Seq[AnnotatedStatement]) @@ -80,51 +82,44 @@ object ProofParser { def isLowerWord(s: String): Boolean = s.head.isLower && s.tail.forall(_.isLetterOrDigit) inline def quoted(s: String): String = if isLowerWord(s) then s else s"'$s'" - def termToFOFTerm(term: K.Term): FOF.Term = { - val K.Term(label, args) = term - label match - case K.ConstantFunctionLabel(id, arity) => + def termToFOFTerm(term: K.Expression): FOF.Term = { + term match { + case K.Variable(id, K.Term) => FOF.Variable(quoted("X" + id)) + case K.Constant(id, K.Term) => FOF.AtomicTerm(quoted("c" + id), Seq()) + case K.Multiapp(K.Constant(id, typ), args) => FOF.AtomicTerm(quoted("c" + id), args.map(termToFOFTerm)) - case K.SchematicFunctionLabel(id, arity) => + case K.Multiapp(K.Variable(id, typ), args) => FOF.AtomicTerm(quoted("s" + id), args.map(termToFOFTerm)) - case K.VariableLabel(id) => FOF.Variable("X" + id) + case K.Epsilon(v, f) => throw new Exception("Epsilon terms are not supported") + case _ => throw new Exception("The expression is not purely first order") + } } - def formulaToFOFFormula(formula: K.Formula): FOF.Formula = { + def formulaToFOFFormula(formula: K.Expression): FOF.Formula = { formula match - case K.AtomicFormula(label, args) => - label match - case K.equality => FOF.Equality(termToFOFTerm(args(0)), termToFOFTerm(args(1))) - case K.top => FOF.AtomicFormula("$true", Seq()) - case K.bot => FOF.AtomicFormula("$false", Seq()) - case K.ConstantAtomicLabel(id, arity) => FOF.AtomicFormula(quoted("c" + id), args.map(termToFOFTerm)) - case K.SchematicPredicateLabel(id, arity) => FOF.AtomicFormula(quoted("s" + id), args.map(termToFOFTerm)) - case K.VariableFormulaLabel(id) => FOF.AtomicFormula(quoted("s" + id), Seq()) - case K.ConnectorFormula(label, args) => - label match - case K.Neg => FOF.UnaryFormula(FOF.~, formulaToFOFFormula(args.head)) - case K.Implies => FOF.BinaryFormula(FOF.Impl, formulaToFOFFormula(args(0)), formulaToFOFFormula(args(1))) - case K.Iff => FOF.BinaryFormula(FOF.<=>, formulaToFOFFormula(args(0)), formulaToFOFFormula(args(1))) - case K.And => - if args.size == 0 then FOF.AtomicFormula("$true", Seq()) - else if args.size == 1 then formulaToFOFFormula(args(0)) - else FOF.BinaryFormula(FOF.&, formulaToFOFFormula(args(0)), formulaToFOFFormula(args(1))) - case K.Or => - if args.size == 0 then FOF.AtomicFormula("$false", Seq()) - else if args.size == 1 then formulaToFOFFormula(args(0)) - else FOF.BinaryFormula(FOF.|, formulaToFOFFormula(args(0)), formulaToFOFFormula(args(1))) - case scl: K.SchematicConnectorLabel => throw new Exception(s"Schematic connectors are unsupported") - case K.BinderFormula(label, bound, inner) => - label match - case K.Forall => FOF.QuantifiedFormula(FOF.!, Seq("X" + bound.id), formulaToFOFFormula(inner)) - case K.Exists => FOF.QuantifiedFormula(FOF.?, Seq("X" + bound.id), formulaToFOFFormula(inner)) - case K.ExistsOne => ??? + case K.equality(left, right) => + FOF.Equality(termToFOFTerm(left), termToFOFTerm(right)) + case K.top => FOF.AtomicFormula("$true", Seq()) + case K.bot => FOF.AtomicFormula("$false", Seq()) + case K.neg(f) => FOF.UnaryFormula(FOF.~, formulaToFOFFormula(f)) + case K.and(f1, f2) => FOF.BinaryFormula(FOF.&, formulaToFOFFormula(f1), formulaToFOFFormula(f2)) + case K.or(f1, f2) => FOF.BinaryFormula(FOF.|, formulaToFOFFormula(f1), formulaToFOFFormula(f2)) + case K.implies(f1, f2) => FOF.BinaryFormula(FOF.Impl, formulaToFOFFormula(f1), formulaToFOFFormula(f2)) + case K.iff(f1, f2) => FOF.BinaryFormula(FOF.<=>, formulaToFOFFormula(f1), formulaToFOFFormula(f2)) + case K.forall(K.Lambda(v, f)) => FOF.QuantifiedFormula(FOF.!, Seq(quoted("X" + v.id)), formulaToFOFFormula(f)) + case K.exists(K.Lambda(v, f)) => FOF.QuantifiedFormula(FOF.?, Seq(quoted("X" + v.id)), formulaToFOFFormula(f)) + case K.Multiapp(K.Constant(id, typ), args) => + FOF.AtomicFormula(quoted("c" + id), args.map(termToFOFTerm)) + case K.Multiapp(K.Variable(id, typ), args) => + FOF.AtomicFormula(quoted("s" + id), args.map(termToFOFTerm)) + case _ => throw new Exception("The expression is not purely first order") + } - def formulaToFOFStatement(formula: K.Formula): FOF.Statement = { + def formulaToFOFStatement(formula: K.Expression): FOF.Statement = { FOF.Logical(formulaToFOFFormula(formula)) } - def reconstructProof(file: File)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.SCProof = { + def reconstructProof(file: File)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.SCProof = { val problem = Parser.problem(io.Source.fromFile(file)) val nameMap = scala.collection.mutable.Map[String, (Int, FOF.Sequent)]() var prems = List[K.Sequent]() @@ -155,11 +150,8 @@ object ProofParser { K.SCProof(steps.reverse.toIndexedSeq, prems.reverse.toIndexedSeq) } - def annotatedStatementToProofStep(ann: FOFAnnotated, numbermap: String => Int, sequentmap: String => FOF.Sequent)(using - mapAtom: (String, Int) => K.AtomicLabel, - mapTerm: (String, Int) => K.TermLabel, - mapVariable: String => K.VariableLabel - ): Option[(K.SCProofStep, String)] = { + def annotatedStatementToProofStep(ann: FOFAnnotated, numbermap: String => Int, sequentmap: String => FOF.Sequent) + (using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): Option[(K.SCProofStep, String)] = { given (String => Int) = numbermap given (String => FOF.Sequent) = sequentmap val r = ann match { @@ -197,7 +189,7 @@ object ProofParser { } } object Term { - def unapply(ann_seq: GeneralTerm)(using mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[K.Term] = + def unapply(ann_seq: GeneralTerm)(using maps: MapTriplet): Option[K.Expression] = ann_seq match { case GeneralTerm(List(GeneralFormulaData(FOTData(term))), None) => Some(convertTermToKernel(term)) case _ => None @@ -256,7 +248,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("hyp", Seq(StrOrNum(n), StrOrNum(m)), Seq())) => if (sequent.lhs(n.toInt) == sequent.rhs(m.toInt)) then @@ -272,7 +264,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("cut", Seq(StrOrNum(n), StrOrNum(m)), Seq(t1, t2))) => val formula1 = sequentmap(t1).rhs(n.toInt) @@ -289,7 +281,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftHyp", Seq(StrOrNum(n), StrOrNum(m)), Seq())) => val left = sequent.lhs.map(convertToKernel) @@ -305,7 +297,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotNot", Seq(StrOrNum(n)), Seq(t1))) => Some((K.Weakening(convertToKernel(sequent), numbermap(t1)), name)) @@ -316,7 +308,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftAnd", Seq(StrOrNum(n)), Seq(t1))) => Some((K.Weakening(convertToKernel(sequent), numbermap(t1)), name)) @@ -327,7 +319,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotOr", Seq(StrOrNum(n)), Seq(t1))) => Some((K.Weakening(convertToKernel(sequent), numbermap(t1)), name)) @@ -338,7 +330,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotImp", Seq(StrOrNum(n)), Seq(t1))) => Some((K.Weakening(convertToKernel(sequent), numbermap(t1)), name)) @@ -350,12 +342,12 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotAnd", Seq(StrOrNum(n)), Seq(t1, t2))) => val f = sequent.lhs(n.toInt) val (a, b) = convertToKernel(f) match { - case K.ConnectorFormula(K.Neg, Seq(K.ConnectorFormula(K.And, Seq(x, y)))) => (x, y) + case K.Neg(K.And(x, y)) => (x, y) case _ => throw new Exception(s"Expected a negated conjunction, but got $f") } Some((K.LeftOr(convertToKernel(sequent), Seq(numbermap(t1), numbermap(t2)), Seq(K.Neg(a), K.Neg(b))), name)) @@ -367,12 +359,12 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftOr", Seq(StrOrNum(n)), Seq(t1, t2))) => val f = sequent.lhs(n.toInt) val (a, b) = convertToKernel(f) match { - case K.ConnectorFormula(K.Or, Seq(x, y)) => (x, y) + case K.Or(x, y) => (x, y) case _ => throw new Exception(s"Expected a disjunction, but got $f") } Some((K.LeftOr(convertToKernel(sequent), Seq(numbermap(t1), numbermap(t2)), Seq(a, b))), name) @@ -384,12 +376,12 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftImp1", Seq(StrOrNum(n)), Seq(t1, t2))) => val f = sequent.lhs(n.toInt) val (a, b) = convertToKernel(f) match { - case K.ConnectorFormula(K.Implies, Seq(x, y)) => (x, y) + case K.Implies(x, y) => (x, y) case _ => throw new Exception(s"Expected an implication, but got $f") } Some((K.LeftImplies(convertToKernel(sequent), numbermap(t1), numbermap(t2), a, b), name)) @@ -401,12 +393,12 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftImp2", Seq(StrOrNum(n)), Seq(t1, t2))) => val f = sequent.lhs(n.toInt) val (a, b) = convertToKernel(f) match { - case K.ConnectorFormula(K.Implies, Seq(x, y)) => (x, y) + case K.Implies(x, y) => (x, y) case _ => throw new Exception(s"Expected an implication, but got $f") } Some((K.LeftOr(convertToKernel(sequent), Seq(numbermap(t1), numbermap(t2)), Seq(K.Neg(a), b)), name)) @@ -418,19 +410,19 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotForall", Seq(StrOrNum(n), Term(xl)), Seq(t1))) => // x has to be a GeneralTerm representinf a variable, i.e. $fot(x) val f = sequent.lhs(n.toInt) val x = xl match - case K.Term(x: K.VariableLabel, Seq()) => x + case x: K.Variable => x case _ => throw new Exception(s"Expected a variable, but got $xl") - val (y: K.VariableLabel, phi: K.Formula) = convertToKernel(f) match { - case K.ConnectorFormula(K.Neg, Seq(K.BinderFormula(K.Forall, x, phi))) => (x, phi) + val (y: K.Variable, phi: K.Expression) = convertToKernel(f) match { + case K.Neg(K.forall(K.Lambda(x, phi))) => (x, phi) case _ => throw new Exception(s"Expected a universal quantification, but got $f") } if x == y then Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), phi, x), name)) - else Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), K.substituteVariablesInFormula(K.ConnectorFormula(K.Neg, Seq(phi)), Map(y -> xl), Seq()), x), name)) + else Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), K.substituteVariables(K.Neg(phi), Map(y -> xl)), x), name)) case _ => None } } @@ -439,19 +431,19 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftEx", Seq(StrOrNum(n), Term(xl)), Seq(t1))) => // x has to be a GeneralTerm representinf a variable, i.e. $fot(x) val f = sequent.lhs(n.toInt) val x = xl match - case K.Term(x: K.VariableLabel, Seq()) => x + case x:K.Variable => x case _ => throw new Exception(s"Expected a variable, but got $xl") - val (y: K.VariableLabel, phi: K.Formula) = convertToKernel(f) match { - case K.BinderFormula(K.Exists, x, phi) => (x, phi) + val (y: K.Variable, phi: K.Expression) = convertToKernel(f) match { + case K.Exists(K.Lambda(x, phi)) => (x, phi) case _ => throw new Exception(s"Expected an existential quantification, but got $f") } if x == y then Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), phi, x), name)) - else Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), K.substituteVariablesInFormula(phi, Map(y -> xl), Seq()), x), name)) + else Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), K.substituteVariables(phi, Map(y -> xl)), x), name)) case _ => None } } @@ -460,12 +452,12 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftForall", Seq(StrOrNum(n), Term(t)), Seq(t1))) => val f = sequent.lhs(n.toInt) val (x, phi) = convertToKernel(f) match { - case K.BinderFormula(K.Forall, x, phi) => (x, phi) + case K.Forall(K.Lambda(x, phi)) => (x, phi) case _ => throw new Exception(s"Expected a universal quantification, but got $f") } Some((K.LeftForall(convertToKernel(sequent), numbermap(t1), phi, x, t), name)) @@ -477,15 +469,15 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotEx", Seq(StrOrNum(n), Term(t)), Seq(t1))) => val f = sequent.lhs(n.toInt) val (x, phi) = convertToKernel(f) match { - case K.ConnectorFormula(K.Neg, Seq(K.BinderFormula(K.Exists, x, phi))) => (x, phi) + case K.Neg(K.Exists(K.Lambda(x, phi))) => (x, phi) case _ => throw new Exception(s"Expected a negated existential quantification, but got $f") } - Some((K.LeftForall(convertToKernel(sequent), numbermap(t1), K.ConnectorFormula(K.Neg, Seq(phi)), x, t), name)) + Some((K.LeftForall(convertToKernel(sequent), numbermap(t1), K.Neg(phi), x, t), name)) case _ => None } } @@ -494,7 +486,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("rightNot", Seq(StrOrNum(n)), Seq(t1))) => Some((K.Weakening(convertToKernel(sequent), numbermap(t1)), name)) diff --git a/lisa-utils/src/main/scala/lisa/utils/tptp/package.scala b/lisa-utils/src/main/scala/lisa/utils/tptp/package.scala index 56192dee..5c4de54e 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/package.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/package.scala @@ -19,7 +19,7 @@ sealed trait AnnotatedStatement { } } -case class AnnotatedFormula(role: String, name: String, formula: K.Formula, annotations: TPTP.Annotations) extends AnnotatedStatement +case class AnnotatedFormula(role: String, name: String, formula: K.Expression, annotations: TPTP.Annotations) extends AnnotatedStatement case class AnnotatedSequent(role: String, name: String, sequent: K.Sequent, annotations: TPTP.Annotations) extends AnnotatedStatement diff --git a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala index dcc762fa..d73d8126 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -10,7 +10,7 @@ import lisa.fol.FOL.{_, given} * General utilities for unification, substitution, and rewriting */ object UnificationUtils { - +/* extension [A](seq: Seq[A]) { /** @@ -151,7 +151,6 @@ object UnificationUtils { reference: Formula, template: Formula, takenTermVariables: Iterable[Variable] = Iterable.empty, - takenFormulaVariables: Iterable[VariableFormula] = Iterable.empty ): Option[(FormulaSubstitution, TermSubstitution)] = { val context = RewriteContext( takenTermVars = takenTermVariables.toSet, @@ -615,5 +614,5 @@ object UnificationUtils { } } } - +*/ }