ANTLR: Czy istnieje prosty przykład?


230

Chciałbym zacząć od ANTLR, ale po spędzeniu kilku godzin na przejrzeniu przykładów na stronie antlr.org nadal nie mogę dobrze zrozumieć gramatyki języka Java.

Czy istnieje jakiś prosty przykład, coś w rodzaju kalkulatora czterech operacji zaimplementowanego z ANTLR przechodzącym przez definicję parsera aż do kodu źródłowego Java?


2
Ten dokładny przykład jest używany jako samouczek na stronie Antlr, ostatnio sprawdziłem.
Cory Petosky,

1
@Cory Petosky: czy możesz podać link?
Eli,

Właśnie opublikowałem pierwsze części samouczka wideo na ANTLR. Zobacz javadude.com/articles/antlr3xtut Mam nadzieję, że okaże się to pomocne!
Scott Stanchfield

2
Ja również udostępniam twoje wyszukiwanie.
Paul Draper,

1
Najlepszą odpowiedzią na ANTLR 4 jest książka Parra „The Definitive ANTLR 4 Reference”.
james.garriss

Odpowiedzi:


447

Uwaga : ta odpowiedź dotyczy ANTLR3 ! Jeśli szukasz przykładu ANTLR4 , to pytania i odpowiedzi pokazują, jak utworzyć prosty analizator składni wyrażeń i ewaluatora używającego ANTLR4 .


Najpierw tworzysz gramatykę. Poniżej znajduje się mała gramatyka, której można użyć do oceny wyrażeń zbudowanych przy użyciu 4 podstawowych operatorów matematycznych: +, -, * i /. Możesz również grupować wyrażenia za pomocą nawiasów.

Zauważ, że ta gramatyka jest po prostu bardzo podstawowa: nie obsługuje operatorów jednoargumentowych (minus w: -1 + 9) ani dziesiętnych, takich jak .99 (bez wiodącej liczby), żeby wymienić tylko dwa niedociągnięcia. To tylko przykład, nad którym możesz popracować.

Oto zawartość pliku gramatyki 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;}
    ;

(Reguły analizatora składni rozpoczynają się od małej litery, a reguły leksykalne zaczynają się od dużej litery)

Po utworzeniu gramatyki będziesz chciał wygenerować z niej parser i leksykon. Pobierz słoik ANTLR i zapisz go w tym samym katalogu, co plik gramatyki.

Wykonaj następujące polecenie w powłoce / wierszu polecenia:

java -cp antlr-3.2.jar org.antlr.Tool Exp.g

Nie powinien generować żadnego komunikatu o błędzie, a pliki ExpLexer.java , ExpParser.java i Exp.tokens powinny teraz zostać wygenerowane.

Aby sprawdzić, czy wszystko działa poprawnie, utwórz tę klasę testową:

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();
    }
}

i skompiluj to:

// *nix/MacOS
javac -cp .:antlr-3.2.jar ANTLRDemo.java

// Windows
javac -cp .;antlr-3.2.jar ANTLRDemo.java

a następnie uruchom:

// *nix/MacOS
java -cp .:antlr-3.2.jar ANTLRDemo

// Windows
java -cp .;antlr-3.2.jar ANTLRDemo

Jeśli wszystko pójdzie dobrze, nic nie jest drukowane na konsoli. Oznacza to, że parser nie znalazł żadnego błędu. Po zmianie "12*(5-6)"na, "12*(5-6"a następnie ponownej kompilacji i uruchomieniu, należy wydrukować następujące informacje:

line 0:-1 mismatched input '<EOF>' expecting ')'

Okej, teraz chcemy dodać do gramatyki trochę kodu Java, aby parser rzeczywiście zrobił coś pożytecznego. Dodanie kodu można wykonać poprzez umieszczenie w gramatyce {i umieszczenie }w niej zwykłego kodu Java.

Ale najpierw: wszystkie reguły parsera w pliku gramatyki powinny zwracać pierwotną podwójną wartość. Możesz to zrobić, dodając returns [double value]po każdej regule:

grammar Exp;

eval returns [double value]
    :    additionExp
    ;

additionExp returns [double value]
    :    multiplyExp 
         ( '+' multiplyExp 
         | '-' multiplyExp
         )* 
    ;

// ...

co wymaga małego wyjaśnienia: oczekuje się, że każda reguła zwróci podwójną wartość. Teraz, aby „wchodzić w interakcje” z wartością zwracaną double value(która NIE znajduje się w zwykłym bloku kodu Java {...}) z wnętrza bloku kodu, musisz dodać znak dolara przed 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); }
    ;

// ...

Oto gramatyka, ale teraz z dodanym kodem Java:

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;}
    ;

a ponieważ nasza evalreguła zwraca teraz podwójną wartość, zmień ANTLRDemo.java na:

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
    }
}

Ponownie (ponownie) wygeneruj nowy leksyk i parser ze swojej gramatyki (1), skompiluj wszystkie klasy (2) i uruchom ANTLRDemo (3):

// *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

a teraz zobaczysz wynik wyrażenia 12*(5-6)wydrukowanego na konsoli!

Ponownie: jest to bardzo krótkie wyjaśnienie. Zachęcam do przeglądania wiki ANTLR i przeczytania kilku samouczków i / lub zagrania z tym, co właśnie opublikowałem.

Powodzenia!

EDYTOWAĆ:

Ten post pokazuje, jak rozszerzyć powyższy przykład, aby Map<String, Double>można było podać zmienną w podanym wyrażeniu.

Aby ten kod działał z aktualną wersją Antlr (czerwiec 2014), musiałem wprowadzić kilka zmian. ANTLRStringStreammusiał zostać ANTLRInputStream, zwrócona wartość musiała zmienić się z parser.eval()na parser.eval().value, a ja musiałem usunąć WSklauzulę na końcu, ponieważ takie wartości atrybutów, $channelktóre nie mogą już pojawiać się w akcjach leksykalnych.


1
Gdzie parser.eval()zdarzają się implemenacje ? To nie jest jasne TUTAJ lub na ANTLR3 Wiki!

1
@Jarrod, proszę, przepraszam, naprawdę cię nie rozumiem. evalto reguła analizatora składni, która zwraca a double. Jest więc eval()metoda, którą możesz wywołać na instancji ExpParser, tak jak pokazałem w ANTLRDemo.main(...). Po wygenerowaniu lexera / parsera, po prostu otwórz plik, ExpParser.javaa zobaczysz, że istnieje eval()metoda zwracająca a double.
Bart Kiers,

@ Bart Badam to od tygodnia - jest to pierwszy przykład, który był tak szczegółowy i kompletny, że zadziałał za pierwszym razem i wydaje mi się, że rozumiem. Prawie się poddałem. Dzięki!
Vineel Shah,

13

Bardzo pomocny jest mega poradnik ANTLR autorstwa Gabriele Tomassetti

Zawiera przykłady gramatyki, przykłady odwiedzających w różnych językach (Java, JavaScript, C # i Python) i wiele innych rzeczy. Wysoce rekomendowane.

EDYCJA: inne przydatne artykuły Gabriele Tomassetti na ANTLR


Świetny samouczek!
Manish Patel

Antlr ma teraz cpp również jako język docelowy. Czy są jakieś samouczki z przykładem na CPP?
vineeshvs

Ten sam facet stworzył samouczek dla ANTLR w C ++ tomassetti.me/getting-started-antlr-cpp Zgaduję, że znajdziesz tu to, czego szukasz tutaj lub w wielkim samouczku
solo

7

W przypadku Antlr 4 proces generowania kodu Java jest poniżej:

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

Zaktualizuj odpowiednio swoją nazwę słoika w ścieżce klasy.


2

Na https://github.com/BITPlan/com.bitplan.antlr znajdziesz bibliotekę Java ANTLR z kilkoma przydatnymi klasami pomocników i kilkoma kompletnymi przykładami. Jest gotowy do użycia z maven, a jeśli lubisz zaćmienie i maven.

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

jest prostym językiem wyrażeń, który może wykonywać operacje mnożenia i dodawania. https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestExpParser.java ma dla niego odpowiednie testy jednostkowe.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/iri/IRIParser.g4 to parser IRI, który został podzielony na trzy części:

  1. gramatyka parsera
  2. gramatyka leksykalna
  3. importowana gramatyka LexBasic

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIRIParser.java przeprowadza testy jednostkowe.

Osobiście uważam, że jest to najtrudniejsza część, aby rozwiązać problem. Zobacz http://wiki.bitplan.com/index.php/ANTLR_maven_plugin

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

zawiera trzy kolejne przykłady, które zostały utworzone dla problemu z wydajnością ANTLR4 we wcześniejszej wersji. Tymczasem problemy zostały naprawione, jak pokazuje test https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIssue994.java .


2

wersja 4.7.1 była nieco inna: do importu:

import org.antlr.v4.runtime.*;

dla głównego segmentu - zwróć uwagę na CharStreams:

CharStream in = CharStreams.fromString("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.