Scalaにはパーザコンビネータライブラリという、構文解析を行うための専用ライブラリが存在します。今回は、前回に説明したHello, World!パーザをより簡潔に書けるように修正していくほか、JSONのサブセットのパーザの実装までを解説します。
Hello, World!パーザを改良してみる
strメソッド - 文字列を直接扱えるように
前回説明したリスト5のパーザは、プリミティブとして1文字にマッチするパーザしか扱えなかったため、いちいちそれを~でつないでいくのが面倒でした。そこで、str("Hello,World!")とするだけでHello, World!にマッチするパーザを生成できるようにします。次のstrメソッドをHelloWorld objectに追加してみてください(リスト8)。
import scala.util.parsing.combinator.Parsers
import scala.util.parsing.input.CharSequenceReader
object HelloWorld extends Parsers {
type Elem = Char
val EOF = elem("EOF", _ == CharSequenceReader.EofCh)
def str(s: String): Parser[String] =
if(s.length > 0)
elem(s.charAt(0)) ~ str(s.substring(1)) ^^ { case a ~ b => a + b }
else success("")
val messageAll: Parser[Any] = message ~ EOF
lazy val message: Parser[Any] =
str("Hello, World!") ~ (message | success(null))
}
import HelloWorld._
messageAll(new CharSequenceReader(args(0))) match {
case Success(_, _) => println("Hello, World!")
case _ => println("Error!")
}
elem('H') ~ elem('e') ~ ...と書いていた部分がだいぶ簡潔になっているのがわかると思います。では、strメソッドの定義を見て行きましょう。strメソッドのシグニチャは
def str(s: String): Parser[String]
であり、文字列を引数にとって、その文字列にマッチするパーザを返します。前回述べたように、文字列にマッチするパーザはelemメソッドと~演算子の組み合わせで作ることができるので、それを利用してstrメソッドを再帰的に定義しています。
ここで、^^という演算子が出てきます。これは、パーザと関数を引数に取り、左辺のパーザの解析結果を右辺の関数で加工して新しい解析結果にするパーザを生成する演算子です。~演算子はParser[A]とParser[B]を引数に取り、Parser[A ~ B]という型を返しますが、A ~ B型の結果の中身を取り出すには、パターンマッチを利用して、{ case a ~ b => a + b }のようにする必要があります。ここでは、aは最初の1文字でbは残りの文字列なので、aとbを連結した新しい文字列が解析結果になります。このように、パーザコンビネータでは標準で不足している機能があっても、ユーザーがどんどん新しいメソッドや演算子を継ぎ足していくことが簡単にできます。