TP4 -- L3 -- Langage et Compilation

Analyse lexicale et syntaxique avec ANTLR

Premier Parseur

Pour reconnaître un langage non régulier, nous allons utiliser un parseur en plus du lexeur. Les règles utilisent le même format que pour le lexeur, mais ici les non terminaux doivent commencer par une minuscule. Il est commode de mettre l'axiome en premier, ici dans l'exemple : file. Il est également recommandé de terminer sa définition par le jeton système EOF qui est produit automatiquement par le lexeur lors de la rencontre de la fin du fichier d'entrée.
grammar AnBn;

// grammar rules 

file 
    :   anbn EOF ;

anbn  
    :   A anbn B
    |   /* vide */ ;

// lexer rules
A   :   'a';
B   :   'b';
C   :   'c';

UNMATCH :  . ->  skip; 
Pour la partie lexeur, on ne propage au parseur que les jetons A, B et C. En effet grâce à la directive skip, tout ce qui est reconnu par la règle lexicale UNMATCH est simplement caché. On utilise en général cet artifice pour les commentaires ou les espaces non significatifs.
Créer la grammaire AnBn.g4 et la compiler.

Utilisation de antlr4-grun

Il existe un outil permettant de visualiser l'arbre de syntaxe obtenu lors de l'analyse d'une expression : antlr4-grun

On le lance sur les machines du département avec

 $ antlr4-grun 
qui est équivalent à :
 $ java org.antlr.v4.runtime.misc.TestRig 
(pour peu que la variable CLASSPATH soit correctement configurée).

Pour le lancer, il suffit de préciser la grammaire, le point d'entrée (l'axiome) ainsi que l'option -gui puis d'entrer le texte à analyser et finir avec ctrl+D (EOF).

 $ antlr4-grun AnBn 'anbn' -gui

aaaaabbbbb 
Quel est le résultat d'analyse avec les entrées:

Une calculette

Soit la grammaire attribuée suivante qui permet d'analyser des sommes et produits d'entiers.
grammar Calculette;

start
    : expr EOF;

expr
    : expr '*' expr
    | expr '+' expr
    | ENTIER
    ;

// lexer
NEWLINE : '\r'? '\n'  -> skip;

WS :   (' '|'\t')+ -> skip  ;

ENTIER : ('0'..'9')+  ;

UNMATCH : . -> skip ;
Montrer que cette grammaire est ambiguë.

En pratique, ANTLR4 va autoriser cette grammaire et produire un analyseur.

Compiler cette grammaire et la tester avec antlr4-grun sur l'exemple 4 + 5 * 6 + 4

On décide alors de modifier le choix de la règle expr par:

expr
   : expr '+'expr
   | expr '*' expr
   | ENTIER
   ;
Recompiler cette grammaire et la tester avec antlr4-grun sur l'exemple 4 + 5 * 6 + 4. Commenter.
Enrichir la grammaire avec les expressions parenthésées

Grammaire attribuée

Nous allons profiter de la capacité d'ANTLR à utiliser des grammaires attribuées afin de calculer la valeur des expressions. On associe à chaque symbole un attribut synthétisé val. On manipule ces attributs dans les actions en préfixant les symboles par un dollar.

expr  returns [ int val ]
    : a=expr '+' b=expr {$val = $a.val + $b.val;}
    | ...
    ;
Compléter cette grammaire à l'aide d'annotations pour effectuer le calcul de l'expression et ajouter le support de la soustraction et de la division.
Il est probable que vous ayez besoin d'une fonction pour vous faciliter la vie. Vous pouvez ajouter des variables ou des fonctions au parseur dans une section de code java après le nom de la grammaire.
@parser::members {

    private int evalexpr (int x, String op, int y) {
        if ( op.equals("*") ){
            return x*y;
        } else if ( op.equals("+") ){
            return x+y;
        } else {
           System.err.println("Opérateur arithmétique incorrect : '"+op+"'");
           throw new IllegalArgumentException("Opérateur arithmétique incorrect : '"+op+"'");
        }
    }
}
On notera qu'il est possible de récupérer le contenu d'un jeton (token) à l'aide de la méthode suivante:
expr returns [ int val ]
    : a=expr op=('*'|'/') b=expr {System.out.println($op.text);}
On peut de manière similaire récupérer la valeur du jeton comme entier (si adapté) avec .int. Pour déboguer on peut aussi accéder à la ligne et à la position (.line et .pos).
Tester de manière approfondie avant de passer à la suite
On s'inspire de la précédence en java : multiplication et division entière (à précédence égale) puis addition et soustraction binaire (à précédence égale). À précédence égale, on évalue de la gauche vers la droite. Vous pouvez vérifier avec les exemples suivants qui doivent tous donner la réponse finale.
42
24+24-6
5*8+2*1
6*4/5+38
42+1+2+-3
5*8+2*-1/-1
(5*6*7*11 + 2)/11*5-1008
(5*6*7*11 + 2)/(11*5)
(5*6*7*11 + 2)/11/5
(5*6*7*11 + 2)/(11/5)-1114