markdown のサブセット mini-mark のパーサを実装した話。
パーサを実装といっても、PEGで表記したものを parboiled で実装しただけです。
PEGという文法があり、これを定義しておけばパーサになる(ただしPEGを解釈して実際のパーサに変換してくれる何かしらのツール等が必要ですが)という世界らしい。(よくわかっていません。) ここでは、PEGの Java 実装の一つであるらしい parboiled を使ってパーサを実装してみます。
こんな感じのマークアップされたテキストをパースしたい。 説明の都合上これを mini-mark と呼びます。
# h1 header
This is a paragraph.
## h2 header
This is another paragraph.
厳密な記述ではないですが、だいたいこんな感じでしょうか。
Doc ← Block*
Block ← NewLine / HeadLine / ParaLine
HeadLine ← '#'+ Spaces ParaLine
ParaLine ← Inline NewLine
Inline ← \n以外のすべての文字*
NewLine ← '\n'
Spaces ← ' '+
以下を念頭にPEG表記をparboiledに変換します。
記述方法の詳細情報はこちら
https://github.com/sirthias/parboiled/wiki/Rule-Construction-in-Java
完成したコードは以下の通り。 PEG表記と比べて冗長さは増えましたが、ほぼそのまま機械的に置きかえるだけで済むのは素敵です。
MiniMarkParser.groovy
@BuildParseTree
class MiniMarkParser extends BaseParser<Object> {
Rule Doc() {
ZeroOrMore(Block())
}
Rule Block(){
FirstOf( NewLine(), HeadLine(), ParaLine() )
}
Rule HeadLine(){
Sequence( OneOrMore('#'), Spaces(), ParaLine() )
}
Rule ParaLine(){
Sequence( Inline(), NewLine() )
}
Rule Inline(){
ZeroOrMore(NoneOf('\n'))
}
Rule NewLine(){
String('\n')
}
Rule Spaces(){
OneOrMore(' ')
}
}
パース対象となる文字列の文法だけ定義して、あとは機械的な作業でパーサーの実装がつくることができれば、実装が楽なだけでなく、保守の面から考えてもとても魅力的です。