From b80015182981044b6f5fca4139d112549bc9ffb9 Mon Sep 17 00:00:00 2001 From: Nicolas Voirol <voirol.nicolas@gmail.com> Date: Fri, 21 Oct 2016 09:38:15 +0200 Subject: [PATCH] Reorganized options and added a simple main to Inox --- src/main/scala/inox/Context.scala | 2 +- src/main/scala/inox/Main.scala | 167 ++++++++++++++++++ src/main/scala/inox/Options.scala | 68 ++----- src/main/scala/inox/ast/Printers.scala | 6 +- .../inox/evaluators/ContextualEvaluator.scala | 3 +- .../scala/inox/evaluators/Evaluator.scala | 9 +- .../inox/evaluators/SolvingEvaluator.scala | 7 +- src/main/scala/inox/solvers/Solver.scala | 18 +- .../scala/inox/solvers/SolverFactory.scala | 6 +- .../inox/solvers/smtlib/CVC4Solver.scala | 1 - .../inox/solvers/smtlib/SMTLIBDebugger.scala | 2 +- .../solvers/unrolling/UnrollingSolver.scala | 11 +- 12 files changed, 195 insertions(+), 105 deletions(-) create mode 100644 src/main/scala/inox/Main.scala diff --git a/src/main/scala/inox/Context.scala b/src/main/scala/inox/Context.scala index 1cf9d8dab..54a8bf20d 100644 --- a/src/main/scala/inox/Context.scala +++ b/src/main/scala/inox/Context.scala @@ -25,7 +25,7 @@ object Context { Context( reporter, new InterruptManager(reporter), - options = Options.empty + InoxOptions.optDebug(Set(ast.DebugSectionTrees: DebugSection)) + options = Options.empty + Main.optDebug(Set(ast.DebugSectionTrees: DebugSection)) ) } } diff --git a/src/main/scala/inox/Main.scala b/src/main/scala/inox/Main.scala new file mode 100644 index 000000000..e19602e40 --- /dev/null +++ b/src/main/scala/inox/Main.scala @@ -0,0 +1,167 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package inox + +import solvers._ + +trait MainHelpers { + + protected def getDebugSections: Set[DebugSection] = Set( + ast.DebugSectionTrees, + datagen.DebugSectionDataGen, + solvers.DebugSectionSolver + ) + + protected final val debugSections = getDebugSections + + final object optDebug extends OptionDef[Set[DebugSection]] { + import OptionParsers._ + val name = "debug" + val default = Set[DebugSection]() + val usageRhs = "d1,d2,..." + + private val debugParser: OptionParser[Set[DebugSection]] = s => { + if (s == "all") Some(debugSections) + else debugSections.find(_.name == s).map(Set(_)) + } + + val parser: String => Option[Set[DebugSection]] = { + setParser[Set[DebugSection]](debugParser)(_).map(_.flatten) + } + } + + protected def getOptions: Map[OptionDef[_], String] = Map( + optTimeout -> "Set a timeout for each proof attempt (in sec.)", + optSelectedSolvers -> { + "Use solvers s1, s2,...\nAvailable: " + + solvers.SolverFactory.solverNames.toSeq.sortBy(_._1).map { + case (name, desc) => f"\n $name%-14s : $desc" + } + }, + optDebug -> { + val sects = debugSections.toSeq.map(_.name).sorted + val (first, second) = sects.splitAt(sects.length/2 + 1) + "Enable detailed messages per component.\nAvailable:\n" + + " " + first.mkString(", ") + ",\n" + + " " + second.mkString(", ") + }, + ast.optPrintPositions -> "Attach positions to trees when printing", + ast.optPrintUniqueIds -> "Always print unique ids", + ast.optPrintTypes -> "Attach types to trees when printing", + solvers.optCheckModels -> "Double-check counter-examples with evaluator", + solvers.optSilentErrors -> "Fail silently into UNKNOWN when encountering an error", + solvers.unrolling.optUnrollFactor -> "Number of unrollings to perform in each unfold step", + solvers.unrolling.optFeelingLucky -> "Use evaluator to find counter-examples early", + solvers.unrolling.optUnrollAssumptions -> "Use unsat-assumptions to drive unfolding while remaining fair", + solvers.smtlib.optCVC4Options -> "Pass extra options to CVC4", + evaluators.optMaxCalls -> "Maximum number of function invocations allowed during evaluation", + evaluators.optIgnoreContracts -> "Don't fail on invalid contracts during evaluation" + ) + + protected final val options = getOptions + + private def helpString(opt: OptionDef[_]): String = { + f"${opt.usageDesc}%-28s" + options(opt).replaceAll("\n", "\n" + " " * 28) + } + + /** The current files on which Inox is running. + * + * This option cannot be filled through option parsing and must always be + * instantiated manually (see [[processOptions]]). We therefore MUST NOT add + * it to the [[options]] map! + */ + final object optFiles extends OptionDef[Seq[java.io.File]] { + val name = "files" + val default = Seq[java.io.File]() + val usageRhs = "No possible usage" + val parser = { (_: String) => throw FatalError("Unparsable option \"files\"") } + } + + protected def processOptions(args: Seq[String]): Context = { + val initReporter = new DefaultReporter(Set()) + + val opts = args.filter(_.startsWith("--")) + + val files = args.filterNot(_.startsWith("-")).map(new java.io.File(_)) + + val inoxOptions: Seq[OptionValue[_]] = opts.map { opt => + val (name, value) = OptionsHelpers.nameValue(opt) getOrElse initReporter.fatalError( + s"Malformed option $opt. Options should have the form --name or --name=value" + ) + + // find respective OptionDef, or report an error + val df = options.keys.find(_.name == name) getOrElse initReporter.fatalError( + s"Unknown option: $name\nTry the --help option for more information." + ) + + df.parse(value)(initReporter) + } + + val reporter = new DefaultReporter( + inoxOptions.collectFirst { + case OptionValue(`optDebug`, sections) => sections.asInstanceOf[Set[DebugSection]] + }.getOrElse(Set[DebugSection]()) + ) + + reporter.whenDebug(DebugSectionOptions) { debug => + debug("Options considered:") + for (io <- inoxOptions) debug(io.toString) + } + + Context( + reporter = reporter, + options = Options(inoxOptions), + interruptManager = new utils.InterruptManager(reporter) + ) + } + + def exit(error: Boolean) = sys.exit(if (error) 1 else 0) + + def setup(args: Array[String]): Context = { + val ctx = try { + processOptions(args.toList) + } catch { + case FatalError(msg) => + exit(error = true) + } + + ctx.interruptManager.registerSignalHandler() + + ctx + } +} + +object Main extends MainHelpers { + + def main(args: Array[String]): Unit = { + val ctx = setup(args) + + val files = ctx.options.findOptionOrDefault(optFiles) + if (files.isEmpty) { + ctx.reporter.fatalError(s"Input file was not specified.\nTry the --help option for more information.") + } else { + for (file <- files) { + val (syms, expr) = new tip.Parser(file).parseScript + val program = InoxProgram(ctx, syms) + import program._ + import program.ctx._ + + import SolverResponses._ + SimpleSolverAPI(SolverFactory.default(program)).solveSAT(expr) match { + case SatWithModel(model) => + reporter.info(" => SAT") + for ((vd, res) <- model) { + reporter.info(f"${vd.asString}%-15s -> ${res.asString}") + } + exit(error = false) + case Unsat => + reporter.info(" => UNSAT") + exit(error = false) + case Unknown => + reporter.info(" => UNKNOWN") + exit(error = true) + } + } + } + } +} diff --git a/src/main/scala/inox/Options.scala b/src/main/scala/inox/Options.scala index 2b06bd458..bce9b9cd1 100644 --- a/src/main/scala/inox/Options.scala +++ b/src/main/scala/inox/Options.scala @@ -7,9 +7,10 @@ import OptionParsers._ import scala.util.Try import scala.reflect.ClassTag +object DebugSectionOptions extends DebugSection("options") + abstract class OptionDef[A] { val name: String - val description: String def default: A val parser: OptionParser[A] val usageRhs: String @@ -19,10 +20,6 @@ abstract class OptionDef[A] { else s"--$name=$usageRhs" } - def helpString = { - f"$usageDesc%-28s" + description.replaceAll("\n", "\n" + " " * 28) - } - private def parseValue(s: String)(implicit reporter: Reporter): A = { parser(s).getOrElse( reporter.fatalError( @@ -40,7 +37,6 @@ abstract class OptionDef[A] { def apply(value: A): OptionValue[A] = OptionValue(this)(value) - // @mk: FIXME: Is this cool? override def equals(other: Any) = other match { case that: OptionDef[_] => this.name == that.name case _ => false @@ -49,20 +45,20 @@ abstract class OptionDef[A] { override def hashCode = name.hashCode } -case class FlagOptionDef(name: String, description: String, default: Boolean) extends OptionDef[Boolean] { +case class FlagOptionDef(name: String, default: Boolean) extends OptionDef[Boolean] { val parser = booleanParser val usageRhs = "" } -case class StringOptionDef(name: String, description: String, default: String, usageRhs: String) extends OptionDef[String] { +case class StringOptionDef(name: String, default: String, usageRhs: String) extends OptionDef[String] { val parser = stringParser } -case class LongOptionDef(name: String, description: String, default: Long, usageRhs: String) extends OptionDef[Long] { +case class LongOptionDef(name: String, default: Long, usageRhs: String) extends OptionDef[Long] { val parser = longParser } -case class IntOptionDef(name: String, description: String, default: Int, usageRhs: String) extends OptionDef[Int] { +case class IntOptionDef(name: String, default: Int, usageRhs: String) extends OptionDef[Int] { val parser = intParser } @@ -184,51 +180,11 @@ object Options { def empty: Options = Options(Seq()) } -object InoxOptions { - - val optSelectedSolvers = new OptionDef[Set[String]] { - val name = "solvers" - val description = "Use solvers s1, s2,...\n" + - solvers.SolverFactory.solversPretty - val default = Set("nativez3") - val parser = setParser(stringParser) - val usageRhs = "s1,s2,..." - } - - val optDebug = new OptionDef[Set[DebugSection]] { - import OptionParsers._ - val name = "debug" - val description = { - /* - val sects = DebugSections.all.toSeq.map(_.name).sorted - val (first, second) = sects.splitAt(sects.length/2 + 1) - */ - "Enable detailed messages per component." /* + - "\nAvailable:\n" + - " " + first.mkString(", ") + ",\n" + - " " + second.mkString(", ")*/ - } - val default = Set[DebugSection]() - val usageRhs = "d1,d2,..." - private val debugParser: OptionParser[Set[DebugSection]] = s => { - /* - if (s == "all") { - Some(DebugSections.all) - } else { - DebugSections.all.find(_.name == s).map(Set(_)) - }*/ - None - - } - val parser: String => Option[Set[DebugSection]] = { - setParser[Set[DebugSection]](debugParser)(_).map(_.flatten) - } - } +object optTimeout extends LongOptionDef("timeout", 0L, "t") - val optTimeout = LongOptionDef( - "timeout", - "Set a timeout for attempting to prove a verification condition/ repair a function (in sec.)", - 0L, - "t" - ) +object optSelectedSolvers extends OptionDef[Set[String]] { + val name = "solvers" + val default = Set("nativez3") + val parser = setParser(stringParser) + val usageRhs = "s1,s2,..." } diff --git a/src/main/scala/inox/ast/Printers.scala b/src/main/scala/inox/ast/Printers.scala index 998527990..14a811659 100644 --- a/src/main/scala/inox/ast/Printers.scala +++ b/src/main/scala/inox/ast/Printers.scala @@ -7,9 +7,9 @@ import utils._ import org.apache.commons.lang3.StringEscapeUtils import scala.language.implicitConversions -object optPrintPositions extends FlagOptionDef("printpositions", "Attach positions to trees when printing", false) -object optPrintUniqueIds extends FlagOptionDef("printids", "Always print unique ids", false) -object optPrintTypes extends FlagOptionDef("printtypes", "Attach types to trees when printing", false) +object optPrintPositions extends FlagOptionDef("printpositions", false) +object optPrintUniqueIds extends FlagOptionDef("printids", false) +object optPrintTypes extends FlagOptionDef("printtypes", false) trait Printers { self: Trees => diff --git a/src/main/scala/inox/evaluators/ContextualEvaluator.scala b/src/main/scala/inox/evaluators/ContextualEvaluator.scala index 3b4380a5f..cc58e8188 100644 --- a/src/main/scala/inox/evaluators/ContextualEvaluator.scala +++ b/src/main/scala/inox/evaluators/ContextualEvaluator.scala @@ -3,8 +3,7 @@ package inox package evaluators -object optMaxCalls extends IntOptionDef("maxcalls", - "Maximum number of function invocations allowed during evaluation", 50000, "<PosInt> | -1 (for unbounded") +object optMaxCalls extends IntOptionDef("maxcalls", 50000, "<PosInt> | -1 (for unbounded") trait ContextualEvaluator extends Evaluator { import program._ diff --git a/src/main/scala/inox/evaluators/Evaluator.scala b/src/main/scala/inox/evaluators/Evaluator.scala index 4283f2bfb..7dc2ee94a 100644 --- a/src/main/scala/inox/evaluators/Evaluator.scala +++ b/src/main/scala/inox/evaluators/Evaluator.scala @@ -3,14 +3,7 @@ package inox package evaluators -object EvaluatorOptions { - val options = Seq( - optIgnoreContracts - ) -} - -object optIgnoreContracts extends FlagOptionDef( - "ignorecontracts", "Don't fail on invalid contracts during evaluation", false) +object optIgnoreContracts extends FlagOptionDef("ignorecontracts", false) trait Evaluator { val program: Program diff --git a/src/main/scala/inox/evaluators/SolvingEvaluator.scala b/src/main/scala/inox/evaluators/SolvingEvaluator.scala index 1a720c78a..40ad462ce 100644 --- a/src/main/scala/inox/evaluators/SolvingEvaluator.scala +++ b/src/main/scala/inox/evaluators/SolvingEvaluator.scala @@ -14,10 +14,9 @@ trait SolvingEvaluator extends Evaluator { import program.symbols._ private object optForallCache extends OptionDef[MutableMap[program.trees.Forall, Boolean]] { - val parser = { (_: String) => throw FatalError("Unparsable option \"bankOption\"") } - val name = "bank-option" - val description = "Evaluation bank shared between solver and evaluator" - val usageRhs = "" + val parser = { (_: String) => throw FatalError("Unparsable option \"forallCache\"") } + val name = "forall-cache" + val usageRhs = "No possible usage" def default = MutableMap.empty } diff --git a/src/main/scala/inox/solvers/Solver.scala b/src/main/scala/inox/solvers/Solver.scala index ca04942d7..b90817b93 100644 --- a/src/main/scala/inox/solvers/Solver.scala +++ b/src/main/scala/inox/solvers/Solver.scala @@ -5,22 +5,8 @@ package solvers import utils._ -object SolverOptions { - val options = Seq( - optCheckModels, - optSilentErrors, - unrolling.optUnrollFactor, - unrolling.optFeelingLucky, - unrolling.optUnrollAssumptions, - smtlib.optCVC4Options - ) -} - -object optCheckModels extends FlagOptionDef( - "checkmodels", "Double-check counter-examples with evaluator", false) - -object optSilentErrors extends FlagOptionDef( - "silenterrors", "Fail silently into UNKNOWN when encountering an error", false) +object optCheckModels extends FlagOptionDef("checkmodels", false) +object optSilentErrors extends FlagOptionDef("silenterrors", false) case object DebugSectionSolver extends DebugSection("solver") diff --git a/src/main/scala/inox/solvers/SolverFactory.scala b/src/main/scala/inox/solvers/SolverFactory.scala index c02ac30a0..8f6610dca 100644 --- a/src/main/scala/inox/solvers/SolverFactory.scala +++ b/src/main/scala/inox/solvers/SolverFactory.scala @@ -113,10 +113,6 @@ object SolverFactory { } } - val solversPretty = "Available: " + solverNames.toSeq.sortBy(_._1).map { - case (name, desc) => f"\n $name%-14s : $desc" - } - val solvers: Set[String] = solverNames.map(_._1).toSet def apply(name: String, p: InoxProgram, opts: Options): SolverFactory { @@ -127,7 +123,7 @@ object SolverFactory { def apply(p: InoxProgram, opts: Options): SolverFactory { val program: p.type type S <: TimeoutSolver { val program: p.type } - } = opts.findOptionOrDefault(InoxOptions.optSelectedSolvers).toSeq match { + } = opts.findOptionOrDefault(optSelectedSolvers).toSeq match { case Seq() => throw FatalError("No selected solver") case Seq(single) => apply(single, p, opts) case multiple => PortfolioSolverFactory(p) { diff --git a/src/main/scala/inox/solvers/smtlib/CVC4Solver.scala b/src/main/scala/inox/solvers/smtlib/CVC4Solver.scala index 428431877..092792f46 100644 --- a/src/main/scala/inox/solvers/smtlib/CVC4Solver.scala +++ b/src/main/scala/inox/solvers/smtlib/CVC4Solver.scala @@ -8,7 +8,6 @@ import inox.OptionParsers._ object optCVC4Options extends OptionDef[Set[String]] { val name = "solver:cvc4" - val description = "Pass extra arguments to CVC4" val default = Set[String]() val parser = setParser(stringParser) val usageRhs = "<cvc4-opt>" diff --git a/src/main/scala/inox/solvers/smtlib/SMTLIBDebugger.scala b/src/main/scala/inox/solvers/smtlib/SMTLIBDebugger.scala index 89d23a60d..37eaca9f6 100644 --- a/src/main/scala/inox/solvers/smtlib/SMTLIBDebugger.scala +++ b/src/main/scala/inox/solvers/smtlib/SMTLIBDebugger.scala @@ -21,7 +21,7 @@ trait SMTLIBDebugger extends SMTLIBTarget { /* Printing VCs */ protected lazy val debugOut: Option[java.io.FileWriter] = { if (ctx.reporter.isDebugEnabled) { - val file = "" // TODO: real file name + val file = ctx.options.findOptionOrDefault(Main.optFiles).headOption.map(_.getName).getOrElse("NA") val n = DebugFileNumbers.next(targetName + file) val fileName = s"smt-sessions/$targetName-$file-$n.smt2" diff --git a/src/main/scala/inox/solvers/unrolling/UnrollingSolver.scala b/src/main/scala/inox/solvers/unrolling/UnrollingSolver.scala index 9929f0e05..08f296e02 100644 --- a/src/main/scala/inox/solvers/unrolling/UnrollingSolver.scala +++ b/src/main/scala/inox/solvers/unrolling/UnrollingSolver.scala @@ -12,14 +12,9 @@ import combinators._ import scala.collection.mutable.{Map => MutableMap} -object optUnrollFactor extends LongOptionDef( - "unrollfactor", "Number of unfoldings to perform in each unfold step", default = 1, "<PosInt>") - -object optFeelingLucky extends FlagOptionDef( - "feelinglucky", "Use evaluator to find counter-examples early", false) - -object optUnrollAssumptions extends FlagOptionDef( - "unrollassumptions", "Use unsat-assumptions to drive unfolding while remaining fair", false) +object optUnrollFactor extends LongOptionDef("unrollfactor", default = 1, "<PosInt>") +object optFeelingLucky extends FlagOptionDef("feelinglucky", false) +object optUnrollAssumptions extends FlagOptionDef("unrollassumptions", false) trait AbstractUnrollingSolver extends Solver { self => -- GitLab