diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index 843898188..f714f5d53 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -102,6 +102,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths { override def scratch = true override def consoleClasspath = testClasspath + override def compileOptions = super.compileOptions ++ compileOptions("-Xelide-below", "3000") } trait Licensed extends BasicScalaProject { diff --git a/tasks/CompletionService.scala b/tasks/CompletionService.scala new file mode 100644 index 000000000..9dc924df4 --- /dev/null +++ b/tasks/CompletionService.scala @@ -0,0 +1,44 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +trait CompletionService[A, R] +{ + def submit(node: A, work: () => R): () => R + def take(): R +} + +import java.util.concurrent.{Callable, CompletionService => JCompletionService, Executor, Executors, ExecutorCompletionService} + +object CompletionService +{ + def apply[A, T](poolSize: Int): (CompletionService[A,T], () => Unit) = + { + val pool = Executors.newFixedThreadPool(2) + (apply[A,T]( pool ), () => pool.shutdownNow() ) + } + def apply[A, T](x: Executor): CompletionService[A,T] = + apply(new ExecutorCompletionService[T](x)) + def apply[A, T](completion: JCompletionService[T]): CompletionService[A,T] = + new CompletionService[A, T] { + def submit(node: A, work: () => T) = { + val future = completion.submit { new Callable[T] { def call = work() } } + () => future.get() + } + def take() = completion.take().get() + } + + def manage[A, T](service: CompletionService[A,T])(setup: A => Unit, cleanup: A => Unit): CompletionService[A,T] = + wrap(service) { (node, work) => () => + setup(node) + try { work() } + finally { cleanup(node) } + } + def wrap[A, T](service: CompletionService[A,T])(w: (A, () => T) => (() => T)): CompletionService[A,T] = + new CompletionService[A,T] + { + def submit(node: A, work: () => T) = service.submit(node, w(node, work)) + def take() = service.take() + } +} \ No newline at end of file diff --git a/tasks/Execute.scala b/tasks/Execute.scala new file mode 100644 index 000000000..5fb362a10 --- /dev/null +++ b/tasks/Execute.scala @@ -0,0 +1,318 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +// TODO: Incomplete needs to be parameterized with A[_] and have val node + +import Node._ +import Types._ +import Execute._ + +import scala.annotation.tailrec +import scala.collection.{mutable, JavaConversions} +import mutable.Map + +object Execute +{ + trait Part1of2K[M[_[_], _], A[_]] { type Apply[T] = M[A, T] } + type NodeT[A[_]] = Part1of2K[Node, A] + + def idMap[A,B]: Map[A, B] = JavaConversions.asMap(new java.util.IdentityHashMap[A,B]) + def pMap[A[_], B[_]]: PMap[A,B] = new DelegatingPMap[A, B](idMap) + private[sbt] def completed(p: => Unit): Completed = new Completed { + def process() { p } + } +} +sealed trait Completed { + def process(): Unit +} + + +final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: A ~> NodeT[A]#Apply ) +{ + type Strategy = CompletionService[A[_], Completed] + + private[this] val forward = idMap[A[_], IDSet[A[_]] ] + private[this] val reverse = idMap[A[_], Iterable[A[_]] ] + private[this] val callers = pMap[A, Compose[IDSet,A]#Apply ] + private[this] val state = idMap[A[_], State] + private[this] val viewCache = pMap[A, NodeT[A]#Apply] + private[this] val results = pMap[A, Result] + + private[this] type State = State.Value + private[this] object State extends Enumeration { + val Pending, Running, Calling, Done = Value + } + import State.{Pending, Running, Calling, Done} + + def dump: String = "State: " + state.toString + "\n\nResults: " + results + "\n\nCalls: " + callers + "\n\n" + + def run[T](root: A[T])(implicit strategy: Strategy) = + { + assert(state.isEmpty, "Execute already running/ran.") + + addNew(root) + processAll() + assert( results contains root, "No result for root node." ) + results(root) + } + + def processAll()(implicit strategy: Strategy) + { + @tailrec def next() + { + pre { + assert( !reverse.isEmpty, "Nothing to process." ) + assert( state.values.exists( _ == Running ), "Nothing running") + } + + (strategy.take()).process() + if( !reverse.isEmpty ) next() + } + next() + + post { + assert( reverse.isEmpty, "Did not process everything." ) + assert( complete, "Not all state was Done." ) + } + } + + + def call[T](node: A[T], target: A[T])(implicit strategy: Strategy) + { + if(checkCycles) cycleCheck(node, target) + pre { + assert( running(node) ) + readyInv( node ) + } + + state(node) = Calling + addChecked(target) + addCaller(node, target) + + post { + assert( calling(node) ) + assert( callers(target) contains node ) + readyInv( node ) + } + } + + def retire[T](node: A[T], result: Result[T])(implicit strategy: Strategy) + { + pre { + assert( running(node) | calling(node) ) + readyInv( node ) + } + + results(node) = result + state(node) = Done + remove( reverse, node ) foreach { dep => notifyDone(node, dep) } + callers.remove( node ).flatten.foreach { c => retire(c, result) } + + post { + assert( done(node) ) + assert( results(node) == result ) + readyInv( node ) + assert( ! (reverse contains node) ) + assert( ! (callers contains node) ) + } + } + + def notifyDone( node: A[_], dependent: A[_] )(implicit strategy: Strategy) + { + val f = forward(dependent) + f -= node + if( f.isEmpty ) { + remove(forward, dependent) + ready( dependent ) + } + } + /** Ensures the given node has been added to the system. + * Once added, a node is pending until its inputs and dependencies have completed. + * Its computation is then evaluated and made available for nodes that have it as an input.*/ + def addChecked[T](node: A[T])(implicit strategy: Strategy) + { + if( !added(node)) addNew(node) + + post { addedInv( node ) } + } + /** Adds a node that has not yet been registered with the system. + * If all of the node's dependencies have finished, the node's computation scheduled to run. + * The node's dependencies will be added (transitively) if they are not already registered. + * */ + def addNew[T](node: A[T])(implicit strategy: Strategy) + { + pre { newPre(node) } + + val v = register( node ) + val deps = dependencies(v) + val active = deps filter notDone + + if( active.isEmpty) + ready( node ) + else + { + forward(node) = IDSet(active) + val newD = active filter isNew + newD foreach { x => addNew(x) } + active foreach { addReverse(_, node) } + } + + post { + addedInv( node ) + assert( running(node) ^ pending(node) ) + if( running(node) ) runningInv( node ) + if( pending(node) ) pendingInv( node ) + } + } + /** Called when a pending 'node' becomes runnable. All of its dependencies must be done. This schedules the node's computation with 'strategy'.*/ + def ready[T]( node: A[T] )(implicit strategy: Strategy) + { + pre { + assert( pending(node) ) + readyInv( node ) + assert( reverse contains node ) + } + + state(node) = Running + submit(node) + + post { + readyInv( node ) + assert( reverse contains node ) + assert( running( node ) ) + } + } + /** Enters the given node into the system. */ + def register[T](node: A[T]): Node[A, T] = + { + state(node) = Pending + reverse(node) = Seq() + viewCache.getOrUpdate(node, view(node)) + } + def incomplete(in: A[_]): Option[(A[_], Incomplete)] = + results(in) match { + case Value(v) => None + case Inc(inc) => Some( (in, inc) ) + } + /** Send the work for this node to the provided Strategy. */ + def submit[T]( node: A[T] )(implicit strategy: Strategy) + { + val v = view(node) + val rs: v.Inputs#Map[Result] = v.inputs.map(results) + val ud = v.unitDependencies.flatMap(incomplete) + strategy.submit( node, () => work(node, v.work(rs, ud)) ) + } + /** Evaluates the computation 'f' for 'node'. + * This returns a Completed instance, which contains the post-processing to perform after the result is retrieved from the Strategy.*/ + def work[T](node: A[T], f: => Either[A[T], T])(implicit strategy: Strategy): Completed = + { + val result = + try { Right(f) } + catch { + case i: Incomplete => Left(i) + case e => Left( Incomplete(Incomplete.Error, directCause = Some(e)) ) + } + completed { + result match { + case Left(i) => retire(node, Inc(i)) + case Right(Right(v)) => retire(node, Value(v)) + case Right(Left(target)) => call(node, target) + } + } + } + + def remove[K, V](map: Map[K, V], k: K): V = map.remove(k).getOrElse(error("Key '" + k + "' not in map :\n" + map)) + + def addReverse(node: A[_], dependent: A[_]): Unit = reverse(node) ++= Seq(dependent) + def addCaller[T](caller: A[T], target: A[T]): Unit = callers.getOrUpdate(target, IDSet.create[A[T]]) += caller + + def dependencies(node: A[_]): Iterable[A[_]] = dependencies(view(node)) + def dependencies(v: Node[A, _]): Iterable[A[_]] = v.unitDependencies ++ v.inputs.toList + + // Contracts + + def addedInv(node: A[_]): Unit = topologicalSort(node) foreach addedCheck + def addedCheck(node: A[_]) + { + assert( added(node), "Not added: " + node ) + assert( viewCache contains node, "Not in view cache: " + node ) + dependencyCheck( node ) + } + def dependencyCheck( node: A[_] ) + { + dependencies( node ) foreach { dep => + def onOpt[T](o: Option[T])(f: T => Boolean) = o match { case None => false; case Some(x) => f(x) } + def checkForward = onOpt( forward.get(node) ) { _ contains dep } + def checkReverse = onOpt( reverse.get(dep) ){ _.toSet contains node } + assert( done(dep) ^ ( checkForward && checkReverse ) ) + } + } + def pendingInv(node: A[_]) + { + assert( atState(node, Pending) ) + assert( dependencies( node ) exists notDone ) + } + def runningInv( node: A[_] ) + { + assert( dependencies( node ) forall done ) + assert( ! (forward contains node) ) + } + def newPre(node: A[_]) + { + isNew(node) + assert(!(reverse contains node)) + assert(!(forward contains node)) + assert(!(callers contains node)) + assert(!(viewCache contains node)) + assert(!(results contains node)) + } + + def topologicalSort(node: A[_]): Seq[A[_]] = + { + val seen = IDSet.create[A[_]] + def visit(n: A[_]): List[A[_]] = + (seen process n)( List[A[_]]() ) { + node :: (List[A[_]]() /: dependencies(n) ) { (ss, dep) => visit(dep) ::: ss} + } + + visit(node).reverse + } + + def readyInv(node: A[_]) + { + assert( dependencies(node) forall done ) + assert( ! ( forward contains node ) ) + } + + // cyclic reference checking + + def cycleCheck[T](node: A[T], target: A[T]) + { + if(node eq target) cyclic(node, target, "Cannot call self") + val all = IDSet.create[A[T]] + def allCallers(n: A[T]): Unit = (all process n)(()) { callers.get(n).flatten.foreach(allCallers) } + allCallers(node) + if(all contains target) cyclic(node, target, "Cyclic reference") + } + def cyclic[T](caller: A[T], target: A[T], msg: String) = throw new Incomplete(message = Some(msg), directCause = Some( new CyclicException(caller, target, msg) ) ) + final class CyclicException[T](val caller: A[T], val target: A[T], msg: String) extends Exception(msg) + + // state testing + + def pending(d: A[_]) = atState(d, Pending) + def running(d: A[_]) = atState(d, Running) + def calling(d: A[_]) = atState(d, Calling) + def done(d: A[_]) = atState(d, Done) + def notDone(d: A[_]) = !done(d) + def atState(d: A[_], s: State) = state.get(d) == Some(s) + def isNew(d: A[_]) = !added(d) + def added(d: A[_]) = state contains d + def complete = state.values.forall(_ == Done) + + import scala.annotation.elidable + import elidable._ + @elidable(ASSERTION) def pre(f: => Unit) = f + @elidable(ASSERTION) def post(f: => Unit) = f +} diff --git a/tasks/Incomplete.scala b/tasks/Incomplete.scala index 9ac78e556..06b551892 100644 --- a/tasks/Incomplete.scala +++ b/tasks/Incomplete.scala @@ -3,4 +3,10 @@ */ package sbt -trait Incomplete \ No newline at end of file +import Incomplete.{Error, Value => IValue} +final case class Incomplete(tpe: IValue = Error, message: Option[String] = None, causes: Seq[Incomplete] = Nil, directCause: Option[Throwable] = None) + extends Exception(message.orNull, directCause.orNull) + +object Incomplete extends Enumeration { + val Skipped, Error = Value +} \ No newline at end of file diff --git a/tasks/Node.scala b/tasks/Node.scala index 90a5cd6e6..b57c5dc00 100644 --- a/tasks/Node.scala +++ b/tasks/Node.scala @@ -9,27 +9,27 @@ import Types._ trait Node[A[_], T] { type Inputs <: MList[A] - type Results <: Inputs#Map[Result] + type Results = Inputs#Map[Result] val inputs: Inputs def unitDependencies: Iterable[A[_]] - def work(results: Results, units: Iterable[Incomplete]): Either[A[T], T] + def work(results: Results, units: UnitResults[A]): Either[A[T], T] } object Node { - def pure[T](f: => T): PureNode[T]= map[Id, T, MNil](MNil, Nil)((_,_) => f) + /*def pure[T](f: () => T): PureNode[T]= map[Id, T, MNil](MNil, Nil)((_,_) => f() ) - def map[A[_], T, Inputs0 <: MList[A]](inputs0: Inputs0, deps0: Iterable[A[_]])(work0: (Inputs0#Map[Result], Iterable[Incomplete]) => T): - Node[A,T] { type Inputs = Inputs0; type Results = Inputs0#Map[Result] } = + def map[A[_], T, Inputs0 <: MList[A]](inputs0: Inputs0, deps0: Iterable[A[_]])(work0: (Inputs0#Map[Result], UnitResults[A]) => T): + Node[A,T] { type Inputs = Inputs0 } = new Node[A,T] { type Inputs = Inputs0 - type Results = Inputs0#Map[Result] val inputs = inputs0 def unitDependencies = deps0 - def work(results: Results, units: Iterable[Incomplete]) = Right(work0(results, units)) + def work(results: Results, units: UnitResults[A]) = Right(work0(results, units)) } - type PureNode[T] = Node[Id, T] { type Inputs = MNil; type Results = MNil } + type PureNode[T] = Node[Id, T] { type Inputs = MNil; type Results = MNil }*/ + type UnitResults[A[_]] = Iterable[(A[_], Incomplete)] } diff --git a/tasks/Result.scala b/tasks/Result.scala index e4fad1e14..326b9e66a 100644 --- a/tasks/Result.scala +++ b/tasks/Result.scala @@ -3,7 +3,23 @@ */ package sbt -// used instead of Either[Throwable, T] for type inference +// used instead of Either[Incomplete, T] for type inference + +/** Result of completely evaluating a task.*/ sealed trait Result[+T] -final case class Exc(cause: Throwable) extends Result[Nothing] -final case class Value[+T](value: T) extends Result[T] \ No newline at end of file +/** Indicates the task did not complete normally and so it does not have a value.*/ +final case class Inc(cause: Incomplete) extends Result[Nothing] +/** Indicates the task completed normally and produced the given `value`.*/ +final case class Value[+T](value: T) extends Result[T] + +object Result +{ + type Id[X] = X + val tryValue = new (Result ~> Id) { + def apply[T](r: Result[T]): T = + r match { + case Value(v) => v + case Inc(i) => throw i + } + } +} \ No newline at end of file diff --git a/tasks/Test.scala b/tasks/Test.scala new file mode 100644 index 000000000..8929685ae --- /dev/null +++ b/tasks/Test.scala @@ -0,0 +1,114 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +import Types._ +import Node._ +import Task._ +import Execute._ + +sealed trait Task[+T] +sealed case class Pure[+T](eval: () => T) extends Task[T] +sealed case class Mapped[+T, In <: MList[Task]](in: In, f: In#Map[Result] => T) extends Task[T] +sealed case class MapAll[+T, In <: MList[Task]](in: In, f: In#Map[Result]#Raw => T) extends Task[T] +sealed case class FlatMapAll[+T, In <: MList[Task]](in: In, f: In#Map[Result]#Raw => Task[T]) extends Task[T] +sealed case class FlatMapped[+T, In <: MList[Task]](in: In, f: In#Map[Result] => Task[T]) extends Task[T] + +object Task +{ + implicit val taskToNode = new (Task ~> NodeT[Task]#Apply) { + def apply[T](t: Task[T]): Node[Task, T] = t match { + case Pure(eval) => toNode[T, MNil](MNil, _ => Right(eval()) ) + case Mapped(in, f) => toNode[T, in.type](in, right ∙ f ) + case MapAll(in, f) => toNode[T, in.type](in, right ∙ (f compose all) ) + case FlatMapAll(in, f) => toNode[T, in.type](in, left ∙ (f compose all) ) + case FlatMapped(in, f) => toNode[T, in.type](in, left ∙ f ) + } + } + def toNode[T, In <: MList[Task]](in: In, f: In#Map[Result] => Either[Task[T], T]): Node[Task, T] = new Node[Task, T] { + type Inputs = In + val inputs = in + def unitDependencies = Nil + def work(results: Results, units: UnitResults[Task]) = f(results) + } + + def pure[T](name: String)(f: => T): Pure[T] = new Pure(f _) { override def toString = name } + def mapped[T, In0 <: MList[Task]](name: String)(in0: In0)(f0: In0#Map[Result] => T): Mapped[T, In0] = new Mapped(in0, f0) { override def toString = name } + def flat[T, In0 <: MList[Task]](name: String)(in0: In0)(f0: In0#Map[Result] => Task[T]): FlatMapped[T, In0] = new FlatMapped(in0, f0) { override def toString = name } + def mapAll[T, In0 <: MList[Task]](name: String)(in0: In0)(f0: In0#Map[Result]#Raw => T): MapAll[T, In0] = new MapAll(in0, f0) { override def toString = name } + def flatAll[T, In0 <: MList[Task]](name: String)(in0: In0)(f0: In0#Map[Result]#Raw => Task[T]): FlatMapAll[T, In0] = new FlatMapAll(in0, f0) { override def toString = name } + + def all[In <: MList[Result]]: In => In#Raw = in => + { + val incs = in.toList.collect { case Inc(i) => i } + if(incs.isEmpty) in.down(Result.tryValue) else throw Incomplete(causes = incs) + } +} +object Test +{ + val a = pure("a")(3) + val b = pure[Boolean]("b")(error("test")) + val b2 = pure("b2")(true) + val c = pure("x")("asdf") + val i3 = a :^: b :^: c :^: MNil + val i32 = a :^: b2 :^: c :^: MNil + + val fh= (_: Int :+: Boolean :+: String :+: HNil) match + { case aa :+: bb :+: cc :+: HNil => aa + " " + bb + " " + cc } + val h1 = mapAll("h1")(i3)(fh) + val h2 = mapAll("h2")(i32)(fh) + + val f: i3.Map[Result] => Any = { + case Value(aa) :^: Value(bb) :^: Value(cc) :^: MNil => aa + " " + bb + " " + cc + case x => + val cs = x.toList.collect { case Inc(x) => x } // workaround for double definition bug + throw Incomplete(causes = cs) + } + val d2 = mapped("d2")(i32)(f) + val f2: i3.Map[Result] => Task[Any] = { + case Value(aa) :^: Value(bb) :^: Value(cc) :^: MNil => new Pure(() => aa + " " + bb + " " + cc) + case x => d3 + } + lazy val d = flat("d")(i3)(f2) + val f3: i3.Map[Result] => Task[Any] = { + case Value(aa) :^: Value(bb) :^: Value(cc) :^: MNil => new Pure(() => aa + " " + bb + " " + cc) + case x => d2 + } + lazy val d3= flat("d3")(i3)(f3) + + def d4(i: Int): Task[Int] = flat("d4")(MNil){ _ => val x = math.random; if(x < 0.01) pure(x.toString)(i); else d4(i+1) } + + lazy val pureEval = + new (Pure ~> Result) { + def apply[T](p: Pure[T]): Result[T] = + try { Value(p.eval()) } + catch { case e: Exception => throw Incomplete(Incomplete.Error, directCause = Some(e)) } + } + + lazy val resultA = d.f( d.in.map(pureEval) ) + + def execute[T](root: Task[T]) = { + val (service, shutdown) = CompletionService[Task[_], Completed](2) + implicit val wrapped = CompletionService.manage(service)(x => println("Starting: " + x), x => println("Finished: " + x) ) + + val x = new Execute[Task](true)(taskToNode) + try { x.run(root) } finally { shutdown(); println(x.dump) } + } + + def go() + { + def run[T](root: Task[T]) = + println("Result : " + execute(root)) + + run(a) + run(b) + run(b2) + run(c) + run(d) + run(d2) + run( d4(0) ) + run(h1) + run(h2) + } +} \ No newline at end of file diff --git a/util/collection/IDSet.scala b/util/collection/IDSet.scala new file mode 100644 index 000000000..29ecf469d --- /dev/null +++ b/util/collection/IDSet.scala @@ -0,0 +1,45 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +/** A mutable set interface that uses object identity to test for set membership.*/ +trait IDSet[T] +{ + def apply(t: T): Boolean + def contains(t: T): Boolean + def += (t: T): Unit + def ++=(t: Iterable[T]): Unit + def -= (t: T): Boolean + def all: collection.Iterable[T] + def isEmpty: Boolean + def foreach(f: T => Unit): Unit + def process[S](t: T)(ifSeen: S)(ifNew: => S): S +} + +object IDSet +{ + implicit def toTraversable[T]: IDSet[T] => Traversable[T] = _.all + def apply[T](values: T*): IDSet[T] = apply(values) + def apply[T](values: Iterable[T]): IDSet[T] = + { + val s = create[T] + s ++= values + s + } + def create[T]: IDSet[T] = new IDSet[T] { + private[this] val backing = new java.util.IdentityHashMap[T, AnyRef] + private[this] val Dummy: AnyRef = "" + + def apply(t: T) = contains(t) + def contains(t: T) = backing.containsKey(t) + def foreach(f: T => Unit) = all foreach f + def += (t: T) = backing.put(t, Dummy) + def ++=(t: Iterable[T]) = t foreach += + def -= (t:T) = if(backing.remove(t) eq null) false else true + def all = collection.JavaConversions.asIterable(backing.keySet) + def isEmpty = backing.isEmpty + def process[S](t: T)(ifSeen: S)(ifNew: => S) = if(contains(t)) ifSeen else { this += t ; ifNew } + override def toString = backing.toString + } +} \ No newline at end of file diff --git a/util/collection/MList.scala b/util/collection/MList.scala index 58d2724c3..b350858c3 100644 --- a/util/collection/MList.scala +++ b/util/collection/MList.scala @@ -7,6 +7,11 @@ import Types._ sealed trait MList[+M[_]] { + // For converting MList[Id] to an HList + // This is useful because type inference doesn't work well with Id + type Raw <: HList + def down(implicit ev: M ~> Id): Raw + type Map[N[_]] <: MList[N] def map[N[_]](f: M ~> N): Map[N] @@ -14,6 +19,9 @@ sealed trait MList[+M[_]] } final case class MCons[H, +T <: MList[M], +M[_]](head: M[H], tail: T) extends MList[M] { + type Raw = H :+: tail.Raw + def down(implicit f: M ~> Id): Raw = HCons(f(head), tail.down(f)) + type Map[N[_]] = MCons[H, tail.Map[N], N] def map[N[_]](f: M ~> N) = MCons( f(head), tail.map(f) ) @@ -23,6 +31,9 @@ final case class MCons[H, +T <: MList[M], +M[_]](head: M[H], tail: T) extends ML } sealed class MNil extends MList[Nothing] { + type Raw = HNil + def down(implicit f: Nothing ~> Id) = HNil + type Map[N[_]] = MNil def map[N[_]](f: Nothing ~> N) = MNil @@ -30,4 +41,4 @@ sealed class MNil extends MList[Nothing] def toList = Nil } -object MNil extends MNil +object MNil extends MNil \ No newline at end of file diff --git a/util/collection/PMap.scala b/util/collection/PMap.scala new file mode 100644 index 000000000..bc5e092af --- /dev/null +++ b/util/collection/PMap.scala @@ -0,0 +1,43 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +import Types._ + +trait PMap[K[_], V[_]] extends (K ~> V) +{ + def apply[T](k: K[T]): V[T] + def get[T](k: K[T]): Option[V[T]] + def update[T](k: K[T], v: V[T]): Unit + def contains[T](k: K[T]): Boolean + def remove[T](k: K[T]): Option[V[T]] + def getOrUpdate[T](k: K[T], make: => V[T]): V[T] +} +object PMap +{ + implicit def toFunction[K[_], V[_]](map: PMap[K,V]): K[_] => V[_] = k => map(k) +} + +abstract class AbstractPMap[K[_], V[_]] extends PMap[K,V] +{ + def apply[T](k: K[T]): V[T] = get(k).get + def contains[T](k: K[T]): Boolean = get(k).isDefined +} + +import collection.mutable.Map + +/** Only suitable for K that is invariant in its parameter. +* Option and List keys are not, for example, because None <:< Option[String] and None <: Option[Int].*/ +class DelegatingPMap[K[_], V[_]](backing: Map[K[_], V[_]]) extends AbstractPMap[K,V] +{ + def get[T](k: K[T]): Option[V[T]] = cast[T]( backing.get(k) ) + def update[T](k: K[T], v: V[T]) { backing(k) = v } + def remove[T](k: K[T]) = cast( backing.remove(k) ) + def getOrUpdate[T](k: K[T], make: => V[T]) = cast[T]( backing.getOrElseUpdate(k, make) ) + + private[this] def cast[T](v: V[_]): V[T] = v.asInstanceOf[V[T]] + private[this] def cast[T](o: Option[V[_]]): Option[V[T]] = o map cast[T] + + override def toString = backing.toString +} \ No newline at end of file diff --git a/util/collection/Param.scala b/util/collection/Param.scala new file mode 100644 index 000000000..3271465d9 --- /dev/null +++ b/util/collection/Param.scala @@ -0,0 +1,31 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +import Types._ + +// Used to emulate ~> literals +trait Param[A[_], B[_]] +{ + type T + def in: A[T] + def ret(out: B[T]) + def ret: B[T] +} + +object Param +{ + implicit def pToT[A[_], B[_]](p: Param[A,B] => Unit): A~>B = new (A ~> B) { + def apply[s](a: A[s]): B[s] = { + val v: Param[A,B] { type T = s} = new Param[A,B] { type T = s + def in = a + private var r: B[T] = _ + def ret(b: B[T]) {r = b} + def ret: B[T] = r + } + p(v) + v.ret + } + } +} \ No newline at end of file diff --git a/util/collection/Rewrite.scala b/util/collection/Rewrite.scala new file mode 100644 index 000000000..40b40add4 --- /dev/null +++ b/util/collection/Rewrite.scala @@ -0,0 +1,42 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +import Types._ + +trait Rewrite[A[_]] +{ + def apply[T](node: A[T], rewrite: Rewrite[A]): A[T] +} +object Rewrite +{ + def Id[A[_]]: Rewrite[A] = new Rewrite[A] { def apply[T](node: A[T], rewrite: Rewrite[A]) = node } + + implicit def specificF[T](f: T => T): Rewrite[Const[T]#Apply] = new Rewrite[Const[T]#Apply] { + def apply[S](node:T, rewrite: Rewrite[Const[T]#Apply]): T = f(node) + } + implicit def pToRewrite[A[_]](p: Param[A,A] => Unit): Rewrite[A] = toRewrite(Param.pToT(p)) + implicit def toRewrite[A[_]](f: A ~> A): Rewrite[A] = new Rewrite[A] { + def apply[T](node: A[T], rewrite:Rewrite[A]) = f(node) + } + def compose[A[_]](a: Rewrite[A], b: Rewrite[A]): Rewrite[A] = + new Rewrite[A] { + def apply[T](node: A[T], rewrite: Rewrite[A]) = + a(b(node, rewrite), rewrite) + } + implicit def rewriteOps[A[_]](outer: Rewrite[A]): RewriteOps[A] = + new RewriteOps[A] { + def ∙(g: A ~> A): Rewrite[A] = compose(outer, g) + def andThen(g: A ~> A): Rewrite[A] = compose(g, outer) + def ∙(g: Rewrite[A]): Rewrite[A] = compose(outer, g) + def andThen(g: Rewrite[A]): Rewrite[A] = compose(g, outer) + } + def apply[A[_], T](value: A[T])(implicit rewrite: Rewrite[A]): A[T] = rewrite(value, rewrite) +} +trait RewriteOps[A[_]] +{ + def andThen(g: A ~> A): Rewrite[A] + def ∙(g: A ~> A): Rewrite[A] + def ∙(g: Rewrite[A]): Rewrite[A] +} \ No newline at end of file diff --git a/util/collection/TypeFunctions.scala b/util/collection/TypeFunctions.scala index 95fb8ae9e..00a6fc772 100644 --- a/util/collection/TypeFunctions.scala +++ b/util/collection/TypeFunctions.scala @@ -7,14 +7,26 @@ trait TypeFunctions { type Id[X] = X trait Const[A] { type Apply[B] = A } - trait P1of2[M[_,_], A] { type Apply[B] = M[A,B] } - trait Down[M[_]] { type Apply[B] = Id[M[B]] } + trait Compose[A[_], B[_]] { type Apply[T] = A[B[T]] } + trait P1of2[M[_,_], A] { type Apply[B] = M[A,B]; type Flip[B] = M[B, A] } - trait ~>[-A[_], +B[_]] - { - def apply[T](a: A[T]): B[T] - } - def Id: Id ~> Id = - new (Id ~> Id) { def apply[T](a: T): T = a } + final val left = new (Id ~> P1of2[Left, Nothing]#Flip) { def apply[T](t: T) = Left(t) } + final val right = new (Id ~> P1of2[Right, Nothing]#Apply) { def apply[T](t: T) = Right(t) } + final val some = new (Id ~> Some) { def apply[T](t: T) = Some(t) } } -object TypeFunctions extends TypeFunctions \ No newline at end of file +object TypeFunctions extends TypeFunctions + +trait ~>[-A[_], +B[_]] +{ outer => + def apply[T](a: A[T]): B[T] + // directly on ~> because of type inference limitations + final def ∙[C[_]](g: C ~> A): C ~> B = new (C ~> B) { def apply[T](c: C[T]) = outer.apply(g(c)) } + final def ∙[C,D](g: C => D)(implicit ev: D <:< A[D]): C => B[D] = i => apply(ev(g(i)) ) + final def fn[T] = (t: A[T]) => apply[T](t) +} +object ~> +{ + import TypeFunctions._ + val Id: Id ~> Id = new (Id ~> Id) { def apply[T](a: T): T = a } + implicit def tcIdEquals: (Id ~> Id) = Id +} \ No newline at end of file diff --git a/util/collection/Types.scala b/util/collection/Types.scala index de468216d..de6cf5aec 100644 --- a/util/collection/Types.scala +++ b/util/collection/Types.scala @@ -8,4 +8,4 @@ object Types extends TypeFunctions val :^: = MCons val :+: = HCons type :+:[H, T <: HList] = HCons[H,T] -} \ No newline at end of file +} diff --git a/util/collection/src/test/scala/LiteralTest.scala b/util/collection/src/test/scala/LiteralTest.scala new file mode 100644 index 000000000..76fffe80a --- /dev/null +++ b/util/collection/src/test/scala/LiteralTest.scala @@ -0,0 +1,17 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +import Types._ + +// compilation test +object LiteralTest { + def x[A[_],B[_]](f: A ~> B) = f + + import Param._ + val f = x { (p: Param[Option,List]) => p.ret( p.in.toList ) } + + val a: List[Int] = f( Some(3) ) + val b: List[String] = f( Some("aa") ) +} \ No newline at end of file diff --git a/util/collection/src/test/scala/MListTest.scala b/util/collection/src/test/scala/MListTest.scala new file mode 100644 index 000000000..ffb86b18e --- /dev/null +++ b/util/collection/src/test/scala/MListTest.scala @@ -0,0 +1,19 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +import Types._ + +object MTest { + val f = new (Option ~> List) { def apply[T](o: Option[T]): List[T] = o.toList } + + val x = Some(3) :^: Some("asdf") :^: MNil + val y = x map f + val m1a = y match { case List(3) :^: List("asdf") :^: MNil => println("true") } + val m1b = (List(3) :^: MNil) match { case yy :^: MNil => println("true") } + + val head = new (List ~> Id) { def apply[T](xs: List[T]): T = xs.head } + val z = y.map[Id](head).down + val m2 = z match { case 3 :+: "asdf" :+: HNil => println("true") } +} diff --git a/util/collection/src/test/scala/PMapTest.scala b/util/collection/src/test/scala/PMapTest.scala new file mode 100644 index 000000000..1ea66daaa --- /dev/null +++ b/util/collection/src/test/scala/PMapTest.scala @@ -0,0 +1,18 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +import Types._ + +// compilation test +object PMapTest +{ + val mp = new DelegatingPMap[Some, Id](new collection.mutable.HashMap) + mp(Some("asdf")) = "a" + mp(Some(3)) = 9 + val x = Some(3) :^: Some("asdf") :^: MNil + val y = x.map[Id](mp) + val z = y.down + z match { case 9 :+: "a" :+: HNil => println("true") } +} \ No newline at end of file diff --git a/util/collection/src/test/scala/RewriteTest.scala b/util/collection/src/test/scala/RewriteTest.scala new file mode 100644 index 000000000..c2ca1b237 --- /dev/null +++ b/util/collection/src/test/scala/RewriteTest.scala @@ -0,0 +1,50 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +import Types._ + +object RewriteTest +{ + // dist and add0 show the awkwardness when not just manipulating superstructure: + // would have to constrain the parameters to Term to be instances of Zero/Eq somehow + val dist: Rewrite[Term] = (p: Param[Term, Term]) => p.ret( p.in match { + case Add(Mult(a,b),Mult(c,d)) if a == c=> Mult(a, Add(b,d)) + case x => x + }) + val add0: Rewrite[Term] = (p: Param[Term, Term]) => p.ret( p.in match { + case Add(V(0), y) => y + case Add(x, V(0)) => x + case x => x + }) + val rewriteBU= new Rewrite[Term] { + def apply[T](node: Term[T], rewrite: Rewrite[Term]) = { + def r[T](node: Term[T]) = rewrite(node, rewrite) + node match { + case Add(x, y) => Add(r(x), r(y)) + case Mult(x, y) => Mult(r(x), r(y)) + case x => x + } + } + } + + val d2 = dist ∙ add0 ∙ rewriteBU + + implicit def toV(t: Int): V[Int] = V(t) + implicit def toVar(s: String): Var[Int] = Var[Int](s) + + val t1: Term[Int] = Add(Mult(3,4), Mult(4, 5)) + val t2: Term[Int] = Add(Mult(4,4), Mult(4, 5)) + val t3: Term[Int] = Add(Mult(Add("x", 0),4), Mult("x", 5)) + + println( Rewrite(t1)(d2) ) + println( Rewrite(t2)(d2) ) + println( Rewrite(t3)(d2) ) +} + +sealed trait Term[T] +final case class Add[T](a: Term[T], b: Term[T]) extends Term[T] +final case class Mult[T](a: Term[T], b: Term[T]) extends Term[T] +final case class V[T](v: T) extends Term[T] +final case class Var[T](name: String) extends Term[T]