Wiki 記法のパーサを作る [Tcl]
今回は手近なところで Tcl を使って Wiki 記法のパーサを作ってみようと思う。
これは以前 Tcl で Wiki 記法を採用したソフトウェアを作成したけどあまり美しくないやりかたでゴリゴリ書いていたのでプログラム的に触りにくいものになってしまった反省というのもある。
Tcl でパーサを作成するのためのライブラリはいくつかあるが、今回は Yeti/Ylex を使う。
http://www.fpx.de/fp/Software/Yeti/
Wiki 記法の文法は以下のとおりとする。
-記事は空行区切りで並べられたブロック要素群である。
-「*」で始まるブロックは見出しである。レベル6見出しまで対応
-「-」で始まるブロックは順序なしリストである。
-「+」で始まるブロックは順序ありリストである。
-それ以外のブロックは段落である。
まずは字句解析器を作る。Ylex では
1. yeti::ylex をインスタンス化してスキャナジェネレータを作る
2. add メソッドを使ってスキャナを定義
3. dump メソッドを eval するとスキャナのクラスができる
4. スキャナクラスをインスタンス化するとスキャナができる
という、ちょっとややこしい手順を踏む。
set sg [yeti::ylex #auto -name wikiscan]
$sg add {
{\+([^\n]*\n)} { return [list PLUS $1] }
{-([^\n]*\n)} { return [list MINUS $1] }
{(\*{1,6})([^\n]*\n)} { return [list ASTERISK[string length $1] $2] }
{[^\n]+\n} { return [list LINE $yytext] }
{\n} { return BLANK_LINE }
}
eval [$sg dump]; delete object $sg
set scanner [wikiscan #auto]
基本的に行ベースでトークン化している。アスタリスクで始まる行はちょっとズル?をして1行にまとめた。
このコードでは sg がスキャナジェネレータで、wikiparse がスキャナのクラスで、scanner がスキャナである。
次は Yeti を使った構文解析。これも Ylex と同様の遠まわしなやり方になる。
set pg [yeti::yeti #auto -name wikiparse]
$pg add {
start {BLOCKS BLANK_LINES} {return $1}
BLANK_LINES {BLANK_LINE} {}
BLANK_LINES {BLANK_LINES BLANK_LINE} {}
BLOCKS {} {}
BLOCKS {BLOCK} {return $1}
BLOCKS {BLOCKS BLANK_LINES BLOCK} {return $1$3}
BLOCK {P} {return "<p>$1</p>\n"}
BLOCK {H1} {return "<h1>$1</h1>\n"}
BLOCK {H2} {return "<h2>$1</h2>\n"}
BLOCK {H3} {return "<h3>$1</h3>\n"}
BLOCK {H4} {return "<h4>$1</h4>\n"}
BLOCK {H5} {return "<h5>$1</h5>\n"}
BLOCK {H6} {return "<h6>$1</h6>\n"}
BLOCK {OL} {return "<ol>$1</ol>\n"}
BLOCK {UL} {return "<ul>$1</ul>\n"}
P {LINES} {return $1}
H1 {ASTERISK1} {return $1}
H1 {ASTERISK1 LINES} {return $1$2}
H2 {ASTERISK2} {return $1}
H2 {ASTERISK2 LINES} {return $1$2}
H3 {ASTERISK3} {return $1}
H3 {ASTERISK3 LINES} {return $1$2}
H4 {ASTERISK4} {return $1}
H4 {ASTERISK4 LINES} {return $1$2}
H5 {ASTERISK5} {return $1}
H5 {ASTERISK5 LINES} {return $1$2}
H6 {ASTERISK6} {return $1}
H6 {ASTERISK6 LINES} {return $1$2}
OL {OLI} {return $1}
OL {OL OLI} {return $1$2}
OLI {PLUS} {return "<li>$1</li>\n"}
OLI {PLUS LINES} {return "<li>$1$2</li>\n"}
UL {ULI} {return $1}
UL {UL ULI} {return $1$2}
ULI {MINUS} {return "<li>$1</li>\n"}
ULI {MINUS LINES} {return "<li>$1$2</li>\n"}
LINES {LINE} {return $1}
LINES {LINE LINES} {return $1$2}
}
eval [$pg dump]; delete object $pg
set parser [wikiparse #auto -scanner $scanner]
pg がパーサジェネレータで wikiparse がパーサのクラスで parser がパーサである。こっちは H1 から H6 までをまとめる方法が思いつかなかったので愚直に書いた。
実際にパーシングを行うには以下のようにする。
$scanner start {
*見出し1
段落
**見出し2
-リストA
-リストB
+リスト1
+リスト1
}
$parser reset
puts [$parser parse]
delete object $parser
実行結果は以下のとおり。ちゃんとできた。
<h1>見出し1 </h1> <p>段落 </p> <h2>見出し2 </h2> <ul><li>リストA </li> <li>リストB </li> </ul> <ol><li>リスト1 </li> <li>リスト1 </li> </ol>
コメント 0