summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala
blob: c136e6d78536d246c0f08bdb9b315466b7ac03f1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package scala.tools.reflect
package quasiquotes

import java.util.UUID.randomUUID
import scala.collection.{immutable, 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 Cardinality._

  // Step 1: Transform Scala source with holes into vanilla Scala source

  lazy val holeMap = new HoleMap()
  lazy val posMap = mutable.ListMap[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, cardinality: Cardinality) = {
      val placeholderName = c.freshName(TermName(nme.QUASIQUOTE_PREFIX + sessionSuffix))
      sb.append(placeholderName)
      holeMap(placeholderName) = Hole(tree, cardinality)
    }

    foreach2(args, parts.init) { case (tree, (p, pos)) =>
      val (part, cardinality) = parseDots(p)
      appendPart(part, pos)
      appendHole(tree, cardinality)
    }
    val (p, pos) = parts.last
    appendPart(p, pos)

    sb.toString
  }

  class HoleMap {
    private val underlying = mutable.ListMap[String, Hole]()
    private val accessed = mutable.Set[String]()
    def unused: Set[Name] = (underlying.keys.toSet -- accessed).map(TermName(_))
    def contains(key: Name) = underlying.contains(key.toString)
    def apply(key: Name) = {
      val s = key.toString
      accessed += s
      underlying(s)
    }
    def update(key: Name, hole: Hole) = {
      underlying += key.toString -> hole
    }
    def get(key: Name) = {
      val s = key.toString
      accessed += s
      underlying.get(s)
    }
  }

  // Step 2: Transform vanilla Scala AST into an AST with holes

  trait HolePlaceholder {
    def matching: PartialFunction[Any, Name]
    def unapply(scrutinee: Any): Option[(Tree, Location, Cardinality)] = {
      val name = matching.lift(scrutinee)
      name.flatMap { holeMap.get(_).map { case Hole(repr, loc, card) => (repr, loc, card) } }
    }
  }

  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
      case ValDef(_, name, TypeTree(), EmptyTree) => name
    }
  }

  object ModsPlaceholder extends HolePlaceholder {
    def matching = {
      case Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(s: String)))) => TermName(s)
    }
  }

  object AnnotPlaceholder {
    def unapply(tree: Tree): Option[(Tree, Location, Cardinality, List[Tree])] = tree match {
      case Apply(Select(New(Placeholder(tree, loc, card)), nme.CONSTRUCTOR), args) => Some(tree, loc, card, args)
      case _ => None
    }
  }

  object TuplePlaceholder {
    def unapply(tree: Tree): Option[List[Tree]] = tree match {
      case Apply(Ident(nme.QUASIQUOTE_TUPLE), args) => Some(args)
      case _ => None
    }
  }

  object TupleTypePlaceholder {
    def unapply(tree: Tree): Option[List[Tree]] = tree match {
      case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) => Some(args)
      case _ => None
    }
  }

  object FunctionTypePlaceholder {
    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[Tree] = scrutinee match {
      case Placeholder(tree, SymbolLocation, _) => Some(tree)
      case _ => None
    }
  }

  object CasePlaceholder {
    def unapply(tree: Tree): Option[(Tree, Location, Cardinality)] = tree match {
      case CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), List(Placeholder(tree, location, card))), EmptyTree, EmptyTree) => Some((tree, location, card))
      case _ => None
    }
  }

  object RefineStatPlaceholder {
    def unapply(tree: Tree): Option[(Tree, Location, Cardinality)] = tree match {
      case ValDef(_, Placeholder(tree, location, card), Ident(tpnme.QUASIQUOTE_REFINE_STAT), _) => Some((tree, location, card))
      case _ => None
    }
  }

  object EarlyDefPlaceholder {
    def unapply(tree: Tree): Option[(Tree, Location, Cardinality)] = tree match {
      case ValDef(_, Placeholder(tree, location, card), Ident(tpnme.QUASIQUOTE_EARLY_DEF), _) => Some((tree, location, card))
      case _ => None
    }
  }
}