From 282469fb9e77e01c9083a45396db0dceee7db8ea Mon Sep 17 00:00:00 2001
From: Etienne Kneuss <ekneuss@gmail.com>
Date: Wed, 1 Apr 2015 14:31:54 +0200
Subject: [PATCH] Implement --watch to rerun Leon on file changes

---
 src/main/scala/leon/Main.scala               | 33 +++++++--
 src/main/scala/leon/utils/FilesWatcher.scala | 70 ++++++++++++++++++++
 2 files changed, 99 insertions(+), 4 deletions(-)
 create mode 100644 src/main/scala/leon/utils/FilesWatcher.scala

diff --git a/src/main/scala/leon/Main.scala b/src/main/scala/leon/Main.scala
index d39cdeca8..95ff9c045 100644
--- a/src/main/scala/leon/Main.scala
+++ b/src/main/scala/leon/Main.scala
@@ -36,6 +36,7 @@ object Main {
       LeonFlagOptionDef ("repair",      "--repair",             "Repair selected functions"),
       LeonFlagOptionDef ("synthesis",   "--synthesis",          "Partial synthesis of choose() constructs"),
       LeonFlagOptionDef ("xlang",       "--xlang",              "Support for extra program constructs (imperative,...)"),
+      LeonFlagOptionDef ("watch",       "--watch",              "Rerun pipeline when file changes"),
       LeonValueOptionDef("solvers",     "--solvers=s1,s2",      "Use solvers s1 and s2 (fairz3,enum,smt)"),
       LeonValueOptionDef("debug",       "--debug=<sections..>", "Enables specific messages"),
       LeonFlagOptionDef ("noop",        "--noop",               "No operation performed, just output program"),
@@ -241,6 +242,8 @@ object Main {
     pipeProcess
   }
 
+  private var hasFatal = false;
+
   def main(args: Array[String]) {
     val argsl = args.toList
 
@@ -263,10 +266,31 @@ object Main {
         sys.exit(1)
     }
 
+    ctx.interruptManager.registerSignalHandler()
 
-    try {
-      ctx.interruptManager.registerSignalHandler()
+    val doWatch = ctx.options.exists {
+      case LeonFlagOption("watch", value) => value
+      case _ => false
+    }
 
+    if (doWatch) {
+      val watcher = new FilesWatcher(ctx, ctx.files)
+      watcher.onChange {
+        execute(args, ctx)
+      }
+    } else {
+      execute(args, ctx)
+    }
+
+    if (hasFatal) {
+      sys.exit(1)
+    } else {
+      sys.exit(0)
+    }
+  }
+
+  def execute(args: Seq[String], ctx: LeonContext): Unit = {
+    try {
       // Compute leon pipeline
       val pipeline = computePipeline(ctx.settings)
 
@@ -290,7 +314,7 @@ object Main {
       }
     } catch {
       case LeonFatalError(None) =>
-        sys.exit(1)
+        hasFatal = true
 
       case LeonFatalError(Some(msg)) =>
         // For the special case of fatal errors not sent though Reporter, we
@@ -301,7 +325,8 @@ object Main {
           case _: LeonFatalError =>
         }
 
-        sys.exit(1)
+        hasFatal = true
     }
   }
+
 }
diff --git a/src/main/scala/leon/utils/FilesWatcher.scala b/src/main/scala/leon/utils/FilesWatcher.scala
new file mode 100644
index 000000000..7972e7695
--- /dev/null
+++ b/src/main/scala/leon/utils/FilesWatcher.scala
@@ -0,0 +1,70 @@
+/* Copyright 2009-2014 EPFL, Lausanne */
+
+package leon
+
+import leon.utils._
+import solvers.SolverFactory
+import java.io.File
+import java.nio.file._
+import scala.collection.JavaConversions._
+import java.security.MessageDigest
+import java.math.BigInteger
+
+case class FilesWatcher(ctx: LeonContext, files: Seq[File]) {
+  val toWatch = files.map(_.getAbsoluteFile).toSet
+  val dirs    = toWatch.map(_.getParentFile)
+
+  def onChange(body: => Unit): Unit = {
+    val watcher = FileSystems.getDefault().newWatchService()
+    val paths = dirs.map(_.toPath.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY))
+
+    var lastHashes = toWatch.map(md5file)
+
+    body
+    ctx.reporter.info("Waiting for source changes...")
+
+    while (true) {
+      var key = watcher.take()
+
+      val events = key.pollEvents()
+
+      if (events.exists{_.context match {
+        case (p: Path) => toWatch(p.toFile.getAbsoluteFile)
+        case e => false
+      }}) {
+        val currentHashes = toWatch.map(md5file)
+        if (currentHashes != lastHashes) {
+          lastHashes = currentHashes
+
+          ctx.reporter.info("Detected new version!")
+          body
+          ctx.reporter.info("Waiting for source changes...")
+        }
+      }
+
+      key.reset()
+    }
+  }
+
+  def md5file(f: File): String = {
+    var buffer = new Array[Byte](1024)
+    val md = MessageDigest.getInstance("MD5");
+    try {
+      val is = Files.newInputStream(f.toPath)
+      var read = 0;
+      do {
+        read = is.read(buffer);
+        if (read > 0) {
+          md.update(buffer, 0, read)
+        }
+      } while (read != -1);
+      is.close()
+
+      val bytes = md.digest();
+      ("%0" + (bytes.length << 1) + "X").format(new BigInteger(1, bytes));
+    } catch {
+      case _: RuntimeException =>
+        ""
+    }
+  }
+}
-- 
GitLab