Scalaでパーサコンビネータ

Scala 2.6.0-RC1 でscala.util.parsing.combinatorが標準パッケージになりました。というわけでリファレンスとちょっとしたサンプルくらしかなかったのだけど、とりあえず書いてみた。

どう書く?org に投稿した、ExcelライクCSVのパーサ。

 1import scala.util.parsing.combinator.{Parsers, ImplicitConversions, ~, mkTilde}
 2import scala.util.parsing.input.CharArrayReader
 3import Character.isISOControl
 4
 5object CSVParser {
 6 trait Base
 7 case class Field(s:String) extends Base {
 8   override def toString = s
 9 }
10 case class Record(fields: List[Field]) extends Base
11 case class File(records :List[Record]) extends Base
12
13 def mkString(cs :List[Any]) = cs.mkString("")
14 class CSVParser extends Parsers {
15   type Elem = Char
16   def notMeta(c:Elem) = c!=',' && c!='\n' && c!='"' && !isISOControl(c)
17
18   lazy val file   = record.*('\n') ^^ File
19   lazy val record = (field|quotedField|nullableField).*(',') ^^ Record
20   lazy val field = chars.+ ^^ {cs => Field(mkString(cs))}
21   lazy val nullableField = chars.* ^^ {cs => Field("")}
22   lazy val quotedField = '"' ~ (charsInQuote|quoteInQuote).* ~ '"' ^^ {cs => Field(mkString(cs))}
23   lazy val charsInQuote = elem("chars in field", _!='"')
24   lazy val quoteInQuote = repN(2, quote) ^^ {cs => '"'}
25   lazy val quote  = '"' ^^ success
26   lazy val chars  = elem("chars", notMeta)
27 }
28}
29
30val data = """
31"aaa","b
32bb","ccc",zzz,"y""Y""y",xxx
33""".trim
34
35(new CSVParser.CSVParser).file(new
36CharArrayReader(data.toCharArray)).map(file => {
37 file.records.map({record =>
38   val fields = record.fields
39   (1 to fields.length).foreach(i => println(i +" => " + fields(i-1)))
40 })
41})

とりあえずこんな感じ。これはダイレクトにParsersクラスを直接継承してるけど、StdTokenParsersってかんじのParserもあるし、StdLexicalってかんじなLexerもあってこれはなかなか。

時間が出来たらもうちょっといじってみよう。よさげな解説してるサイトがあったら是非教えてください。

comments powered by Disqus