From baa60750b30f0142b2fc2519432cc1036d8dd4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Mayer?= <a-mikmay@microsoft.com> Date: Wed, 25 Nov 2015 14:07:26 +0100 Subject: [PATCH] Added support for stream of templates. Now the Boolean problem is solved. Removed duplicate methods already present in StreamUtils.scala Added benchmark JsonRender.scala --- .../leon/programsets/DirectProgramSet.scala | 5 +- .../leon/programsets/JoinProgramSet.scala | 55 +++----- .../scala/leon/programsets/ProgramSet.scala | 4 +- .../leon/programsets/UnionProgramset.scala | 20 +-- .../scala/leon/purescala/PrettyPrinter.scala | 13 +- .../scala/leon/purescala/ScalaPrinter.scala | 8 ++ src/main/scala/leon/purescala/Types.scala | 2 +- src/main/scala/leon/synthesis/Rules.scala | 4 +- .../leon/synthesis/rules/StringRender.scala | 123 ++++++++++++------ src/main/scala/leon/utils/StreamUtils.scala | 32 +++++ .../solvers/StringSolverSuite.scala | 4 +- testcases/stringrender/JsonRender.scala | 42 ++++++ 12 files changed, 210 insertions(+), 102 deletions(-) create mode 100644 testcases/stringrender/JsonRender.scala diff --git a/src/main/scala/leon/programsets/DirectProgramSet.scala b/src/main/scala/leon/programsets/DirectProgramSet.scala index e9d45d4f6..1a08a98dd 100644 --- a/src/main/scala/leon/programsets/DirectProgramSet.scala +++ b/src/main/scala/leon/programsets/DirectProgramSet.scala @@ -6,10 +6,13 @@ import purescala.Extractors._ import purescala.Constructors._ import purescala.Types._ +object DirectProgramSet { + def apply[T](p: Stream[T]): DirectProgramSet[T] = new DirectProgramSet(p) +} /** * @author Mikael */ -class DirectProgramSet(val p: Stream[Expr]) extends ProgramSet { +class DirectProgramSet[T](val p: Stream[T]) extends ProgramSet[T] { def programs = p } \ No newline at end of file diff --git a/src/main/scala/leon/programsets/JoinProgramSet.scala b/src/main/scala/leon/programsets/JoinProgramSet.scala index 9f5045050..e12442c9e 100644 --- a/src/main/scala/leon/programsets/JoinProgramSet.scala +++ b/src/main/scala/leon/programsets/JoinProgramSet.scala @@ -1,4 +1,5 @@ -package leon.programsets +package leon +package programsets import leon.purescala import purescala.Expressions._ @@ -6,46 +7,30 @@ import purescala.Extractors._ import purescala.Constructors._ import purescala.Types._ +/** + * @author Mikael + */ object JoinProgramSet { - def cantorPair(x: Int, y: Int): Int = { - ((x + y) * (x + y + 1)) / 2 + y + def apply[T, U1, U2](sets: (ProgramSet[U1], ProgramSet[U2]), recombine: (U1, U2) => T): Join2ProgramSet[T, U1, U2] = { + new Join2ProgramSet(sets, recombine) } - - - def reverseCantorPair(z: Int): (Int, Int) = { - val t = Math.floor((-1.0f + Math.sqrt(1.0f + 8.0f * z))/2.0f).toInt; - val x = t * (t + 3) / 2 - z; - val y = z - t * (t + 1) / 2; - (x, y) + def apply[T, U](sets: Seq[ProgramSet[U]], recombine: Seq[U] => T): JoinProgramSet[T, U] = { + new JoinProgramSet(sets, recombine) } - - /** Combines two streams into one using cantor's unparining function. - * Ensures that the stream terminates if both streams terminate */ - def combine[A, B](sa: Stream[A], sb: Stream[B]): Stream[(A, B)] = { - def combineRec[A, B](sa: Stream[A], sb: Stream[B])(i: Int): Stream[(A, B)] = { - val (x, y) = reverseCantorPair(i) - if(!sa.isDefinedAt(x) && !sb.isDefinedAt(y)) Stream.Empty - else if(sa.isDefinedAt(x) && sb.isDefinedAt(y)) (sa(x), sb(y)) #:: combineRec(sa, sb)(i+1) - else combineRec(sa, sb)(i+1) - } - combineRec(sa, sb)(0) + def direct[U1, U2](sets: (ProgramSet[U1], ProgramSet[U2])): Join2ProgramSet[(U1, U2), U1, U2] = { + new Join2ProgramSet(sets, (u1: U1, u2: U2) => (u1, u2)) } - - /** Combines an arbitrary number of streams together. */ - def combine[A](sa: Seq[Stream[A]]): Stream[Seq[A]] = { - if(sa.length == 0) Stream(Seq()) else - if(sa.length == 1) sa(0).map(k => Seq(k)) else - if(sa.length == 2) combine(sa(0), sa(1)).map(k => Seq(k._1, k._2)) else // Optimization - combine(combine(sa.take(sa.length / 2)), combine(sa.drop(sa.length / 2))).map(k => k._1 ++ k._2) + def direct[U](sets: Seq[ProgramSet[U]]): JoinProgramSet[Seq[U], U] = { + new JoinProgramSet(sets, (id: Seq[U]) => id) } } +class Join2ProgramSet[T, U1, U2](sets: (ProgramSet[U1], ProgramSet[U2]), recombine: (U1, U2) => T) extends ProgramSet[T] { + def programs: Stream[T] = utils.StreamUtils.cartesianProduct(sets._1.programs, sets._2.programs).map(recombine.tupled) +} + +class JoinProgramSet[T, U](sets: Seq[ProgramSet[U]], recombine: Seq[U] => T) extends ProgramSet[T] { + def programs: Stream[T] = utils.StreamUtils.cartesianProduct(sets.map(_.programs)).map(recombine) +} -/** - * @author Mikael - */ -class JoinProgramSet(sets: Seq[ProgramSet], recombine: Seq[Expr] => Expr) extends ProgramSet { - - def programs: Stream[Expr] = JoinProgramSet.combine(sets.map(_.programs)).map(recombine) -} \ No newline at end of file diff --git a/src/main/scala/leon/programsets/ProgramSet.scala b/src/main/scala/leon/programsets/ProgramSet.scala index 453aeb4bc..04025f016 100644 --- a/src/main/scala/leon/programsets/ProgramSet.scala +++ b/src/main/scala/leon/programsets/ProgramSet.scala @@ -9,6 +9,6 @@ import purescala.Types._ /** * @author Mikael */ -abstract class ProgramSet { - def programs: Stream[Expr] +abstract class ProgramSet[T] { + def programs: Stream[T] } \ No newline at end of file diff --git a/src/main/scala/leon/programsets/UnionProgramset.scala b/src/main/scala/leon/programsets/UnionProgramset.scala index 8fdc39d4c..2e089b6bb 100644 --- a/src/main/scala/leon/programsets/UnionProgramset.scala +++ b/src/main/scala/leon/programsets/UnionProgramset.scala @@ -1,4 +1,5 @@ -package leon.programsets +package leon +package programsets import leon.purescala import purescala.Expressions._ @@ -7,23 +8,14 @@ import purescala.Constructors._ import purescala.Types._ object UnionProgramSet { - - /** Interleaves two streams, ensuring that if both terminate, the resulting stream terminates. */ - def combine[A](sa: Stream[A], sb: Stream[A]): Stream[A] = { - if(sa.isEmpty) sb else if(sb.isEmpty) sa else sa.head #:: combine(sb, sa.tail) - } - - /** Interleaves an arbitrary number of streams, ensuring that if all of them terminate, the resulting stream terminates. */ - def combine[A](s: Seq[Stream[A]]): Stream[A] = { - if(s.isEmpty) Stream.Empty else - if(s.head.isEmpty) combine(s.tail) else - s.head.head #:: combine(s.tail ++ Seq(s.head.tail)) + def apply[T](sets: Seq[ProgramSet[T]]): UnionProgramSet[T] = { + new UnionProgramSet(sets) } } /** * @author Mikael */ -class UnionProgramset(sets: Seq[ProgramSet]) extends ProgramSet { - def programs = UnionProgramSet.combine(sets.map(_.programs)) +class UnionProgramSet[T](sets: Seq[ProgramSet[T]]) extends ProgramSet[T] { + def programs = utils.StreamUtils.interleave(sets.map(_.programs)) } \ No newline at end of file diff --git a/src/main/scala/leon/purescala/PrettyPrinter.scala b/src/main/scala/leon/purescala/PrettyPrinter.scala index dd62e8b85..f77fc57bd 100644 --- a/src/main/scala/leon/purescala/PrettyPrinter.scala +++ b/src/main/scala/leon/purescala/PrettyPrinter.scala @@ -50,6 +50,8 @@ class PrettyPrinter(opts: PrinterOptions, p"${df.id}" } } + + private val dbquote = "\"" def pp(tree: Tree)(implicit ctx: PrinterContext): Unit = { @@ -167,7 +169,7 @@ class PrettyPrinter(opts: PrinterOptions, case IntegerToString(expr) => p"StrOps.bigIntToString($expr)" case CharToString(expr) => p"StrOps.charToString($expr)" case RealToString(expr) => p"StrOps.realToString($expr)" - case StringConcat(lhs, rhs) => p"$lhs + $rhs" + case StringConcat(lhs, rhs) => optP { p"$lhs + $rhs" } case SubString(expr, start, end) => p"StrOps.substring($expr, $start, $end)" case StringLength(expr) => p"StrOps.length($expr)" @@ -180,7 +182,13 @@ class PrettyPrinter(opts: PrinterOptions, case CharLiteral(v) => p"$v" case BooleanLiteral(v) => p"$v" case UnitLiteral() => p"()" - case StringLiteral(v) => p""""$v"""" + case StringLiteral(v) => + if(v.indexOf("\n") != -1 && v.indexOf("\"\"\"") == -1) { + p"$dbquote$dbquote$dbquote$v$dbquote$dbquote$dbquote" + } else { + val escaped = v.replaceAll(dbquote, "\\\\\"").replaceAll("\n","\\n").replaceAll("\r","\\r") + p"$dbquote$escaped$dbquote" + } case GenericValue(tp, id) => p"$tp#$id" case Tuple(exprs) => p"($exprs)" case TupleSelect(t, i) => p"$t._$i" @@ -623,6 +631,7 @@ class PrettyPrinter(opts: PrinterOptions, case (_, Some(_: Require)) => false case (_, Some(_: Definition)) => false case (_, Some(_: MatchExpr | _: MatchCase | _: Let | _: LetDef | _: IfExpr | _ : CaseClass | _ : Lambda)) => false + case (ex: StringConcat, Some(_: StringConcat)) => false case (b1 @ BinaryMethodCall(_, _, _), Some(b2 @ BinaryMethodCall(_, _, _))) if precedence(b1) > precedence(b2) => false case (BinaryMethodCall(_, _, _), Some(_: FunctionInvocation)) => true case (_, Some(_: FunctionInvocation)) => false diff --git a/src/main/scala/leon/purescala/ScalaPrinter.scala b/src/main/scala/leon/purescala/ScalaPrinter.scala index e030086b9..8ef0cc52f 100644 --- a/src/main/scala/leon/purescala/ScalaPrinter.scala +++ b/src/main/scala/leon/purescala/ScalaPrinter.scala @@ -15,6 +15,7 @@ class ScalaPrinter(opts: PrinterOptions, opgm: Option[Program], sb: StringBuffer = new StringBuffer) extends PrettyPrinter(opts, opgm, sb) { + private val dbquote = "\"" override def pp(tree: Tree)(implicit ctx: PrinterContext): Unit = { tree match { @@ -39,6 +40,13 @@ class ScalaPrinter(opts: PrinterOptions, case m @ FiniteMap(els, k, v) => p"Map[$k,$v]($els)" case InfiniteIntegerLiteral(v) => p"BigInt($v)" + case StringLiteral(v) => + if(v.indexOf("\n") != -1 && v.indexOf("\"\"\"") == -1) { + p"$dbquote$dbquote$dbquote$v$dbquote$dbquote$dbquote" + } else { + val escaped = v.replaceAll(dbquote, "\\\\\"").replaceAll("\n","\\n").replaceAll("\r","\\r") + p"$dbquote$escaped$dbquote" + } case a@FiniteArray(elems, oDef, size) => import ExprOps._ diff --git a/src/main/scala/leon/purescala/Types.scala b/src/main/scala/leon/purescala/Types.scala index e69678d1f..654971373 100644 --- a/src/main/scala/leon/purescala/Types.scala +++ b/src/main/scala/leon/purescala/Types.scala @@ -110,7 +110,7 @@ object Types { def knownDescendants = classDef.knownDescendants.map( _.typed(tps) ) - def knownCCDescendants = classDef.knownCCDescendants.map( _.typed(tps) ) + def knownCCDescendants: Seq[CaseClassType] = classDef.knownCCDescendants.map( _.typed(tps) ) lazy val fieldsTypes = fields.map(_.getType) diff --git a/src/main/scala/leon/synthesis/Rules.scala b/src/main/scala/leon/synthesis/Rules.scala index 0f5894259..5565aa4e5 100644 --- a/src/main/scala/leon/synthesis/Rules.scala +++ b/src/main/scala/leon/synthesis/Rules.scala @@ -37,6 +37,7 @@ abstract class PreprocessingRule(name: String) extends Rule(name) { object Rules { /** Returns the list of all available rules for synthesis */ def all = List[Rule]( + StringRender, Unification.DecompTrivialClash, Unification.OccursCheck, // probably useless Disunification.Decomp, @@ -61,8 +62,7 @@ object Rules { //IntegerEquation, //IntegerInequalities, IntInduction, - InnerCaseSplit, - StringRender + InnerCaseSplit //new OptimisticInjection(_), //new SelectiveInlining(_), //ADTLongInduction, diff --git a/src/main/scala/leon/synthesis/rules/StringRender.scala b/src/main/scala/leon/synthesis/rules/StringRender.scala index a53084a00..140de9f11 100644 --- a/src/main/scala/leon/synthesis/rules/StringRender.scala +++ b/src/main/scala/leon/synthesis/rules/StringRender.scala @@ -27,6 +27,7 @@ import leon.solvers.ModelBuilder import leon.solvers.string.StringSolver import scala.annotation.tailrec import leon.purescala.DefOps +import leon.programsets.{UnionProgramSet, DirectProgramSet, JoinProgramSet} /** A template generator for a given type tree. @@ -139,9 +140,9 @@ case object StringRender extends Rule("StringRender") { } /** Returns a stream of assignments compatible with input/output examples */ - def findAssignments(ctx: LeonContext, p: Program, inputs: Seq[Identifier], examples: ExamplesBank, template: Expr)(implicit hctx: SearchContext): Stream[Map[Identifier, String]] = { + def findAssignments(p: Program, inputs: Seq[Identifier], examples: ExamplesBank, template: Expr)(implicit hctx: SearchContext): Stream[Map[Identifier, String]] = { //new Evaluator() - val e = new StringTracingEvaluator(ctx, p) + val e = new StringTracingEvaluator(hctx.context, p) @tailrec def gatherEquations(s: List[InOutExample], acc: ListBuffer[Equation] = ListBuffer()): Option[SProblem] = s match { case Nil => Some(acc.toList) @@ -154,45 +155,66 @@ case object StringRender extends Rule("StringRender") { val evalResult = e.eval(template, modelResult) evalResult.result match { case None => - ctx.reporter.debug("Eval = None : ["+template+"] in ["+inputs.zip(in)+"]") + hctx.reporter.debug("Eval = None : ["+template+"] in ["+inputs.zip(in)+"]") None case Some((sfExpr, abstractSfExpr)) => //ctx.reporter.debug("Eval = ["+sfExpr+"] (from "+abstractSfExpr+")") val sf = toStringForm(sfExpr) val rhs = toStringLiteral(rhExpr.head) if(sf.isEmpty || rhs.isEmpty) { - ctx.reporter.ifDebug(printer => printer("sf empty or rhs empty ["+sfExpr+"] => ["+sf+"] in ["+rhs+"]")) + hctx.reporter.ifDebug(printer => printer("sf empty or rhs empty ["+sfExpr+"] => ["+sf+"] in ["+rhs+"]")) None } else gatherEquations(q, acc += ((sf.get, rhs.get))) } } else { - ctx.reporter.ifDebug(printer => printer("RHS.length != 1 : ["+rhExpr+"]")) + hctx.reporter.ifDebug(printer => printer("RHS.length != 1 : ["+rhExpr+"]")) None } } gatherEquations(examples.valids.collect{ case io:InOutExample => io }.toList) match { case Some(problem) => - ctx.reporter.ifDebug(printer => printer("Problem: ["+StringSolver.renderProblem(problem)+"]")) + //hctx.reporter.ifDebug(printer => printer("Problem: ["+StringSolver.renderProblem(problem)+"]")) StringSolver.solve(problem) case None => - ctx.reporter.ifDebug(printer => printer("No problem found")) + hctx.reporter.ifDebug(printer => printer("No problem found")) Stream.empty } } - def findSolutions(examples: ExamplesBank, program: Program, template: Expr, templateWithInlineDefs: Expr)(implicit hctx: SearchContext, p: Problem): RuleApplication = { - val solutions = findAssignments(hctx.context, program, p.as, examples, template) - //hctx.reporter.ifDebug(printer => printer("Solutions: ["+solutions.toList+"]")) + def findSolutions(examples: ExamplesBank, template: Stream[Expr], funDefs: Seq[(FunDef, Stream[Expr])])(implicit hctx: SearchContext, p: Problem): RuleApplication = { + // Fun is a stream of many function applications. + val funs= JoinProgramSet.direct(funDefs.map(fbody => fbody._2.map((fbody._1, _))).map(d => DirectProgramSet(d))) - solutionStreamToRuleApplication(p, templateWithInlineDefs, solutions) + val wholeTemplates = JoinProgramSet.direct(funs, DirectProgramSet(template)) + + /*val newProgram = DefOps.addFunDefs(hctx.program, funDefs.values, hctx.sctx.functionContext) + val newExpr = (expr /: funDefs.values) { + case (e, fd) => LetDef(fd, e) + }*/ + def computeSolutions(funDefsBodies: Seq[(FunDef, Expr)], template: Expr): Stream[Assignment] = { + val funDefs = for((funDef, body) <- funDefsBodies) yield { funDef.body = Some(body); funDef } + val newProgram = DefOps.addFunDefs(hctx.program, funDefs, hctx.sctx.functionContext) + findAssignments(newProgram, p.as, examples, template) + } + + val tagged_solutions = + for{(funDefs, template) <- wholeTemplates.programs} yield computeSolutions(funDefs, template).map((funDefs, template, _)) + + solutionStreamToRuleApplication(p, leon.utils.StreamUtils.interleave(tagged_solutions)) } - def solutionStreamToRuleApplication(p: Problem, template: Expr, solutions: Stream[Assignment]): RuleApplication = { + def solutionStreamToRuleApplication(p: Problem, solutions: Stream[(Seq[(FunDef, Expr)], Expr, Assignment)]): RuleApplication = { if(solutions.isEmpty) RuleFailed() else { - RuleClosed(solutions.map((assignment: Map[Identifier, String]) => { - val term = ExprOps.simplifyString(ExprOps.replaceFromIDs(assignment.mapValues(StringLiteral), template)) - Solution(pre=p.pc, defs=Set(), term=term) - })) + RuleClosed( + for((funDefsBodies, singleTemplate, assignment) <- solutions) yield { + val template = (singleTemplate /: funDefsBodies) { + case (e, (fd, body)) => + fd.body = Some(body) + LetDef(fd, e) + } + val term = ExprOps.simplifyString(ExprOps.replaceFromIDs(assignment.mapValues(StringLiteral), template)) + Solution(pre=p.pc, defs=Set(), term=term) + }) } } @@ -216,9 +238,9 @@ case object StringRender extends Rule("StringRender") { } /** Pre-updates of the function definition */ - def preUpdateFunDefBody(tpe: DependentType, fd: FunDef, assignments: Map[DependentType, FunDef]): Map[DependentType, FunDef] = { + def preUpdateFunDefBody(tpe: DependentType, fd: FunDef, assignments: Map[DependentType, (FunDef, Stream[Expr])]): Map[DependentType, (FunDef, Stream[Expr])] = { assignments.get(tpe) match { - case None => assignments + (tpe -> fd) + case None => assignments + (tpe -> ((fd, Stream.Empty))) case Some(_) => assignments } } @@ -230,29 +252,36 @@ case object StringRender extends Rule("StringRender") { **/ def createFunDefsTemplates( currentCaseClassParent: Option[TypeTree], - adtToString: Map[DependentType, FunDef], - inputs: Seq[Identifier])(implicit hctx: SearchContext): (Expr, Map[DependentType, FunDef]) = { + adtToString: Map[DependentType, (FunDef, Stream[Expr])], + inputs: Seq[Identifier])(implicit hctx: SearchContext): (Stream[Expr], Map[DependentType, (FunDef, Stream[Expr])]) = { /* Returns a list of expressions converting the list of inputs to string. * Holes should be inserted before, after and in-between for solving concatenation. * @return Along with the list, an updated function definitions to transform (parent-dependent) types to strings */ @tailrec def gatherInputs( currentCaseClassParent: Option[TypeTree], - assignments1: Map[DependentType, FunDef], + assignments1: Map[DependentType, (FunDef, Stream[Expr])], inputs: List[Identifier], - result: ListBuffer[Expr] = ListBuffer()): (List[Expr], Map[DependentType, FunDef]) = inputs match { + result: ListBuffer[Stream[Expr]] = ListBuffer()): (List[Stream[Expr]], Map[DependentType, (FunDef, Stream[Expr])]) = inputs match { case Nil => (result.toList, assignments1) case input::q => val dependentType = DependentType(currentCaseClassParent, input.getType) assignments1.get(dependentType) match { case Some(fd) => - gatherInputs(currentCaseClassParent, assignments1, q, result += functionInvocation(fd, Seq(Variable(input)))) + // TODO : Maybe find other functions. + gatherInputs(currentCaseClassParent, assignments1, q, result += Stream(functionInvocation(fd._1, Seq(Variable(input))))) case None => // No function can render the current type. input.getType match { - case BooleanType => // Special case. - gatherInputs(currentCaseClassParent, assignments1, q, result += booleanTemplate(input).instantiate) + case StringType => + gatherInputs(currentCaseClassParent, assignments1, q, result += Stream(Variable(input))) + case BooleanType => + // Special case. But might be overkill. + // It should be possible to have generic conversion instead, else it needs two examples, which might be cu + // gatherInputs(currentCaseClassParent, assignments1, q, result += booleanTemplate(input).instantiate) + // OR + gatherInputs(currentCaseClassParent, assignments1, q, result += Stream(BooleanToString(Variable(input)), booleanTemplate(input).instantiate)) case WithStringconverter(converter) => // Base case - gatherInputs(currentCaseClassParent, assignments1, q, result += converter(Variable(input))) + gatherInputs(currentCaseClassParent, assignments1, q, result += Stream(converter(Variable(input)))) case t: ClassType => // Create the empty function body and updates the assignments parts. val fd = createEmptyFunDef(dependentType) @@ -260,26 +289,32 @@ case object StringRender extends Rule("StringRender") { t.root match { case act@AbstractClassType(acd@AbstractClassDef(id, tparams, parent), tps) => // Create a complete FunDef body with pattern matching - val (assignments3, cases) = ((assignments2, ListBuffer[MatchCase]()) /: act.knownCCDescendants) { + val (assignments3, cases) = ((assignments2, ListBuffer[Stream[MatchCase]]()) /: act.knownCCDescendants) { case ((assignments2tmp, acc), CaseClassType(ccd@CaseClassDef(id, tparams, parent, isCaseObject), tparams2)) => val typeMap = tparams.zip(tparams2).toMap def resolveType(t: TypeTree): TypeTree = TypeOps.instantiateType(t, typeMap) val fields = ccd.fields.map(vd => ( TypeOps.instantiateType(vd, typeMap).id )) val pattern = CaseClassPattern(None, ccd.typed(tparams2), fields.map(k => WildcardPattern(Some(k)))) val (rhs, assignments2tmp2) = createFunDefsTemplates(Some(t), assignments2tmp, fields) // Invoke functions for each of the fields. - (assignments2tmp2, acc += MatchCase(pattern, None, rhs)) + (assignments2tmp2, acc += rhs.map(MatchCase(pattern, None, _))) case ((adtToString, acc), e) => hctx.reporter.fatalError("Could not handle this class definition for string rendering " + e) } - fd.body = Some(MatchExpr(Variable(fd.params(0).id), cases)) - gatherInputs(currentCaseClassParent, assignments3, q, result += functionInvocation(fd, Seq(Variable(input)))) + val recombine = (cases: Seq[MatchCase]) => MatchExpr(Variable(fd.params(0).id), cases) + val allMatchExprs = JoinProgramSet(cases.map(DirectProgramSet(_)), recombine).programs + val assignments4 = assignments3 + (dependentType -> (fd, allMatchExprs)) + gatherInputs(currentCaseClassParent, assignments4, q, result += Stream(functionInvocation(fd, Seq(Variable(input))))) case cct@CaseClassType(ccd@CaseClassDef(id, tparams, parent, isCaseObject), tparams2) => val typeMap = tparams.zip(tparams2).toMap def resolveType(t: TypeTree): TypeTree = TypeOps.instantiateType(t, typeMap) val fields = ccd.fields.map(vd => TypeOps.instantiateType(vd, typeMap).id) val pattern = CaseClassPattern(None, ccd.typed(tparams2), fields.map(k => WildcardPattern(Some(k)))) val (rhs, assignments3) = createFunDefsTemplates(Some(t), assignments2, fields) // Invoke functions for each of the fields. - fd.body = Some(MatchExpr(Variable(fd.params(0).id), Seq(MatchCase(pattern, None, rhs)))) - gatherInputs(currentCaseClassParent, assignments3, q, result += functionInvocation(fd, Seq(Variable(input)))) + + val recombine = (cases: Seq[MatchCase]) => MatchExpr(Variable(fd.params(0).id), cases) + val cases = rhs.map(MatchCase(pattern, None, _)) + val allMatchExprs = cases.map(acase => recombine(Seq(acase))) + val assignments4 = assignments3 + (dependentType -> (fd, allMatchExprs)) + gatherInputs(currentCaseClassParent, assignments4, q, result += Stream(functionInvocation(fd, Seq(Variable(input))))) } case TypeParameter(t) => hctx.reporter.fatalError("Could not handle type parameter for string rendering " + t) @@ -291,21 +326,23 @@ case object StringRender extends Rule("StringRender") { val (exprs, assignments) = gatherInputs(currentCaseClassParent, adtToString, inputs.toList) /** Add post, pre and in-between holes, and returns a single expr along with the new assignments. */ - val template = exprs match { + val template: Stream[Expr] = exprs match { case Nil => - StringTemplateGenerator(Hole => Hole).instantiate + Stream(StringTemplateGenerator(Hole => Hole).instantiate) case exprList => - StringTemplateGenerator(Hole => { - StringConcat((StringConcat(Hole, exprs.head) /: exprs.tail) { - case (finalExpr, expr) => StringConcat(StringConcat(finalExpr, Hole), expr) - }, Hole) - }).instantiate + JoinProgramSet(exprList.map(DirectProgramSet(_)), (exprs: Seq[Expr]) => + StringTemplateGenerator(Hole => { + StringConcat((StringConcat(Hole, exprs.head) /: exprs.tail) { + case (finalExpr, expr) => StringConcat(StringConcat(finalExpr, Hole), expr) + }, Hole) + }).instantiate + ).programs } (template, assignments) } def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { - + hctx.reporter.debug("StringRender:Output variables="+p.xs+", their types="+p.xs.map(_.getType)) p.xs match { case List(IsTyped(v, StringType)) => val description = "Creates a standard string conversion function" @@ -344,11 +381,11 @@ case object StringRender extends Rule("StringRender") { )) hctx.reporter.ifDebug(printer => printer("Inferred expression:\n" + expr + toDebug)) - val newProgram = DefOps.addFunDefs(hctx.program, funDefs.values, hctx.sctx.functionContext) + /*val newProgram = DefOps.addFunDefs(hctx.program, funDefs.values, hctx.sctx.functionContext) val newExpr = (expr /: funDefs.values) { case (e, fd) => LetDef(fd, e) - } - findSolutions(examples, newProgram, expr, newExpr) + }*/ + findSolutions(examples, expr, funDefs.values.toSeq) } ruleInstantiations.toList diff --git a/src/main/scala/leon/utils/StreamUtils.scala b/src/main/scala/leon/utils/StreamUtils.scala index 2ea08a593..09a09ce40 100644 --- a/src/main/scala/leon/utils/StreamUtils.scala +++ b/src/main/scala/leon/utils/StreamUtils.scala @@ -4,6 +4,14 @@ package leon.utils object StreamUtils { + /** Interleaves a stream of streams. + * {{{ + * If streams = ((1,2,3),(4,5),(6,7,8,9),(10...), + * Order taken 0 1 2 + * Order taken 3 4 5 6 7 + * Order taken 8 9}}} + * interleave(streams) = (1, 2, 4, 3, 5, 6, 7, 10, 8, 9... + **/ def interleave[T](streams: Stream[Stream[T]]): Stream[T] = { def rec(streams: Stream[Stream[T]], diag: Int): Stream[T] = { if(streams.isEmpty) Stream() else { @@ -15,6 +23,7 @@ object StreamUtils { rec(streams, 1) } + /** Applies the interleaving to a finite sequence of streams. */ def interleave[T](streams : Seq[Stream[T]]) : Stream[T] = { if (streams.isEmpty) Stream() else { val nonEmpty = streams filter (_.nonEmpty) @@ -22,6 +31,29 @@ object StreamUtils { } } + private def cantorPair(x: Int, y: Int): Int = { + ((x + y) * (x + y + 1)) / 2 + y + } + + private def reverseCantorPair(z: Int): (Int, Int) = { + val t = Math.floor((-1.0f + Math.sqrt(1.0f + 8.0f * z))/2.0f).toInt; + val x = t * (t + 3) / 2 - z; + val y = z - t * (t + 1) / 2; + (x, y) + } + + /** Combines two streams into one using cantor's unpairing function. + * Ensures that the stream terminates if both streams terminate */ + def cartesianProduct[A, B](sa: Stream[A], sb: Stream[B]): Stream[(A, B)] = { + def combineRec[A, B](sa: Stream[A], sb: Stream[B])(i: Int): Stream[(A, B)] = { + val (x, y) = reverseCantorPair(i) + if(!sa.isDefinedAt(x) && !sb.isDefinedAt(y)) Stream.Empty + else if(sa.isDefinedAt(x) && sb.isDefinedAt(y)) (sa(x), sb(y)) #:: combineRec(sa, sb)(i+1) + else combineRec(sa, sb)(i+1) + } + combineRec(sa, sb)(0) + } + def cartesianProduct[T](streams : Seq[Stream[T]]) : Stream[List[T]] = { val dimensions = streams.size val vectorizedStreams = streams.map(new VectorizedStream(_)) diff --git a/src/test/scala/leon/integration/solvers/StringSolverSuite.scala b/src/test/scala/leon/integration/solvers/StringSolverSuite.scala index fcb7d3080..dc34901da 100644 --- a/src/test/scala/leon/integration/solvers/StringSolverSuite.scala +++ b/src/test/scala/leon/integration/solvers/StringSolverSuite.scala @@ -168,7 +168,7 @@ class StringSolverSuite extends FunSuite with Matchers { solve(problem) should not be 'empty } - /*test("solveJadProblem") { + test("solveJadProblem") { val lhs = """const26+const22+const7+const+const8+const3+const9+const5+"5"+const6+const10+const23+const18+const11+const+const12+const19+const18+const7+const1+const8+const2+const9+const4+const10+const19+const18+const13+const+const14+const3+const15+const5+"5"+const6+const16+const4+const17+const19+const18+const11+const1+const12+const19+const18+const13+const1+const14+const2+const15+const4+const16+const5+"5"+const6+const17+const19+const21+const20+const20+const20+const20+const20+const24+const27""" var idMap = Map[String, Identifier]() val lhsSf = for(elem <- lhs.split("\\+")) yield { @@ -195,5 +195,5 @@ T2: ret Pop() -> 5""" println("Problem to solve:" + StringSolver.renderProblem(problem)) solve(problem) should not be 'empty - }*/ + } } \ No newline at end of file diff --git a/testcases/stringrender/JsonRender.scala b/testcases/stringrender/JsonRender.scala new file mode 100644 index 000000000..2b8587845 --- /dev/null +++ b/testcases/stringrender/JsonRender.scala @@ -0,0 +1,42 @@ +/** + * Name: ListRender.scala + * Creation: 14.10.2015 + * Author: Mika�l Mayer (mikael.mayer@epfl.ch) + * Comments: First benchmark ever for solving string transformation problems. List specifications. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object JsonRender { + /** Synthesis by example specs */ + @inline def psStandard(x: Int, y: String, z: Boolean) = (res: String) =>((x, y, z), res) passes { + case (31, "routed", true) => """{ field1: 31, field2: "routed", field3: true }""" + } + + @inline def psTupled1(xy: (Int, String), z: Boolean) = (res: String) =>((xy, z), res) passes { + case ((31, "routed"), true) => """{ field1: 31, field2: "routed", field3: true }""" + } + + @inline def psTupled2(x: Int, yz: (String, Boolean)) = (res: String) =>((x, yz), res) passes { + case (31, ("routed", true)) => """{ field1: 31, field2: "routed", field3: true }""" + } + + ////////////////////////////////////////////// + // Non-incremental examples: pure synthesis // + ////////////////////////////////////////////// + def synthesizeStandard(x: Int, y: String, z: Boolean): String = { + ???[String] + } ensuring psStandard(x, y, z) + + def synthesizeTupled1(xy: (Int, String), z: Boolean): String = { + ???[String] + } ensuring psTupled1(xy, z) + + def synthesizeTupled2(x: Int, yz: (String, Boolean)): String = { + ???[String] + } ensuring psTupled2(x, yz) +} \ No newline at end of file -- GitLab