package scala.reflect package quasiquotes import java.util.UUID.randomUUID import scala.collection.mutable /** Emulates hole support (see Holes.scala) in the quasiquote parser (see Parsers.scala). * A principled solution to splicing into Scala syntax would be a parser that natively supports holes. * Unfortunately, that's outside of our reach in Scala 2.11, so we have to emulate. * This trait stores knowledge of how to represent the holes as something understandable by the parser * and how to recover holes from the results of parsing the produced representation. */ trait Placeholders { self: Quasiquotes => import global._ import Rank._ import universeTypes._ // Step 1: Transform Scala source with holes into vanilla Scala source lazy val posMap = mutable.LinkedHashMap[Position, (Int, Int)]() lazy val code = { val sb = new StringBuilder() val sessionSuffix = randomUUID().toString.replace("-", "").substring(0, 8) + "$" def appendPart(value: String, pos: Position) = { val start = sb.length sb.append(value) val end = sb.length posMap += pos -> ((start, end)) } def appendHole(tree: Tree, rank: Rank) = { val placeholderName = c.freshName(TermName(nme.QUASIQUOTE_PREFIX + sessionSuffix)) sb.append(placeholderName) val holeTree = if (method != nme.unapply) tree else Bind(placeholderName, tree) holeMap(placeholderName) = Hole(rank, holeTree) } val iargs = method match { case nme.apply => args case nme.unapply => internal.subpatterns(args.head).get case _ => global.abort("unreachable") } foreach2(iargs, parts.init) { case (tree, (p, pos)) => val (part, rank) = parseDots(p) appendPart(part, pos) appendHole(tree, rank) } val (p, pos) = parts.last appendPart(p, pos) sb.toString } object holeMap { private val underlying = mutable.LinkedHashMap.empty[String, Hole] private val accessed = mutable.Set.empty[String] def unused: Set[Name] = (underlying.keys.toSet -- accessed).map(TermName(_)) def contains(key: Name): Boolean = underlying.contains(key.toString) def apply(key: Name): Hole = { val skey = key.toString val value = underlying(skey) accessed += skey value } def update(key: Name, hole: Hole) = underlying += key.toString -> hole def get(key: Name): Option[Hole] = { val skey = key.toString underlying.get(skey).map { v => accessed += skey v } } def keysIterator: Iterator[TermName] = underlying.keysIterator.map(TermName(_)) } // Step 2: Transform vanilla Scala AST into an AST with holes trait HolePlaceholder { def matching: PartialFunction[Any, Name] def unapply(scrutinee: Any): Option[Hole] = { val name = matching.lift(scrutinee) name.flatMap { holeMap.get(_) } } } object Placeholder extends HolePlaceholder { def matching = { case name: Name => name case Ident(name) => name case Bind(name, Ident(nme.WILDCARD)) => name case TypeDef(_, name, List(), TypeBoundsTree(EmptyTree, EmptyTree)) => name } } object ModsPlaceholder extends HolePlaceholder { def apply(name: Name) = Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(name.toString)))) def matching = { case Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(s: String)))) => TermName(s) } } object AnnotPlaceholder extends HolePlaceholder { def matching = { case Apply(Select(New(Ident(name)), nme.CONSTRUCTOR), Nil) => name } } object ParamPlaceholder extends HolePlaceholder { def apply(flags: FlagSet, name: Name) = ValDef(Modifiers(flags), nme.QUASIQUOTE_PARAM, Ident(name), EmptyTree) def matching = { case ValDef(_, nme.QUASIQUOTE_PARAM, Ident(name), EmptyTree) => name } } object TuplePlaceholder { def apply(args: List[Tree]) = Apply(Ident(nme.QUASIQUOTE_TUPLE), args) def unapply(tree: Tree): Option[List[Tree]] = tree match { case Apply(Ident(nme.QUASIQUOTE_TUPLE), args) => Some(args) case _ => None } } object TupleTypePlaceholder { def apply(args: List[Tree]) = AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) def unapply(tree: Tree): Option[List[Tree]] = tree match { case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) => Some(args) case _ => None } } object FunctionTypePlaceholder { def apply(args: List[Tree], res: Tree) = AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), args :+ res) def unapply(tree: Tree): Option[(List[Tree], Tree)] = tree match { case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), args :+ res) => Some((args, res)) case _ => None } } object SymbolPlaceholder { def unapply(scrutinee: Any): Option[Hole] = scrutinee match { case Placeholder(hole: ApplyHole) if hole.tpe <:< symbolType => Some(hole) case _ => None } } object CasePlaceholder { def apply(name: Name) = CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), Ident(name) :: Nil), EmptyTree, EmptyTree) def unapply(tree: Tree): Option[Hole] = tree match { case CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), List(Placeholder(hole))), EmptyTree, EmptyTree) => Some(hole) case _ => None } } object RefineStatPlaceholder { def apply(name: Name) = ValDef(NoMods, nme.QUASIQUOTE_REFINE_STAT, Ident(name), EmptyTree) def unapply(tree: Tree): Option[Hole] = tree match { case ValDef(_, nme.QUASIQUOTE_REFINE_STAT, Ident(Placeholder(hole)), _) => Some(hole) case _ => None } } object EarlyDefPlaceholder { def apply(name: Name) = ValDef(Modifiers(Flag.PRESUPER), nme.QUASIQUOTE_EARLY_DEF, Ident(name), EmptyTree) def unapply(tree: Tree): Option[Hole] = tree match { case ValDef(_, nme.QUASIQUOTE_EARLY_DEF, Ident(Placeholder(hole)), _) => Some(hole) case _ => None } } object PackageStatPlaceholder { def apply(name: Name) = ValDef(NoMods, nme.QUASIQUOTE_PACKAGE_STAT, Ident(name), EmptyTree) def unapply(tree: Tree): Option[Hole] = tree match { case ValDef(NoMods, nme.QUASIQUOTE_PACKAGE_STAT, Ident(Placeholder(hole)), EmptyTree) => Some(hole) case _ => None } } object ForEnumPlaceholder { def apply(name: Name) = build.SyntacticValFrom(Bind(name, Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) def unapply(tree: Tree): Option[Hole] = tree match { case build.SyntacticValFrom(Bind(Placeholder(hole), Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) => Some(hole) case _ => None } } }