2013-08-12 8 views
8

Ich bin ein Neuling zum Parsen und möchte etwas Clojure Code analysieren. Ich hoffe, dass jemand ein Beispiel dafür liefern kann, wie Clojure-Code instpariert werden kann. Ich muss nur Zahlen, Symbole, Schlüsselwörter, Sexps, Vektoren und Leerzeichen machen.Wie definieren wir eine Grammatik für Clojure-Code mit instparse?

Einige Beispiele, die ich analysieren möchten:

(+ 1 2 
    (+ 3 4)) 

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

InstaParse ist wirklich reich https://github.com/Engelberg/instarse – Chiron

+0

Könnten Sie ein Beispiel für das gewünschte Ergebnis liefern? –

+0

vielleicht [: sexp [: sym +] [: num 1] [: num 2] [: sexp [: sym +] [: num 3] [: num 4]]] ?? Ich weiß es nicht wirklich ... es ist mit dieser Frage zu tun: http://stackoverflow.com/questions/18184834/how-would-you-get-the-clojure-reader-to-keep-formatting – zcaudate

Antwort

22

Nun gibt es zwei Teile auf Ihre Frage. Der erste Teil analysiert den Ausdruck

(+ 1 2 
    (+ 3 4)) 

Der zweite Teil wandelt die Ausgabe in das Ergebnis, das Sie möchten. Um ein gutes Verständnis dieser Prinzipien zu bekommen, empfehle ich Udacity's Programming Languages course. Carin Meier blog post ist auch sehr hilfreich.

Der beste Weg zu verstehen, wie der Parser funktioniert, ist, ihn in kleinere Teile zu zerlegen. Also werden wir zuerst ein paar Parser-Regeln untersuchen, und im zweiten Teil werden wir unsere Sexs bauen.

  1. Ein einfaches Beispiel

    Sie zuerst eine Grammatik schreiben müssen, die instaparse sagt, wie den gegebenen Ausdruck zu analysieren. Wir beginnen, indem nur die Zahl Parsen 1:

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

    sexp beschreibt die höchste Stufe Grammatik für die sexpression. Unsere Grammatik besagt, dass der Sexp nur eine Nummer haben kann. Die nächste Zeile besagt, dass die Zahl eine beliebige Zahl von 0 bis 9 sein kann, und die + ähnelt der Regex +, was bedeutet, dass eine Zahl beliebig oft wiederholt werden muss. Wenn wir unsere Parser ausführen erhalten wir folgenden Parse-Baum:

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

    Ingoring Parenthesis

    können wir bestimmte Werte ignorieren, indem spitze Klammern < unsere Grammatik hinzuzufügen. Wenn wir also "(1)" als einfach 1 analysieren wollen, können wir unsere Grammatik rechts:

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

    und wenn wir den Parser erneut ausführen, wird es die linke und die rechte Klammer ignorieren:

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

    Dieser Wille werden hilfreich, wenn wir unten die Grammatik für sexp schreiben.

    Hinzufügen Spaces

    geschieht nun, wenn wir Räume hinzufügen und (parser "(1)") laufen? Nun erhalten wir einen Fehler:

    Das ist, weil wir das Konzept des Raums in unserer Grammatik nicht definiert haben!So können wir Räume als solche hinzufügen:

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

    Auch die * ist ähnlich der regex * und bedeutet Null oder mehr als einem Auftreten eines Raumes. Das bedeutet, dass die folgenden Beispiele werden alle das gleiche Ergebnis zurück:

    (parser "(1)")   => [:sexp [:number "1"]] 
    (parser "(1)")  => [:sexp [:number "1"]] 
    (parser "(  1)") => [:sexp [:number "1"]] 
    
  2. Aufbau der Sexp

    Wir werden langsam unsere Grammatik von Grund auf neu aufzubauen. Es könnte nützlich sein, das Endprodukt here zu betrachten, nur um einen Überblick zu geben, wohin wir gehen.

    Also, ein Sexp enthält mehr als nur Zahlen wie durch unsere einfache Grammatik definiert. Eine Ansicht auf hoher Ebene, die wir von sexp haben können, besteht darin, sie als eine Operation zwischen zwei Klammern zu betrachten. Also grundsätzlich als (operation). Wir können dies direkt in unsere Grammatik schreiben.

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

    Wie ich oben erwähnt, sind die eckigen Klammern < sagen instaparse diese Werte zu ignorieren, wenn sie den Parsing-Baum macht. Was ist eine Operation? Nun, eine Operation besteht aus einem Operator, wie +, und einigen Argumenten wie den Nummern 1 und 2. So können wir unsere Grammatik sagen schreiben wie:

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

    Wir haben leider nur eine möglichen Betreiber angegeben, +, nur die Dinge einfach zu halten. Wir haben auch die Zahlengrammatikregel aus dem obigen einfachen Beispiel eingefügt. Unsere Grammatik ist jedoch sehr begrenzt. Der einzige gültige Sex, den es analysieren kann, ist (+1). Das liegt daran, dass wir das Konzept der Leerzeichen nicht berücksichtigt haben und dass Argumente nur eine Zahl haben dürfen. In diesem Schritt werden wir zwei Dinge tun. Wir fügen Leerzeichen hinzu und wir geben an, dass Argumente mehr als eine Zahl haben können.

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

    Wir haben space durch den Raum Grammatikregel verwenden wir in dem einfachen Beispiel definiert. Wir haben ein neues snumber erstellt, das als space und ein number definiert ist, und das + snumber hinzugefügt, um anzugeben, dass es einmal erscheinen muss, aber es beliebig oft wiederholen kann. So können wir unseren Parser als so laufen:

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

    Wir können unsere Grammatik robuste, indem args Rückbezug auf sexp machen. So können wir Sex in unserem Sex haben! Wir können dies tun, indem wir ssexp erstellen, das space zu sexp hinzufügt und dann ssexp zu args hinzufügt.

    (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]+' 
         ")) 
    

    Jetzt können wir

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

    Dieser Schritt ausführen können mit einer beliebigen Anzahl von Werkzeugen durchgeführt werden, die auf Bäumen arbeiten, wie enlive, Reißverschlüsse, Spiel, und Baum -seq. InstaParse enthält jedoch auch eine eigene nützliche Funktion namens insta\transform. Wir können unsere Transformationen erstellen, indem wir die Schlüssel in unserem Syntaxbaum durch die gültigen Clojure-Funktionen ersetzen.Zum Beispiel wird :numberread-string, um unsere Strings in gültige Zahlen zu verwandeln, :args wird vector, um unsere Argumente zu bilden.

    So wollen wir diese zu transformieren:

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

    In diese:

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

    Wir können tun, dass durch unsere Transformationsoptionen definieren:

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

    Die einzige heikle Sache Hier wurde die Funktion choose-op hinzugefügt. Was wir wollen, ist, die Funktion + an apply zu übergeben, aber wenn wir operator durch + ersetzen, wird es + als eine regelmäßige Funktion verwenden. So wird es unser Baum zu dieser Transformation:

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

    Aber choose-op verwendet, wird es + als Argument an apply als solche übergeben:

    ... (apply + (vector ... 
    

Fazit

Wir kann jetzt unseren kleinen Interpreter laufen lassen, indem wir den Parser und den Transformator zusammensetzen:

Sie können den endgültigen Code in diesem Tutorial here finden.

Hoffentlich reicht diese kurze Einführung, um eigene Projekte in Angriff zu nehmen. Sie können neue Zeilen erstellen, indem Sie eine Grammatik für \n deklarieren, und Sie können sogar auswählen, Leerzeichen in Ihrem Syntaxbaum nicht zu ignorieren, indem Sie die spitzen Klammern < entfernen. Das könnte hilfreich sein, da Sie versuchen, die Einrückung beizubehalten. Hoffe das hilft, wenn nicht nur einen Kommentar schreiben!

+1

Link zu Carin Meier's Blog-Post ist jetzt [dies] (http://gigasquidsoftware.com/blog/2013/05/01/growing-a-language-with-clojure-and-instaParse/) – ducky

+0

danke, habe ich aktualisiert Verknüpfung. – pooya72

Verwandte Themen