Skip to content
Permalink
Browse files

[scalac-plugin] Recursive support `~(A(...), ~(B(...), (x, y)))`. Add…

… specifications for pattern matching in `val` statement. Update documentation.
  • Loading branch information
cchantep
cchantep committed Aug 17, 2014
1 parent d6d2a26 commit 0942aaefa304f7ef13e5260b779e2f51d65bb4be
@@ -10,7 +10,7 @@ object Acolyte extends Build with Dependencies
aggregate(scalacPlugin, reactiveMongo, jdbcDriver, jdbcScala, studio).
settings(
organization in ThisBuild := "org.eu.acolyte",
version in ThisBuild := "1.0.24",
version in ThisBuild := "1.0.24-1",
javaOptions in ThisBuild ++= Seq("-source", "1.6", "-target", "1.6"),
scalaVersion in ThisBuild := "2.10.4",
crossScalaVersions in ThisBuild := Seq("2.10.4", "2.11.2"),
@@ -27,7 +27,7 @@ case class Regex(e: String) {
}
```

Then provided rich syntax can be used as following (see [complete example](./src/test/scala/acolyte/ExtractorComponentSpec.scala#L189)):
Then provided rich syntax can be used as following (see [complete example](./src/test/scala/acolyte/ExtractorComponentSpec.scala#L379)):

```scala
str match {
@@ -43,7 +43,12 @@ str match {
// if there are exactly 3 '/' in str,
// matches and assign indexes to a, b and c
case _ 5
case ~(Regex("^cp (.+)"), ~(Regex("([/a-z]+) ([/a-z]+)"),
(src @ ~(IndexOf('/'), a :: b :: Nil),
dest @ ~(IndexOf('/'), c :: _)))) 5
// rich syntax can be used recursively
case _ 6
}
```

@@ -60,7 +65,7 @@ str match {

> Syntax like `(a, b)` (where `3` is selected) doesn't represent a tuple there, but multiple (list of) bindings.
It also works with partial function (see [more examples](./src/test/scala/acolyte/ExtractorComponentSpec.scala#L159)).
It also works with partial function (see [more examples](./src/test/scala/acolyte/ExtractorComponentSpec.scala#L322)).

```scala
val partialFun: String Int = {
@@ -85,7 +90,7 @@ val partialFun: String ⇒ Int = { str: String ⇒
}
```

Anonymous partial functions are refactored too (see [more examples](./src/test/scala/acolyte/ExtractorComponentSpec.scala#L178)):
Anonymous partial functions are refactored too (see [more examples](./src/test/scala/acolyte/ExtractorComponentSpec.scala#L359)):

```scala
def test(s: Option[String]): Option[Int] = s map {
@@ -96,6 +101,20 @@ def test(s: Option[String]): Option[Int] = s map {
}
```

Pattern matching in `val` statement is enriched in the same way ((see [more examples](./src/test/scala/acolyte/ExtractorComponentSpec.scala#L299)).

```scala
val ~(Regex("([A-Z]+):([0-9]+)"), (tag, priority)) = "EN:456"
// tag == "EN" && priority == "456"
```

Such case is refactored as following:

```scala
val Xtr1 = Regex("([A-Z]+):([0-9]+)")
val Xtr1(tag, priority) = "FR:123"
```

## Usage

> If you have another `~` symbol, it will have to be renamed at `import pkg.{ ~ ⇒ renamed }`.
@@ -50,6 +50,7 @@ class AcolytePlugin(val global: Global) extends Plugin {
Ident,
Literal,
Match,
Name,
Position,
Select,
Tree,
@@ -71,8 +72,8 @@ class AcolytePlugin(val global: Global) extends Plugin {
case _ super.transform(tree)
}

val tildeTerm = global.newTermName("$tilde")
val scalaTerm = global.newTermName("scala")
val TildeTerm = global.newTermName("$tilde")
val ScalaTerm = global.newTermName("scala")

@inline private def refactorMatch(orig: Match): Tree = orig match {
case Match(t, cs) {
@@ -106,45 +107,111 @@ class AcolytePlugin(val global: Global) extends Plugin {
orig
}

@inline private def rewriteApply(top: Position, id: Tree, x: List[Tree], vds: ListBuffer[ValDef]): Apply = (id, x.headOption) match {
case (Ident(TildeTerm), Some(xt @ Apply(ex, xa))) {
val bs = x.tail

val xpo: Option[List[Tree]] = bs.headOption match {
case Some(Apply(Select(Ident(scalaTerm), st), ua)) if (
st.toString startsWith "Tuple") Some(ua)
case Some(ap @ Apply(_, _)) Some(ap :: Nil)
case Some(bn @ Bind(_, _)) Some(bn :: Nil)
case Some(id @ Ident(_)) Some(id :: Nil)
case Some(li @ Literal(Constant(_))) Some(li :: Nil)
case None Some(Nil)
case _ None
}

xpo.fold({
reporter.error(top, s"""Invalid ~ pattern: ${bs.headOption.fold("None")(global.showRaw(_))}""")
//abort("Invalid ~ pattern")
Apply(id, x)
}) { xp
val (vd, ai) = refactorPattern(top, ex, xa, xp)
vds += vd
Apply(ai, xp)
}
}
case (Ident(TildeTerm), _)
reporter.error(top,
s"Invalid ~ pattern: application expected in bindings: $x")
Apply(id, x)

case _ Apply(id, x)
}

// Transformation states
sealed trait TxState

private case class AState(
id: Tree, orig: List[Tree], refact: List[Tree]) extends TxState

private case class BState(
name: Name /* binding name */ ,
orig: Option[Tree], dest: Option[Tree]) extends TxState

private object BState {
def apply(name: Name, target: Tree): BState =
BState(name, Some(target), None)

}

@annotation.tailrec
private def refactorApply(top: Position, cur: TxState, up: List[TxState], vds: ListBuffer[ValDef]): Apply = (cur, up) match {
case (AState(id, ::(Apply(i, ts), as), xs), _)
refactorApply(top, AState(i, ts, Nil),
AState(id, as, xs) :: up, vds)

case (AState(id, ::(Bind(n, t), as), xs), _)
refactorApply(top, BState(n, t), AState(id, as, xs) :: up, vds)

case (bn @ BState(id, Some(Apply(i, ts)), _), _)
refactorApply(top, AState(i, ts, Nil), bn :: up, vds)

case (bn @ BState(id, Some(Bind(n, t)), _), _)
refactorApply(top, BState(n, t), bn :: up, vds)

case (AState(id, a :: as, xs), _)
refactorApply(top, AState(id, as, xs :+ a), up, vds)

case (BState(an, Some(at), _), ::(BState(bn, _, _), us))
refactorApply(top, BState(bn, None, Some(Bind(an, at))), us, vds)

case (BState(an, Some(at), _), ::(AState(ai, ts, xs), us))
refactorApply(top, AState(ai, ts, xs :+ Bind(an, at)), us, vds)

case (BState(an, _, Some(at)), ::(BState(bn, _, _), us))
refactorApply(top, BState(bn, None, Some(Bind(an, at))), us, vds)

case (BState(an, _, Some(at)), ::(AState(ai, ts, xs), us))
refactorApply(top, AState(ai, ts, xs :+ Bind(an, at)), us, vds)

case (AState(id, Nil, xs), ::(AState(iu, y, z), us))
val ap = rewriteApply(top, id, xs, vds)
refactorApply(top, AState(iu, y, z :+ ap), us, vds)

case (AState(id, Nil, xs), ::(BState(un, _, _), us))
val ap = rewriteApply(top, id, xs, vds)
refactorApply(top, BState(un, None, Some(ap)), us, vds)

case (AState(id, Nil, xs), Nil) rewriteApply(top, id, xs, vds)

case _
reporter.error(top, s"Invalid ~ pattern: $cur, $up")
global.abort(s"Invalid ~ pattern: $cur, $up")

}

private def caseDefTransformer(vds: ListBuffer[ValDef]) =
new global.Transformer {
override def transform(tree: Tree): Tree = tree match {
case oa @ Apply(Ident(it), x) if (it == tildeTerm) {
(x.headOption, x.tail) match {
case (Some(xt @ Apply(ex, xa)), bs) {
val xpo: Option[List[Tree]] = bs.headOption match {
case Some(Apply(Select(Ident(scalaTerm), st), ua)) if (
st.toString startsWith "Tuple") Some(ua)
case Some(ap @ Apply(_, _)) Some(ap :: Nil)
case Some(bn @ Bind(_, _)) Some(bn :: Nil)
case Some(id @ Ident(_)) Some(id :: Nil)
case Some(li @ Literal(Constant(_))) Some(li :: Nil)
case None Some(Nil)
case _ None
}

xpo.fold({
reporter.error(oa.pos, s"""Invalid ~ pattern: ${bs.headOption.fold("None")(global.showRaw(_))}""")
//abort("Invalid ~ pattern")
oa
}) { xp
val (vd, rp) = refactorPattern(xt.pos, ex, xa, xp)
vds += vd
rp
}
}
case _
reporter.error(oa.pos, "Invalid ~ pattern")
//abort("Invalid ~ pattern")
oa

}
}
case oa @ Apply(id, xa)
refactorApply(oa.pos, AState(id, xa, Nil), Nil, vds)
case _ super.transform(tree)
}
}

@inline private def refactorPattern[T](xp: Position, ex: Tree, xa: List[Tree], ua: List[Tree]): (ValDef, Apply) = {
@inline private def refactorPattern[T](xp: Position, ex: Tree, xa: List[Tree], ua: List[Tree]): (ValDef, Ident) = {

import global.{ atPos, show, newTermName, Ident, Modifiers }

@@ -160,7 +227,7 @@ class AcolytePlugin(val global: Global) extends Plugin {

val vdp = withSource(xp.withPoint(0))(new BatchSourceFile(file, vdc), 0)

(atPos(vdp)(vd), Apply(Ident(xn), ua))
atPos(vdp)(vd) -> Ident(xn)
}
}
}

0 comments on commit 0942aae

Please sign in to comment.
You can’t perform that action at this time.