Hinweis : Diese Antwort gilt für ANTLR3 ! Wenn Sie nach einem ANTLR4- Beispiel suchen , wird in diesen Fragen und Antworten gezeigt, wie Sie mit ANTLR4 einen einfachen Ausdrucksparser und Evaluator erstellen .
Sie erstellen zuerst eine Grammatik. Im Folgenden finden Sie eine kleine Grammatik, mit der Sie Ausdrücke auswerten können, die mit den vier grundlegenden mathematischen Operatoren erstellt wurden: +, -, * und /. Sie können Ausdrücke auch in Klammern gruppieren.
Beachten Sie, dass diese Grammatik nur eine sehr grundlegende ist: Sie behandelt keine unären Operatoren (das Minus in: -1 + 9) oder Dezimalstellen wie 0,99 (ohne führende Zahl), um nur zwei Mängel zu nennen. Dies ist nur ein Beispiel, an dem Sie selbst arbeiten können.
Hier ist der Inhalt der Grammatikdatei Exp.g :
grammar Exp;
/* This will be the entry point of our parser. */
eval
: additionExp
;
/* Addition and subtraction have the lowest precedence. */
additionExp
: multiplyExp
( '+' multiplyExp
| '-' multiplyExp
)*
;
/* Multiplication and division have a higher precedence. */
multiplyExp
: atomExp
( '*' atomExp
| '/' atomExp
)*
;
/* An expression atom is the smallest part of an expression: a number. Or
when we encounter parenthesis, we're making a recursive call back to the
rule 'additionExp'. As you can see, an 'atomExp' has the highest precedence. */
atomExp
: Number
| '(' additionExp ')'
;
/* A number: can be an integer value, or a decimal value */
Number
: ('0'..'9')+ ('.' ('0'..'9')+)?
;
/* We're going to ignore all white space characters */
WS
: (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
;
(Parser-Regeln beginnen mit einem Kleinbuchstaben und Lexer-Regeln beginnen mit einem Großbuchstaben.)
Nach dem Erstellen der Grammatik möchten Sie einen Parser und einen Lexer daraus generieren. Laden Sie das ANTLR-Glas herunter und speichern Sie es im selben Verzeichnis wie Ihre Grammatikdatei.
Führen Sie den folgenden Befehl an Ihrer Shell / Eingabeaufforderung aus:
java -cp antlr-3.2.jar org.antlr.Tool Exp.g
Es sollte keine Fehlermeldung angezeigt werden , und die Dateien ExpLexer.java , ExpParser.java und Exp.tokens sollten jetzt generiert werden.
Erstellen Sie diese Testklasse, um festzustellen, ob alles ordnungsgemäß funktioniert:
import org.antlr.runtime.*;
public class ANTLRDemo {
public static void main(String[] args) throws Exception {
ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
parser.eval();
}
}
und kompiliere es:
// *nix/MacOS
javac -cp .:antlr-3.2.jar ANTLRDemo.java
// Windows
javac -cp .;antlr-3.2.jar ANTLRDemo.java
und dann ausführen:
// *nix/MacOS
java -cp .:antlr-3.2.jar ANTLRDemo
// Windows
java -cp .;antlr-3.2.jar ANTLRDemo
Wenn alles gut geht, wird nichts auf die Konsole gedruckt. Dies bedeutet, dass der Parser keinen Fehler gefunden hat. Wenn Sie ändern "12*(5-6)"
in "12*(5-6"
und dann neu zu kompilieren und ausführen, sollte die folgende gedruckt werden:
line 0:-1 mismatched input '<EOF>' expecting ')'
Okay, jetzt möchten wir der Grammatik ein bisschen Java-Code hinzufügen, damit der Parser tatsächlich etwas Nützliches tut. Das Hinzufügen von Code kann durch Platzieren {
und }
Einfügen von einfachem Java-Code in Ihre Grammatik erfolgen.
Aber zuerst: Alle Parserregeln in der Grammatikdatei sollten einen primitiven Doppelwert zurückgeben. Sie können dies tun, indem Sie returns [double value]
nach jeder Regel Folgendes hinzufügen :
grammar Exp;
eval returns [double value]
: additionExp
;
additionExp returns [double value]
: multiplyExp
( '+' multiplyExp
| '-' multiplyExp
)*
;
// ...
Das bedarf keiner Erklärung: Von jeder Regel wird erwartet, dass sie einen doppelten Wert zurückgibt. Um nun mit dem Rückgabewert double value
(der sich NICHT in einem einfachen Java-Codeblock befindet {...}
) aus einem Codeblock heraus "zu interagieren" , müssen Sie ein Dollarzeichen vor value
:
grammar Exp;
/* This will be the entry point of our parser. */
eval returns [double value]
: additionExp { /* plain code block! */ System.out.println("value equals: "+$value); }
;
// ...
Hier ist die Grammatik, aber jetzt mit dem hinzugefügten Java-Code:
grammar Exp;
eval returns [double value]
: exp=additionExp {$value = $exp.value;}
;
additionExp returns [double value]
: m1=multiplyExp {$value = $m1.value;}
( '+' m2=multiplyExp {$value += $m2.value;}
| '-' m2=multiplyExp {$value -= $m2.value;}
)*
;
multiplyExp returns [double value]
: a1=atomExp {$value = $a1.value;}
( '*' a2=atomExp {$value *= $a2.value;}
| '/' a2=atomExp {$value /= $a2.value;}
)*
;
atomExp returns [double value]
: n=Number {$value = Double.parseDouble($n.text);}
| '(' exp=additionExp ')' {$value = $exp.value;}
;
Number
: ('0'..'9')+ ('.' ('0'..'9')+)?
;
WS
: (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
;
und da unsere eval
Regel jetzt ein Double zurückgibt, ändern Sie Ihre ANTLRDemo.java in Folgendes:
import org.antlr.runtime.*;
public class ANTLRDemo {
public static void main(String[] args) throws Exception {
ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
System.out.println(parser.eval()); // print the value
}
}
Generieren Sie erneut (neu) einen neuen Lexer und Parser aus Ihrer Grammatik (1), kompilieren Sie alle Klassen (2) und führen Sie ANTLRDemo (3) aus:
// *nix/MacOS
java -cp antlr-3.2.jar org.antlr.Tool Exp.g // 1
javac -cp .:antlr-3.2.jar ANTLRDemo.java // 2
java -cp .:antlr-3.2.jar ANTLRDemo // 3
// Windows
java -cp antlr-3.2.jar org.antlr.Tool Exp.g // 1
javac -cp .;antlr-3.2.jar ANTLRDemo.java // 2
java -cp .;antlr-3.2.jar ANTLRDemo // 3
und Sie sehen jetzt das Ergebnis des Ausdrucks 12*(5-6)
, der auf Ihrer Konsole gedruckt ist!
Nochmals: Dies ist eine sehr kurze Erklärung. Ich ermutige Sie, im ANTLR-Wiki zu stöbern und einige Tutorials zu lesen und / oder ein bisschen mit dem zu spielen, was ich gerade gepostet habe.
Viel Glück!
BEARBEITEN:
Dieser Beitrag zeigt, wie Sie das obige Beispiel erweitern Map<String, Double>
können, damit ein bereitgestellt werden kann, das Variablen im angegebenen Ausdruck enthält.
Damit dieser Code mit einer aktuellen Version von Antlr (Juni 2014) funktioniert, musste ich einige Änderungen vornehmen. ANTLRStringStream
zu werden , benötigt ANTLRInputStream
benötigt, der zurückgegebene Wert auf Abwechslung von parser.eval()
zu parser.eval().value
, und ich brauchte das entfernen WS
Klausel am Ende, weil Attributwerte wie $channel
nicht mehr in Lexer Aktionen erscheinen erlaubt.