2013-08-12 8 views
8

私は構文解析の初心者であり、いくつかのクロージャーコードを分析したいと考えています。私は誰かが無差別を使ってどのようにクロージャーコードを解析できるかの例を提供できることを期待しています。私は数字、記号、キーワード、擬似文字、ベクトルと空白を行うだけです。instaparseを使用してクロージャーコードの文法を定義するにはどうすればよいですか?

私が解析したいいくつかの例:

(+ 1 2 
    (+ 3 4)) 

{:hello "there" 
:look '(i am 
      indented)} 
+0

Instaparseは本当に豊富ですhttps://github.com/Engelberg/instaparse – Chiron

+0

希望の結果の例を教えてください。 –

+0

多分[:sexp [:sym +] [:num 1] [:num 2] [:sexp [:sym +] [:num 3] [:num 4]]] ??私は実際には知りません...それはこの質問と関係があります:http://stackoverflow.com/questions/18184834/how-would-you-get-the-clojure-reader-to-keep-formatting – zcaudate

答えて

22

はまあ、あなたの質問には2つの部分があります。最初の部分は式を解析しています

(+ 1 2 
    (+ 3 4)) 

2番目の部分は、結果を必要な結果に変換しています。これらの原則をよく理解するために、UdacityのProgramming Languages course. Carin Meierのblog postも非常に役に立ちます。

パーサーがどのように機能するかを理解するには、小さな部分に分割するのが最も良い方法です。したがって、まず最初にいくつかの解析ルールを調べ、2番目のパートではセックスを作成します。

  1. 簡単な例

    あなたが最初に与えられた式を解析する方法instaparse告げる文法を記述する必要があります。私達はちょうど数1を解析することから始めましょう:

    (def parser 
        (insta/parser 
         "sexp = number 
         number = #'[0-9]+' 
         ")) 
    

    S式はsexpressionのための最高レベルの文法を説明します。私たちの文法によれば、sexpは数字だけを持つことができます。次の行には数字が0-9の任意の数字であることが示されており、+は正規表現+と似ています。つまり、何回でも何度も繰り返される数字が1つ必要です。我々はパーサを実行した場合、我々は、以下のパースツリーを取得:

    (parser "1")  
    => [:sexp [:number "1"]] 
    

    Ingoringカッコ

    を私たちは文法に角括弧<を追加することによって、特定の値を無視することができます。だから我々は、単に1"(1)"を解析したい場合は、私たちのように私たちの文法を右することができます

    (def parser 
        (insta/parser 
         "sexp = lparen number rparen 
         <lparen> = <'('> 
         <rparen> = <')'> 
         number = #'[0-9]+' 
         ")) 
    

    、我々は再びパーサを実行する場合、それは左と右のかっこを無視します:

    (parser "(1)") 
    => [:sexp [:number "1"]] 
    

    この意志は、下記のsexpの文法を書くときに役立ちます。私たちは、スペースを追加し、(parser "(1)")を実行する場合

    追加スペース

    は今どうなりますか?さて、エラーが発生します:

    (parser "(1)") 
    => Parse error at line 1, column 2: 
        (1) 
        ^
        Expected: 
        #"[0-9]+" 
    

    これは、私たちの文法でスペースの概念が定義されていないためです。だから我々は、次のようなスペースを追加することができます

    (def parser 
        (insta/parser 
         "sexp = lparen space number space rparen 
         <lparen> = <'('> 
         <rparen> = <')'> 
         number = #'[0-9]+' 
         <space> = <#'[ ]*'> 
         ")) 
    

    を再び*は正規表現*に似ており、それがゼロまたはスペースの複数の出現を意味します。私たちは、ゆっくりと地面から私たちの文法を構築しようとしているS式

    を構築

    (parser "(1)")   => [:sexp [:number "1"]] 
    (parser "(1)")  => [:sexp [:number "1"]] 
    (parser "(  1)") => [:sexp [:number "1"]] 
    
  2. :それは、次の例は、すべて同じ結果を返すことを意味します。最終製品のhereを見て、どこに向かっているのかを概観すると便利です。

    したがって、sexpには、私たちの単純な文法で定義された数以上のものが含まれています。私たちがsexpを持つことができる1つの高レベルのビューは、それらを2つのかっこの間の操作として見ることです。基本的には(operation)となります。これを文法に直接書くことができます。私は前述したように、それは構文解析ツリーを作っているとき

    (def parser 
        (insta/parser 
         "sexp = lparen operation rparen 
         <lparen> = <'('> 
         <rparen> = <')'> 
         operation = ??? 
         ")) 
    

    、角度付きブラケットは<、これらの値を無視するinstaparse言います。オペレーションとは何ですか?操作は、+のような演算子と、数字12のようないくつかの引数で構成されます。私達はちょうど物事をシンプルに保つために、唯一の可能なオペレータ、+を述べ

    (def parser 
        (insta/parser 
         "sexp = lparen operation rparen 
         <lparen> = <'('> 
         <rparen> = <')'> 
         operation = operator + args 
         operator = '+' 
         args = number 
         number = #'[0-9]+' 
         ")) 
    

    :だから私たちは私たちのような文法を書くと言うことができます。また、上記の単純な例の数値文法規則も示しました。しかし、私たちの文法は非常に限られています。解析できる唯一の有効なsexpは(+1)です。スペースの概念は含まれておらず、argsには1つの数字しかないと述べているからです。したがって、このステップでは2つのことを行います。スペースを追加し、argsに複数の番号を付けることができるようにします。

    (def parser 
        (insta/parser 
         "sexp = lparen operation rparen 
         <lparen> = <'('> 
         <rparen> = <')'> 
         operation = operator + args 
         operator = '+' 
         args = snumber+ 
         <snumber> = space number 
         <space> = <#'[ ]*'> 
         number = #'[0-9]+' 
         ")) 
    

    単純な例で定義したスペース文法ルールを使用してspaceを追加しました。 spacenumberと定義された新しいsnumberを作成し、+を追加して1回表示する必要がありますが、何回でも繰り返すことができると述べています。だから我々は、ように私たちのパーサを実行することができます。

    (parser "(+ 1 2)") 
    => [:sexp [:operation [:operator "+"] [:args [:number "1"] [:number "2"]]]] 
    

    我々は戻ってsexpからargs参照を持っていることによって私たちの文法をより堅牢にすることができます。そうすれば、私たちはsexpでsexpを持つことができます! ssexpを作成し、spacesexpに追加してから、ssexpargsに追加することでこれを行うことができます。

    (def parser 
        (insta/parser 
         "sexp = lparen operation rparen 
         <lparen> = <'('> 
         <rparen> = <')'> 
         operation = operator + args 
         operator = '+' 
         args = snumber+ ssexp* 
         <ssexp> = space sexp 
         <snumber> = space number 
         <space> = <#'[ ]*'> 
         number = #'[0-9]+' 
         ")) 
    

    今、私たちは

    (parser "(+ 1 2 (+ 1 2))") 
    => [:sexp 
         [:operation 
         [:operator "+"] 
         [:args 
         [:number "1"] 
         [:number "2"] 
         [:sexp 
          [:operation [:operator "+"] [:args [:number "1"] [:number "2"]]]]]]] 
    
  3. 変換

    このステップは、任意の木に働く多くのツールは、そのようなenlive、ジッパー、マッチ、そして木を使用して行うことができますを実行することができます-seq。しかし、無期限には、insta\transformという独自の有用な機能も含まれています。パーズツリーのキーを有効なクロージャー機能で置き換えることで、変換を構築できます。たとえば、:numberread-stringになり、文字列を有効な数値に変換すると、:argsvectorになり、引数を作成できます。

    だから、私たちはこの変換したい:この中へ

    [:sexp [:operation [:operator "+"] [:args [:number "1"] [:number "2"]]]] 
    

    (identity (apply + (vector (read-string "1") (read-string "2")))) 
    => 3 
    

    当社は、変換オプションを定義することであることを行うことができます。

    (defn choose-op [op] 
    (case op 
        "+" +)) 
    (def transform-options 
        {:number read-string 
        :args vector 
        :operator choose-op 
        :operation apply 
        :sexp identity 
    }) 
    

    だけトリッキーなことをしますここに関数choose-opを追加しました。私たちが望むのは、関数+applyに渡すことですが、operator+に置き換えると、通常の関数として+が使用されます。

    ... (apply (+ (vector ... 
    

    をしかしchoose-opを使用することによって、それは、次のようなapplyへの引数として+を渡します:だから、これまで私たちの木を変換します

    ... (apply + (vector ... 
    

結論

私たちは、パーサーとトランスフォーマーを一緒に置くことで、小さな通訳を実行できます:

(defn lisp [input] 
    (->> (parser input) (insta/transform transform-options))) 

(lisp "(+ 1 2)") 
    => 3 

(lisp "(+ 1 2(+ 3 4))") 
    => 10 

このチュートリアルで使用されている最終コードはhereです。

この短い紹介は、あなた自身のプロジェクトに進むには十分です。 \nの文法を宣言することによって新しい行を作成することができます。また、角括弧<を削除することで、パースツリー内のスペースを無視しないこともできます。インデントを維持しようとしている場合は、参考になるかもしれません。これが助けてほしいと思っています。

+1

Carin Meierさんのブログ記事へのリンクは、[this](http://gigasquidsoftware.com/blog/2013/05/01/growing-a-language-with-clojure-and-instaparse/) – ducky

+0

です。リンク。 – pooya72