Permalink
Browse files

[scalac-plugin] Support application other than a single value or tupl…

…e-like one as bindings:

```scala
str match {
  case str @ ~(IndexOf('/'), a :: b :: c :: _) ⇒ ??? // Support :: application
  case _ ⇒ ???
}
```
  • Loading branch information...
cchantep
cchantep committed Aug 15, 2014
1 parent 445d2f9 commit d6d2a26b601fd76d09b6c626f87998f28489cd0e
View
@@ -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.23",
version in ThisBuild := "1.0.24",
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"),
View
@@ -31,15 +31,19 @@ Then provided rich syntax can be used as following (see [complete example](./src
```scala
str match {
case ~(Regex("^a.*")) 1 // no binding
case ~(Regex("^a.*")) 1 // no binding
case ~(Regex("# ([A-Z]+).*"), a) 2
case ~(Regex("# ([A-Z]+).*"), a) 2
// if str == "# BCD123", then a = "BCD"
case ~(Regex("([0-9]+);([a-z]+)"), (a, b)) 3
case ~(Regex("([0-9]+);([a-z]+)"), (a, b)) 3
// if str == "234;xyz", then a = "234" and b = "xyz"
case _ 4
case str @ ~(IndexOf('/'), a :: b :: c :: _) 4
// if there are exactly 3 '/' in str,
// matches and assign indexes to a, b and c
case _ 5
}
```
@@ -54,6 +58,8 @@ 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)).
```scala
@@ -51,6 +51,7 @@ class AcolytePlugin(val global: Global) extends Plugin {
Literal,
Match,
Position,
Select,
Tree,
ValDef
}
@@ -71,6 +72,7 @@ class AcolytePlugin(val global: Global) extends Plugin {
}
val tildeTerm = global.newTermName("$tilde")
val scalaTerm = global.newTermName("scala")
@inline private def refactorMatch(orig: Match): Tree = orig match {
case Match(t, cs) {
@@ -111,7 +113,9 @@ class AcolytePlugin(val global: Global) extends Plugin {
(x.headOption, x.tail) match {
case (Some(xt @ Apply(ex, xa)), bs) {
val xpo: Option[List[Tree]] = bs.headOption match {
case Some(Apply(_, ua)) Some(ua)
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)
@@ -24,6 +24,12 @@ object ExtractorComponentSpec extends org.specs2.mutable.Specification
"rich match without binding: ~(IntRange(10, 20), i)" in {
patternMatching("12") aka "matching" mustEqual List("range:12")
}
"rich match with pattern in bindings: ~(IndexOf('/'), a :: b :: c :: _)".
in({
patternMatching("/path/to/file") aka "matching" mustEqual (
List("IndexOf: /path/to/file", "0", "5", "8"))
})
}
"Extractor with unapplySeq" should {
@@ -70,6 +76,12 @@ object ExtractorComponentSpec extends org.specs2.mutable.Specification
"rich match without binding: ~(IntRange(10, 20), i)" in {
recursivePatternMatching("12") aka "matching" mustEqual List("range:12")
}
"rich match with pattern in bindings: ~(IndexOf('/'), a :: b :: c :: _)".
in({
recursivePatternMatching("/path/to/file") aka "matching" mustEqual (
List("IndexOf: /path/to/file", "0", "5", "8"))
})
}
"Extractor with unapplySeq" should {
@@ -117,6 +129,12 @@ object ExtractorComponentSpec extends org.specs2.mutable.Specification
"rich match without binding: ~(IntRange(10, 20), i)" in {
partialFun1("12") aka "matching" mustEqual List("range:12")
}
"rich match with pattern in bindings: ~(IndexOf('/'), a :: b :: c :: _)".
in({
partialFun1("/path/to/file") aka "matching" mustEqual (
List("IndexOf: /path/to/file", "0", "5", "8"))
})
}
"Extractor with unapplySeq" should {
@@ -164,6 +182,12 @@ object ExtractorComponentSpec extends org.specs2.mutable.Specification
"rich match without binding: ~(IntRange(10, 20), i)" in {
partialFun2("12") aka "matching" mustEqual List("range:12")
}
"rich match with pattern in bindings: ~(IndexOf('/'), a :: b :: c :: _)".
in({
partialFun2("/path/to/file") aka "matching" mustEqual (
List("IndexOf: /path/to/file", "0", "5", "8"))
})
}
"Extractor with unapplySeq" should {
@@ -211,6 +235,12 @@ object ExtractorComponentSpec extends org.specs2.mutable.Specification
"rich match without binding: ~(IntRange(10, 20), i)" in {
partialFun3(Some("12")) aka "matching" must_== Some(List("range:12"))
}
"rich match with pattern in bindings: ~(IndexOf('/'), a :: b :: c :: _)".
in({
partialFun3(Some("/path/to/file")) aka "matching" must_== Some(
List("IndexOf: /path/to/file", "0", "5", "8"))
})
}
"Extractor with unapplySeq" should {
@@ -250,6 +280,9 @@ sealed trait PartialFunctionTest {
case ~(Regex("# ([A-Z]+).*"), a) List(a)
case str @ ~(Regex("([0-9]+);([a-z]+)"), (a, b)) List(str, b, a)
case str @ ~(Regex("# magic: ([a-z]+).*"), "stop") List(s"Literal: $str")
case str @ ~(IndexOf('/'), a :: b :: c :: _)
List(s"IndexOf: $str", a.toString, b.toString, c.toString)
}
def partialFun2: String List[String] = {
@@ -260,6 +293,9 @@ sealed trait PartialFunctionTest {
case ~(Regex("# ([A-Z]+).*"), a) List(a)
case str @ ~(Regex("([0-9]+);([a-z]+)"), (a, b)) List(str, b, a)
case str @ ~(Regex("# magic: ([a-z]+).*"), "stop") List(s"Literal: $str")
case str @ ~(IndexOf('/'), a :: b :: c :: _)
List(s"IndexOf: $str", a.toString, b.toString, c.toString)
}
/* Anonymous partial function */
@@ -271,30 +307,38 @@ sealed trait PartialFunctionTest {
case ~(Regex("# ([A-Z]+).*"), a) List(a)
case str @ ~(Regex("([0-9]+);([a-z]+)"), (a, b)) List(str, b, a)
case str @ ~(Regex("# magic: ([a-z]+).*"), "stop") List(s"Literal: $str")
case str @ ~(IndexOf('/'), a :: b :: c :: _)
List(s"IndexOf: $str", a.toString, b.toString, c.toString)
}
}
sealed trait MatchTest {
def patternMatching(s: String): List[String] = s match {
case ~(IntRange(5, 10), _) List("5-to-10")
case ~(IntRange(10, 20), i) List(s"range:$i")
case Integer(n) List(s"num-$n")
case ~(Regex("^a.*")) List("no-binding")
case ~(Regex("# ([A-Z]+).*"), a) List(a)
case str @ ~(Regex("([0-9]+);([a-z]+)"), (a, b)) List(str, b, a)
case ~(IntRange(5, 10), _) List("5-to-10")
case ~(IntRange(10, 20), i) List(s"range:$i")
case Integer(n) List(s"num-$n")
case ~(Regex("^a.*")) List("no-binding")
case ~(Regex("# ([A-Z]+).*"), a) List(a)
case str @ ~(Regex("([0-9]+);([a-z]+)"), (a, b)) List(str, b, a)
case str @ ~(Regex("# magic: ([a-z]+).*"), "stop") List(s"Literal: $str")
case str @ ~(IndexOf('/'), a :: b :: c :: _)
List(s"IndexOf: $str", a.toString, b.toString, c.toString)
case x Nil
}
def recursivePatternMatching(s: String): List[String] = s match {
case v v match {
case ~(IntRange(5, 10), _) List("5-to-10")
case ~(IntRange(10, 20), i) List(s"range:$i")
case Integer(n) List(s"num-$n")
case ~(Regex("^a.*")) List("no-binding")
case ~(Regex("# ([A-Z]+).*"), a) List(a)
case ~(IntRange(5, 10), _) List("5-to-10")
case ~(IntRange(10, 20), i) List(s"range:$i")
case Integer(n) List(s"num-$n")
case ~(Regex("^a.*")) List("no-binding")
case ~(Regex("# ([A-Z]+).*"), a) List(a)
case str @ ~(Regex("([0-9]+);([a-z]+)"), (a, b)) List(str, b, a)
case str @ ~(Regex("# magic: ([a-z]+).*"), "stop") List(s"Literal: $str")
case str @ ~(Regex("# magic: ([a-z]+).*"), "stop")
List(s"Literal: $str")
case str @ ~(IndexOf('/'), a :: b :: c :: _)
List(s"IndexOf: $str", a.toString, b.toString, c.toString)
case x Nil
}
}
@@ -333,3 +377,15 @@ sealed case class IntRange(min: Int, max: Int) {
if (i < min || i > max) None else Some(i)
}
}
/** Character index extractor */
sealed case class IndexOf(ch: Char) {
def unapply(v: String): Option[List[Int]] = {
val is = v.foldLeft(0 -> List.empty[Int]) { (st, c)
val (i, l) = st
i + 1 -> { if (c == ch) l :+ i else l }
}._2
if (is.isEmpty) None else Some(is)
}
}

0 comments on commit d6d2a26

Please sign in to comment.