ANTLR: C'è un semplice esempio?


230

Vorrei iniziare con ANTLR, ma dopo aver trascorso alcune ore a rivedere gli esempi sul sito antlr.org , non riesco ancora a capire chiaramente la grammatica del processo Java.

C'è qualche semplice esempio, qualcosa come una calcolatrice a quattro operazioni implementata con ANTLR che passa attraverso la definizione del parser e arriva fino al codice sorgente Java?


2
Quell'esempio preciso è usato come tutorial sul sito di Antlr, per ultimo ho controllato.
Cory Petosky,

1
@Cory Petosky: puoi fornire il link?
Eli,

Ho appena pubblicato le prime parti di un tutorial video su ANTLR. Vedi javadude.com/articles/antlr3xtut Spero che lo trovi utile!
Scott Stanchfield,

2
Anch'io condivido la tua ricerca.
Paul Draper,

1
La migliore risposta per ANTLR 4 è acquistare il libro di Parr "The Definitive ANTLR 4 Reference".
james.garriss

Risposte:


448

Nota : questa risposta è per ANTLR3 ! Se siete alla ricerca di un ANTLR4 esempio, allora questo Q & A viene illustrato come creare un semplice parser espressione e valutatore utilizzando ANTLR4 .


Per prima cosa crei una grammatica. Di seguito è una piccola grammatica che è possibile utilizzare per valutare le espressioni costruite utilizzando i 4 operatori matematici di base: +, -, * e /. Puoi anche raggruppare le espressioni usando la parentesi.

Si noti che questa grammatica è solo molto semplice: non gestisce operatori unari (il meno in: -1 + 9) o decimali come .99 (senza un numero iniziale), per nominare solo due carenze. Questo è solo un esempio su cui puoi lavorare su te stesso.

Ecco il contenuto del file di grammatica 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;}
    ;

(Le regole del parser iniziano con una lettera minuscola e le regole del lexer iniziano con una lettera maiuscola)

Dopo aver creato la grammatica, ti consigliamo di generare un parser e un lexer da esso. Scarica il vaso ANTLR e salvalo nella stessa directory del tuo file di grammatica.

Eseguire il comando seguente sulla shell / prompt dei comandi:

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

Non dovrebbe generare alcun messaggio di errore e ora dovrebbero essere generati i file ExpLexer.java , ExpParser.java e Exp.tokens .

Per vedere se tutto funziona correttamente, crea questa classe di test:

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

e compilarlo:

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

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

e quindi eseguirlo:

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

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

Se tutto va bene, non viene stampato nulla sulla console. Ciò significa che il parser non ha trovato alcun errore. Quando si cambia "12*(5-6)"in, "12*(5-6"quindi si ricompila ed si esegue, dovrebbe essere stampato quanto segue:

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

Bene, ora vogliamo aggiungere un po 'di codice Java alla grammatica in modo che il parser faccia effettivamente qualcosa di utile. L'aggiunta di codice può essere effettuata posizionando {e }all'interno della grammatica con un semplice codice Java al suo interno.

Ma prima: tutte le regole del parser nel file grammaticale dovrebbero restituire un doppio valore primitivo. Puoi farlo aggiungendo returns [double value]dopo ogni regola:

grammar Exp;

eval returns [double value]
    :    additionExp
    ;

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

// ...

che ha bisogno di poche spiegazioni: ogni regola dovrebbe restituire un doppio valore. Ora per "interagire" con il valore restituito double value(che NON è all'interno di un semplice blocco di codice Java {...}) all'interno di un blocco di codice, dovrai aggiungere un segno di dollaro davanti a 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); }
    ;

// ...

Ecco la grammatica ma ora con l'aggiunta del codice 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;}
    ;

e poiché la nostra evalregola ora restituisce un doppio, cambia il tuo ANTLRDemo.java in questo:

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

Di nuovo (ri) generare un nuovo lexer e un parser dalla grammatica (1), compilare tutte le classi (2) ed eseguire 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

e ora vedrai il risultato dell'espressione 12*(5-6)stampato sulla tua console!

Ancora: questa è una spiegazione molto breve. Ti incoraggio a navigare nel wiki di ANTLR e leggere alcuni tutorial e / o giocare un po 'con quello che ho appena pubblicato.

In bocca al lupo!

MODIFICARE:

Questo post mostra come estendere l'esempio sopra in modo che a Map<String, Double>possa essere fornito un che contiene variabili nell'espressione fornita.

Per far funzionare questo codice con una versione corrente di Antlr (giugno 2014) ho dovuto apportare alcune modifiche. ANTLRStringStreamdoveva diventare ANTLRInputStream, il valore restituito doveva cambiare da parser.eval()a parser.eval().value, e avevo bisogno di rimuovere la WSclausola alla fine, perché i valori di attributo come quelli $channelnon possono più apparire nelle azioni lexer.


1
Dove parser.eval()avvengono le implementazioni di ? Non è chiaro QUI o sul Wiki di ANTLR3!

1
@Jarrod, err, scusa, non ti capisco davvero. evalè una regola del parser che restituisce a double. Quindi c'è un eval()metodo che puoi chiamare su un'istanza di un ExpParser, proprio come ho dimostrato nel ANTLRDemo.main(...). Dopo aver generato un lexer / parser, apri semplicemente il file ExpParser.javae vedrai che esiste un eval()metodo che restituisce a double.
Bart Kiers,

@Bart Lo sto cercando da una settimana - questo è il primo esempio che è stato effettivamente dettagliato e abbastanza completo per funzionare la prima volta e che penso di aver capito. Mi ero quasi arreso. Grazie!
Vineel Shah,

13

Il mega tutorial di ANTLR di Gabriele Tomassetti è molto utile

Ha esempi di grammatica, esempi di visitatori in diverse lingue (Java, JavaScript, C # e Python) e molte altre cose. Altamente raccomandato.

EDIT: altri articoli utili di Gabriele Tomassetti su ANTLR


Ottimo tutorial!
Manish Patel

Antlr ora ha cpp anche come lingua di destinazione. Ci sono tutorial con esempio su cpp?
Vineeshvs,

Lo stesso ragazzo ha realizzato un tutorial per ANTLR in C ++ tomassetti.me/getting-started-antlr-cpp Immagino che troverai quello che cerchi qui o nel mega tutorial
solo

7

Per Antlr 4 il processo di generazione del codice java è il seguente: -

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

Aggiorna di conseguenza il nome del tuo vaso nel percorso di classe.


2

Su https://github.com/BITPlan/com.bitplan.antlr troverai una libreria java ANTLR con alcune utili classi di supporto e alcuni esempi completi. È pronto per essere utilizzato con Maven e se ti piacciono le eclissi e Maven.

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

è un linguaggio Expression semplice che può fare moltiplicare e aggiungere operazioni. https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestExpParser.java ha i test unitari corrispondenti per questo.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/iri/IRIParser.g4 è un parser IRI che è stato suddiviso in tre parti:

  1. grammatica del parser
  2. grammatica lexer
  3. grammatica LexBasic importata

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIRIParser.java ha i test unitari per questo.

Personalmente ho trovato questa la parte più difficile da ottenere. Vedi http://wiki.bitplan.com/index.php/ANTLR_maven_plugin

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

contiene altri tre esempi che sono stati creati per un problema di prestazioni di ANTLR4 in una versione precedente. Nel frattempo questo problema è stato risolto come mostra la https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIssue994.java .


2

la versione 4.7.1 era leggermente diversa: per importazione:

import org.antlr.v4.runtime.*;

per il segmento principale, notare i CharStreams:

CharStream in = CharStreams.fromString("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.