Differences

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

Link to this comparison view

alf:laboratoare:05 [2020/03/16 21:39]
diana.ghindaoanu
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 ===== 
  
-  - **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. 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 suivant(**2p**)  +<note important>​ 
-  - <​code>​ +Après chaque modification apportée à la grammaire (fichier ''​Alf.g4''​), vous devez exécuter la commande suivante ​pour enregistrer les changements: <​code ​bash
-int a <- 2; +npm run antlr4ts
-double b <- 5.5; +
-string c <- "​alf";​+
 </​code>​ </​code>​
  
-  - 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**) +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
-    - La grammaire doit refléter l'ordre mathématique (ordre d'exécution des opérations mathématiques). (**1p**) +npx tsc 
-    - Pour les chaînes de caractères on ne peut utiliser que l'​opérateur + (concaténation). (**0.5p**) +node index.js
-  - Faites le calcul des expressions de l'​exercice précédent. Par exemple, ​pour 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**) +</​note>​ 
-  - **Bonus**: Changez la grammaire ​pour que les variables puissent ​être déclarées en utilisant ​des expressions. ​(**1p**)<​code>​ + 
-int a <- 2 + 3 * 5; +===== Exercices ===== 
-</​code>​ + 
-   +  - 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)** 
-==== Solutions ==== +  - 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; 
-[[https://github.com/​UPB-FILS/​alf/​tree/​master/​TP/​TP5|Solutions]]+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.1584387552.txt.gz · Last modified: 2020/03/16 21:39 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