diff --git a/src/main/scala/leon/evaluators/AbstractEvaluator.scala b/src/main/scala/leon/evaluators/AbstractEvaluator.scala new file mode 100644 index 0000000000000000000000000000000000000000..a876001683a2592e567b42f68747e4b86d462d20 --- /dev/null +++ b/src/main/scala/leon/evaluators/AbstractEvaluator.scala @@ -0,0 +1,96 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package evaluators + +import purescala.Extractors.Operator +import purescala.Expressions._ +import purescala.Types._ +import purescala.Definitions.{TypedFunDef, Program} +import purescala.DefOps +import purescala.TypeOps +import purescala.ExprOps +import purescala.Expressions.Expr +import leon.utils.DebugSectionSynthesis + +/** The evaluation returns a pair (e, t), + * where e is the expression evaluated as much as possible, and t is the way the expression has been evaluated. + * Caution: If and Match statement require the condition to be non-abstract. */ +class AbstractEvaluator(ctx: LeonContext, prog: Program) extends ContextualEvaluator(ctx, prog, 50000) with HasDefaultGlobalContext with HasDefaultRecContext { + lazy val scalaEv = new ScalacEvaluator(underlying, ctx, prog) + + /** Evaluates resuts which can be evaluated directly + * For example, concatenation of two string literals */ + val underlying = new DefaultEvaluator(ctx, prog) + underlying.setEvaluationFailOnChoose(true) + override type Value = (Expr, Expr) + + override val description: String = "Evaluates string programs but keeps the formula which generated the string" + override val name: String = "String Tracing evaluator" + + protected def e(expr: Expr)(implicit rctx: RC, gctx: GC): (Expr, Expr) = expr match { + case Variable(id) => + rctx.mappings.get(id) match { + case Some(v) if v != expr => + e(v) + case _ => + (expr, expr) + } + + case e if ExprOps.isValue(e) => + (e, e) + + case IfExpr(cond, thenn, elze) => + val first = underlying.e(cond) + first match { + case BooleanLiteral(true) => + ctx.reporter.ifDebug(printer => printer(thenn))(DebugSectionSynthesis) + e(thenn) + case BooleanLiteral(false) => e(elze) + case _ => throw EvalError(typeErrorMsg(first, BooleanType)) + } + + case MatchExpr(scrut, cases) => + val (escrut, tscrut) = e(scrut) + val rscrut = escrut + cases.toStream.map(c => underlying.matchesCase(rscrut, c)).find(_.nonEmpty) match { + case Some(Some((c, mappings))) => + e(c.rhs)(rctx.withNewVars(mappings), gctx) + case _ => + throw RuntimeError("MatchError: "+rscrut.asString+" did not match any of the cases :" + cases) + } + + case FunctionInvocation(tfd, args) => + if (gctx.stepsLeft < 0) { + throw RuntimeError("Exceeded number of allocated methods calls ("+gctx.maxSteps+")") + } + gctx.stepsLeft -= 1 + val evArgs = args map e + val evArgsValues = evArgs.map(_._1) + val evArgsOrigin = evArgs.map(_._2) + + // build a mapping for the function... + val frame = rctx.withNewVars(tfd.paramSubst(evArgsValues)) + + val callResult = if ((evArgsValues forall ExprOps.isValue) && tfd.fd.annotations("extern") && ctx.classDir.isDefined) { + (scalaEv.call(tfd, evArgsValues), FunctionInvocation(tfd, evArgsOrigin)) + } else { + if((!tfd.hasBody && !rctx.mappings.isDefinedAt(tfd.id)) || tfd.body.exists(b => ExprOps.exists(e => e.isInstanceOf[Choose])(b))) { + (FunctionInvocation(tfd, evArgsValues), FunctionInvocation(tfd, evArgsOrigin)) + } else { + val body = tfd.body.getOrElse(rctx.mappings(tfd.id)) + e(body)(frame, gctx) + } + } + callResult + case Operator(es, builder) => + val (ees, ts) = es.map(e).unzip + if(ees forall ExprOps.isValue) { + (underlying.e(builder(ees)), builder(ts)) + } else { + (builder(ees), builder(ts)) + } + } + + +} diff --git a/src/main/scala/leon/evaluators/ContextualEvaluator.scala b/src/main/scala/leon/evaluators/ContextualEvaluator.scala index 0fc33102a04716816fc3b2a83faa1384b37da1fd..eb57bbbf3550d8d177f92188d12d1ff3a3abbbd5 100644 --- a/src/main/scala/leon/evaluators/ContextualEvaluator.scala +++ b/src/main/scala/leon/evaluators/ContextualEvaluator.scala @@ -20,7 +20,9 @@ abstract class ContextualEvaluator(ctx: LeonContext, prog: Program, val maxSteps def initRC(mappings: Map[Identifier, Expr]): RC def initGC(model: solvers.Model, check: Boolean): GC - case class EvalError(msg : String) extends Exception + case class EvalError(msg : String) extends Exception { + override def getMessage = msg + Option(super.getMessage).map("\n" + _).getOrElse("") + } case class RuntimeError(msg : String) extends Exception case class QuantificationError(msg: String) extends Exception @@ -72,7 +74,7 @@ abstract class ContextualEvaluator(ctx: LeonContext, prog: Program, val maxSteps protected def e(expr: Expr)(implicit rctx: RC, gctx: GC): Value - def typeErrorMsg(tree : Expr, expected : TypeTree) : String = s"Type error : expected ${expected.asString}, found ${tree.asString}." + def typeErrorMsg(tree : Expr, expected : TypeTree) : String = s"Type error : expected ${expected.asString}, found ${tree.asString} of type ${tree.getType}." } diff --git a/src/main/scala/leon/evaluators/RecursiveEvaluator.scala b/src/main/scala/leon/evaluators/RecursiveEvaluator.scala index f7240f1b763951aee607241cc07bf41a5c535e4a..193361f0e85a43eef47e1f2376cfcef1e8fffc56 100644 --- a/src/main/scala/leon/evaluators/RecursiveEvaluator.scala +++ b/src/main/scala/leon/evaluators/RecursiveEvaluator.scala @@ -28,6 +28,10 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int lazy val scalaEv = new ScalacEvaluator(this, ctx, prog) protected var clpCache = Map[(Choose, Seq[Expr]), Expr]() + + private var evaluationFailsOnChoose = false + /** Sets the flag if when encountering a Choose, it should fail instead of solving it. */ + def setEvaluationFailOnChoose(b: Boolean) = { this.evaluationFailsOnChoose = b; this } protected[evaluators] def e(expr: Expr)(implicit rctx: RC, gctx: GC): Expr = expr match { case Variable(id) => @@ -37,7 +41,7 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int case Some(v) => v case None => - throw EvalError("No value for identifier " + id.asString + " in mapping.") + throw EvalError("No value for identifier " + id.asString + " in mapping " + rctx.mappings) } case Application(caller, args) => @@ -577,6 +581,9 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int e(p.asConstraint) case choose: Choose => + if(evaluationFailsOnChoose) { + throw EvalError("Evaluator set to not solve choose constructs") + } implicit val debugSection = utils.DebugSectionSynthesis @@ -637,7 +644,7 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int case Some(Some((c, mappings))) => e(c.rhs)(rctx.withNewVars(mappings), gctx) case _ => - throw RuntimeError("MatchError: "+rscrut.asString+" did not match any of the cases") + throw RuntimeError("MatchError: "+rscrut.asString+" did not match any of the cases:\n"+cases) } case gl: GenericValue => gl @@ -645,9 +652,7 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int case l : Literal[_] => l case other => - context.reporter.error(other.getPos, "Error: don't know how to handle " + other.asString + " in Evaluator ("+other.getClass+").") - println("RecursiveEvaluator error:" + other.asString) - throw EvalError("Unhandled case in Evaluator : " + other.asString) + throw EvalError("Unhandled case in Evaluator : [" + other.getClass + "] " + other.asString) } def matchesCase(scrut: Expr, caze: MatchCase)(implicit rctx: RC, gctx: GC): Option[(MatchCase, Map[Identifier, Expr])] = { diff --git a/src/main/scala/leon/evaluators/StringTracingEvaluator.scala b/src/main/scala/leon/evaluators/StringTracingEvaluator.scala deleted file mode 100644 index 62cde913fae123ab0fcd8c3d019953af9efb5942..0000000000000000000000000000000000000000 --- a/src/main/scala/leon/evaluators/StringTracingEvaluator.scala +++ /dev/null @@ -1,127 +0,0 @@ -/* Copyright 2009-2015 EPFL, Lausanne */ - -package leon -package evaluators - -import purescala.Extractors.Operator -import purescala.Expressions._ -import purescala.Types._ -import purescala.Definitions.{TypedFunDef, Program} -import purescala.DefOps -import purescala.Expressions.Expr -import leon.utils.DebugSectionSynthesis -import org.apache.commons.lang3.StringEscapeUtils - -class StringTracingEvaluator(ctx: LeonContext, prog: Program) extends ContextualEvaluator(ctx, prog, 50000) with HasDefaultGlobalContext with HasDefaultRecContext { - - val underlying = new DefaultEvaluator(ctx, prog) { - override protected[evaluators] def e(expr: Expr)(implicit rctx: RC, gctx: GC): Expr = expr match { - - case FunctionInvocation(TypedFunDef(fd, Nil), Seq(input)) if fd == prog.library.escape.get => - e(input) match { - case StringLiteral(s) => - StringLiteral(StringEscapeUtils.escapeJava(s)) - case _ => throw EvalError(typeErrorMsg(input, StringType)) - } - - case FunctionInvocation(tfd, args) => - if (gctx.stepsLeft < 0) { - throw RuntimeError("Exceeded number of allocated methods calls ("+gctx.maxSteps+")") - } - gctx.stepsLeft -= 1 - - val evArgs = args map e - - // build a mapping for the function... - val frame = rctx.withNewVars(tfd.paramSubst(evArgs)) - - val callResult = if (tfd.fd.annotations("extern") && ctx.classDir.isDefined) { - scalaEv.call(tfd, evArgs) - } else { - if(!tfd.hasBody && !rctx.mappings.isDefinedAt(tfd.id)) { - throw EvalError("Evaluation of function with unknown implementation.") - } - - val body = tfd.body.getOrElse(rctx.mappings(tfd.id)) - e(body)(frame, gctx) - } - - callResult - - case Variable(id) => - rctx.mappings.get(id) match { - case Some(v) if v != expr => - e(v) - case Some(v) => - v - case None => - expr - } - case StringConcat(s1, s2) => - val es1 = e(s1) - val es2 = e(s2) - (es1, es2) match { - case (StringLiteral(_), StringLiteral(_)) => - (super.e(StringConcat(es1, es2))) - case _ => - StringConcat(es1, es2) - } - case expr => - super.e(expr) - } - } - override type Value = (Expr, Expr) - - override val description: String = "Evaluates string programs but keeps the formula which generated the string" - override val name: String = "String Tracing evaluator" - - protected def e(expr: Expr)(implicit rctx: RC, gctx: GC): (Expr, Expr) = expr match { - case Variable(id) => - rctx.mappings.get(id) match { - case Some(v) if v != expr => - e(v) - case Some(v) => - (v, expr) - case None => - (expr, expr) - } - - case StringConcat(s1, s2) => - val (es1, t1) = e(s1) - val (es2, t2) = e(s2) - (es1, es2) match { - case (StringLiteral(_), StringLiteral(_)) => - (underlying.e(StringConcat(es1, es2)), StringConcat(t1, t2)) - case _ => - (StringConcat(es1, es2), StringConcat(t1, t2)) - } - case StringLength(s1) => - val (es1, t1) = e(s1) - es1 match { - case StringLiteral(_) => - (underlying.e(StringLength(es1)), StringLength(t1)) - case _ => - (StringLength(es1), StringLength(t1)) - } - - case expr@StringLiteral(s) => - (expr, expr) - - case IfExpr(cond, thenn, elze) => - val first = underlying.e(cond) - first match { - case BooleanLiteral(true) => - ctx.reporter.ifDebug(printer => printer(thenn))(DebugSectionSynthesis) - e(thenn) - case BooleanLiteral(false) => e(elze) - case _ => throw EvalError(typeErrorMsg(first, BooleanType)) - } - - case Operator(es, builder) => - val (ees, ts) = es.map(e).unzip - (underlying.e(builder(ees)), builder(ts)) - - } - - -} diff --git a/src/main/scala/leon/purescala/Constructors.scala b/src/main/scala/leon/purescala/Constructors.scala index e3ce7d1e01780ce02ba424396dabd3670f426966..23b32e0ee96ec98dca890a4a17ae8541c20244cb 100644 --- a/src/main/scala/leon/purescala/Constructors.scala +++ b/src/main/scala/leon/purescala/Constructors.scala @@ -317,6 +317,13 @@ object Constructors { } /** $encodingof simplified `fn(realArgs)` (function application). + * Transforms + * {{{ ((x: A, y: B) => g(x, y))(c, d) }}} + * into + * {{{val x0 = c + * val y0 = d + * g(x0, y0)}}} + * and further simplifies it. * @see [[purescala.Expressions.Lambda Lambda]] * @see [[purescala.Expressions.Application Application]] */ diff --git a/src/main/scala/leon/purescala/DefOps.scala b/src/main/scala/leon/purescala/DefOps.scala index 2eeb5a4d44fbde9af89ae5d5c103bed1ba1b75ed..4efc97986400f312afeb19674615e9822c56c8e3 100644 --- a/src/main/scala/leon/purescala/DefOps.scala +++ b/src/main/scala/leon/purescala/DefOps.scala @@ -285,7 +285,8 @@ object DefOps { * By default it is the function invocation using the new function definition. * @return the new program with a map from the old functions to the new functions */ def replaceFunDefs(p: Program)(fdMapF: FunDef => Option[FunDef], - fiMapF: (FunctionInvocation, FunDef) => Option[Expr] = defaultFiMap) = { + fiMapF: (FunctionInvocation, FunDef) => Option[Expr] = defaultFiMap) + : (Program, Map[FunDef, FunDef])= { var fdMapCache = Map[FunDef, Option[FunDef]]() def fdMap(fd: FunDef): FunDef = { diff --git a/src/main/scala/leon/purescala/ExprOps.scala b/src/main/scala/leon/purescala/ExprOps.scala index ec304c0aef6dd48e9dd01edd7d3ed25f190d1a82..19d20604ddc9ff9c3187c2fe24ee50f46e658850 100644 --- a/src/main/scala/leon/purescala/ExprOps.scala +++ b/src/main/scala/leon/purescala/ExprOps.scala @@ -1190,6 +1190,12 @@ object ExprOps extends { val Deconstructor = Operator } with SubTreeOps[Expr] { case Minus(Plus(e1, e2), Plus(e3, e4)) if e1 == e4 && e2 == e3 => InfiniteIntegerLiteral(0) case Minus(Plus(e1, e2), Plus(Plus(e3, e4), e5)) if e1 == e4 && e2 == e3 => UMinus(e5) + case StringConcat(StringLiteral(""), a) => a + case StringConcat(a, StringLiteral("")) => a + case StringConcat(StringLiteral(a), StringLiteral(b)) => StringLiteral(a+b) + case StringConcat(StringLiteral(a), StringConcat(StringLiteral(b), c)) => StringConcat(StringLiteral(a+b), c) + case StringConcat(StringConcat(c, StringLiteral(a)), StringLiteral(b)) => StringConcat(c, StringLiteral(a+b)) + case StringConcat(a, StringConcat(b, c)) => StringConcat(StringConcat(a, b), c) //default case e => e }).copiedFrom(expr) @@ -1278,6 +1284,181 @@ object ExprOps extends { val Deconstructor = Operator } with SubTreeOps[Expr] { case _ => false } + + /** Checks whether two expressions can be homomorphic and returns the corresponding mapping */ + def canBeHomomorphic(t1: Expr, t2: Expr): Option[Map[Identifier, Identifier]] = { + val freeT1Variables = ExprOps.variablesOf(t1) + val freeT2Variables = ExprOps.variablesOf(t2) + + def mergeContexts(a: Option[Map[Identifier, Identifier]], b: =>Option[Map[Identifier, Identifier]]) = a match { + case Some(m) => + b match { + case Some(n) if (m.keySet & n.keySet) forall (key => m(key) == n(key)) => + Some(m ++ n) + case _ =>None + } + case _ => None + } + object Same { + def unapply(tt: (Expr, Expr)): Option[(Expr, Expr)] = { + if (tt._1.getClass == tt._2.getClass) { + Some(tt) + } else { + None + } + } + } + implicit class AugmentedContext(c: Option[Map[Identifier, Identifier]]) { + def &&(other: => Option[Map[Identifier, Identifier]]) = mergeContexts(c, other) + def --(other: Seq[Identifier]) = + c.map(_ -- other) + } + implicit class AugmentedBooleant(c: Boolean) { + def &&(other: => Option[Map[Identifier, Identifier]]) = if(c) other else None + } + implicit class AugmentedSeq[T](c: Seq[T]) { + def mergeall(p: T => Option[Map[Identifier, Identifier]]) = + (Option(Map[Identifier, Identifier]()) /: c) { + case (s, c) => s && p(c) + } + } + + + def idHomo(i1: Identifier, i2: Identifier): Option[Map[Identifier, Identifier]] = { + if(!(freeT1Variables(i1) || freeT2Variables(i2)) || i1 == i2) Some(Map(i1 -> i2)) else None + } + + def fdHomo(fd1: FunDef, fd2: FunDef): Option[Map[Identifier, Identifier]] = { + if(fd1.params.size == fd2.params.size) { + val newMap = Map(( + (fd1.id -> fd2.id) +: + (fd1.paramIds zip fd2.paramIds)): _*) + Option(newMap) && isHomo(fd1.fullBody, fd2.fullBody) + } else None + } + + def isHomo(t1: Expr, t2: Expr): Option[Map[Identifier, Identifier]] = { + def casesMatch(cs1 : Seq[MatchCase], cs2 : Seq[MatchCase]) : Option[Map[Identifier, Identifier]] = { + def patternHomo(p1: Pattern, p2: Pattern): (Boolean, Map[Identifier, Identifier]) = (p1, p2) match { + case (InstanceOfPattern(ob1, cd1), InstanceOfPattern(ob2, cd2)) => + (ob1.size == ob2.size && cd1 == cd2, Map((ob1 zip ob2).toSeq : _*)) + + case (WildcardPattern(ob1), WildcardPattern(ob2)) => + (ob1.size == ob2.size, Map((ob1 zip ob2).toSeq : _*)) + + case (CaseClassPattern(ob1, ccd1, subs1), CaseClassPattern(ob2, ccd2, subs2)) => + val m = Map[Identifier, Identifier]() ++ (ob1 zip ob2) + + if (ob1.size == ob2.size && ccd1 == ccd2 && subs1.size == subs2.size) { + (subs1 zip subs2).map { case (p1, p2) => patternHomo(p1, p2) }.foldLeft((true, m)) { + case ((b1, m1), (b2,m2)) => (b1 && b2, m1 ++ m2) + } + } else { + (false, Map()) + } + + case (UnapplyPattern(ob1, fd1, subs1), UnapplyPattern(ob2, fd2, subs2)) => + val m = Map[Identifier, Identifier]() ++ (ob1 zip ob2) + + if (ob1.size == ob2.size && fd1 == fd2 && subs1.size == subs2.size) { + (subs1 zip subs2).map { case (p1, p2) => patternHomo(p1, p2) }.foldLeft((true, m)) { + case ((b1, m1), (b2,m2)) => (b1 && b2, m1 ++ m2) + } + } else { + (false, Map()) + } + + case (TuplePattern(ob1, subs1), TuplePattern(ob2, subs2)) => + val m = Map[Identifier, Identifier]() ++ (ob1 zip ob2) + + if (ob1.size == ob2.size && subs1.size == subs2.size) { + (subs1 zip subs2).map { case (p1, p2) => patternHomo(p1, p2) }.foldLeft((true, m)) { + case ((b1, m1), (b2,m2)) => (b1 && b2, m1 ++ m2) + } + } else { + (false, Map()) + } + + case (LiteralPattern(ob1, lit1), LiteralPattern(ob2,lit2)) => + (ob1.size == ob2.size && lit1 == lit2, (ob1 zip ob2).toMap) + + case _ => + (false, Map()) + } + + (cs1 zip cs2).mergeall { + case (MatchCase(p1, g1, e1), MatchCase(p2, g2, e2)) => + val (h, nm) = patternHomo(p1, p2) + val g: Option[Map[Identifier, Identifier]] = (g1, g2) match { + case (Some(g1), Some(g2)) => Some(nm) && isHomo(g1,g2) + case (None, None) => Some(Map()) + case _ => None + } + val e = Some(nm) && isHomo(e1, e2) + + h && g && e + } + + } + + import synthesis.Witnesses.Terminating + + val res: Option[Map[Identifier, Identifier]] = (t1, t2) match { + case (Variable(i1), Variable(i2)) => + idHomo(i1, i2) + + case (Let(id1, v1, e1), Let(id2, v2, e2)) => + isHomo(v1, v2) && + isHomo(e1, e2) && Some(Map(id1 -> id2)) + + case (LetDef(fds1, e1), LetDef(fds2, e2)) => + fds1.size == fds2.size && + { + val zipped = fds1.zip(fds2) + (zipped mergeall (fds => fdHomo(fds._1, fds._2))) && Some(zipped.map(fds => fds._1.id -> fds._2.id).toMap) && + isHomo(e1, e2) + } + + case (MatchExpr(s1, cs1), MatchExpr(s2, cs2)) => + cs1.size == cs2.size && casesMatch(cs1,cs2) && isHomo(s1, s2) + + case (Passes(in1, out1, cs1), Passes(in2, out2, cs2)) => + (cs1.size == cs2.size && casesMatch(cs1,cs2)) && isHomo(in1,in2) && isHomo(out1,out2) + + case (FunctionInvocation(tfd1, args1), FunctionInvocation(tfd2, args2)) => + // TODO: Check type params + fdHomo(tfd1.fd, tfd2.fd) && + (args1 zip args2).mergeall{ case (a1, a2) => isHomo(a1, a2) } + + case (Terminating(tfd1, args1), Terminating(tfd2, args2)) => + // TODO: Check type params + fdHomo(tfd1.fd, tfd2.fd) && + (args1 zip args2).mergeall{ case (a1, a2) => isHomo(a1, a2) } + + case (Lambda(defs, body), Lambda(defs2, body2)) => + // We remove variables introduced by lambdas. + (isHomo(body, body2) && + (defs zip defs2).mergeall{ case (ValDef(a1), ValDef(a2)) => Option(Map(a1 -> a2)) } + ) -- (defs.map(_.id)) + + case (v1, v2) if isValue(v1) && isValue(v2) => + v1 == v2 && Some(Map[Identifier, Identifier]()) + + case Same(Operator(es1, _), Operator(es2, _)) => + (es1.size == es2.size) && + (es1 zip es2).mergeall{ case (e1, e2) => isHomo(e1, e2) } + + case _ => + None + } + + res + } + + isHomo(t1,t2) + + + } // ensuring (res => res.isEmpty || isHomomorphic(t1, t2)(res.get)) /** Checks whether two trees are homomoprhic modulo an identifier map. * @@ -1408,7 +1589,8 @@ object ExprOps extends { val Deconstructor = Operator } with SubTreeOps[Expr] { fdHomo(tfd1.fd, tfd2.fd) && (args1 zip args2).forall{ case (a1, a2) => isHomo(a1, a2) } - // TODO: Seems a lot is missing, like Literals + case (v1, v2) if isValue(v1) && isValue(v2) => + v1 == v2 case Same(Deconstructor(es1, _), Deconstructor(es2, _)) => (es1.size == es2.size) && @@ -1980,4 +2162,42 @@ object ExprOps extends { val Deconstructor = Operator } with SubTreeOps[Expr] { fun } + /** Returns true if expr is a value of type t */ + def isValueOfType(e: Expr, t: TypeTree): Boolean = { + (e, t) match { + case (StringLiteral(_), StringType) => true + case (IntLiteral(_), Int32Type) => true + case (InfiniteIntegerLiteral(_), IntegerType) => true + case (CharLiteral(_), CharType) => true + case (FractionalLiteral(_, _), RealType) => true + case (BooleanLiteral(_), BooleanType) => true + case (UnitLiteral(), UnitType) => true + case (GenericValue(t, _), tp) => t == tp + case (Tuple(elems), TupleType(bases)) => + elems zip bases forall (eb => isValueOfType(eb._1, eb._2)) + case (FiniteSet(elems, tbase), SetType(base)) => + tbase == base && + (elems forall isValue) + case (FiniteMap(elems, tk, tv), MapType(from, to)) => + tk == from && tv == to && + (elems forall (kv => isValueOfType(kv._1, from) && isValueOfType(kv._2, to) )) + case (NonemptyArray(elems, defaultValues), ArrayType(base)) => + elems.values forall (x => isValueOfType(x, base)) + case (EmptyArray(tpe), ArrayType(base)) => + tpe == base + case (CaseClass(ct, args), ct2@AbstractClassType(classDef, tps)) => + TypeOps.isSubtypeOf(ct, ct2) && + ((args zip ct.fieldsTypes) forall (argstyped => isValueOfType(argstyped._1, argstyped._2))) + case (CaseClass(ct, args), ct2@CaseClassType(classDef, tps)) => + ct == ct2 && + ((args zip ct.fieldsTypes) forall (argstyped => isValueOfType(argstyped._1, argstyped._2))) + case (Lambda(valdefs, body), FunctionType(ins, out)) => + (valdefs zip ins forall (vdin => vdin._1.getType == vdin._2)) && + body.getType == out + case _ => false + } + } + + /** Returns true if expr is a value. Stronger than isGround */ + val isValue = (e: Expr) => isValueOfType(e, e.getType) } diff --git a/src/main/scala/leon/purescala/ScopeSimplifier.scala b/src/main/scala/leon/purescala/ScopeSimplifier.scala index 380554d8c3d8c566bb6da32d274ecf1eea199d11..e06055dc4d9dfce8b24d2d8ddb698ebbbc781079 100644 --- a/src/main/scala/leon/purescala/ScopeSimplifier.scala +++ b/src/main/scala/leon/purescala/ScopeSimplifier.scala @@ -3,6 +3,7 @@ package leon package purescala +import collection.mutable.ListBuffer import Common._ import Definitions._ import Expressions._ @@ -11,11 +12,15 @@ import Constructors.letDef class ScopeSimplifier extends Transformer { case class Scope(inScope: Set[Identifier] = Set(), oldToNew: Map[Identifier, Identifier] = Map(), funDefs: Map[FunDef, FunDef] = Map()) { - + def register(oldNew: (Identifier, Identifier)): Scope = { val newId = oldNew._2 copy(inScope = inScope + newId, oldToNew = oldToNew + oldNew) } + + def register(oldNews: Seq[(Identifier, Identifier)]): Scope = { + (this /: oldNews){ case (oldScope, oldNew) => oldScope.register(oldNew) } + } def registerFunDef(oldNew: (FunDef, FunDef)): Scope = { copy(funDefs = funDefs + oldNew) @@ -45,20 +50,21 @@ class ScopeSimplifier extends Transformer { } val fds_mapping = for((fd, newId) <- fds_newIds) yield { + val localScopeToRegister = ListBuffer[(Identifier, Identifier)]() // We record the mapping of these variables only for the function. val newArgs = for(ValDef(id) <- fd.params) yield { - val newArg = genId(id, newScope) - newScope = newScope.register(id -> newArg) + val newArg = genId(id, newScope.register(localScopeToRegister)) + localScopeToRegister += (id -> newArg) // This renaming happens only inside the function. ValDef(newArg) } val newFd = fd.duplicate(id = newId, params = newArgs) newScope = newScope.registerFunDef(fd -> newFd) - (newFd, fd) + (newFd, localScopeToRegister, fd) } - for((newFd, fd) <- fds_mapping) { - newFd.fullBody = rec(fd.fullBody, newScope) + for((newFd, localScopeToRegister, fd) <- fds_mapping) { + newFd.fullBody = rec(fd.fullBody, newScope.register(localScopeToRegister)) } letDef(fds_mapping.map(_._1), rec(body, newScope)) diff --git a/src/main/scala/leon/purescala/SelfPrettyPrinter.scala b/src/main/scala/leon/purescala/SelfPrettyPrinter.scala index aa4c204d00187ab211a7164a45ae41d8d77d8f8f..0257a0b7a7c5592215206eefbb6928c46cc9995f 100644 --- a/src/main/scala/leon/purescala/SelfPrettyPrinter.scala +++ b/src/main/scala/leon/purescala/SelfPrettyPrinter.scala @@ -1,58 +1,124 @@ package leon.purescala -import leon.evaluators.StringTracingEvaluator import leon.purescala +import leon.solvers.{ HenkinModel, Model, SolverFactory } +import leon.LeonContext +import leon.evaluators +import leon.utils.StreamUtils +import leon.purescala.Quantification._ +import leon.utils.DebugSectionSynthesis +import leon.utils.DebugSectionVerification import purescala.Definitions.Program -import leon.evaluators.StringTracingEvaluator import purescala.Expressions._ import purescala.Types.StringType -import leon.utils.DebugSectionSynthesis -import leon.utils.DebugSectionVerification -import leon.purescala.Quantification._ import purescala.Constructors._ import purescala.ExprOps._ -import purescala.Expressions.{Pattern, Expr} +import purescala.Expressions._ +import purescala.Expressions.{Choose } import purescala.Extractors._ import purescala.TypeOps._ import purescala.Types._ import purescala.Common._ -import purescala.Expressions._ import purescala.Definitions._ -import leon.solvers.{ HenkinModel, Model, SolverFactory } -import leon.LeonContext -import leon.evaluators +import scala.collection.mutable.ListBuffer +import leon.evaluators.DefaultEvaluator + +object SelfPrettyPrinter { + def prettyPrintersForType(inputType: TypeTree)(implicit ctx: LeonContext, program: Program): Stream[Lambda] = { + (new SelfPrettyPrinter).prettyPrintersForType(inputType) + } + def print(v: Expr, orElse: =>String, excluded: Set[FunDef] = Set())(implicit ctx: LeonContext, program: Program): String = { + (new SelfPrettyPrinter).print(v, orElse, excluded) + } +} /** This pretty-printer uses functions defined in Leon itself. * If not pretty printing function is defined, return the default value instead * @param The list of functions which should be excluded from pretty-printing (to avoid rendering counter-examples of toString methods using the method itself) * @return a user defined string for the given typed expression. */ -object SelfPrettyPrinter { - def print(v: Expr, orElse: =>String, excluded: FunDef => Boolean = Set())(implicit ctx: LeonContext, program: Program): String = { - (program.definedFunctions find { - case fd if !excluded(fd) => - fd.returnType == StringType && fd.params.length == 1 && TypeOps.isSubtypeOf(v.getType, fd.params.head.getType) && fd.id.name.toLowerCase().endsWith("tostring") && +class SelfPrettyPrinter { + private var allowedFunctions = Set[FunDef]() + private var excluded = Set[FunDef]() + /** Functions whose name does not need to end with `tostring` or which can be abstract, i.e. which may contain a choose construct.*/ + def allowFunction(fd: FunDef) = { allowedFunctions += fd; this } + + def excludeFunctions(fds: Set[FunDef]) = { excluded ++= fds; this } + def excludeFunction(fd: FunDef) = { excluded += fd; this } + + /** Returns a list of possible lambdas that can transform the input type to a String*/ + def prettyPrintersForType(inputType: TypeTree/*, existingPp: Map[TypeTree, List[Lambda]] = Map()*/)(implicit ctx: LeonContext, program: Program): Stream[Lambda] = { + // Use the other argument if you need recursive typing (?) + program.definedFunctions.toStream flatMap { + fd => + val isCandidate = fd.returnType == StringType && + fd.params.length >= 1 && + !excluded(fd) && + (allowedFunctions(fd) || ( + //TypeOps.isSubtypeOf(v.getType, fd.params.head.getType) && + fd.id.name.toLowerCase().endsWith("tostring") && program.callGraph.transitiveCallees(fd).forall { fde => !purescala.ExprOps.exists( _.isInstanceOf[Choose])(fde.fullBody) + })) + if(isCandidate) { + prettyPrinterFromCandidate(fd, inputType) + } else Stream.Empty + } + } + + + def prettyPrinterFromCandidate(fd: FunDef, inputType: TypeTree)(implicit ctx: LeonContext, program: Program): Stream[Lambda] = { + TypeOps.canBeSubtypeOf(inputType, fd.tparams.map(_.tp), fd.params.head.getType) match { + case Some(genericTypeMap) => + val defGenericTypeMap = genericTypeMap.map{ case (k, v) => (Definitions.TypeParameterDef(k), v) } + def gatherPrettyPrinters(funIds: List[Identifier], acc: ListBuffer[Stream[Lambda]] = ListBuffer()): Option[Stream[List[Lambda]]] = funIds match { + case Nil => Some(StreamUtils.cartesianProduct(acc.toList)) + case funId::tail => // For each function, find an expression which could be provided if it exists. + funId.getType match { + case FunctionType(Seq(in), StringType) => // Should have one argument. + val candidates = prettyPrintersForType(in) + gatherPrettyPrinters(tail, acc += candidates) + case _ => None + } + } + val funIds = fd.params.tail.map(x => TypeOps.instantiateType(x.id, defGenericTypeMap)).toList + gatherPrettyPrinters(funIds) match { + case Some(l) => for(lambdas <- l) yield { + val x = FreshIdentifier("x", fd.params.head.getType) // verify the type + Lambda(Seq(ValDef(x)), functionInvocation(fd, Variable(x)::lambdas)) + } + case _ => Stream.empty } - }) match { - case Some(fd) => - //println("Found fd: " + fd.id.name) - val ste = new StringTracingEvaluator(ctx, program) - try { - val result = ste.eval(FunctionInvocation(fd.typed, Seq(v))) + case None => Stream.empty + } + } + + + /** Actually prints the expression with as alternative the given orElse */ + def print(v: Expr, orElse: =>String, excluded: Set[FunDef] = Set())(implicit ctx: LeonContext, program: Program): String = { + this.excluded = excluded + val s = prettyPrintersForType(v.getType) // TODO: Included the variable excluded if necessary. + if(s.isEmpty) { + orElse + } else { + val l: Lambda = s.head + println("Executing pretty printer for type " + v.getType + " : " + l + " on " + v) + val ste = new DefaultEvaluator(ctx, program) + try { + val toEvaluate = application(l, Seq(v)) + val result = ste.eval(toEvaluate) result.result match { - case Some((StringLiteral(res), _)) if res != "" => + case Some(StringLiteral(res)) if res != "" => res - case _ => - orElse - } - } catch { - case e: evaluators.ContextualEvaluator#EvalError => + case res => + println("not a string literal " + res) orElse } - case None => - orElse + } catch { + case e: evaluators.ContextualEvaluator#EvalError => + println("Error " + e.msg) + orElse + } } } } \ No newline at end of file diff --git a/src/main/scala/leon/synthesis/ExamplesFinder.scala b/src/main/scala/leon/synthesis/ExamplesFinder.scala index 1755966e0223c0a4fa75eb01eb4a681ed1ddbf1d..78a483446624a28482772efe5752a4f6f44e1995 100644 --- a/src/main/scala/leon/synthesis/ExamplesFinder.scala +++ b/src/main/scala/leon/synthesis/ExamplesFinder.scala @@ -22,6 +22,13 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { implicit val ctx = ctx0 val reporter = ctx.reporter + + private var keepAbstractExamples = false + /** If true, will not evaluate examples to check them. */ + def setKeepAbstractExamples(b: Boolean) = { this.keepAbstractExamples = b; this} + /** Sets if evalution of the result of tests should stop on choose statements. + * Useful for programming by Example */ + def setEvaluationFailOnChoose(b: Boolean) = { evaluator.setEvaluationFailOnChoose(b); this } def extractFromFunDef(fd: FunDef, partition: Boolean): ExamplesBank = fd.postcondition match { case Some(Lambda(Seq(ValDef(id)), post)) => @@ -38,6 +45,8 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { Some(InOutExample(fd.params.map(p => t(p.id)), Seq(t(id)))) } else if ((ids & insIds) == insIds) { Some(InExample(fd.params.map(p => t(p.id)))) + } else if((ids & outsIds) == outsIds) { // Examples provided on a part of the inputs. + Some(InOutExample(fd.params.map(p => t.getOrElse(p.id, Variable(p.id))), Seq(t(id)))) } else { None } @@ -64,27 +73,31 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { case None => ExamplesBank(Nil, Nil) } - - // Extract examples from the passes found in expression + + /** Extract examples from the passes found in expression */ def extractFromProblem(p: Problem): ExamplesBank = { val testClusters = extractTestsOf(and(p.pc, p.phi)) // Finally, we keep complete tests covering all as++xs val allIds = (p.as ++ p.xs).toSet val insIds = p.as.toSet - + val outsIds = p.xs.toSet + val examples = testClusters.toSeq.flatMap { t => val ids = t.keySet if ((ids & allIds) == allIds) { Some(InOutExample(p.as.map(t), p.xs.map(t))) } else if ((ids & insIds) == insIds) { Some(InExample(p.as.map(t))) + } else if((ids & outsIds) == outsIds) { // Examples provided on a part of the inputs. + Some(InOutExample(p.as.map(p => t.getOrElse(p, Variable(p))), p.xs.map(t))) } else { None } } def isValidExample(ex: Example): Boolean = { + if(this.keepAbstractExamples) return true // TODO: Abstract interpretation here ? val (mapping, cond) = ex match { case io: InOutExample => (Map((p.as zip io.ins) ++ (p.xs zip io.outs): _*), And(p.pc, p.phi)) @@ -114,6 +127,7 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { ExamplesBank(generatedExamples.toSeq ++ solverExamples.toList, Nil) } + /** Extracts all passes constructs from the given postcondition, merges them if needed */ private def extractTestsOf(e: Expr): Set[Map[Identifier, Expr]] = { val allTests = collect[Map[Identifier, Expr]] { case Passes(ins, outs, cases) => @@ -130,14 +144,15 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { case _ => test } } - - // Check whether we can extract all ids from example - val results = exs.collect { case e if infos.forall(_._2.isDefinedAt(e)) => - infos.map{ case (id, f) => id -> f(e) }.toMap + try { + // Check whether we can extract all ids from example + val results = exs.collect { case e if this.keepAbstractExamples || infos.forall(_._2.isDefinedAt(e)) => + infos.map{ case (id, f) => id -> f(e) }.toMap + } + results.toSet + } catch { + case e: IDExtractionException => Set() } - - results.toSet - case _ => Set() }(e) @@ -146,7 +161,8 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { consolidateTests(allTests) } - + /** Processes ((in, out) passes { + * cs[=>Case pattExpr if guard => outR]*/ private def caseToExamples(in: Expr, out: Expr, cs: MatchCase, examplesPerCase: Int = 5): Seq[(Expr,Expr)] = { def doSubstitute(subs : Seq[(Identifier, Expr)], e : Expr) = @@ -180,16 +196,26 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { // If the input contains free variables, it does not provide concrete examples. // We will instantiate them according to a simple grammar to get them. + if(this.keepAbstractExamples) { + cs.optGuard match { + case Some(BooleanLiteral(false)) => + Seq() + case None => + Seq((pattExpr, cs.rhs)) + case Some(pred) => + Seq((Require(pred, pattExpr), cs.rhs)) + } + } else { + val dataGen = new GrammarDataGen(evaluator) - val dataGen = new GrammarDataGen(evaluator) - - val theGuard = replace(Map(in -> pattExpr), cs.optGuard.getOrElse(BooleanLiteral(true))) + val theGuard = replace(Map(in -> pattExpr), cs.optGuard.getOrElse(BooleanLiteral(true))) - dataGen.generateFor(freeVars, theGuard, examplesPerCase, 1000).toSeq map { vals => - val inst = freeVars.zip(vals).toMap - val inR = replaceFromIDs(inst, pattExpr) - val outR = replaceFromIDs(inst, doSubstitute(ieMap, cs.rhs)) - (inR, outR) + dataGen.generateFor(freeVars, theGuard, examplesPerCase, 1000).toSeq map { vals => + val inst = freeVars.zip(vals).toMap + val inR = replaceFromIDs(inst, pattExpr) + val outR = replaceFromIDs(inst, doSubstitute(ieMap, cs.rhs)) + (inR, outR) + } } } } @@ -232,6 +258,8 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { } consolidated } + + case class IDExtractionException(msg: String) extends Exception(msg) /** Extract ids in ins/outs args, and compute corresponding extractors for values map * @@ -251,13 +279,13 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { case Tuple(vs) => vs.map(extractIds).zipWithIndex.flatMap{ case (ids, i) => ids.map{ case (id, e) => - (id, andThen({ case Tuple(vs) => vs(i) }, e)) + (id, andThen({ case Tuple(vs) => vs(i) case e => throw new IDExtractionException("Expected Tuple, got " + e) }, e)) } } case CaseClass(cct, args) => args.map(extractIds).zipWithIndex.flatMap { case (ids, i) => ids.map{ case (id, e) => - (id, andThen({ case CaseClass(cct2, vs) if cct2 == cct => vs(i) } ,e)) + (id, andThen({ case CaseClass(cct2, vs) if cct2 == cct => vs(i) case e => throw new IDExtractionException("Expected Case class of type " + cct + ", got " + e) } ,e)) } } diff --git a/src/main/scala/leon/synthesis/Solution.scala b/src/main/scala/leon/synthesis/Solution.scala index dfe90d2f775e8ed3736020cb1e827c75fe602ad9..affa41df46292ae73a6b9a87150283ec8c0ee997 100644 --- a/src/main/scala/leon/synthesis/Solution.scala +++ b/src/main/scala/leon/synthesis/Solution.scala @@ -31,7 +31,8 @@ class Solution(val pre: Expr, val defs: Set[FunDef], val term: Expr, val isTrust } def toExpr = { - letDef(defs.toList, guardedTerm) + if(defs.isEmpty) guardedTerm else + LetDef(defs.toList, guardedTerm) } // Projects a solution (ignore several output variables) diff --git a/src/main/scala/leon/synthesis/disambiguation/ExamplesAdder.scala b/src/main/scala/leon/synthesis/disambiguation/ExamplesAdder.scala index 6ce5f020ae88c649458b857afca76b207b68916f..9303b4d1ef9813a38109b35c17adadf546297252 100644 --- a/src/main/scala/leon/synthesis/disambiguation/ExamplesAdder.scala +++ b/src/main/scala/leon/synthesis/disambiguation/ExamplesAdder.scala @@ -3,17 +3,46 @@ package leon package synthesis package disambiguation +import purescala.Types.FunctionType import purescala.Common.FreshIdentifier import purescala.Constructors.{ and, tupleWrap } import purescala.Definitions.{ FunDef, Program, ValDef } -import purescala.ExprOps.expressionToPattern +import purescala.ExprOps import purescala.Extractors.TopLevelAnds import purescala.Expressions._ /** * @author Mikael */ +object ExamplesAdder { + def replaceGenericValuesByVariable(e: Expr): (Expr, Map[Expr, Expr]) = { + var assignment = Map[Expr, Expr]() + var extension = 'a' + var id = "" + (ExprOps.postMap({ expr => expr match { + case g@GenericValue(tpe, index) => + val newIdentifier = FreshIdentifier(tpe.id.name.take(1).toLowerCase() + tpe.id.name.drop(1) + extension + id, tpe.id.getType) + if(extension != 'z' && extension != 'Z') + extension = (extension.toInt + 1).toChar + else if(extension == 'z') // No more than 52 generic variables in practice? + extension = 'A' + else { + if(id == "") id = "1" else id = (id.toInt + 1).toString + } + + val newVar = Variable(newIdentifier) + assignment += g -> newVar + Some(newVar) + case _ => None + } })(e), assignment) + } +} + class ExamplesAdder(ctx0: LeonContext, program: Program) { + import ExamplesAdder._ + var _removeFunctionParameters = false + + def setRemoveFunctionParameters(b: Boolean) = { _removeFunctionParameters = b; this } /** Accepts the nth alternative of a question (0 being the current one) */ def acceptQuestion[T <: Expr](fd: FunDef, q: Question[T], alternativeIndex: Int): Unit = { @@ -24,7 +53,8 @@ class ExamplesAdder(ctx0: LeonContext, program: Program) { /** Adds the given input/output examples to the function definitions */ def addToFunDef(fd: FunDef, examples: Seq[(Expr, Expr)]) = { - val inputVariables = tupleWrap(fd.params.map(p => Variable(p.id): Expr)) + val params = if(_removeFunctionParameters) fd.params.filter(x => !x.getType.isInstanceOf[FunctionType]) else fd.params + val inputVariables = tupleWrap(params.map(p => Variable(p.id): Expr)) val newCases = examples.map{ case (in, out) => exampleToCase(in, out) } fd.postcondition match { case Some(Lambda(Seq(ValDef(id)), post)) => @@ -65,12 +95,12 @@ class ExamplesAdder(ctx0: LeonContext, program: Program) { } private def exampleToCase(in: Expr, out: Expr): MatchCase = { - val (inPattern, inGuard) = expressionToPattern(in) - if(inGuard != BooleanLiteral(true)) { + val (inPattern, inGuard) = ExprOps.expressionToPattern(in) + if(inGuard == BooleanLiteral(true)) { + MatchCase(inPattern, None, out) + } else /*if (in == in_raw) { } *else*/ { val id = FreshIdentifier("out", in.getType, true) MatchCase(WildcardPattern(Some(id)), Some(Equals(Variable(id), in)), out) - } else { - MatchCase(inPattern, None, out) } } } diff --git a/src/main/scala/leon/synthesis/disambiguation/QuestionBuilder.scala b/src/main/scala/leon/synthesis/disambiguation/QuestionBuilder.scala index d77c06c97de2808e38ab4c0e218fb614a99d8298..81f98f86432dc54ffb446f473fee3a1afcf358ca 100644 --- a/src/main/scala/leon/synthesis/disambiguation/QuestionBuilder.scala +++ b/src/main/scala/leon/synthesis/disambiguation/QuestionBuilder.scala @@ -13,6 +13,8 @@ import purescala.DefOps import grammars._ import solvers.ModelBuilder import scala.collection.mutable.ListBuffer +import evaluators.AbstractEvaluator +import scala.annotation.tailrec object QuestionBuilder { /** Sort methods for questions. You can build your own */ @@ -124,11 +126,48 @@ class QuestionBuilder[T <: Expr]( private def run(s: Solution, elems: Seq[(Identifier, Expr)]): Option[Expr] = { val newProgram = DefOps.addFunDefs(p, s.defs, p.definedFunctions.head) - val e = new DefaultEvaluator(c, newProgram) + val e = new AbstractEvaluator(c, newProgram) val model = new ModelBuilder model ++= elems val modelResult = model.result() - e.eval(s.term, modelResult).result + for{x <- e.eval(s.term, modelResult).result + res = x._1 + simp = ExprOps.simplifyArithmetic(res)} + yield simp + } + + /** Make all generic values unique. + * Duplicate generic values are not suitable for disambiguating questions since they remove an order. */ + def makeGenericValuesUnique(a: Expr): Expr = { + var genVals = Set[Expr with Terminal]() + def freshenValue(g: Expr with Terminal): Option[Expr with Terminal] = g match { + case g: GenericValue => Some(GenericValue(g.tp, g.id + 1)) + case StringLiteral(s) => + val i = s.lastIndexWhere { c => c < '0' || c > '9' } + val prefix = s.take(i+1) + val suffix = s.drop(i+1) + Some(StringLiteral(prefix + (if(suffix == "") "0" else (suffix.toInt + 1).toString))) + case InfiniteIntegerLiteral(i) => Some(InfiniteIntegerLiteral(i+1)) + case IntLiteral(i) => if(i == Integer.MAX_VALUE) None else Some(IntLiteral(i+1)) + case CharLiteral(c) => if(c == Char.MaxValue) None else Some(CharLiteral((c+1).toChar)) + case otherLiteral => None + } + @tailrec @inline def freshValue(g: Expr with Terminal): Expr with Terminal = { + if(genVals contains g) + freshenValue(g) match { + case None => g + case Some(v) => freshValue(v) + } + else { + genVals += g + g + } + } + ExprOps.postMap{ e => e match { + case g:Expr with Terminal => + Some(freshValue(g)) + case _ => None + }}(a) } /** Returns a list of input/output questions to ask to the user. */ @@ -136,7 +175,10 @@ class QuestionBuilder[T <: Expr]( if(solutions.isEmpty) return Nil val datagen = new GrammarDataGen(new DefaultEvaluator(c, p), value_enumerator) - val enumerated_inputs = datagen.generateMapping(input, BooleanLiteral(true), expressionsToTake, expressionsToTake).toList + val enumerated_inputs = datagen.generateMapping(input, BooleanLiteral(true), expressionsToTake, expressionsToTake) + .map(inputs => + inputs.map(id_expr => + (id_expr._1, makeGenericValuesUnique(id_expr._2)))).toList val solution = solutions.head val alternatives = solutions.drop(1).take(solutionsToTake).toList diff --git a/src/main/scala/leon/synthesis/rules/StringRender.scala b/src/main/scala/leon/synthesis/rules/StringRender.scala index 7b72da5396b179a82d4f8111335b8c82b11c76c8..62878dd31761050b839f794beb066bceca051a56 100644 --- a/src/main/scala/leon/synthesis/rules/StringRender.scala +++ b/src/main/scala/leon/synthesis/rules/StringRender.scala @@ -6,23 +6,27 @@ package rules import scala.annotation.tailrec import scala.collection.mutable.ListBuffer - +import bonsai.enumerators.MemoizedEnumerator +import evaluators.DefaultEvaluator +import evaluators.AbstractEvaluator +import purescala.Definitions.{FunDef, ValDef, Program, TypedFunDef, CaseClassDef, AbstractClassDef} import purescala.Common._ import purescala.Types._ import purescala.Constructors._ -import purescala.Definitions._ import purescala.Expressions._ import purescala.Extractors._ import purescala.TypeOps import purescala.DefOps import purescala.ExprOps - -import evaluators.StringTracingEvaluator +import purescala.SelfPrettyPrinter +import solvers.Model +import solvers.ModelBuilder +import solvers.string.StringSolver import synthesis.programsets.DirectProgramSet import synthesis.programsets.JoinProgramSet +import leon.utils.DebugSectionSynthesis + -import solvers.ModelBuilder -import solvers.string.StringSolver /** A template generator for a given type tree. @@ -67,60 +71,84 @@ case object StringRender extends Rule("StringRender") { var EDIT_ME = "_edit_me_" - var _defaultTypeToString: Option[Map[TypeTree, FunDef]] = None - - def defaultMapTypeToString()(implicit hctx: SearchContext): Map[TypeTree, FunDef] = { - _defaultTypeToString.getOrElse{ - // Updates the cache with the functions converting standard types to string. - val res = (hctx.program.library.StrOps.toSeq.flatMap { StrOps => - StrOps.defs.collect{ case d: FunDef if d.params.length == 1 && d.returnType == StringType => d.params.head.getType -> d } - }).toMap - _defaultTypeToString = Some(res) - res - } - } - - /** Returns a toString function converter if it has been defined. */ - class WithFunDefConverter(implicit hctx: SearchContext) { - def unapply(tpe: TypeTree): Option[FunDef] = { - _defaultTypeToString.flatMap(_.get(tpe)) - } - } + var enforceDefaultStringMethodsIfAvailable = true + var enforceSelfStringMethodsIfAvailable = false val booleanTemplate = (a: Expr) => StringTemplateGenerator(Hole => IfExpr(a, Hole, Hole)) - /** Returns a seq of expressions such as `x + y + "1" + y + "2" + z` associated to an expected result string `"1, 2"`. - * We use these equations so that we can find the values of the constants x, y, z and so on. - * This uses a custom evaluator which does not concatenate string but reminds the calculation. - */ - def createProblems(inlineFunc: Seq[FunDef], inlineExpr: Expr, examples: ExamplesBank): Seq[(Expr, String)] = ??? + import StringSolver.{StringFormToken, StringForm, Problem => SProblem, Equation, Assignment} - /** For each solution to the problem such as `x + "1" + y + j + "2" + z = 1, 2`, outputs all possible assignments if they exist. */ - def solveProblems(problems: Seq[(Expr, String)]): Seq[Map[Identifier, String]] = ??? + /** Augment the left-hand-side to have possible function calls, such as x + "const" + customToString(_) ... + * Function calls will be eliminated when converting to a valid problem. + */ + sealed abstract class AugmentedStringFormToken + case class RegularStringFormToken(e: StringFormToken) extends AugmentedStringFormToken + case class OtherStringFormToken(e: Expr) extends AugmentedStringFormToken + type AugmentedStringForm = List[AugmentedStringFormToken] - import StringSolver.{StringFormToken, StringForm, Problem => SProblem, Equation, Assignment} + /** Augments the right-hand-side to have possible function calls, such as "const" + customToString(_) ... + * Function calls will be eliminated when converting to a valid problem. + */ + sealed abstract class AugmentedStringChunkRHS + case class RegularStringChunk(e: String) extends AugmentedStringChunkRHS + case class OtherStringChunk(e: Expr) extends AugmentedStringChunkRHS + type AugmentedStringLiteral = List[AugmentedStringChunkRHS] /** Converts an expression to a stringForm, suitable for StringSolver */ - def toStringForm(e: Expr, acc: List[StringFormToken] = Nil)(implicit hctx: SearchContext): Option[StringForm] = e match { + def toStringForm(e: Expr, acc: List[AugmentedStringFormToken] = Nil)(implicit hctx: SearchContext): Option[AugmentedStringForm] = e match { case StringLiteral(s) => - Some(Left(s)::acc) - case Variable(id) => Some(Right(id)::acc) + Some(RegularStringFormToken(Left(s))::acc) + case Variable(id) => Some(RegularStringFormToken(Right(id))::acc) case StringConcat(lhs, rhs) => toStringForm(rhs, acc).flatMap(toStringForm(lhs, _)) + case e:Application => Some(OtherStringFormToken(e)::acc) + case e:FunctionInvocation => Some(OtherStringFormToken(e)::acc) case _ => None } /** Returns the string associated to the expression if it is computable */ - def toStringLiteral(e: Expr): Option[String] = e match { - case StringLiteral(s) => Some(s) - case StringConcat(lhs, rhs) => toStringLiteral(lhs).flatMap(k => toStringLiteral(rhs).map(l => k + l)) + def toStringLiteral(e: Expr): Option[AugmentedStringLiteral] = e match { + case StringLiteral(s) => Some(List(RegularStringChunk(s))) + case StringConcat(lhs, rhs) => + toStringLiteral(lhs).flatMap(k => toStringLiteral(rhs).map(l => (k.init, k.last, l) match { + case (kinit, RegularStringChunk(s), RegularStringChunk(sp)::ltail) => + kinit ++ (RegularStringChunk(s + sp)::ltail) + case _ => k ++ l + })) + case e: Application => Some(List(OtherStringChunk(e))) + case e: FunctionInvocation => Some(List(OtherStringChunk(e))) case _ => None } + /** Converts an equality AugmentedStringForm == AugmentedStringLiteral to a list of equations + * For that, splits both strings on function applications. If they yield the same value, we can split, else it fails. */ + def toEquations(lhs: AugmentedStringForm, rhs: AugmentedStringLiteral): Option[List[Equation]] = { + def rec(lhs: AugmentedStringForm, rhs: AugmentedStringLiteral, + accEqs: ListBuffer[Equation], accLeft: ListBuffer[StringFormToken], accRight: StringBuffer): Option[List[Equation]] = (lhs, rhs) match { + case (Nil, Nil) => + (accLeft.toList, accRight.toString) match { + case (Nil, "") => Some(accEqs.toList) + case (lhs, rhs) => Some((accEqs += ((lhs, rhs))).toList) + } + case (OtherStringFormToken(e)::lhstail, OtherStringChunk(f)::rhstail) => + if(ExprOps.canBeHomomorphic(e, f).nonEmpty) { + rec(lhstail, rhstail, accEqs += ((accLeft.toList, accRight.toString)), ListBuffer[StringFormToken](), new StringBuffer) + } else None + case (OtherStringFormToken(e)::lhstail, Nil) => + None + case (Nil, OtherStringChunk(f)::rhstail) => + None + case (lhs, RegularStringChunk(s)::rhstail) => + rec(lhs, rhstail, accEqs, accLeft, accRight append s) + case (RegularStringFormToken(e)::lhstail, rhs) => + rec(lhstail, rhs, accEqs, accLeft += e, accRight) + } + rec(lhs, rhs, ListBuffer[Equation](), ListBuffer[StringFormToken](), new StringBuffer) + } + /** Returns a stream of assignments compatible with input/output examples for the given template */ def findAssignments(p: Program, inputs: Seq[Identifier], examples: ExamplesBank, template: Expr)(implicit hctx: SearchContext): Stream[Map[Identifier, String]] = { - //new Evaluator() - val e = new StringTracingEvaluator(hctx.context, p) + val e = new AbstractEvaluator(hctx.context, p) @tailrec def gatherEquations(s: List[InOutExample], acc: ListBuffer[Equation] = ListBuffer()): Option[SProblem] = s match { case Nil => Some(acc.toList) @@ -132,34 +160,39 @@ case object StringRender extends Rule("StringRender") { val evalResult = e.eval(template, modelResult) evalResult.result match { case None => - hctx.reporter.debug("Eval = None : ["+template+"] in ["+inputs.zip(in)+"]") + hctx.reporter.info("Eval = None : ["+template+"] in ["+inputs.zip(in)+"]") + hctx.reporter.info(evalResult) 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) { - hctx.reporter.ifDebug(printer => printer("sf empty or rhs empty ["+sfExpr+"] => ["+sf+"] in ["+rhs+"]")) - None - } else gatherEquations(q, acc += ((sf.get, rhs.get))) + (sf, rhs) match { + case (Some(sfget), Some(rhsget)) => + toEquations(sfget, rhsget) match { + case Some(equations) => + gatherEquations(q, acc ++= equations) + case None => + hctx.reporter.info("Could not extract equations from ["+sfget+"] == ["+rhsget+"]\n coming from ... == " + rhExpr) + None + } + case _ => + hctx.reporter.info("sf empty or rhs empty ["+sfExpr+"] => ["+sf+"] in ["+rhs+"]") + None + } } } else { - hctx.reporter.ifDebug(printer => printer("RHS.length != 1 : ["+rhExpr+"]")) + hctx.reporter.info("RHS.length != 1 : ["+rhExpr+"]") None } } gatherEquations((examples.valids ++ examples.invalids).collect{ case io:InOutExample => io }.toList) match { - case Some(problem) => - hctx.reporter.debug("Problem: ["+StringSolver.renderProblem(problem)+"]") - val res = StringSolver.solve(problem) - hctx.reporter.debug("Solution found:"+res.nonEmpty) - res - case None => - hctx.reporter.ifDebug(printer => printer("No problem found")) - Stream.empty + case Some(problem) => StringSolver.solve(problem) + case None => Stream.empty } } + /** With a given (template, fundefs, consts) will find consts so that (expr, funs) passes all the examples */ def findSolutions(examples: ExamplesBank, template: Stream[WithIds[Expr]], funDefs: Seq[(FunDef, Stream[WithIds[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))) @@ -169,7 +202,7 @@ case object StringRender extends Rule("StringRender") { def computeSolutions(funDefsBodies: Seq[(FunDef, WithIds[Expr])], template: WithIds[Expr]): Stream[Assignment] = { val funDefs = for((funDef, body) <- funDefsBodies) yield { funDef.body = Some(body._1); funDef } val newProgram = DefOps.addFunDefs(hctx.program, funDefs, hctx.sctx.functionContext) - findAssignments(newProgram, p.as, examples, template._1) + findAssignments(newProgram, p.as.filter{ x => !x.getType.isInstanceOf[FunctionType] }, examples, template._1) } val tagged_solutions = @@ -212,7 +245,7 @@ case object StringRender extends Rule("StringRender") { var transformMap = Map[FunDef, FunDef]() def mapExpr(body: Expr): Expr = { ExprOps.preMap((e: Expr) => e match { - case FunctionInvocation(TypedFunDef(fd, _), args) if fd != program.library.escape.get => Some(FunctionInvocation(getMapping(fd).typed, args)) + case FunctionInvocation(TypedFunDef(fd, _), args) if fds(fd) => Some(FunctionInvocation(getMapping(fd).typed, args)) case e => None })(body) } @@ -234,10 +267,6 @@ case object StringRender extends Rule("StringRender") { case class DependentType(caseClassParent: Option[TypeTree], inputName: String, typeToConvert: TypeTree) - object StringSynthesisContext { - def empty(implicit hctx: SearchContext) = new StringSynthesisContext(None, new StringSynthesisResult(Map(), Set())) - } - type MapFunctions = Map[DependentType, (FunDef, Stream[WithIds[Expr]])] /** Result of the current synthesis process */ @@ -259,17 +288,39 @@ case object StringRender extends Rule("StringRender") { s0 } } + type StringConverters = Map[TypeTree, List[Expr => Expr]] + + /** Companion object to create a StringSynthesisContext */ + object StringSynthesisContext { + def empty( + abstractStringConverters: StringConverters, + originalInputs: Set[Expr], + provided_functions: Seq[Identifier])(implicit hctx: SearchContext) = + new StringSynthesisContext(None, new StringSynthesisResult(Map(), Set()), + abstractStringConverters, + originalInputs, + provided_functions) + } /** Context for the current synthesis process */ class StringSynthesisContext( val currentCaseClassParent: Option[TypeTree], - val result: StringSynthesisResult + val result: StringSynthesisResult, + val abstractStringConverters: StringConverters, + val originalInputs: Set[Expr], + val provided_functions: Seq[Identifier] )(implicit hctx: SearchContext) { def add(d: DependentType, f: FunDef, s: Stream[WithIds[Expr]]): StringSynthesisContext = { - new StringSynthesisContext(currentCaseClassParent, result.add(d, f, s)) + new StringSynthesisContext(currentCaseClassParent, result.add(d, f, s), + abstractStringConverters, + originalInputs, + provided_functions) } def copy(currentCaseClassParent: Option[TypeTree]=currentCaseClassParent, result: StringSynthesisResult = result): StringSynthesisContext = - new StringSynthesisContext(currentCaseClassParent, result) + new StringSynthesisContext(currentCaseClassParent, result, + abstractStringConverters, + originalInputs, + provided_functions) def freshFunName(s: String) = result.freshFunName(s) } @@ -289,7 +340,8 @@ case object StringRender extends Rule("StringRender") { val funName = funName3(0).toLower + funName3.substring(1) val funId = FreshIdentifier(ctx.freshFunName(funName), alwaysShowUniqueID = true) val argId= FreshIdentifier(tpe.typeToConvert.asString(hctx.context).toLowerCase()(0).toString, tpe.typeToConvert) - val fd = new FunDef(funId, Nil, ValDef(argId) :: Nil, StringType) // Empty function. + val tparams = hctx.sctx.functionContext.tparams + val fd = new FunDef(funId, tparams, ValDef(argId) :: ctx.provided_functions.map(ValDef(_)).toList, StringType) // Empty function. fd } @@ -365,54 +417,89 @@ case object StringRender extends Rule("StringRender") { val dependentType = DependentType(ctx.currentCaseClassParent, input.asString(hctx.program)(hctx.context), input.getType) ctx.result.adtToString.get(dependentType) match { case Some(fd) => - gatherInputs(ctx, q, result += Stream((functionInvocation(fd._1, Seq(input)), Nil))) + gatherInputs(ctx, q, result += Stream((functionInvocation(fd._1, input::ctx.provided_functions.toList.map(Variable)), Nil))) case None => // No function can render the current type. + // We should not rely on calling the original function on the first line of the body of the function itself. + val exprs1s = (new SelfPrettyPrinter) + .allowFunction(hctx.sctx.functionContext) + .excludeFunction(hctx.sctx.functionContext) + .prettyPrintersForType(input.getType)(hctx.context, hctx.program) + .map(l => (application(l, Seq(input)), List[Identifier]())) // Use already pre-defined pretty printers. + val exprs1 = exprs1s.toList.sortBy{ case (Lambda(_, FunctionInvocation(fd, _)), _) if fd == hctx.sctx.functionContext => 0 case _ => 1} + val exprs2 = ctx.abstractStringConverters.getOrElse(input.getType, Nil).map(f => (f(input), List[Identifier]())) + val defaultConverters: Stream[WithIds[Expr]] = exprs1.toStream #::: exprs2.toStream + val recursiveConverters: Stream[WithIds[Expr]] = + (new SelfPrettyPrinter) + .prettyPrinterFromCandidate(hctx.sctx.functionContext, input.getType)(hctx.context, hctx.program) + .map(l => (application(l, Seq(input)), List[Identifier]())) + + def mergeResults(templateConverters: =>Stream[WithIds[Expr]]): Stream[WithIds[Expr]] = { + if(defaultConverters.isEmpty) templateConverters + else if(enforceDefaultStringMethodsIfAvailable) { + if(enforceSelfStringMethodsIfAvailable) + recursiveConverters #::: defaultConverters + else { + defaultConverters #::: recursiveConverters + } + } + else recursiveConverters #::: defaultConverters #::: templateConverters + } + input.getType match { case StringType => gatherInputs(ctx, q, result += - (Stream((input, Nil), - (FunctionInvocation( - hctx.program.library.escape.get.typed, - Seq(input)): Expr, Nil)))) + mergeResults(Stream((input, Nil), + (functionInvocation( + hctx.program.library.escape.get, List(input)): Expr, Nil)))) case BooleanType => val (bTemplate, vs) = booleanTemplate(input).instantiateWithVars - gatherInputs(ctx, q, result += Stream((BooleanToString(input), Nil), (bTemplate, vs))) + gatherInputs(ctx, q, result += mergeResults(Stream((BooleanToString(input), Nil), (bTemplate, vs)))) case WithStringconverter(converter) => // Base case - gatherInputs(ctx, q, result += Stream((converter(input), Nil))) + gatherInputs(ctx, q, result += mergeResults(Stream((converter(input), Nil)))) case t: ClassType => - // Create the empty function body and updates the assignments parts. - val fd = createEmptyFunDef(ctx, dependentType) - val ctx2 = preUpdateFunDefBody(dependentType, fd, ctx) // Inserts the FunDef in the assignments so that it can already be used. - t.root match { - case act@AbstractClassType(acd@AbstractClassDef(id, tparams, parent), tps) => - // Create a complete FunDef body with pattern matching - - val allKnownDescendantsAreCCAndHaveZeroArgs = act.knownCCDescendants.forall { x => x match { - case CaseClassType(ccd@CaseClassDef(id, tparams, parent, isCaseObject), tparams2) => ccd.fields.isEmpty - case _ => false - }} - - //TODO: Test other templates not only with Wilcard patterns, but more cases options for non-recursive classes (e.g. Option, Boolean, Finite parameterless case classes.) - val (ctx3, cases) = ((ctx2, ListBuffer[Stream[WithIds[MatchCase]]]()) /: act.knownCCDescendants) { - case ((ctx22, acc), cct@CaseClassType(ccd@CaseClassDef(id, tparams, parent, isCaseObject), tparams2)) => - val (newCases, result) = extractCaseVariants(cct, ctx22) - val ctx23 = ctx22.copy(result = result) - (ctx23, acc += newCases) - case ((adtToString, acc), e) => hctx.reporter.fatalError("Could not handle this class definition for string rendering " + e) - } - - val allMatchExprsEnd = JoinProgramSet(cases.map(DirectProgramSet(_)), mergeMatchCases(fd)).programs // General pattern match expressions - val allMatchExprs = if(allKnownDescendantsAreCCAndHaveZeroArgs) { - Stream(constantPatternMatching(fd, act)) ++ allMatchExprsEnd - } else allMatchExprsEnd - gatherInputs(ctx3.add(dependentType, fd, allMatchExprs), q, result += Stream((functionInvocation(fd, Seq(input)), Nil))) - case cct@CaseClassType(ccd@CaseClassDef(id, tparams, parent, isCaseObject), tparams2) => - val (newCases, result3) = extractCaseVariants(cct, ctx2) - val allMatchExprs = newCases.map(acase => mergeMatchCases(fd)(Seq(acase))) - gatherInputs(ctx2.copy(result = result3).add(dependentType, fd, allMatchExprs), q, result += Stream((functionInvocation(fd, Seq(input)), Nil))) + if(enforceDefaultStringMethodsIfAvailable && !defaultConverters.isEmpty) { + gatherInputs(ctx, q, result += defaultConverters) + } else { + // Create the empty function body and updates the assignments parts. + val fd = createEmptyFunDef(ctx, dependentType) + val ctx2 = preUpdateFunDefBody(dependentType, fd, ctx) // Inserts the FunDef in the assignments so that it can already be used. + t.root match { + case act@AbstractClassType(acd@AbstractClassDef(id, tparams, parent), tps) => + // Create a complete FunDef body with pattern matching + + val allKnownDescendantsAreCCAndHaveZeroArgs = act.knownCCDescendants.forall { x => x match { + case CaseClassType(ccd@CaseClassDef(id, tparams, parent, isCaseObject), tparams2) => ccd.fields.isEmpty + case _ => false + }} + + //TODO: Test other templates not only with Wilcard patterns, but more cases options for non-recursive classes (e.g. Option, Boolean, Finite parameterless case classes.) + val (ctx3, cases) = ((ctx2, ListBuffer[Stream[WithIds[MatchCase]]]()) /: act.knownCCDescendants) { + case ((ctx22, acc), cct@CaseClassType(ccd@CaseClassDef(id, tparams, parent, isCaseObject), tparams2)) => + val (newCases, result) = extractCaseVariants(cct, ctx22) + val ctx23 = ctx22.copy(result = result) + (ctx23, acc += newCases) + case ((adtToString, acc), e) => hctx.reporter.fatalError("Could not handle this class definition for string rendering " + e) + } + + val allMatchExprsEnd = JoinProgramSet(cases.map(DirectProgramSet(_)), mergeMatchCases(fd)).programs // General pattern match expressions + val allMatchExprs = if(allKnownDescendantsAreCCAndHaveZeroArgs) { + Stream(constantPatternMatching(fd, act)) ++ allMatchExprsEnd + } else allMatchExprsEnd + gatherInputs(ctx3.add(dependentType, fd, allMatchExprs), q, + result += Stream((functionInvocation(fd, input::ctx.provided_functions.toList.map(Variable)), Nil))) + case cct@CaseClassType(ccd@CaseClassDef(id, tparams, parent, isCaseObject), tparams2) => + val (newCases, result3) = extractCaseVariants(cct, ctx2) + val allMatchExprs = newCases.map(acase => mergeMatchCases(fd)(Seq(acase))) + gatherInputs(ctx2.copy(result = result3).add(dependentType, fd, allMatchExprs), q, + result += Stream((functionInvocation(fd, input::ctx.provided_functions.toList.map(Variable)), Nil))) + } } case TypeParameter(t) => - hctx.reporter.fatalError("Could not handle type parameter for string rendering " + t) + if(defaultConverters.isEmpty) { + hctx.reporter.fatalError("Could not handle type parameter for string rendering " + t) + } else { + gatherInputs(ctx, q, result += mergeResults(Stream.empty)) + } case tpe => hctx.reporter.fatalError("Could not handle class type for string rendering " + tpe) } @@ -448,7 +535,7 @@ case object StringRender extends Rule("StringRender") { } template } - (templates.flatten, ctx2.result) // TODO: Flatten or interleave? + (templates.flatten, ctx2.result) } def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { @@ -456,15 +543,32 @@ case object StringRender extends Rule("StringRender") { p.xs match { case List(IsTyped(v, StringType)) => val description = "Creates a standard string conversion function" - - val defaultToStringFunctions = defaultMapTypeToString() - + val examplesFinder = new ExamplesFinder(hctx.context, hctx.program) + .setKeepAbstractExamples(true) + .setEvaluationFailOnChoose(true) val examples = examplesFinder.extractFromProblem(p) + val abstractStringConverters: StringConverters = + (p.as.flatMap { case x => x.getType match { + case FunctionType(Seq(aType), StringType) => List((aType, (arg: Expr) => application(Variable(x), Seq(arg)))) + case _ => Nil + }}).groupBy(_._1).mapValues(_.map(_._2)) + + val (inputVariables, functionVariables) = p.as.partition ( x => x.getType match { + case f: FunctionType => false + case _ => true + }) + val ruleInstantiations = ListBuffer[RuleInstantiation]() + val originalInputs = inputVariables.map(Variable) ruleInstantiations += RuleInstantiation("String conversion") { - val (expr, synthesisResult) = createFunDefsTemplates(StringSynthesisContext.empty, p.as.map(Variable)) + val (expr, synthesisResult) = createFunDefsTemplates( + StringSynthesisContext.empty( + abstractStringConverters, + originalInputs.toSet, + functionVariables + ), originalInputs) val funDefs = synthesisResult.adtToString /*val toDebug: String = (("\nInferred functions:" /: funDefs)( (t, s) => diff --git a/src/main/scala/leon/verification/VerificationCondition.scala b/src/main/scala/leon/verification/VerificationCondition.scala index e23c6fe92317ae92e9177dd4feb56c77a9a6bb64..36f442f2322e78248d2d454956fa46ddedb28037 100644 --- a/src/main/scala/leon/verification/VerificationCondition.scala +++ b/src/main/scala/leon/verification/VerificationCondition.scala @@ -7,7 +7,6 @@ import leon.purescala.Definitions._ import leon.purescala.Types._ import leon.purescala.PrettyPrinter import leon.utils.Positioned -import leon.evaluators.StringTracingEvaluator import leon.solvers._ import leon.LeonContext import leon.purescala.SelfPrettyPrinter diff --git a/src/main/scala/leon/verification/VerificationReport.scala b/src/main/scala/leon/verification/VerificationReport.scala index d695f4ca0877382eeb83838a3cf08267ee935cb6..bb28f259db42657e3365922fdcdad64442ecc1ba 100644 --- a/src/main/scala/leon/verification/VerificationReport.scala +++ b/src/main/scala/leon/verification/VerificationReport.scala @@ -3,7 +3,6 @@ package leon package verification -import evaluators.StringTracingEvaluator import utils.DebugSectionSynthesis import utils.DebugSectionVerification import leon.purescala diff --git a/src/test/scala/leon/integration/purescala/ExprOpsSuite.scala b/src/test/scala/leon/integration/purescala/ExprOpsSuite.scala index 17260f7e5263cce8e61e57f5e8b684d1a736232f..fe548c1b013e3cbef8e5a7024286c1e9e8781e0f 100644 --- a/src/test/scala/leon/integration/purescala/ExprOpsSuite.scala +++ b/src/test/scala/leon/integration/purescala/ExprOpsSuite.scala @@ -7,6 +7,7 @@ import leon.test._ import leon.purescala.Constructors._ import leon.purescala.Expressions._ import leon.purescala.ExprOps._ +import leon.purescala.Definitions._ import leon.purescala.Common._ class ExprOpsSuite extends LeonTestSuiteWithProgram with helpers.ExpressionsDSL { @@ -101,5 +102,35 @@ class ExprOpsSuite extends LeonTestSuiteWithProgram with helpers.ExpressionsDSL assert(isSubtypeOf(simplestValue(act).getType, act)) assert(simplestValue(cct).getType == cct) } + + test("canBeHomomorphic") { implicit fix => + import leon.purescala.ExprOps.canBeHomomorphic + import leon.purescala.Types._ + import leon.purescala.Definitions._ + val d = FreshIdentifier("d", IntegerType) + val x = FreshIdentifier("x", IntegerType) + val y = FreshIdentifier("y", IntegerType) + assert(canBeHomomorphic(Variable(d), Variable(x)).isEmpty) + val l1 = Lambda(Seq(ValDef(x)), Variable(x)) + val l2 = Lambda(Seq(ValDef(y)), Variable(y)) + assert(canBeHomomorphic(l1, l2).nonEmpty) + val fType = FunctionType(Seq(IntegerType), IntegerType) + val f = FreshIdentifier("f", + FunctionType(Seq(IntegerType, fType, fType), IntegerType)) + val farg1 = FreshIdentifier("arg1", IntegerType) + val farg2 = FreshIdentifier("arg2", fType) + val farg3 = FreshIdentifier("arg3", fType) + + val fdef = new FunDef(f, Seq(), Seq(ValDef(farg1), ValDef(farg2), ValDef(farg3)), IntegerType) + + // Captured variables should be silent, even if they share the same identifier in two places of the code. + assert(canBeHomomorphic( + FunctionInvocation(fdef.typed, Seq(Variable(d), l1, l2)), + FunctionInvocation(fdef.typed, Seq(Variable(d), l1, l1))).nonEmpty) + + assert(canBeHomomorphic( + StringLiteral("1"), + StringLiteral("2")).isEmpty) + } } diff --git a/src/test/scala/leon/integration/solvers/StringRenderSuite.scala b/src/test/scala/leon/integration/solvers/StringRenderSuite.scala new file mode 100644 index 0000000000000000000000000000000000000000..cd6bd2ee75a9594d2c0be081db265e75c7a45620 --- /dev/null +++ b/src/test/scala/leon/integration/solvers/StringRenderSuite.scala @@ -0,0 +1,431 @@ +package leon.integration.solvers + + +import org.scalatest.FunSuite +import org.scalatest.Matchers +import leon.test.helpers.ExpressionsDSL +import leon.solvers.string.StringSolver +import leon.purescala.Common.FreshIdentifier +import leon.purescala.Common.Identifier +import leon.purescala.Expressions._ +import leon.purescala.Definitions._ +import leon.purescala.DefOps +import leon.purescala.ExprOps +import leon.purescala.Types._ +import leon.purescala.TypeOps +import leon.purescala.Constructors._ +import leon.synthesis.rules.{StringRender, TypedTemplateGenerator} +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._ +import leon.purescala.Types.Int32Type +import leon.test.LeonTestSuiteWithProgram +import leon.synthesis.SourceInfo +import leon.synthesis.CostModels +import leon.synthesis.graph.SimpleSearch +import leon.synthesis.graph.AndNode +import leon.synthesis.SearchContext +import leon.synthesis.Synthesizer +import leon.synthesis.SynthesisSettings +import leon.synthesis.RuleApplication +import leon.synthesis.RuleClosed +import leon.evaluators._ +import leon.LeonContext +import leon.synthesis.rules.DetupleInput +import leon.synthesis.Rules +import leon.solvers.ModelBuilder +import scala.language.implicitConversions + +class StringRenderSuite extends LeonTestSuiteWithProgram with Matchers with ScalaFutures { + test("Template Generator simple"){ case (ctx: LeonContext, program: Program) => + val TTG = new TypedTemplateGenerator(IntegerType) {} + val e = TTG(hole => Plus(hole, hole)) + val (expr, vars) = e.instantiateWithVars + vars should have length 2 + expr shouldEqual Plus(Variable(vars(0)), Variable(vars(1))) + + val f = TTG.nested(hole => (Minus(hole, expr), vars)) + val (expr2, vars2) = f.instantiateWithVars + vars2 should have length 3 + vars2(0) shouldEqual vars(0) + vars2(1) shouldEqual vars(1) + expr2 shouldEqual Minus(Variable(vars2(2)), expr) + } + + trait withSymbols { + val x = FreshIdentifier("x", StringType) + val y = FreshIdentifier("y", StringType) + val i = FreshIdentifier("i", IntegerType) + val f = FreshIdentifier("f", FunctionType(Seq(IntegerType), StringType)) + val fd = new FunDef(f, Nil, Seq(ValDef(i)), StringType) + val fdi = FunctionInvocation(fd.typed, Seq(Variable(i))) + } + + test("toEquations working"){ case (ctx: LeonContext, program: Program) => + import StringRender._ + new withSymbols { + val lhs = RegularStringFormToken(Left("abc"))::RegularStringFormToken(Right(x))::OtherStringFormToken(fdi)::Nil + val rhs = RegularStringChunk("abcdef")::OtherStringChunk(fdi)::Nil + val p = toEquations(lhs, rhs) + p should not be 'empty + p.get should have length 1 + } + } + + test("toEquations working 2"){ case (ctx: LeonContext, program: Program) => + import StringRender._ + new withSymbols { + val lhs = RegularStringFormToken(Left("abc"))::RegularStringFormToken(Right(x))::OtherStringFormToken(fdi)::RegularStringFormToken(Right(y))::Nil + val rhs = RegularStringChunk("abcdef")::OtherStringChunk(fdi)::RegularStringChunk("123")::Nil + val p = toEquations(lhs, rhs) + p should not be 'empty + p.get should have length 2 + } + } + + test("toEquations failing"){ case (ctx: LeonContext, program: Program) => + import StringRender._ + new withSymbols { + val lhs = RegularStringFormToken(Left("abc"))::RegularStringFormToken(Right(x))::RegularStringFormToken(Right(y))::Nil + val rhs = RegularStringChunk("abcdef")::OtherStringChunk(fdi)::RegularStringChunk("123")::Nil + val p = toEquations(lhs, rhs) + p should be ('empty) + } + } + + def applyStringRenderOn(functionName: String): (FunDef, Program) = { + val ci = synthesisInfos(functionName) + val search = new SimpleSearch(ctx, ci, ci.problem, CostModels.default, Some(200)) + val synth = new Synthesizer(ctx, program, ci, SynthesisSettings(rules = Seq(StringRender))) + val orNode = search.g.root + if (!orNode.isExpanded) { + val hctx = SearchContext(synth.sctx, synth.ci, orNode, search) + orNode.expand(hctx) + } + val andNodes = orNode.descendants.collect { + case n: AndNode => + n + } + + val rulesApps = (for ((t, i) <- andNodes.zipWithIndex) yield { + val status = if (t.isDeadEnd) { + "closed" + } else { + "open" + } + t.ri.asString -> i + }).toMap + rulesApps should contain key "String conversion" + + val rid = rulesApps("String conversion") + val path = List(rid) + + val solutions = (search.traversePath(path) match { + case Some(an: AndNode) => + val hctx = SearchContext(synth.sctx, synth.ci, an, search) + an.ri.apply(hctx) + case _ => throw new Exception("Was not an and node") + }) match { + case RuleClosed(solutions) => solutions + case _ => fail("no solution found") + } + solutions should not be 'empty + val newProgram = DefOps.addFunDefs(synth.program, solutions.head.defs, synth.sctx.functionContext) + val newFd = ci.fd.duplicate() + newFd.body = Some(solutions.head.term) + val (newProgram2, _) = DefOps.replaceFunDefs(newProgram)({ fd => + if(fd == ci.fd) { + Some(newFd) + } else None + }, { (fi: FunctionInvocation, fd: FunDef) => + Some(FunctionInvocation(fd.typed, fi.args)) + }) + (newFd, newProgram2) + } + + def getFunctionArguments(functionName: String) = { + program.lookupFunDef("StringRenderSuite." + functionName) match { + case Some(fd) => fd.params + case None => + fail("Could not find function " + functionName + " in sources. Other functions:" + program.definedFunctions.map(_.id.name).sorted) + } + } + + implicit class StringUtils(s: String) { + def replaceByExample: String = + s.replaceAll("\\((\\w+):(.*) by example", "\\($1:$2 ensuring { (res: String) => ($1, res) passes { case _ if false => \"\" } }") + } + + val sources = List(""" + |import leon.lang._ + |import leon.collection._ + |import leon.lang.synthesis._ + |import leon.annotation._ + | + |object StringRenderSuite { + | def literalSynthesis(i: Int): String = ??? ensuring { (res: String) => (i, res) passes { case 42 => ":42." } } + | + | def booleanSynthesis(b: Boolean): String = ??? ensuring { (res: String) => (b, res) passes { case true => "yes" case false => "no" } } + | def booleanSynthesis2(b: Boolean): String = ??? ensuring { (res: String) => (b, res) passes { case true => "B: true" } } + | //def stringEscape(s: String): String = ??? ensuring { (res: String) => (s, res) passes { case "\n" => "done...\\n" } } + | + | case class Dummy(s: String) + | def dummyToString(d: Dummy): String = "{" + d.s + "}" + | + | case class Dummy2(s: String) + | def dummy2ToString(d: Dummy2): String = "<" + d.s + ">" + | + | case class Config(i: BigInt, t: (Int, String)) + | + | def configToString(c: Config): String = ??? ensuring { (res: String) => (c, res) passes { case Config(BigInt(1), (2, "3")) => "1: 2 -> 3" } } + | def configToString2(c: Config): String = ??? ensuring { (res: String) => (c, res) passes { case Config(BigInt(1), (2, "3")) => "3: 1 -> 2" } } + | + | sealed abstract class Tree + | case class Knot(left: Tree, right: Tree) extends Tree + | case class Bud(v: String) extends Tree + | + | def treeToString(t: Tree): String = ???[String] ensuring { + | (res : String) => (t, res) passes { + | case Knot(Knot(Bud("17"), Bud("14")), Bud("23")) => + | "<<17Y14>Y23>" + | case Bud("foo") => + | "foo" + | case Knot(Bud("foo"), Bud("foo")) => + | "<fooYfoo>" + | case Knot(Bud("foo"), Knot(Bud("bar"), Bud("foo"))) => + | "<fooY<barYfoo>>" + | } + | } + | + | sealed abstract class BList[T] + | case class BCons[T](head: (T, T), tail: BList[T]) extends BList[T] + | case class BNil[T]() extends BList[T] + | + | def bListToString[T](b: BList[T], f: T => String) = ???[String] ensuring + | { (res: String) => (b, res) passes { case BNil() => "[]" case BCons(a, BCons(b, BCons(c, BNil()))) => "[" + f(a._1) + "-" + f(a._2) + ", " + f(b._1) + "-" + f(b._2) + ", " + f(c._1) + "-" + f(c._2) + "]" }} + | + | // Handling one rendering function at a time. + | case class BConfig(flags: BList[Dummy]) + | def bConfigToString(b: BConfig): String = ???[String] ensuring + | { (res: String) => (b, res) passes { case BConfig(BNil()) => "Config" + bListToString[Dummy](BNil(), (x: Dummy) => dummyToString(x)) } } + | + | def customListToString[T](l: List[T], f: T => String): String = ???[String] ensuring + | { (res: String) => (l, res) passes { case _ if false => "" } } + | + | // Handling multiple rendering functions at the same time. + | case class DConfig(dums: List[Dummy], dums2: List[Dummy2]) + | def dConfigToString(dc: DConfig): String = ???[String] ensuring + | { (res: String) => (dc, res) passes { + | case DConfig(Nil(), Nil()) => + | "Solution:\n " + customListToString[Dummy](List[Dummy](), (x : Dummy) => dummyToString(x)) + "\n " + customListToString[Dummy2](List[Dummy2](), (x: Dummy2) => dummy2ToString(x)) } } + | + | case class Node(tag: String, l: List[Edge]) + | case class Edge(start: Node, end: Node) + | + | def nodeToString(n: Node): String = ??? by example + | def edgeToString(e: Edge): String = ??? by example + | def listEdgeToString(l: List[Edge]): String = ??? by example + |} + """.stripMargin.replaceByExample) + implicit val (ctx, program) = getFixture() + + val synthesisInfos = SourceInfo.extractFromProgram(ctx, program).map(si => si.fd.id.name -> si ).toMap + + def synthesizeAndTest(functionName: String, tests: (Seq[Expr], String)*) { + val (fd, program) = applyStringRenderOn(functionName) + val when = new DefaultEvaluator(ctx, program) + val args = getFunctionArguments(functionName) + for((in, out) <- tests) { + val expr = functionInvocation(fd, in) + when.eval(expr) match { + case EvaluationResults.Successful(value) => value shouldEqual StringLiteral(out) + case EvaluationResults.EvaluatorError(msg) => fail(/*program + "\n" + */msg) + case EvaluationResults.RuntimeError(msg) => fail(/*program + "\n" + */"Runtime: " + msg) + } + } + } + def synthesizeAndAbstractTest(functionName: String)(tests: (FunDef, Program) => Seq[(Seq[Expr], Expr)]) { + val (fd, program) = applyStringRenderOn(functionName) + val when_abstract = new AbstractEvaluator(ctx, program) + val args = getFunctionArguments(functionName) + for((in, out) <- tests(fd, program)) { + val expr = functionInvocation(fd, in) + when_abstract.eval(expr) match { + case EvaluationResults.Successful(value) => val m = ExprOps.canBeHomomorphic(value._1, out) + assert(m.nonEmpty, value._1 + " was not homomorphic with " + out) + case EvaluationResults.EvaluatorError(msg) => fail(/*program + "\n" + */msg) + case EvaluationResults.RuntimeError(msg) => fail(/*program + "\n" + */"Runtime: " + msg) + } + } + } + abstract class CCBuilder(ccName: String, prefix: String = "StringRenderSuite.")(implicit program: Program) { + val caseClassName = prefix + ccName + def getType: TypeTree = program.lookupCaseClass(caseClassName).get.typed + def apply(s: Expr*): CaseClass = { + CaseClass(program.lookupCaseClass(caseClassName).get.typed, s.toSeq) + } + def apply(s: String): CaseClass = this.apply(StringLiteral(s)) + } + abstract class ParamCCBuilder(caseClassName: String, prefix: String = "StringRenderSuite.")(implicit program: Program) { + def apply(types: TypeTree*)(s: Expr*): CaseClass = { + CaseClass(program.lookupCaseClass(prefix+caseClassName).get.typed(types), + s.toSeq) + } + } + def method(fName: String)(implicit program: Program) = { + program.lookupFunDef("StringRenderSuite." + fName).get + } + abstract class paramMethod(fName: String)(implicit program: Program) { + val fd = program.lookupFunDef("StringRenderSuite." + fName).get + def apply(types: TypeTree*)(args: Expr*) = + FunctionInvocation(fd.typed(types), args) + } + // Mimics the file above, allows construction of expressions. + case class Constructors(program: Program) { + implicit val p = program + implicit def CCBuilderToType(c: CCBuilder): TypeTree = c.getType + object Knot extends CCBuilder("Knot") + object Bud extends CCBuilder("Bud") + object Dummy extends CCBuilder("Dummy") + object Dummy2 extends CCBuilder("Dummy2") + object Cons extends ParamCCBuilder("Cons", "leon.collection.") + object Nil extends ParamCCBuilder("Nil", "leon.collection.") + object List { + def apply(types: TypeTree*)(elems: Expr*): CaseClass = { + elems.toList match { + case collection.immutable.Nil => Nil(types: _*)() + case a::b => Cons(types: _*)(a, List(types: _*)(b: _*)) + } + } + } + + object BCons extends ParamCCBuilder("BCons") + object BNil extends ParamCCBuilder("BNil") + object BList { + def apply(types: TypeTree*)(elems: Expr*): CaseClass = { + elems.toList match { + case collection.immutable.Nil => BNil(types: _*)() + case a::b => BCons(types: _*)(a, BList(types: _*)(b: _*)) + } + } + def helper(types: TypeTree*)(elems: (Expr, Expr)*): CaseClass = { + this.apply(types: _*)(elems.map(x => tupleWrap(Seq(x._1, x._2))): _*) + } + } + object Config extends CCBuilder("Config") { + def apply(i: Int, b: (Int, String)): CaseClass = + this.apply(InfiniteIntegerLiteral(i), tupleWrap(Seq(IntLiteral(b._1), StringLiteral(b._2)))) + } + object BConfig extends CCBuilder("BConfig") + object DConfig extends CCBuilder("DConfig") + lazy val dummyToString = method("dummyToString") + lazy val dummy2ToString = method("dummy2ToString") + lazy val bListToString = method("bListToString") + object customListToString extends paramMethod("customListToString") + } + + test("Literal synthesis"){ case (ctx: LeonContext, program: Program) => + synthesizeAndTest("literalSynthesis", + Seq(IntLiteral(156)) -> ":156.", + Seq(IntLiteral(-5)) -> ":-5.") + } + + test("boolean Synthesis"){ case (ctx: LeonContext, program: Program) => + synthesizeAndTest("booleanSynthesis", + Seq(BooleanLiteral(true)) -> "yes", + Seq(BooleanLiteral(false)) -> "no") + } + + test("Boolean synthesis 2"){ case (ctx: LeonContext, program: Program) => + synthesizeAndTest("booleanSynthesis2", + Seq(BooleanLiteral(true)) -> "B: true", + Seq(BooleanLiteral(false)) -> "B: false") + } + + /*test("String escape synthesis"){ case (ctx: LeonContext, program: Program) => + synthesizeAndTest("stringEscape", + Seq(StringLiteral("abc")) -> "done...abc", + Seq(StringLiteral("\t")) -> "done...\\t") + + }*/ + + test("Case class synthesis"){ case (ctx: LeonContext, program: Program) => + val c = Constructors(program); import c._ + StringRender.enforceDefaultStringMethodsIfAvailable = false + synthesizeAndTest("configToString", + Seq(Config(4, (5, "foo"))) -> "4: 5 -> foo") + } + + test("Out of order synthesis"){ case (ctx: LeonContext, program: Program) => + val c = Constructors(program); import c._ + StringRender.enforceDefaultStringMethodsIfAvailable = false + synthesizeAndTest("configToString2", + Seq(Config(4, (5, "foo"))) -> "foo: 4 -> 5") + } + + test("Recursive case class synthesis"){ case (ctx: LeonContext, program: Program) => + val c = Constructors(program); import c._ + synthesizeAndTest("treeToString", + Seq(Knot(Knot(Bud("aa"), Bud("bb")), Knot(Bud("mm"), Bud("nn")))) -> + "<<aaYbb>Y<mmYnn>>") + } + + test("Abstract synthesis"){ case (ctx: LeonContext, program: Program) => + val c = Constructors(program); import c._ + val d = FreshIdentifier("d", Dummy) + + synthesizeAndTest("bListToString", + Seq(BList.helper(Dummy)( + (Dummy("a"), Dummy("b")), + (Dummy("c"), Dummy("d"))), + Lambda(Seq(ValDef(d)), FunctionInvocation(dummyToString.typed, Seq(Variable(d))))) + -> + "[{a}-{b}, {c}-{d}]") + + } + + + test("Pretty-printing using inferred not yet defined functions in argument"){ case (ctx: LeonContext, program: Program) => + StringRender.enforceDefaultStringMethodsIfAvailable = true + synthesizeAndAbstractTest("bConfigToString"){ (fd: FunDef, program: Program) => + val c = Constructors(program); import c._ + val arg = BList.helper(Dummy)((Dummy("a"), Dummy("b"))) + val d = FreshIdentifier("d", Dummy) + val lambdaDummyToString = Lambda(Seq(ValDef(d)), FunctionInvocation(dummyToString.typed, Seq(Variable(d)))) + val listDummyToString = functionInvocation(bListToString, Seq(arg, lambdaDummyToString)) + Seq(Seq(BConfig(arg)) -> + StringConcat(StringLiteral("Config"), listDummyToString)) + } + } + + test("Pretty-printing using an existing not yet defined parametrized function") { case (ctx: LeonContext, program: Program) => + StringRender.enforceDefaultStringMethodsIfAvailable = true + + synthesizeAndAbstractTest("dConfigToString"){ (fd: FunDef, program: Program) => + val c = Constructors(program); import c._ + + val listDummy1 = c.List(Dummy)(Dummy("a"), Dummy("b"), Dummy("c")) + val listDummy2 = c.List(Dummy2)(Dummy2("1"), Dummy2("2")) + val arg = DConfig(listDummy1, listDummy2) + + val d = FreshIdentifier("d", Dummy) + val lambdaDummyToString = Lambda(Seq(ValDef(d)), FunctionInvocation(dummyToString.typed, Seq(Variable(d)))) + val d2 = FreshIdentifier("d2", Dummy2) + val lambdaDummy2ToString = Lambda(Seq(ValDef(d2)), FunctionInvocation(dummy2ToString.typed, Seq(Variable(d2)))) + + Seq(Seq(arg) -> + StringConcat(StringConcat(StringConcat( + StringLiteral("Solution:\n "), + customListToString(Dummy)(listDummy1, lambdaDummyToString)), + StringLiteral("\n ")), + customListToString(Dummy2)(listDummy2, lambdaDummy2ToString))) + } + } +} \ No newline at end of file diff --git a/testcases/stringrender/CalendarRender.scala b/testcases/stringrender/CalendarRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..062c908be0926f37871f4168330fd556b1d50a42 --- /dev/null +++ b/testcases/stringrender/CalendarRender.scala @@ -0,0 +1,59 @@ +import leon.lang._ +import leon.lang.synthesis._ +import leon.annotation._ +import leon.collection._ + +object CalendartoString { + val dayEventsSep = "\n" + val eventsSep = "\n" + val daysSep = "\n\n" + val hoursSep = "-" + val dayPlusPrefix = " (D+" + val dayPlusSuffix = ")" + val hoursDescriptionSep = " : " + val titleDescriptionSep = "\n" + + case class Week(days: List[Day]) + case class Day(name: String, events: List[Event]) + case class Event(startHour: Int, startMinute: Int, durationMinutes: Int, description: String) + + def renderHour(h: Int, m: Int) = { + //require(m >= 0 && m < 60 && h > 0) + val h_adjusted = h + m / 60 + val realh = h_adjusted % 24 + val realm = m % 60 + val days = h_adjusted / 24 + realh + "h" + (if(realm == 0) "" else (if(realm < 10) "0" + realm else realm.toString)) + (if(days > 0) dayPlusPrefix + days + dayPlusSuffix else "") + } + + def renderEvent(e: Event) = { + renderHour(e.startHour, e.startMinute) + hoursSep + renderHour(e.startHour, e.startMinute + e.durationMinutes) + hoursDescriptionSep + e.description + } + + def renderList[T](l: List[T], f: T => String, prefix: String, separator: String, suffix: String): String = l match { + case Nil() => prefix + suffix + case Cons(e, tail:Cons[T]) => prefix + f(e) + separator + renderList(tail, f, "", separator, suffix) + case Cons(e, Nil()) => prefix + f(e) + suffix + } + + def renderDay(d: Day): String = { + renderList(d.events, (e: Event) => renderEvent(e), d.name + dayEventsSep, eventsSep, "") + } + + def renderWeek(s: Week): String = { + renderList(s.days, (d: Day) => renderDay(d), """Dear manager, +Here is what happened last week: +""", daysSep, "") + } ensuring { (res: String) => + (s, res) passes { + case Week(Cons(Day("Wednesday", Cons(Event(8, 30, 60, "First meeting"), Cons(Event(23, 15, 420, "Bus journey"), Nil()))), Cons(Day("Thursday", Cons(Event(12, 0, 65, "Meal with client"), Nil())), Nil()))) => """Dear manager, +Here is what happened last week: +Wednesday +8h30-9h30 : First meeting +23h15-6h15 (D+1) : Bus journey + +Thursday +12h-13h05 : Meal with client""" + } + } +} \ No newline at end of file diff --git a/testcases/stringrender/CustomRender.scala b/testcases/stringrender/CustomRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..7f81d50f0bf756096222944dbb14bf6a9b405720 --- /dev/null +++ b/testcases/stringrender/CustomRender.scala @@ -0,0 +1,23 @@ +/** + * Name: CustomRender.scala + * Creation: 15.1.2015 + * Author: Mikael Mayer (mikael.mayer@epfl.ch) + * Comments: Custom generic rendering + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object CustomRender { + def generic_list[T](l: List[T], f: T => String): String = { + ??? + } ensuring { + (res: String) => ((l, res) passes { + case Nil() => "[]" + case Cons(a, Cons(b, Nil())) => "[" + f(a) + ", " + f(b) + "]" + }) + } +} \ No newline at end of file diff --git a/testcases/stringrender/JsonRender.scala b/testcases/stringrender/JsonRender.scala index 2b858784576e6cd97a282a2cba59246f5c4fa84f..274ed3a0f9af8ed9128f14c983abf99d714c3393 100644 --- a/testcases/stringrender/JsonRender.scala +++ b/testcases/stringrender/JsonRender.scala @@ -1,8 +1,8 @@ /** - * 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. + * Name: JsonRender.scala + * Creation: 25.11.2015 + * Author: Mikael Mayer (mikael.mayer@epfl.ch) + * Comments: Json specifications */ import leon.lang._ diff --git a/testcases/stringrender/ModularRender.scala b/testcases/stringrender/ModularRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..4757e3cc3135fa5818abf1ee34fd4b8a072a00fe --- /dev/null +++ b/testcases/stringrender/ModularRender.scala @@ -0,0 +1,42 @@ +/** + * Name: ModularRender.scala + * Creation: 26.01.2015 + * Author: Mikael Mayer (mikael.mayer@epfl.ch) + * Comments: Modular rendering, in one order or the other. + */ +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object ModularRender { + def customToString[T](l : List[T], f : (T) => String): String = { + ??? + } ensuring { + (res : String) => (l, res) passes { + case Nil() => + "[]" + case Cons(t, Nil()) => + "[" + f(t) + "]" + case Cons(ta, Cons(tb, Nil())) => + "[" + f(ta) + ", " + f(tb) + "]" + case Cons(ta, Cons(tb, Cons(tc, Nil()))) => + "[" + f(ta) + ", " + f(tb) + ", " + f(tc) + "]" + } + } + + def booleanToString(b: Boolean) = if(!b) "Up" else "Down" + + case class Configuration(flags: List[Boolean]) + + // We want to write Config:[Up,Down,Up....] + def ConfigToString(config : Configuration): String = { + ??? + } ensuring { + (res : String) => (config, res) passes { + case _ if false => + "" + } + } +} diff --git a/testcases/stringrender/SymbolGrammarRender.scala b/testcases/stringrender/SymbolGrammarRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..5998e6b3895f59c4cff518f74e0ca860a59b2b4e --- /dev/null +++ b/testcases/stringrender/SymbolGrammarRender.scala @@ -0,0 +1,60 @@ +/** + * Name: SymbolGrammarRender.scala + * Creation: 14.01.2016 + * Author: Mika�l Mayer (mikael.mayer@epfl.ch) + * Comments: Grammar rendering specifications starting with symbols + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object GrammarRender { + /** A tagged symbol */ + abstract class Symbol + /** A tagged non-terminal, used for markovization */ + case class NonTerminal(tag: String, vcontext: List[Symbol], hcontext: List[Symbol]) extends Symbol + /** A tagged terminal */ + case class Terminal(tag: String) extends Symbol + + /** All possible right-hand-side of rules */ + case class Expansion(ls: List[List[Symbol]]) + /** A grammar here has a start sequence instead of a start symbol */ + case class Grammar(start: List[Symbol], rules: List[(Symbol, Expansion)]) + + def symbol_markov(s: Grammar): String = { + ???[String] + } ensuring { + (res : String) => (s, res) passes { + case Terminal("foo") => + "Tfoo" + case Terminal("\"'\n\t") => + "T\"'\n\t" + case NonTerminal("foo", Nil(), Nil()) => + "Nfoo" + case NonTerminal("\"'\n\t", Nil(), Nil()) => + "N\"'\n\t" + case NonTerminal("foo", Nil(), Cons(Terminal("foo"), Nil())) => + "Nfoo_hTfoo" + case NonTerminal("foo", Cons(Terminal("foo"), Nil()), Nil()) => + "Nfoo_vTfoo" + case NonTerminal("foo", Nil(), Cons(NonTerminal("foo", Nil(), Nil()), Cons(NonTerminal("foo", Nil(), Nil()), Nil()))) => + "Nfoo_hNfoo_Nfoo" + case NonTerminal("foo", Cons(Terminal("foo"), Nil()), Cons(NonTerminal("foo", Nil(), Nil()), Nil())) => + "Nfoo_vTfoo_hNfoo" + case NonTerminal("foo", Cons(NonTerminal("foo", Nil(), Nil()), Cons(NonTerminal("foo", Nil(), Nil()), Nil())), Nil()) => + "Nfoo_vNfoo_Nfoo" + } + } + + def grammarToString(p : Grammar): String = { + ???[String] + } ensuring { + (res : String) => (p, res) passes { + case _ if false => + "" + } + } +} \ No newline at end of file diff --git a/testcases/web/synthesis/25_String_OutOfOrder.scala b/testcases/web/synthesis/25_String_OutOfOrder.scala index 72785cf605cfbfe26bb70abd37651c32d5eddd42..ec855caeee8e2f2eb67b0743438d85c7902dc47d 100644 --- a/testcases/web/synthesis/25_String_OutOfOrder.scala +++ b/testcases/web/synthesis/25_String_OutOfOrder.scala @@ -5,13 +5,13 @@ import leon.collection.ListOps._ import leon.lang.synthesis._ object OutOfOrderToString { - def argumentsToString(i: Int, j: Int): String = { + def arguments(i: Int, j: Int): String = { ??? } ensuring { (res: String) => ((i, j), res) passes { case (1, 2) => "2, 1" } } - def tupleToString(i: (Int, Int)): String = { + def tuple(i: (Int, Int)): String = { ??? } ensuring { (res: String) => (i, res) passes { case (1, 2) => "2, 1" @@ -27,7 +27,7 @@ object OutOfOrderToString { } } - def listPairToString(l : List[(Int, Int)]): String = { + def listPair(l : List[(Int, Int)]): String = { ???[String] } ensuring { (res : String) => (l, res) passes { @@ -36,7 +36,7 @@ object OutOfOrderToString { } } - def reverselistPairToString(l: List[(Int, Int)]): String = { + def reverselistPair(l: List[(Int, Int)]): String = { ??? } ensuring { (res: String) => (l, res) passes { case Cons((1, 2), Cons((3,4), Nil())) => "4 -> 3, 2 -> 1" @@ -44,7 +44,7 @@ object OutOfOrderToString { case class Rule(input: Int, applied: Option[Int]) - def ruleToString(r : Rule): String = { + def rule(r : Rule): String = { ??? } ensuring { (res : String) => (r, res) passes { diff --git a/testcases/web/synthesis/26_Modular_Render.scala b/testcases/web/synthesis/26_Modular_Render.scala new file mode 100644 index 0000000000000000000000000000000000000000..54a5cc3fbf9f4af852d4de797307af8e84278c4f --- /dev/null +++ b/testcases/web/synthesis/26_Modular_Render.scala @@ -0,0 +1,43 @@ +/** + * Name: ModularRender.scala + * Creation: 26.01.2015 + * Author: Mikael Mayer (mikael.mayer@epfl.ch) + * Comments: Modular rendering, in one order or the other. + */ +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object ModularRender { + def customToString[T](l : List[T], f : (T) => String): String = { + ??? + } ensuring { + (res : String) => (l, res) passes { + case _ if false => "" + } + } + + def booleanToString(b: Boolean) = if(b) "Up" else "Down" + def intToString(b: BigInt) = b.toString + + case class Configuration(flags: List[Boolean], strokes: List[BigInt]) + + // We want to write + // Solution: + // [Up, Down, Up....] + // [1, 2, 5, ...] + def ConfigToString(config : Configuration): String = { + ??? + } ensuring { + (res : String) => (config, res) passes { + case _ if false => + "" + } + } + + /** Wrong lemma for demonstration */ + def configurationsAreSimple(c: Configuration) = + (c.flags.size < 3 || c.strokes.size < 2 || c.flags(0) == c.flags(1) || c.strokes(0) == c.strokes(1)) holds +}