この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
instaparseとは
今、Clojure使いの間では大きな話題となっている、パーサ(構文解析プログラム)作成用ライブラリです。 正規表現を使うのと同じくらい簡単にパーサが作成できるとのことです。 Clojure作者である、Rich Hickeyも大興奮(ソース:twitter)らしいです。
では簡単にinstaparseの特徴を解説します。 githubに機能一覧があるのですが、そこから代表的ないくつかの特徴を抜粋。
- 入力として文字列を受け取り、EBNF記法を用いて構文解析ツリーを生成する
- 出力フォーマットは、Clojureで人気の形式(hiccupとenlive)の両方をサポート
- 解析エラーの詳細なレポートを出してくれる
文字列をEBNF記法でうけとってパーサが簡単に書けるというみたいです。 Githubのチュートリアルを参考に、instaparseを動かしてみましょう。
環境構築方法
今回使用した動作環境は以下のとおりです。
- OS : MacOS X 10.7.5
- Clojure : 1.5.1
- Leiningen : 2.1.2
instaparseを使ってみる
まずはleiningenでプロジェクトを作成します。
% lein new insta
次に依存するライブラリをinsta/project.cljに記述しましょう。下記のように、instaparseを依存ライブラリに追加してください。
(defproject insta "HEAD"
:dependencies [[org.clojure/clojure "1.5.1"]
[instaparse "1.0.1"]]
:main insta.main)
project.cljを記述したらlein depsで依存ライブラリを取得しておきます。
% lein deps
では、src/insta/main.cljを下記のように記述しましょう。
(ns insta.main
(:require [instaparse.core :as insta]))
(def as-and-bs
(insta/parser
"S = AB*
AB = A B
A = 'a'+
B = 'b'+"))
(defn -main [& args]
(as-and-bs "aaabbaabbb"))
parser関数で渡している文字列が、構文解析ルールです。(BNFの詳細については割愛) 上記ルールにしたがって「aaabbaabbb」を解析させると、次のようになります。
% lein run
[:S [:AB [:A "a" "a" "a"] [:B "b" "b"]] [:AB [:A "a" "a"] [:B "b" "b" "b"]]]
先ほどは出力形式がhiccup形式でしたが、enlive形式にすることもできます。 次のようにparserの引数として渡すか、set-default-output-format!関数を使用して:enliveをセットします。
;関数でセットするか
(insta/set-default-output-format! :enlive)
;もしくはparserの引数で渡す
(def as-and-bs
(insta/parser
"S = AB*
AB = A B
A = 'a'+
B = 'b'+" :output-format :enlive))
これで実行すると、enlive形式でパース結果が出力されます。
% lein run
{:tag :S, :content ({:tag :AB, :content ({:tag :A, :content ("a" "a" "a")} {:tag :B, :content ("b" "b")})} {:tag :AB, :content ({:tag :A, :content ("a" "a")} {:tag :B, :content ("b" "b" "b")})})}
さて、次は構築されたツリーを変換する例をみてみましょう。 次の関数はスペース区切りの文章をアルファベット(:word)と数値(:number)にパースします。
(def words-and-numbers-one-character-at-a-time
(insta/parser
"sentence = token (<whitespace> token)*
<token> = word | number
whitespace = #'\\s+'
word = letter+
number = digit+
<letter> = #'[a-zA-Z]'
<digit> = #'[0-9]'"))
;呼び出し.main関数の中に記述してください
(words-and-numbers-one-character-at-a-time "abc 123 def")
実行すると、次のようにパースされますが、1文字ずつの文字として分割されているので、使いやすいとはいえません。
% lein run
[:sentence [:word "a" "b" "c"] [:number "1" "2" "3"] [:word "d" "e" "f"]]
そういったケースにtransform関数を使用すると、パースされた結果の値に対して関数を適用した結果を取得できます。 次の記述では、:wordの結果は文字列として連結を、:numberの結果は、文字列連結した後、数値に変換しています。
;transformで変換.main関数の中に記述してください
(insta/transform
{:word str,
:number (comp read-string str)}
(words-and-numbers-one-character-at-a-time "abc 123 def"))
実行してみましょう。結果が使いやすくなりました。
% lein run
[:sentence "abc" 123 "def"]
;なお、enlive形式だとこうなる
;{:tag :sentence, :content ("abc" 123 "def")}
まとめ
さて、今回はClojureのパーサ作成ライブラリを紹介しました。 私はBNFやパーサについては詳しくないのですが、その私でも簡単なサンプルなら動かすことができたので、 かなりすごいライブラリなのではないでしょうか。