kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Extractorを使った正規表現ライブラリの簡易ラッパー

Scalaの正規表現ライブラリは、Java正規表現ライブラリのラッパーになっていて、Javaのものよりもだいぶ使いやすくはなっているが、パターンマッチに使う時は、以下のように必ずvalなどでいったん変数に代入しなければならないという欠点がある。

scala> val numPat = """[0-9]+""".r
numPat: scala.util.matching.Regex = [0-9]+

scala> "123" match { case numPat() => println("matched") }
matched

そこで、簡単な場合には、パターンにいちいち名前を付けずに済むようなライブラリをExtractorを使って書いてみた。これを使うと、たとえば上のコードは次のように書ける:

scala> ("123", """[0-9]+""") match { case Success() => println("matched") }
matched

他にも、部分一致やプレフィクスに対するマッチを行うオプション、文字列から整数や浮動小数点数への変換を行うExtractorも用意してあり、以下のように書くこともできる。

scala> ("abc123", """([0-9]+)""", Part) match {
     |   case Success(PInt(num)) => println(num + 1)
     | }
124

scala> ("1.23abc", """([0-9]+.[0-9]+)""", Pre) match {
     |   case Success(PFloat(num)) => println(num - 2)
     | }
-0.77

コードは、以下。まあ、何が言いたかったかというと、Extractorは使いようによっては結構便利なので、色々応用例を考えてみると良いのではないかということ。

object RegexPatterns {
  import scala.util.matching._
  object PFloat {
    def unapply(s: String): Option[Float] = try {
      Some(s.toFloat)
    } catch { case _:NumberFormatException => None }
  }
  object PInt {
    def unapply(s: String): Option[Int] = try {
      Some(s.toInt)
    } catch { case _:NumberFormatException => None }
  }
  abstract sealed class MatchingMode
  case object Pre extends MatchingMode
  case object Part extends MatchingMode
  case object All extends MatchingMode
  object Success {
    def unapplySeq(arg: (String, String, MatchingMode)): Option[List[String]] = {
      val (str, pat) = (arg._1, arg._2.r)
      arg._3 match  {
        case Pre =>
          for(matched <- pat findPrefixMatchOf str)
            yield (1 to matched.groupCount) map (matched group _) toList
        case Part =>
          for(matched <- pat findFirstMatchIn str)
            yield (1 to matched.groupCount) map (matched group _) toList
        case All => pat.unapplySeq(str)
      }
    }
    def unapplySeq(arg: (String, String)): Option[List[String]] =
      unapplySeq((arg._1, arg._2, All))
  }
}