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 eval
regola 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. ANTLRStringStream
doveva diventare ANTLRInputStream
, il valore restituito doveva cambiare da parser.eval()
a parser.eval().value
, e avevo bisogno di rimuovere la WS
clausola alla fine, perché i valori di attributo come quelli $channel
non possono più apparire nelle azioni lexer.