パーサコンビネータ再び
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を使っているのが心残り