Remarque : cette réponse est pour ANTLR3 ! Si vous recherchez un exemple ANTLR4 , cette Q&R montre comment créer un analyseur d'expression simple et un évaluateur à l'aide d' ANTLR4 .
Vous créez d'abord une grammaire. Vous trouverez ci-dessous une petite grammaire que vous pouvez utiliser pour évaluer les expressions construites à l'aide des 4 opérateurs mathématiques de base: +, -, * et /. Vous pouvez également grouper des expressions à l'aide de parenthèses.
Notez que cette grammaire est juste très basique: elle ne gère pas les opérateurs unaires (le moins dans: -1 + 9) ou les décimales comme .99 (sans numéro de tête), pour ne nommer que deux défauts. Ceci est juste un exemple sur lequel vous pouvez travailler.
Voici le contenu du fichier de grammaire 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;}
;
(Les règles de l'analyseur commencent par une lettre minuscule et les règles de lexer commencent par une lettre majuscule)
Après avoir créé la grammaire, vous voudrez en générer un analyseur et un lexeur. Téléchargez le pot ANTLR et stockez-le dans le même répertoire que votre fichier de grammaire.
Exécutez la commande suivante sur votre shell / invite de commande:
java -cp antlr-3.2.jar org.antlr.Tool Exp.g
Il ne devrait pas produire de message d'erreur et les fichiers ExpLexer.java , ExpParser.java et Exp.tokens doivent maintenant être générés.
Pour voir si tout fonctionne correctement, créez cette classe de 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();
}
}
et compilez-le:
// *nix/MacOS
javac -cp .:antlr-3.2.jar ANTLRDemo.java
// Windows
javac -cp .;antlr-3.2.jar ANTLRDemo.java
puis exécutez-le:
// *nix/MacOS
java -cp .:antlr-3.2.jar ANTLRDemo
// Windows
java -cp .;antlr-3.2.jar ANTLRDemo
Si tout se passe bien, rien n'est imprimé sur la console. Cela signifie que l'analyseur n'a trouvé aucune erreur. Lorsque vous passez "12*(5-6)"
à "12*(5-6"
, puis recompilez et exécutez-le, il devrait être imprimé ce qui suit:
line 0:-1 mismatched input '<EOF>' expecting ')'
Bon, maintenant nous voulons ajouter un peu de code Java à la grammaire pour que l'analyseur fasse réellement quelque chose d'utile. L'ajout de code peut être fait en plaçant {
et à l' }
intérieur de votre grammaire avec du code Java simple à l'intérieur.
Mais d'abord: toutes les règles de l'analyseur dans le fichier de grammaire doivent renvoyer une valeur double primitive. Vous pouvez le faire en ajoutant returns [double value]
après chaque règle:
grammar Exp;
eval returns [double value]
: additionExp
;
additionExp returns [double value]
: multiplyExp
( '+' multiplyExp
| '-' multiplyExp
)*
;
// ...
ce qui nécessite peu d'explications: chaque règle devrait renvoyer une valeur double. Maintenant, pour "interagir" avec la valeur de retour double value
(qui n'est PAS à l'intérieur d'un bloc de code Java ordinaire {...}
) à l'intérieur d'un bloc de code, vous devrez ajouter un signe dollar devant 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); }
;
// ...
Voici la grammaire mais maintenant avec le code Java ajouté:
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;}
;
et puisque notre eval
règle retourne maintenant un double, changez votre ANTLRDemo.java en ceci:
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
}
}
Encore une fois (générez) un nouveau lexer et analyseur à partir de votre grammaire (1), compilez toutes les classes (2) et exécutez 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
et vous verrez maintenant le résultat de l'expression 12*(5-6)
imprimé sur votre console!
Encore une fois: ceci est une très brève explication. Je vous encourage à parcourir le wiki ANTLR et à lire quelques tutoriels et / ou jouer un peu avec ce que je viens de poster.
Bonne chance!
ÉDITER:
Cette publication montre comment étendre l'exemple ci-dessus afin de Map<String, Double>
pouvoir fournir un qui contient des variables dans l'expression fournie.
Pour que ce code fonctionne avec une version actuelle d'Antlr (juin 2014), j'ai dû apporter quelques modifications. ANTLRStringStream
nécessaire pour devenir ANTLRInputStream
, la valeur renvoyée devait passer de parser.eval()
à parser.eval().value
, et je devais supprimer la WS
clause à la fin, car les valeurs d'attribut telles que $channel
ne sont plus autorisées à apparaître dans les actions de lexer.