Kern のプリミティブパーサ
Kern にもプリミティブなパーサがあって、これらを組み合わせていくことで大きなパーサを作るというのが関数型らしいパーサの作り方なわけですが。 それらをちょっと見ていきます。 Kern けっこうかわいいわあ。
(use 'blancas.kern.core)
return は常に成功し、与えられた値を返す。
doc.core> (run (return "foo") "xyz") "foo" nil
fail は与えられたエラーメッセージとともに失敗する。
doc.core> (:ok (parse (fail "foo") "xyz")) false doc.core> (run (fail "this is bad") "xyz") line 1 column 1 this is bad nil
satisfy は与えられた述語を入力の最初の文字に適用し、それによって成功か失敗かが決まる。
doc.core> (run (satisfy #(= \x %)) "xyz") \x nil
any-char は入力された文字を返す。
doc.core> (run any-char "x") \x nil
letter は大文字小文字のどちらも成功する。
doc.core> (run letter "z") \z nil
lower, upper はそのまんま。
doc.core> (run lower "a") \a nil doc.core> (run upper "M") \M nil
white-space は空白文字を受け付ける。
doc.core> (run white-space "\t") \tab nil
space はスペースだけ。 tab もまあそんなかんじ。
doc.core> (run space " ") \space nil
digit は \0 から \9 までの数字を受け付ける。 hex-digit はそれに加えて \A から \F まで。 oct-digit は \0 から \7 まで。
doc.core> (run digit "9") \9 nil
alpha-num は英数字。
doc.core> (run (many alpha-num) "9A") [\9 \A] nil
sym* は特定の文字だけ。 sym- は大文字小文字関係なく。ただし結果として得る文字は sym- に渡された文字。
doc.core> (run (sym* \x) "x") \x nil
token* は Haskell でいう string パーサ。複数のシーケンスが渡された場合、どれかが成功するまで試され続ける。 token- は大文字小文字関係なく。
doc.core> (run (token* "foo" "bar" "baz") "bazaar") "baz" nil
word は token と同じようなものだけれど、許可しない終端を設定できる。 word- は大文字小文字関係なく。
doc.core> (run (word* (one-of* "|-/") "foobar")) "foobar" nil doc.core> (run (word* (one-of* "|-/") "football" "foobar") "foobar*") "foobar" nil doc.core> (run (word* (one-of* "|-/") "foobar") "foobar/") line 1 column 8 unexpected / expecting end of foobar nil
one-of は与えられた文字列のどれかの文字であれば受け付ける。 none-of はその逆。
doc.core> (run (one-of* "xyz") "zap") \z nil
new-line* は \n を受け付ける。 eof はもし入力が空なら成功する。
field* は与えられた文字列で区切られる文字列をパースする。
doc.core> (run (field ",;") "California,") "California" nil
split-on は空白か与えられた文字列で区切られる文字列を分割する。
doc.core> (run (split-on ",./") "Now, is the time") ["Now" "is the time"] nil
split は空白で区切られたテキストを分割する。
doc.core> (run split "Now is the time") ["Now" "is" "the" "time"]
Clojure の パーサコンビネータライブラリ Kern
Clojure でパースするとき、みんなどうしてるんだろう。 Parsec のようなものがあればいいのに〜、と思って探してみたところ、この Kern が一番よさげだった。
特徴
- 状態モナドベースのコンビネータ群
- C, Java, Haskell, Shell の構文をサポート
- パースと式の評価をサポート
- 正確で詳しいエラーメッセージ
- エラーメッセージは簡単に多言語化可能
- パーサの内部状態にクライアントコードからアクセス可能
- サンプルあり
セットアップ
leiningen がインストールされているなら、 project.clj の :dependencies に
[org.blancas/kern "0.6.1"]
で、コード中に
(use 'blancas.kern.core)
Haskell の Parsec っぽい
HaskellのParsecですねという感じ。というか記号とかもそのまんま。 ただちょいちょい違うところがあるかなあという印象。
例えば数字のパース。
doc.core> (run digit "123") \1 nil doc.core> (run (many digit) "123") [\1 \2 \3] nil
run はパース結果を印字する。パースしてない残りの入力はそのまま。パーサの状態は run* で見れる。
doc.core> (run* digit "123") {:input (\2 \3), :pos {:src "", :line 1, :col 2}, :value \1, :ok true, :empty false, :user nil, :error nil} nil
文字列以外にも入力としてファイルとかも受け付ける。 runf は パーサとファイル名、文字コードを受け取ってパースする。
コンビネータ <*> は与えられたパーサを順に実行し結果をベクタにして返す。
doc.core> (run (<*> letter letter digit) "os8") [\o \s \8] nil
parse は run みたいな動作をするけれどパーサの状態を返すという点で異なる。 :ok フィールドをチェックすればパースに成功したかどうか分かる。 :value セレクタを用いればパーサの結果が得られる。
doc.core> (let [st (parse (token* "abc") "abc")] (when (:ok st) (str "got " (:value st)))) "got abc"