30 August, 2008

[Scala] How to Use Combinator Parser (3)

Scala の Parser Combinator の使い方の勉強 (続き)

 今回は小ネタ。

 例えばXMLとかでタグが対応していたりとか、(La)TeXのenvironmentなどの¥bigin{xxx}...¥end{xxx}のように、ある範囲の最初と最後に同じ文字列が出てくることを要請したい場合。こんな感じで実現出来る。

 例えばXML風のタグの対応を実装する場合、

def tagged:Parser[Tagged] =
"<"~>ident~">"~tkn~"</"~ident<~">" ^? {
case i~_~t~_~j if i==j => Tagged(i,t)
}

 a~>b, c<~d というのは、a~b, c~d と同じ様なものだが、a, d の結果を使用しない場合に使えるscala.util.parsing.combinator.Parsers.Parserのメソッド。今回の場合は最初の"<"と最後の">"はマッチさせても後で使わないので。但し、~と違って、a~b~c~... と繋いで結果のcaseを作れないので、"</"とかは~で前後を繋いで、caseでは_を使って結果を使わない事を示してみた。
 ^? というのは ^^ と同じ様なものだが、^^ は case にマッチする事が必要。^? の場合、caseのマッチで失敗した場合にはパース失敗と出来る。今回は i!=j になる場合は失敗させるため、^? を使う。

 全体のコードはこんな感じ。(...しかし不等号をblogに書くのはいちいちエスケープするのが面倒だった...)

package test;

import scala.util.parsing.combinator._

abstract class Tkn
case class Lit(s:String) extends Tkn
case class Tagged(tag:String, t:Tkn) extends Tkn
object Test3 extends JavaTokenParsers {
def tagged:Parser[Tagged] =
"<"~>ident~">"~tkn~"</"~ident<~">" ^? {
case i~_~t~_~j if i==j => Tagged(i,t)
}
def tkn:Parser[Tkn] =
( tagged
| ident ^^ {case s => Lit(s)}
)
def main(args:Array[String]) {
val s = "<aaa><bbb>ccc</bbb></aaa>"
println(s)
println(parse(tagged, s))
}
}

結果は下記の様な感じ

結果1
<aaa><bbb>ccc</bbb></aaa>
[1.26] parsed: Tagged(aaa,Tagged(bbb,Lit(ccc)))

---
結果2
<aaa><bbb>ccc</bbbb></aaa>
[1.21] failure: Constructor function not defined at ((((bbb~>)~Lit(ccc))~</)~bbbb)

<aaa><bbb>ccc</bbbb></aaa>
---
結果3
<aaa><bbb>ccc</bbb></aaaa>
[1.27] failure: Constructor function not defined at ((((aaa~>)~Tagged(bbb,Lit(ccc)))~</)~aaaa)

<aaa><bbb>ccc</bbb></aaaa>

No comments: