Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
..
Failed to load latest commit information.
src
readme.md

readme.md

Acolyte Scalac plugin

Scala compiler plugin

Match component

Scala pattern matching involves use of either case class or extractor.

When extractor needs parameters to be created, it should be declared as stable identifier before match block. e.g. With regular expression matching:

val Letter = "[a-zA-Z]+".r

"String" match {
  case Letter  true
}

Match component included in this plugin provides syntax ~(extractorFactory[, bindings]) for rich pattern matching.

Consider following extractor, instantiated with one parameter:

case class Regex(e: String) {
  lazy val re = e.r
  def unapplySeq(target: Any): Option[List[String]] = re.unapplySeq(target)
}

Then provided rich syntax can be used as following (see complete example):

str match {
  case ~(Regex("^a.*"))                         1 // no binding

  case ~(Regex("# ([A-Z]+).*"), a)              2 
  // if str == "# BCD123", then a = "BCD"

  case ~(Regex("([0-9]+);([a-z]+)"), (a, b))    3
  // if str == "234;xyz", then a = "234" and b = "xyz"

  case str @ ~(IndexOf('/'), a :: b :: c :: _)  4
  // if there are exactly 3 '/' in str, 
  // matches and assign indexes to a, b and c

  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
}

It will be refactored by plugin, so that required stable identifiers will be available for matching:

val Xtr1 = Regex("^a.*")
// ...
str match {
  case Xtr1  1 // no binding
  // ...
}

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).

val partialFun: String  Int = {
  case ~(Regex("^a.*"))                       1
  case ~(Regex("# ([A-Z]+).*"), a)            2 
  case ~(Regex("([0-9]+);([a-z]+)"), (a, b))  3
  case _                                      4
}
// Or def partialFun: String => Int = ...

It will be refactored as:

val partialFun: String  Int = { str: String 
  val Xtr1 = Regex("^a.*")
  // ...
  str match {
    case Xtr1  1
    // ...
  }
}

Anonymous partial functions are refactored too (see more examples):

def test(s: Option[String]): Option[Int] = s map {
  case ~(Regex("^a.*"))                       1
  case ~(Regex("# ([A-Z]+).*"), a)            2 
  case ~(Regex("([0-9]+);([a-z]+)"), (a, b))  3
  case _                                      4
}

Pattern matching in val statement is enriched in the same way ((see more examples).

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

Such case is refactored as following:

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 }.

SBT usage

Scalac plugin can be used with SBT project, using its compiler plugins support:

autoCompilerPlugins := true

addCompilerPlugin("org.eu.acolyte" %% "scalac-plugin" % "VERSION")

scalacOptions += "-P:acolyte:debug" // Optional

Maven usage

Maven scala plugin supports compiler plugin, so you can do:

<project>
  <!-- ... -->
  <build>
    <!-- ... -->
    <plugings>
      <plugin>
        <groupId>org.scala-tools</groupId>
        <artifactId>maven-scala-plugin</artifactId>
        <!-- ... version -->
        <configuration>
          <!-- ... -->
          <args><!-- Optional: enable debug -->
            <arg>-P:acolyte:debug</arg>
          </args>

          <compilerPlugins>
            <compilerPlugin>
              <groupId>org.eu.acolyte</groupId>
              <artifactId>scalac-plugin_2.10</artifactId>
              <version>VERSION</version>
            </compilerPlugin>
          </compilerPlugins>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Plugin options

There is few option for this plugin.

  • -P:acolyte:debug: Display debug while compiling with (e.g. refactored match code).

Compilation errors

As match component refactors match AST when ~(…, …) is used, then if there is compilation error around that location will be mentioned as /path/to/file.scala#refactored-match-M (with M informational index of refactored match).

If there is an error with given extractor factory, you will get something like:

[error] /path/to/file.scala#refactored-match-M:1: Compilation error.
[error] Error details.
[error] val Xtr1 = B() // generated from ln L, col C

Comment // generated from ln L, col C indicates location in original code, before it gets refactored.

If there result from given extractor factory is not a valid extract, it will raise:

[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

When using ~(Xtractor(params)), following error can be raised if unapply from Xtractor wait at least one argument.

[error] /path/to/file.scala#refactored-match-M:1: not enough patterns for class Clazz offering Xtractor: expected 1, found 0