プログラミングHaskell 8.6 パーサーの部品

述語pを満足する一文字用のパーサー sat p を定義する。

def sat(p: Char => Boolean): Parser[Char] = {
    item.flatMap(x => p(x) match {
      case true => succeed(x)
      case false => failure
    })
  }

これを使えば、数字・アルファベットとかのパーサーも。

  def digit: Parser[Char] = sat(_.isDigit)
  def lower: Parser[Char] = sat(_.isLower)
  def upper: Parser[Char] = sat(_.isUpper)
  def letter: Parser[Char] = sat(_.isLetter)
  def alphanum: Parser[Char] = sat(_.isLetterOrDigit)
  def char(x: Char): Parser[Char] = sat(_ == x)

実行。

  println("sample9: " + parse(digit)("123"))
  println("sample10: " + parse(digit)("abc"))
  println("sample11: " + parse(char('a'))("abc"))
  println("sample12: " + parse(char('a'))("123"))
sample9: List((1,23))
sample10: List()
sample11: List((a,bc))
sample12: List()

パーサーcharを使って string xsを定義。

 def string(s: String): Parser[String] = s.toList match {
    case Nil => succeed("")
    case x :: xs => for {
      _ <- char(x)
      _ <- string(xs.mkString)
    } yield (x :: xs).mkString
  }

実行。

  println("sample13: " + parse(string("abc"))("abcdef"))
  println("sample14: " + parse(string("abc"))("ab1234"))
sample13: List((abc,def))
sample14: List()

次の二つのパーサー many pmany1 p は、パーサーpを失敗するまでできるだけ多く適用し、適用が成功した結果をリストにして返す。

相互再帰になってる。自分で発想できなそう・・・・

  def many1[A](p: Parser[A]): Parser[List[A]] = for {
    v <- p
    vs <- many(p)
  } yield (v :: vs)
  def many[A](p: Parser[A]): Parser[List[A]] = many1(p) +++ succeed(Nil)

実行。

  println("sample15: " + parse(many(digit))("123abc"))
  println("sample16: " + parse(many(digit))("abcdef"))
  println("sample17: " + parse(many1(digit))("abcdef"))
sample15: List((List(1, 2, 3),abc))
sample16: List((List(),abcdef))
sample17: List()

パーサー manymany1を使うと、「識別子(変数名)」のパーサーを定義できる。識別子は、小文字で始まり、0個以上のアルファベット文字か数字文字が続く。数字文字が一つ以上繰り返される「自然数」のパーサーや、空白文字やタブ文字、あるいは改行文字が一つ以上繰り返される「空白」のパーサーも定義できる。

本文では readって関数が使われてるけど、これはキャストする関数っぽい。この(本文の)場合だと、型推論からIntってことがわかるからキャスト先は省略されてるのかな。。Scalaではxsは List[Char]だから、 mkStringでStringにして、 toIntでキャスト。

  def ident: Parser[String] = for {
    x <- lower
    xs <- many(alphanum)
  } yield (x :: xs).mkString

  def nat: Parser[Int] = for {
    xs <- many1(digit)
  } yield xs.mkString.toInt

  def space: Parser[Unit] = for {
    _ <- many(sat(_.isSpaceChar))
  } yield(())

実行。

  println("sample18: " + parse(ident)("abc def"))
  println("sample19: " + parse(nat)("123 abc"))
  println("sample20: " + parse(space)(" abc"))
sample18: List((abc, def))
sample19: List((123, abc))
sample20: List(((),abc))

だんだんわかってきた!きがする!!!many, many1すごい。