diff --git a/src/main/scala/leon/Main.scala b/src/main/scala/leon/Main.scala index 583296566..de9d740ae 100644 --- a/src/main/scala/leon/Main.scala +++ b/src/main/scala/leon/Main.scala @@ -30,7 +30,8 @@ object Main { invariant.engine.InferInvariantsPhase, laziness.LazinessEliminationPhase, genc.GenerateCPhase, - genc.CFileOutputPhase + genc.CFileOutputPhase, + comparison.ComparisonPhase ) } @@ -59,9 +60,11 @@ object Main { 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 optGenc = LeonFlagOptionDef("genc", "Generate C code", false) + val optComparison = LeonFlagOptionDef("compare", "Compare the targeted program to the Leon exemple suit", false) + override val definedOptions: Set[LeonOptionDef[Any]] = - Set(optTermination, optRepair, optSynthesis, optIsabelle, optNoop, optHelp, optEval, optVerify, optInstrument, optInferInv, optLazyEval, optGenc) + Set(optTermination, optRepair, optSynthesis, optIsabelle, optNoop, optHelp, optEval, optVerify, optInstrument, optInferInv, optLazyEval, optGenc, optComparison) } lazy val allOptions: Set[LeonOptionDef[Any]] = allComponents.flatMap(_.definedOptions) @@ -177,6 +180,8 @@ object Main { val instrumentF = ctx.findOptionOrDefault(optInstrument) val lazyevalF = ctx.findOptionOrDefault(optLazyEval) val analysisF = verifyF && terminationF + val comparisonF = ctx.findOptionOrDefault(optComparison) + // Check consistency in options if (helpF) { @@ -206,6 +211,7 @@ object Main { else if (instrumentF) InstrumentationPhase andThen FileOutputPhase else if (gencF) GenerateCPhase andThen CFileOutputPhase else if (lazyevalF) LazinessEliminationPhase + else if (comparisonF) comparison.ComparisonPhase andThen PrintReportPhase else verification } @@ -217,6 +223,7 @@ object Main { private var hasFatal = false def main(args: Array[String]) { + val argsl = args.toList // Process options diff --git a/src/main/scala/leon/comparison/Comparator.scala b/src/main/scala/leon/comparison/Comparator.scala new file mode 100644 index 000000000..f3badba75 --- /dev/null +++ b/src/main/scala/leon/comparison/Comparator.scala @@ -0,0 +1,14 @@ +package leon.comparison + +import leon.LeonContext +import leon.purescala.Expressions.Expr + +/** + * Created by joachimmuth on 04.05.16. + */ +trait Comparator { + val name: String + + def compare(exprCorpus: Expr, expr: Expr)(implicit context: LeonContext): (Double, String) + +} diff --git a/src/main/scala/leon/comparison/ComparatorClassList.scala b/src/main/scala/leon/comparison/ComparatorClassList.scala new file mode 100644 index 000000000..399c934a6 --- /dev/null +++ b/src/main/scala/leon/comparison/ComparatorClassList.scala @@ -0,0 +1,65 @@ +package leon.comparison + +import leon.{GlobalOptions, LeonContext, utils} +import leon.comparison.Utils._ +import leon.purescala.Expressions._ + +/** + * Created by joachimmuth on 02.05.16. + * + * This method shares similarities with the ComparatorByList. + * We keep the idea of comparing a list of expressions (disregarding their order), but now, instead of comparing + * two expressions (i.e. tree) we will extract the type of each expression. + * + * x match { + * case 1 => 'a' + * case 2 => 'b' + * } + * + * ComparatorByList -> {complete match, leaf(a), leaf(b)} + * ComparatorByListType -> {node(match), leaf(a), leaf(b)} + * + * x match { + * case 1 => 'a' + * case 2 => 'c' + * } + * + * ComparatorByList -> similarity 33% + * ComparatorByListType -> similarity 66% + */ +object ComparatorClassList extends Comparator { + val name = "ClassList" + + def compare(exprCorpus: Expr, expr: Expr)(implicit context: LeonContext) = { + implicit val debugSection = utils.DebugSectionComparison + val listClassesA = collectClass(exprCorpus) + val listClassesB = collectClass(expr) + + val similarExpr: Int = pairsOfSimilarExp(listClassesA, listClassesB) + + val score = Utils.matchScore(similarExpr, listClassesA.size, listClassesB.size) + + if (context.findOption(GlobalOptions.optDebug).isDefined) { + context.reporter.debug("-----------") + context.reporter.debug("COMPARATOR " + name) + context.reporter.debug("Expressions: \n" + expr + exprCorpus) + context.reporter.debug("List of classes: \n" + listClassesB + listClassesA) + context.reporter.debug("-----------") + } + + (score, "") + } + + + def pairsOfSimilarExp(listExprCorpus: List[Class[_]], listExpr: List[Class[_]]): Int = { + def helper(listExprCorpus: List[Class[_]], listExpr: List[Class[_]], acc: Int): Int = + listExpr match { + case Nil => acc + case x :: xs if listExprCorpus.contains(x) => helper(listExprCorpus diff List(x), xs, acc + 1) + case x :: xs => helper(listExprCorpus, xs, acc) + } + + helper(listExprCorpus, listExpr, 0) + } + +} diff --git a/src/main/scala/leon/comparison/ComparatorClassTree.scala b/src/main/scala/leon/comparison/ComparatorClassTree.scala new file mode 100644 index 000000000..8b2b039e2 --- /dev/null +++ b/src/main/scala/leon/comparison/ComparatorClassTree.scala @@ -0,0 +1,182 @@ +package leon.comparison + +import leon.{GlobalOptions, LeonContext, utils} +import leon.comparison.Utils._ +import leon.purescala.Expressions._ + + +/** + * Created by joachimmuth on 12.05.16. + * + * Go through both trees in parallel and compare each expression based on its class. Try to find the biggest + * common tree. + * + * Procedure: + * - Find all possible pair of roots. Then consider all children of both roots, and check which are + * similar. + * - Consider all possible combinations of children and repeat the same operation on them. + * - Pick the biggest tree in the list of all possible similar tree + * - Remove the tree overlapping the one chosen, and search if it exists an other common tree. Repeat. + * - End with all common and exclusive common tree shared by trees A and B + * + * The MatchScore is calculated with the size of the common tree, compared with the sizes of trees A and B. + */ +object ComparatorClassTree extends Comparator { + val name: String = "ClassTree" + + + def compare(exprCorpus: Expr, expr: Expr)(implicit context: LeonContext) = { + implicit val debugSection = utils.DebugSectionComparison + val roots = possibleRoots(exprCorpus, expr) + + val trees = roots.flatMap(possibleTrees(_)) + val exclusives = exclusivesTrees(trees) + val sum = exclusives.foldLeft(0)((acc, tree) => acc + tree.size) + + val listClassesA = collectClass(exprCorpus) + val listClassesB = collectClass(expr) + + val score = matchScore(sum, listClassesA.size, listClassesB.size) + + if (context.findOption(GlobalOptions.optDebug).isDefined) { + context.reporter.debug("---------------------") + context.reporter.debug("COMPARATOR " + name) + context.reporter.debug("Expressions: \n" + exprCorpus + expr) + context.reporter.debug("Common Tree: \n" + exclusives) + context.reporter.debug("---------------------") + } + + (score, "") + } + + /** + * Extract all non-overlapping trees, in size order + * + * @param trees + * @return + */ + def exclusivesTrees(trees: List[FuncTree[(Expr, Expr)]]): List[FuncTree[(Expr, Expr)]] = trees match { + case Nil => Nil + case x :: xs => + val biggest = trees.sortBy(-_.size).head + val rest = trees.filter(tree => flatList(tree).intersect(flatList(biggest)).isEmpty) + List(biggest) ++ exclusivesTrees(rest) + } + + def flatList(tree: FuncTree[(Expr, Expr)]): List[Expr] = tree.toList.flatMap(p => List(p._1, p._2)) + + /** + * list of all similar pair of expressions, based on classes. + * + * @param exprCorpus + * @param expr + * @return + */ + def possibleRoots(exprCorpus: Expr, expr: Expr): List[(Expr, Expr)] = { + val expressionsA = collectExpr(exprCorpus) + val expressionsB = collectExpr(expr) + + val pairOfPossibleRoots = for { + exprA <- expressionsA + exprB <- expressionsB + if areSimilar(exprA.getClass, exprB.getClass) + } yield { + (exprA, exprB) + } + + pairOfPossibleRoots + } + + + /** + * With a pair of roots, find all children and find all combination of matching children in order to create a list + * of all possible matching tree. Then recursively call itself on each pair of children. + * + * @param pair of matching root + * @return ether a Leaf or a List of all possible similar trees starting with this pair of roots + */ + def possibleTrees(pair: (Expr, Expr)): List[FuncTree[(Expr, Expr)]] = { + val exprA = pair._1 + val exprB = pair._2 + val childrenA = getChildren(exprA) + val childrenB = getChildren(exprB) + + + val pairOfMatchingChildren = findPairOfMatchingChildren(childrenA, childrenB) + val combinationOfChildren = combineChildren(pairOfMatchingChildren) + + + if (pairOfMatchingChildren.isEmpty) { + List(FuncTree(pair, List())) + } else { + combinationOfChildren.foldLeft(List(): List[FuncTree[(Expr, Expr)]])( + (listOfTree, children) => listOfTree ++ treesWithChildCombination(pair, children.map(p => possibleTrees(p))) + ) + } + } + + def findPairOfMatchingChildren(childrenA: List[Expr], childrenB: List[Expr]): List[(Expr, Expr)] = { + for { + childA <- childrenA + childB <- childrenB + if areSimilar(childA.getClass, childB.getClass) + } yield { + (childA, childB) + } + } + + /** All possible combination of pairs of children, given the condition that one child can only be used once. + * + * IMPROVEMENT: Here, it would be possible to already filter some cases. + * When we do the combination, we try all cases, using one pair of matching, two, three, ... we could only keep the + * ones using maximum of possible children, as we only want the biggest tree. + * + * @param pairs + * @return + */ + def combineChildren(pairs: List[(Expr, Expr)]): List[List[(Expr, Expr)]] = { + combine(pairs).filterNot(p => isSameChildUsedTwice(p)).toList + } + + def isSameChildUsedTwice(list: List[(Expr, Expr)]): Boolean = { + list.map(_._1).distinct.size != list.size || + list.map(_._2).distinct.size != list.size + } + + def combine(in: List[(Expr, Expr)]): Seq[List[(Expr, Expr)]] = { + for { + len <- 1 to in.length + combinations <- in combinations len + } yield combinations + } + + /** + * As we recursively call the method, children will create list of possibilities, as the root does. All this possible + * combination need to be transformed into a list of complete tree. + * + * Technically, we have a combination of Children that return each a list of possible trees. So the upper-level tree + * (whom root is named pair) can have all possible combination of theses lists as children. + * + * @param pair + * @param listChildren + * @return + */ + def treesWithChildCombination(pair: (Expr, Expr), listChildren: List[List[FuncTree[(Expr, Expr)]]]): List[FuncTree[(Expr, Expr)]] = { + def combine(list: List[List[FuncTree[(Expr, Expr)]]]): List[List[FuncTree[(Expr, Expr)]]] = list match { + case Nil => List(Nil) + case x :: xs => + for { + j <- combine(xs) + i <- x + } yield i :: j + } + + combine(listChildren).map(children => FuncTree(pair, children)) + } + + + def areSimilar(getClass: Class[_ <: Expr], getClass1: Class[_ <: Expr]) = { + getClass == getClass1 + } + +} diff --git a/src/main/scala/leon/comparison/ComparatorDirectScoreTree.scala b/src/main/scala/leon/comparison/ComparatorDirectScoreTree.scala new file mode 100644 index 000000000..5575cef94 --- /dev/null +++ b/src/main/scala/leon/comparison/ComparatorDirectScoreTree.scala @@ -0,0 +1,199 @@ +package leon.comparison + +import leon.{GlobalOptions, LeonContext, utils} +import leon.comparison.Scores._ +import leon.comparison.Utils._ +import leon.purescala.Expressions._ + +/** + * Created by joachimmuth on 02.06.16. + * + * Travers in parallel two trees. Instead of comparing Class like in ClassTree, we directly compute a score of the pair + * like in ScoreTree. This allow the most flexible comparison. + * + */ +object ComparatorDirectScoreTree extends Comparator { + override val name: String = "DirectScoreTree" + + + override def compare(exprCorpus: Expr, expr: Expr)(implicit context: LeonContext): (Double, String) = { + implicit val debugSection = utils.DebugSectionComparison + val roots = possibleRoots(exprCorpus, expr) + val trees = roots.flatMap(possibleTrees(_)) + if (trees.isEmpty) return (0.0, "") + + val (bestTree, score) = selectBestTree(trees, exprCorpus, expr) + + if (context.findOption(GlobalOptions.optDebug).isDefined) { + context.reporter.debug("---------------------") + context.reporter.debug("COMPARATOR " + name) + context.reporter.debug("Expressions: \n" + exprCorpus + expr) + context.reporter.debug("Common Tree: \n" + bestTree) + context.reporter.debug("---------------------") + } + + (score, " (" + bestTree.size + ")") + } + + + /** + * Define whether a pair of expressions worth be combined (i.e its score is above 0.0%) + * This is kind of a local optimization, instead of creating all possibles trees (what would be enormous). + * The threshold can be modified. + * + * @param exprsA + * @param exprsB + * @return + */ + def pairAndScoreExpr(exprsA: List[Expr], exprsB: List[Expr]): List[Value] = { + val pairOfExprs = for { + exprA <- exprsA + exprB <- exprsB + score = computeScore(exprA, exprB) + if score > 0.0 + } yield { + Value(exprA, exprB, score) + } + + pairOfExprs + } + + def possibleRoots(exprA: Expr, exprB: Expr): List[Value] = { + val exprsA = collectExpr(exprA) + val exprsB = collectExpr(exprB) + + pairAndScoreExpr(exprsA, exprsB) + } + + def matchChildren(childrenA: List[Expr], childrenB: List[Expr]): List[Value] = + pairAndScoreExpr(childrenA, childrenB) + + + /** + * With a pair of roots, find all children and find all combination of matching children in order to create a list + * of all possible matching tree. Then recursively call itself on each pair of children. + * + * @param value of root + * @return ether a Leaf or a List of all possible similar trees starting with this pair of roots + */ + def possibleTrees(value: Value): List[FuncTree[Value]] = { + val exprA = value.a + val exprB = value.b + val childrenA = getChildren(exprA) + val childrenB = getChildren(exprB) + + + val pairOfMatchingChildren = matchChildren(childrenA, childrenB) + val combinationOfChildren = combineChildren(pairOfMatchingChildren) + + + if (pairOfMatchingChildren.isEmpty) { + List(FuncTree(value, List())) + } else { + combinationOfChildren.foldLeft(List(): List[FuncTree[Value]])( + (listOfTrees, children) => listOfTrees ++ flatCombination(value, children.map(p => possibleTrees(p))) + ) + } + } + + + /** + * Distributes score computation according to expressions + * + * Allow some flexibilities, we can even compare two different expressions and give it a non-zero score. + * We can also go though some expression to compare deeper proprieties, like: + * - order in if-else statement (TO DO) + * - exclusiveness of MatchCases in a case-match statement (TO DO) + * - value of the expression + * - ... + * + * @param exprA + * @param exprB + * @return + */ + def computeScore(exprA: Expr, exprB: Expr): Double = (exprA, exprB) match { + case (x, y: Choose) => 0.1 + case (x: MatchExpr, y: MatchExpr) => scoreMatchExpr(x, y) + case (x: CaseClass, y: CaseClass) => scoreCaseClass(x, y) + case (x, y) if x.getClass == y.getClass => + 1.0 + case _ => 0.0 + } + + + /** All possible combination of pairs of children, given the condition that one child can only be used once. + * + * @param pairs + * @return + */ + def combineChildren(pairs: List[Value]): List[List[Value]] = { + combine(pairs).filterNot(p => isSameChildUsedTwice(p)).toList + } + + def isSameChildUsedTwice(list: List[Value]): Boolean = { + list.map(_.a).distinct.size != list.size || + list.map(_.b).distinct.size != list.size + } + + def combine[T](in: List[T]): Seq[List[T]] = { + for { + len <- 1 to in.length + combinations <- in combinations len + } yield combinations + } + + + /** + * Intuition: As we recursively call the method, children will create list of possibilities, as the root does. + * All this possible combination need to be transformed into a list of complete tree. + * + * Technical: we have a combination of Children that return each a list of possible trees. So the upper-level tree + * (whom root is named pair) can have all possible combination of theses lists as children. + * + * @param value of root, in our case type is : (Expr, Expr, Double) i.e. + * @param listChildren + * @return + */ + def flatCombination[T](value: T, listChildren: List[List[FuncTree[T]]]): List[FuncTree[T]] = { + def combine(list: List[List[FuncTree[T]]]): List[List[FuncTree[T]]] = list match { + case Nil => List(Nil) + case x :: xs => + for { + j <- combine(xs) + i <- x + } yield i :: j + } + + combine(listChildren).map(children => FuncTree(value, children)) + } + + /** + * Normalize a score of the tree + * + * Must pay attention to the fact that small trees will always be chosen. To balance that: consider the size of the + * tree and the size of the compared expression to create a "weight". + * + * @param tree + * @return + */ + + def normalizedScoreTree(tree: FuncTree[Value], exprA: Expr, exprB: Expr): Double = { + val scoreTree = geometricMean(tree) + val sizeA = collectExpr(exprA).size.toDouble + val sizeB = collectExpr(exprB).size.toDouble + val weight = (1 / Math.max(sizeA, sizeB)) * tree.size.toDouble + + scoreTree * weight + } + + def geometricMean(tree: FuncTree[Value]): Double = + Math.pow(tree.toList.foldLeft(1.0)((acc, tree) => acc * tree.score), 1 / tree.size.toDouble) + + def selectBestTree(trees: List[FuncTree[Value]], exprCorpus: Expr, expr: Expr) = { + val biggest = trees.sortBy(t => -normalizedScoreTree(t, exprCorpus, expr)).head + val score = normalizedScoreTree(biggest, exprCorpus, expr) + (biggest, score) + } + + +} diff --git a/src/main/scala/leon/comparison/ComparatorExprList.scala b/src/main/scala/leon/comparison/ComparatorExprList.scala new file mode 100644 index 000000000..36c9144e4 --- /dev/null +++ b/src/main/scala/leon/comparison/ComparatorExprList.scala @@ -0,0 +1,92 @@ +package leon.comparison + +import leon.{GlobalOptions, LeonContext, utils} +import leon.comparison.Utils._ +import leon.purescala.Expressions.{CaseClassPattern, _} + +/** + * Created by joachimmuth on 25.04.16. + * + * This way of basic comparison flat both functional trees into lists and compare them in every possible combination. + * + * The easy-to-understand way of working provides a point of comparison for further advanced method. + * + * For clarity, we always pass "corpus function" first in argument + */ +object ComparatorExprList extends Comparator { + val name = "ExprList" + + def compare(exprCorpus: Expr, expr: Expr)(implicit context: LeonContext) = { + implicit val debugSection = utils.DebugSectionComparison + val exprsA = collectExpr(exprCorpus) + val exprsB = collectExpr(expr) + + val numberOfSimilarExpr: Int = pairsOfSimilarExp(exprsA, exprsB) + + val score = Utils.matchScore(numberOfSimilarExpr, exprsA.size, exprsB.size) + + if (context.findOption(GlobalOptions.optDebug).isDefined) { + context.reporter.debug("---------------------") + context.reporter.debug("COMPARATOR " + name) + context.reporter.debug("Expressions: \n" + exprCorpus + "\n" + expr) + context.reporter.debug("similar expr: \n" + numberOfSimilarExpr) + context.reporter.debug("---------------------") + } + + (score, "") + } + + + def pairsOfSimilarExp(exprsCorpus: List[Expr], exprs: List[Expr]): Int = { + val normalizedExprsA = exprsCorpus.map(normalizeStructure(_)) + val normalizedExprsB = exprs.map(normalizeStructure(_)) + + normalizedExprsA.intersect(normalizedExprsB).size + } + + + /** + * TO BE DELETED ??? + * --> used in ScoreTree method ??? + * + * Extract a map from a PatternMatch function (match...case) in order to be comparable without caring about the + * order + * (waring: this comparison make sense only if the MatchCases are exclusives) + * + * e.g : list match { + * case x::y::xs => foo(x, y) ++ recursion(xs) + * case x::xs => Nil + * } + * + * is not equal to: + * + * list match { + * case x::xs => Nil + * case x::y::xs => foo(x, y) ++ recursion(xs) + * } + * + * @param matchCases + * @return + */ + def extractPatternMatchMap(matchCases: Seq[MatchCase]) = { + matchCases.map(a => a.pattern match { + case InstanceOfPattern(_, ct) => (ct.classDef -> a.rhs) + case CaseClassPattern(_, ct, _) => (ct.classDef -> a.rhs) + case _ => (a.pattern -> a.rhs) + }).toMap + } + + + def extractValueMatchMap(matchCases: Seq[MatchCase]) = { + matchCases.map(a => a.pattern -> a.rhs).toMap + } + + // IDEE: comparer les types plutôt que les pattern complet des match case, et éventuellement oublié les optGuard + def compareExpr(exprA: Expr, exprB: Expr): Boolean = { + val normalizedExprA = normalizeStructure(exprA) + val normalizedExprB = normalizeStructure(exprB) + + normalizedExprA == normalizedExprB + } + +} diff --git a/src/main/scala/leon/comparison/ComparatorScoreTree.scala b/src/main/scala/leon/comparison/ComparatorScoreTree.scala new file mode 100644 index 000000000..bf8dcb108 --- /dev/null +++ b/src/main/scala/leon/comparison/ComparatorScoreTree.scala @@ -0,0 +1,213 @@ +package leon.comparison + +import leon.{GlobalOptions, LeonContext, utils} +import leon.comparison.Utils._ +import leon.purescala.Common.Tree +import leon.purescala.Definitions.{CaseClassDef, ClassDef} +import leon.purescala.Expressions._ +import leon.purescala.Types.{ClassType, TypeTree} + +/** + * Created by joachimmuth on 19.05.16. + * + * This give a score to the biggest common tree found by ClassTree. + * A common tree is composed by pair of expression (A, B) which have the same type, but are maybe not perfectly + * equivalent. + * + * ScoreTree take each pair and compute a score for it, then make a geometrical mean over all these scores. + * (The mean "balances" the fact that a big tree will always have a poorer score than a little) + * + * IMPORTANT: ScoreTree scores an already chosen common Tree, coming from ComparatorClassTree. A frequent 100% score + * doesn't mean that FunDef match at 100%, but that the elements of this common Tree match at 100% + * + */ +object ComparatorScoreTree extends Comparator { + override val name: String = "ScoreTree" + + def compare(exprCorpus: Expr, expr: Expr)(implicit context: LeonContext): (Double, String) = { + implicit val debugSection = utils.DebugSectionComparison + val pairOfRoots = ComparatorClassTree.possibleRoots(exprCorpus, expr) + val allPossibleTrees = pairOfRoots flatMap ComparatorClassTree.possibleTrees + if (allPossibleTrees == Nil) return (0.0, "") + + val biggest = allPossibleTrees.sortBy(-_.size).head + val score: Double = computeScore(biggest) + val normalizedScore = normalize(score, biggest.size) + + if (context.findOption(GlobalOptions.optDebug).isDefined) { + context.reporter.debug("---------------------") + context.reporter.debug("COMPARATOR " + name) + context.reporter.debug("Expressions: \n" + exprCorpus + "\n" + expr) + context.reporter.debug("Biggest: \n" + biggest) + context.reporter.debug("Score: \n" + score) + context.reporter.debug("---------------------") + } + + (normalizedScore, "") + } + + /** + * Geometric mean of obtained score, to balance the difference between small and big tree. + * + * @param score + * @param n : size of tree + * @return + */ + def normalize(score: Double, n: Int) = Math.pow(score, 1 / n.toDouble) + + + /** + * Give a score to each pair of expression. In combination with "normalize" function, do a Geometric Mean on this + * score. They all have the same weight. + * + * @param tree + * @return + */ + def computeScore(tree: FuncTree[(Expr, Expr)]): Double = { + // we treat each type differently with its proper scorer + val score = tree.value match { + case (x: MatchExpr, y: MatchExpr) => scoreMatchExpr(x, y) + case (x: CaseClass, y: CaseClass) => scoreCaseClass(x, y) + case _ => 1.0 + + } + + // if tree is a node, recursively travers children. If it is a leaf, just return the score + // we process a geometrical mean + tree.children.foldLeft(score)((acc, child) => acc * computeScore(child)) + } + + /** + * Compare only the MatchCases conditions + * + * @return + */ + def scoreMatchExpr(x: MatchExpr, y: MatchExpr): Double = { + def scoreMatchCase(casesA: Seq[MatchCase], casesB: Seq[MatchCase]): Double = { + val mapCasesA = toMap(casesA) + val mapCasesB = toMap(casesB) + scoreMapMatchCase(mapCasesA, mapCasesB) + } + + /** + * Compare the map containing the MatchCases + * NOW: just check how many similar key they have + * NEXT: take into account the order; some MatchCase can be non exclusive, meaning that it "eats" the next one: + * case x if x < 4 => ... + * case x if x < 10 => ... + * doesn't have the same meaning if inverted. + * + * NEXT: take into account invoked expression + * + * @param mapCasesA + * @param mapCasesB + * @return + */ + def scoreMapMatchCase(mapCasesA: Map[Tree, Expr], mapCasesB: Map[Tree, Expr]): Double = { + val all = mapCasesA.keys.size + mapCasesB.keys.size.toDouble + val diff = ((mapCasesA.keySet -- mapCasesB.keySet) ++ (mapCasesB.keySet -- mapCasesA.keySet)).size.toDouble + (all - diff) / all + } + + def toMap(cases: Seq[MatchCase]) = { + cases.map(a => a.pattern match { + case InstanceOfPattern(_, ct) => ct -> a.rhs + case CaseClassPattern(_, ct, _) => ct -> a.rhs + case _ => a.pattern -> a.rhs + }).toMap + } + + scoreMatchCase(x.cases, y.cases) + } + + + /** + * One hard piece is to compare two case clase, because it cannot be normalized like value + * + * @return + */ + + def scoreClassDef(classA: ClassDef, classB: ClassDef): Double = { + (classA, classB) match { + case (a, b) if (a.isAbstract && b.isAbstract) => + if (a.knownCCDescendants.size == b.knownCCDescendants.size) 1.0 + else 0.0 + case (a: CaseClassDef, b: CaseClassDef) => + scoreCaseClassDef(a, b) + case _ => 0.0 + + } + } + + def compareTypeTree(typeA: TypeTree, typeB: TypeTree): Double = (typeA, typeB) match { + case (a: ClassType, b: ClassType) => scoreClassDef(a.classDef, b.classDef) + case _ => 0.0 + } + + def scoreCaseClass(a: CaseClass, b: CaseClass): Double = { + scoreCaseClassDef(a.ct.classDef, b.ct.classDef) + } + + + /** + * Compare two CaseClass definition taking into account different parameter: + * - the number of arguments of it's own type + * - the number of arguments of it's parent type + * - the other arguments of primitive types + * + * NEXT: take into account matching between parents + * NEXT: take into account others parameters ? + * + * @param a + * @param b + * @return + */ + def scoreCaseClassDef(a: CaseClassDef, b: CaseClassDef): Double = { + val ownTypeA: Int = argumentsOfOwnType(a) + val ownTypeB: Int = argumentsOfOwnType(b) + val scoreOwnType = percent(ownTypeA, ownTypeB) + + val parentTypeA: Int = argumentsOfParentType(a) + val parentTypeB: Int = argumentsOfParentType(b) + val scoreParentType = percent(parentTypeA, parentTypeB) + + val otherTypeA = a.fields.map(_.getType).filterNot(_.isInstanceOf[ClassDef]) + val otherTypeB = b.fields.map(_.getType).filterNot(_.isInstanceOf[ClassDef]) + val scoreOtherType = scoreSeqType(otherTypeA, otherTypeB) + + // arithmetic mean instead of geometric, we don't want to have 0.0% if one of these criteria don't match + val score: Double = mean(List(scoreOwnType, scoreParentType, scoreOtherType)) + + score + } + + + def scoreSeqType(a: Seq[TypeTree], b: Seq[TypeTree]): Double = { + val intersect = a.intersect(b).size + matchScore(intersect, a.size, b.size) + } + + /** + * Count how many occurrences of its own type appear in its arguments + * (to be improved if multiples types) + * + * @param a the case class + * @return + */ + def argumentsOfOwnType(a: CaseClassDef): Int = + a.fields.map(_.getType).count(a.tparams.map(_.tp.getType).contains(_)) + + + /** + * Count how many occurrences of its parent type appear in its arguments + * + * @param a + * @return + */ + def argumentsOfParentType(a: CaseClassDef): Int = a match { + case _ if a.hasParent => a.fields.map(_.getType).count(_ == a.parent.get.getType) + case _ => 0 + } + + +} diff --git a/src/main/scala/leon/comparison/ComparisonCorpus.scala b/src/main/scala/leon/comparison/ComparisonCorpus.scala new file mode 100644 index 000000000..2af98860c --- /dev/null +++ b/src/main/scala/leon/comparison/ComparisonCorpus.scala @@ -0,0 +1,55 @@ +package leon.comparison + +import java.io.File +import java.nio.file.{Paths, Files} + +import leon.LeonContext +import leon.frontends.scalac.ExtractionPhase +import leon.purescala.Definitions.{FunDef, Program} +import leon.utils.PreprocessingPhase + + +/** + * Created by joachimmuth on 24.03.16. + * + * Extract list of all FunDef existing in the objects of the targeted folder. + * Typically the folder is "comparison-base" (chosen in ComparisonPhase). + */ +case class ComparisonCorpus(ctx: LeonContext, folder: String) { + + val program: Program = extraction(recursiveListFilesInString(folder)) + val funDefs: List[FunDef] = ComparisonPhase.getFunDef(ctx, program) + + def extraction(files: List[String]): _root_.leon.purescala.Definitions.Program = { + val extraction = ExtractionPhase andThen new PreprocessingPhase(false) + val (_, prog) = extraction.run(ctx, files) + prog + } + + def recursiveListFiles(f: File): List[File] = { + if (!(f.isDirectory || f.isFile)) { + ctx.reporter.error("Path " + f + " is neither a file nor a directory.") + ctx.reporter.error("Program aborted") + java.lang.System.exit(0) + } + + if (f.isDirectory) { + val (files, dirs) = f.listFiles().toList.partition(_.isFile) + files ++ dirs.filter(_.isDirectory).flatMap(recursiveListFiles) + } else { + List(f) + } + } + + def recursiveListFilesInString(f: String): List[String] = { + if (!Files.exists(Paths.get(f))) { + ctx.reporter.error("Path of corpus folder doesn't exist: " + f) + ctx.reporter.error("Program aborted") + java.lang.System.exit(0) + } + val file = new File(f) + recursiveListFiles(file).map(f => f.getCanonicalPath) + } + + +} diff --git a/src/main/scala/leon/comparison/ComparisonPhase.scala b/src/main/scala/leon/comparison/ComparisonPhase.scala new file mode 100644 index 000000000..116abb34d --- /dev/null +++ b/src/main/scala/leon/comparison/ComparisonPhase.scala @@ -0,0 +1,101 @@ +package leon +package comparison + +import leon.purescala.Definitions._ +import leon.purescala.Expressions._ +import leon.comparison.Utils._ +import leon.utils.DebugSection + +/** + * Created by joachimmuth on 23.03.16. + */ +object ComparisonPhase extends SimpleLeonPhase[Program, ComparisonReport] { + + override val description: String = "Comparison phase between input program and Leon example suite" + override val name: String = "Comparison" + + val comparators: List[Comparator] = List( + ComparatorExprList, + ComparatorClassList, + ComparatorClassTree, + ComparatorScoreTree, + ComparatorDirectScoreTree + ) + + val comparatorsNames = comparators map (_.name) + + override def apply(ctx: LeonContext, program: Program): ComparisonReport = { + implicit val context = ctx + + + val corpus = ComparisonCorpus(ctx, "testcases/comparison/corpus") + val funDefsCorpus = corpus.funDefs + val funDefs = getFunDef(ctx, program) + + + // contain pair of function compared and score given by each comparator + // a space is let for a comment string, for example size of common tree for DirectScoreTree comparator + val comparison = combinationOfFunDef(funDefsCorpus, funDefs) + + val autocompletedHoles = + funDefs filter hasHole map (fun => Completor.suggestCompletion(fun, corpus)) + + ComparisonReport(ctx, program, corpus, comparatorsNames, comparison, autocompletedHoles) + } + + + /** + * Compare each function from corpus to argument program + * + * @param funDefsCorpus + * @param funDefs + * @return + */ + def combinationOfFunDef(funDefsCorpus: List[FunDef], funDefs: List[FunDef]) + (implicit context: LeonContext) = { + + for { + funDef <- funDefs + funDefCorpus <- funDefsCorpus + scores = comparators map (_.compare(funDefCorpus.body.get, funDef.body.get)) + if scores.map(_._1) exists (_ > 0.0) + } yield { + (funDef, funDefCorpus, scores) + } + } + + /** + * Method inspired from VerficationPhase + * A filter with undesirable FunDef added + * Ensure that every FunDef has a body + * + * @param ctx + * @param program + * @return + */ + def getFunDef(ctx: LeonContext, program: Program): List[FunDef] = { + def excludeByDefault(fd: FunDef): Boolean = fd.annotations contains "library" + val filterFuns: Option[Seq[String]] = ctx.findOption(GlobalOptions.optFunctions) + val fdFilter = { + import OptionsHelpers._ + + filterInclusive(filterFuns.map(fdMatcher(program)), Some(excludeByDefault _)) + } + + val funDefs = program.definedFunctions.filter(fdFilter).sortWith((fd1, fd2) => fd1.getPos < fd2.getPos).tail + + funDefs filter { f => + f.qualifiedName(program) != "Ensuring.ensuring" && + f.qualifiedName(program) != "WebPage.apply" && + f.qualifiedName(program) != "Style" && + hasBody(f) + } + } + + def hasBody(funDef: FunDef): Boolean = funDef.body match { + case Some(b) => true + case None => false + } + + +} diff --git a/src/main/scala/leon/comparison/ComparisonReport.scala b/src/main/scala/leon/comparison/ComparisonReport.scala new file mode 100644 index 000000000..31515d7c6 --- /dev/null +++ b/src/main/scala/leon/comparison/ComparisonReport.scala @@ -0,0 +1,126 @@ +package leon.comparison + +import leon.LeonContext +import leon.purescala.Definitions.{FunDef, Program} +import leon.purescala.Expressions.Expr +import leon.utils.ASCIIHelpers._ +import leon.utils.Report + +import scala.reflect.quasiquotes.Holes + +/** + * Created by joachimmuth on 23.03.16. + * + */ +case class ComparisonReport( + ctx: LeonContext, + program: Program, + base: ComparisonCorpus, + comparatorsName: List[String], + listFD: List[(FunDef, FunDef, List[(Double, String)])], + holes: List[(FunDef, Option[FunDef], Option[Expr])]) extends Report { + + + def summaryString: String = { + + // some information about the rendered table + // ctx.reporter.info("argument program: name of compared function and its size") + // ctx.reporter.info("base: name of function extracted from our corpus and its size") + // ctx.reporter.info("ExprList: compare each expression subtree one by one and count number of exact match" + + // "compared with total number of expression in both function") + // ctx.reporter.info("ClassList: compare each type of expressions one by one and count number of exact" + + // "match compared with total number of expression in both functions") + // ctx.reporter.info("ClassTree: Find the biggest common tree based on type of expression, and count its size" + + // "compared with size of both functions") + // ctx.reporter.info("ScoreTree: Compute a score for the found ClassTree") + // ctx.reporter.info("DirectScoreTree: Find the biggest common tree based directly on a score method, and give the" + + // "score next to the size of both function and the size of the tree.") + + + // report table + var t = Table("Comparison Summary") + + t += Row( + Seq( + Cell("argument program"), + Cell("size"), + Cell("base"), + Cell("size") + ) ++ + comparatorsName.map(p => Cell(p)) + ) + + t += Separator + + t ++= listFD.map( + fd => Row( + Seq( + Cell(fd._1.qualifiedName(program)), + Cell(size(fd._1)), + Cell(fd._2.qualifiedName(base.program)), + Cell(size(fd._2)) + ) ++ + fd._3.map(p => Cell(percentage(p._1) + " " + p._2)) + ) + ) + + val warningScoreTree: String = "IMPORTANT: ScoreTree scores an already chosen common Tree, coming" + + " from ComparatorClassTree (i.e. the biggest). \n" + + "A frequent 100% score does not mean that FunDef match at 100%, " + + "but that the elements of this common Tree match at 100% \n" + + val noteParenthesis: String = "NOTE: In parenthesis are the sizes of trees and the size of common trees in case of" + + "DirectScore" + + + // report completion holes + var stringHole: String = "" + + if (holes.nonEmpty) { + var reportHoles: Table = Table("Completion of Holes") + + reportHoles += Row(Seq( + Cell("Incomplete function"), + Cell("Corpus function used to complete it") + )) + + reportHoles += Separator + + reportHoles ++= holes.map( + h => Row(Seq( + Cell(h._1.qualifiedName(program)), + Cell(handleOptionFD(h._2, base.program)) + )) + ) + + stringHole += reportHoles.render + "\n" + stringHole += holes.map(p => printFilledHoles(p._1, p._3)).mkString("") + } + + t.render + "\n" + warningScoreTree + "\n" + stringHole + } + + private def percentage(d: Double): String = new java.text.DecimalFormat("#.##").format(d * 100) ++ "%" + + private def size(f: FunDef): String = "(" + Utils.collectExpr(f.body.get).size + ")" + + def handleOptionFD(f: Option[FunDef], program: Program): String = f match { + case Some(funDef) => funDef.qualifiedName(program) + case None => "no matching funDef found" + } + + def printFilledHoles(incompleteFun: FunDef, completedExpr: Option[Expr]): String = completedExpr match { + case None => "" + case Some(expr) => + "\n================================================\n" + + "Incomplete function " + incompleteFun.qualifiedName(program) + " was:\n" + + "================================================\n" + + incompleteFun.body.get.toString + "\n" + + "==============================\n" + + "Function is now completed as :\n" + + "==============================\n" + + expr.toString + "\n" + } + + +} diff --git a/src/main/scala/leon/comparison/Completor.scala b/src/main/scala/leon/comparison/Completor.scala new file mode 100644 index 000000000..d98bd01e3 --- /dev/null +++ b/src/main/scala/leon/comparison/Completor.scala @@ -0,0 +1,138 @@ +package leon.comparison + +import leon.LeonContext +import leon.purescala.Definitions.FunDef +import leon.purescala.Expressions._ +import leon.comparison.Utils._ +import leon.comparison.ComparatorDirectScoreTree._ +import leon.purescala.ExprOps + +/** + * Created by joachimmuth on 08.06.16. + * + * Consider a holed function and try to find the best match (if there is some!) between this and one of the function + * existing in the corpus. + * + * To do so, we use a similar method as ComparatorDirectScoreTree were we create all possible common tree shared by + * two functions. As ComparatorDirectScoreTree creates pair of expression based on their score, we only need to do a + * little modification in order to consider the Hole as a possible match, based on their type. + * We choose then the function with the common tree having the higher score. + * + * We use this corpus function to fill the hole, and we replace the hole by the matching expression in the common tree. + * + * We return the holed function, the corpus function used to complete it, and the completed function (if there is some). + * + * IMPORTANT: at the moment the object if implemented to handle one and only one hole + */ +object Completor { + + case class Suggestion(expr: Option[Expr]) + + + def suggestCompletion(funDef: FunDef, corpus: ComparisonCorpus)(implicit context: LeonContext): + (FunDef, Option[FunDef], Option[Expr]) = { + val expr = funDef.body.get + + // for each function of corpus, search all roots of common tree that include the hole + val funDefAndRoots = corpus.funDefs map (f => (f, possibleRootsWithHoles(f, expr))) + + // for each function of corpus, search all common tree respective to these roots + // (filter only the ones containing the hole) + val funDefAndTrees = funDefAndRoots map { p => + (p._1, p._2 flatMap possibleTrees filter hasHole) + } + + // if no function match, return None + if (funDefAndTrees.isEmpty) + return (funDef, None, None) + + val suggestion = selectBestSuggestion(expr, funDefAndTrees) + + suggestion match { + case None => (funDef, None, None) + case Some(pair) => (funDef, Some(pair._1), Some(fillHole(expr, pair._2))) + } + } + + + def possibleRootsWithHoles(funDefCorpus: FunDef, expr: Expr): List[Value] = + possibleRoots(funDefCorpus.body.get, expr) filter (e => Utils.hasHole(e.b)) + + def getBody(funDef: FunDef): Expr = funDef.body.get + + def hasHole(tree: FuncTree[Value]): Boolean = { + tree.toList map (p => p.b) exists (e => e.isInstanceOf[Choose]) + } + + + /** + * select the best suggestion to fill the hole i.e. the common tree having the higher score between all the common + * trees and all the function of corpus + * + * @param funDefAndTrees + * @return + */ + def selectBestSuggestion(expr: Expr, funDefAndTrees: List[(FunDef, List[FuncTree[Value]])]): + Option[(FunDef, Expr)] = { + val funDefAndBestTree = funDefAndTrees map { p => + (p._1, selectBestTreeOption(p._2, expr, getBody(p._1))) + } + + selectBestFun(funDefAndBestTree) match { + case None => None + case Some(pair) => Some(pair._1, findPairOfTheHole(pair._2)) + } + } + + def selectBestTreeOption(list: List[FuncTree[Value]], exprA: Expr, exprB: Expr): Option[(FuncTree[Value], Double)] = list match { + case Nil => None + case x :: xs => Some(selectBestTree(list, exprA, exprB)) + } + + def selectBestFun(list: List[(FunDef, Option[(FuncTree[Value], Double)])]): Option[(FunDef, FuncTree[Value])] = { + val listWithScore = list filterNot (p => p._2.isEmpty) + + listWithScore match { + case Nil => None + case x :: xs => { + val best = listWithScore.sortBy(p => -p._2.get._2).head + Some(best._1, best._2.get._1) + } + } + } + + def scoreOptionTree(tree: Option[FuncTree[Value]]): Double = tree match { + case None => 0.0 + case Some(t) => geometricMean(t) + } + + + /** + * Find in common tree the value that paired the hole and the expression from the corpus. The hole will be filled + * with this hole + * + * @param tree + * @return + */ + def findPairOfTheHole(tree: FuncTree[Value]): Expr = + (tree.toList filter (p => p.b.isInstanceOf[Choose])).head.a + + /** + * Travers recursively the tree until finding the hole and replace it by + * the wished expression. + * + * @param exprToFill + * @param corpus + * @return + */ + def fillHole(exprToFill: Expr, corpus: Expr): Expr = { + val filledTree = ExprOps.preMap { + case Choose(expr) => Some(corpus) + case _ => None + }(exprToFill) + + filledTree + } + + +} diff --git a/src/main/scala/leon/comparison/FuncTree.scala b/src/main/scala/leon/comparison/FuncTree.scala new file mode 100644 index 000000000..0d7744cca --- /dev/null +++ b/src/main/scala/leon/comparison/FuncTree.scala @@ -0,0 +1,17 @@ +package leon.comparison + +/** + * Created by joachimmuth on 12.05.16. + * + * Basic class just to allow easy store of expressions-tree + */ + +case class FuncTree[T](value: T, children: List[FuncTree[T]]) { + def isLeaf: Boolean = children.isEmpty + + def isNode: Boolean = !isLeaf + + def size: Int = 1 + children.foldLeft(0)((acc, child) => acc + child.size) + + def toList: List[T] = value +: children.flatMap(child => child.toList) +} diff --git a/src/main/scala/leon/comparison/Scores.scala b/src/main/scala/leon/comparison/Scores.scala new file mode 100644 index 000000000..4dfea3bc4 --- /dev/null +++ b/src/main/scala/leon/comparison/Scores.scala @@ -0,0 +1,150 @@ +package leon.comparison + +import leon.comparison.Utils._ +import leon.purescala.Common.Tree +import leon.purescala.Definitions.{CaseClassDef, ClassDef} +import leon.purescala.Expressions.{CaseClassPattern, _} +import leon.purescala.Types.{ClassType, TypeTree} + +/** + * Created by joachimmuth on 02.06.16. + * + * This object contains all the scoring method to handle different cases + */ +object Scores { + + /** + * compare only the MatchCases conditions, not the returned value, which are compared through main compareExp + * function + * + * @return + */ + def scoreMatchExpr(x: MatchExpr, y: MatchExpr): Double = { + def scoreMatchCase(casesB: Seq[MatchCase], cases: Seq[MatchCase]): Double = { + val mapCasesB = toMap(casesB) + val mapCases = toMap(cases) + scoreMapMatchCase(mapCasesB, mapCases) + } + + /** + * Compare the map containing the MatchCases + * NOW: just check how many similar key they have + * NEXT: take into account the order; some MatchCase can be non exclusive, meaning that it "eats" the next one: + * case x if x < 4 => ... + * case x if x < 10 => ... + * doesn't have the same meaning if inverted. + * + * NEXT: take into account invoked expression + * + * @param mapCasesB + * @param mapCases + * @return + */ + def scoreMapMatchCase(mapCasesB: Map[Tree, Expr], mapCases: Map[Tree, Expr]): Double = { + val all = mapCasesB.keys.size + mapCases.keys.size.toDouble + val diff = ((mapCasesB.keySet -- mapCases.keySet) ++ (mapCases.keySet -- mapCasesB.keySet)).size.toDouble + (all - diff) / all + } + + def toMap(cases: Seq[MatchCase]) = { + cases.map(a => a.pattern match { + case InstanceOfPattern(_, ct) => ct -> a.rhs + case CaseClassPattern(_, ct, _) => ct -> a.rhs + case _ => a.pattern -> a.rhs + }).toMap + } + + scoreMatchCase(x.cases, y.cases) + } + + + /** + * One hard piece is to compare two case clase, because it cannot be normalized like value + * + * @return + */ + + def scoreClassDef(classA: ClassDef, classB: ClassDef): Double = { + (classA, classB) match { + case (a, b) if (a.isAbstract && b.isAbstract) => + if (a.knownCCDescendants.size == b.knownCCDescendants.size) 1.0 + else 0.0 + case (a: CaseClassDef, b: CaseClassDef) => + scoreCaseClassDef(a, b) + case _ => 0.0 + + } + } + + def compareTypeTree(typeA: TypeTree, typeB: TypeTree): Double = (typeA, typeB) match { + case (a: ClassType, b: ClassType) => scoreClassDef(a.classDef, b.classDef) + case _ => 0.0 + } + + def scoreCaseClass(a: CaseClass, b: CaseClass): Double = { + scoreCaseClassDef(a.ct.classDef, b.ct.classDef) + } + + + /** + * Compare two CaseClass definition taking into account different parameter: + * - the number of arguments of it's own type + * - the number of arguments of it's parent type + * - the other arguments of primitive types + * + * NEXT: take into account matching between parents + * NEXT: take into account others parameters ? + * + * @param a + * @param b + * @return + */ + def scoreCaseClassDef(a: CaseClassDef, b: CaseClassDef): Double = { + val ownTypeA: Int = argumentsOfOwnType(a) + val ownTypeB: Int = argumentsOfOwnType(b) + val scoreOwnType = percent(ownTypeA, ownTypeB) + + val parentTypeA: Int = argumentsOfParentType(a) + val parentTypeB: Int = argumentsOfParentType(b) + val scoreParentType = percent(parentTypeA, parentTypeB) + + val otherTypeA = a.fields.map(_.getType).filterNot(_.isInstanceOf[ClassDef]) + val otherTypeB = b.fields.map(_.getType).filterNot(_.isInstanceOf[ClassDef]) + val scoreOtherType = scoreSeqType(otherTypeA, otherTypeB) + + // arithmetic mean instead of geometric, we don't want to have 0.0% if one of these criteria don't match + val score: Double = mean(List(scoreOwnType, scoreParentType, scoreOtherType)) + + score + } + + + def scoreSeqType(a: Seq[TypeTree], b: Seq[TypeTree]): Double = { + val intersect = a.intersect(b).size + matchScore(intersect, a.size, b.size) + } + + /** + * Count how many occurrences of its own type appear in its arguments + * (to be improved if multiples types) + * + * @param a the case class + * @return + */ + def argumentsOfOwnType(a: CaseClassDef): Int = + a.fields.map(_.getType).count(a.tparams.map(_.tp.getType).contains(_)) + + + /** + * Count how many occurrences of its parent type appear in its arguments + * + * @param a + * @return + */ + def argumentsOfParentType(a: CaseClassDef): Int = a match { + case _ if a.hasParent => a.fields.map(_.getType).count(_ == a.parent.get.getType) + case _ => 0 + } + + +} diff --git a/src/main/scala/leon/comparison/Utils.scala b/src/main/scala/leon/comparison/Utils.scala new file mode 100644 index 000000000..2000f0922 --- /dev/null +++ b/src/main/scala/leon/comparison/Utils.scala @@ -0,0 +1,243 @@ +package leon.comparison + +import leon.purescala.Common.Identifier +import leon.purescala.Definitions.FunDef +import leon.purescala.ExprOps +import leon.purescala.ExprOps._ +import leon.purescala.Expressions._ + +/** + * Created by joachimmuth on 25.04.16. + */ +object Utils { + + case class Value(a: Expr, b: Expr, score: Double) + + def hasHole(funDef: FunDef): Boolean = + hasHole(funDef.body.get) + + def hasHole(expr: Expr): Boolean = + collectExpr(expr) exists (e => e.isInstanceOf[Hole] || e.isInstanceOf[Choose]) + + /** + * Method to handle update made recently that changed argument and output of ExprOps.normalizeStructure + * + * @param expr + * @return + */ + def normalizeStructure(expr: Expr): Expr = { + val allVars: Seq[Identifier] = fold[Seq[Identifier]] { + (expr, idSeqs) => idSeqs.foldLeft(expr match { + case Lambda(args, _) => args.map(_.id) + case Forall(args, _) => args.map(_.id) + 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) + case Variable(id) => Seq(id) + case _ => Seq.empty[Identifier] + })((acc, seq) => acc ++ seq) + }(expr).distinct + + ExprOps.normalizeStructure(allVars, expr)._2 + } + + + def percent(a: Int, b: Int): Double = { + if (a == 0 && b == 0) 1.0 + else if (a == 0 || b == 0) 0.0 + else Math.min(a.toDouble / b.toDouble, b.toDouble / a.toDouble) + } + + def matchScore(a: Int, option1: Int, option2: Int): Double = + Math.min(percent(a, option1), percent(a, option2)) + + + /** + * Arithmetic means + */ + def mean(a: Double): Double = a + + def mean(a: Double, b: Double): Double = (a + b) / 2 + + def mean(a: Double, b: Double, c: Double): Double = (a + b + c) / 3 + + def mean(a: Double, b: Double, c: Double, d: Double): Double = (a + b + c + d) / 4 + + def mean(list: List[Double]): Double = list.foldLeft(0.0)(_ + _) / list.size.toDouble + + + /** + * Use GenTreeOps.fold to travers tree and collect attribute of their expression. + * + * No use of GenTreeOps.collect because we DO want to preserve duplicate element (GenTreeOps.collect return a + * Set, which doesn't) + * @param expr + * @return + */ + def collectClass(expr: Expr): List[Class[_]] = { + val listClass: Seq[Class[_]] = ExprOps.fold{ + (expr, seq: Seq[Seq[Class[_]]]) => seq.flatten :+ expr.getClass + }(expr) + + listClass.toList + } + + + def collectExpr(expr: Expr): List[Expr] = { + val listExpr: Seq[Expr] = ExprOps.fold{ + (expr, seq: Seq[Seq[Expr]]) => seq.flatten :+ expr + }(expr) + + listExpr.toList + } + + /** + * Give a list of all children of one parent. Why do we need to use "traverse" function to get them? Because + * there is a lot of possible CaseClass extending Expr, and we want to deal with any of them. + * + * @param expr + * @return + */ + def getChildren(expr: Expr): List[Expr] = + traverse(expr)(expr => Nil)(expr => List(expr)) + + + /** + * Main function allowing to traverse a tree. This function is made in a way to be the more generic possible, + * letting choose how we deal with the parent (the current Expr) and its children. + * + * The function "collect" derives from it, by choosing "onChild" to be a recursive call. + * The function "getChild" also derives from it, by doing nothing with the parent and adding the children to the + * list, but without recursion. + * + * As we can see, this function allow to do a lot of things and can be used in the future to test new configuration + * + * @param expr the parent of the tree we want to traverse. Never forget, we use the term "parent" but expr contains + * all the tree + * @param onParent function applied to this parent. It can neglect it (expr => Nil), store it (expr => List(expr)) + * or store one of its parameter (expr => List(expr.getClass)) + * @param onChild function applied to the children expression of "expr". It can be recursive (as in "collect" + * function) or can store some information about it (as in "getChildren) + * @tparam T the type of the returned list we want to get after traversing the tree + * @return a list containing all the chosen values stored by both function "onParent" and "onChildren" + */ + def traverse[T](expr: Expr)(onParent: Expr => List[T])(onChild: Expr => List[T]): List[T] = expr match { + case Require(pred, body) => onParent(expr) ++ onChild(pred) ++ onChild(body) + case Ensuring(body, pred) => onParent(expr) ++ onChild(body) ++ onChild(pred) + case Assert(pred, _, body) => onParent(expr) ++ onChild(pred) ++ onChild(body) + case Let(binder, value, body) => onParent(expr) ++ onChild(value) ++ onChild(body) + + // how to handle fds (function definition) ?? + case LetDef(fds, body) => onParent(expr) ++ onChild(body) + + case Application(callee, args) => onParent(expr) ++ onChild(callee) ++ args.flatMap(onChild(_)) + case Lambda(_, body) => onParent(expr) ++ onChild(expr) + case Forall(_, body) => onParent(expr) ++ onChild(body) + case FunctionInvocation(_, args) => + onParent(expr) + case IfExpr(cond, thenn, elze) => onParent(expr) ++ onChild(cond) ++ onChild(thenn) ++ onChild(elze) + + // we don't list the scrutinee + // method cases.expression return both optGuard and rhs + case MatchExpr(scrutinee, cases) => + onParent(expr) ++ cases.flatMap(m => m.expressions.flatMap(e => onChild(e))) + + case CaseClass(_, args) => onParent(expr) ++ args.flatMap(onChild(_)) + case CaseClassSelector(_, caseClass, _) => + onParent(expr) ++ onChild(caseClass) + case Equals(lhs, rhs) => + onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + + /* Propositional logic */ + case And(exprs) => + onParent(expr) ++ exprs.flatMap(onChild(_)) + case Or(exprs) => + onParent(expr) ++ exprs.flatMap(onChild(_)) + case Implies(lhs, rhs) => onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case Not(argExpr) => + onParent(expr) ++ onChild(argExpr) + + case IsInstanceOf(argExpr, _) => + onParent(expr) ++ onChild(argExpr) + case AsInstanceOf(argExpr, _) => + onParent(expr) ++ onChild(argExpr) + + /* Integer arithmetic */ + case Plus(lhs, rhs) => onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case Minus(lhs, rhs) => + onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case UMinus(argExpr) => + onParent(expr) ++ onChild(argExpr) + case Times(lhs, rhs) => + onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case Division(lhs, rhs) => onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case Remainder(lhs, rhs) => + onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case Modulo(lhs, rhs) => onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case GreaterThan(lhs, rhs) => + onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case GreaterEquals(lhs, rhs) => onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case LessThan(lhs, rhs) => + onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case LessEquals(lhs, rhs) => + onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + + /* Real arithmetic */ + case RealPlus(lhs, rhs) => onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case RealMinus(lhs, rhs) => onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case RealDivision(lhs, rhs) => + onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case RealTimes(lhs, rhs) => onParent(expr) ++ onChild(lhs) ++ onChild(rhs) + case RealUMinus(argExpr) => + onParent(expr) ++ onChild(argExpr) + + /* Tuple operations */ + case Tuple(exprs) => onParent(expr) ++ exprs.flatMap(onChild(_)) + case TupleSelect(tuple, _) => + onParent(expr) ++ onChild(tuple) + + /* Set operations */ + case FiniteSet(elements, _) => onParent(expr) ++ elements.flatMap(onChild(_)) + case ElementOfSet(element, set) => + onParent(expr) ++ onChild(element) ++ onChild(set) + case SetCardinality(set) => onParent(expr) ++ onChild(set) + case SubsetOf(set1, set2) => onParent(expr) ++ onChild(set1) ++ onChild(set2) + case SetIntersection(set1, set2) => + onParent(expr) ++ onChild(set1) ++ onChild(set2) + case SetUnion(set1, set2) => + onParent(expr) ++ onChild(set1) ++ onChild(set2) + case SetDifference(set1, set2) => onParent(expr) ++ onChild(set1) ++ onChild(set2) + + /* Map operations */ + case FiniteMap(pairs, _, _) => + onParent(expr) ++ pairs.toList.flatMap(t => onChild(t._1) ++ onChild(t._2)) + case MapApply(map, key) => onParent(expr) ++ onChild(map) ++ onChild(key) + case MapUnion(map1, map2) => onParent(expr) ++ onChild(map1) ++ onChild(map2) + case MapDifference(map, keys) => onParent(expr) ++ onChild(map) ++ onChild(keys) + case MapIsDefinedAt(map, key) => onParent(expr) ++ onChild(map) ++ onChild(key) + + /* Array operations */ + case ArraySelect(array, index) => onParent(expr) ++ onChild(array) ++ onChild(index) + case ArrayUpdated(array, index, newValue) => + onParent(expr) ++ onChild(array) ++ onChild(index) ++ onChild(newValue) + case ArrayLength(array) => onParent(expr) ++ onChild(array) + case NonemptyArray(elems, defaultLength) => onParent(expr) ++ elems.flatMap(t => onChild(t._2)) + case EmptyArray(_) => onParent(expr) + + /* Holes */ + case Choose(pred) => onParent(expr) + //case Hole(_, alts) => onParent(expr) ++ alts.flatMap(onChild(_)) + + + // default value for any easy-to-handled or Terminal expression + // including: NoTree, Error, Variable, MethodInvocation, This, all Literal, ConverterToString + // leave aside (at least for the moment): String Theory, BitVector Operation, Special trees for synthesis (holes, ...) + case x if x.isInstanceOf[Expr] => onParent(expr) + + //default value for error handling, should never reach that + case _ => Nil + } + + +} diff --git a/src/main/scala/leon/utils/DebugSections.scala b/src/main/scala/leon/utils/DebugSections.scala index 7e454e7ea..8877d8870 100644 --- a/src/main/scala/leon/utils/DebugSections.scala +++ b/src/main/scala/leon/utils/DebugSections.scala @@ -25,6 +25,7 @@ case object DebugSectionTypes extends DebugSection("types", 1 << 1 case object DebugSectionIsabelle extends DebugSection("isabelle", 1 << 14) case object DebugSectionReport extends DebugSection("report", 1 << 15) case object DebugSectionGenC extends DebugSection("genc", 1 << 16) +case object DebugSectionComparison extends DebugSection("comparison", 1 << 17) object DebugSections { val all = Set[DebugSection]( @@ -44,6 +45,7 @@ object DebugSections { DebugSectionTypes, DebugSectionIsabelle, DebugSectionReport, - DebugSectionGenC + DebugSectionGenC, + DebugSectionComparison ) } diff --git a/src/sphinx/installation.rst b/src/sphinx/installation.rst index b4deaf194..964378eba 100644 --- a/src/sphinx/installation.rst +++ b/src/sphinx/installation.rst @@ -222,3 +222,95 @@ but instead package leon.test.myTestPackage + + + + +Using Leon in IntelliJ +---------------------- + +You can maybe encounter some problems installing Leon, because of the various dependencies needed. +Here is a step-by-step guide having as a goal to help you to install your first Leon project and to be sure that every +dependency is present and correctly linked. + +The advantage of using IntelliJ IDEA is that it provides a Scala plugin which allows you to directly import SBT project +into it. + +**Installation:** + +* `SBT 0.13.x `_ (as described above) +* `IntelliJ IDEA `_ +* Leon project files, available in GitHub (as described above) +* Java SE Development Kit 8 or 7 (as described above) +* Support for at least one external solver (See :ref:`smt-solvers`) + +**Setup STB in your project:** + +Leon requires the Build class, which is a class created by SBT in its folder *target* when you compile your project the +first time. Also, it will be comfortable to run Leon through terminal command, in order to easily specify arguments. +We will resolve both of theses things running the command: + +.. code-block:: bash + + $ sbt clean compile + $ sbt package script + +This will create the *leon* executable file that you can now use typing ```./leon```. You are not required to recompile +this file after modifying your project, because *leon* file is actually a script that modifies the +scala compiler, giving it the path to Leon, and then compile the file you specified. You can open *leon* +in terminal ```cat leon``` to realize that. + +**Setup IntelliJ:** + +IntelliJ provides a Scala plugin. It will normally offer to install it by its own the first time you load a scala file, +anyway we suggest you install it manually before starting. Doing so will allow you to use sbt wizard import +in the next step. + +In *Preferences -> Plugins* search for *Scala* and install it. + +**Import Leon:** + +Use the *New project from existing sources...* (*Import project* in welcome window) wizard of IntelliJ to import Leon. +Select the *SBT* external model and let +IntelliJ install it with the default options. Specify the Java SDK 1.8. When choosing the modules to import, only +select *Leon* and *Leon-build* +(maybe called *root* and *root-build*), we will import the other modules later manually. + +You would now see only the *Leon* module in the IntelliJ project explorer. If you see *bonsai* or *smt-lib*, just +delete them. + +**Setup dependencies:** + +By right-clicking on Leon, choose *Open Module Setting*. Here you will set all the dependencies, in the so-named tab. If +SBT import tool worked well, you will see all needed dependencies present in your list and we will enable some of them. +Anyway, if some of them are not present (which happened to me), you can add it by your own clicking +``` "+" --> Add jar or folder --> ... ```. I will specify the path where you can find each dependency. + +Enable: + +* scala-smt-lib (can be found at ~/.sbt/0.13/staging/.../scala-smtlib/target/scala-2.11/classes) +* bonsai (can be found at ~/.sbt/0.13/stagging/.../bonsai/target/scala-2.11/classes) +* libisabel +* libisabel-setub +* scala-lang:... (enable all of them) (can be found at ~/.ivy2/cache/org.scala-lang/...) +* scalatest + +If project has no SDK, add Java Library 1.8 (JDK 1.8) + +The scala-lang:scala-library and scala-lang:scala-compiler must stand for the scala SDK provided by intelliJ, so +normally you haven't to add it. Anyway, if you encounter some problems, download it at with "+" -> Library -> Global Library +-> New Library and select the latest *Ivy* available. Ensure you have at least Scala 2.11 and NOT 2.10. Ensure also that +this added scala SDK is listed BELOW the scala-lang provided by SBT, so it has lower priority. + +*.sbt* and *.ivy2* are folders created by SBT and are placed in your home folder. + +If you find that some other modules are required to your project, feel free to add them but keep them below the ones +described in the priority list. + +**Check your installation:** + +*Make* the project in IntelliJ and try running it on some test files, like + +.. code-block:: bash + + $ ./leon testcases/verification/datastructures/AssociativeList.scala diff --git a/testcases/comparison/corpus/ClassBase.scala b/testcases/comparison/corpus/ClassBase.scala new file mode 100644 index 000000000..1402b3ecc --- /dev/null +++ b/testcases/comparison/corpus/ClassBase.scala @@ -0,0 +1,73 @@ +import leon.annotation._ +import leon.lang._ +import leon.collection._ + + +object ClassBase { + + + def match_value(x: Int): Char = x match { + case 1 => 'a' + case 2 => 'b' + } + + def inversed_match_value(x: Int): Char = x match { + case 2 => 'b' + case 1 => 'a' + } + + + + def encapsulated_match_value(x: Int): Char = x match { + case x2 if x2 < 10 => + x2 match { + case 1 => 'a' + case 2 => 'b' + } + case _ => 'z' + } + + def large_match_value(x:Int): Char = x match { + case 1 => 'a' + case 2 => 'b' + case 3 => 'c' + case 4 => 'd' + } + + def match_with_val(x:Int): Char = { + val somme = x match { + case 1 => 'a' + case 2 => 'c' + } + + if (somme == 'a') 'a' else 'b' + } + + def dummy_replace(x: BigInt, list: List[Char], char: Char): List[Char] = { + val size = list.size + val ret: List[Char] = if (x < 0 || x > size) { + list + } else { + val before: List[Char] = list.take(x) + val after: List[Char] = list.drop(x+1) + before ++ List(char) ++ after + } + + ret + } + + case class B(x: Int) extends A + case class C(x: Int, y: Int) extends A + abstract class A + + def class_B(x: Int): B = B(x) + + def class_C(x:Int, y: Int): C = C(x, y) + + + + + + + +} diff --git a/testcases/comparison/to-compare/ClassToCompare.scala b/testcases/comparison/to-compare/ClassToCompare.scala new file mode 100644 index 000000000..c175f4e1d --- /dev/null +++ b/testcases/comparison/to-compare/ClassToCompare.scala @@ -0,0 +1,63 @@ +import leon.annotation._ +import leon.lang._ +import leon.collection._ +import leon.lang.synthesis._ + + +object ClassToCompare { + + + def match_value(x: Int): Char = x match { + case 1 => 'a' + case 2 => 'b' + } + + case class B(x: Int) extends A + abstract class A + + def class_B(x: Int): B = B(x) + + def hole_match_value(x:Int): Char = x match { + case 1 => 'a' + case 2 => ??? [Char] + } + + def hole_large_match_value(x:Int): Char = x match { + case 1 => 'a' + case 2 => 'b' + case 3 => ??? [Char] + case 4 => 'd' + } + + def hole_encapsulated_match_value(x: Int): Char = x match { + case x2 if x2 < 10 => + x2 match { + case 1 => ??? [Char] + case 2 => 'b' + } + case _ => 'z' + } + + def hole_that_doesnt_match(x: Int, y: Int): Char ={ + if (x > y) 'x' + else ??? [Char] + } + + def hole_match_val(x:Int): Char = { + val somme: Char = ??? [Char] + + if (somme == 'a') 'a' else 'b' + } + + def hole_replace(x: BigInt, list: List[Char], char: Char): List[Char] = { + val size = list.size + val ret: List[Char] = if (x < 0 || x > size) { + list + } else { + ??? [List[Char]] + } + ret + } + + +} \ No newline at end of file