diff --git a/src/main/scala/leon/solvers/string/StringSolver.scala b/src/main/scala/leon/solvers/string/StringSolver.scala index 64ee55a93e36b52f7388c1df907736af622cff0d..34aa4a96db74c2ffbe7a59cb37096b3e9a2daeaa 100644 --- a/src/main/scala/leon/solvers/string/StringSolver.scala +++ b/src/main/scala/leon/solvers/string/StringSolver.scala @@ -153,8 +153,11 @@ object StringSolver { def andThen(other: ProblemSimplicationPhase): ProblemSimplicationPhase = new ProblemSimplicationPhase { def run(p: Problem, s: Assignment) = { ProblemSimplicationPhase.this.run(p, s) match { - case Some((p, s)) => other.run(p, s) - case None => None + case Some((p, s)) => + //println("Problem before " + other.getClass.getName.substring(33) + ":" + (p, s)) + other.run(p, s) + case None => + None } } } @@ -217,6 +220,7 @@ object StringSolver { case sentence::q => obtainAssignments(q, assignments) } obtainAssignments(p, s).map(newAssignments => { + //println("Obtained new assignments: " + newAssignments) val newProblem = if(newAssignments.nonEmpty) reduceProblem(newAssignments)(p) else p (newProblem, s ++ newAssignments) }) @@ -352,6 +356,27 @@ object StringSolver { } } + /** If a left-hand side of the equation appears somewhere else, replace it by the right-hand-side of this equation */ + object PropagateEquations extends ProblemSimplicationPhase { + def run(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { + var newP = p + for((lhs, rhs) <- p if lhs.length >= 2) { + var indexInP = 0 + for(eq@(lhs2, rhs2) <- newP) { + if(!(lhs2 eq lhs) || !(rhs2 eq rhs)) { + val i = lhs2.indexOfSlice(lhs) + if(i != -1) { + val res = (lhs2.take(i) ++ Seq(Left(rhs)) ++ lhs2.drop(i + lhs.size), rhs2) + newP = newP.updated(indexInP, res) + } + } + indexInP += 1 + } + } + Some((newP, s)) + } + } + /** returns a simplified version of the problem. If it is not satisfiable, returns None. */ val simplifyProblem: ProblemSimplicationPhase = { loopUntilConvergence(DistinctEquation andThen @@ -367,7 +392,7 @@ object StringSolver { /** Composition of simplifyProblem and noLeftRightConstants */ val forwardStrategy = - loopUntilConvergence(simplifyProblem andThen noLeftRightConstants andThen PropagateMiddleConstants) + loopUntilConvergence(simplifyProblem andThen noLeftRightConstants andThen PropagateMiddleConstants andThen PropagateEquations) /** Solves the equation x1x2x3...xn = CONSTANT diff --git a/src/test/scala/leon/integration/solvers/StringSolverSuite.scala b/src/test/scala/leon/integration/solvers/StringSolverSuite.scala index f7def0be36f864e9bd8666b03d0a9764a57ad728..bb99cbb6482734c80c4565f14a29b773f5faab6c 100644 --- a/src/test/scala/leon/integration/solvers/StringSolverSuite.scala +++ b/src/test/scala/leon/integration/solvers/StringSolverSuite.scala @@ -7,51 +7,80 @@ import leon.solvers.string.StringSolver import leon.purescala.Common.FreshIdentifier import leon.purescala.Common.Identifier import scala.collection.mutable.{HashMap => MMap} +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import org.scalatest.concurrent.Timeouts +import org.scalatest.concurrent.ScalaFutures +import org.scalatest.time.SpanSugar._ +import org.scalatest.FunSuite +import org.scalatest.concurrent.Timeouts +import org.scalatest.concurrent.ScalaFutures +import org.scalatest.time.SpanSugar._ /** * @author Mikael */ -class StringSolverSuite extends FunSuite with Matchers { - val k = "xyzuvw".toSeq.map((k: Char) => k -> FreshIdentifier(k.toString)).toMap - lazy val x = k('x') - lazy val y = k('y') - lazy val z = k('z') - lazy val u = k('u') - lazy val v = k('v') - lazy val w = k('w') +class StringSolverSuite extends FunSuite with Matchers with ScalaFutures { + val k = new MMap[String, Identifier] + + val x = FreshIdentifier("x") + val y = FreshIdentifier("y") + val z = FreshIdentifier("z") + val u = FreshIdentifier("u") + val v = FreshIdentifier("v") + val w = FreshIdentifier("w") + k ++= List("x" -> x, "y" -> y, "z" -> z, "u" -> u, "v" -> v, "w" -> w) implicit class EquationMaker(lhs: String) { - def convertStringToStringFrom(s: String) = { - s.toList.map((c: Char) => (k.get(c) match {case Some(v) => Right(v) case _ => Left(c.toString) }): StringSolver.StringFormToken) + def convertStringToStringFrom(s: String)(implicit idMap: MMap[String, Identifier]): StringSolver.StringForm = { + for(elem <- s.split("\\+").toList) yield { + if(elem.startsWith("\"")) { + Left(elem.substring(1, elem.length - 1)) + } else if(elem(0).isDigit) { + Left(elem) + } else { + val id = idMap.getOrElse(elem, { + val res = FreshIdentifier(elem) + idMap += elem -> res + res + }) + Right(id) + } + } } - def ===(rhs: String): StringSolver.Equation = { + def ===(rhs: String)(implicit k: MMap[String, Identifier]): StringSolver.Equation = { (convertStringToStringFrom(lhs), rhs) } - def ====(rhs: String): StringSolver.GeneralEquation = { + def ====(rhs: String)(implicit k: MMap[String, Identifier]): StringSolver.GeneralEquation = { (convertStringToStringFrom(lhs), convertStringToStringFrom(rhs)) } } import StringSolver._ + def m = MMap[String, Identifier]() + test("simplifyProblem"){ + implicit val kk = k simplifyProblem(List("x" === "1"), Map()) should equal (Some((Nil, Map(x -> "1")))) simplifyProblem(List("y" === "23"), Map()) should equal (Some((Nil, Map(y -> "23")))) simplifyProblem(List("1" === "1"), Map()) should equal (Some((Nil, Map()))) simplifyProblem(List("2" === "1"), Map()) should equal (None) - simplifyProblem(List("x" === "1", "yx" === "12"), Map()) should equal (Some((List("y1" === "12"), Map(x -> "1")))) + simplifyProblem(List("x" === "1", "y+x" === "12"), Map()) should equal (Some((List("y+1" === "12"), Map(x -> "1")))) } test("noLeftRightConstants") { + implicit val kk = k noLeftRightConstants(List("x" === "1"), Map()) should equal (Some((List("x" === "1"), Map()))) - noLeftRightConstants(List("y2" === "12"), Map()) should equal (Some((List("y" === "1"), Map()))) - noLeftRightConstants(List("3z" === "31"), Map()) should equal (Some((List("z" === "1"), Map()))) - noLeftRightConstants(List("1u2" === "1, 2"), Map()) should equal (Some((List("u" === ", "), Map()))) + noLeftRightConstants(List("y+2" === "12"), Map()) should equal (Some((List("y" === "1"), Map()))) + noLeftRightConstants(List("3+z" === "31"), Map()) should equal (Some((List("z" === "1"), Map()))) + noLeftRightConstants(List("1+u+2" === "1, 2"), Map()) should equal (Some((List("u" === ", "), Map()))) } test("forwardStrategy") { - forwardStrategy(List("x3" === "123", "xy" === "1245"), Map()) should equal (Some((Nil, Map(x -> "12", y -> "45")))) - forwardStrategy(List("yz" === "4567", "x3" === "123", "xy" === "1245"), Map()) should equal (Some((Nil, Map(x -> "12", y -> "45", z -> "67")))) + implicit val kk = k + forwardStrategy(List("x+3" === "123", "x+y" === "1245"), Map()) should equal (Some((Nil, Map(x -> "12", y -> "45")))) + forwardStrategy(List("y+z" === "4567", "x+3" === "123", "x+y" === "1245"), Map()) should equal (Some((Nil, Map(x -> "12", y -> "45", z -> "67")))) } test("occurrences") { @@ -82,32 +111,36 @@ class StringSolverSuite extends FunSuite with Matchers { test("solve switch") { - solve(List("xy" === "1234", "yx" === "1234")).toSet should equal (Set(Map(x -> "1234", y -> ""), Map(x -> "", y -> "1234"))) - solve(List("xy" === "1234", "yx" === "3412")).toList should equal (List(Map(x -> "12", y -> "34"))) - solve(List("xy" === "1234", "yx" === "4123")).toList should equal (List(Map(x -> "123", y -> "4"))) - solve(List("xy" === "1234", "yx" === "2341")).toList should equal (List(Map(x -> "1", y -> "234"))) + implicit val kk = k + solve(List("x+y" === "1234", "y+x" === "1234")).toSet should equal (Set(Map(x -> "1234", y -> ""), Map(x -> "", y -> "1234"))) + solve(List("x+y" === "1234", "y+x" === "3412")).toList should equal (List(Map(x -> "12", y -> "34"))) + solve(List("x+y" === "1234", "y+x" === "4123")).toList should equal (List(Map(x -> "123", y -> "4"))) + solve(List("x+y" === "1234", "y+x" === "2341")).toList should equal (List(Map(x -> "1", y -> "234"))) } test("solve inner") { - solve(List("x2y" === "123")).toList should equal (List(Map(x -> "1", y -> "3"))) - solve(List("x2yz" === "123")).toSet should equal (Set(Map(x -> "1", y -> "3", z -> ""), Map(x -> "1", y -> "", z -> "3"))) - solve(List("x2y" === "12324")).toSet should equal (Set(Map(x -> "1", y -> "324"), Map(x -> "123", y -> "4"))) + implicit val kk = k + solve(List("x+2+y" === "123")).toList should equal (List(Map(x -> "1", y -> "3"))) + solve(List("x+2+y+z" === "123")).toSet should equal (Set(Map(x -> "1", y -> "3", z -> ""), Map(x -> "1", y -> "", z -> "3"))) + solve(List("x+2+y" === "12324")).toSet should equal (Set(Map(x -> "1", y -> "324"), Map(x -> "123", y -> "4"))) } test("isTransitivelyBounded") { + implicit val kk = k isTransitivelyBounded(List("1" ==== "2")) should be(true) isTransitivelyBounded(List("2" ==== "2")) should be(true) - isTransitivelyBounded(List("x2" ==== "2")) should be(true) - isTransitivelyBounded(List("x2" ==== "1")) should be(true) - isTransitivelyBounded(List("x2" ==== "2x")) should be(false) - isTransitivelyBounded(List("x2" ==== "2y")) should be(false) - isTransitivelyBounded(List("x2" ==== "2y", "y" ==== "1")) should be(true) - isTransitivelyBounded(List("x2" ==== "2x", "x" ==== "1")) should be(true) - isTransitivelyBounded(List("xyz" ==== "1234", "uv" ==== "y42x")) should be(true) + isTransitivelyBounded(List("x+2" ==== "2")) should be(true) + isTransitivelyBounded(List("x+2" ==== "1")) should be(true) + isTransitivelyBounded(List("x+2" ==== "2+x")) should be(false) + isTransitivelyBounded(List("x+2" ==== "2+y")) should be(false) + isTransitivelyBounded(List("x+2" ==== "2+y", "y" ==== "1")) should be(true) + isTransitivelyBounded(List("x+2" ==== "2+x", "x" ==== "1")) should be(true) + isTransitivelyBounded(List("x+y+z" ==== "1234", "u+v" ==== "y+42+x")) should be(true) } test("solveGeneralProblem") { - solveGeneralProblem(List("xy" ==== "12", "uv" ==== "yx")).toSet should equal ( + implicit val kk = k + solveGeneralProblem(List("x+y" ==== "12", "u+v" ==== "y+x")).toSet should equal ( Set( Map(x -> "", y -> "12", u -> "", v -> "12"), Map(x -> "", y -> "12", u -> "1", v -> "2"), @@ -123,8 +156,9 @@ class StringSolverSuite extends FunSuite with Matchers { } test("constantPropagate") { + implicit val kk = k val complexString = "abcdefmlkjsdqfmlkjqezpijbmkqjsdfmijzmajmpqjmfldkqsjmkj" - solve(List("w5xyzuv" === (complexString+"5"))).toList should equal ( + solve(List("w+5+x+y+z+u+v" === (complexString+"5"))).toList should equal ( List(Map(w -> complexString, x -> "", y -> "", @@ -134,60 +168,42 @@ class StringSolverSuite extends FunSuite with Matchers { } test("constantPropagate2") { + implicit val kk = k val complexString = "abcdefmlkjsdqfmlkjqezpijbmkqjsdfmijzmajmpqjmfldkqsjmkj" val complexString2 = complexString.reverse val complexString3 = "flmqmslkjdqfmleomijgmlkqsjdmfijmqzoijdfmlqksjdofijmez" - solve(List("w5x5z" === (complexString+"5" + complexString2 + "5" + complexString3))).toList should equal ( + solve(List("w+5+x+5+z" === (complexString+"5" + complexString2 + "5" + complexString3))).toList should equal ( List(Map(w -> complexString, x -> complexString2, z -> complexString3))) } - def makeSf(lhs: String)(implicit idMap: MMap[String, Identifier]): StringForm = { - for(elem <- lhs.split("\\+").toList) yield { - if(elem.startsWith("\"")) { - Left(elem.substring(1, elem.length - 1)) - } else { - val id = idMap.getOrElse(elem, { - val res = FreshIdentifier(elem) - idMap += elem -> res - res - }) - Right(id) - } - } - } test("ListInt") { implicit val idMap = MMap[String, Identifier]() - val lhs1 = makeSf("""const8+const4+"12"+const5+const+"-1"+const1+const3+const2+const6+const9""") - val lhs2 = makeSf("""const8+const4+"1"+const5+const3+const6+const9""") - val lhs3 = makeSf("""const8+const7+const9""") - val rhs1 = "(12, -1)" - val rhs2 = "(1)" - val rhs3 = "()" - val problem = List((lhs1, rhs1), (lhs2, rhs2), (lhs3, rhs3)) + val problem = List( + """const8+const4+"12"+const5+const+"-1"+const1+const3+const2+const6+const9""" === "(12, -1)", + """const8+const4+"1"+const5+const3+const6+const9""" === "(1)", + """const8+const7+const9""" === "()") solve(problem) should not be 'empty } test("ListInt as List(...)") { implicit val idMap = MMap[String, Identifier]() - val lhs1 = makeSf("const8+const7+const9") - val rhs1 = "List()" - val lhs2 = makeSf("""const8+const4+"1"+const5+const3+const6+const9""") - val rhs2 = "List(1)" - val lhs3 = makeSf("""const8+const4+"12"+const5+const+"-1"+const1+const3+const2+const6+const9""") - val rhs3 = "List(12, -1)" - val problem = List((lhs1, rhs1), (lhs2, rhs2), (lhs3, rhs3)) + val problem = List("const8" === "List(", + "const8+const7+const9" === "List()", + """const8+const4+"1"+const5+const3+const6+const9""" === "List(1)", + """const8+const4+"12"+const5+const+"-1"+const1+const3+const2+const6+const9""" === "List(12, -1)") val solution = solve(problem) solution should not be 'empty val firstSolution = solution(0) + println("First solution " + firstSolution) firstSolution(idMap("const8")) should equal("List(") + firstSolution(idMap("const4")) should equal("") } test("solveJadProblem") { val lhs = """const38+const34+const7+"T1"+const8+const3+const9+const5+"5"+const6+const10+const35+const30+const13+"T1"+const14+const31+const30+const7+"T2"+const8+const2+const9+const4+const10+const31+const30+const25+"T1"+const26+"Push"+const27+const20+"5"+const21+const28+const22+const29+const31+const30+const13+"T2"+const14+const31+const30+const25+"T2"+const26+"Pop"+const27+const19+const28+const23+"5"+const24+const29+const31+const33+const32+const32+const32+const32+const32+const36+const39=="T1: call Push(5)""" implicit val idMap = MMap[String, Identifier]() - val lhsSf = makeSf(lhs) val expected = """T1: call Push(5) T1: internal @@ -196,9 +212,19 @@ T1: ret Push(5) T2: internal T2: ret Pop() -> 5""" - val problem: Problem = List((lhsSf.toList, expected)) + val problem: Problem = List(lhs === expected) println("Problem to solve:" + StringSolver.renderProblem(problem)) solve(problem) should not be 'empty } + + test("SolvePropagateEquationProblem") { + implicit val idMap = MMap[String, Identifier]() + val problem = List( + "a+b+c+d" === "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", + "k+a+b+c+d" === "212345678912345678901234567890123456789012345678912345678901234567890123456789012345678901234567891" + ) + val p = Future { solve(problem) } + assert(p.isReadyWithin(2 seconds), "Could not solve propagate") + } } \ No newline at end of file