Permalink
Browse files

[scalac-plugin] Match component specs

  • Loading branch information...
cchantep
cchantep committed Feb 23, 2014
1 parent b4d565d commit b5994bd1773f63e7c04eccc2b24b6378c10b8f4d
@@ -63,19 +63,17 @@ trait ScalaCompiler {
javacOptions in Test ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
compile in Test <<= (compile in Test).dependsOn(clean in Test).dependsOn(
packageBin in (plugin, Compile)/* make sure plugin.jar is available */),
- scalacOptions <++= (version in ThisBuild).
+ scalacOptions in Test <++= (version in ThisBuild).
zip(baseDirectory in (plugin, Compile)).
zip(name in (plugin, Compile)) map { d =>
val ((v, b), n) = d
val j = b / "target" / "scala-2.10" / "%s_2.10-%s.jar".format(n, v)
- Seq("-feature", "-deprecation",
+ Seq("-feature", "-deprecation", "-P:acolyte:debug",
"-Xplugin:%s".format(j.getAbsolutePath))
},
resolvers += "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/snapshots/",
- autoCompilerPlugins := true,
- addCompilerPlugin("org.eu.acolyte" %% "scala-compiler-plugin" % "1.0.14"),
- libraryDependencies ++= Seq("org.specs2" %% "specs2" % "2.3.2"),
+ libraryDependencies ++= Seq("org.specs2" %% "specs2" % "2.3.2" % "test"),
publishTo := None).dependsOn(plugin)
}
@@ -1,4 +1,4 @@
<plugin>
- <name>rich-match</name>
- <classname>acolyte.MatchPlugin</classname>
+ <name>acolyte</name>
+ <classname>acolyte.AcolytePlugin</classname>
</plugin>
@@ -4,13 +4,24 @@ import scala.tools.nsc.Global
import scala.tools.nsc.plugins.{ Plugin, PluginComponent }
import scala.tools.nsc.transform.Transform
-class MatchPlugin(val global: Global) extends Plugin {
- val name = "rich-match"
- val description = "Rich or refactored pattern matching"
+class AcolytePlugin(val global: Global) extends Plugin {
+ val name = "acolyte"
+ val description = "Syntax extensions: rich pattern matching"
val components = List[PluginComponent](MatchComponent)
+ var debug: Boolean with NotNull = false
+
+ override def processOptions(options: List[String], error: String Unit) {
+ for (o options) {
+ if (o == "debug") debug = true
+ }
+ }
+
+ override val optionsHelp: Option[String] = Some(
+ " -P:acolyte:debug Enable debug")
+
private object MatchComponent extends PluginComponent with Transform {
- val global: MatchPlugin.this.global.type = MatchPlugin.this.global
+ val global: AcolytePlugin.this.global.type = AcolytePlugin.this.global
override val runsRightAfter = Some("parser")
override val runsAfter = runsRightAfter.toList
override val runsBefore = List[String]("typer")
@@ -21,7 +32,7 @@ class MatchPlugin(val global: Global) extends Plugin {
object MatchTransformer extends global.Transformer {
import scala.collection.mutable.ListBuffer
import global.{
- abort,
+ //abort,
reporter,
Block,
CaseDef,
@@ -32,8 +43,15 @@ class MatchPlugin(val global: Global) extends Plugin {
}
override def transform(tree: Tree): Tree = tree match {
- case m @ Match(_, _) refactorMatch(m)
- case _ super.transform(tree)
+ case m @ Match(_, _) {
+ val richMatch = refactorMatch(m)
+
+ if (debug) reporter.info(m.pos,
+ s"Rich Match refactored: ${global show richMatch}", true)
+
+ richMatch
+ }
+ case _ super.transform(tree)
}
val tildeTerm = global.newTermName("$tilde")
@@ -52,9 +70,9 @@ class MatchPlugin(val global: Global) extends Plugin {
val vds = ListBuffer[ValDef]()
val cds = cs.map {
- case ocd @ CaseDef(Apply(Ident(it), xt), g, by) if (
+ case ocd @ CaseDef(Apply(Ident(it), x), g, by) if (
it == tildeTerm)
- (xt.headOption, xt.tail) match {
+ (x.headOption, x.tail) match {
case (Some(xt @ Apply(ex, xa)), Apply(_, ua) :: Nil)
val (vd, cd) = caseDef(vds.size, ocd.pos, xt.pos, ex, xa,
ua, g, by)
@@ -73,19 +91,17 @@ class MatchPlugin(val global: Global) extends Plugin {
vds += vd
cd
- /*
- case (Some(xt @ Apply(ex, xa)),
- Literal(Constant(())) :: Nil) ⇒
- val (vd, cd) = caseDef(vds.size, ocd.pos, xt.pos, ex, xa,
- List(Literal(Constant(()))), g, by)
- vds += vd
- cd
- */
+ case (Some(xt @ Apply(ex, xa)), Nil)
+ // no binding
+ val (vd, cd) = caseDef(vds.size, ocd.pos, xt.pos, ex, xa,
+ Nil, g, by)
+ vds += vd
+ cd
case _
reporter.error(ocd.pos, "Invalid ~ pattern")
- abort("Invalid ~ pattern")
-
+ //abort("Invalid ~ pattern"
+ ocd
}
case cd cd
@@ -96,7 +112,8 @@ class MatchPlugin(val global: Global) extends Plugin {
}
case _
reporter.error(orig.pos, "Invalid Match")
- abort("Invalid Match")
+ //abort("Invalid Match")
+ orig
}
@inline private def caseDef[T](i: Int, cp: Position, xp: Position, ex: Tree, xa: List[Tree], ua: List[Tree], g: Tree, b: Tree): (ValDef, CaseDef) = {
@@ -124,30 +141,13 @@ class MatchPlugin(val global: Global) extends Plugin {
val vdp = xp.withPoint(0).withSource(new BatchSourceFile(file, vdc), 0)
- /*
-[error] /path/to/file.scala#refactored-match-M:1: Compilation error.
-[error] Error details.
-[error] val Xtr1 = B() // generated from ln L, col C
- */
-
// CaseDef
- /*
- val pat = ua match {
- case Literal(Constant(())) :: Nil ⇒ Apply(Ident(xn), Nil)
- case _ ⇒ Apply(Ident(xn), ua)
- }
- */
val pat = Apply(Ident(xn), ua)
val cd = CaseDef(pat, g, b)
val cdc =
s"${show(cd)} // generated from ln ${cp.line}, col ${cp.column - 5}"
val cdp = cp.withPoint(0).withSource(new BatchSourceFile(file, cdc), 0)
- /*
-[error] /path/to/file.scala#refactored-match-M:1: value Xtr0 is not a case class constructor, nor does it have an unapply/unapplySeq method
-[error] case Xtr1((a @ _)) => Nil // generated from ln L, col C
- */
-
(atPos(vdp)(vd), atPos(cdp)(cd))
}
}
View
@@ -0,0 +1,50 @@
+# Acolyte Scala compiler plugin
+
+## Match component
+
+Scala pattern matching: case class | extractor
+Extract created with arg -> Stable identifier (ex regexp)
+-> ~(..., ...)
+
+```scala
+case class Regex(e: String) {
+ lazy val re = e.r
+ def unapplySeq(target: Any): Option[List[String]] = re.unapplySeq(target)
+}
+```
+
+```scala
+str match {
+ case ~(Regex("^a.*")) 1
+ case ~(Regex("# ([A-Z]+).*"), a) 2
+ case ~(Regex("([0-9]+);([a-z]+)"), (a, b)) 3
+ case _ 4
+}
+```
+
+## Plugin options
+
+-P:acolyte:debug
+
+## Compilation errors
+
+```
+[error] /path/to/file.scala#refactored-match-M:1: Compilation error.
+[error] Error details.
+[error] val Xtr1 = B() // generated from ln L, col C
+```
+
+```
+[error] /path/to/file.scala#refactored-match-M:1: value Xtr0 is not a case class constructor, nor does it have an unapply/unapplySeq method
+[error] case Xtr1((a @ _)) => Nil // generated from ln L, col C
+```
+
+## SBT usage
+
+```scala
+autoCompilerPlugins := true
+
+addCompilerPlugin("org.eu.acolyte" %% "scala-compiler-plugin" % "1.0.14")
+
+scalacOptions += "-P:acolyte:debug"
+```
@@ -0,0 +1,87 @@
+package acolyte
+
+object MatchComponentSpec
+ extends org.specs2.mutable.Specification with MatchTest {
+
+ "Match component" title
+
+ "Basic Pattern matching" should {
+ "match extractor: Integer(n)" in {
+ patternMatching("456") aka "matching" mustEqual List("num-456")
+ }
+
+ "not match" in {
+ patternMatching("@") aka "matching" mustEqual Nil
+ }
+ }
+
+ "Extract with unapply" should {
+ "rich match without binding: ~(IntRange(5, 10))" in {
+ patternMatching("7") aka "matching" mustEqual List("5-to-10")
+ }
+
+ "rich match without binding: ~(IntRange(10, 20), i)" in {
+ patternMatching("12") aka "matching" mustEqual List("range:12")
+ }
+ }
+
+ "Extractor with unapplySeq" should {
+ "rich match without binding: ~(Regex(re))" in {
+ patternMatching("abc") aka "matching" mustEqual List("no-binding")
+ }
+
+ "rich match with one binding: ~(Regex(re), a)" in {
+ patternMatching("# BCD.") aka "matching" mustEqual List("BCD")
+ }
+
+ "rich match with several bindings: ~(Regex(re), (a, b))" in {
+ patternMatching("123;xyz") aka "matching" mustEqual List("xyz", "123")
+ }
+ }
+}
+
+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 ~(Regex("([0-9]+);([a-z]+)"), (a, b)) List(b, a)
+ case x Nil
+ }
+}
+
+/**
+ * Extractor factory based on regular expression.
+ *
+ * {{{
+ * val res: Boolean = rmatch {
+ * ~("abc") match {
+ * case ~(Regex("(.+)b([a-z]+)$"), (x, y)) => true // x == "a", y == "b"
+ * case _ => false
+ * }
+ * }
+ * }}}
+ */
+sealed case class Regex(e: String) {
+ lazy val re = e.r
+
+ /** See [[scala.util.matching.Regex.unapplySeq]]. */
+ def unapplySeq(target: Any): Option[List[String]] = re.unapplySeq(target)
+}
+
+/** Integer extractor */
+object Integer {
+ val re = "^[0-9]+$".r
+ def unapply(v: String): Option[String] =
+ re.unapplySeq(v).fold[Option[String]](None)(_ Some(v))
+}
+
+/** Integer range extractor */
+sealed case class IntRange(min: Int, max: Int) {
+ def unapply(v: String): Option[Int] = Integer.unapply(v) flatMap { s
+ val i = s.toInt
+ if (i < min || i > max) None else Some(i)
+ }
+}
@@ -1,63 +0,0 @@
-package acolyte
-
-object MatchPluginSpec extends org.specs2.mutable.Specification {
- "Match plugin" title
-
- case class A(e: String) {
- lazy val re = e.r
- def unapplySeq(target: Any): Option[List[String]] = re.unapplySeq(target)
- }
-
- case class B(e: String)
-
- "X" should {
- "Y" in {
- "x" match {
- case ~(B(".*"), a) => Nil
- case _ => Nil
- }
-
- true must beTrue
- }
- }
-
- /*
- "Regex extractor" should {
- "match without binding" in {
- regex("abc") aka "matching" mustEqual Nil
- }
-
- "match with one binding" in {
- regex("# BCD.") aka "matching" mustEqual List("BCD")
- }
-
- "match with several bindings" in {
- regex("123;xyz") aka "matching" mustEqual List("xyz", "123")
- }
-
- "not match" in {
- regex("456") aka "matching" mustEqual List("fallback")
- }
- }
- */
-}
-
-/*
-sealed trait RMatchTest {
- import acolyte.macros.{ Regex, Xt, rmatch }
-
- object XX {
- def unapply(v: Any): Option[String] = ???
- }
-
- def regex(s: String): List[String] = rmatch {
- s match {
- case Xt(Regex("^a.*"), ()) ⇒ Nil
- case XX(".*c$") ⇒ Nil
- case Xt(Regex("# ([A-Z]+).*"), a) ⇒ List(a)
- case Xt(Regex("([0-9]+);([a-z]+)"), (a, b)) ⇒ List(b, a)
- case x ⇒ List(x)
- }
- }
-}
- */
Oops, something went wrong.

0 comments on commit b5994bd

Please sign in to comment.