基本的なパーサー

プログラミングHaskellより。

ここで、他のパーサーを構築するのに利用する基本的なパーサー三つを定義しよう。

ちょっと本の表記から変えます。

  • return予約語なので、メソッド名をreturn -> succeedにします。

  • String => List[Char]への変換は明示的に行う(Stringにunapplyメソッドがないからパターンマッチが行われてなさそう)*1

// def succeed[T]: T => Parser[T]
// def failure[T]: Parser[T]
// def item: Parser[Char]

def succeed[T](t: T) = (inp: String) => Seq((t, inp))
def failure[T] = (inp: String) => Seq()
def item = (inp: String) => inp.toList match {
  case List() => Seq()
  case x :: xs => Seq((x, xs.toString))
}

こんな感じだろうか.....

パーサーは関数なので、通常の関数適用を使って、パーサーを文字列へ適用できる。 しかし、パーサーの実現方法を抽象化して、独自の適用関数を定義するほうが望ましい

わからない!どうしてそっちのが望ましいんだろう。。

//  def parse[T]: Parser[T] => String => Seq[(T, String)]
def parse[T](p: Parser[T])(inp: String): Seq[(T, String)] = p(inp)

どう振る舞うのかの例を示す。

以下のように書いたScalaプログラムを動かしてみます。

object Main extends App {
  import Parsers._
  println("sample1: " + parse(succeed(1))("abc"))
  println("sample2: " + parse(failure)("abc"))
  println("sample3: " + parse(item)(""))
  println("sample4: " + parse(item)("abc"))
}

object Parsers {
  type Parser[T] = (String) => Seq[(T, String)]

  def succeed[T](t: T) = (inp: String) => Seq((t, inp))
  def failure[T] = (inp: String) => Seq()
  def item = (inp: String) => inp.toList match {
    case List() => Seq()
    case x :: xs => Seq((x, xs.mkString))
  }
  def parse[T](p: Parser[T])(inp: String): Seq[(T, String)] = p(inp)
}

結果。

sample1: List((1,abc))
sample2: List()
sample3: List()
sample4: List((a,bc))

早速心が折れてしまいそうだけどw、少しずつ続けよう。

*1:"hoge".foreach(println)が可能なのは、Predef内でscala.collection.immutable.StringOpsに暗黙的に変換されており、StringOpsがいろいろ持っているため。ここにもunapplyがない。気がする。