![[Clojure]instaparseでパーサを作成](https://devio2023-media.developers.io/wp-content/uploads/2013/04/clojure.png)
[Clojure]instaparseでパーサを作成
この記事は公開されてから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やパーサについては詳しくないのですが、その私でも簡単なサンプルなら動かすことができたので、
かなりすごいライブラリなのではないでしょうか。








