Parsecを使ってパースする
こんにちわ、高校生です。
にわかLisperです。みなさん、本当にClojure書いてますか?
正規表現とかしんどいし関数型言語ならParsec使えよ!という話があると思うので適当に書いてみました。
まずはcsvですよね。
(use '[clarsec]) (def input "Year,Make,Model,Length 1997,Ford,Model-350,234 2000,Mercury,\"Model 800\",238") (def cell (lexeme (<|> string-literal (stringify (many (none-of ",\n")))))) (def line (sep-by-1 cell comma)) (def csv (sep-by line eol)) (:value (parse$ csv input))
まあこんな感じです。分かる人にはまあなんとなくわかると思います。
結果:
(("Year" "Make" "Model" "Length") ("1997" "Ford" "Model-350" "234") ("2000" "Mercury" "Model 800" "238"))
とまあパースできてますね。
次に簡易でxmlをパースしてみます。
(use '[clarsec]) (use '[clarsec.monad]) (def input " <library> <book> <title>Joy of Clojure</title> <author>Fogus</author> </book> <book> <title>Structured and Interpretation of Computer Programs</title> <author>MIT</author> </book> </library>") (defn arrows [p] (between (symb "<") (symb ">") p)) (def open-tag (arrows identifier)) (defn close-tag [expect-name] (arrows (symb (str "/" expect-name)))) (defn element [p] (let-bind [tag-name open-tag contents p (close-tag tag-name)] {(keyword tag-name) contents})) (def xml (let [list$ #(flatten (list %&))] (element (<|> (<$> #(apply merge-with list$ %) (many1 (lazy xml))) (stringify (many (<|> letter space))))))) (:value (parse$ xml input))
betweenとか便利ですよね。
えーlet-bindとかありますけど。。。モナド周りとかまあなんとなくといった感じです。
要素は辞書形式で表しています。
結果をflattenで綺麗にしています。
xmlの定義で自身を参照していますが、lazyにしておかないとStackが溢れます。
(要はdelayで必要になったときに計算を始めます)
結果はこんな感じ。
{:library {:book ({:author "Fogus", :title "Joy of Clojure"} {:author "MIT", :title "Structured and Interpretation of Computer Programs"})}}
エラーになる場合
;; 数値を許可していないので失敗する (parse$ xml "<test>ABC123</test>")
結果
{:error-line <test>ABC1, :line-number 1, :type :failed}
とある程度どこで失敗したかわかります。
clarsecあれこれ
元々、clarsecはvimclojureの人が公開していたのですが闇に消えています。
他の開発者の人がそれらをgithubとかにあげていたのですが、エラー処理がなかったりエラー処理を実装しようとしていて放置されていたりと散々な感じでした。
仕方ないのである程度みんなのコードを寄せ集めたりして色々やって少し動くとこまでできました。
リポジトリは以下です。複雑なものはまだ試してないので自己責任で。
いろいろ手直ししてくれる人も募集しています。
https://github.com/mopemope/clarsec2