diff --git a/.gitignore b/.gitignore index 4ef5b5eae48b78d3f7dd97318ed2794a9123564f..ef2d597a52fd3c709f822ffae558db110263d543 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,14 @@ out-classes #travis /travis/builds -# Isabelle -contrib +# Windows /bin/ +/cvc4.exe +/libz3.dll +/libz3.lib +/msvcp100.dll +/msvcr100.dll +/scalaz3.dll +/scalaz3.lib +/vcomp100.dll +/z3.exe diff --git a/.larabot.conf b/.larabot.conf index 1fc820daf1738f75bbf6ebf71fc37daa1d4c203e..6a037e0addc616364cb0b049075beb1f7c2d18c7 100644 --- a/.larabot.conf +++ b/.larabot.conf @@ -2,4 +2,18 @@ commands = [ "sbt -batch test" "sbt -batch integration:test" "sbt -batch regression:test" + "sbt -batch genc:test" +] + +trusted = [ + "colder" + "larsrh" + "mantognini" + "manoskouk" + "MikaelMayer" + "ravimad" + "regb" + "romac" + "samarion" + "vkuncak" ] diff --git a/build.sbt b/build.sbt index 6517cf1e6f24679d60aef375c7dcf1577739779b..0e3e147a10e395fb58a0ebb3f981b9919deb56b9 100644 --- a/build.sbt +++ b/build.sbt @@ -31,13 +31,16 @@ resolvers ++= Seq( "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots" ) +val libisabelleVersion = "0.2" + libraryDependencies ++= Seq( "org.scala-lang" % "scala-compiler" % "2.11.7", "org.scalatest" %% "scalatest" % "2.2.4" % "test", "com.typesafe.akka" %% "akka-actor" % "2.3.4", - "info.hupel" %% "libisabelle" % "0.1.1", - "info.hupel" %% "libisabelle-setup" % "0.1.1", - "info.hupel" %% "pide-2015" % "0.1.1", + "info.hupel" %% "libisabelle" % libisabelleVersion, + "info.hupel" %% "libisabelle-setup" % libisabelleVersion, + "info.hupel" %% "pide-2015" % libisabelleVersion, + "org.slf4j" % "slf4j-nop" % "1.7.13", "org.ow2.asm" % "asm-all" % "5.0.4", "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.6.0-rc2" ) @@ -91,7 +94,7 @@ sourceGenerators in Compile <+= Def.task { IO.write(build, s"""|package leon | |object Build { - | val baseDirectory = \"${baseDirectory.value.toString}\" + | val baseDirectory = \"\"\"${baseDirectory.value.toString}\"\"\" | val libFiles = List( | ${libFiles.mkString("\"\"\"", "\"\"\",\n \"\"\"", "\"\"\"")} | ) @@ -138,17 +141,26 @@ parallelExecution in IsabelleTest := false fork in IsabelleTest := true +// GenC Tests +lazy val GenCTest = config("genc") extend(Test) + +testOptions in GenCTest := Seq(Tests.Argument("-oDF"), Tests.Filter(_ startsWith "leon.genc.")) + +parallelExecution in GenCTest := false -def ghProject(repo: String, version: String) = RootProject(uri(s"${repo}#${version}")) -lazy val bonsai = ghProject("git://github.com/colder/bonsai.git", "0fec9f97f4220fa94b1f3f305f2e8b76a3cd1539") +def ghProject(repo: String, version: String) = RootProject(uri(s"${repo}#${version}")) + +lazy val bonsai = ghProject("git://github.com/colder/bonsai.git", "10eaaee4ea0ff6567f4f866922cb871bae2da0ac") lazy val scalaSmtLib = ghProject("git://github.com/regb/scala-smtlib.git", "372bb14d0c84953acc17f9a7e1592087adb0a3e1") lazy val root = (project in file(".")). - configs(RegressionTest, IsabelleTest, IntegrTest). + configs(RegressionTest, IsabelleTest, GenCTest, IntegrTest). dependsOn(bonsai, scalaSmtLib). settings(inConfig(RegressionTest)(Defaults.testTasks ++ testSettings): _*). settings(inConfig(IntegrTest)(Defaults.testTasks ++ testSettings): _*). settings(inConfig(IsabelleTest)(Defaults.testTasks ++ testSettings): _*). + settings(inConfig(GenCTest)(Defaults.testTasks ++ testSettings): _*). settings(inConfig(Test)(Defaults.testTasks ++ testSettings): _*) + diff --git a/library/annotation/package.scala b/library/annotation/package.scala index 14a770b4d33a6234bff9220dcdc2c250559efe43..b1ac6dbf90d66bf8b2a2ae0a3f312f621fa9c301 100644 --- a/library/annotation/package.scala +++ b/library/annotation/package.scala @@ -17,12 +17,14 @@ package object annotation { class extern extends StaticAnnotation @ignore class inline extends StaticAnnotation + @ignore + class internal extends StaticAnnotation // Orb annotations @ignore class monotonic extends StaticAnnotation @ignore - class compose extends StaticAnnotation + class compose extends StaticAnnotation @ignore class axiom extends StaticAnnotation @ignore diff --git a/library/collection/package.scala b/library/collection/package.scala index e163eaf4a70c525be71644ec0e05afeb4f63d87a..8a5ae170763909c20153f373915d6a4e9e726ca0 100644 --- a/library/collection/package.scala +++ b/library/collection/package.scala @@ -9,7 +9,7 @@ import leon.lang.synthesis.choose package object collection { - @library + @internal @library def setToList[A](set: Set[A]): List[A] = choose { (x: List[A]) => x.content == set } diff --git a/library/lang/Either.scala b/library/lang/Either.scala new file mode 100644 index 0000000000000000000000000000000000000000..9cc2ea4e9537b424cbce6963e2a4038f3480d01a --- /dev/null +++ b/library/lang/Either.scala @@ -0,0 +1,27 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon.lang + +import leon.annotation._ + +/** + * @author Viktor + */ +@library +sealed abstract class Either[A,B] { + def isLeft : Boolean + def isRight : Boolean + def swap : Either[B,A] +} +@library +case class Left[A,B](content: A) extends Either[A,B] { + def isLeft = true + def isRight = false + def swap = Right[B,A](content) +} +@library +case class Right[A,B](content: B) extends Either[A,B] { + def isLeft = false + def isRight = true + def swap = Left[B,A](content) +} \ No newline at end of file diff --git a/library/lang/StrOps.scala b/library/lang/StrOps.scala new file mode 100644 index 0000000000000000000000000000000000000000..723ce5be82d7a841c743640ba4f10284076c0dfe --- /dev/null +++ b/library/lang/StrOps.scala @@ -0,0 +1,23 @@ +package leon.lang + +import leon.annotation._ + +/** + * @author Mikael + */ +object StrOps { + @ignore + def concat(a: String, b: String): String = { + a + b + } + @ignore + def length(a: String): BigInt = { + BigInt(a.length) + } + @ignore + def substring(a: String, start: BigInt, end: BigInt): String = { + if(start > end || start >= length(a) || end <= 0) "" else a.substring(start.toInt, end.toInt) + } + @internal @library + def escape(s: String): String = s // Wrong definition, but it will eventually use StringEscapeUtils.escapeJava(s) at parsing and compile time. +} \ No newline at end of file diff --git a/library/lang/package.scala b/library/lang/package.scala index bb7ec69122f4814bd61b1ce79f94c2eab9054c74..b19ec529bdb3975956de00a7da8e8ad39bcf34e4 100644 --- a/library/lang/package.scala +++ b/library/lang/package.scala @@ -70,5 +70,4 @@ package object lang { } } } - } diff --git a/library/lang/string/String.scala b/library/lang/string/String.scala deleted file mode 100644 index 0a3e72bd1a6f234a79eba234a646b2c0844ecfaf..0000000000000000000000000000000000000000 --- a/library/lang/string/String.scala +++ /dev/null @@ -1,28 +0,0 @@ -/* Copyright 2009-2015 EPFL, Lausanne */ - -package leon.lang.string - -import leon.annotation._ -import leon.collection._ - -@library -case class String(chars: List[Char]) { - def +(that: String): String = { - String(this.chars ++ that.chars) - } - - def size = chars.size - - def length = size - - @ignore - override def toString = { - - "\""+charsToString(chars)+"\"" - } - @ignore - def charsToString(chars: List[Char]): java.lang.String = chars match { - case Cons(h, t) => h + charsToString(t) - case Nil() => "" - } -} diff --git a/library/lang/string/package.scala b/library/lang/string/package.scala deleted file mode 100644 index e3cd3d7aebda60a240fd47feba7c4e2552b26d64..0000000000000000000000000000000000000000 --- a/library/lang/string/package.scala +++ /dev/null @@ -1,24 +0,0 @@ -/* Copyright 2009-2015 EPFL, Lausanne */ - -package leon.lang - -import leon.annotation._ -import leon.collection._ -import scala.language.implicitConversions - -import scala.collection.immutable.{List => ScalaList} - -package object string { - @ignore - implicit def strToStr(s: java.lang.String): leon.lang.string.String = { - String(listToList(s.toList)) - } - - @ignore - def listToList[A](s: ScalaList[A]): List[A] = s match { - case scala.::(h, t) => - Cons(h, listToList(t)) - case _ => - Nil[A]() - } -} diff --git a/src/main/isabelle/Leon_Library.thy b/src/main/isabelle/Leon_Library.thy index 62d7108d45ecd4371b450e5b2ba123292647d687..4327381f8b44ca5855f23aa86e9e9af689409a60 100644 --- a/src/main/isabelle/Leon_Library.thy +++ b/src/main/isabelle/Leon_Library.thy @@ -5,6 +5,8 @@ begin axiomatization error :: "nat \<Rightarrow> 'a" +typedecl string + declare [[code abort: error]] declare null_rec[simp] diff --git a/src/main/java/leon/codegen/runtime/StrOps.java b/src/main/java/leon/codegen/runtime/StrOps.java new file mode 100644 index 0000000000000000000000000000000000000000..34abc12d7703c8751fe01fe5c60f1efa18b57715 --- /dev/null +++ b/src/main/java/leon/codegen/runtime/StrOps.java @@ -0,0 +1,53 @@ +package leon.codegen.runtime; + +import org.apache.commons.lang3.StringEscapeUtils; + +public class StrOps { + public static String concat(String a, String b) { + return a + b; + } + + public static BigInt length(String a) { + return new BigInt(String.valueOf(a.length())); + } + + public static String substring(String a, BigInt start, BigInt end) { + if (start.greaterEquals(end) || start.greaterEquals(length(a)) + || end.lessEquals(new BigInt("0"))) + throw new RuntimeException("Invalid substring indices : " + start + ", " + end + " for string \""+a+"\""); + else + return a.substring(start.underlying().intValue(), end.underlying() + .intValue()); + } + + public static String bigIntToString(BigInt a) { + return a.toString(); + } + + public static String intToString(int a) { + return String.valueOf(a); + } + + public static String doubleToString(double a) { + return String.valueOf(a); + } + + public static String booleanToString(boolean a) { + if (a) + return "true"; + else + return "false"; + } + + public static String charToString(char a) { + return String.valueOf(a); + } + + public static String realToString(Real a) { + return ""; // TODO: Not supported at this moment. + } + + public static String escape(String s) { + return StringEscapeUtils.escapeJava(s); + } +} diff --git a/src/main/scala/leon/Main.scala b/src/main/scala/leon/Main.scala index 71b49841f1c647c6cb851f529da76a1864037c5e..8adc16bfc19e9febdb0eb3be678db28989d649fb 100644 --- a/src/main/scala/leon/Main.scala +++ b/src/main/scala/leon/Main.scala @@ -3,7 +3,6 @@ package leon import leon.utils._ -import leon.laziness.LazinessEliminationPhase object Main { @@ -29,14 +28,14 @@ object Main { solvers.isabelle.IsabellePhase, transformations.InstrumentationPhase, invariant.engine.InferInvariantsPhase, - laziness.LazinessEliminationPhase - ) + laziness.LazinessEliminationPhase, + genc.GenerateCPhase, + genc.CFileOutputPhase) } // Add whatever you need here. - lazy val allComponents : Set[LeonComponent] = allPhases.toSet ++ Set( - solvers.z3.FairZ3Component, MainComponent, SharedOptions, solvers.smtlib.SMTLIBCVC4Component, solvers.isabelle.Component - ) + lazy val allComponents: Set[LeonComponent] = allPhases.toSet ++ Set( + solvers.z3.FairZ3Component, MainComponent, SharedOptions, solvers.smtlib.SMTLIBCVC4Component, solvers.isabelle.Component) /* * This object holds the options that determine the selected pipeline of Leon. @@ -46,20 +45,21 @@ object Main { val name = "main" val description = "Selection of Leon functionality. Default: verify" - val optEval = LeonStringOptionDef("eval", "Evaluate ground functions through code generation or evaluation (default: evaluation)", "default", "[code|default]") + val optEval = LeonStringOptionDef("eval", "Evaluate ground functions through code generation or evaluation (default: evaluation)", "default", "[code|default]") val optTermination = LeonFlagOptionDef("termination", "Check program termination. Can be used along --verify", false) - val optRepair = LeonFlagOptionDef("repair", "Repair selected functions", false) - val optSynthesis = LeonFlagOptionDef("synthesis", "Partial synthesis of choose() constructs", false) - val optIsabelle = LeonFlagOptionDef("isabelle", "Run Isabelle verification", false) - val optNoop = LeonFlagOptionDef("noop", "No operation performed, just output program", false) - val optVerify = LeonFlagOptionDef("verify", "Verify function contracts", false) - val optHelp = LeonFlagOptionDef("help", "Show help message", false) + val optRepair = LeonFlagOptionDef("repair", "Repair selected functions", false) + val optSynthesis = LeonFlagOptionDef("synthesis", "Partial synthesis of choose() constructs", false) + val optIsabelle = LeonFlagOptionDef("isabelle", "Run Isabelle verification", false) + val optNoop = LeonFlagOptionDef("noop", "No operation performed, just output program", false) + val optVerify = LeonFlagOptionDef("verify", "Verify function contracts", false) + val optHelp = LeonFlagOptionDef("help", "Show help message", false) val optInstrument = LeonFlagOptionDef("instrument", "Instrument the code for inferring time/depth/stack bounds", false) val optInferInv = LeonFlagOptionDef("inferInv", "Infer invariants from (instrumented) the code", false) - val optLazyEval = LeonFlagOptionDef("lazy", "Handles programs that may use the lazy construct", false) + val optLazyEval = LeonFlagOptionDef("lazy", "Handles programs that may use the lazy construct", false) + val optGenc = LeonFlagOptionDef("genc", "Generate C code", false) override val definedOptions: Set[LeonOptionDef[Any]] = - Set(optTermination, optRepair, optSynthesis, optIsabelle, optNoop, optHelp, optEval, optVerify, optInstrument, optInferInv, optLazyEval) + Set(optTermination, optRepair, optSynthesis, optIsabelle, optNoop, optHelp, optEval, optVerify, optInstrument, optInferInv, optLazyEval, optGenc) } lazy val allOptions: Set[LeonOptionDef[Any]] = allComponents.flatMap(_.definedOptions) @@ -83,7 +83,7 @@ object Main { for (c <- (allComponents - MainComponent - SharedOptions).toSeq.sortBy(_.name) if c.definedOptions.nonEmpty) { reporter.info("") reporter.info(s"${c.name} (${c.description})") - for(opt <- c.definedOptions.toSeq.sortBy(_.name)) { + for (opt <- c.definedOptions.toSeq.sortBy(_.name)) { // there is a non-breaking space at the beginning of the string :) reporter.info(opt.helpString) } @@ -110,13 +110,12 @@ object Main { val (name, value) = try { OptionsHelpers.nameValue(opt) } catch { - case _ : IllegalArgumentException => + case _: IllegalArgumentException => initReporter.fatalError( - s"Malformed option $opt. Options should have the form --name or --name=value" - ) + s"Malformed option $opt. Options should have the form --name or --name=value") } // Find respective LeonOptionDef, or report an unknown option - val df = allOptions.find(_. name == name).getOrElse{ + val df = allOptions.find(_.name == name).getOrElse { initReporter.error(s"Unknown option: $name") displayHelp(initReporter, error = true) } @@ -126,8 +125,7 @@ object Main { val reporter = new DefaultReporter( leonOptions.collectFirst { case LeonOption(SharedOptions.optDebug, sections) => sections.asInstanceOf[Set[DebugSection]] - }.getOrElse(Set[DebugSection]()) - ) + }.getOrElse(Set[DebugSection]())) reporter.whenDebug(DebugSectionOptions) { debug => debug("Options considered by Leon:") @@ -138,8 +136,7 @@ object Main { reporter = reporter, files = files, options = leonOptions.toSeq, - interruptManager = new InterruptManager(reporter) - ) + interruptManager = new InterruptManager(reporter)) } def computePipeline(ctx: LeonContext): Pipeline[List[String], Any] = { @@ -147,7 +144,7 @@ object Main { import purescala.Definitions.Program import purescala.RestoreMethods import utils.FileOutputPhase - import frontends.scalac.{ExtractionPhase, ClassgenPhase} + import frontends.scalac.{ ExtractionPhase, ClassgenPhase } import synthesis.SynthesisPhase import termination.TerminationPhase import xlang.FixReportLabels @@ -155,23 +152,31 @@ object Main { import repair.RepairPhase import evaluators.EvaluationPhase import solvers.isabelle.IsabellePhase + import genc.GenerateCPhase + import genc.CFileOutputPhase import MainComponent._ import invariant.engine.InferInvariantsPhase import transformations._ - - val helpF = ctx.findOptionOrDefault(optHelp) - val noopF = ctx.findOptionOrDefault(optNoop) - val synthesisF = ctx.findOptionOrDefault(optSynthesis) - val xlangF = ctx.findOptionOrDefault(SharedOptions.optXLang) - val repairF = ctx.findOptionOrDefault(optRepair) - val isabelleF = ctx.findOptionOrDefault(optIsabelle) + import laziness._ + + val helpF = ctx.findOptionOrDefault(optHelp) + val noopF = ctx.findOptionOrDefault(optNoop) + val synthesisF = ctx.findOptionOrDefault(optSynthesis) + val xlangF = ctx.findOptionOrDefault(SharedOptions.optXLang) + val repairF = ctx.findOptionOrDefault(optRepair) + val isabelleF = ctx.findOptionOrDefault(optIsabelle) val terminationF = ctx.findOptionOrDefault(optTermination) - val verifyF = ctx.findOptionOrDefault(optVerify) - val evalF = ctx.findOption(optEval).isDefined + val verifyF = ctx.findOptionOrDefault(optVerify) + val gencF = ctx.findOptionOrDefault(optGenc) + val evalF = ctx.findOption(optEval).isDefined val inferInvF = ctx.findOptionOrDefault(optInferInv) val instrumentF = ctx.findOptionOrDefault(optInstrument) - val lazyevalF = ctx.findOptionOrDefault(optLazyEval) - val analysisF = verifyF && terminationF + val lazyevalF = ctx.findOptionOrDefault(optLazyEval) + val analysisF = verifyF && terminationF + // Check consistency in options + if (gencF && !xlangF) { + ctx.reporter.fatalError("Generating C code with --genc requires --xlang") + } if (helpF) { displayVersion(ctx.reporter) @@ -179,8 +184,8 @@ object Main { } else { val pipeBegin: Pipeline[List[String], Program] = ClassgenPhase andThen - ExtractionPhase andThen - new PreprocessingPhase(xlangF) + ExtractionPhase andThen + new PreprocessingPhase(xlangF) val verification = if (xlangF) VerificationPhase andThen FixReportLabels else VerificationPhase @@ -194,8 +199,9 @@ object Main { else if (evalF) EvaluationPhase else if (inferInvF) InferInvariantsPhase else if (instrumentF) InstrumentationPhase andThen FileOutputPhase + else if (gencF) GenerateCPhase andThen CFileOutputPhase else if (lazyevalF) LazinessEliminationPhase - else InstrumentationPhase andThen verification + else verification } pipeBegin andThen @@ -214,7 +220,7 @@ object Main { } catch { case LeonFatalError(None) => - exit(error=true) + exit(error = true) case LeonFatalError(Some(msg)) => // For the special case of fatal errors not sent though Reporter, we @@ -225,7 +231,7 @@ object Main { case _: LeonFatalError => } - exit(error=true) + exit(error = true) } ctx.interruptManager.registerSignalHandler() @@ -233,7 +239,7 @@ object Main { val doWatch = ctx.findOptionOrDefault(SharedOptions.optWatch) if (doWatch) { - val watcher = new FilesWatcher(ctx, ctx.files ++ Build.libFiles.map{ new java.io.File(_)}) + val watcher = new FilesWatcher(ctx, ctx.files ++ Build.libFiles.map { new java.io.File(_) }) watcher.onChange { execute(args, ctx) } diff --git a/src/main/scala/leon/codegen/CodeGeneration.scala b/src/main/scala/leon/codegen/CodeGeneration.scala index 8559b404dd23361ced4eefd6d967fa6d1252f786..86405da600c7ec006c41da3c4c33433354775b66 100644 --- a/src/main/scala/leon/codegen/CodeGeneration.scala +++ b/src/main/scala/leon/codegen/CodeGeneration.scala @@ -63,6 +63,7 @@ trait CodeGeneration { private[codegen] val JavaListClass = "java/util/List" private[codegen] val JavaIteratorClass = "java/util/Iterator" + private[codegen] val JavaStringClass = "java/lang/String" private[codegen] val TupleClass = "leon/codegen/runtime/Tuple" private[codegen] val SetClass = "leon/codegen/runtime/Set" @@ -83,6 +84,7 @@ trait CodeGeneration { private[codegen] val GenericValuesClass = "leon/codegen/runtime/GenericValues" private[codegen] val MonitorClass = "leon/codegen/runtime/LeonCodeGenRuntimeMonitor" private[codegen] val HenkinClass = "leon/codegen/runtime/LeonCodeGenRuntimeHenkinMonitor" + private[codegen] val StrOpsClass = "leon/codegen/runtime/StrOps" def idToSafeJVMName(id: Identifier) = { scala.reflect.NameTransformer.encode(id.uniqueName).replaceAll("\\.", "\\$") @@ -137,6 +139,9 @@ trait CodeGeneration { case TypeParameter(_) => "L" + ObjectClass + ";" + + case StringType => + "L" + JavaStringClass + ";" case _ => throw CompilationException("Unsupported type : " + tpe) } @@ -192,18 +197,10 @@ trait CodeGeneration { val idParams = (if (requireMonitor) Seq(monitorID) else Seq.empty) ++ funDef.paramIds val newMapping = idParams.zipWithIndex.toMap.mapValues(_ + (if (!isStatic) 1 else 0)) - val body = funDef.body.getOrElse(throw CompilationException("Can't compile a FunDef without body: "+funDef.id.name)) - - val bodyWithPre = if(funDef.hasPrecondition && params.checkContracts) { - IfExpr(funDef.precondition.get, body, Error(body.getType, "Precondition failed")) + val body = if (params.checkContracts) { + funDef.fullBody } else { - body - } - - val bodyWithPost = funDef.postcondition match { - case Some(post) if params.checkContracts => - Ensuring(bodyWithPre, post).toAssert - case _ => bodyWithPre + funDef.body.getOrElse(throw CompilationException("Can't compile a FunDef without body: "+funDef.id.name)) } val locals = NoLocals.withVars(newMapping) @@ -213,7 +210,7 @@ trait CodeGeneration { ch << InvokeVirtual(MonitorClass, "onInvoke", "()V") } - mkExpr(bodyWithPost, ch)(locals) + mkExpr(body, ch)(locals) funDef.returnType match { case ValueType() => @@ -797,6 +794,9 @@ trait CodeGeneration { case en @ Ensuring(_, _) => mkExpr(en.toAssert, ch) + case Require(pre, body) => + mkExpr(IfExpr(pre, body, Error(body.getType, "Precondition failed")), ch) + case Let(i,d,b) => mkExpr(d, ch) val slot = ch.getFreshVar @@ -818,6 +818,9 @@ trait CodeGeneration { case UnitLiteral() => ch << Ldc(1) + + case StringLiteral(v) => + ch << Ldc(v) case InfiniteIntegerLiteral(v) => ch << New(BigIntClass) << DUP @@ -979,7 +982,11 @@ trait CodeGeneration { mkUnbox(tpe, ch) case _ => } - + + case FunctionInvocation(TypedFunDef(fd, Nil), Seq(a)) if fd == program.library.escape.get => + mkExpr(a, ch) + ch << InvokeStatic(StrOpsClass, "escape", s"(L$JavaStringClass;)L$JavaStringClass;") + case FunctionInvocation(TypedFunDef(fd, Seq(tp)), Seq(set)) if fd == program.library.setToList.get => val nil = CaseClass(CaseClassType(program.library.Nil.get, Seq(tp)), Seq()) @@ -1159,6 +1166,38 @@ trait CodeGeneration { case f @ Forall(args, body) => mkForall(args.map(_.id).toSet, body, ch) + // String processing => + case StringConcat(l, r) => + mkExpr(l, ch) + mkExpr(r, ch) + ch << InvokeStatic(StrOpsClass, "concat", s"(L$JavaStringClass;L$JavaStringClass;)L$JavaStringClass;") + + case StringLength(a) => + mkExpr(a, ch) + ch << InvokeStatic(StrOpsClass, "length", s"(L$JavaStringClass;)L$BigIntClass;") + + case Int32ToString(a) => + mkExpr(a, ch) + ch << InvokeStatic(StrOpsClass, "intToString", s"(I)L$JavaStringClass;") + case BooleanToString(a) => + mkExpr(a, ch) + ch << InvokeStatic(StrOpsClass, "booleanToString", s"(Z)L$JavaStringClass;") + case IntegerToString(a) => + mkExpr(a, ch) + ch << InvokeStatic(StrOpsClass, "bigIntToString", s"(L$BigIntClass;)L$JavaStringClass;") + case CharToString(a) => + mkExpr(a, ch) + ch << InvokeStatic(StrOpsClass, "charToString", s"(C)L$JavaStringClass;") + case RealToString(a) => + mkExpr(a, ch) + ch << InvokeStatic(StrOpsClass, "realToString", s"(L$RealClass;)L$JavaStringClass;") + + case SubString(a, start, end) => + mkExpr(a, ch) + mkExpr(start, ch) + mkExpr(end, ch) + ch << InvokeStatic(StrOpsClass, "substring", s"(L$JavaStringClass;L$BigIntClass;L$BigIntClass;)L$JavaStringClass;") + // Arithmetic case Plus(l, r) => mkExpr(l, ch) @@ -1488,6 +1527,9 @@ trait CodeGeneration { case IntegerType => ch << CheckCast(BigIntClass) + case StringType => + ch << CheckCast(JavaStringClass) + case RealType => ch << CheckCast(RationalClass) diff --git a/src/main/scala/leon/codegen/CompilationUnit.scala b/src/main/scala/leon/codegen/CompilationUnit.scala index 556cf7b05420bbdf29583b46ba13121e5a5e0328..26f976c095489f22cbc97dd9ee810d1d71ef9f99 100644 --- a/src/main/scala/leon/codegen/CompilationUnit.scala +++ b/src/main/scala/leon/codegen/CompilationUnit.scala @@ -177,6 +177,9 @@ class CompilationUnit(val ctx: LeonContext, case FractionalLiteral(n, d) => new runtime.Rational(n.toString, d.toString) + + case StringLiteral(v) => + new java.lang.String(v) case GenericValue(tp, id) => e @@ -293,6 +296,9 @@ class CompilationUnit(val ctx: LeonContext, case (c: java.lang.Character, CharType) => CharLiteral(c.toChar) + case (c: java.lang.String, StringType) => + StringLiteral(c) + case (cc: runtime.CaseClass, ct: ClassType) => val fields = cc.productElements() diff --git a/src/main/scala/leon/datagen/GrammarDataGen.scala b/src/main/scala/leon/datagen/GrammarDataGen.scala index 8cf1044ef0dcf3429c8df805dc6a51f18fc17a4d..cd86c707ddb893918e512f6d7101cc4cc92b6405 100644 --- a/src/main/scala/leon/datagen/GrammarDataGen.scala +++ b/src/main/scala/leon/datagen/GrammarDataGen.scala @@ -20,7 +20,7 @@ class GrammarDataGen(evaluator: Evaluator, grammar: ExpressionGrammar[TypeTree] implicit val ctx = evaluator.context def generate(tpe: TypeTree): Iterator[Expr] = { - val enum = new MemoizedEnumerator[TypeTree, Expr](grammar.getProductions) + val enum = new MemoizedEnumerator[TypeTree, Expr, Generator[TypeTree, Expr]](grammar.getProductions) enum.iterator(tpe) } diff --git a/src/main/scala/leon/datagen/VanuatooDataGen.scala b/src/main/scala/leon/datagen/VanuatooDataGen.scala index 7aa99973c1a0ac2e510a5f2f512d501703081ac9..b82dc6a0f256fec3c3cf733addad76f899456f4a 100644 --- a/src/main/scala/leon/datagen/VanuatooDataGen.scala +++ b/src/main/scala/leon/datagen/VanuatooDataGen.scala @@ -32,7 +32,7 @@ class VanuatooDataGen(ctx: LeonContext, p: Program) extends DataGenerator { val booleans = (for (b <- Set(true, false)) yield { b -> Constructor[Expr, TypeTree](List(), BooleanType, s => BooleanLiteral(b), ""+b) }).toMap - + val chars = (for (c <- Set('a', 'b', 'c', 'd')) yield { c -> Constructor[Expr, TypeTree](List(), CharType, s => CharLiteral(c), ""+c) }).toMap @@ -41,16 +41,25 @@ class VanuatooDataGen(ctx: LeonContext, p: Program) extends DataGenerator { (n, d) -> Constructor[Expr, TypeTree](List(), RealType, s => FractionalLiteral(n, d), "" + n + "/" + d) }).toMap + val strings = (for (b <- Set("", "a", "foo", "bar")) yield { + b -> Constructor[Expr, TypeTree](List(), StringType, s => StringLiteral(b), b) + }).toMap + + def intConstructor(i: Int) = ints(i) def bigIntConstructor(i: Int) = bigInts(i) def boolConstructor(b: Boolean) = booleans(b) - + def charConstructor(c: Char) = chars(c) def rationalConstructor(n: Int, d: Int) = rationals(n -> d) + def stringConstructor(s: String) = strings(s) + + lazy val stubValues = ints.values ++ bigInts.values ++ booleans.values ++ chars.values ++ rationals.values ++ strings.values + def cPattern(c: Constructor[Expr, TypeTree], args: Seq[VPattern[Expr, TypeTree]]) = { ConstructorPattern[Expr, TypeTree](c, args) } @@ -108,7 +117,7 @@ class VanuatooDataGen(ctx: LeonContext, p: Program) extends DataGenerator { case mt @ MapType(from, to) => constructors.getOrElse(mt, { val cs = for (size <- List(0, 1, 2, 5)) yield { - val subs = (1 to size).flatMap(i => List(from, to)).toList + val subs = (1 to size).flatMap(i => List(from, to)).toList Constructor[Expr, TypeTree](subs, mt, s => FiniteMap(s.grouped(2).map(t => (t(0), t(1))).toMap, from, to), mt.asString(ctx)+"@"+size) } constructors += mt -> cs @@ -175,6 +184,9 @@ class VanuatooDataGen(ctx: LeonContext, p: Program) extends DataGenerator { case (c: java.lang.Character, CharType) => (cPattern(charConstructor(c), List()), true) + case (b: java.lang.String, StringType) => + (cPattern(stringConstructor(b), List()), true) + case (cc: codegen.runtime.CaseClass, ct: ClassType) => val r = cc.__getRead() @@ -297,7 +309,6 @@ class VanuatooDataGen(ctx: LeonContext, p: Program) extends DataGenerator { None }) - val stubValues = ints.values ++ bigInts.values ++ booleans.values ++ chars.values ++ rationals.values val gen = new StubGenerator[Expr, TypeTree](stubValues.toSeq, Some(getConstructors _), treatEmptyStubsAsChildless = true) diff --git a/src/main/scala/leon/evaluators/CodeGenEvaluator.scala b/src/main/scala/leon/evaluators/CodeGenEvaluator.scala index d92d168bf2361e0ca054a7e88c276dd7cc34e839..533ba695ca27f478a03ac7b6cf53d46885e11309 100644 --- a/src/main/scala/leon/evaluators/CodeGenEvaluator.scala +++ b/src/main/scala/leon/evaluators/CodeGenEvaluator.scala @@ -83,29 +83,29 @@ class CodeGenEvaluator(ctx: LeonContext, val unit : CompilationUnit) extends Eva override def compile(expression: Expr, args: Seq[Identifier]) : Option[solvers.Model=>EvaluationResult] = { compileExpr(expression, args).map(ce => (model: solvers.Model) => { - if (args.exists(arg => !model.isDefinedAt(arg))) { - EvaluationResults.EvaluatorError("Model undefined for free arguments") - } else try { - EvaluationResults.Successful(ce.eval(model)) - } catch { - case e : ArithmeticException => - EvaluationResults.RuntimeError(e.getMessage) - - case e : ArrayIndexOutOfBoundsException => - EvaluationResults.RuntimeError(e.getMessage) - - case e : LeonCodeGenRuntimeException => - EvaluationResults.RuntimeError(e.getMessage) - - case e : LeonCodeGenEvaluationException => - EvaluationResults.EvaluatorError(e.getMessage) - - case e : java.lang.ExceptionInInitializerError => - EvaluationResults.RuntimeError(e.getException.getMessage) - - case so : java.lang.StackOverflowError => - EvaluationResults.RuntimeError("Stack overflow") - } - }) + if (args.exists(arg => !model.isDefinedAt(arg))) { + EvaluationResults.EvaluatorError("Model undefined for free arguments") + } else try { + EvaluationResults.Successful(ce.eval(model)) + } catch { + case e : ArithmeticException => + EvaluationResults.RuntimeError(e.getMessage) + + case e : ArrayIndexOutOfBoundsException => + EvaluationResults.RuntimeError(e.getMessage) + + case e : LeonCodeGenRuntimeException => + EvaluationResults.RuntimeError(e.getMessage) + + case e : LeonCodeGenEvaluationException => + EvaluationResults.EvaluatorError(e.getMessage) + + case e : java.lang.ExceptionInInitializerError => + EvaluationResults.RuntimeError(e.getException.getMessage) + + case so : java.lang.StackOverflowError => + EvaluationResults.RuntimeError("Stack overflow") + } + }) + } } -} diff --git a/src/main/scala/leon/evaluators/RecursiveEvaluator.scala b/src/main/scala/leon/evaluators/RecursiveEvaluator.scala index c3f9eed0f0d55b0f75628d491a998facd29aa346..75c691edf1f7e5a4ca056bf8c8448bff9b5ead77 100644 --- a/src/main/scala/leon/evaluators/RecursiveEvaluator.scala +++ b/src/main/scala/leon/evaluators/RecursiveEvaluator.scala @@ -14,8 +14,9 @@ import purescala.Common._ import purescala.Expressions._ import purescala.Definitions._ import leon.solvers.{HenkinModel, Model, SolverFactory} - import scala.collection.mutable.{Map => MutableMap} +import leon.purescala.DefOps +import org.apache.commons.lang3.StringEscapeUtils abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int) extends ContextualEvaluator(ctx, prog, maxSteps) @@ -28,7 +29,7 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int protected var clpCache = Map[(Choose, Seq[Expr]), Expr]() - protected def e(expr: Expr)(implicit rctx: RC, gctx: GC): Expr = expr match { + protected[evaluators] def e(expr: Expr)(implicit rctx: RC, gctx: GC): Expr = expr match { case Variable(id) => rctx.mappings.get(id) match { case Some(v) if v != expr => @@ -104,6 +105,13 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int def mkCons(h: Expr, t: Expr) = CaseClass(CaseClassType(cons, Seq(tp)), Seq(h,t)) els.foldRight(nil)(mkCons) + case FunctionInvocation(TypedFunDef(fd, Nil), Seq(input)) if fd == program.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+")") @@ -231,6 +239,42 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int case RealMinus(l,r) => e(RealPlus(l, RealUMinus(r))) + + case StringConcat(l, r) => + (e(l), e(r)) match { + case (StringLiteral(i1), StringLiteral(i2)) => StringLiteral(i1 + i2) + case (le,re) => throw EvalError(typeErrorMsg(le, StringType)) + } + case StringLength(a) => e(a) match { + case StringLiteral(a) => InfiniteIntegerLiteral(a.length) + case res => throw EvalError(typeErrorMsg(res, IntegerType)) + } + case SubString(a, start, end) => (e(a), e(start), e(end)) match { + case (StringLiteral(a), InfiniteIntegerLiteral(b), InfiniteIntegerLiteral(c)) => + StringLiteral(a.substring(b.toInt, c.toInt)) + case res => throw EvalError(typeErrorMsg(res._1, StringType)) + } + case Int32ToString(a) => e(a) match { + case IntLiteral(i) => StringLiteral(i.toString) + case res => throw EvalError(typeErrorMsg(res, Int32Type)) + } + case CharToString(a) => + e(a) match { + case CharLiteral(i) => StringLiteral(i.toString) + case res => throw EvalError(typeErrorMsg(res, CharType)) + } + case IntegerToString(a) => e(a) match { + case InfiniteIntegerLiteral(i) => StringLiteral(i.toString) + case res => throw EvalError(typeErrorMsg(res, IntegerType)) + } + case BooleanToString(a) => e(a) match { + case BooleanLiteral(i) => StringLiteral(i.toString) + case res => throw EvalError(typeErrorMsg(res, BooleanType)) + } + case RealToString(a) => e(a) match { + case FractionalLiteral(n, d) => StringLiteral(n.toString + "/" + d.toString) + case res => throw EvalError(typeErrorMsg(res, RealType)) + } case BVPlus(l,r) => (e(l), e(r)) match { @@ -589,7 +633,6 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int case MatchExpr(scrut, cases) => val rscrut = e(scrut) - cases.toStream.map(c => matchesCase(rscrut, c)).find(_.nonEmpty) match { case Some(Some((c, mappings))) => e(c.rhs)(rctx.withNewVars(mappings), gctx) @@ -603,6 +646,7 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int 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) } @@ -685,7 +729,7 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int val henkinModel: HenkinModel = gctx.model match { case hm: HenkinModel => hm case _ => throw EvalError("Can't evaluate foralls without henkin model") - } +} val TopLevelAnds(conjuncts) = body e(andJoin(conjuncts.flatMap { conj => diff --git a/src/main/scala/leon/evaluators/StringTracingEvaluator.scala b/src/main/scala/leon/evaluators/StringTracingEvaluator.scala new file mode 100644 index 0000000000000000000000000000000000000000..62cde913fae123ab0fcd8c3d019953af9efb5942 --- /dev/null +++ b/src/main/scala/leon/evaluators/StringTracingEvaluator.scala @@ -0,0 +1,127 @@ +/* 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/frontends/scalac/ASTExtractors.scala b/src/main/scala/leon/frontends/scalac/ASTExtractors.scala index fb64b3311dfdd2af5b06df85ab2cb26088d3233e..67e5c4502ad1810b323616916e3edfab9c05a8ea 100644 --- a/src/main/scala/leon/frontends/scalac/ASTExtractors.scala +++ b/src/main/scala/leon/frontends/scalac/ASTExtractors.scala @@ -50,6 +50,7 @@ trait ASTExtractors { protected lazy val someClassSym = classFromName("scala.Some") protected lazy val byNameSym = classFromName("scala.<byname>") protected lazy val bigIntSym = classFromName("scala.math.BigInt") + protected lazy val stringSym = classFromName("java.lang.String") protected def functionTraitSym(i:Int) = { require(1 <= i && i <= 22) classFromName("scala.Function" + i) @@ -62,6 +63,8 @@ trait ASTExtractors { def isBigIntSym(sym : Symbol) : Boolean = getResolvedTypeSym(sym) == bigIntSym + def isStringSym(sym : Symbol) : Boolean = getResolvedTypeSym(sym) match { case `stringSym` => true case _ => false } + def isByNameSym(sym : Symbol) : Boolean = getResolvedTypeSym(sym) == byNameSym // Resolve type aliases @@ -110,29 +113,36 @@ trait ASTExtractors { } def hasBigIntType(t : Tree) = isBigIntSym(t.tpe.typeSymbol) + + def hasStringType(t : Tree) = isStringSym(t.tpe.typeSymbol) def hasRealType(t : Tree) = isRealSym(t.tpe.typeSymbol) - + /** A set of helpers for extracting trees.*/ object ExtractorHelpers { + /** Extracts the identifier as `"Ident(name)"` (who needs this?!) */ object ExIdNamed { def unapply(id: Ident): Option[String] = Some(id.toString) } + /** Extracts the tree and its type (who needs this?!) */ object ExHasType { def unapply(tr: Tree): Option[(Tree, Symbol)] = Some((tr, tr.tpe.typeSymbol)) } + /** Extracts the string representation of a name of something having the `Name` trait */ object ExNamed { def unapply(name: Name): Option[String] = Some(name.toString) } + /** Returns the full dot-separated names of the symbol as a list of strings */ object ExSymbol { def unapplySeq(t: Tree): Option[Seq[String]] = { Some(t.symbol.fullName.toString.split('.').toSeq) } } + /** Matches nested `Select(Select(...Select(a, b) ...y) , z)` and returns the list `a,b, ... y,z` */ object ExSelected { def unapplySeq(select: Select): Option[Seq[String]] = select match { case Select(This(scalaName), name) => @@ -153,8 +163,8 @@ trait ASTExtractors { object StructuralExtractors { import ExtractorHelpers._ + /** Extracts the 'ensuring' contract from an expression. */ object ExEnsuredExpression { - /** Extracts the 'ensuring' contract from an expression. */ def unapply(tree: Apply): Option[(Tree,Tree)] = tree match { case Apply(Select(Apply(TypeApply(ExSelected("scala", "Predef", "Ensuring"), _ :: Nil), body :: Nil), ExNamed("ensuring")), contract :: Nil) => Some((body, contract)) @@ -162,6 +172,7 @@ trait ASTExtractors { } } + /** Matches the `holds` expression at the end of any boolean expression, and return the boolean expression.*/ object ExHoldsExpression { def unapply(tree: Select) : Option[Tree] = tree match { case Select( @@ -172,6 +183,7 @@ trait ASTExtractors { } } + /** Matches an implication `lhs ==> rhs` and returns (lhs, rhs)*/ object ExImplies { def unapply(tree: Apply) : Option[(Tree, Tree)] = tree match { case @@ -189,9 +201,9 @@ trait ASTExtractors { } } + /** Extracts the 'require' contract from an expression (only if it's the + * first call in the block). */ object ExRequiredExpression { - /** Extracts the 'require' contract from an expression (only if it's the - * first call in the block). */ def unapply(tree: Apply): Option[Tree] = tree match { case Apply(ExSelected("scala", "Predef", "require"), contractBody :: Nil) => Some(contractBody) @@ -199,6 +211,7 @@ trait ASTExtractors { } } + /** Extracts the `(input, output) passes { case In => Out ...}` and returns (input, output, list of case classes) */ object ExPasses { def unapply(tree : Apply) : Option[(Tree, Tree, List[CaseDef])] = tree match { case Apply( @@ -223,11 +236,9 @@ trait ASTExtractors { } - + /** Returns a string literal from a constant string literal. */ object ExStringLiteral { def unapply(tree: Tree): Option[String] = tree match { - case Apply(ExSelected("leon", "lang", "string", "package", "strToStr"), (str: Literal) :: Nil) => - Some(str.value.stringValue) case Literal(c @ Constant(i)) if c.tpe == StringClass.tpe => Some(c.stringValue) case _ => @@ -235,6 +246,7 @@ trait ASTExtractors { } } + /** Returns the arguments of an unapply pattern */ object ExUnapplyPattern { def unapply(tree: Tree): Option[(Symbol, Seq[Tree])] = tree match { case UnApply(Apply(s, _), args) => @@ -243,6 +255,7 @@ trait ASTExtractors { } } + /** Returns the argument of a bigint literal, either from scala or leon */ object ExBigIntLiteral { def unapply(tree: Tree): Option[Tree] = tree match { case Apply(ExSelected("scala", "package", "BigInt", "apply"), n :: Nil) => @@ -254,6 +267,7 @@ trait ASTExtractors { } } + /** Returns the two components (n, d) of a real n/d literal */ object ExRealLiteral { def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { case Apply(ExSelected("leon", "lang", "Real", "apply"), n :: d :: Nil) => @@ -262,6 +276,8 @@ trait ASTExtractors { None } } + + /** Matches Real(x) when n is an integer and returns x */ object ExRealIntLiteral { def unapply(tree: Tree): Option[Tree] = tree match { case Apply(ExSelected("leon", "lang", "Real", "apply"), n :: Nil) => @@ -271,7 +287,7 @@ trait ASTExtractors { } } - + /** Matches the construct int2bigInt(a) and returns a */ object ExIntToBigInt { def unapply(tree: Tree): Option[Tree] = tree match { case Apply(ExSelected("math", "BigInt", "int2bigInt"), tree :: Nil) => Some(tree) @@ -279,7 +295,7 @@ trait ASTExtractors { } } - + /** Matches the construct List[tpe](a, b, ...) and returns tpe and arguments */ object ExListLiteral { def unapply(tree: Apply): Option[(Tree, List[Tree])] = tree match { case Apply( @@ -291,9 +307,9 @@ trait ASTExtractors { } } + /** Extracts the 'assert' contract from an expression (only if it's the + * first call in the block). */ object ExAssertExpression { - /** Extracts the 'assert' contract from an expression (only if it's the - * first call in the block). */ def unapply(tree: Apply): Option[(Tree, Option[String])] = tree match { case Apply(ExSelected("scala", "Predef", "assert"), contractBody :: Nil) => Some((contractBody, None)) @@ -304,11 +320,10 @@ trait ASTExtractors { } } - + /** Matches an object with no type parameters, and regardless of its + * visibility. Does not match on case objects or the automatically generated companion + * objects of case classes (or any synthetic class). */ object ExObjectDef { - /** Matches an object with no type parameters, and regardless of its - * visibility. Does not match on case objects or the automatically generated companion - * objects of case classes (or any synthetic class). */ def unapply(cd: ClassDef): Option[(String,Template)] = cd match { case ClassDef(_, name, tparams, impl) if (cd.symbol.isModuleClass || cd.symbol.hasPackageFlag) && @@ -322,10 +337,10 @@ trait ASTExtractors { } } + /** Matches an abstract class or a trait with no type parameters, no + * constructor args (in the case of a class), no implementation details, + * no abstract members. */ object ExAbstractClass { - /** Matches an abstract class or a trait with no type parameters, no - * constrctor args (in the case of a class), no implementation details, - * no abstract members. */ def unapply(cd: ClassDef): Option[(String, Symbol, Template)] = cd match { // abstract class case ClassDef(_, name, tparams, impl) if cd.symbol.isAbstractClass => Some((name.toString, cd.symbol, impl)) @@ -334,10 +349,12 @@ trait ASTExtractors { } } + /** Returns true if the class definition is a case class */ private def isCaseClass(cd: ClassDef): Boolean = { cd.symbol.isCase && !cd.symbol.isAbstractClass && cd.impl.body.size >= 8 } + /** Returns true if the class definition is an implicit class */ private def isImplicitClass(cd: ClassDef): Boolean = { cd.symbol.isImplicit } @@ -509,9 +526,8 @@ trait ASTExtractors { true case _ => false } - } - + object ExDefaultValueFunction{ /** Matches a function that defines the default value of a parameter */ def unapply(dd: DefDef): Option[(Symbol, Seq[Symbol], Seq[ValDef], Type, String, Int, Tree)] = { @@ -520,11 +536,11 @@ trait ASTExtractors { case DefDef(_, name, tparams, vparamss, tpt, rhs) if( vparamss.size <= 1 && name != nme.CONSTRUCTOR && sym.isSynthetic ) => - + // Split the name into pieces, to find owner of the parameter + param.index // Form has to be <owner name>$default$<param index> val symPieces = sym.name.toString.reverse.split("\\$", 3).reverseMap(_.reverse) - + try { if (symPieces(1) != "default" || symPieces(0) == "copy") throw new IllegalArgumentException("") val ownerString = symPieces(0) diff --git a/src/main/scala/leon/frontends/scalac/CodeExtraction.scala b/src/main/scala/leon/frontends/scalac/CodeExtraction.scala index 43d977fb3a273014e8d7e2874e05996268c0b465..3e93664008654edef6fa057c56451db0a108bc6b 100644 --- a/src/main/scala/leon/frontends/scalac/CodeExtraction.scala +++ b/src/main/scala/leon/frontends/scalac/CodeExtraction.scala @@ -153,7 +153,7 @@ trait CodeExtraction extends ASTExtractors { } private def isIgnored(s: Symbol) = { - (annotationsOf(s) contains "ignore") || s.fullName.toString.endsWith(".main") + (annotationsOf(s) contains "ignore") } private def isLibrary(u: CompilationUnit) = Build.libFiles contains u.source.file.absolute.path @@ -246,13 +246,13 @@ trait CodeExtraction extends ASTExtractors { // ignore None - case t@ExAbstractClass(o2, sym, _) => + case t @ ExAbstractClass(o2, sym, _) => Some(getClassDef(sym, t.pos)) - case t@ExCaseClass(o2, sym, args, _) => + case t @ ExCaseClass(o2, sym, args, _) => Some(getClassDef(sym, t.pos)) - case t@ExObjectDef(n, templ) => + case t @ ExObjectDef(n, templ) => // Module val id = FreshIdentifier(n) val leonDefs = templ.body.flatMap { @@ -481,16 +481,31 @@ trait CodeExtraction extends ASTExtractors { Nil } - val tparams = tparamsMap.map(t => TypeParameterDef(t._2)) - - val defCtx = DefContext(tparamsMap.toMap) - val parent = sym.tpe.parents.headOption match { case Some(TypeRef(_, parentSym, tps)) if seenClasses contains parentSym => getClassDef(parentSym, sym.pos) match { case acd: AbstractClassDef => + val defCtx = DefContext(tparamsMap.toMap) val newTps = tps.map(extractType(_)(defCtx, sym.pos)) - Some(AbstractClassType(acd, newTps)) + val zip = (newTps zip tparamsMap.map(_._2)) + if (newTps.size != tparamsMap.size) { + outOfSubsetError(sym.pos, "Child classes should have the same number of type parameters as their parent") + None + } else if (zip.exists { + case (TypeParameter(_), _) => false + case _ => true + }) { + outOfSubsetError(sym.pos, "Child class type params should have a simple mapping to parent params") + None + } else if (zip.exists { + case (TypeParameter(id), ctp) => id.name != ctp.id.name + case _ => false + }) { + outOfSubsetError(sym.pos, "Child type params should be identical to parent class's (e.g. C[T1,T2] extends P[T1,T2])") + None + } else { + Some(acd.typed -> acd.tparams) + } case cd => outOfSubsetError(sym.pos, s"Class $id cannot extend ${cd.id}") @@ -501,11 +516,18 @@ trait CodeExtraction extends ASTExtractors { None } + val tparams = parent match { + case Some((p, tparams)) => tparams + case None => tparamsMap.map(t => TypeParameterDef(t._2)) + } + + val defCtx = DefContext((tparamsMap.map(_._1) zip tparams.map(_.tp)).toMap) + // Extract class val cd = if (sym.isAbstractClass) { - AbstractClassDef(id, tparams, parent) + AbstractClassDef(id, tparams, parent.map(_._1)) } else { - CaseClassDef(id, tparams, parent, sym.isModuleClass) + CaseClassDef(id, tparams, parent.map(_._1), sym.isModuleClass) } cd.setPos(sym.pos) //println(s"Registering $sym") @@ -513,7 +535,7 @@ trait CodeExtraction extends ASTExtractors { cd.addFlags(annotationsOf(sym).map { case (name, args) => ClassFlag.fromName(name, args) }.toSet) // Register parent - parent.foreach(_.classDef.registerChild(cd)) + parent.map(_._1).foreach(_.classDef.registerChild(cd)) // Extract case class fields cd match { @@ -522,30 +544,14 @@ trait CodeExtraction extends ASTExtractors { val fields = args.map { case (fsym, t) => val tpe = leonType(t.tpt.tpe)(defCtx, fsym.pos) val id = cachedWithOverrides(fsym, Some(ccd), tpe) - LeonValDef(id.setPos(t.pos), Some(tpe)).setPos(t.pos) + if (tpe != id.getType) println(tpe, id.getType) + LeonValDef(id.setPos(t.pos)).setPos(t.pos) } //println(s"Fields of $sym") ccd.setFields(fields) case _ => } - // Validates type parameters - parent foreach { pct => - if(pct.classDef.tparams.size == tparams.size) { - val pcd = pct.classDef - val ptps = pcd.tparams.map(_.tp) - - val targetType = AbstractClassType(pcd, ptps) - val fromChild = cd.typed(ptps).parent.get - - if (fromChild != targetType) { - outOfSubsetError(sym.pos, "Child type should form a simple bijection with parent class type (e.g. C[T1,T2] extends P[T1,T2])") - } - } else { - outOfSubsetError(sym.pos, "Child classes should have the same number of type parameters as their parent") - } - } - //println(s"Body of $sym") // We collect the methods and fields @@ -619,6 +625,8 @@ trait CodeExtraction extends ASTExtractors { } } + private var isLazy = Set[LeonValDef]() + private var defsToDefs = Map[Symbol, FunDef]() private def defineFunDef(sym: Symbol, within: Option[LeonClassDef] = None)(implicit dctx: DefContext): FunDef = { @@ -629,9 +637,16 @@ trait CodeExtraction extends ASTExtractors { val newParams = sym.info.paramss.flatten.map{ sym => val ptpe = leonType(sym.tpe)(nctx, sym.pos) - val newID = FreshIdentifier(sym.name.toString, ptpe).setPos(sym.pos) + val tpe = if (sym.isByNameParam) FunctionType(Seq(), ptpe) else ptpe + val newID = FreshIdentifier(sym.name.toString, tpe).setPos(sym.pos) owners += (newID -> None) - LeonValDef(newID).setPos(sym.pos) + val vd = LeonValDef(newID).setPos(sym.pos) + + if (sym.isByNameParam) { + isLazy += vd + } + + vd } val tparamsDef = tparams.map(t => TypeParameterDef(t._2)) @@ -768,8 +783,9 @@ trait CodeExtraction extends ASTExtractors { vd.defaultValue = paramsToDefaultValues.get(s.symbol) } - val newVars = for ((s, vd) <- params zip funDef.params) yield { - s.symbol -> (() => Variable(vd.id)) + val newVars = for ((s, vd) <- params zip funDef.params) yield s.symbol -> { + if (s.symbol.isByNameParam) () => Application(Variable(vd.id), Seq()) + else () => Variable(vd.id) } val fctx = dctx.withNewVars(newVars).copy(isExtern = funDef.annotations("extern")) @@ -892,16 +908,8 @@ trait CodeExtraction extends ASTExtractors { case ExInt32Literal(i) => (LiteralPattern(binder, IntLiteral(i)), dctx) case ExBooleanLiteral(b) => (LiteralPattern(binder, BooleanLiteral(b)), dctx) case ExUnitLiteral() => (LiteralPattern(binder, UnitLiteral()), dctx) - case sLit@ExStringLiteral(s) => - val consClass = libraryCaseClass(sLit.pos, "leon.collection.Cons") - val nilClass = libraryCaseClass(sLit.pos, "leon.collection.Nil") - val nil = CaseClassPattern(None, CaseClassType(nilClass, Seq(CharType)), Seq()) - val consType = CaseClassType(consClass, Seq(CharType)) - def mkCons(hd: Pattern, tl: Pattern) = CaseClassPattern(None, consType, Seq(hd,tl)) - val chars = s.toCharArray//.asInstanceOf[Seq[Char]] - def charPat(ch : Char) = LiteralPattern(None, CharLiteral(ch)) - (chars.foldRight(nil)( (ch: Char, p : Pattern) => mkCons( charPat(ch), p)), dctx) - + case ExStringLiteral(s) => (LiteralPattern(binder, StringLiteral(s)), dctx) + case up@ExUnapplyPattern(s, args) => implicit val p: Position = NoPosition val fd = getFunDef(s, up.pos) @@ -1112,18 +1120,25 @@ trait CodeExtraction extends ASTExtractors { val newDctx = dctx.copy(tparams = dctx.tparams ++ tparamsMap) + val restTree = rest match { + case Some(rst) => extractTree(rst) + case None => UnitLiteral() + } + rest = None + val oldCurrentFunDef = currentFunDef val funDefWithBody = extractFunBody(fd, params, b)(newDctx) currentFunDef = oldCurrentFunDef - - val restTree = rest match { - case Some(rst) => extractTree(rst) - case None => UnitLiteral() + + val (other_fds, block) = restTree match { + case LetDef(fds, block) => + (fds, block) + case _ => + (Nil, restTree) } - rest = None - LetDef(funDefWithBody, restTree) + LetDef(funDefWithBody +: other_fds, block) // FIXME case ExDefaultValueFunction @@ -1380,6 +1395,9 @@ trait CodeExtraction extends ASTExtractors { val rr = extractTree(r) (rl, rr) match { + case (IsTyped(_, ArrayType(_)), IsTyped(_, ArrayType(_))) => + outOfSubsetError(tr, "Leon does not support array comparison") + case (IsTyped(_, rt), IsTyped(_, lt)) if typesCompatible(lt, rt) => Not(Equals(rl, rr)) @@ -1398,6 +1416,9 @@ trait CodeExtraction extends ASTExtractors { val rr = extractTree(r) (rl, rr) match { + case (IsTyped(_, ArrayType(_)), IsTyped(_, ArrayType(_))) => + outOfSubsetError(tr, "Leon does not support array comparison") + case (IsTyped(_, rt), IsTyped(_, lt)) if typesCompatible(lt, rt) => Equals(rl, rr) @@ -1497,22 +1518,13 @@ trait CodeExtraction extends ASTExtractors { CharLiteral(c) case str @ ExStringLiteral(s) => - val chars = s.toList.map(CharLiteral) - - val consChar = CaseClassType(libraryCaseClass(str.pos, "leon.collection.Cons"), Seq(CharType)) - val nilChar = CaseClassType(libraryCaseClass(str.pos, "leon.collection.Nil"), Seq(CharType)) - - val charList = chars.foldRight(CaseClass(nilChar, Seq())) { - case (c, s) => CaseClass(consChar, Seq(c, s)) - } - - CaseClass(CaseClassType(libraryCaseClass(str.pos, "leon.lang.string.String"), Seq()), Seq(charList)) - + StringLiteral(s) case ExImplies(lhs, rhs) => Implies(extractTree(lhs), extractTree(rhs)).setPos(current.pos) - + case c @ ExCall(rec, sym, tps, args) => + // The object on which it is called is null if the symbol sym is a valid function in the scope and not a method. val rrec = rec match { case t if (defsToDefs contains sym) && !isMethod(sym) => null @@ -1530,26 +1542,44 @@ trait CodeExtraction extends ASTExtractors { val fd = getFunDef(sym, c.pos) val newTps = tps.map(t => extractType(t)) + val argsByName = (fd.params zip args).map(p => if (isLazy(p._1)) Lambda(Seq(), p._2) else p._2) - FunctionInvocation(fd.typed(newTps), args) + FunctionInvocation(fd.typed(newTps), argsByName) case (IsTyped(rec, ct: ClassType), _, args) if isMethod(sym) => val fd = getFunDef(sym, c.pos) val cd = methodToClass(fd) val newTps = tps.map(t => extractType(t)) + val argsByName = (fd.params zip args).map(p => if (isLazy(p._1)) Lambda(Seq(), p._2) else p._2) - MethodInvocation(rec, cd, fd.typed(newTps), args) + MethodInvocation(rec, cd, fd.typed(newTps), argsByName) case (IsTyped(rec, ft: FunctionType), _, args) => application(rec, args) - case (IsTyped(rec, cct: CaseClassType), name, Nil) if cct.fields.exists(_.id.name == name) => - - val fieldID = cct.fields.find(_.id.name == name).get.id + case (IsTyped(rec, cct: CaseClassType), name, Nil) if cct.classDef.fields.exists(_.id.name == name) => + val fieldID = cct.classDef.fields.find(_.id.name == name).get.id caseClassSelector(cct, rec, fieldID) + //String methods + case (IsTyped(a1, StringType), "toString", List()) => + a1 + case (IsTyped(a1, WithStringconverter(converter)), "toString", List()) => + converter(a1) + case (IsTyped(a1, StringType), "+", List(IsTyped(a2, StringType))) => + StringConcat(a1, a2) + case (IsTyped(a1, StringType), "+", List(IsTyped(a2, WithStringconverter(converter)))) => + StringConcat(a1, converter(a2)) + case (IsTyped(a1, WithStringconverter(converter)), "+", List(IsTyped(a2, StringType))) => + StringConcat(converter(a1), a2) + case (IsTyped(a1, StringType), "length", List()) => + StringLength(a1) + case (IsTyped(a1, StringType), "substring", List(IsTyped(start, IntegerType | Int32Type))) => + SubString(a1, start, StringLength(a1)) + case (IsTyped(a1, StringType), "substring", List(IsTyped(start, IntegerType | Int32Type), IsTyped(end, IntegerType | Int32Type))) => + SubString(a1, start, end) //BigInt methods case (IsTyped(a1, IntegerType), "+", List(IsTyped(a2, IntegerType))) => Plus(a1, a2) @@ -1726,8 +1756,11 @@ trait CodeExtraction extends ASTExtractors { case (IsTyped(a1, CharType), "<=", List(IsTyped(a2, CharType))) => LessEquals(a1, a2) - case (_, name, _) => - outOfSubsetError(tr, "Unknown call to "+name) + case (a1, name, a2) => + val typea1 = a1.getType + val typea2 = a2.map(_.getType).mkString(",") + val sa2 = a2.mkString(",") + outOfSubsetError(tr, "Unknown call to " + name + s" on $a1 ($typea1) with arguments $sa2 of type $typea2") } // default behaviour is to complain :) @@ -1773,6 +1806,9 @@ trait CodeExtraction extends ASTExtractors { case TypeRef(_, sym, _) if isRealSym(sym) => RealType + + case TypeRef(_, sym, _) if isStringSym(sym) => + StringType case TypeRef(_, sym, btt :: Nil) if isScalaSetSym(sym) => outOfSubsetError(pos, "Scala's Set API is no longer extracted. Make sure you import leon.lang.Set that defines supported Set operations.") @@ -1810,7 +1846,7 @@ trait CodeExtraction extends ASTExtractors { case TypeRef(_, sym, tps) if isByNameSym(sym) => extractType(tps.head) - case TypeRef(_, sym, tps) => + case tr @ TypeRef(_, sym, tps) => val leontps = tps.map(extractType) if (sym.isAbstractType) { diff --git a/src/main/scala/leon/frontends/scalac/ExtractionPhase.scala b/src/main/scala/leon/frontends/scalac/ExtractionPhase.scala index a99c1df18b880ea6e7d0f379041deade3172902a..ffb4fa90c3bb15c682f7886f3d3bbf9ee79c508b 100644 --- a/src/main/scala/leon/frontends/scalac/ExtractionPhase.scala +++ b/src/main/scala/leon/frontends/scalac/ExtractionPhase.scala @@ -25,13 +25,16 @@ object ExtractionPhase extends SimpleLeonPhase[List[String], Program] { val settings = new Settings + def getFiles(path: String): Option[Array[String]] = + scala.util.Try(new File(path).listFiles().map{ _.getAbsolutePath }).toOption + val scalaLib = Option(scala.Predef.getClass.getProtectionDomain.getCodeSource).map{ _.getLocation.getPath }.orElse( for { // We are in Eclipse. Look in Eclipse plugins to find scala lib eclipseHome <- Option(System.getenv("ECLIPSE_HOME")) pluginsHome = eclipseHome + "/plugins" - plugins <- scala.util.Try(new File(pluginsHome).listFiles().map{ _.getAbsolutePath }).toOption + plugins <- getFiles(pluginsHome) path <- plugins.find{ _ contains "scala-library"} } yield path).getOrElse( ctx.reporter.fatalError( "No Scala library found. If you are working in Eclipse, " + diff --git a/src/main/scala/leon/genc/CAST.scala b/src/main/scala/leon/genc/CAST.scala new file mode 100644 index 0000000000000000000000000000000000000000..9a9242859e799488eeb8a42170f6fcedd1b0dd13 --- /dev/null +++ b/src/main/scala/leon/genc/CAST.scala @@ -0,0 +1,296 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package genc + +import utils.UniqueCounter + +/* + * Here are defined classes used to represent AST of C programs. + */ + +object CAST { // C Abstract Syntax Tree + + sealed abstract class Tree + case object NoTree extends Tree + + + /* ------------------------------------------------------------ Types ----- */ + abstract class Type(val rep: String) extends Tree { + override def toString = rep + + def mutable: Type = this match { + case Const(typ) => typ.mutable + case _ => this + } + } + + /* Type Modifiers */ + case class Const(typ: Type) extends Type(s"$typ const") + case class Pointer(typ: Type) extends Type(s"$typ*") + + /* Primitive Types */ + case object Int32 extends Type("int32_t") // Requires <stdint.h> + case object Bool extends Type("bool") // Requires <stdbool.h> + case object Void extends Type("void") + + /* Compound Types */ + case class Struct(id: Id, fields: Seq[Var]) extends Type(id.name) + + + /* --------------------------------------------------------- Literals ----- */ + case class IntLiteral(v: Int) extends Stmt + case class BoolLiteral(b: Boolean) extends Stmt + + + /* ----------------------------------------------------- Definitions ----- */ + abstract class Def extends Tree + + case class Prog(structs: Seq[Struct], functions: Seq[Fun]) extends Def + + case class Fun(id: Id, retType: Type, params: Seq[Var], body: Stmt) extends Def + + case class Id(name: String) extends Def { + // `|` is used as the margin delimiter and can cause trouble in some situations + def fixMargin = + if (name.size > 0 && name(0) == '|') "| " + name + else name + } + + case class Var(id: Id, typ: Type) extends Def + + /* ----------------------------------------------------------- Stmts ----- */ + abstract class Stmt extends Tree + case object NoStmt extends Stmt + + case class Compound(stmts: Seq[Stmt]) extends Stmt + + case class Assert(pred: Stmt, error: Option[String]) extends Stmt { // Requires <assert.h> + require(pred.isValue) + } + + case class DeclVar(x: Var) extends Stmt + + case class DeclInitVar(x: Var, value: Stmt) extends Stmt { + require(value.isValue) + } + + case class Assign(lhs: Stmt, rhs: Stmt) extends Stmt { + require(lhs.isValue && rhs.isValue) + } + + // Note: we don't need to differentiate between specific + // operators so we only keep track of the "kind" of operator + // with an Id. + case class UnOp(op: Id, rhs: Stmt) extends Stmt { + require(rhs.isValue) + } + + case class MultiOp(op: Id, stmts: Seq[Stmt]) extends Stmt { + require(stmts.length > 1 && stmts.forall { _.isValue }) + } + + case class SubscriptOp(ptr: Stmt, idx: Stmt) extends Stmt { + require(ptr.isValue && idx.isValue) + } + + case object Break extends Stmt + + case class Return(stmt: Stmt) extends Stmt { + require(stmt.isValue) + } + + case class IfElse(cond: Stmt, thn: Stmt, elze: Stmt) extends Stmt { + require(cond.isValue) + } + + case class While(cond: Stmt, body: Stmt) extends Stmt { + require(cond.isValue) + } + + case class AccessVar(id: Id) extends Stmt + case class AccessRef(id: Id) extends Stmt + case class AccessAddr(id: Id) extends Stmt + case class AccessField(struct: Stmt, field: Id) extends Stmt { + require(struct.isValue) + } + + case class Call(id: Id, args: Seq[Stmt]) extends Stmt { + require(args forall { _.isValue }) + } + + case class StructInit(args: Seq[(Id, Stmt)], struct: Struct) extends Stmt { + require(args forall { _._2.isValue }) + } + + case class ArrayInit(length: Stmt, valueType: Type, defaultValue: Stmt) extends Stmt { + require(length.isValue && defaultValue.isValue) + } + + case class ArrayInitWithValues(valueType: Type, values: Seq[Stmt]) extends Stmt { + require(values forall { _.isValue }) + + lazy val length = values.length + } + + + /* -------------------------------------------------------- Factories ----- */ + object Op { + def apply(op: String, rhs: Stmt) = UnOp(Id(op), rhs) + def apply(op: String, rhs: Stmt, lhs: Stmt) = MultiOp(Id(op), rhs :: lhs :: Nil) + def apply(op: String, stmts: Seq[Stmt]) = MultiOp(Id(op), stmts) + } + + object Val { + def apply(id: Id, typ: Type) = typ match { + case Const(_) => Var(id, typ) // avoid const of const + case _ => Var(id, Const(typ)) + } + } + + /* "Templatetized" Types */ + object Tuple { + def apply(bases: Seq[Type]) = { + val name = Id("__leon_tuple_" + bases.mkString("_") + "_t") + + val fields = bases.zipWithIndex map { + case (typ, idx) => Var(getNthId(idx + 1), typ) + } + + Struct(name, fields) + } + + // Indexes start from 1, not 0! + def getNthId(n: Int) = Id("_" + n) + } + + object Array { + def apply(base: Type) = { + val name = Id("__leon_array_" + base + "_t") + val data = Var(dataId, Pointer(base)) + val length = Var(lengthId, Int32) + val fields = data :: length :: Nil + + Struct(name, fields) + } + + def lengthId = Id("length") + def dataId = Id("data") + } + + + /* ---------------------------------------------------- Introspection ----- */ + implicit class IntrospectionOps(val stmt: Stmt) { + def isLiteral = stmt match { + case _: IntLiteral => true + case _: BoolLiteral => true + case _ => false + } + + // True if statement can be used as a value + def isValue: Boolean = isLiteral || { + stmt match { + //case _: Assign => true it's probably the case but for now let's ignore it + case c: Compound => c.stmts.size == 1 && c.stmts.head.isValue + case _: UnOp => true + case _: MultiOp => true + case _: SubscriptOp => true + case _: AccessVar => true + case _: AccessRef => true + case _: AccessAddr => true + case _: AccessField => true + case _: Call => true + case _: StructInit => true + case _: ArrayInit => true + case _: ArrayInitWithValues => true + case _ => false + } + } + + def isPure: Boolean = isLiteral || { + stmt match { + case NoStmt => true + case Compound(stmts) => stmts forall { _.isPure } + case Assert(pred, _) => pred.isPure + case UnOp(_, rhs) => rhs.isPure + case MultiOp(_, stmts) => Compound(stmts).isPure + case SubscriptOp(ptr, idx) => (ptr ~ idx).isPure + case IfElse(c, t, e) => (c ~ t ~ e).isPure + case While(c, b) => (c ~ b).isPure + case AccessVar(_) => true + case AccessRef(_) => true + case AccessAddr(_) => true + case AccessField(s, _) => s.isPure + // case Call(id, args) => true if args are pure and function `id` is pure too + case _ => false + } + } + + def isPureValue = isValue && isPure + } + + + /* ------------------------------------------------------------- DSL ----- */ + // Operator ~~ appends and flattens nested compounds + implicit class StmtOps(val stmt: Stmt) { + // In addition to combining statements together in a compound + // we remove the empty ones and if the resulting compound + // has only one statement we return this one without being + // wrapped into a Compound + def ~(other: Stmt) = { + val stmts = (stmt, other) match { + case (Compound(stmts), Compound(others)) => stmts ++ others + case (stmt , Compound(others)) => stmt +: others + case (Compound(stmts), other ) => stmts :+ other + case (stmt , other ) => stmt :: other :: Nil + } + + def isNoStmt(s: Stmt) = s match { + case NoStmt => true + case _ => false + } + + val compound = Compound(stmts filterNot isNoStmt) + compound match { + case Compound(stmts) if stmts.length == 0 => NoStmt + case Compound(stmts) if stmts.length == 1 => stmts.head + case compound => compound + } + } + + def ~~(others: Seq[Stmt]) = stmt ~ Compound(others) + } + + implicit class StmtsOps(val stmts: Seq[Stmt]) { + def ~~(other: Stmt) = other match { + case Compound(others) => Compound(stmts) ~~ others + case other => Compound(stmts) ~ other + } + + def ~~~(others: Seq[Stmt]) = Compound(stmts) ~~ others + } + + val True = BoolLiteral(true) + val False = BoolLiteral(false) + + + /* ------------------------------------------------ Fresh Generators ----- */ + object FreshId { + private var counter = -1 + private val leonPrefix = "__leon_" + + def apply(prefix: String = ""): Id = { + counter += 1 + Id(leonPrefix + prefix + counter) + } + } + + object FreshVar { + def apply(typ: Type, prefix: String = "") = Var(FreshId(prefix), typ) + } + + object FreshVal { + def apply(typ: Type, prefix: String = "") = Val(FreshId(prefix), typ) + } +} + diff --git a/src/main/scala/leon/genc/CConverter.scala b/src/main/scala/leon/genc/CConverter.scala new file mode 100644 index 0000000000000000000000000000000000000000..a979fd8dd0c28351948270048adf53ed597a1317 --- /dev/null +++ b/src/main/scala/leon/genc/CConverter.scala @@ -0,0 +1,708 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package genc + +import purescala.Common._ +import purescala.Definitions._ +import purescala.Expressions._ +import purescala.Types._ +import xlang.Expressions._ + +import scala.reflect.ClassTag + +// don't import CAST._ to decrease possible confusion between the two ASTs + +class CConverter(val ctx: LeonContext, val prog: Program) { + def convert: CAST.Prog = convertToProg(prog) + + // Global data: keep track of the custom types and function of the input program + // Using sequences and not sets to keep track of order/dependencies + private var typeDecls = Seq[CAST.Struct]() + private var functions = Seq[CAST.Fun]() + + // Extra information about inner functions' context + // See classes VarInfo and FunCtx and functions convertToFun and + // FunctionInvocation conversion + private var funExtraArgss = Map[CAST.Id, Seq[CAST.Id]]() + private val emptyFunCtx = FunCtx(Seq()) + + private def registerType(typ: CAST.Struct) { + // Types might be processed more than once as the corresponding CAST type + // is not cached and need to be reconstructed several time if necessary + if (!typeDecls.contains(typ)) { + typeDecls = typeDecls :+ typ + debug(s"New type registered: $typ") + } + } + + private def registerFun(fun: CAST.Fun) { + // Unlike types, functions should not get defined multiple times as this + // would imply invalidating funExtraArgss + if (functions contains fun) + internalError("Function ${fun.id} defined more than once") + else + functions = functions :+ fun + } + + // Register extra function argument for the function named `id` + private def registerFunExtraArgs(id: CAST.Id, params: Seq[CAST.Id]) { + funExtraArgss = funExtraArgss + ((id, params)) + } + + // Get the extra argument identifiers for the function named `id` + private def getFunExtraArgs(id: CAST.Id) = funExtraArgss.getOrElse(id, Seq()) + + // Apply the conversion function and make sure the resulting AST matches our expectation + private def convertTo[T](tree: Tree)(implicit funCtx: FunCtx, ct: ClassTag[T]): T = convert(tree) match { + case t: T => t + case x => internalError(s"Expected an instance of $ct when converting $tree but got $x") + } + + // Generic conversion function + // Currently simple aliases in case we need later to have special treatment instead + private def convertToType (tree: Tree)(implicit funCtx: FunCtx) = convertTo[CAST.Type](tree) + private def convertToStruct(tree: Tree)(implicit funCtx: FunCtx) = convertTo[CAST.Struct](tree) + private def convertToId (tree: Tree)(implicit funCtx: FunCtx) = convertTo[CAST.Id](tree) + private def convertToStmt (tree: Tree)(implicit funCtx: FunCtx) = convertTo[CAST.Stmt](tree) + private def convertToVar (tree: Tree)(implicit funCtx: FunCtx) = convertTo[CAST.Var](tree) + + private def convertToProg(prog: Program): CAST.Prog = { + // Only process the main unit + val (mainUnits, _) = prog.units partition { _.isMainUnit } + + if (mainUnits.size == 0) fatalError("No main unit in the program") + if (mainUnits.size >= 2) fatalError("Multiple main units in the program") + + val mainUnit = mainUnits.head + + debug(s"Converting the main unit:\n$mainUnit") + collectSymbols(mainUnit) + + CAST.Prog(typeDecls, functions) + } + + // Look for function and structure definitions + private def collectSymbols(unit: UnitDef) { + implicit val defaultFunCtx = emptyFunCtx + + unit.defs.foreach { + case ModuleDef(_, funDefs, _) => + funDefs.foreach { + case fd: FunDef => convertToFun(fd) // the function gets registered here + case cc: CaseClassDef => convertToStruct(cc) // the type declaration gets registered here + + case x => internalError(s"Unknown function definition $x: ${x.getClass}") + } + + case x => internalError(s"Unexpected definition $x instead of a ModuleDef") + } + } + + // A variable can be locally declared (e.g. function parameter or local variable) + // or it can be "inherited" from a more global context (e.g. inner functions have + // access to their outer function parameters). + private case class VarInfo(x: CAST.Var, local: Boolean) { + // Transform a local variable into a global variable + def lift = VarInfo(x, false) + + // Generate CAST variable declaration for function signature + def toParam = CAST.Var(x.id, CAST.Pointer(x.typ)) + + // Generate CAST access statement + def toArg = if (local) CAST.AccessAddr(x.id) else CAST.AccessVar(x.id) + } + + private case class FunCtx(vars: Seq[VarInfo]) { + // Transform local variables into "outer" variables + def lift = FunCtx(vars map { _.lift }) + + // Create a new context with more local variables + def extend(x: CAST.Var): FunCtx = extend(Seq(x)) + def extend(xs: Seq[CAST.Var]): FunCtx = { + val newVars = xs map { VarInfo(_, true) } + FunCtx(vars ++ newVars) + } + + // Check if a given variable's identifier exists in the context and is an "outer" variable + def hasOuterVar(id: CAST.Id) = vars exists { vi => !vi.local && vi.x.id == id } + + // List all variables' ids + def extractIds = vars map { _.x.id } + + // Generate arguments for the given identifiers according to the current context + def toArgs(ids: Seq[CAST.Id]) = { + val filtered = vars filter { ids contains _.x.id } + filtered map { _.toArg } + } + + // Generate parameters (var + type) + def toParams = vars map { _.toParam } + } + + // Extract inner functions too + private def convertToFun(fd: FunDef)(implicit funCtx: FunCtx) = { + // Forbid return of array as they are allocated on the stack + if (containsArrayType(fd.returnType)) { + fatalError("Returning arrays is currently not allowed") + } + + val id = convertToId(fd.id) + val retType = convertToType(fd.returnType) + val stdParams = fd.params map convertToVar + + // Prepend existing variables from the outer function context to + // this function's parameters + val extraParams = funCtx.toParams + val params = extraParams ++ stdParams + + // Function Context: + // 1) Save the variables of the current context for later function invocation + // 2) Lift & augment funCtx with the current function's arguments + // 3) Propagate it to the current function's body + + registerFunExtraArgs(id, funCtx.extractIds) + + val funCtx2 = funCtx.lift.extend(stdParams) + + val b = convertToStmt(fd.fullBody)(funCtx2) + val body = retType match { + case CAST.Void => b + case _ => injectReturn(b) + } + + val fun = CAST.Fun(id, retType, params, body) + registerFun(fun) + + fun + } + + private def convert(tree: Tree)(implicit funCtx: FunCtx): CAST.Tree = tree match { + /* ---------------------------------------------------------- Types ----- */ + case Int32Type => CAST.Int32 + case BooleanType => CAST.Bool + case UnitType => CAST.Void + + case ArrayType(base) => + val typ = CAST.Array(convertToType(base)) + registerType(typ) + typ + + case TupleType(bases) => + val typ = CAST.Tuple(bases map convertToType) + registerType(typ) + typ + + case cd: CaseClassDef => + if (cd.isAbstract) fatalError("Abstract types are not supported") + if (cd.hasParent) fatalError("Inheritance is not supported") + if (cd.isCaseObject) fatalError("Case Objects are not supported") + if (cd.tparams.length > 0) fatalError("Type Parameters are not supported") + if (cd.methods.length > 0) fatalError("Methods are not yet supported") + + val id = convertToId(cd.id) + val fields = cd.fields map convertToVar + val typ = CAST.Struct(id, fields) + + registerType(typ) + typ + + case CaseClassType(cd, _) => convertToStruct(cd) // reuse `case CaseClassDef` + + /* ------------------------------------------------------- Literals ----- */ + case IntLiteral(v) => CAST.IntLiteral(v) + case BooleanLiteral(b) => CAST.BoolLiteral(b) + case UnitLiteral() => CAST.NoStmt + + /* ------------------------------------ Definitions and Statements ----- */ + case id: Identifier => + if (id.name == "main") CAST.Id("main") // and not `main0` + else CAST.Id(id.uniqueName) + + // Function parameter + case vd: ValDef => buildVal(vd.id, vd.getType) + + // Accessing variable + case v: Variable => buildAccessVar(v.id) + + case Block(exprs, last) => + // Interleave the "bodies" of flatten expressions and their values + // and generate a Compound statement + (exprs :+ last) map convertToStmt reduce { _ ~ _ } + + case Let(b, v, r) => buildLet(b, v, r, false) + case LetVar(b, v, r) => buildLet(b, v, r, true) + + case LetDef(fds, rest) => + fds foreach convertToFun // The functions get registered there + convertToStmt(rest) + + case Assignment(varId, expr) => + val f = convertAndFlatten(expr) + val x = buildAccessVar(varId) + + val assign = CAST.Assign(x, f.value) + + f.body ~~ assign + + case tuple @ Tuple(exprs) => + val struct = convertToStruct(tuple.getType) + val types = struct.fields map { _.typ } + val fs = convertAndNormaliseExecution(exprs, types) + val args = fs.values.zipWithIndex map { + case (arg, idx) => (CAST.Tuple.getNthId(idx + 1), arg) + } + + fs.bodies ~~ CAST.StructInit(args, struct) + + case TupleSelect(tuple1, idx) => // here idx is already 1-based + val struct = convertToStruct(tuple1.getType) + val tuple2 = convertToStmt(tuple1) + + val fs = normaliseExecution((tuple2, struct) :: Nil) + + val tuple = fs.values.head + + fs.bodies ~~ CAST.AccessField(tuple, CAST.Tuple.getNthId(idx)) + + case ArrayLength(array1) => + val array2 = convertToStmt(array1) + val arrayType = convertToType(array1.getType) + + val fs = normaliseExecution((array2, arrayType) :: Nil) + + val array = fs.values.head + + fs.bodies ~~ CAST.AccessField(array, CAST.Array.lengthId) + + case ArraySelect(array1, index1) => + val array2 = convertToStmt(array1) + val arrayType = convertToType(array1.getType) + val index2 = convertToStmt(index1) + + val fs = normaliseExecution((array2, arrayType) :: (index2, CAST.Int32) :: Nil) + + val array = fs.values(0) + val index = fs.values(1) + val ptr = CAST.AccessField(array, CAST.Array.dataId) + val select = CAST.SubscriptOp(ptr, index) + + fs.bodies ~~ select + + case NonemptyArray(elems, Some((value1, length1))) if elems.isEmpty => + val length2 = convertToStmt(length1) + val valueType = convertToType(value1.getType) + val value2 = convertToStmt(value1) + + val fs = normaliseExecution((length2, CAST.Int32) :: (value2, valueType) :: Nil) + val length = fs.values(0) + val value = fs.values(1) + + fs.bodies ~~ CAST.ArrayInit(length, valueType, value) + + case NonemptyArray(elems, Some(_)) => + fatalError("NonemptyArray with non empty elements is not supported") + + case NonemptyArray(elems, None) => // Here elems is non-empty + // Sort values according the the key (aka index) + val indexes = elems.keySet.toSeq.sorted + val values = indexes map { elems(_) } + + // Assert all types are the same + val types = values map { e => convertToType(e.getType) } + val typ = types(0) + val allSame = types forall { _ == typ } + if (!allSame) fatalError("Heterogenous arrays are not supported") + + val fs = convertAndNormaliseExecution(values, types) + + fs.bodies ~~ CAST.ArrayInitWithValues(typ, fs.values) + + case ArrayUpdate(array1, index1, newValue1) => + val array2 = convertToStmt(array1) + val index2 = convertToStmt(index1) + val newValue2 = convertToStmt(newValue1) + val values = array2 :: index2 :: newValue2 :: Nil + + val arePure = values forall { _.isPure } + val areValues = array2.isValue && index2.isValue // no newValue here + + newValue2 match { + case CAST.IfElse(cond, thn, elze) if arePure && areValues => + val array = array2 + val index = index2 + val ptr = CAST.AccessField(array, CAST.Array.dataId) + val select = CAST.SubscriptOp(ptr, index) + + val ifelse = buildIfElse(cond, injectAssign(select, thn), + injectAssign(select, elze)) + + ifelse + + case _ => + val arrayType = convertToType(array1.getType) + val indexType = CAST.Int32 + val valueType = convertToType(newValue1.getType) + val types = arrayType :: indexType :: valueType :: Nil + + val fs = normaliseExecution(values, types) + + val array = fs.values(0) + val index = fs.values(1) + val newValue = fs.values(2) + + val ptr = CAST.AccessField(array, CAST.Array.dataId) + val select = CAST.SubscriptOp(ptr, index) + val assign = CAST.Assign(select, newValue) + + fs.bodies ~~ assign + } + + case CaseClass(typ, args1) => + val struct = convertToStruct(typ) + val types = struct.fields map { _.typ } + val argsFs = convertAndNormaliseExecution(args1, types) + val fieldsIds = typ.classDef.fieldsIds map convertToId + val args = fieldsIds zip argsFs.values + + argsFs.bodies ~~ CAST.StructInit(args, struct) + + case CaseClassSelector(_, x1, fieldId) => + val struct = convertToStruct(x1.getType) + val x2 = convertToStmt(x1) + + val fs = normaliseExecution((x2, struct) :: Nil) + val x = fs.values.head + + fs.bodies ~~ CAST.AccessField(x, convertToId(fieldId)) + + case LessThan(lhs, rhs) => buildBinOp(lhs, "<", rhs) + case GreaterThan(lhs, rhs) => buildBinOp(lhs, ">", rhs) + case LessEquals(lhs, rhs) => buildBinOp(lhs, "<=", rhs) + case GreaterEquals(lhs, rhs) => buildBinOp(lhs, ">=", rhs) + case Equals(lhs, rhs) => buildBinOp(lhs, "==", rhs) + + case Not(rhs) => buildUnOp ( "!", rhs) + + case And(exprs) => buildMultiOp("&&", exprs) + case Or(exprs) => buildMultiOp("||", exprs) + + case BVPlus(lhs, rhs) => buildBinOp(lhs, "+", rhs) + case BVMinus(lhs, rhs) => buildBinOp(lhs, "-", rhs) + case BVUMinus(rhs) => buildUnOp ( "-", rhs) + case BVTimes(lhs, rhs) => buildBinOp(lhs, "*", rhs) + case BVDivision(lhs, rhs) => buildBinOp(lhs, "/", rhs) + case BVRemainder(lhs, rhs) => buildBinOp(lhs, "%", rhs) + case BVNot(rhs) => buildUnOp ( "~", rhs) + case BVAnd(lhs, rhs) => buildBinOp(lhs, "&", rhs) + case BVOr(lhs, rhs) => buildBinOp(lhs, "|", rhs) + case BVXOr(lhs, rhs) => buildBinOp(lhs, "^", rhs) + case BVShiftLeft(lhs, rhs) => buildBinOp(lhs, "<<", rhs) + case BVAShiftRight(lhs, rhs) => buildBinOp(lhs, ">>", rhs) + case BVLShiftRight(lhs, rhs) => fatalError("operator >>> not supported") + + // Ignore assertions for now + case Ensuring(body, _) => convert(body) + case Require(_, body) => convert(body) + case Assert(_, _, body) => convert(body) + + case IfExpr(cond1, thn1, elze1) => + val condF = convertAndFlatten(cond1) + val thn = convertToStmt(thn1) + val elze = convertToStmt(elze1) + + condF.body ~~ buildIfElse(condF.value, thn, elze) + + case While(cond1, body1) => + val cond = convertToStmt(cond1) + val body = convertToStmt(body1) + + if (cond.isPureValue) { + CAST.While(cond, body) + } else { + // Transform while (cond) { body } into + // while (true) { if (cond) { body } else { break } } + val condF = flatten(cond) + val ifelse = condF.body ~~ buildIfElse(condF.value, CAST.NoStmt, CAST.Break) + CAST.While(CAST.True, ifelse ~ body) + } + + case FunctionInvocation(tfd @ TypedFunDef(fd, _), stdArgs) => + // In addition to regular function parameters, add the callee's extra parameters + val id = convertToId(fd.id) + val types = tfd.params map { p => convertToType(p.getType) } + val fs = convertAndNormaliseExecution(stdArgs, types) + val extraArgs = funCtx.toArgs(getFunExtraArgs(id)) + val args = extraArgs ++ fs.values + + fs.bodies ~~ CAST.Call(id, args) + + case unsupported => + fatalError(s"$unsupported (of type ${unsupported.getClass}) is currently not supported by GenC") + } + + private def buildVar(id: Identifier, typ: TypeTree)(implicit funCtx: FunCtx) = + CAST.Var(convertToId(id), convertToType(typ)) + + private def buildVal(id: Identifier, typ: TypeTree)(implicit funCtx: FunCtx) = + CAST.Val(convertToId(id), convertToType(typ)) + + private def buildAccessVar(id1: Identifier)(implicit funCtx: FunCtx) = { + // Depending on the context, we have to deference the variable + val id = convertToId(id1) + if (funCtx.hasOuterVar(id)) CAST.AccessRef(id) + else CAST.AccessVar(id) + } + + private def buildLet(id: Identifier, value: Expr, rest1: Expr, forceVar: Boolean) + (implicit funCtx: FunCtx): CAST.Stmt = { + val (x, stmt) = buildDeclInitVar(id, value, forceVar) + + // Augment ctx for the following instructions + val funCtx2 = funCtx.extend(x) + val rest = convertToStmt(rest1)(funCtx2) + + stmt ~ rest + } + + + // Create a new variable for the given value, potentially immutable, and initialize it + private def buildDeclInitVar(id: Identifier, v: Expr, forceVar: Boolean) + (implicit funCtx: FunCtx): (CAST.Var, CAST.Stmt) = { + val valueF = convertAndFlatten(v) + val typ = v.getType + + valueF.value match { + case CAST.IfElse(cond, thn, elze) => + val x = buildVar(id, typ) + val decl = CAST.DeclVar(x) + val ifelse = buildIfElse(cond, injectAssign(x, thn), injectAssign(x, elze)) + val init = decl ~ ifelse + + (x, valueF.body ~~ init) + + case value => + val x = if (forceVar) buildVar(id, typ) else buildVal(id, typ) + val init = CAST.DeclInitVar(x, value) + + (x, valueF.body ~~ init) + } + } + + private def buildBinOp(lhs: Expr, op: String, rhs: Expr)(implicit funCtx: FunCtx) = { + buildMultiOp(op, lhs :: rhs :: Nil) + } + + private def buildUnOp(op: String, rhs1: Expr)(implicit funCtx: FunCtx) = { + val rhsF = convertAndFlatten(rhs1) + rhsF.body ~~ CAST.Op(op, rhsF.value) + } + + private def buildMultiOp(op: String, exprs: Seq[Expr])(implicit funCtx: FunCtx): CAST.Stmt = { + require(exprs.length >= 2) + + val stmts = exprs map convertToStmt + val types = exprs map { e => convertToType(e.getType) } + + buildMultiOp(op, stmts, types) + } + + private def buildMultiOp(op: String, stmts: Seq[CAST.Stmt], types: Seq[CAST.Type]): CAST.Stmt = { + // Default operator constuction when either pure statements are involved + // or no shortcut can happen + def defaultBuild = { + val fs = normaliseExecution(stmts, types) + fs.bodies ~~ CAST.Op(op, fs.values) + } + + if (stmts forall { _.isPureValue }) defaultBuild + else op match { + case "&&" => + // Apply short-circuit if needed + if (stmts.length == 2) { + // Base case: + // { { a; v } && { b; w } } + // is mapped onto + // { a; if (v) { b; w } else { false } } + val av = flatten(stmts(0)) + val bw = stmts(1) + + if (bw.isPureValue) defaultBuild + else av.body ~~ buildIfElse(av.value, bw, CAST.False) + } else { + // Recursive case: + // { { a; v } && ... } + // is mapped onto + // { a; if (v) { ... } else { false } } + val av = flatten(stmts(0)) + val rest = buildMultiOp(op, stmts.tail, types.tail) + + if (rest.isPureValue) defaultBuild + else av.body ~~ buildIfElse(av.value, rest, CAST.False) + } + + case "||" => + // Apply short-circuit if needed + if (stmts.length == 2) { + // Base case: + // { { a; v } || { b; w } } + // is mapped onto + // { a; if (v) { true } else { b; w } } + val av = flatten(stmts(0)) + val bw = stmts(1) + + if (bw.isPureValue) defaultBuild + else av.body ~~ buildIfElse(av.value, CAST.True, bw) + } else { + // Recusrive case: + // { { a; v } || ... } + // is mapped onto + // { a; if (v) { true } else { ... } } + val av = flatten(stmts(0)) + val rest = buildMultiOp(op, stmts.tail, types.tail) + + if (rest.isPureValue) defaultBuild + else av.body ~~ buildIfElse(av.value, CAST.True, rest) + } + + case _ => + defaultBuild + } + } + + // Flatten `if (if (cond1) thn1 else elze1) thn2 else elze2` + // into `if (cond1) { if (thn1) thn2 else elz2 } else { if (elz1) thn2 else elze2 }` + // or, if possible, into `if ((cond1 && thn1) || elz1) thn2 else elz2` + // + // Flatten `if (true) thn else elze` into `thn` + // Flatten `if (false) thn else elze` into `elze` + private def buildIfElse(cond: CAST.Stmt, thn2: CAST.Stmt, elze2: CAST.Stmt): CAST.Stmt = { + val condF = flatten(cond) + + val ifelse = condF.value match { + case CAST.IfElse(cond1, thn1, elze1) => + if (cond1.isPure && thn1.isPure && elze1.isPure) { + val bools = CAST.Bool :: CAST.Bool :: Nil + val ands = cond1 :: thn1 :: Nil + val ors = buildMultiOp("&&", ands, bools) :: elze1 :: Nil + val condX = buildMultiOp("||", ors, bools) + CAST.IfElse(condX, thn2, elze2) + } else { + buildIfElse(cond1, buildIfElse(thn1, thn2, elze2), buildIfElse(elze1, thn2, elze2)) + } + + case CAST.True => thn2 + case CAST.False => elze2 + case cond2 => CAST.IfElse(cond2, thn2, elze2) + } + + condF.body ~~ ifelse + } + + private def injectReturn(stmt: CAST.Stmt): CAST.Stmt = { + val f = flatten(stmt) + + f.value match { + case CAST.IfElse(cond, thn, elze) => + f.body ~~ CAST.IfElse(cond, injectReturn(thn), injectReturn(elze)) + + case _ => + f.body ~~ CAST.Return(f.value) + } + } + + private def injectAssign(x: CAST.Var, stmt: CAST.Stmt): CAST.Stmt = { + injectAssign(CAST.AccessVar(x.id), stmt) + } + + private def injectAssign(x: CAST.Stmt, stmt: CAST.Stmt): CAST.Stmt = { + val f = flatten(stmt) + + f.value match { + case CAST.IfElse(cond, thn, elze) => + f.body ~~ CAST.IfElse(cond, injectAssign(x, thn), injectAssign(x, elze)) + + case _ => + f.body ~~ CAST.Assign(x, f.value) + } + } + + // Flattened represents a non-empty statement { a; b; ...; y; z } + // split into body { a; b; ...; y } and value z + private case class Flattened(value: CAST.Stmt, body: Seq[CAST.Stmt]) + + // FlattenedSeq does the same as Flattened for a sequence of non-empty statements + private case class FlattenedSeq(values: Seq[CAST.Stmt], bodies: Seq[CAST.Stmt]) + + private def flatten(stmt: CAST.Stmt) = stmt match { + case CAST.Compound(stmts) if stmts.isEmpty => internalError(s"Empty compound cannot be flattened") + case CAST.Compound(stmts) => Flattened(stmts.last, stmts.init) + case stmt => Flattened(stmt, Seq()) + } + + private def convertAndFlatten(expr: Expr)(implicit funCtx: FunCtx) = flatten(convertToStmt(expr)) + + // Normalise execution order of, for example, function parameters; + // `types` represents the expected type of the corresponding values + // in case an intermediary variable needs to be created + private def convertAndNormaliseExecution(exprs: Seq[Expr], types: Seq[CAST.Type]) + (implicit funCtx: FunCtx) = { + require(exprs.length == types.length) + normaliseExecution(exprs map convertToStmt, types) + } + + private def normaliseExecution(typedStmts: Seq[(CAST.Stmt, CAST.Type)]): FlattenedSeq = + normaliseExecution(typedStmts map { _._1 }, typedStmts map { _._2 }) + + private def normaliseExecution(stmts: Seq[CAST.Stmt], types: Seq[CAST.Type]): FlattenedSeq = { + require(stmts.length == types.length) + + // Create temporary variables if needed + val stmtsFs = stmts map flatten + val fs = (stmtsFs zip types) map { + case (f, _) if f.value.isPureValue => f + + case (f, typ) => + // Similarly to buildDeclInitVar: + val (tmp, body) = f.value match { + case CAST.IfElse(cond, thn, elze) => + val tmp = CAST.FreshVar(typ.mutable, "normexec") + val decl = CAST.DeclVar(tmp) + val ifelse = buildIfElse(cond, injectAssign(tmp, thn), injectAssign(tmp, elze)) + val body = f.body ~~ decl ~ ifelse + + (tmp, body) + + case value => + val tmp = CAST.FreshVal(typ, "normexec") + val body = f.body ~~ CAST.DeclInitVar(tmp, f.value) + + (tmp, body) + } + + val value = CAST.AccessVar(tmp.id) + flatten(body ~ value) + } + + val empty = Seq[CAST.Stmt]() + val bodies = fs.foldLeft(empty){ _ ++ _.body } + val values = fs map { _.value } + + FlattenedSeq(values, bodies) + } + + private def containsArrayType(typ: TypeTree): Boolean = typ match { + case Int32Type => false + case BooleanType => false + case UnitType => false + case ArrayType(_) => true + case TupleType(bases) => bases exists containsArrayType + case CaseClassType(cd, _) => cd.fields map { _.getType } exists containsArrayType + } + + private def internalError(msg: String) = ctx.reporter.internalError(msg) + private def fatalError(msg: String) = ctx.reporter.fatalError(msg) + private def debug(msg: String) = ctx.reporter.debug(msg)(utils.DebugSectionGenC) + +} + diff --git a/src/main/scala/leon/genc/CFileOutputPhase.scala b/src/main/scala/leon/genc/CFileOutputPhase.scala new file mode 100644 index 0000000000000000000000000000000000000000..f2b7797183da17ccb6e326a85a779a4ef58be9e2 --- /dev/null +++ b/src/main/scala/leon/genc/CFileOutputPhase.scala @@ -0,0 +1,54 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package genc + +import java.io.File +import java.io.FileWriter +import java.io.BufferedWriter + +object CFileOutputPhase extends UnitPhase[CAST.Prog] { + + val name = "C File Output" + val description = "Output converted C program to the specified file (default leon.c)" + + val optOutputFile = new LeonOptionDef[String] { + val name = "o" + val description = "Output file" + val default = "leon.c" + val usageRhs = "file" + val parser = OptionParsers.stringParser + } + + override val definedOptions: Set[LeonOptionDef[Any]] = Set(optOutputFile) + + def apply(ctx: LeonContext, program: CAST.Prog) { + // Get the output file name from command line options, or use default + val outputFile = new File(ctx.findOptionOrDefault(optOutputFile)) + val parent = outputFile.getParentFile() + try { + if (parent != null) { + parent.mkdirs() + } + } catch { + case _ : java.io.IOException => ctx.reporter.fatalError("Could not create directory " + parent) + } + + // Output C code to the file + try { + val fstream = new FileWriter(outputFile) + val out = new BufferedWriter(fstream) + + val p = new CPrinter + p.print(program) + + out.write(p.toString) + out.close() + + ctx.reporter.info(s"Output written to $outputFile") + } catch { + case _ : java.io.IOException => ctx.reporter.fatalError("Could not write on " + outputFile) + } + } + +} diff --git a/src/main/scala/leon/genc/CPrinter.scala b/src/main/scala/leon/genc/CPrinter.scala new file mode 100644 index 0000000000000000000000000000000000000000..eec404f0c5283bbf09e56dbe79e8335286f61ae6 --- /dev/null +++ b/src/main/scala/leon/genc/CPrinter.scala @@ -0,0 +1,198 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package genc + +import CAST._ +import CPrinterHelpers._ + +class CPrinter(val sb: StringBuffer = new StringBuffer) { + override def toString = sb.toString + + def print(tree: Tree) = pp(tree)(PrinterContext(0, this)) + + def pp(tree: Tree)(implicit ctx: PrinterContext): Unit = tree match { + /* ---------------------------------------------------------- Types ----- */ + case typ: Type => c"${typ.toString}" + + + /* ------------------------------------------------------- Literals ----- */ + case IntLiteral(v) => c"$v" + case BoolLiteral(b) => c"$b" + + + /* --------------------------------------------------- Definitions ----- */ + case Prog(structs, functions) => + c"""|/* ------------------------------------ includes ----- */ + | + |${nary(includeStmts, sep = "\n")} + | + |/* ---------------------- data type declarations ----- */ + | + |${nary(structs map StructDecl, sep = "\n")} + | + |/* ----------------------- data type definitions ----- */ + | + |${nary(structs map StructDef, sep = "\n")} + | + |/* ----------------------- function declarations ----- */ + | + |${nary(functions map FunDecl, sep = "\n")} + | + |/* ------------------------ function definitions ----- */ + | + |${nary(functions, sep = "\n")} + |""" + + case f @ Fun(_, _, _, body) => + c"""|${FunSign(f)} + |{ + | $body + |} + |""" + + case Id(name) => c"$name" + + + /* --------------------------------------------------------- Stmts ----- */ + case NoStmt => c"/* empty */" + + case Compound(stmts) => + val lastIdx = stmts.length - 1 + + for ((stmt, idx) <- stmts.zipWithIndex) { + if (stmt.isValue) c"$stmt;" + else c"$stmt" + + if (idx != lastIdx) + c"$NewLine" + } + + case Assert(pred, Some(error)) => c"assert($pred); /* $error */" + case Assert(pred, None) => c"assert($pred);" + + case Var(id, _) => c"$id" + case DeclVar(Var(id, typ)) => c"$typ $id;" + + // If the length is a literal we don't need VLA + case DeclInitVar(Var(id, typ), ai @ ArrayInit(IntLiteral(length), _, _)) => + val buffer = FreshId("buffer") + val values = for (i <- 0 until length) yield ai.defaultValue + c"""|${ai.valueType} $buffer[${ai.length}] = { $values }; + |$typ $id = { .length = ${ai.length}, .data = $buffer }; + |""" + + // TODO depending on the type of array (e.g. `char`) or the value (e.g. `0`), we could use `memset`. + case DeclInitVar(Var(id, typ), ai: ArrayInit) => // Note that `typ` is a struct here + val buffer = FreshId("vla_buffer") + val i = FreshId("i") + c"""|${ai.valueType} $buffer[${ai.length}]; + |for (${Int32} $i = 0; $i < ${ai.length}; ++$i) { + | $buffer[$i] = ${ai.defaultValue}; + |} + |$typ $id = { .length = ${ai.length}, .data = $buffer }; + |""" + + case DeclInitVar(Var(id, typ), ai: ArrayInitWithValues) => // Note that `typ` is a struct here + val buffer = FreshId("buffer") + c"""|${ai.valueType} $buffer[${ai.length}] = { ${ai.values} }; + |$typ $id = { .length = ${ai.length}, .data = $buffer }; + |""" + + case DeclInitVar(Var(id, typ), value) => + c"$typ $id = $value;" + + case Assign(lhs, rhs) => + c"$lhs = $rhs;" + + case UnOp(op, rhs) => c"($op$rhs)" + case MultiOp(op, stmts) => c"""${nary(stmts, sep = s" ${op.fixMargin} ", + opening = "(", closing = ")")}""" + case SubscriptOp(ptr, idx) => c"$ptr[$idx]" + + case Break => c"break;" + case Return(stmt) => c"return $stmt;" + + case IfElse(cond, thn, elze) => + c"""|if ($cond) + |{ + | $thn + |} + |else + |{ + | $elze + |} + |""" + + case While(cond, body) => + c"""|while ($cond) + |{ + | $body + |} + |""" + + case AccessVar(id) => c"$id" + case AccessRef(id) => c"(*$id)" + case AccessAddr(id) => c"(&$id)" + case AccessField(struct, field) => c"$struct.$field" + case Call(id, args) => c"$id($args)" + + case StructInit(args, struct) => + c"(${struct.id}) { " + for ((id, stmt) <- args.init) { + c".$id = $stmt, " + } + if (!args.isEmpty) { + val (id, stmt) = args.last + c".$id = $stmt " + } + c"}" + + /* --------------------------------------------------------- Error ----- */ + case tree => sys.error(s"CPrinter: <<$tree>> was not handled properly") + } + + + def pp(wt: WrapperTree)(implicit ctx: PrinterContext): Unit = wt match { + case FunDecl(f) => + c"${FunSign(f)};$NewLine" + + case FunSign(Fun(id, retType, Nil, _)) => + c"""|$retType + |$id($Void)""" + + case FunSign(Fun(id, retType, params, _)) => + c"""|$retType + |$id(${nary(params map DeclParam)})""" + + case DeclParam(Var(id, typ)) => + c"$typ $id" + + case StructDecl(s) => + c"struct $s;" + + case StructDef(Struct(name, fields)) => + c"""|typedef struct $name { + | ${nary(fields map DeclParam, sep = ";\n", closing = ";")} + |} $name; + |""" + + case NewLine => + c"""| + |""" + } + + /** Hardcoded list of required include files from C standard library **/ + lazy val includes = "assert.h" :: "stdbool.h" :: "stdint.h" :: Nil + lazy val includeStmts = includes map { i => s"#include <$i>" } + + /** Wrappers to distinguish how the data should be printed **/ + sealed abstract class WrapperTree + case class FunDecl(f: Fun) extends WrapperTree + case class FunSign(f: Fun) extends WrapperTree + case class DeclParam(x: Var) extends WrapperTree + case class StructDecl(s: Struct) extends WrapperTree + case class StructDef(s: Struct) extends WrapperTree + case object NewLine extends WrapperTree +} + diff --git a/src/main/scala/leon/genc/CPrinterHelper.scala b/src/main/scala/leon/genc/CPrinterHelper.scala new file mode 100644 index 0000000000000000000000000000000000000000..5c32df8b7289b0ca5510d04680ab449e915a12dd --- /dev/null +++ b/src/main/scala/leon/genc/CPrinterHelper.scala @@ -0,0 +1,86 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package genc + +import CAST.Tree + +/* Printer helpers adapted to C code generation */ + +case class PrinterContext( + indent: Int, + printer: CPrinter +) + +object CPrinterHelpers { + implicit class Printable(val f: PrinterContext => Any) extends AnyVal { + def print(ctx: PrinterContext) = f(ctx) + } + + implicit class PrinterHelper(val sc: StringContext) extends AnyVal { + def c(args: Any*)(implicit ctx: PrinterContext): Unit = { + val printer = ctx.printer + import printer.WrapperTree + val sb = printer.sb + + val strings = sc.parts.iterator + val expressions = args.iterator + + var extraInd = 0 + var firstElem = true + + while(strings.hasNext) { + val s = strings.next.stripMargin + + // Compute indentation + val start = s.lastIndexOf('\n') + if(start >= 0 || firstElem) { + var i = start + 1 + while(i < s.length && s(i) == ' ') { + i += 1 + } + extraInd = (i - start - 1) / 2 + } + + firstElem = false + + // Make sure new lines are also indented + sb.append(s.replaceAll("\n", "\n" + (" " * ctx.indent))) + + val nctx = ctx.copy(indent = ctx.indent + extraInd) + + if (expressions.hasNext) { + val e = expressions.next + + e match { + case ts: Seq[Any] => + nary(ts).print(nctx) + + case t: Tree => + printer.pp(t)(nctx) + + case wt: WrapperTree => + printer.pp(wt)(nctx) + + case p: Printable => + p.print(nctx) + + case e => + sb.append(e.toString) + } + } + } + } + } + + def nary(ls: Seq[Any], sep: String = ", ", opening: String = "", closing: String = ""): Printable = { + val (o, c) = if(ls.isEmpty) ("", "") else (opening, closing) + val strs = o +: List.fill(ls.size-1)(sep) :+ c + + implicit pctx: PrinterContext => + new StringContext(strs: _*).c(ls: _*) + } + +} + + diff --git a/src/main/scala/leon/genc/GenerateCPhase.scala b/src/main/scala/leon/genc/GenerateCPhase.scala new file mode 100644 index 0000000000000000000000000000000000000000..c30d32274db7370a8f9af5616df3cbe505b0389e --- /dev/null +++ b/src/main/scala/leon/genc/GenerateCPhase.scala @@ -0,0 +1,20 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package genc + +import purescala.Definitions.Program + +object GenerateCPhase extends SimpleLeonPhase[Program, CAST.Prog] { + + val name = "Generate C" + val description = "Generate equivalent C code from Leon's AST" + + def apply(ctx: LeonContext, program: Program) = { + ctx.reporter.debug("Running code conversion phase: " + name)(utils.DebugSectionLeon) + val cprogram = new CConverter(ctx, program).convert + cprogram + } + +} + diff --git a/src/main/scala/leon/grammars/DepthBoundedGrammar.scala b/src/main/scala/leon/grammars/DepthBoundedGrammar.scala index 65708427e167de7647ae22a7e3df6abb6f83ede0..fc999be644bf2c4a7a20a73403cf7b1001bb9b68 100644 --- a/src/main/scala/leon/grammars/DepthBoundedGrammar.scala +++ b/src/main/scala/leon/grammars/DepthBoundedGrammar.scala @@ -3,11 +3,8 @@ package leon package grammars -import purescala.Types._ -import purescala.Expressions._ - -case class DepthBoundedGrammar[T](g: ExpressionGrammar[Label[T]], bound: Int) extends ExpressionGrammar[Label[T]] { - def computeProductions(l: Label[T])(implicit ctx: LeonContext): Seq[Gen] = g.computeProductions(l).flatMap { +case class DepthBoundedGrammar[T](g: ExpressionGrammar[NonTerminal[T]], bound: Int) extends ExpressionGrammar[NonTerminal[T]] { + def computeProductions(l: NonTerminal[T])(implicit ctx: LeonContext): Seq[Gen] = g.computeProductions(l).flatMap { case gen => if (l.depth == Some(bound) && gen.subTrees.nonEmpty) { Nil diff --git a/src/main/scala/leon/grammars/EmbeddedGrammar.scala b/src/main/scala/leon/grammars/EmbeddedGrammar.scala index bcee19b0118b896468994acbfe80a497f02012fd..8dcbc6ec10f9aa42895e5f876cdd4d72479de229 100644 --- a/src/main/scala/leon/grammars/EmbeddedGrammar.scala +++ b/src/main/scala/leon/grammars/EmbeddedGrammar.scala @@ -12,7 +12,7 @@ import purescala.Constructors._ * * We rely on a bijection between Li and Lo labels */ -case class EmbeddedGrammar[Ti <% Typed, To <% Typed](innerGrammar: ExpressionGrammar[Ti], iToo: Ti => To, oToi: To => Ti) extends ExpressionGrammar[To] { +case class EmbeddedGrammar[Ti <: Typed, To <: Typed](innerGrammar: ExpressionGrammar[Ti], iToo: Ti => To, oToi: To => Ti) extends ExpressionGrammar[To] { def computeProductions(t: To)(implicit ctx: LeonContext): Seq[Gen] = { innerGrammar.computeProductions(oToi(t)).map { innerGen => nonTerminal(innerGen.subTrees.map(iToo), innerGen.builder) diff --git a/src/main/scala/leon/grammars/Empty.scala b/src/main/scala/leon/grammars/Empty.scala index 56b332309329acb8017ccbb8bdb9f71711e22eb2..70ebddc98f21fc872aef8635fe36de7e9ba9bbce 100644 --- a/src/main/scala/leon/grammars/Empty.scala +++ b/src/main/scala/leon/grammars/Empty.scala @@ -3,8 +3,8 @@ package leon package grammars -import purescala.Types._ +import purescala.Types.Typed -case class Empty[T <% Typed]() extends ExpressionGrammar[T] { +case class Empty[T <: Typed]() extends ExpressionGrammar[T] { def computeProductions(t: T)(implicit ctx: LeonContext): Seq[Gen] = Nil } diff --git a/src/main/scala/leon/grammars/ExpressionGrammar.scala b/src/main/scala/leon/grammars/ExpressionGrammar.scala index 465904f5db3b4e7289f8ce0b8a13aa86798bf72e..ac394ab840bddf0d498080a04e447ce66de07caa 100644 --- a/src/main/scala/leon/grammars/ExpressionGrammar.scala +++ b/src/main/scala/leon/grammars/ExpressionGrammar.scala @@ -3,15 +3,13 @@ package leon package grammars -import bonsai._ - import purescala.Expressions._ import purescala.Types._ import purescala.Common._ import scala.collection.mutable.{HashMap => MutableMap} -abstract class ExpressionGrammar[T <% Typed] { +abstract class ExpressionGrammar[T <: Typed] { type Gen = Generator[T, Expr] private[this] val cache = new MutableMap[T, Seq[Gen]]() diff --git a/src/main/scala/leon/grammars/Generator.scala b/src/main/scala/leon/grammars/Generator.scala new file mode 100644 index 0000000000000000000000000000000000000000..18d132e2c25ea222324dc05809220f12d0fb7100 --- /dev/null +++ b/src/main/scala/leon/grammars/Generator.scala @@ -0,0 +1,16 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package grammars + +import bonsai.{Generator => Gen} + +object GrammarTag extends Enumeration { + val Top = Value +} +import GrammarTag._ + +class Generator[T, R](subTrees: Seq[T], builder: Seq[R] => R, tag: Value) extends Gen[T,R](subTrees, builder) +object Generator { + def apply[T, R](subTrees: Seq[T], builder: Seq[R] => R, tag: Value = Top) = new Generator(subTrees, builder, tag) +} \ No newline at end of file diff --git a/src/main/scala/leon/grammars/Grammars.scala b/src/main/scala/leon/grammars/Grammars.scala index 2194aa539afe94dc06182b5acaf12bb669de0612..23b1dd5a14cfeb82dd4555832e777597615b337e 100644 --- a/src/main/scala/leon/grammars/Grammars.scala +++ b/src/main/scala/leon/grammars/Grammars.scala @@ -24,7 +24,7 @@ object Grammars { default(sctx.program, p.as.map(_.toVariable), sctx.functionContext, sctx.settings.functionsToIgnore, p.ws, p.pc) } - def typeDepthBound[T <% Typed](g: ExpressionGrammar[T], b: Int) = { + def typeDepthBound[T <: Typed](g: ExpressionGrammar[T], b: Int) = { g.filter(g => g.subTrees.forall(t => typeDepth(t.getType) <= b)) } } diff --git a/src/main/scala/leon/grammars/Label.scala b/src/main/scala/leon/grammars/NonTerminal.scala similarity index 58% rename from src/main/scala/leon/grammars/Label.scala rename to src/main/scala/leon/grammars/NonTerminal.scala index 456f184e7edb0e2f28eb9cb5b8830b77bf336aac..7492ffac5c17df326084f846857c6ac3bebe1775 100644 --- a/src/main/scala/leon/grammars/Label.scala +++ b/src/main/scala/leon/grammars/NonTerminal.scala @@ -1,9 +1,11 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + package leon package grammars import purescala.Types._ -case class Label[T](t: TypeTree, l: T, depth: Option[Int] = None) extends Typed { +case class NonTerminal[T](t: TypeTree, l: T, depth: Option[Int] = None) extends Typed { val getType = t override def asString(implicit ctx: LeonContext) = t.asString+"#"+l+depth.map(d => "@"+d).getOrElse("") diff --git a/src/main/scala/leon/grammars/Or.scala b/src/main/scala/leon/grammars/Or.scala index ae0bc96976f1a4d2d349a72060e391686b4cab52..e691a245984eaeb11277b9278505b49cf623fed3 100644 --- a/src/main/scala/leon/grammars/Or.scala +++ b/src/main/scala/leon/grammars/Or.scala @@ -5,7 +5,7 @@ package grammars import purescala.Types._ -case class Union[T <% Typed](gs: Seq[ExpressionGrammar[T]]) extends ExpressionGrammar[T] { +case class Union[T <: Typed](gs: Seq[ExpressionGrammar[T]]) extends ExpressionGrammar[T] { val subGrammars: Seq[ExpressionGrammar[T]] = gs.flatMap { case u: Union[T] => u.subGrammars case g => Seq(g) diff --git a/src/main/scala/leon/grammars/SimilarTo.scala b/src/main/scala/leon/grammars/SimilarTo.scala index c134546d2af88039d2ef5f9a9a030e331ae80161..77e912792965d860fc934eb016370c8f2b57fd8f 100644 --- a/src/main/scala/leon/grammars/SimilarTo.scala +++ b/src/main/scala/leon/grammars/SimilarTo.scala @@ -13,7 +13,7 @@ import purescala.Expressions._ import synthesis._ -case class SimilarTo(e: Expr, terminals: Set[Expr] = Set(), sctx: SynthesisContext, p: Problem) extends ExpressionGrammar[Label[String]] { +case class SimilarTo(e: Expr, terminals: Set[Expr] = Set(), sctx: SynthesisContext, p: Problem) extends ExpressionGrammar[NonTerminal[String]] { val excludeFCalls = sctx.settings.functionsToIgnore @@ -23,11 +23,11 @@ case class SimilarTo(e: Expr, terminals: Set[Expr] = Set(), sctx: SynthesisConte OneOf(terminals.toSeq :+ e) || FunctionCalls(sctx.program, sctx.functionContext, p.as.map(_.getType), excludeFCalls) || SafeRecursiveCalls(sctx.program, p.ws, p.pc), - { (t: TypeTree) => Label(t, "B", None)}, - { (l: Label[String]) => l.getType } + { (t: TypeTree) => NonTerminal(t, "B", None)}, + { (l: NonTerminal[String]) => l.getType } ), 1) - type L = Label[String] + type L = NonTerminal[String] val getNext: () => Int = { var counter = -1 @@ -41,7 +41,7 @@ case class SimilarTo(e: Expr, terminals: Set[Expr] = Set(), sctx: SynthesisConte def computeProductions(t: L)(implicit ctx: LeonContext): Seq[Gen] = { t match { - case Label(_, "B", _) => normalGrammar.computeProductions(t) + case NonTerminal(_, "B", _) => normalGrammar.computeProductions(t) case _ => val allSimilar = similarCache.getOrElse { @@ -59,7 +59,7 @@ case class SimilarTo(e: Expr, terminals: Set[Expr] = Set(), sctx: SynthesisConte def getLabel(t: TypeTree) = { val tpe = bestRealType(t) val c = getNext() - Label(tpe, "G"+c) + NonTerminal(tpe, "G"+c) } def isCommutative(e: Expr) = e match { diff --git a/src/main/scala/leon/grammars/SizeBoundedGrammar.scala b/src/main/scala/leon/grammars/SizeBoundedGrammar.scala index 8756b136abf0f6bbffe6721bb3439aebc76a0a6b..1b25e30f61aa74598feb255366fe10a153bc9e30 100644 --- a/src/main/scala/leon/grammars/SizeBoundedGrammar.scala +++ b/src/main/scala/leon/grammars/SizeBoundedGrammar.scala @@ -6,13 +6,13 @@ package grammars import purescala.Types._ import leon.utils.SeqUtils.sumTo -case class SizedLabel[T <% Typed](underlying: T, size: Int) extends Typed { +case class SizedLabel[T <: Typed](underlying: T, size: Int) extends Typed { val getType = underlying.getType override def asString(implicit ctx: LeonContext) = underlying.asString+"|"+size+"|" } -case class SizeBoundedGrammar[T <% Typed](g: ExpressionGrammar[T]) extends ExpressionGrammar[SizedLabel[T]] { +case class SizeBoundedGrammar[T <: Typed](g: ExpressionGrammar[T]) extends ExpressionGrammar[SizedLabel[T]] { def computeProductions(sl: SizedLabel[T])(implicit ctx: LeonContext): Seq[Gen] = { if (sl.size <= 0) { Nil diff --git a/src/main/scala/leon/grammars/ValueGrammar.scala b/src/main/scala/leon/grammars/ValueGrammar.scala index 5442b4ee7723fb124aab8aea65647ed762a9f0ec..98850c8df4adcf3e776970c176cf37c251823917 100644 --- a/src/main/scala/leon/grammars/ValueGrammar.scala +++ b/src/main/scala/leon/grammars/ValueGrammar.scala @@ -25,6 +25,13 @@ case object ValueGrammar extends ExpressionGrammar[TypeTree] { terminal(InfiniteIntegerLiteral(1)), terminal(InfiniteIntegerLiteral(5)) ) + case StringType => + List( + terminal(StringLiteral("")), + terminal(StringLiteral("a")), + terminal(StringLiteral("foo")), + terminal(StringLiteral("bar")) + ) case tp: TypeParameter => for (ind <- (1 to 3).toList) yield { diff --git a/src/main/scala/leon/invariant/engine/UnfoldingTemplateSolver.scala b/src/main/scala/leon/invariant/engine/UnfoldingTemplateSolver.scala index 48e4547b22b41890d83a8e4e75cc8dcdb8072f72..6ae355f4c499f79a15322442b0399eb98a80df33 100644 --- a/src/main/scala/leon/invariant/engine/UnfoldingTemplateSolver.scala +++ b/src/main/scala/leon/invariant/engine/UnfoldingTemplateSolver.scala @@ -42,7 +42,7 @@ class UnfoldingTemplateSolver(ctx: InferenceContext, program: Program, rootFd: F def constructVC(funDef: FunDef): (Expr, Expr) = { val body = funDef.body.get - val Lambda(Seq(ValDef(resid, _)), _) = funDef.postcondition.get + val Lambda(Seq(ValDef(resid)), _) = funDef.postcondition.get val resvar = resid.toVariable val simpBody = matchToIfThenElse(body) @@ -220,7 +220,7 @@ class UnfoldingTemplateSolver(ctx: InferenceContext, program: Program, rootFd: F val resvar = FreshIdentifier("res", fd.returnType, true) // FIXME: Is this correct (ResultVariable(fd.returnType) -> resvar.toVariable)) val ninv = replace(Map(ResultVariable(fd.returnType) -> resvar.toVariable), inv) - Some(Lambda(Seq(ValDef(resvar, Some(fd.returnType))), ninv)) + Some(Lambda(Seq(ValDef(resvar)), ninv)) } } else if (fd.postcondition.isDefined) { val Lambda(resultBinder, _) = fd.postcondition.get diff --git a/src/main/scala/leon/invariant/templateSolvers/NLTemplateSolver.scala b/src/main/scala/leon/invariant/templateSolvers/NLTemplateSolver.scala index 1194695b928accbfdfb40d79ff35eb165fedbebd..d4d72a507b3f204170cee0e99b9522dc7e1ffc66 100644 --- a/src/main/scala/leon/invariant/templateSolvers/NLTemplateSolver.scala +++ b/src/main/scala/leon/invariant/templateSolvers/NLTemplateSolver.scala @@ -59,8 +59,8 @@ class NLTemplateSolver(ctx: InferenceContext, program: Program, private val farkasSolver = new FarkasLemmaSolver(ctx, program) private val startFromEarlierModel = true private val disableCegis = true - private val useIncrementalSolvingForVCs = false - private val useCVCToCheckVCs = true + private val useIncrementalSolvingForVCs = true + private val useCVCToCheckVCs = false private val usePortfolio = false // portfolio has a bug in incremental solving //this is private mutable state used by initialized during every call to 'solve' and used by 'solveUNSAT' diff --git a/src/main/scala/leon/invariant/util/TreeUtil.scala b/src/main/scala/leon/invariant/util/TreeUtil.scala index e73d4871b07a59c0f0be0c7a53b4c6be9c9bf7f5..fa7edc9ff183c501b1a7c2b73548adac7558bd8a 100644 --- a/src/main/scala/leon/invariant/util/TreeUtil.scala +++ b/src/main/scala/leon/invariant/util/TreeUtil.scala @@ -77,11 +77,11 @@ object ProgramUtil { } res } - + def createTemplateFun(plainTemp: Expr): FunctionInvocation = { val tmpl = Lambda(getTemplateIds(plainTemp).toSeq.map(id => ValDef(id)), plainTemp) - val tmplFd = new FunDef(FreshIdentifier("tmpl", FunctionType(Seq(tmpl.getType), BooleanType), false), Seq(), Seq(ValDef(FreshIdentifier("arg", tmpl.getType), - Some(tmpl.getType))), BooleanType) + val tmplFd = new FunDef(FreshIdentifier("tmpl", FunctionType(Seq(tmpl.getType), BooleanType), false), Seq(), + Seq(ValDef(FreshIdentifier("arg", tmpl.getType))), BooleanType) tmplFd.body = Some(BooleanLiteral(true)) FunctionInvocation(TypedFunDef(tmplFd, Seq()), Seq(tmpl)) } @@ -120,7 +120,7 @@ object ProgramUtil { * will be removed */ def assignTemplateAndCojoinPost(funToTmpl: Map[FunDef, Expr], prog: Program, - funToPost: Map[FunDef, Expr] = Map(), uniqueIdDisplay: Boolean = true): Program = { + funToPost: Map[FunDef, Expr] = Map(), uniqueIdDisplay : Boolean = true): Program = { val funMap = functionsWOFields(prog.definedFunctions).foldLeft(Map[FunDef, FunDef]()) { case (accMap, fd) if fd.isTheoryOperation => @@ -256,7 +256,7 @@ object ProgramUtil { funDef.fullBody match { case Ensuring(_, post) => { post match { - case Lambda(Seq(ValDef(fromRes, _)), _) => Some(fromRes) + case Lambda(Seq(ValDef(fromRes)), _) => Some(fromRes) } } case _ => None @@ -273,7 +273,6 @@ object ProgramUtil { } object PredicateUtil { - /** * Returns a constructor for the let* and also the current * body of let* @@ -402,8 +401,8 @@ object PredicateUtil { def isADTConstructor(e: Expr): Boolean = e match { case Equals(Variable(_), CaseClass(_, _)) => true - case Equals(Variable(_), Tuple(_)) => true - case _ => false + case Equals(Variable(_), Tuple(_)) => true + case _ => false } def isMultFunctions(fd: FunDef) = { @@ -424,24 +423,24 @@ object PredicateUtil { def createAnd(exprs: Seq[Expr]): Expr = { val newExprs = exprs.filterNot(conj => conj == tru) newExprs match { - case Seq() => tru + case Seq() => tru case Seq(e) => e - case _ => And(newExprs) + case _ => And(newExprs) } } def createOr(exprs: Seq[Expr]): Expr = { val newExprs = exprs.filterNot(disj => disj == fls) newExprs match { - case Seq() => fls + case Seq() => fls case Seq(e) => e - case _ => Or(newExprs) + case _ => Or(newExprs) } } def isNumericType(t: TypeTree) = t match { case IntegerType | RealType => true - case _ => false + case _ => false } def precOrTrue(fd: FunDef): Expr = fd.precondition match { diff --git a/src/main/scala/leon/laziness/LazinessEliminationPhase.scala b/src/main/scala/leon/laziness/LazinessEliminationPhase.scala index ef1a5415e44ddb9b3b728db4c02f9deec2822a5b..929ee38e2f5c9d3712f48a146d730f0f62f65fbd 100644 --- a/src/main/scala/leon/laziness/LazinessEliminationPhase.scala +++ b/src/main/scala/leon/laziness/LazinessEliminationPhase.scala @@ -51,7 +51,7 @@ object LazinessEliminationPhase extends TransformationPhase { val dumpProgWOInstSpecs = true val dumpInstrumentedProgram = true val debugSolvers = false - val skipStateVerification = true + val skipStateVerification = false val skipResourceVerification = false val name = "Laziness Elimination Phase" @@ -144,7 +144,7 @@ object LazinessEliminationPhase extends TransformationPhase { def failOnClosures(argMem: Boolean, e: Expr) = e match { case finv: FunctionInvocation if isLazyInvocation(finv)(p) => finv match { - case FunctionInvocation(_, Seq(FunctionInvocation(callee, _))) if isMemoized(callee.fd) => argMem + case FunctionInvocation(_, Seq(Lambda(_, FunctionInvocation(callee, _)))) if isMemoized(callee.fd) => argMem case _ => !argMem } case _ => false @@ -162,7 +162,7 @@ object LazinessEliminationPhase extends TransformationPhase { false } else { def nestedSusp(e: Expr) = e match { - case finv @ FunctionInvocation(_, Seq(call: FunctionInvocation)) if isLazyInvocation(finv)(p) && isLazyInvocation(call)(p) => true + case finv @ FunctionInvocation(_, Seq(Lambda(_, call: FunctionInvocation))) if isLazyInvocation(finv)(p) && isLazyInvocation(call)(p) => true case _ => false } val nestedCheckFailed = exists(nestedSusp)(fd.body.getOrElse(Util.tru)) diff --git a/src/main/scala/leon/laziness/LazyClosureConverter.scala b/src/main/scala/leon/laziness/LazyClosureConverter.scala index 404f968f6102006ea8bfe0f06ccb814b5fc2eb2b..00d2bbaa88cb979f8f610600c0f4fcc12b60f04d 100644 --- a/src/main/scala/leon/laziness/LazyClosureConverter.scala +++ b/src/main/scala/leon/laziness/LazyClosureConverter.scala @@ -37,8 +37,8 @@ import purescala.TypeOps._ * (e) replace accesses to $.value with calls to dispatch with current state */ class LazyClosureConverter(p: Program, ctx: LeonContext, - closureFactory: LazyClosureFactory, - funsManager : LazyFunctionsManager) { + closureFactory: LazyClosureFactory, + funsManager: LazyFunctionsManager) { val debug = false // flags //val removeRecursionViaEval = false @@ -50,11 +50,24 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, val lazyTnames = closureFactory.lazyTypeNames val lazyops = closureFactory.lazyops + /** + * Note this method has side-effects + */ + var idmap = Map[Identifier, Identifier]() + def makeIdOfType(oldId: Identifier, tpe: TypeTree): Identifier = { + if (oldId.getType != tpe) { + val freshid = FreshIdentifier(oldId.name, tpe, true) + idmap += (oldId -> freshid) + freshid + } else oldId + } + val funMap = p.definedFunctions.collect { case fd if (fd.hasBody && !fd.isLibrary) => // replace lazy types in parameters and return values val nparams = fd.params map { vd => - ValDef(vd.id, Some(replaceLazyTypes(vd.getType))) // override type of identifier + val nparam = makeIdOfType(vd.id, replaceLazyTypes(vd.getType)) + ValDef(nparam) } val nretType = replaceLazyTypes(fd.returnType) val stparams = @@ -77,7 +90,7 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, // body of these functions are defined later } else { new FunDef(FreshIdentifier(fd.id.name), fd.tparams ++ (stparams map TypeParameterDef), - nparams, nretType) + nparams, nretType) } // copy annotations fd.flags.foreach(nfd.addFlag(_)) @@ -94,13 +107,13 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, //TODO: Optimization: we can omit come functions whose translations will not be recursive. def takesStateButIndep(fd: FunDef) = - funsNeedStates(fd) && funsManager.hasStateIndependentBehavior(fd) + funsNeedStates(fd) && funsManager.hasStateIndependentBehavior(fd) /** * A set of uninterpreted functions that are used * in specs to ensure that value part is independent of the state */ - val uiFuncs : Map[FunDef, (FunDef, Option[FunDef])] = funMap.collect { + val uiFuncs: Map[FunDef, (FunDef, Option[FunDef])] = funMap.collect { case (k, v) if takesStateButIndep(k) => val params = v.params.take(k.params.size) // ignore the state params val retType = @@ -109,26 +122,26 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, valtype } else v.returnType val tparams = (params.map(_.getType) :+ retType).flatMap(getTypeParameters(_)).distinct - val tparamsDef = tparams.map(TypeParameterDef(_)) + val tparamsDef = tparams.map(TypeParameterDef(_)) val ufd = new FunDef(FreshIdentifier(v.id.name + "UI"), tparamsDef, params, retType) // we also need to create a function that assumes that the result equals // the uninterpreted function val valres = ValDef(FreshIdentifier("valres", retType)) val pred = new FunDef(FreshIdentifier(v.id.name + "ValPred"), tparamsDef, - params :+ valres, BooleanType) + params :+ valres, BooleanType) val resid = FreshIdentifier("res", pred.returnType) pred.fullBody = Ensuring( - Equals(valres.id.toVariable, - FunctionInvocation(TypedFunDef(ufd, tparams), params.map(_.id.toVariable))), // res = funUI(..) - Lambda(Seq(ValDef(resid)), resid.toVariable)) // holds + Equals(valres.id.toVariable, + FunctionInvocation(TypedFunDef(ufd, tparams), params.map(_.id.toVariable))), // res = funUI(..) + Lambda(Seq(ValDef(resid)), resid.toVariable)) // holds pred.addFlag(Annotation("axiom", Seq())) // mark it as @library (k -> (ufd, Some(pred))) case (k, v) if lazyops(k) => // here 'v' cannot for sure take state params, otherwise it must be state indep - if(funsNeedStates(k)) - throw new IllegalStateException("Lazyop that has a state dependent behavior: "+k) + if (funsNeedStates(k)) + throw new IllegalStateException("Lazyop that has a state dependent behavior: " + k) else { val tparams = (v.params.map(_.getType) :+ v.returnType).flatMap(getTypeParameters(_)).distinct val tparamsDef = tparams.map(TypeParameterDef(_)) @@ -299,7 +312,7 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, def mapExpr(expr: Expr)(implicit stTparams: Seq[TypeParameter]): (Option[Expr] => Expr, Boolean) = expr match { - case finv @ FunctionInvocation(_, Seq(FunctionInvocation(TypedFunDef(argfd, tparams), args))) // lazy construction ? + case finv @ FunctionInvocation(_, Seq(Lambda(_, FunctionInvocation(TypedFunDef(argfd, tparams), args)))) // lazy construction ? if isLazyInvocation(finv)(p) => val op = (nargs: Seq[Expr]) => ((st: Option[Expr]) => { val adt = closureFactory.closureOfLazyOp(argfd) @@ -331,7 +344,7 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, }, false) mapNAryOperator(args, op) - case finv @ FunctionInvocation(_, Seq(arg)) if isEagerInvocation(finv)(p) => + case finv @ FunctionInvocation(_, Seq(Lambda(_, arg))) if isEagerInvocation(finv)(p) => // here arg is guaranteed to be a variable ((st: Option[Expr]) => { val rootType = bestRealType(arg.getType) @@ -387,16 +400,16 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, }, false) case finv @ FunctionInvocation(_, Seq(recvr, stArgs @ _*)) if isWithStateFun(finv)(p) => - // recvr is a `WithStateCaseClass` and `stArgs` could be arbitrary expressions that return values of types of fileds of state + // recvr is a `WithStateCaseClass` and `stArgs` could be arbitrary expressions that return values of types of fields of state val numStates = closureFactory.state.fields.size - if(stArgs.size != numStates) - throw new IllegalStateException("The arguments to `withState` should equal the number of states: "+numStates) + if (stArgs.size != numStates) + throw new IllegalStateException("The arguments to `withState` should equal the number of states: " + numStates) val CaseClass(_, Seq(exprNeedingState)) = recvr val (nexprCons, exprReturnsState) = mapExpr(exprNeedingState) val nstConses = stArgs map mapExpr - if(nstConses.exists(_._2)) // any 'stArg' returning state - throw new IllegalStateException("One of the arguments to `withState` returns a new state, which is not supported: "+finv) + if (nstConses.exists(_._2)) // any 'stArg' returning state + throw new IllegalStateException("One of the arguments to `withState` returns a new state, which is not supported: " + finv) else { ((st: Option[Expr]) => { // create a new state using the nstConses @@ -404,16 +417,6 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, val tparams = nstSets.flatMap(nst => getTypeParameters(nst.getType)).distinct val nst = CaseClass(CaseClassType(closureFactory.state, tparams), nstSets) nexprCons(Some(nst)) - /* // compute the baseType - stArg.getType match { - case SetType(lazyType) => // note that stArg would still have the set type - val baseType = unwrapLazyType(lazyType).get - val tname = typeNameWOParams(baseType) - val newStates = st + (tname -> nst) - nexpr(newStates) - case t => - throw new IllegalStateException(s"$stArg should have a set type, current type: "+t) - }*/ }, exprReturnsState) } @@ -536,6 +539,13 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, mapNAryOperator(args, (nargs: Seq[Expr]) => ((st: Option[Expr]) => CaseClass(ntype, nargs), false)) + // need to reset field ids of case class select + case CaseClassSelector(cct, clExpr, fieldId) if fieldMap.contains(fieldId) => + val ntype = replaceLazyTypes(cct).asInstanceOf[CaseClassType] + val nfield = fieldMap(fieldId) + mapNAryOperator(Seq(clExpr), + (nargs: Seq[Expr]) => ((st: Option[Expr]) => CaseClassSelector(ntype, nargs.head, nfield), false)) + case Operator(args, op) => // here, 'op' itself does not create a new state mapNAryOperator(args, @@ -577,115 +587,119 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, } } - def assignBodiesToFunctions = funMap foreach { - case (fd, nfd) => - //println("Considering function: "+fd) - // Here, using name to identify 'state' parameters - val stateParam = nfd.params.collectFirst { - case vd if isStateParam(vd.id) => - vd.id.toVariable - } - val stType = stateParam.map(_.getType.asInstanceOf[CaseClassType]) - // Note: stTparams may be provided even if stParam is not required. - val stTparams = nfd.tparams.collect{ - case tpd if isPlaceHolderTParam(tpd.tp) => tpd.tp - } - val (nbodyFun, bodyUpdatesState) = mapExpr(fd.body.get)(stTparams) - val nbody = nbodyFun(stateParam) - val bodyWithState = - if (!bodyUpdatesState && funsRetStates(fd)) - Tuple(Seq(nbody, stateParam.get)) - else - nbody - nfd.body = Some(simplifyLets(bodyWithState)) - //println(s"Body of ${fd.id.name} after conversion&simp: ${nfd.body}") - - // Important: specifications use lazy semantics but - // their state changes are ignored after their execution. - // This guarantees their observational purity/transparency - // collect class invariants that need to be added - if (fd.hasPrecondition) { - val (npreFun, preUpdatesState) = mapExpr(fd.precondition.get)(stTparams) - nfd.precondition = - if (preUpdatesState) - Some(TupleSelect(npreFun(stateParam), 1)) // ignore state updated by pre - else Some(npreFun(stateParam)) - } + def assignBodiesToFunctions = { + val paramMap: Map[Expr, Expr] = idmap.map(e => (e._1.toVariable -> e._2.toVariable)) + funMap foreach { + case (fd, nfd) => + //println("Considering function: "+fd) + // Here, using name to identify 'state' parameters + val stateParam = nfd.params.collectFirst { + case vd if isStateParam(vd.id) => + vd.id.toVariable + } + val stType = stateParam.map(_.getType.asInstanceOf[CaseClassType]) + // Note: stTparams may be provided even if stParam is not required. + val stTparams = nfd.tparams.collect { + case tpd if isPlaceHolderTParam(tpd.tp) => tpd.tp + } + val (nbodyFun, bodyUpdatesState) = mapExpr(fd.body.get)(stTparams) + val nbody = nbodyFun(stateParam) + val bodyWithState = + if (!bodyUpdatesState && funsRetStates(fd)) + Tuple(Seq(nbody, stateParam.get)) + else + nbody + nfd.body = Some(simplifyLets(replace(paramMap, bodyWithState))) + //println(s"Body of ${fd.id.name} after conversion&simp: ${nfd.body}") + + // Important: specifications use lazy semantics but + // their state changes are ignored after their execution. + // This guarantees their observational purity/transparency + // collect class invariants that need to be added + if (fd.hasPrecondition) { + val (npreFun, preUpdatesState) = mapExpr(fd.precondition.get)(stTparams) + val npre = replace(paramMap, npreFun(stateParam)) + nfd.precondition = + if (preUpdatesState) + Some(TupleSelect(npre, 1)) // ignore state updated by pre + else Some(npre) + } - // create a new result variable - val newres = - if (fd.hasPostcondition) { - val Lambda(Seq(ValDef(r, _)), _) = fd.postcondition.get - FreshIdentifier(r.name, bodyWithState.getType) - } else FreshIdentifier("r", nfd.returnType) - - // create an output state map - val outState = - if (bodyUpdatesState || funsRetStates(fd)) { - Some(TupleSelect(newres.toVariable, 2)) - } else - stateParam - - // create a specification that relates input-output states - val stateRel = - if (funsRetStates(fd)) { // add specs on states - val instates = fieldsOfState(stateParam.get, stType.get) - val outstates = fieldsOfState(outState.get, stType.get) - val stateRel = - if(fd.annotations.contains("invstate")) Equals.apply _ - else SubsetOf.apply _ - Some(createAnd((instates zip outstates).map(p => stateRel(p._1, p._2)))) - } else None - //println("stateRel: "+stateRel) - - // create a predicate that ensures that the value part is independent of the state - val valRel = - if (takesStateButIndep(fd)) { // add specs on value - val uipred = uiFuncs(fd)._2.get - val args = nfd.params.take(fd.params.size).map(_.id.toVariable) - val retarg = - if(funsRetStates(fd)) - TupleSelect(newres.toVariable, 1) + // create a new result variable + val newres = + if (fd.hasPostcondition) { + val Lambda(Seq(ValDef(r)), _) = fd.postcondition.get + FreshIdentifier(r.name, bodyWithState.getType) + } else FreshIdentifier("r", nfd.returnType) + + // create an output state map + val outState = + if (bodyUpdatesState || funsRetStates(fd)) { + Some(TupleSelect(newres.toVariable, 2)) + } else + stateParam + + // create a specification that relates input-output states + val stateRel = + if (funsRetStates(fd)) { // add specs on states + val instates = fieldsOfState(stateParam.get, stType.get) + val outstates = fieldsOfState(outState.get, stType.get) + val stateRel = + if (fd.annotations.contains("invstate")) Equals.apply _ + else SubsetOf.apply _ + Some(createAnd((instates zip outstates).map(p => stateRel(p._1, p._2)))) + } else None + //println("stateRel: "+stateRel) + + // create a predicate that ensures that the value part is independent of the state + val valRel = + if (takesStateButIndep(fd)) { // add specs on value + val uipred = uiFuncs(fd)._2.get + val args = nfd.params.take(fd.params.size).map(_.id.toVariable) + val retarg = + if (funsRetStates(fd)) + TupleSelect(newres.toVariable, 1) else newres.toVariable - Some(FunctionInvocation(TypedFunDef(uipred, nfd.tparams.map(_.tp)), + Some(FunctionInvocation(TypedFunDef(uipred, nfd.tparams.map(_.tp)), args :+ retarg)) - } else None - - val targetPost = - if (fd.hasPostcondition) { - val Lambda(Seq(ValDef(resid, _)), post) = fd.postcondition.get - // bind calls to instate and outstate calls to their respective values - val tpost = simplePostTransform { - case e if LazinessUtil.isInStateCall(e)(p) => - val baseType = getTypeArguments(e.getType).head - val tname = typeNameWOParams(baseType) - closureFactory.selectFieldOfState(tname, stateParam.get, stType.get) - - case e if LazinessUtil.isOutStateCall(e)(p) => - val baseType = getTypeArguments(e.getType).head - val tname = typeNameWOParams(baseType) - closureFactory.selectFieldOfState(tname, outState.get, stType.get) - - case e => e - }(post) - // thread state through postcondition - val (npostFun, postUpdatesState) = mapExpr(tpost)(stTparams) - val resval = - if (bodyUpdatesState || funsRetStates(fd)) - TupleSelect(newres.toVariable, 1) - else newres.toVariable - val npostWithState = replace(Map(resid.toVariable -> resval), npostFun(outState)) - val npost = - if (postUpdatesState) { - TupleSelect(npostWithState, 1) // ignore state updated by post - } else - npostWithState - Some(npost) - } else { - None - } - nfd.postcondition = Some(Lambda(Seq(ValDef(newres)), + } else None + + val targetPost = + if (fd.hasPostcondition) { + val Lambda(Seq(ValDef(resid)), post) = fd.postcondition.get + val resval = + if (bodyUpdatesState || funsRetStates(fd)) + TupleSelect(newres.toVariable, 1) + else newres.toVariable + // thread state through postcondition + val (npostFun, postUpdatesState) = mapExpr(post)(stTparams) + // bind calls to instate and outstate calls to their respective values + val tpost = simplePostTransform { + case e if LazinessUtil.isInStateCall(e)(p) => + val baseType = getTypeArguments(e.getType).head + val tname = typeNameWOParams(baseType) + closureFactory.selectFieldOfState(tname, stateParam.get, stType.get) + + case e if LazinessUtil.isOutStateCall(e)(p) => + val baseType = getTypeArguments(e.getType).head + val tname = typeNameWOParams(baseType) + closureFactory.selectFieldOfState(tname, outState.get, stType.get) + + case e => e + }(replace(paramMap ++ Map(resid.toVariable -> resval), npostFun(outState))) + + val npost = + if (postUpdatesState) { + TupleSelect(tpost, 1) // ignore state updated by post + } else + tpost + Some(npost) + } else { + None + } + nfd.postcondition = Some(Lambda(Seq(ValDef(newres)), createAnd(stateRel.toList ++ valRel.toList ++ targetPost.toList))) + } } def assignContractsForEvals = evalFunctions.foreach { @@ -702,7 +716,7 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, val binder = FreshIdentifier("t", ctype) val pattern = InstanceOfPattern(Some(binder), ctype) // t.clres == res._1 - val clause1 = if(!ismem) { + val clause1 = if (!ismem) { val clresField = cdef.fields.last Equals(TupleSelect(postres.toVariable, 1), CaseClassSelector(ctype, binder.toVariable, clresField.id)) @@ -731,10 +745,19 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, /** * Overrides the types of the lazy fields in the case class definitions + * Note: here we reset CaseClass fields instead of having to duplicate the + * entire class hierarchy. */ + var fieldMap = Map[Identifier, Identifier]() + def copyField(oldId: Identifier, tpe: TypeTree): Identifier = { + val freshid = FreshIdentifier(oldId.name, tpe) + fieldMap += (oldId -> freshid) + freshid + } + def transformCaseClasses = p.definedClasses.foreach { - case ccd @ CaseClassDef(id, tparamDefs, superClass, isCaseObj) - if !ccd.flags.contains(Annotation("library", Seq())) => + case ccd @ CaseClassDef(id, tparamDefs, superClass, isCaseObj) if !ccd.flags.contains(Annotation("library", Seq())) && + ccd.fields.exists(vd => isLazyType(vd.getType)) => val nfields = ccd.fields.map { fld => unwrapLazyType(fld.getType) match { case None => fld @@ -743,7 +766,7 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, val typeArgs = getTypeArguments(btype) //println(s"AbsType: $clType type args: $typeArgs") val adtType = AbstractClassType(clType, typeArgs) - ValDef(fld.id, Some(adtType)) // overriding the field type + ValDef(copyField(fld.id, adtType)) } } ccd.setFields(nfields) @@ -771,8 +794,8 @@ class LazyClosureConverter(p: Program, ctx: LeonContext, case d => Seq(d) }), closureFactory.allClosuresAndParents ++ Seq(closureFactory.state) ++ - closureCons.values ++ evalFunctions.values ++ - computeFunctions.values ++ uiStateFuns.values ++ - closureFactory.stateUpdateFuns.values, anchor) + closureCons.values ++ evalFunctions.values ++ + computeFunctions.values ++ uiStateFuns.values ++ + closureFactory.stateUpdateFuns.values, anchor) } } diff --git a/src/main/scala/leon/laziness/LazyClosureFactory.scala b/src/main/scala/leon/laziness/LazyClosureFactory.scala index 9211191548e8094b083315d74f47272c23be68d2..a36a6d8431de2e350abe8b0a49c971a1d146ba01 100644 --- a/src/main/scala/leon/laziness/LazyClosureFactory.scala +++ b/src/main/scala/leon/laziness/LazyClosureFactory.scala @@ -39,7 +39,7 @@ class LazyClosureFactory(p: Program) { val lazyopsList = p.definedFunctions.flatMap { case fd if (fd.hasBody) => filter(isLazyInvocation)(fd.body.get) map { - case FunctionInvocation(_, Seq(FunctionInvocation(tfd, _))) => tfd.fd + case FunctionInvocation(_, Seq(Lambda(_, FunctionInvocation(tfd, _)))) => tfd.fd } case _ => Seq() }.distinct @@ -118,7 +118,7 @@ class LazyClosureFactory(p: Program) { } if (!ismem) { // create a case class to represent eager evaluation (when handling lazy ops) - val clresType = ops(0).returnType match { + val clresType = ops.head.returnType match { case NAryType(tparams, tcons) => tcons(absTParams) } val eagerid = FreshIdentifier("Eager" + TypeUtil.typeNameWOParams(clresType)) @@ -220,8 +220,8 @@ class LazyClosureFactory(p: Program) { } val nst = CaseClass(stType, nargs) updateFun.body = Some(nst) - // add inline annotation of optimization - updateFun.addFlag(IsInlined) + // Inlining this seems to slow down verification. Why!! + //updateFun.addFlag(IsInlined) (tn -> updateFun) }.toMap } diff --git a/src/main/scala/leon/laziness/LazyExpressionLifter.scala b/src/main/scala/leon/laziness/LazyExpressionLifter.scala index 9cdceceb76a69f473263cc544f36c8b5fff47fff..17cb6e858cb38f765576f7e4dd5e48a5df59b422 100644 --- a/src/main/scala/leon/laziness/LazyExpressionLifter.scala +++ b/src/main/scala/leon/laziness/LazyExpressionLifter.scala @@ -81,10 +81,11 @@ object LazyExpressionLifter { prog.modules.foreach { md => def exprLifter(inmem: Boolean)(fl: Option[FreeVarListIterator])(expr: Expr) = expr match { // is this $(e) where e is not a funtion - case finv @ FunctionInvocation(lazytfd, Seq(arg)) if isLazyInvocation(finv)(prog) => + case finv @ FunctionInvocation(lazytfd, Seq(callByNameArg)) if isLazyInvocation(finv)(prog) => + val Lambda(Seq(), arg) = callByNameArg // extract the call-by-name parameter arg match { case _: FunctionInvocation => - finv // subexpressions have already been evaluated + finv case _ => val freevars = variablesOf(arg).toList val tparams = freevars.map(_.getType).flatMap(getTypeParameters).distinct @@ -114,14 +115,22 @@ object LazyExpressionLifter { if (createUniqueIds) fvVars :+ fl.get.nextExpr else fvVars - FunctionInvocation(lazytfd, Seq(FunctionInvocation(TypedFunDef(argfun, tparams), params))) + FunctionInvocation(lazytfd, Seq(Lambda(Seq(), + FunctionInvocation(TypedFunDef(argfun, tparams), params)))) } // is the argument of eager invocation not a variable ? - case finv @ FunctionInvocation(TypedFunDef(fd, _), Seq(arg)) if isEagerInvocation(finv)(prog) && !arg.isInstanceOf[Variable] => - val rootType = bestRealType(arg.getType) - val freshid = FreshIdentifier("t", rootType) - Let(freshid, arg, FunctionInvocation(TypedFunDef(fd, Seq(rootType)), Seq(freshid.toVariable))) + case finv @ FunctionInvocation(TypedFunDef(fd, Seq(tp)), cbn@Seq(Lambda(Seq(), arg))) if isEagerInvocation(finv)(prog) => + val rootType = bestRealType(tp) + val ntps = Seq(rootType) + arg match { + case _: Variable => + FunctionInvocation(TypedFunDef(fd, ntps), cbn) + case _ => + val freshid = FreshIdentifier("t", rootType) + Let(freshid, arg, FunctionInvocation(TypedFunDef(fd, ntps), + Seq(Lambda(Seq(), freshid.toVariable)))) + } // is this an invocation of a memoized function ? case FunctionInvocation(TypedFunDef(fd, targs), args) if isMemoized(fd) && !inmem => @@ -129,7 +138,7 @@ object LazyExpressionLifter { val tfd = TypedFunDef(fdmap.getOrElse(fd, fd), targs) val finv = FunctionInvocation(tfd, args) // enclose the call within the $ and force it - val susp = FunctionInvocation(TypedFunDef(lazyFun, Seq(tfd.returnType)), Seq(finv)) + val susp = FunctionInvocation(TypedFunDef(lazyFun, Seq(tfd.returnType)), Seq(Lambda(Seq(), finv))) FunctionInvocation(TypedFunDef(valueFun, Seq(tfd.returnType)), Seq(susp)) // every other function calls ? diff --git a/src/main/scala/leon/laziness/TypeChecker.scala b/src/main/scala/leon/laziness/TypeChecker.scala index d28e0f19348cea1c1fb66c04a5b3f63f94cdc4ea..96687b85645037f2d5beb9e374f9397eda25c675 100644 --- a/src/main/scala/leon/laziness/TypeChecker.scala +++ b/src/main/scala/leon/laziness/TypeChecker.scala @@ -43,7 +43,7 @@ object TypeChecker { val (btype, nbody) = rec(body) (btype, Let(nid, nval, nbody)) - case Ensuring(body, Lambda(Seq(resdef @ ValDef(resid, _)), postBody)) => + case Ensuring(body, Lambda(Seq(resdef @ ValDef(resid)), postBody)) => body match { case NoTree(tpe) => val nres = makeIdOfType(resid, tpe) diff --git a/src/main/scala/leon/laziness/TypeRectifier.scala b/src/main/scala/leon/laziness/TypeRectifier.scala index 53cae67fca4b396d58cddfd5855a55644790ed90..ac5c69d9ae7a50f7897d2f5d548386d6ee97a611 100644 --- a/src/main/scala/leon/laziness/TypeRectifier.scala +++ b/src/main/scala/leon/laziness/TypeRectifier.scala @@ -137,7 +137,7 @@ class TypeRectifier(p: Program, clFactory: LazyClosureFactory) { }.toMap val instf = instantiateTypeParameters(tpMap) _ val paramMap = fd.params.map { - case vd @ ValDef(id, _) => + case vd @ ValDef(id) => (id -> FreshIdentifier(id.name, instf(vd.getType))) }.toMap val ntparams = fd.tparams.map(tpd => tpMap(tpd.tp)).distinct.collect { diff --git a/src/main/scala/leon/purescala/CallGraph.scala b/src/main/scala/leon/purescala/CallGraph.scala index a07b807ddaad26817322878c46d575f0a4ec1306..edeaab26953ef6734b2a334e440e04dfb59d38c5 100644 --- a/src/main/scala/leon/purescala/CallGraph.scala +++ b/src/main/scala/leon/purescala/CallGraph.scala @@ -7,106 +7,77 @@ import Definitions._ import Expressions._ import ExprOps._ -class CallGraph(p: Program) { +import utils.Graphs._ - private var _calls = Set[(FunDef, FunDef)]() +class CallGraph(p: Program) { - private var _callers = Map[FunDef, Set[FunDef]]() // if 'foo' calls 'bar': Map(bar -> Set(foo)) - private var _callees = Map[FunDef, Set[FunDef]]() // if 'foo' calls 'bar': Map(foo -> Set(bar)) + private def collectCallsInPats(fd: FunDef)(p: Pattern): Set[(FunDef, FunDef)] = + (p match { + case u: UnapplyPattern => Set((fd, u.unapplyFun.fd)) + case _ => Set() + }) ++ p.subPatterns.flatMap(collectCallsInPats(fd)) - private var _transitiveCalls = Set[(FunDef, FunDef)]() - private var _transitiveCallers = Map[FunDef, Set[FunDef]]() - private var _transitiveCallees = Map[FunDef, Set[FunDef]]() + private def collectCalls(fd: FunDef)(e: Expr): Set[(FunDef, FunDef)] = e match { + case f @ FunctionInvocation(f2, _) => Set((fd, f2.fd)) + case MatchExpr(_, cases) => cases.toSet.flatMap((mc: MatchCase) => collectCallsInPats(fd)(mc.pattern)) + case _ => Set() + } - def allCalls = _calls - def allTransitiveCalls = _transitiveCalls + lazy val graph: DiGraph[FunDef, SimpleEdge[FunDef]] = { + var g = DiGraph[FunDef, SimpleEdge[FunDef]]() - def isRecursive(f: FunDef) = transitivelyCalls(f, f) + for (fd <- p.definedFunctions; c <- collect(collectCalls(fd))(fd.fullBody)) { + g += SimpleEdge(c._1, c._2) + } - def calls(from: FunDef, to: FunDef) = _calls contains (from -> to) - def callers(to: FunDef) = _callers.getOrElse(to, Set()) - def callees(from: FunDef) = _callees.getOrElse(from, Set()) + g + } - def transitivelyCalls(from: FunDef, to: FunDef) = _transitiveCalls contains (from -> to) - def transitiveCallers(to: FunDef) = _transitiveCallers.getOrElse(to, Set()) - def transitiveCallees(from: FunDef) = _transitiveCallees.getOrElse(from, Set()) + lazy val allCalls = graph.E.map(e => e._1 -> e._2) - // multi-source/dest - def callees(from: Set[FunDef]): Set[FunDef] = from.flatMap(callees) - def callers(to: Set[FunDef]): Set[FunDef] = to.flatMap(callers) - def transitiveCallees(from: Set[FunDef]): Set[FunDef] = from.flatMap(transitiveCallees) - def transitiveCallers(to: Set[FunDef]): Set[FunDef] = to.flatMap(transitiveCallers) + def isRecursive(f: FunDef) = { + graph.transitiveSucc(f) contains f + } - lazy val stronglyConnectedComponents: Seq[Set[FunDef]] = { - def rec(funs: Set[FunDef]): Seq[Set[FunDef]] = { - if (funs.isEmpty) Seq() - else { - val h = funs.head - val component = transitiveCallees(h).filter{ transitivelyCalls(_, h) } + h - component +: rec(funs -- component) - } - } - rec(p.definedFunctions.toSet) + def calls(from: FunDef, to: FunDef) = { + graph.E contains SimpleEdge(from, to) } - - def stronglyConnectedComponent(fd: FunDef) = - stronglyConnectedComponents.find{ _.contains(fd) }.getOrElse(Set(fd)) - private def init() { - _calls = Set() - _callers = Map() - _callees = Map() + def callers(to: FunDef): Set[FunDef] = { + graph.pred(to) + } + def callers(tos: Set[FunDef]): Set[FunDef] = { + tos.flatMap(callers) + } - // Collect all calls - p.definedFunctions.foreach(scanForCalls) + def callees(from: FunDef): Set[FunDef] = { + graph.succ(from) + } - _transitiveCalls = _calls - _transitiveCallers = _callers - _transitiveCallees = _callees + def callees(froms: Set[FunDef]): Set[FunDef] = { + froms.flatMap(callees) + } - // Transitive calls - transitiveClosure() + def transitiveCallers(to: FunDef): Set[FunDef] = { + graph.transitivePred(to) } - private def collectCallsInPats(fd: FunDef)(p: Pattern): Set[(FunDef, FunDef)] = - (p match { - case u: UnapplyPattern => Set((fd, u.unapplyFun.fd)) - case _ => Set() - }) ++ p.subPatterns.flatMap(collectCallsInPats(fd)) + def transitiveCallers(tos: Set[FunDef]): Set[FunDef] = { + tos.flatMap(transitiveCallers) + } - private def collectCalls(fd: FunDef)(e: Expr): Set[(FunDef, FunDef)] = e match { - case f @ FunctionInvocation(f2, _) => Set((fd, f2.fd)) - case MatchExpr(_, cases) => cases.toSet.flatMap((mc: MatchCase) => collectCallsInPats(fd)(mc.pattern)) - case _ => Set() + def transitiveCallees(from: FunDef): Set[FunDef] = { + graph.transitiveSucc(from) } - private def scanForCalls(fd: FunDef) { - for( (from, to) <- collect(collectCalls(fd))(fd.fullBody) ) { - _calls += (from -> to) - _callees += (from -> (_callees.getOrElse(from, Set()) + to)) - _callers += (to -> (_callers.getOrElse(to, Set()) + from)) - } + def transitiveCallees(froms: Set[FunDef]): Set[FunDef] = { + froms.flatMap(transitiveCallees) } - private def transitiveClosure() { - var changed = true - while(changed) { - val newCalls = _transitiveCalls.flatMap { - case (from, to) => _transitiveCallees.getOrElse(to, Set()).map((from, _)) - } -- _transitiveCalls - - if (newCalls.nonEmpty) { - for ((from, to) <- newCalls) { - _transitiveCalls += (from -> to) - _transitiveCallees += (from -> (_transitiveCallees.getOrElse(from, Set()) + to)) - _transitiveCallers += (to -> (_transitiveCallers.getOrElse(to, Set()) + from)) - } - } else { - changed =false - } - } + def transitivelyCalls(from: FunDef, to: FunDef): Boolean = { + graph.transitiveSucc(from) contains to } - init() + lazy val stronglyConnectedComponents = graph.stronglyConnectedComponents.N } diff --git a/src/main/scala/leon/purescala/Constructors.scala b/src/main/scala/leon/purescala/Constructors.scala index 4b960a043d2baf82b96a05a7133bf05faf6fafe6..e3ce7d1e01780ce02ba424396dabd3670f426966 100644 --- a/src/main/scala/leon/purescala/Constructors.scala +++ b/src/main/scala/leon/purescala/Constructors.scala @@ -19,9 +19,10 @@ import Types._ * */ object Constructors { - /** If `isTuple`, the whole expression is returned. This is to avoid a situation like - * `tupleSelect(tupleWrap(Seq(Tuple(x,y))),1) -> x`, which is not expected. - * Instead, + /** If `isTuple`: + * `tupleSelect(tupleWrap(Seq(Tuple(x,y))),1) -> x` + * `tupleSelect(tupleExpr,1) -> tupleExpr._1` + * If not `isTuple` (usually used only in the case of a tuple of arity 1) * `tupleSelect(tupleWrap(Seq(Tuple(x,y))),1) -> Tuple(x,y)`. * @see [[purescala.Expressions.TupleSelect]] */ @@ -40,6 +41,17 @@ object Constructors { */ def tupleSelect(t: Expr, index: Int, originalSize: Int): Expr = tupleSelect(t, index, originalSize > 1) + /** $encodingof ``def foo(..) {} ...; e``. + * @see [[purescala.Expressions.LetDef]] + */ + def letDef(defs: Seq[FunDef], e: Expr) = { + if (defs.isEmpty) { + e + } else { + LetDef(defs, e) + } + } + /** $encodingof ``val id = e; bd``, and returns `bd` if the identifier is not bound in `bd`. * @see [[purescala.Expressions.Let]] */ @@ -144,7 +156,7 @@ object Constructors { resType match { case Some(tpe) => - casesFiltered.filter(c => isSubtypeOf(c.rhs.getType, tpe) || isSubtypeOf(tpe, c.rhs.getType)) + casesFiltered.filter(c => typesCompatible(c.rhs.getType, tpe)) case None => casesFiltered } @@ -280,7 +292,27 @@ object Constructors { if (a == b && isDeterministic(a)) { BooleanLiteral(true) } else { - Equals(a, b) + (a, b) match { + case (a: Literal[_], b: Literal[_]) => + if (a.value == b.value) { + BooleanLiteral(true) + } else { + BooleanLiteral(false) + } + + case _ => + Equals(a, b) + } + } + } + + def assertion(c: Expr, err: Option[String], res: Expr) = { + if (c == BooleanLiteral(true)) { + res + } else if (c == BooleanLiteral(false)) { + Error(res.getType, err.getOrElse("Assertion failed")) + } else { + Assert(c, err, res) } } @@ -295,9 +327,9 @@ object Constructors { var defs: Seq[(Identifier, Expr)] = Seq() val subst = formalArgs.zip(realArgs).map { - case (ValDef(from, _), to:Variable) => + case (ValDef(from), to:Variable) => from -> to - case (ValDef(from, _), e) => + case (ValDef(from), e) => val fresh = from.freshen defs :+= (fresh -> e) from -> Variable(fresh) @@ -384,4 +416,8 @@ object Constructors { case _ => Require(pred, body) } + def ensur(e: Expr, pred: Expr) = { + Ensuring(e, tupleWrapArg(pred)) + } + } diff --git a/src/main/scala/leon/purescala/DefOps.scala b/src/main/scala/leon/purescala/DefOps.scala index 1e5e120112330ca85fccfdded79f425c0035214b..81ebbdbec38e1484baf106eac9652d62297ae493 100644 --- a/src/main/scala/leon/purescala/DefOps.scala +++ b/src/main/scala/leon/purescala/DefOps.scala @@ -151,7 +151,7 @@ object DefOps { if (useUniqueIds) { List(d.id.uniqueName) } else { - List(d.id.name) + List(d.id.toString) } } } diff --git a/src/main/scala/leon/purescala/Definitions.scala b/src/main/scala/leon/purescala/Definitions.scala index 103ed1bee94c2203d288848184d62fd6b2d2ce09..8fb753d23d35364059115f7032bef3310c492d82 100644 --- a/src/main/scala/leon/purescala/Definitions.scala +++ b/src/main/scala/leon/purescala/Definitions.scala @@ -3,7 +3,6 @@ package leon package purescala -import sun.reflect.generics.tree.ReturnType import utils.Library import Common._ import Expressions._ @@ -41,27 +40,21 @@ object Definitions { } } - /** A ValDef represents a parameter of a [[purescala.Definitions.FunDef function]] or - * a [[purescala.Definitions.CaseClassDef case class]]. - * - * The optional [[tpe]], if present, overrides the type of the underlying Identifier [[id]]. - * This is useful to instantiate argument types of polymorphic classes. To be consistent, - * never use the type of [[id]] directly; use [[ValDef#getType]] instead. - */ - case class ValDef(id: Identifier, tpe: Option[TypeTree] = None) extends Definition with Typed { + /** + * A ValDef declares a new identifier to be of a certain type. + * The optional tpe, if present, overrides the type of the underlying Identifier id + * This is useful to instantiate argument types of polymorphic functions + */ + case class ValDef(id: Identifier) extends Definition with Typed { self: Serializable => - val getType = tpe getOrElse id.getType + val getType = id.getType var defaultValue : Option[FunDef] = None def subDefinitions = Seq() - /** Transform this [[ValDef]] into a [[Expressions.Variable Variable]] - * - * Warning: the variable will not have the same type as this ValDef, but currently - * the Identifier type is enough for all uses in Leon. - */ + /** Transform this [[ValDef]] into a [[Expressions.Variable Variable]] */ def toVariable : Variable = Variable(id) } @@ -91,6 +84,10 @@ object Definitions { def lookupAll(name: String) = DefOps.searchWithin(name, this) def lookup(name: String) = lookupAll(name).headOption + + def lookupCaseClass(name: String) = lookupAll(name).collect{ case c: CaseClassDef => c }.headOption + def lookupAbstractClass(name: String) = lookupAll(name).collect{ case c: AbstractClassDef => c }.headOption + def lookupFunDef(name: String) = lookupAll(name).collect{ case c: FunDef => c }.headOption } object Program { @@ -117,6 +114,7 @@ object Definitions { } } + /** Object definition */ case class UnitDef( id: Identifier, pack : PackageRef, @@ -160,7 +158,7 @@ object Definitions { object UnitDef { def apply(id: Identifier, modules : Seq[ModuleDef]) : UnitDef = - UnitDef(id,Nil, Nil, modules,true) + UnitDef(id, Nil, Nil, modules, true) } /** Objects work as containers for class definitions, functions (def's) and @@ -211,8 +209,8 @@ object Definitions { // If this class was a method. owner is the original owner of the method case class IsMethod(owner: ClassDef) extends FunctionFlag // If this function represents a loop that was there before XLangElimination - // Contains a copy of the original looping function - case class IsLoop(orig: FunDef) extends FunctionFlag + // Contains a link to the FunDef where the loop was defined + case class IsLoop(owner: FunDef) extends FunctionFlag // If extraction fails of the function's body fais, it is marked as abstract case object IsAbstract extends FunctionFlag // Currently, the only synthetic functions are those that calculate default values of parameters @@ -408,13 +406,13 @@ object Definitions { def subDefinitions = params ++ tparams ++ directlyNestedFuns.toList /** Duplication of this [[FunDef]]. - * @note This will not replace recursive function calls + * @note This will not replace recursive function calls in [[fullBody]] */ def duplicate( - id: Identifier = this.id.freshen, - tparams: Seq[TypeParameterDef] = this.tparams, - params: Seq[ValDef] = this.params, - returnType: TypeTree = this.returnType + id: Identifier = this.id.freshen, + tparams: Seq[TypeParameterDef] = this.tparams, + params: Seq[ValDef] = this.params, + returnType: TypeTree = this.returnType ): FunDef = { val fd = new FunDef(id, tparams, params, returnType) fd.fullBody = this.fullBody @@ -485,11 +483,20 @@ object Definitions { def translated(e: Expr): Expr = instantiateType(e, typesMap, paramsMap) + /** A mapping from this [[TypedFunDef]]'s formal parameters to real arguments + * + * @param realArgs The arguments to which the formal argumentas are mapped + * */ def paramSubst(realArgs: Seq[Expr]) = { require(realArgs.size == params.size) (paramIds zip realArgs).toMap } + /** Substitute this [[TypedFunDef]]'s formal parameters with real arguments in some expression + * + * @param realArgs The arguments to which the formal argumentas are mapped + * @param e The expression in which the substitution will take place + */ def withParamSubst(realArgs: Seq[Expr], e: Expr) = { replaceFromIDs(paramSubst(realArgs), e) } @@ -509,11 +516,10 @@ object Definitions { if (typesMap.isEmpty) { (fd.params, Map()) } else { - val newParams = fd.params.map { - case vd @ ValDef(id, _) => - val newTpe = translated(vd.getType) - val newId = FreshIdentifier(id.name, newTpe, true).copiedFrom(id) - ValDef(newId).setPos(vd) + val newParams = fd.params.map { vd => + val newTpe = translated(vd.getType) + val newId = FreshIdentifier(vd.id.name, newTpe, true).copiedFrom(vd.id) + vd.copy(id = newId).setPos(vd) } val paramsMap: Map[Identifier, Identifier] = (fd.params zip newParams).map { case (vd1, vd2) => vd1.id -> vd2.id }.toMap diff --git a/src/main/scala/leon/purescala/ExprOps.scala b/src/main/scala/leon/purescala/ExprOps.scala index 84817e872598f75d8b2902aea003896d2eb0cd90..49d945ec6217a42578da7fef97bf572252cb6c26 100644 --- a/src/main/scala/leon/purescala/ExprOps.scala +++ b/src/main/scala/leon/purescala/ExprOps.scala @@ -309,9 +309,11 @@ object ExprOps { def preTransformWithBinders(f: (Expr, Set[Identifier]) => Expr, initBinders: Set[Identifier] = Set())(e: Expr) = { import xlang.Expressions.LetVar def rec(binders: Set[Identifier], e: Expr): Expr = (f(e, binders) match { - case LetDef(fd, bd) => - fd.fullBody = rec(binders ++ fd.paramIds, fd.fullBody) - LetDef(fd, rec(binders, bd)) + case LetDef(fds, bd) => + fds.foreach(fd => { + fd.fullBody = rec(binders ++ fd.paramIds, fd.fullBody) + }) + LetDef(fds, rec(binders, bd)) case Let(i, v, b) => Let(i, rec(binders + i, v), rec(binders + i, b)) case LetVar(i, v, b) => @@ -346,7 +348,7 @@ object ExprOps { e match { case Variable(i) => subvs + i case Old(i) => subvs + i - case LetDef(fd, _) => subvs -- fd.params.map(_.id) + case LetDef(fds, _) => subvs -- fds.flatMap(_.params.map(_.id)) case Let(i, _, _) => subvs - i case LetVar(i, _, _) => subvs - i case MatchExpr(_, cses) => subvs -- cses.flatMap(_.pattern.binders) @@ -377,8 +379,8 @@ object ExprOps { /** Returns functions in directly nested LetDefs */ def directlyNestedFunDefs(e: Expr): Set[FunDef] = { fold[Set[FunDef]]{ - case (LetDef(fd,_), Seq(fromFd, fromBd)) => fromBd + fd - case (_, subs) => subs.flatten.toSet + case (LetDef(fds,_), fromFdsFromBd) => fromFdsFromBd.last ++ fds + case (_, subs) => subs.flatten.toSet }(e) } @@ -514,7 +516,7 @@ object ExprOps { (expr, idSeqs) => idSeqs.foldLeft(expr match { case Lambda(args, _) => args.map(_.id) case Forall(args, _) => args.map(_.id) - case LetDef(fd, _) => fd.paramIds + case LetDef(fds, _) => fds.flatMap(_.paramIds) case Let(i, _, _) => Seq(i) case MatchExpr(_, cses) => cses.flatMap(_.pattern.binders) case Passes(_, _, cses) => cses.flatMap(_.pattern.binders) @@ -540,8 +542,8 @@ object ExprOps { } val normalized = postMap { - case Lambda(args, body) => Some(Lambda(args.map(vd => ValDef(subst(vd.id), vd.tpe)), body)) - case Forall(args, body) => Some(Forall(args.map(vd => ValDef(subst(vd.id), vd.tpe)), body)) + case Lambda(args, body) => Some(Lambda(args.map(vd => vd.copy(id = subst(vd.id))), body)) + case Forall(args, body) => Some(Forall(args.map(vd => vd.copy(id = subst(vd.id))), body)) case Let(i, e, b) => Some(Let(subst(i), e, b)) case MatchExpr(scrut, cses) => Some(MatchExpr(scrut, cses.map { cse => cse.copy(pattern = replacePatternBinders(cse.pattern, subst)) @@ -792,7 +794,7 @@ object ExprOps { }) case CaseClassPattern(_, cct, subps) => - val subExprs = (subps zip cct.fields) map { + val subExprs = (subps zip cct.classDef.fields) map { case (p, f) => p.binder.map(_.toVariable).getOrElse(caseClassSelector(cct, in, f.id)) } @@ -869,8 +871,8 @@ object ExprOps { } case CaseClassPattern(ob, cct, subps) => - assert(cct.fields.size == subps.size) - val pairs = cct.fields.map(_.id).toList zip subps.toList + assert(cct.classDef.fields.size == subps.size) + val pairs = cct.classDef.fields.map(_.id).toList zip subps.toList val subTests = pairs.map(p => rec(caseClassSelector(cct, in, p._1), p._2)) val together = and(bind(ob, in) +: subTests :_*) and(IsInstanceOf(in, cct), together) @@ -881,7 +883,7 @@ object ExprOps { val subTests = subps.zipWithIndex.map{case (p, i) => rec(tupleSelect(in, i+1, subps.size), p)} and(bind(ob, in) +: subTests: _*) - case up@UnapplyPattern(ob, fd, subps) => + case up @ UnapplyPattern(ob, fd, subps) => def someCase(e: Expr) = { // In the case where unapply returns a Some, it is enough that the subpatterns match andJoin(unwrapTuple(e, subps.size) zip subps map { case (ex, p) => rec(ex, p).setPos(p) }).setPos(e) @@ -905,7 +907,7 @@ object ExprOps { pattern match { case CaseClassPattern(b, cct, subps) => assert(cct.fields.size == subps.size) - val pairs = cct.fields.map(_.id).toList zip subps.toList + val pairs = cct.classDef.fields.map(_.id).toList zip subps.toList val subMaps = pairs.map(p => mapForPattern(caseClassSelector(cct, asInstOf(in, cct), p._1), p._2)) val together = subMaps.flatten.toMap bindIn(b, Some(cct)) ++ together @@ -1101,6 +1103,7 @@ object ExprOps { /** Returns simplest value of a given type */ def simplestValue(tpe: TypeTree) : Expr = tpe match { + case StringType => StringLiteral("") case Int32Type => IntLiteral(0) case RealType => FractionalLiteral(0, 1) case IntegerType => InfiniteIntegerLiteral(0) @@ -1145,6 +1148,15 @@ object ExprOps { case _ => throw LeonFatalError("I can't choose simplest value for type " + tpe) } + /* Checks if a given expression is 'real' and does not contain generic + * values. */ + def isRealExpr(v: Expr): Boolean = { + !exists { + case gv: GenericValue => true + case _ => false + }(v) + } + def valuesOf(tp: TypeTree): Stream[Expr] = { import utils.StreamUtils._ tp match { @@ -1238,23 +1250,23 @@ object ExprOps { def pre(e : Expr) = e match { - case LetDef(fd, expr) if fd.hasPrecondition => - val pre = fd.precondition.get - - solver.solveVALID(pre) match { - case Some(true) => - fd.precondition = None - - case Some(false) => solver.solveSAT(pre) match { - case (Some(false), _) => - fd.precondition = Some(BooleanLiteral(false).copiedFrom(e)) - case _ => + case LetDef(fds, expr) => + for(fd <- fds if fd.hasPrecondition) { + val pre = fd.precondition.get + + solver.solveVALID(pre) match { + case Some(true) => + fd.precondition = None + + case Some(false) => solver.solveSAT(pre) match { + case (Some(false), _) => + fd.precondition = Some(BooleanLiteral(false).copiedFrom(e)) + case _ => + } + case None => } - case None => - } - - e - + } + e case IfExpr(cond, thenn, elze) => try { solver.solveVALID(cond) match { @@ -1372,12 +1384,26 @@ object ExprOps { val valuator = valuateWithModel(model) _ replace(vars.map(id => Variable(id) -> valuator(id)).toMap, expr) } + + /** Simple, local optimization on string */ + def simplifyString(expr: Expr): Expr = { + def simplify0(expr: Expr): Expr = (expr match { + case StringConcat(StringLiteral(""), b) => b + case StringConcat(b, StringLiteral("")) => b + case StringConcat(StringLiteral(a), StringLiteral(b)) => StringLiteral(a + b) + case StringLength(StringLiteral(a)) => InfiniteIntegerLiteral(a.length) + case SubString(StringLiteral(a), InfiniteIntegerLiteral(start), InfiniteIntegerLiteral(end)) => StringLiteral(a.substring(start.toInt, end.toInt)) + case _ => expr + }).copiedFrom(expr) + simplify0(expr) + fixpoint(simplePostTransform(simplify0))(expr) + } /** Simple, local simplification on arithmetic * * You should not assume anything smarter than some constant folding and - * simple cancelation. To avoid infinite cycle we only apply simplification - * that reduce the size of the tree. The only guarentee from this function is + * simple cancellation. To avoid infinite cycle we only apply simplification + * that reduce the size of the tree. The only guarantee from this function is * to not augment the size of the expression and to be sound. */ def simplifyArithmetic(expr: Expr): Expr = { @@ -1465,11 +1491,11 @@ object ExprOps { } } - /** Checks whether a predicate is inductive on a certain identfier. + /** Checks whether a predicate is inductive on a certain identifier. * * isInductive(foo(a, b), a) where a: List will check whether * foo(Nil, b) and - * foo(Cons(h,t), b) => foo(t, b) + * foo(t, b) => foo(Cons(h,t), b) */ def isInductiveOn(sf: SolverFactory[Solver])(expr: Expr, on: Identifier): Boolean = on match { case IsTyped(origId, AbstractClassType(cd, tps)) => @@ -1480,8 +1506,8 @@ object ExprOps { val isType = IsInstanceOf(Variable(on), cct) - val recSelectors = cct.fields.collect { - case vd if vd.getType == on.getType => vd.id + val recSelectors = (cct.classDef.fields zip cct.fieldsTypes).collect { + case (vd, tpe) if tpe == on.getType => vd.id } if (recSelectors.isEmpty) { @@ -1615,9 +1641,15 @@ object ExprOps { isHomo(v1, v2) && isHomo(e1, e2)(map + (id1 -> id2)) - case (LetDef(fd1, e1), LetDef(fd2, e2)) => - fdHomo(fd1, fd2) && - isHomo(e1, e2)(map + (fd1.id -> fd2.id)) + case (LetDef(fds1, e1), LetDef(fds2, e2)) => + fds1.size == fds2.size && + { + val zipped = fds1.zip(fds2) + zipped.forall( fds => + fdHomo(fds._1, fds._2) + ) && + isHomo(e1, e2)(map ++ zipped.map(fds => fds._1.id -> fds._2.id)) + } case (MatchExpr(s1, cs1), MatchExpr(s2, cs2)) => cs1.size == cs2.size && isHomo(s1, s2) && casesMatch(cs1,cs2) @@ -1759,6 +1791,10 @@ object ExprOps { case UnitType => // Anything matches () ps.nonEmpty + + case StringType => + // Can't possibly pattern match against all Strings one by one + ps exists (_.isInstanceOf[WildcardPattern]) case Int32Type => // Can't possibly pattern match against all Ints one by one @@ -1800,7 +1836,8 @@ object ExprOps { */ def flattenFunctions(fdOuter: FunDef, ctx: LeonContext, p: Program): FunDef = { fdOuter.body match { - case Some(LetDef(fdInner, FunctionInvocation(tfdInner2, args))) if fdInner == tfdInner2.fd => + case Some(LetDef(fdsInner, FunctionInvocation(tfdInner2, args))) if fdsInner.size == 1 && fdsInner.head == tfdInner2.fd => + val fdInner = fdsInner.head val argsDef = fdOuter.paramIds val argsCall = args.collect { case Variable(id) => id } @@ -1913,8 +1950,8 @@ object ExprOps { * @see [[Expressions.Require]] */ def withPostcondition(expr: Expr, oie: Option[Expr]) = (oie, expr) match { - case (Some(npost), Ensuring(b, post)) => Ensuring(b, npost) - case (Some(npost), b) => Ensuring(b, npost) + case (Some(npost), Ensuring(b, post)) => ensur(b, npost) + case (Some(npost), b) => ensur(b, npost) case (None, Ensuring(b, p)) => b case (None, b) => b } @@ -1998,7 +2035,7 @@ object ExprOps { collect[(TypedFunDef, Seq[Expr])] { case InvocationExtractor(tfd, args) => Set(tfd -> args) case _ => Set.empty - } (expr) + }(expr) object ApplicationExtractor { private def flatApplication(expr: Expr): Option[(Expr, Seq[Expr])] = expr match { @@ -2006,7 +2043,7 @@ object ExprOps { case Application(caller: Application, args) => flatApplication(caller) match { case Some((c, prevArgs)) => Some((c, prevArgs ++ args)) case None => None - } + } case Application(caller, args) => Some((caller, args)) case _ => None } @@ -2087,12 +2124,12 @@ object ExprOps { import synthesis.Witnesses.Terminating val res1 = preMap({ - case LetDef(fd, b) => - val nfd = fd.duplicate() + case LetDef(lfds, b) => + val nfds = lfds.map(fd => fd -> fd.duplicate()) - fds += fd -> nfd + fds ++= nfds - Some(LetDef(nfd, b)) + Some(LetDef(nfds.map(_._2), b)) case FunctionInvocation(tfd, args) => if (fds contains tfd.fd) { @@ -2114,7 +2151,7 @@ object ExprOps { // we now remove LetDefs val res2 = preMap({ - case LetDef(fd, b) => + case LetDef(fds, b) => Some(b) case _ => None @@ -2154,7 +2191,7 @@ object ExprOps { * @param collectFIs Whether we also want to collect preconditions for function invocations * @return A sequence of pairs (expression, condition) */ - def collectCorrectnessConditions(e: Expr, collectFIs: Boolean = true): Seq[(Expr, Expr)] = { + def collectCorrectnessConditions(e: Expr, collectFIs: Boolean = false): Seq[(Expr, Expr)] = { val conds = collectWithPC { case m @ MatchExpr(scrut, cases) => @@ -2166,11 +2203,11 @@ object ExprOps { case a @ Assert(cond, _, _) => (a, cond) - case e @ Ensuring(body, post) => + /*case e @ Ensuring(body, post) => (e, application(post, Seq(body))) case r @ Require(pred, e) => - (r, pred) + (r, pred)*/ case fi @ FunctionInvocation(tfd, args) if tfd.hasPrecondition && collectFIs => (fi, tfd.withParamSubst(args, tfd.precondition.get)) @@ -2189,6 +2226,17 @@ object ExprOps { ) } - + def tupleWrapArg(fun: Expr) = fun.getType match { + case FunctionType(args, res) if args.size > 1 => + val newArgs = fun match { + case Lambda(args, _) => args map (_.id) + case _ => args map (arg => FreshIdentifier("x", arg.getType, alwaysShowUniqueID = true)) + } + val res = FreshIdentifier("res", TupleType(args map (_.getType)), alwaysShowUniqueID = true) + val patt = TuplePattern(None, newArgs map (arg => WildcardPattern(Some(arg)))) + Lambda(Seq(ValDef(res)), MatchExpr(res.toVariable, Seq(SimpleCase(patt, application(fun, newArgs map (_.toVariable)))))) + case _ => + fun + } } diff --git a/src/main/scala/leon/purescala/Expressions.scala b/src/main/scala/leon/purescala/Expressions.scala index 5042413fc537df0eb20f27888efb934a68c8e0b8..2a714abaf6143eac9bb1d86cb762d87fec150176 100644 --- a/src/main/scala/leon/purescala/Expressions.scala +++ b/src/main/scala/leon/purescala/Expressions.scala @@ -159,12 +159,13 @@ object Expressions { } } - /** $encodingof `def ... = ...; ...` (local function definition) + /** $encodingof multiple `def ... = ...; ...` (local function definition and possibly mutually recursive) * - * @param fd The function definition. + * @param fds The function definitions. * @param body The body of the expression after the function */ - case class LetDef(fd: FunDef, body: Expr) extends Expr { + case class LetDef(fds: Seq[FunDef], body: Expr) extends Expr { + assert(fds.nonEmpty) val getType = body.getType } @@ -331,7 +332,7 @@ object Expressions { // Hacky, but ok lazy val optionType = unapplyFun.returnType.asInstanceOf[AbstractClassType] lazy val Seq(noneType, someType) = optionType.knownCCDescendants.sortBy(_.fields.size) - lazy val someValue = someType.fields.head + lazy val someValue = someType.classDef.fields.head // Pattern match unapply(scrut) // In case of None, return noneCase. // In case of Some(v), return someCase(v). @@ -370,9 +371,9 @@ object Expressions { * [[cases]] should be nonempty. If you are not sure about this, you should use * [[purescala.Constructors#passes purescala's constructor passes]] * - * @param in - * @param out - * @param cases + * @param in The input expression + * @param out The output expression + * @param cases The cases to compare against */ case class Passes(in: Expr, out : Expr, cases : Seq[MatchCase]) extends Expr { require(cases.nonEmpty) @@ -420,6 +421,10 @@ object Expressions { val getType = UnitType val value = () } + /** $encodingof a string literal */ + case class StringLiteral(value: String) extends Literal[String] { + val getType = StringType + } /** Generic values. Represent values of the generic type `tp`. @@ -547,7 +552,44 @@ object Expressions { else Untyped } } - + + abstract class ConverterToString(fromType: TypeTree, toType: TypeTree) extends Expr { + def expr: Expr + val getType = if(expr.getType == fromType) toType else Untyped + } + + /* String Theory */ + /** $encodingof `expr.toString` for Int32 to String */ + case class Int32ToString(expr: Expr) extends ConverterToString(Int32Type, StringType) + /** $encodingof `expr.toString` for boolean to String */ + case class BooleanToString(expr: Expr) extends ConverterToString(BooleanType, StringType) + /** $encodingof `expr.toString` for BigInt to String */ + case class IntegerToString(expr: Expr) extends ConverterToString(IntegerType, StringType) + /** $encodingof `expr.toString` for char to String */ + case class CharToString(expr: Expr) extends ConverterToString(CharType, StringType) + /** $encodingof `expr.toString` for real to String */ + case class RealToString(expr: Expr) extends ConverterToString(RealType, StringType) + /** $encodingof `lhs + rhs` for strings */ + case class StringConcat(lhs: Expr, rhs: Expr) extends Expr { + val getType = { + if (lhs.getType == StringType && rhs.getType == StringType) StringType + else Untyped + } + } + /** $encodingof `lhs.subString(start, end)` for strings */ + case class SubString(expr: Expr, start: Expr, end: Expr) extends Expr { + val getType = { + if (expr.getType == StringType && (start == IntegerType || start == Int32Type) && (end == IntegerType || end == Int32Type)) StringType + else Untyped + } + } + /** $encodingof `lhs.length` for strings */ + case class StringLength(expr: Expr) extends Expr { + val getType = { + if (expr.getType == StringType) Int32Type + else Untyped + } + } /* Integer arithmetic */ @@ -685,11 +727,11 @@ object Expressions { case class BVShiftLeft(lhs: Expr, rhs: Expr) extends Expr { val getType = Int32Type } - /** $encodingof `... >>> ...` $noteBitvector (logical shift) */ + /** $encodingof `... >> ...` $noteBitvector (arithmetic shift, sign-preserving) */ case class BVAShiftRight(lhs: Expr, rhs: Expr) extends Expr { val getType = Int32Type } - /** $encodingof `... >> ...` $noteBitvector (arithmetic shift, sign-preserving) */ + /** $encodingof `... >>> ...` $noteBitvector (logical shift) */ case class BVLShiftRight(lhs: Expr, rhs: Expr) extends Expr { val getType = Int32Type } diff --git a/src/main/scala/leon/purescala/Extractors.scala b/src/main/scala/leon/purescala/Extractors.scala index 47dfd7b7c576bc2e331bf5f7369b2ff6d94e805c..e2581dd8cdb33e3d025e8206d72dd32fc4ca59f7 100644 --- a/src/main/scala/leon/purescala/Extractors.scala +++ b/src/main/scala/leon/purescala/Extractors.scala @@ -27,6 +27,18 @@ object Extractors { Some((Seq(t), (es: Seq[Expr]) => RealUMinus(es.head))) case BVNot(t) => Some((Seq(t), (es: Seq[Expr]) => BVNot(es.head))) + case StringLength(t) => + Some((Seq(t), (es: Seq[Expr]) => StringLength(es.head))) + case Int32ToString(t) => + Some((Seq(t), (es: Seq[Expr]) => Int32ToString(es.head))) + case BooleanToString(t) => + Some((Seq(t), (es: Seq[Expr]) => BooleanToString(es.head))) + case IntegerToString(t) => + Some((Seq(t), (es: Seq[Expr]) => IntegerToString(es.head))) + case CharToString(t) => + Some((Seq(t), (es: Seq[Expr]) => CharToString(es.head))) + case RealToString(t) => + Some((Seq(t), (es: Seq[Expr]) => RealToString(es.head))) case SetCardinality(t) => Some((Seq(t), (es: Seq[Expr]) => SetCardinality(es.head))) case CaseClassSelector(cd, e, sel) => @@ -60,11 +72,13 @@ object Extractors { Some((Seq(body), (es: Seq[Expr]) => Forall(args, es.head))) /* Binary operators */ - case LetDef(fd, body) => Some(( - Seq(fd.fullBody, body), + case LetDef(fds, rest) => Some(( + fds.map(_.fullBody) ++ Seq(rest), (es: Seq[Expr]) => { - fd.fullBody = es(0) - LetDef(fd, es(1)) + for((fd, i) <- fds.zipWithIndex) { + fd.fullBody = es(i) + } + LetDef(fds, es(fds.length)) } )) case Equals(t1, t2) => @@ -121,6 +135,8 @@ object Extractors { Some(Seq(t1, t2), (es: Seq[Expr]) => times(es(0), es(1))) case RealDivision(t1, t2) => Some(Seq(t1, t2), (es: Seq[Expr]) => RealDivision(es(0), es(1))) + case StringConcat(t1, t2) => + Some(Seq(t1, t2), (es: Seq[Expr]) => StringConcat(es(0), es(1))) case ElementOfSet(t1, t2) => Some(Seq(t1, t2), (es: Seq[Expr]) => ElementOfSet(es(0), es(1))) case SubsetOf(t1, t2) => @@ -157,6 +173,7 @@ object Extractors { case CaseClass(cd, args) => Some((args, CaseClass(cd, _))) case And(args) => Some((args, and)) case Or(args) => Some((args, or)) + case SubString(t1, a, b) => Some((t1::a::b::Nil, es => SubString(es(0), es(1), es(2)))) case FiniteSet(els, base) => Some((els.toSeq, els => FiniteSet(els.toSet, base))) case FiniteMap(args, f, t) => { @@ -237,22 +254,6 @@ object Extractors { trait Extractable { def extract: Option[(Seq[Expr], Seq[Expr] => Expr)] } - - object StringLiteral { - def unapply(e: Expr)(implicit pgm: Program): Option[String] = e match { - case CaseClass(cct, args) => - for { - libS <- pgm.library.String - if cct.classDef == libS - (_, chars) <- isListLiteral(args.head) - if chars.forall(_.isInstanceOf[CharLiteral]) - } yield { - chars.collect{ case CharLiteral(c) => c }.mkString - } - case _ => - None - } - } object TopLevelOrs { // expr1 AND (expr2 AND (expr3 AND ..)) => List(expr1, expr2, expr3) def unapply(e: Expr): Option[Seq[Expr]] = e match { @@ -276,6 +277,17 @@ object Extractors { object IsTyped { def unapply[T <: Typed](e: T): Option[(T, TypeTree)] = Some((e, e.getType)) } + + object WithStringconverter { + def unapply(t: TypeTree): Option[Expr => Expr] = t match { + case BooleanType => Some(BooleanToString) + case Int32Type => Some(Int32ToString) + case IntegerType => Some(IntegerToString) + case CharType => Some(CharToString) + case RealType => Some(RealToString) + case _ => None + } + } object FiniteArray { def unapply(e: Expr): Option[(Map[Int, Expr], Option[Expr], Expr)] = e match { diff --git a/src/main/scala/leon/purescala/FunctionClosure.scala b/src/main/scala/leon/purescala/FunctionClosure.scala index 65fd1de8a934bb802f57a39f95e17721e6e78f95..07c6781a8389be1df4e16681cd518bb043cb608a 100644 --- a/src/main/scala/leon/purescala/FunctionClosure.scala +++ b/src/main/scala/leon/purescala/FunctionClosure.scala @@ -26,13 +26,13 @@ object FunctionClosure extends TransformationPhase { private def close(fd: FunDef): Seq[FunDef] = { // Directly nested functions with their p.c. - val nestedWithPaths = { + val nestedWithPathsFull = { val funDefs = directlyNestedFunDefs(fd.fullBody) collectWithPC { - case LetDef(fd1, body) if funDefs(fd1) => fd1 + case LetDef(fd1, body) => fd1.filter(funDefs) }(fd.fullBody) - }.toMap - + } + val nestedWithPaths = (for((fds, path) <- nestedWithPathsFull; fd <- fds) yield (fd, path)).toMap val nestedFuns = nestedWithPaths.keys.toSeq // Transitively called funcions from each function @@ -62,7 +62,7 @@ object FunctionClosure extends TransformationPhase { // Remove LetDefs from fd fd.fullBody = preMap({ - case LetDef(fd, bd) => + case LetDef(fds, bd) => Some(bd) case _ => None @@ -145,11 +145,11 @@ object FunctionClosure extends TransformationPhase { ) newFd.fullBody = preMap { - case FunctionInvocation(tfd, args) if tfd.fd == inner => + case fi@FunctionInvocation(tfd, args) if tfd.fd == inner => Some(FunctionInvocation( newFd.typed(tfd.tps ++ tpFresh.map{ _.tp }), args ++ freshVals.drop(args.length).map(Variable) - )) + ).setPos(fi)) case _ => None }(instBody) diff --git a/src/main/scala/leon/purescala/MethodLifting.scala b/src/main/scala/leon/purescala/MethodLifting.scala index 6a8c8f9c9415a6ebc3b9c46de5d5e326a50858da..2014739eaa3fba0c84ce10685087262e7e227872 100644 --- a/src/main/scala/leon/purescala/MethodLifting.scala +++ b/src/main/scala/leon/purescala/MethodLifting.scala @@ -27,7 +27,7 @@ object MethodLifting extends TransformationPhase { // Common for both cases val ct = ccd.typed val binder = FreshIdentifier(ccd.id.name.toLowerCase, ct, true) - val fBinders = ct.fields.map{ f => f.id -> f.id.freshen }.toMap + val fBinders = (ccd.fieldsIds zip ct.fields).map(p => p._1 -> p._2.id.freshen).toMap def subst(e: Expr): Expr = e match { case CaseClassSelector(`ct`, This(`ct`), i) => Variable(fBinders(i)).setPos(e) @@ -37,19 +37,19 @@ object MethodLifting extends TransformationPhase { e } - ccd.methods.find( _.id == fdId).map { m => + ccd.methods.find(_.id == fdId).map { m => // Ancestor's method is a method in the case class - val subPatts = ct.fields map (f => WildcardPattern(Some(fBinders(f.id)))) + val subPatts = ccd.fields map (f => WildcardPattern(Some(fBinders(f.id)))) val patt = CaseClassPattern(Some(binder), ct, subPatts) val newE = simplePreTransform(subst)(breakDown(m.fullBody)) val cse = SimpleCase(patt, newE).setPos(newE) (List(cse), true) - } orElse ccd.fields.find( _.id == fdId).map { f => + } orElse ccd.fields.find(_.id == fdId).map { f => // Ancestor's method is a case class argument in the case class - val subPatts = ct.fields map (fld => + val subPatts = ccd.fields map (fld => if (fld.id == f.id) WildcardPattern(Some(fBinders(f.id))) else @@ -112,7 +112,7 @@ object MethodLifting extends TransformationPhase { val fdParams = fd.params map { vd => val newId = FreshIdentifier(vd.id.name, tSubst(vd.id.getType)) - ValDef(newId).setPos(vd.getPos) + vd.copy(id = newId).setPos(vd.getPos) } val paramsMap = fd.params.zip(fdParams).map{ case (from, to) => from.id -> to.id }.toMap val eSubst: Expr => Expr = instantiateType(_, tMap, paramsMap) @@ -140,7 +140,7 @@ object MethodLifting extends TransformationPhase { val retType = instantiateType(fd.returnType, tparamsMap) val fdParams = fd.params map { vd => val newId = FreshIdentifier(vd.id.name, instantiateType(vd.id.getType, tparamsMap)) - ValDef(newId).setPos(vd.getPos) + vd.copy(id = newId).setPos(vd.getPos) } val receiver = FreshIdentifier("thiss", recType).setPos(cd.id) diff --git a/src/main/scala/leon/purescala/PrettyPrinter.scala b/src/main/scala/leon/purescala/PrettyPrinter.scala index 755c540d1f3b23c505488d49ecd945fa324d0080..039bef507339294874ad59e7e38dfe335b8f6a2f 100644 --- a/src/main/scala/leon/purescala/PrettyPrinter.scala +++ b/src/main/scala/leon/purescala/PrettyPrinter.scala @@ -11,7 +11,9 @@ import Extractors._ import PrinterHelpers._ import ExprOps.{isListLiteral, simplestValue} import Expressions._ +import Constructors._ import Types._ +import org.apache.commons.lang3.StringEscapeUtils case class PrinterContext( current: Tree, @@ -50,6 +52,8 @@ class PrettyPrinter(opts: PrinterOptions, p"${df.id}" } } + + private val dbquote = "\"" def pp(tree: Tree)(implicit ctx: PrinterContext): Unit = { @@ -87,9 +91,11 @@ class PrettyPrinter(opts: PrinterOptions, p"""|val $b = $d |$e""" - case LetDef(fd,body) => - p"""|$fd - |$body""" + case LetDef(a::q,body) => + p"""|$a + |${letDef(q, body)}""" + case LetDef(Nil,body) => + p"""$body""" case Require(pre, body) => p"""|require($pre) @@ -159,10 +165,23 @@ class PrettyPrinter(opts: PrinterOptions, case Or(exprs) => optP { p"${nary(exprs, "| || ")}" } // Ugliness award! The first | is there to shield from stripMargin() case Not(Equals(l, r)) => optP { p"$l \u2260 $r" } case Implies(l,r) => optP { p"$l ==> $r" } + case BVNot(expr) => p"~$expr" case UMinus(expr) => p"-$expr" case BVUMinus(expr) => p"-$expr" case RealUMinus(expr) => p"-$expr" case Equals(l,r) => optP { p"$l == $r" } + + + case Int32ToString(expr) => p"$expr.toString" + case BooleanToString(expr) => p"$expr.toString" + case IntegerToString(expr) => p"$expr.toString" + case CharToString(expr) => p"$expr.toString" + case RealToString(expr) => p"$expr.toString" + case StringConcat(lhs, rhs) => optP { p"$lhs + $rhs" } + + case SubString(expr, start, end) => p"leon.lang.StrOps.substring($expr, $start, $end)" + case StringLength(expr) => p"leon.lang.StrOps.length($expr)" + case IntLiteral(v) => p"$v" case InfiniteIntegerLiteral(v) => p"$v" case FractionalLiteral(n, d) => @@ -171,6 +190,13 @@ class PrettyPrinter(opts: PrinterOptions, case CharLiteral(v) => p"$v" case BooleanLiteral(v) => p"$v" case UnitLiteral() => p"()" + case StringLiteral(v) => + if(v.count(c => c == '\n') >= 1 && v.length >= 80 && v.indexOf("\"\"\"") == -1) { + p"$dbquote$dbquote$dbquote$v$dbquote$dbquote$dbquote" + } else { + val escaped = StringEscapeUtils.escapeJava(v) + p"$dbquote$escaped$dbquote" + } case GenericValue(tp, id) => p"$tp#$id" case Tuple(exprs) => p"($exprs)" case TupleSelect(t, i) => p"$t._$i" @@ -334,7 +360,7 @@ class PrettyPrinter(opts: PrinterOptions, case Not(expr) => p"\u00AC$expr" - case vd@ValDef(id, _) => + case vd @ ValDef(id) => p"$id : ${vd.getType}" vd.defaultValue.foreach { fd => p" = ${fd.body.get}" } @@ -429,6 +455,7 @@ class PrettyPrinter(opts: PrinterOptions, case RealType => p"Real" case CharType => p"Char" case BooleanType => p"Boolean" + case StringType => p"String" case ArrayType(bt) => p"Array[$bt]" case SetType(bt) => p"Set[$bt]" case MapType(ft,tt) => p"Map[$ft, $tt]" @@ -603,6 +630,7 @@ class PrettyPrinter(opts: PrinterOptions, case (e: Expr, _) if isSimpleExpr(e) => false case (e: Expr, Some(within: Expr)) if noBracesSub(within) contains e => false case (_: Expr, Some(_: MatchCase)) => false + case (_: LetDef, Some(_: LetDef)) => false case (e: Expr, Some(_)) => true case _ => false } @@ -623,11 +651,11 @@ class PrettyPrinter(opts: PrinterOptions, protected def requiresParentheses(ex: Tree, within: Option[Tree]): Boolean = (ex, within) match { case (pa: PrettyPrintable, _) => pa.printRequiresParentheses(within) case (_, None) => false - case (_, Some(_: Ensuring)) => false - case (_, Some(_: Assert)) => false - case (_, Some(_: Require)) => false - case (_, Some(_: Definition)) => false - case (_, Some(_: MatchExpr | _: MatchCase | _: Let | _: LetDef | _: IfExpr | _ : CaseClass | _ : Lambda)) => false + case (_, Some( + _: Ensuring | _: Assert | _: Require | _: Definition | _: MatchExpr | + _: MatchCase | _: Let | _: LetDef | _: IfExpr | _ : CaseClass | _ : Lambda | _ : Choose + )) => false + case (ex: StringConcat, Some(_: StringConcat)) => false case (b1 @ BinaryMethodCall(_, _, _), Some(b2 @ BinaryMethodCall(_, _, _))) if precedence(b1) > precedence(b2) => false case (BinaryMethodCall(_, _, _), Some(_: FunctionInvocation)) => true case (_, Some(_: FunctionInvocation)) => false diff --git a/src/main/scala/leon/purescala/ScalaPrinter.scala b/src/main/scala/leon/purescala/ScalaPrinter.scala index e030086b9ba4d0c73a3357878358ac58bb84f1fb..67cc994649e6b454ee8389fbfdd7674da4aebb6a 100644 --- a/src/main/scala/leon/purescala/ScalaPrinter.scala +++ b/src/main/scala/leon/purescala/ScalaPrinter.scala @@ -9,12 +9,14 @@ import Common._ import Expressions._ import Types._ import Definitions._ +import org.apache.commons.lang3.StringEscapeUtils /** This pretty-printer only print valid scala syntax */ class ScalaPrinter(opts: PrinterOptions, opgm: Option[Program], sb: StringBuffer = new StringBuffer) extends PrettyPrinter(opts, opgm, sb) { + private val dbquote = "\"" override def pp(tree: Tree)(implicit ctx: PrinterContext): Unit = { tree match { @@ -39,7 +41,6 @@ class ScalaPrinter(opts: PrinterOptions, case m @ FiniteMap(els, k, v) => p"Map[$k,$v]($els)" case InfiniteIntegerLiteral(v) => p"BigInt($v)" - case a@FiniteArray(elems, oDef, size) => import ExprOps._ val ArrayType(underlying) = a.getType diff --git a/src/main/scala/leon/purescala/ScopeSimplifier.scala b/src/main/scala/leon/purescala/ScopeSimplifier.scala index 9eca530580c248510f266520306834734d4746fa..f0ff379ffd6ec0419d631777c623098b21838a57 100644 --- a/src/main/scala/leon/purescala/ScopeSimplifier.scala +++ b/src/main/scala/leon/purescala/ScopeSimplifier.scala @@ -34,23 +34,32 @@ class ScopeSimplifier extends Transformer { val sb = rec(b, scope.register(i -> si)) Let(si, se, sb) - case LetDef(fd: FunDef, body: Expr) => - val newId = genId(fd.id, scope) - var newScope = scope.register(fd.id -> newId) - - val newArgs = for(ValDef(id, tpe) <- fd.params) yield { - val newArg = genId(id, newScope) - newScope = newScope.register(id -> newArg) - ValDef(newArg, tpe) + case LetDef(fds, body: Expr) => + var newScope: Scope = scope + // First register all functions + val fds_newIds = for(fd <- fds) yield { + val newId = genId(fd.id, scope) + newScope = newScope.register(fd.id -> newId) + (fd, newId) } - - val newFd = fd.duplicate(id = newId, params = newArgs) - - newScope = newScope.registerFunDef(fd -> newFd) - - newFd.fullBody = rec(fd.fullBody, newScope) - - LetDef(newFd, rec(body, newScope)) + + val fds_mapping = for((fd, newId) <- fds_newIds) yield { + val newArgs = for(ValDef(id) <- fd.params) yield { + val newArg = genId(id, newScope) + newScope = newScope.register(id -> newArg) + ValDef(newArg) + } + + val newFd = fd.duplicate(id = newId, params = newArgs) + + newScope = newScope.registerFunDef(fd -> newFd) + (newFd, fd) + } + + for((newFd, fd) <- fds_mapping) { + newFd.fullBody = rec(fd.fullBody, newScope) + } + LetDef(fds_mapping.map(_._1), rec(body, newScope)) case MatchExpr(scrut, cases) => val rs = rec(scrut, scope) diff --git a/src/main/scala/leon/purescala/SelfPrettyPrinter.scala b/src/main/scala/leon/purescala/SelfPrettyPrinter.scala new file mode 100644 index 0000000000000000000000000000000000000000..aa4c204d00187ab211a7164a45ae41d8d77d8f8f --- /dev/null +++ b/src/main/scala/leon/purescala/SelfPrettyPrinter.scala @@ -0,0 +1,58 @@ +package leon.purescala + +import leon.evaluators.StringTracingEvaluator +import leon.purescala +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.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 + +/** 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") && + program.callGraph.transitiveCallees(fd).forall { fde => + !purescala.ExprOps.exists( _.isInstanceOf[Choose])(fde.fullBody) + } + }) 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))) + + result.result match { + case Some((StringLiteral(res), _)) if res != "" => + res + case _ => + orElse + } + } catch { + case e: evaluators.ContextualEvaluator#EvalError => + orElse + } + case None => + orElse + } + } +} \ No newline at end of file diff --git a/src/main/scala/leon/purescala/TypeOps.scala b/src/main/scala/leon/purescala/TypeOps.scala index 51bed3eaf0f5fc2586f25fc7ff38cd6d8d3857b9..db655365c24a7304831e818849238bba0a849de9 100644 --- a/src/main/scala/leon/purescala/TypeOps.scala +++ b/src/main/scala/leon/purescala/TypeOps.scala @@ -167,6 +167,11 @@ object TypeOps { } } + def isParametricType(tpe: TypeTree): Boolean = tpe match { + case (tp: TypeParameter) => true + case NAryType(tps, builder) => tps.exists(isParametricType) + } + // Helpers for instantiateType private def typeParamSubst(map: Map[TypeParameter, TypeTree])(tpe: TypeTree): TypeTree = tpe match { case (tp: TypeParameter) => map.getOrElse(tp, tp) @@ -185,14 +190,6 @@ object TypeOps { freshId(id, typeParamSubst(tps map { case (tpd, tp) => tpd.tp -> tp })(id.getType)) } - def instantiateType(vd: ValDef, tps: Map[TypeParameterDef, TypeTree]): ValDef = { - val ValDef(id, forcedType) = vd - ValDef( - freshId(id, instantiateType(id.getType, tps)), - forcedType map ((tp: TypeTree) => instantiateType(tp, tps)) - ) - } - def instantiateType(tpe: TypeTree, tps: Map[TypeParameterDef, TypeTree]): TypeTree = { if (tps.isEmpty) { tpe @@ -307,32 +304,43 @@ object TypeOps { val newId = freshId(id, tpeSub(id.getType)) Let(newId, srec(value), rec(idsMap + (id -> newId))(body)).copiedFrom(l) - case l @ LetDef(fd, bd) => - val id = fd.id.freshen - val tparams = fd.tparams map { p => - TypeParameterDef(tpeSub(p.tp).asInstanceOf[TypeParameter]) + case l @ LetDef(fds, bd) => + val fds_mapping = for(fd <- fds) yield { + val id = fd.id.freshen + val tparams = fd.tparams map { p => + TypeParameterDef(tpeSub(p.tp).asInstanceOf[TypeParameter]) + } + val returnType = tpeSub(fd.returnType) + val params = fd.params map (vd => vd.copy(id = freshId(vd.id, tpeSub(vd.getType)))) + val newFd = fd.duplicate(id, tparams, params, returnType) + val subCalls = preMap { + case fi @ FunctionInvocation(tfd, args) if tfd.fd == fd => + Some(FunctionInvocation(newFd.typed(tfd.tps), args).copiedFrom(fi)) + case _ => + None + } _ + (fd, newFd, subCalls) + } + // We group the subcalls functions all in once + val subCalls = (((None:Option[Expr => Expr]) /: fds_mapping) { + case (None, (_, _, subCalls)) => Some(subCalls) + case (Some(fn), (_, _, subCalls)) => Some(fn andThen subCalls) + }).get + + // We apply all the functions mappings at once + val newFds = for((fd, newFd, _) <- fds_mapping) yield { + val fullBody = rec(idsMap ++ fd.paramIds.zip(newFd.paramIds))(subCalls(fd.fullBody)) + newFd.fullBody = fullBody + newFd } - val returnType = tpeSub(fd.returnType) - val params = fd.params map (instantiateType(_, tps)) - val newFd = fd.duplicate(id, tparams, params, returnType) - - val subCalls = preMap { - case fi @ FunctionInvocation(tfd, args) if tfd.fd == fd => - Some(FunctionInvocation(newFd.typed(tfd.tps), args).copiedFrom(fi)) - case _ => - None - } _ - val fullBody = rec(idsMap ++ fd.paramIds.zip(newFd.paramIds))(subCalls(fd.fullBody)) - newFd.fullBody = fullBody - val newBd = srec(subCalls(bd)).copiedFrom(bd) - LetDef(newFd, newBd).copiedFrom(l) + LetDef(newFds, newBd).copiedFrom(l) case l @ Lambda(args, body) => val newArgs = args.map { arg => val tpe = tpeSub(arg.getType) - ValDef(freshId(arg.id, tpe)) + arg.copy(id = freshId(arg.id, tpe)) } val mapping = args.map(_.id) zip newArgs.map(_.id) Lambda(newArgs, rec(idsMap ++ mapping)(body)).copiedFrom(l) @@ -340,7 +348,7 @@ object TypeOps { case f @ Forall(args, body) => val newArgs = args.map { arg => val tpe = tpeSub(arg.getType) - ValDef(freshId(arg.id, tpe)) + arg.copy(id = freshId(arg.id, tpe)) } val mapping = args.map(_.id) zip newArgs.map(_.id) Forall(newArgs, rec(idsMap ++ mapping)(body)).copiedFrom(f) diff --git a/src/main/scala/leon/purescala/Types.scala b/src/main/scala/leon/purescala/Types.scala index d39fda3338fbeab4dfa9c453a58afa6298bb6ccd..3a0a85bb24045df18ab65b0afa488a34c8921315 100644 --- a/src/main/scala/leon/purescala/Types.scala +++ b/src/main/scala/leon/purescala/Types.scala @@ -51,6 +51,7 @@ object Types { abstract class BitVectorType(val size: Int) extends TypeTree case object Int32Type extends BitVectorType(32) + case object StringType extends TypeTree class TypeParameter private (name: String) extends TypeTree { val id = FreshIdentifier(name, this) @@ -102,14 +103,20 @@ object Types { if (tmap.isEmpty) { classDef.fields } else { - // This is the only case where ValDef overrides the type of its Identifier - classDef.fields.map(vd => ValDef(vd.id, Some(instantiateType(vd.getType, tmap)))) + // !! WARNING !! + // vd.id changes but this should not be an issue as selector uses + // classDef.params ids which do not change! + classDef.fields.map { vd => + val newTpe = instantiateType(vd.getType, tmap) + val newId = FreshIdentifier(vd.id.name, newTpe).copiedFrom(vd.id) + vd.copy(id = newId).setPos(vd) + } } } def knownDescendants = classDef.knownDescendants.map( _.typed(tps) ) - def knownCCDescendants = classDef.knownCCDescendants.map( _.typed(tps) ) + def knownCCDescendants: Seq[CaseClassType] = classDef.knownCCDescendants.map( _.typed(tps) ) lazy val fieldsTypes = fields.map(_.getType) diff --git a/src/main/scala/leon/repair/RepairTrackingEvaluator.scala b/src/main/scala/leon/repair/RepairTrackingEvaluator.scala index fd12f7216f482fa50ce84816caa79fa8a450d689..429b34c19bbc20667bac9a6723c9c51740a57293 100644 --- a/src/main/scala/leon/repair/RepairTrackingEvaluator.scala +++ b/src/main/scala/leon/repair/RepairTrackingEvaluator.scala @@ -20,7 +20,7 @@ class RepairTrackingEvaluator(ctx: LeonContext, prog: Program) extends Recursive type RC = CollectingRecContext def initRC(mappings: Map[Identifier, Expr]) = CollectingRecContext(mappings, None) - + type FI = (FunDef, Seq[Expr]) // This is a call graph to track dependencies of function invocations. diff --git a/src/main/scala/leon/repair/Repairman.scala b/src/main/scala/leon/repair/Repairman.scala index 4959ae799c8f34b25dbdf1cac812ef8d954b4717..37f187679897bbec908ffeaf18f5385198f915c9 100644 --- a/src/main/scala/leon/repair/Repairman.scala +++ b/src/main/scala/leon/repair/Repairman.scala @@ -2,9 +2,11 @@ package leon package repair - + +import purescala.Common._ import purescala.Definitions._ import purescala.Expressions._ +import purescala.Extractors._ import purescala.ExprOps._ import purescala.Types._ import purescala.DefOps._ @@ -98,6 +100,60 @@ class Repairman(ctx0: LeonContext, initProgram: Program, fd: FunDef, verifTimeou bh.write() } + reporter.ifDebug { printer => + import java.io.FileWriter + import java.text.SimpleDateFormat + import java.util.Date + + val categoryName = fd.getPos.file.toString.split("/").dropRight(1).lastOption.getOrElse("?") + val benchName = categoryName+"."+fd.id.name + + val defs = visibleFunDefsFromMain(program).collect { + case fd: FunDef => 1 + fd.params.size + formulaSize(fd.fullBody) + } + + val pSize = defs.sum; + val fSize = formulaSize(fd.body.get) + + def localizedExprs(n: graph.Node): List[Expr] = { + n match { + case on: graph.OrNode => + on.selected.map(localizedExprs).flatten + case an: graph.AndNode if an.ri.rule == Focus => + an.selected.map(localizedExprs).flatten + case an: graph.AndNode => + val TopLevelAnds(clauses) = an.p.ws + + val res = clauses.collect { + case Guide(expr) => expr + } + + res.toList + } + } + + val locSize = localizedExprs(search.g.root).map(formulaSize).sum; + + val (solSize, proof) = solutions.headOption match { + case Some((sol, trusted)) => + val solExpr = sol.toSimplifiedExpr(ctx, program) + val totalSolSize = formulaSize(solExpr) + (locSize+totalSolSize-fSize, if (trusted) "$\\chmark$" else "") + case _ => + (0, "X") + } + + val date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + + val fw = new java.io.FileWriter("repair-report.txt", true) + + try { + fw.write(f"$date: $benchName%-30s & $pSize%4d & $fSize%4d & $locSize%4d & $solSize%4d & ${timeTests/1000.0}%2.1f & ${timeSynth/1000.0}%2.1f & $proof%7s \\\\\n") + } finally { + fw.close + } + }(DebugSectionReport) + if (synth.settings.generateDerivationTrees) { val dot = new DotGenerator(search.g) dot.writeFile("derivation"+ dotGenIds.nextGlobal + ".dot") @@ -186,7 +242,7 @@ class Repairman(ctx0: LeonContext, initProgram: Program, fd: FunDef, verifTimeou val maxValid = 400 val evaluator = new CodeGenEvaluator(ctx, program, CodeGenParams.default) - val enum = new MemoizedEnumerator[TypeTree, Expr](ValueGrammar.getProductions) + val enum = new MemoizedEnumerator[TypeTree, Expr, Generator[TypeTree, Expr]](ValueGrammar.getProductions) val inputs = enum.iterator(tupleTypeWrap(fd.params map { _.getType})).map(unwrapTuple(_, fd.params.size)) diff --git a/src/main/scala/leon/solvers/EnumerationSolver.scala b/src/main/scala/leon/solvers/EnumerationSolver.scala index d7d52d371ab45f7a0cbddab50bab14bc552de62e..a9bbe8b45823cde5776d67155642c6f819d3a76b 100644 --- a/src/main/scala/leon/solvers/EnumerationSolver.scala +++ b/src/main/scala/leon/solvers/EnumerationSolver.scala @@ -48,6 +48,7 @@ class EnumerationSolver(val context: LeonContext, val program: Program) extends private var model = Model.empty + /** @inheritdoc */ def check: Option[Boolean] = { val timer = context.timers.solvers.enum.check.start() val res = try { @@ -78,6 +79,7 @@ class EnumerationSolver(val context: LeonContext, val program: Program) extends res } + /** @inheritdoc */ def getModel: Model = { model } diff --git a/src/main/scala/leon/solvers/Solver.scala b/src/main/scala/leon/solvers/Solver.scala index bc58705858d6ef833e67d0d701438be859c2915e..c8e4bd9b95c0b25f5fbf0c869062ac89dbcc7728 100644 --- a/src/main/scala/leon/solvers/Solver.scala +++ b/src/main/scala/leon/solvers/Solver.scala @@ -23,7 +23,9 @@ trait Solver extends Interruptible { assertCnstr(Not(vc.condition)) } + /** Returns Some(true) if it found a satisfying model, Some(false) if no model exists, and None otherwise */ def check: Option[Boolean] + /** Returns the model if it exists */ def getModel: Model def getResultSolver: Option[Solver] = Some(this) diff --git a/src/main/scala/leon/solvers/SolverFactory.scala b/src/main/scala/leon/solvers/SolverFactory.scala index 99d5abc48d07397fe35162d766e0d6d4a5094a35..67d28f877019a3e4741df6d268c68fc2d86d17ee 100644 --- a/src/main/scala/leon/solvers/SolverFactory.scala +++ b/src/main/scala/leon/solvers/SolverFactory.scala @@ -134,14 +134,32 @@ object SolverFactory { // Fast solver used by simplifications, to discharge simple tautologies def uninterpreted(ctx: LeonContext, program: Program): SolverFactory[TimeoutSolver] = { - if (hasNativeZ3) { - SolverFactory(() => new UninterpretedZ3Solver(ctx, program) with TimeoutSolver) - } else { - if (!reported) { - ctx.reporter.warning("The Z3 native interface is not available, falling back to smt-based solver.") - reported = true + val names = ctx.findOptionOrDefault(SharedOptions.optSelectedSolvers) + + if ((names contains "fairz3") && !hasNativeZ3) { + if (hasZ3) { + if (!reported) { + ctx.reporter.warning("The Z3 native interface is not available, falling back to smt-z3.") + reported = true + } + SolverFactory(() => new SMTLIBZ3Solver(ctx, program) with TimeoutSolver) + } else if (hasCVC4) { + if (!reported) { + ctx.reporter.warning("The Z3 native interface is not available, falling back to smt-cvc4.") + reported = true + } + SolverFactory(() => new SMTLIBCVC4Solver(ctx, program) with TimeoutSolver) + } else { + ctx.reporter.fatalError("No SMT solver available: native Z3 api could not load and 'cvc4' or 'z3' binaries were not found in PATH.") } + } else if(names contains "smt-cvc4") { + SolverFactory(() => new SMTLIBCVC4Solver(ctx, program) with TimeoutSolver) + } else if(names contains "smt-z3") { SolverFactory(() => new SMTLIBZ3Solver(ctx, program) with TimeoutSolver) + } else if ((names contains "fairz3") && hasNativeZ3) { + SolverFactory(() => new UninterpretedZ3Solver(ctx, program) with TimeoutSolver) + } else { + ctx.reporter.fatalError("No SMT solver available: native Z3 api could not load and 'cvc4' or 'z3' binaries were not found in PATH.") } } @@ -159,7 +177,7 @@ object SolverFactory { } lazy val hasZ3 = try { - Z3Interpreter.buildDefault.free() + new Z3Interpreter("z3", Array("-in", "-smt2")) true } catch { case e: java.io.IOException => @@ -167,7 +185,7 @@ object SolverFactory { } lazy val hasCVC4 = try { - CVC4Interpreter.buildDefault.free() + new CVC4Interpreter("cvc4", Array("-q", "--lang", "smt2.5")) true } catch { case e: java.io.IOException => diff --git a/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala b/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala index b9c0dfd94e449ac5a7d130e1279da8ebb7c19b92..1235d69c6d16a2726971cac386732fdcea501b95 100644 --- a/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala +++ b/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala @@ -16,6 +16,7 @@ import utils._ import z3.FairZ3Component.{optFeelingLucky, optUseCodeGen, optAssumePre, optNoChecks, optUnfoldFactor} import templates._ import evaluators._ +import Template._ class UnrollingSolver(val context: LeonContext, val program: Program, underlying: Solver) extends Solver @@ -116,7 +117,7 @@ class UnrollingSolver(val context: LeonContext, val program: Program, underlying val optEnabler = evaluator.eval(b, model).result if (optEnabler == Some(BooleanLiteral(true))) { - val optArgs = m.args.map(arg => evaluator.eval(Matcher.argValue(arg), model).result) + val optArgs = m.args.map(arg => evaluator.eval(arg.encoded, model).result) if (optArgs.forall(_.isDefined)) { Set(optArgs.map(_.get)) } else { diff --git a/src/main/scala/leon/solvers/isabelle/AdaptationPhase.scala b/src/main/scala/leon/solvers/isabelle/AdaptationPhase.scala index 911af8d84b6b6ccc94dd6ed781a77ca99e8a7771..2d3218c7b09edfa465618117a66b7f428f755b1c 100644 --- a/src/main/scala/leon/solvers/isabelle/AdaptationPhase.scala +++ b/src/main/scala/leon/solvers/isabelle/AdaptationPhase.scala @@ -26,7 +26,7 @@ object AdaptationPhase extends TransformationPhase { CaseClassType(dummy, List(tp)) def mkDummyParameter(tp: TypeParameter) = - ValDef(FreshIdentifier("dummy", mkDummyTyp(tp)), Some(mkDummyTyp(tp))) + ValDef(FreshIdentifier("dummy", mkDummyTyp(tp))) def mkDummyArgument(tree: TypeTree) = CaseClass(CaseClassType(dummy, List(tree)), Nil) diff --git a/src/main/scala/leon/solvers/isabelle/Component.scala b/src/main/scala/leon/solvers/isabelle/Component.scala index 3b75a8c64fb7a68db86fc30456a53d8df8e2626d..4f66f3a6da61dbfbe6be5e9c335dcb9633981bcd 100644 --- a/src/main/scala/leon/solvers/isabelle/Component.scala +++ b/src/main/scala/leon/solvers/isabelle/Component.scala @@ -8,6 +8,8 @@ import scala.concurrent.ExecutionContext.Implicits.global import leon._ +import edu.tum.cs.isabelle.setup._ + object Component extends LeonComponent { val name = "Isabelle" @@ -16,13 +18,13 @@ object Component extends LeonComponent { val leonBase = Paths.get(Option(System.getProperty("leon.base")).getOrElse(".")).toAbsolutePath() - val isabelleBase = - leonBase.resolve("contrib").toAbsolutePath() + val platform = + Platform.guess.getOrElse(Platform.genericPlatform("generic", leonBase.resolve("contrib").toAbsolutePath())) val optBase = LeonStringOptionDef( name = "isabelle:base", description = "Base directory of the Isabelle installation", - default = isabelleBase.toString, + default = platform.setupStorage.toString, usageRhs = "path" ) diff --git a/src/main/scala/leon/solvers/isabelle/IsabelleEnvironment.scala b/src/main/scala/leon/solvers/isabelle/IsabelleEnvironment.scala index 33094b93682b53bf6a7e92b8d63280c810ded523..5856374e289d5f39c9669ec93ba6bef0e91e2693 100644 --- a/src/main/scala/leon/solvers/isabelle/IsabelleEnvironment.scala +++ b/src/main/scala/leon/solvers/isabelle/IsabelleEnvironment.scala @@ -16,7 +16,7 @@ import leon.purescala.Common._ import leon.solvers._ import leon.utils._ -import edu.tum.cs.isabelle.{impl => impl2015, _} +import edu.tum.cs.isabelle._ import edu.tum.cs.isabelle.api._ import edu.tum.cs.isabelle.setup._ @@ -51,28 +51,35 @@ object IsabelleEnvironment { } }.toList - val setup = Setup.detectSetup(base, version) match { - case Some(setup) => Future.successful { setup } - case None if !download => + val home = base.resolve(s"Isabelle${version.identifier}") + + val setup = + if (Files.isDirectory(home)) + Future.successful { Setup(home, Component.platform, version) } + else if (!download) context.reporter.fatalError(s"No $version found at $base. Please install manually or set '${Component.optDownload.name}' flag to true.") - case _ => - context.reporter.info(s"No $version found at $base") - context.reporter.info(s"Preparing $version environment ...") - Setup.installTo(Files.createDirectories(base), version) - } + else + Component.platform match { + case o: OfficialPlatform => + context.reporter.info(s"No $version found at $base") + context.reporter.info(s"Preparing $version environment ...") + Setup.install(o, version) + case _ => + context.reporter.fatalError(s"No $version found at $base. Platform unsupported, please install manually.") + } val system = setup.flatMap { setup => - val env = new impl2015.Environment(setup.home) - val config = env.Configuration.fromPath(Component.leonBase, "Leon") + val env = Implementations.makeEnvironment(setup.home, classOf[edu.tum.cs.isabelle.impl.Environment]) + val config = Configuration.fromPath(Component.leonBase, "Leon") if (build) { context.reporter.info(s"Building session ...") - if (!System.build(env)(config)) + if (!System.build(env, config)) context.reporter.internalError("Build failed") } context.reporter.info(s"Starting $version instance ...") - System.create(env)(config) + System.create(env, config) } val thy = system.flatMap { system => diff --git a/src/main/scala/leon/solvers/isabelle/IsabelleSolver.scala b/src/main/scala/leon/solvers/isabelle/IsabelleSolver.scala index 40c754bc62b26a5bea51c9b00a84f5fb0d868efe..3b3e81860e38a1da24d0ce41f2d53a73a449732d 100644 --- a/src/main/scala/leon/solvers/isabelle/IsabelleSolver.scala +++ b/src/main/scala/leon/solvers/isabelle/IsabelleSolver.scala @@ -14,7 +14,6 @@ import leon.utils.Interruptible import leon.verification.VC import edu.tum.cs.isabelle._ -import edu.tum.cs.isabelle.api.ProverResult class IsabelleSolver(val context: LeonContext, program: Program, types: Types, functions: Functions, system: System) extends Solver with Interruptible { self: TimeoutSolver => diff --git a/src/main/scala/leon/solvers/isabelle/Types.scala b/src/main/scala/leon/solvers/isabelle/Types.scala index 0e609155104023776b5c53934db9cc15cb09c811..7d2b4d1d8d54667343ae8e6d6a458130c5baef13 100644 --- a/src/main/scala/leon/solvers/isabelle/Types.scala +++ b/src/main/scala/leon/solvers/isabelle/Types.scala @@ -127,6 +127,9 @@ final class Types(context: LeonContext, program: Program, system: System)(implic case Some(datatype) => datatype.typ }}.map { Type(_, args) } } + case StringType => + context.reporter.warning("Strings are not yet supported, translating to unspecified type") + Future.successful { Type("Leon_Library.string", Nil) } case _ if strict => context.reporter.fatalError(s"Unsupported type $tree, can't be inferred") case _ => diff --git a/src/main/scala/leon/solvers/isabelle/package.scala b/src/main/scala/leon/solvers/isabelle/package.scala index 421d64091da4f2c125dd25a2a52c611210e0e7c3..26f01fb5caa1afaae1a14d7d910ae9ff6045eef2 100644 --- a/src/main/scala/leon/solvers/isabelle/package.scala +++ b/src/main/scala/leon/solvers/isabelle/package.scala @@ -12,7 +12,6 @@ import leon.purescala.Expressions._ import leon.verification.VC import edu.tum.cs.isabelle._ -import edu.tum.cs.isabelle.api.ProverResult object `package` { diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4CounterExampleSolver.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4CounterExampleSolver.scala index 135d47ebcb1bb0a492d46258b81e840d0916c75f..3a64bd19737e9f7c7f15c78c0044a71f49721df8 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4CounterExampleSolver.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4CounterExampleSolver.scala @@ -15,7 +15,7 @@ class SMTLIBCVC4CounterExampleSolver(context: LeonContext, program: Program) ext "--rewrite-divk", "--produce-models", "--print-success", - "--lang", "smt", + "--lang", "smt2.5", "--fmf-fun" ) ++ userDefinedOps(ctx) } diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4ProofSolver.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4ProofSolver.scala index f02cce59c78a4e81b3071248b747127fdce4797d..eaf78aca5e78cb49fe1ccf3bb7d07556514c61d0 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4ProofSolver.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4ProofSolver.scala @@ -17,7 +17,7 @@ class SMTLIBCVC4ProofSolver(context: LeonContext, program: Program) extends SMTL Seq( "-q", "--print-success", - "--lang", "smt", + "--lang", "smt2.5", "--quant-ind", "--rewrite-divk", "--conjecture-gen", diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Solver.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Solver.scala index dc267825c8356451944b5fb6b7f9de5d55504232..b71af967f9671fd13f7c6a4625e592649fb470ba 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Solver.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Solver.scala @@ -19,11 +19,13 @@ class SMTLIBCVC4Solver(context: LeonContext, program: Program) extends SMTLIBSol Seq( "-q", "--produce-models", - "--no-incremental", - "--tear-down-incremental", + "--incremental", +// "--no-incremental", +// "--tear-down-incremental", +// "--dt-rewrite-error-sel", // Removing since it causes CVC4 to segfault on some inputs "--rewrite-divk", "--print-success", - "--lang", "smt" + "--lang", "smt2.5" ) ++ userDefinedOps(ctx).toSeq } } diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Target.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Target.scala index f1cf73142d51debaeda0fc5064096e522f049955..87cc849b41d2507d7e0ca53a351b2f605d387b46 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Target.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Target.scala @@ -14,6 +14,7 @@ import _root_.smtlib.parser.Terms.{Identifier => SMTIdentifier, Forall => SMTFor import _root_.smtlib.parser.Commands._ import _root_.smtlib.interpreters.CVC4Interpreter import _root_.smtlib.theories.experimental.Sets +import _root_.smtlib.theories.experimental.Strings trait SMTLIBCVC4Target extends SMTLIBTarget { @@ -30,7 +31,7 @@ trait SMTLIBCVC4Target extends SMTLIBTarget { tpe match { case SetType(base) => Sets.SetSort(declareSort(base)) - + case StringType => Strings.StringSort() case _ => super.declareSort(t) } @@ -109,6 +110,31 @@ trait SMTLIBCVC4Target extends SMTLIBTarget { case FiniteSet(elems, _) => elems }).toSet, base) + case (SString(v), Some(StringType)) => + StringLiteral(v) + + case (Strings.Length(a), _) => + val aa = fromSMT(a) + StringLength(aa) + + case (Strings.Concat(a, b, c @ _*), _) => + val aa = fromSMT(a) + val bb = fromSMT(b) + (StringConcat(aa, bb) /: c.map(fromSMT(_))) { + case (s, cc) => StringConcat(s, cc) + } + + case (Strings.Substring(s, start, offset), _) => + val ss = fromSMT(s) + val tt = fromSMT(start) + val oo = fromSMT(offset) + oo match { + case Minus(otherEnd, `tt`) => SubString(ss, tt, otherEnd) + case _ => SubString(ss, tt, Plus(tt, oo)) + } + + case (Strings.At(a, b), _) => fromSMT(Strings.Substring(a, b, SNumeral(1))) + case _ => super.fromSMT(t, otpe) } @@ -138,7 +164,14 @@ trait SMTLIBCVC4Target extends SMTLIBTarget { case SetDifference(a, b) => Sets.Setminus(toSMT(a), toSMT(b)) case SetUnion(a, b) => Sets.Union(toSMT(a), toSMT(b)) case SetIntersection(a, b) => Sets.Intersection(toSMT(a), toSMT(b)) - + case StringLiteral(v) => + declareSort(StringType) + Strings.StringLit(v) + case StringLength(a) => Strings.Length(toSMT(a)) + case StringConcat(a, b) => Strings.Concat(toSMT(a), toSMT(b)) + case SubString(a, start, Plus(start2, length)) if start == start2 => + Strings.Substring(toSMT(a),toSMT(start),toSMT(length)) + case SubString(a, start, end) => Strings.Substring(toSMT(a),toSMT(start),toSMT(Minus(end, start))) case _ => super.toSMT(e) } diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBQuantifiedTarget.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBQuantifiedTarget.scala index d49e3316716ae2277af5ea35c6115d076e7029bd..d52ac0704e65d119bf3f81c4a30d58723f9aea81 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBQuantifiedTarget.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBQuantifiedTarget.scala @@ -9,8 +9,6 @@ import purescala.Definitions._ import purescala.Constructors._ import purescala.ExprOps._ -import _root_.smtlib.parser.Commands.{Assert => _, FunDef => _, _} - trait SMTLIBQuantifiedTarget extends SMTLIBTarget { protected var currentFunDef: Option[FunDef] = None @@ -30,14 +28,10 @@ trait SMTLIBQuantifiedTarget extends SMTLIBTarget { val inductiveHyps = for { fi@FunctionInvocation(tfd, args) <- functionCallsOf(cond).toSeq } yield { - val formalToRealArgs = tfd.paramIds.zip(args).toMap - val post = tfd.postcondition map { post => - application( - replaceFromIDs(formalToRealArgs, post), - Seq(fi) - ) - } getOrElse BooleanLiteral(true) - + val post = application( + tfd.withParamSubst(args, tfd.postOrTrue), + Seq(fi) + ) and(tfd.precOrTrue, post) } diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBSolver.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBSolver.scala index f13ed21a2929a0b845e9c0138092bb36b5fc6428..72ecfb9d34039ad03675d2d5523c34462f9de20e 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBSolver.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBSolver.scala @@ -9,7 +9,7 @@ import purescala.Expressions._ import purescala.ExprOps._ import purescala.Definitions._ -import _root_.smtlib.parser.Commands.{Assert => SMTAssert, _} +import _root_.smtlib.parser.Commands.{Assert => SMTAssert, FunDef => SMTFunDef, _} import _root_.smtlib.parser.Terms.{Identifier => SMTIdentifier, _} import _root_.smtlib.parser.CommandsResponses.{Error => ErrorResponse, _} @@ -62,27 +62,37 @@ abstract class SMTLIBSolver(val context: LeonContext, val program: Program) } protected def getModel(filter: Identifier => Boolean): Model = { - val syms = variables.aSet.filter(filter).toList.map(variables.aToB) + val syms = variables.aSet.filter(filter).map(variables.aToB) if (syms.isEmpty) { Model.empty } else { try { - val cmd: Command = GetValue( - syms.head, - syms.tail.map(s => QualifiedIdentifier(SMTIdentifier(s))) - ) + val cmd = GetModel() emit(cmd) match { - case GetValueResponseSuccess(valuationPairs) => + case GetModelResponseSuccess(smodel) => + var modelFunDefs = Map[SSymbol, DefineFun]() - new Model(valuationPairs.collect { - case (SimpleSymbol(sym), value) if variables.containsB(sym) => - val id = variables.toA(sym) + // first-pass to gather functions + for (me <- smodel) me match { + case me @ DefineFun(SMTFunDef(a, args, _, _)) if args.nonEmpty => + modelFunDefs += a -> me + case _ => + } + + var model = Map[Identifier, Expr]() + + for (me <- smodel) me match { + case DefineFun(SMTFunDef(s, args, kind, e)) if syms(s) => + val id = variables.toA(s) + model += id -> fromSMT(e, id.getType)(Map(), modelFunDefs) + case _ => + } + + new Model(model) - (id, fromSMT(value, id.getType)(Map(), Map())) - }.toMap) case _ => - Model.empty //FIXME improve this + Model.empty // FIXME improve this } } catch { case e : SMTLIBUnsupportedError => @@ -100,6 +110,7 @@ abstract class SMTLIBSolver(val context: LeonContext, val program: Program) variables.push() genericValues.push() sorts.push() + lambdas.push() functions.push() errors.push() @@ -113,6 +124,7 @@ abstract class SMTLIBSolver(val context: LeonContext, val program: Program) variables.pop() genericValues.pop() sorts.pop() + lambdas.pop() functions.pop() errors.pop() diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBTarget.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBTarget.scala index 07c2eaa567dd41aca7e8f239910b9856ae3ad268..47017bcf1471770f1b1e9fb574a81bed34f4c515 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBTarget.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBTarget.scala @@ -11,6 +11,7 @@ import purescala.Expressions._ import purescala.Extractors._ import purescala.ExprOps._ import purescala.Types._ +import purescala.TypeOps._ import purescala.Constructors._ import purescala.Definitions._ @@ -18,7 +19,7 @@ import _root_.smtlib.common._ import _root_.smtlib.printer.{ RecursivePrinter => SMTPrinter } import _root_.smtlib.parser.Commands.{ Constructor => SMTConstructor, - FunDef => _, + FunDef => SMTFunDef, Assert => _, _ } @@ -31,6 +32,7 @@ import _root_.smtlib.parser.Terms.{ } import _root_.smtlib.parser.CommandsResponses.{ Error => ErrorResponse, _ } import _root_.smtlib.theories._ +import _root_.smtlib.theories.experimental._ import _root_.smtlib.interpreters.ProcessInterpreter trait SMTLIBTarget extends Interruptible { @@ -124,6 +126,7 @@ trait SMTLIBTarget extends Interruptible { /* Symbol handling */ protected object SimpleSymbol { + def apply(sym: SSymbol) = QualifiedIdentifier(SMTIdentifier(sym)) def unapply(term: Term): Option[SSymbol] = term match { case QualifiedIdentifier(SMTIdentifier(sym, Seq()), None) => Some(sym) case _ => None @@ -131,9 +134,7 @@ trait SMTLIBTarget extends Interruptible { } import scala.language.implicitConversions - protected implicit def symbolToQualifiedId(s: SSymbol): QualifiedIdentifier = { - QualifiedIdentifier(SMTIdentifier(s)) - } + protected implicit def symbolToQualifiedId(s: SSymbol): QualifiedIdentifier = SimpleSymbol(s) protected val adtManager = new ADTManager(context) @@ -147,14 +148,15 @@ trait SMTLIBTarget extends Interruptible { protected def freshSym(name: String): SSymbol = id2sym(FreshIdentifier(name)) /* Metadata for CC, and variables */ - protected val constructors = new IncrementalBijection[TypeTree, SSymbol]() - protected val selectors = new IncrementalBijection[(TypeTree, Int), SSymbol]() - protected val testers = new IncrementalBijection[TypeTree, SSymbol]() - protected val variables = new IncrementalBijection[Identifier, SSymbol]() + protected val constructors = new IncrementalBijection[TypeTree, SSymbol]() + protected val selectors = new IncrementalBijection[(TypeTree, Int), SSymbol]() + protected val testers = new IncrementalBijection[TypeTree, SSymbol]() + protected val variables = new IncrementalBijection[Identifier, SSymbol]() protected val genericValues = new IncrementalBijection[GenericValue, SSymbol]() - protected val sorts = new IncrementalBijection[TypeTree, Sort]() - protected val functions = new IncrementalBijection[TypedFunDef, SSymbol]() - protected val errors = new IncrementalBijection[Unit, Boolean]() + protected val sorts = new IncrementalBijection[TypeTree, Sort]() + protected val functions = new IncrementalBijection[TypedFunDef, SSymbol]() + protected val lambdas = new IncrementalBijection[FunctionType, SSymbol]() + protected val errors = new IncrementalBijection[Unit, Boolean]() protected def hasError = errors.getB(()) contains true protected def addError() = errors += () -> true @@ -247,7 +249,7 @@ trait SMTLIBTarget extends Interruptible { declareSort(RawArrayType(from, library.optionType(to))) case FunctionType(from, to) => - Sort(SMTIdentifier(SSymbol("Array")), Seq(declareSort(tupleTypeWrap(from)), declareSort(to))) + Ints.IntSort() case tp: TypeParameter => declareUninterpretedSort(tp) @@ -330,6 +332,20 @@ trait SMTLIBTarget extends Interruptible { } } + protected def declareLambda(tpe: FunctionType): SSymbol = { + val realTpe = bestRealType(tpe).asInstanceOf[FunctionType] + lambdas.cachedB(realTpe) { + val id = FreshIdentifier("dynLambda") + val s = id2sym(id) + emit(DeclareFun( + s, + (realTpe +: realTpe.from).map(declareSort), + declareSort(realTpe.to) + )) + s + } + } + /* Translate a Leon Expr to an SMTLIB term */ def sortToSMT(s: Sort): SExpr = { @@ -523,7 +539,8 @@ trait SMTLIBTarget extends Interruptible { * ===== Everything else ===== */ case ap @ Application(caller, args) => - ArraysEx.Select(toSMT(caller), toSMT(tupleWrap(args))) + val dyn = declareLambda(caller.getType.asInstanceOf[FunctionType]) + FunctionApplication(dyn, (caller +: args).map(toSMT)) case Not(u) => Core.Not(toSMT(u)) case UMinus(u) => Ints.Neg(toSMT(u)) @@ -612,6 +629,92 @@ trait SMTLIBTarget extends Interruptible { /* Translate an SMTLIB term back to a Leon Expr */ protected def fromSMT(t: Term, otpe: Option[TypeTree] = None)(implicit lets: Map[SSymbol, Term], letDefs: Map[SSymbol, DefineFun]): Expr = { + object EQ { + def unapply(t: Term): Option[(Term, Term)] = t match { + case Core.Equals(e1, e2) => Some((e1, e2)) + case FunctionApplication(f, Seq(e1, e2)) if f.toString == "=" => Some((e1, e2)) + case _ => None + } + } + + object AND { + def unapply(t: Term): Option[Seq[Term]] = t match { + case Core.And(e1, e2) => Some(Seq(e1, e2)) + case FunctionApplication(SimpleSymbol(SSymbol("and")), args) => Some(args) + case _ => None + } + def apply(ts: Seq[Term]): Term = ts match { + case Seq() => throw new IllegalArgumentException + case Seq(t) => t + case _ => FunctionApplication(SimpleSymbol(SSymbol("and")), ts) + } + } + + object Num { + def unapply(t: Term): Option[BigInt] = t match { + case SNumeral(n) => Some(n) + case FunctionApplication(f, Seq(SNumeral(n))) if f.toString == "-" => Some(-n) + case _ => None + } + } + + def extractLambda(n: BigInt, ft: FunctionType): Expr = { + val FunctionType(from, to) = ft + lambdas.getB(ft) match { + case None => simplestValue(ft) + case Some(dynLambda) => letDefs.get(dynLambda) match { + case None => simplestValue(ft) + case Some(DefineFun(SMTFunDef(a, SortedVar(dispatcher, dkind) +: args, rkind, body))) => + val lambdaArgs = from.map(tpe => FreshIdentifier("x", tpe, true)) + val argsMap: Map[Term, Identifier] = (args.map(sv => symbolToQualifiedId(sv.name)) zip lambdaArgs).toMap + + val d = symbolToQualifiedId(dispatcher) + def dispatch(t: Term): Term = t match { + case Core.ITE(EQ(di, Num(ni)), thenn, elze) if di == d => + if (ni == n) thenn else dispatch(elze) + case Core.ITE(AND(EQ(di, Num(ni)) +: rest), thenn, elze) if di == d => + if (ni == n) Core.ITE(AND(rest), thenn, dispatch(elze)) else dispatch(elze) + case _ => t + } + + def extract(t: Term): Expr = { + def recCond(term: Term): Seq[Expr] = term match { + case AND(es) => + es.foldLeft(Seq.empty[Expr]) { + case (seq, e) => seq ++ recCond(e) + } + case EQ(e1, e2) => + argsMap.get(e1).map(l => l -> e2) orElse argsMap.get(e2).map(l => l -> e1) match { + case Some((lambdaArg, term)) => Seq(Equals(lambdaArg.toVariable, fromSMT(term, lambdaArg.getType))) + case _ => Seq.empty + } + case arg => + argsMap.get(arg) match { + case Some(lambdaArg) => Seq(lambdaArg.toVariable) + case _ => Seq.empty + } + } + + def recCases(term: Term): Expr = term match { + case Core.ITE(cond, thenn, elze) => + IfExpr(andJoin(recCond(cond)), recCases(thenn), recCases(elze)) + case AND(es) if to == BooleanType => + andJoin(recCond(term)) + case EQ(e1, e2) if to == BooleanType => + andJoin(recCond(term)) + case _ => + fromSMT(term, to) + } + + val body = recCases(t) + Lambda(lambdaArgs.map(ValDef(_)), body) + } + + extract(dispatch(body)) + } + } + } + // Use as much information as there is, if there is an expected type, great, but it might not always be there (t, otpe) match { case (_, Some(UnitType)) => @@ -645,9 +748,12 @@ trait SMTLIBTarget extends Interruptible { } } + case (Num(n), Some(ft: FunctionType)) => + extractLambda(n, ft) + case (SNumeral(n), Some(RealType)) => FractionalLiteral(n, 1) - + case (FunctionApplication(SimpleSymbol(SSymbol("ite")), Seq(cond, thenn, elze)), t) => IfExpr( fromSMT(cond, Some(BooleanType)), @@ -696,11 +802,13 @@ trait SMTLIBTarget extends Interruptible { val rargs = args.zip(tt.bases).map(fromSMT) tupleWrap(rargs) - case ArrayType(baseType) => + case at@ArrayType(baseType) => val IntLiteral(size) = fromSMT(args(0), Int32Type) val RawArrayValue(_, elems, default) = fromSMT(args(1), RawArrayType(Int32Type, baseType)) - if (size > 10) { + if (size < 0) { + unsupported(at, "Cannot build an array of negative length: " + size) + } else if (size > 10) { val definedElements = elems.collect { case (IntLiteral(i), value) => (i, value) } diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Solver.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Solver.scala index 466e49c0cbd42ebeaf3336248536b8bb79da5191..dc56d76d7037ae3c0c14907bdecb38dff92a99a8 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Solver.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Solver.scala @@ -15,6 +15,8 @@ import _root_.smtlib.theories.Core.{Equals => _, _} class SMTLIBZ3Solver(context: LeonContext, program: Program) extends SMTLIBSolver(context, program) with SMTLIBZ3Target { + def getProgram: Program = program + // EK: We use get-model instead in order to extract models for arrays override def getModel: Model = { diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Target.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Target.scala index f9f8e386c628c3550983afece5a984197e3e07ba..2d0ac830bcd2773e65e0a3cf5f6b57c753fdd8ca 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Target.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Target.scala @@ -8,15 +8,15 @@ import purescala.Common._ import purescala.Expressions._ import purescala.Constructors._ import purescala.Types._ - +import purescala.Definitions._ import _root_.smtlib.parser.Terms.{Identifier => SMTIdentifier, _} import _root_.smtlib.parser.Commands.{FunDef => SMTFunDef, _} import _root_.smtlib.interpreters.Z3Interpreter import _root_.smtlib.theories.Core.{Equals => SMTEquals, _} import _root_.smtlib.theories.ArraysEx +import leon.solvers.z3.Z3StringConversion -trait SMTLIBZ3Target extends SMTLIBTarget { - +trait SMTLIBZ3Target extends SMTLIBTarget with Z3StringConversion[Term] { def targetName = "z3" def interpreterOps(ctx: LeonContext) = { @@ -40,11 +40,11 @@ trait SMTLIBZ3Target extends SMTLIBTarget { override protected def declareSort(t: TypeTree): Sort = { val tpe = normalizeType(t) sorts.cachedB(tpe) { - tpe match { + convertType(tpe) match { case SetType(base) => super.declareSort(BooleanType) declareSetSort(base) - case _ => + case t => super.declareSort(t) } } @@ -69,9 +69,13 @@ trait SMTLIBZ3Target extends SMTLIBTarget { Sort(SMTIdentifier(setSort.get), Seq(declareSort(of))) } - override protected def fromSMT(t: Term, otpe: Option[TypeTree] = None) + override protected def fromSMT(t: Term, expected_otpe: Option[TypeTree] = None) (implicit lets: Map[SSymbol, Term], letDefs: Map[SSymbol, DefineFun]): Expr = { - (t, otpe) match { + val otpe = expected_otpe match { + case Some(StringType) => Some(listchar) + case _ => expected_otpe + } + val res = (t, otpe) match { case (SimpleSymbol(s), Some(tp: TypeParameter)) => val n = s.name.split("!").toList.last GenericValue(tp, n.toInt) @@ -96,6 +100,16 @@ trait SMTLIBZ3Target extends SMTLIBTarget { case _ => super.fromSMT(t, otpe) } + expected_otpe match { + case Some(StringType) => + StringLiteral(convertToString(res)(program)) + case _ => res + } + } + + def convertToTarget(e: Expr)(implicit bindings: Map[Identifier, Term]): Term = toSMT(e) + def targetApplication(tfd: TypedFunDef, args: Seq[Term])(implicit bindings: Map[Identifier, Term]): Term = { + FunctionApplication(declareFunction(tfd), args) } override protected def toSMT(e: Expr)(implicit bindings: Map[Identifier, Term]): Term = e match { @@ -132,6 +146,7 @@ trait SMTLIBZ3Target extends SMTLIBTarget { case SetIntersection(l, r) => ArrayMap(SSymbol("and"), toSMT(l), toSMT(r)) + case StringConverted(result) => result case _ => super.toSMT(e) } diff --git a/src/main/scala/leon/solvers/string/StringSolver.scala b/src/main/scala/leon/solvers/string/StringSolver.scala new file mode 100644 index 0000000000000000000000000000000000000000..d681d500f42611e19b241ddb5f22425eef8dad33 --- /dev/null +++ b/src/main/scala/leon/solvers/string/StringSolver.scala @@ -0,0 +1,629 @@ +package leon.solvers.string + +import leon.purescala.Common._ +import leon.purescala.Definitions._ +import leon.purescala.Expressions._ +import leon.solvers.Solver +import leon.utils.Interruptible +import leon.LeonContext +import scala.collection.mutable.ListBuffer +import vanuatoo.Pattern +import scala.annotation.tailrec + +/** + * @author Mikael + */ +object StringSolver { + type Assignment = Map[Identifier, String] + + type StringFormToken = Either[String, Identifier] + + type StringForm = List[StringFormToken] + + type Equation = (StringForm, String) + + /** Sequences of equalities such as xyz"1"uv"2" = "1, 2" */ + type Problem = List[Equation] + + def renderStringForm(sf: StringForm): String = sf match { + case Left(const)::Nil => "\""+const+"\"" + case Right(id)::Nil => id.toString + case Left(const)::q => "\""+const+"\"+" + renderStringForm(q) + case Right(id)::q => id.toString + "+" + renderStringForm(q) + case Nil => "" + } + + def renderProblem(p: Problem): String = { + def renderEquation(e: Equation): String = { + renderStringForm(e._1) + "==\""+e._2+"\"" + } + p match {case Nil => "" + case e::q => renderEquation(e) + ", " + renderProblem(q)} + } + + /** Evaluates a String form. Requires the solution to have an assignment to all identifiers. */ + @tailrec def evaluate(s: Assignment, acc: StringBuffer = new StringBuffer(""))(sf: StringForm): String = sf match { + case Nil => acc.toString + case Left(constant)::q => evaluate(s, acc append constant)(q) + case Right(identifier)::q => evaluate(s, acc append s(identifier))(q) + } + + /** Assigns the new values to the equations and simplify them at the same time. */ + @tailrec def reduceStringForm(s: Assignment, acc: ListBuffer[StringFormToken] = ListBuffer())(sf: StringForm): StringForm = sf match { + case Nil => acc.toList + case (l@Left(constant))::(l2@Left(constant2))::q => reduceStringForm(s, acc)(Left(constant + constant2)::q) + case (l@Left(constant))::(r2@Right(id))::q => + s.get(id) match { + case Some(sid) => + reduceStringForm(s, acc)(Left(constant + sid)::q) + case None => + reduceStringForm(s, (acc += l) += r2)(q) + } + case (l@Left(constant))::q => reduceStringForm(s, acc += l)(q) + case (r@Right(id))::q => + s.get(id) match { + case Some(sid) => + reduceStringForm(s, acc)(Left(sid)::q) + case None => + reduceStringForm(s, acc += r)(q) + } + } + + /** Assignes the variable to their values in all equations and simplifies them. */ + def reduceProblem(s: Assignment, acc: ListBuffer[Equation] = ListBuffer())(p: Problem): Problem = p match { + case Nil => acc.toList + case ((sf, rhs)::q) => reduceProblem(s, acc += ((reduceStringForm(s)(sf): StringForm, rhs)))(q) + } + + /** Returns true if the assignment is a solution to the problem */ + def errorcheck(p: Problem, s: Assignment): Option[String] = { + val res = p flatMap {eq => + val resultNotCorrect = reduceStringForm(s)(eq._1) match { + case Left(a)::Nil if a == eq._2 => None + case Nil if eq._2 == "" => None + case res => Some(res) + } + if(resultNotCorrect.nonEmpty) Some(renderStringForm(eq._1) + " == "+ renderStringForm(resultNotCorrect.get) + ", but expected " + eq._2) else None + } + if(res == Nil) None else Some(res.mkString("\n") + "\nAssignment: " + s) + } + + /** Concatenates constants together */ + def reduceStringForm(s: StringForm): StringForm = { + @tailrec def rec(s: StringForm, acc: ListBuffer[StringFormToken] = ListBuffer()): StringForm = s match { + case Nil => acc.toList + case Left("")::q => rec(q, acc) + case Left(c)::Left(d)::q => rec(Left(c+d)::q, acc) + case a::q => rec(q, acc += a) + } + rec(s) + } + + /** Split the stringFrom at the last constant. + * @param s The concatenation of constants and variables + * @return (a, b, c) such that a ++ Seq(b) ++ c == s and b is the last constant of s */ + def splitAtLastConstant(s: StringForm): (StringForm, StringFormToken, StringForm) = { + val i = s.lastIndexWhere(x => x.isInstanceOf[Left[_, _]]) + (s.take(i), s(i), s.drop(i+1)) + } + + /** Use `val ConsReverse(init, last) = list` to retrieve the n-1 elements and the last element directly. */ + object ConsReverse { + def unapply[A](l: List[A]): Option[(List[A], A)] = { + if(l.nonEmpty) Some((l.init, l.last)) else None + } + def apply[A](q: List[A], a: A): List[A] = q :+ a + } + + + /** Returns all start positions of the string s in text.*/ + def occurrences(s: String, text: String): List[Int] = { + ("(?="+java.util.regex.Pattern.quote(s)+")").r.findAllMatchIn(text).map(m => m.start).toList + } + + /** Returns a list of all possible positions of the constants inside the string */ + def repartitions(constants: List[String], text: String): List[List[Int]] = constants match { + case Nil => List(List()) + case c::q => + occurrences(c, text).flatMap(startPos => + repartitions(q, text.substring(startPos + c.length)) + .map(startPos :: _.map(_ + (startPos + c.length)))) + } + + /** Computes the Combinatorial coefficient c(n, k)*/ + def cnk(n: BigInt, k: BigInt): BigInt = { + var res = BigInt(1) + var i = 0 + while(i < k) { + res *= n - i + i+=1 + } + i = 2 + while(i <= k) { + res /= i + i+=1 + } + res + } + + def prioritizedPositions(s: String): Stream[Int] = { + val separations = "\\b".r.findAllMatchIn(s).map(m => m.start).toList + separations.toStream #::: { + val done = separations.toSet + for( i <- (0 to s.length).toStream if !done(i)) yield i + } + } + + + /** A single pass simplification process. Converts as much as equations to assignments if possible. */ + trait ProblemSimplicationPhase { self => + def apply(p: Problem, s: Assignment) = run(p, s) + def run(p: Problem, s: Assignment): Option[(Problem, Assignment)] + def andThen(other: ProblemSimplicationPhase): ProblemSimplicationPhase = new ProblemSimplicationPhase { + def run(p: Problem, s: Assignment) = { + ProblemSimplicationPhase.this.run(p, s) match { + case Some((p, s)) => + //println("Problem before " + other.getClass.getName.substring(33) + ":" + (renderProblem(p), s)) + other.run(p, s) + case None => + None + } + } + } + /** Applies a phase until the output does not change anymore */ + def loopUntilConvergence = new ProblemSimplicationPhase { + def run(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { + leon.utils.fixpoint[Option[(Problem, Assignment)]]{ + case None => None + case Some((problem: Problem, assignment: Assignment)) => self.run(problem, assignment) + }(Option((p, s))) + } + } + } + def loopUntilConvergence(psp: ProblemSimplicationPhase) = psp.loopUntilConvergence + + /** Removes duplicate equations from the problem */ + object DistinctEquation extends ProblemSimplicationPhase { + def run(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { + Some((p.distinct, s)) + } + } + + /** Merge constant in string forms */ + object MergeConstants extends ProblemSimplicationPhase { + def run(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { + @tailrec def mergeConstants(p: Problem, acc: ListBuffer[Equation] = ListBuffer()): Option[Problem] = p match { + case Nil => Some(acc.toList) + case (sf, rhs)::q => mergeConstants(q, acc += ((reduceStringForm(sf), rhs))) + } + mergeConstants(p).map((_, s)) + } + } + + /** Unsat if Const1 = Const2 but constants are different. + * if Const1 = Const2 and constants are same, remove equality. */ + object SimplifyConstants extends ProblemSimplicationPhase { + def run(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { + @tailrec def simplifyConstants(p: Problem, acc: ListBuffer[Equation] = ListBuffer()): Option[Problem] = p match { + case Nil => Some(acc.toList) + case (Nil, rhs)::q => if("" != rhs) None else simplifyConstants(q, acc) + case (List(Left(c)), rhs)::q => if(c != rhs) None else simplifyConstants(q, acc) + case sentence::q => simplifyConstants(q, acc += sentence) + } + simplifyConstants(p).map((_, s)) + } + } + + /** . Get new assignments from equations such as id = Const, and propagate them */ + object PropagateAssignments extends ProblemSimplicationPhase { + def run(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { + @tailrec def obtainAssignments(p: Problem, assignments: Assignment = Map()): Option[Assignment] = p match { + case Nil => Some(assignments) + case (List(Right(id)), rhs)::q => + assignments.get(id) match { // It was assigned already. + case Some(v) => + if(rhs != v) None else obtainAssignments(q, assignments) + case None => + obtainAssignments(q, assignments + (id -> rhs)) + } + case sentence::q => obtainAssignments(q, assignments) + } + obtainAssignments(p, s).map(newAssignments => { + //println("Obtained new assignments: " + newAssignments) + val newProblem = if(newAssignments.nonEmpty) reduceProblem(newAssignments)(p) else p + (newProblem, s ++ newAssignments) + }) + } + } + + /** Removes all constants from the left of the equations (i.e. "ab" x ... = "abcd" ==> x ... = "cd" ) */ + object RemoveLeftConstants extends ProblemSimplicationPhase { + def run(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { + @tailrec def removeLeftconstants(p: Problem, acc: ListBuffer[Equation] = ListBuffer()): Option[Problem] = p match { + case Nil => Some(acc.toList) + case ((Left(constant)::q, rhs))::ps => + if(rhs.startsWith(constant)) { + removeLeftconstants(ps, acc += ((q, rhs.substring(constant.length)))) + } else None + case (t@(q, rhs))::ps => + removeLeftconstants(ps, acc += t) + } + removeLeftconstants(p).map((_, s)) + } + } + + /** Removes all constants from the right of the equations (i.e. ... x "cd" = "abcd" ==> ... x = "ab" ) */ + object RemoveRightConstants extends ProblemSimplicationPhase { + def run(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { + @tailrec def removeRightConstants(p: Problem, acc: ListBuffer[Equation] = ListBuffer()): Option[Problem] = p match { + case Nil => Some(acc.toList) + case (ConsReverse(q, Left(constant)), rhs)::ps => + if(rhs.endsWith(constant)) { + removeRightConstants(ps, acc += ((q, rhs.substring(0, rhs.length - constant.length)))) + } else None + case (t@(q, rhs))::ps => + removeRightConstants(ps, acc += t) + } + removeRightConstants(p).map((_, s)) + } + } + + /** If constants can have only one position in an equation, splits the equation. + * If an equation is empty, treats all left-hand-side identifiers as empty. + * Requires MergeConstants so that empty constants have been removed. */ + object PropagateMiddleConstants extends ProblemSimplicationPhase { + def run(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { + @tailrec def constantPropagate(p: Problem, assignments: Assignment = Map(), newProblem: ListBuffer[Equation] = ListBuffer()): Option[(Problem, Assignment)] = { + p match { + case Nil => + Some((newProblem.toList, assignments)) + case (ids, "")::q => // All identifiers should be empty. + val constants = ids.find { + case Left(s) if s != "" => true + case Right(_) => false + } + (constants match { + case Some(_) => None + case None => // Ok now let's assign all variables to empty string. + val newMap = (ids.collect{ case Right(id) => id -> ""}) + val newAssignments = (Option(assignments) /: newMap) { + case (None, (id, rhs)) => None + case (Some(a), (id, rhs)) => + a.get(id) match { // It was assigned already. + case Some(v) => + if(rhs != v) None else Some(a) + case None => + Some(a + (id -> rhs)) + } + } + newAssignments + }) match { + case None => None + case Some(a) => + constantPropagate(q, a, newProblem) + } + case (sentence@(ids, rhs))::q => // If the constants have an unique position, we should split the equation. + val constants = ids.collect { + case l@Left(s) => s + } + // Check if constants form a partition in the string, i.e. getting the .indexOf() the first time is the only way to split the string. + + if(constants.length > 0) { + + var pos = -2 + var lastPos = -2 + var lastConst = "" + var invalid = false + + for(c <- constants) { + val i = rhs.indexOfSlice(c, pos) + lastPos = i + lastConst = c + if(i == -1) invalid = true + pos = i + c.length + } + if(invalid) None else { + val i = rhs.indexOfSlice(lastConst, lastPos + 1) + if(i == -1) { // OK it's the smallest position possible, hence we can split at the last position. + val (before, constant, after) = splitAtLastConstant(ids) + val firstConst = rhs.substring(0, lastPos) + val secondConst = rhs.substring(lastPos + lastConst.length) + constantPropagate((before, firstConst)::(after, secondConst)::q, assignments, newProblem) + } else { + // Other splitting strategy: independent constants ? + + val constantsSplit = constants.flatMap{ c => { + val i = rhs.indexOfSlice(c, -1) + val j = rhs.indexOfSlice(c, i + 1) + if(j == -1 && i != -1) { + List((c, i)) + } else Nil + }} + + constantsSplit match { + case Nil => + constantPropagate(q, assignments, newProblem += sentence) + case ((c, i)::_) => + val (before, after) = ids.splitAt(ids.indexOf(Left(c))) + val firstconst = rhs.substring(0, i) + val secondConst = rhs.substring(i+c.length) + constantPropagate((before, firstconst)::(after.tail, secondConst)::q, assignments, newProblem) + } + + } + } + } else { + constantPropagate(q, assignments, newProblem += sentence) + } + case sentence::q => constantPropagate(q, assignments, newProblem += sentence) + } + } + constantPropagate(p).map(ps => { + val newP = if(ps._2.nonEmpty) reduceProblem(ps._2)(p) else p + (ps._1, s ++ ps._2) + }) + } + } + + /** If a left-hand side of the equation appears somewhere else, replace it by the right-hand-side of this equation */ + object PropagateEquations extends ProblemSimplicationPhase { + def run(p: Problem, s: Assignment): Option[(Problem, Assignment)] = { + var newP = p.distinct + for((lhs, rhs) <- newP if lhs.length >= 2) { // Original distinct p. + var indexInP = 0 + for(eq@(lhs2, rhs2) <- newP) { + if((!(lhs2 eq lhs) || !(rhs2 eq rhs))) { + val i = lhs2.indexOfSlice(lhs) + if(i != -1) { + val res = (lhs2.take(i) ++ Seq(Left(rhs)) ++ lhs2.drop(i + lhs.size), rhs2) + newP = newP.updated(indexInP, res) + } + } + indexInP += 1 + } + } + Some((newP, s)) + } + } + + /** returns a simplified version of the problem. If it is not satisfiable, returns None. */ + val simplifyProblem: ProblemSimplicationPhase = { + loopUntilConvergence(DistinctEquation andThen + MergeConstants andThen + SimplifyConstants andThen + PropagateAssignments) + } + + /** Removes all constants from the left and right of the equations */ + val noLeftRightConstants: ProblemSimplicationPhase = { + RemoveLeftConstants andThen RemoveRightConstants + } + + /** Composition of simplifyProblem and noLeftRightConstants */ + val forwardStrategy = + loopUntilConvergence(simplifyProblem andThen noLeftRightConstants andThen PropagateMiddleConstants andThen PropagateEquations) + + + /** Solves the equation x1x2x3...xn = CONSTANT + * Prioritizes split positions in the CONSTANT that are word boundaries + * Prioritizes variables which have a higher frequency */ + def simpleSplit(ids: List[Identifier], rhs: String)(implicit statistics: Map[Identifier, Int]): Stream[Assignment] = { + ids match { + case Nil => if(rhs == "") Stream(Map()) else Stream.empty + case List(x) => + Stream(Map(x -> rhs)) + case x :: ys => + val (bestVar, bestScore, worstScore) = (((None:Option[(Identifier, Int, Int)]) /: ids) { + case (None, x) => val sx = statistics(x) + Some((x, sx, sx)) + case (s@Some((x, i, ws)), y) => val yi = statistics(y) + if(i >= yi) Some((x, i, Math.min(yi, ws))) else Some((y, yi, ws)) + }).get + val pos = prioritizedPositions(rhs) + val numBestVars = ids.count { x => x == bestVar } + + if(worstScore == bestScore) { + for{ + i <- pos // Prioritization on positions which are separators. + xvalue = rhs.substring(0, i) + rvalue = rhs.substring(i) + remaining_splits = simpleSplit(ys, rvalue) + remaining_split <- remaining_splits + if !remaining_split.contains(x) || remaining_split(x) == xvalue + } yield (remaining_split + (x -> xvalue)) + } else { // A variable appears more than others in the same equation, so its changes are going to propagate more. + val indexBestVar = ids.indexOf(bestVar) + val strings = if(indexBestVar == 0) { // Test only string prefixes or empty string + (for{j <- (rhs.length to 1 by -1).toStream + if pos contains j} yield rhs.substring(0, j)) #::: + (for{j <- (rhs.length to 1 by -1).toStream + if !(pos contains j)} yield rhs.substring(0, j)) #::: + Stream("") + } else { + val lastIndexBestVar = ids.lastIndexOf(bestVar) + if(lastIndexBestVar == ids.length - 1) { + (for{ i <- pos.toStream // Try to maximize the size of the string from the start + if i != rhs.length + } yield rhs.substring(i)) #::: + Stream("") + } else { // Inside, can be anything. + (for{ i <- pos.toStream // Try to maximize the size of the string from the start + if i != rhs.length + j <- rhs.length to (i+1) by -1 + if pos contains j} yield rhs.substring(i, j)) #::: + (for{ i <- pos.toStream + if i != rhs.length + j <- rhs.length to (i+1) by -1 + if !(pos contains j)} yield rhs.substring(i, j)) #::: + Stream("") + } + } + //println("Best variable:" + bestVar + " going to test " + strings.toList) + + (for(str <- strings.distinct + if java.util.regex.Pattern.quote(str).r.findAllMatchIn(rhs).length >= numBestVars + ) yield { + Map(bestVar -> str) + }) + } + } + } + + @tailrec def statsStringForm(e: StringForm, acc: Map[Identifier, Int] = Map()): Map[Identifier, Int] = e match { + case Nil => acc + case Right(id)::q => statsStringForm(q, acc + (id -> (acc.getOrElse(id, 0) + 1))) + case (_::q) => statsStringForm(q, acc) + } + + @tailrec def stats(p: Problem, acc: Map[Identifier, Int] = Map()): Map[Identifier, Int] = p match { + case Nil => acc + case (sf, rhs)::q => + stats(q, (statsStringForm(sf) /: acc) { case (m, (k, v)) => m + (k -> (m.getOrElse(k, 0) + v)) }) + } + + /** Deduces possible new assignments from the problem. */ + def splitStrategy(p: Problem): Stream[Assignment] = { + // Look for the equation with the least degree of freedom. + if(p.isEmpty) return Stream(Map()) + + var minStatement = p.head + var minweight = BigInt(-1) + var minConstants = List[String]() + var minIdentifiersGrouped = List[List[Identifier]]() + // Counts the number of possible enumerations, take the least. + for((lhs, rhs) <- p) { + val constants = lhs.collect{ case Left(constant) => constant } + val identifiers_grouped = ListBuffer[List[Identifier]]() + var current_buffer = ListBuffer[Identifier]() + for(e <- lhs) e match { + case Left(constant) => // At this point, there is only one constant here. + identifiers_grouped.append(current_buffer.toList) + current_buffer.clear() + case Right(identifier) => + current_buffer.append(identifier) + } + identifiers_grouped.append(current_buffer.toList) + var weight = BigInt(9) + for(positions <- repartitions(constants, rhs)) { + var tmp_weight = BigInt(1) + var prev_position = 0 + for(((p, c), ids) <- positions.zip(constants).zip(identifiers_grouped.init)) { + tmp_weight *= cnk(p - prev_position, ids.length - 1) // number of positions + prev_position = p + c.length + } + tmp_weight *= cnk(rhs.length - prev_position, identifiers_grouped.last.length - 1) + weight += tmp_weight + } + if(minweight == -1 || weight < minweight) { + minweight = weight + minStatement = (lhs, rhs) + minConstants = constants + minIdentifiersGrouped = identifiers_grouped.toList + } + } + val (lhs, rhs) = minStatement + val constants = minConstants + val identifiers_grouped = minIdentifiersGrouped + val statistics = stats(p) + if(identifiers_grouped.length > 1) { + // There might be different repartitions of the first boundary constant. We need to investigate all of them. + repartitions(constants, rhs).map(_.head).distinct.toStream.flatMap(p => { + val firstString = rhs.substring(0, p) // We only split on the first string. + simpleSplit(identifiers_grouped.head, firstString)(statistics) + }) + } else if(identifiers_grouped.length == 1) { + simpleSplit(identifiers_grouped.head, rhs)(statistics) // All new assignments + } else { + if(rhs == "") Stream(Map()) else Stream.Empty + } + } + + /** Solves the problem and returns all possible satisfying assignment */ + def solve(p: Problem): Stream[Assignment] = { + val realProblem = forwardStrategy.run(p, Map()) + /*if(realProblem.nonEmpty && realProblem.get._1.nonEmpty) { + println("Problem:\n"+renderProblem(p)) + println("Solutions:\n"+realProblem.get._2) + println("Real problem:\n"+renderProblem(realProblem.get._1)) + }*/ + + realProblem match { + case None => + Stream.Empty + case Some((Nil, solution)) => + Stream(solution) + case Some((problem, partialSolution)) => + // 1) No double constants ("a" + "b" have been replaced by "ab") + // 2) No just an identifier on the LHS (x = "a" has been replaced by an assignment + // 3) No leading or trailing constants in equation ("a" + ... + "b" = "axyzb" has been replaced by ... = "xyz") + + /* Equation of the type + variables "constant" variables "constant".... variables = "constant". + For each constant we check its possible positions in the output, which determines possible assignments. + + Then since variables = constant, we can just iterate on them. + Heuristic: We need to resolve smaller equations first. + */ + for{assignment <- splitStrategy(problem) + newProblem = reduceProblem(assignment)(problem) + remainingSolution <- solve(newProblem) + } yield { + partialSolution ++ assignment ++ remainingSolution + } + } + } + + //////////////////////////////////////////////// + //// Transitively Bounded problem extension //// + //////////////////////////////////////////////// + + /** More general types of equations */ + type GeneralEquation = (StringForm, StringForm) + + /** Supposes that all variables are transitively bounded by length*/ + type GeneralProblem = List[GeneralEquation] + + def variablesStringForm(sf: StringForm): Set[Identifier] = (sf.collect{ case Right(id) => id }).toSet + def variables(gf: GeneralEquation): Set[Identifier] = variablesStringForm(gf._1) ++ variablesStringForm(gf._2) + + /** Returns true if the problem is transitively bounded */ + def isTransitivelyBounded(b: GeneralProblem, transitivelyBounded: Set[Identifier] = Set()): Boolean = { + def isBounded(sf: GeneralEquation) = { + variablesStringForm(sf._1).forall(transitivelyBounded) || variablesStringForm(sf._2).forall(transitivelyBounded) + } + val (bounded, notbounded) = b.partition(isBounded _) + + if(notbounded == Nil) true + else if(notbounded == b) false + else { + isTransitivelyBounded(notbounded, transitivelyBounded ++ bounded.flatMap { x => variables(x) }) + } + } + + /** Propagates an assignment into a general equation */ + def reduceGeneralEquation(a: Assignment)(g: GeneralEquation): GeneralEquation = g match { + case (sf1, sf2) => (reduceStringForm(a)(sf1), reduceStringForm(a)(sf2)) + } + + /** Solves the bounded problem version. + * Requires all the variables to be transitively bounded by size. */ + def solveGeneralProblem(b: GeneralProblem): Stream[Assignment] = { + val realProblem = b map { case (lhs, rhs) => (reduceStringForm(lhs), reduceStringForm(rhs)) } + + def partition(b: GeneralProblem, bounded: ListBuffer[Equation] = ListBuffer(), unbounded: ListBuffer[GeneralEquation] = ListBuffer()): (Problem, GeneralProblem) = b match { + case Nil => (bounded.toList, unbounded.toList) + case (sf1, List(Left(a)))::q => partition(q, bounded += ((sf1, a)), unbounded) + case (sf1, Nil)::q => partition(q, bounded += ((sf1, "")), unbounded) + case (List(Left(a)), sf1)::q => partition(q, bounded += ((sf1, a)), unbounded) + case (Nil, sf1)::q => partition(q, bounded += ((sf1, "")), unbounded) + case a::q => partition(q, bounded, unbounded += a) + } + + val (bounded, unbounded) = partition(realProblem) + + if(unbounded == Nil) solve(bounded) else + solve(bounded).flatMap(assignment => { + solveGeneralProblem(unbounded.map(reduceGeneralEquation(assignment)(_))).map(assignment ++ _) + }) + } +} \ No newline at end of file diff --git a/src/main/scala/leon/solvers/templates/LambdaManager.scala b/src/main/scala/leon/solvers/templates/LambdaManager.scala index 75d163815a17fbf31c56b684d58e3d26efe7feaa..036654c25ab64839efa5af4cb0d49a6566ffcf20 100644 --- a/src/main/scala/leon/solvers/templates/LambdaManager.scala +++ b/src/main/scala/leon/solvers/templates/LambdaManager.scala @@ -12,9 +12,10 @@ import purescala.Types._ import utils._ import Instantiation._ +import Template._ -case class App[T](caller: T, tpe: FunctionType, args: Seq[T]) { - override def toString = "(" + caller + " : " + tpe + ")" + args.mkString("(", ",", ")") +case class App[T](caller: T, tpe: FunctionType, args: Seq[Arg[T]]) { + override def toString = "(" + caller + " : " + tpe + ")" + args.map(_.encoded).mkString("(", ",", ")") } object LambdaTemplate { @@ -72,6 +73,28 @@ object LambdaTemplate { } } +trait KeyedTemplate[T, E <: Expr] { + val dependencies: Map[Identifier, T] + val structuralKey: E + + lazy val key: (E, Seq[T]) = { + def rec(e: Expr): Seq[Identifier] = e match { + case Variable(id) => + if (dependencies.isDefinedAt(id)) { + Seq(id) + } else { + Seq.empty + } + + case Operator(es, _) => es.flatMap(rec) + + case _ => Seq.empty + } + + structuralKey -> rec(structuralKey).distinct.map(dependencies) + } +} + class LambdaTemplate[T] private ( val ids: (Identifier, T), val encoder: TemplateEncoder[T], @@ -87,34 +110,39 @@ class LambdaTemplate[T] private ( val quantifications: Seq[QuantificationTemplate[T]], val matchers: Map[T, Set[Matcher[T]]], val lambdas: Seq[LambdaTemplate[T]], - private[templates] val dependencies: Map[Identifier, T], - private[templates] val structuralKey: Lambda, - stringRepr: () => String) extends Template[T] { + val dependencies: Map[Identifier, T], + val structuralKey: Lambda, + stringRepr: () => String) extends Template[T] with KeyedTemplate[T, Lambda] { val args = arguments.map(_._2) val tpe = ids._1.getType.asInstanceOf[FunctionType] - def substitute(substituter: T => T): LambdaTemplate[T] = { + def substitute(substituter: T => T, matcherSubst: Map[T, Matcher[T]]): LambdaTemplate[T] = { val newStart = substituter(start) val newClauses = clauses.map(substituter) val newBlockers = blockers.map { case (b, fis) => val bp = if (b == start) newStart else b - bp -> fis.map(fi => fi.copy(args = fi.args.map(substituter))) + bp -> fis.map(fi => fi.copy( + args = fi.args.map(_.substitute(substituter, matcherSubst)) + )) } val newApplications = applications.map { case (b, fas) => val bp = if (b == start) newStart else b - bp -> fas.map(fa => fa.copy(caller = substituter(fa.caller), args = fa.args.map(substituter))) + bp -> fas.map(fa => fa.copy( + caller = substituter(fa.caller), + args = fa.args.map(_.substitute(substituter, matcherSubst)) + )) } - val newQuantifications = quantifications.map(_.substitute(substituter)) + val newQuantifications = quantifications.map(_.substitute(substituter, matcherSubst)) val newMatchers = matchers.map { case (b, ms) => val bp = if (b == start) newStart else b - bp -> ms.map(_.substitute(substituter)) + bp -> ms.map(_.substitute(substituter, matcherSubst)) } - val newLambdas = lambdas.map(_.substitute(substituter)) + val newLambdas = lambdas.map(_.substitute(substituter, matcherSubst)) val newDependencies = dependencies.map(p => p._1 -> substituter(p._2)) @@ -139,45 +167,20 @@ class LambdaTemplate[T] private ( ) } - private lazy val str : String = stringRepr() - override def toString : String = str - - lazy val key: (Expr, Seq[T]) = { - def rec(e: Expr): Seq[Identifier] = e match { - case Variable(id) => - if (dependencies.isDefinedAt(id)) { - Seq(id) - } else { - Seq.empty - } - - case Operator(es, _) => es.flatMap(rec) - - case _ => Seq.empty - } - - structuralKey -> rec(structuralKey).distinct.map(dependencies) - } - - override def equals(that: Any): Boolean = that match { - case t: LambdaTemplate[T] => - val (lambda1, deps1) = key - val (lambda2, deps2) = t.key - (lambda1 == lambda2) && { - (deps1 zip deps2).forall { case (id1, id2) => - (manager.byID.get(id1), manager.byID.get(id2)) match { - case (Some(t1), Some(t2)) => t1 == t2 - case _ => id1 == id2 - } - } - } - - case _ => false + def withId(idT: T): LambdaTemplate[T] = { + val substituter = encoder.substitute(Map(ids._2 -> idT)) + new LambdaTemplate[T]( + ids._1 -> idT, encoder, manager, pathVar, arguments, condVars, exprVars, condTree, + clauses map substituter, // make sure the body-defining clause is inlined! + blockers, applications, quantifications, matchers, lambdas, + dependencies, structuralKey, stringRepr + ) } - override def hashCode: Int = key.hashCode + private lazy val str : String = stringRepr() + override def toString : String = str - override def instantiate(substMap: Map[T, T]): Instantiation[T] = { + override def instantiate(substMap: Map[T, Arg[T]]): Instantiation[T] = { super.instantiate(substMap) ++ manager.instantiateAxiom(this, substMap) } } @@ -186,7 +189,7 @@ class LambdaManager[T](encoder: TemplateEncoder[T]) extends TemplateManager(enco private[templates] lazy val trueT = encoder.encodeExpr(Map.empty)(BooleanLiteral(true)) protected[templates] val byID = new IncrementalMap[T, LambdaTemplate[T]] - protected val byType = new IncrementalMap[FunctionType, Set[LambdaTemplate[T]]].withDefaultValue(Set.empty) + protected val byType = new IncrementalMap[FunctionType, Map[(Expr, Seq[T]), LambdaTemplate[T]]].withDefaultValue(Map.empty) protected val applications = new IncrementalMap[FunctionType, Set[(T, App[T])]].withDefaultValue(Set.empty) protected val freeLambdas = new IncrementalMap[FunctionType, Set[T]].withDefaultValue(Set.empty) @@ -203,27 +206,30 @@ class LambdaManager[T](encoder: TemplateEncoder[T]) extends TemplateManager(enco } } - def instantiateLambda(template: LambdaTemplate[T]): Instantiation[T] = { - val idT = template.ids._2 - var clauses : Clauses[T] = equalityClauses(template) - var appBlockers : AppBlockers[T] = Map.empty.withDefaultValue(Set.empty) + def instantiateLambda(template: LambdaTemplate[T]): (T, Instantiation[T]) = { + byType(template.tpe).get(template.key) match { + case Some(template) => + (template.ids._2, Instantiation.empty) - // make sure the new lambda isn't equal to any free lambda var - clauses ++= freeLambdas(template.tpe).map(pIdT => encoder.mkNot(encoder.mkEquals(pIdT, idT))) + case None => + val idT = encoder.encodeId(template.ids._1) + val newTemplate = template.withId(idT) - byID += idT -> template + var clauses : Clauses[T] = equalityClauses(newTemplate) + var appBlockers : AppBlockers[T] = Map.empty.withDefaultValue(Set.empty) - if (byType(template.tpe)(template)) { - (clauses, Map.empty, Map.empty) - } else { - byType += template.tpe -> (byType(template.tpe) + template) + // make sure the new lambda isn't equal to any free lambda var + clauses ++= freeLambdas(newTemplate.tpe).map(pIdT => encoder.mkNot(encoder.mkEquals(idT, pIdT))) - for (blockedApp @ (_, App(caller, tpe, args)) <- applications(template.tpe)) { - val equals = encoder.mkEquals(idT, caller) - appBlockers += (blockedApp -> (appBlockers(blockedApp) + TemplateAppInfo(template, equals, args))) - } + byID += idT -> newTemplate + byType += newTemplate.tpe -> (byType(newTemplate.tpe) + (newTemplate.key -> newTemplate)) + + for (blockedApp @ (_, App(caller, tpe, args)) <- applications(newTemplate.tpe)) { + val equals = encoder.mkEquals(idT, caller) + appBlockers += (blockedApp -> (appBlockers(blockedApp) + TemplateAppInfo(newTemplate, equals, args))) + } - (clauses, Map.empty, appBlockers) + (idT, (clauses, Map.empty, appBlockers)) } } @@ -244,7 +250,7 @@ class LambdaManager[T](encoder: TemplateEncoder[T]) extends TemplateManager(enco // make sure that even if byType(tpe) is empty, app is recorded in blockers // so that UnrollingBank will generate the initial block! val init = instantiation withApps Map(key -> Set.empty) - val inst = byType(tpe).foldLeft(init) { + val inst = byType(tpe).values.foldLeft(init) { case (instantiation, template) => val equals = encoder.mkEquals(template.ids._2, caller) instantiation withApp (key -> TemplateAppInfo(template, equals, args)) @@ -260,7 +266,7 @@ class LambdaManager[T](encoder: TemplateEncoder[T]) extends TemplateManager(enco private def equalityClauses(template: LambdaTemplate[T]): Seq[T] = { val (s1, deps1) = template.key - byType(template.tpe).map { that => + byType(template.tpe).values.map { that => val (s2, deps2) = that.key val equals = encoder.mkEquals(template.ids._2, that.ids._2) if (s1 == s2) { diff --git a/src/main/scala/leon/solvers/templates/QuantificationManager.scala b/src/main/scala/leon/solvers/templates/QuantificationManager.scala index c7f715b5eefe573d88eeb94b45293ba7cde646bf..bc31267c09d8023390ac541b0cb99ecece6691ea 100644 --- a/src/main/scala/leon/solvers/templates/QuantificationManager.scala +++ b/src/main/scala/leon/solvers/templates/QuantificationManager.scala @@ -14,23 +14,17 @@ import purescala.Types._ import purescala.Quantification.{QuantificationTypeMatcher => QTM} import Instantiation._ +import Template._ import scala.collection.mutable.{Map => MutableMap, Set => MutableSet, Stack => MutableStack, Queue} -object Matcher { - def argValue[T](arg: Either[T, Matcher[T]]): T = arg match { - case Left(value) => value - case Right(matcher) => matcher.encoded - } -} - -case class Matcher[T](caller: T, tpe: TypeTree, args: Seq[Either[T, Matcher[T]]], encoded: T) { +case class Matcher[T](caller: T, tpe: TypeTree, args: Seq[Arg[T]], encoded: T) { override def toString = caller + args.map { case Right(m) => m.toString case Left(v) => v.toString }.mkString("(",",",")") - def substitute(substituter: T => T, matcherSubst: Map[T, Matcher[T]] = Map.empty): Matcher[T] = copy( + def substitute(substituter: T => T, matcherSubst: Map[T, Matcher[T]]): Matcher[T] = copy( caller = substituter(caller), args = args.map { case Left(v) => matcherSubst.get(v) match { @@ -58,11 +52,13 @@ class QuantificationTemplate[T]( val blockers: Map[T, Set[TemplateCallInfo[T]]], val applications: Map[T, Set[App[T]]], val matchers: Map[T, Set[Matcher[T]]], - val lambdas: Seq[LambdaTemplate[T]]) { + val lambdas: Seq[LambdaTemplate[T]], + val dependencies: Map[Identifier, T], + val structuralKey: Forall) extends KeyedTemplate[T, Forall] { lazy val start = pathVar._2 - def substitute(substituter: T => T): QuantificationTemplate[T] = { + def substitute(substituter: T => T, matcherSubst: Map[T, Matcher[T]]): QuantificationTemplate[T] = { new QuantificationTemplate[T]( quantificationManager, pathVar._1 -> substituter(start), @@ -76,15 +72,22 @@ class QuantificationTemplate[T]( condTree, clauses.map(substituter), blockers.map { case (b, fis) => - substituter(b) -> fis.map(fi => fi.copy(args = fi.args.map(substituter))) + substituter(b) -> fis.map(fi => fi.copy( + args = fi.args.map(_.substitute(substituter, matcherSubst)) + )) }, applications.map { case (b, apps) => - substituter(b) -> apps.map(app => app.copy(caller = substituter(app.caller), args = app.args.map(substituter))) + substituter(b) -> apps.map(app => app.copy( + caller = substituter(app.caller), + args = app.args.map(_.substitute(substituter, matcherSubst)) + )) }, matchers.map { case (b, ms) => - substituter(b) -> ms.map(_.substitute(substituter)) + substituter(b) -> ms.map(_.substitute(substituter, matcherSubst)) }, - lambdas.map(_.substitute(substituter)) + lambdas.map(_.substitute(substituter, matcherSubst)), + dependencies.map { case (id, value) => id -> substituter(value) }, + structuralKey ) } } @@ -104,7 +107,9 @@ object QuantificationTemplate { condTree: Map[Identifier, Set[Identifier]], guardedExprs: Map[Identifier, Seq[Expr]], lambdas: Seq[LambdaTemplate[T]], - subst: Map[Identifier, T] + baseSubstMap: Map[Identifier, T], + dependencies: Map[Identifier, T], + proposition: Forall ): QuantificationTemplate[T] = { val q2s: (Identifier, T) = q2 -> encoder.encodeId(q2) @@ -113,11 +118,15 @@ object QuantificationTemplate { val (clauses, blockers, applications, matchers, _) = Template.encode(encoder, pathVar, quantifiers, condVars, exprVars, guardedExprs, lambdas, - substMap = subst + q2s + insts + guards + qs) + substMap = baseSubstMap + q2s + insts + guards + qs) + + val (structuralQuant, structSubst) = normalizeStructure(proposition) + val keyDeps = dependencies.map { case (id, idT) => structSubst(id) -> idT } + val key = structuralQuant.asInstanceOf[Forall] new QuantificationTemplate[T](quantificationManager, - pathVar, qs, q2s, insts, guards._2, quantifiers, - condVars, exprVars, condTree, clauses, blockers, applications, matchers, lambdas) + pathVar, qs, q2s, insts, guards._2, quantifiers, condVars, exprVars, condTree, + clauses, blockers, applications, matchers, lambdas, keyDeps, key) } } @@ -130,9 +139,10 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage private val known = new IncrementalSet[T] private val lambdaAxioms = new IncrementalSet[(LambdaTemplate[T], Seq[(Identifier, T)])] + private val templates = new IncrementalMap[(Expr, Seq[T]), T] override protected def incrementals: List[IncrementalState] = - List(quantifications, instCtx, handled, ignored, known, lambdaAxioms) ++ super.incrementals + List(quantifications, instCtx, handled, ignored, known, lambdaAxioms, templates) ++ super.incrementals private sealed abstract class MatcherKey(val tpe: TypeTree) private case class CallerKey(caller: T, tt: TypeTree) extends MatcherKey(tt) @@ -386,7 +396,7 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage * matchers in the argument and quorum positions */ allMappings.filter { s => - def expand(ms: Traversable[(Either[T,Matcher[T]], Either[T,Matcher[T]])]): Set[(Matcher[T], Matcher[T])] = ms.flatMap { + def expand(ms: Traversable[(Arg[T], Arg[T])]): Set[(Matcher[T], Matcher[T])] = ms.flatMap { case (Right(qm), Right(m)) => Set(qm -> m) ++ expand(qm.args zip m.args) case _ => Set.empty[(Matcher[T], Matcher[T])] }.toSet @@ -398,11 +408,11 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage } } - private def extractSubst(mapping: Set[(Set[T], Matcher[T], Matcher[T])]): (Set[T], Map[T,Either[T, Matcher[T]]], Boolean) = { + private def extractSubst(mapping: Set[(Set[T], Matcher[T], Matcher[T])]): (Set[T], Map[T,Arg[T]], Boolean) = { var constraints: Set[T] = Set.empty var eqConstraints: Set[(T, T)] = Set.empty var matcherEqs: List[(T, T)] = Nil - var subst: Map[T, Either[T, Matcher[T]]] = Map.empty + var subst: Map[T, Arg[T]] = Map.empty for { (bs, qm @ Matcher(qcaller, _, qargs, _), m @ Matcher(caller, _, args, _)) <- mapping @@ -411,16 +421,16 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage (qarg, arg) <- (qargs zip args) } qarg match { case Left(quant) if subst.isDefinedAt(quant) => - eqConstraints += (quant -> Matcher.argValue(arg)) + eqConstraints += (quant -> arg.encoded) case Left(quant) if quantified(quant) => subst += quant -> arg case Right(qam) => - val argVal = Matcher.argValue(arg) + val argVal = arg.encoded eqConstraints += (qam.encoded -> argVal) matcherEqs :+= qam.encoded -> argVal } - val substituter = encoder.substitute(subst.mapValues(Matcher.argValue)) + val substituter = encoder.substitute(subst.mapValues(_.encoded)) val substConstraints = constraints.filter(_ != trueT).map(substituter) val substEqs = eqConstraints.map(p => substituter(p._1) -> p._2) .filter(p => p._1 != p._2).map(p => encoder.mkEquals(p._1, p._2)) @@ -437,25 +447,25 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage val (enablers, subst, isStrict) = extractSubst(mapping) val (enabler, optEnabler) = freshBlocker(enablers) - if (optEnabler.isDefined) { - instantiation = instantiation withClause encoder.mkEquals(enabler, optEnabler.get) - } - - val baseSubstMap = exprVars.map { case (id, idT) => idT -> encoder.encodeId(id) } ++ - freshConds(pathVar._1 -> enabler, condVars, condTree) - val lambdaSubstMap = lambdas map (lambda => lambda.ids._2 -> encoder.encodeId(lambda.ids._1)) - val substMap = subst.mapValues(Matcher.argValue) ++ baseSubstMap ++ lambdaSubstMap ++ instanceSubst(enablers) + val baseSubst = subst ++ instanceSubst(enablers).mapValues(Left(_)) + val (substMap, inst) = Template.substitution(encoder, QuantificationManager.this, + condVars, exprVars, condTree, Seq.empty, lambdas, baseSubst, pathVar._1, enabler) if (!skip(substMap)) { + if (optEnabler.isDefined) { + instantiation = instantiation withClause encoder.mkEquals(enabler, optEnabler.get) + } + + instantiation ++= inst instantiation ++= Template.instantiate(encoder, QuantificationManager.this, clauses, blockers, applications, Seq.empty, Map.empty[T, Set[Matcher[T]]], lambdas, substMap) - val msubst = subst.collect { case (c, Right(m)) => c -> m } - val substituter = encoder.substitute(substMap) + val msubst = substMap.collect { case (c, Right(m)) => c -> m } + val substituter = encoder.substitute(substMap.mapValues(_.encoded)) for ((b,ms) <- allMatchers; m <- ms) { val sb = enablers ++ (if (b == start) Set.empty else Set(substituter(b))) - val sm = m.substitute(substituter, matcherSubst = msubst) + val sm = m.substitute(substituter, msubst) if (matchers(m)) { handled += sb -> sm @@ -473,7 +483,7 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage protected def instanceSubst(enablers: Set[T]): Map[T, T] - protected def skip(subst: Map[T, T]): Boolean = false + protected def skip(subst: Map[T, Arg[T]]): Boolean = false } private class Quantification ( @@ -545,10 +555,11 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage Map(guardVar -> guardT, blocker -> newBlocker) } - override protected def skip(subst: Map[T, T]): Boolean = { - val substituter = encoder.substitute(subst) + override protected def skip(subst: Map[T, Arg[T]]): Boolean = { + val substituter = encoder.substitute(subst.mapValues(_.encoded)) + val msubst = subst.collect { case (c, Right(m)) => c -> m } allMatchers.forall { case (b, ms) => - ms.forall(m => matchers(m) || instCtx(Set(substituter(b)) -> m.substitute(substituter))) + ms.forall(m => matchers(m) || instCtx(Set(substituter(b)) -> m.substitute(substituter, msubst))) } } } @@ -577,9 +588,9 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage (m: Matcher[T]) => m.args.collect { case Left(a) if quantified(a) => a }.toSet) } - def instantiateAxiom(template: LambdaTemplate[T], substMap: Map[T, T]): Instantiation[T] = { - val quantifiers = template.arguments map { - case (id, idT) => id -> substMap(idT) + def instantiateAxiom(template: LambdaTemplate[T], substMap: Map[T, Arg[T]]): Instantiation[T] = { + val quantifiers = template.arguments flatMap { + case (id, idT) => substMap(idT).left.toOption.map(id -> _) } filter (p => isQuantifier(p._2)) if (quantifiers.isEmpty || lambdaAxioms(template -> quantifiers)) { @@ -591,19 +602,24 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage val guard = FreshIdentifier("guard", BooleanType, true) val guardT = encoder.encodeId(guard) - val substituter = encoder.substitute(substMap + (template.start -> blockerT)) - val allMatchers = template.matchers map { case (b, ms) => substituter(b) -> ms.map(_.substitute(substituter)) } + val substituter = encoder.substitute(substMap.mapValues(_.encoded) + (template.start -> blockerT)) + val msubst = substMap.collect { case (c, Right(m)) => c -> m } + + val allMatchers = template.matchers map { case (b, ms) => + substituter(b) -> ms.map(_.substitute(substituter, msubst)) + } + val qMatchers = allMatchers.flatMap(_._2).toSet - val encArgs = template.args map substituter + val encArgs = template.args map (arg => Left(arg).substitute(substituter, msubst)) val app = Application(Variable(template.ids._1), template.arguments.map(_._1.toVariable)) - val appT = encoder.encodeExpr((template.arguments.map(_._1) zip encArgs).toMap + template.ids)(app) - val selfMatcher = Matcher(template.ids._2, template.tpe, encArgs.map(Left(_)), appT) + val appT = encoder.encodeExpr((template.arguments.map(_._1) zip encArgs.map(_.encoded)).toMap + template.ids)(app) + val selfMatcher = Matcher(template.ids._2, template.tpe, encArgs, appT) val enablingClause = encoder.mkImplies(guardT, blockerT) instantiateAxiom( - template.pathVar._1 -> substMap(template.start), + template.pathVar._1 -> substituter(template.start), blockerT, guardT, quantifiers, @@ -614,12 +630,17 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage template.condTree, (template.clauses map substituter) :+ enablingClause, template.blockers map { case (b, fis) => - substituter(b) -> fis.map(fi => fi.copy(args = fi.args.map(substituter))) + substituter(b) -> fis.map(fi => fi.copy( + args = fi.args.map(_.substitute(substituter, msubst)) + )) }, template.applications map { case (b, apps) => - substituter(b) -> apps.map(app => app.copy(caller = substituter(app.caller), args = app.args.map(substituter))) + substituter(b) -> apps.map(app => app.copy( + caller = substituter(app.caller), + args = app.args.map(_.substitute(substituter, msubst)) + )) }, - template.lambdas map (_.substitute(substituter)) + template.lambdas map (_.substitute(substituter, msubst)) ) } } @@ -664,73 +685,70 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage for { m <- matchers - sm = m.substitute(substituter) + sm = m.substitute(substituter, Map.empty) if !instCtx.corresponding(sm).exists(_._2.args == sm.args) } instantiation ++= instCtx.instantiate(Set.empty, sm)(quantifications.toSeq : _*) instantiation } - def instantiateQuantification(template: QuantificationTemplate[T], substMap: Map[T, T]): Instantiation[T] = { - val quantified = template.quantifiers.map(_._2).toSet - val matchQuorums = extractQuorums(quantified, template.matchers.flatMap(_._2).toSet, template.lambdas) - - var instantiation = Instantiation.empty[T] + def instantiateQuantification(template: QuantificationTemplate[T]): (T, Instantiation[T]) = { + templates.get(template.key) match { + case Some(idT) => + (idT, Instantiation.empty) - val qs = for (matchers <- matchQuorums) yield { - val newQ = encoder.encodeId(template.qs._1) - val subst = substMap + (template.qs._2 -> newQ) - - val substituter = encoder.substitute(subst) - val quantification = new Quantification( - template.pathVar._1 -> substituter(template.start), - template.qs._1 -> newQ, - template.q2s, template.insts, template.guardVar, - quantified, - matchers map (_.substitute(substituter)), - template.matchers map { case (b, ms) => substituter(b) -> ms.map(_.substitute(substituter)) }, - template.condVars, - template.exprVars, - template.condTree, - template.clauses map substituter, - template.blockers map { case (b, fis) => - substituter(b) -> fis.map(fi => fi.copy(args = fi.args.map(substituter))) - }, - template.applications map { case (b, fas) => - substituter(b) -> fas.map(fa => fa.copy(caller = substituter(fa.caller), args = fa.args.map(substituter))) - }, - template.lambdas map (_.substitute(substituter)) - ) + case None => + val qT = encoder.encodeId(template.qs._1) + val quantified = template.quantifiers.map(_._2).toSet + val matchQuorums = extractQuorums(quantified, template.matchers.flatMap(_._2).toSet, template.lambdas) - quantifications += quantification + var instantiation = Instantiation.empty[T] - val newCtx = new InstantiationContext() - for ((b,m) <- instCtx.instantiated) { - instantiation ++= newCtx.instantiate(b, m)(quantification) - } - instCtx.merge(newCtx) + val qs = for (matchers <- matchQuorums) yield { + val newQ = encoder.encodeId(template.qs._1) + val substituter = encoder.substitute(Map(template.qs._2 -> newQ)) + + val quantification = new Quantification( + template.pathVar, + template.qs._1 -> newQ, + template.q2s, template.insts, template.guardVar, + quantified, matchers, template.matchers, + template.condVars, template.exprVars, template.condTree, + template.clauses map substituter, // one clause depends on 'q' (and therefore 'newQ') + template.blockers, template.applications, template.lambdas + ) + + quantifications += quantification + + val newCtx = new InstantiationContext() + for ((b,m) <- instCtx.instantiated) { + instantiation ++= newCtx.instantiate(b, m)(quantification) + } + instCtx.merge(newCtx) - quantification.qs._2 - } + quantification.qs._2 + } - instantiation = instantiation withClause { - val newQs = - if (qs.isEmpty) trueT - else if (qs.size == 1) qs.head - else encoder.mkAnd(qs : _*) - encoder.mkImplies(substMap(template.start), encoder.mkEquals(substMap(template.qs._2), newQs)) - } + instantiation = instantiation withClause { + val newQs = + if (qs.isEmpty) trueT + else if (qs.size == 1) qs.head + else encoder.mkAnd(qs : _*) + encoder.mkImplies(template.start, encoder.mkEquals(qT, newQs)) + } - val quantifierSubst = uniformSubst(template.quantifiers) - val substituter = encoder.substitute(substMap ++ quantifierSubst) + val quantifierSubst = uniformSubst(template.quantifiers) + val substituter = encoder.substitute(quantifierSubst) - for { - (_, ms) <- template.matchers; m <- ms - sm = m.substitute(substituter) - if !instCtx.corresponding(sm).exists(_._2.args == sm.args) - } instantiation ++= instCtx.instantiate(Set.empty, sm)(quantifications.toSeq : _*) + for { + (_, ms) <- template.matchers; m <- ms + sm = m.substitute(substituter, Map.empty) + if !instCtx.corresponding(sm).exists(_._2.args == sm.args) + } instantiation ++= instCtx.instantiate(Set.empty, sm)(quantifications.toSeq : _*) - instantiation + templates += template.key -> qT + (qT, instantiation) + } } def instantiateMatcher(blocker: T, matcher: Matcher[T]): Instantiation[T] = { @@ -779,7 +797,7 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage var prev = emptyT for ((b, m) <- insts.toSeq) { val next = encoder.encodeId(setNext) - val argsMap = (elems zip m.args).map { case (idT, arg) => idT -> Matcher.argValue(arg) } + val argsMap = (elems zip m.args).map { case (idT, arg) => idT -> arg.encoded } val substMap = Map(guardT -> b, setPrevT -> prev, setNextT -> next) ++ argsMap prev = next clauses += encoder.substitute(substMap)(setT) @@ -787,7 +805,7 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage val setMap = Map(setPrevT -> prev) for ((b, m) <- ctx.toSeq) { - val substMap = setMap ++ (elems zip m.args).map(p => p._1 -> Matcher.argValue(p._2)) + val substMap = setMap ++ (elems zip m.args).map(p => p._1 -> p._2.encoded) clauses += encoder.substitute(substMap)(encoder.mkImplies(b, containsT)) } } diff --git a/src/main/scala/leon/solvers/templates/TemplateGenerator.scala b/src/main/scala/leon/solvers/templates/TemplateGenerator.scala index 60ddb145058eeb33b4bcb796307fba87d586d52f..aa0e5dad6506be945919cb22361cce649f40c077 100644 --- a/src/main/scala/leon/solvers/templates/TemplateGenerator.scala +++ b/src/main/scala/leon/solvers/templates/TemplateGenerator.scala @@ -65,7 +65,9 @@ class TemplateGenerator[T](val encoder: TemplateEncoder[T], val invocationEqualsBody : Option[Expr] = lambdaBody match { case Some(body) if isRealFunDef => - val b : Expr = And(Equals(invocation, body), liftedEquals(invocation, body, lambdaArguments)) + val b : Expr = And( + liftedEquals(invocation, body, lambdaArguments), + Equals(invocation, body)) Some(if(prec.isDefined) { Implies(prec.get, b) @@ -121,7 +123,7 @@ class TemplateGenerator[T](val encoder: TemplateEncoder[T], } private def lambdaArgs(expr: Expr): Seq[Identifier] = expr match { - case Lambda(args, body) => args.map(_.id) ++ lambdaArgs(body) + case Lambda(args, body) => args.map(_.id.freshen) ++ lambdaArgs(body) case IsTyped(_, _: FunctionType) => sys.error("Only applicable on lambda chains") case _ => Seq.empty } @@ -133,7 +135,7 @@ class TemplateGenerator[T](val encoder: TemplateEncoder[T], val arguments = currArgs.map(_.toVariable) val apply = if (inline) application _ else Application val (appliedInv, appliedBody) = (apply(i, arguments), apply(b, arguments)) - Equals(appliedInv, appliedBody) +: rec(appliedInv, appliedBody, nextArgs, false) + rec(appliedInv, appliedBody, nextArgs, false) :+ Equals(appliedInv, appliedBody) case _ => assert(args.isEmpty, "liftedEquals should consume all provided arguments") Seq.empty @@ -205,7 +207,7 @@ class TemplateGenerator[T](val encoder: TemplateEncoder[T], // id => expr && ... && expr var guardedExprs = Map[Identifier, Seq[Expr]]() def storeGuarded(guardVar : Identifier, expr : Expr) : Unit = { - assert(expr.getType == BooleanType, expr.asString(Program.empty)(LeonContext.empty)) + assert(expr.getType == BooleanType, expr.asString(Program.empty)(LeonContext.empty) + " is not of type Boolean") val prev = guardedExprs.getOrElse(guardVar, Nil) @@ -453,8 +455,10 @@ class TemplateGenerator[T](val encoder: TemplateEncoder[T], Equals(Variable(q), And(Variable(q2), Variable(inst))) ) ++ qGuarded.getOrElse(pathVar, Seq.empty))) + val dependencies: Map[Identifier, T] = vars.filterNot(quantifiers).map(id => id -> localSubst(id)).toMap val template = QuantificationTemplate[T](encoder, manager, pathVar -> encodedCond(pathVar), - qs, q2, inst, guard, idQuantifiers zip trQuantifiers, qConds, qExprs, qTree, allGuarded, qTemplates, localSubst) + qs, q2, inst, guard, idQuantifiers zip trQuantifiers, qConds, qExprs, qTree, allGuarded, qTemplates, localSubst, + dependencies, Forall(quantifiers.toSeq.sortBy(_.uniqueName).map(ValDef(_)), flatConj)) registerQuantification(template) Variable(q) } diff --git a/src/main/scala/leon/solvers/templates/TemplateInfo.scala b/src/main/scala/leon/solvers/templates/TemplateInfo.scala index 033f15dd6f251026a260ed6344212239e1714a37..27df9b25d13412c106f2bf30d6c75b1266b19d93 100644 --- a/src/main/scala/leon/solvers/templates/TemplateInfo.scala +++ b/src/main/scala/leon/solvers/templates/TemplateInfo.scala @@ -5,15 +5,22 @@ package solvers package templates import purescala.Definitions.TypedFunDef +import Template.Arg -case class TemplateCallInfo[T](tfd: TypedFunDef, args: Seq[T]) { +case class TemplateCallInfo[T](tfd: TypedFunDef, args: Seq[Arg[T]]) { override def toString = { - tfd.signature+args.mkString("(", ", ", ")") + tfd.signature + args.map { + case Right(m) => m.toString + case Left(v) => v.toString + }.mkString("(", ", ", ")") } } -case class TemplateAppInfo[T](template: LambdaTemplate[T], equals: T, args: Seq[T]) { +case class TemplateAppInfo[T](template: LambdaTemplate[T], equals: T, args: Seq[Arg[T]]) { override def toString = { - template.ids._2 + "|" + equals + args.mkString("(", ",", ")") + template.ids._2 + "|" + equals + args.map { + case Right(m) => m.toString + case Left(v) => v.toString + }.mkString("(", ",", ")") } } diff --git a/src/main/scala/leon/solvers/templates/TemplateManager.scala b/src/main/scala/leon/solvers/templates/TemplateManager.scala index bb6629ec16988a79eb686259aea4fa32c6111dbb..2b75f08f0480cf272515bb8d8393e01e29d4dbf1 100644 --- a/src/main/scala/leon/solvers/templates/TemplateManager.scala +++ b/src/main/scala/leon/solvers/templates/TemplateManager.scala @@ -57,6 +57,7 @@ object Instantiation { } import Instantiation.{empty => _, _} +import Template.Arg trait Template[T] { self => val encoder : TemplateEncoder[T] @@ -76,27 +77,14 @@ trait Template[T] { self => lazy val start = pathVar._2 - private var substCache : Map[Seq[T],Map[T,T]] = Map.empty - - def instantiate(aVar: T, args: Seq[T]): Instantiation[T] = { - - val baseSubstMap : Map[T,T] = substCache.get(args) match { - case Some(subst) => subst - case None => - val subst = exprVars.map { case (id, idT) => idT -> encoder.encodeId(id) } ++ - manager.freshConds(pathVar._1 -> aVar, condVars, condTree) ++ - (this.args zip args) - substCache += args -> subst - subst - } - - val lambdaSubstMap = lambdas.map(lambda => lambda.ids._2 -> encoder.encodeId(lambda.ids._1)) - val quantificationSubstMap = quantifications.map(q => q.qs._2 -> encoder.encodeId(q.qs._1)) - val substMap : Map[T,T] = baseSubstMap ++ lambdaSubstMap ++ quantificationSubstMap + (start -> aVar) - instantiate(substMap) + def instantiate(aVar: T, args: Seq[Arg[T]]): Instantiation[T] = { + val (substMap, instantiation) = Template.substitution(encoder, manager, + condVars, exprVars, condTree, quantifications, lambdas, + (this.args zip args).toMap + (start -> Left(aVar)), pathVar._1, aVar) + instantiation ++ instantiate(substMap) } - protected def instantiate(substMap: Map[T, T]): Instantiation[T] = { + protected def instantiate(substMap: Map[T, Arg[T]]): Instantiation[T] = { Template.instantiate(encoder, manager, clauses, blockers, applications, quantifications, matchers, lambdas, substMap) } @@ -106,6 +94,23 @@ trait Template[T] { self => object Template { + type Arg[T] = Either[T, Matcher[T]] + + implicit class ArgWrapper[T](arg: Arg[T]) { + def encoded: T = arg match { + case Left(value) => value + case Right(matcher) => matcher.encoded + } + + def substitute(substituter: T => T, matcherSubst: Map[T, Matcher[T]]): Arg[T] = arg match { + case Left(v) => matcherSubst.get(v) match { + case Some(m) => Right(m) + case None => Left(substituter(v)) + } + case Right(m) => Right(m.substitute(substituter, matcherSubst)) + } + } + private def invocationMatcher[T](encodeExpr: Expr => T)(tfd: TypedFunDef, args: Seq[Expr]): Matcher[T] = { assert(tfd.returnType.isInstanceOf[FunctionType], "invocationMatcher() is only defined on function-typed defs") @@ -144,9 +149,9 @@ object Template { encodeExpr(Implies(Variable(b), e)) }).toSeq - val optIdCall = optCall.map(tfd => TemplateCallInfo[T](tfd, arguments.map(_._2))) + val optIdCall = optCall.map(tfd => TemplateCallInfo[T](tfd, arguments.map(p => Left(p._2)))) val optIdApp = optApp.map { case (idT, tpe) => - App(idT, bestRealType(tpe).asInstanceOf[FunctionType], arguments.map(_._2)) + App(idT, bestRealType(tpe).asInstanceOf[FunctionType], arguments.map(p => Left(p._2))) } lazy val invocMatcher = optCall.filter(_.returnType.isInstanceOf[FunctionType]) @@ -163,12 +168,7 @@ object Template { var matchInfos : Set[Matcher[T]] = Set.empty for (e <- es) { - funInfos ++= firstOrderCallsOf(e).map(p => TemplateCallInfo(p._1, p._2.map(encodeExpr))) - appInfos ++= firstOrderAppsOf(e).map { case (c, args) => - App(encodeExpr(c), bestRealType(c.getType).asInstanceOf[FunctionType], args.map(encodeExpr)) - } - - matchInfos ++= fold[Map[Expr, Matcher[T]]] { (expr, res) => + val exprToMatcher = fold[Map[Expr, Matcher[T]]] { (expr, res) => val result = res.flatten.toMap result ++ (expr match { @@ -183,7 +183,19 @@ object Template { Some(expr -> Matcher(encodeExpr(c), bestRealType(c.getType), encodedArgs, encodeExpr(expr))) case _ => None }) - }(e).values + }(e) + + def encodeArg(arg: Expr): Arg[T] = exprToMatcher.get(arg) match { + case Some(matcher) => Right(matcher) + case None => Left(encodeExpr(arg)) + } + + funInfos ++= firstOrderCallsOf(e).map(p => TemplateCallInfo(p._1, p._2.map(encodeArg))) + appInfos ++= firstOrderAppsOf(e).map { case (c, args) => + App(encodeExpr(c), bestRealType(c.getType).asInstanceOf[FunctionType], args.map(encodeArg)) + } + + matchInfos ++= exprToMatcher.values } val calls = funInfos -- optIdCall @@ -194,7 +206,7 @@ object Template { val matchs = matchInfos.filter { case m @ Matcher(mc, mtpe, margs, _) => !optIdApp.exists { case App(ac, atpe, aargs) => - mc == ac && mtpe == atpe && margs.map(Matcher.argValue) == aargs + mc == ac && mtpe == atpe && margs == aargs } } ++ (if (funInfos.exists(info => Some(info) == optIdCall)) invocMatcher else None) @@ -212,8 +224,9 @@ object Template { " * Activating boolean : " + pathVar._1 + "\n" + " * Control booleans : " + condVars.keys.mkString(", ") + "\n" + " * Expression vars : " + exprVars.keys.mkString(", ") + "\n" + - " * Clauses : " + - (for ((b,es) <- guardedExprs; e <- es) yield (b + " ==> " + e)).mkString("\n ") + "\n" + + " * Clauses : " + (if (guardedExprs.isEmpty) "\n" else { + "\n " + (for ((b,es) <- guardedExprs; e <- es) yield (b + " ==> " + e)).mkString("\n ") + "\n" + }) + " * Invocation-blocks :" + (if (blockers.isEmpty) "\n" else { "\n " + blockers.map(p => p._1 + " ==> " + p._2).mkString("\n ") + "\n" }) + @@ -231,6 +244,60 @@ object Template { (clauses, encodedBlockers, encodedApps, encodedMatchers, stringRepr) } + def substitution[T]( + encoder: TemplateEncoder[T], + manager: QuantificationManager[T], + condVars: Map[Identifier, T], + exprVars: Map[Identifier, T], + condTree: Map[Identifier, Set[Identifier]], + quantifications: Seq[QuantificationTemplate[T]], + lambdas: Seq[LambdaTemplate[T]], + baseSubst: Map[T, Arg[T]], + pathVar: Identifier, + aVar: T + ): (Map[T, Arg[T]], Instantiation[T]) = { + val freshSubst = exprVars.map { case (id, idT) => idT -> encoder.encodeId(id) } ++ + manager.freshConds(pathVar -> aVar, condVars, condTree) + val matcherSubst = baseSubst.collect { case (c, Right(m)) => c -> m } + var subst = freshSubst.mapValues(Left(_)) ++ baseSubst + + // /!\ CAREFUL /!\ + // We have to be wary while computing the lambda subst map since lambdas can + // depend on each other. However, these dependencies cannot be cyclic so it + // suffices to make sure the traversal order is correct. + var instantiation : Instantiation[T] = Instantiation.empty + var seen : Set[LambdaTemplate[T]] = Set.empty + + val lambdaKeys = lambdas.map(lambda => lambda.ids._1 -> lambda).toMap + def extractSubst(lambda: LambdaTemplate[T]): Unit = { + for { + dep <- lambda.dependencies.flatMap(p => lambdaKeys.get(p._1)) + if !seen(dep) + } extractSubst(dep) + + if (!seen(lambda)) { + val substMap = subst.mapValues(_.encoded) + val substLambda = lambda.substitute(encoder.substitute(substMap), matcherSubst) + val (idT, inst) = manager.instantiateLambda(substLambda) + instantiation ++= inst + subst += lambda.ids._2 -> Left(idT) + seen += lambda + } + } + + for (l <- lambdas) extractSubst(l) + + for (q <- quantifications) { + val substMap = subst.mapValues(_.encoded) + val substQuant = q.substitute(encoder.substitute(substMap), matcherSubst) + val (qT, inst) = manager.instantiateQuantification(substQuant) + instantiation ++= inst + subst += q.qs._2 -> Left(qT) + } + + (subst, instantiation) + } + def instantiate[T]( encoder: TemplateEncoder[T], manager: QuantificationManager[T], @@ -240,33 +307,27 @@ object Template { quantifications: Seq[QuantificationTemplate[T]], matchers: Map[T, Set[Matcher[T]]], lambdas: Seq[LambdaTemplate[T]], - substMap: Map[T, T] + substMap: Map[T, Arg[T]] ): Instantiation[T] = { - val substituter : T => T = encoder.substitute(substMap) + val substituter : T => T = encoder.substitute(substMap.mapValues(_.encoded)) + val msubst = substMap.collect { case (c, Right(m)) => c -> m } val newClauses = clauses.map(substituter) val newBlockers = blockers.map { case (b,fis) => - substituter(b) -> fis.map(fi => fi.copy(args = fi.args.map(substituter))) + substituter(b) -> fis.map(fi => fi.copy(args = fi.args.map(_.substitute(substituter, msubst)))) } var instantiation: Instantiation[T] = (newClauses, newBlockers, Map.empty) - for (lambda <- lambdas) { - instantiation ++= manager.instantiateLambda(lambda.substitute(substituter)) - } - for ((b,apps) <- applications; bp = substituter(b); app <- apps) { - val newApp = app.copy(caller = substituter(app.caller), args = app.args.map(substituter)) + val newApp = app.copy(caller = substituter(app.caller), args = app.args.map(_.substitute(substituter, msubst))) instantiation ++= manager.instantiateApp(bp, newApp) } for ((b, matchs) <- matchers; bp = substituter(b); m <- matchs) { - instantiation ++= manager.instantiateMatcher(bp, m.substitute(substituter)) - } - - for (q <- quantifications) { - instantiation ++= manager.instantiateQuantification(q, substMap) + val newMatcher = m.substitute(substituter, msubst) + instantiation ++= manager.instantiateMatcher(bp, newMatcher) } instantiation @@ -343,8 +404,8 @@ class FunctionTemplate[T] private( private lazy val str : String = stringRepr() override def toString : String = str - override def instantiate(aVar: T, args: Seq[T]): (Seq[T], Map[T, Set[TemplateCallInfo[T]]], Map[(T, App[T]), Set[TemplateAppInfo[T]]]) = { - if (!isRealFunDef) manager.registerFree(tfd.params.map(_.id) zip args) + override def instantiate(aVar: T, args: Seq[Arg[T]]): Instantiation[T] = { + if (!isRealFunDef) manager.registerFree(tfd.params.map(_.id) zip args.map(_.left.get)) super.instantiate(aVar, args) } } diff --git a/src/main/scala/leon/solvers/templates/UnrollingBank.scala b/src/main/scala/leon/solvers/templates/UnrollingBank.scala index d1c4432a3c69f1882c9b4a1787a12dc8cf64f520..4543262d74204dd9f77d3eeea8882bf543956a90 100644 --- a/src/main/scala/leon/solvers/templates/UnrollingBank.scala +++ b/src/main/scala/leon/solvers/templates/UnrollingBank.scala @@ -171,7 +171,7 @@ class UnrollingBank[T <% Printable](ctx: LeonContext, templateGenerator: Templat // define an activating boolean... val template = templateGenerator.mkTemplate(expr) - val trArgs = template.tfd.params.map(vd => bindings(Variable(vd.id))) + val trArgs = template.tfd.params.map(vd => Left(bindings(Variable(vd.id)))) for (vd <- template.tfd.params if vd.getType.isInstanceOf[FunctionType]) { functionVars += vd.getType -> (functionVars.getOrElse(vd.getType, Set()) + bindings(vd.toVariable)) @@ -240,16 +240,22 @@ class UnrollingBank[T <% Printable](ctx: LeonContext, templateGenerator: Templat callInfos --= ids val apps = ids.flatMap(id => blockerToApps.get(id)) - val thisAppInfos = apps.map(app => app -> appInfos(app)) + val thisAppInfos = apps.map(app => app -> { + val (gen, _, _, _, infos) = appInfos(app) + (gen, infos) + }) + blockerToApps --= ids appInfos --= apps - for ((app, (_, _, _, _, infos)) <- thisAppInfos if infos.nonEmpty) { + for ((app, (_, infos)) <- thisAppInfos if infos.nonEmpty) { val extension = extendAppBlock(app, infos) reporter.debug(" -> extending lambda blocker: " + extension) newClauses :+= extension } + var fastAppInfos : Map[(T, App[T]), (Int, Set[TemplateAppInfo[T]])] = Map.empty + for ((id, (gen, _, _, infos)) <- newCallInfos; info @ TemplateCallInfo(tfd, args) <- infos) { var newCls = Seq[T]() @@ -268,13 +274,23 @@ class UnrollingBank[T <% Printable](ctx: LeonContext, templateGenerator: Templat //reporter.debug(template) val (newExprs, callBlocks, appBlocks) = template.instantiate(defBlocker, args) - val blockExprs = freshAppBlocks(appBlocks.keys) + + // we handle obvious appBlocks in an immediate manner in order to increase + // performance for folds that just pass a lambda around to recursive calls + val (fastApps, nextApps) = appBlocks.partition(p => p._2.toSeq match { + case Seq(TemplateAppInfo(_, equals, _)) if equals == manager.trueT => true + case _ => false + }) + + fastAppInfos ++= fastApps.mapValues(gen -> _) + + val blockExprs = freshAppBlocks(nextApps.keys) for((b, newInfos) <- callBlocks) { registerCallBlocker(nextGeneration(gen), b, newInfos) } - for ((app, newInfos) <- appBlocks) { + for ((app, newInfos) <- nextApps) { registerAppBlocker(nextGeneration(gen), app, newInfos) } @@ -296,7 +312,7 @@ class UnrollingBank[T <% Printable](ctx: LeonContext, templateGenerator: Templat newClauses ++= newCls } - for ((app @ (b, _), (gen, _, _, _, infos)) <- thisAppInfos; info @ TemplateAppInfo(template, equals, args) <- infos) { + for ((app @ (b, _), (gen, infos)) <- thisAppInfos ++ fastAppInfos; info @ TemplateAppInfo(template, equals, args) <- infos) { var newCls = Seq.empty[T] val lambdaBlocker = lambdaBlockers.get(info) match { diff --git a/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala b/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala index 528f1b28dd1267391862bfaef4fd9fac5a7be9ef..ac1e8855a4a53ed5ef2f66c34d4b015d9a26ba3f 100644 --- a/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala +++ b/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala @@ -91,10 +91,11 @@ trait AbstractZ3Solver extends Solver { protected val adtManager = new ADTManager(context) // Bijections between Leon Types/Functions/Ids to Z3 Sorts/Decls/ASTs - protected val functions = new IncrementalBijection[TypedFunDef, Z3FuncDecl]() - protected val generics = new IncrementalBijection[GenericValue, Z3FuncDecl]() - protected val sorts = new IncrementalBijection[TypeTree, Z3Sort]() - protected val variables = new IncrementalBijection[Expr, Z3AST]() + protected val functions = new IncrementalBijection[TypedFunDef, Z3FuncDecl]() + protected val generics = new IncrementalBijection[GenericValue, Z3FuncDecl]() + protected val lambdas = new IncrementalBijection[FunctionType, Z3FuncDecl]() + protected val sorts = new IncrementalBijection[TypeTree, Z3Sort]() + protected val variables = new IncrementalBijection[Expr, Z3AST]() protected val constructors = new IncrementalBijection[TypeTree, Z3FuncDecl]() protected val selectors = new IncrementalBijection[(TypeTree, Int), Z3FuncDecl]() @@ -108,6 +109,7 @@ trait AbstractZ3Solver extends Solver { z3 = new Z3Context(z3cfg) functions.clear() + lambdas.clear() generics.clear() sorts.clear() variables.clear() @@ -190,7 +192,6 @@ trait AbstractZ3Solver extends Solver { } } } - } // Prepares some of the Z3 sorts, but *not* the tuple sorts; these are created on-demand. @@ -256,313 +257,330 @@ trait AbstractZ3Solver extends Solver { case ft @ FunctionType(from, to) => sorts.cachedB(ft) { - val fromSort = typeToSort(tupleTypeWrap(from)) - val toSort = typeToSort(to) - - z3.mkArraySort(fromSort, toSort) + val symbol = z3.mkFreshStringSymbol(ft.toString) + z3.mkUninterpretedSort(symbol) } case other => throw SolverUnsupportedError(other, this) } + + protected[leon] def toZ3Formula(expr: Expr, initialMap: Map[Identifier, Z3AST] = Map.empty): Z3AST = { - var z3Vars: Map[Identifier,Z3AST] = if(initialMap.nonEmpty) { + implicit var z3Vars: Map[Identifier,Z3AST] = if(initialMap.nonEmpty) { initialMap } else { // FIXME TODO pleeeeeeeease make this cleaner. Ie. decide what set of // variable has to remain in a map etc. variables.aToB.collect{ case (Variable(id), p2) => id -> p2 } } - - def rec(ex: Expr): Z3AST = ex match { - - // TODO: Leave that as a specialization? - case LetTuple(ids, e, b) => { - z3Vars = z3Vars ++ ids.zipWithIndex.map { case (id, ix) => - val entry = id -> rec(tupleSelect(e, ix + 1, ids.size)) - entry + new Z3StringConversion[Z3AST] { + def getProgram = AbstractZ3Solver.this.program + def convertToTarget(e: Expr)(implicit bindings: Map[Identifier, Z3AST]): Z3AST = { + rec(e) } - val rb = rec(b) - z3Vars = z3Vars -- ids - rb - } - - case p @ Passes(_, _, _) => - rec(p.asConstraint) - - case me @ MatchExpr(s, cs) => - rec(matchToIfThenElse(me)) - - case Let(i, e, b) => { - val re = rec(e) - z3Vars = z3Vars + (i -> re) - val rb = rec(b) - z3Vars = z3Vars - i - rb - } - - case Waypoint(_, e, _) => rec(e) - case a @ Assert(cond, err, body) => - rec(IfExpr(cond, body, Error(a.getType, err.getOrElse("Assertion failed")).setPos(a.getPos)).setPos(a.getPos)) - - case e @ Error(tpe, _) => { - val newAST = z3.mkFreshConst("errorValue", typeToSort(tpe)) - // Might introduce dupplicates (e), but no worries here - variables += (e -> newAST) - newAST - } - case v @ Variable(id) => z3Vars.get(id) match { - case Some(ast) => - ast - case None => { - variables.getB(v) match { - case Some(ast) => + def targetApplication(tfd: TypedFunDef, args: Seq[Z3AST])(implicit bindings: Map[Identifier, Z3AST]): Z3AST = { + z3.mkApp(functionDefToDecl(tfd), args: _*) + } + def rec(ex: Expr): Z3AST = ex match { + + // TODO: Leave that as a specialization? + case LetTuple(ids, e, b) => { + z3Vars = z3Vars ++ ids.zipWithIndex.map { case (id, ix) => + val entry = id -> rec(tupleSelect(e, ix + 1, ids.size)) + entry + } + val rb = rec(b) + z3Vars = z3Vars -- ids + rb + } + + case p @ Passes(_, _, _) => + rec(p.asConstraint) + + case me @ MatchExpr(s, cs) => + rec(matchToIfThenElse(me)) + + case Let(i, e, b) => { + val re = rec(e) + z3Vars = z3Vars + (i -> re) + val rb = rec(b) + z3Vars = z3Vars - i + rb + } + + case Waypoint(_, e, _) => rec(e) + case a @ Assert(cond, err, body) => + rec(IfExpr(cond, body, Error(a.getType, err.getOrElse("Assertion failed")).setPos(a.getPos)).setPos(a.getPos)) + + case e @ Error(tpe, _) => { + val newAST = z3.mkFreshConst("errorValue", typeToSort(tpe)) + // Might introduce dupplicates (e), but no worries here + variables += (e -> newAST) + newAST + } + case v @ Variable(id) => z3Vars.get(id) match { + case Some(ast) => ast - - case None => + case None => { + variables.getB(v) match { + case Some(ast) => + ast + + case None => val newAST = z3.mkFreshConst(id.uniqueName, typeToSort(v.getType)) z3Vars = z3Vars + (id -> newAST) variables += (v -> newAST) newAST + } } + } + + case ite @ IfExpr(c, t, e) => z3.mkITE(rec(c), rec(t), rec(e)) + case And(exs) => z3.mkAnd(exs.map(rec): _*) + case Or(exs) => z3.mkOr(exs.map(rec): _*) + case Implies(l, r) => z3.mkImplies(rec(l), rec(r)) + case Not(Equals(l, r)) => z3.mkDistinct(rec(l), rec(r)) + case Not(e) => z3.mkNot(rec(e)) + case IntLiteral(v) => z3.mkInt(v, typeToSort(Int32Type)) + case InfiniteIntegerLiteral(v) => z3.mkNumeral(v.toString, typeToSort(IntegerType)) + case FractionalLiteral(n, d) => z3.mkNumeral(s"$n / $d", typeToSort(RealType)) + case CharLiteral(c) => z3.mkInt(c, typeToSort(CharType)) + case BooleanLiteral(v) => if (v) z3.mkTrue() else z3.mkFalse() + case Equals(l, r) => z3.mkEq(rec( l ), rec( r ) ) + case Plus(l, r) => z3.mkAdd(rec(l), rec(r)) + case Minus(l, r) => z3.mkSub(rec(l), rec(r)) + case Times(l, r) => z3.mkMul(rec(l), rec(r)) + case Division(l, r) => { + val rl = rec(l) + val rr = rec(r) + z3.mkITE( + z3.mkGE(rl, z3.mkNumeral("0", typeToSort(IntegerType))), + z3.mkDiv(rl, rr), + z3.mkUnaryMinus(z3.mkDiv(z3.mkUnaryMinus(rl), rr)) + ) + } + case Remainder(l, r) => { + val q = rec(Division(l, r)) + z3.mkSub(rec(l), z3.mkMul(rec(r), q)) + } + case Modulo(l, r) => { + z3.mkMod(rec(l), rec(r)) + } + case UMinus(e) => z3.mkUnaryMinus(rec(e)) + + case RealPlus(l, r) => z3.mkAdd(rec(l), rec(r)) + case RealMinus(l, r) => z3.mkSub(rec(l), rec(r)) + case RealTimes(l, r) => z3.mkMul(rec(l), rec(r)) + case RealDivision(l, r) => z3.mkDiv(rec(l), rec(r)) + case RealUMinus(e) => z3.mkUnaryMinus(rec(e)) + + case BVPlus(l, r) => z3.mkBVAdd(rec(l), rec(r)) + case BVMinus(l, r) => z3.mkBVSub(rec(l), rec(r)) + case BVTimes(l, r) => z3.mkBVMul(rec(l), rec(r)) + case BVDivision(l, r) => z3.mkBVSdiv(rec(l), rec(r)) + case BVRemainder(l, r) => z3.mkBVSrem(rec(l), rec(r)) + case BVUMinus(e) => z3.mkBVNeg(rec(e)) + case BVNot(e) => z3.mkBVNot(rec(e)) + case BVAnd(l, r) => z3.mkBVAnd(rec(l), rec(r)) + case BVOr(l, r) => z3.mkBVOr(rec(l), rec(r)) + case BVXOr(l, r) => z3.mkBVXor(rec(l), rec(r)) + case BVShiftLeft(l, r) => z3.mkBVShl(rec(l), rec(r)) + case BVAShiftRight(l, r) => z3.mkBVAshr(rec(l), rec(r)) + case BVLShiftRight(l, r) => z3.mkBVLshr(rec(l), rec(r)) + case LessThan(l, r) => l.getType match { + case IntegerType => z3.mkLT(rec(l), rec(r)) + case RealType => z3.mkLT(rec(l), rec(r)) + case Int32Type => z3.mkBVSlt(rec(l), rec(r)) + case CharType => z3.mkBVSlt(rec(l), rec(r)) + } + case LessEquals(l, r) => l.getType match { + case IntegerType => z3.mkLE(rec(l), rec(r)) + case RealType => z3.mkLE(rec(l), rec(r)) + case Int32Type => z3.mkBVSle(rec(l), rec(r)) + case CharType => z3.mkBVSle(rec(l), rec(r)) + //case _ => throw new IllegalStateException(s"l: $l, Left type: ${l.getType} Expr: $ex") + } + case GreaterThan(l, r) => l.getType match { + case IntegerType => z3.mkGT(rec(l), rec(r)) + case RealType => z3.mkGT(rec(l), rec(r)) + case Int32Type => z3.mkBVSgt(rec(l), rec(r)) + case CharType => z3.mkBVSgt(rec(l), rec(r)) + } + case GreaterEquals(l, r) => l.getType match { + case IntegerType => z3.mkGE(rec(l), rec(r)) + case RealType => z3.mkGE(rec(l), rec(r)) + case Int32Type => z3.mkBVSge(rec(l), rec(r)) + case CharType => z3.mkBVSge(rec(l), rec(r)) + } + + case StringConverted(result) => + result + + case u : UnitLiteral => + val tpe = normalizeType(u.getType) + typeToSort(tpe) + val constructor = constructors.toB(tpe) + constructor() + + case t @ Tuple(es) => + val tpe = normalizeType(t.getType) + typeToSort(tpe) + val constructor = constructors.toB(tpe) + constructor(es.map(rec): _*) + + case ts @ TupleSelect(t, i) => + val tpe = normalizeType(t.getType) + typeToSort(tpe) + val selector = selectors.toB((tpe, i-1)) + selector(rec(t)) + + case c @ CaseClass(ct, args) => + typeToSort(ct) // Making sure the sort is defined + val constructor = constructors.toB(ct) + constructor(args.map(rec): _*) + + case c @ CaseClassSelector(cct, cc, sel) => + typeToSort(cct) // Making sure the sort is defined + val selector = selectors.toB(cct, c.selectorIndex) + selector(rec(cc)) + + case AsInstanceOf(expr, ct) => + rec(expr) + + case IsInstanceOf(e, act: AbstractClassType) => + act.knownCCDescendants match { + case Seq(cct) => + rec(IsInstanceOf(e, cct)) + case more => + val i = FreshIdentifier("e", act, alwaysShowUniqueID = true) + rec(Let(i, e, orJoin(more map(IsInstanceOf(Variable(i), _))))) + } + + case IsInstanceOf(e, cct: CaseClassType) => + typeToSort(cct) // Making sure the sort is defined + val tester = testers.toB(cct) + tester(rec(e)) + + case al @ ArraySelect(a, i) => + val tpe = normalizeType(a.getType) + + val sa = rec(a) + val content = selectors.toB((tpe, 1))(sa) + + z3.mkSelect(content, rec(i)) + + case al @ ArrayUpdated(a, i, e) => + val tpe = normalizeType(a.getType) + + val sa = rec(a) + val ssize = selectors.toB((tpe, 0))(sa) + val scontent = selectors.toB((tpe, 1))(sa) + + val newcontent = z3.mkStore(scontent, rec(i), rec(e)) + + val constructor = constructors.toB(tpe) + + constructor(ssize, newcontent) + + case al @ ArrayLength(a) => + val tpe = normalizeType(a.getType) + val sa = rec(a) + selectors.toB((tpe, 0))(sa) + + case arr @ FiniteArray(elems, oDefault, length) => + val at @ ArrayType(base) = normalizeType(arr.getType) + typeToSort(at) + + val default = oDefault.getOrElse(simplestValue(base)) + + val ar = rec(RawArrayValue(Int32Type, elems.map { + case (i, e) => IntLiteral(i) -> e + }, default)) + + constructors.toB(at)(rec(length), ar) + + case f @ FunctionInvocation(tfd, args) => + z3.mkApp(functionDefToDecl(tfd), args.map(rec): _*) + + case fa @ Application(caller, args) => + val ft @ FunctionType(froms, to) = normalizeType(caller.getType) + val funDecl = lambdas.cachedB(ft) { + val sortSeq = (ft +: froms).map(tpe => typeToSort(tpe)) + val returnSort = typeToSort(to) + + val name = FreshIdentifier("dynLambda").uniqueName + z3.mkFreshFuncDecl(name, sortSeq, returnSort) + } + z3.mkApp(funDecl, (caller +: args).map(rec): _*) + + case ElementOfSet(e, s) => z3.mkSetMember(rec(e), rec(s)) + case SubsetOf(s1, s2) => z3.mkSetSubset(rec(s1), rec(s2)) + case SetIntersection(s1, s2) => z3.mkSetIntersect(rec(s1), rec(s2)) + case SetUnion(s1, s2) => z3.mkSetUnion(rec(s1), rec(s2)) + case SetDifference(s1, s2) => z3.mkSetDifference(rec(s1), rec(s2)) + case f @ FiniteSet(elems, base) => elems.foldLeft(z3.mkEmptySet(typeToSort(base)))((ast, el) => z3.mkSetAdd(ast, rec(el))) + + case RawArrayValue(keyTpe, elems, default) => + val ar = z3.mkConstArray(typeToSort(keyTpe), rec(default)) + + elems.foldLeft(ar) { + case (array, (k, v)) => z3.mkStore(array, rec(k), rec(v)) + } + + /** + * ===== Map operations ===== + */ + case m @ FiniteMap(elems, from, to) => + val MapType(_, t) = normalizeType(m.getType) + + rec(RawArrayValue(from, elems.map{ + case (k, v) => (k, CaseClass(library.someType(t), Seq(v))) + }.toMap, CaseClass(library.noneType(t), Seq()))) + + case MapApply(m, k) => + val mt @ MapType(_, t) = normalizeType(m.getType) + typeToSort(mt) + + val el = z3.mkSelect(rec(m), rec(k)) + + // Really ?!? We don't check that it is actually != None? + selectors.toB(library.someType(t), 0)(el) + + case MapIsDefinedAt(m, k) => + val mt @ MapType(_, t) = normalizeType(m.getType) + typeToSort(mt) + + val el = z3.mkSelect(rec(m), rec(k)) + + testers.toB(library.someType(t))(el) + + case MapUnion(m1, FiniteMap(elems, _, _)) => + val mt @ MapType(_, t) = normalizeType(m1.getType) + typeToSort(mt) + + elems.foldLeft(rec(m1)) { case (m, (k,v)) => + z3.mkStore(m, rec(k), rec(CaseClass(library.someType(t), Seq(v)))) + } + + + case gv @ GenericValue(tp, id) => + z3.mkApp(genericValueToDecl(gv)) + + case other => + unsupported(other) } - } - - case ite @ IfExpr(c, t, e) => z3.mkITE(rec(c), rec(t), rec(e)) - case And(exs) => z3.mkAnd(exs.map(rec): _*) - case Or(exs) => z3.mkOr(exs.map(rec): _*) - case Implies(l, r) => z3.mkImplies(rec(l), rec(r)) - case Not(Equals(l, r)) => z3.mkDistinct(rec(l), rec(r)) - case Not(e) => z3.mkNot(rec(e)) - case IntLiteral(v) => z3.mkInt(v, typeToSort(Int32Type)) - case InfiniteIntegerLiteral(v) => z3.mkNumeral(v.toString, typeToSort(IntegerType)) - case FractionalLiteral(n, d) => z3.mkNumeral(s"$n / $d", typeToSort(RealType)) - case CharLiteral(c) => z3.mkInt(c, typeToSort(CharType)) - case BooleanLiteral(v) => if (v) z3.mkTrue() else z3.mkFalse() - case Equals(l, r) => z3.mkEq(rec( l ), rec( r ) ) - case Plus(l, r) => z3.mkAdd(rec(l), rec(r)) - case Minus(l, r) => z3.mkSub(rec(l), rec(r)) - case Times(l, r) => z3.mkMul(rec(l), rec(r)) - case Division(l, r) => { - val rl = rec(l) - val rr = rec(r) - z3.mkITE( - z3.mkGE(rl, z3.mkNumeral("0", typeToSort(IntegerType))), - z3.mkDiv(rl, rr), - z3.mkUnaryMinus(z3.mkDiv(z3.mkUnaryMinus(rl), rr)) - ) - } - case Remainder(l, r) => { - val q = rec(Division(l, r)) - z3.mkSub(rec(l), z3.mkMul(rec(r), q)) - } - case Modulo(l, r) => { - z3.mkMod(rec(l), rec(r)) - } - case UMinus(e) => z3.mkUnaryMinus(rec(e)) - - case RealPlus(l, r) => z3.mkAdd(rec(l), rec(r)) - case RealMinus(l, r) => z3.mkSub(rec(l), rec(r)) - case RealTimes(l, r) => z3.mkMul(rec(l), rec(r)) - case RealDivision(l, r) => z3.mkDiv(rec(l), rec(r)) - case RealUMinus(e) => z3.mkUnaryMinus(rec(e)) - - case BVPlus(l, r) => z3.mkBVAdd(rec(l), rec(r)) - case BVMinus(l, r) => z3.mkBVSub(rec(l), rec(r)) - case BVTimes(l, r) => z3.mkBVMul(rec(l), rec(r)) - case BVDivision(l, r) => z3.mkBVSdiv(rec(l), rec(r)) - case BVRemainder(l, r) => z3.mkBVSrem(rec(l), rec(r)) - case BVUMinus(e) => z3.mkBVNeg(rec(e)) - case BVNot(e) => z3.mkBVNot(rec(e)) - case BVAnd(l, r) => z3.mkBVAnd(rec(l), rec(r)) - case BVOr(l, r) => z3.mkBVOr(rec(l), rec(r)) - case BVXOr(l, r) => z3.mkBVXor(rec(l), rec(r)) - case BVShiftLeft(l, r) => z3.mkBVShl(rec(l), rec(r)) - case BVAShiftRight(l, r) => z3.mkBVAshr(rec(l), rec(r)) - case BVLShiftRight(l, r) => z3.mkBVLshr(rec(l), rec(r)) - case LessThan(l, r) => l.getType match { - case IntegerType => z3.mkLT(rec(l), rec(r)) - case RealType => z3.mkLT(rec(l), rec(r)) - case Int32Type => z3.mkBVSlt(rec(l), rec(r)) - case CharType => z3.mkBVSlt(rec(l), rec(r)) - } - case LessEquals(l, r) => l.getType match { - case IntegerType => z3.mkLE(rec(l), rec(r)) - case RealType => z3.mkLE(rec(l), rec(r)) - case Int32Type => z3.mkBVSle(rec(l), rec(r)) - case CharType => z3.mkBVSle(rec(l), rec(r)) - //case _ => throw new IllegalStateException(s"l: $l, Left type: ${l.getType} Expr: $ex") - } - case GreaterThan(l, r) => l.getType match { - case IntegerType => z3.mkGT(rec(l), rec(r)) - case RealType => z3.mkGT(rec(l), rec(r)) - case Int32Type => z3.mkBVSgt(rec(l), rec(r)) - case CharType => z3.mkBVSgt(rec(l), rec(r)) - } - case GreaterEquals(l, r) => l.getType match { - case IntegerType => z3.mkGE(rec(l), rec(r)) - case RealType => z3.mkGE(rec(l), rec(r)) - case Int32Type => z3.mkBVSge(rec(l), rec(r)) - case CharType => z3.mkBVSge(rec(l), rec(r)) - } - - case u : UnitLiteral => - val tpe = normalizeType(u.getType) - typeToSort(tpe) - val constructor = constructors.toB(tpe) - constructor() - - case t @ Tuple(es) => - val tpe = normalizeType(t.getType) - typeToSort(tpe) - val constructor = constructors.toB(tpe) - constructor(es.map(rec): _*) - - case ts @ TupleSelect(t, i) => - val tpe = normalizeType(t.getType) - typeToSort(tpe) - val selector = selectors.toB((tpe, i-1)) - selector(rec(t)) - - case c @ CaseClass(ct, args) => - typeToSort(ct) // Making sure the sort is defined - val constructor = constructors.toB(ct) - constructor(args.map(rec): _*) - - case c @ CaseClassSelector(cct, cc, sel) => - typeToSort(cct) // Making sure the sort is defined - val selector = selectors.toB(cct, c.selectorIndex) - selector(rec(cc)) - - case AsInstanceOf(expr, ct) => - rec(expr) - - case IsInstanceOf(e, act: AbstractClassType) => - act.knownCCDescendants match { - case Seq(cct) => - rec(IsInstanceOf(e, cct)) - case more => - val i = FreshIdentifier("e", act, alwaysShowUniqueID = true) - rec(Let(i, e, orJoin(more map(IsInstanceOf(Variable(i), _))))) - } - - case IsInstanceOf(e, cct: CaseClassType) => - typeToSort(cct) // Making sure the sort is defined - val tester = testers.toB(cct) - tester(rec(e)) - - case al @ ArraySelect(a, i) => - val tpe = normalizeType(a.getType) - - val sa = rec(a) - val content = selectors.toB((tpe, 1))(sa) - - z3.mkSelect(content, rec(i)) - - case al @ ArrayUpdated(a, i, e) => - val tpe = normalizeType(a.getType) - - val sa = rec(a) - val ssize = selectors.toB((tpe, 0))(sa) - val scontent = selectors.toB((tpe, 1))(sa) - - val newcontent = z3.mkStore(scontent, rec(i), rec(e)) - - val constructor = constructors.toB(tpe) - - constructor(ssize, newcontent) - - case al @ ArrayLength(a) => - val tpe = normalizeType(a.getType) - val sa = rec(a) - selectors.toB((tpe, 0))(sa) - - case arr @ FiniteArray(elems, oDefault, length) => - val at @ ArrayType(base) = normalizeType(arr.getType) - typeToSort(at) - - val default = oDefault.getOrElse(simplestValue(base)) - - val ar = rec(RawArrayValue(Int32Type, elems.map { - case (i, e) => IntLiteral(i) -> e - }, default)) - - constructors.toB(at)(rec(length), ar) - - case f @ FunctionInvocation(tfd, args) => - z3.mkApp(functionDefToDecl(tfd), args.map(rec): _*) - - case fa @ Application(caller, args) => - z3.mkSelect(rec(caller), rec(tupleWrap(args))) - - case ElementOfSet(e, s) => z3.mkSetMember(rec(e), rec(s)) - case SubsetOf(s1, s2) => z3.mkSetSubset(rec(s1), rec(s2)) - case SetIntersection(s1, s2) => z3.mkSetIntersect(rec(s1), rec(s2)) - case SetUnion(s1, s2) => z3.mkSetUnion(rec(s1), rec(s2)) - case SetDifference(s1, s2) => z3.mkSetDifference(rec(s1), rec(s2)) - case f @ FiniteSet(elems, base) => elems.foldLeft(z3.mkEmptySet(typeToSort(base)))((ast, el) => z3.mkSetAdd(ast, rec(el))) - - case RawArrayValue(keyTpe, elems, default) => - val ar = z3.mkConstArray(typeToSort(keyTpe), rec(default)) - - elems.foldLeft(ar) { - case (array, (k, v)) => z3.mkStore(array, rec(k), rec(v)) - } - - /** - * ===== Map operations ===== - */ - case m @ FiniteMap(elems, from, to) => - val MapType(_, t) = normalizeType(m.getType) - - rec(RawArrayValue(from, elems.map{ - case (k, v) => (k, CaseClass(library.someType(t), Seq(v))) - }.toMap, CaseClass(library.noneType(t), Seq()))) - - case MapApply(m, k) => - val mt @ MapType(_, t) = normalizeType(m.getType) - typeToSort(mt) - - val el = z3.mkSelect(rec(m), rec(k)) - - // Really ?!? We don't check that it is actually != None? - selectors.toB(library.someType(t), 0)(el) - - case MapIsDefinedAt(m, k) => - val mt @ MapType(_, t) = normalizeType(m.getType) - typeToSort(mt) - - val el = z3.mkSelect(rec(m), rec(k)) - - testers.toB(library.someType(t))(el) - - case MapUnion(m1, FiniteMap(elems, _, _)) => - val mt @ MapType(_, t) = normalizeType(m1.getType) - typeToSort(mt) - - elems.foldLeft(rec(m1)) { case (m, (k,v)) => - z3.mkStore(m, rec(k), rec(CaseClass(library.someType(t), Seq(v)))) - } - - case gv @ GenericValue(tp, id) => - z3.mkApp(genericValueToDecl(gv)) - - case other => - unsupported(other) - } - - rec(expr) + }.rec(expr) } protected[leon] def fromZ3Formula(model: Z3Model, tree: Z3AST, tpe: TypeTree): Expr = { - - def rec(t: Z3AST, tpe: TypeTree): Expr = { + def rec(t: Z3AST, expected_tpe: TypeTree): Expr = { val kind = z3.getASTKind(t) - - kind match { - case Z3NumeralIntAST(Some(v)) => { + val tpe = Z3StringTypeConversion.convert(expected_tpe)(program) + val res = kind match { + case Z3NumeralIntAST(Some(v)) => val leading = t.toString.substring(0, 2 min t.toString.length) if(leading == "#x") { _root_.smtlib.common.Hexadecimal.fromString(t.toString.substring(2)) match { @@ -570,27 +588,50 @@ trait AbstractZ3Solver extends Solver { tpe match { case Int32Type => IntLiteral(hexa.toInt) case CharType => CharLiteral(hexa.toInt.toChar) + case IntegerType => InfiniteIntegerLiteral(BigInt(hexa.toInt)) case other => unsupported(other, "Unexpected target type for BV value") } case None => unsound(t, "could not translate hexadecimal Z3 numeral") - } + } } else { - InfiniteIntegerLiteral(v) + tpe match { + case Int32Type => IntLiteral(v) + case CharType => CharLiteral(v.toChar) + case IntegerType => InfiniteIntegerLiteral(BigInt(v)) + case other => + unsupported(other, "Unexpected type for BV value: " + other) + } } - } - case Z3NumeralIntAST(None) => { - _root_.smtlib.common.Hexadecimal.fromString(t.toString.substring(2)) match { + + case Z3NumeralIntAST(None) => + val ts = t.toString + reporter.ifDebug(printer => printer(ts))(DebugSectionSynthesis) + if(ts.length > 4 && ts.substring(0, 2) == "bv" && ts.substring(ts.length - 4) == "[32]") { + val integer = ts.substring(2, ts.length - 4) + tpe match { + case Int32Type => + IntLiteral(integer.toLong.toInt) + case CharType => CharLiteral(integer.toInt.toChar) + case IntegerType => + InfiniteIntegerLiteral(BigInt(integer)) + case _ => + reporter.fatalError("Unexpected target type for BV value: " + tpe.asString) + } + } else { + _root_.smtlib.common.Hexadecimal.fromString(t.toString.substring(2)) match { case Some(hexa) => tpe match { case Int32Type => IntLiteral(hexa.toInt) case CharType => CharLiteral(hexa.toInt.toChar) case _ => unsound(t, "unexpected target type for BV value: " + tpe.asString) } - case None => unsound(t, "could not translate Z3NumeralIntAST numeral") + case None => unsound(t, "could not translate Z3NumeralIntAST numeral") + } } - } + case Z3NumeralRealAST(n: BigInt, d: BigInt) => FractionalLiteral(n, d) + case Z3AppAST(decl, args) => val argsSize = args.size if(argsSize == 0 && (variables containsB t)) { @@ -647,6 +688,22 @@ trait AbstractZ3Solver extends Solver { case None => unsound(t, "invalid array AST") } + case ft @ FunctionType(fts, tt) => lambdas.getB(ft) match { + case None => simplestValue(ft) + case Some(decl) => model.getModelFuncInterpretations.find(_._1 == decl) match { + case None => simplestValue(ft) + case Some((_, mapping, elseValue)) => + val leonElseValue = rec(elseValue, tt) + PartialLambda(mapping.flatMap { case (z3Args, z3Result) => + if (t == z3Args.head) { + List((z3Args.tail zip fts).map(p => rec(p._1, p._2)) -> rec(z3Result, tt)) + } else { + Nil + } + }, Some(leonElseValue), ft) + } + } + case tp: TypeParameter => val id = t.toString.split("!").last.toInt GenericValue(tp, id) @@ -669,13 +726,6 @@ trait AbstractZ3Solver extends Solver { FiniteMap(elems, from, to) } - case ft @ FunctionType(fts, tt) => - rec(t, RawArrayType(tupleTypeWrap(fts), tt)) match { - case r: RawArrayValue => - val elems = r.elems.toSeq.map { case (k, v) => unwrapTuple(k, fts.size) -> v } - PartialLambda(elems, Some(r.default), ft) - } - case tpe @ SetType(dt) => model.getSetValue(t) match { case None => unsound(t, "invalid set AST") @@ -719,8 +769,14 @@ trait AbstractZ3Solver extends Solver { } case _ => unsound(t, "unexpected AST") } + expected_tpe match { + case StringType => + StringLiteral(Z3StringTypeConversion.convertToString(res)(program)) + case _ => res + } } - rec(tree, tpe) + + rec(tree, normalizeType(tpe)) } protected[leon] def softFromZ3Formula(model: Z3Model, tree: Z3AST, tpe: TypeTree) : Option[Expr] = { @@ -734,7 +790,8 @@ trait AbstractZ3Solver extends Solver { } def idToFreshZ3Id(id: Identifier): Z3AST = { - z3.mkFreshConst(id.uniqueName, typeToSort(id.getType)) + val correctType = Z3StringTypeConversion.convert(id.getType)(program) + z3.mkFreshConst(id.uniqueName, typeToSort(correctType)) } def reset() = { diff --git a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala index c129243d15b661d9e42d578447fdea6cb1aea3e0..c975fd2d37e7563137d9b1fd4cbe0f2cbb7892f4 100644 --- a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala +++ b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala @@ -17,6 +17,7 @@ import purescala.ExprOps._ import purescala.Types._ import solvers.templates._ +import Template._ import evaluators._ @@ -66,7 +67,7 @@ class FairZ3Solver(val context: LeonContext, val program: Program) if (optEnabler == Some(true)) { val optArgs = (m.args zip fromTypes).map { - p => softFromZ3Formula(model, model.eval(Matcher.argValue(p._1), true).get, p._2) + p => softFromZ3Formula(model, model.eval(p._1.encoded, true).get, p._2) } if (optArgs.forall(_.isDefined)) { @@ -136,23 +137,23 @@ class FairZ3Solver(val context: LeonContext, val program: Program) private val freeVars = new IncrementalSet[Identifier]() private val constraints = new IncrementalSeq[Expr]() + val tr = implicitly[Z3AST => Printable] val unrollingBank = new UnrollingBank(context, templateGenerator) + private val incrementals: List[IncrementalState] = List( + errors, freeVars, constraints, functions, generics, lambdas, sorts, variables, + constructors, selectors, testers, unrollingBank + ) + def push() { - errors.push() solver.push() - unrollingBank.push() - freeVars.push() - constraints.push() + incrementals.foreach(_.push()) } def pop() { - errors.pop() solver.pop(1) - unrollingBank.pop() - freeVars.pop() - constraints.pop() + incrementals.foreach(_.pop()) } override def check: Option[Boolean] = { @@ -245,7 +246,7 @@ class FairZ3Solver(val context: LeonContext, val program: Program) solver.assertCnstr(clause) } - reporter.debug(" - Verifying model transitivity") + reporter.debug(" - Enforcing model transitivity") val timer = context.timers.solvers.z3.check.start() solver.push() // FIXME: remove when z3 bug is fixed val res = solver.checkAssumptions((assumptionsAsZ3 ++ unrollingBank.satisfactionAssumptions) :_*) diff --git a/src/main/scala/leon/solvers/z3/Z3StringConversion.scala b/src/main/scala/leon/solvers/z3/Z3StringConversion.scala new file mode 100644 index 0000000000000000000000000000000000000000..3daf1ad4964ad73e8c4d9701ae4e65d0f4170897 --- /dev/null +++ b/src/main/scala/leon/solvers/z3/Z3StringConversion.scala @@ -0,0 +1,94 @@ +package leon +package solvers +package z3 + +import purescala.Common._ +import purescala.Expressions._ +import purescala.Constructors._ +import purescala.Types._ +import purescala.Definitions._ +import _root_.smtlib.parser.Terms.{Identifier => SMTIdentifier, _} +import _root_.smtlib.parser.Commands.{FunDef => SMTFunDef, _} +import _root_.smtlib.interpreters.Z3Interpreter +import _root_.smtlib.theories.Core.{Equals => SMTEquals, _} +import _root_.smtlib.theories.ArraysEx +import leon.utils.Bijection + +object Z3StringTypeConversion { + def convert(t: TypeTree)(implicit p: Program) = new Z3StringTypeConversion { def getProgram = p }.convertType(t) + def convertToString(e: Expr)(implicit p: Program) = new Z3StringTypeConversion{ def getProgram = p }.convertToString(e) +} + +trait Z3StringTypeConversion { + val stringBijection = new Bijection[String, Expr]() + + lazy val conschar = program.lookupCaseClass("leon.collection.Cons") match { + case Some(cc) => cc.typed(Seq(CharType)) + case _ => throw new Exception("Could not find Cons in Z3 solver") + } + lazy val nilchar = program.lookupCaseClass("leon.collection.Nil") match { + case Some(cc) => cc.typed(Seq(CharType)) + case _ => throw new Exception("Could not find Nil in Z3 solver") + } + lazy val listchar = program.lookupAbstractClass("leon.collection.List") match { + case Some(cc) => cc.typed(Seq(CharType)) + case _ => throw new Exception("Could not find List in Z3 solver") + } + def lookupFunDef(s: String): FunDef = program.lookupFunDef(s) match { + case Some(fd) => fd + case _ => throw new Exception("Could not find function "+s+" in program") + } + lazy val list_size = lookupFunDef("leon.collection.List.size").typed(Seq(CharType)) + lazy val list_++ = lookupFunDef("leon.collection.List.++").typed(Seq(CharType)) + lazy val list_take = lookupFunDef("leon.collection.List.take").typed(Seq(CharType)) + lazy val list_drop = lookupFunDef("leon.collection.List.drop").typed(Seq(CharType)) + lazy val list_slice = lookupFunDef("leon.collection.List.slice").typed(Seq(CharType)) + + private lazy val program = getProgram + + def getProgram: Program + + def convertType(t: TypeTree): TypeTree = t match { + case StringType => listchar + case _ => t + } + def convertToString(e: Expr)(implicit p: Program): String = + stringBijection.cachedA(e) { + e match { + case CaseClass(_, Seq(CharLiteral(c), l)) => c + convertToString(l) + case CaseClass(_, Seq()) => "" + } + } + def convertFromString(v: String) = + stringBijection.cachedB(v) { + v.toList.foldRight(CaseClass(nilchar, Seq())){ + case (char, l) => CaseClass(conschar, Seq(CharLiteral(char), l)) + } + } +} + +trait Z3StringConversion[TargetType] extends Z3StringTypeConversion { + def convertToTarget(e: Expr)(implicit bindings: Map[Identifier, TargetType]): TargetType + def targetApplication(fd: TypedFunDef, args: Seq[TargetType])(implicit bindings: Map[Identifier, TargetType]): TargetType + + object StringConverted { + def unapply(e: Expr)(implicit bindings: Map[Identifier, TargetType]): Option[TargetType] = e match { + case StringLiteral(v) => + // No string support for z3 at this moment. + val stringEncoding = convertFromString(v) + Some(convertToTarget(stringEncoding)) + case StringLength(a) => + Some(targetApplication(list_size, Seq(convertToTarget(a)))) + case StringConcat(a, b) => + Some(targetApplication(list_++, Seq(convertToTarget(a), convertToTarget(b)))) + case SubString(a, start, Plus(start2, length)) if start == start2 => + Some(targetApplication(list_take, + Seq(targetApplication(list_drop, Seq(convertToTarget(a), convertToTarget(start))), convertToTarget(length)))) + case SubString(a, start, end) => + Some(targetApplication(list_slice, Seq(convertToTarget(a), convertToTarget(start), convertToTarget(end)))) + case _ => None + } + + def apply(t: TypeTree): TypeTree = convertType(t) + } +} \ No newline at end of file diff --git a/src/main/scala/leon/synthesis/ConversionPhase.scala b/src/main/scala/leon/synthesis/ConversionPhase.scala index d5f4e842f698e54d353d4170c1591073debefd5e..13f1788fffec11ca03139210d52815699d386324 100644 --- a/src/main/scala/leon/synthesis/ConversionPhase.scala +++ b/src/main/scala/leon/synthesis/ConversionPhase.scala @@ -89,6 +89,7 @@ object ConversionPhase extends UnitPhase[Program] { * choose(x => post(a, x)) * } ensuring { x => post(a, x) } * + * (in practice, there will be no pre-and postcondition) */ def convert(e : Expr, ctx : LeonContext) : Expr = { @@ -188,6 +189,8 @@ object ConversionPhase extends UnitPhase[Program] { // extract spec from chooses at the top-level fullBody match { + case Require(_, Choose(spec)) => + withPostcondition(fullBody, Some(spec)) case Choose(spec) => withPostcondition(fullBody, Some(spec)) case _ => diff --git a/src/main/scala/leon/synthesis/CostModel.scala b/src/main/scala/leon/synthesis/CostModel.scala index 6a76522b09b3438a4c2706788b128fad36783919..f2d422468431b9406dd64be264f0c2157853aad5 100644 --- a/src/main/scala/leon/synthesis/CostModel.scala +++ b/src/main/scala/leon/synthesis/CostModel.scala @@ -8,6 +8,7 @@ import graph._ import purescala.Expressions._ import purescala.ExprOps._ +/** A named way of computing the cost of problem and solutions.*/ abstract class CostModel(val name: String) { def solution(s: Solution): Cost def problem(p: Problem): Cost @@ -20,6 +21,7 @@ abstract class CostModel(val name: String) { } } +/** Represents a cost used when comparing synthesis solutions for example */ case class Cost(minSize: Int) extends AnyVal with Ordered[Cost] { def compare(that: Cost): Int = { this.minSize-that.minSize @@ -30,6 +32,7 @@ case class Cost(minSize: Int) extends AnyVal with Ordered[Cost] { } } +/** Contains all and the default [CostModel] */ object CostModels { def default: CostModel = WeightedBranchesCostModel @@ -39,6 +42,7 @@ object CostModels { ) } +/** Wrapped cost model. Not used at this moment. */ class WrappedCostModel(cm: CostModel, name: String) extends CostModel(name) { def solution(s: Solution): Cost = cm.solution(s) @@ -50,6 +54,8 @@ class WrappedCostModel(cm: CostModel, name: String) extends CostModel(name) { def impossible = cm.impossible } +/** Computes a cost corresponding of the size of the solution expression divided by 10. + * For problems, returns a cost of 1 */ class SizeBasedCostModel(name: String) extends CostModel(name) { def solution(s: Solution) = { Cost(formulaSize(s.toExpr)/10) diff --git a/src/main/scala/leon/synthesis/ExamplesBank.scala b/src/main/scala/leon/synthesis/ExamplesBank.scala index 1dfddd0ca025caf3c7faafce3904c304be362f43..265b35a5e5136bdcbfbd6e458350580c42bae9e3 100644 --- a/src/main/scala/leon/synthesis/ExamplesBank.scala +++ b/src/main/scala/leon/synthesis/ExamplesBank.scala @@ -72,6 +72,7 @@ case class ExamplesBank(valids: Seq[Example], invalids: Seq[Example]) { ExamplesBank(valids.flatMap(f), invalids.flatMap(f)) } + /** Expands each input example through the function f */ def mapIns(f: Seq[Expr] => List[Seq[Expr]]) = { map { case InExample(in) => @@ -82,6 +83,7 @@ case class ExamplesBank(valids: Seq[Example], invalids: Seq[Example]) { } } + /** Expands each output example through the function f */ def mapOuts(f: Seq[Expr] => List[Seq[Expr]]) = { map { case InOutExample(in, out) => @@ -158,8 +160,8 @@ object ExamplesBank { def empty = ExamplesBank(Nil, Nil) } -// Same as an ExamplesBank, but with identifiers corresponding to values. This -// allows us to evaluate expressions +/** Same as an ExamplesBank, but with identifiers corresponding to values. This + * allows us to evaluate expressions. */ case class QualifiedExamplesBank(as: List[Identifier], xs: List[Identifier], eb: ExamplesBank)(implicit hctx: SearchContext) { def removeOuts(toRemove: Set[Identifier]) = { @@ -173,10 +175,12 @@ case class QualifiedExamplesBank(as: List[Identifier], xs: List[Identifier], eb: eb mapIns { (in: Seq[Expr]) => List(toKeep.map(in)) } } + /** Filter inputs throught expr which is an expression evaluating to a boolean */ def filterIns(expr: Expr): ExamplesBank = { filterIns(m => hctx.sctx.defaultEvaluator.eval(expr, m).result == Some(BooleanLiteral(true))) } + /** Filters inputs through the predicate pred, with an assignment of input variables to expressions. */ def filterIns(pred: Map[Identifier, Expr] => Boolean): ExamplesBank = { eb mapIns { in => val m = (as zip in).toMap @@ -188,6 +192,8 @@ case class QualifiedExamplesBank(as: List[Identifier], xs: List[Identifier], eb: } } + /** Maps inputs through the function f + * @return A new ExampleBank */ def mapIns(f: Seq[(Identifier, Expr)] => List[Seq[Expr]]) = { eb map { case InExample(in) => diff --git a/src/main/scala/leon/synthesis/ExamplesFinder.scala b/src/main/scala/leon/synthesis/ExamplesFinder.scala index 45f399b1b56dc60ed8fe3d9f90ca6f58debf1940..5077f9467dab2edd3abf8fdf7da01d5eae59ff5e 100644 --- a/src/main/scala/leon/synthesis/ExamplesFinder.scala +++ b/src/main/scala/leon/synthesis/ExamplesFinder.scala @@ -27,7 +27,7 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { val reporter = ctx.reporter def extractFromFunDef(fd: FunDef, partition: Boolean): ExamplesBank = fd.postcondition match { - case Some(Lambda(Seq(ValDef(id, _)), post)) => + case Some(Lambda(Seq(ValDef(id)), post)) => // @mk FIXME: make this more general val tests = extractTestsOf(post) @@ -171,48 +171,58 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { // The input contains no free vars. Trivially return input-output pair Seq((pattExpr, doSubstitute(ieMap,cs.rhs))) } else { - // If the input contains free variables, it does not provide concrete examples. - // We will instantiate them according to a simple grammar to get them. - val enum = new MemoizedEnumerator[TypeTree, Expr](ValueGrammar.getProductions) - val values = enum.iterator(tupleTypeWrap(freeVars.map{ _.getType })) - val instantiations = values.map { - v => freeVars.zip(unwrapTuple(v, freeVars.size)).toMap - } - - def filterGuard(e: Expr, mapping: Map[Identifier, Expr]): Boolean = cs.optGuard match { - case Some(guard) => - // in -> e should be enough. We shouldn't find any subexpressions of in. - evaluator.eval(replace(Map(in -> e), guard), mapping) match { - case EvaluationResults.Successful(BooleanLiteral(true)) => true - case _ => false - } + // Extract test cases such as case x if x == s => + ((pattExpr, ieMap, cs.optGuard) match { + case (Variable(id), Seq(), Some(Equals(Variable(id2), s))) if id == id2 => + Some((Seq((s, doSubstitute(ieMap, cs.rhs))))) + case (Variable(id), Seq(), Some(Equals(s, Variable(id2)))) if id == id2 => + Some((Seq((s, doSubstitute(ieMap, cs.rhs))))) + case (a, b, c) => + None + }) getOrElse { + // If the input contains free variables, it does not provide concrete examples. + // We will instantiate them according to a simple grammar to get them. + val enum = new MemoizedEnumerator[TypeTree, Expr, Generator[TypeTree, Expr]](ValueGrammar.getProductions) + val values = enum.iterator(tupleTypeWrap(freeVars.map { _.getType })) + val instantiations = values.map { + v => freeVars.zip(unwrapTuple(v, freeVars.size)).toMap + } - case None => - true - } + def filterGuard(e: Expr, mapping: Map[Identifier, Expr]): Boolean = cs.optGuard match { + case Some(guard) => + // in -> e should be enough. We shouldn't find any subexpressions of in. + evaluator.eval(replace(Map(in -> e), guard), mapping) match { + case EvaluationResults.Successful(BooleanLiteral(true)) => true + case _ => false + } - (for { - inst <- instantiations.toSeq - inR = replaceFromIDs(inst, pattExpr) - outR = replaceFromIDs(inst, doSubstitute(ieMap, cs.rhs)) - if filterGuard(inR, inst) - } yield (inR,outR) ).take(examplesPerCase) + case None => + true + } + + if(cs.optGuard == Some(BooleanLiteral(false))) { + Nil + } else (for { + inst <- instantiations.toSeq + inR = replaceFromIDs(inst, pattExpr) + outR = replaceFromIDs(inst, doSubstitute(ieMap, cs.rhs)) + if filterGuard(inR, inst) + } yield (inR, outR)).take(examplesPerCase) + } } } } - /** - * Check if two tests are compatible. - * Compatible should evaluate to the same value for the same identifier - */ + /** Check if two tests are compatible. + * Compatible should evaluate to the same value for the same identifier + */ private def isCompatible(m1: Map[Identifier, Expr], m2: Map[Identifier, Expr]) = { val ks = m1.keySet & m2.keySet ks.nonEmpty && ks.map(m1) == ks.map(m2) } - /** - * Merge tests t1 and t2 if they are compatible. Return m1 if not. - */ + /** Merge tests t1 and t2 if they are compatible. Return m1 if not. + */ private def mergeTest(m1: Map[Identifier, Expr], m2: Map[Identifier, Expr]) = { if (!isCompatible(m1, m2)) { m1 @@ -221,12 +231,12 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { } } - /** - * we now need to consolidate different clusters of compatible tests together - * t1: a->1, c->3 - * t2: a->1, b->4 - * => a->1, b->4, c->3 - */ + /** we now need to consolidate different clusters of compatible tests together + * t1: a->1, c->3 + * t1: a->1, c->3 + * t2: a->1, b->4 + * => a->1, b->4, c->3 + */ private def consolidateTests(ts: Set[Map[Identifier, Expr]]): Set[Map[Identifier, Expr]] = { var consolidated = Set[Map[Identifier, Expr]]() @@ -240,19 +250,18 @@ class ExamplesFinder(ctx0: LeonContext, program: Program) { consolidated } - /** - * Extract ids in ins/outs args, and compute corresponding extractors for values map - * - * Examples: - * (a,b) => - * a -> _.1 - * b -> _.2 - * - * Cons(a, Cons(b, c)) => - * a -> _.head - * b -> _.tail.head - * c -> _.tail.tail - */ + /** Extract ids in ins/outs args, and compute corresponding extractors for values map + * + * Examples: + * (a,b) => + * a -> _.1 + * b -> _.2 + * + * Cons(a, Cons(b, c)) => + * a -> _.head + * b -> _.tail.head + * c -> _.tail.tail + */ private def extractIds(e: Expr): Seq[(Identifier, PartialFunction[Expr, Expr])] = e match { case Variable(id) => List((id, { case e => e })) diff --git a/src/main/scala/leon/synthesis/FileInterface.scala b/src/main/scala/leon/synthesis/FileInterface.scala index 25edd338fd2111314dd050d55da9ca14209a62d1..cba6629dad2aab1cddc0b857bc0fbe7bd4d66d76 100644 --- a/src/main/scala/leon/synthesis/FileInterface.scala +++ b/src/main/scala/leon/synthesis/FileInterface.scala @@ -45,14 +45,37 @@ class FileInterface(reporter: Reporter) { } } - def substitute(str: String, fromTree: Tree, printer: (Int) => String): String = { + def substitute(originalCode: String, fromTree: Tree, printer: (Int) => String): String = { fromTree.getPos match { case rp: RangePosition => val from = rp.pointFrom val to = rp.pointTo - val before = str.substring(0, from) - val after = str.substring(to, str.length) + val before = originalCode.substring(0, from) + val after = originalCode.substring(to, originalCode.length) + + // Get base indentation of last line: + val lineChars = before.substring(before.lastIndexOf('\n')+1).toList + + val indent = lineChars.takeWhile(_ == ' ').size + + val res = printer(indent/2) + + before + res + after + + case p => + sys.error("Substitution requires RangePos on the input tree: "+fromTree +": "+fromTree.getClass+" GOT" +p) + } + } + + def insertAfter(originalCode: String, fromTree: Tree, printer: (Int) => String): String = { + fromTree.getPos match { + case rp: RangePosition => + val from = rp.pointFrom + val to = rp.pointTo + + val before = originalCode.substring(0, to) + val after = originalCode.substring(to, originalCode.length) // Get base indentation of last line: val lineChars = before.substring(before.lastIndexOf('\n')+1).toList diff --git a/src/main/scala/leon/synthesis/LinearEquations.scala b/src/main/scala/leon/synthesis/LinearEquations.scala index da0f918103a959b5576a3909ebebeab3ec901305..546bcd3d932c6a6f13bbdd10930f02843e63fff0 100644 --- a/src/main/scala/leon/synthesis/LinearEquations.scala +++ b/src/main/scala/leon/synthesis/LinearEquations.scala @@ -12,8 +12,8 @@ import evaluators._ import synthesis.Algebra._ object LinearEquations { - //eliminate one variable from normalizedEquation t + a1*x1 + ... + an*xn = 0 - //return a mapping for each of the n variables in (pre, map, freshVars) + /** Eliminates one variable from normalizedEquation t + a1*x1 + ... + an*xn = 0 + * @return a mapping for each of the n variables in (pre, map, freshVars) */ def elimVariable(evaluator: Evaluator, as: Set[Identifier], normalizedEquation: List[Expr]): (Expr, List[Expr], List[Identifier]) = { require(normalizedEquation.size > 1) require(normalizedEquation.tail.forall{case InfiniteIntegerLiteral(i) if i != BigInt(0) => true case _ => false}) @@ -50,13 +50,13 @@ object LinearEquations { } - //compute a list of solution of the equation c1*x1 + ... + cn*xn where coef = [c1 ... cn] - //return the solution in the form of a list of n-1 vectors that form a basis for the set - //of solutions, that is res=(v1, ..., v{n-1}) and any solution x* to the original solution - //is a linear combination of the vi's - //Intuitively, we are building a "basis" for the "vector space" of solutions (although we are over - //integers, so it is not a vector space). - //we are returning a matrix where the columns are the vectors + /** Computes a list of solutions to the equation c1*x1 + ... + cn*xn where coef = [c1 ... cn] + * @return the solution in the form of a list of n-1 vectors that form a basis for the set + * of solutions, that is res=(v1, ..., v{n-1}) and any solution x* to the original solution + * is a linear combination of the vi's + * Intuitively, we are building a "basis" for the "vector space" of solutions (although we are over + * integers, so it is not a vector space). + * we are returning a matrix where the columns are the vectors */ def linearSet(evaluator: Evaluator, as: Set[Identifier], coef: Array[BigInt]): Array[Array[BigInt]] = { val K = Array.ofDim[BigInt](coef.length, coef.length-1) @@ -82,8 +82,9 @@ object LinearEquations { K } - //as are the parameters while xs are the variable for which we want to find one satisfiable assignment - //return (pre, sol) with pre a precondition under which sol is a solution mapping to the xs + /** @param as The parameters + * @param xs The variable for which we want to find one satisfiable assignment + * @return (pre, sol) with pre a precondition under which sol is a solution mapping to the xs */ def particularSolution(as: Set[Identifier], xs: Set[Identifier], equation: Equals): (Expr, Map[Identifier, Expr]) = { val lhs = equation.lhs val rhs = equation.rhs @@ -93,7 +94,7 @@ object LinearEquations { (pre, orderedXs.zip(sols).toMap) } - //return a particular solution to t + c1x + c2y = 0, with (pre, (x0, y0)) + /** @return a particular solution to t + c1x + c2y = 0, with (pre, (x0, y0)) */ def particularSolution(as: Set[Identifier], t: Expr, c1: Expr, c2: Expr): (Expr, (Expr, Expr)) = { val (InfiniteIntegerLiteral(i1), InfiniteIntegerLiteral(i2)) = (c1, c2) val (v1, v2) = extendedEuclid(i1, i2) @@ -109,7 +110,7 @@ object LinearEquations { ) } - //the equation must at least contain the term t and one variable + /** the equation must at least contain the term t and one variable */ def particularSolution(as: Set[Identifier], normalizedEquation: List[Expr]): (Expr, List[Expr]) = { require(normalizedEquation.size >= 2) val t: Expr = normalizedEquation.head diff --git a/src/main/scala/leon/synthesis/Rules.scala b/src/main/scala/leon/synthesis/Rules.scala index 51a5a15ec12700d48ccd054665189372110e01e1..cd27e272d53e9669f4c2d1f2c8e07356819b4827 100644 --- a/src/main/scala/leon/synthesis/Rules.scala +++ b/src/main/scala/leon/synthesis/Rules.scala @@ -37,14 +37,17 @@ abstract class PreprocessingRule(name: String) extends Rule(name) { object Rules { /** Returns the list of all available rules for synthesis */ def all = List[Rule]( + StringRender, Unification.DecompTrivialClash, Unification.OccursCheck, // probably useless Disunification.Decomp, ADTDual, - //OnePoint, + OnePoint, Ground, CaseSplit, + IndependentSplit, IfSplit, + InputSplit, UnusedInput, EquivalentInputs, UnconstrainedOutput, @@ -71,6 +74,7 @@ object Rules { } +/** When applying this to a [SearchContext] it returns a wrapped stream of solutions or a new list of problems. */ abstract class RuleInstantiation(val description: String, val onSuccess: SolutionBuilder = SolutionBuilderCloser()) (implicit val problem: Problem, val rule: Rule) { @@ -80,13 +84,21 @@ abstract class RuleInstantiation(val description: String, def asString(implicit ctx: LeonContext) = description } +object RuleInstantiation { + def apply(description: String)(f: => RuleApplication)(implicit problem: Problem, rule: Rule): RuleInstantiation = { + new RuleInstantiation(description) { + def apply(hctx: SearchContext): RuleApplication = f + } + } +} + /** - * Wrapper class for a function returning a recomposed solution from a list of - * subsolutions - * - * We also need to know the types of the expected sub-solutions to use them in - * cost-models before having real solutions. - */ + * Wrapper class for a function returning a recomposed solution from a list of + * subsolutions + * + * We also need to know the types of the expected sub-solutions to use them in + * cost-models before having real solutions. + */ abstract class SolutionBuilder { val types: Seq[TypeTree] @@ -113,13 +125,15 @@ case class SolutionBuilderCloser(osol: Option[Solution] = None) extends Solution } /** - * Results of applying rule instantiations - * - * Can either close meaning a stream of solutions are available (can be empty, - * if it failed) - */ + * Results of applying rule instantiations + * + * Can either close meaning a stream of solutions are available (can be empty, + * if it failed) + */ sealed abstract class RuleApplication +/** Result of applying rule instantiation, finished, resulting in a stream of solutions */ case class RuleClosed(solutions: Stream[Solution]) extends RuleApplication +/** Result of applying rule instantiation, resulting is a nnew list of problems */ case class RuleExpanded(sub: List[Problem]) extends RuleApplication object RuleClosed { @@ -161,6 +175,7 @@ trait RuleDSL { } } + /** Groups sub-problems and a callback merging the solutions to produce a global solution.*/ def decomp(sub: List[Problem], onSuccess: List[Solution] => Option[Solution], description: String) (implicit problem: Problem): RuleInstantiation = { @@ -182,8 +197,8 @@ trait RuleDSL { } - // pc corresponds to the pc to reach the point where the solution is used. It - // will be used if the sub-solution has a non-true pre. + /** @param pc corresponds to the post-condition to reach the point where the solution is used. It + * will be used if the sub-solution has a non-true precondition. */ def termWrap(f: Expr => Expr, pc: Expr = BooleanLiteral(true)): List[Solution] => Option[Solution] = { case List(s) => val pre = if (s.pre == BooleanLiteral(true)) { diff --git a/src/main/scala/leon/synthesis/Solution.scala b/src/main/scala/leon/synthesis/Solution.scala index e87a7883d9bf41adb30c0daa4517dce0dfbf5577..dfe90d2f775e8ed3736020cb1e827c75fe602ad9 100644 --- a/src/main/scala/leon/synthesis/Solution.scala +++ b/src/main/scala/leon/synthesis/Solution.scala @@ -31,7 +31,7 @@ class Solution(val pre: Expr, val defs: Set[FunDef], val term: Expr, val isTrust } def toExpr = { - defs.foldLeft(guardedTerm){ case (t, fd) => LetDef(fd, t) } + letDef(defs.toList, guardedTerm) } // Projects a solution (ignore several output variables) diff --git a/src/main/scala/leon/synthesis/SynthesisPhase.scala b/src/main/scala/leon/synthesis/SynthesisPhase.scala index 72cfffec3f8ef3fd8a526d778fda1f26a9ec1a41..b9ba6df1f688e01edf631e995ba2f8623bcfc5fe 100644 --- a/src/main/scala/leon/synthesis/SynthesisPhase.scala +++ b/src/main/scala/leon/synthesis/SynthesisPhase.scala @@ -6,6 +6,7 @@ package synthesis import purescala.ExprOps._ import purescala.ScalaPrinter +import leon.utils._ import purescala.Definitions.{Program, FunDef} import leon.utils.ASCIIHelpers @@ -20,12 +21,11 @@ object SynthesisPhase extends TransformationPhase { val optDerivTrees = LeonFlagOptionDef( "derivtrees", "Generate derivation trees", false) // CEGIS options - val optCEGISShrink = LeonFlagOptionDef( "cegis:shrink", "Shrink non-det programs when tests pruning works well", true) val optCEGISOptTimeout = LeonFlagOptionDef( "cegis:opttimeout", "Consider a time-out of CE-search as untrusted solution", true) val optCEGISVanuatoo = LeonFlagOptionDef( "cegis:vanuatoo", "Generate inputs using new korat-style generator", false) override val definedOptions : Set[LeonOptionDef[Any]] = - Set(optManual, optCostModel, optDerivTrees, optCEGISShrink, optCEGISOptTimeout, optCEGISVanuatoo) + Set(optManual, optCostModel, optDerivTrees, optCEGISOptTimeout, optCEGISVanuatoo) def processOptions(ctx: LeonContext): SynthesisSettings = { val ms = ctx.findOption(optManual) @@ -57,7 +57,6 @@ object SynthesisPhase extends TransformationPhase { manualSearch = ms, functions = ctx.findOption(SharedOptions.optFunctions) map { _.toSet }, cegisUseOptTimeout = ctx.findOption(optCEGISOptTimeout), - cegisUseShrink = ctx.findOption(optCEGISShrink), cegisUseVanuatoo = ctx.findOption(optCEGISVanuatoo) ) } @@ -74,21 +73,25 @@ object SynthesisPhase extends TransformationPhase { val synthesizer = new Synthesizer(ctx, program, ci, options) - val (search, solutions) = synthesizer.validate(synthesizer.synthesize(), allowPartial = true) + val to = new TimeoutFor(ctx.interruptManager) - try { - if (options.generateDerivationTrees) { - val dot = new DotGenerator(search.g) - dot.writeFile("derivation"+dotGenIds.nextGlobal+".dot") - } + to.interruptAfter(options.timeoutMs) { + val (search, solutions) = synthesizer.validate(synthesizer.synthesize(), allowPartial = true) + + try { + if (options.generateDerivationTrees) { + val dot = new DotGenerator(search.g) + dot.writeFile("derivation"+dotGenIds.nextGlobal+".dot") + } - val (sol, _) = solutions.head + val (sol, _) = solutions.head - val expr = sol.toSimplifiedExpr(ctx, program) - fd.body = fd.body.map(b => replace(Map(ci.source -> expr), b)) - functions += fd - } finally { - synthesizer.shutdown() + val expr = sol.toSimplifiedExpr(ctx, program) + fd.body = fd.body.map(b => replace(Map(ci.source -> expr), b)) + functions += fd + } finally { + synthesizer.shutdown() + } } } diff --git a/src/main/scala/leon/synthesis/SynthesisSettings.scala b/src/main/scala/leon/synthesis/SynthesisSettings.scala index 9d227bdb611f2ca44eb70a95ce52bc506712e160..5202818e18765ebf4086ef41d1685967a14940d0 100644 --- a/src/main/scala/leon/synthesis/SynthesisSettings.scala +++ b/src/main/scala/leon/synthesis/SynthesisSettings.scala @@ -17,7 +17,6 @@ case class SynthesisSettings( // Cegis related options cegisUseOptTimeout: Option[Boolean] = None, - cegisUseShrink: Option[Boolean] = None, cegisUseVanuatoo: Option[Boolean] = None ) diff --git a/src/main/scala/leon/synthesis/Synthesizer.scala b/src/main/scala/leon/synthesis/Synthesizer.scala index 35fdfb845ae0a5de611ab3a01a3111005aff7a05..bafed6ec2bab51539bfc0547563bbad2aeea873e 100644 --- a/src/main/scala/leon/synthesis/Synthesizer.scala +++ b/src/main/scala/leon/synthesis/Synthesizer.scala @@ -8,6 +8,7 @@ import purescala.ExprOps._ import purescala.DefOps._ import purescala.ScalaPrinter import solvers._ +import leon.utils._ import scala.concurrent.duration._ @@ -34,9 +35,10 @@ class Synthesizer(val context : LeonContext, } } - def synthesize(): (Search, Stream[Solution]) = { + private var lastTime: Long = 0 - reporter.ifDebug { printer => + def synthesize(): (Search, Stream[Solution]) = { + reporter.ifDebug { printer => printer(problem.eb.asString("Tests available for synthesis")(context)) } @@ -47,8 +49,12 @@ class Synthesizer(val context : LeonContext, val sols = s.search(sctx) val diff = t.stop() + + lastTime = diff + reporter.info("Finished in "+diff+"ms") + (s, sols) } @@ -62,6 +68,43 @@ class Synthesizer(val context : LeonContext, validateSolution(s, sol, 5.seconds) } + // Print out report for synthesis, if necessary + reporter.ifDebug { printer => + import java.io.FileWriter + import java.text.SimpleDateFormat + import java.util.Date + + val categoryName = ci.fd.getPos.file.toString.split("/").dropRight(1).lastOption.getOrElse("?") + val benchName = categoryName+"."+ci.fd.id.name + var time = lastTime/1000.0; + + val defs = visibleDefsFrom(ci.fd)(program).collect { + case cd: ClassDef => 1 + cd.fields.size + case fd: FunDef => 1 + fd.params.size + formulaSize(fd.fullBody) + } + + val psize = defs.sum; + + + val (size, calls, proof) = result.headOption match { + case Some((sol, trusted)) => + val expr = sol.toSimplifiedExpr(context, program) + (formulaSize(expr), functionCallsOf(expr).size, if (trusted) "$\\surd$" else "") + case _ => + (0, 0, "X") + } + + val date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + + val fw = new java.io.FileWriter("synthesis-report.txt", true) + + try { + fw.write(f"$date: $benchName%-50s & $psize%4d & $size%4d & $calls%4d & $proof%7s & $time%2.1f \\\\\n") + } finally { + fw.close + } + }(DebugSectionReport) + (s, if (result.isEmpty && allowPartial) { List((new PartialSolution(s.g, true).getSolution, false)).toStream } else { diff --git a/src/main/scala/leon/synthesis/disambiguation/ExamplesAdder.scala b/src/main/scala/leon/synthesis/disambiguation/ExamplesAdder.scala new file mode 100644 index 0000000000000000000000000000000000000000..6e9dc237667e286cdbd9a82875a986dfbf6b8aba --- /dev/null +++ b/src/main/scala/leon/synthesis/disambiguation/ExamplesAdder.scala @@ -0,0 +1,79 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ +package leon +package synthesis +package disambiguation + +import leon.LeonContext +import leon.purescala.Expressions._ +import purescala.Common.FreshIdentifier +import purescala.Constructors.{ and, tupleWrap } +import purescala.Definitions.{ FunDef, Program, ValDef } +import purescala.ExprOps.expressionToPattern +import purescala.Expressions.{ BooleanLiteral, Equals, Expr, Lambda, MatchCase, Passes, Variable, WildcardPattern } +import purescala.Extractors.TopLevelAnds +import leon.purescala.Expressions._ + +/** + * @author Mikael + */ +class ExamplesAdder(ctx0: LeonContext, program: Program) { + + /** Accepts the nth alternative of a question (0 being the current one) */ + def acceptQuestion[T <: Expr](fd: FunDef, q: Question[T], alternativeIndex: Int): Unit = { + val newIn = tupleWrap(q.inputs) + val newOut = if(alternativeIndex == 0) q.current_output else q.other_outputs(alternativeIndex - 1) + addToFunDef(fd, Seq((newIn, newOut))) + } + + /** 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 newCases = examples.map{ case (in, out) => exampleToCase(in, out) } + fd.postcondition match { + case Some(Lambda(Seq(ValDef(id)), post)) => + if(fd.postcondition.exists { e => purescala.ExprOps.exists(_.isInstanceOf[Passes])(e) }) { + post match { + case TopLevelAnds(exprs) => + val i = exprs.lastIndexWhere { x => x match { + case Passes(in, out, cases) if in == inputVariables && out == Variable(id) => true + case _ => false + } } + if(i == -1) { + fd.postcondition = Some(Lambda(Seq(ValDef(id)), and(post, Passes(inputVariables, Variable(id), newCases)))) + //ctx0.reporter.info("No top-level passes in postcondition, adding it: " + fd) + } else { + val newPasses = exprs(i) match { + case Passes(in, out, cases) => + Passes(in, out, (cases ++ newCases).distinct ) + case _ => ??? + } + val newPost = and(exprs.updated(i, newPasses) : _*) + fd.postcondition = Some(Lambda(Seq(ValDef(id)), newPost)) + //ctx0.reporter.info("Adding the example to the passes postcondition: " + fd) + } + } + } else { + fd.postcondition = Some(Lambda(Seq(ValDef(id)), and(post, Passes(inputVariables, Variable(id), newCases)))) + //ctx0.reporter.info("No passes in postcondition, adding it:" + fd) + } + case None => + val id = FreshIdentifier("res", fd.returnType, false) + fd.postcondition = Some(Lambda(Seq(ValDef(id)), Passes(inputVariables, Variable(id), newCases))) + //ctx0.reporter.info("No postcondition, adding it: " + fd) + } + fd.body match { // TODO: Is it correct to discard the choose construct inside the body? + case Some(Choose(Lambda(Seq(ValDef(id)), bodyChoose))) => fd.body = Some(Hole(id.getType, Nil)) + case _ => + } + } + + private def exampleToCase(in: Expr, out: Expr): MatchCase = { + val (inPattern, inGuard) = expressionToPattern(in) + if(inGuard != BooleanLiteral(true)) { + 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/Question.scala b/src/main/scala/leon/synthesis/disambiguation/Question.scala new file mode 100644 index 0000000000000000000000000000000000000000..eed7bfc1a7337a8282224677198286443335ea90 --- /dev/null +++ b/src/main/scala/leon/synthesis/disambiguation/Question.scala @@ -0,0 +1,9 @@ +package leon +package synthesis.disambiguation + +import purescala.Expressions.Expr + +/** + * @author Mikael + */ +case class Question[T <: Expr](inputs: Seq[Expr], current_output: T, other_outputs: List[T]) \ No newline at end of file diff --git a/src/main/scala/leon/synthesis/disambiguation/QuestionBuilder.scala b/src/main/scala/leon/synthesis/disambiguation/QuestionBuilder.scala new file mode 100644 index 0000000000000000000000000000000000000000..bfa6a62120af6171d001b6a026630734eb6c10fd --- /dev/null +++ b/src/main/scala/leon/synthesis/disambiguation/QuestionBuilder.scala @@ -0,0 +1,175 @@ +package leon +package synthesis.disambiguation + +import synthesis.Solution +import evaluators.DefaultEvaluator +import purescala.Expressions._ +import purescala.ExprOps +import purescala.Constructors._ +import purescala.Extractors._ +import purescala.Types.{StringType, TypeTree} +import purescala.Common.Identifier +import purescala.Definitions.Program +import purescala.DefOps +import grammars.ValueGrammar +import bonsai.enumerators.MemoizedEnumerator +import solvers.ModelBuilder +import scala.collection.mutable.ListBuffer +import grammars._ + +object QuestionBuilder { + /** Sort methods for questions. You can build your own */ + trait QuestionSortingType { + def apply[T <: Expr](e: Question[T]): Int + } + object QuestionSortingType { + case object IncreasingInputSize extends QuestionSortingType { + def apply[T <: Expr](q: Question[T]) = q.inputs.map(i => ExprOps.count(e => 1)(i)).sum + } + case object DecreasingInputSize extends QuestionSortingType{ + def apply[T <: Expr](q: Question[T]) = -IncreasingInputSize(q) + } + } + // Add more if needed. + + /** Sort methods for question's answers. You can (and should) build your own. */ + abstract class AlternativeSortingType[T <: Expr] extends Ordering[T] { self => + /** Prioritizes this comparison operator against the second one. */ + def &&(other: AlternativeSortingType[T]): AlternativeSortingType[T] = new AlternativeSortingType[T] { + def compare(e: T, f: T): Int = { + val ce = self.compare(e, f) + if(ce == 0) other.compare(e, f) else ce + } + } + } + object AlternativeSortingType { + /** Presents shortest alternatives first */ + case class ShorterIsBetter[T <: Expr]()(implicit c: LeonContext) extends AlternativeSortingType[T] { + def compare(e: T, f: T) = e.asString.length - f.asString.length + } + /** Presents balanced alternatives first */ + case class BalancedParenthesisIsBetter[T <: Expr]()(implicit c: LeonContext) extends AlternativeSortingType[T] { + def convert(e: T): Int = { + val s = e.asString + var openP, openB, openC = 0 + for(c <- s) c match { + case '(' if openP >= 0 => openP += 1 + case ')' => openP -= 1 + case '{' if openB >= 0 => openB += 1 + case '}' => openB -= 1 + case '[' if openC >= 0 => openC += 1 + case ']' => openC -= 1 + case _ => + } + Math.abs(openP) + Math.abs(openB) + Math.abs(openC) + } + def compare(e: T, f: T): Int = convert(e) - convert(f) + } + } + + /** Specific enumeration of strings, which can be used with the QuestionBuilder#setValueEnumerator method */ + object SpecialStringValueGrammar extends ExpressionGrammar[TypeTree] { + def computeProductions(t: TypeTree)(implicit ctx: LeonContext): Seq[Gen] = t match { + case StringType => + List( + terminal(StringLiteral("")), + terminal(StringLiteral("a")), + terminal(StringLiteral("\"'\n\t")), + terminal(StringLiteral("Lara 2007")) + ) + case _ => ValueGrammar.computeProductions(t) + } + } +} + +/** + * Builds a set of disambiguating questions for the problem + * + * {{{ + * def f(input: input.getType): T = + * [element of r.solution] + * }}} + * + * @tparam T A subtype of Expr that will be the type used in the Question[T] results. + * @param input The identifier of the unique function's input. Must be typed or the type should be defined by setArgumentType + * @param ruleApplication The set of solutions for the body of f + * @param filter A function filtering which outputs should be considered for comparison. + * It takes as input the sequence of outputs already considered for comparison, and the new output. + * It should return Some(result) if the result can be shown, and None else. + * @return An ordered + * + */ +class QuestionBuilder[T <: Expr]( + input: Seq[Identifier], + solutions: Stream[Solution], + filter: (Seq[T], Expr) => Option[T])(implicit c: LeonContext, p: Program) { + import QuestionBuilder._ + private var _argTypes = input.map(_.getType) + private var _questionSorMethod: QuestionSortingType = QuestionSortingType.IncreasingInputSize + private var _alternativeSortMethod: AlternativeSortingType[T] = AlternativeSortingType.BalancedParenthesisIsBetter[T]() && AlternativeSortingType.ShorterIsBetter[T]() + private var solutionsToTake = 30 + private var expressionsToTake = 30 + private var keepEmptyAlternativeQuestions: T => Boolean = Set() + private var value_enumerator: ExpressionGrammar[TypeTree] = ValueGrammar + + /** Sets the way to sort questions. See [[QuestionSortingType]] */ + def setSortQuestionBy(questionSorMethod: QuestionSortingType) = { _questionSorMethod = questionSorMethod; this } + /** Sets the way to sort alternatives. See [[AlternativeSortingType]] */ + def setSortAlternativesBy(alternativeSortMethod: AlternativeSortingType[T]) = { _alternativeSortMethod = alternativeSortMethod; this } + /** Sets the argument type. Not needed if the input identifier is already assigned a type. */ + def setArgumentType(argTypes: List[TypeTree]) = { _argTypes = argTypes; this } + /** Sets the number of solutions to consider. Default is 15 */ + def setSolutionsToTake(n: Int) = { solutionsToTake = n; this } + /** Sets the number of expressions to consider. Default is 15 */ + def setExpressionsToTake(n: Int) = { expressionsToTake = n; this } + /** Sets if when there is no alternative, the question should be kept. */ + def setKeepEmptyAlternativeQuestions(b: T => Boolean) = {keepEmptyAlternativeQuestions = b; this } + /** Sets the way to enumerate expressions */ + def setValueEnumerator(v: ExpressionGrammar[TypeTree]) = value_enumerator = v + + 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 model = new ModelBuilder + model ++= elems + val modelResult = model.result() + e.eval(s.term, modelResult).result + } + + /** Returns a list of input/output questions to ask to the user. */ + def result(): List[Question[T]] = { + if(solutions.isEmpty) return Nil + + val enum = new MemoizedEnumerator[TypeTree, Expr, Generator[TypeTree,Expr]](value_enumerator.getProductions) + val values = enum.iterator(tupleTypeWrap(_argTypes)) + val instantiations = values.map { + v => input.zip(unwrapTuple(v, input.size)) + } + + val enumerated_inputs = instantiations.take(expressionsToTake).toList + + val solution = solutions.head + val alternatives = solutions.drop(1).take(solutionsToTake).toList + val questions = ListBuffer[Question[T]]() + for{possible_input <- enumerated_inputs + current_output_nonfiltered <- run(solution, possible_input) + current_output <- filter(Seq(), current_output_nonfiltered)} { + + val alternative_outputs = ((ListBuffer[T](current_output) /: alternatives) { (prev, alternative) => + run(alternative, possible_input) match { + case Some(alternative_output) if alternative_output != current_output => + filter(prev, alternative_output) match { + case Some(alternative_output_filtered) => + prev += alternative_output_filtered + case _ => prev + } + case _ => prev + } + }).drop(1).toList.distinct + if(alternative_outputs.nonEmpty || keepEmptyAlternativeQuestions(current_output)) { + questions += Question(possible_input.map(_._2), current_output, alternative_outputs.sortWith((e,f) => _alternativeSortMethod.compare(e, f) <= 0)) + } + } + questions.toList.sortBy(_questionSorMethod(_)) + } +} \ No newline at end of file diff --git a/src/main/scala/leon/synthesis/graph/Graph.scala b/src/main/scala/leon/synthesis/graph/Graph.scala index b4db0e4715ece63609a1d38cd06891b87c8d6c9e..6183c2858d74ff3e82d5b5c4dc1a4354265d040f 100644 --- a/src/main/scala/leon/synthesis/graph/Graph.scala +++ b/src/main/scala/leon/synthesis/graph/Graph.scala @@ -5,6 +5,7 @@ package synthesis package graph import leon.utils.StreamUtils.cartesianProduct +import leon.utils.DebugSectionSynthesis sealed class Graph(val cm: CostModel, problem: Problem) { val root = new RootNode(cm, problem) @@ -27,11 +28,10 @@ sealed class Graph(val cm: CostModel, problem: Problem) { } sealed abstract class Node(cm: CostModel, val parent: Option[Node]) { - var parents: List[Node] = parent.toList - var descendants: List[Node] = Nil def asString(implicit ctx: LeonContext): String + var descendants: List[Node] = Nil // indicates whether this particular node has already been expanded var isExpanded: Boolean = false def expand(hctx: SearchContext) @@ -52,7 +52,7 @@ sealed abstract class Node(cm: CostModel, val parent: Option[Node]) { cm.isImpossible(cost) } - // For non-terminals, selected childs for solution + // For non-terminals, selected children for solution var selected: List[Node] = Nil def composeSolutions(sols: List[Stream[Solution]]): Stream[Solution] @@ -93,10 +93,15 @@ sealed abstract class Node(cm: CostModel, val parent: Option[Node]) { def updateCost(): Unit = { cost = computeCost() - parents.foreach(_.updateCost()) + parent.foreach(_.updateCost()) } } +/** Represents the conjunction of search nodes. + * @param cm The cost model used when prioritizing, evaluating and expanding + * @param parent Some node. None if it is the root node. + * @param ri The rule instantiation that created this AndNode. + **/ class AndNode(cm: CostModel, parent: Option[Node], val ri: RuleInstantiation) extends Node(cm, parent) { val p = ri.problem @@ -130,7 +135,7 @@ class AndNode(cm: CostModel, parent: Option[Node], val ri: RuleInstantiation) ex info(prefix+"Solved"+(if(sol.isTrusted) "" else " (untrusted)")+" with: "+sol.asString+"...") } - parents.foreach{ p => + parent.foreach{ p => p.updateCost() if (isSolved) { p.onSolved(this) @@ -166,7 +171,7 @@ class AndNode(cm: CostModel, parent: Option[Node], val ri: RuleInstantiation) ex // Everything is solved correctly if (solveds.size == descendants.size) { isSolved = true - parents.foreach(_.onSolved(this)) + parent.foreach(_.onSolved(this)) } } @@ -176,13 +181,17 @@ class OrNode(cm: CostModel, parent: Option[Node], val p: Problem) extends Node(c override def asString(implicit ctx: LeonContext) = "\u2228 "+p.asString + implicit val debugSection = DebugSectionSynthesis + def getInstantiations(hctx: SearchContext): List[RuleInstantiation] = { val rules = hctx.sctx.rules val rulesPrio = rules.groupBy(_.priority).toSeq.sortBy(_._1) for ((_, rs) <- rulesPrio) { + val results = rs.flatMap{ r => + hctx.context.reporter.ifDebug(printer => printer("Testing rule: " + r)) hctx.context.timers.synthesis.instantiations.get(r.asString(hctx.sctx.context)).timed { r.instantiateOn(hctx, p) } @@ -211,7 +220,7 @@ class OrNode(cm: CostModel, parent: Option[Node], val p: Problem) extends Node(c def onSolved(desc: Node): Unit = { isSolved = true selected = List(desc) - parents.foreach(_.onSolved(this)) + parent.foreach(_.onSolved(this)) } def composeSolutions(solss: List[Stream[Solution]]): Stream[Solution] = { diff --git a/src/main/scala/leon/synthesis/programsets/DirectProgramSet.scala b/src/main/scala/leon/synthesis/programsets/DirectProgramSet.scala new file mode 100644 index 0000000000000000000000000000000000000000..2887c57fcb80b26e40bf07324b8dc8a0bfc5b5eb --- /dev/null +++ b/src/main/scala/leon/synthesis/programsets/DirectProgramSet.scala @@ -0,0 +1,18 @@ +package leon.synthesis.programsets + +import leon.purescala +import purescala.Expressions._ +import purescala.Extractors._ +import purescala.Constructors._ +import purescala.Types._ + +object DirectProgramSet { + def apply[T](p: Stream[T]): DirectProgramSet[T] = new DirectProgramSet(p) +} + +/** + * @author Mikael + */ +class DirectProgramSet[T](val p: Stream[T]) extends ProgramSet[T] { + def programs = p +} \ No newline at end of file diff --git a/src/main/scala/leon/synthesis/programsets/JoinProgramSet.scala b/src/main/scala/leon/synthesis/programsets/JoinProgramSet.scala new file mode 100644 index 0000000000000000000000000000000000000000..c0147227a9cacfa0d52b0d94728f1585d3e0cef7 --- /dev/null +++ b/src/main/scala/leon/synthesis/programsets/JoinProgramSet.scala @@ -0,0 +1,36 @@ +package leon +package synthesis.programsets + +import leon.purescala +import purescala.Expressions._ +import purescala.Extractors._ +import purescala.Constructors._ +import purescala.Types._ + +/** + * @author Mikael + */ +object JoinProgramSet { + + def apply[T, U1, U2](sets: (ProgramSet[U1], ProgramSet[U2]), recombine: (U1, U2) => T): Join2ProgramSet[T, U1, U2] = { + new Join2ProgramSet(sets, recombine) + } + def apply[T, U](sets: Seq[ProgramSet[U]], recombine: Seq[U] => T): JoinProgramSet[T, U] = { + new JoinProgramSet(sets, recombine) + } + def direct[U1, U2](sets: (ProgramSet[U1], ProgramSet[U2])): Join2ProgramSet[(U1, U2), U1, U2] = { + new Join2ProgramSet(sets, (u1: U1, u2: U2) => (u1, u2)) + } + def direct[U](sets: Seq[ProgramSet[U]]): JoinProgramSet[Seq[U], U] = { + new JoinProgramSet(sets, (id: Seq[U]) => id) + } +} + +class Join2ProgramSet[T, U1, U2](sets: (ProgramSet[U1], ProgramSet[U2]), recombine: (U1, U2) => T) extends ProgramSet[T] { + def programs: Stream[T] = utils.StreamUtils.cartesianProduct(sets._1.programs, sets._2.programs).map(recombine.tupled) +} + +class JoinProgramSet[T, U](sets: Seq[ProgramSet[U]], recombine: Seq[U] => T) extends ProgramSet[T] { + def programs: Stream[T] = utils.StreamUtils.cartesianProduct(sets.map(_.programs)).map(recombine) +} + diff --git a/src/main/scala/leon/synthesis/programsets/ProgramSet.scala b/src/main/scala/leon/synthesis/programsets/ProgramSet.scala new file mode 100644 index 0000000000000000000000000000000000000000..ebfcf8fa19de9036c1b6d7e4d2c862038cde8756 --- /dev/null +++ b/src/main/scala/leon/synthesis/programsets/ProgramSet.scala @@ -0,0 +1,14 @@ +package leon.synthesis.programsets + +import leon.purescala +import purescala.Expressions._ +import purescala.Extractors._ +import purescala.Constructors._ +import purescala.Types._ + +/** + * @author Mikael + */ +abstract class ProgramSet[T] { + def programs: Stream[T] +} \ No newline at end of file diff --git a/src/main/scala/leon/synthesis/programsets/UnionProgramset.scala b/src/main/scala/leon/synthesis/programsets/UnionProgramset.scala new file mode 100644 index 0000000000000000000000000000000000000000..f57c53053fc661c6b0d644d720724fdd7a227bdc --- /dev/null +++ b/src/main/scala/leon/synthesis/programsets/UnionProgramset.scala @@ -0,0 +1,21 @@ +package leon +package synthesis.programsets + +import leon.purescala +import purescala.Expressions._ +import purescala.Extractors._ +import purescala.Constructors._ +import purescala.Types._ + +object UnionProgramSet { + def apply[T](sets: Seq[ProgramSet[T]]): UnionProgramSet[T] = { + new UnionProgramSet(sets) + } +} + +/** + * @author Mikael + */ +class UnionProgramSet[T](sets: Seq[ProgramSet[T]]) extends ProgramSet[T] { + def programs = utils.StreamUtils.interleave(sets.map(_.programs)) +} \ No newline at end of file diff --git a/src/main/scala/leon/synthesis/rules/ADTDual.scala b/src/main/scala/leon/synthesis/rules/ADTDual.scala index 392670edff7420493b7d17d87ff94358f962c172..ed7c0652b60384f362bbecc9db8dd93d9a2e2523 100644 --- a/src/main/scala/leon/synthesis/rules/ADTDual.scala +++ b/src/main/scala/leon/synthesis/rules/ADTDual.scala @@ -9,6 +9,11 @@ import purescala.ExprOps._ import purescala.Extractors._ import purescala.Constructors._ +/** For a `case class A(b: B, c: C)` and expressions `X,Y,D` the latest not containing any output variable, replaces + * `A(X, Y) = D` + * by the following equivalent + * `D.isInstanceOf[A] && X = D.b && Y = D.c` + * */ case object ADTDual extends NormalizingRule("ADTDual") { def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { val xs = p.xs.toSet @@ -18,10 +23,10 @@ case object ADTDual extends NormalizingRule("ADTDual") { val (toRemove, toAdd) = exprs.collect { case eq @ Equals(cc @ CaseClass(ct, args), e) if (variablesOf(e) -- as).isEmpty && (variablesOf(cc) & xs).nonEmpty => - (eq, IsInstanceOf(e, ct) +: (ct.fields zip args).map{ case (vd, ex) => Equals(ex, caseClassSelector(ct, e, vd.id)) } ) + (eq, IsInstanceOf(e, ct) +: (ct.classDef.fields zip args).map{ case (vd, ex) => Equals(ex, caseClassSelector(ct, e, vd.id)) } ) case eq @ Equals(e, cc @ CaseClass(ct, args)) if (variablesOf(e) -- as).isEmpty && (variablesOf(cc) & xs).nonEmpty => - (eq, IsInstanceOf(e, ct) +: (ct.fields zip args).map{ case (vd, ex) => Equals(ex, caseClassSelector(ct, e, vd.id)) } ) + (eq, IsInstanceOf(e, ct) +: (ct.classDef.fields zip args).map{ case (vd, ex) => Equals(ex, caseClassSelector(ct, e, vd.id)) } ) }.unzip if (toRemove.nonEmpty) { diff --git a/src/main/scala/leon/synthesis/rules/ADTInduction.scala b/src/main/scala/leon/synthesis/rules/ADTInduction.scala index e358ffd754831b8ded7555bb35f796e0e426e210..7461f4d58c1a9213dd68168e4f7e4551eeee1711 100644 --- a/src/main/scala/leon/synthesis/rules/ADTInduction.scala +++ b/src/main/scala/leon/synthesis/rules/ADTInduction.scala @@ -12,27 +12,41 @@ import purescala.ExprOps._ import purescala.Types._ import purescala.Definitions._ +/** For every inductive variable, outputs a recursive solution if it exists */ case object ADTInduction extends Rule("ADT Induction") { def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { + /* All input variables which are inductive in the post condition, along with their abstract data type. */ val candidates = p.as.collect { case IsTyped(origId, act: AbstractClassType) if isInductiveOn(hctx.sctx.solverFactory)(p.pc, origId) => (origId, act) } val instances = for (candidate <- candidates) yield { val (origId, ct) = candidate + // All input variables except the current inductive one. val oas = p.as.filterNot(_ == origId) + // The return type (all output variables). val resType = tupleTypeWrap(p.xs.map(_.getType)) + // A new fresh variable similar to the current inductive one to perform induction. val inductOn = FreshIdentifier(origId.name, origId.getType, true) + + // Duplicated arguments names based on existing remaining input variables. val residualArgs = oas.map(id => FreshIdentifier(id.name, id.getType, true)) + + // Mapping from existing input variables to the new duplicated ones. val residualMap = (oas zip residualArgs).map{ case (id, id2) => id -> Variable(id2) }.toMap + + // The value definition to be used in arguments of the recursive method. val residualArgDefs = residualArgs.map(ValDef(_)) + // Returns true if the case class has a field of type the one of the induction variable + // E.g. for `List` it returns true since `Cons(a: T, q: List[T])` and Cons is a List[T] def isAlternativeRecursive(ct: CaseClassType): Boolean = { ct.fields.exists(_.getType == origId.getType) } + // True if one of the case classes has a field with the type being the one of the induction variable val isRecursive = ct.knownCCDescendants.exists(isAlternativeRecursive) // Map for getting a formula in the context of within the recursive function @@ -40,6 +54,7 @@ case object ADTInduction extends Rule("ADT Induction") { if (isRecursive) { + // Transformation of conditions, variables and axioms to use the inner variables of the inductive function. val innerPhi = substAll(substMap, p.phi) val innerPC = substAll(substMap, p.pc) val innerWS = substAll(substMap, p.ws) @@ -79,6 +94,7 @@ case object ADTInduction extends Rule("ADT Induction") { case sols => var globalPre = List[Expr]() + // The recursive inner function val newFun = new FunDef(FreshIdentifier("rec", alwaysShowUniqueID = true), Nil, ValDef(inductOn) +: residualArgDefs, resType) val cases = for ((sol, (problem, pre, cct, ids, calls)) <- sols zip subProblemsInfo) yield { @@ -114,7 +130,7 @@ case object ADTInduction extends Rule("ADT Induction") { } Some(decomp(subProblemsInfo.map(_._1).toList, onSuccess, s"ADT Induction on '${origId.asString}'")) - } else { + } else { // If none of the descendants of the type is recursive, then nothing can be done. None } } diff --git a/src/main/scala/leon/synthesis/rules/ADTSplit.scala b/src/main/scala/leon/synthesis/rules/ADTSplit.scala index 7ed08608763f7a461c6c6eb66de6561fbd9fc85e..df2c44193412a55af004dfa7695901044a4b5b53 100644 --- a/src/main/scala/leon/synthesis/rules/ADTSplit.scala +++ b/src/main/scala/leon/synthesis/rules/ADTSplit.scala @@ -12,6 +12,8 @@ import purescala.Extractors._ import purescala.Constructors._ import purescala.Definitions._ +/** Abstract data type split. If a variable is typed as an abstract data type, then + * it will create a match case statement on all known subtypes. */ case object ADTSplit extends Rule("ADT Split.") { def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { // We approximate knowledge of types based on facts found at the top-level @@ -94,7 +96,7 @@ case object ADTSplit extends Rule("ADT Split.") { val cases = for ((sol, (cct, problem, pattern)) <- sols zip subInfo) yield { if (sol.pre != BooleanLiteral(true)) { - val substs = (for ((field,arg) <- cct.fields zip problem.as ) yield { + val substs = (for ((field,arg) <- cct.classDef.fields zip problem.as ) yield { (arg, caseClassSelector(cct, id.toVariable, field.id)) }).toMap globalPre ::= and(IsInstanceOf(Variable(id), cct), replaceFromIDs(substs, sol.pre)) diff --git a/src/main/scala/leon/synthesis/rules/Assert.scala b/src/main/scala/leon/synthesis/rules/Assert.scala index 4cdb0fbf29a435940881ceadf5c1a70ab4bc8001..d2cdb5b69f1f88b847ad91499e3946257ef6c167 100644 --- a/src/main/scala/leon/synthesis/rules/Assert.scala +++ b/src/main/scala/leon/synthesis/rules/Assert.scala @@ -5,9 +5,12 @@ package synthesis package rules import purescala.ExprOps._ +import purescala.TypeOps._ import purescala.Extractors._ +import purescala.Expressions._ import purescala.Constructors._ +/** Moves the preconditions without output variables to the precondition. */ case object Assert extends NormalizingRule("Assert") { def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { p.phi match { @@ -16,13 +19,20 @@ case object Assert extends NormalizingRule("Assert") { val (exprsA, others) = exprs.partition(e => (variablesOf(e) & xsSet).isEmpty) if (exprsA.nonEmpty) { + // If there is no more postcondition, then output the solution. if (others.isEmpty) { - Some(solve(Solution(andJoin(exprsA), Set(), tupleWrap(p.xs.map(id => simplestValue(id.getType)))))) + val simplestOut = simplestValue(tupleTypeWrap(p.xs.map(_.getType))) + + if (!isRealExpr(simplestOut)) { + None + } else { + Some(solve(Solution(pre = andJoin(exprsA), defs = Set(), term = simplestOut))) + } } else { val sub = p.copy(pc = andJoin(p.pc +: exprsA), phi = andJoin(others), eb = p.qeb.filterIns(andJoin(exprsA))) Some(decomp(List(sub), { - case (s @ Solution(pre, defs, term)) :: Nil => Some(Solution(andJoin(exprsA :+ pre), defs, term, s.isTrusted)) + case (s @ Solution(pre, defs, term)) :: Nil => Some(Solution(pre=andJoin(exprsA :+ pre), defs, term, s.isTrusted)) case _ => None }, "Assert "+andJoin(exprsA).asString)) } diff --git a/src/main/scala/leon/synthesis/rules/BottomUpTegis.scala b/src/main/scala/leon/synthesis/rules/BottomUpTegis.scala index 990be35e1801b5145997ec3da71471a504febec5..2f3869af16b71f9635e36d27774f55a7cee7140c 100644 --- a/src/main/scala/leon/synthesis/rules/BottomUpTegis.scala +++ b/src/main/scala/leon/synthesis/rules/BottomUpTegis.scala @@ -8,15 +8,13 @@ import purescala.Expressions._ import purescala.Common._ import purescala.Types._ import purescala.Constructors._ -import purescala.Quantification._ import evaluators._ import codegen.CodeGenParams -import utils._ import grammars._ -import bonsai._ import bonsai.enumerators._ +import bonsai.{Generator => Gen} case object BottomUpTEGIS extends BottomUpTEGISLike[TypeTree]("BU TEGIS") { def getGrammar(sctx: SynthesisContext, p: Problem) = { @@ -26,7 +24,7 @@ case object BottomUpTEGIS extends BottomUpTEGISLike[TypeTree]("BU TEGIS") { def getRootLabel(tpe: TypeTree): TypeTree = tpe } -abstract class BottomUpTEGISLike[T <% Typed](name: String) extends Rule(name) { +abstract class BottomUpTEGISLike[T <: Typed](name: String) extends Rule(name) { def getGrammar(sctx: SynthesisContext, p: Problem): ExpressionGrammar[T] def getRootLabel(tpe: TypeTree): T @@ -110,7 +108,7 @@ abstract class BottomUpTEGISLike[T <% Typed](name: String) extends Rule(name) { val targetType = tupleTypeWrap(p.xs.map(_.getType)) val wrappedTests = tests.map { case (is, os) => (is, tupleWrap(os))} - val enum = new BottomUpEnumerator[T, Expr, Expr]( + val enum = new BottomUpEnumerator[T, Expr, Expr, Generator[T, Expr]]( grammar.getProductions, wrappedTests, { (vecs, gen) => diff --git a/src/main/scala/leon/synthesis/rules/CEGISLike.scala b/src/main/scala/leon/synthesis/rules/CEGISLike.scala index 5d06aa76a3c1186e806edf605431643e6b2a9359..d577f7f9fe1f260f4af9ca4d3cb20ca868e37fcc 100644 --- a/src/main/scala/leon/synthesis/rules/CEGISLike.scala +++ b/src/main/scala/leon/synthesis/rules/CEGISLike.scala @@ -22,7 +22,7 @@ import evaluators._ import datagen._ import codegen.CodeGenParams -abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { +abstract class CEGISLike[T <: Typed](name: String) extends Rule(name) { case class CegisParams( grammar: ExpressionGrammar[T], @@ -45,14 +45,10 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { // CEGIS Flags to activate or deactivate features val useOptTimeout = sctx.settings.cegisUseOptTimeout.getOrElse(true) val useVanuatoo = sctx.settings.cegisUseVanuatoo.getOrElse(false) - val useShrink = sctx.settings.cegisUseShrink.getOrElse(false) // Limits the number of programs CEGIS will specifically validate individually val validateUpTo = 3 - // Shrink the program when the ratio of passing cases is less than the threshold - val shrinkThreshold = 1.0/2 - val interruptManager = sctx.context.interruptManager val params = getParams(sctx, p) @@ -67,9 +63,7 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { val grammar = SizeBoundedGrammar(params.grammar) - def rootLabel(tpe: TypeTree) = SizedLabel(params.rootLabel(tpe), termSize) - - def xLabels = p.xs.map(x => rootLabel(x.getType)) + def rootLabel = SizedLabel(params.rootLabel(tupleTypeWrap(p.xs.map(_.getType))), termSize) var nAltsCache = Map[SizedLabel[T], Int]() @@ -84,7 +78,7 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { } def allProgramsCount(): Int = { - xLabels.map(countAlternatives).product + countAlternatives(rootLabel) } /** @@ -108,7 +102,7 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { // C identifiers corresponding to p.xs - private var rootCs: Seq[Identifier] = Seq() + private var rootC: Identifier = _ private var bs: Set[Identifier] = Set() @@ -120,9 +114,9 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { private var slots = Map[SizedLabel[T], Int]().withDefaultValue(0) - private def streamOf(t: SizedLabel[T]): Stream[Identifier] = { - FreshIdentifier(t.asString, t.getType, true) #:: streamOf(t) - } + private def streamOf(t: SizedLabel[T]): Stream[Identifier] = Stream.continually( + FreshIdentifier(t.asString, t.getType, true) + ) def rewind(): Unit = { slots = Map[SizedLabel[T], Int]().withDefaultValue(0) @@ -179,9 +173,9 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { val cGen = new CGenerator() - rootCs = for (l <- xLabels) yield { - val c = cGen.getNext(l) - defineCTreeFor(l, c) + rootC = { + val c = cGen.getNext(rootLabel) + defineCTreeFor(rootLabel, c) c } @@ -244,7 +238,7 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { } } - allProgramsFor(rootCs) + allProgramsFor(Seq(rootC)) } private def debugCTree(cTree: Map[Identifier, Seq[(Identifier, Seq[Expr] => Expr, Seq[Identifier])]], @@ -299,11 +293,11 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { cToFd(c).fullBody = body } - // Top-level expression for rootCs - val expr = tupleWrap(rootCs.map { c => - val fd = cToFd(c) + // Top-level expression for rootC + val expr = { + val fd = cToFd(rootC) FunctionInvocation(fd.typed, fd.params.map(_.toVariable)) - }) + } (expr, cToFd.values.toSeq) } @@ -391,8 +385,8 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { //println(programCTree.asString) //println(".. "*30) - //val evaluator = new DualEvaluator(sctx.context, programCTree, CodeGenParams.default) - val evaluator = new DefaultEvaluator(sctx.context, programCTree) + //val evaluator = new DualEvaluator(sctx.context, programCTree, CodeGenParams.default) + val evaluator = new DefaultEvaluator(sctx.context, programCTree) tester = { (ex: Example, bValues: Set[Identifier]) => @@ -441,7 +435,7 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { } } - tupleWrap(rootCs.map(c => getCValue(c))) + getCValue(rootC) } /** @@ -521,7 +515,7 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { val bvs = if (isMinimal) { bs } else { - rootCs.flatMap(filterBTree).toSet + filterBTree(rootC) } excludedPrograms += bvs @@ -685,7 +679,7 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { baseExampleInputs += InExample(p.as.map(a => simplestValue(a.getType))) } else { val solverf = sctx.solverFactory - val solver = solverf.getNewSolver.setTimeout(exSolverTo) + val solver = solverf.getNewSolver().setTimeout(exSolverTo) solver.assertCnstr(p.pc) @@ -829,14 +823,13 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { if (nPassing <= validateUpTo) { // All programs failed verification, we filter everything out and unfold - //ndProgram.shrinkTo(Set(), unfolding == maxUnfoldings) doFilter = false skipCESearch = true } } } - if (doFilter && !(nPassing < nInitial * shrinkThreshold && useShrink)) { + if (doFilter) { sctx.reporter.debug("Excluding "+wrongPrograms.size+" programs") wrongPrograms.foreach { ndProgram.excludeProgram(_, true) @@ -899,8 +892,8 @@ abstract class CEGISLike[T <% Typed](name: String) extends Rule(name) { } else { result = Some(RuleFailed()) } - } } + } case Some(None) => skipCESearch = true diff --git a/src/main/scala/leon/synthesis/rules/CEGLESS.scala b/src/main/scala/leon/synthesis/rules/CEGLESS.scala index 6b0bbdd15e257841026da4f64cf8e0857f8ed1c7..c12edac075bc8525d395d5f792ef4579c0d109f1 100644 --- a/src/main/scala/leon/synthesis/rules/CEGLESS.scala +++ b/src/main/scala/leon/synthesis/rules/CEGLESS.scala @@ -11,7 +11,7 @@ import utils._ import grammars._ import Witnesses._ -case object CEGLESS extends CEGISLike[Label[String]]("CEGLESS") { +case object CEGLESS extends CEGISLike[NonTerminal[String]]("CEGLESS") { def getParams(sctx: SynthesisContext, p: Problem) = { val TopLevelAnds(clauses) = p.ws @@ -30,11 +30,11 @@ case object CEGLESS extends CEGISLike[Label[String]]("CEGLESS") { } } - val guidedGrammar = guides.map(SimilarTo(_, inputs.toSet, sctx, p)).foldLeft[ExpressionGrammar[Label[String]]](Empty())(_ || _) + val guidedGrammar = Union(guides.map(SimilarTo(_, inputs.toSet, sctx, p))) CegisParams( grammar = guidedGrammar, - rootLabel = { (tpe: TypeTree) => Label(tpe, "G0") }, + rootLabel = { (tpe: TypeTree) => NonTerminal(tpe, "G0") }, maxUnfoldings = (0 +: guides.map(depth(_) + 1)).max ) } diff --git a/src/main/scala/leon/synthesis/rules/DetupleInput.scala b/src/main/scala/leon/synthesis/rules/DetupleInput.scala index ed68fbfac84543b1922b44cb91273043756b3cb1..2ae2b1d5d0292a6ed725055e61b1b4af4100a63c 100644 --- a/src/main/scala/leon/synthesis/rules/DetupleInput.scala +++ b/src/main/scala/leon/synthesis/rules/DetupleInput.scala @@ -30,20 +30,28 @@ case object DetupleInput extends NormalizingRule("Detuple In") { * into a list of fresh typed identifiers, the tuple of these new identifiers, * and the mapping of those identifiers to their respective expressions. */ - def decompose(id: Identifier): (List[Identifier], Expr, Map[Identifier, Expr]) = id.getType match { + def decompose(id: Identifier): (List[Identifier], Expr, Map[Identifier, Expr], Expr => Seq[Expr]) = id.getType match { case cct @ CaseClassType(ccd, _) if !ccd.isAbstract => val newIds = cct.fields.map{ vd => FreshIdentifier(vd.id.name, vd.getType, true) } val map = (ccd.fields zip newIds).map{ case (vd, nid) => nid -> caseClassSelector(cct, Variable(id), vd.id) }.toMap - (newIds.toList, CaseClass(cct, newIds.map(Variable)), map) + val tMap: (Expr => Seq[Expr]) = { + case CaseClass(ccd, fields) => fields + } + + (newIds.toList, CaseClass(cct, newIds.map(Variable)), map, tMap) case TupleType(ts) => val newIds = ts.zipWithIndex.map{ case (t, i) => FreshIdentifier(id.name+"_"+(i+1), t, true) } val map = newIds.zipWithIndex.map{ case (nid, i) => nid -> TupleSelect(Variable(id), i+1) }.toMap - (newIds.toList, tupleWrap(newIds.map(Variable)), map) + val tMap: (Expr => Seq[Expr]) = { + case Tuple(fields) => fields + } + + (newIds.toList, tupleWrap(newIds.map(Variable)), map, tMap) case _ => sys.error("woot") } @@ -55,9 +63,11 @@ case object DetupleInput extends NormalizingRule("Detuple In") { var reverseMap = Map[Identifier, Expr]() + var ebMapInfo = Map[Identifier, Expr => Seq[Expr]]() + val subAs = p.as.map { a => if (isDecomposable(a)) { - val (newIds, expr, map) = decompose(a) + val (newIds, expr, map, tMap) = decompose(a) subProblem = subst(a -> expr, subProblem) subPc = subst(a -> expr, subPc) @@ -65,12 +75,25 @@ case object DetupleInput extends NormalizingRule("Detuple In") { reverseMap ++= map + ebMapInfo += a -> tMap + newIds } else { List(a) } } + var eb = p.qeb.mapIns { info => + List(info.flatMap { case (id, v) => + ebMapInfo.get(id) match { + case Some(m) => + m(v) + case None => + List(v) + } + }) + } + val newAs = subAs.flatten // Recompose CaseClasses and Tuples. @@ -101,7 +124,7 @@ case object DetupleInput extends NormalizingRule("Detuple In") { case other => other } - val sub = Problem(newAs, subWs, subPc, subProblem, p.xs) + val sub = Problem(newAs, subWs, subPc, subProblem, p.xs, eb) val s = {substAll(reverseMap, _:Expr)} andThen { simplePostTransform(recompose) } diff --git a/src/main/scala/leon/synthesis/rules/EqualitySplit.scala b/src/main/scala/leon/synthesis/rules/EqualitySplit.scala index 132ea9f766720d776af801e36ac58cc1f6b87b73..79595656c4c53cca6e47747ac34a52f436697b31 100644 --- a/src/main/scala/leon/synthesis/rules/EqualitySplit.scala +++ b/src/main/scala/leon/synthesis/rules/EqualitySplit.scala @@ -13,6 +13,8 @@ import solvers._ import scala.concurrent.duration._ +/** For every pair of input variables of the same type, + * checks equality and output an If-Then-Else statement with the two new branches. */ case object EqualitySplit extends Rule("Eq. Split") { def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { // We approximate knowledge of equality based on facts found at the top-level diff --git a/src/main/scala/leon/synthesis/rules/EquivalentInputs.scala b/src/main/scala/leon/synthesis/rules/EquivalentInputs.scala index 227dd691c29734e0dd85feb179f40fac15ceea7a..6b2f9e8585a98ab9677aa5f1c439b2a1afa136ef 100644 --- a/src/main/scala/leon/synthesis/rules/EquivalentInputs.scala +++ b/src/main/scala/leon/synthesis/rules/EquivalentInputs.scala @@ -24,7 +24,7 @@ case object EquivalentInputs extends NormalizingRule("EquivalentInputs") { val ccSubsts = for (IsInstanceOf(s, cct: CaseClassType) <- instanceOfs) yield { - val fieldsVals = (for (f <- cct.fields) yield { + val fieldsVals = (for (f <- cct.classDef.fields) yield { val id = f.id clauses.find { diff --git a/src/main/scala/leon/synthesis/rules/Ground.scala b/src/main/scala/leon/synthesis/rules/Ground.scala index 49663409fab1fb22dc102e35cfcfd3fb0ab5cdb4..d36cb15c0ff6f487d5d7b5e296fda0c406406886 100644 --- a/src/main/scala/leon/synthesis/rules/Ground.scala +++ b/src/main/scala/leon/synthesis/rules/Ground.scala @@ -19,8 +19,14 @@ case object Ground extends Rule("Ground") { val result = solver.solveSAT(p.phi) match { case (Some(true), model) => - val sol = Solution(BooleanLiteral(true), Set(), tupleWrap(p.xs.map(valuateWithModel(model)))) - RuleClosed(sol) + val solExpr = tupleWrap(p.xs.map(valuateWithModel(model))) + + if (!isRealExpr(solExpr)) { + RuleFailed() + } else { + val sol = Solution(BooleanLiteral(true), Set(), solExpr) + RuleClosed(sol) + } case (Some(false), model) => RuleClosed(Solution.UNSAT(p)) case _ => diff --git a/src/main/scala/leon/synthesis/rules/IndependentSplit.scala b/src/main/scala/leon/synthesis/rules/IndependentSplit.scala new file mode 100644 index 0000000000000000000000000000000000000000..72be06b9cff76a63ced4506ef086a58622326bef --- /dev/null +++ b/src/main/scala/leon/synthesis/rules/IndependentSplit.scala @@ -0,0 +1,80 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package synthesis +package rules + +import leon.utils._ +import purescala.Expressions._ +import purescala.ExprOps._ +import purescala.Extractors._ +import purescala.Constructors._ +import purescala.Common._ +import purescala.Types.CaseClassType + +case object IndependentSplit extends NormalizingRule("IndependentSplit") { + def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { + val TopLevelAnds(clauses) = and(p.pc, p.phi) + + var independentClasses = Set[Set[Identifier]]() + + // We group connect variables together + for(c <- clauses) { + val vs = variablesOf(c) + + var newClasses = Set[Set[Identifier]]() + + var thisClass = vs + + for (cl <- independentClasses) { + if ((cl & vs).nonEmpty) { + thisClass ++= cl + } else { + newClasses += cl + } + } + + independentClasses = newClasses + thisClass + } + + val outClasses = independentClasses.map(cl => cl & p.xs.toSet).filter(_.nonEmpty) + + if (outClasses.size > 1) { + + val TopLevelAnds(phiClauses) = p.phi + + val subs = (for (cl <- outClasses.toList) yield { + val xs = p.xs.filter(cl) + + if (xs.nonEmpty) { + val phi = andJoin(phiClauses.filter(c => (variablesOf(c) & cl).nonEmpty)) + + val xsToRemove = p.xs.filterNot(cl).toSet + + val eb = p.qeb.removeOuts(xsToRemove) + + Some(p.copy(phi = phi, xs = xs, eb = eb)) + } else { + None + } + }).flatten + + val onSuccess: List[Solution] => Option[Solution] = { sols => + + val infos = subs.map(_.xs).zip(sols.map(_.term)) + + val term = infos.foldLeft(tupleWrap(p.xs.map(_.toVariable))) { + case (expr, (xs, term)) => + letTuple(xs, term, expr) + } + + Some(Solution(andJoin(sols.map(_.pre)), sols.map(_.defs).flatten.toSet, term, sols.forall(_.isTrusted))) + } + + + List(decomp(subs, onSuccess, "Independent Clusters")) + } else { + Nil + } + } +} diff --git a/src/main/scala/leon/synthesis/rules/InputSplit.scala b/src/main/scala/leon/synthesis/rules/InputSplit.scala new file mode 100644 index 0000000000000000000000000000000000000000..4b9ffef53793528b628b41485c0a9156e171f188 --- /dev/null +++ b/src/main/scala/leon/synthesis/rules/InputSplit.scala @@ -0,0 +1,49 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package synthesis +package rules + +import leon.purescala.Common.Identifier +import purescala.Expressions._ +import purescala.Extractors._ +import purescala.ExprOps._ +import purescala.Constructors._ +import purescala.Types._ + +import solvers._ + +import scala.concurrent.duration._ + +case object InputSplit extends Rule("In. Split") { + def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { + p.as.filter(_.getType == BooleanType).flatMap { a => + def getProblem(v: Boolean): Problem = { + def replaceA(e: Expr) = replaceFromIDs(Map(a -> BooleanLiteral(v)), e) + + p.copy( + as = p.as.filterNot(_ == a), + ws = replaceA(p.ws), + pc = replaceA(p.pc), + phi = replaceA(p.phi), + eb = p.qeb.removeIns(Set(a)) + ) + } + + val sub1 = getProblem(true) + val sub2 = getProblem(false) + + val onSuccess: List[Solution] => Option[Solution] = { + case List(s1, s2) => + Some(Solution(or(and(Equals(Variable(a), BooleanLiteral(true)), s1.pre), + and(Equals(Variable(a), BooleanLiteral(false)), s2.pre)), + s1.defs ++ s2.defs, + IfExpr(Variable(a), s1.term, s2.term), s1.isTrusted && s2.isTrusted)) + case _ => + None + } + + Some(decomp(List(sub1, sub2), onSuccess, s"Split on '$a'")) + } + } +} diff --git a/src/main/scala/leon/synthesis/rules/OnePoint.scala b/src/main/scala/leon/synthesis/rules/OnePoint.scala index 451dc8c3ce04c537f30d1f4c84416e9dbcb577e9..3afcaf5729d215edbaea19e128fddb1bc27a6fe9 100644 --- a/src/main/scala/leon/synthesis/rules/OnePoint.scala +++ b/src/main/scala/leon/synthesis/rules/OnePoint.scala @@ -10,6 +10,7 @@ import purescala.ExprOps._ import purescala.Extractors._ import purescala.Constructors._ +/** If there is a top-level equality such as x = e where e does not contains x, then we can output the assignment and replace x anywhere else. */ case object OnePoint extends NormalizingRule("One-point") { def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { val TopLevelAnds(exprs) = p.phi diff --git a/src/main/scala/leon/synthesis/rules/OptimisticGround.scala b/src/main/scala/leon/synthesis/rules/OptimisticGround.scala index 6085ef9eb2067fa092ffacfdefbf27532b02b5d9..679e49e38fb25ce3c4f79a26e1078651eccb69a5 100644 --- a/src/main/scala/leon/synthesis/rules/OptimisticGround.scala +++ b/src/main/scala/leon/synthesis/rules/OptimisticGround.scala @@ -36,7 +36,6 @@ case object OptimisticGround extends Rule("Optimistic Ground") { //println("SOLVING " + phi + " ...") solver.solveSAT(phi) match { case (Some(true), satModel) => - val newNotPhi = valuateWithModelIn(notPhi, xss, satModel) //println("REFUTING " + Not(newNotPhi) + "...") @@ -46,8 +45,15 @@ case object OptimisticGround extends Rule("Optimistic Ground") { predicates = valuateWithModelIn(phi, ass, invalidModel) +: predicates case (Some(false), _) => - result = Some(RuleClosed(Solution(BooleanLiteral(true), Set(), tupleWrap(p.xs.map(valuateWithModel(satModel)))))) - + // Model apprears valid, but it might be a fake expression (generic values) + val outExpr = tupleWrap(p.xs.map(valuateWithModel(satModel))) + + if (!isRealExpr(outExpr)) { + // It does contain a generic value, we skip + predicates = valuateWithModelIn(phi, xss, satModel) +: predicates + } else { + result = Some(RuleClosed(Solution(BooleanLiteral(true), Set(), outExpr))) + } case _ => continue = false result = None diff --git a/src/main/scala/leon/synthesis/rules/StringRender.scala b/src/main/scala/leon/synthesis/rules/StringRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..f03c54b560e81428851c83b86b9430d4e706e20f --- /dev/null +++ b/src/main/scala/leon/synthesis/rules/StringRender.scala @@ -0,0 +1,490 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package synthesis +package rules + +import scala.annotation.tailrec +import scala.collection.mutable.ListBuffer + +import bonsai.enumerators.MemoizedEnumerator +import leon.evaluators.DefaultEvaluator +import leon.evaluators.StringTracingEvaluator +import leon.synthesis.programsets.DirectProgramSet +import leon.synthesis.programsets.JoinProgramSet +import leon.purescala.Common.FreshIdentifier +import leon.purescala.Common.Identifier +import leon.purescala.DefOps +import leon.purescala.Definitions.FunDef +import leon.purescala.Definitions.FunDef +import leon.purescala.Definitions.ValDef +import leon.purescala.ExprOps +import leon.solvers.Model +import leon.solvers.ModelBuilder +import leon.solvers.string.StringSolver +import leon.utils.DebugSectionSynthesis +import purescala.Constructors._ +import purescala.Definitions._ +import purescala.ExprOps._ +import purescala.Expressions._ +import purescala.Extractors._ +import purescala.TypeOps +import purescala.Types._ + + +/** A template generator for a given type tree. + * Extend this class using a concrete type tree, + * Then use the apply method to get a hole which can be a placeholder for holes in the template. + * Each call to the ``.instantiate` method of the subsequent Template will provide different instances at each position of the hole. + */ +abstract class TypedTemplateGenerator(t: TypeTree) { + import StringRender.WithIds + /** Provides a hole which can be */ + def apply(f: Expr => Expr): TemplateGenerator = { + val id = FreshIdentifier("ConstToInstantiate", t, true) + new TemplateGenerator(f(Variable(id)), id, t) + } + def nested(f: Expr => WithIds[Expr]): TemplateGenerator = { + val id = FreshIdentifier("ConstToInstantiate", t, true) + val res = f(Variable(id)) + new TemplateGenerator(res._1, id, t, res._2) + } + class TemplateGenerator(template: Expr, varId: Identifier, t: TypeTree, initialHoles: List[Identifier] = Nil) { + private val optimizationVars = ListBuffer[Identifier]() ++= initialHoles + private def Const: Variable = { + val res = FreshIdentifier("const", t, true) + optimizationVars += res + Variable(res) + } + private def instantiate: Expr = { + ExprOps.postMap({ + case Variable(id) if id == varId => Some(Const) + case _ => None + })(template) + } + def instantiateWithVars: WithIds[Expr] = (instantiate, optimizationVars.toList) + } +} + +/** + * @author Mikael + */ +case object StringRender extends Rule("StringRender") { + type WithIds[T] = (T, List[Identifier]) + + 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)) + } + } + + 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)] = ??? + + /** 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]] = ??? + + import StringSolver.{StringFormToken, StringForm, Problem => SProblem, Equation, Assignment} + + /** Converts an expression to a stringForm, suitable for StringSolver */ + def toStringForm(e: Expr, acc: List[StringFormToken] = Nil)(implicit hctx: SearchContext): Option[StringForm] = e match { + case StringLiteral(s) => + Some(Left(s)::acc) + case Variable(id) => Some(Right(id)::acc) + case StringConcat(lhs, rhs) => + toStringForm(rhs, acc).flatMap(toStringForm(lhs, _)) + 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)) + case _ => None + } + + /** 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) + + @tailrec def gatherEquations(s: List[InOutExample], acc: ListBuffer[Equation] = ListBuffer()): Option[SProblem] = s match { + case Nil => Some(acc.toList) + case InOutExample(in, rhExpr)::q => + if(rhExpr.length == 1) { + val model = new ModelBuilder + model ++= inputs.zip(in) + val modelResult = model.result() + val evalResult = e.eval(template, modelResult) + evalResult.result match { + case None => + hctx.reporter.debug("Eval = None : ["+template+"] in ["+inputs.zip(in)+"]") + None + case Some((sfExpr, abstractSfExpr)) => + //ctx.reporter.debug("Eval = ["+sfExpr+"] (from "+abstractSfExpr+")") + val sf = toStringForm(sfExpr) + val rhs = toStringLiteral(rhExpr.head) + if(sf.isEmpty || rhs.isEmpty) { + hctx.reporter.ifDebug(printer => printer("sf empty or rhs empty ["+sfExpr+"] => ["+sf+"] in ["+rhs+"]")) + None + } else gatherEquations(q, acc += ((sf.get, rhs.get))) + } + } else { + hctx.reporter.ifDebug(printer => printer("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 + } + } + + 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))) + + val wholeTemplates = JoinProgramSet.direct(funs, DirectProgramSet(template)) + + 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) + } + + val tagged_solutions = + for{(funDefs, template) <- wholeTemplates.programs} yield computeSolutions(funDefs, template).map((funDefs, template, _)) + + solutionStreamToRuleApplication(p, leon.utils.StreamUtils.interleave(tagged_solutions))(hctx.program) + } + + /** Find ambiguities not containing _edit_me_ to ask to the user */ + def askQuestion(input: List[Identifier], r: RuleClosed)(implicit c: LeonContext, p: Program): List[disambiguation.Question[StringLiteral]] = { + //if !s.contains(EDIT_ME) + val qb = new disambiguation.QuestionBuilder(input, r.solutions, (seq: Seq[Expr], expr: Expr) => expr match { + case s@StringLiteral(slv) if !slv.contains(EDIT_ME) => Some(s) + case _ => None + }) + qb.result() + } + + /** Converts the stream of solutions to a RuleApplication */ + def solutionStreamToRuleApplication(p: Problem, solutions: Stream[(Seq[(FunDef, WithIds[Expr])], WithIds[Expr], Assignment)])(implicit program: Program): RuleApplication = { + if(solutions.isEmpty) RuleFailed() else { + RuleClosed( + for((funDefsBodies, (singleTemplate, ids), assignment) <- solutions) yield { + val fds = for((fd, (body, ids)) <- funDefsBodies) yield { + val initMap = ids.map(_ -> StringLiteral(EDIT_ME)).toMap + fd.body = Some(ExprOps.simplifyString(ExprOps.replaceFromIDs(initMap ++ assignment.mapValues(StringLiteral), body))) + fd + } + val initMap = ids.map(_ -> StringLiteral(EDIT_ME)).toMap + val term = ExprOps.simplifyString(ExprOps.replaceFromIDs(initMap ++ assignment.mapValues(StringLiteral), singleTemplate)) + val (finalTerm, finalDefs) = makeFunctionsUnique(term, fds.toSet) + + Solution(pre=p.pc, defs=finalDefs, term=finalTerm) + }) + } + } + + /** Crystallizes a solution so that it will not me modified if the body of fds is modified. */ + def makeFunctionsUnique(term: Expr, fds: Set[FunDef])(implicit program: Program): (Expr, Set[FunDef]) = { + 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 e => None + })(body) + } + + def getMapping(fd: FunDef): FunDef = { + transformMap.getOrElse(fd, { + val newfunDef = new FunDef(fd.id.freshen, fd.tparams, fd.params, fd.returnType) // With empty body + transformMap += fd -> newfunDef + newfunDef.body = fd.body.map(mapExpr _) + newfunDef + }) + } + + (mapExpr(term), fds.map(getMapping _)) + } + + + object StringTemplateGenerator extends TypedTemplateGenerator(StringType) + + 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 */ + class StringSynthesisResult( + val adtToString: MapFunctions, + val funNames: Set[String] + ) { + def add(d: DependentType, f: FunDef, s: Stream[WithIds[Expr]]): StringSynthesisResult = { + new StringSynthesisResult(adtToString + (d -> ((f, s))), funNames + f.id.name) + } + def freshFunName(s: String): String = { + if(!funNames(s)) return s + var i = 1 + var s0 = s + do { + i += 1 + s0 = s + i + } while(funNames(s+i)) + s0 + } + } + + /** Context for the current synthesis process */ + class StringSynthesisContext( + val currentCaseClassParent: Option[TypeTree], + val result: StringSynthesisResult + )(implicit hctx: SearchContext) { + def add(d: DependentType, f: FunDef, s: Stream[WithIds[Expr]]): StringSynthesisContext = { + new StringSynthesisContext(currentCaseClassParent, result.add(d, f, s)) + } + def copy(currentCaseClassParent: Option[TypeTree]=currentCaseClassParent, result: StringSynthesisResult = result): StringSynthesisContext = + new StringSynthesisContext(currentCaseClassParent, result) + def freshFunName(s: String) = result.freshFunName(s) + } + + /** Creates an empty function definition for the dependent type */ + def createEmptyFunDef(ctx: StringSynthesisContext, tpe: DependentType)(implicit hctx: SearchContext): FunDef = { + def defaultFunName(t: TypeTree) = t match { + case AbstractClassType(c, d) => c.id.asString(hctx.context) + case CaseClassType(c, d) => c.id.asString(hctx.context) + case t => t.asString(hctx.context) + } + + val funName2 = tpe.caseClassParent match { + case None => defaultFunName(tpe.typeToConvert) + "_" + tpe.inputName + "_s" + case Some(t) => defaultFunName(tpe.typeToConvert) + "In"+defaultFunName(t) + "_" + tpe.inputName + "_s" + } + val funName3 = funName2.replaceAll("[^a-zA-Z0-9_]","") + 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. + fd + } + + /** Pre-updates of the function definition */ + def preUpdateFunDefBody(tpe: DependentType, fd: FunDef, ctx: StringSynthesisContext): StringSynthesisContext = { + ctx.result.adtToString.get(tpe) match { + case None => ctx.add(tpe, fd, Stream.Empty) + case Some(_) => ctx + } + } + + /** Assembles multiple MatchCase to a singleMatchExpr using the function definition fd */ + private val mergeMatchCases = (fd: FunDef) => (cases: Seq[WithIds[MatchCase]]) => (MatchExpr(Variable(fd.params(0).id), cases.map(_._1)), cases.map(_._2).flatten.toList) + + /** Returns a (possibly recursive) template which can render the inputs in their order. + * Returns an expression and path-dependent pretty printers which can be used. + * + * @param inputs The list of inputs. Make sure each identifier is typed. + **/ + def createFunDefsTemplates( + ctx: StringSynthesisContext, + inputs: Seq[Expr])(implicit hctx: SearchContext): (Stream[WithIds[Expr]], StringSynthesisResult) = { + + def extractCaseVariants(cct: CaseClassType, ctx: StringSynthesisContext) + : (Stream[WithIds[MatchCase]], StringSynthesisResult) = cct match { + case CaseClassType(ccd@CaseClassDef(id, tparams, parent, isCaseObject), tparams2) => + val typeMap = tparams.zip(tparams2).toMap + val fields = ccd.fields.map(vd => TypeOps.instantiateType(vd.id, typeMap) ) + val pattern = CaseClassPattern(None, ccd.typed(tparams2), fields.map(k => WildcardPattern(Some(k)))) + val (rhs, result) = createFunDefsTemplates(ctx.copy(currentCaseClassParent=Some(cct)), fields.map(Variable)) // Invoke functions for each of the fields. + val newCases = rhs.map(e => (MatchCase(pattern, None, e._1), e._2)) + (newCases, result) + } + + /* Returns a constant pattern matching in which all classes are rendered using their proper name + * For example: + * {{{ + * sealed abstract class Thread + * case class T1() extends Thread() + * case Class T2() extends Thread() + * }}} + * Will yield the following expression: + * {{{t match { + * case T1() => "T1" + * case T2() => "T2" + * } + * }}} + * + */ + def constantPatternMatching(fd: FunDef, act: AbstractClassType): WithIds[MatchExpr] = { + val cases = (ListBuffer[WithIds[MatchCase]]() /: act.knownCCDescendants) { + case (acc, cct@CaseClassType(ccd@CaseClassDef(id, tparams, parent, isCaseObject), tparams2)) => + val typeMap = tparams.zip(tparams2).toMap + val fields = ccd.fields.map(vd => TypeOps.instantiateType(vd.id, typeMap) ) + val pattern = CaseClassPattern(None, ccd.typed(tparams2), fields.map(k => WildcardPattern(Some(k)))) + val rhs = StringLiteral(id.asString) + MatchCase(pattern, None, rhs) + acc += ((MatchCase(pattern, None, rhs), Nil)) + case (acc, e) => hctx.reporter.fatalError("Could not handle this class definition for string rendering " + e) + } + mergeMatchCases(fd)(cases) + } + + /* Returns a list of expressions converting the list of inputs to string. + * Each expression is tagged with a list of identifiers, which is the list of variables which need to be found. + * @return Along with the list, an updated function definitions to transform (parent-dependent) types to strings */ + @tailrec def gatherInputs( + ctx: StringSynthesisContext, + inputs: List[Expr], + result: ListBuffer[Stream[WithIds[Expr]]] = ListBuffer()): (List[Stream[WithIds[Expr]]], StringSynthesisResult) = inputs match { + case Nil => (result.toList, ctx.result) + case input::q => + 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))) + case None => // No function can render the current type. + input.getType match { + case StringType => + gatherInputs(ctx, q, result += + (Stream((input, Nil), + (FunctionInvocation( + hctx.program.library.escape.get.typed, + Seq(input)): Expr, Nil)))) + case BooleanType => + val (bTemplate, vs) = booleanTemplate(input).instantiateWithVars + gatherInputs(ctx, q, result += Stream((BooleanToString(input), Nil), (bTemplate, vs))) + case WithStringconverter(converter) => // Base case + gatherInputs(ctx, q, result += 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))) + } + case TypeParameter(t) => + hctx.reporter.fatalError("Could not handle type parameter for string rendering " + t) + case tpe => + hctx.reporter.fatalError("Could not handle class type for string rendering " + tpe) + } + } + } + var ctx2 = ctx // We gather the functions only once. + // Flatten tuple types + val newInputs = inputs.flatMap{ case input => + input.getType match { + case TupleType(bases) => + val blength = bases.length + for(index <- 1 to blength) yield tupleSelect(input, index, blength) + case _ => List(input) + } + } + // Get all permutations + val templates = for(inputPermutation <- newInputs.permutations.toStream) yield { + val (exprs, result3) = gatherInputs(ctx2, inputPermutation.toList) + ctx2 = ctx2.copy(result=result3) + /* Add post, pre and in-between holes, and returns a single expr along with the new assignments. */ + val template: Stream[WithIds[Expr]] = exprs match { + case Nil => + Stream(StringTemplateGenerator(Hole => Hole).instantiateWithVars) + case exprList => + JoinProgramSet(exprList.map(DirectProgramSet(_)), (exprs: Seq[WithIds[Expr]]) => + StringTemplateGenerator.nested(Hole => { + val res = ((StringConcat(Hole, exprs.head._1), exprs.head._2) /: exprs.tail) { + case ((finalExpr, finalIds), (expr, ids)) => (StringConcat(StringConcat(finalExpr, Hole), expr), finalIds ++ ids) + } + (StringConcat(res._1, Hole), res._2) + }).instantiateWithVars + ).programs + } + template + } + (templates.flatten, ctx2.result) // TODO: Flatten or interleave? + } + + def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { + //hctx.reporter.debug("StringRender:Output variables="+p.xs+", their types="+p.xs.map(_.getType)) + p.xs match { + case List(IsTyped(v, StringType)) => + val description = "Creates a standard string conversion function" + + val defaultToStringFunctions = defaultMapTypeToString() + + val examplesFinder = new ExamplesFinder(hctx.context, hctx.program) + val examples = examplesFinder.extractFromProblem(p) + + val ruleInstantiations = ListBuffer[RuleInstantiation]() + ruleInstantiations += RuleInstantiation("String conversion") { + val (expr, synthesisResult) = createFunDefsTemplates(StringSynthesisContext.empty, p.as.map(Variable)) + val funDefs = synthesisResult.adtToString + + /*val toDebug: String = (("\nInferred functions:" /: funDefs)( (t, s) => + t + "\n" + s._2._1.toString + ))*/ + //hctx.reporter.debug("Inferred expression:\n" + expr + toDebug) + + findSolutions(examples, expr, funDefs.values.toSeq) + } + + ruleInstantiations.toList + + case _ => Nil + } + } +} \ No newline at end of file diff --git a/src/main/scala/leon/synthesis/rules/TEGISLike.scala b/src/main/scala/leon/synthesis/rules/TEGISLike.scala index 2bc58307fb66449e13d889fceab03b45bb4c868f..91084ae4f6d69d055c36f0ce2c75bc4b41bfa763 100644 --- a/src/main/scala/leon/synthesis/rules/TEGISLike.scala +++ b/src/main/scala/leon/synthesis/rules/TEGISLike.scala @@ -17,7 +17,7 @@ import scala.collection.mutable.{HashMap => MutableMap} import bonsai.enumerators._ -abstract class TEGISLike[T <% Typed](name: String) extends Rule(name) { +abstract class TEGISLike[T <: Typed](name: String) extends Rule(name) { case class TegisParams( grammar: ExpressionGrammar[T], rootLabel: TypeTree => T, @@ -29,8 +29,6 @@ abstract class TEGISLike[T <% Typed](name: String) extends Rule(name) { def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { - return Nil - List(new RuleInstantiation(this.name) { def apply(hctx: SearchContext): RuleApplication = { val sctx = hctx.sctx @@ -73,7 +71,7 @@ abstract class TEGISLike[T <% Typed](name: String) extends Rule(name) { val evalParams = CodeGenParams.default.copy(maxFunctionInvocations = 2000) val evaluator = new DualEvaluator(sctx.context, sctx.program, evalParams) - val enum = new MemoizedEnumerator[T, Expr](grammar.getProductions) + val enum = new MemoizedEnumerator[T, Expr, Generator[T, Expr]](grammar.getProductions) val targetType = tupleTypeWrap(p.xs.map(_.getType)) diff --git a/src/main/scala/leon/synthesis/rules/TEGLESS.scala b/src/main/scala/leon/synthesis/rules/TEGLESS.scala index d8de10cbb952b764916386f9accdca98129f2c71..f56b26b26d859566c77c91f484deac41930f2aa7 100644 --- a/src/main/scala/leon/synthesis/rules/TEGLESS.scala +++ b/src/main/scala/leon/synthesis/rules/TEGLESS.scala @@ -10,7 +10,7 @@ import utils._ import Witnesses._ import grammars._ -case object TEGLESS extends TEGISLike[Label[String]]("TEGLESS") { +case object TEGLESS extends TEGISLike[NonTerminal[String]]("TEGLESS") { def getParams(sctx: SynthesisContext, p: Problem) = { val TopLevelAnds(clauses) = p.ws @@ -28,11 +28,11 @@ case object TEGLESS extends TEGISLike[Label[String]]("TEGLESS") { } } - val guidedGrammar = guides.map(SimilarTo(_, inputs.toSet, sctx, p)).foldLeft[ExpressionGrammar[Label[String]]](Empty())(_ || _) + val guidedGrammar = guides.map(SimilarTo(_, inputs.toSet, sctx, p)).foldLeft[ExpressionGrammar[NonTerminal[String]]](Empty())(_ || _) TegisParams( grammar = guidedGrammar, - rootLabel = { (tpe: TypeTree) => Label(tpe, "G0") } + rootLabel = { (tpe: TypeTree) => NonTerminal(tpe, "G0") } ) } } diff --git a/src/main/scala/leon/synthesis/rules/UnconstrainedOutput.scala b/src/main/scala/leon/synthesis/rules/UnconstrainedOutput.scala index f753d3884e71e4ba76c84b72985f6401c0666256..eb3d83ed8d13b8235a98b48d4c209a2db5a7f08c 100644 --- a/src/main/scala/leon/synthesis/rules/UnconstrainedOutput.scala +++ b/src/main/scala/leon/synthesis/rules/UnconstrainedOutput.scala @@ -7,10 +7,13 @@ package rules import purescala.Expressions._ import purescala.ExprOps._ import purescala.Constructors._ +import purescala.TypeOps._ case object UnconstrainedOutput extends NormalizingRule("Unconstr.Output") { def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { - val unconstr = p.xs.toSet -- variablesOf(p.phi) + val unconstr = (p.xs.toSet -- variablesOf(p.phi)).filter { x => + isRealExpr(simplestValue(x.getType)) + } if (unconstr.nonEmpty) { val sub = p.copy(xs = p.xs.filterNot(unconstr), eb = p.qeb.removeOuts(unconstr)) diff --git a/src/main/scala/leon/synthesis/rules/UnusedInput.scala b/src/main/scala/leon/synthesis/rules/UnusedInput.scala index 677c9b852aafeadcfc96bd63f2b98aaaaf289940..4570501d7646b3f8d77d4fb484451ff957ac723f 100644 --- a/src/main/scala/leon/synthesis/rules/UnusedInput.scala +++ b/src/main/scala/leon/synthesis/rules/UnusedInput.scala @@ -5,10 +5,13 @@ package synthesis package rules import purescala.ExprOps._ +import purescala.TypeOps._ case object UnusedInput extends NormalizingRule("UnusedInput") { def instantiateOn(implicit hctx: SearchContext, p: Problem): Traversable[RuleInstantiation] = { - val unused = p.as.toSet -- variablesOf(p.phi) -- variablesOf(p.pc) -- variablesOf(p.ws) + val unused = (p.as.toSet -- variablesOf(p.phi) -- variablesOf(p.pc) -- variablesOf(p.ws)).filter { a => + !isParametricType(a.getType) + } if (unused.nonEmpty) { val sub = p.copy(as = p.as.filterNot(unused), eb = p.qeb.removeIns(unused)) diff --git a/src/main/scala/leon/termination/Processor.scala b/src/main/scala/leon/termination/Processor.scala index bbd87b999e653a943598915e7d2e38a9aba20bff..3f7be09f145d2768ff00f8573f078d6e90bc1b2d 100644 --- a/src/main/scala/leon/termination/Processor.scala +++ b/src/main/scala/leon/termination/Processor.scala @@ -34,7 +34,7 @@ trait Solvable extends Processor { val sizeUnit : UnitDef = UnitDef(FreshIdentifier("$size"),Seq(sizeModule)) val newProgram : Program = program.copy( units = sizeUnit :: program.units) - SolverFactory.getFromSettings(context, newProgram).withTimeout(1000.millisecond) + SolverFactory.getFromSettings(context, newProgram).withTimeout(10.seconds) } type Solution = (Option[Boolean], Map[Identifier, Expr]) diff --git a/src/main/scala/leon/termination/SelfCallsProcessor.scala b/src/main/scala/leon/termination/SelfCallsProcessor.scala index 157dbce24bf26aef4e44ae7bb9928dca348be98b..320c230c2cae410d7862c9ec9c15ea2b849cdba6 100644 --- a/src/main/scala/leon/termination/SelfCallsProcessor.scala +++ b/src/main/scala/leon/termination/SelfCallsProcessor.scala @@ -30,7 +30,7 @@ class SelfCallsProcessor(val checker: TerminationChecker) extends Processor { def rec(e0: Expr): Boolean = e0 match { case Assert(pred: Expr, error: Option[String], body: Expr) => rec(pred) || rec(body) case Let(binder: Identifier, value: Expr, body: Expr) => rec(value) || rec(body) - case LetDef(fd: FunDef, body: Expr) => rec(body) // don't enter fd because we don't know if it will be called + case LetDef(fds, body: Expr) => rec(body) // don't enter fds because we don't know if it will be called case FunctionInvocation(tfd: TypedFunDef, args: Seq[Expr]) => tfd.fd == f /* <-- success in proving non-termination */ || args.exists(arg => rec(arg)) || (tfd.fd.hasBody && (!seenFunDefs.contains(tfd.fd)) && { diff --git a/src/main/scala/leon/transformations/InstrumentationUtil.scala b/src/main/scala/leon/transformations/InstrumentationUtil.scala index 43a9e23efe7c524342fe935514701438d993e9eb..6d0a97b577c2bccd5733f5196346d10a73d8db0e 100644 --- a/src/main/scala/leon/transformations/InstrumentationUtil.scala +++ b/src/main/scala/leon/transformations/InstrumentationUtil.scala @@ -65,7 +65,7 @@ object InstUtil { val vary = yid.toVariable val args = Seq(xid, yid) val maxType = FunctionType(Seq(IntegerType, IntegerType), IntegerType) - val mfd = new FunDef(FreshIdentifier("max", maxType, false), Seq(), args.map((arg) => ValDef(arg, Some(arg.getType))), IntegerType) + val mfd = new FunDef(FreshIdentifier("max", maxType, false), Seq(), args.map(arg => ValDef(arg)), IntegerType) val cond = GreaterEquals(varx, vary) mfd.body = Some(IfExpr(cond, varx, vary)) diff --git a/src/main/scala/leon/transformations/IntToRealProgram.scala b/src/main/scala/leon/transformations/IntToRealProgram.scala index 21229282b398fcc10cd9548ddaead2f25d8087a3..0c313f2ff3ce79809420969a49bc2d3b6f11b2f3 100644 --- a/src/main/scala/leon/transformations/IntToRealProgram.scala +++ b/src/main/scala/leon/transformations/IntToRealProgram.scala @@ -72,8 +72,7 @@ abstract class ProgramTypeTransformer { } def mapDecl(decl: ValDef): ValDef = { - val newtpe = mapType(decl.getType) - new ValDef(mapId(decl.id), Some(newtpe)) + decl.copy(id = mapId(decl.id)) } def mapType(tpe: TypeTree): TypeTree = { @@ -141,9 +140,9 @@ abstract class ProgramTypeTransformer { // FIXME //add a new postcondition newfd.fullBody = if (fd.postcondition.isDefined && newfd.body.isDefined) { - val Lambda(Seq(ValDef(resid, _)), pexpr) = fd.postcondition.get + val Lambda(Seq(ValDef(resid)), pexpr) = fd.postcondition.get val tempRes = mapId(resid).toVariable - Ensuring(newfd.body.get, Lambda(Seq(ValDef(tempRes.id, Some(tempRes.getType))), transformExpr(pexpr))) + Ensuring(newfd.body.get, Lambda(Seq(ValDef(tempRes.id)), transformExpr(pexpr))) // Some(mapId(resid), transformExpr(pexpr)) } else NoTree(fd.returnType) @@ -233,4 +232,4 @@ class RealToIntProgram extends ProgramTypeTransformer { } def mappedFun(fd: FunDef): FunDef = newFundefs(fd) -} \ No newline at end of file +} diff --git a/src/main/scala/leon/transformations/NonlinearityEliminationPhase.scala b/src/main/scala/leon/transformations/NonlinearityEliminationPhase.scala index d17968dc05b789b936554b9c10dbde22ac360dfb..a696dae0580baf44c6ef955c72ccdcce9791bdab 100644 --- a/src/main/scala/leon/transformations/NonlinearityEliminationPhase.scala +++ b/src/main/scala/leon/transformations/NonlinearityEliminationPhase.scala @@ -26,7 +26,7 @@ object MultFuncs { val vary = yid.toVariable val args = Seq(xid, yid) val funcType = FunctionType(Seq(domain, domain), domain) - val mfd = new FunDef(FreshIdentifier("pmult", funcType, false), Seq(), args.map((arg) => ValDef(arg, Some(arg.getType))), domain) + val mfd = new FunDef(FreshIdentifier("pmult", funcType, false), Seq(), args.map(arg => ValDef(arg)), domain) val tmfd = TypedFunDef(mfd, Seq()) //define a body (a) using mult(x,y) = if(x == 0 || y ==0) 0 else mult(x-1,y) + y @@ -47,7 +47,7 @@ object MultFuncs { val post1 = Implies(guard, defn2) // mfd.postcondition = Some((resvar.id, And(Seq(post0, post1)))) - mfd.fullBody = Ensuring(mfd.body.get, Lambda(Seq(ValDef(resvar.id, Some(resvar.getType))), And(Seq(post0, post1)))) + mfd.fullBody = Ensuring(mfd.body.get, Lambda(Seq(ValDef(resvar.id)), And(Seq(post0, post1)))) //set function properties (for now, only monotonicity) mfd.addFlags(Set(Annotation("theoryop", Seq()), Annotation("monotonic", Seq()))) //"distributive" ? mfd @@ -59,7 +59,7 @@ object MultFuncs { val yid = FreshIdentifier("y", domain) val args = Seq(xid, yid) val funcType = FunctionType(Seq(domain, domain), domain) - val fd = new FunDef(FreshIdentifier("mult", funcType, false), Seq(), args.map((arg) => ValDef(arg, Some(arg.getType))), domain) + val fd = new FunDef(FreshIdentifier("mult", funcType, false), Seq(), args.map(arg => ValDef(arg)), domain) val tpivMultFun = TypedFunDef(pivMultFun, Seq()) //the body is defined as mult(x,y) = val px = if(x < 0) -x else x; diff --git a/src/main/scala/leon/transformations/SerialInstrumentationPhase.scala b/src/main/scala/leon/transformations/SerialInstrumentationPhase.scala index 0838abe3f5fcbb6f91e52ef5bdfcf2ce3bc873c7..cee20aca2c56e6c7c17bc4dd9c5405fa49f77d85 100644 --- a/src/main/scala/leon/transformations/SerialInstrumentationPhase.scala +++ b/src/main/scala/leon/transformations/SerialInstrumentationPhase.scala @@ -32,10 +32,10 @@ object InstrumentationPhase extends TransformationPhase { } class SerialInstrumenter(program: Program, - exprInstOpt : Option[(Map[FunDef, FunDef], SerialInstrumenter, FunDef) => ExprInstrumenter] = None) { + exprInstOpt: Option[(Map[FunDef, FunDef], SerialInstrumenter, FunDef) => ExprInstrumenter] = None) { val debugInstrumentation = false - val exprInstFactory = exprInstOpt.getOrElse((x: Map[FunDef, FunDef], y : SerialInstrumenter, z: FunDef) => new ExprInstrumenter(x, y)(z)) + val exprInstFactory = exprInstOpt.getOrElse((x: Map[FunDef, FunDef], y: SerialInstrumenter, z: FunDef) => new ExprInstrumenter(x, y)(z)) val instToInstrumenter: Map[Instrumentation, Instrumenter] = Map(Time -> new TimeInstrumenter(program, this), Depth -> new DepthInstrumenter(program, this), @@ -121,7 +121,7 @@ class SerialInstrumenter(program: Program, def mapPost(pred: Expr, from: FunDef, to: FunDef) = { pred match { - case Lambda(Seq(ValDef(fromRes, _)), postCond) if (instFuncs.contains(from)) => + case Lambda(Seq(ValDef(fromRes)), postCond) if (instFuncs.contains(from)) => val toResId = FreshIdentifier(fromRes.name, to.returnType, true) val newpost = postMap((e: Expr) => e match { case Variable(`fromRes`) => @@ -251,7 +251,7 @@ class ExprInstrumenter(funMap: Map[FunDef, FunDef], serialInst: SerialInstrument val instexprs = instrumenters.map { m => val calleeInst = if (serialInst.funcInsts(fd).contains(m.inst) && - fd.isUserFunction) { + fd.isUserFunction) { // ignoring fields here List(serialInst.selectInst(fd)(resvar, m.inst)) } else List() @@ -266,6 +266,7 @@ class ExprInstrumenter(funMap: Map[FunDef, FunDef], serialInst: SerialInstrument } Let(resvar.id, newFunInv, Tuple(resvar +: instexprs)) } + } else throw new UnsupportedOperationException("Lazy fields are not handled in instrumentation." + " Consider using the --lazy option and rewrite your program using lazy constructor `$`") @@ -430,6 +431,7 @@ class ExprInstrumenter(funMap: Map[FunDef, FunDef], serialInst: SerialInstrument val instExprs = instrumenters map { m => m.instrumentBody(newe, selectInst(bodyId.toVariable, m.inst)) + } Let(bodyId, transformed, Tuple(TupleSelect(bodyId.toVariable, 1) +: instExprs)) diff --git a/src/main/scala/leon/transformations/StackSpacePhase.scala b/src/main/scala/leon/transformations/StackSpacePhase.scala index 9cca2ef0ab94c7f2c85bcf8781e4c4f44307fd64..d80cb20dc46f2adacc1980d364ebf563eb1fb892 100644 --- a/src/main/scala/leon/transformations/StackSpacePhase.scala +++ b/src/main/scala/leon/transformations/StackSpacePhase.scala @@ -141,7 +141,7 @@ class StackSpaceInstrumenter(p: Program, si: SerialInstrumenter) extends Instrum (1 + valTemp + bodyTemp, Math.max(valStack, bodyStack)) } - case LetDef(fd: FunDef, body: Expr) => { + case LetDef(fds, body: Expr) => { // The function definition does not take up stack space. Goes into the constant pool estimateTemporaries(body) } diff --git a/src/main/scala/leon/utils/DebugSections.scala b/src/main/scala/leon/utils/DebugSections.scala index c18881e47f23fd9c5503320888158cc38f68b10d..b3133d700fed92bb500423201dc4288832b07319 100644 --- a/src/main/scala/leon/utils/DebugSections.scala +++ b/src/main/scala/leon/utils/DebugSections.scala @@ -23,6 +23,8 @@ case object DebugSectionLeon extends DebugSection("leon", 1 << 1 case object DebugSectionXLang extends DebugSection("xlang", 1 << 12) case object DebugSectionTypes extends DebugSection("types", 1 << 13) case object DebugSectionIsabelle extends DebugSection("isabelle", 1 << 14) +case object DebugSectionReport extends DebugSection("report", 1 << 15) +case object DebugSectionGenC extends DebugSection("genc", 1 << 16) object DebugSections { val all = Set[DebugSection]( @@ -40,6 +42,8 @@ object DebugSections { DebugSectionLeon, DebugSectionXLang, DebugSectionTypes, - DebugSectionIsabelle + DebugSectionIsabelle, + DebugSectionReport, + DebugSectionGenC ) } diff --git a/src/main/scala/leon/utils/GraphPrinters.scala b/src/main/scala/leon/utils/GraphPrinters.scala new file mode 100644 index 0000000000000000000000000000000000000000..b86882f2d42d6e7ea1816b249b3d35689b31ab67 --- /dev/null +++ b/src/main/scala/leon/utils/GraphPrinters.scala @@ -0,0 +1,215 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package utils + +import Graphs._ + +object GraphPrinters { + abstract class Printer[N, E <: EdgeLike[N], G <: DiGraphLike[N, E, G]] { + + def asString(g: G): String + + def asFile(g: G, file: String) { + import java.io.{BufferedWriter, FileWriter} + val out = new BufferedWriter(new FileWriter(file)) + out.write(asString(g)) + out.close() + } + } + + case class DotNode(label: String, opts: Map[String, String] = Map()) { + def toOpts(dot: DotHelpers): String = { + s"""[label="${dot.escape(label)}"]""" + } + } + + class DotPrinter[N, E <: EdgeLike[N], G <: DiGraphLike[N, E, G]] extends Printer[N, E, G] { + + val dot = new DotHelpers + + private var nToLabel: N => String = { (n: N) => n.toString } + + private var nToOpts: Seq[N => Seq[(String, String)]] = Nil + + def setNodeLabel(f: N => String): Unit = { + nToLabel = f + } + + def colorize(f: N => Boolean, color: String) = { + nToOpts :+= { (n: N) => + if (f(n)) List(("color", color)) else Nil + } + } + + def highlight(f: N => Boolean) = colorize(f, "red") + + + def drawNode(n: N)(implicit res: StringBuffer): Unit = { + var opts = Map[String, String]() + opts += "label" -> ("\"" + dot.escape(nToLabel(n)) + "\"") + + for (f <- nToOpts) { + opts ++= f(n) + } + + res append (nToS(n) +" "+opts.map{ case (n, v) => s"$n=$v" }.mkString("[", ", ", "]")+";\n") + } + + def nToS(n: N): String = { + dot.uniqueName("n", n) + } + + def eToS(e: EdgeLike[N]): String = { + dot.uniqueName("e", e) + } + + def drawEdge(e: E)(implicit res: StringBuffer): Unit = { + e match { + case le: LabeledEdge[_, N] => + res append dot.box(eToS(e), le.l.toString) + res append dot.arrow(nToS(e._1), eToS(e)) + res append dot.arrow(eToS(e), nToS(e._2)) + case _ => + res append dot.arrow(nToS(e._1), nToS(e._2)) + } + } + + def asString(g: G): String = { + implicit val res = new StringBuffer() + + res append "digraph D {\n" + + g.N.foreach(drawNode) + + g.E.foreach(drawEdge) + + res append "}\n" + + res.toString + } + } + + class DotHelpers { + private var _nextName = 0 + private var _nextColor = 0 + + private def nextName = { + _nextName += 1 + _nextName.toString + } + + private var names = Map[Any, String]() + + def uniqueName(prefix: String, obj: Any) = { + if (!names.contains(obj)) { + names = names + (obj -> (prefix+nextName)) + } + + names(obj) + } + + val bgColors = List("bisque", "khaki", "mistyrose", "lightcyan", "mediumorchid", "aquamarine", "antiquewhite") + + def nextColor = { + _nextColor += 1 + val colornumber: String = if((_nextColor/bgColors.size)%3 == 0) "" else ((_nextColor/bgColors.size)%3)+""; + bgColors(_nextColor%bgColors.size)+colornumber + } + + val colors = List("khaki", "khaki1", "khaki2", "khaki3", + "khaki4", "lavender", "lavenderblush", "lavenderblush1", "lavenderblush2", + "lavenderblush3", "lavenderblush4", "lawngreen", "lemonchiffon", + "lemonchiffon1", "lemonchiffon2", "lemonchiffon3", "lemonchiffon4", + "lightblue", "lightblue1", "lightblue2", "lightblue3", "lightblue4", + "lightcoral", "lightcyan", "lightcyan1", "lightcyan2", "lightcyan3", + "lightcyan4", "lightgoldenrod", "lightgoldenrod1", "lightgoldenrod2", + "lightgoldenrod3", "lightgoldenrod4", "lightgoldenrodyellow", "lightgray", + "lightgrey", "lightpink", "lightpink1", "lightpink2", "lightpink3", + "lightpink4", "lightsalmon", "lightsalmon1", "lightsalmon2", "lightsalmon3", + "lightsalmon4", "lightseagreen", "lightskyblue", "lightskyblue1", + "lightskyblue2", "lightskyblue3", "lightskyblue4", "lightslateblue", + "lightslategray", "lightslategrey", "lightsteelblue", "lightsteelblue1", + "lightsteelblue2", "lightsteelblue3", "lightsteelblue4", "lightyellow", + "lightyellow1", "lightyellow2", "lightyellow3", "lightyellow4", "limegreen", + "linen", "magenta", "magenta1", "magenta2", "magenta3", "magenta4", "maroon", + "maroon1", "maroon2", "maroon3", "maroon4", "mediumaquamarine", "mediumblue", + "mediumorchid", "mediumorchid1", "mediumorchid2", "mediumorchid3", + "mediumorchid4", "mediumpurple", "mediumpurple1", "mediumpurple2", + "mediumpurple3", "mediumpurple4", "mediumseagreen", "mediumslateblue", + "mediumspringgreen", "mediumturquoise", "mediumvioletred", "midnightblue", + "mintcream", "mistyrose", "mistyrose1", "mistyrose2", "mistyrose3", + "mistyrose4", "moccasin", "navajowhite", "navajowhite1", "navajowhite2", + "navajowhite3", "navajowhite4", "navy", "navyblue", "none", "oldlace", + "olivedrab", "olivedrab1", "olivedrab2", "olivedrab3", "olivedrab4", "orange", + "orange1", "orange2", "orange3", "orange4", "orangered", "orangered1", + "orangered2", "orangered3", "orangered4", "orchid", "orchid1", "orchid2", + "orchid3", "orchid4", "palegoldenrod", "palegreen", "palegreen1", "palegreen2", + "palegreen3", "palegreen4", "paleturquoise", "paleturquoise1", + "paleturquoise2", "paleturquoise3", "paleturquoise4", "palevioletred", + "palevioletred1", "palevioletred2", "palevioletred3", "palevioletred4", + "papayawhip", "peachpuff", "peachpuff1", "peachpuff2", "peachpuff3", + "peachpuff4", "peru", "pink", "pink1", "pink2", "pink3", "pink4", "plum", + "plum1", "plum2", "plum3", "plum4", "powderblue", "purple", "purple1", + "purple2", "purple3", "purple4", "red", "red1", "red2", "red3", "red4", + "rosybrown", "rosybrown1", "rosybrown2", "rosybrown3", "rosybrown4", + "royalblue", "royalblue1", "royalblue2", "royalblue3", "royalblue4", + "saddlebrown", "salmon", "salmon1", "salmon2", "salmon3", "salmon4", + "sandybrown", "seagreen", "seagreen1", "seagreen2", "seagreen3", "seagreen4", + "seashell", "seashell1", "seashell2", "seashell3", "seashell4", "sienna", + "sienna1", "sienna2", "sienna3", "sienna4", "skyblue", "skyblue1", "skyblue2", + "skyblue3", "skyblue4", "slateblue", "slateblue1", "slateblue2", "slateblue3", + "slateblue4", "slategray", "slategray1", "slategray2", "slategray3", + "slategray4", "slategrey", "snow", "snow1", "snow2", "snow3", "snow4", + "springgreen", "springgreen1", "springgreen2", "springgreen3", "springgreen4", + "steelblue", "steelblue1", "steelblue2", "steelblue3", "steelblue4", "tan", + "tan1", "tan2", "tan3", "tan4", "thistle", "thistle1", "thistle2", "thistle3", + "thistle4", "tomato", "tomato1", "tomato2", "tomato3", "tomato4", + "transparent", "turquoise", "turquoise1", "turquoise2", "turquoise3", + "turquoise4", "violet", "violetred", "violetred1", "violetred2", "violetred3", + "violetred4", "wheat", "wheat1", "wheat2", "wheat3", "wheat4", "white", + "whitesmoke", "yellow", "yellow1", "yellow2", "yellow3", "yellow4", + "yellowgreen"); + + def randomColor = { + _nextColor += 1 + colors(_nextColor % colors.size) + } + + def escape(s: String) = + s.replaceAll("\\\\n", "__NEWLINE__") + .replaceAll("\\\\", "\\\\\\\\") + .replaceAll("\"", "\\\\\"") + .replaceAll("\\\n", "\\\\n") + .replaceAll("[^<>@a-zA-Z0-9;$.,!# \t=^:_\\\\\"'*+/&()\\[\\]{}\u03B5-]", "?") + .replaceAll("__NEWLINE__", "\\\\n") + + def escapeStrict(s: String) = s.replaceAll("[^a-zA-Z0-9_]", "_") + + def labeledArrow(x: String, label: String, y: String, options: List[String] = Nil) = + arrow(x, y, "label=\""+escape(label)+"\"" :: options) + + def labeledDashedArrow(x: String, label: String, y: String, options: List[String] = Nil) = + arrow(x, y, "label=\""+escape(label)+"\"" :: "style=dashed" :: options) + + def arrow(x: String, y: String, options: List[String] = Nil) = { + " "+x+" -> "+y+options.mkString(" [", " ", "]")+";\n" + } + + def box(id : String, name : String, options: List[String] = Nil) = { + node(id, name, "shape=box" :: "color=lightblue" :: "style=filled" :: options) + } + + def invisNode(id : String, name : String, options: List[String] = Nil) = { + node(id, name, "shape=none" :: options) + } + + def dashedNode(id : String, name : String, options: List[String] = Nil) = { + node(id, name, "style=dashed" :: options) + } + + def node(id: String, name: String, options: List[String] = Nil) = { + id +("label=\""+escape(name)+"\"" :: options).mkString(" [", ", ", "]")+";\n" + } + } +} diff --git a/src/main/scala/leon/utils/Graphs.scala b/src/main/scala/leon/utils/Graphs.scala index b3aeef558790ce9427e376d0e0adba2851813b7b..1910c2aa25715acd325c53c9874bd1b86ec2a6f5 100644 --- a/src/main/scala/leon/utils/Graphs.scala +++ b/src/main/scala/leon/utils/Graphs.scala @@ -4,212 +4,228 @@ package leon package utils object Graphs { - abstract class VertexAbs extends Serializable { - val name: String - - override def toString = name + trait EdgeLike[Node] { + def _1: Node + def _2: Node } - abstract class EdgeAbs[V <: VertexAbs] extends Serializable { - val v1: V - val v2: V - - override def toString = v1 + "->" + v2 - } - - case class SimpleEdge[V <: VertexAbs](v1: V, v2: V) extends EdgeAbs[V] - - abstract class LabeledEdgeAbs[T, V <: VertexAbs] extends EdgeAbs[V] { - val label: T - } - - case class SimpleLabeledEdge[T, V <: VertexAbs](v1: V, label: T, v2: V) extends LabeledEdgeAbs[T, V] - - trait DirectedGraph[V <: VertexAbs, E <: EdgeAbs[V], G <: DirectedGraph[V,E,G]] extends Serializable { - type Vertex = V - type Edge = E - type This = G + case class SimpleEdge[Node](_1: Node, _2: Node) extends EdgeLike[Node] + case class LabeledEdge[Label, Node](_1: Node, l: Label, _2: Node) extends EdgeLike[Node] + trait DiGraphLike[Node, Edge <: EdgeLike[Node], G <: DiGraphLike[Node, Edge, G]] { // The vertices - def V: Set[Vertex] + def N: Set[Node] // The edges def E: Set[Edge] + // Returns the set of incoming edges for a given vertex - def inEdges(v: Vertex) = E.filter(_.v2 == v) + def inEdges(n: Node) = E.filter(_._2 == n) // Returns the set of outgoing edges for a given vertex - def outEdges(v: Vertex) = E.filter(_.v1 == v) + def outEdges(n: Node) = E.filter(_._1 == n) // Returns the set of edges between two vertices - def edgesBetween(from: Vertex, to: Vertex) = { - E.filter(e => e.v1 == from && e.v2 == to) + def edgesBetween(from: Node, to: Node) = { + E.filter(e => e._1 == from && e._2 == to) } - /** - * Basic Graph Operations: - */ - // Adds a new vertex - def + (v: Vertex): This + def + (n: Node): G // Adds new vertices - def ++ (vs: Traversable[Vertex]): This + def ++ (ns: Traversable[Node]): G // Adds a new edge - def + (e: Edge): This + def + (e: Edge): G // Removes a vertex from the graph - def - (from: Vertex): This + def - (from: Node): G // Removes a number of vertices from the graph - def -- (from: Traversable[Vertex]): This + def -- (from: Traversable[Node]): G // Removes an edge from the graph - def - (from: Edge): This - - /** - * Advanced Graph Operations: - */ - - // Merges two graphs - def union(that: This): This - // Return the strongly connected components, sorted topologically - def stronglyConnectedComponents: Seq[Set[Vertex]] - // Topological sorting - def topSort: Option[Seq[Vertex]] - // All nodes leading to v - def transitivePredecessors(v: Vertex): Set[Vertex] - // All nodes reachable from v - def transitiveSuccessors(v: Vertex): Set[Vertex] - // Is v1 reachable from v2 - def isReachable(v1: Vertex, v2: Vertex): Boolean + def - (from: Edge): G } - case class DirectedGraphImp[Vertex <: VertexAbs, Edge <: EdgeAbs[Vertex]]( - vertices: Set[Vertex], - edges: Set[Edge], - ins: Map[VertexAbs, Set[Edge]], - outs: Map[VertexAbs, Set[Edge]] - ) extends DirectedGraph[Vertex, Edge, DirectedGraphImp[Vertex, Edge]] { - - override def equals(o: Any): Boolean = o match { - case other: DirectedGraphImp[_, _] => - this.vertices == other.vertices && - this.edges == other.edges && - (this.ins.keySet ++ other.ins.keySet).forall (k => this.ins(k) == other.ins(k)) && - (this.outs.keySet ++ other.outs.keySet).forall(k => this.outs(k) == other.outs(k)) - - case _ => false + case class DiGraph[Node, Edge <: EdgeLike[Node]](N: Set[Node] = Set[Node](), E: Set[Edge] = Set[Edge]()) extends DiGraphLike[Node, Edge, DiGraph[Node, Edge]] with DiGraphOps[Node, Edge, DiGraph[Node, Edge]]{ + def +(n: Node) = copy(N=N+n) + def ++(ns: Traversable[Node]) = copy(N=N++ns) + def +(e: Edge) = (this+e._1+e._2).copy(E = E + e) + + def -(n: Node) = copy(N = N-n, E = E.filterNot(e => e._1 == n || e._2 == n)) + def --(ns: Traversable[Node]) = { + val toRemove = ns.toSet + copy(N = N--ns, E = E.filterNot(e => toRemove.contains(e._1) || toRemove.contains(e._2))) } - def this (vertices: Set[Vertex], edges: Set[Edge]) = - this(vertices, - edges, - edges.groupBy(_.v2: VertexAbs).withDefaultValue(Set()), - edges.groupBy(_.v1: VertexAbs).withDefaultValue(Set())) - - def this() = this(Set(), Set()) - - val V = vertices - val E = edges - - def + (v: Vertex) = copy( - vertices = vertices+v - ) - - override def inEdges(v: Vertex) = ins(v) - override def outEdges(v: Vertex) = outs(v) - - def ++ (vs: Traversable[Vertex]) = copy( - vertices = vertices++vs - ) - - def -- (vs: Traversable[Vertex]) = copy( - vertices = vertices--vs, - edges = edges -- vs.flatMap(outs) -- vs.flatMap(ins), - ins = ((ins -- vs) map { case (vm, edges) => vm -> (edges -- vs.flatMap(outs)) }).withDefaultValue(Set()) , - outs = ((outs -- vs) map { case (vm, edges) => vm -> (edges -- vs.flatMap(ins)) }).withDefaultValue(Set()) - ) - - def + (e: Edge) = copy( - vertices = vertices + e.v1 + e.v2, - edges = edges + e, - ins = ins + (e.v2 -> (ins(e.v2) + e)), - outs = outs + (e.v1 -> (outs(e.v1) + e)) - ) - - def - (v: Vertex) = copy( - vertices = vertices-v, - edges = edges -- outs(v) -- ins(v), - ins = ((ins - v) map { case (vm, edges) => vm -> (edges -- outs(v)) }).withDefaultValue(Set()) , - outs = ((outs - v) map { case (vm, edges) => vm -> (edges -- ins(v)) }).withDefaultValue(Set()) - ) - - def - (e: Edge) = copy( - vertices = vertices, - edges = edges-e, - ins = ins + (e.v2 -> (ins(e.v2) - e)), - outs = outs + (e.v1 -> (outs(e.v1) - e)) - ) - - def union(that: This): This = copy( - vertices = this.V ++ that.V, - edges = this.E ++ that.E, - ins = ((this.ins.keySet ++ that.ins.keySet) map { k => k -> (this.ins(k) ++ that.ins(k)) }).toMap.withDefaultValue(Set()), - outs = ((this.outs.keySet ++ that.outs.keySet) map { k => k -> (this.outs(k) ++ that.outs(k)) }).toMap.withDefaultValue(Set()) - ) - - def stronglyConnectedComponents: Seq[Set[Vertex]] = ??? - - def topSort = { - val sccs = stronglyConnectedComponents - if (sccs.forall(_.size == 1)) { - Some(sccs.flatten) - } else { - None - } + def -(e: Edge) = copy(E = E-e) + } + + + trait DiGraphOps[Node, Edge <: EdgeLike[Node], G <: DiGraphLike[Node, Edge, G]] { + this: G => + + def sources: Set[Node] = { + N -- E.map(_._2) } - def transitivePredecessors(v: Vertex): Set[Vertex] = { - var seen = Set[Vertex]() - def rec(v: Vertex): Set[Vertex] = { - if (seen(v)) { - Set() - } else { - seen += v - val ins = inEdges(v).map(_.v1) - ins ++ ins.flatMap(rec) + def sinks: Set[Node] = { + N -- E.map(_._1) + } + + def stronglyConnectedComponents: DiGraph[Set[Node], SimpleEdge[Set[Node]]] = { + // Tarjan's algorithm + var index = 0 + var stack = List[Node]() + + var indexes = Map[Node, Int]() + var lowlinks = Map[Node, Int]() + var onStack = Set[Node]() + + var nodesToScc = Map[Node, Set[Node]]() + var res = DiGraph[Set[Node], SimpleEdge[Set[Node]]]() + + def strongConnect(n: Node): Unit = { + indexes += n -> index + lowlinks += n -> index + index += 1 + + stack = n :: stack + onStack += n + + for (m <- succ(n)) { + if (!(indexes contains m)) { + strongConnect(m) + lowlinks += n -> (lowlinks(n) min lowlinks(m)) + } else if (onStack(m)) { + lowlinks += n -> (lowlinks(n) min indexes(m)) + } + } + + if (lowlinks(n) == indexes(n)) { + val i = stack.indexOf(n)+1 + val ns = stack.take(i) + stack = stack.drop(i) + val scc = ns.toSet + onStack --= ns + nodesToScc ++= ns.map(n => n -> scc) + res += scc } } - rec(v) - } - def transitiveSuccessors(v: Vertex): Set[Vertex] = { - var seen = Set[Vertex]() - def rec(v: Vertex): Set[Vertex] = { - if (seen(v)) { - Set() - } else { - seen += v - val outs = outEdges(v).map(_.v2) - outs ++ outs.flatMap(rec) + + for (n <- N if !(indexes contains n)) { + strongConnect(n) + } + + for (e <- E) { + val s1 = nodesToScc(e._1) + val s2 = nodesToScc(e._2) + if (s1 != s2) { + res += SimpleEdge(s1, s2) } } - rec(v) + + res } - def isReachable(v1: Vertex, v2: Vertex): Boolean = { - var seen = Set[Vertex]() - def rec(v: Vertex): Boolean = { - if (seen(v)) { - false - } else { - seen += v - val outs = outEdges(v).map(_.v2) - if (outs(v2)) { - true - } else { - outs.exists(rec) + def topSort: Seq[Node] = { + var res = List[Node]() + + var temp = Set[Node]() + var perm = Set[Node]() + + def visit(n: Node) { + if (temp(n)) { + throw new IllegalArgumentException("Graph is not a DAG") + } else if (!perm(n)) { + temp += n + for (n2 <- succ(n)) { + visit(n2) } + perm += n + temp -= n + res ::= n } } - rec(v1) + + for (n <- N if !temp(n) && !perm(n)) { + visit(n) + } + + res + } + + def depthFirstSearch(from: Node)(f: Node => Unit): Unit = { + var visited = Set[Node]() + + val stack = new collection.mutable.Stack[Node]() + + stack.push(from) + + while(stack.nonEmpty) { + val n = stack.pop + visited += n + f(n) + for (n2 <- succ(n) if !visited(n2)) { + stack.push(n2) + } + } + } + + def fold[T](from: Node)( + follow: Node => Traversable[Node], + map: Node => T, + compose: List[T] => T): T = { + + var visited = Set[Node]() + + def visit(n: Node): T = { + visited += n + + val toFollow = follow(n).filterNot(visited) + visited ++= toFollow + + compose(map(n) :: toFollow.toList.map(visit)) + } + + compose(follow(from).toList.map(visit)) + } + + def succ(from: Node): Set[Node] = { + outEdges(from).map(_._2) + } + + def pred(from: Node): Set[Node] = { + inEdges(from).map(_._1) + } + + def transitiveSucc(from: Node): Set[Node] = { + fold[Set[Node]](from)( + succ, + Set(_), + _.toSet.flatten + ) } - override def toString = "DGraph[V: "+vertices+" | E:"+edges+"]" + def transitivePred(from: Node): Set[Node] = { + fold[Set[Node]](from)( + pred, + Set(_), + _.toSet.flatten + ) + } + + def breadthFirstSearch(from: Node)(f: Node => Unit): Unit = { + var visited = Set[Node]() + + val queue = new collection.mutable.Queue[Node]() + + queue += from + + while(queue.nonEmpty) { + val n = queue.dequeue + visited += n + f(n) + for (n2 <- succ(n) if !visited(n2)) { + queue += n2 + } + } + } } } diff --git a/src/main/scala/leon/utils/IncrementalSet.scala b/src/main/scala/leon/utils/IncrementalSet.scala index 163da296ca85e4681a4281acd24f27d083be96fc..311e55096efe8a98d1ccb310f2dae39752e21a20 100644 --- a/src/main/scala/leon/utils/IncrementalSet.scala +++ b/src/main/scala/leon/utils/IncrementalSet.scala @@ -6,6 +6,7 @@ import scala.collection.mutable.{Stack, Set => MSet} import scala.collection.mutable.Builder import scala.collection.{Iterable, IterableLike, GenSet} +/** A stack of mutable sets with a set-like API and methods to push and pop */ class IncrementalSet[A] extends IncrementalState with Iterable[A] with IterableLike[A, Set[A]] @@ -14,32 +15,41 @@ class IncrementalSet[A] extends IncrementalState private[this] val stack = new Stack[MSet[A]]() override def repr = stack.flatten.toSet + /** Removes all the elements */ override def clear(): Unit = { stack.clear() } + /** Removes all the elements and creates a new set */ def reset(): Unit = { clear() push() } + /** Creates one more set level */ def push(): Unit = { stack.push(MSet()) } + /** Removes one set level */ def pop(): Unit = { stack.pop() } + /** Returns true if the set contains elem */ def apply(elem: A) = repr.contains(elem) + /** Returns true if the set contains elem */ def contains(elem: A) = repr.contains(elem) + /** Returns an iterator over all the elements */ def iterator = stack.flatten.iterator + /** Add an element to the head set */ def += (elem: A) = { stack.head += elem; this } + /** Removes an element from all stacked sets */ def -= (elem: A) = { stack.foreach(_ -= elem); this } override def newBuilder = new scala.collection.mutable.SetBuilder(Set.empty[A]) def result = this - push() + push() // By default, creates a new empty mutable set ready to add elements to it. } diff --git a/src/main/scala/leon/utils/Library.scala b/src/main/scala/leon/utils/Library.scala index ef386d5de05acb264ad9e5db698e0e626cf63ad0..0ddd21078ffe772986bd5487d964d352184cd2e6 100644 --- a/src/main/scala/leon/utils/Library.scala +++ b/src/main/scala/leon/utils/Library.scala @@ -16,11 +16,13 @@ case class Library(pgm: Program) { lazy val Some = lookup("leon.lang.Some").collectFirst { case ccd : CaseClassDef => ccd } lazy val None = lookup("leon.lang.None").collectFirst { case ccd : CaseClassDef => ccd } - lazy val String = lookup("leon.lang.string.String").collectFirst { case ccd : CaseClassDef => ccd } + lazy val StrOps = lookup("leon.lang.StrOps").collectFirst { case md: ModuleDef => md } lazy val Dummy = lookup("leon.lang.Dummy").collectFirst { case ccd : CaseClassDef => ccd } lazy val setToList = lookup("leon.collection.setToList").collectFirst { case fd : FunDef => fd } + + lazy val escape = lookup("leon.lang.StrOps.escape").collectFirst { case fd : FunDef => fd } def lookup(name: String): Seq[Definition] = { pgm.lookupAll(name) diff --git a/src/main/scala/leon/utils/PreprocessingPhase.scala b/src/main/scala/leon/utils/PreprocessingPhase.scala index 7bfeece2f6f35e88c4557db2ecafe4c684a1198d..06f35dc3a47df0a48836e6137eaae96218745d39 100644 --- a/src/main/scala/leon/utils/PreprocessingPhase.scala +++ b/src/main/scala/leon/utils/PreprocessingPhase.scala @@ -9,7 +9,7 @@ import leon.solvers.isabelle.AdaptationPhase import leon.verification.InjectAsserts import leon.xlang.{NoXLangFeaturesChecking, XLangDesugaringPhase} -class PreprocessingPhase(desugarXLang: Boolean = false) extends LeonPhase[Program, Program] { +class PreprocessingPhase(desugarXLang: Boolean = false, genc: Boolean = false) extends LeonPhase[Program, Program] { val name = "preprocessing" val description = "Various preprocessings on Leon programs" @@ -37,21 +37,29 @@ class PreprocessingPhase(desugarXLang: Boolean = false) extends LeonPhase[Progra TypingPhase andThen synthesis.ConversionPhase andThen CheckADTFieldsTypes andThen - InjectAsserts andThen InliningPhase - val pipeX = if(desugarXLang) { + val pipeX = if (!genc && desugarXLang) { + // Do not desugar when generating C code XLangDesugaringPhase andThen debugTrees("Program after xlang desugaring") } else { NoopPhase[Program]() } + def pipeEnd = if (genc) { + // No InjectAsserts, FunctionClosure and AdaptationPhase phases + NoopPhase[Program]() + } else { + InjectAsserts andThen + FunctionClosure andThen + AdaptationPhase + } + val phases = pipeBegin andThen pipeX andThen - FunctionClosure andThen - AdaptationPhase andThen + pipeEnd andThen debugTrees("Program after pre-processing") phases.run(ctx, p) diff --git a/src/main/scala/leon/utils/SCC.scala b/src/main/scala/leon/utils/SCC.scala index d451accf70c817d243a4355e696cd7e9e00e91e3..dbde69fc1e03b3a0e6655806b75c09fec93003bf 100644 --- a/src/main/scala/leon/utils/SCC.scala +++ b/src/main/scala/leon/utils/SCC.scala @@ -3,8 +3,11 @@ package leon package utils -/** This could be defined anywhere, it's just that the - termination checker is the only place where it is used. */ +/** Returns the list of strongly connected sets of vertices. + * A set is said strongly connected is from any vertex we can reach another vertex transitively. + * + * This could be defined anywhere, it's just that the + * termination checker is the only place where it is used. */ object SCC { def scc[T](graph : Map[T,Set[T]]) : List[Set[T]] = { // The first part is a shameless adaptation from Wikipedia diff --git a/src/main/scala/leon/utils/StreamUtils.scala b/src/main/scala/leon/utils/StreamUtils.scala index 2ea08a593725b6f28f4e96be85a00088eb1f9f76..521e98aa6eb35e28e820c4e0aea8ff11709643e1 100644 --- a/src/main/scala/leon/utils/StreamUtils.scala +++ b/src/main/scala/leon/utils/StreamUtils.scala @@ -4,24 +4,56 @@ package leon.utils object StreamUtils { + /** Interleaves a stream of streams. + * {{{ + * If streams = ((1,2,3),(4,5),(6,7,8,9),(10...), + * Order taken 0 1 2 + * Order taken 3 4 5 6 7 + * Order taken 8 9}}} + * interleave(streams) = (1, 2, 4, 3, 5, 6, 7, 10, 8, 9... + **/ def interleave[T](streams: Stream[Stream[T]]): Stream[T] = { def rec(streams: Stream[Stream[T]], diag: Int): Stream[T] = { if(streams.isEmpty) Stream() else { val (take, leave) = streams.splitAt(diag) val (nonEmpty, empty) = take partition (_.nonEmpty) - nonEmpty.map(_.head) ++ rec(nonEmpty.map(_.tail) ++ leave, diag + 1 - empty.size) + nonEmpty.map(_.head) #::: rec(nonEmpty.map(_.tail) ++ leave, diag + 1 - empty.size) } } rec(streams, 1) } + /** Applies the interleaving to a finite sequence of streams. */ def interleave[T](streams : Seq[Stream[T]]) : Stream[T] = { if (streams.isEmpty) Stream() else { val nonEmpty = streams filter (_.nonEmpty) - nonEmpty.toStream.map(_.head) ++ interleave(nonEmpty.map(_.tail)) + nonEmpty.toStream.map(_.head) #::: interleave(nonEmpty.map(_.tail)) } } + private def cantorPair(x: Int, y: Int): Int = { + ((x + y) * (x + y + 1)) / 2 + y + } + + private def reverseCantorPair(z: Int): (Int, Int) = { + val t = Math.floor((-1.0f + Math.sqrt(1.0f + 8.0f * z))/2.0f).toInt; + val x = t * (t + 3) / 2 - z; + val y = z - t * (t + 1) / 2; + (x, y) + } + + /** Combines two streams into one using cantor's unpairing function. + * Ensures that the stream terminates if both streams terminate */ + def cartesianProduct[A, B](sa: Stream[A], sb: Stream[B]): Stream[(A, B)] = { + def combineRec[A, B](sa: Stream[A], sb: Stream[B])(i: Int): Stream[(A, B)] = { + val (x, y) = reverseCantorPair(i) + if(!sa.isDefinedAt(x) && !sb.isDefinedAt(y)) Stream.Empty + else if(sa.isDefinedAt(x) && sb.isDefinedAt(y)) (sa(x), sb(y)) #:: combineRec(sa, sb)(i+1) + else combineRec(sa, sb)(i+1) + } + combineRec(sa, sb)(0) + } + def cartesianProduct[T](streams : Seq[Stream[T]]) : Stream[List[T]] = { val dimensions = streams.size val vectorizedStreams = streams.map(new VectorizedStream(_)) diff --git a/src/main/scala/leon/utils/UnitElimination.scala b/src/main/scala/leon/utils/UnitElimination.scala index 3d486b57fa08a985fb3e9198f07d9c73ef694fff..f4f603393728dd5a7b748c990486533b1cd18db6 100644 --- a/src/main/scala/leon/utils/UnitElimination.scala +++ b/src/main/scala/leon/utils/UnitElimination.scala @@ -93,25 +93,39 @@ object UnitElimination extends TransformationPhase { } } - case LetDef(fd, b) => - if(fd.returnType == UnitType) + case LetDef(fds, b) => + val nonUnits = fds.filter(fd => fd.returnType != UnitType) + if(nonUnits.isEmpty) { removeUnit(b) - else { - val (newFd, rest) = if(fd.params.exists(vd => vd.getType == UnitType)) { - val freshFunDef = fd.duplicate(params = fd.params.filterNot(vd => vd.getType == UnitType)) - fun2FreshFun += (fd -> freshFunDef) - freshFunDef.fullBody = removeUnit(fd.fullBody) - val restRec = removeUnit(b) - fun2FreshFun -= fd - (freshFunDef, restRec) - } else { - fun2FreshFun += (fd -> fd) - fd.body = fd.body.map(b => removeUnit(b)) - val restRec = removeUnit(b) + } else { + val fdtoFreshFd = for(fd <- nonUnits) yield { + val m = if(fd.params.exists(vd => vd.getType == UnitType)) { + val freshFunDef = fd.duplicate(params = fd.params.filterNot(vd => vd.getType == UnitType)) + fd -> freshFunDef + } else { + fd -> fd + } + fun2FreshFun += m + m + } + for((fd, freshFunDef) <- fdtoFreshFd) { + if(fd.params.exists(vd => vd.getType == UnitType)) { + freshFunDef.fullBody = removeUnit(fd.fullBody) + } else { + fd.body = fd.body.map(b => removeUnit(b)) + } + } + val rest = removeUnit(b) + val newFds = for((fd, freshFunDef) <- fdtoFreshFd) yield { fun2FreshFun -= fd - (fd, restRec) + if(fd.params.exists(vd => vd.getType == UnitType)) { + freshFunDef + } else { + fd + } } - LetDef(newFd, rest) + + LetDef(newFds, rest) } case ite@IfExpr(cond, tExpr, eExpr) => diff --git a/src/main/scala/leon/verification/DefaultTactic.scala b/src/main/scala/leon/verification/DefaultTactic.scala index 70c4c57d6f12156700733f08cafba9bfe244c6d3..244b38b30a885d588ef4b38336d9765c60e55417 100644 --- a/src/main/scala/leon/verification/DefaultTactic.scala +++ b/src/main/scala/leon/verification/DefaultTactic.scala @@ -25,13 +25,13 @@ class DefaultTactic(vctx: VerificationContext) extends Tactic(vctx) { val calls = collectWithPC { case c @ FunctionInvocation(tfd, _) if tfd.hasPrecondition => - (c, tfd.precondition.get) + c }(fd.fullBody) calls.map { - case ((fi @ FunctionInvocation(tfd, args), pre), path) => - val pre2 = tfd.withParamSubst(args, pre) - val vc = implies(path, pre2) + case (fi @ FunctionInvocation(tfd, args), path) => + val pre = tfd.withParamSubst(args, tfd.precondition.get) + val vc = implies(path, pre) val fiS = sizeLimit(fi.asString, 40) VC(vc, fd, VCKinds.Info(VCKinds.Precondition, s"call $fiS")).setPos(fi) } @@ -65,17 +65,14 @@ class DefaultTactic(vctx: VerificationContext) extends Tactic(vctx) { VCKinds.Assert } - fd.body.toSeq.flatMap { body => - // We don't collect preconditions here, because these are handled by generatePreconditions - val calls = collectCorrectnessConditions(body, collectFIs = false) + // We don't collect preconditions here, because these are handled by generatePreconditions + val calls = collectCorrectnessConditions(fd.fullBody) - calls.map { - case (e, correctnessCond) => - val vc = implies(fd.precOrTrue, correctnessCond) - - VC(vc, fd, eToVCKind(e)).setPos(e) - } + calls.map { + case (e, correctnessCond) => + VC(correctnessCond, fd, eToVCKind(e)).setPos(e) } } + } diff --git a/src/main/scala/leon/verification/InductionTactic.scala b/src/main/scala/leon/verification/InductionTactic.scala index 65f96a090d4449845921252737bb35a3c3b9a327..dd437c224170c908e9175815354748a4236e4880 100644 --- a/src/main/scala/leon/verification/InductionTactic.scala +++ b/src/main/scala/leon/verification/InductionTactic.scala @@ -21,7 +21,7 @@ class InductionTactic(vctx: VerificationContext) extends DefaultTactic(vctx) { } private def selectorsOfParentType(parentType: ClassType, cct: CaseClassType, expr: Expr): Seq[Expr] = { - val childrenOfSameType = cct.fields.filter(_.getType == parentType) + val childrenOfSameType = (cct.classDef.fields zip cct.fieldsTypes).collect { case (vd, tpe) if tpe == parentType => vd } for (field <- childrenOfSameType) yield { caseClassSelector(cct, expr, field.id) } diff --git a/src/main/scala/leon/verification/InjectAsserts.scala b/src/main/scala/leon/verification/InjectAsserts.scala index 418da12eb62e1fb1ead33bc0317da9ceb9662e28..4e126827bd6cf352692c43e8433857b8894615d4 100644 --- a/src/main/scala/leon/verification/InjectAsserts.scala +++ b/src/main/scala/leon/verification/InjectAsserts.scala @@ -30,8 +30,6 @@ object InjectAsserts extends SimpleLeonPhase[Program, Program] { Assert(indexUpTo(i, ArrayLength(a)), Some("Array index out of range"), i).setPos(i), v ).setPos(e)) - case e @ ArrayUpdate(a, i, _) => - Some(Assert(indexUpTo(i, ArrayLength(a)), Some("Array index out of range"), e).setPos(e)) case e @ MapApply(m,k) => Some(Assert(MapIsDefinedAt(m, k), Some("Map undefined at this index"), e).setPos(e)) @@ -42,34 +40,34 @@ object InjectAsserts extends SimpleLeonPhase[Program, Program] { ).setPos(e)) case e @ Division(_, d) => - Some(Assert(Not(Equals(d, InfiniteIntegerLiteral(0))), + Some(assertion(not(equality(d, InfiniteIntegerLiteral(0))), Some("Division by zero"), e ).setPos(e)) case e @ Remainder(_, d) => - Some(Assert(Not(Equals(d, InfiniteIntegerLiteral(0))), + Some(assertion(not(equality(d, InfiniteIntegerLiteral(0))), Some("Remainder by zero"), e ).setPos(e)) case e @ Modulo(_, d) => - Some(Assert(Not(Equals(d, InfiniteIntegerLiteral(0))), + Some(assertion(not(equality(d, InfiniteIntegerLiteral(0))), Some("Modulo by zero"), e ).setPos(e)) case e @ BVDivision(_, d) => - Some(Assert(Not(Equals(d, IntLiteral(0))), + Some(assertion(not(equality(d, IntLiteral(0))), Some("Division by zero"), e ).setPos(e)) case e @ BVRemainder(_, d) => - Some(Assert(Not(Equals(d, IntLiteral(0))), + Some(assertion(not(equality(d, IntLiteral(0))), Some("Remainder by zero"), e ).setPos(e)) case e @ RealDivision(_, d) => - Some(Assert(Not(Equals(d, FractionalLiteral(0, 1))), + Some(assertion(not(equality(d, FractionalLiteral(0, 1))), Some("Division by zero"), e ).setPos(e)) diff --git a/src/main/scala/leon/verification/VerificationCondition.scala b/src/main/scala/leon/verification/VerificationCondition.scala index 873f79c03fa046ce3a68a43b654ccfef30cda502..c19f75220ba31e4fd3142b36d09f0fbaedde2f5d 100644 --- a/src/main/scala/leon/verification/VerificationCondition.scala +++ b/src/main/scala/leon/verification/VerificationCondition.scala @@ -4,10 +4,13 @@ package leon.verification import leon.purescala.Expressions._ 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 /** This is just to hold some history information. */ case class VC(condition: Expr, fd: FunDef, kind: VCKind) extends Positioned { @@ -61,7 +64,8 @@ case class VCResult(status: VCStatus, solvedWith: Option[Solver], timeMs: Option // large arrays faithfully in ScalaPrinter is hard, while PrettyPrinter // is free to simplify val strings = cex.toSeq.sortBy(_._1.name).map { - case (id, v) => (id.asString(context), PrettyPrinter(v)) + case (id, v) => + (id.asString(context), SelfPrettyPrinter.print(v, PrettyPrinter(v))(vctx.context, vctx.program)) } if (strings.nonEmpty) { @@ -93,4 +97,5 @@ object VCStatus { case object Unknown extends VCStatus("unknown") case object Timeout extends VCStatus("timeout") case object Cancelled extends VCStatus("cancelled") + case object Crashed extends VCStatus("crashed") } diff --git a/src/main/scala/leon/verification/VerificationReport.scala b/src/main/scala/leon/verification/VerificationReport.scala index 18ae32c8952eea57c46dfeb5698bd2a62430646d..d695f4ca0877382eeb83838a3cf08267ee935cb6 100644 --- a/src/main/scala/leon/verification/VerificationReport.scala +++ b/src/main/scala/leon/verification/VerificationReport.scala @@ -3,10 +3,28 @@ package leon package verification +import evaluators.StringTracingEvaluator +import utils.DebugSectionSynthesis +import utils.DebugSectionVerification +import leon.purescala import purescala.Definitions.Program +import purescala.Expressions._ +import purescala.Types.StringType +import purescala.TypeOps +import purescala.Quantification._ +import purescala.Constructors._ +import purescala.ExprOps._ +import purescala.Expressions.{Pattern, Expr} +import purescala.Extractors._ +import purescala.TypeOps._ +import purescala.Types._ +import purescala.Common._ +import purescala.Expressions._ +import purescala.Definitions._ +import purescala.SelfPrettyPrinter +import leon.solvers.{ HenkinModel, Model, SolverFactory } case class VerificationReport(program: Program, results: Map[VC, Option[VCResult]]) { - val vrs: Seq[(VC, VCResult)] = results.toSeq.sortBy { case (vc, _) => (vc.fd.id.name, vc.kind.toString) }.map { case (vc, or) => (vc, or.getOrElse(VCResult.unknown)) } diff --git a/src/main/scala/leon/xlang/EpsilonElimination.scala b/src/main/scala/leon/xlang/EpsilonElimination.scala index a09eeb63754895abb24fdd3506ad15cb48aba3b6..51b23be1b9a6e4fe62bc6ed0b1cad83d0c83ffea 100644 --- a/src/main/scala/leon/xlang/EpsilonElimination.scala +++ b/src/main/scala/leon/xlang/EpsilonElimination.scala @@ -30,7 +30,7 @@ object EpsilonElimination extends UnitPhase[Program] { }.toMap ++ Seq((epsilonVar, Variable(resId))) val postcondition = replace(eMap, pred) newFunDef.postcondition = Some(Lambda(Seq(ValDef(resId)), postcondition)) - LetDef(newFunDef, FunctionInvocation(newFunDef.typed, bSeq map Variable)) + LetDef(Seq(newFunDef), FunctionInvocation(newFunDef.typed, bSeq map Variable)) case (other, _) => other }, fd.paramIds.toSet)(fd.fullBody) diff --git a/src/main/scala/leon/xlang/ImperativeCodeElimination.scala b/src/main/scala/leon/xlang/ImperativeCodeElimination.scala index e9802d500208b96b5af448391d69c6660a05f843..45bb36770cddca417ba51582cd7824fc09152199 100644 --- a/src/main/scala/leon/xlang/ImperativeCodeElimination.scala +++ b/src/main/scala/leon/xlang/ImperativeCodeElimination.scala @@ -129,68 +129,22 @@ object ImperativeCodeElimination extends UnitPhase[Program] { (resId.toVariable, scope, scrutFun ++ modifiedVars.zip(freshIds).toMap) case wh@While(cond, body) => - //TODO: rewrite by re-using the nested function transformation code - val (condRes, condScope, condFun) = toFunction(cond) - val (_, bodyScope, bodyFun) = toFunction(body) - val condBodyFun = condFun ++ bodyFun - - val modifiedVars: Seq[Identifier] = condBodyFun.keys.toSet.intersect(varsInScope).toSeq - - if(modifiedVars.isEmpty) - (UnitLiteral(), (b: Expr) => b, Map()) - else { - val whileFunVars = modifiedVars.map(id => FreshIdentifier(id.name, id.getType)) - val modifiedVars2WhileFunVars = modifiedVars.zip(whileFunVars).toMap - val whileFunValDefs = whileFunVars.map(ValDef(_)) - val whileFunReturnType = tupleTypeWrap(whileFunVars.map(_.getType)) - val whileFunDef = new FunDef(parent.id.freshen, Nil, whileFunValDefs, whileFunReturnType).setPos(wh) - whileFunDef.addFlag(IsLoop(parent)) - - val whileFunCond = condScope(condRes) - val whileFunRecursiveCall = replaceNames(condFun, - bodyScope(FunctionInvocation(whileFunDef.typed, modifiedVars.map(id => condBodyFun(id).toVariable)).setPos(wh))) - val whileFunBaseCase = - tupleWrap(modifiedVars.map(id => condFun.getOrElse(id, modifiedVars2WhileFunVars(id)).toVariable)) - val whileFunBody = replaceNames(modifiedVars2WhileFunVars, - condScope(IfExpr(whileFunCond, whileFunRecursiveCall, whileFunBaseCase))) - whileFunDef.body = Some(whileFunBody) - - val resVar = Variable(FreshIdentifier("res", whileFunReturnType)) - val whileFunVars2ResultVars: Map[Expr, Expr] = - whileFunVars.zipWithIndex.map{ case (v, i) => - (v.toVariable, tupleSelect(resVar, i+1, whileFunVars.size)) - }.toMap - val modifiedVars2ResultVars: Map[Expr, Expr] = modifiedVars.map(id => - (id.toVariable, whileFunVars2ResultVars(modifiedVars2WhileFunVars(id).toVariable))).toMap - - //the mapping of the trivial post condition variables depends on whether the condition has had some side effect - val trivialPostcondition: Option[Expr] = Some(Not(replace( - modifiedVars.map(id => (condFun.getOrElse(id, id).toVariable, modifiedVars2ResultVars(id.toVariable))).toMap, - whileFunCond))) - val invariantPrecondition: Option[Expr] = wh.invariant.map(expr => replaceNames(modifiedVars2WhileFunVars, expr)) - val invariantPostcondition: Option[Expr] = wh.invariant.map(expr => replace(modifiedVars2ResultVars, expr)) - whileFunDef.precondition = invariantPrecondition - whileFunDef.postcondition = trivialPostcondition.map( expr => - Lambda( - Seq(ValDef(resVar.id)), - and(expr, invariantPostcondition.getOrElse(BooleanLiteral(true))).setPos(wh) - ).setPos(wh) - ) - - val finalVars = modifiedVars.map(_.freshen) - val finalScope = (body: Expr) => { - val tupleId = FreshIdentifier("t", whileFunReturnType) - LetDef(whileFunDef, Let( - tupleId, - FunctionInvocation(whileFunDef.typed, modifiedVars.map(_.toVariable)).setPos(wh), - finalVars.zipWithIndex.foldLeft(body) { (b, id) => - Let(id._1, tupleSelect(tupleId.toVariable, id._2 + 1, finalVars.size), b) - } - )) - } + val whileFunDef = new FunDef(parent.id.freshen, Nil, Nil, UnitType).setPos(wh) + whileFunDef.addFlag(IsLoop(parent)) + whileFunDef.body = Some( + IfExpr(cond, + Block(Seq(body), FunctionInvocation(whileFunDef.typed, Seq()).setPos(wh)), + UnitLiteral())) + whileFunDef.precondition = wh.invariant + whileFunDef.postcondition = Some( + Lambda( + Seq(ValDef(FreshIdentifier("bodyRes", UnitType))), + and(Not(getFunctionalResult(cond)), wh.invariant.getOrElse(BooleanLiteral(true))).setPos(wh) + ).setPos(wh) + ) - (UnitLiteral(), finalScope, modifiedVars.zip(finalVars).toMap) - } + val newExpr = LetDef(Seq(whileFunDef), FunctionInvocation(whileFunDef.typed, Seq()).setPos(wh)).setPos(wh) + toFunction(newExpr) case Block(Seq(), expr) => toFunction(expr) @@ -198,14 +152,13 @@ object ImperativeCodeElimination extends UnitPhase[Program] { case Block(exprs, expr) => val (scope, fun) = exprs.foldRight((body: Expr) => body, Map[Identifier, Identifier]())((e, acc) => { val (accScope, accFun) = acc - val (_, rScope, rFun) = toFunction(e) + val (rVal, rScope, rFun) = toFunction(e) val scope = (body: Expr) => { - val withoutPrec = rScope(replaceNames(rFun, accScope(body))) - e match { + rVal match { case FunctionInvocation(tfd, args) if tfd.hasPrecondition => - Assert(tfd.withParamSubst(args, tfd.precondition.get), Some("Precondition failed"), withoutPrec) + rScope(replaceNames(rFun, Let(FreshIdentifier("tmp", tfd.returnType), rVal, accScope(body)))) case _ => - withoutPrec + rScope(replaceNames(rFun, accScope(body))) } } @@ -257,86 +210,98 @@ object ImperativeCodeElimination extends UnitPhase[Program] { (TupleSelect(tmpTuple.toVariable, 1), scope, newMap) } - case None => + case None => (FunctionInvocation(tfd, recArgs).copiedFrom(fi), argScope, argFun) } - case LetDef(fd, b) => - - def fdWithoutSideEffects = { - fd.body.foreach { bd => - val (fdRes, fdScope, _) = toFunction(bd) - fd.body = Some(fdScope(fdRes)) - } - val (bodyRes, bodyScope, bodyFun) = toFunction(b) - (bodyRes, (b2: Expr) => LetDef(fd, bodyScope(b2)).setPos(fd).copiedFrom(expr), bodyFun) - } - - fd.body match { - case Some(bd) => { - - val modifiedVars: List[Identifier] = - collect[Identifier]({ - case Assignment(v, _) => Set(v) - case _ => Set() - })(bd).intersect(state.varsInScope).toList + case LetDef(fds, b) => - if(modifiedVars.isEmpty) fdWithoutSideEffects else { + if(fds.size > 1) { + //TODO: no support for true mutually recursion + toFunction(LetDef(Seq(fds.head), LetDef(fds.tail, b))) + } else { - val freshNames: List[Identifier] = modifiedVars.map(id => id.freshen) + val fd = fds.head - val newParams: Seq[ValDef] = fd.params ++ freshNames.map(n => ValDef(n)) - val freshVarDecls: List[Identifier] = freshNames.map(id => id.freshen) - - val rewritingMap: Map[Identifier, Identifier] = - modifiedVars.zip(freshVarDecls).toMap - val freshBody = - preMap({ - case Assignment(v, e) => rewritingMap.get(v).map(nv => Assignment(nv, e)) - case Variable(id) => rewritingMap.get(id).map(nid => Variable(nid)) - case _ => None - })(bd) - val wrappedBody = freshNames.zip(freshVarDecls).foldLeft(freshBody)((body, p) => { - LetVar(p._2, Variable(p._1), body) - }) - - val newReturnType = TupleType(fd.returnType :: modifiedVars.map(_.getType)) - - val newFd = new FunDef(fd.id.freshen, fd.tparams, newParams, newReturnType).setPos(fd) + def fdWithoutSideEffects = { + fd.body.foreach { bd => + val (fdRes, fdScope, _) = toFunction(bd) + fd.body = Some(fdScope(fdRes)) + } + val (bodyRes, bodyScope, bodyFun) = toFunction(b) + (bodyRes, (b2: Expr) => LetDef(Seq(fd), bodyScope(b2)).setPos(fd).copiedFrom(expr), bodyFun) + } - val (fdRes, fdScope, fdFun) = - toFunction(wrappedBody)( - State(state.parent, Set(), - state.funDefsMapping + (fd -> ((newFd, freshVarDecls)))) - ) - val newRes = Tuple(fdRes :: freshVarDecls.map(vd => fdFun(vd).toVariable)) - val newBody = fdScope(newRes) - - newFd.body = Some(newBody) - newFd.precondition = fd.precondition.map(prec => { - replace(modifiedVars.zip(freshNames).map(p => (p._1.toVariable, p._2.toVariable)).toMap, prec) - }) - newFd.postcondition = fd.postcondition.map(post => { - val Lambda(Seq(res), postBody) = post - val newRes = ValDef(FreshIdentifier(res.id.name, newFd.returnType)) - - val newBody = - replace( - modifiedVars.zipWithIndex.map{ case (v, i) => - (v.toVariable, TupleSelect(newRes.toVariable, i+2)): (Expr, Expr)}.toMap ++ - modifiedVars.zip(freshNames).map{ case (ov, nv) => - (Old(ov), nv.toVariable)}.toMap + - (res.toVariable -> TupleSelect(newRes.toVariable, 1)), - postBody) - Lambda(Seq(newRes), newBody).setPos(post) - }) - - val (bodyRes, bodyScope, bodyFun) = toFunction(b)(state.withFunDef(fd, newFd, modifiedVars)) - (bodyRes, (b2: Expr) => LetDef(newFd, bodyScope(b2)).copiedFrom(expr), bodyFun) + fd.body match { + case Some(bd) => { + + val modifiedVars: List[Identifier] = + collect[Identifier]({ + case Assignment(v, _) => Set(v) + case FunctionInvocation(tfd, _) => state.funDefsMapping.get(tfd.fd).map(p => p._2.toSet).getOrElse(Set()) + case _ => Set() + })(bd).intersect(state.varsInScope).toList + + if(modifiedVars.isEmpty) fdWithoutSideEffects else { + + val freshNames: List[Identifier] = modifiedVars.map(id => id.freshen) + + val newParams: Seq[ValDef] = fd.params ++ freshNames.map(n => ValDef(n)) + val freshVarDecls: List[Identifier] = freshNames.map(id => id.freshen) + + val rewritingMap: Map[Identifier, Identifier] = + modifiedVars.zip(freshVarDecls).toMap + val freshBody = + preMap({ + case Assignment(v, e) => rewritingMap.get(v).map(nv => Assignment(nv, e)) + case Variable(id) => rewritingMap.get(id).map(nid => Variable(nid)) + case _ => None + })(bd) + val wrappedBody = freshNames.zip(freshVarDecls).foldLeft(freshBody)((body, p) => { + LetVar(p._2, Variable(p._1), body) + }) + + val newReturnType = TupleType(fd.returnType :: modifiedVars.map(_.getType)) + + val newFd = new FunDef(fd.id.freshen, fd.tparams, newParams, newReturnType).setPos(fd) + newFd.addFlags(fd.flags) + + val (fdRes, fdScope, fdFun) = + toFunction(wrappedBody)( + State(state.parent, + Set(), + state.funDefsMapping.map{case (fd, (nfd, mvs)) => (fd, (nfd, mvs.map(v => rewritingMap.getOrElse(v, v))))} + + (fd -> ((newFd, freshVarDecls)))) + ) + val newRes = Tuple(fdRes :: freshVarDecls.map(vd => fdFun(vd).toVariable)) + val newBody = fdScope(newRes) + + newFd.body = Some(newBody) + newFd.precondition = fd.precondition.map(prec => { + replace(modifiedVars.zip(freshNames).map(p => (p._1.toVariable, p._2.toVariable)).toMap, prec) + }) + newFd.postcondition = fd.postcondition.map(post => { + val Lambda(Seq(res), postBody) = post + val newRes = ValDef(FreshIdentifier(res.id.name, newFd.returnType)) + + val newBody = + replace( + modifiedVars.zipWithIndex.map{ case (v, i) => + (v.toVariable, TupleSelect(newRes.toVariable, i+2)): (Expr, Expr)}.toMap ++ + modifiedVars.zip(freshNames).map{ case (ov, nv) => + (Old(ov), nv.toVariable)}.toMap + + (res.toVariable -> TupleSelect(newRes.toVariable, 1)), + postBody) + Lambda(Seq(newRes), newBody).setPos(post) + }) + + val (bodyRes, bodyScope, bodyFun) = toFunction(b)(state.withFunDef(fd, newFd, modifiedVars)) + (bodyRes, (b2: Expr) => LetDef(Seq(newFd), bodyScope(b2)).copiedFrom(expr), bodyFun) + } } + case None => fdWithoutSideEffects } - case None => fdWithoutSideEffects } case c @ Choose(b) => @@ -351,6 +316,15 @@ object ImperativeCodeElimination extends UnitPhase[Program] { val ifExpr = args.reduceRight((el, acc) => IfExpr(el, BooleanLiteral(true), acc)) toFunction(ifExpr) + //TODO: this should be handled properly by the Operator case, but there seems to be a subtle bug in the way Let's are lifted + // which leads to Assert refering to the wrong value of a var in some cases. + case a@Assert(cond, msg, body) => + val (condVal, condScope, condFun) = toFunction(cond) + val (bodyRes, bodyScope, bodyFun) = toFunction(body) + val scope = (body: Expr) => condScope(Assert(condVal, msg, replaceNames(condFun, bodyScope(body))).copiedFrom(a)) + (bodyRes, scope, condFun ++ bodyFun) + + case n @ Operator(args, recons) => val (recArgs, scope, fun) = args.foldRight((Seq[Expr](), (body: Expr) => body, Map[Identifier, Identifier]()))((arg, acc) => { val (accArgs, accScope, accFun) = acc @@ -358,6 +332,7 @@ object ImperativeCodeElimination extends UnitPhase[Program] { val newScope = (body: Expr) => argScope(replaceNames(argFun, accScope(body))) (argVal +: accArgs, newScope, argFun ++ accFun) }) + (recons(recArgs).copiedFrom(n), scope, fun) case _ => @@ -367,4 +342,13 @@ object ImperativeCodeElimination extends UnitPhase[Program] { def replaceNames(fun: Map[Identifier, Identifier], expr: Expr) = replaceFromIDs(fun mapValues Variable, expr) + + /* Extract functional result value. Useful to remove side effect from conditions when moving it to post-condition */ + private def getFunctionalResult(expr: Expr): Expr = { + preMap({ + case Block(_, res) => Some(res) + case _ => None + })(expr) + } + } diff --git a/src/sphinx/genc.rst b/src/sphinx/genc.rst new file mode 100644 index 0000000000000000000000000000000000000000..faac1f142f843e4014dbac4603784c4fa37ebe16 --- /dev/null +++ b/src/sphinx/genc.rst @@ -0,0 +1,122 @@ +.. _genc: + +Safe C Code +=========== + +Leon can generate from Scala code an equivalent and safe C99 code. Using the verification, repair and +synthesis features of Leon this conversion can be made safely. Additionally, the produced code can be +compiled with any standard-compliant C99 compiler to target the desired hardware architecture +without extra dependencies. + +To convert a Scala program, one can use the ``--genc`` and ``--o=<output.c>`` command line options +of Leon. + +.. NOTE:: + Currently the memory model is limited to stack-allocated memory. Hence, no dynamic allocation + is done using ``malloc`` function family. + + +Supported Features +------------------ + +The supported subset of Scala includes part of the core languages features, as well as some +extensions from :ref:`XLang <xlang>`, while ensuring the same expression execution order in both +languages. + +Currently all type and function definitions need to be included in one top level object. + + +Types +***** + +The following raw types and their corresponding literals are supported: + +.. list-table:: + :header-rows: 1 + + * - Scala + - C99 + * - ``Unit`` + - ``void`` + * - ``Boolean`` + - ``bool`` + * - ``Int`` (32-bit Integer) + - ``int32_t`` + +Tuples +^^^^^^ + +Using ``TupleN[T1, ..., TN]`` results in the creation of a C structure with the same +fields and types for every combination of any supported type ``T1, ..., TN``. The name of the +generated structure will be unique and reflect the sequence of types. + + +Arrays +^^^^^^ + +``Array[T]`` are implemented using regular C array when the array size is known at compile time, or +using Variable Length Array (VLA) when the size is only available at runtime. Both types of array +use the same unique structure type to keep track of the length of the array and its allocated +memory. + +.. NOTE:: + + Arrays live on the stack and therefore cannot be returned by functions. This limitation is + extended to other types having an array as field. + + +Arrays can be created using the companion object, e.g. ``Array(1, 2, 3)``, or using the +``Array.fill`` method, e.g. ``Array.fill(size)(value)``. + + +Case Classes +^^^^^^^^^^^^ + +The support for classes is restricted to non-recursive case classes for which fields are immutable. +Instances of such data-types live on the stack. + + +Functions +********* + +Functions and nested functions are supported, with access to the variables in their respective +scopes. However, higher order functions are as of this moment not supported. + +Since strings of characters are currently not available, to generate an executable program, one has +to define a main function without any argument that returns an integer: ``def main: Int = ...``. + +Both ``val`` and ``var`` are supported with the limitations imposed by the :ref:`XLang <xlang>` +extensions. + + +Constructs +********** + +The idiomatic ``if`` statements such as ``val b = if (x >= 0) true else false`` are converted into +a sequence of equivalent statements. + +Imperative ``while`` loops are also supported. + +Assertions, invariant, pre- and post-conditions are not translated into C99 and are simply ignored. + + +Operators +********* + +The following operators are supported: + +.. list-table:: + :header-rows: 1 + + * - Category + - Operators + * - Boolean operators + - ``&&``, ``||``, ``!``, ``!=``, ``==`` + * - Comparision operators over integers + - ``<``, ``<=``, ``==``, ``!=``, ``>=``, ``>`` + * - Arithmetic operators over integers + - ``+``, ``-`` (unary & binary), ``*``, ``/``, ``%`` + * - Bitwise operators over integers + - ``&``, ``|``, ``^``, ``~``, ``<<``, ``>>`` + + diff --git a/src/sphinx/index.rst b/src/sphinx/index.rst index a20401170d34be2ef293878e72b678aaac462598..cf46d2c300d3821a4cb987d657bded60597ce534 100644 --- a/src/sphinx/index.rst +++ b/src/sphinx/index.rst @@ -25,6 +25,7 @@ Contents: limitations synthesis repair + genc options faq references diff --git a/src/sphinx/installation.rst b/src/sphinx/installation.rst index 1af4a8eaffb032fce8d512e25556a9ffaae3fb72..b4deaf194eb4f84eac0b14dabb42221cfb999056 100644 --- a/src/sphinx/installation.rst +++ b/src/sphinx/installation.rst @@ -86,7 +86,7 @@ x64, do the following: 2. Copy ``unmanaged/common/vanuatoo*.jar`` to ``unmanaged/64/`` You may be able to obtain additional tips on getting Leon to work on Windows -from Mikael Mayer, http://people.epfl.ch/mikael.mayer +from [Mikael Mayer](http://people.epfl.ch/mikael.mayer) or on [his dedicated web page](http://lara.epfl.ch/~mayer/leon/), .. _smt-solvers: diff --git a/src/sphinx/options.rst b/src/sphinx/options.rst index 1c929cd29cdc5281378cd322389b867902f0ff2b..a5be01e35269e12864cd6716947727209497b337 100644 --- a/src/sphinx/options.rst +++ b/src/sphinx/options.rst @@ -20,41 +20,46 @@ Choosing which Leon feature to use The first group of options determine which feature of Leon will be used. These options are mutually exclusive (except when noted). By default, ``--verify`` is chosen. -* ``--eval`` - +* ``--eval`` + Evaluates parameterless functions and value definitions. - + * ``--verify`` - + Proves or disproves function contracts, as explained in the :ref:`verification` section. * ``--repair`` - + Runs program :ref:`repair <repair>`. - + * ``--synthesis`` - + Partially synthesizes ``choose()`` constructs (see :ref:`synthesis` section). * ``--termination`` - + Runs termination analysis. Can be used along ``--verify``. -* ``--inferInv`` +* ``--inferInv`` + + Infer invariants from the (instrumented) code (using Orb). - Infer invariants from the (instrumented) code (using Orb) +* ``--instrument`` -* ``--instrument`` + Instrument the code for inferring time/depth/stack bounds (using Orb). - Instrument the code for inferring time/depth/stack bounds (using Orb) +* ``--genc`` + + Translate a Scala program into C99 equivalent code (see :ref:`genc` section); requires + ``--xlang``. * ``--noop`` - + Runs the program through the extraction and preprocessing phases, then outputs it in the specified directory. Used mostly for debugging purposes. * ``--help`` - + Prints a helpful message, then exits. @@ -65,44 +70,46 @@ Additional top-level options These options are available to all Leon components: * ``--debug=d1,d2,...`` - + Enables printing detailed messages for the components d1,d2,... . - Available components are: + Available components are: * ``datagen`` (Data generators) - + * ``eval`` (Evaluators) - + + * ``genc`` (C code generation) + * ``isabelle`` (:ref:`The Isabelle-based solver <isabelle>`) * ``leon`` (The top-level component) - + * ``options`` (Options parsed by Leon) - + * ``positions`` (When printing, attach positions to trees) * ``repair`` (Program repair) - + * ``solver`` (SMT solvers and their wrappers) - + * ``synthesis`` (Program synthesis) - + * ``termination`` (Termination analysis) - + * ``timers`` (Timers, timer pools) - + * ``trees`` (Manipulation of trees) - + * ``types`` (When printing, attach types to expressions) * ``verification`` (Verification) - + * ``xlang`` (Transformation of XLang into Pure Scala programs) * ``--functions=f1,f2,...`` - - Only consider functions f1, f2, ... . This applies to all functionalities + + Only consider functions f1, f2, ... . This applies to all functionalities where Leon manipulates the input in a per-function basis. Leon will match against suffixes of qualified names. For instance: @@ -111,22 +118,22 @@ These options are available to all Leon components: This option supports ``_`` as wildcard: ``--functions=List._`` will match all ``List`` methods. -* ``--solvers=s1,s2,...`` - - Use solvers s1, s2,... . If more than one solver is chosen, all chosen +* ``--solvers=s1,s2,...`` + + Use solvers s1, s2,... . If more than one solver is chosen, all chosen solvers will be used in parallel, and the best result will be presented. By default, the ``fairz3`` solver is picked. - - Some solvers are specialized in proving verification conditions - and will have hard time finding a counterexample in case of an invalid + + Some solvers are specialized in proving verification conditions + and will have hard time finding a counterexample in case of an invalid verification condition, whereas some are specialized in finding counterexamples, and some provide a compromise between the two. Also, some solvers do not as of now support higher-order functions. Available solvers include: - - * ``enum`` - + + * ``enum`` + Uses enumeration-based techniques to discover counterexamples. This solver does not actually invoke an SMT solver, and operates entirely on the level of Leon trees. @@ -134,50 +141,50 @@ These options are available to all Leon components: * ``fairz3`` Native Z3 with z3-templates for unfolding recursive functions (default). - + * ``smt-cvc4`` - - CVC4 through SMT-LIB. An algorithm within Leon takes up the unfolding - of recursive functions, handling of lambdas etc. To use this or any + + CVC4 through SMT-LIB. An algorithm within Leon takes up the unfolding + of recursive functions, handling of lambdas etc. To use this or any of the following CVC4-based solvers, you need to have the ``cvc4`` executable in your system path (the latest unstable version is recommended). - + * ``smt-cvc4-cex`` - + CVC4 through SMT-LIB, in-solver finite-model-finding, for counter-examples only. Recursive functions are not unrolled, but encoded through the ``define-funs-rec`` construct available in the new SMTLIB-2.5 standard. Currently, this solver does not handle higher-order functions. - + * ``smt-cvc4-proof`` - - CVC4 through SMT-LIB, for proofs only. Functions are encoded as in + + CVC4 through SMT-LIB, for proofs only. Functions are encoded as in ``smt-cvc4-cex``. Currently, this solver does not handle higher-order functions. - + * ``smt-z3`` - - Z3 through SMT-LIB. To use this or the next solver, you need to + + Z3 through SMT-LIB. To use this or the next solver, you need to have the ``z3`` executable in your program path (the latest stable version is recommended). Inductive reasoning happens on the Leon side (similarly to ``smt-cvc4``). - + * ``smt-z3-q`` - - Z3 through SMT-LIB, but (recursive) functions are not unrolled and are + + Z3 through SMT-LIB, but (recursive) functions are not unrolled and are instead encoded with universal quantification. For example, ``def foo(x:A) = e`` would be encoded by asserting - + .. math:: - + \forall (x:A). foo(x) = e even if ``e`` contains an invocation to ``foo``. Currently, this solver does not handle higher-order functions. - + * ``unrollz3`` - + Native Z3, but inductive reasoning happens within Leon (similarly to ``smt-z3``). * ``ground`` @@ -187,21 +194,23 @@ These options are available to all Leon components: * ``isabelle`` Solve verification conditions via Isabelle. - + * ``--strict`` - Terminate Leon after each phase if a non-fatal error is encountered + Terminate Leon after each phase if a non-fatal error is encountered (such as a failed verification condition). By default, this option is activated. * ``--timeout=t`` Set a timeout for each attempt to prove one verification condition/ repair one function (in sec.) - + * ``--xlang`` - + Support for additional language constructs described in :ref:`xlang`. - These constructs are desugared into :ref:`purescala` before other operations. + These constructs are desugared into :ref:`purescala` before other operations, + except for the ``--genc`` option which uses the original constructs to generate + :ref:`genc`. Additional Options (by component) --------------------------------- @@ -222,10 +231,12 @@ File Output *********** * ``--o=dir`` - + Output files to the directory ``dir`` (default: leon.out). Used when ``--noop`` is selected. + When used with ``--genc`` this option designates the output *file*. + Code Extraction *************** @@ -245,21 +256,21 @@ Synthesis Shrink non-deterministic programs when tests pruning works well. * ``--cegis:vanuatoo`` - + Generate inputs using new korat-style generator. - + * ``--costmodel=cm`` - + Use a specific cost model for this search. Available: ``Naive``, ``WeightedBranches`` * ``--derivtrees`` - + Generate a derivation tree for every synthesized function. The trees will be output in ``*.dot`` files. * ``--manual=cmd`` - + Override Leon's automated search through the space of programs during synthesis. Instead, the user can navigate the program space manually by choosing which deductive synthesis rules is instantiated each time. @@ -277,26 +288,26 @@ Fair-z3 Solver Double-check counter-examples with evaluator. * ``--codegen`` - + Use compiled evaluator instead of interpreter. * ``--evalground`` - + Use evaluator on functions applied to ground arguments. - + * ``--feelinglucky`` - + Use evaluator to find counter-examples early. * ``--unrollcores`` - + Use unsat-cores to drive unrolling while remaining fair. - + CVC4 Solver *********** * ``--solver:cvc4=<cvc4-opt>`` - + Pass extra command-line arguments to CVC4. Isabelle diff --git a/src/sphinx/synthesis.rst b/src/sphinx/synthesis.rst index b70298a9bb949dbf42142bd07964a7dc605b1c25..17ed8eab14623f8fb09cc2ac04e73b55e4700209 100644 --- a/src/sphinx/synthesis.rst +++ b/src/sphinx/synthesis.rst @@ -104,7 +104,7 @@ this rule generates two sub-chooses, and combines them as follows: } -Inequalite Split +Inequality Split ^^^^^^^^^^^^^^^^ Given two input variables `a` and `b` of numeric type, this rule @@ -404,3 +404,45 @@ generally faster way of discovering candidate expressions. However, these expressions are not guaranteed to be valid since they have only been validated by tests. Leon's synthesis framework supports untrusted solutions which trigger an end-to-end validation step that relies on verification. + + +String Conversion +^^^^^^^^^^^^^^^^^ + +This rule applies to pretty-printing problems given a non-empty list of examples, i.e. of the type: + +.. code-block:: scala + + choose ((x: String) => + (input, x) passes { + case InputExample1 => "Output Example1" + case InputExample2 => "Output Example2" + } + ) + +It will create a set of functions and an expression that will be consistent with the example. The example need to ensure the following properties: + +* **Primitive display**: All primitive values (int, boolean, Bigint) present in the InputExample must appear in the OutputExample. The exception being Boolean which can also be rendered differently (e.g. as "yes" and "no") +* **Linearization**: The output example must use the same order as the definition of InputExample; that is, no Set, Map or out of order rendering (e.g. ``case (1, 2) => "2, 1"`` will not work) + +To further optimize the search, it is also better to ensure the following property + +* **Constant case class display**: By default, if a hierarchy of case classes only contains parameterless variants, such as + +.. code-block:: scala + + abstract class StackThread + case class T1() extends StackThread + case class T2() extends StackThread + +it will first try to render expressions with the following function: + +.. code-block:: scala + + def StackThreadToString(t: StackThread) = t match { + case T1() => "T1" + case T2() => "T2" + } + CONST + StackThreadToString(t) + CONST + +where CONST will be inferred from the examples. diff --git a/src/sphinx/verification.rst b/src/sphinx/verification.rst index 10b27b6f73b1b96922d28a052414a3f77c12087e..eed3450933411c847f3d6a053b7bf112bd3be25e 100644 --- a/src/sphinx/verification.rst +++ b/src/sphinx/verification.rst @@ -215,3 +215,17 @@ As an example, the following code should issue a warning with Scala: But Leon will prove that the pattern matching is actually exhaustive, relying on the given precondition. + +Pretty-printing +--------------- + +If a global function name ends with "``toString``" with any case, has only one argument and returns a string, this function will be used when printing verification examples. This function can be synthesized (see the synthesis section). For example, + +.. code-block:: scala + + def intToString(i: Int) = "#" + i.toString + ",..." + def allIntsAreLessThan9(i: Int) = i <= 9 holds + +It will display the counter example for ``allIntsAreLessThan9`` as: + + Counter-example: ``#10,...`` diff --git a/src/test/resources/regression/frontends/error/simple/ArrayEquals1.scala b/src/test/resources/regression/frontends/error/simple/ArrayEquals1.scala new file mode 100644 index 0000000000000000000000000000000000000000..39a9c4b2be2fd3a581ee3209f0d83f16dc8e03b7 --- /dev/null +++ b/src/test/resources/regression/frontends/error/simple/ArrayEquals1.scala @@ -0,0 +1,10 @@ +package leon.lang._ + +object ArrayEqual1 { + + def f: Boolean = { + Array(1,2,3) == Array(1,2,3) + } ensuring(res => res) + +} + diff --git a/src/test/resources/regression/frontends/error/simple/ArrayEquals2.scala b/src/test/resources/regression/frontends/error/simple/ArrayEquals2.scala new file mode 100644 index 0000000000000000000000000000000000000000..2035e0d45c3cc8236009c5b70f0c0e2ac3b95124 --- /dev/null +++ b/src/test/resources/regression/frontends/error/simple/ArrayEquals2.scala @@ -0,0 +1,10 @@ +package leon.lang._ + +object ArrayEqual2 { + + def f: Boolean = { + Array(1,2,3) != Array(1,2,3) + } ensuring(res => !res) + +} + diff --git a/src/test/resources/regression/genc/invalid/AbsFun.scala b/src/test/resources/regression/genc/invalid/AbsFun.scala new file mode 100644 index 0000000000000000000000000000000000000000..aff9a2c80f70f5e3aef079220c1c36b29b40b2e3 --- /dev/null +++ b/src/test/resources/regression/genc/invalid/AbsFun.scala @@ -0,0 +1,66 @@ +import leon.lang._ + +object AbsFun { + + + def isPositive(a : Array[Int], size : Int) : Boolean = { + require(a.length >= 0 && size <= a.length) + rec(0, a, size) + } + + def rec(i: Int, a: Array[Int], size: Int) : Boolean = { + require(a.length >= 0 && size <= a.length && i >= 0) + + if(i >= size) true + else { + if (a(i) < 0) + false + else + rec(i + 1, a, size) + } + } + + // Returning Arrays is not supported by GenC + def abs(tab: Array[Int]): Array[Int] = { + require(tab.length >= 0) + val t = while0(Array.fill(tab.length)(0), 0, tab) + t._1 + } ensuring(res => isPositive(res, res.length)) + + + def while0(t: Array[Int], k: Int, tab: Array[Int]): (Array[Int], Int) = { + require(tab.length >= 0 && + t.length == tab.length && + k >= 0 && + k <= tab.length && + isPositive(t, k)) + + if(k < tab.length) { + val nt = if(tab(k) < 0) { + t.updated(k, -tab(k)) + } else { + t.updated(k, tab(k)) + } + while0(nt, k+1, tab) + } else { + (t, k) + } + } ensuring(res => + res._2 >= tab.length && + res._1.length == tab.length && + res._2 >= 0 && + res._2 <= tab.length && + isPositive(res._1, res._2)) + + def property(t: Array[Int], k: Int): Boolean = { + require(isPositive(t, k) && t.length >= 0 && k >= 0) + if(k < t.length) { + val nt = if(t(k) < 0) { + t.updated(k, -t(k)) + } else { + t.updated(k, t(k)) + } + isPositive(nt, k+1) + } else true + } holds +} diff --git a/src/test/resources/regression/genc/invalid/LinearSearch.scala b/src/test/resources/regression/genc/invalid/LinearSearch.scala new file mode 100644 index 0000000000000000000000000000000000000000..88cadb0ef7dc5ca5425a3f1661495cb08af4df65 --- /dev/null +++ b/src/test/resources/regression/genc/invalid/LinearSearch.scala @@ -0,0 +1,40 @@ +import leon.lang._ + +/* The calculus of Computation textbook */ + +object LinearSearch { + + def linearSearch(a: Array[Int], c: Int): Boolean = ({ + require(a.length >= 0) + var i = 0 + var found = false + (while(i < a.length) { + if(a(i) == c) + found = true + i = i + 1 + }) invariant( + i <= a.length && + i >= 0 && + (if(found) contains(a, i, c) else !contains(a, i, c)) + ) + found + }) ensuring(res => if(res) contains(a, a.length, c) else !contains(a, a.length, c)) + + def contains(a: Array[Int], size: Int, c: Int): Boolean = { + require(a.length >= 0 && size >= 0 && size <= a.length) + content(a, size).contains(c) + } + + // Set not supported by GenC + def content(a: Array[Int], size: Int): Set[Int] = { + require(a.length >= 0 && size >= 0 && size <= a.length) + var set = Set.empty[Int] + var i = 0 + (while(i < size) { + set = set ++ Set(a(i)) + i = i + 1 + }) invariant(i >= 0 && i <= size) + set + } + +} diff --git a/src/test/resources/regression/genc/unverified/BinarySearch.scala b/src/test/resources/regression/genc/unverified/BinarySearch.scala new file mode 100644 index 0000000000000000000000000000000000000000..aec7d2b5ddabc0132d6f9e885eb18c80617f7dc9 --- /dev/null +++ b/src/test/resources/regression/genc/unverified/BinarySearch.scala @@ -0,0 +1,82 @@ +import leon.lang._ + +/* VSTTE 2008 - Dafny paper */ + +object BinarySearch { + + def binarySearch(a: Array[Int], key: Int): Int = ({ + require(a.length > 0 && sorted(a, 0, a.length - 1)) + var low = 0 + var high = a.length - 1 + var res = -1 + + (while(low <= high && res == -1) { + //val i = (high + low) / 2 + val i = low + ((high - low) / 2) + val v = a(i) + + if(v == key) + res = i + + if(v > key) + high = i - 1 + else if(v < key) + low = i + 1 + }) invariant( + res >= -1 && + res < a.length && + 0 <= low && + low <= high + 1 && + high >= -1 && + high < a.length && + (if (res >= 0) + a(res) == key else + (!occurs(a, 0, low, key) && !occurs(a, high + 1, a.length, key)) + ) + ) + res + }) ensuring(res => + res >= -1 && + res < a.length && + (if(res >= 0) a(res) == key else !occurs(a, 0, a.length, key))) + + + def occurs(a: Array[Int], from: Int, to: Int, key: Int): Boolean = { + require(a.length >= 0 && to <= a.length && from >= 0) + def rec(i: Int): Boolean = { + require(i >= 0) + if(i >= to) + false + else { + if(a(i) == key) true else rec(i+1) + } + } + if(from >= to) + false + else + rec(from) + } + + + def sorted(a: Array[Int], l: Int, u: Int) : Boolean = { + require(a.length >= 0 && l >= 0 && l <= u && u < a.length) + val t = sortedWhile(true, l, l, u, a) + t._1 + } + + def sortedWhile(isSorted: Boolean, k: Int, l: Int, u: Int, a: Array[Int]): (Boolean, Int) = { + require(a.length >= 0 && l >= 0 && l <= u && u < a.length && k >= l && k <= u) + if(k < u) { + sortedWhile(if(a(k) > a(k + 1)) false else isSorted, k + 1, l, u, a) + } else (isSorted, k) + } + + + def main = { + val a = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + val i = binarySearch(a, 2) + i - 2 // i should be 2 + } + +} + diff --git a/src/test/resources/regression/genc/unverified/BinarySearchFun.scala b/src/test/resources/regression/genc/unverified/BinarySearchFun.scala new file mode 100644 index 0000000000000000000000000000000000000000..ebc97f6c6d046e2907bda89c795def7026cb4c8f --- /dev/null +++ b/src/test/resources/regression/genc/unverified/BinarySearchFun.scala @@ -0,0 +1,70 @@ +import leon.lang._ + +object BinarySearchFun { + + def binarySearch(a: Array[Int], key: Int, low: Int, high: Int): Int = ({ + require(a.length > 0 && sorted(a, low, high) && + 0 <= low && low <= high + 1 && high < a.length + ) + + if (low <= high) { + //val i = (high + low) / 2 + val i = low + (high - low) / 2 + + val v = a(i) + + if (v == key) i + else if (v > key) binarySearch(a, key, low, i - 1) + else binarySearch(a, key, i + 1, high) + } else -1 + }) ensuring(res => + res >= -1 && + res < a.length && ( + if (res >= 0) + a(res) == key + else + (high < 0 || (!occurs(a, low, low + (high - low) / 2, key) && + !occurs(a, low + (high - low) / 2, high, key))) + ) + ) + + + def occurs(a: Array[Int], from: Int, to: Int, key: Int): Boolean = { + require(a.length >= 0 && to <= a.length && from >= 0) + def rec(i: Int): Boolean = { + require(i >= 0) + if (i >= to) + false + else { + if (a(i) == key) true else rec(i+1) + } + } + if (from >= to) + false + else + rec(from) + } + + + def sorted(a: Array[Int], l: Int, u: Int) : Boolean = { + require(a.length >= 0 && l >= 0 && l <= u + 1 && u < a.length) + val t = sortedWhile(true, l, l, u, a) + t._1 + } + + def sortedWhile(isSorted: Boolean, k: Int, l: Int, u: Int, a: Array[Int]): (Boolean, Int) = { + require(a.length >= 0 && l >= 0 && l <= u+1 && u < a.length && k >= l && k <= u + 1) + if(k < u) { + sortedWhile(if(a(k) > a(k + 1)) false else isSorted, k + 1, l, u, a) + } else (isSorted, k) + } + + def main = { + val a = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + val i = binarySearch(a, 2, 0, a.length - 1) // should be 2 + val j = binarySearch(a, 11, 0, a.length - 1) // should be -1 + + (i - 2) + (j + 1) // == 0 + } +} + diff --git a/src/test/resources/regression/genc/unverified/MaxSum.scala b/src/test/resources/regression/genc/unverified/MaxSum.scala new file mode 100644 index 0000000000000000000000000000000000000000..033dfed30a6930f4041c60a1c4f8a9dfbb87d934 --- /dev/null +++ b/src/test/resources/regression/genc/unverified/MaxSum.scala @@ -0,0 +1,71 @@ +import leon.lang._ + +/* VSTTE 2010 challenge 1 */ + +object MaxSum { + + def maxSum(a: Array[Int]): (Int, Int) = ({ + require(a.length >= 0 && isPositive(a)) + var sum = 0 + var max = 0 + var i = 0 + (while(i < a.length) { + if(max < a(i)) + max = a(i) + sum = sum + a(i) + i = i + 1 + }) invariant (sum <= i * max && i >= 0 && i <= a.length) + (sum, max) + }) ensuring(res => res._1 <= a.length * res._2) + + + def isPositive(a: Array[Int]): Boolean = { + require(a.length >= 0) + def rec(i: Int): Boolean = { + require(i >= 0) + if(i >= a.length) + true + else { + if(a(i) < 0) + false + else + rec(i+1) + } + } + rec(0) + } + + def summ(to : Int): Int = ({ + require(to >= 0) + var i = 0 + var s = 0 + (while (i < to) { + s = s + i + i = i + 1 + }) invariant (s >= 0 && i >= 0 && s == i*(i-1)/2 && i <= to) + s + }) ensuring(res => res >= 0 && res == to*(to-1)/2) + + + def sumsq(to : Int): Int = ({ + require(to >= 0) + var i = 0 + var s = 0 + (while (i < to) { + s = s + i*i + i = i + 1 + }) invariant (s >= 0 && i >= 0 && s == (i-1)*i*(2*i-1)/6 && i <= to) + s + }) ensuring(res => res >= 0 && res == (to-1)*to*(2*to-1)/6) + + def main = { + val a = Array(1, 4, 6, 0, 234, 999) + val sm = maxSum(a) + val sum = sm._1 + val max = sm._2 + if (sum == 1244 && max == 999) 0 + else -1 + } ensuring { _ == 0 } + +} + diff --git a/src/test/resources/regression/genc/valid/AbsArray.scala b/src/test/resources/regression/genc/valid/AbsArray.scala new file mode 100644 index 0000000000000000000000000000000000000000..581684e7c50d6034e751c152be9afd185e227e73 --- /dev/null +++ b/src/test/resources/regression/genc/valid/AbsArray.scala @@ -0,0 +1,23 @@ +import leon.lang._ + +object AbsArray { + def main = { + val a = Array(0, -1, 2, -3) + + def abs() { + require(a.length < 10000) + + var i = 0; + (while (i < a.length && i < 10000) { + a(i) = if (a(i) < 0) -a(i) else a(i) + i = i + 1 + }) invariant (i >= 0 && i <= 10000) + } + + abs() + + a(0) + a(1) - 1 + a(2) - 2 + a(3) - 3 // == 0 + } ensuring { _ == 0 } +} + + diff --git a/src/test/resources/regression/genc/valid/CaseClass.scala b/src/test/resources/regression/genc/valid/CaseClass.scala new file mode 100644 index 0000000000000000000000000000000000000000..83f5988594336f12e67ca63980334a747fcadbb2 --- /dev/null +++ b/src/test/resources/regression/genc/valid/CaseClass.scala @@ -0,0 +1,20 @@ +import leon.lang._ + +object CaseClass { + + case class Color(r: Int, g: Int, b: Int) + + def red = Color(0, 255, 0) + def cyan = Color(0, 255, 255) + + def sub(c: Color, d: Color) = Color(c.r - d.r, c.g - d.g, c.b - d.b) + + def main = { + val c = red + val d = cyan + val z = sub(c, d).g + z + } ensuring { _ == 0 } + +} + diff --git a/src/test/resources/regression/genc/valid/ExpressionOrder.scala b/src/test/resources/regression/genc/valid/ExpressionOrder.scala new file mode 100644 index 0000000000000000000000000000000000000000..21ea9766dffad887414d56fb3c281934ea429f1d --- /dev/null +++ b/src/test/resources/regression/genc/valid/ExpressionOrder.scala @@ -0,0 +1,153 @@ +import leon.lang._ + +object ExpressionOrder { + case class Pixel(rgb: Int) + case class Matrix(data: Array[Int], w: Int, h: Int) + + def void = () + + def fun = 0xffffff + def foo = 4 + def bar(i: Int) = i * 2 + def baz(i: Int, j: Int) = bar(i) - bar(j) + + def syntaxCheck(i: Int) { + val p = Pixel(fun) + val m = Matrix(Array(0, 1, 2, 3), 2, 2) + + val z = baz(foo, bar(foo)) + val a = Array(0, 1, foo / 2, 3, bar(2), z / 1) + + val t = (true, foo, bar(a(0))) + + val a2 = Array.fill(4)(2) + val a3 = Array.fill(if (i <= 0) 1 else i)(bar(i)) + val b = Array(1, 2, 0) + b(1) = if (bar(b(1)) % 2 == 0) 42 else 58 + + def f1 = (if (i < 0) a else b)(0) + def f2 = (if (i < 0) a else b).length + + //def f3 = (if (i < 0) a else b)(0) = 0 // <- not supported + + val c = (0, true, 2) + val d = (if (i > 0) i else -i, false, 0) + + def f4 = (if (i < 0) d else c)._2 // expression result unused + } + + def main = { + bool2int(test0(false), 1) + + bool2int(test1(42), 2) + + bool2int(test2(58), 4) + + bool2int(test3(false), 8) + + bool2int(test4(false), 16) + + bool2int(test6, 32) + + bool2int(test7, 64) + + bool2int(test8, 128) + } ensuring { _ == 0 } + + def test0(b: Boolean) = { + val f = b && !b // == false + + var c = 0 + + val x = f && { c = 1; true } + + c == 0 + }.holds + + def test1(i: Int) = { + require(i > 0) + + val j = i / i * 3 // == 3 + + var c = 0 + val x = { c = c + 3; j } + { c = c + 1; j } * { c = c * 2; j } + + c == 8 && j == 3 && x == 12 + }.holds + + def test2(i: Int) = { + var c = 0; + val x = if (i < 0) { c = 1; -i } else { c = 2; i } + + if (i < 0) c == 1 + else c == 2 + }.holds + + def test3(b: Boolean) = { + val f = b && !b // == false + + var c = 0 + val x = f || { c = 1; true } || { c = 2; false } + + c == 1 + }.holds + + def test4(b: Boolean) = { + var i = 10 + var c = 0 + + val f = b && !b // == false + val t = b || !b // == true + + // The following condition is executed 11 times, + // and only during the last execution is the last + // operand evaluated + while ({ c = c + 1; t } && i > 0 || { c = c * 2; f }) { + i = i - 1 + } + + i == 0 && c == 22 + }.holds + + def test5(b: Boolean) = { + val f = b && !b // == false + + var c = if (f) 0 else -1 + + c = c + (if (f) 0 else 1) + + c == 0 + }.holds + + def test6 = { + val a = Array(0, 1, 2, 3, 4) + + def rec(b: Boolean, i: Int): Boolean = { + require(i >= 0 && i < 2147483647) // 2^31 - 1 + + if (i + 1 < a.length) rec(if (a(i) < a(i + 1)) b else false, i + 1) + else b + } + + rec(true, 0) + }.holds + + def test7 = { + var c = 1 + + val a = Array(0, 1, 2, 3, 4) + + a(if(a(0) == 0) { c = c + 1; 0 } else { c = c + 2; 1 }) = { c = c * 2; -1 } + + c == 4 + }.holds + + def test8 = { + var x = 0 + + def bar(y: Int) = { + def fun(z: Int) = 1 * x * (y + z) + + fun(3) + } + + bar(2) == 0 + }.holds + + def bool2int(b: Boolean, f: Int) = if (b) 0 else f; +} + + diff --git a/src/test/resources/regression/genc/valid/IntegralColor.scala b/src/test/resources/regression/genc/valid/IntegralColor.scala new file mode 100644 index 0000000000000000000000000000000000000000..c64cb0fa18ac395dad37b2a8a8ffa7fa187872a2 --- /dev/null +++ b/src/test/resources/regression/genc/valid/IntegralColor.scala @@ -0,0 +1,175 @@ +import leon.lang._ + +object IntegralColor { + + def isValidComponent(x: Int) = x >= 0 && x <= 255 + + def getRed(rgb: Int): Int = { + (rgb & 0x00FF0000) >> 16 + } ensuring isValidComponent _ + + def getGreen(rgb: Int): Int = { + (rgb & 0x0000FF00) >> 8 + } ensuring isValidComponent _ + + def getBlue(rgb: Int): Int = { + rgb & 0x000000FF + } ensuring isValidComponent _ + + def getGray(rgb: Int): Int = { + (getRed(rgb) + getGreen(rgb) + getBlue(rgb)) / 3 + } ensuring isValidComponent _ + + def testColorSinglePixel: Boolean = { + val color = 0x20C0FF + + 32 == getRed(color) && 192 == getGreen(color) && + 255 == getBlue(color) && 159 == getGray(color) + }.holds + + case class Color(r: Int, g: Int, b: Int) + + def getColor(rgb: Int) = Color(getRed(rgb), getGreen(rgb), getBlue(rgb)) + + /* + *case class Image(width: Int, height: Int, buffer: Array[Int]) { + * // currently not enforced: + * require(width <= 1000 && height <= 1000 && buffer.length == width * height) + *} + */ + + def matches(value: Array[Int], expected: Array[Int]): Boolean = { + require(value.length == expected.length) + + var test = true + var idx = 0 + (while (idx < value.length) { + test = test && value(idx) == expected(idx) + idx = idx + 1 + }) invariant { idx >= 0 && idx <= value.length } + + test + } + + def testColorWholeImage: Boolean = { + val WIDTH = 2 + val HEIGHT = 2 + + /* + *val source = Image(WIDTH, HEIGHT, Array(0x20c0ff, 0x123456, 0xffffff, 0x000000)) + *val expected = Image(WIDTH, HEIGHT, Array(159, 52, 255, 0)) // gray convertion + *val gray = Image(WIDTH, HEIGHT, Array.fill(4)(0)) + */ + + val source = Array(0x20c0ff, 0x123456, 0xffffff, 0x000000) + val expected = Array(159, 52, 255, 0) // gray convertion + val gray = Array.fill(4)(0) + + // NOTE: Cannot define a toGray function as XLang doesn't allow mutating + // arguments and GenC doesn't allow returning arrays + + var idx = 0 + (while (idx < WIDTH * HEIGHT) { + gray(idx) = getGray(source(idx)) + idx = idx + 1 + }) invariant { idx >= 0 && idx <= WIDTH * HEIGHT && gray.length == WIDTH * HEIGHT } + // NB: the last invariant is very important -- without it the verification times out + + matches(gray, expected) + }.holds + + // Only for square kernels + case class Kernel(size: Int, buffer: Array[Int]) + + def isKernelValid(kernel: Kernel): Boolean = + kernel.size > 0 && kernel.size < 1000 && kernel.size % 2 == 1 && + kernel.buffer.length == kernel.size * kernel.size + + def applyFilter(gray: Array[Int], size: Int, idx: Int, kernel: Kernel): Int = { + require(size > 0 && size < 1000 && + gray.length == size * size && + idx >= 0 && idx < gray.length && + isKernelValid(kernel)) + + def up(x: Int): Int = { + if (x < 0) 0 else x + } ensuring { _ >= 0 } + + def down(x: Int): Int = { + if (x >= size) size - 1 else x + } ensuring { _ < size } + + def fix(x: Int): Int = { + down(up(x)) + } ensuring { res => res >= 0 && res < size } + + def at(row: Int, col: Int): Int = { + val r = fix(row) + val c = fix(col) + + gray(r * size + c) + } + + val mid = kernel.size / 2 + + val i = idx / size + val j = idx % size + + var res = 0 + var p = -mid + (while (p <= mid) { + var q = -mid + + (while (q <= mid) { + val krow = p + mid + val kcol = q + mid + + assert(krow >= 0 && krow < kernel.size) + assert(kcol >= 0 && kcol < kernel.size) + + val kidx = krow * kernel.size + kcol + + res += at(i + p, j + q) * kernel.buffer(kidx) + + q = q + 1 + }) invariant { q >= -mid && q <= mid + 1 } + + p = p + 1 + }) invariant { p >= -mid && p <= mid + 1 } + + res + } + + def testFilterConvolutionSmooth: Boolean = { + val gray = Array(127, 255, 51, 0) + val expected = Array(124, 158, 76, 73) + val size = 2 // grey is size x size + + // NOTE: Cannot define a `smoothed` function as XLang doesn't allow mutating + // arguments and GenC doesn't allow returning arrays + + val kernel = Kernel(3, Array(1, 1, 1, + 1, 2, 1, + 1, 1, 1)) + + val smoothed = Array.fill(gray.length)(0) + assert(smoothed.length == expected.length) + + var idx = 0; + (while (idx < smoothed.length) { + smoothed(idx) = applyFilter(gray, size, idx, kernel) / 10 + idx = idx + 1 + }) invariant { idx >= 0 && idx <= smoothed.length && smoothed.length == gray.length } + + matches(smoothed, expected) + }.holds + + + def main: Int = { + if (testColorSinglePixel && testColorWholeImage && testFilterConvolutionSmooth) 0 + else 1 + } ensuring { _ == 0 } + +} + + diff --git a/src/test/resources/regression/genc/valid/RecursionAndNestedFunctions.scala b/src/test/resources/regression/genc/valid/RecursionAndNestedFunctions.scala new file mode 100644 index 0000000000000000000000000000000000000000..25f69538a19412bd8ae8267446d4eb5f05f6247b --- /dev/null +++ b/src/test/resources/regression/genc/valid/RecursionAndNestedFunctions.scala @@ -0,0 +1,37 @@ +import leon.lang._ + +object RecursionAndNestedFunctions { + + // Complex way to return i + def zzz(i: Int): Int = { + val x = 0 + + def rec(j: Int): Int = { + if (i - x == j) i + else if (j > i) rec(j - 1) + else rec(j + 1) + } ensuring { _ == i } + + rec(4) + } ensuring { _ == i } + + + // Complex way to compute 100 + 2 * i + def foo(i: Int) = { + var j = i + def bar(x: Int) = { + //j = j - 1 <- not supported by leon + val y = x + i + def baz(z: Int) = z + y + i + //j = j + 1 <- not supported by leon + baz(42) + } + bar(58) + j - i + } ensuring { _ == 100 + 2 * i } + + def main() = { + foo(2) - zzz(104) + } ensuring { _ == 0 } + +} + diff --git a/src/test/resources/regression/genc/valid/TupleArray.scala b/src/test/resources/regression/genc/valid/TupleArray.scala new file mode 100644 index 0000000000000000000000000000000000000000..1f2354e0613430fb95a995da6a741dda3939b143 --- /dev/null +++ b/src/test/resources/regression/genc/valid/TupleArray.scala @@ -0,0 +1,25 @@ +import leon.lang._ + +object TupleArray { + def exists(av: (Array[Int], Int)): Boolean = { + require(av._1.length < 10000) + + var i = 0 + var found = false + (while (!found && i < av._1.length) { + found = av._1(i) == av._2 + i = i + 1 + }) invariant (i >= 0 && i < 10000) + found + } + + def main = { + val a = Array(0, 1, 5, -5, 9) + val e1 = exists((a, 0)) + val e2 = exists((a, -1)) + if (e1 && !e2) 0 + else -1 + } ensuring { _ == 0 } + +} + diff --git a/src/test/resources/regression/termination/valid/QuickSort.scala b/src/test/resources/regression/termination/valid/QuickSort.scala index e356bdc2a2b8a1c412e90a3033ae269faf6d7df0..d4fb11ea68a7e8c96f056754c24be2e9a5f9ef4e 100644 --- a/src/test/resources/regression/termination/valid/QuickSort.scala +++ b/src/test/resources/regression/termination/valid/QuickSort.scala @@ -1,5 +1,6 @@ /* Copyright 2009-2015 EPFL, Lausanne */ +import leon.annotation._ import leon.lang._ object QuickSort { @@ -29,7 +30,7 @@ object QuickSort { case Nil() => bList case _ => rev_append(reverse(aList),bList) } - + def greater(n:Int,list:List) : List = list match { case Nil() => Nil() case Cons(x,xs) => if (n < x) Cons(x,greater(n,xs)) else greater(n,xs) @@ -51,6 +52,7 @@ object QuickSort { case Cons(x,xs) => append(append(quickSort(smaller(x,xs)),Cons(x,equals(x,xs))),quickSort(greater(x,xs))) }) ensuring(res => contents(res) == contents(list)) // && is_sorted(res)) + @ignore def main(args: Array[String]): Unit = { val ls: List = Cons(5, Cons(2, Cons(4, Cons(5, Cons(1, Cons(8,Nil())))))) diff --git a/src/test/resources/regression/termination/valid/SimpInterpret.scala b/src/test/resources/regression/termination/valid/SimpInterpret.scala index 76fcd4145509440741b521bf26fa97dcc9af2dd9..c5255506dbb0b20308a0210ea3268a9432abaa14 100644 --- a/src/test/resources/regression/termination/valid/SimpInterpret.scala +++ b/src/test/resources/regression/termination/valid/SimpInterpret.scala @@ -1,15 +1,15 @@ /* Copyright 2009-2015 EPFL, Lausanne */ -//import leon.annotation._ +import leon.annotation._ import leon.lang._ object Interpret { - abstract class BoolTree + abstract class BoolTree case class Eq(t1 : IntTree, t2 : IntTree) extends BoolTree case class And(t1 : BoolTree, t2 : BoolTree) extends BoolTree case class Not(t : BoolTree) extends BoolTree - abstract class IntTree + abstract class IntTree case class Const(c:Int) extends IntTree case class Var() extends IntTree case class Plus(t1 : IntTree, t2 : IntTree) extends IntTree @@ -22,7 +22,7 @@ object Interpret { } def beval(t:BoolTree, x0 : Int) : Boolean = { - t match { + t match { case Less(t1, t2) => ieval(t1,x0) < ieval(t2,x0) case Eq(t1, t2) => ieval(t1,x0) == ieval(t2,x0) case And(t1, t2) => beval(t1,x0) && beval(t2,x0) @@ -62,6 +62,7 @@ object Interpret { !treeBad(If(Less(Const(0),Var()), Var(), Minus(Const(0),Var()))) }.holds + @ignore def main(args : Array[String]) { thereIsGoodTree() } diff --git a/src/test/resources/regression/verification/isabelle/valid/Nats.scala b/src/test/resources/regression/verification/isabelle/valid/Nats.scala new file mode 100644 index 0000000000000000000000000000000000000000..4838665b6f56dc0a9c84b79036049c8db869fecb --- /dev/null +++ b/src/test/resources/regression/verification/isabelle/valid/Nats.scala @@ -0,0 +1,77 @@ +import leon.annotation._ +import leon.collection._ +import leon.lang._ + +@isabelle.typ(name = "Nat.nat") +sealed abstract class Nat { + + @isabelle.function(term = "op <=") + def <=(that: Nat): Boolean = this match { + case Zero() => true + case Succ(p) => + that match { + case Zero() => false + case Succ(q) => p <= q + } + } + + @isabelle.function(term = "op +") + def +(that: Nat): Nat = (this match { + case Zero() => that + case Succ(pred) => Succ(pred + that) + }) ensuring { res => + this <= res && that <= res + } + + @isabelle.function(term = "op *") + def *(that: Nat): Nat = this match { + case Zero() => Zero() + case Succ(pred) => that + pred * that + } + +} + +@isabelle.constructor(name = "Groups.zero_class.zero") +case class Zero() extends Nat + +@isabelle.constructor(name = "Nat.Suc") +case class Succ(pred: Nat) extends Nat + +object Nats { + + @isabelle.function(term = "Groups_List.monoid_add_class.listsum") + def listSum(xs: List[Nat]): Nat = xs match { + case Nil() => Zero() + case Cons(x, xs) => x + listSum(xs) + } + + @isabelle.function(term = "length") + def length[A](xs: List[A]): Nat = xs match { + case Nil() => Zero() + case Cons(x, xs) => Succ(length(xs)) + } + + @isabelle.script( + name = "Map_Fst_Zip", + source = "declare map_fst_zip[simp del]" + ) + @isabelle.proof(method = """(clarsimp, induct rule: list_induct2, auto)""") + def mapFstZip[A, B](xs: List[A], ys: List[B]) = { + require(length(xs) == length(ys)) + xs.zip(ys).map(_._1) + } ensuring { _ == xs } + + def addCommute(x: Nat, y: Nat) = + (x + y == y + x).holds + + def sumReverse(xs: List[Nat]) = + (listSum(xs) == listSum(xs.reverse)).holds + + def sumZero[A](xs: List[A]) = + (listSum(xs.map(_ => Zero())) == Zero()).holds + + @isabelle.proof(method = """(induct "<var xs>", auto)""") + def sumConstant[A](xs: List[A], k: Nat) = + (listSum(xs.map(_ => k)) == length(xs) * k).holds + +} diff --git a/src/test/resources/regression/verification/newsolvers/valid/InsertionSort.scala b/src/test/resources/regression/verification/newsolvers/valid/InsertionSort.scala index 05889fcb726ff88728959172f2127128831802c2..3252c9acf942d682f3cef10cc358cb7c837b44bb 100644 --- a/src/test/resources/regression/verification/newsolvers/valid/InsertionSort.scala +++ b/src/test/resources/regression/verification/newsolvers/valid/InsertionSort.scala @@ -34,7 +34,7 @@ object InsertionSort { case Nil() => true case Cons(x, Nil()) => true case Cons(x, Cons(y, ys)) => x <= y && isSorted(Cons(y, ys)) - } + } /* Inserting element 'e' into a sorted list 'l' produces a sorted list with * the expected content and size */ @@ -43,8 +43,8 @@ object InsertionSort { l match { case Nil() => Cons(e,Nil()) case Cons(x,xs) => if (x <= e) Cons(x,sortedIns(e, xs)) else Cons(e, l) - } - } ensuring(res => contents(res) == contents(l) ++ Set(e) + } + } ensuring(res => contents(res) == contents(l) ++ Set(e) && isSorted(res) && size(res) == size(l) + 1 ) @@ -54,11 +54,12 @@ object InsertionSort { def sort(l: List): List = (l match { case Nil() => Nil() case Cons(x,xs) => sortedIns(x, sort(xs)) - }) ensuring(res => contents(res) == contents(l) + }) ensuring(res => contents(res) == contents(l) && isSorted(res) && size(res) == size(l) ) + @ignore def main(args: Array[String]): Unit = { val ls: List = Cons(5, Cons(2, Cons(4, Cons(5, Cons(1, Cons(8,Nil())))))) diff --git a/src/test/resources/regression/verification/purescala/invalid/CallByName1.scala b/src/test/resources/regression/verification/purescala/invalid/CallByName1.scala new file mode 100644 index 0000000000000000000000000000000000000000..c96ab1617e254c19d8b08a8ecbd818d9cdc305e4 --- /dev/null +++ b/src/test/resources/regression/verification/purescala/invalid/CallByName1.scala @@ -0,0 +1,17 @@ +import leon.lang._ + +object CallByName1 { + def byName1(i: Int, a: => Int): Int = { + if (i > 0) a + 1 + else 0 + } + + def byName2(i: Int, a: => Int): Int = { + if (i > 0) byName1(i - 1, a) + 2 + else 0 + } + + def test(): Boolean = { + byName1(1, byName2(3, 0)) == 0 && byName1(1, byName2(3, 0)) == 1 + }.holds +} diff --git a/src/test/resources/regression/verification/purescala/invalid/PropositionalLogic.scala b/src/test/resources/regression/verification/purescala/invalid/PropositionalLogic.scala index a8927f360e817d10761723c8a4b7e9085bf0738d..aa00e12d446e1b11984f741500279a5647cfe426 100644 --- a/src/test/resources/regression/verification/purescala/invalid/PropositionalLogic.scala +++ b/src/test/resources/regression/verification/purescala/invalid/PropositionalLogic.scala @@ -10,14 +10,11 @@ object PropositionalLogic { case class Or(lhs: Formula, rhs: Formula) extends Formula case class Implies(lhs: Formula, rhs: Formula) extends Formula case class Not(f: Formula) extends Formula - case class Literal(id: Int) extends Formula + case class Literal(id: BigInt) extends Formula def simplify(f: Formula): Formula = (f match { - case And(lhs, rhs) => And(simplify(lhs), simplify(rhs)) - case Or(lhs, rhs) => Or(simplify(lhs), simplify(rhs)) case Implies(lhs, rhs) => Or(Not(simplify(lhs)), simplify(rhs)) - case Not(f) => Not(simplify(f)) - case Literal(_) => f + case _ => f }) ensuring(isSimplified(_)) def isSimplified(f: Formula): Boolean = f match { @@ -28,18 +25,6 @@ object PropositionalLogic { case Literal(_) => true } - def nnf(formula: Formula): Formula = (formula match { - case And(lhs, rhs) => And(nnf(lhs), nnf(rhs)) - case Or(lhs, rhs) => Or(nnf(lhs), nnf(rhs)) - case Implies(lhs, rhs) => Implies(nnf(lhs), nnf(rhs)) - case Not(And(lhs, rhs)) => Or(nnf(Not(lhs)), nnf(Not(rhs))) - case Not(Or(lhs, rhs)) => And(nnf(Not(lhs)), nnf(Not(rhs))) - case Not(Implies(lhs, rhs)) => And(nnf(lhs), nnf(Not(rhs))) - case Not(Not(f)) => nnf(f) - case Not(Literal(_)) => formula - case Literal(_) => formula - }) ensuring(isNNF(_)) - def isNNF(f: Formula): Boolean = f match { case And(lhs, rhs) => isNNF(lhs) && isNNF(rhs) case Or(lhs, rhs) => isNNF(lhs) && isNNF(rhs) @@ -49,39 +34,14 @@ object PropositionalLogic { case Literal(_) => true } - def vars(f: Formula): Set[Int] = { - require(isNNF(f)) - f match { - case And(lhs, rhs) => vars(lhs) ++ vars(rhs) - case Or(lhs, rhs) => vars(lhs) ++ vars(rhs) - case Implies(lhs, rhs) => vars(lhs) ++ vars(rhs) - case Not(Literal(i)) => Set[Int](i) - case Literal(i) => Set[Int](i) - } - } - - def fv(f : Formula) = { vars(nnf(f)) } // @induct // def wrongCommutative(f: Formula) : Boolean = { // nnf(simplify(f)) == simplify(nnf(f)) // }.holds - @induct - def simplifyBreaksNNF(f: Formula) : Boolean = { + def simplifyBreaksNNF(f: Formula) : Boolean = { require(isNNF(f)) isNNF(simplify(f)) }.holds - - @induct - def nnfIsStable(f: Formula) : Boolean = { - require(isNNF(f)) - nnf(f) == f - }.holds - - @induct - def simplifyIsStable(f: Formula) : Boolean = { - require(isSimplified(f)) - simplify(f) == f - }.holds } diff --git a/src/test/resources/regression/verification/purescala/invalid/Unapply1.scala b/src/test/resources/regression/verification/purescala/invalid/Unapply1.scala index 20ff95383b4f7042b0eba32fcc4e5c8b0cea3680..674ca7c69fa29333755d8908f707ae9931d20bf2 100644 --- a/src/test/resources/regression/verification/purescala/invalid/Unapply1.scala +++ b/src/test/resources/regression/verification/purescala/invalid/Unapply1.scala @@ -6,10 +6,14 @@ object Unap1 { } object Unapply1 { + + sealed abstract class Bool + case class True() extends Bool + case class False() extends Bool - def bar: Boolean = { (42, false, ()) match { - case Unap1(_, b) if b => b + def bar: Bool = { (42, False().asInstanceOf[Bool], ()) match { + case Unap1(_, b) if b == True() => b case Unap1((), b) => b - }} ensuring { res => res } + }} ensuring { res => res == True() } } diff --git a/src/test/resources/regression/verification/purescala/invalid/Unapply2.scala b/src/test/resources/regression/verification/purescala/invalid/Unapply2.scala index ae4167c20026f0e926494af6cf3a11f25e9399e5..7efd6e220fd1c7fcd08fbaa5b7016bf6801e151f 100644 --- a/src/test/resources/regression/verification/purescala/invalid/Unapply2.scala +++ b/src/test/resources/regression/verification/purescala/invalid/Unapply2.scala @@ -5,7 +5,12 @@ object Unap2 { } object Unapply { - def bar: Boolean = { (42, false, ()) match { - case Unap2(_, b) if b => b - }} ensuring { res => res } + + sealed abstract class Bool + case class True() extends Bool + case class False() extends Bool + + def bar: Bool = { (42, False().asInstanceOf[Bool], ()) match { + case Unap2(_, b) if b == True() => b + }} ensuring { res => res == True() } } diff --git a/src/test/resources/regression/verification/purescala/valid/CallByName1.scala b/src/test/resources/regression/verification/purescala/valid/CallByName1.scala new file mode 100644 index 0000000000000000000000000000000000000000..912acdc481e8191bd072bd7eaeb13684cd93c384 --- /dev/null +++ b/src/test/resources/regression/verification/purescala/valid/CallByName1.scala @@ -0,0 +1,9 @@ +import leon.lang._ + +object CallByName1 { + def add(a: => Int, b: => Int): Int = a + b + + def test(): Int = { + add(1,2) + } ensuring (_ == 3) +} diff --git a/src/test/resources/regression/verification/purescala/valid/Formulas.scala b/src/test/resources/regression/verification/purescala/valid/Formulas.scala new file mode 100644 index 0000000000000000000000000000000000000000..0fafe4158a2fcbd1b6654d21dbfa072ec42d614f --- /dev/null +++ b/src/test/resources/regression/verification/purescala/valid/Formulas.scala @@ -0,0 +1,50 @@ +import leon.lang._ +import leon._ + +object Formulas { + abstract class Expr + case class And(lhs: Expr, rhs: Expr) extends Expr + case class Or(lhs: Expr, rhs: Expr) extends Expr + case class Implies(lhs: Expr, rhs: Expr) extends Expr + case class Not(e : Expr) extends Expr + case class BoolLiteral(i: BigInt) extends Expr + + def exists(e: Expr, f: Expr => Boolean): Boolean = { + f(e) || (e match { + case And(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Or(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Implies(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Not(e) => exists(e, f) + case _ => false + }) + } + + def existsImplies(e: Expr): Boolean = { + e.isInstanceOf[Implies] || (e match { + case And(lhs, rhs) => existsImplies(lhs) || existsImplies(rhs) + case Or(lhs, rhs) => existsImplies(lhs) || existsImplies(rhs) + case Implies(lhs, rhs) => existsImplies(lhs) || existsImplies(rhs) + case Not(e) => existsImplies(e) + case _ => false + }) + } + + abstract class Value + case class BoolValue(b: Boolean) extends Value + case class IntValue(i: BigInt) extends Value + case object Error extends Value + + def desugar(e: Expr): Expr = { + e match { + case And(lhs, rhs) => And(desugar(lhs), desugar(rhs)) + case Or(lhs, rhs) => Or(desugar(lhs), desugar(rhs)) + case Implies(lhs, rhs) => + Or(Not(desugar(lhs)), desugar(rhs)) + case Not(e) => Not(desugar(e)) + case e => e + } + } ensuring { out => + !existsImplies(out) && + !exists(out, f => f.isInstanceOf[Implies]) + } +} diff --git a/src/test/resources/regression/verification/purescala/valid/InsertionSort.scala b/src/test/resources/regression/verification/purescala/valid/InsertionSort.scala index 7c89f364600e95edb8c0f266cb5e22f65d1adbea..0ec78ff0d3050b6333e3fd1e229dd57f2e72cea2 100644 --- a/src/test/resources/regression/verification/purescala/valid/InsertionSort.scala +++ b/src/test/resources/regression/verification/purescala/valid/InsertionSort.scala @@ -34,7 +34,7 @@ object InsertionSort { case Nil() => true case Cons(x, Nil()) => true case Cons(x, Cons(y, ys)) => x <= y && isSorted(Cons(y, ys)) - } + } /* Inserting element 'e' into a sorted list 'l' produces a sorted list with * the expected content and size */ @@ -43,8 +43,8 @@ object InsertionSort { l match { case Nil() => Cons(e,Nil()) case Cons(x,xs) => if (x <= e) Cons(x,sortedIns(e, xs)) else Cons(e, l) - } - } ensuring(res => contents(res) == contents(l) ++ Set(e) + } + } ensuring(res => contents(res) == contents(l) ++ Set(e) && isSorted(res) && size(res) == size(l) + 1 ) @@ -54,11 +54,12 @@ object InsertionSort { def sort(l: List): List = (l match { case Nil() => Nil() case Cons(x,xs) => sortedIns(x, sort(xs)) - }) ensuring(res => contents(res) == contents(l) + }) ensuring(res => contents(res) == contents(l) && isSorted(res) && size(res) == size(l) ) + @ignore def main(args: Array[String]): Unit = { val ls: List = Cons(5, Cons(2, Cons(4, Cons(5, Cons(1, Cons(8,Nil())))))) diff --git a/src/test/resources/regression/verification/purescala/valid/Unapply.scala b/src/test/resources/regression/verification/purescala/valid/Unapply.scala index 941b1f370d740204e8fa850a9d5e5a787b1c401e..1885837d990c32185a42c1232a53de48c3935340 100644 --- a/src/test/resources/regression/verification/purescala/valid/Unapply.scala +++ b/src/test/resources/regression/verification/purescala/valid/Unapply.scala @@ -5,8 +5,18 @@ object Unap { } object Unapply { - def bar: Boolean = { (42, true, ()) match { - case Unap(_, b) if b => b - case Unap((), b) => !b - }} ensuring { res => res } + + sealed abstract class Bool + case class True() extends Bool + case class False() extends Bool + + def not(b: Bool): Bool = b match { + case True() => False() + case False() => True() + } + + def bar: Bool = { (42, True().asInstanceOf[Bool], ()) match { + case Unap(_, b) if b == True() => b + case Unap((), b) => not(b) + }} ensuring { res => res == True() } } diff --git a/src/test/resources/regression/verification/xlang/invalid/Assert1.scala b/src/test/resources/regression/verification/xlang/invalid/Assert1.scala new file mode 100644 index 0000000000000000000000000000000000000000..8027179df9bcda58c7300e571506f481bb78791e --- /dev/null +++ b/src/test/resources/regression/verification/xlang/invalid/Assert1.scala @@ -0,0 +1,14 @@ +package test.resources.regression.verification.xlang.invalid + +/* Copyright 2009-2015 EPFL, Lausanne */ + +object Assert1 { + + def foo(): Int = { + var a = 0 + a += 1 + assert(a == 0) + a + } + +} diff --git a/src/test/resources/regression/verification/xlang/invalid/Assert2.scala b/src/test/resources/regression/verification/xlang/invalid/Assert2.scala new file mode 100644 index 0000000000000000000000000000000000000000..cc8b2a591015ea765a1549a75b51821770c3c9bf --- /dev/null +++ b/src/test/resources/regression/verification/xlang/invalid/Assert2.scala @@ -0,0 +1,12 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +object Assert2 { + + def foo(): Int = { + var a = 0 + assert(a == 1) + a += 1 + a + } + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Array6.scala b/src/test/resources/regression/verification/xlang/valid/Array6.scala new file mode 100644 index 0000000000000000000000000000000000000000..ffcc9e3621ede9050a85b19ce2651d57a71b3680 --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Array6.scala @@ -0,0 +1,17 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +import leon.lang._ + +object Array6 { + + def test(): Int = { + var c = 1 + val a = Array(0,1,2,3) + a({ + if(a(0) == 0) { c = c+1; 1} + else { c = c+2; 2} + }) = { c = c*2; -1} + c + } ensuring(res => res == 4) + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Assert1.scala b/src/test/resources/regression/verification/xlang/valid/Assert1.scala new file mode 100644 index 0000000000000000000000000000000000000000..d9ae5c6753320b35d193042044144a91ecf19070 --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Assert1.scala @@ -0,0 +1,12 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +object Assert1 { + + def foo(): Int = { + var a = 0 + a += 1 + assert(a == 1) + a + } + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Assert2.scala b/src/test/resources/regression/verification/xlang/valid/Assert2.scala new file mode 100644 index 0000000000000000000000000000000000000000..0f5131346f9e9b56ab3a08198cc8a3bb87049fd3 --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Assert2.scala @@ -0,0 +1,12 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +object Assert2 { + + def foo(): Int = { + var a = 0 + assert(a == 0) + a += 1 + a + } + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Assert3.scala b/src/test/resources/regression/verification/xlang/valid/Assert3.scala new file mode 100644 index 0000000000000000000000000000000000000000..49e2f6c55c0f43ffd8dc16b016b13d363f62735a --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Assert3.scala @@ -0,0 +1,20 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +import leon.lang._ + +object Assert3 { + + def test(i: Int): Int = { + var j = i + + assert(j == i) + j += 1 + assert(j == i + 1) + j += 2 + assert(j == i + 3) + + j + + } + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Sequencing1.scala b/src/test/resources/regression/verification/xlang/valid/Sequencing1.scala new file mode 100644 index 0000000000000000000000000000000000000000..d1041aef525a2837fcbf9e9e4059d78308b773b7 --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Sequencing1.scala @@ -0,0 +1,10 @@ +object Sequencing1 { + + def test(): Int = { + var x = 0 + x += 1 + x *= 2 + x + } ensuring(x => x == 2) + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Sequencing2.scala b/src/test/resources/regression/verification/xlang/valid/Sequencing2.scala new file mode 100644 index 0000000000000000000000000000000000000000..e152b00adb31cfa38183972c8d565c952b686730 --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Sequencing2.scala @@ -0,0 +1,10 @@ +object Sequencing2 { + + def test(): Int = { + var x = 0 + x += 5 + x *= 10 + x + } ensuring(x => x == 50) + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Sequencing3.scala b/src/test/resources/regression/verification/xlang/valid/Sequencing3.scala new file mode 100644 index 0000000000000000000000000000000000000000..cd9d94c465a6b47d2d0ee080fd8693a41bb58bd0 --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Sequencing3.scala @@ -0,0 +1,18 @@ +object Sequencing3 { + + def f(x: Int): Int = { + require(x < 10) + x + } + + def test(): Int = { + var x = 0 + f(x) + x += 5 + f(x) + x += 5 + + x + } + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Sequencing4.scala b/src/test/resources/regression/verification/xlang/valid/Sequencing4.scala new file mode 100644 index 0000000000000000000000000000000000000000..fb6802192e0fd9e96531b4d886443e7a7efbdbdd --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Sequencing4.scala @@ -0,0 +1,10 @@ +object Sequencing4 { + + def test(): Int = { + var x = 5 + + {x = x + 1; x} + {x = x * 2; x} + + } ensuring(res => res == 18) + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Sequencing5.scala b/src/test/resources/regression/verification/xlang/valid/Sequencing5.scala new file mode 100644 index 0000000000000000000000000000000000000000..1a958a5ffd5ce919b6f3f0f49b29abee1ab83050 --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Sequencing5.scala @@ -0,0 +1,15 @@ +object Sequencing5 { + + + def test(): (Int, Int, Int) = { + var x = 5 + + ( + {x = x + 1; x}, + {x = x * 2; x}, + {x = x - 1; x} + ) + + } ensuring(res => res._1 == 6 && res._2 == 12 && res._3 == 11) + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Sequencing6.scala b/src/test/resources/regression/verification/xlang/valid/Sequencing6.scala new file mode 100644 index 0000000000000000000000000000000000000000..26dd91b8db8c1965991d1f49fa9eae9f549c69d8 --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Sequencing6.scala @@ -0,0 +1,21 @@ +object Sequencing6 { + + def f(x1: Int, x2: Int, x3: Int): Int = { + require(x1 == 6 && x2 == 12 && x3 == 11) + x3 + } + + def test(): Int = { + var x = 5 + + f( + {x = x + 1; x}, + {x = x * 2; x}, + {x = x - 1; x} + ) + + x + + } ensuring(res => res == 11) + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Sequencing7.scala b/src/test/resources/regression/verification/xlang/valid/Sequencing7.scala new file mode 100644 index 0000000000000000000000000000000000000000..f3628703bf5c0a073a05797b773246d8ff01960c --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Sequencing7.scala @@ -0,0 +1,17 @@ +package test.resources.regression.verification.xlang.valid + +object Sequencing7 { + + + def test(): Int = { + var x = 5 + + {x = x + 1; x} + {x = x * 2; x} + {x = x - 1; x} + + x + + } ensuring(res => res == 11) + +} diff --git a/src/test/resources/regression/verification/xlang/valid/Sequencing8.scala b/src/test/resources/regression/verification/xlang/valid/Sequencing8.scala new file mode 100644 index 0000000000000000000000000000000000000000..a37092ecffa8a91f589bc2ec7e32328d1b56ceca --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/Sequencing8.scala @@ -0,0 +1,11 @@ +object Sequencing8 { + + def test(): Int = { + var x = 5 + + (x = x + 1, (x = x * 2, (x = x - 1, x = x * 2))) + + x + } ensuring(res => res == 22) + +} diff --git a/src/test/resources/regression/verification/xlang/valid/WhileAsFun1.scala b/src/test/resources/regression/verification/xlang/valid/WhileAsFun1.scala new file mode 100644 index 0000000000000000000000000000000000000000..b81ea38598aed9b208da96bc88cd9385260b2e5b --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/WhileAsFun1.scala @@ -0,0 +1,25 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ +import leon.lang._ + +object WhileAsFun1 { + + + def counterN(n: Int): Int = { + require(n > 0) + + var i = 0 + def rec(): Unit = { + require(i >= 0 && i <= n) + if(i < n) { + i += 1 + rec() + } else { + () + } + } ensuring(_ => i >= 0 && i <= n && i >= n) + rec() + + i + } ensuring(_ == n) + +} diff --git a/src/test/resources/regression/verification/xlang/valid/WhileAsFun2.scala b/src/test/resources/regression/verification/xlang/valid/WhileAsFun2.scala new file mode 100644 index 0000000000000000000000000000000000000000..968aadfdbf044fd057d3628a6adffd4e917fdf67 --- /dev/null +++ b/src/test/resources/regression/verification/xlang/valid/WhileAsFun2.scala @@ -0,0 +1,33 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ +import leon.lang._ + +object WhileAsFun2 { + + + def counterN(n: Int): Int = { + require(n > 0) + + var counter = 0 + + def inc(): Unit = { + counter += 1 + } + + var i = 0 + def rec(): Unit = { + require(i >= 0 && counter == i && i <= n) + if(i < n) { + inc() + i += 1 + rec() + } else { + () + } + } ensuring(_ => i >= 0 && counter == i && i <= n && i >= n) + rec() + + + counter + } ensuring(_ == n) + +} diff --git a/src/test/scala/leon/genc/GenCSuite.scala b/src/test/scala/leon/genc/GenCSuite.scala new file mode 100644 index 0000000000000000000000000000000000000000..df6d30e5c0869cb6f05ba08195fab4ed73593d89 --- /dev/null +++ b/src/test/scala/leon/genc/GenCSuite.scala @@ -0,0 +1,219 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon +package genc + +import leon.test.LeonRegressionSuite + +import leon.frontends.scalac.ExtractionPhase +import leon.regression.verification.XLangVerificationSuite +import leon.purescala.Definitions.Program +import leon.utils.{ PreprocessingPhase, UniqueCounter } + +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.sys.process._ + +import org.scalatest.{ Args, Status } + +import java.io.ByteArrayInputStream +import java.nio.file.{ Files, Path } + +class GenCSuite extends LeonRegressionSuite { + + private val testDir = "regression/genc/" + private lazy val tmpDir = Files.createTempDirectory("genc") + private val ccflags = "-std=c99 -g -O0" + private val maxExecutionTime = 2 // seconds + + private val counter = new UniqueCounter[Unit] + counter.nextGlobal // Start with 1 + + private case class ExtendedContext(leon: LeonContext, tmpDir: Path, progName: String) + + // Tests are run as follows: + // - before mkTest is run, all valid test are verified using XLangVerificationSuite + // - The classic ExtractionPhase & PreprocessingPhase are run on all input files + // (this way the libraries are evaluated only once) + // - A Program is constructed for each input file + // - At this point no error should have occurred or something would be wrong + // with the extraction phases + // - For each Program P: + // + if P is expected to be convertible to C, then we make sure that: + // * the GenerateCPhase run without trouble, + // * the generated C code compiles using a C99 compiler without error, + // * and that, when run, the exit code is 0 + // + if P is expected to be non-convertible to C, then we make sure that: + // * the GenerateCPhase fails + private def mkTest(files: List[String], cat: String)(block: (ExtendedContext, Program) => Unit) = { + val extraction = + ExtractionPhase andThen + new PreprocessingPhase(true, true) + + val ctx = createLeonContext(files:_*) + + try { + val (_, ast) = extraction.run(ctx, files) + + val programs = { + val (user, lib) = ast.units partition { _.isMainUnit } + user map ( u => Program(u :: lib) ) + } + + for { prog <- programs } { + val name = prog.units.head.id.name + val ctx = createLeonContext(s"--o=$tmpDir/$name.c") + val xCtx = ExtendedContext(ctx, tmpDir, name) + + val displayName = s"$cat/$name.scala" + val index = counter.nextGlobal + + test(f"$index%3d: $displayName") { + block(xCtx, prog) + } + } + } catch { + case fe: LeonFatalError => + test("Compilation") { + fail(ctx, "Unexpected fatal error while setting up tests", fe) + } + } + } + + // Run a process with a timeout and return the status code + private def runProcess(pb: ProcessBuilder): Int = runProcess(pb.run) + private def runProcess(p: Process): Int = { + val f = Future(blocking(p.exitValue())) + try { + Await.result(f, duration.Duration(maxExecutionTime, "sec")) + } catch { + case _: TimeoutException => + p.destroy() + throw LeonFatalError("timeout reached") + } + } + + // Determine which C compiler is available + private def detectCompiler: Option[String] = { + val testCode = "int main() { return 0; }" + val testBinary = s"$tmpDir/test" + + // NOTE this code might print error on stderr when a non-existing compiler + // is used. It seems that even with a special ProcessLogger the RuntimeException + // is printed for some reason. + + def testCompiler(cc: String): Boolean = try { + def input = new ByteArrayInputStream(testCode.getBytes()) + val process = s"$cc $ccflags -o $testBinary -xc -" #< input #&& s"$testBinary" + runProcess(process) == 0 + } catch { + case _: java.lang.RuntimeException => false + } + + val knownCompiler = "cc" :: "clang" :: "gcc" :: "mingw" :: Nil + // Note that VS is not in the list as we cannot specify C99 dialect + + knownCompiler find testCompiler + } + + private def convert(xCtx: ExtendedContext)(prog: Program) = { + try { + GenerateCPhase(xCtx.leon, prog) + } catch { + case fe: LeonFatalError => + fail(xCtx.leon, "Convertion to C unexpectedly failed", fe) + } + } + + private def saveToFile(xCtx: ExtendedContext)(cprog: CAST.Prog) = { + CFileOutputPhase(xCtx.leon, cprog) + } + + private def compile(xCtx: ExtendedContext, cc: String)(unused: Unit) = { + val basename = s"${xCtx.tmpDir}/${xCtx.progName}" + val sourceFile = s"$basename.c" + val compiledProg = basename + + val process = Process(s"$cc $ccflags $sourceFile -o $compiledProg") + val status = runProcess(process) + + assert(status == 0, "Compilation of converted program failed") + } + + private def evaluate(xCtx: ExtendedContext)(unused: Unit) = { + val compiledProg = s"${xCtx.tmpDir}/${xCtx.progName}" + + // TODO memory limit + val process = Process(compiledProg) + + val status = runProcess(process) + assert(status == 0, s"Evaluation of converted program failed with status [$status]") + } + + private def forEachFileIn(cat: String)(block: (ExtendedContext, Program) => Unit) { + val fs = filesInResourceDir(testDir + cat, _.endsWith(".scala")).toList + + fs foreach { file => + assert(file.exists && file.isFile && file.canRead, + s"Benchmark ${file.getName} is not a readable file") + } + + val files = fs map { _.getPath } + + mkTest(files, cat)(block) + } + + protected def testDirectory(cc: String, dir: String) = forEachFileIn(dir) { (xCtx, prog) => + val converter = convert(xCtx) _ + val saver = saveToFile(xCtx) _ + val compiler = compile(xCtx, cc) _ + val evaluator = evaluate(xCtx) _ + + val pipeline = converter andThen saver andThen compiler andThen evaluator + + pipeline(prog) + } + + protected def testValid(cc: String) = testDirectory(cc, "valid") + protected def testUnverified(cc: String) = testDirectory(cc, "unverified"); + + protected def testInvalid() = forEachFileIn("invalid") { (xCtx, prog) => + intercept[LeonFatalError] { + GenerateCPhase(xCtx.leon, prog) + } + } + + class AltVerificationSuite(override val testDir: String) extends XLangVerificationSuite { + override def testAll() = testValid() // Test only the valid ones + + override def suiteName = "Verification Suite For GenC" + + // Add a timeout for the verification + override val optionVariants = List(List("--solvers=smt-z3,ground")) + } + + // Run verification suite as a nested suite + override def nestedSuites = { + // Use our test dir and not the one from XLangVerificationSuite + scala.collection.immutable.IndexedSeq(new AltVerificationSuite(testDir)) + } + + protected def testAll() = { + // Set C compiler according to the platform we're currently running on + detectCompiler match { + case Some(cc) => + testValid(cc) + testUnverified(cc) + case None => + test("dectecting C compiler") { fail("no C compiler found") } + } + + testInvalid() + } + + override def run(testName: Option[String], args: Args): Status = { + testAll() + super.run(testName, args) + } +} + diff --git a/src/test/scala/leon/integration/solvers/SolversSuite.scala b/src/test/scala/leon/integration/solvers/SolversSuite.scala index 7ba3913305d25006906faa9791fa719ffccd8121..d568e471f08eb4cf1a558675419daabd9e9c940a 100644 --- a/src/test/scala/leon/integration/solvers/SolversSuite.scala +++ b/src/test/scala/leon/integration/solvers/SolversSuite.scala @@ -32,56 +32,57 @@ class SolversSuite extends LeonTestSuiteWithProgram { ) else Nil) } - val types = Seq( - BooleanType, - UnitType, - CharType, + val types = Seq( + BooleanType, + UnitType, + CharType, RealType, - IntegerType, - Int32Type, - TypeParameter.fresh("T"), - SetType(IntegerType), - MapType(IntegerType, IntegerType), + IntegerType, + Int32Type, + StringType, + TypeParameter.fresh("T"), + SetType(IntegerType), + MapType(IntegerType, IntegerType), FunctionType(Seq(IntegerType), IntegerType), - TupleType(Seq(IntegerType, BooleanType, Int32Type)) - ) + TupleType(Seq(IntegerType, BooleanType, Int32Type)) + ) - val vs = types.map(FreshIdentifier("v", _).toVariable) + val vs = types.map(FreshIdentifier("v", _).toVariable) - // We need to make sure models are not co-finite + // We need to make sure models are not co-finite val cnstrs = vs.map(v => v.getType match { - case UnitType => - Equals(v, simplestValue(v.getType)) - case SetType(base) => - Not(ElementOfSet(simplestValue(base), v)) - case MapType(from, to) => - Not(Equals(MapApply(v, simplestValue(from)), simplestValue(to))) + case UnitType => + Equals(v, simplestValue(v.getType)) + case SetType(base) => + Not(ElementOfSet(simplestValue(base), v)) + case MapType(from, to) => + Not(Equals(MapApply(v, simplestValue(from)), simplestValue(to))) case FunctionType(froms, to) => Not(Equals(Application(v, froms.map(simplestValue)), simplestValue(to))) - case _ => - not(Equals(v, simplestValue(v.getType))) + case _ => + not(Equals(v, simplestValue(v.getType))) }) def checkSolver(solver: Solver, vs: Set[Variable], cnstr: Expr)(implicit fix: (LeonContext, Program)): Unit = { - try { - solver.assertCnstr(cnstr) + try { + solver.assertCnstr(cnstr) - solver.check match { - case Some(true) => - val model = solver.getModel - for (v <- vs) { - if (model.isDefinedAt(v.id)) { - assert(model(v.id).getType === v.getType, "Extracting value of type "+v.getType) - } else { - fail("Model does not contain "+v.id+" of type "+v.getType) + solver.check match { + case Some(true) => + val model = solver.getModel + for (v <- vs) { + if (model.isDefinedAt(v.id)) { + assert(model(v.id).getType === v.getType, s"Solver $solver - Extracting value of type "+v.getType) + } else { + fail(s"Solver $solver - Model does not contain "+v.id.uniqueName+" of type "+v.getType) + } } - } - case _ => - fail("Constraint "+cnstr.asString+" is unsat!?") + case _ => + fail(s"Solver $solver - Constraint "+cnstr.asString+" is unsat!?") + } + } finally { + solver.free() } - } finally { - solver.free - } } // Check that we correctly extract several types from solver models @@ -98,6 +99,6 @@ class SolversSuite extends LeonTestSuiteWithProgram { for ((v,cnstr) <- vs zip cnstrs) { val solver = new EnumerationSolver(fix._1, fix._2) checkSolver(solver, Set(v), cnstr) - } +} } } diff --git a/src/test/scala/leon/integration/solvers/StringSolverSuite.scala b/src/test/scala/leon/integration/solvers/StringSolverSuite.scala new file mode 100644 index 0000000000000000000000000000000000000000..7c0d3e13c044f4cd2491041cf28319986a6df42b --- /dev/null +++ b/src/test/scala/leon/integration/solvers/StringSolverSuite.scala @@ -0,0 +1,243 @@ +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 scala.collection.mutable.{HashMap => MMap} +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import org.scalatest.concurrent.Timeouts +import org.scalatest.concurrent.ScalaFutures +import org.scalatest.time.SpanSugar._ +import org.scalatest.FunSuite +import org.scalatest.concurrent.Timeouts +import org.scalatest.concurrent.ScalaFutures +import org.scalatest.time.SpanSugar._ + +/** + * @author Mikael + */ +class StringSolverSuite extends FunSuite with Matchers with ScalaFutures { + val k = new MMap[String, Identifier] + + val x = FreshIdentifier("x") + val y = FreshIdentifier("y") + val z = FreshIdentifier("z") + val u = FreshIdentifier("u") + val v = FreshIdentifier("v") + val w = FreshIdentifier("w") + k ++= List("x" -> x, "y" -> y, "z" -> z, "u" -> u, "v" -> v, "w" -> w) + + implicit class EquationMaker(lhs: String) { + def convertStringToStringFrom(s: String)(implicit idMap: MMap[String, Identifier]): StringSolver.StringForm = { + for(elem <- s.split("\\+").toList) yield { + if(elem.startsWith("\"")) { + Left(elem.substring(1, elem.length - 1)) + } else if(elem(0).isDigit) { + Left(elem) + } else { + val id = idMap.getOrElse(elem, { + val res = FreshIdentifier(elem) + idMap += elem -> res + res + }) + Right(id) + } + } + } + + def ===(rhs: String)(implicit k: MMap[String, Identifier]): StringSolver.Equation = { + (convertStringToStringFrom(lhs), rhs) + } + def ====(rhs: String)(implicit k: MMap[String, Identifier]): StringSolver.GeneralEquation = { + (convertStringToStringFrom(lhs), convertStringToStringFrom(rhs)) + } + } + + import StringSolver._ + + def m = MMap[String, Identifier]() + + test("simplifyProblem"){ + implicit val kk = k + simplifyProblem(List("x" === "1"), Map()) should equal (Some((Nil, Map(x -> "1")))) + simplifyProblem(List("y" === "23"), Map()) should equal (Some((Nil, Map(y -> "23")))) + simplifyProblem(List("1" === "1"), Map()) should equal (Some((Nil, Map()))) + simplifyProblem(List("2" === "1"), Map()) should equal (None) + simplifyProblem(List("x" === "1", "y+x" === "12"), Map()) should equal (Some((List("y+1" === "12"), Map(x -> "1")))) + } + + test("noLeftRightConstants") { + implicit val kk = k + noLeftRightConstants(List("x" === "1"), Map()) should equal (Some((List("x" === "1"), Map()))) + noLeftRightConstants(List("y+2" === "12"), Map()) should equal (Some((List("y" === "1"), Map()))) + noLeftRightConstants(List("3+z" === "31"), Map()) should equal (Some((List("z" === "1"), Map()))) + noLeftRightConstants(List("1+u+2" === "1, 2"), Map()) should equal (Some((List("u" === ", "), Map()))) + } + test("forwardStrategy") { + implicit val kk = k + forwardStrategy(List("x+3" === "123", "x+y" === "1245"), Map()) should equal (Some((Nil, Map(x -> "12", y -> "45")))) + forwardStrategy(List("y+z" === "4567", "x+3" === "123", "x+y" === "1245"), Map()) should equal (Some((Nil, Map(x -> "12", y -> "45", z -> "67")))) + } + + test("occurrences") { + occurrences("*?)*","*?)*?)**?)*???*") should equal (List(0, 3, 7)) + } + + test("repartitions") { + repartitions(List("*"), "?*???????") should equal (List(List(1))) + repartitions(List("*","*"), "?*???????") should equal (List()) + repartitions(List("1","2"), "?*1??2???") should equal (List(List(2, 5))) + repartitions(List("*","*"), "?*????*???*") should equal (List(List(1, 6), List(1, 10), List(6, 10))) + } + + test("simpleSplit") { + implicit val stats = Map(x -> 1, y -> 1) // Dummy stats + simpleSplit(List(), "").toList should equal (List(Map())) + simpleSplit(List(), "abc").toList should equal (Nil) + simpleSplit(List(x), "abc").toList should equal (List(Map(x -> "abc"))) + simpleSplit(List(x, y), "ab").toList should equal (List(Map(x -> "", y -> "ab"), Map(x -> "ab", y -> ""), Map(x -> "a", y -> "b"))) + simpleSplit(List(x, x), "ab").toList should equal (Nil) + simpleSplit(List(x, y, x), "aba").toList should equal (List(Map(x -> "", y -> "aba"), Map(x -> "a", y -> "b"))) + } + + test("simpleSplitPriority") { // Just guesses some values for y. + implicit val stats = Map(x -> 1, y -> 2) + simpleSplit(List(x, y, y, y), "a121212").toList should equal (List(Map(y -> "12"), Map(y -> "2"), Map(y -> ""))) + } + + + test("solve switch") { + implicit val kk = k + solve(List("x+y" === "1234", "y+x" === "1234")).toSet should equal (Set(Map(x -> "1234", y -> ""), Map(x -> "", y -> "1234"))) + solve(List("x+y" === "1234", "y+x" === "3412")).toList should equal (List(Map(x -> "12", y -> "34"))) + solve(List("x+y" === "1234", "y+x" === "4123")).toList should equal (List(Map(x -> "123", y -> "4"))) + solve(List("x+y" === "1234", "y+x" === "2341")).toList should equal (List(Map(x -> "1", y -> "234"))) + } + + test("solve inner") { + implicit val kk = k + solve(List("x+2+y" === "123")).toList should equal (List(Map(x -> "1", y -> "3"))) + solve(List("x+2+y+z" === "123")).toSet should equal (Set(Map(x -> "1", y -> "3", z -> ""), Map(x -> "1", y -> "", z -> "3"))) + solve(List("x+2+y" === "12324")).toSet should equal (Set(Map(x -> "1", y -> "324"), Map(x -> "123", y -> "4"))) + } + + test("isTransitivelyBounded") { + implicit val kk = k + isTransitivelyBounded(List("1" ==== "2")) should be(true) + isTransitivelyBounded(List("2" ==== "2")) should be(true) + isTransitivelyBounded(List("x+2" ==== "2")) should be(true) + isTransitivelyBounded(List("x+2" ==== "1")) should be(true) + isTransitivelyBounded(List("x+2" ==== "2+x")) should be(false) + isTransitivelyBounded(List("x+2" ==== "2+y")) should be(false) + isTransitivelyBounded(List("x+2" ==== "2+y", "y" ==== "1")) should be(true) + isTransitivelyBounded(List("x+2" ==== "2+x", "x" ==== "1")) should be(true) + isTransitivelyBounded(List("x+y+z" ==== "1234", "u+v" ==== "y+42+x")) should be(true) + } + + test("solveGeneralProblem") { + implicit val kk = k + solveGeneralProblem(List("x+y" ==== "12", "u+v" ==== "y+x")).toSet should equal ( + Set( + Map(x -> "", y -> "12", u -> "", v -> "12"), + Map(x -> "", y -> "12", u -> "1", v -> "2"), + Map(x -> "", y -> "12", u -> "12", v -> ""), + Map(x -> "1", y -> "2", u -> "", v -> "21"), + Map(x -> "1", y -> "2", u -> "2", v -> "1"), + Map(x -> "1", y -> "2", u -> "21", v -> ""), + Map(x -> "12", y -> "", u -> "", v -> "12"), + Map(x -> "12", y -> "", u -> "1", v -> "2"), + Map(x -> "12", y -> "", u -> "12", v -> "") + ) + ) + } + + test("constantPropagate") { + implicit val kk = k + val complexString = "abcdefmlkjsdqfmlkjqezpijbmkqjsdfmijzmajmpqjmfldkqsjmkj" + solve(List("w+5+x+y+z+u+v" === (complexString+"5"))).toList should equal ( + List(Map(w -> complexString, + x -> "", + y -> "", + z -> "", + u -> "", + v -> ""))) + } + + test("constantPropagate2") { + implicit val kk = k + val complexString = "abcdefmlkjsdqfmlkjqezpijbmkqjsdfmijzmajmpqjmfldkqsjmkj" + val complexString2 = complexString.reverse + val complexString3 = "flmqmslkjdqfmleomijgmlkqsjdmfijmqzoijdfmlqksjdofijmez" + solve(List("w+5+x+5+z" === (complexString+"5" + complexString2 + "5" + complexString3))).toList should equal ( + List(Map(w -> complexString, + x -> complexString2, + z -> complexString3))) + } + + test("ListInt") { + implicit val idMap = MMap[String, Identifier]() + val problem = List( + """const8+const4+"12"+const5+const+"-1"+const1+const3+const2+const6+const9""" === "(12, -1)", + """const8+const4+"1"+const5+const3+const6+const9""" === "(1)", + """const8+const7+const9""" === "()") + val solutions = solve(problem) + solutions should not be 'empty + val solution = solutions.head + errorcheck(problem, solution) should be(None) + } + + test("ListInt as List(...)") { + implicit val idMap = MMap[String, Identifier]() + val problem = List("const8" === "List(", + "const8+const7+const9" === "List()", + """const8+const4+"1"+const5+const3+const6+const9""" === "List(1)", + """const8+const4+"12"+const5+const+"-1"+const1+const3+const2+const6+const9""" === "List(12, -1)") + val solution = solve(problem) + solution should not be 'empty + val firstSolution = solution(0) + firstSolution(idMap("const8")) should equal("List(") + firstSolution(idMap("const4")) should equal("") + } + + test("solveJadProblem") { + val lhs = """const38+const34+const7+"T1"+const8+const3+const9+const5+"5"+const6+const10+const35+const30+const13+"T1"+const14+const31+const30+const7+"T2"+const8+const2+const9+const4+const10+const31+const30+const25+"T1"+const26+"Push"+const27+const20+"5"+const21+const28+const22+const29+const31+const30+const13+"T2"+const14+const31+const30+const25+"T2"+const26+"Pop"+const27+const19+const28+const23+"5"+const24+const29+const31+const33+const32+const32+const32+const32+const32+const36+const39=="T1: call Push(5)""" + implicit val idMap = MMap[String, Identifier]() + + val expected = """T1: call Push(5) +T1: internal +T2: call Pop() +T1: ret Push(5) +T2: internal +T2: ret Pop() -> 5""" + + val problem: Problem = List(lhs === expected) + + solve(problem) should not be 'empty + } + + test("SolvePropagateEquationProblem") { + implicit val idMap = MMap[String, Identifier]() + val problem = List( + "a+b+c+d" === "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", + "k+a+b+c+d" === "21234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891" + ) + val p = Future { solve(problem) } + assert(p.isReadyWithin(2 seconds), "Could not solve propagate") + p.futureValue should be('empty) + } + + test("SolveRightCheckingProblem") { + implicit val idMap = MMap[String, Identifier]() + val problem = List( + "u+v+w+a+b+c+d" === "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", + "u+v+w+k+a+k+b+c+d" === "21234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567892" + ) + val p = Future { solve(problem) } + assert(p.isReadyWithin(2 seconds), "Could not solve propagate") + p.futureValue should not be('empty) + } +} \ No newline at end of file diff --git a/src/test/scala/leon/regression/termination/TerminationSuite.scala b/src/test/scala/leon/regression/termination/TerminationSuite.scala index c58a4b0aceab03a00257ed48429028412e9cf643..6c2df0820a2b0f0c19ee393c12e1d70b36bbf233 100644 --- a/src/test/scala/leon/regression/termination/TerminationSuite.scala +++ b/src/test/scala/leon/regression/termination/TerminationSuite.scala @@ -38,7 +38,7 @@ class TerminationSuite extends LeonRegressionSuite { "verification/purescala/valid/InductiveQuantification.scala" ) - val t = if (ignored.exists(displayName.endsWith)) { + val t = if (ignored.exists(displayName.replaceAll("\\\\","/").endsWith)) { ignore _ } else { test _ diff --git a/src/test/scala/leon/regression/verification/NewSolversSuite.scala b/src/test/scala/leon/regression/verification/NewSolversSuite.scala index 15e02a693988e44be555c5e954be65cab4203086..5a340d1f870e019f33460fb6e386c50e58fbcbff 100644 --- a/src/test/scala/leon/regression/verification/NewSolversSuite.scala +++ b/src/test/scala/leon/regression/verification/NewSolversSuite.scala @@ -5,6 +5,7 @@ package leon.regression.verification import _root_.smtlib.interpreters._ import leon._ import leon.verification.VerificationPhase +import leon.solvers.SolverFactory /* @EK: Disabled for now as many tests fail class NewSolversSuite extends VerificationSuite { @@ -14,21 +15,9 @@ class NewSolversSuite extends VerificationSuite { val pipeBack = AnalysisPhase val optionVariants: List[List[String]] = { - val isCVC4Available = try { - CVC4Interpreter.buildDefault.free() - true - } catch { - case e: java.io.IOException => - false - } - - val isZ3Available = try { - Z3Interpreter.buildDefault.free() - true - } catch { - case e: java.io.IOException => - false - } + val isCVC4Available = SolverFactory.hasCVC4 + + val isZ3Available = SolverFactory.hasZ3 ( if (isCVC4Available) diff --git a/src/test/scala/leon/regression/verification/VerificationSuite.scala b/src/test/scala/leon/regression/verification/VerificationSuite.scala index b23fb0c4e3516121a1489752ed89dcb0835ee2d2..f2ae97880694baac498b94570f81beb1c21c422f 100644 --- a/src/test/scala/leon/regression/verification/VerificationSuite.scala +++ b/src/test/scala/leon/regression/verification/VerificationSuite.scala @@ -81,7 +81,7 @@ trait VerificationSuite extends LeonRegressionSuite { private[verification] def forEachFileIn(cat: String)(block: Output => Unit) { val fs = filesInResourceDir(testDir + cat, _.endsWith(".scala")).toList - fs foreach { file => + fs foreach { file => assert(file.exists && file.isFile && file.canRead, s"Benchmark ${file.getName} is not a readable file") } diff --git a/src/test/scala/leon/regression/verification/XLangVerificationSuite.scala b/src/test/scala/leon/regression/verification/XLangVerificationSuite.scala index 8511ebfea95d4d6a412d82922ffb1e6fb870d312..6fe7b07408500929063b4f57f24e1147a6cfce35 100644 --- a/src/test/scala/leon/regression/verification/XLangVerificationSuite.scala +++ b/src/test/scala/leon/regression/verification/XLangVerificationSuite.scala @@ -3,19 +3,14 @@ package leon.regression.verification import smtlib.interpreters.Z3Interpreter +import leon.solvers.SolverFactory // If you add another regression test, make sure it contains exactly one object, whose name matches the file name. // This is because we compile all tests from each folder together. class XLangVerificationSuite extends VerificationSuite { val optionVariants: List[List[String]] = { - val isZ3Available = try { - Z3Interpreter.buildDefault.free() - true - } catch { - case e: java.io.IOException => - false - } + val isZ3Available = SolverFactory.hasZ3 List( List(), @@ -30,3 +25,4 @@ class XLangVerificationSuite extends VerificationSuite { val testDir: String = "regression/verification/xlang/" override val desugarXLang = true } + diff --git a/src/test/scala/leon/regression/verification/purescala/PureScalaVerificationSuite.scala b/src/test/scala/leon/regression/verification/purescala/PureScalaVerificationSuite.scala index 569eb95df2f7f66a05d7a825765adb6123b4d854..6c450c1c8613f74c5a6679aaaf6035f59e593e66 100644 --- a/src/test/scala/leon/regression/verification/purescala/PureScalaVerificationSuite.scala +++ b/src/test/scala/leon/regression/verification/purescala/PureScalaVerificationSuite.scala @@ -4,6 +4,7 @@ package leon.regression.verification package purescala import smtlib.interpreters.{CVC4Interpreter, Z3Interpreter} +import leon.solvers.SolverFactory // If you add another regression test, make sure it contains one object whose name matches the file name // This is because we compile all tests from each folder together. @@ -11,21 +12,9 @@ abstract class PureScalaVerificationSuite extends VerificationSuite { val testDir = "regression/verification/purescala/" - val isZ3Available = try { - Z3Interpreter.buildDefault.free() - true - } catch { - case e: java.io.IOException => - false - } + val isZ3Available = SolverFactory.hasZ3 - val isCVC4Available = try { - CVC4Interpreter.buildDefault.free() - true - } catch { - case e: java.io.IOException => - false - } + val isCVC4Available = SolverFactory.hasCVC4 val opts: List[List[String]] = { List( diff --git a/src/test/scala/leon/test/LeonRegressionSuite.scala b/src/test/scala/leon/test/LeonRegressionSuite.scala index 58fdce2c6b789ddae0bbaee845f54ad6fe09de2c..4181fee97e7a4a38e3f30349458366ba62138d12 100644 --- a/src/test/scala/leon/test/LeonRegressionSuite.scala +++ b/src/test/scala/leon/test/LeonRegressionSuite.scala @@ -33,7 +33,7 @@ trait LeonRegressionSuite extends FunSuite with Timeouts { body } catch { case fe: LeonFatalError => - throw new TestFailedException("Uncaught LeonFatalError", fe, 5) + throw new TestFailedException("Uncaught LeonFatalError" + fe.msg.map(": " + _).getOrElse(""), fe, 5) } } } diff --git a/src/test/scala/leon/unit/utils/GraphsSuite.scala b/src/test/scala/leon/unit/utils/GraphsSuite.scala new file mode 100644 index 0000000000000000000000000000000000000000..984a427fafa5632aacacd2d667f5f1d0dec7a435 --- /dev/null +++ b/src/test/scala/leon/unit/utils/GraphsSuite.scala @@ -0,0 +1,275 @@ +/* Copyright 2009-2015 EPFL, Lausanne */ + +package leon.unit.utils + +import leon.test._ +import leon.purescala.Common._ +import leon.utils.Graphs._ + +class GraphsSuite extends LeonTestSuite { + abstract class Node + case object A extends Node + case object B extends Node + case object C extends Node + case object D extends Node + case object E extends Node + case object F extends Node + case object G extends Node + case object H extends Node + + import scala.language.implicitConversions + + implicit def tToEdge(t: (Node, Node)): SimpleEdge[Node] = { + SimpleEdge(t._1, t._2) + } + + def g1 = { + var g = new DiGraph[Node, SimpleEdge[Node]]() + + g ++= Set(A, B, C, D) + + g += A -> B + g += B -> A + + g += B -> C + + g += C -> D + g += D -> C + + // A D + // ↕ ↕ + // B → C + + g + } + + def g2 = { + var g = new DiGraph[Node, SimpleEdge[Node]]() + + g ++= Set(A, B, C, D) + + g += A -> B + g += B -> B + g += B -> C + g += C -> D + + // A → B → C → D + // ↻ + + g + } + + def g3 = { + var g = new DiGraph[Node, SimpleEdge[Node]]() + + g ++= Set(A, B, C, D, E, F, G, H) + + g += A -> B + g += B -> C + g += C -> D + + g += E -> A + g += A -> F + g += B -> F + g += F -> E + g += F -> G + + g += C -> G + g += G -> C + g += H -> G + + + // A → B → C → D + // ↑↘ ↓ ↕ + // E ← F → G ← H + + g + } + + def g4 = { + var g = new DiGraph[Node, SimpleEdge[Node]]() + + g ++= Set(A, B, C, D, E, F, G, H) + + g += A -> B + g += B -> C + g += C -> D + + g += A -> F + g += B -> F + g += F -> E + g += F -> G + + g += C -> G + g += H -> G + + + // A → B → C → D + // ↘ ↓ ↓ + // E ← F → G ← H + + g + } + + def g5 = { + var g = new DiGraph[Node, SimpleEdge[Node]]() + + g ++= Set(A, B, C, D, E, F, G, H) + + g += A -> B + g += B -> C + + g += A -> D + g += D -> E + + + // A → B → C F G H + // ↘ + // D → E + + g + } + + + test("Graphs basic 1") { ctx => + val g = g1 + assert(g.N.size === 4) + assert(g.E.size === 5) + + assert(g.outEdges(A) === Set(SimpleEdge(A, B))) + assert(g.outEdges(B) === Set(SimpleEdge(B, A), SimpleEdge(B, C))) + + assert(g.inEdges(B) === Set(SimpleEdge(A, B))) + assert(g.inEdges(C) === Set(SimpleEdge(B, C), SimpleEdge(D, C))) + + assert(g.edgesBetween(A, B).size === 1) + } + + test("Graphs sinks/sources 1") { ctx => + val g = g1 + + assert(g.sources == Set()) + assert(g.sinks == Set()) + } + + test("Graphs sinks/sources 2") { ctx => + val g = g2 + + assert(g.N.size === 4) + assert(g.E.size === 4) + + assert(g.sources == Set(A)) + assert(g.sinks == Set(D)) + } + + test("Graphs SCC 1") { ctx => + val g = g1 + + val gs = g.stronglyConnectedComponents + + assert(gs.N.size === 2) + assert(gs.E.size === 1) + } + + test("Graphs SCC 2") { ctx => + val g = g2 + + val gs = g.stronglyConnectedComponents + + assert(gs.N.size === 4) + assert(gs.E.size === 3) + } + + test("Graphs SCC 3") { ctx => + val g = g3 + + val gs = g.stronglyConnectedComponents + + assert(gs.N === Set(Set(A, B, E, F), Set(C, G), Set(D), Set(H))) + } + + def assertBefore[T](s: Seq[T])(n1: T, n2: T) { + assert(s.indexOf(n1) < s.indexOf(n2), s"Node '$n1' should be before '$n2'"); + } + + test("Graphs top-sort 1") { ctx => + val g = g4 + + + val seq = g.topSort + + val before = assertBefore(seq)_ + + before(A, B) + before(B, F) + before(A, C) + } + + test("Graphs top-sort 2 (cyclic graph fails)") { ctx => + val g = g1 + + intercept[IllegalArgumentException] { + g.topSort + } + } + + test("Graphs top-sort 3 (SCC is acyclic)") { ctx => + val g = g3 + + val gs = g.stronglyConnectedComponents + + val c1: Set[Node] = Set(A, B, E, F) + val c2: Set[Node] = Set(C, G) + val c3: Set[Node] = Set(D) + val c4: Set[Node] = Set(H) + + + val ns = gs.topSort + + val before = assertBefore(ns)_ + before(c1, c2) + before(c2, c3) + before(c4, c2) + } + + test("Graphs DFS") { ctx => + val g = g5 + + var visited = List[Node]() + g.depthFirstSearch(A) { n => + visited ::= n + } + visited = visited.reverse + + def isBefore(a: Node, b: Node) = visited.indexOf(a) < visited.indexOf(b) + + assert(!isBefore(B, D) || isBefore(C, E)) + assert(!isBefore(D, B) || isBefore(E, C)) + } + + test("Graphs BFS") { ctx => + val g = g5 + + var visited = List[Node]() + g.breadthFirstSearch(A) { n => + visited ::= n + } + visited = visited.reverse + + def isBefore(a: Node, b: Node) = visited.indexOf(a) < visited.indexOf(b) + + assert(isBefore(B, E)) + assert(isBefore(D, C)) + } + + test("Graphs pred/succ 1") { ctx => + val g = g2 + + assert(g.succ(B) == Set(B, C)) + assert(g.pred(B) == Set(B, A)) + + assert(g.transitiveSucc(B) == Set(B, C, D)) + assert(g.transitivePred(B) == Set(A, B)) + assert(g.transitivePred(C) == Set(A, B)) + } +} diff --git a/testcases/repair/Compiler/Compiler4.scala b/testcases/repair/Compiler/Compiler4.scala index f49670ddc31632343fea32f1b8d39ca288f214d4..6323496df930172e84b71bf5c55006eb33767c6a 100644 --- a/testcases/repair/Compiler/Compiler4.scala +++ b/testcases/repair/Compiler/Compiler4.scala @@ -150,7 +150,7 @@ object Desugar { case Trees.LessThan(lhs, rhs) => LessThan(desugar(lhs), desugar(rhs)) case Trees.And (lhs, rhs) => Ite(desugar(lhs), desugar(rhs), Literal(0)) case Trees.Or (lhs, rhs) => Ite(desugar(lhs), Literal(1), desugar(rhs)) - case Trees.Not(e) => Ite(desugar(e), Literal(1), Literal(1)) // FIMXE should be 0 + case Trees.Not(e) => Ite(desugar(e), Literal(1), Literal(1)) // FIXME should be 0 case Trees.Eq(lhs, rhs) => Eq(desugar(lhs), desugar(rhs)) case Trees.Ite(cond, thn, els) => Ite(desugar(cond), desugar(thn), desugar(els)) diff --git a/testcases/repair/Compiler/Compiler6.scala b/testcases/repair/Compiler/Compiler6.scala index 44bf523ec7aff63376ba24312594045296536dcf..dc633b1ae3d4d929e262ea44d6466a41ee4babf0 100644 --- a/testcases/repair/Compiler/Compiler6.scala +++ b/testcases/repair/Compiler/Compiler6.scala @@ -207,6 +207,8 @@ object Simplifier { case e => e } } ensuring { - res => eval(res) == eval(e) + res => eval(res) == eval(e) && ((e, res) passes { + case Plus(IntLiteral(BigInt(0)), IntLiteral(a)) => IntLiteral(a) + }) } } diff --git a/testcases/repair/DaysToYears/DaysToYears.scala b/testcases/repair/DaysToYears/DaysToYears.scala index bb6c518b3b743ea02d0188c8a9b1a802e6685e16..3a71a49602ff9d818df2cf2e1d2476a78f9e0b3a 100644 --- a/testcases/repair/DaysToYears/DaysToYears.scala +++ b/testcases/repair/DaysToYears/DaysToYears.scala @@ -1,10 +1,11 @@ +import leon.annotation._ import leon.lang._ object DaysToYears { val base : Int = 1980 - + def isLeapYear(y : Int): Boolean = y % 4 == 0 - + def daysToYears(days : Int): Int = { require(days > 0) daysToYears1(base, days)._1 @@ -17,16 +18,17 @@ object DaysToYears { else if (days > 365 && !isLeapYear(year)) daysToYears1(year + 1, days - 365) else (year, days) - } ensuring { res => + } ensuring { res => res._2 <= 366 && - res._2 > 0 && + res._2 > 0 && res._1 >= base && - (((year,days), res) passes { + (((year,days), res) passes { case (1980, 366 ) => (1980, 366) case (1980, 1000) => (1982, 269) }) - } + } + @ignore def main(args : Array[String]) = { println(daysToYears1(base, 10593 )) println(daysToYears1(base, 366 )) diff --git a/testcases/repair/DaysToYears/DaysToYears1.scala b/testcases/repair/DaysToYears/DaysToYears1.scala index 13eec09ae4ee1a611fc5a7d916ed92e061086112..1ef2d337989c7dc1a718637777d6e97726d7a276 100644 --- a/testcases/repair/DaysToYears/DaysToYears1.scala +++ b/testcases/repair/DaysToYears/DaysToYears1.scala @@ -1,10 +1,11 @@ +import leon.annotation._ import leon.lang._ object DaysToYears { val base : Int = 1980 - + def isLeapYear(y : Int): Boolean = y % 4 == 0 - + def daysToYears(days : Int): Int = { require(days > 0) daysToYears1(base, days)._1 @@ -17,17 +18,18 @@ object DaysToYears { else if (days > 365 && !isLeapYear(year)) daysToYears1(year, days - 365) // FIXME forgot +1 else (year, days) - } ensuring { res => + } ensuring { res => res._2 <= 366 && - res._2 > 0 && + res._2 > 0 && res._1 >= base && - (((year,days), res) passes { + (((year,days), res) passes { case (1999, 14 ) => (1999, 14) case (1980, 366) => (1980, 366) case (1981, 366) => (1982, 1) }) - } + } + @ignore def main(args : Array[String]) = { println(daysToYears1(base, 10593 )) println(daysToYears1(base, 366 )) diff --git a/testcases/repair/DaysToYears/DaysToYears2.scala b/testcases/repair/DaysToYears/DaysToYears2.scala index de0d31a707bda925004cca4dd3af1784995d0051..40396f05c6c3e40c4cfd0beccee02ed7d4c59553 100644 --- a/testcases/repair/DaysToYears/DaysToYears2.scala +++ b/testcases/repair/DaysToYears/DaysToYears2.scala @@ -1,10 +1,11 @@ +import leon.annotation._ import leon.lang._ object DaysToYears { val base : Int = 1980 - + def isLeapYear(y : Int): Boolean = y % 4 == 0 - + def daysToYears(days : Int): Int = { require(days > 0) daysToYears1(base, days)._1 @@ -16,17 +17,18 @@ object DaysToYears { daysToYears1(year + 1, days - 366) // TODO this branch cannot be solved although it is correct because it depends on the erroneous branch else if (days > 365 && !isLeapYear(year)) daysToYears1(year + 1, days - 365) - else (year + 1, days) // FIXME +1 - } ensuring { res => + else (year + 1, days) // FIXME +1 + } ensuring { res => res._2 <= 366 && - res._2 > 0 && + res._2 > 0 && res._1 >= base && - (((year,days), res) passes { + (((year,days), res) passes { case (1980, 366 ) => (1980, 366) case (1980, 1000) => (1982, 269) }) - } + } + @ignore def main(args : Array[String]) = { println(daysToYears1(base, 10593 )) println(daysToYears1(base, 366 )) diff --git a/testcases/repair/Heap/Heap3.scala b/testcases/repair/Heap/Heap3.scala index 3305b5d15a0731bd3aeaa1269c2404807f49a266..8e3013399534679c58221bd4d5547a14a76d9b57 100644 --- a/testcases/repair/Heap/Heap3.scala +++ b/testcases/repair/Heap/Heap3.scala @@ -51,7 +51,7 @@ object Heaps { case Node(v, l, r) => heapSize(l) + 1 + heapSize(r) }} ensuring(_ >= 0) - private def merge(h1: Heap, h2: Heap) : Heap = { + def merge(h1: Heap, h2: Heap) : Heap = { require( hasLeftistProperty(h1) && hasLeftistProperty(h2) && hasHeapProperty(h1) && hasHeapProperty(h2) diff --git a/testcases/repair/List/List13.scala b/testcases/repair/List/List13.scala index 914f0caacd49b42448ec428df24af6ea155c53d2..1093efc3dc951ee8307c2257d8dcec2d154c9ecd 100644 --- a/testcases/repair/List/List13.scala +++ b/testcases/repair/List/List13.scala @@ -78,14 +78,22 @@ sealed abstract class List[T] { } } - def drop(i: BigInt): List[T] = (this, i) match { - case (Nil(), _) => Nil() - case (Cons(h, t), i) => - if (i == 0) { - Cons(h, t) - } else { - t.drop(i) // FIXME Should be -1 - } + def drop(i: BigInt): List[T] = { + require(i >= 0) + (this, i) match { + case (Nil(), _) => Nil[T]() + case (Cons(h, t), i) => + if (i == 0) { + Cons[T](h, t) + } else { + t.drop(i) // FIXME Should be -1 + } + } + } ensuring { (res: List[T]) => + ((this, i), res) passes { + case (Cons(a, Cons(b, Nil())), BigInt(1)) => Cons(b, Nil()) + case (Cons(a, Cons(b, Nil())), BigInt(2)) => Nil() + } } def slice(from: BigInt, to: BigInt): List[T] = { diff --git a/testcases/repair/List/List4.scala b/testcases/repair/List/List4.scala index b6cb5e276741be29d86da86b7c43cbe7e1639c62..1ee180b3331e9cc51df34207b8ab5b6fec9b710b 100644 --- a/testcases/repair/List/List4.scala +++ b/testcases/repair/List/List4.scala @@ -78,16 +78,18 @@ sealed abstract class List[T] { } } - def drop(i: BigInt): List[T] = { (this, i) match { - case (Nil(), _) => Nil[T]() - case (Cons(h, t), i) => - // FIXME - //if (i == 0) { - // Cons(h, t) - //} else { - t.drop(i-1) - //} - }} ensuring { ((this, i), _) passes { + def drop(i: BigInt): List[T] = { + (this, i) match { + case (Nil(), _) => Nil[T]() + case (Cons(h, t), i) => + // FIXME + if (i != 0) { + Cons(h, t) + } else { + t.drop(i-1) + } + } + } ensuring { ((this, i), _) passes { case (Cons(_, Nil()), BigInt(42)) => Nil() case (l@Cons(_, _), BigInt(0)) => l case (Cons(a, Cons(b, Nil())), BigInt(1)) => Cons(b, Nil()) diff --git a/testcases/repair/List/List5.scala b/testcases/repair/List/List5.scala index 743cced6533536eac8c03bbc3f2381378b8190b8..f13f91da3d55d7cdcf0eb0d3ef41027e1a8e94d8 100644 --- a/testcases/repair/List/List5.scala +++ b/testcases/repair/List/List5.scala @@ -97,11 +97,11 @@ sealed abstract class List[T] { case Nil() => Nil[T]() case Cons(h, t) => val r = t.replace(from, to) - //if (h == from) { FIXME - // Cons(to, r) - //} else { + if (h != from) { // FIXME + Cons(to, r) + } else { Cons(h, r) - //} + } }} ensuring { res => (((this.content -- Set(from)) ++ (if (this.content contains from) Set(to) else Set[T]())) == res.content) && res.size == this.size diff --git a/testcases/repair/List/List7.scala b/testcases/repair/List/List7.scala index 7eef0585e9cb467f75da05fdb7957db4f9838afa..41972f4cefffa0bd63245856b2d31b0cfe241c10 100644 --- a/testcases/repair/List/List7.scala +++ b/testcases/repair/List/List7.scala @@ -191,7 +191,7 @@ sealed abstract class List[T] { } }} ensuring { res => if (this.content contains e) { - res.isDefined && this.size > res.get && this.apply(res.get) == e + res.isDefined && this.size > res.get && this.apply(res.get) == e && res.get >= 0 } else { res.isEmpty } diff --git a/testcases/repair/List/List8.scala b/testcases/repair/List/List8.scala index faf7571e9582f652e3ba494b11eb34c5923d4dac..02f5294c26e7f3b4f29e307db07186927bfff82c 100644 --- a/testcases/repair/List/List8.scala +++ b/testcases/repair/List/List8.scala @@ -191,7 +191,7 @@ sealed abstract class List[T] { } }} ensuring { res => if (this.content contains e) { - res.isDefined && this.size > res.get && this.apply(res.get) == e + res.isDefined && this.size > res.get && this.apply(res.get) == e && res.get >= 0 } else { res.isEmpty } diff --git a/testcases/repair/List/List9.scala b/testcases/repair/List/List9.scala index 12a8152b1701fe993f36c33392eebda724ec4aad..104e2ecabdfa0ac8bef8cc179ff68acc1a7c6ccc 100644 --- a/testcases/repair/List/List9.scala +++ b/testcases/repair/List/List9.scala @@ -191,7 +191,7 @@ sealed abstract class List[T] { } }} ensuring { res => if (this.content contains e) { - res.isDefined && this.size > res.get && this.apply(res.get) == e + res.isDefined && this.size > res.get && this.apply(res.get) == e && res.get >= 0 } else { res.isEmpty } diff --git a/testcases/repair/MergeSort/MergeSort1.scala b/testcases/repair/MergeSort/MergeSort1.scala index 88b0319086023675c766cf8f6c74ccd420d2a182..340dfbc46077306b64752e73cebeb725ef40a781 100644 --- a/testcases/repair/MergeSort/MergeSort1.scala +++ b/testcases/repair/MergeSort/MergeSort1.scala @@ -5,7 +5,7 @@ object MergeSort { def split(l : List[BigInt]) : (List[BigInt],List[BigInt]) = { l match { case Cons(a, Cons(b, t)) => val (rec1, rec2) = split(t) - (rec1, rec2) // FIXME: Forgot a,b + (rec1, Cons(b, rec2)) // FIXME: Forgot a case other => (other, Nil[BigInt]()) }} ensuring { res => val (l1, l2) = res diff --git a/testcases/repair/runTests.sh b/testcases/repair/runTests.sh index 55ca5cdf9f74828855e9ce85eb8aa18bf8ba3169..896054f51dc75131d690b14dfa00dd8a3a747c46 100755 --- a/testcases/repair/runTests.sh +++ b/testcases/repair/runTests.sh @@ -15,53 +15,56 @@ echo "################################" >> $summaryLog echo "# Category, File, function, p.S, fuS, foS, Tms, Fms, Rms, verif?" >> $summaryLog #All benchmarks: -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=desugar testcases/repair/Compiler/Compiler1.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=desugar testcases/repair/Compiler/Compiler2.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=desugar testcases/repair/Compiler/Compiler3.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=desugar testcases/repair/Compiler/Compiler4.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=desugar testcases/repair/Compiler/Compiler5.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=simplify testcases/repair/Compiler/Compiler6.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=simplify testcases/repair/Compiler/Compiler7.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap3.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap4.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap5.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap6.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap7.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=insert testcases/repair/Heap/Heap8.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=makeN testcases/repair/Heap/Heap9.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap10.scala | tee -a $fullLog +echo "=====================================================================" >> repair-report.txt -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=nnf testcases/repair/PropLogic/PropLogic1.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=nnf testcases/repair/PropLogic/PropLogic2.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=nnf testcases/repair/PropLogic/PropLogic3.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=nnf testcases/repair/PropLogic/PropLogic4.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=nnf testcases/repair/PropLogic/PropLogic5.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=desugar testcases/repair/Compiler/Compiler1.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=desugar testcases/repair/Compiler/Compiler2.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=desugar testcases/repair/Compiler/Compiler3.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=desugar testcases/repair/Compiler/Compiler4.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=desugar testcases/repair/Compiler/Compiler5.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=simplify testcases/repair/Compiler/Compiler6.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=pad testcases/repair/List/List1.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=++ testcases/repair/List/List2.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=:+ testcases/repair/List/List3.scala | tee -a $fullLog -./leon --repair --timeout=30 --functions=drop testcases/repair/List/List4.scala | tee -a $fullLog -./leon --repair --timeout=30 --functions=replace testcases/repair/List/List5.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=count testcases/repair/List/List6.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=find testcases/repair/List/List7.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=find testcases/repair/List/List8.scala | tee -a $fullLog -./leon --repair --timeout=30 --functions=find testcases/repair/List/List9.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=size testcases/repair/List/List10.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=sum testcases/repair/List/List11.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=- testcases/repair/List/List12.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=drop testcases/repair/List/List13.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=power testcases/repair/Numerical/Numerical1.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=moddiv testcases/repair/Numerical/Numerical3.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap3.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap4.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap5.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap6.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap7.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/Heap/Heap10.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=insert testcases/repair/Heap/Heap8.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=makeN testcases/repair/Heap/Heap9.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=split testcases/repair/MergeSort/MergeSort1.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/MergeSort/MergeSort2.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/MergeSort/MergeSort3.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/MergeSort/MergeSort4.scala | tee -a $fullLog -./leon --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/MergeSort/MergeSort5.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=nnf testcases/repair/PropLogic/PropLogic1.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=nnf testcases/repair/PropLogic/PropLogic2.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=nnf testcases/repair/PropLogic/PropLogic3.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=nnf testcases/repair/PropLogic/PropLogic4.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=nnf testcases/repair/PropLogic/PropLogic5.scala | tee -a $fullLog + +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=pad testcases/repair/List/List1.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=++ testcases/repair/List/List2.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=:+ testcases/repair/List/List3.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --functions=replace testcases/repair/List/List5.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=count testcases/repair/List/List6.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=find testcases/repair/List/List7.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=find testcases/repair/List/List8.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --functions=find testcases/repair/List/List9.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=size testcases/repair/List/List10.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=sum testcases/repair/List/List11.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=- testcases/repair/List/List12.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --functions=drop testcases/repair/List/List4.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=drop testcases/repair/List/List13.scala | tee -a $fullLog + +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=power testcases/repair/Numerical/Numerical1.scala | tee -a $fullLog +#./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=moddiv testcases/repair/Numerical/Numerical3.scala | tee -a $fullLog + +./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=split testcases/repair/MergeSort/MergeSort1.scala | tee -a $fullLog +./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/MergeSort/MergeSort2.scala | tee -a $fullLog +./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/MergeSort/MergeSort3.scala | tee -a $fullLog +./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/MergeSort/MergeSort4.scala | tee -a $fullLog +./leon --debug=report --repair --timeout=30 --solvers=fairz3,enum --functions=merge testcases/repair/MergeSort/MergeSort5.scala | tee -a $fullLog # Average results -cat $log >> $summaryLog -awk '{ total1 += $7; total2 += $8; total3 += $9; count++ } END { printf "#%74s Avg: %5d, %5d, %5d\n\n", "", total1/count, total2/count, total3/count }' $log >> $summaryLog +#cat $log >> $summaryLog +#awk '{ total1 += $7; total2 += $8; total3 += $9; count++ } END { printf "#%74s Avg: %5d, %5d, %5d\n\n", "", total1/count, total2/count, total3/count }' $log >> $summaryLog diff --git a/testcases/runtime/SquareRoot.scala b/testcases/runtime/SquareRoot.scala index 87b3e985a6209da18606c28ca189440043b6e25d..95bdf52f6c9e0967da3af474ba70cadc30e9cc45 100644 --- a/testcases/runtime/SquareRoot.scala +++ b/testcases/runtime/SquareRoot.scala @@ -1,3 +1,4 @@ +import leon.annotation._ import leon.lang._ import leon.lang.synthesis._ @@ -23,6 +24,7 @@ object SquareRoot { } } + @ignore def main(a: Array[String]) { isqrt2(42) } diff --git a/testcases/stringrender/BinaryTreeRender.scala b/testcases/stringrender/BinaryTreeRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..3e939217dee43b7963fc2d784a25b597e05bc3de --- /dev/null +++ b/testcases/stringrender/BinaryTreeRender.scala @@ -0,0 +1,107 @@ +/** + * Name: BinaryTreeRender.scala + * Creation: 14.10.2015 + * Author: Mikael Mayer (mikael.mayer@epfl.ch) + * Comments: Binary tree rendering specifications. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object TreeRender { + sealed abstract class Tree[T] + case class Node[T](left: Tree[T], i: T, right: Tree[T]) extends Tree[T] + case class Leaf[T]() extends Tree[T] + + /** Synthesis by example specs */ + @inline def psStandard(s: Tree[Int]) = (res: String) => (s, res) passes { + case Node(Node(Leaf[Int](), 2, Leaf[Int]()), 1, Node(Leaf[Int](), -3, Leaf[Int]())) => "Node(Node(Leaf(), 2, Leaf()), 1, Node(Leaf(), -3, Leaf()))" + case Node(Leaf[Int](), 1, Leaf[Int]()) => "Node(Leaf(), 1, Leaf())" + case Leaf[Int]() => "Leaf()" + } + + @inline def psRemoveNode(s: Tree[Int]) = (res: String) => (s, res) passes { + case Node(Node(Leaf(), 2, Leaf()), 1, Node(Leaf(), -3, Leaf())) => "((Leaf(), 2, Leaf()), 1, (Leaf(), -3, Leaf()))" + case Node(Leaf(), 1, Leaf()) => "(Leaf(), 1, Leaf())" + case Leaf() => "Leaf()" + } + + @inline def psRemoveLeaf(s: Tree[Int]) = (res: String) => (s, res) passes { + case Node(Node(Leaf(), 2, Leaf()), 1, Node(Leaf(), -3, Leaf())) => "((, 2, ), 1, (, -3, ))" + case Node(Leaf(), 1, Leaf()) => "(, 1, )" + case Leaf() => "" + } + + @inline def psRemoveComma(s: Tree[Int]) = (res: String) => (s, res) passes { + case Node(Node(Leaf(), 2, Leaf()), 1, Node(Leaf(), -3, Leaf())) => "((2), 1, (-3))" + case Node(Leaf(), 1, Node(Leaf(), 2, Leaf())) => "(1, (2))" + case Node(Node(Leaf(), 2, Leaf()), 1, Leaf()) => "((2), 1)" + case Leaf() => "" + } + + @inline def psRemoveParentheses(s: Tree[Int]) = (res: String) => (s, res) passes { + case Node(Node(Leaf(), 2, Leaf()), 1, Node(Leaf(), -3, Leaf())) => "(2), 1, (-3)" + case Node(Leaf(), 1, Node(Leaf(), 2, Leaf())) => "1, (2)" + case Node(Node(Leaf(), 2, Leaf()), 1, Leaf()) => "(2), 1" + case Leaf() => "" + } + + @inline def psPrefix(s: Tree[Int]) = (res: String) => (s, res) passes { + case Node(Node(Leaf(), 2, Leaf()), 1, Node(Leaf(), -3, Leaf())) => "1 (2) (-3)" + case Node(Leaf(), 1, Node(Leaf(), 2, Leaf())) => "1 () (2)" + case Node(Node(Leaf(), 2, Leaf()), 1, Leaf()) => "1 (2) ()" + case Leaf() => "()" + } + + @inline def psLispLike(s: Tree[Int]) = (res: String) => (s, res) passes { + case Node(Node(Leaf(), 2, Leaf()), 1, Node(Leaf(), -3, Leaf())) => "(1 (2) (-3))" + case Node(Leaf(), 1, Node(Leaf(), 2, Leaf())) => "(1 () (2))" + case Node(Node(Leaf(), 2, Leaf()), 1, Leaf()) => "(1 (2) ())" + case Leaf() => "()" + } + + @inline def psSuffix(s: Tree[Int]) = (res: String) => (s, res) passes { + case Node(Node(Leaf(), 2, Leaf()), 1, Node(Leaf(), -3, Leaf())) => "(2) (-3) 1" + case Node(Leaf(), 1, Node(Leaf(), 2, Leaf())) => "() (2) 1" + case Node(Node(Leaf(), 2, Leaf()), 1, Leaf()) => "(2) () 1" + case Leaf() => "()" + } + + ////////////////////////////////////////////// + // Non-incremental examples: pure synthesis // + ////////////////////////////////////////////// + def synthesizeStandard(s: Tree[Int]): String = { + ???[String] + } ensuring psStandard(s) + + def synthesizeRemoveNode(s: Tree[Int]): String = { + ???[String] + } ensuring psRemoveNode(s) + + def synthesizeRemoveLeaf(s: Tree[Int]): String = { + ???[String] + } ensuring psRemoveLeaf(s) + + def synthesizeRemoveComma(s: Tree[Int]): String = { + ???[String] + } ensuring psRemoveComma(s) + + def synthesizeRemoveParentheses(s: Tree[Int]): String = { + ???[String] + } ensuring psRemoveParentheses(s) + + def synthesizePrefix(s: Tree[Int]): String = { + ???[String] + } ensuring psPrefix(s) + + def synthesizeLispLike(s: Tree[Int]): String = { + ???[String] + } ensuring psLispLike(s) + + def synthesizeSuffix(s: Tree[Int]): String = { + ???[String] + } ensuring psSuffix(s) +} \ No newline at end of file diff --git a/testcases/stringrender/ChoiceRender.scala b/testcases/stringrender/ChoiceRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..3547d6fdcdc45dc00539f28b8610d80de798af38 --- /dev/null +++ b/testcases/stringrender/ChoiceRender.scala @@ -0,0 +1,31 @@ +/** + * Name: TupleWrapperRender.scala + * Creation: 15.10.2015 + * Author: Mikaël Mayer (mikael.mayer@epfl.ch) + * Comments: Tuple rendering specifications. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object ChoiceRender { + sealed abstract class Thread + case class T1() extends Thread + case class T2() extends Thread + case class T3() extends Thread + + sealed abstract class Action + case class Call(t: Thread, args: Option[Int]) extends Action + + @inline def psStandard(s: Action) = (res: String) => (s, res) passes { + case Call(T1(),None()) => "T1 call" + case Call(T1(),Some(2)) => "T1 -> 2" + } + + def synthesizeStandard(s: Action): String = { + ???[String] + } ensuring psStandard(s) +} \ No newline at end of file diff --git a/testcases/stringrender/ConstantRender.scala b/testcases/stringrender/ConstantRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..f208fdb2b09a09846307340fc79c778f8fa8392b --- /dev/null +++ b/testcases/stringrender/ConstantRender.scala @@ -0,0 +1,43 @@ +/** + * Name: TupleWrapperRender.scala + * Creation: 15.10.2015 + * Author: Mikaël Mayer (mikael.mayer@epfl.ch) + * Comments: Tuple rendering specifications. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object ConstantRender { + sealed abstract class Thread + case class T1() extends Thread + case class T2() extends Thread + case class T3() extends Thread + + @inline def psStandard(s: Thread) = (res: String) => (s, res) passes { + case T1() => "T1" + } + + @inline def psSuffix(s: Thread) = (res: String) => (s, res) passes { + case T1() => "T1()" + } + + @inline def psPrefix(s: Thread) = (res: String) => (s, res) passes { + case T1() => "Call: T1()" + } + + def synthesizeStandard(s: Thread): String = { + ???[String] + } ensuring psStandard(s) + + def synthesizeSuffix(s: Thread): String = { + ???[String] + } ensuring psSuffix(s) + + def synthesizePrefix(s: Thread): String = { + ???[String] + } ensuring psPrefix(s) +} \ No newline at end of file diff --git a/testcases/stringrender/Default.scala b/testcases/stringrender/Default.scala new file mode 100644 index 0000000000000000000000000000000000000000..baab8ba1801da72afeca7ed6cb6cfc073f381454 --- /dev/null +++ b/testcases/stringrender/Default.scala @@ -0,0 +1,55 @@ +/** + * Name: Default.scala + * Creation: 15.10.2015 + * Author: Mikaël Mayer (mikael.mayer@epfl.ch) + * Comments: Benchmark for solving default string transformation problems. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +/** + * @author Mikael + */ +object Default { + @inline def psIntStandard(s: Int) = (res: String) => (s, res) passes { + case 1 => "1" + case 42 => "42" + case -3 => "-3" + } + + @inline def psBigIntStandard(s: BigInt) = (res: String) => (s, res) passes { + case BigInt(1) => "1" + case BigInt(42) => "42" + case BigInt(-3) => "-3" + } + + @inline def psBooleanStandard(s: Boolean) = (res: String) => (s, res) passes { + case true => "true" + case false => "false" + } + + @inline def psBoolean2(s: Boolean) = (res: String) => (s, res) passes { + case true => "yes" + case false => "no" + } + + def synthesizeIntStandard(s: Int): String = { + ???[String] + } ensuring psIntStandard(s) + + def synthesizeBigIntStandard(s: BigInt): String = { + ???[String] + } ensuring psBigIntStandard(s) + + def synthesizeBooleanStandard(s: Boolean): String = { + ???[String] + } ensuring psBooleanStandard(s) + + def synthesizeBoolean2(s: Boolean): String = { + ???[String] + } ensuring psBoolean2(s) +} \ No newline at end of file diff --git a/testcases/stringrender/DoubleListRender.scala b/testcases/stringrender/DoubleListRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..bd0dba714601685ae9c3ed22d199ff8c6a825daf --- /dev/null +++ b/testcases/stringrender/DoubleListRender.scala @@ -0,0 +1,63 @@ +/** + * Name: DoubleListRender.scala + * Creation: 09.12.2015 + * Author: Mikael Mayer (mikael.mayer@epfl.ch) + * Comments: Double lists rendering specification (requires many disambiguation rounds) + */ +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object DoubleListRender { + abstract class A + case class B(i: AA, a: A) extends A + case class N() extends A + + abstract class AA + case class BB(i: A, a: AA) extends AA + case class NN() extends AA + + // Obviously wrong, but it is useful to use the display function. + def twoOutOfThreeAsAreTheSame(a: A, b: A, c: A): Boolean = { + a == b || a == c || b == c + } holds + + def mutual_lists(a : A): String = { + ??? + } ensuring { + (res : String) => (a, res) passes { + case N() => + "[]" + case B(NN(), N()) => + "[()]" + case B(NN(), B(NN(), N())) => + "[(), ()]" + case B(BB(N(), NN()), N()) => + "[([])]" + case B(NN(), B(NN(), B(NN(), N()))) => + "[(), (), ()]" + case B(BB(N(), BB(N(), NN())), N()) => + "[([], [])]" + case B(NN(), B(BB(N(), NN()), N())) => + "[(), ([])]" + case B(BB(N(), NN()), B(NN(), N())) => + "[([]), ()]" + case B(BB(B(NN(), N()), NN()), N()) => + "[([()])]" + case B(BB(N(), BB(N(), BB(N(), NN()))), N()) => + "[([], [], [])]" + case B(NN(), B(BB(N(), NN()), B(NN(), N()))) => + "[(), ([]), ()]" + case B(BB(B(NN(), N()), BB(N(), NN())), N()) => + "[([()], [])]" + case B(BB(B(BB(N(), NN()), N()), NN()), N()) => + "[([([])])]" + case B(BB(B(NN(), B(NN(), N())), NN()), N()) => + "[([(), ()])]" + case B(BB(N(), BB(B(NN(), N()), NN())), N()) => + "[([], [()])]" + } + } +} \ No newline at end of file diff --git a/testcases/stringrender/Example-Stack.scala b/testcases/stringrender/Example-Stack.scala new file mode 100644 index 0000000000000000000000000000000000000000..05943c72e59fade4c78a3788c17ae031a286f92d --- /dev/null +++ b/testcases/stringrender/Example-Stack.scala @@ -0,0 +1,359 @@ +import leon.collection._ +import leon.collection.List +import leon.lang._ +import leon.proof.check +import leon.lang.synthesis._ +import scala.language.postfixOps + + +/** The Concurrency object defines the semantics of concurrent programs. + * + * It gives the definition of libraries, and gives a function + * isLibraryExecution which describes valid executions of the library. + * We show the files AtomicStack and TreiberStack how to instantiate it in + * order to describe specific implementations. + */ + + + +object Concurrency { + + /** The class Method gives a syntax to define a method of a library. + * + * A Method is a tuple (initials,transitions,finals) where: + * "initials" gives the initial state of the method depending on the argument + * "transitions" in transition relation, which specifies how local and global + * states are updated + * "finals" gives the final states, and the corresponding return value + * a state mapped to None means it's not final and the method cannot return here + * + * ArgData is the type of arguments values, given to the method + * RetData is the type of the values returned by the method + * LocalState is the type representing the local variables and control-flow positions + * of the method + */ + + case class Method[ArgData,RetData,LocalState,GlobalState]( + initials: ArgData => LocalState, + transitions: (LocalState,GlobalState) => (LocalState,GlobalState), + finals: LocalState => Option[RetData] + ) + + + /** A Library associates to each method name a Method instance */ + + case class Library[MethodName,ArgData,RetData,LocalState,GlobalState]( + methods: MethodName => Method[ArgData,RetData,LocalState,GlobalState] + ) + + + /** The Event class represents low-level events. + * + * Each event is executed by a particular thread (type Tid). + + * An event can be a call event. In which case, the event has information + * about the method `m' called, and the argument `arg' with which m was + * called. + * + * An event can be a return event. In which case, the event the same + * information than the corresponding call event, plus the return + * value `rv' (in RetData) which was returned. + * + * Otherwise, an event is an internal event (inside a method). + */ + + abstract class Event[Tid,MethodName,ArgData,RetData] + case class CallEvent[Tid,MethodName,ArgData,RetData] + (tid: Tid, m: MethodName, arg: ArgData) extends Event[Tid,MethodName,ArgData,RetData] + case class RetEvent[Tid,MethodName,ArgData,RetData] + (tid: Tid, m: MethodName, arg: ArgData, rv: RetData) extends Event[Tid,MethodName,ArgData,RetData] + case class InternalEvent[Tid,MethodName,ArgData,RetData] + (tid: Tid) extends Event[Tid,MethodName,ArgData,RetData] + + + + /** The Configuration class describes the whole state of a concurrent system. + * + * More precisely, it is a pair composed of a global state, and a map giving + * for each thread, the local state of the method in which the thread is. + * The map also stores information about the method name and the argument + * value with which the method was called. + * A thread mapped to None means that the thread is not currently calling + * any method. + * + * Intuitively, the global state represents the valuation of the global + * variables which are shared between the different methods. For programs + * which can use memory allocation, it should also represent the heap. + */ + + case class Configuration[Tid,MethodName,ArgData,LocalState,GlobalState]( + gs: GlobalState, + control: List[(Tid,Option[(MethodName,ArgData,LocalState)])] + ) + + + /** This class describes client's of a library. + * + * A client can be composed of multiple thread. It specifies for each + * thread, the sequence of calls made to the library, with the expected* + * return values. + */ + + case class Client[Tid,MethodName,ArgData,RetData](threads: Tid => List[Event[Tid,MethodName,ArgData,RetData]]) +} + +object AtomicStack { + + import Concurrency._ + + + + /** Represents the states of the control-flow graph of the push and pop + * methods. + */ + + abstract class StackState + case class ValueState(v: BigInt) extends StackState + case class EmptyState() extends StackState + case class InitState() extends StackState + case class FinalState() extends StackState + case class BlockState() extends StackState + + + abstract class StackTid + case class T1() extends StackTid + case class T2() extends StackTid + + + /** We now describe the Atomic Stack library. + * + * The arguments taken by push and pop are of type Option[BigInt]. + * Typically the pop method won't take an argument (None), while + * push will take a BigInt argument (Some[BigInt]). + * + * Similarly, the type of return values is also Option[BigInt]. + * + */ + + def initialsPush(arg: Option[BigInt]): StackState = arg match { + case None() => BlockState() + case Some(arg) => ValueState(arg) + } + + def transitionsPush(ls: StackState, gs: List[BigInt]): (StackState,List[BigInt]) = (ls,gs) match { + case (ValueState(arg),_) => (FinalState(), arg :: gs) + case _ => (BlockState(), gs) + } + + def finalsPush(ls: StackState): Option[Option[BigInt]] = ls match { + case FinalState() => Some(None()) + case _ => None() + } + + val PushMethod: Method[Option[BigInt],Option[BigInt],StackState,List[BigInt]] = { + Method(initialsPush,transitionsPush,finalsPush) + } + + + def initialsPop(arg: Option[BigInt]): StackState = InitState() + + def transitionsPop(ls: StackState, gs: List[BigInt]): (StackState,List[BigInt]) = (ls,gs) match { + case (InitState(),Nil()) => (EmptyState(), Nil()) + case (InitState(),Cons(rv,rvs)) => (ValueState(rv),rvs) + case _ => (BlockState(), gs) + } + + def finalsPop(ls: StackState): Option[Option[BigInt]] = ls match { + case EmptyState() => Some(None()) + case ValueState(arg) => Some(Some(arg)) + case _ => None() + } + + val PopMethod: Method[Option[BigInt],Option[BigInt],StackState,List[BigInt]] = { + Method(initialsPop,transitionsPop,finalsPop) + } + + abstract class StackMethodName + case class Push() extends StackMethodName + case class Pop() extends StackMethodName + + + def methods(name: StackMethodName): Method[Option[BigInt],Option[BigInt],StackState,List[BigInt]] = name match { + case Push() => PushMethod + case Pop() => PopMethod + } + + val libAtomicStack = Library[StackMethodName,Option[BigInt],Option[BigInt],StackState,List[BigInt]](methods) + + + def threads(tid: StackTid): List[Event[StackTid,StackMethodName,Option[BigInt],Option[BigInt]]] = tid match { + case T1() => + List( + CallEvent(T1(),Push(),Some(5)), + RetEvent(T1(),Push(),Some(5),None()) + ) + case T2() => + List( + CallEvent(T2(),Pop(),None()), + RetEvent(T2(),Pop(),None(),Some(5)) + ) + } + + val client: Client[StackTid,StackMethodName,Option[BigInt],Option[BigInt]] = Client(threads) + + def threadToStringSimplest(p: StackTid): String = { + ???[String] + } ensuring { + res => (p, res) passes { + case T1() + => + "T1: call Push(5)" + } + } + + def threadToStringSimple0(p: Event[StackTid,StackMethodName,Option[BigInt],Option[BigInt]]): String = { + ???[String] + } ensuring { + res => (p, res) passes { + case CallEvent(T1(), Push(), Some(BigInt(5))) + => + "T1: call Push(5)" + } + } + + def threadToStringSimple1(p: List[Event[StackTid,StackMethodName,Option[BigInt],Option[BigInt]]]): String = { + ???[String] + } ensuring { + res => (p, res) passes { + case Cons(CallEvent(T1(), Push(), Some(BigInt(5))), + Cons(InternalEvent(T1()), Nil())) + => + "T1: call Push(5)\nT1: internal" + } + } + + def threadToStringSimple2(p: List[Event[StackTid,StackMethodName,Option[BigInt],Option[BigInt]]]): String = { + ???[String] + } ensuring { + res => (p, res) passes { + case Cons(RetEvent(T1(), Push(), Some(BigInt(5)), None()), + Cons(InternalEvent(T2()), + Cons(RetEvent(T2(), Pop(), None(), Some(BigInt(5))), Nil()))) + => + "T1: ret Push(5)\nT2: internal\nT2: ret Pop() -> 5" + } + } + + + /** This is THE method we want to render */ + def threadToString(p: List[Event[StackTid,StackMethodName,Option[BigInt],Option[BigInt]]]): String = { + ???[String] + } ensuring { + res => (p, res) passes { + case Cons(CallEvent(T1(), Push(), Some(BigInt(5))), + Cons(InternalEvent(T1()), + Cons(CallEvent(T2(), Pop(), None()), + Cons(RetEvent(T1(), Push(), Some(BigInt(5)), None()), + Cons(InternalEvent(T2()), + Cons(RetEvent(T2(), Pop(), None(), Some(BigInt(5))), Nil())))))) + => + "T1: call Push(5)\nT1: internal\nT2: call Pop()\nT1: ret Push(5)\nT2: internal\nT2: ret Pop() -> 5" + } + } + + // Warning: Spacing differs from records to records. + // Warning: The displaying of a tuple might depend on its operands. + def configurationToString(p: List[Configuration[StackTid, StackMethodName, Option[BigInt], StackState, List[BigInt]]]): String = { + ???[String] + } ensuring { + res => (p, res) passes { + case Cons(Configuration(Nil(), Cons((T1(), Some((Push(), Some(BigInt(5)), ValueState(BigInt(5))))), Nil())), + Cons(Configuration(Cons(BigInt(5), Nil()), Cons((T1(), Some((Push(), Some(BigInt(5)), FinalState()))), Nil())), + Cons(Configuration(Cons(BigInt(5), Nil()), Cons((T2(), Some((Pop(), None(), InitState()))), Cons((T1(), Some((Push(), Some(BigInt(5)), FinalState()))), Nil()))), + Cons(Configuration(Cons(BigInt(5), Nil()), Cons((T2(), Some((Pop(), None(), InitState()))), Cons((T1(), None()), Nil()))), + Cons(Configuration(Nil(), Cons((T2(), Some((Pop(), None(), ValueState(BigInt(5))))), Cons((T1(), None()), Nil()))), + Cons(Configuration(Nil(), Cons((T2(), None()), Cons((T1(), None()), Nil()))), Nil())))))) => + """([], { + T1 -> Push(5): ValueState(5) +}) + + +([5], { + T1 -> Push(5): FinalState +}) + + +([5], { + T2 -> Pop(): InitState; + T1 -> Push(5): FinalState +}) + + +([5], { + T2 -> Pop(): InitState; + T1 -> idle +}) + + +([], { + T2 -> Pop(): ValueState(5); + T1 -> idle +}) + + +([], { + T2 -> idle; + T1 -> idle +})""" + + } + } + /* + /// Out of order configurationToString + def configurationToStringOOO(p: List[Configuration[StackTid, StackMethodName, Option[BigInt], StackState, List[BigInt]]]): String = { + ???[String] + } ensuring { + res => (p, res) passes { + case Cons(Configuration(Nil(), Map(T1() -> Some((Push(), Some(BigInt(5)), ValueState(BigInt(5)))))), + Cons(Configuration(Cons(BigInt(5), Nil()), Map(T1() -> Some((Push(), Some(BigInt(5)), FinalState())))), + Cons(Configuration(Cons(BigInt(5), Nil()), Map(T2() -> Some((Pop(), None(), InitState())), T1() -> Some((Push(), Some(BigInt(5)), FinalState())))), + Cons(Configuration(Cons(BigInt(5), Nil()), Map(T2() -> Some((Pop(), None(), InitState())), T1() -> None())), + Cons(Configuration(Nil(), Map(T2() -> Some((Pop(), None(), ValueState(BigInt(5)))), T1() -> None())), + Cons(Configuration(Nil(), Map(T2() -> None(), T1() -> None())), Nil())))))) => + """([], { + T1 -> ValueState(5) in Push(5) +}) + + +([5], { + T1 -> FinalState in Push(5) +}) + + +([5], { + T2 -> InitState in Pop(); + T1 -> FinalState in Push(5) +}) + + +([5], { + T2 -> InitState in Pop(); + T1 -> idle +}) + + +([], { + T2 -> ValueState(5) in Pop(); + T1 -> idle +}) + + +([], { + T2 -> idle; + T1 -> idle +})""" + + } + }*/ +} + diff --git a/testcases/stringrender/GrammarRender.scala b/testcases/stringrender/GrammarRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..a2de93e9b41bc552db923b9eec2cce3fdf89d41b --- /dev/null +++ b/testcases/stringrender/GrammarRender.scala @@ -0,0 +1,241 @@ +/** + * Name: GrammarRender.scala + * Creation: 15.10.2015 + * Author: Mika�l Mayer (mikael.mayer@epfl.ch) + * Comments: Grammar rendering specifications. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object GrammarRender { + abstract class Symbol + case class Terminal(i: Int) extends Symbol + case class NonTerminal(i: Int) extends Symbol + + case class Rule(left: Symbol, right: List[Symbol]) + case class Grammar(start: Symbol, rules: List[Rule]) + + /** Synthesis by example specs */ + @inline def psStandard(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "Grammar(NonTerminal(0), Nil())" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil()))" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil())))" + } + + @inline def psRemoveNames(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "(S0, ())" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "(S0, ((S0, (t1, ())), ()))" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "(S0, ((S0, (t1, ())), ((S0, (S0, (t1, ()))), ())))" + } + + @inline def psArrowRules(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "(S0, ())" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "(S0, ((S0 -> (t1, ())), ()))" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "(S0, ((S0 -> (t1, ())), ((S0 -> (S0, (t1, ()))), ())))" + } + + @inline def psListRules(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "(S0, [])" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "(S0, [S0 -> [t1]])" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "(S0, [S0 -> [t1], S0 -> [S0, t1])" + } + + // The difficulty here is that two lists have to be rendered differently. + @inline def psSpaceRules(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "(S0, [])" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "(S0, [S0 -> t1])" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "(S0, [S0 -> t1, S0 -> S0 t1)" + } + + // Full HTML generation + @inline def psHTMLRules(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "<b>Start:</b> S0<br><pre></pre>" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "<b>Start:</b> S0<br><pre>S0 -> t1</pte>" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "<b>Start:</b> S0<br><pre>S0 -> t1<br>S0 -> S0 t1</pte>" + } + //Render in plain text. + @inline def psPlainTextRules(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => + """Start:S0""" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + """Start:S0 +S0 -> t1""" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + """Start:S0 +S0 -> t1 +S0 -> S0 t1""" + } + + /** Each function's body is the solution of the previous problem. + * We want to check that the Leon can repair the programs and also find some ambiguities.*/ + def render0RemoveNames(s: Grammar): String = { + def renderSymbol(s: Symbol) = s match { + case NonTerminal(i) => "NonTerminal(" + i + ")" + case Terminal(i) => "Terminal(" + i + ")" + } + def renderRule(r: Rule): String = { + def renderListSymbol(s: List[Symbol]): String = s match { + case Nil() => "Nil()" + case Cons(a, q) => "Cons(" + rendersymbol(a) + ", " + renderListsymbol(q) + ")" + } + "Rule(" + renderSymbol(r.left) + ", " + renderListSymbol(r.right) + ")" + } + def renderListRules(s: List[Rules]): String = s match { + case Nil() => "Nil()" + case Cons(r, q) => "Cons(" + renderRule(r) + ", " + renderListRules(q) + ")" + } + "Grammar(" + renderSymbol(s.start) + ", " + renderListRules(s.rules) + ")" + } ensuring psRemoveNames(s) + + def render1ArrowRules(s: Grammar): String = { + def renderSymbol(s: Symbol) = s match { + case NonTerminal(i) => "S" + i + case Terminal(i) => "t" + i + } + def renderRule(r: Rule): String = { + def renderListSymbol(s: List[Symbol]): String = s match { + case Nil() => "()" + case Cons(a, q) => "(" + rendersymbol(a) + ", " + renderListsymbol(q) + ")" + } + "(" + renderSymbol(r.left) + ", " + renderListSymbol(r.right) + ")" + } + def renderListRules(s: List[Rules]): String = s match { + case Nil() => "()" + case Cons(r, q) => "(" + renderRule(r) + ", " + renderListRules(q) + ")" + } + "(" + renderSymbol(s.start) + ", " + renderListRules(s.rules) + ")" + } ensuring psArrowRules(s) + + def render2ListRules(s: Grammar): String = { + def renderSymbol(s: Symbol) = s match { + case NonTerminal(i) => "S" + i + case Terminal(i) => "t" + i + } + def renderRule(r: Rule): String = { + def renderListSymbol(s: List[Symbol]): String = s match { + case Nil() => "()" + case Cons(a, q) => "(" + rendersymbol(a) + ", " + renderListsymbol(q) + ")" + } + "(" + renderSymbol(r.left) + " -> " + renderListSymbol(r.right) + ")" + } + def renderListRules(s: List[Rules]): String = s match { + case Nil() => "()" + case Cons(r, q) => "(" + renderRule(r) + ", " + renderListRules(q) + ")" + } + "(" + renderSymbol(s.start) + ", " + renderListRules(s.rules) + ")" + } ensuring psListRules(s) + + def render3SpaceRules(s: Grammar): String = { + def renderSymbol(s: Symbol) = s match { + case NonTerminal(i) => "S" + i + case Terminal(i) => "t" + i + } + def renderRule(r: Rule): String = { + def renderListSymbol(s: List[Symbol]): String = s match { + case Nil() => "" + case Cons(a, Nil()) => rendersymbol(a) + case Cons(a, q) => rendersymbol(a) + ", " + renderListsymbol(q) + } + renderSymbol(r.left) + " -> [" + renderListSymbol(r.right) + "]" + } + def renderListRules(s: List[Rules]): String = s match { + case Nil() => "" + case Cons(r, Nil()) => renderRule(r) + case Cons(r, q) => renderRule(r) + ", " + renderListRules(q) + } + "(" + renderSymbol(s.start) + ", [" + renderListRules(s.rules) + "])" + } ensuring psSpaceRules(s) + + def render4HTMLRules(s: Grammar): String = { + def renderSymbol(s: Symbol) = s match { + case NonTerminal(i) => "S" + i + case Terminal(i) => "t" + i + } + def renderRule(r: Rule): String = { + def renderListSymbol(s: List[Symbol]): String = s match { + case Nil() => "" + case Cons(a, Nil()) => rendersymbol(a) + case Cons(a, q) => rendersymbol(a) + " " + renderListsymbol(q) + } + renderSymbol(r.left) + " -> " + renderListSymbol(r.right) + "" + } + def renderListRules(s: List[Rules]): String = s match { + case Nil() => "" + case Cons(r, Nil()) => renderRule(r) + case Cons(r, q) => renderRule(r) + ", " + renderListRules(q) + } + "(" + renderSymbol(s.start) + ", [" + renderListRules(s.rules) + "])" + } ensuring psHTMLRules(s) + + /* The initial body of this function is the solution of render3 */ + def render5PlainTextRules(s: Grammar): String = { + def renderSymbol(s: Symbol) = s match { + case NonTerminal(i) => "S" + i + case Terminal(i) => "t" + i + } + def renderRule(r: Rule): String = { + def renderListSymbol(s: List[Symbol]): String = s match { + case Nil() => "" + case Cons(a, Nil()) => rendersymbol(a) + case Cons(a, q) => rendersymbol(a) + " " + renderListsymbol(q) + } + renderSymbol(r.left) + " -> " + renderListSymbol(r.right) + "" + } + def renderListRules(s: List[Rules]): String = s match { + case Nil() => "" + case Cons(r, Nil()) => renderRule(r) + case Cons(r, q) => renderRule(r) + ", " + renderListRules(q) + } + "(" + renderSymbol(s.start) + ", [" + renderListRules(s.rules) + "])" + } ensuring psPlainTextRules(s) + + ////////////////////////////////////////////// + // Non-incremental examples: pure synthesis // + ////////////////////////////////////////////// + def synthesizeStandard(s: Grammar): String = { + ???[String] + } ensuring psStandard(s) + + def synthesizeRemoveNames(s: Grammar): String = { + ???[String] + } ensuring psRemoveNames(s) + + def synthesizeArrowRules(s: Grammar): String = { + ???[String] + } ensuring psArrowRules(s) + + def synthesizeListRules(s: Grammar): String = { + ???[String] + } ensuring psListRules(s) + + def synthesizeSpaceRules(s: Grammar): String = { + ???[String] + } ensuring psSpaceRules(s) + + def synthesizeHTMLRules(s: Grammar): String = { + ???[String] + } ensuring psHTMLRules(s) + + def synthesizePlainTextRules(s: Grammar): String = { + ???[String] + } ensuring psPlainTextRules(s) + + def allGrammarsAreIdentical(g: Grammar, g2: Grammar) = (g == g2 || g.rules == g2.rules) holds + +} \ No newline at end of file diff --git a/testcases/stringrender/IntWrapperRender.scala b/testcases/stringrender/IntWrapperRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..fda0004afa3f52454d62d544e272c5d40be229a6 --- /dev/null +++ b/testcases/stringrender/IntWrapperRender.scala @@ -0,0 +1,82 @@ +/** + * Name: IntWrapperRender.scala + * Creation: 15.10.2015 + * Author: Mikaël Mayer (mikael.mayer@epfl.ch) + * Comments: Wrapped integer specifications. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object IntWrapperRender { + case class IntWrapper(i: Int) + + @inline def psStandard(s: IntWrapper) = (res: String) => (s, res) passes { + case IntWrapper(0) => "IntWrapper(0)" + case IntWrapper(-1) => "IntWrapper(-1)" + case IntWrapper(12) => "IntWrapper(12)" + } + + @inline def psUnwrapped(s: IntWrapper) = (res: String) => (s, res) passes { + case IntWrapper(0) => "0" + case IntWrapper(-1) => "-1" + case IntWrapper(12) => "12" + } + + @inline def psNameChangedPrefix(s: IntWrapper) = (res: String) => (s, res) passes { + case IntWrapper(0) => "number: 0" + case IntWrapper(-1) => "number: -1" + case IntWrapper(12) => "number: 12" + } + + @inline def psNameChangedSuffix(s: IntWrapper) = (res: String) => (s, res) passes { + case IntWrapper(0) => "0.0" + case IntWrapper(-1) => "-1.0" // Here there should be an ambiguity before this line. + case IntWrapper(12) => "12.0" + } + + /*@inline def psDuplicate(s: IntWrapper) = (res: String) => (s, res) passes { + case IntWrapper(0) => "0 0" + case IntWrapper(-1) => "-1 -1" + case IntWrapper(12) => "12 12" + }*/ + + def repairUnwrapped(s: IntWrapper): String = { + "IntWrapper(" + s.i + ")" + } ensuring psUnwrapped(s) + + def repairNameChangedPrefix(s: IntWrapper): String = { + "IntWrapper(" + s.i + ")" + } ensuring psNameChangedPrefix(s) + + def repairNameChangedSuffix(s: IntWrapper): String = { + "IntWrapper(" + s.i + ")" + } ensuring psNameChangedSuffix(s) + + /*def repairDuplicate(s: IntWrapper): String = { + "IntWrapper(" + s.i + ")" + } ensuring psDuplicate(s)*/ + + def synthesizeStandard(s: IntWrapper): String = { + ???[String] + } ensuring psStandard(s) + + def synthesizeUnwrapped(s: IntWrapper): String = { + ???[String] + } ensuring psUnwrapped(s) + + def synthesizeNameChangedPrefix(s: IntWrapper): String = { + ???[String] + } ensuring psNameChangedPrefix(s) + + def synthesizeNameChangedSuffix(s: IntWrapper): String = { + ???[String] + } ensuring psNameChangedSuffix(s) + + /*def synthesizeDuplicate(s: IntWrapper): String = { + ???[String] + } ensuring psDuplicate(s)*/ +} \ No newline at end of file diff --git a/testcases/stringrender/JsonRender.scala b/testcases/stringrender/JsonRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..2b858784576e6cd97a282a2cba59246f5c4fa84f --- /dev/null +++ b/testcases/stringrender/JsonRender.scala @@ -0,0 +1,42 @@ +/** + * Name: ListRender.scala + * Creation: 14.10.2015 + * Author: Mika�l Mayer (mikael.mayer@epfl.ch) + * Comments: First benchmark ever for solving string transformation problems. List specifications. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object JsonRender { + /** Synthesis by example specs */ + @inline def psStandard(x: Int, y: String, z: Boolean) = (res: String) =>((x, y, z), res) passes { + case (31, "routed", true) => """{ field1: 31, field2: "routed", field3: true }""" + } + + @inline def psTupled1(xy: (Int, String), z: Boolean) = (res: String) =>((xy, z), res) passes { + case ((31, "routed"), true) => """{ field1: 31, field2: "routed", field3: true }""" + } + + @inline def psTupled2(x: Int, yz: (String, Boolean)) = (res: String) =>((x, yz), res) passes { + case (31, ("routed", true)) => """{ field1: 31, field2: "routed", field3: true }""" + } + + ////////////////////////////////////////////// + // Non-incremental examples: pure synthesis // + ////////////////////////////////////////////// + def synthesizeStandard(x: Int, y: String, z: Boolean): String = { + ???[String] + } ensuring psStandard(x, y, z) + + def synthesizeTupled1(xy: (Int, String), z: Boolean): String = { + ???[String] + } ensuring psTupled1(xy, z) + + def synthesizeTupled2(x: Int, yz: (String, Boolean)): String = { + ???[String] + } ensuring psTupled2(x, yz) +} \ No newline at end of file diff --git a/testcases/stringrender/ListBigIntRender.scala b/testcases/stringrender/ListBigIntRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..9dddd97df7f03b2fedf576da86eb606e8d9f79ce --- /dev/null +++ b/testcases/stringrender/ListBigIntRender.scala @@ -0,0 +1,183 @@ +/** + * Name: ListBigIntRender.scala + * Creation: 15.10.2015 + * Author: Mika�l Mayer (mikael.mayer@epfl.ch) + * Comments: List of BigInt specifications. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object ListBigIntRender { + /** Synthesis by example specs */ + @inline def psWithBigInts(s: List[BigInt]) = (res: String) => (s, res) passes { + case Cons(BigInt(12), Cons(BigInt(-1), Nil())) => "Cons(BigInt(12), Cons(BigInt(-1), Nil()))" + case Cons(BigInt(1), Nil()) => "Cons(BigInt(1), Nil())" + case Nil() => "Nil()" + } + + @inline def psStandard(s: List[BigInt]) = (res: String) => (s, res) passes { + case Cons(BigInt(12), Cons(BigInt(-1), Nil())) => "Cons(12, Cons(-1, Nil()))" + case Cons(BigInt(1), Nil()) => "Cons(1, Nil())" + case Nil() => "Nil()" + } + + @inline def psRemoveCons(s: List[BigInt]) = (res: String) => (s, res) passes { + case Cons(BigInt(12), Cons(BigInt(-1), Nil())) => "(12, (-1, Nil()))" + case Cons(BigInt(1), Nil()) => "(1, Nil())" + case Nil() => "Nil()" + } + + @inline def psRemoveNil(s: List[BigInt]) = (res: String) => (s, res) passes { + case Cons(BigInt(12), Cons(BigInt(-1), Nil())) => "(12, (-1, ))" + case Cons(BigInt(1), Nil()) => "(1, )" + case Nil() => "" + } + + @inline def psRemoveLastComma(s: List[BigInt]) = (res: String) => (s, res) passes { + case Cons(BigInt(12), Cons(BigInt(-1), Nil())) => "(12, (-1))" + case Cons(BigInt(1), Nil()) => "(1)" + case Nil() => "()" + } + + @inline def psRemoveParentheses(s: List[BigInt]) = (res: String) => (s, res) passes { + case Cons(BigInt(12), Cons(BigInt(-1), Nil())) => "12, -1" + case Cons(BigInt(1), Nil()) => "1" + case Nil() => "" + } + + @inline def psWrapParentheses(s: List[BigInt]) = (res: String) => (s, res) passes { + case Cons(BigInt(12), Cons(BigInt(-1), Nil())) => "(12, -1)" + case Cons(BigInt(1), Nil()) => "(1)" + case Nil() => "()" + } + + @inline def psList(s: List[BigInt]) = (res: String) => (s, res) passes { + case Nil() => "List()" + case Cons(BigInt(1), Nil()) => "List(1)" + case Cons(BigInt(12), Cons(BigInt(-1), Nil())) => "List(12, -1)" + } + + /** Each function's body is the solution of the previous problem. + * We want to check that the Leon can repair the programs and also find some ambiguities.*/ + /*def render0RemoveBigInt(s: List[BigInt]): String = { + def renderBigInt(b: BigInt): String = { + b.toString() + } + s match { + case Nil() => "Nil()" + case Cons(a, b) => "Cons(" + renderBigInt(a) + ", " + render1RemoveCons(b) + ")" + } + } ensuring psRemoveCons(s) + + def render1RemoveCons(s: List[BigInt]): String = { + def renderBigInt(b: BigInt): String = { + val a = b.toString() + a.substring(6, a.length - 1) + } + s match { + case Nil() => "Nil()" + case Cons(a, b) => "Cons(" + renderBigInt(a) + ", " + render1RemoveCons(b) + ")" + } + } ensuring psRemoveCons(s) + + def render2RemoveNil(s: List[BigInt]): String = { + def renderBigInt(b: BigInt): String = { + val a = b.toString() + a.substring(6, a.length - 1) + } + s match { + case Nil() => "Nil()" + case Cons(a, b) => "(" + renderBigInt(a) + ", " + render2RemoveNil(b) + ")" + } + } ensuring psRemoveNil(s) + + def render3RemoveLastComma(s: List[BigInt]): String = { + def renderBigInt(b: BigInt): String = { + val a = b.toString() + a.substring(6, a.length - 1) + } + s match { + case Nil() => "" + case Cons(a, b) => "(" + renderBigInt(a) + ", " + render3RemoveLastComma(b) + ")" + } + } ensuring psRemoveLastComma(s) + + def render4RemoveParentheses(s: List[BigInt]): String = { + def renderBigInt(b: BigInt): String = { + val a = b.toString() + a.substring(6, a.length - 1) + } + s match { + case Nil() => "" + case Cons(a, Nil()) => "(" + renderBigInt(a) + ")" + case Cons(a, b) => "(" + renderBigInt(a) + ", " + render4RemoveParentheses(b) + ")" + } + } ensuring psRemoveParentheses(s)*/ + + /* harder example: It needs to repair by wrapping the recursive call in a sub-method + * in order to add strings to the left and to the right (see render6List) */ + /*def render5WrapParentheses(s: List[BigInt]): String = { + def renderBigInt(b: BigInt): String = { + val a = b.toString() + a.substring(6, a.length - 1) + } + s match { + case Nil() => "" + case Cons(a, Nil()) => renderBigInt(a) + case Cons(a, b) => renderBigInt(a) + ", " + render5WrapParentheses(b) + } + } ensuring psWrapParentheses(s) + + def render6List(s: List[BigInt]): String = { + def renderBigInt(b: BigInt): String = { + val a = b.toString() + a.substring(6, a.length - 1) + } + def rec(s: List[BigInt]): String = + s match { + case Nil() => "" + case Cons(a, Nil()) => renderBigInt(a) + case Cons(a, b) => renderBigInt(a) + ", " + render6List(b) + } + "(" + rec(s) + ")" + } ensuring psList(s)*/ + + ////////////////////////////////////////////// + // Non-incremental examples: pure synthesis // + ////////////////////////////////////////////// + def synthesizeStandard(s: List[BigInt]): String = { + ???[String] + } ensuring psStandard(s) + + def synthesizeWithBigInts(s: List[BigInt]): String = { + ???[String] + } ensuring psWithBigInts(s) + + def synthesizeRemoveCons(s: List[BigInt]): String = { + ???[String] + } ensuring psRemoveCons(s) + + def synthesizeRemoveNil(s: List[BigInt]): String = { + ???[String] + } ensuring psRemoveNil(s) + + def synthesizeRemoveLastComma(s: List[BigInt]): String = { + ???[String] + } ensuring psRemoveLastComma(s) + + def synthesizeRemoveParentheses(s: List[BigInt]): String = { + ???[String] + } ensuring psRemoveParentheses(s) + + def synthesizeWrapParentheses(s: List[BigInt]): String = { + ???[String] + } ensuring psWrapParentheses(s) + + def synthesizeList(s: List[BigInt]): String = { + ???[String] + } ensuring psList(s) +} \ No newline at end of file diff --git a/testcases/stringrender/ListRender.scala b/testcases/stringrender/ListRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..fbf9b8d7093c7183370e03ba46067877d79e977e --- /dev/null +++ b/testcases/stringrender/ListRender.scala @@ -0,0 +1,138 @@ +/** + * Name: ListRender.scala + * Creation: 14.10.2015 + * Author: Mika�l Mayer (mikael.mayer@epfl.ch) + * Comments: First benchmark ever for solving string transformation problems. List specifications. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object ListRender { + /** Synthesis by example specs */ + @inline def psStandard(s: List[Int]) = (res: String) =>(s, res) passes { + case Cons(12, Cons(-1, Nil())) => "Cons(12, Cons(-1, Nil))" + case Cons(-1, Cons(12, Nil())) => "Cons(-1, Cons(12, Nil))" + case Cons(1, Nil()) => "Cons(1, Nil)" + case Nil() => "Nil" + } + + @inline def psRemoveCons(s: List[Int]) = (res: String) =>(s, res) passes { + case Cons(12, Cons(-1, Nil())) => "(12, (-1, Nil))" + case Cons(1, Nil()) => "(1, Nil)" + case Nil() => "Nil" + } + + @inline def psRemoveNil(s: List[Int]) = (res: String) =>(s, res) passes { + case Cons(12, Cons(-1, Nil())) => "(12, (-1, ))" + case Cons(1, Nil()) => "(1, )" + case Nil() => "" + } + + @inline def psRemoveLastComma(s: List[Int]) = (res: String) =>(s, res) passes { + case Cons(12, Cons(-1, Nil())) => "(12, (-1))" + case Cons(1, Nil()) => "(1)" + case Nil() => "()" + } + + @inline def psRemoveParentheses(s: List[Int]) = (res: String) =>(s, res) passes { + case Cons(12, Cons(-1, Nil())) => "12, -1" + case Cons(1, Nil()) => "1" + case Nil() => "" + } + + @inline def psWrapParentheses(s: List[Int]) = (res: String) =>(s, res) passes { + case Cons(12, Cons(-1, Nil())) => "(12, -1)" + } + + @inline def psList(s: List[Int]) = (res: String) =>(s, res) passes { + case Nil() => "List()" + case Cons(1, Nil()) => "List(1)" + case Cons(12, Cons(-1, Nil())) => "List(12, -1)" + } + + /** Each function's body is the solution of the previous problem. + * We want to check that the Leon can repair the programs and also find some ambiguities.*/ + def render1RemoveCons(s: List[Int]): String = { + s match { + case Nil() => "Nil" + case Cons(a, b) => "Cons(" + a.toString + ", " + render1RemoveCons(b) + ")" + } + } ensuring psRemoveCons(s) + + def render2RemoveNil(s: List[Int]): String = { + s match { + case Nil() => "Nil" + case Cons(a, b) => "(" + a.toString + ", " + render2RemoveNil(b) + ")" + } + } ensuring psRemoveNil(s) + + def render3RemoveLastComma(s: List[Int]): String = { + s match { + case Nil() => "" + case Cons(a, b) => "(" + a.toString + ", " + render3RemoveLastComma(b) + ")" + } + } ensuring psRemoveLastComma(s) + + def render4RemoveParentheses(s: List[Int]): String = { + s match { + case Nil() => "" + case Cons(a, Nil()) => "(" + a.toString + ")" + case Cons(a, b) => "(" + a.toString + ", " + render4RemoveParentheses(b) + ")" + } + } ensuring psRemoveParentheses(s) + + /* harder example: It needs to repair by wrapping the recursive call in a sub-method + * in order to add strings to the left and to the right (see render6List) */ + def render5WrapParentheses(s: List[Int]): String = { + s match { + case Nil() => "" + case Cons(a, Nil()) => a.toString + case Cons(a, b) => a.toString + ", " + render5WrapParentheses(b) + } + } ensuring psWrapParentheses(s) + + def render6List(s: List[Int]): String = { + def rec(s: List[Int]): String = + s match { + case Nil() => "" + case Cons(a, Nil()) => a.toString + case Cons(a, b) => a.toString + ", " + render6List(b) + } + "(" + rec(s) + ")" + } ensuring psList(s) + + ////////////////////////////////////////////// + // Non-incremental examples: pure synthesis // + ////////////////////////////////////////////// + def synthesizeStandard(s: List[Int]): String = { + ???[String] + } ensuring psStandard(s) + + def synthesizeRemoveCons(s: List[Int]): String = { + ???[String] + } ensuring psRemoveCons(s) + + def synthesizeRemoveNil(s: List[Int]): String = { + ???[String] + } ensuring psRemoveNil(s) + + def synthesizeRemoveLastComma(s: List[Int]): String = { + ???[String] + } ensuring psRemoveLastComma(s) + + def synthesizeRemoveParentheses(s: List[Int]): String = { + ???[String] + } ensuring psRemoveParentheses(s) + + def synthesizeWrapParentheses(s: List[Int]): String = { + ???[String] + } ensuring psWrapParentheses(s) + + def synthesizeList(s: List[Int]): String = { + ???[String] + } ensuring psList(s) +} \ No newline at end of file diff --git a/testcases/stringrender/OutOfOrder.scala b/testcases/stringrender/OutOfOrder.scala new file mode 100644 index 0000000000000000000000000000000000000000..72785cf605cfbfe26bb70abd37651c32d5eddd42 --- /dev/null +++ b/testcases/stringrender/OutOfOrder.scala @@ -0,0 +1,57 @@ +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object OutOfOrderToString { + def argumentsToString(i: Int, j: Int): String = { + ??? + } ensuring { (res: String) => ((i, j), res) passes { + case (1, 2) => "2, 1" + } } + + def tupleToString(i: (Int, Int)): String = { + ??? + } ensuring { (res: String) => (i, res) passes { + case (1, 2) => "2, 1" + } } + + + def reverseList(l : List[Int]): String = { + ???[String] + } ensuring { + (res : String) => (l, res) passes { + case Cons(1, Cons(2, Nil())) => + "2, 1" + } + } + + def listPairToString(l : List[(Int, Int)]): String = { + ???[String] + } ensuring { + (res : String) => (l, res) passes { + case Cons((1, 2), Cons((3, 4), Nil())) => + "2 -> 1, 4 -> 3" + } + } + + def reverselistPairToString(l: List[(Int, Int)]): String = { + ??? + } ensuring { (res: String) => (l, res) passes { + case Cons((1, 2), Cons((3,4), Nil())) => "4 -> 3, 2 -> 1" + } } + + case class Rule(input: Int, applied: Option[Int]) + + def ruleToString(r : Rule): String = { + ??? + } ensuring { + (res : String) => (r, res) passes { + case Rule(1, None()) => + "res: 1" + case Rule(4, Some(2)) => + "Push(2): 4" + } + } +} \ No newline at end of file diff --git a/testcases/stringrender/ProblemRender.scala b/testcases/stringrender/ProblemRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..d1d4e4bb5acbedc5ba38b03930a080c9f7aea77c --- /dev/null +++ b/testcases/stringrender/ProblemRender.scala @@ -0,0 +1,40 @@ +/** + * Name: ListRender.scala + * Creation: 14.10.2015 + * Author: Mika�l Mayer (mikael.mayer@epfl.ch) + * Comments: First benchmark ever for solving string transformation problems. List specifications. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object ProblemRender { + case class Identifier(name: String) + sealed abstract class StringFormToken + + case class Left(e: String) extends StringFormToken + case class Right(i: Identifier) extends StringFormToken + + type StringForm = List[StringFormToken] + + type Equation = (StringForm, String) + + /** Sequences of equalities such as xyz"1"uv"2" = "1, 2" */ + type Problem = List[Equation] + + /** Synthesis by example specs */ + @inline def psStandard(s: Problem) = (res: String) => (s, res) passes { + case Cons((Cons(Left("1"), Cons(Right(Identifier("x")), Cons(Left("2"), Nil()))), "1432"), + Cons((Cons(Right(Identifier("y")), Cons(Left("4"), Cons(Right(Identifier("y")), Nil()))), "141"), Nil())) => "\"1\"+x+\"2\"==1432, y+\"4\"+y==\"141\"" + } + ////////////////////////////////////////////// + // Non-incremental examples: pure synthesis // + ////////////////////////////////////////////// + def synthesizeStandard(s: List[Int]): String = { + ???[String] + } ensuring psStandard(s) + +} \ No newline at end of file diff --git a/testcases/stringrender/TupleWrapperRender.scala b/testcases/stringrender/TupleWrapperRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..6df42174bc4f9e4a7cadec3067ce0df8ae028fd1 --- /dev/null +++ b/testcases/stringrender/TupleWrapperRender.scala @@ -0,0 +1,83 @@ +/** + * Name: TupleWrapperRender.scala + * Creation: 15.10.2015 + * Author: Mikaël Mayer (mikael.mayer@epfl.ch) + * Comments: Tuple rendering specifications. + */ + +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object TupleWrapperRender { + case class TupleWrapper(i: Int, j: Int) + + @inline def psStandard(s: TupleWrapper) = (res: String) => (s, res) passes { + case TupleWrapper(0, 0) => "TupleWrapper(0, 0)" + case TupleWrapper(-1, 2) => "TupleWrapper(-1, 2)" + case TupleWrapper(12, 5) => "TupleWrapper(12, 5)" + } + + @inline def psUnwrapped(s: TupleWrapper) = (res: String) => (s, res) passes { + case TupleWrapper(0, 0) => "0, 0" + case TupleWrapper(-1, 2) => "-1, 2" + case TupleWrapper(12, 5) => "12, 5" + } + + @inline def psNameChangedPrefix(s: TupleWrapper) = (res: String) => (s, res) passes { + case TupleWrapper(0, 0) => "x = 0, y = 0" + case TupleWrapper(-1, 2) => "x = -1, y = 2" + case TupleWrapper(12, 5) => "x = 12, y = 5" + } + + @inline def psNameChangedSuffix(s: TupleWrapper) = (res: String) => (s, res) passes { + case TupleWrapper(0, 0) => "0.0,0.0" + case TupleWrapper(-1, 2) => "-1.0,2.0" // Here there should be an ambiguity before this line. + case TupleWrapper(12, 5) => "12.0,5.0" + } + + /*@inline def psDuplicate(s: TupleWrapper) = (res: String) => (s, res) passes { + case TupleWrapper(0, 0) => "d 0,0 0,15 15,15 15,0" + case TupleWrapper(-1, 2) => "d -1,-2 -1,15 15,15 15,2" + case TupleWrapper(12, 5) => "d 12,5 12,15 15,15 15,5" + }*/ + + def repairUnWrapped(s: TupleWrapper): String = { + "TupleWrapper(" + s.i + ", " + s.j + ")" + } ensuring psUnwrapped(s) + + def repairNameChangedPrefix(s: TupleWrapper): String = { + "TupleWrapper(" + s.i + ", " + s.j + ")" + } ensuring psNameChangedPrefix(s) + + def repairNameChangedSuffix(s: TupleWrapper): String = { + "TupleWrapper(" + s.i + ", " + s.j + ")" + } ensuring psNameChangedSuffix(s) + + /*def repairDuplicate(s: TupleWrapper): String = { + "TupleWrapper(" + s.i + ", " + s.j + ")" + } ensuring psDuplicate(s)*/ + + + def synthesizeStandard(s: TupleWrapper): String = { + ???[String] + } ensuring psStandard(s) + + def synthesizeUnwrapped(s: TupleWrapper): String = { + ???[String] + } ensuring psUnwrapped(s) + + def synthesizeNameChangedPrefix(s: TupleWrapper): String = { + ???[String] + } ensuring psNameChangedPrefix(s) + + def synthesizeNameChangedSuffix(s: TupleWrapper): String = { + ???[String] + } ensuring psNameChangedSuffix(s) + + /*def synthesizeDuplicate(s: TupleWrapper): String = { + ???[String] + } ensuring psDuplicate(s)*/ +} \ No newline at end of file diff --git a/testcases/stringrender/TwoArgsWrapperRender.scala b/testcases/stringrender/TwoArgsWrapperRender.scala new file mode 100644 index 0000000000000000000000000000000000000000..e438affa73ea7af0352e7937cc5afb328f25b9cb --- /dev/null +++ b/testcases/stringrender/TwoArgsWrapperRender.scala @@ -0,0 +1,81 @@ +/** + * Name: TupleWrapperRender.scala + * Creation: 15.10.2015 + * Author: Mikaël Mayer (mikael.mayer@epfl.ch) + * Comments: Tuple rendering specifications. + */ + +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ +import leon.lang._ + +object TwoArgsWrapperRender { + @inline def psStandard(s: Int, t: Int) = (res: String) => ((s, t), res) passes { + case (0, 0) => "TupleWrapper(0, 0)" + case (-1, 2) => "TupleWrapper(-1, 2)" + case (12, 5) => "TupleWrapper(12, 5)" + } + + @inline def psUnwrapped(s: Int, t: Int) = (res: String) => ((s, t), res) passes { + case (0, 0) => "0, 0" + case (-1, 2) => "-1, 2" + case (12, 5) => "12, 5" + } + + @inline def psNameChangedPrefix(s: Int, t: Int) = (res: String) => ((s, t), res) passes { + case (0, 0) => "x = 0, y = 0" + case (-1, 2) => "x = -1, y = 2" + case (12, 5) => "x = 12, y = 5" + } + + @inline def psNameChangedSuffix(s: Int, t: Int) = (res: String) => ((s, t), res) passes { + case (0, 0) => "0.0,0.0" + case (-1, 2) => "-1.0,2.0" // Here there should be an ambiguity before this line. + case (12, 5) => "12.0,5.0" + } + + /*def psDuplicate(s: Int, t: Int) = (res: String) => ((s, t), res) passes { + case (0, 0) => "d 0,0 0,15 15,15 15,0" + case (-1, 2) => "d -1,-2 -1,15 15,15 15,2" + case (12, 5) => "d 12,5 12,15 15,15 15,5" + }*/ + + def repairUnWrapped(s: Int, t: Int): String = { + "(" + s + ", " + t + ")" + } ensuring psUnwrapped(s, t) + + def repairNameChangedPrefix(s: Int, t: Int): String = { + "(" + s + ", " + t + ")" + } ensuring psNameChangedPrefix(s, t) + + def repairNameChangedSuffix(s: Int, t: Int): String = { + "(" + s + ", " + t + ")" + } ensuring psNameChangedSuffix(s, t) + + /*def repairDuplicate(s: Int, t: Int): String = { + "(" + s + ", " + t + ")" + } ensuring psDuplicate(s)*/ + + + def synthesizeStandard(s: Int, t: Int): String = { + ???[String] + } ensuring psStandard(s, t) + + def synthesizeUnwrapped(s: Int, t: Int): String = { + ???[String] + } ensuring psUnwrapped(s, t) + + def synthesizeNameChangedPrefix(s: Int, t: Int): String = { + ???[String] + } ensuring psNameChangedPrefix(s, t) + + def synthesizeNameChangedSuffix(s: Int, t: Int): String = { + ???[String] + } ensuring psNameChangedSuffix(s, t) + + /*def synthesizeDuplicate(s: Int, t: Int): String = { + ???[String] + } ensuring psDuplicate(s)*/ +} \ No newline at end of file diff --git a/testcases/stringrender/runRepair.sh b/testcases/stringrender/runRepair.sh new file mode 100644 index 0000000000000000000000000000000000000000..c1b616351dc8cfd326b38358e80e722f3a31f4cf --- /dev/null +++ b/testcases/stringrender/runRepair.sh @@ -0,0 +1,64 @@ +#!/bin/bash +log=stringrender.last +summaryLog=stringrender.log +fullLog=fullLog.log + +echo -n "" > $log; +echo -n "" > "stringrender.table"; + + +echo "################################" >> $summaryLog +echo "#" `hostname` >> $summaryLog +echo "#" `date +"%d.%m.%Y %T"` >> $summaryLog +echo "#" `git log -1 --pretty=format:"%h - %cd"` >> $summaryLog +echo "################################" >> $summaryLog +echo "# Category, File, function, p.S, fuS, foS, Tms, Fms, Rms, verif?" >> $summaryLog + +#All benchmarks: + +#./leon --synthesis --timeout=30 --solvers=smt-cvc4 --functions=synthesizeIntStandard testcases/stringrender/Default.scala | tee -a $fullLog +#./leon --synthesis --timeout=30 --solvers=smt-cvc4 --functions=synthesizeBooleanStandard testcases/stringrender/Default.scala | tee -a $fullLog +#./leon --synthesis --timeout=30 --solvers=smt-cvc4 --functions=synthesizeBoolean2 testcases/stringrender/Default.scala | tee -a $fullLog + +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairUnwrapped testcases/stringrender/IntWrapperRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairNameChangedPrefix testcases/stringrender/IntWrapperRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairNameChangedSuffix testcases/stringrender/IntWrapperRender.scala | tee -a $fullLog +# ./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairDuplicate testcases/stringrender/IntWrapperRender.scala | tee -a $fullLog + +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairUnwrapped testcases/stringrender/TwoArgsWrapperRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairNameChangedPrefix testcases/stringrender/TwoArgsWrapperRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairNameChangedSuffix testcases/stringrender/TwoArgsWrapperRender.scala | tee -a $fullLog +# ./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairDuplicate testcases/stringrender/TwoArgsWrapperRender.scala | tee -a $fullLog + +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairUnwrapped testcases/stringrender/TupleWrapperRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairNameChangedPrefix testcases/stringrender/TupleWrapperRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairNameChangedSuffix testcases/stringrender/TupleWrapperRender.scala | tee -a $fullLog +# ./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=repairDuplicate testcases/stringrender/TupleWrapperRender.scala | tee -a $fullLog + +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render1RemoveCons testcases/stringrender/ListRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render2RemoveNil testcases/stringrender/ListRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render3RemoveLastComma testcases/stringrender/ListRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render4RemoveParentheses testcases/stringrender/ListRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render5WrapParentheses testcases/stringrender/ListRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render6List testcases/stringrender/ListRender.scala | tee -a $fullLog + +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render0RemoveBigInt testcases/stringrender/ListBigIntRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render1RemoveCons testcases/stringrender/ListBigIntRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render2RemoveNil testcases/stringrender/ListBigIntRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render3RemoveLastComma testcases/stringrender/ListBigIntRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render4RemoveParentheses testcases/stringrender/ListBigIntRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render5WrapParentheses testcases/stringrender/ListBigIntRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render6List testcases/stringrender/ListBigIntRender.scala | tee -a $fullLog + +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render0RemoveNames testcases/stringrender/GrammarRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render1ArrowRules testcases/stringrender/GrammarRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render2ListRules testcases/stringrender/GrammarRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render3SpaceRules testcases/stringrender/GrammarRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render4HTMLRules testcases/stringrender/GrammarRender.scala | tee -a $fullLog +./leon --repair --timeout=30 --solvers=smt-cvc4 --functions=render5PlainTextRules testcases/stringrender/GrammarRender.scala | tee -a $fullLog + + +# Average results +cat $log >> $summaryLog +awk '{ total1 += $7; total2 += $8; total3 += $9; count++ } END { printf "#%74s Avg: %5d, %5d, %5d\n\n", "", total1/count, total2/count, total3/count }' $log >> $summaryLog + diff --git a/testcases/synthesis/condabd/benchmarks/BatchedQueue/BatchedQueue.scala b/testcases/synthesis/condabd/benchmarks/BatchedQueue/BatchedQueue.scala index 64a8ae73ddd8553ff4ca364814aa880c41b503f8..3cf66229cb37d2c00e41ab63d0fbe4fb28aa4945 100644 --- a/testcases/synthesis/condabd/benchmarks/BatchedQueue/BatchedQueue.scala +++ b/testcases/synthesis/condabd/benchmarks/BatchedQueue/BatchedQueue.scala @@ -1,3 +1,4 @@ +import leon.annotation._ import leon.lang._ import leon.collection._ @@ -50,7 +51,7 @@ object BatchedQueue { case Cons(_, xs) => checkf(xs, p.r) } } - // + // // def last(p: Queue): Int = { // require(!isEmpty(p)) // p.r match { @@ -66,6 +67,7 @@ object BatchedQueue { (if (isEmpty(p)) true else content(tail(res)) ++ Set(x) == content(tail(res)))) + @ignore def main(args: Array[String]): Unit = { val pair = Queue(Cons(4, Nil), Cons(3, Nil)) diff --git a/testcases/synthesis/etienne-thesis/AddressBook/Make.scala b/testcases/synthesis/etienne-thesis/AddressBook/Make.scala new file mode 100644 index 0000000000000000000000000000000000000000..1fd12466c2c6f932f776c893a66e1a290d2212ca --- /dev/null +++ b/testcases/synthesis/etienne-thesis/AddressBook/Make.scala @@ -0,0 +1,53 @@ +import leon.annotation._ +import leon.lang._ +import leon.lang.synthesis._ + +object AddressBookMake { + + case class Address[A](info: A, priv: Boolean) + + sealed abstract class AddressList[A] { + def size: BigInt = { + this match { + case Nil() => BigInt(0) + case Cons(head, tail) => BigInt(1) + tail.size + } + } ensuring { res => res >= 0 } + + def content: Set[Address[A]] = this match { + case Nil() => Set[Address[A]]() + case Cons(addr, l1) => Set(addr) ++ l1.content + } + } + + case class Cons[A](a: Address[A], tail: AddressList[A]) extends AddressList[A] + case class Nil[A]() extends AddressList[A] + + def allPersonal[A](l: AddressList[A]): Boolean = l match { + case Nil() => true + case Cons(a, l1) => + if (a.priv) allPersonal(l1) + else false + } + + def allBusiness[A](l: AddressList[A]): Boolean = l match { + case Nil() => true + case Cons(a, l1) => + if (a.priv) false + else allBusiness(l1) + } + + case class AddressBook[A](business: AddressList[A], personal: AddressList[A]) { + def size: BigInt = business.size + personal.size + + def content: Set[Address[A]] = business.content ++ personal.content + + def invariant = { + allPersonal(personal) && allBusiness(business) + } + } + + def makeAddressBook[A](as: AddressList[A]): AddressBook[A] = { + choose( (res: AddressBook[A]) => res.content == as.content && res.invariant ) + } +} diff --git a/testcases/synthesis/etienne-thesis/AddressBook/Merge.scala b/testcases/synthesis/etienne-thesis/AddressBook/Merge.scala new file mode 100644 index 0000000000000000000000000000000000000000..92a5b5bfef213e35c4f2a76bbbc9f295e406d1f4 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/AddressBook/Merge.scala @@ -0,0 +1,69 @@ +import leon.annotation._ +import leon.lang._ +import leon.lang.synthesis._ + +object AddressBookMake { + + case class Address[A](info: A, priv: Boolean) + + sealed abstract class AddressList[A] { + def size: BigInt = { + this match { + case Nil() => BigInt(0) + case Cons(head, tail) => BigInt(1) + tail.size + } + } ensuring { res => res >= 0 } + + def content: Set[Address[A]] = this match { + case Nil() => Set[Address[A]]() + case Cons(addr, l1) => Set(addr) ++ l1.content + } + + def ++(that: AddressList[A]): AddressList[A] = { + this match { + case Cons(h, t) => Cons(h, t ++ that) + case Nil() => that + } + } ensuring { + res => res.content == this.content ++ that.content + } + } + + case class Cons[A](a: Address[A], tail: AddressList[A]) extends AddressList[A] + case class Nil[A]() extends AddressList[A] + + def allPersonal[A](l: AddressList[A]): Boolean = l match { + case Nil() => true + case Cons(a, l1) => + if (a.priv) allPersonal(l1) + else false + } + + def allBusiness[A](l: AddressList[A]): Boolean = l match { + case Nil() => true + case Cons(a, l1) => + if (a.priv) false + else allBusiness(l1) + } + + case class AddressBook[A](business: AddressList[A], personal: AddressList[A]) { + def size: BigInt = business.size + personal.size + + def content: Set[Address[A]] = business.content ++ personal.content + + @inline + def invariant = { + allPersonal(personal) && allBusiness(business) + } + } + + def merge[A](a1: AddressBook[A], a2: AddressBook[A]): AddressBook[A] = { + require(a1.invariant && a2.invariant) + + choose( (res: AddressBook[A]) => + res.personal.content == (a1.personal.content ++ a2.personal.content) && + res.business.content == (a1.business.content ++ a2.business.content) && + res.invariant + ) + } +} diff --git a/testcases/synthesis/etienne-thesis/BatchedQueue/Dequeue.scala b/testcases/synthesis/etienne-thesis/BatchedQueue/Dequeue.scala new file mode 100644 index 0000000000000000000000000000000000000000..c44fc062847b9af2264a9b3bba05186a313ab36b --- /dev/null +++ b/testcases/synthesis/etienne-thesis/BatchedQueue/Dequeue.scala @@ -0,0 +1,80 @@ +import leon.lang._ +import leon.lang.synthesis._ + +object BatchedQueue { + sealed abstract class List[T] { + def content: Set[T] = { + this match { + case Cons(h, t) => Set(h) ++ t.content + case Nil() => Set() + } + } + + def size: BigInt = { + this match { + case Cons(h, t) => BigInt(1) + t.size + case Nil() => BigInt(0) + } + } ensuring { _ >= 0 } + + def reverse: List[T] = { + this match { + case Cons(h, t) => t.reverse.append(Cons(h, Nil[T]())) + case Nil() => Nil[T]() + } + } ensuring { res => + this.content == res.content + } + + def append(r: List[T]): List[T] = { + this match { + case Cons(h, t) => Cons(h, t.append(r)) + case Nil() => r + } + } + + def isEmpty: Boolean = { + this == Nil[T]() + } + + def tail: List[T] = { + require(this != Nil[T]()) + this match { + case Cons(h, t) => t + } + } + + def head: T = { + require(this != Nil[T]()) + this match { + case Cons(h, t) => h + } + } + } + + case class Cons[T](h: T, t: List[T]) extends List[T] + case class Nil[T]() extends List[T] + + case class Queue[T](f: List[T], r: List[T]) { + def content: Set[T] = f.content ++ r.content + def size: BigInt = f.size + r.size + + def isEmpty: Boolean = f.isEmpty && r.isEmpty + + def invariant: Boolean = { + (f.isEmpty) ==> (r.isEmpty) + } + + def toList: List[T] = f.append(r.reverse) + + def dequeue: Queue[T] = { + require(invariant && !isEmpty) + + choose { (res: Queue[T]) => + res.size == size-1 && res.toList == this.toList.tail && res.invariant + } + } + } + + val test = Queue[BigInt](Cons(42, Nil()), Nil()).dequeue +} diff --git a/testcases/synthesis/etienne-thesis/BatchedQueue/Enqueue.scala b/testcases/synthesis/etienne-thesis/BatchedQueue/Enqueue.scala new file mode 100644 index 0000000000000000000000000000000000000000..fe01946d158153d2dd9ae2a3be2234ee4cd18aa9 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/BatchedQueue/Enqueue.scala @@ -0,0 +1,88 @@ +import leon.lang._ +import leon.lang.synthesis._ + +object BatchedQueue { + sealed abstract class List[T] { + def content: Set[T] = { + this match { + case Cons(h, t) => Set(h) ++ t.content + case Nil() => Set() + } + } + + def size: BigInt = { + this match { + case Cons(h, t) => BigInt(1) + t.size + case Nil() => BigInt(0) + } + } ensuring { _ >= 0 } + + def reverse: List[T] = { + this match { + case Cons(h, t) => t.reverse.append(Cons(h, Nil[T]())) + case Nil() => Nil[T]() + } + } ensuring { res => + this.content == res.content + } + + def append(r: List[T]): List[T] = { + this match { + case Cons(h, t) => Cons(h, t.append(r)) + case Nil() => r + } + } + + def tail: List[T] = { + require(this != Nil[T]()) + this match { + case Cons(h, t) => t + } + } + + def head: T = { + require(this != Nil[T]()) + this match { + case Cons(h, t) => h + } + } + + def last: T = { + require(this != Nil[T]()) + this match { + case Cons(h, Nil()) => h + case Cons(h, t) => t.last + } + } + } + + case class Cons[T](h: T, t: List[T]) extends List[T] + case class Nil[T]() extends List[T] + + case class Queue[T](f: List[T], r: List[T]) { + def content: Set[T] = f.content ++ r.content + def size: BigInt = f.size + r.size + + def invariant: Boolean = { + (f == Nil[T]()) ==> (r == Nil[T]()) + } + + def toList: List[T] = f.append(r.reverse) + + def enqueue(v: T): Queue[T] = { + require(invariant) + + f match { + case Cons(h, t) => + Queue(f, Cons(v, r)) + case Nil() => + Queue(Cons(v, f), Nil()) + } + + } ensuring { (res: Queue[T]) => + res.invariant && + res.toList.last == v && + res.content == this.content ++ Set(v) + } + } +} diff --git a/testcases/synthesis/etienne-thesis/Compiler/Desugar.scala b/testcases/synthesis/etienne-thesis/Compiler/Desugar.scala new file mode 100644 index 0000000000000000000000000000000000000000..2684c71b2b8bbc9e5fe6cc307899ca7b92d366b4 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/Compiler/Desugar.scala @@ -0,0 +1,118 @@ +import leon.lang._ +import leon.lang.synthesis._ +import leon._ + +object Compiler { + abstract class Expr + case class Plus(lhs: Expr, rhs: Expr) extends Expr + case class Minus(lhs: Expr, rhs: Expr) extends Expr + case class UMinus(e: Expr) extends Expr + case class LessThan(lhs: Expr, rhs: Expr) extends Expr + case class And(lhs: Expr, rhs: Expr) extends Expr + case class Implies(lhs: Expr, rhs: Expr) extends Expr + case class Or(lhs: Expr, rhs: Expr) extends Expr + case class Not(e : Expr) extends Expr + case class Eq(lhs: Expr, rhs: Expr) extends Expr + case class Ite(cond: Expr, thn: Expr, els: Expr) extends Expr + case class BoolLiteral(b : Boolean) extends Expr + case class IntLiteral(i: BigInt) extends Expr + + abstract class Value + case class BoolValue(b: Boolean) extends Value + case class IntValue(i: BigInt) extends Value + case object Error extends Value + + def eval(e: Expr): Value = e match { + case Plus(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => IntValue(il+ir) + case _ => Error + } + + case Minus(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => IntValue(il-ir) + case _ => Error + } + + case UMinus(l) => + eval(l) match { + case IntValue(b) => IntValue(-b) + case _ => Error + } + + case LessThan(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => BoolValue(il < ir) + case _ => Error + } + + case And(l, r) => + eval(l) match { + case b @ BoolValue(false) => b + case b: BoolValue => + eval(r) + case _ => + Error + } + + case Or(l, r) => + eval(l) match { + case b @ BoolValue(true) => + b + case b: BoolValue => + eval(r) + case _ => + Error + } + + case Implies(l, r) => + eval(l) match { + case b @ BoolValue(true) => + eval(r) + case b @ BoolValue(false) => + BoolValue(true) + case _ => Error + } + + case Not(l) => + eval(l) match { + case BoolValue(b) => BoolValue(!b) + case _ => Error + } + + case Eq(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => BoolValue(il == ir) + case (BoolValue(il), BoolValue(ir)) => BoolValue(il == ir) + case _ => Error + } + + case Ite(c, t, e) => + eval(c) match { + case BoolValue(true) => eval(t) + case BoolValue(false) => eval(t) + case _ => Error + } + + case IntLiteral(l) => IntValue(l) + case BoolLiteral(b) => BoolValue(b) + } + + def rewriteMinus(in: Minus): Expr = { + choose{ (out: Expr) => + eval(in) == eval(out) && !(out.isInstanceOf[Minus]) + } + } + + def rewriteImplies(in: Implies): Expr = { + choose{ (out: Expr) => + eval(in) == eval(out) && !(out.isInstanceOf[Implies]) + } + } + + + def plop(x: Expr) = { + eval(x) == Error//eval(Not(IntLiteral(BigInt(2)))) + }.holds +} diff --git a/testcases/synthesis/etienne-thesis/Compiler/DesugarImplies.scala b/testcases/synthesis/etienne-thesis/Compiler/DesugarImplies.scala new file mode 100644 index 0000000000000000000000000000000000000000..de5c544ff368043ca7847376af8cd9ae6b513f4d --- /dev/null +++ b/testcases/synthesis/etienne-thesis/Compiler/DesugarImplies.scala @@ -0,0 +1,144 @@ +import leon.lang._ +import leon.lang.synthesis._ +import leon._ + +object Compiler { + abstract class Expr + case class Plus(lhs: Expr, rhs: Expr) extends Expr + case class Minus(lhs: Expr, rhs: Expr) extends Expr + case class UMinus(e: Expr) extends Expr + case class LessThan(lhs: Expr, rhs: Expr) extends Expr + case class And(lhs: Expr, rhs: Expr) extends Expr + case class Implies(lhs: Expr, rhs: Expr) extends Expr + case class Or(lhs: Expr, rhs: Expr) extends Expr + case class Not(e : Expr) extends Expr + case class Eq(lhs: Expr, rhs: Expr) extends Expr + case class Ite(cond: Expr, thn: Expr, els: Expr) extends Expr + case class BoolLiteral(b : Boolean) extends Expr + case class IntLiteral(i: BigInt) extends Expr + + def exists(e: Expr, f: Expr => Boolean): Boolean = { + f(e) || (e match { + case Plus(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Minus(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case LessThan(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case And(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Or(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Implies(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Eq(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Ite(c, t, e) => exists(c, f) || exists(t, f) || exists(e, f) + case Not(e) => exists(e, f) + case UMinus(e) => exists(e, f) + case _ => false + }) + } + + abstract class Value + case class BoolValue(b: Boolean) extends Value + case class IntValue(i: BigInt) extends Value + case object Error extends Value + + def eval(e: Expr): Value = e match { + case Plus(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => IntValue(il+ir) + case _ => Error + } + + case Minus(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => IntValue(il-ir) + case _ => Error + } + + case UMinus(l) => + eval(l) match { + case IntValue(b) => IntValue(-b) + case _ => Error + } + + case LessThan(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => BoolValue(il < ir) + case _ => Error + } + + case And(l, r) => + eval(l) match { + case b @ BoolValue(false) => b + case b: BoolValue => + eval(r) + case _ => + Error + } + + case Or(l, r) => + eval(l) match { + case b @ BoolValue(true) => + b + case b: BoolValue => + eval(r) + case _ => + Error + } + + case Implies(l, r) => + eval(l) match { + case b @ BoolValue(true) => + eval(r) + case b @ BoolValue(false) => + BoolValue(true) + case _ => Error + } + + case Not(l) => + eval(l) match { + case BoolValue(b) => BoolValue(!b) + case _ => Error + } + + case Eq(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => BoolValue(il == ir) + case (BoolValue(il), BoolValue(ir)) => BoolValue(il == ir) + case _ => Error + } + + case Ite(c, t, e) => + eval(c) match { + case BoolValue(true) => eval(t) + case BoolValue(false) => eval(t) + case _ => Error + } + + case IntLiteral(l) => IntValue(l) + case BoolLiteral(b) => BoolValue(b) + } + + + //def desugar(e: Expr): Expr = { + // choose{ (out: Expr) => + // eval(e) == eval(out) && !exists(out, f => f.isInstanceOf[Implies]) + // } + //} + + def desugar(e: Expr): Expr = { + e match { + case Plus(lhs, rhs) => Plus(desugar(lhs), desugar(rhs)) + case Minus(lhs, rhs) => Minus(desugar(lhs), desugar(rhs)) + case LessThan(lhs, rhs) => LessThan(desugar(lhs), desugar(rhs)) + case And(lhs, rhs) => And(desugar(lhs), desugar(rhs)) + case Or(lhs, rhs) => Or(desugar(lhs), desugar(rhs)) + case Implies(lhs, rhs) => //Implies(desugar(lhs), desugar(rhs)) + Or(Not(desugar(lhs)), desugar(rhs)) + case Eq(lhs, rhs) => Eq(desugar(lhs), desugar(rhs)) + case Ite(c, t, e) => Ite(desugar(c), desugar(t), desugar(e)) + case Not(e) => Not(desugar(e)) + case UMinus(e) => UMinus(desugar(e)) + case e => e + } + } ensuring { out => + //eval(e) == eval(out) && + !exists(out, f => f.isInstanceOf[Implies]) + } +} diff --git a/testcases/synthesis/etienne-thesis/Compiler/RewriteImplies.scala b/testcases/synthesis/etienne-thesis/Compiler/RewriteImplies.scala new file mode 100644 index 0000000000000000000000000000000000000000..4b50b9cbe34043f65d1a8feeaadac929822e6c70 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/Compiler/RewriteImplies.scala @@ -0,0 +1,124 @@ +import leon.lang._ +import leon.lang.synthesis._ +import leon._ + +object Compiler { + abstract class Expr + case class Plus(lhs: Expr, rhs: Expr) extends Expr + case class Minus(lhs: Expr, rhs: Expr) extends Expr + case class UMinus(e: Expr) extends Expr + case class LessThan(lhs: Expr, rhs: Expr) extends Expr + case class And(lhs: Expr, rhs: Expr) extends Expr + case class Implies(lhs: Expr, rhs: Expr) extends Expr + case class Or(lhs: Expr, rhs: Expr) extends Expr + case class Not(e : Expr) extends Expr + case class Eq(lhs: Expr, rhs: Expr) extends Expr + case class Ite(cond: Expr, thn: Expr, els: Expr) extends Expr + case class BoolLiteral(b : Boolean) extends Expr + case class IntLiteral(i: BigInt) extends Expr + + def exists(e: Expr, f: Expr => Boolean): Boolean = { + f(e) || (e match { + case Plus(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Minus(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case LessThan(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case And(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Or(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Implies(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Eq(lhs, rhs) => exists(lhs, f) || exists(rhs, f) + case Ite(c, t, e) => exists(c, f) || exists(t, f) || exists(e, f) + case Not(e) => exists(e, f) + case UMinus(e) => exists(e, f) + case _ => false + }) + } + + abstract class Value + case class BoolValue(b: Boolean) extends Value + case class IntValue(i: BigInt) extends Value + case object Error extends Value + + def eval(e: Expr): Value = e match { + case Plus(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => IntValue(il+ir) + case _ => Error + } + + case Minus(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => IntValue(il-ir) + case _ => Error + } + + case UMinus(l) => + eval(l) match { + case IntValue(b) => IntValue(-b) + case _ => Error + } + + case LessThan(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => BoolValue(il < ir) + case _ => Error + } + + case And(l, r) => + eval(l) match { + case b @ BoolValue(false) => b + case b: BoolValue => + eval(r) + case _ => + Error + } + + case Or(l, r) => + eval(l) match { + case b @ BoolValue(true) => + b + case b: BoolValue => + eval(r) + case _ => + Error + } + + case Implies(l, r) => + eval(l) match { + case b @ BoolValue(true) => + eval(r) + case b @ BoolValue(false) => + BoolValue(true) + case _ => Error + } + + case Not(l) => + eval(l) match { + case BoolValue(b) => BoolValue(!b) + case _ => Error + } + + case Eq(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => BoolValue(il == ir) + case (BoolValue(il), BoolValue(ir)) => BoolValue(il == ir) + case _ => Error + } + + case Ite(c, t, e) => + eval(c) match { + case BoolValue(true) => eval(t) + case BoolValue(false) => eval(t) + case _ => Error + } + + case IntLiteral(l) => IntValue(l) + case BoolLiteral(b) => BoolValue(b) + } + + + def desugar(in: Expr): Expr = { + choose{ (out: Expr) => + eval(in) == eval(out) && !(out.isInstanceOf[Implies]) + } + } +} diff --git a/testcases/synthesis/etienne-thesis/Compiler/RewriteMinus.scala b/testcases/synthesis/etienne-thesis/Compiler/RewriteMinus.scala new file mode 100644 index 0000000000000000000000000000000000000000..de2fa4360de5fa899110b43c171e592be5282803 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/Compiler/RewriteMinus.scala @@ -0,0 +1,107 @@ +import leon.lang._ +import leon.lang.synthesis._ +import leon._ + +object Compiler { + abstract class Expr + case class Plus(lhs: Expr, rhs: Expr) extends Expr + case class Minus(lhs: Expr, rhs: Expr) extends Expr + case class UMinus(e: Expr) extends Expr + case class LessThan(lhs: Expr, rhs: Expr) extends Expr + case class And(lhs: Expr, rhs: Expr) extends Expr + case class Implies(lhs: Expr, rhs: Expr) extends Expr + case class Or(lhs: Expr, rhs: Expr) extends Expr + case class Not(e : Expr) extends Expr + case class Eq(lhs: Expr, rhs: Expr) extends Expr + case class Ite(cond: Expr, thn: Expr, els: Expr) extends Expr + case class BoolLiteral(b : Boolean) extends Expr + case class IntLiteral(i: BigInt) extends Expr + + abstract class Value + case class BoolValue(b: Boolean) extends Value + case class IntValue(i: BigInt) extends Value + case object Error extends Value + + def eval(e: Expr): Value = e match { + case Plus(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => IntValue(il+ir) + case _ => Error + } + + case Minus(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => IntValue(il-ir) + case _ => Error + } + + case UMinus(l) => + eval(l) match { + case IntValue(b) => IntValue(-b) + case _ => Error + } + + case LessThan(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => BoolValue(il < ir) + case _ => Error + } + + case And(l, r) => + eval(l) match { + case b @ BoolValue(false) => b + case b: BoolValue => + eval(r) + case _ => + Error + } + + case Or(l, r) => + eval(l) match { + case b @ BoolValue(true) => + b + case b: BoolValue => + eval(r) + case _ => + Error + } + + case Implies(l, r) => + eval(l) match { + case b @ BoolValue(true) => + eval(r) + case b @ BoolValue(false) => + BoolValue(true) + case _ => Error + } + + case Not(l) => + eval(l) match { + case BoolValue(b) => BoolValue(!b) + case _ => Error + } + + case Eq(l, r) => + (eval(l), eval(r)) match { + case (IntValue(il), IntValue(ir)) => BoolValue(il == ir) + case (BoolValue(il), BoolValue(ir)) => BoolValue(il == ir) + case _ => Error + } + + case Ite(c, t, e) => + eval(c) match { + case BoolValue(true) => eval(t) + case BoolValue(false) => eval(t) + case _ => Error + } + + case IntLiteral(l) => IntValue(l) + case BoolLiteral(b) => BoolValue(b) + } + + def rewriteMinus(in: Minus): Expr = { + choose{ (out: Expr) => + eval(in) == eval(out) && !(out.isInstanceOf[Minus]) + } + } +} diff --git a/testcases/synthesis/etienne-thesis/List/Delete.scala b/testcases/synthesis/etienne-thesis/List/Delete.scala index 46f0710878d25a30e679f034ff07f985078950d1..30716c5919256f052d0a0e741a147d30c5bc82fa 100644 --- a/testcases/synthesis/etienne-thesis/List/Delete.scala +++ b/testcases/synthesis/etienne-thesis/List/Delete.scala @@ -7,7 +7,7 @@ object Delete { case class Cons(head: BigInt, tail: List) extends List case object Nil extends List - def size(l: List) : BigInt = (l match { + def size(l: List): BigInt = (l match { case Nil => BigInt(0) case Cons(_, t) => BigInt(1) + size(t) }) ensuring(res => res >= 0) @@ -17,25 +17,10 @@ object Delete { case Cons(i, t) => Set(i) ++ content(t) } - def insert(in1: List, v: BigInt): List = { - Cons(v, in1) - } ensuring { content(_) == content(in1) ++ Set(v) } - - //def delete(in1: List, v: BigInt): List = { - // in1 match { - // case Cons(h,t) => - // if (h == v) { - // delete(t, v) - // } else { - // Cons(h, delete(t, v)) - // } - // case Nil => - // Nil - // } - //} ensuring { content(_) == content(in1) -- Set(v) } - - def delete(in1: List, v: BigInt) = choose { + def delete(in: List, v: BigInt) = { + ???[List] + } ensuring { (out : List) => - content(out) == content(in1) -- Set(v) + content(out) == content(in) -- Set(v) } } diff --git a/testcases/synthesis/etienne-thesis/SortedList/Delete.scala b/testcases/synthesis/etienne-thesis/SortedList/Delete.scala new file mode 100644 index 0000000000000000000000000000000000000000..788f232d35449cff75ac27442ce7e9cb50f42391 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/SortedList/Delete.scala @@ -0,0 +1,34 @@ +import leon.annotation._ +import leon.lang._ +import leon.lang.synthesis._ + +object SortedListDelete { + sealed abstract class List + case class Cons(head: BigInt, tail: List) extends List + case object Nil extends List + + def size(l: List): BigInt = (l match { + case Nil => BigInt(0) + case Cons(_, t) => BigInt(1) + size(t) + }) ensuring(res => res >= 0) + + def content(l: List): Set[BigInt] = l match { + case Nil => Set.empty[BigInt] + case Cons(i, t) => Set(i) ++ content(t) + } + + def isSorted(list: List): Boolean = list match { + case Nil => true + case Cons(_, Nil) => true + case Cons(x1, Cons(x2, _)) if(x1 > x2) => false + case Cons(_, xs) => isSorted(xs) + } + + def delete(in: List, v: BigInt) = { + require(isSorted(in)) + + choose( (res : List) => + (content(res) == content(in) -- Set(v)) && isSorted(res) + ) + } +} diff --git a/testcases/synthesis/etienne-thesis/SortedList/Diff.scala b/testcases/synthesis/etienne-thesis/SortedList/Diff.scala new file mode 100644 index 0000000000000000000000000000000000000000..d391b85ba60e2b405530ddb45b40d94071263725 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/SortedList/Diff.scala @@ -0,0 +1,53 @@ +import leon.annotation._ +import leon.lang._ +import leon.lang.synthesis._ + +object SortedListDiff { + sealed abstract class List + case class Cons(head: BigInt, tail: List) extends List + case object Nil extends List + + def size(l: List): BigInt = (l match { + case Nil => BigInt(0) + case Cons(_, t) => BigInt(1) + size(t) + }) ensuring(res => res >= 0) + + def content(l: List): Set[BigInt] = l match { + case Nil => Set.empty[BigInt] + case Cons(i, t) => Set(i) ++ content(t) + } + + def isSorted(list: List): Boolean = list match { + case Nil => true + case Cons(_, Nil) => true + case Cons(x1, Cons(x2, _)) if(x1 > x2) => false + case Cons(_, xs) => isSorted(xs) + } + + def delete(in1: List, v: BigInt): List = { + require(isSorted(in1)) + in1 match { + case Cons(h,t) => + if (h < v) { + Cons(h, delete(t, v)) + } else if (h == v) { + delete(t, v) + } else { + in1 + } + case Nil => + Nil + } + } ensuring { res => content(res) == content(in1) -- Set(v) && isSorted(res) } + + + def diff(in1: List, in2: List) = { + require(isSorted(in1) && isSorted(in2)) + + choose { + (out : List) => + (content(out) == content(in1) -- content(in2)) && isSorted(out) + } + } + +} diff --git a/testcases/synthesis/etienne-thesis/SortedList/Insert.scala b/testcases/synthesis/etienne-thesis/SortedList/Insert.scala new file mode 100644 index 0000000000000000000000000000000000000000..0bf80d1e99cef290e602b0da905d6966713c77b7 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/SortedList/Insert.scala @@ -0,0 +1,34 @@ +import leon.annotation._ +import leon.lang._ +import leon.lang.synthesis._ + +object SortedListInsert { + sealed abstract class List + case class Cons(head: BigInt, tail: List) extends List + case object Nil extends List + + def size(l: List): BigInt = (l match { + case Nil => BigInt(0) + case Cons(_, t) => BigInt(1) + size(t) + }) ensuring(res => res >= 0) + + def content(l: List): Set[BigInt] = l match { + case Nil => Set.empty[BigInt] + case Cons(i, t) => Set(i) ++ content(t) + } + + def isSorted(list: List): Boolean = list match { + case Nil => true + case Cons(_, Nil) => true + case Cons(x1, Cons(x2, _)) if(x1 > x2) => false + case Cons(_, xs) => isSorted(xs) + } + + def insert(in1: List, v: BigInt): List = { + require(isSorted(in1)) + + choose { (out : List) => + (content(out) == content(in1) ++ Set(v)) && isSorted(out) + } + } +} diff --git a/testcases/synthesis/etienne-thesis/SortedList/InsertAlways.scala b/testcases/synthesis/etienne-thesis/SortedList/InsertAlways.scala new file mode 100644 index 0000000000000000000000000000000000000000..73e0b240d776780fab6ac8091a8f0c925c56e695 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/SortedList/InsertAlways.scala @@ -0,0 +1,34 @@ +import leon.annotation._ +import leon.lang._ +import leon.lang.synthesis._ + +object SortedListInsertAlways { + sealed abstract class List + case class Cons(head: BigInt, tail: List) extends List + case object Nil extends List + + def size(l: List): BigInt = (l match { + case Nil => BigInt(0) + case Cons(_, t) => BigInt(1) + size(t) + }) ensuring(res => res >= 0) + + def content(l: List): Set[BigInt] = l match { + case Nil => Set.empty[BigInt] + case Cons(i, t) => Set(i) ++ content(t) + } + + def isSorted(list: List): Boolean = list match { + case Nil => true + case Cons(_, Nil) => true + case Cons(x1, Cons(x2, _)) if(x1 > x2) => false + case Cons(_, xs) => isSorted(xs) + } + + def insertAlways(in1: List, v: BigInt) = { + require(isSorted(in1)) + + choose{ (out : List) => + (content(out) == content(in1) ++ Set(v)) && isSorted(out) && size(out) == size(in1) + 1 + } + } +} diff --git a/testcases/synthesis/etienne-thesis/SortedList/InsertionSort.scala b/testcases/synthesis/etienne-thesis/SortedList/InsertionSort.scala new file mode 100644 index 0000000000000000000000000000000000000000..0752ad33fe2249de829d7180e6facaab8fb6e160 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/SortedList/InsertionSort.scala @@ -0,0 +1,49 @@ +import leon.annotation._ +import leon.lang._ +import leon.lang.synthesis._ + +object SortedListInsertionSort { + sealed abstract class List + case class Cons(head: BigInt, tail: List) extends List + case object Nil extends List + + def size(l: List): BigInt = (l match { + case Nil => BigInt(0) + case Cons(_, t) => BigInt(1) + size(t) + }) ensuring(res => res >= 0) + + def content(l: List): Set[BigInt] = l match { + case Nil => Set.empty[BigInt] + case Cons(i, t) => Set(i) ++ content(t) + } + + def isSorted(list: List): Boolean = list match { + case Nil => true + case Cons(_, Nil) => true + case Cons(x1, Cons(x2, _)) if(x1 > x2) => false + case Cons(_, xs) => isSorted(xs) + } + + def insert(in1: List, v: BigInt): List = { + require(isSorted(in1)) + in1 match { + case Cons(h, t) => + if (v < h) { + Cons(v, in1) + } else if (v == h) { + in1 + } else { + Cons(h, insert(t, v)) + } + case Nil => + Cons(v, Nil) + } + + } ensuring { res => (content(res) == content(in1) ++ Set(v)) && isSorted(res) } + + def insertionSort(in1: List): List = { + choose { (out: List) => + content(out) == content(in1) && isSorted(out) + } + } +} diff --git a/testcases/synthesis/etienne-thesis/SortedList/Union.scala b/testcases/synthesis/etienne-thesis/SortedList/Union.scala new file mode 100644 index 0000000000000000000000000000000000000000..d3f11adcccc07e44d1b82c1c6aed7144cb30523c --- /dev/null +++ b/testcases/synthesis/etienne-thesis/SortedList/Union.scala @@ -0,0 +1,51 @@ +import leon.annotation._ +import leon.lang._ +import leon.lang.synthesis._ + +object SortedListUnion { + sealed abstract class List + case class Cons(head: BigInt, tail: List) extends List + case object Nil extends List + + def size(l: List): BigInt = (l match { + case Nil => BigInt(0) + case Cons(_, t) => BigInt(1) + size(t) + }) ensuring(res => res >= 0) + + def content(l: List): Set[BigInt] = l match { + case Nil => Set.empty[BigInt] + case Cons(i, t) => Set(i) ++ content(t) + } + + def isSorted(list: List): Boolean = list match { + case Nil => true + case Cons(_, Nil) => true + case Cons(x1, Cons(x2, _)) if(x1 > x2) => false + case Cons(_, xs) => isSorted(xs) + } + + def insert(in1: List, v: BigInt): List = { + require(isSorted(in1)) + in1 match { + case Cons(h, t) => + if (v < h) { + Cons(v, in1) + } else if (v == h) { + in1 + } else { + Cons(h, insert(t, v)) + } + case Nil => + Cons(v, Nil) + } + + } ensuring { res => (content(res) == content(in1) ++ Set(v)) && isSorted(res) } + + def union(in1: List, in2: List) = { + require(isSorted(in1) && isSorted(in2)) + choose { + (out : List) => + (content(out) == content(in1) ++ content(in2)) && isSorted(out) + } + } +} diff --git a/testcases/synthesis/etienne-thesis/StrictSortedList/Delete.scala b/testcases/synthesis/etienne-thesis/StrictSortedList/Delete.scala new file mode 100644 index 0000000000000000000000000000000000000000..23999d96c3577b80b00b252e0c07509f4b4b2176 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/StrictSortedList/Delete.scala @@ -0,0 +1,34 @@ +import leon.annotation._ +import leon.lang._ +import leon.lang.synthesis._ + +object StrictSortedListDelete { + sealed abstract class List + case class Cons(head: BigInt, tail: List) extends List + case object Nil extends List + + def size(l: List): BigInt = (l match { + case Nil => BigInt(0) + case Cons(_, t) => BigInt(1) + size(t) + }) ensuring(res => res >= 0) + + def content(l: List): Set[BigInt] = l match { + case Nil => Set.empty[BigInt] + case Cons(i, t) => Set(i) ++ content(t) + } + + def isSorted(list: List): Boolean = list match { + case Nil => true + case Cons(_, Nil) => true + case Cons(x1, Cons(x2, _)) if(x1 >= x2) => false + case Cons(_, xs) => isSorted(xs) + } + + def delete(in: List, v: BigInt) = { + require(isSorted(in)) + + choose( (res : List) => + (content(res) == content(in) -- Set(v)) && isSorted(res) + ) + } +} diff --git a/testcases/synthesis/etienne-thesis/StrictSortedList/Insert.scala b/testcases/synthesis/etienne-thesis/StrictSortedList/Insert.scala new file mode 100644 index 0000000000000000000000000000000000000000..65f3c01f9d7b7d8d45136196a6289088df46fa22 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/StrictSortedList/Insert.scala @@ -0,0 +1,34 @@ +import leon.annotation._ +import leon.lang._ +import leon.lang.synthesis._ + +object StrictSortedListInsert { + sealed abstract class List + case class Cons(head: BigInt, tail: List) extends List + case object Nil extends List + + def size(l: List): BigInt = (l match { + case Nil => BigInt(0) + case Cons(_, t) => BigInt(1) + size(t) + }) ensuring(res => res >= 0) + + def content(l: List): Set[BigInt] = l match { + case Nil => Set.empty[BigInt] + case Cons(i, t) => Set(i) ++ content(t) + } + + def isSorted(list: List): Boolean = list match { + case Nil => true + case Cons(_, Nil) => true + case Cons(x1, Cons(x2, _)) if(x1 >= x2) => false + case Cons(_, xs) => isSorted(xs) + } + + def insert(in1: List, v: BigInt): List = { + require(isSorted(in1)) + + choose { (out : List) => + (content(out) == content(in1) ++ Set(v)) && isSorted(out) + } + } +} diff --git a/testcases/synthesis/etienne-thesis/StrictSortedList/Union.scala b/testcases/synthesis/etienne-thesis/StrictSortedList/Union.scala new file mode 100644 index 0000000000000000000000000000000000000000..c10ed419d3c2cc4fefc9690efb37bc723799ef1c --- /dev/null +++ b/testcases/synthesis/etienne-thesis/StrictSortedList/Union.scala @@ -0,0 +1,51 @@ +import leon.annotation._ +import leon.lang._ +import leon.lang.synthesis._ + +object StrictSortedListUnion { + sealed abstract class List + case class Cons(head: BigInt, tail: List) extends List + case object Nil extends List + + def size(l: List): BigInt = (l match { + case Nil => BigInt(0) + case Cons(_, t) => BigInt(1) + size(t) + }) ensuring(res => res >= 0) + + def content(l: List): Set[BigInt] = l match { + case Nil => Set.empty[BigInt] + case Cons(i, t) => Set(i) ++ content(t) + } + + def isSorted(list: List): Boolean = list match { + case Nil => true + case Cons(_, Nil) => true + case Cons(x1, Cons(x2, _)) if(x1 >= x2) => false + case Cons(_, xs) => isSorted(xs) + } + + def insert(in1: List, v: BigInt): List = { + require(isSorted(in1)) + in1 match { + case Cons(h, t) => + if (v < h) { + Cons(v, in1) + } else if (v == h) { + in1 + } else { + Cons(h, insert(t, v)) + } + case Nil => + Cons(v, Nil) + } + + } ensuring { res => (content(res) == content(in1) ++ Set(v)) && isSorted(res) } + + def union(in1: List, in2: List) = { + require(isSorted(in1) && isSorted(in2)) + choose { + (out : List) => + (content(out) == content(in1) ++ content(in2)) && isSorted(out) + } + } +} diff --git a/testcases/synthesis/etienne-thesis/UnaryNumerals/Add.scala b/testcases/synthesis/etienne-thesis/UnaryNumerals/Add.scala new file mode 100644 index 0000000000000000000000000000000000000000..a34d1834fbb5cc2418ca830c5576e64d74169b4f --- /dev/null +++ b/testcases/synthesis/etienne-thesis/UnaryNumerals/Add.scala @@ -0,0 +1,21 @@ +import leon.lang._ +import leon.lang.synthesis._ + +object UnaryNumeralsAdd { + sealed abstract class Num + case object Z extends Num + case class S(pred: Num) extends Num + + def value(n: Num): BigInt = { + n match { + case Z => BigInt(0) + case S(p) => BigInt(1) + value(p) + } + } ensuring (_ >= 0) + + def add(x: Num, y: Num): Num = { + choose { (r : Num) => + value(r) == value(x) + value(y) + } + } +} diff --git a/testcases/synthesis/etienne-thesis/UnaryNumerals/Distinct.scala b/testcases/synthesis/etienne-thesis/UnaryNumerals/Distinct.scala new file mode 100644 index 0000000000000000000000000000000000000000..4287378d1f95225e4ff1ffae57b2d0579d72c3a5 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/UnaryNumerals/Distinct.scala @@ -0,0 +1,30 @@ +import leon.lang._ +import leon.lang.synthesis._ + +object UnaryNumeralsDistinct { + sealed abstract class Num + case object Z extends Num + case class S(pred: Num) extends Num + + def value(n: Num): BigInt = { + n match { + case Z => BigInt(0) + case S(p) => BigInt(1) + value(p) + } + } ensuring (_ >= 0) + + def add(x: Num, y: Num): Num = { + x match { + case S(p) => S(add(p, y)) + case Z => y + } + } ensuring { (r : Num) => + value(r) == value(x) + value(y) + } + + def distinct(x: Num, y: Num): Num = { + choose { (r : Num) => + r != x && r != y + } + } +} diff --git a/testcases/synthesis/etienne-thesis/UnaryNumerals/Mult.scala b/testcases/synthesis/etienne-thesis/UnaryNumerals/Mult.scala new file mode 100644 index 0000000000000000000000000000000000000000..bfa39365e8a8b35555ddc2265f40c775251b8d17 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/UnaryNumerals/Mult.scala @@ -0,0 +1,30 @@ +import leon.lang._ +import leon.lang.synthesis._ + +object UnaryNumeralsMult { + sealed abstract class Num + case object Z extends Num + case class S(pred: Num) extends Num + + def value(n: Num): BigInt = { + n match { + case Z => BigInt(0) + case S(p) => BigInt(1) + value(p) + } + } ensuring (_ >= 0) + + def add(x: Num, y: Num): Num = { + x match { + case S(p) => S(add(p, y)) + case Z => y + } + } ensuring { (r : Num) => + value(r) == value(x) + value(y) + } + + def mult(x: Num, y: Num): Num = { + choose { (r : Num) => + value(r) == value(x) * value(y) + } + } +} diff --git a/testcases/synthesis/etienne-thesis/run.sh b/testcases/synthesis/etienne-thesis/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..ee64d86702076bf5ff909c3437f321498a2afe68 --- /dev/null +++ b/testcases/synthesis/etienne-thesis/run.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +function run { + cmd="./leon --debug=report --timeout=30 --synthesis $1" + echo "Running " $cmd + echo "------------------------------------------------------------------------------------------------------------------" + $cmd; +} + +echo "==================================================================================================================" >> synthesis-report.txt +# These are all the benchmarks included in my thesis +# List +run testcases/synthesis/etienne-thesis/List/Insert.scala +run testcases/synthesis/etienne-thesis/List/Delete.scala +run testcases/synthesis/etienne-thesis/List/Union.scala +run testcases/synthesis/etienne-thesis/List/Diff.scala +run testcases/synthesis/etienne-thesis/List/Split.scala + +# SortedList +run testcases/synthesis/etienne-thesis/SortedList/Insert.scala +run testcases/synthesis/etienne-thesis/SortedList/InsertAlways.scala +run testcases/synthesis/etienne-thesis/SortedList/Delete.scala +run testcases/synthesis/etienne-thesis/SortedList/Union.scala +run testcases/synthesis/etienne-thesis/SortedList/Diff.scala +run testcases/synthesis/etienne-thesis/SortedList/InsertionSort.scala + +# StrictSortedList +run testcases/synthesis/etienne-thesis/StrictSortedList/Insert.scala +run testcases/synthesis/etienne-thesis/StrictSortedList/Delete.scala +run testcases/synthesis/etienne-thesis/StrictSortedList/Union.scala + +# UnaryNumerals +run testcases/synthesis/etienne-thesis/UnaryNumerals/Add.scala +run testcases/synthesis/etienne-thesis/UnaryNumerals/Distinct.scala +run testcases/synthesis/etienne-thesis/UnaryNumerals/Mult.scala + +# BatchedQueue +#run testcases/synthesis/etienne-thesis/BatchedQueue/Enqueue.scala +run testcases/synthesis/etienne-thesis/BatchedQueue/Dequeue.scala + +# AddressBook +#run testcases/synthesis/etienne-thesis/AddressBook/Make.scala +run testcases/synthesis/etienne-thesis/AddressBook/Merge.scala diff --git a/testcases/verification/case-studies/LambdaSound.scala b/testcases/verification/case-studies/LambdaSound.scala index 6208d2039eaf16ba1dbeae614bf6432e2309ee1e..fa59efbc001614acbd338b83883248d07a736a2d 100644 --- a/testcases/verification/case-studies/LambdaSound.scala +++ b/testcases/verification/case-studies/LambdaSound.scala @@ -3,7 +3,6 @@ import leon.annotation._ import leon.collection._ import leon.lang.synthesis._ import leon._ -import leon.lang.string._ object Lambda { case class Id(v: BigInt) diff --git a/testcases/verification/case-studies/Robot3po.scala b/testcases/verification/case-studies/Robot3po.scala index 56709fc5555175ede38fac4f00ec6e772ecb9d7d..b189db5e98852b61f9463232d1036a5fa519a8aa 100644 --- a/testcases/verification/case-studies/Robot3po.scala +++ b/testcases/verification/case-studies/Robot3po.scala @@ -365,7 +365,7 @@ object Robot { } def validState(rs: RobotState)(implicit w: World): Boolean = { - + // 6) Sensors have consistent data val recentData = rs.ns.validData && rs.hs.validData @@ -509,6 +509,7 @@ object Robot { } + @ignore def main(a: Array[String]): Unit = { val map0 = """|XXXXXXXXX |XR FF X diff --git a/testcases/verification/compilation/Interpreter.scala b/testcases/verification/compilation/Interpreter.scala index 40dcab73cdb00b0b65dd960ca3e2427e4fa503c7..1c8e3bbde2b6dc18ba42211f2bfc498a0bcfaf71 100644 --- a/testcases/verification/compilation/Interpreter.scala +++ b/testcases/verification/compilation/Interpreter.scala @@ -1,12 +1,13 @@ +import leon.annotation._ import leon.lang._ object Interpret { - abstract class BoolTree + abstract class BoolTree case class Eq(t1 : IntTree, t2 : IntTree) extends BoolTree case class And(t1 : BoolTree, t2 : BoolTree) extends BoolTree case class Not(t : BoolTree) extends BoolTree - abstract class IntTree + abstract class IntTree case class Const(c:Int) extends IntTree case class Var(index:Int) extends IntTree case class Plus(t1 : IntTree, t2 : IntTree) extends IntTree @@ -19,7 +20,7 @@ object Interpret { } def beval(t:BoolTree, x0 : Int) : Boolean = { - t match { + t match { case Less(t1, t2) => ieval(t1,x0) < ieval(t2,x0) case Eq(t1, t2) => ieval(t1,x0) == ieval(t2,x0) case And(t1, t2) => beval(t1,x0) && beval(t2,x0) @@ -56,6 +57,7 @@ object Interpret { !treeBad(If(Less(Const(0),Var(0)), Var(0), Minus(Const(0),Var(0)))) } holds + @ignore def main(args : Array[String]) { thereIsGoodTree() } diff --git a/testcases/verification/compilation/SimpInterpret.scala b/testcases/verification/compilation/SimpInterpret.scala index fab5035897d86b4b587715cb4d7ef3459c1fed2f..6c61bad49173ecc0ff9edafc432d11eb6ffbbadb 100644 --- a/testcases/verification/compilation/SimpInterpret.scala +++ b/testcases/verification/compilation/SimpInterpret.scala @@ -1,5 +1,5 @@ //import scala.collection.immutable.Set -//import leon.annotation._ +import leon.annotation._ import leon.lang._ object Interpret { @@ -61,6 +61,7 @@ object Interpret { !treeBad(If(Less(Const(0),Var()), Var(), Minus(Const(0),Var()))) } holds + @ignore def main(args : Array[String]) { thereIsGoodTree() } diff --git a/testcases/verification/editor/AsciiToPos.scala b/testcases/verification/editor/AsciiToPos.scala index 4ec536ce71c8f684dc1a2329c51de19cf575269f..1514ccc13733e6e3571a51081a89a2c490a72f30 100644 --- a/testcases/verification/editor/AsciiToPos.scala +++ b/testcases/verification/editor/AsciiToPos.scala @@ -1,6 +1,5 @@ import leon.lang._ import leon.lang.synthesis._ -import leon.lang.string._ import leon.collection._ object Justify { @@ -14,7 +13,7 @@ object Justify { Cons(wordAcc, tokenize0(t, "")) } } else { - tokenize0(t, String(List(h)) + wordAcc) + tokenize0(t, h + wordAcc) } } diff --git a/testcases/verification/graph/MST/MSTMap.scala b/testcases/verification/graph/MST/MSTMap.scala index 1ef72da321d96c6b65977427c2aff485212c990e..e718256b6d226dea77efddadc970ffc44c7dab0f 100644 --- a/testcases/verification/graph/MST/MSTMap.scala +++ b/testcases/verification/graph/MST/MSTMap.scala @@ -15,16 +15,16 @@ object MSTMap { def mst(g : Graph) : Map[(Int,Int), Int] = { require(invariant(g) && isUndirected(g) && isConnected(g) && g.nVertices <= 3) - + var uf_map = Map.empty[Int, Int] //map to represent parent //relationship to model union find var spanningTree = Map.empty[(Int,Int), Int] var alreadyTested = Map.empty[(Int, Int), Int] - + (while(!isConnected(spanningTree, g.nVertices)) { val next_edge = getSmallestEdge(g.nVertices, g.edges, alreadyTested) - + val p1 = uFind(next_edge._1, uf_map) val p2 = uFind(next_edge._2, uf_map) if(p1 != p2) { @@ -45,18 +45,18 @@ object MSTMap { // We only verify that the edge set returned corresponds to some // spanning tree, but not that it is of minimal weight. - + // Here, we always take the smallest edge regardless whether it // creates a cycle or not, // Leon loops def mstBogus(g : Graph) : Map[(Int,Int), Int] = { require(invariant(g) && isUndirected(g) && isConnected(g) && g.nVertices <= 4) - + var edges = g.edges var spanningTree = Map.empty[(Int,Int), Int] var alreadyTested = Map.empty[(Int, Int), Int] - + (while(!isConnected(spanningTree, g.nVertices)) { val next_edge = getSmallestEdge(g.nVertices, edges, alreadyTested) @@ -73,13 +73,13 @@ object MSTMap { spanningTree } ensuring(x => isAcyclic(x, g.nVertices) && isConnected(x, g.nVertices)) - + /* ----------------------------------------------------------------------------- GRAPH FUNCTIONS ----------------------------------------------------------------------------- */ - + def invariant(g : Graph) = { def noSelfLoops(i : Int) : Boolean = { @@ -89,10 +89,10 @@ object MSTMap { else noSelfLoops(i+1) } - + g.nVertices >= 0 && noSelfLoops(0) } - + /** Tests, if g is undirected, that is if for every edge (i,j) there is also and edge (j,i) @@ -108,11 +108,11 @@ object MSTMap { var j = 0 while(j < g.nVertices && res) { res = !edgeSet.isDefinedAt(i,j) || edgeSet.isDefinedAt(j,i) - + //If weights should considered if(res && edgeSet.isDefinedAt(i,j)) res = edgeSet(i,j) == edgeSet(j,i) - + j += 1 } i += 1 @@ -136,11 +136,11 @@ object MSTMap { it this function would always return a valid edge, however, this property is not easily expressible in Leon. */ - + var i = 0 val big = 100000000 var res = (-1,-1,big) - + while(i < nVertices) { var j = 0 while(j < nVertices) { @@ -171,7 +171,7 @@ object MSTMap { require(g.nVertices >= 0 && isUndirected(g)) isConnected(g.edges, g.nVertices) } - + def isConnected(edges : Map[(Int,Int), Int], nVertices : Int) : Boolean = { require(nVertices >= 0) val uf = calculateUF(edges, nVertices)._1 @@ -204,7 +204,7 @@ object MSTMap { def calculateUF(edgeSet : Map[(Int,Int), Int], nVertices : Int) : (Map[Int, Int], Boolean)= { require(nVertices >= 0) - + var i = 0 var uf = Map.empty[Int, Int] var cycle = false @@ -223,7 +223,7 @@ object MSTMap { //"remove" twin edge edges = edges.updated((j,i), -1) - } + } } j += 1 } @@ -232,7 +232,7 @@ object MSTMap { (uf, cycle) } - + /* ************************************* Union find *************************************** */ @@ -246,7 +246,7 @@ object MSTMap { // naive union val p1 = uFind(e1,s) val p2 = uFind(e2, s) - + if(p1 != p2) //naive union s.updated(p1, p2) //only union if theiy are really in different trees @@ -257,7 +257,8 @@ object MSTMap { // fsc -d classes -classpath // ../../leon-2.0/target/scala-2.9.1-1/classes MST.scala - // scala -classpath classes MST + // scala -classpath classes MST + @ignore def main(args: Array[String]) { val edges = Map((1,0) -> 10, (0,1) -> 10, (1,2) -> 12, (2,1) -> 12, (0,2) -> 18, (2,0) -> 18, (3,1) -> 20, (1,3) -> 20) @@ -267,5 +268,5 @@ object MSTMap { println(mst(g)); //works println(mstBogus(g)); // crashes because postcondition doensn't hold } - + } diff --git a/testcases/verification/graph/dijkstras/DijkstrasSortedList.scala b/testcases/verification/graph/dijkstras/DijkstrasSortedList.scala index 4ac5d863e880a484a00a8fef939964607320e6db..b79751c692cf8ebcd7c2f24a85804dc210568a50 100644 --- a/testcases/verification/graph/dijkstras/DijkstrasSortedList.scala +++ b/testcases/verification/graph/dijkstras/DijkstrasSortedList.scala @@ -11,14 +11,14 @@ object DijkstrasSortedList { * Graph representation and algorithms **************************************************************************/ case class Graph(nVertices : Int, edges : Map[(Int,Int), Int]) - + // Disallow self edges? Not actually important since they can be ignored. def invariant(g : Graph) = g.nVertices >= 0 - + // true if x & y have same number of nodes and y has all x's edges def isSubGraph(x : Graph, y : Graph) : Boolean = { require(invariant(x) && invariant(y)) - + var ret : Boolean = (x.nVertices == y.nVertices) if (ret){ var i = 0 @@ -34,11 +34,11 @@ object DijkstrasSortedList { } ret } - + // true if every edge has a weight of at least 0 def nonnegativeWeights(g : Graph) : Boolean = { require(invariant(g)) - + var ret : Boolean = true var i = 0 while(i<g.nVertices) { @@ -54,20 +54,20 @@ object DijkstrasSortedList { } ret } - + // true if b is reachable from a def isReachable(g : Graph, a : Int, b : Int) : Boolean = { require(invariant(g) && a >= 0 && a < g.nVertices && b >= 0 && b < g.nVertices) - + //TODO false } - - + + /*************************************************************************** * Sorted list representation and algorithims **************************************************************************/ - + /** A list containing node, dist pairs. * * Sorted by distance, nearest first. Distance must be non-negative. Node @@ -76,7 +76,7 @@ object DijkstrasSortedList { abstract class SortedNDList case class ListNode(node : Int, dist : Int, next : SortedNDList) extends SortedNDList case class ListEnd() extends SortedNDList - + /** List invariant (see description above) */ @induct def sndInvariant(list : SortedNDList) : Boolean = { @@ -91,18 +91,18 @@ object DijkstrasSortedList { false case ListEnd() => true //len <= 3 } - + invRec(list/*, Set.empty, 0, 0*/) } - + /** Look for node in list and remove, if it exists. */ @induct def removeFromList(list : SortedNDList, node : Int) : SortedNDList ={ // something about this times out require(sndInvariant(list)) - + //println("removeFromList: "+list) - + list match { case ListNode(n,d,next) => if (n == node) @@ -113,7 +113,7 @@ object DijkstrasSortedList { case ListEnd() => list } } ensuring(sndInvariant(_)) // something about this generates an error: is the precondition not checked for _all_ elements or something? - + /** Return true if list contains node */ @induct def listContains(list : SortedNDList, node : Int) : Boolean ={ @@ -126,14 +126,14 @@ object DijkstrasSortedList { case ListEnd() => false } } - + /** Add a new node to the list, such that list remains sorted by distance. * Assume node is not already in list. */ @induct def addSorted(list : SortedNDList, node : Int, dist : Int) : SortedNDList = { // something to do with this times out require(sndInvariant(list) && !listContains(list, node) && node >= 0 && dist >= 0) - + list match { case ListNode(n,d,next) => if (d > dist) // insert here @@ -144,37 +144,37 @@ object DijkstrasSortedList { ListNode(node, dist, list) } } ensuring (sndInvariant(_)) // something to do with this times out - + /** Update node with distance minimum of dist and current value. Add if not * in list, and maintain sorted order. */ @induct def updateDistInList(list : SortedNDList, node : Int, dist : Int) : SortedNDList = { require(sndInvariant(list) && node >= 0 && dist >= 0) - + val listRemoved = removeFromList(list, node) addSorted(listRemoved, node, dist) } ensuring(sndInvariant(_)) - - + + /*************************************************************************** * Dijkstra's algorithm **************************************************************************/ - + def isNodeNotB(list : SortedNDList, b : Int) : Boolean = list match { case ListNode(n, _, _) => n!=b case ListEnd() => false } - + // common precondition: g is a valid graph and a and b are valid nodes def bounds(g : Graph, a : Int, b : Int) : Boolean = invariant(g) && 0 <= a && a < g.nVertices && 0 <= b && b < g.nVertices - + // find the shortest path from a to b in g, and return its distance // return -1 if the two aren't connected def shortestPath(g : Graph, a : Int, b : Int) : Int = { require(bounds(g,a,b) && nonnegativeWeights(g)) - + // We should always have at least some node if we haven't reached b (so // long as b is in the graph and connected to a). @induct @@ -184,20 +184,20 @@ object DijkstrasSortedList { * We should check same invariant at function exit. But there's no point: * it's too much for Leon to reason about at once! */ - + list match { case ListNode(node, dist, next) if (node==b) => list case ListNode(node, dist, next) => var n = 0 var tail : SortedNDList = next - + (while (n < g.nVertices){ if (n != node && !visited.contains(n) && g.edges.isDefinedAt((node,n))) tail = updateDistInList(tail, n, dist+g.edges((node,n))) n = n + 1 }) invariant(sndInvariant(list) && n >= 0 && n <= g.nVertices) - + spVisit (tail, visited ++ Set(node)) case ListEnd() => list @@ -206,7 +206,7 @@ object DijkstrasSortedList { case ListEnd() => !isReachable(g,a,b) case ListNode(node,_,_) => node==b }) - + // We start from a, which has distance 0. All other nodes implicitly have // infinite distance. val startingList : SortedNDList = ListNode(a, 0, ListEnd()) @@ -221,12 +221,13 @@ object DijkstrasSortedList { -1 } } ensuring (res => res >= -1 /*(if (isReachable(g,a,b)) res>=0 else res== -1)*/) - + + @ignore def main(args: Array[String]) { val spanningTreeE = Map((0,1) -> 1, (0,2) -> 2, (2,3) -> 5, (0,3) -> 10, (3,2) -> 0) val spanningTree = Graph(4, spanningTreeE) val singleGraph = Graph(1, Map.empty) - + println(spanningTree) println("from 0 to 3 (should be 7): "+shortestPath(spanningTree,0,3)) println("from 3 to 1 (no path): "+shortestPath(spanningTree,3,1)) diff --git a/testcases/verification/higher-order/valid/MonadsException.scala b/testcases/verification/higher-order/valid/MonadsException.scala index 2c80332435b1fb65653932fb93906d6540685e13..27c87d26177ef8c0d42f49766ab56a2c1e68b13c 100644 --- a/testcases/verification/higher-order/valid/MonadsException.scala +++ b/testcases/verification/higher-order/valid/MonadsException.scala @@ -1,5 +1,4 @@ import leon.lang._ -import leon.lang.string._ object TryMonad { diff --git a/testcases/verification/list-algorithms/InsertionSort.scala b/testcases/verification/list-algorithms/InsertionSort.scala index 35062e263c1a9b869bf2c34a25196ae4fbce3811..980927204dad1e609e93dd8dcf8a364d027e113d 100644 --- a/testcases/verification/list-algorithms/InsertionSort.scala +++ b/testcases/verification/list-algorithms/InsertionSort.scala @@ -51,7 +51,7 @@ object InsertionSort { case Nil() => true case Cons(x, Nil()) => true case Cons(x, Cons(y, ys)) => x <= y && isSorted(Cons(y, ys)) - } + } /* Inserting element 'e' into a sorted list 'l' produces a sorted list with * the expected content and size */ @@ -60,8 +60,8 @@ object InsertionSort { l match { case Nil() => Cons(e,Nil()) case Cons(x,xs) => if (x <= e) Cons(x,sortedIns(e, xs)) else Cons(e, l) - } - } ensuring(res => contents(res) == contents(l) ++ Set(e) + } + } ensuring(res => contents(res) == contents(l) ++ Set(e) && isSorted(res) && size(res) == size(l) + 1 ) @@ -73,8 +73,8 @@ object InsertionSort { l match { case Nil() => Cons(e,Nil()) case Cons(x,xs) => if (x <= e) Cons(x,buggySortedIns(e, xs)) else Cons(e, l) - } - } ensuring(res => contents(res) == contents(l) ++ Set(e) + } + } ensuring(res => contents(res) == contents(l) ++ Set(e) && isSorted(res) && size(res) == size(l) + 1 ) @@ -84,11 +84,12 @@ object InsertionSort { def sort(l: List): List = (l match { case Nil() => Nil() case Cons(x,xs) => sortedIns(x, sort(xs)) - }) ensuring(res => contents(res) == contents(l) + }) ensuring(res => contents(res) == contents(l) && isSorted(res) && size(res) == size(l) ) + @ignore def main(args: Array[String]): Unit = { val ls: List = Cons(5, Cons(2, Cons(4, Cons(5, Cons(1, Cons(8,Nil())))))) diff --git a/testcases/verification/list-algorithms/MergeSort.scala b/testcases/verification/list-algorithms/MergeSort.scala index 1bbd7f607528a9549363dec4d3e73f547f602565..5fc80d117c9eae3bddec01377683eac54f0c37cd 100644 --- a/testcases/verification/list-algorithms/MergeSort.scala +++ b/testcases/verification/list-algorithms/MergeSort.scala @@ -1,4 +1,5 @@ import leon.lang._ +import leon.annotation._ object MergeSort { sealed abstract class List @@ -57,6 +58,7 @@ object MergeSort { }) ensuring(res => contents(res) == contents(list) && is_sorted(res)) + @ignore def main(args: Array[String]): Unit = { val ls: List = Cons(5, Cons(2, Cons(4, Cons(5, Cons(1, Cons(8,Nil())))))) println(ls) diff --git a/testcases/verification/list-algorithms/QuickSort.scala b/testcases/verification/list-algorithms/QuickSort.scala index 30b3621438bd0ef944b61f831b36379deb1470f6..1afbc3a2b4d7c5f5a54560a6aa401d396646390c 100644 --- a/testcases/verification/list-algorithms/QuickSort.scala +++ b/testcases/verification/list-algorithms/QuickSort.scala @@ -53,7 +53,7 @@ object QuickSort { case Nil() => bList case _ => rev_append(reverse(aList),bList) }) ensuring(content(_) == content(aList) ++ content(bList)) - + def greater(n:Int,list:List) : List = list match { case Nil() => Nil() case Cons(x,xs) => if (n < x) Cons(x,greater(n,xs)) else greater(n,xs) @@ -69,7 +69,7 @@ object QuickSort { case Nil() => Nil() case Cons(x,xs) => if (n>x) Cons(x,smaller(n,xs)) else smaller(n,xs) } - + @induct def smallerProp(n: Int, list: List): Boolean = (max(smaller(n, list)) match { case Some(m) => n > m @@ -117,6 +117,7 @@ object QuickSort { case Cons(x,xs) => append(append(quickSort(smaller(x,xs)),Cons(x,equals(x,xs))),quickSort(greater(x,xs))) }) ensuring(res => content(res) == content(list)) // && isSorted(res)) + @ignore def main(args: Array[String]): Unit = { val ls: List = Cons(5, Cons(2, Cons(4, Cons(5, Cons(1, Cons(8,Nil())))))) diff --git a/testcases/verification/math/Prime.scala b/testcases/verification/math/Prime.scala index d8de43f86cf8ec2528e54125dc42148f4cc49e84..1e928dcff5af0eb281f2b192bdd0a88a4cbe0fb5 100644 --- a/testcases/verification/math/Prime.scala +++ b/testcases/verification/math/Prime.scala @@ -1,3 +1,5 @@ +import leon.annotation._ + object Prime { // an attempt at defining isPrime in PureScala... @@ -36,6 +38,7 @@ object Prime { } ensuring(res => !res) // Just for testing. + @ignore def main(args : Array[String]) : Unit = { def test(n : BigInt) : Unit = { println("Is " + n + " prime ? -> " + isPrime(n)) diff --git a/testcases/verification/monads/Freshen.scala b/testcases/verification/monads/Freshen.scala index de8c7eda592ba211a948c4beb5f2925d6a53974f..6fe0ef38f7e673f4e0f9ffbee8bbf0e7e7a5eafa 100644 --- a/testcases/verification/monads/Freshen.scala +++ b/testcases/verification/monads/Freshen.scala @@ -6,7 +6,6 @@ import leon.lang._ object Freshen { import State._ - import leon.lang.string._ case class Id(name: String, id: BigInt) diff --git a/testcases/verification/xlang/BankTransfer.scala b/testcases/verification/xlang/BankTransfer.scala new file mode 100644 index 0000000000000000000000000000000000000000..c533b4e08a6297446aad36d7a31fd04e148ccc37 --- /dev/null +++ b/testcases/verification/xlang/BankTransfer.scala @@ -0,0 +1,73 @@ +import leon.lang._ + +object BankTransfer { + + def okTransaction(): Unit = { + var balance: BigInt = 0 + + def balanceInvariant: Boolean = balance >= 0 + + def deposit(x: BigInt): Unit = { + require(balanceInvariant && x >= 0) + balance += x + } ensuring(_ => balance == old(balance) + x && balanceInvariant) + + def withdrawal(x: BigInt): Unit = { + require(balanceInvariant && x >= 0 && x <= balance) + balance -= x + } ensuring(_ => balance == old(balance) - x && balanceInvariant) + + deposit(35) + withdrawal(30) + } + + def invalidTransaction(): Unit = { + var balance: BigInt = 0 + + def balanceInvariant: Boolean = balance >= 0 + + def deposit(x: BigInt): Unit = { + require(balanceInvariant && x >= 0) + balance += x + } ensuring(_ => balance == old(balance) + x && balanceInvariant) + + def withdrawal(x: BigInt): Unit = { + require(balanceInvariant && x >= 0 && x <= balance) + balance -= x + } ensuring(_ => balance == old(balance) - x && balanceInvariant) + + deposit(35) + withdrawal(40) + } + + + def internalTransfer(): Unit = { + var checking: BigInt = 0 + var saving: BigInt = 0 + + def balance = checking + saving + + def balanceInvariant: Boolean = balance >= 0 + + def deposit(x: BigInt): Unit = { + require(balanceInvariant && x >= 0) + checking += x + } ensuring(_ => checking == old(checking) + x && balanceInvariant) + + def withdrawal(x: BigInt): Unit = { + require(balanceInvariant && x >= 0 && x <= checking) + checking -= x + } ensuring(_ => checking == old(checking) - x && balanceInvariant) + + def checkingToSaving(x: BigInt): Unit = { + require(balanceInvariant && x >= 0 && checking >= x) + checking -= x + saving += x + } ensuring(_ => checking + saving == old(checking) + old(saving) && balanceInvariant) + + deposit(50) + withdrawal(30) + checkingToSaving(10) + } + +} diff --git a/testcases/verification/xlang/InsertionSort.scala b/testcases/verification/xlang/InsertionSort.scala index 0a53cb55f4ef32b775646e1ea43a8c74b33a80c7..71dab126cda6927380c9395b39dcc49f3a550801 100644 --- a/testcases/verification/xlang/InsertionSort.scala +++ b/testcases/verification/xlang/InsertionSort.scala @@ -32,7 +32,7 @@ object InsertionSort { case Nil() => true case Cons(x, Nil()) => true case Cons(x, Cons(y, ys)) => x <= y && isSorted(Cons(y, ys)) - } + } def isReversedSorted(l: List): Boolean = l match { case Nil() => true case Cons(x, Nil()) => true @@ -66,8 +66,8 @@ object InsertionSort { l match { case Nil() => Cons(e,Nil()) case Cons(x,xs) => if (x <= e) Cons(x,sortedIns(e, xs)) else Cons(e, l) - } - } ensuring(res => contents(res) == contents(l) ++ Set(e) + } + } ensuring(res => contents(res) == contents(l) ++ Set(e) && isSorted(res) && size(res) == size(l) + 1 ) @@ -79,8 +79,8 @@ object InsertionSort { l match { case Nil() => Cons(e,Nil()) case Cons(x,xs) => if (x <= e) Cons(x,buggySortedIns(e, xs)) else Cons(e, l) - } - } ensuring(res => contents(res) == contents(l) ++ Set(e) + } + } ensuring(res => contents(res) == contents(l) ++ Set(e) && isSorted(res) && size(res) == size(l) + 1 ) @@ -90,11 +90,12 @@ object InsertionSort { def sort(l: List): List = (l match { case Nil() => Nil() case Cons(x,xs) => sortedIns(x, sort(xs)) - }) ensuring(res => contents(res) == contents(l) + }) ensuring(res => contents(res) == contents(l) && isSorted(res) && size(res) == size(l) ) + @ignore def main(args: Array[String]): Unit = { val ls: List = Cons(5, Cons(2, Cons(4, Cons(5, Cons(1, Cons(8,Nil())))))) diff --git a/testcases/web/demos/005_Tutorial-Monad.scala b/testcases/web/demos/005_Tutorial-Monad.scala index 093e4a2d805a9e8e3ee07dc79eb1adfecc81acdb..ba08a0e1f3f18af7c954c2a06b7c06cc26522369 100644 --- a/testcases/web/demos/005_Tutorial-Monad.scala +++ b/testcases/web/demos/005_Tutorial-Monad.scala @@ -1,4 +1,3 @@ -import leon.lang.string._ import leon.annotation._ sealed abstract class Outcome[T] { diff --git a/testcases/web/demos/008_Tutorial-Robot.scala b/testcases/web/demos/008_Tutorial-Robot.scala index c78dc15f2c48703bae470cd41578c7b141d7d9ee..bca1f367a26df843e65cea76de7029652f0b4906 100644 --- a/testcases/web/demos/008_Tutorial-Robot.scala +++ b/testcases/web/demos/008_Tutorial-Robot.scala @@ -365,7 +365,7 @@ object Robot { } def validState(rs: RobotState)(implicit w: World): Boolean = { - + // 6) Sensors have consistent data val recentData = rs.ns.validData && rs.hs.validData @@ -509,6 +509,7 @@ object Robot { } + @ignore def main(a: Array[String]): Unit = { val map0 = """|XXXXXXXXX |XR FF X diff --git a/testcases/web/synthesis/22_String_List.scala b/testcases/web/synthesis/22_String_List.scala new file mode 100644 index 0000000000000000000000000000000000000000..089d57484e3b881a01852e2ae6fae156c27fdd93 --- /dev/null +++ b/testcases/web/synthesis/22_String_List.scala @@ -0,0 +1,19 @@ +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object ListsToString { + + def allListsAreEmpty(t: List[Int]): Boolean = (t.isEmpty || t.tail.isEmpty || t.tail.tail.isEmpty || t.tail.head == 0) holds + + def listToString(p : List[Int]): String = { + ??? + } ensuring { + (res : String) => (p, res) passes { + case Cons(1, Cons(2, Nil())) => + "[1, 2]" + } + } +} diff --git a/testcases/web/synthesis/23_String_Grammar.scala b/testcases/web/synthesis/23_String_Grammar.scala new file mode 100644 index 0000000000000000000000000000000000000000..d88b921e11d4fbd209afeedea1facd1babb93276 --- /dev/null +++ b/testcases/web/synthesis/23_String_Grammar.scala @@ -0,0 +1,161 @@ +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object GrammarRender { + abstract class Symbol + case class Terminal(i: Int) extends Symbol + case class NonTerminal(i: Int) extends Symbol + + case class Rule(left: Symbol, right: List[Symbol]) + case class Grammar(start: Symbol, rules: List[Rule]) + + /** Given a grammar, expanding with the first, second or third rule for the symbol should yield the same list each time. + * Obviously wrong, but provides meaningful counter-examples. */ + def threeExpansionsIsTheSame(g: Grammar, s: Symbol) = { + require(isGrammar(g) && noEpsilons(g.rules) && isReasonable(g.rules) && !isTerminal(s)) + val a = expand(g.rules, s, BigInt(0)) + val b = expand(g.rules, s, BigInt(1)) + val c = expand(g.rules, s, BigInt(2)) + a == b && b == c + } holds + + /** Synthesis by example specs */ + @inline def psStandard(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "Grammar(NonTerminal(0), Nil())" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil()))" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil())))" + } + + @inline def psRemoveNames(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "(S0, ())" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "(S0, ((S0, (t1, ())), ()))" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "(S0, ((S0, (t1, ())), ((S0, (S0, (t1, ()))), ())))" + } + + @inline def psArrowRules(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "(S0, ())" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "(S0, ((S0 -> (t1, ())), ()))" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "(S0, ((S0 -> (t1, ())), ((S0 -> (S0, (t1, ()))), ())))" + } + + @inline def psListRules(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "(S0, [])" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "(S0, [S0 -> [t1]])" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "(S0, [S0 -> [t1], S0 -> [S0, t1])" + } + + // The difficulty here is that two lists have to be rendered differently. + @inline def psSpaceRules(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "(S0, [])" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "(S0, [S0 -> t1])" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "(S0, [S0 -> t1, S0 -> S0 t1)" + } + + // Full HTML generation + @inline def psHTMLRules(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => "<b>Start:</b> S0<br><pre></pre>" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + "<b>Start:</b> S0<br><pre>S0 -> t1</pre>" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + "<b>Start:</b> S0<br><pre>S0 -> t1<br>S0 -> S0 t1</pre>" + } + //Render in plain text. + @inline def psPlainTextRules(s: Grammar) = (res: String) => (s, res) passes { + case Grammar(NonTerminal(0), Nil()) => + """Start:S0""" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Nil())) => + """Start:S0 +S0 -> t1""" + case Grammar(NonTerminal(0), Cons(Rule(NonTerminal(0), Cons(Terminal(1), Nil())), Cons(Rule(NonTerminal(0), Cons(NonTerminal(0), Cons(Terminal(1), Nil()))), Nil()))) => + """Start:S0 +S0 -> t1 +S0 -> S0 t1""" + } + + ////////////////////////////////////////////// + // Non-incremental examples: pure synthesis // + ////////////////////////////////////////////// + /*def synthesizeStandard(s: Grammar): String = { + ???[String] + } ensuring psStandard(s)*/ + + /*def synthesizeRemoveNames(s: Grammar): String = { + ???[String] + } ensuring psRemoveNames(s)*/ + + /*def synthesizeArrowRules(s: Grammar): String = { + ???[String] + } ensuring psArrowRules(s)*/ + + /*def synthesizeListRules(s: Grammar): String = { + ???[String] + } ensuring psListRules(s)*/ + + /*def synthesizeSpaceRules(s: Grammar): String = { + ???[String] + } ensuring psSpaceRules(s)*/ + + /*def synthesizeHTMLRules(s: Grammar): String = { + ???[String] + } ensuring psHTMLRules(s)*/ + + /*def synthesizePlainTextRulesToString(s: Grammar): String = { + ???[String] + } ensuring psPlainTextRules(s)*/ + + def isTerminal(s: Symbol) = s match { + case Terminal(_) => true + case _ => false + } + + def isGrammarRule(r: Rule) = !isTerminal(r.left) + + def noEpsilon(r: Rule) = r.right match { + case Nil() => false + case Cons(_, _) => true + } + + def areGrammarRule(lr: List[Rule]): Boolean = lr match { + case Nil() => true + case Cons(r, q) => isGrammarRule(r) && areGrammarRule(q) + } + + def noEpsilons(lr: List[Rule]): Boolean = lr match { + case Nil() => true + case Cons(r, q) => noEpsilon(r) && noEpsilons(q) + } + + def isGrammar(g: Grammar) = !isTerminal(g.start) && areGrammarRule(g.rules) + + def isReasonableRule(r: List[Symbol], excluded: Symbol): Boolean = r match { + case Nil() => true + case Cons(rs, q) if rs == excluded => false + case Cons(_, q) => isReasonableRule(q, excluded) + } + + def isReasonable(lr: List[Rule]): Boolean = lr match { + case Nil() => true + case Cons(r, q) => isReasonableRule(r.right, r.left) && isReasonable(q) + } + + def expand(lr: List[Rule], s: Symbol, index: BigInt): List[Symbol] = if(index < 0) List(s) else (lr match { + case Nil() => List(s) + case Cons(Rule(l, r), q) => + if(l == s) { + if(index == 0) r else expand(q, s, index - 1) + } else expand(q, s, index) + }) +} \ No newline at end of file diff --git a/testcases/web/synthesis/24_String_DoubleList.scala b/testcases/web/synthesis/24_String_DoubleList.scala new file mode 100644 index 0000000000000000000000000000000000000000..652f5e4b84ed8837dc56cdce10393a480232c1b0 --- /dev/null +++ b/testcases/web/synthesis/24_String_DoubleList.scala @@ -0,0 +1,63 @@ +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object DoubleListRender { + abstract class A + case class B(i: AA, a: A) extends A + case class N() extends A + + abstract class AA + case class BB(i: A, a: AA) extends AA + case class NN() extends AA + + // Two elements which do not contain each other but are in the same A should be the same. + // Obviously wrong, but for the sake of displaying counter-examples. + def lemma1(a: A, b: A, c: A): Boolean = { + require(containsA_A(a, b) && containsA_A(a, c) && !containsA_A(b, c) && !containsA_A(c, b)) + b == c + } holds + + def AtoString(a : A): String = { + ??? + } ensuring { + (res : String) => (a, res) passes { + case N() => + "[]" + case B(NN(), N()) => + "[()]" + } + } + + def structurallyEqualA(a: A, b: A): Boolean = (a, b) match { + case (N(), N()) => true + case (B(i, k), B(j, l)) => structurallyEqualA(k, l) && structurallyEqualAA(i, j) + } + + def structurallyEqualAA(a: AA, b: AA): Boolean = (a, b) match { + case (NN(), NN()) => true + case (BB(i, k), BB(j, l)) => structurallyEqualAA(k, l) && structurallyEqualA(i, j) + } + + def containsA_A(a: A, b: A): Boolean = (a match { + case N() => false + case B(i, k) => containsAA_A(i, b) || containsA_A(k, b) + }) + + def containsAA_A(a: AA, b: A): Boolean = (a match { + case NN() => false + case BB(i, k) => i == b || containsA_A(i, b) || containsAA_A(k, b) + }) + + def containsA_AA(a: A, b: AA): Boolean = (a match { + case N() => false + case B(i, k) => i == b || containsAA_AA(i, b) || containsA_AA(k, b) + }) + + def containsAA_AA(a: AA, b: AA): Boolean = (a match { + case NN() => false + case BB(i, k) => containsA_AA(i, b) || containsAA_AA(k, b) + }) +} \ No newline at end of file diff --git a/testcases/web/synthesis/25_String_OutOfOrder.scala b/testcases/web/synthesis/25_String_OutOfOrder.scala new file mode 100644 index 0000000000000000000000000000000000000000..72785cf605cfbfe26bb70abd37651c32d5eddd42 --- /dev/null +++ b/testcases/web/synthesis/25_String_OutOfOrder.scala @@ -0,0 +1,57 @@ +import leon.lang._ +import leon.annotation._ +import leon.collection._ +import leon.collection.ListOps._ +import leon.lang.synthesis._ + +object OutOfOrderToString { + def argumentsToString(i: Int, j: Int): String = { + ??? + } ensuring { (res: String) => ((i, j), res) passes { + case (1, 2) => "2, 1" + } } + + def tupleToString(i: (Int, Int)): String = { + ??? + } ensuring { (res: String) => (i, res) passes { + case (1, 2) => "2, 1" + } } + + + def reverseList(l : List[Int]): String = { + ???[String] + } ensuring { + (res : String) => (l, res) passes { + case Cons(1, Cons(2, Nil())) => + "2, 1" + } + } + + def listPairToString(l : List[(Int, Int)]): String = { + ???[String] + } ensuring { + (res : String) => (l, res) passes { + case Cons((1, 2), Cons((3, 4), Nil())) => + "2 -> 1, 4 -> 3" + } + } + + def reverselistPairToString(l: List[(Int, Int)]): String = { + ??? + } ensuring { (res: String) => (l, res) passes { + case Cons((1, 2), Cons((3,4), Nil())) => "4 -> 3, 2 -> 1" + } } + + case class Rule(input: Int, applied: Option[Int]) + + def ruleToString(r : Rule): String = { + ??? + } ensuring { + (res : String) => (r, res) passes { + case Rule(1, None()) => + "res: 1" + case Rule(4, Some(2)) => + "Push(2): 4" + } + } +} \ No newline at end of file diff --git a/testcases/web/verification/03_Insertion_Sort.scala b/testcases/web/verification/03_Insertion_Sort.scala index e53b89b1732bed1dfb1c1389feacc70d4490285d..4b1c8ef7cd0b61ab0c2f91533598be792dd1b809 100644 --- a/testcases/web/verification/03_Insertion_Sort.scala +++ b/testcases/web/verification/03_Insertion_Sort.scala @@ -24,7 +24,7 @@ object InsertionSort { case Nil => true case Cons(x, Nil) => true case Cons(x, Cons(y, ys)) => x <= y && isSorted(Cons(y, ys)) - } + } /* Inserting element 'e' into a sorted list 'l' produces a sorted list with * the expected content and size */ @@ -33,8 +33,8 @@ object InsertionSort { l match { case Nil => Cons(e,Nil) case Cons(x,xs) => if (x <= e) Cons(x,sortedIns(e, xs)) else Cons(e, l) - } - } ensuring(res => contents(res) == contents(l) ++ Set(e) + } + } ensuring(res => contents(res) == contents(l) ++ Set(e) && isSorted(res) && size(res) == size(l) + 1 ) @@ -44,8 +44,8 @@ object InsertionSort { l match { case Nil => Cons(e,Nil) case Cons(x,xs) => if (x <= e) Cons(x,buggySortedIns(e, xs)) else Cons(e, l) - } - } ensuring(res => contents(res) == contents(l) ++ Set(e) + } + } ensuring(res => contents(res) == contents(l) ++ Set(e) && isSorted(res) && size(res) == size(l) + 1 ) @@ -55,7 +55,7 @@ object InsertionSort { def sort(l: List): List = (l match { case Nil => Nil case Cons(x,xs) => sortedIns(x, sort(xs)) - }) ensuring(res => contents(res) == contents(l) + }) ensuring(res => contents(res) == contents(l) && isSorted(res) && size(res) == size(l) ) @@ -69,6 +69,7 @@ object InsertionSort { } } ensuring(res => contents(res) == contents(l1) ++ contents(l2) && isSorted(res)) + @ignore def main(args: Array[String]): Unit = { val ls: List = Cons(5, Cons(2, Cons(4, Cons(5, Cons(1, Cons(8,Nil))))))