summaryrefslogtreecommitdiff
path: root/src/main/scala
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2017-10-26 08:53:49 -0700
committerLi Haoyi <haoyi.sg@gmail.com>2017-10-26 09:08:58 -0700
commit845a11f8ec04a08dfaba94c0aff2d41c02c2f736 (patch)
treef548c500085d581f7d3757b55031d7b45ea9250e /src/main/scala
parentc8cafe5b835f4bfbcb83f1e04553406e32ac0b05 (diff)
downloadmill-845a11f8ec04a08dfaba94c0aff2d41c02c2f736.tar.gz
mill-845a11f8ec04a08dfaba94c0aff2d41c02c2f736.tar.bz2
mill-845a11f8ec04a08dfaba94c0aff2d41c02c2f736.zip
Serialization using play-json now mostly in place
"pure" targets which depend only on their inputs are modeled but their evaluation semantics aren't fleshed out
Diffstat (limited to 'src/main/scala')
-rw-r--r--src/main/scala/forge/DefCtx.scala8
-rw-r--r--src/main/scala/forge/Evaluator.scala37
-rw-r--r--src/main/scala/forge/Target.scala53
-rw-r--r--src/main/scala/forge/package.scala46
4 files changed, 106 insertions, 38 deletions
diff --git a/src/main/scala/forge/DefCtx.scala b/src/main/scala/forge/DefCtx.scala
index 97f9bf3f..3439f755 100644
--- a/src/main/scala/forge/DefCtx.scala
+++ b/src/main/scala/forge/DefCtx.scala
@@ -6,7 +6,9 @@ import scala.language.experimental.macros
import scala.reflect.macros.blackbox._
-final case class DefCtx(label: String)
+final case class DefCtx(baseLabel: String, anonId: Option[Int]){
+ def label = baseLabel + anonId.getOrElse("")
+}
object DefCtx{
@compileTimeOnly("A DefCtx can only be provided directly within a T{} macro")
implicit def dummy: DefCtx with Int = ???
@@ -22,7 +24,7 @@ object T{
override def transform(tree: c.Tree): c.Tree = {
if (tree.toString.startsWith("forge.") && tree.toString.endsWith(".DefCtx.dummy")) {
count += 1
- c.typecheck(q"forge.DefCtx(sourcecode.Enclosing() + $count)")
+ c.typecheck(q"forge.DefCtx(sourcecode.Enclosing(), Some($count))")
}else tree match{
case Apply(fun, args) =>
val extendedParams = fun.tpe.paramLists.head.padTo(
@@ -61,7 +63,7 @@ object T{
val newArgs = for(x <- args) yield {
if (x.toString.startsWith("forge.") && x.toString.endsWith(".DefCtx.dummy")) {
isTransformed = true
- c.typecheck(q"forge.DefCtx(sourcecode.Enclosing())")
+ c.typecheck(q"forge.DefCtx(sourcecode.Enclosing(), None)")
}else transformer.transform(x)
}
diff --git a/src/main/scala/forge/Evaluator.scala b/src/main/scala/forge/Evaluator.scala
index 43b4f353..1bff722b 100644
--- a/src/main/scala/forge/Evaluator.scala
+++ b/src/main/scala/forge/Evaluator.scala
@@ -2,6 +2,7 @@ package forge
import java.nio.{file => jnio}
+import play.api.libs.json.Json
import sourcecode.Enclosing
import scala.collection.mutable
@@ -9,8 +10,7 @@ import scala.collection.mutable
class Evaluator(workspacePath: jnio.Path,
enclosingBase: DefCtx){
- val resultCache = mutable.Map.empty[String, (Int, Any)]
-
+ val resultCache = mutable.Map.empty[String, (Int, String)]
def evaluate(targets: Seq[Target[_]]): Evaluator.Results = {
jnio.Files.createDirectories(workspacePath)
@@ -20,26 +20,29 @@ class Evaluator(workspacePath: jnio.Path,
for (target <- sortedTargets){
val inputResults = target.inputs.map(results).toIndexedSeq
- val targetDestPath = {
- val enclosingStr = target.defCtx.label
- val targetDestPath = workspacePath.resolve(
- jnio.Paths.get(enclosingStr.stripSuffix(enclosingBase.label))
- )
- deleteRec(targetDestPath)
- targetDestPath
-
- }
+ val enclosingStr = target.defCtx.label
+ val targetDestPath = workspacePath.resolve(
+ jnio.Paths.get(enclosingStr.stripSuffix(enclosingBase.label))
+ )
+ deleteRec(targetDestPath)
val inputsHash = inputResults.hashCode
- resultCache.get(target.defCtx.label) match{
- case Some((hash, res)) if hash == inputsHash && !target.dirty =>
- results(target) = res
+ (target.dirty, resultCache.get(target.defCtx.label)) match{
+ case (Some(dirtyCheck), Some((hash, res)))
+ if hash == inputsHash && !dirtyCheck() =>
+ results(target) = target.formatter.reads(Json.parse(res)).get
+
case _ =>
evaluated.append(target)
- val res = target.evaluate(new Args(inputResults, targetDestPath))
+ if (target.defCtx.anonId.isDefined && target.dirty.isEmpty) {
+ val res = target.evaluate(new Args(inputResults, targetDestPath))
+ results(target) = res
+ }else{
+ val (res, serialized) = target.evaluateAndWrite(new Args(inputResults, targetDestPath))
+ resultCache(target.defCtx.label) = (inputsHash, serialized)
+ results(target) = res
+ }
- resultCache(target.defCtx.label) = (inputsHash, res)
- results(target) = res
}
}
diff --git a/src/main/scala/forge/Target.scala b/src/main/scala/forge/Target.scala
index d8536c60..93fa022e 100644
--- a/src/main/scala/forge/Target.scala
+++ b/src/main/scala/forge/Target.scala
@@ -2,7 +2,8 @@ package forge
import java.nio.{file => jnio}
-trait Target[T] extends Target.Ops[T]{
+import play.api.libs.json.{Format, Json}
+abstract class Target[T](implicit formatter: Format[T]) extends Target.Ops[T]{
/**
* Where in the Scala codebase was this target defined?
*/
@@ -19,62 +20,78 @@ trait Target[T] extends Target.Ops[T]{
/**
* Even if this target's inputs did not change, does it need to re-evaluate
- * anyway? e.g. if the files this target represents on disk changed
+ * anyway?
+ *
+ * - None means it never needs to re-evaluate unless its inputs do
+ * - Some(f) contains a function that returns whether or not it should re-evaluate,
+ * e.g. if the files this target represents on disk changed
*/
- def dirty: Boolean = false
+ val dirty: Option[() => Boolean] = Some(() => false)
}
object Target{
- trait Ops[T]{ this: Target[T] =>
+ abstract class Ops[T](implicit val formatter: Format[T]){ this: Target[T] =>
+ def evaluateAndWrite(args: Args): (T, String) = {
+ val res = evaluate(args)
+ val str = formatter.writes(res)
+ (res, Json.stringify(str))
+ }
val defCtx: DefCtx
- def map[V](f: T => V)(implicit defCtx: DefCtx) = {
+ def map[V: Format](f: T => V)(implicit defCtx: DefCtx) = {
new Target.Mapped(this, f, defCtx)
}
- def zip[V](other: Target[V])(implicit defCtx: DefCtx) = {
+ def zip[V: Format](other: Target[V])(implicit defCtx: DefCtx) = {
new Target.Zipped(this, other, defCtx)
}
- def ~[V, R](other: Target[V])
+ def ~[V: Format, R: Format](other: Target[V])
(implicit s: Implicits.Sequencer[T, V, R], defCtx: DefCtx): Target[R] = {
this.zip(other).map(s.apply _ tupled)
}
override def toString = this.getClass.getName + "@" + defCtx.label
}
- def test(inputs: Target[Int]*)(implicit defCtx: DefCtx) = new Test(inputs, defCtx)
+ def test(inputs: Target[Int]*)(implicit defCtx: DefCtx) = {
+ new Test(inputs, defCtx, pure = false)
+ }
+ def testPure(inputs: Target[Int]*)(implicit defCtx: DefCtx) = {
+ new Test(inputs, defCtx, pure = true)
+ }
/**
* A dummy target that takes any number of inputs, and whose output can be
* controlled externally, so you can construct arbitrary dataflow graphs and
* test how changes propagate.
*/
- class Test(val inputs: Seq[Target[Int]], val defCtx: DefCtx) extends Target[Int]{
+ class Test(val inputs: Seq[Target[Int]],
+ val defCtx: DefCtx,
+ val pure: Boolean) extends Target[Int]{
var counter = 0
var lastCounter = counter
def evaluate(args: Args) = {
lastCounter = counter
- counter + args.args.map(_.asInstanceOf[Int]).sum
+ counter + args.args.map(_.asInstanceOf[Int]).sum
}
- override def dirty = lastCounter != counter
+ override val dirty = if (pure) None else Some(() => lastCounter != counter)
}
- def traverse[T](source: Seq[Target[T]])(implicit defCtx: DefCtx) = {
+ def traverse[T: Format](source: Seq[Target[T]])(implicit defCtx: DefCtx) = {
new Traverse[T](source, defCtx)
}
- class Traverse[T](val inputs: Seq[Target[T]], val defCtx: DefCtx) extends Target[Seq[T]]{
+ class Traverse[T: Format](val inputs: Seq[Target[T]], val defCtx: DefCtx) extends Target[Seq[T]]{
def evaluate(args: Args) = {
for (i <- 0 until args.length)
yield args(i).asInstanceOf[T]
}
}
- class Mapped[T, V](source: Target[T], f: T => V,
- val defCtx: DefCtx) extends Target[V]{
+ class Mapped[T, V: Format](source: Target[T], f: T => V,
+ val defCtx: DefCtx) extends Target[V]{
def evaluate(args: Args) = f(args(0))
val inputs = List(source)
}
- class Zipped[T, V](source1: Target[T],
- source2: Target[V],
- val defCtx: DefCtx) extends Target[(T, V)]{
+ class Zipped[T: Format, V: Format](source1: Target[T],
+ source2: Target[V],
+ val defCtx: DefCtx) extends Target[(T, V)]{
def evaluate(args: Args) = (args(0), args(0))
val inputs = List(source1, source1)
}
diff --git a/src/main/scala/forge/package.scala b/src/main/scala/forge/package.scala
new file mode 100644
index 00000000..8145acd3
--- /dev/null
+++ b/src/main/scala/forge/package.scala
@@ -0,0 +1,46 @@
+import play.api.libs.json._
+import java.nio.{file => jnio}
+
+import ammonite.ops.Bytes
+
+package object forge {
+
+ implicit object pathFormat extends Format[jnio.Path]{
+ def reads(json: JsValue) = json match{
+ case JsString(v) => JsSuccess(jnio.Paths.get(v))
+ case _ => JsError("Paths must be a String")
+ }
+ def writes(o: jnio.Path) = JsString(o.toAbsolutePath.toString)
+ }
+
+ implicit object bytesFormat extends Format[Bytes]{
+ def reads(json: JsValue) = json match{
+ case JsString(v) => JsSuccess(
+ new Bytes(javax.xml.bind.DatatypeConverter.parseBase64Binary(v))
+ )
+ case _ => JsError("Bytes must be a String")
+ }
+ def writes(o: Bytes) = {
+ JsString(javax.xml.bind.DatatypeConverter.printBase64Binary(o.array))
+ }
+ }
+
+ implicit def EitherFormat[T: Format, V: Format] = new Format[Either[T, V]]{
+ def reads(json: JsValue) = json match{
+ case JsObject(struct) =>
+ (struct.get("type"), struct.get("value")) match{
+ case (Some(JsString("Left")), Some(v)) => implicitly[Reads[T]].reads(v).map(Left(_))
+ case (Some(JsString("Right")), Some(v)) => implicitly[Reads[V]].reads(v).map(Right(_))
+ case _ => JsError("Either object layout is unknown")
+ }
+ case _ => JsError("Either must be an Object")
+ }
+ def writes(o: Either[T, V]) = o match{
+ case Left(v) => Json.obj("type" -> "Left", "value" -> implicitly[Writes[T]].writes(v))
+ case Right(v) => Json.obj("type" -> "Right", "value" -> implicitly[Writes[V]].writes(v))
+ }
+ }
+
+ implicit val crFormat = Json.format[ammonite.ops.CommandResult]
+ implicit val tsFormat = Json.format[Target.Subprocess.Result]
+}