水島宏太
 筑波大学第三学群情報学類を卒業したのち、現在は、筑波大学大学院システム情報工学研究科コンピュータサイエンス専攻博士後期課程3年。プログラミング言語や処理系に強い興味を持っている。現在の研究テーマは、プログラミング言語の構文解析アルゴリズム。Scala勉強会を不定期で行うなど、研究の合間にScalaの普及活動を行っている。

 Scalaにはパーザコンビネータライブラリという、構文解析を行うための専用ライブラリが存在します。今回は、前回に説明したHello, World!パーザをより簡潔に書けるように修正していくほか、JSONのサブセットのパーザの実装までを解説します。

Hello, World!パーザを改良してみる

strメソッド - 文字列を直接扱えるように

 前回説明したリスト5のパーザは、プリミティブとして1文字にマッチするパーザしか扱えなかったため、いちいちそれを~でつないでいくのが面倒でした。そこで、str("Hello,World!")とするだけでHello, World!にマッチするパーザを生成できるようにします。次のstrメソッドをHelloWorld objectに追加してみてください(リスト8)。

リスト8●少し簡潔なHello, World!パーザ
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を連結した新しい文字列が解析結果になります。このように、パーザコンビネータでは標準で不足している機能があっても、ユーザーがどんどん新しいメソッドや演算子を継ぎ足していくことが簡単にできます。