パーサコンビネータ再び

inforno :: Scalaでスタック指向言語をサクッと実装する
という、2.6.0以降に導入された新しいパーサコンビネータを使った記事があったのに触発されて、
再びパーサコンビネータにチャレンジしてみる。


題材は、以前と同じ「CSVパーサ」


とりあえず、上記サイトを参考にレクサまでを書いてみる。
(書いたというかほとんどサンプルのまま)

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.syntactical._
import scala.util.parsing.combinator.lexical._
import scala.util.parsing.input.CharArrayReader.EofCh

class Lexer extends StdLexical with ImplicitConversions {
  override def token:Parser[Token] =
    ( string        ^^  StringLit
    | '-' ~> number ^^  { case num => NumericLit("-" + num) }
    | number        ^^  NumericLit
    | EofCh         ^^^ EOF
    | delim
    | '\"' ~> failure("Unterminated string")
    | anystring ^^ StringLit
    )

  def anystring = rep(charSeq | chrExcept(EofCh, ',', '\n'))            ^^ {case x => mkS(x)}
  def string    = '\"' ~> rep(charSeq | chrExcept('\"', EofCh)) <~ '\"' ^^ {case x => mkS(x)}
  def number    = zero | (nonzero ~ rep(digit)                          ^^ {case x ~ y => mkS(x :: y)})

  def charSeq:Parser[String] = 
    ( '\\' ~ '\"' ^^^ "\""
     |'\\' ~ '\\' ^^^ "\\"
     |'\\' ~ '/'  ^^^ "/"
     |'\\' ~ 'b'  ^^^ "\b"
     |'\\' ~ '0'  ^^^ ""
     |'\\' ~ 'f'  ^^^ "\f"
     |'\\' ~ 'n'  ^^^ "\n" 
     |'\\' ~ 'r'  ^^^ "\r"
     |'\\' ~ 't'  ^^^ "\t"
    )
  
  def zero:Parser[String] = '0' ^^^ "0"
  def nonzero             = elem("nonzero digit", d => d.isDigit && d != '0')
  
  def mkS[A](seq:Seq[A])  = seq mkString ""
}

で、使ってみる。

object MyLexer {
  def main(args:Array[String]) {
    val lexical = new Lexer
    lexical.delimiters ++= List(",")

    import lexical.{NumericLit, StringLit}	  

    val scanner = new lexical.Scanner("""
1,2,3,4,-5
aiueo,abc,def
"1","2","3,3","4","5"
"hogehoge,[\"],[\\],[\b],[\0],(\f),[\n],(\r),[\t]
piyopiyo","1行目
2行目
3行目",123テストabc,"errorにならない・・・まあいいか,piyopiyo
""")
  
   var scn = scanner
   while(! scn.atEnd) {
     scn.first match {
     case t:StringLit => println(t)
     case t:NumericLit => println(t)
     case _ => ;
     }	
     scn = scn.rest
   } 
  }
}

結果は、

1
2
3
4
-5
"aiueo"
"abc"
"def"
"1"
"2"
"3,3"
"4"
"5"
"hogehoge,["],[\],],[],(
                        ),[
),[	]
piyopiyo"
"1行目
2行目
3行目"
123
"テストabc"
""errorにならないなぁ・・・まあいいか"
"piyopiyo"

今日はここまで

今後の課題は以下のとおり

  • 改行デリミタを導入したい(新しいトークンを定義することになる?)
  • 正式にいうと「"hogehoge」の様なフィールドはエラーにすべき?
  • 「^^」と「^^^」の違いをいまいちきちんと理解していない気がする。
  • scannerから読み出してくる部分でvarを使っているのが心残り