Nun gibt es zwei Teile auf Ihre Frage. Der erste Teil analysiert den Ausdruck
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.
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"]]
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"]]]]]]]
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 :number
read-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 ...
Wir kann jetzt unseren kleinen Interpreter laufen lassen, indem wir den Parser und den Transformator zusammensetzen:
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!
InstaParse ist wirklich reich https://github.com/Engelberg/instarse – Chiron
Könnten Sie ein Beispiel für das gewünschte Ergebnis liefern? –
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