diff --git a/macros/src/main/scala/me/rexim/morganey/meta/QuotationMacro.scala b/macros/src/main/scala/me/rexim/morganey/meta/QuotationMacro.scala index 0926f4f..831fb66 100644 --- a/macros/src/main/scala/me/rexim/morganey/meta/QuotationMacro.scala +++ b/macros/src/main/scala/me/rexim/morganey/meta/QuotationMacro.scala @@ -1,10 +1,17 @@ package me.rexim.morganey.meta import me.rexim.morganey.ast._ +import me.rexim.morganey.meta.QuotationMacro._ import StringContext.treatEscapes import scala.reflect.macros.whitebox +private[meta] object QuotationMacro { + + val bigNumberThreshold = 10 + +} + private[meta] class QuotationMacro(val c: whitebox.Context) { import c.universe._ @@ -35,6 +42,9 @@ private[meta] class QuotationMacro(val c: whitebox.Context) { private val LambdaTermTpe = typeOf[LambdaTerm] + private lazy val morganeyNumber = implicitly[Unlift[Int]] + private lazy val morganeyIntList = implicitly[Unlift[List[Int]]] + private lazy val liftList = implicitly[Lift[List[LambdaTerm]]] private lazy val liftList_ = c.inferImplicitValue(typeOf[Lift[List[LambdaTerm]]], silent = true) private lazy val unliftList = implicitly[Unlift[List[LambdaTerm]]] @@ -50,8 +60,8 @@ private[meta] class QuotationMacro(val c: whitebox.Context) { private def unliftableT(tpe: Type): Type = appliedType(UnliftableTpe, tpe) private case class Lifted(exp: Tree, preamble: List[Tree] = Nil) { - def define(decl: Tree): Lifted = - Lifted(exp, preamble :+ decl) + def define(decls: Tree*): Lifted = + Lifted(exp, preamble ++ decls.toList) def wrap(f: Tree => Tree): Lifted = Lifted(f(exp), preamble) @@ -95,17 +105,40 @@ private[meta] class QuotationMacro(val c: whitebox.Context) { private def liftPrimitiveTerm(term: LambdaTerm): Lifted = term match { case LambdaVar(DottedHole(_)) => c.abort(c.enclosingPosition, "Illegal usage of ..!") + case LambdaVar(Hole(hole)) => replaceHole(hole, dotted = false) match { case Left(x) => x case Right((x, _)) => x } + case LambdaVar(name) => Lifted(q"_root_.me.rexim.morganey.ast.LambdaVar($name)") + + // generate special code for detecting int lists (also strings) + case morganeyIntList(xs) if isUnapply => + val const = TermName(c.freshName("const")) + val preamble = + q""" + val $const = + new _root_.me.rexim.morganey.meta.UnapplyConst[_root_.scala.collection.immutable.List[Int]]($xs) + """ + Lifted(pq"$const()").define(preamble) + + // generate special code for detecting big constant numbers and characters + case morganeyNumber(n) if isUnapply && n > bigNumberThreshold => + val const = TermName(c.freshName("const")) + val preamble = + q""" + val $const = new _root_.me.rexim.morganey.meta.UnapplyConst[Int]($n) + """ + Lifted(pq"$const()").define(preamble) + case LambdaFunc(param, body) => liftPrimitiveTerm(param).wrap2(liftComplexTerm(body)) { case (paramTree, bodyTree) => q"_root_.me.rexim.morganey.ast.LambdaFunc($paramTree, $bodyTree)" } + case LambdaApp(left, right) => liftComplexTerm(left).wrap2(liftComplexTerm(right)) { case (leftTree, rightTree) => q"_root_.me.rexim.morganey.ast.LambdaApp($leftTree, $rightTree)" diff --git a/macros/src/main/scala/me/rexim/morganey/meta/UnapplyConst.scala b/macros/src/main/scala/me/rexim/morganey/meta/UnapplyConst.scala new file mode 100644 index 0000000..9e30566 --- /dev/null +++ b/macros/src/main/scala/me/rexim/morganey/meta/UnapplyConst.scala @@ -0,0 +1,15 @@ +package me.rexim.morganey.meta + +import me.rexim.morganey.ast.LambdaTerm + +/** + * Helper class to match a constant in the quotation at runtime + */ +class UnapplyConst[T](n: T)(implicit morganeyValue: Unliftable[T]) { + + def unapply(term: LambdaTerm): Boolean = term match { + case morganeyValue(m) => n == m + case _ => false + } + +} diff --git a/macros/src/main/scala/me/rexim/morganey/meta/UnapplyEach.scala b/macros/src/main/scala/me/rexim/morganey/meta/UnapplyEach.scala index b7f2239..cdad317 100644 --- a/macros/src/main/scala/me/rexim/morganey/meta/UnapplyEach.scala +++ b/macros/src/main/scala/me/rexim/morganey/meta/UnapplyEach.scala @@ -6,6 +6,9 @@ import me.rexim.morganey.monad._ import scala.collection.generic.CanBuildFrom import scala.language.higherKinds +/** + * Helper class to convert each LambdaTerm to T in a collection + */ class UnapplyEach[T, CC[+X] <: TraversableOnce[X]](unliftT: Unliftable[T]) (implicit cbf: CanBuildFrom[CC[LambdaTerm], T, CC[T]], cbfo: CanBuildFrom[CC[LambdaTerm], Option[T], CC[Option[T]]]) { diff --git a/macros/src/test/scala/me/rexim/morganey/meta/InterpolatorSpec.scala b/macros/src/test/scala/me/rexim/morganey/meta/InterpolatorSpec.scala index ea89439..b5de56d 100644 --- a/macros/src/test/scala/me/rexim/morganey/meta/InterpolatorSpec.scala +++ b/macros/src/test/scala/me/rexim/morganey/meta/InterpolatorSpec.scala @@ -49,6 +49,12 @@ class InterpolatorSpec extends FlatSpec with Matchers with TestTerms { m"$numbers" should be (pair(zero, pair(one, pair(two, zero, "z"), "z"), "z")) } + "Matching against big strings" should "be possible with the quotation mechanism" in { + val string = "The quick brown fox jumps over the lazy dog" + // `string` is represented by 4057 applications in morganey + val m""" "The quick brown fox jumps over the lazy dog" """ = m"$string" + } + "Lambda terms" should "be converted back to Scala values during unlifting" in { val m"${nZero: Int}" = zero nZero should be (0)