From 92493830c0c3c0991a1538329bf66bf3d66ca821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Mayer?= <a-mikmay@microsoft.com> Date: Thu, 19 Nov 2015 17:36:31 +0100 Subject: [PATCH] Converted foldLefts and for comprehensions to tailrec methods, better suited for the task --- .../leon/solvers/string/StringSolver.scala | 104 +++++++++--------- .../leon/synthesis/rules/StringRender.scala | 45 ++++---- 2 files changed, 75 insertions(+), 74 deletions(-) diff --git a/src/main/scala/leon/solvers/string/StringSolver.scala b/src/main/scala/leon/solvers/string/StringSolver.scala index 1276a6813..b953b8cb1 100644 --- a/src/main/scala/leon/solvers/string/StringSolver.scala +++ b/src/main/scala/leon/solvers/string/StringSolver.scala @@ -8,6 +8,7 @@ import leon.utils.Interruptible import leon.LeonContext import scala.collection.mutable.ListBuffer import vanuatoo.Pattern +import scala.annotation.tailrec /** * @author Mikael @@ -25,14 +26,14 @@ object StringSolver { type Problem = List[Equation] /** Evaluates a String form. Requires the solution to have an assignment to all identifiers. */ - def evaluate(s: Assignment, acc: StringBuffer = new StringBuffer(""))(sf: StringForm): String = sf match { + @tailrec def evaluate(s: Assignment, acc: StringBuffer = new StringBuffer(""))(sf: StringForm): String = sf match { case Nil => acc.toString case Left(constant)::q => evaluate(s, acc append constant)(q) case Right(identifier)::q => evaluate(s, acc append s(identifier))(q) } /** Assigns the new values to the equations and simplify them at the same time. */ - def reduceStringForm(s: Assignment, acc: ListBuffer[StringFormToken] = ListBuffer())(sf: StringForm): StringForm = sf match { + @tailrec def reduceStringForm(s: Assignment, acc: ListBuffer[StringFormToken] = ListBuffer())(sf: StringForm): StringForm = sf match { case Nil => acc.toList case (l@Left(constant))::(l2@Left(constant2))::q => reduceStringForm(s, acc)(Left(constant + constant2)::q) case (l@Left(constant))::(r2@Right(id))::q => @@ -65,57 +66,57 @@ object StringSolver { /** Concatenates constants together */ def reduceStringForm(s: StringForm): StringForm = { - def rec(s: StringForm, acc: StringForm): StringForm = s match { - case Nil => acc + @tailrec def rec(s: StringForm, acc: ListBuffer[StringFormToken] = ListBuffer()): StringForm = s match { + case Nil => acc.toList case Left(c)::Left(d)::q => rec(Left(c+d)::q, acc) - case a::q => rec(q, a::acc) + case a::q => rec(q, acc += a) } - rec(s, Nil).reverse + rec(s) } /** returns a simplified version of the problem. If it is not satisfiable, returns None. */ - def simplifyProblem(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { + @tailrec def simplifyProblem(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { // Invariant: Every assigned var does not appear in the problem. - // 1. Merge constant in string forms - val constantMerge: Option[Problem] = ((Option(List[Equation]()) /: p){ - case (None, eq) => None - case (Some(building_problem), (sf, rhs)) => Some((reduceStringForm(sf), rhs)::building_problem) - }).map(_.reverse) - - if(constantMerge == None) return None + @tailrec def mergeConstants(p: Problem, acc: ListBuffer[Equation] = ListBuffer()): Option[Problem] = p match { + case Nil => Some(acc.toList) + case (sf, rhs)::q => mergeConstants(q, acc += ((reduceStringForm(sf), rhs))) + } // 2. Unsat if Const1 = Const2 but constants are different. // 2bis. if Const1 = Const2 and constants are same, remove equality. - val simplified = (((Some(Nil): Option[Problem]) /: constantMerge.get){ - case (None, eq) => None - case (Some(building_problem), (Nil, rhs)) => if("" != rhs) None else Some(building_problem) - case (Some(building_problem), (List(Left(c)), rhs)) => if(c != rhs) None else Some(building_problem) - case (Some(building_problem), sentence) => Some(sentence::building_problem) - }).map(_.reverse) - - if(simplified == None) return None + @tailrec def simplifyConstants(p: Problem, acc: ListBuffer[Equation] = ListBuffer()): Option[Problem] = p match { + case Nil => Some(acc.toList) + case (Nil, rhs)::q => if("" != rhs) None else simplifyConstants(q, acc) + case (List(Left(c)), rhs)::q => if(c != rhs) None else simplifyConstants(q, acc) + case sentence::q => simplifyConstants(q, acc += sentence) + } // 3. Get new assignments from equations such as id = Const. - val newAssignments = (Option(Map[Identifier, String]()) /: p){ - case (None, _) => None - case (Some(assignments), (List(Right(id)), rhs)) => + @tailrec def obtainAssignments(p: Problem, assignments: Assignment = Map()): Option[Assignment] = p match { + case Nil => Some(assignments) + case (List(Right(id)), rhs)::q => assignments.get(id) match { // It was assigned already. case Some(v) => - if(rhs != v) None else Some(assignments) - case _ => - Some(assignments + (id -> rhs)) + if(rhs != v) None else obtainAssignments(q, assignments) + case None => + obtainAssignments(q, assignments + (id -> rhs)) } - case (s@Some(assignments), sentence) => s + case sentence::q => obtainAssignments(q, assignments) } + val simplifiedOpt = mergeConstants(p) + .flatMap(simplifyConstants(_)) - if(newAssignments == None) return None - - // 4. If there are new assignments, forward them to the equation and relaunch the simplification. - if(newAssignments.get.nonEmpty) - simplifyProblem(reduceProblem(newAssignments.get)(simplified.get), s ++ newAssignments.get) - else { // No new assignments, simplification over. - Option((simplified.get, s)) + simplifiedOpt match { + case None => None + case Some(simplified) => + // 4. If there are new assignments, forward them to the equation and relaunch the simplification. + val newAssignmentsOpt = obtainAssignments(simplified) + newAssignmentsOpt match { + case Some(newAssignments) if newAssignments.nonEmpty => + simplifyProblem(reduceProblem(newAssignments)(simplified), s ++ newAssignments) + case _ => Some((simplified, s)) + } } } @@ -128,28 +129,27 @@ object StringSolver { // Removes all constants from the left and right of the equations def noLeftRightConstants(p: Problem): Option[Problem] = { - val noLeftConstants: Option[Problem] = ((Option(List[Equation]()) /: p){ - case (None, eq) => None - case (Some(building_problem), (Left(constant)::q, rhs)) => + @tailrec def removeLeftconstants(p: Problem, acc: ListBuffer[Equation] = ListBuffer()): Option[Problem] = p match { + case Nil => Some(acc.toList) + case ((Left(constant)::q, rhs))::ps => if(rhs.startsWith(constant)) { - Some((q, rhs.substring(constant.length))::building_problem) + removeLeftconstants(ps, acc += ((q, rhs.substring(constant.length)))) } else None - case (Some(building_problem), (q, rhs)) => - Some((q, rhs)::building_problem) - }).map(_.reverse) - if(noLeftConstants == None) return None + case (t@(q, rhs))::ps => + removeLeftconstants(ps, acc += t) + } - val noRightConstants: Option[Problem] = ((Option(List[Equation]()) /: noLeftConstants.get){ - case (None, eq) => None - case (Some(building_problem), (ConsReverse(q, Left(constant)), rhs)) => + @tailrec def removeRightConstants(p: Problem, acc: ListBuffer[Equation] = ListBuffer()): Option[Problem] = p match { + case Nil => Some(acc.toList) + case (ConsReverse(q, Left(constant)), rhs)::ps => if(rhs.endsWith(constant)) { - Some((q, rhs.substring(0, rhs.length - constant.length))::building_problem) + removeRightConstants(ps, acc += ((q, rhs.substring(0, rhs.length - constant.length)))) } else None - case (Some(building_problem), (q, rhs)) => - Some((q, rhs)::building_problem) - }).map(_.reverse) + case (t@(q, rhs))::ps => + removeRightConstants(ps, acc += t) + } - noRightConstants + removeLeftconstants(p).flatMap(removeRightConstants(_)) } /** Composition of simplifyProble and noLeftRightConstants */ diff --git a/src/main/scala/leon/synthesis/rules/StringRender.scala b/src/main/scala/leon/synthesis/rules/StringRender.scala index d2cfd2060..f32546a02 100644 --- a/src/main/scala/leon/synthesis/rules/StringRender.scala +++ b/src/main/scala/leon/synthesis/rules/StringRender.scala @@ -23,6 +23,7 @@ import leon.evaluators.DefaultEvaluator import leon.solvers.Model import leon.solvers.ModelBuilder import leon.solvers.string.StringSolver +import scala.annotation.tailrec /** A template generator for a given type tree. @@ -113,7 +114,7 @@ case object StringRender extends Rule("StringRender") { Stream.Empty } } - import StringSolver.{StringFormToken, StringForm, Problem => SProblem} + import StringSolver.{StringFormToken, StringForm, Problem => SProblem, Equation} /** Converts an expression to a stringForm, suitable for StringSolver */ def toStringForm(e: Expr, acc: List[StringFormToken] = Nil): Option[StringForm] = e match { @@ -137,25 +138,26 @@ case object StringRender extends Rule("StringRender") { val e = new DefaultEvaluator(ctx, p) var invalid = false - val equations: SProblem = for{ - (in, rhsExpr) <- examples.valids.collect{ case InOutExample(in, out) => (in, out) }.toList - if !invalid || rhsExpr.length != 1 - model = new ModelBuilder - _ = model ++= inputs.zip(in) - result = e.eval(template, model.result()).result - _ = invalid ||= result.isEmpty - if !invalid - sf = toStringForm(result.get) - rhs = toStringLiteral(rhsExpr.head) - _ = invalid ||= sf.isEmpty || rhs.isEmpty - if !invalid - } yield { - (sf.get, rhs.get) + + @tailrec def gatherEquations(s: List[InOutExample], acc: ListBuffer[Equation] = ListBuffer()): Option[SProblem] = s match { + case Nil => Some(acc.toList) + case InOutExample(in, rhExpr)::q => + if(rhExpr.length == 1) { + val model = new ModelBuilder + model ++= inputs.zip(in) + e.eval(template, model.result()).result match { + case None => None + case Some(sfExpr) => + val sf = toStringForm(sfExpr) + val rhs = toStringLiteral(rhExpr.head) + if(sf.isEmpty || rhs.isEmpty) None + else gatherEquations(q, acc += ((sf.get, rhs.get))) + } + } else None } - if(!invalid) { - StringSolver.solve(equations) - } else { - Stream.empty + gatherEquations(examples.valids.collect{ case io:InOutExample => io }.toList) match { + case Some(problem) => StringSolver.solve(problem) + case None => Stream.empty } } @@ -176,7 +178,8 @@ case object StringRender extends Rule("StringRender") { * If the input is constant * - just a variable to compute a constant. * If there are several variables - * - All possible ways of linearly unbuilding the structure + * - All possible ways of linearly unbuilding the structure. + * - Or maybe try to infer top-bottom the order of variables from examples? * if a variable is a primitive * - Introduce an ordered string containing the content. * @@ -184,8 +187,6 @@ case object StringRender extends Rule("StringRender") { * If there are multiple solutions, we generate one deeper example and ask the user which one should be better. */ - - object StringTemplateGenerator extends TypedTemplateGenerator(StringType) val booleanTemplate = (a: Identifier) => StringTemplateGenerator(Hole => IfExpr(Variable(a), Hole, Hole)) -- GitLab