ANTLR: Y a-t-il un exemple simple?

230

J'aimerais commencer avec ANTLR, mais après avoir passé quelques heures à passer en revue les exemples sur le site antlr.org , je ne parviens toujours pas à comprendre clairement le processus de grammaire vers Java.

Y a-t-il un exemple simple, quelque chose comme une calculatrice à quatre opérations implémentée avec ANTLR passant par la définition de l'analyseur et jusqu'au code source Java?

Eli
la source
2
Cet exemple précis est utilisé comme tutoriel sur le site d'Antlr, la dernière fois que j'ai vérifié.
Cory Petosky
1
@Cory Petosky: pouvez-vous fournir le lien?
Eli
Je viens de poster les premières parties d'un tutoriel vidéo sur ANTLR. Voir javadude.com/articles/antlr3xtut J'espère que cela vous sera utile!
Scott Stanchfield
2
Je partage moi aussi votre recherche.
Paul Draper
1
La meilleure réponse pour ANTLR 4 est d'acheter le livre de Parr "The Definitive ANTLR 4 Reference".
james.garriss

Réponses:

448

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 evalrè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. ANTLRStringStreamnécessaire pour devenir ANTLRInputStream, la valeur renvoyée devait passer de parser.eval()à parser.eval().value, et je devais supprimer la WSclause à la fin, car les valeurs d'attribut telles que $channelne sont plus autorisées à apparaître dans les actions de lexer.

Bart Kiers
la source
1
D'où viennent les implémentations parser.eval()? Ce n'est pas clair ICI ou sur le Wiki ANTLR3!
1
@Jarrod, euh, désolé, je ne te comprends pas vraiment. evalest une règle d'analyse qui renvoie a double. Il existe donc une eval()méthode que vous pouvez invoquer sur une instance d'un ExpParser, comme je l'ai démontré dans le ANTLRDemo.main(...). Après avoir généré un lexer / analyseur, ouvrez simplement le fichier ExpParser.javaet vous verrez qu'il existe une eval()méthode renvoyant a double.
Bart Kiers
@Bart Je fais des recherches sur ce sujet depuis une semaine - c'est le premier exemple qui était en fait suffisamment détaillé et complet pour fonctionner la première fois et que je pense comprendre. J'avais presque abandonné. Merci!
Vineel Shah
13

Le méga tutoriel ANTLR de Gabriele Tomassetti est très utile

Il contient des exemples de grammaire, des exemples de visiteurs dans différents langages (Java, JavaScript, C # et Python) et bien d'autres choses. Hautement recommandé.

EDIT: autres articles utiles de Gabriele Tomassetti sur ANTLR

solo
la source
Grand tutoriel!
Manish Patel
Antlr a désormais également cpp comme langue cible. Existe-t-il des tutoriels avec exemple sur cpp?
Vineeshvs
Le même gars a fait un tutoriel pour ANTLR en C ++ tomassetti.me/getting-started-antlr-cpp Je suppose que vous allez trouver ce que vous cherchez ici ou dans le méga tutoriel
solo
7

Pour Antlr 4, le processus de génération de code java est ci-dessous: -

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

Mettez à jour le nom de votre bocal dans classpath en conséquence.

Abhishek K
la source
2

Sur https://github.com/BITPlan/com.bitplan.antlr, vous trouverez une bibliothèque java ANTLR avec quelques classes d'aide utiles et quelques exemples complets. Il est prêt à être utilisé avec maven et si vous aimez l'éclipse et le maven.

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

est un langage d'expression simple qui peut multiplier et ajouter des opérations. https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestExpParser.java a les tests unitaires correspondants pour cela.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/iri/IRIParser.g4 est un analyseur IRI qui a été divisé en trois parties:

  1. grammaire de l'analyseur
  2. lexer grammar
  3. grammaire LexBasic importée

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIRIParser.java a les tests unitaires pour cela.

Personnellement, j'ai trouvé que c'était la partie la plus délicate pour réussir. Voir http://wiki.bitplan.com/index.php/ANTLR_maven_plugin

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

contient trois autres exemples qui ont été créés pour un problème de performances d'ANTLR4 dans une version antérieure. En attendant, ce problème a été résolu comme le montre le cas de test https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIssue994.java .

Wolfgang Fahl
la source
2

la version 4.7.1 était légèrement différente: pour l'importation:

import org.antlr.v4.runtime.*;

pour le segment principal - notez les CharStreams:

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