diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2017-11-05 07:08:02 -0800 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2017-11-05 07:08:02 -0800 |
commit | 9e1ba56eb49fb62d20fc4ede0627194304a854e8 (patch) | |
tree | b5c9acd55034649f0ac480a29d7bdc2dce8dadb9 /core/src | |
parent | 2fb5b569d30ac09cfda7ce6450c54d0fff6f32ae (diff) | |
download | mill-9e1ba56eb49fb62d20fc4ede0627194304a854e8.tar.gz mill-9e1ba56eb49fb62d20fc4ede0627194304a854e8.tar.bz2 mill-9e1ba56eb49fb62d20fc4ede0627194304a854e8.zip |
Split out `ApplicativeMacros` from `Target`
Diffstat (limited to 'core/src')
-rw-r--r-- | core/src/main/scala/forge/define/ApplicativeMacros.scala | 105 | ||||
-rw-r--r-- | core/src/main/scala/forge/define/Target.scala | 108 | ||||
-rw-r--r-- | core/src/main/scala/forge/define/ZipTarget.scala | 40 | ||||
-rw-r--r-- | core/src/main/scala/forge/package.scala | 7 | ||||
-rw-r--r-- | core/src/test/scala/forge/ApplicativeMacrosTests.scala | 9 | ||||
-rw-r--r-- | core/src/test/scala/forge/MacroErrorTests.scala | 3 |
6 files changed, 149 insertions, 123 deletions
diff --git a/core/src/main/scala/forge/define/ApplicativeMacros.scala b/core/src/main/scala/forge/define/ApplicativeMacros.scala new file mode 100644 index 00000000..1461d009 --- /dev/null +++ b/core/src/main/scala/forge/define/ApplicativeMacros.scala @@ -0,0 +1,105 @@ +package forge.define + +import forge.util.Args + +import scala.collection.mutable +import scala.reflect.macros.blackbox.Context + +object ApplicativeMacros { + trait Zippable[T[_]]{ + def map[A, B](a: T[A], f: A => B): T[B] + def zipMap[R]()(f: () => R) = map(zip(), (_: Unit) => f()) + def zipMap[A, R](a: T[A])(f: A => R) = map(a, f) + def zipMap[A, B, R](a: T[A], b: T[B])(f: (A, B) => R) = map(zip(a, b), f.tupled) + def zipMap[A, B, C, R](a: T[A], b: T[B], c: T[C])(f: (A, B, C) => R) = map(zip(a, b, c), f.tupled) + def zipMap[A, B, C, D, R](a: T[A], b: T[B], c: T[C], d: T[D])(f: (A, B, C, D) => R) = map(zip(a, b, c, d), f.tupled) + def zipMap[A, B, C, D, E, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E])(f: (A, B, C, D, E) => R) = map(zip(a, b, c, d, e), f.tupled) + def zipMap[A, B, C, D, E, F, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F])(cb: (A, B, C, D, E, F) => R) = map(zip(a, b, c, d, e, f), cb.tupled) + def zipMap[A, B, C, D, E, F, G, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F], g: T[G])(cb: (A, B, C, D, E, F, G) => R) = map(zip(a, b, c, d, e, f, g), cb.tupled) + def zip(): T[Unit] + def zip[A](a: T[A]): T[Tuple1[A]] + def zip[A, B](a: T[A], b: T[B]): T[(A, B)] + def zip[A, B, C](a: T[A], b: T[B], c: T[C]): T[(A, B, C)] + def zip[A, B, C, D](a: T[A], b: T[B], c: T[C], d: T[D]): T[(A, B, C, D)] + def zip[A, B, C, D, E](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E]): T[(A, B, C, D, E)] + def zip[A, B, C, D, E, F](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F]): T[(A, B, C, D, E, F)] + def zip[A, B, C, D, E, F, G](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F], g: T[G]): T[(A, B, C, D, E, F, G)] + } + trait Cacher[C]{ + private[this] val cacherLazyMap = mutable.Map.empty[sourcecode.Enclosing, C] + protected[this] def cachedTarget[T <: C](t: => T) + (implicit c: sourcecode.Enclosing): T = synchronized{ + cacherLazyMap.getOrElseUpdate(c, t).asInstanceOf[T] + } + } + + def impl0[M[_], T: c.WeakTypeTag](c: Context)(t: c.Expr[M[T]]): c.Expr[M[T]] = { + wrapCached(c)(t.tree) + } + def impl[M[_], T: c.WeakTypeTag](c: Context)(t: c.Expr[T])(implicit tt: c.WeakTypeTag[M[_]]): c.Expr[M[T]] = { + import c.universe._ + def rec(t: Tree): Iterator[c.Tree] = Iterator(t) ++ t.children.flatMap(rec(_)) + + val bound = collection.mutable.Buffer.empty[(c.Tree, Symbol)] + val targetApplySym = tt.tpe.member(TermName("apply")) + + // Derived from @olafurpg's + // https://gist.github.com/olafurpg/596d62f87bf3360a29488b725fbc7608 + val (startPos, endPos) = rec(t.tree) + .map(t => (t.pos.start, t.pos.end)) + .reduce[(Int, Int)]{ case ((s1, e1), (s2, e2)) => (math.min(s1, s2), math.max(e1, e2))} + + val macroSource = t.tree.pos.source + val transformed = c.internal.typingTransform(t.tree) { + case (t @ q"$fun.apply()", api) if t.symbol == targetApplySym => + + val used = rec(t) + val banned = used.filter(x => + x.symbol.pos.source == macroSource && + x.symbol.pos.start >= startPos && + x.symbol.pos.end <= endPos + ) + if (banned.hasNext){ + val banned0 = banned.next() + c.abort( + banned0.pos, + "Target#apply() call cannot use `" + banned0.symbol + "` defined within the T{...} block" + ) + } + val tempName = c.freshName(TermName("tmp")) + val tempSym = c.internal.newTermSymbol(api.currentOwner, tempName) + c.internal.setInfo(tempSym, t.tpe) + val tempIdent = Ident(tempSym) + c.internal.setType(tempIdent, t.tpe) + bound.append((fun, tempSym)) + tempIdent + case (t, api) => api.default(t) + } + + val (exprs, symbols) = bound.unzip + + val bindings = symbols.map(c.internal.valDef(_)) + + wrapCached(c)(q"${c.prefix}.zipMap(..$exprs){ (..$bindings) => $transformed }") + } + def wrapCached[M[_], T](c: Context)(t: c.Tree) = { + import c.universe._ + val owner = c.internal.enclosingOwner + val ownerIsCacherClass = + owner.owner.isClass && + owner.owner.asClass.baseClasses.exists(_.fullName == "forge.define.ApplicativeMacros.Cacher") + + if (ownerIsCacherClass && !owner.isMethod){ + c.abort( + c.enclosingPosition, + "T{} members defined in a Cacher class/trait/object body must be defs" + ) + }else{ + val embedded = + if (!ownerIsCacherClass) t + else q"this.cachedTarget($t)" + + c.Expr[M[T]](embedded) + } + } +} diff --git a/core/src/main/scala/forge/define/Target.scala b/core/src/main/scala/forge/define/Target.scala index 1d8ecdee..591fd68e 100644 --- a/core/src/main/scala/forge/define/Target.scala +++ b/core/src/main/scala/forge/define/Target.scala @@ -31,89 +31,16 @@ abstract class Target[T] extends Target.Ops[T]{ def apply(): T = ??? } -object Target{ - trait Cacher{ - private[this] val cacherLazyMap = mutable.Map.empty[sourcecode.Enclosing, Target[_]] - protected[this] def cachedTarget[T](t: => Target[T]) - (implicit c: sourcecode.Enclosing): Target[T] = synchronized{ - cacherLazyMap.getOrElseUpdate(c, t).asInstanceOf[Target[T]] - } - } +object Target extends ApplicativeMacros.Zippable[Target]{ + + type Cacher = ApplicativeMacros.Cacher[Target[_]] class Target0[T](t: T) extends Target[T]{ lazy val t0 = t val inputs = Nil def evaluate(args: Args) = t0 } - def apply[T](t: Target[T]): Target[T] = macro impl0[T] - def apply[T](t: T): Target[T] = macro impl[T] - def impl0[T: c.WeakTypeTag](c: Context)(t: c.Expr[Target[T]]): c.Expr[Target[T]] = { - wrapCached(c)(t.tree) - } - def impl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]): c.Expr[Target[T]] = { - import c.universe._ - def rec(t: Tree): Iterator[c.Tree] = Iterator(t) ++ t.children.flatMap(rec(_)) - val bound = collection.mutable.Buffer.empty[(c.Tree, Symbol)] - val targetApplySym = c.universe.typeOf[Target[_]].member(TermName("apply")) - // Derived from @olafurpg's - // https://gist.github.com/olafurpg/596d62f87bf3360a29488b725fbc7608 - - val (startPos, endPos) = rec(t.tree) - .map(t => (t.pos.start, t.pos.end)) - .reduce[(Int, Int)]{ case ((s1, e1), (s2, e2)) => (math.min(s1, s2), math.max(e1, e2))} - - val macroSource = t.tree.pos.source - val transformed = c.internal.typingTransform(t.tree) { - case (t @ q"$fun.apply()", api) if t.symbol == targetApplySym => - - val used = rec(t) - val banned = used.filter(x => - x.symbol.pos.source == macroSource && - x.symbol.pos.start >= startPos && - x.symbol.pos.end <= endPos - ) - if (banned.hasNext){ - val banned0 = banned.next() - c.abort( - banned0.pos, - "Target#apply() call cannot use `" + banned0.symbol + "` defined within the T{...} block" - ) - } - val tempName = c.freshName(TermName("tmp")) - val tempSym = c.internal.newTermSymbol(api.currentOwner, tempName) - c.internal.setInfo(tempSym, t.tpe) - val tempIdent = Ident(tempSym) - c.internal.setType(tempIdent, t.tpe) - bound.append((fun, tempSym)) - tempIdent - case (t, api) => api.default(t) - } - - val (exprs, symbols) = bound.unzip - - val bindings = symbols.map(c.internal.valDef(_)) - - wrapCached(c)(q"forge.zipMap(..$exprs){ (..$bindings) => $transformed }") - } - def wrapCached[T](c: Context)(t: c.Tree) = { - import c.universe._ - val owner = c.internal.enclosingOwner - val ownerIsCacherClass = - owner.owner.isClass && - owner.owner.asClass.baseClasses.exists(_.fullName == "forge.define.Target.Cacher") - - if (ownerIsCacherClass && !owner.isMethod){ - c.abort( - c.enclosingPosition, - "T{} members defined in a Cacher class/trait/object body must be defs" - ) - }else{ - val embedded = - if (!ownerIsCacherClass) t - else q"this.cachedTarget($t)" - - c.Expr[Target[T]](embedded) - } - } + def apply[T](t: Target[T]): Target[T] = macro ApplicativeMacros.impl0[Target, T] + def apply[T](t: T): Target[T] = macro ApplicativeMacros.impl[Target, T] abstract class Ops[T]{ this: Target[T] => def map[V](f: T => V) = new Target.Mapped(this, f) @@ -172,4 +99,29 @@ object Target{ implicit val tsFormat: Format[Target.Subprocess.Result] = Json.format } } + + def map[A, B](t: Target[A], f: A => B) = t.map(f) + def zip() = new Target.Target0(()) + def zip[A](a: Target[A]) = a.map(Tuple1(_)) + def zip[A, B](a: Target[A], b: Target[B]) = a.zip(b) + def zip[A, B, C](a: Target[A], b: Target[B], c: Target[C]) = new Target[(A, B, C)]{ + val inputs = Seq(a, b, c) + def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2)) + } + def zip[A, B, C, D](a: Target[A], b: Target[B], c: Target[C], d: Target[D]) = new Target[(A, B, C, D)]{ + val inputs = Seq(a, b, c, d) + def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3)) + } + def zip[A, B, C, D, E](a: Target[A], b: Target[B], c: Target[C], d: Target[D], e: Target[E]) = new Target[(A, B, C, D, E)]{ + val inputs = Seq(a, b, c, d, e) + def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3), args[E](4)) + } + def zip[A, B, C, D, E, F](a: Target[A], b: Target[B], c: Target[C], d: Target[D], e: Target[E], f: Target[F]) = new Target[(A, B, C, D, E, F)]{ + val inputs = Seq(a, b, c, d, e, f) + def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3), args[E](4), args[F](5)) + } + def zip[A, B, C, D, E, F, G](a: Target[A], b: Target[B], c: Target[C], d: Target[D], e: Target[E], f: Target[F], g: Target[G]) = new Target[(A, B, C, D, E, F, G)]{ + val inputs = Seq(a, b, c, d, e, f, g) + def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3), args[E](4), args[F](5), args[G](6)) + } } diff --git a/core/src/main/scala/forge/define/ZipTarget.scala b/core/src/main/scala/forge/define/ZipTarget.scala deleted file mode 100644 index 1faef07e..00000000 --- a/core/src/main/scala/forge/define/ZipTarget.scala +++ /dev/null @@ -1,40 +0,0 @@ -package forge.define - -import forge.util.Args - -object ZipTarget -trait ZipTarget { - val T = Target - type T[V] = Target[V] - def zipMap[R]()(f: () => R) = new Target.Target0(f()) - def zipMap[A, R](a: T[A])(f: A => R) = a.map(f) - def zipMap[A, B, R](a: T[A], b: T[B])(f: (A, B) => R) = zip(a, b).map(f.tupled) - def zipMap[A, B, C, R](a: T[A], b: T[B], c: T[C])(f: (A, B, C) => R) = zip(a, b, c).map(f.tupled) - def zipMap[A, B, C, D, R](a: T[A], b: T[B], c: T[C], d: T[D])(f: (A, B, C, D) => R) = zip(a, b, c, d).map(f.tupled) - def zipMap[A, B, C, D, E, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E])(f: (A, B, C, D, E) => R) = zip(a, b, c, d, e).map(f.tupled) - def zipMap[A, B, C, D, E, F, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F])(cb: (A, B, C, D, E, F) => R) = zip(a, b, c, d, e, f).map(cb.tupled) - def zipMap[A, B, C, D, E, F, G, R](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F], g: T[G])(cb: (A, B, C, D, E, F, G) => R) = zip(a, b, c, d, e, f, g).map(cb.tupled) - def zip() = new Target.Target0(()) - def zip[A](a: T[A]) = a.map(Tuple1(_)) - def zip[A, B](a: T[A], b: T[B]) = a.zip(b) - def zip[A, B, C](a: T[A], b: T[B], c: T[C]) = new T[(A, B, C)]{ - val inputs = Seq(a, b, c) - def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2)) - } - def zip[A, B, C, D](a: T[A], b: T[B], c: T[C], d: T[D]) = new T[(A, B, C, D)]{ - val inputs = Seq(a, b, c, d) - def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3)) - } - def zip[A, B, C, D, E](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E]) = new T[(A, B, C, D, E)]{ - val inputs = Seq(a, b, c, d, e) - def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3), args[E](4)) - } - def zip[A, B, C, D, E, F](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F]) = new T[(A, B, C, D, E, F)]{ - val inputs = Seq(a, b, c, d, e, f) - def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3), args[E](4), args[F](5)) - } - def zip[A, B, C, D, E, F, G](a: T[A], b: T[B], c: T[C], d: T[D], e: T[E], f: T[F], g: T[G]) = new T[(A, B, C, D, E, F, G)]{ - val inputs = Seq(a, b, c, d, e, f, g) - def evaluate(args: Args) = (args[A](0), args[B](1), args[C](2), args[D](3), args[E](4), args[F](5), args[G](6)) - } -} diff --git a/core/src/main/scala/forge/package.scala b/core/src/main/scala/forge/package.scala index 58401c64..48f5132a 100644 --- a/core/src/main/scala/forge/package.scala +++ b/core/src/main/scala/forge/package.scala @@ -1,7 +1,6 @@ -import forge.define.ZipTarget import forge.util.JsonFormatters -package object forge extends ZipTarget with JsonFormatters{ - val Target = define.Target - type Target[T] = define.Target[T] +package object forge extends JsonFormatters{ + val T = define.Target + type T[T] = define.Target[T] } diff --git a/core/src/test/scala/forge/ApplicativeMacrosTests.scala b/core/src/test/scala/forge/ApplicativeMacrosTests.scala new file mode 100644 index 00000000..24851283 --- /dev/null +++ b/core/src/test/scala/forge/ApplicativeMacrosTests.scala @@ -0,0 +1,9 @@ +package forge +import utest._ +object ApplicativeMacrosTests extends TestSuite{ + val tests = Tests{ + 'hello - { + 'hello + } + } +} diff --git a/core/src/test/scala/forge/MacroErrorTests.scala b/core/src/test/scala/forge/MacroErrorTests.scala index 29f68f4d..9f41fadc 100644 --- a/core/src/test/scala/forge/MacroErrorTests.scala +++ b/core/src/test/scala/forge/MacroErrorTests.scala @@ -10,9 +10,10 @@ object MacroErrorTests extends TestSuite{ val expectedMsg = "T{} members defined in a Cacher class/trait/object body must be defs" - val err = compileError("object Foo extends Target.Cacher{ val x = T{1} }") + val err = compileError("object Foo extends forge.define.Target.Cacher{ val x = T{1} }") assert(err.msg == expectedMsg) } + 'badTmacro - { // Make sure we can reference values from outside the T{...} block as part // of our `Target#apply()` calls, but we cannot reference any values that |