ANTLR: Gibt es ein einfaches Beispiel?

230

Ich würde gerne mit ANTLR beginnen, aber nachdem ich einige Stunden damit verbracht habe, die Beispiele auf der Website antlr.org zu überprüfen , kann ich immer noch kein klares Verständnis für den Grammatik-Java-Prozess erlangen.

Gibt es ein einfaches Beispiel, so etwas wie einen mit ANTLR implementierten Taschenrechner mit vier Operationen, der die Parserdefinition und den gesamten Java-Quellcode durchläuft?

Eli
quelle
2
Dieses genaue Beispiel wird als Tutorial auf Antlrs Website verwendet, das ich zuletzt überprüft habe.
Cory Petosky
1
@Cory Petosky: Kannst du den Link liefern?
Eli
Ich habe gerade die ersten Teile eines Video-Tutorials auf ANTLR gepostet. Siehe javadude.com/articles/antlr3xtut Ich hoffe, Sie finden es hilfreich!
Scott Stanchfield
2
Ich teile auch Ihre Suche.
Paul Draper
1
Die beste Antwort für ANTLR 4 ist Parrs Buch "The Definitive ANTLR 4 Reference".
James.garriss

Antworten:

447

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 evalRegel 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. ANTLRStringStreamzu werden , benötigt ANTLRInputStreambenötigt, der zurückgegebene Wert auf Abwechslung von parser.eval()zu parser.eval().value, und ich brauchte das entfernen WSKlausel am Ende, weil Attributwerte wie $channelnicht mehr in Lexer Aktionen erscheinen erlaubt.

Bart Kiers
quelle
1
Wo parser.eval()passieren die Implementierungen von ? Das ist HIER oder im ANTLR3-Wiki nicht klar!
1
@ Jarrod, ähm, sorry, ich verstehe dich nicht wirklich. evalist eine Parser-Regel, die a zurückgibt double. Es gibt also eine eval()Methode, die Sie für eine Instanz von aufrufen können ExpParser, wie ich in der ANTLRDemo.main(...). Öffnen ExpParser.javaSie nach dem Generieren eines Lexers / Parsers einfach die Datei und Sie werden sehen, dass es eine eval()Methode gibt, die a zurückgibt double.
Bart Kiers
@Bart Ich habe dies eine Woche lang untersucht - dies ist das erste Beispiel, das tatsächlich detailliert und vollständig genug war, um beim ersten Mal zu funktionieren, und das ich zu verstehen glaube. Ich hatte fast aufgegeben. Vielen Dank!
Vineel Shah
13

Das Mega-Tutorial von ANTLR von Gabriele Tomassetti ist sehr hilfreich

Es enthält Grammatikbeispiele, Beispiele für Besucher in verschiedenen Sprachen (Java, JavaScript, C # und Python) und viele andere Dinge. Sehr empfehlenswert.

EDIT: andere nützliche Artikel von Gabriele Tomassetti auf ANTLR

Solo
quelle
Tolles Tutorial!
Manish Patel
Antlr hat jetzt auch cpp als Zielsprache. Gibt es Tutorials mit Beispiel auf cpp?
Vineeshvs
Der gleiche Typ hat ein Tutorial für ANTLR in C ++ erstellt. Tomassetti.me/getting-started-antlr-cpp Ich vermute , Sie werden hier oder im Mega-Tutorial
Solo
7

Für Antlr 4 ist der Java-Code-Generierungsprozess wie folgt: -

java -cp antlr-4.5.3-complete.jar org.antlr.v4.Tool Exp.g

Aktualisieren Sie Ihren JAR-Namen im Klassenpfad entsprechend.

Abhishek K.
quelle
2

Unter https://github.com/BITPlan/com.bitplan.antlr finden Sie eine ANTLR-Java-Bibliothek mit einigen nützlichen Hilfsklassen und einigen vollständigen Beispielen. Es ist bereit, mit Maven verwendet zu werden und wenn Sie Eclipse und Maven mögen.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/exp/Exp.g4

ist eine einfache Ausdruckssprache, die Multiplikations- und Additionsoperationen ausführen kann. https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestExpParser.java verfügt über die entsprechenden Komponententests.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/iri/IRIParser.g4 ist ein IRI-Parser, der in drei Teile unterteilt wurde:

  1. Parser-Grammatik
  2. Lexer Grammatik
  3. importierte LexBasic-Grammatik

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIRIParser.java hat die Unit-Tests dafür.

Persönlich fand ich das der schwierigste Teil, um es richtig zu machen. Siehe http://wiki.bitplan.com/index.php/ANTLR_maven_plugin

https://github.com/BITPlan/com.bitplan.antlr/tree/master/src/main/antlr4/com/bitplan/expr

enthält drei weitere Beispiele, die für ein Leistungsproblem von ANTLR4 in einer früheren Version erstellt wurden. In der Zwischenzeit wurde dieses Problem behoben, wie der Testfall https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIssue994.java zeigt.

Wolfgang Fahl
quelle
2

Version 4.7.1 war etwas anders: für den Import:

import org.antlr.v4.runtime.*;

Beachten Sie für das Hauptsegment die CharStreams:

CharStream in = CharStreams.fromString("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
user1562431
quelle