みどりねこ日記

よくわからないけど、頑張りますよ。

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"