Differences

This shows you the differences between two versions of the page.

Link to this comparison view

alf:laboratoare:05 [2020/03/16 21:42]
diana.ghindaoanu [Exercises]
alf:laboratoare:05 [2022/04/04 23:19] (current)
diana.ghindaoanu [Exercices]
Line 1: Line 1:
-====== TP 5 - Parser ======+====== TP 5 - Parser ​du langage. Introduction à l'​AST ​======
  
-===== Fichier ​de jetons ​(tokens) et grammaire =====+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. ​
  
-Le jison se compose ​de deux parties: ​une partie ​de description ​de jetons (tokens)(fichier.l) et une partie ​de description ​de grammaire (fichier.y).+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 lexerCela permet ​de combiner les deux types de règles dans un même fichier ​de grammaire.
  
-<​note>​ +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)**.
-Dans les fichiers Jison (.et .y), le caractère échappant peut être fait avec ""​ ou \ +
-</​note>​+
  
-<code jison example.l+L'​**AST** est une représentation arborescente de la structure syntaxique abstraite du code source écrit dans un langage de programmationChaque 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.
-Ws         \s+ +
-Digit      [0-9]+
  
-%% 
-"​-" ​       { return '​-';​ } 
-"​+" ​       { return '​+';​ } 
-{Ws}       { /* empty space */ } 
-{Digit}+ ​  { return '​NUMBER';​ } 
  
-</​code>​+{{ :​alf:​laboratoare:​ex1.jpg?​550&​nolink }}
  
  
-<code jison example.y>​ +===== ANTLR4 Visitor =====
-// when it is ambiguous, derive the left part +
-%left '​+'​ '​-'​+
  
-%{ +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**).
-     ​// javascript code +
-        ​// use this to declare global ​  +
-        // javascript code that your parser uses +
-%}+
  
-%%  
-start: expr {  
-                 ​return $$;  
-            }; 
-  
-expr: expr '​+'​ expr { ​ 
- console.log ('rule expr + expr'​); ​ 
- console.log ($1 + ' + ' + $3);  
- } 
-      | expr '​-'​ expr {  
-      console.log ('rule expr - expr'​);​ 
-      console.log ($1 + ' - ' + $3);  
-      } 
-      | NUMBER {  
-   console.log ('rule NUMBER'​);​ 
-   console.log ('​number '​+$1); ​ 
-   $$ = $1;  
-   }; 
-</​code>​ 
  
-==== Actions ==== +Ainsi, ANTLR4 peut parcourir l'AST progressivement,​ en appelant notre code aux nœuds ​d'intérêt. ​
-La forme d'une règle de grammaire est+
  
-<​code>​ +Les caractéristiques du **Visitor** sont
-rulealternative1 { action statements } +   * Les méthodes doivent accompagner (//walk//) leurs enfants avec des appels de visite explicites 
-      | alternative2 { action statements } +   * Oublier d'​appeler ''​visit()''​ sur les enfants d'un nœud signifie que ces sous-arbres ne sont pas visités 
-      | alternative3 { action statements } +   * 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
-      | alternativen { action statements }; +
-</code>+
  
-Ici vous pouvez écrire du code JavaScript. 
  
-Les caractères spéciaux sont+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. ​
  
-  * %%$$%% : La valeur par laquelle la règle sera remplacée +<​code>​ 
-  * %%$1%% ​La valeur du premier élément de la règle (peut être la valeur d'une autre règle calculée) +start       declaration ​                 #​declarationStatement 
-  * %%$2%% : La valeur du second élément de la règle (peut être la valeur d'une autre règle calculée) +            ;
-  * %%$3%% ... +
-  * ... +
-  * %%$n%% - La valeur du n<​sup>​ème</​sup>​ élément de la règle (peut être la valeur d'une autre règle calculée)+
  
-<​note>​ +declaration : type VARIABLE EQ value       #​variableDeclaration 
-Si "​return"​ est utilisé dans n'​importe quelle action, cela arrêtera le parser et renverra cette valeur dans le code javascript qui a exécuté parse (). +            ;
-</​note>​+
  
-La règle de début renvoie généralement une valeur.+type        : INT                           #​typeInt 
 +            | FLOAT                         #​typeFloat 
 +            | STRING ​                       #​typeString 
 +            ;
  
-==== Les Erreurs ====+value       : INT_NUMBER ​                   #valueInt 
 +            | FLOAT_NUMBER ​                 #​valueFloat 
 +            | STRING_TEXT ​                  #​valueString 
 +            ; 
 +</​code>​ 
 +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:
  
-Si le parser ne comprend pas un jeton ou une règle, il lancera une exception.+<code javascript>​ 
 +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; 
 +
 +</​code>​ 
 +Ces fonctions seront écrasées (//​overwritten//​) plus tard afin de visiter les noeuds.
  
-L'​exception a le format suivant 
  
-  * //message// - Le message d'​erreur +===== Création ​des noeuds =====
-  * //hash// - Un dictionnaire contenant ​des informations sur l'​erreur +
-    * //text// - Le texte (token) qui a provoqué l'​erreur +
-    * //token// - Le jeton (token) qui a provoqué l'​erreur +
-    * //line// - Le numéro de ligne où l'​erreur est (en commençant par 0) +
-    * //loc// - Des détails sur la position où l'​erreur est +
-      * //​first_line//​ +
-      * //​last_line//​ +
-      * //​first_column//​ +
-      * //​last_column//​ +
-    * //​expected//​ - Une liste de jetons (tokens) attendus+
  
-===== Générer le parser =====+Pour générer l'​**AST** a l'aide du **Visitor**,​ on va créer des classes représentatives pour chaque noeud. ​
  
-Pour générer ​le parserexécutez jison en utilisant ​les fichiers .l et .y+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 retournerpour chaque type de noeud, un objet contenant, outre les propriétés spécifiques,​ le nom de la classe en tant qu**id** du noeud.
  
-<code bash> +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.
-jison example.y example.l +
-</​code>​+
  
-Cela générera un fichier JavaScript que vous pouvez utiliser pour diviser un texte en jetons et le parser en utilisant les jetons et la grammaire décrits dans le fichier jison.+<code javascript>​
  
-===== Utilisation du parser généré =====+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 */ 
 +         
 +    } 
 +}
  
-Pour utiliser le parser généré, importez le fichier de parser et utilisez l'​objet parser. Cet objet a une fonction parse qui reçoit un string ​et le parse. Au moment de l'​analyse,​ il exécute les actions définies dans le fichier jison et retourne la valeur renvoyée par l'​action de règle de démarrage.+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 */ 
 +        ​
  
-<code javascript main.js>​ +    }
-"use strict";​ +
-  +
-// import the generated Parser +
-var parser = require ('​./​example.js'​).parser;​ +
-  +
-// add a text to the parser +
-try +
-+
-        // run the parser using a string +
- parser.parse ('​22+3-4+6'​);​+
 } }
-catch (e) +class TypeNode extends ASTNode { 
-+    constructor(public readonly type_name: string) { 
-        ​// display the error message and data +        ​super(); 
- console.log ​(e.message); +    } 
- console.log ​(e.hash);+    toJSON() 
 +        /* TODO: Return here an object having the id "​type"​ and the following properties: type */ 
 +    }
 } }
 </​code>​ </​code>​
  
-===== L'​arbre ​de parse ===== +===== Visiteur de l'arbre =====
-L'​arbre d'​analyse doit être généré manuellement. Cela signifie qu'au lieu de définir une valeur sur %%$$%%, nous définissons un objet qui est en fait un nœud dans l'​arbre de parse.+
  
-<code jison>+Pour //visiter// les composants et générer l'​arbre,​ il faut surcharger les méthodes du visiteur de la façon suivante:
  
-...+<code typescript>​ 
 +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 
 +        ) 
 +    } 
 +</​code>​
  
-start: expr  {  
-                  return { 
-                    type: '​start',​ // type of node 
-                    items: [$$] // array of children - here just one 
-                  }  
-             }; 
-              
-expr: expr '​+'​ expr  {  
-                        $$ = { 
-                           type: '​expr',​ // type of node 
-                           ​items:​ [$1, $2, $3] // array of children - here just one 
-                        }  
-                     }; 
-  ​ 
  
-...+Pour voir le résultat de l'​arbre généré, on va l'​afficher dans un fichier:
  
 +<code javascript>​
 +const visitor = new MyAlfVisitor();​
 +fs.writeFileSync('​./​output.json',​ JSON.stringify(visitor.visit(tree).toJSON(),​ null, 4), '​utf8'​);​
 </​code>​ </​code>​
  
-===== Exercises ===== 
  
-<code+<note important
-int a <- 2; +Après chaque modification apportée à la grammaire (fichier ''​Alf.g4''​), ​vous devez exécuter ​la commande suivante ​pour enregistrer ​les changements: ​<​code ​bash
-double b <- 5.5; +npm run antlr4ts
-string c <- "​alf";​ +
-</​code>​  +
-  - Telechargez la structure du TP, disponible sur [[https://​github.com/​UPB-FILS/​alf/​tree/​master/​TP/​TP5|git]].Écrivez les fichiers jison (.y et .l) pour la declaration des variables, qui a le format de l'​exemple precedent. **Attention!** Vous devez compléter le fichier .l avec les règles pour les jetons, ainsi que le fichier .y qui contient ​la description de cette grammaire.(**2p**)  +
-  - Ajoutez dans le fichier ​jison les expressions mathématiques (+, -, *, /, %). Affichez le message ​'No errors' ​si la grammaire a été analysée avec succès.(**1.5**) +
-    - La grammaire doit refléter l'ordre mathématique (ordre d'exécution des opérations mathématiques). (**1p**) +
-    - Pour les chaînes de caractères on ne peut utiliser que l'​opérateur + (concaténation). (**0.5p**) +
-  - Faites le calcul des expressions de l'​exercice précédent. Par exemplepour l'​expression ''​ 2 + 3 * 5 '',​ le programme doit afficher ​la valeur 17 apres l'​analyse de la grammaire. (**1.5p**) +
-    - S'il n'y a pas de variables, affichez le résultat. (**0.5p**) +
-    - Si l'​expression contient des variables, affichez le message: "The expression contains variables: var1, var2, var3, ...", où //var1//, //var2// et //var3// sont les noms des variables trouvées. (**1p**) +
-  - Ajoutez les parantheses //()// aux expressions de l'​exercice 2 et écrivez la règle ​pour qu'​elles soient acceptées. Par exemple, pour l'​expression ''​ (2 + 3) * 5 ''​ votre programme doit afficher la valeur 25.(**1p**) +
-  - Affichez ​les erreurs en format texte, en donnant des explications en ce qui les concerne à l'​utilisateur. (**1p**) +
-  - Faites la grammaire accepter plus d'une expression. Les expressions sont séparées par ;. Le programme affichera la valeur de chaque expression. Les expressions seront lues à partir d'un fichier donné comme paramètre dans la ligne de commande. (**1p**) +
-  - Pour chaque opération, affichez aussi les opérandes et l'​opérateur. (**1p**) ​<​code>​ +
-2 + 3 * 5; +
-/* +
-Operator: * +
-Operands: 3, 5 +
-15 +
-Operator: + +
-Operands: 2, 15 +
-17 +
-*/+
 </​code>​ </​code>​
-  - Ajoutez aussi des variables booléenes et les opérations logiques ||, && et ! (//ou//, //et// et //non//). (**1p**) + 
-  - **Bonus**Changez la grammaire pour que les variables puissent être déclarées en utilisant des expressions. (**1p**)<​code>​ +Après chaque modification apportée au fichier ''​index.ts'',​ vous devez exécuter ​les commandes suivantes pour voir le résultat correct: <​code ​bash
-int a <- 2 + 3 * 5;+npx tsc 
 +node index.js
 </​code>​ </​code>​
-  ​ +</​note>​ 
-==== Solutions ​==== + 
-[[https://​github.com/​UPB-FILS/​alf/tree/master/TP/TP5|Solutions]]+===== Exercices ===== 
 + 
 +  - Créez un nouveau projet ANLTR4. Téléchargez la structure du TP depuis le [[https://​github.com/​UPB-FILS-ALF/TP/tree/main/TP5|github repository]], inspectez la structure de la grammaire et complétez les fonctions **toJSON()** selon les indications marquées par TODO. **(2p)** 
 +  - 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)** <code bash>​float _var1 = 7.5; 
 +string _var2 = '​alf';</​code>​ 
 +   - 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)** <code bash> int _var1 = 1; 
 +5*(2+4)/​7;</​code>​ 
 +   - 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** <code bash>​float _var1 = 5*(2+4)/​7;</​code>​ 
 +   - **BONUS**: Affichez le contenu de l'​arbre d'​analyse dans un fichier ayant le format ''​JSON''​. **(1p)** 
 + 
  
alf/laboratoare/05.1584387727.txt.gz · Last modified: 2020/03/16 21:42 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