TP 4 - Parser

Assignment

Vous devez accepter l'assignment d'ici est travailler avec ce repository: Lab 4

Le but de ce TP est d'introduire la notion de parser en ANTLR4.

Un parser représente la partie d'un compilateur qui reconnaît des jetons (tokens) ordonnés et spécifiés par un ensemble de règles d'analyse. Cet ensemble de règles décrit un “langage” source reçu par le parser sous la forme d'instructions séquentielles du programme source, des entrées de la ligne de commande, de balises ou d'autres.

L'analyseur généré fait correspondre les jetons d'entrée (tokens définis par le lexer) aux règles, émettant un arbre de syntaxe abstraite (AST), dont on va discuter plus dans les TPs suivants. Cet arbre représente la sortie de l'analyseur.

Règles du parser

La règle la plus simple est juste un nom de règle suivi d'une seule alternative terminée par un point-virgule, comme on l'a vu dans le TP précédent:

grammar Alf;

start:;

Lorsqu'une règle se compose de plusieurs aternatives, on utilise le séparateur | pour les différencier:

grammar Alf;

start: expression ';' ;

expression: addition
          | subtraction
          | multiplication
          | division
          ;

Actions et attributs

Les actions représentent des blocs de texte écrits dans le langage de programmation cible et délimités par des accolades {}. Elles sont déclenchées selon leur position dans la grammaire.

grammar Alf;

declaration: type VARIABLE ';'   { System.out.println("This is a declaration statement!"); }
           ;
type       : 'String '
           | 'number '
           | 'boolean '
           ;
VARIABLE: ([a-zA-Z]+);

Dans la plupart des cas, les actions sont utilisées pour accéder aux attributs des tokens et aux références des regles qu'on a définies. Par exemple, dans la grammaire suivant, on va afficher pour chaque type d'expression la valeur numérique des opérants et le token pour l'opérateur:

grammar Alf;

start: expression
     ;

expression: left=expression op=MUL right=expression        {if(!$left.text.isEmpty() && !$op.text.isEmpty() && !$right.text.isEmpty()) System.out.println($left.text + " " + $op.text + " " + $right.text + " ");}
          | left=expression op=DIV right=expression        {if(!$left.text.isEmpty() && !$op.text.isEmpty() && !$right.text.isEmpty()) System.out.println($left.text + " " + $op.text + " " + $right.text + " ");}
          | left=expression op=PLUS right=expression       {if(!$left.text.isEmpty() && !$op.text.isEmpty() && !$right.text.isEmpty()) System.out.println($left.text + " " + $op.text + " " + $right.text + " ");}
          | left=expression op=MINUS right=expression      {if(!$left.text.isEmpty() && !$op.text.isEmpty() && !$right.text.isEmpty()) System.out.println($left.text + " " + $op.text + " " + $right.text + " ");}
          | INT
          ;

WS      : ([' ']+) -> skip;   /* Skip whitespaces */
NEWLINE : ([\r\n]+);
INT     : ([0-9]+) ;
MUL     : ('*');
DIV     : ('/');
PLUS    : ('+');
MINUS   : ('-');

Tous les jetons (tokens) incluent un ensemble d'attributs prédéfinis, read-only, qui décrivent des propriétés utiles pour les tokens et auxquels les actions peuvent accéder a travers l'instruction $label.attribute, où label représente le nom du jeton/de la règle et attribute le nom de l'attribut.

Token Attributes

  • text: string - Le texte correspondant au jeton; peut être traduit dans un appel de fonction getText(). Exemple: $ID.text.
  • type: number - Le type du jeton (nombre entier différent de 0), tel que INT; peut être traduit dans un appel de fonction getType(). Exemple: $ID.type.
  • line: number - Le numéro de la ligne ou le jeton peut etre trouvé, commençant par 1; peut être traduit dans un appel de la fonction getLine(). Exemple: $ID.line.
  • pos: number - La position ou le premier caractère du jeton se trouve dans une ligne, commençant par 0; peut être traduit dans un appel de la fonction togetCharPositionInLine(). Example: $ID.pos.
  • index: number: L'indice général d'un token dans le flux des jetons, commençant par 0; peut être traduit dans un appel de la fonction getTokenIndex(). Example: $ID.index.
  • int: number - La valeur entière du texte d'un jeton, supposant que le texte est une valeur numerique valide; peut être traduit dans un appel de la fonction Integer.valueOf(text-of-token). Example: $INT.int.

Génération du parser

Après avoir décrit une grammaire dans un fichier .g4 avec des jetons et des règles, il faut faire clic-droit sur le fichier et appuyer sur la commande Generate ANTLR Recognizer. (Pour configurer les fichiers, voir TP3)

Cette commande va générer les fichiers .java correspondants, dont on va se servir pour diviser un texte en jetons et pour l'analyser a l'aide du parser.

Utilisation du parser

Un exemple d'utilisation du parser dans un fichier Main.java:

// Import generated files
import com.Alf.parser.AlfLexer;
import com.Alf.parser.AlfParser;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
 
 
public class Main {
    public static void main(String[] args) {
        String input = "1+2*4/5";
        AlfLexer lexer = new AlfLexer(CharStreams.fromString(input));
        AlfParser parser = new AlfParser(new CommonTokenStream(lexer));
 
        // Parse the input, where `start` is whatever entry point you defined
        AlfParser.StartContext tree = parser.start();
    }
}

Visualisation de l'arbre d'analyse

Pour la visualisation de l'arbre d'analyse, on utilise la fontion du plug-in ANTLRv4 ANTLR Preview. Clic-drot sur la premiere regle definie dans la grammaire et ecrivez en gauche le texte qu'on doit analyser. En droit, on peut voir l'arbre genere. Pour plus d'informations, voir TP3.

Exercices

Pour chaque exercise, faites une capture d'ecran de l'arbre d'analyse et mettez-le dans le directoire ParseTree qui se trouve dans le Github repository.

  1. Ajoutez dans la grammaire d'une declaration (donnee dans le laboratoire) les instructions nécessaires pour la déclaration complexe des variables, selon le langage suivant: (2p)
    int _var1 = 2;
    float _var2 = 5.55;
    String _var3 = "alf"; 

    Suivez les étapes nécessaires pour visualiser l'arbre d'analyse et testez chaque règle à l'aide du fichier texte pour les données.

  2. Ajoutez à votre grammaire des jetons et des règles pour les expressions mathématiques. Votre grammaire doit reconnaître les opérations et les opérateurs d'addition, soustraction, multiplication et division (+, -, *, /, %). Suivez les étapes nécessaires pour visualiser l'arbre d'analyse et testez la fonctionnalité du programme pour l'expression 2+5/10-7. (3p)
  3. Repétez l'exercice précédent en tenant compte du fait qu'une expression peut aussi contenir des variables. Comme mentionné dans le premier exercice, les variables sont de la forme _var1. Vérifiez la correctitude de la grammaire en ajoutant l'expression 15/_var1-5; au fichier texte pour les données.(1p)
  4. Ajoutez à la grammaire les jetons et les règles nécessaires pour accépter des parantheses à l'intérieur des expressions. Suivez les étapes nécessaires pour la visualisation de l'arbre et testez le programme en ajoutant l'expression (2+5)/3 au fichier texte pour les données. (2p)
  5. Faites la grammaire accepter plus d'une expression, supposant que les expressions sont séparées par ;, mais aussi par des lignes vides. Modifiez le contenu du fichier texte pour les données en ajoutant les expressions suivantes et visualisez l'arbre d'analyse: (2p)
    String _var1 = "alf";
    (2+7)/5*3;
  6. BONUS: Changez la grammaire pour que les variables puissent être déclarées en utilisant des expressions et visualisez l'arbre d'analyse pour l'instruction suivante: (1.5p)
    int _var1 = 2 + 3 * 5;
alf/laboratoare/04_fr_java.txt · Last modified: 2023/03/28 03:27 by alexandra.negoita02
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0