Differences

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

Link to this comparison view

alf:laboratoare:05 [2020/03/17 07:56]
alexandru.radovici
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 ​======
  
-<​note>​ +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
-**Microsoft Teams**: [[https://​teams.microsoft.com/​l/​channel/​19%3a7f9bb2a2a0494f95b24a71536b470c7b%40thread.tacv2/​TP%25205%2520-%2520Parser?​groupId=0c1b36fc-0332-4f9d-98cf-85bad7e6c898&​tenantId=2d8cc8ba-8dda-4334-9e5c-fac2092e9bac +
-|TP 4]]+
  
-  * username: votre username ​de fils.curs.pub.ro (username@stud.fils.upb.ro) +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.
-  * password: votre mot de passe de fils.curs.pub.ro +
  
-Selectez channel **TP 4 - Lexer** ​et appuyez sur le boutton ​**Join**.+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)**.
  
-Les archives avec les exercices seront envoyées à l'adresse //​diana.ghindaoanu@stud.fils.upb.ro// +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.
-</​note>​+
  
-===== Fichier de jetons (tokens) et grammaire ===== 
  
-Le jison se compose de deux partiesune partie de description de jetons (tokens)(fichier.l) et une partie de description de grammaire (fichier.y).+{{ :alf:​laboratoare:​ex1.jpg?​550&​nolink }}
  
-<​note>​ 
-Dans les fichiers Jison (.l et .y), le caractère échappant peut être fait avec ""​ ou \ 
-</​note>​ 
  
-<code jison example.l>​ +===== ANTLR4 Visitor =====
-Ws         \s+ +
-Digit      [0-9]+
  
-%% +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**).
-"​-" ​       { return ​'-'; } +
-"​+" ​       { return '​+';​ } +
-{Ws}       { /empty space */ +
-{Digit}+ ​  { return ​'NUMBER'; }+
  
-</​code>​ 
  
 +Ainsi, ANTLR4 peut parcourir l'AST progressivement,​ en appelant notre code aux nœuds d'​intérêt. ​
  
-<code jison example.y>​ +Les caractéristiques du **Visitor** sont: 
-// when it is ambiguous, derive the left part +   * Les méthodes doivent accompagner (//walk//) leurs enfants avec des appels de visite explicites 
-%left '+' '​-'​+   * 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
  
-%{ 
-     // javascript code 
-        // use this to declare global  ​ 
-        // javascript code that your parser uses 
-%} 
  
-%%  +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: 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 ==== +
-La forme d'une règle de grammaire est+
  
 <​code>​ <​code>​
-rulealternative1 { action statements } +start       declaration ​                 #​declarationStatement 
-      | alternative2 { action statements } +            ;
-      | alternative3 { action statements } +
-      ... +
-      | alternativen { action statements }; +
-</​code>​+
  
-Ici vous pouvez écrire du code JavaScript.+declaration : type VARIABLE EQ value       #​variableDeclaration 
 +            ;
  
-Les caractères spéciaux sont+type        : INT                           #​typeInt 
 +            | FLOAT                         #​typeFloat 
 +            | STRING ​                       #​typeString 
 +            ;
  
-  * %%$$%% ​La valeur par laquelle la règle sera remplacée +value       INT_NUMBER ​                   #valueInt 
-  * %%$1%% : La valeur du premier élément de la règle (peut être la valeur d'une autre règle calculée) +            | FLOAT_NUMBER ​                 #valueFloat 
-  * %%$2%% : La valeur du second élément de la règle (peut être la valeur d'une autre règle calculée) +            | STRING_TEXT ​                  #​valueString 
-  * %%$3%% ... +            ; 
-  * ... +</code> 
-  * %%$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)+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:
  
-<note> +<code javascript
-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 (). +export interface AlfVisitor<​Result>​ extends ParseTreeVisitor<​Result>​ { 
-</note>+ 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.
  
-La règle de début renvoie généralement une valeur. 
  
-==== Les Erreurs ​====+===== Création des noeuds =====
  
-Si le parser ne comprend pas un jeton ou une règleil lancera une exception.+Pour générer l'​**AST** a l'aide du **Visitor**on va créer des classes représentatives pour chaque noeud
  
-L'exception a le format suivant+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 qu**id** du noeud.
  
-  * //message// - Le message d'erreur +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.
-  * //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 =====+<code javascript>​
  
-Pour générer le parserexécutez jison en utilisant les fichiers .l et .y+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:​ stringpublic readonly variable: string, public readonly op: string, public readonly value: string|number) { 
 +        super(); 
 +    } 
 +    toJSON() {
  
-<code bash> +        /* TODO: Return here an object having the id "​declaration"​ and the following properties: variable_type,​ variable and value *
-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. +
- +
-===== Utilisation du parser généré =====+
  
-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.1584424582.txt.gz · Last modified: 2020/03/17 07:56 by alexandru.radovici
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