TP 5 - Parser du langage. Introduction à l'AST

Le principal role du parser est de reconnaître les ordres contextuels décrits par les jetons spécifiés par un ensemble de règles d'analyse.

Lorsqu'on utilise ANTLR4 pour créer un parser, il est important de se rappeler qu'ANTLR4 suppose que les noms de grammaire de l'analyseur commencent par une lettre minuscule, par opposition à un nom de grammaire de lexer. Cela permet de combiner les deux types de règles dans un même fichier de grammaire.

A partir de l'ensemble de règles définies dans la grammaire, le parser réalise la correspondance des jetons d'entrée et génère comme sortie l'AST (Abstract Syntax Tree).

L'AST est une représentation arborescente de la structure syntaxique abstraite du code source écrit dans un langage de programmation. Chaque nœud de l'arborescence désigne un jeton identifié, reconnu comme étant dans un ordre correct pour le langage défini dans la grammaire.

ex1.jpg

ANTLR4 Visitor

En ANTLR, le Visitor représente une fonctionnalité qui permet de parcourir l'arbre d'analyse (parse tree) généré automatiquement, son principal role étant de visiter et traiter les noeuds du parse tree, pour construire l'arbre d'analyse (AST).

Ainsi, ANTLR4 peut parcourir l'AST progressivement, en appelant notre code aux nœuds d'intérêt.

Les caractéristiques du Visitor sont:

  • Les méthodes doivent accompagner (walk) leurs enfants avec des appels de visite explicites
  • Oublier d'appeler visit() sur les enfants d'un nœud signifie que ces sous-arbres ne sont pas visités
  • Les méthodes peuvent renvoyer n'importe quel type personnalisé
  • Il utilise la pile d'appels (call stack) pour gérer les traversées d'arbres

Afin d'obtenir des Visitors plus précis des arbres d'analyse, on peut étiquetter les alternatives des règles en utilisant l'opérateur #. Il est obligatoire soit d'associer des étiquettes pour toutes les sous-regles, soit pour aucune.

start       : declaration                  #declarationStatement
            ;

declaration : type VARIABLE EQ value       #variableDeclaration
            ;

type        : INT                           #typeInt
            | FLOAT                         #typeFloat
            | STRING                        #typeString
            ;

value       : INT_NUMBER                    #valueInt
            | FLOAT_NUMBER                  #valueFloat
            | STRING_TEXT                   #valueString
            ;

Si on va étiquetter les alternatives, dans le fichier AlfParser.ts on obtiendra des classes spécifiques pour chaque type de sous-regle et dans le fichier AlfVisitor.ts les interfaces exportées incluront des fonctions dépendant du contexte pour chaque étiquette définie:

export interface AlfVisitor<Result> extends ParseTreeVisitor<Result> {
	visitDeclarationStatement?: (ctx: DeclarationStatementContext) => Result;
        visitVariable?: (ctx: VariableContext) => Result;
        visitTypeInt?: (ctx: TypeIntContext) => Result;
        visitTypeFloat?: (ctx: TypeFloatContext) => Result;
        visitTypeString?: (ctx: TypeStringContext) => Result;
        visitValueInt?: (ctx: ValueIntContext) => Result;
        visitValueFloat?: (ctx: ValueFloatContext) => Result;
        visitValueString?: (ctx: ValueStringContext) => Result;
}

Ces fonctions seront écrasées (overwritten) plus tard afin de visiter les noeuds.

Création des noeuds

Pour générer l'AST a l'aide du Visitor, on va créer des classes représentatives pour chaque noeud.

La classe abstraite ASTNode décrit de façon générale un noeud de l'arbre, contenant aussi la fonction toJSON(), dont le but principal est de retourner, pour chaque type de noeud, un objet contenant, outre les propriétés spécifiques, le nom de la classe en tant quid du noeud.

Chaque type de noeud est représenté comme une classe enfant qui étend la classe ASTNode et qui contient dans le constructor ses paramètres spécifiques, comme dans l'exemple suivant.

abstract class ASTNode {
    constructor(){};
}
class StatementNode extends ASTNode {
    constructor(public readonly statement: ASTNode) {
        super();
    }
    toJSON() {
        /* TODO: Return here an object having the id "statement" and the statement a list of instructions */
    }
}
class DeclarationNode extends ASTNode {
    constructor(public readonly variable_type: string, public readonly variable: string, public readonly op: string, public readonly value: string|number) {
        super();
    }
    toJSON() {
 
        /* TODO: Return here an object having the id "declaration" and the following properties: variable_type, variable and value */
 
    }
}
 
class ValueNode extends ASTNode {
    constructor(public readonly value: number|string) {
        super();
    }
    toJSON() {
        /*TODO: Return here an object having the id "value" and the following properties: value */
 
 
    }
}
class TypeNode extends ASTNode {
    constructor(public readonly type_name: string) {
        super();
    }
    toJSON() {
        /* TODO: Return here an object having the id "type" and the following properties: type */
    }
}

Visiteur de l'arbre

Pour visiter les composants et générer l'arbre, il faut surcharger les méthodes du visiteur de la façon suivante:

class MyAlfVisitor extends AbstractParseTreeVisitor<ASTNode> implements AlfVisitor<ASTNode> {
    defaultResult() {
        return new StatementNode({} as ASTNode);
    }
    visitVariableDeclaration(ctx: VariableDeclarationContext): DeclarationNode {
        return new DeclarationNode(
            (this.visit(ctx.type()) as TypeNode).type_name,
            ctx.VARIABLE().text,
            ctx.EQ().text,
            (this.visit(ctx.value()) as ValueNode).value
        );
    }
    visitValueInt(ctx: ValueIntContext): ValueNode {
        return new ValueNode(
            parseInt(ctx.INT_NUMBER().text)
        );
    }
     visitTypeString(ctx: TypeStringContext): TypeNode {
        return new TypeNode(
            ctx.STRING().text
        )
    }

Pour voir le résultat de l'arbre généré, on va l'afficher dans un fichier:

const visitor = new MyAlfVisitor();
fs.writeFileSync('./output.json', JSON.stringify(visitor.visit(tree).toJSON(), null, 4), 'utf8');

Après chaque modification apportée à la grammaire (fichier Alf.g4), vous devez exécuter la commande suivante pour enregistrer les changements:

npm run antlr4ts

Après chaque modification apportée au fichier index.ts, vous devez exécuter les commandes suivantes pour voir le résultat correct:

npx tsc
node index.js

Exercices

  1. Créez un nouveau projet ANLTR4. Téléchargez la structure du TP depuis le github repository, inspectez la structure de la grammaire et complétez les fonctions toJSON() selon les indications marquées par TODO. (2p)
  2. Ajoutez au fichier de grammaire les règles pour accepter plusieurs instructions. Les instructions peuvent etre séparées par ; et une ou plusieurs lignes vides. Ajoutez les classes et les méthodes nécessaires pour pouvoir visiter les noeuds. Dans l'AST, chaque instruction doit etre ajoutée dans la liste statements du noeud principal. Testez le programme pour les instructions suivantes: (3p)
    float _var1 = 7.5;
    string _var2 = 'alf';
  3. Ajoutez à la grammaire décrite dans l'exemple les règles nécessaires pour l'utilisation des expressions (plusieurs expressions) du laboratoire précédent. Votre grammaire doit accepter maintenent aussi les déclarations des variables, que les expressions. Ajoutez les classes et les méthodes nécessaires pour visiter les nouveaux noeuds et générer l'arbre. Le noued correspondant aux expressions doit avoir les propriétés suivantes:, id: “expression”, left (le neoud correspondant a l'opérande de gauche), right (le neoud correspondant a l'opérande de droite), op (la valeur de l'opérateur) Testez la correctitude de votre grammaire en lisant les instructions suivantes depuis un fichier texte: (3p)
     int _var1 = 1;
    5*(2+4)/7;
  4. Ajoutez des règles et modifiez les méthodes du Visitor pour que les variables puissent prendre des valeurs qui sont des expressions. Testez pour l'instruction suivante: 2p
    float _var1 = 5*(2+4)/7;
  5. BONUS: Affichez le contenu de l'arbre d'analyse dans un fichier ayant le format JSON. (1p)
alf/laboratoare/05.txt · Last modified: 2022/04/04 23:19 by diana.ghindaoanu
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