This shows you the differences between two versions of the page.
alf:laboratoare:05_fr_java [2023/04/02 13:24] alexandra.negoita02 created |
alf:laboratoare:05_fr_java [2023/04/04 09:19] (current) alexandra.negoita02 [Exercices] |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== TP 5 - Parser du langage. Introduction à l'AST ====== | ====== TP 5 - Parser du langage. Introduction à l'AST ====== | ||
+ | <note warning> | ||
+ | Vous devez **accepter** l'assignment d'ici est travailler avec ce **repository**: [[https://classroom.github.com/a/g8KTvWH-|Lab 5]] | ||
+ | </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. | 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. | ||
Line 10: | Line 14: | ||
- | {{ :alf:laboratoare:ex1.jpg?550&nolink }} | + | {{ :alf:laboratoare:parseTree_rules.png?550&nolink }} |
Line 30: | Line 34: | ||
<code> | <code> | ||
- | start : declaration #declarationStatement | + | grammar Alf; |
+ | |||
+ | start : declaration #declarationRule | ||
; | ; | ||
- | declaration : type VARIABLE EQ value #variableDeclaration | + | declaration : type VARIABLE EQ value #variableDeclaration |
; | ; | ||
Line 44: | Line 50: | ||
| FLOAT_NUMBER #valueFloat | | FLOAT_NUMBER #valueFloat | ||
| STRING_TEXT #valueString | | STRING_TEXT #valueString | ||
+ | | VARIABLE #valueVariable | ||
; | ; | ||
+ | |||
+ | WS : (' ') -> skip; | ||
+ | NEWLINE : ([\r\n]+) -> skip; | ||
+ | VARIABLE : ('_'[a-zA-Z0-9]+); | ||
+ | INT : 'int'; | ||
+ | FLOAT : 'float'; | ||
+ | STRING : 'string'; | ||
+ | EQ : '='; | ||
+ | SEMICOLON : ';'; | ||
+ | INT_NUMBER : ([0-9]+); | ||
+ | FLOAT_NUMBER: ([0-9]+'.'[0-9]+); | ||
+ | STRING_TEXT : ('"'~["]+'"'|'\''~[']+'\''); | ||
</code> | </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 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: | ||
- | <code javascript> | + | <code java> |
- | export interface AlfVisitor<Result> extends ParseTreeVisitor<Result> { | + | public interface AlfVisitor<T> extends ParseTreeVisitor<T> { |
- | visitDeclarationStatement?: (ctx: DeclarationStatementContext) => Result; | + | T visitDeclarationRule(AlfParser.DeclarationRuleContext ctx); |
- | visitVariable?: (ctx: VariableContext) => Result; | + | T visitVariableDeclaration(AlfParser.VariableDeclarationContext ctx); |
- | visitTypeInt?: (ctx: TypeIntContext) => Result; | + | T visitTypeInt(AlfParser.TypeIntContext ctx); |
- | visitTypeFloat?: (ctx: TypeFloatContext) => Result; | + | T visitTypeFloat(AlfParser.TypeFloatContext ctx); |
- | visitTypeString?: (ctx: TypeStringContext) => Result; | + | T visitTypeString(AlfParser.TypeStringContext ctx); |
- | visitValueInt?: (ctx: ValueIntContext) => Result; | + | T visitValueInt(AlfParser.ValueIntContext ctx); |
- | visitValueFloat?: (ctx: ValueFloatContext) => Result; | + | T visitValueFloat(AlfParser.ValueFloatContext ctx); |
- | visitValueString?: (ctx: ValueStringContext) => Result; | + | T visitValueString(AlfParser.ValueStringContext ctx); |
+ | T visitValueVariable(AlfParser.ValueVariableContext ctx); | ||
} | } | ||
</code> | </code> | ||
Line 73: | Line 93: | ||
<code javascript> | <code javascript> | ||
+ | // On fait une classe abstraite ASTNode qui sera hérité par toutes les noeuds | ||
+ | // ASTNode.java | ||
abstract class ASTNode { | abstract class ASTNode { | ||
- | constructor(){}; | + | ASTNode(){} |
} | } | ||
- | class StatementNode extends ASTNode { | + | |
- | constructor(public readonly statement: ASTNode) { | + | // Puis, tous les autres noeuds qui sont nommees dans notre grammaire |
+ | // DeclarationNode.java | ||
+ | public class DeclarationNode extends ASTNode{ | ||
+ | String id = "declaration"; | ||
+ | String variableType; | ||
+ | String variable; | ||
+ | ASTNode value; | ||
+ | DeclarationNode(String variableType, String variable, ASTNode value) { | ||
super(); | super(); | ||
- | } | + | this.variableType = variableType; |
- | toJSON() { | + | this.variable = variable; |
- | /* TODO: Return here an object having the id "statement" and the statement a list of instructions */ | + | this.value = value; |
} | } | ||
} | } | ||
- | class DeclarationNode extends ASTNode { | + | |
- | constructor(public readonly variable_type: string, public readonly variable: string, public readonly op: string, public readonly value: string|number) { | + | // TypeNode.java |
+ | public class TypeNode extends ASTNode{ | ||
+ | String id = "value"; | ||
+ | String typeName; | ||
+ | TypeNode(String typeName) { | ||
super(); | super(); | ||
- | } | + | this.typeName = typeName; |
- | toJSON() { | + | |
- | + | ||
- | /* TODO: Return here an object having the id "declaration" and the following properties: variable_type, variable and value */ | + | |
- | | + | |
} | } | ||
} | } | ||
- | class ValueNode extends ASTNode { | + | // ValueNode.java |
- | constructor(public readonly value: number|string) { | + | public class ValueNode extends ASTNode{ |
+ | String id = "value"; | ||
+ | public Object value; | ||
+ | ValueNode(Object value) { | ||
super(); | super(); | ||
- | } | + | this.value = value; |
- | 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 */ | + | |
} | } | ||
} | } | ||
+ | |||
</code> | </code> | ||
===== Visiteur de l'arbre ===== | ===== 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: | + | Pour //visiter// les composants et générer l'arbre, il faut surcharger les méthodes du visiteur de base de la façon suivante: on fait notre classe visiteur, //MyAlfVisitor//, qui va implementer l'interface du visiteur genree par la grammaire //AlfVisitor//. |
+ | L'action de visiter un noeud représente parcourir l'arbre d'analyse créé par le parser, la création d'un objet du type de règle mentionné dans l'AST et sa transmission à la notre classe visiteur. | ||
- | <code typescript> | + | <code java> |
- | class MyAlfVisitor extends AbstractParseTreeVisitor<ASTNode> implements AlfVisitor<ASTNode> { | + | public class MyAlfVisitor extends AbstractParseTreeVisitor<ASTNode> implements AlfVisitor<ASTNode> { |
- | defaultResult() { | + | @Override |
- | return new StatementNode({} as ASTNode); | + | public ASTNode visitDeclarationRule(AlfParser.DeclarationRuleContext ctx) { |
+ | return (DeclarationNode) this.visit(ctx.declaration()); | ||
} | } | ||
- | visitVariableDeclaration(ctx: VariableDeclarationContext): DeclarationNode { | + | |
+ | @Override | ||
+ | public ASTNode visitVariableDeclaration(AlfParser.VariableDeclarationContext ctx) { | ||
return new DeclarationNode( | return new DeclarationNode( | ||
- | (this.visit(ctx.type()) as TypeNode).type_name, | + | ((TypeNode) this.visit(ctx.type())).typeName, |
- | ctx.VARIABLE().text, | + | ctx.VARIABLE().getText(), |
- | ctx.EQ().text, | + | String.valueOf(((ValueNode) this.visit(ctx.value())).value) |
- | (this.visit(ctx.value()) as ValueNode).value | + | |
); | ); | ||
} | } | ||
- | visitValueInt(ctx: ValueIntContext): ValueNode { | + | |
- | return new ValueNode( | + | @Override |
- | parseInt(ctx.INT_NUMBER().text) | + | public ASTNode visitTypeInt(AlfParser.TypeIntContext ctx) { |
+ | return new TypeNode( | ||
+ | ctx.INT().getText() | ||
); | ); | ||
} | } | ||
- | visitTypeString(ctx: TypeStringContext): TypeNode { | + | |
+ | @Override | ||
+ | public ASTNode visitTypeFloat(AlfParser.TypeFloatContext ctx) { | ||
return new TypeNode( | return new TypeNode( | ||
- | ctx.STRING().text | + | ctx.FLOAT().getText() |
- | ) | + | ); |
} | } | ||
- | </code> | ||
+ | @Override | ||
+ | public ASTNode visitTypeString(AlfParser.TypeStringContext ctx) { | ||
+ | return new TypeNode( | ||
+ | ctx.STRING().getText() | ||
+ | ); | ||
+ | } | ||
- | Pour voir le résultat de l'arbre généré, on va l'afficher dans un fichier: | + | @Override |
+ | public ASTNode visitValueInt(AlfParser.ValueIntContext ctx) { | ||
+ | return new ValueNode( | ||
+ | Integer.parseInt(ctx.INT_NUMBER().getText()) | ||
+ | ); | ||
+ | } | ||
- | <code javascript> | + | @Override |
- | const visitor = new MyAlfVisitor(); | + | public ASTNode visitValueFloat(AlfParser.ValueFloatContext ctx) { |
- | fs.writeFileSync('./output.json', JSON.stringify(visitor.visit(tree).toJSON(), null, 4), 'utf8'); | + | return new ValueNode( |
- | </code> | + | Float.parseFloat(ctx.FLOAT_NUMBER().getText()) |
+ | ); | ||
+ | } | ||
+ | @Override | ||
+ | public ASTNode visitValueString(AlfParser.ValueStringContext ctx) { | ||
+ | return new ValueNode( | ||
+ | ctx.STRING_TEXT().getText() | ||
+ | ); | ||
+ | } | ||
- | <note important> | + | @Override |
- | Après chaque modification apportée à la grammaire (fichier ''Alf.g4''), vous devez exécuter la commande suivante pour enregistrer les changements: <code bash> | + | public ASTNode visitValueVariable(AlfParser.ValueVariableContext ctx) { |
- | npm run antlr4ts | + | return new ValueNode( |
+ | ctx.VARIABLE().getText() | ||
+ | ); | ||
+ | } | ||
+ | } | ||
</code> | </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> | + | |
- | npx tsc | + | Pour voir le résultat de l'arbre généré, on utilise le format de notation [[https://www.w3schools.com/js/js_json_intro.asp|JSON]]. Pour utiliser JSON pour afficher des classes Java, on doit telecharger et importer le paquet [[https://jar-download.com/artifacts/com.google.code.gson/gson/2.8.2/source-code|GSON]], et de l'ajouter dans le projet Intellij avec ''File > Project Structure > Modules''. La page de modules doit ressembler à ça: |
- | node index.js | + | {{ :alf:laboratoare:gson_module.png?550&nolink }} |
+ | |||
+ | Et le code pour afficher le contenu JSON dans un fichier ''output.json'': | ||
+ | <code java> | ||
+ | // Une methode toJSON qui utilise les fonctions du paquet GSON pour interpreter un noeud AST: | ||
+ | static String toJSON(ASTNode visitor) { | ||
+ | GsonBuilder builder = new GsonBuilder(); | ||
+ | builder.setPrettyPrinting(); | ||
+ | Gson gson = builder.create(); | ||
+ | return gson.toJson(visitor); | ||
+ | } | ||
+ | |||
+ | // Appeller dans Main.java: | ||
+ | writeToFile("files\\output.json", toJSON(visitor.visit(tree))); | ||
</code> | </code> | ||
+ | Pour un fichier ''sample.txt'' contenant le texte ''int _var = 5;'' | ||
+ | {{ :alf:laboratoare:json_output.png?550&nolink }} | ||
+ | |||
+ | |||
+ | <note important> | ||
+ | Après chaque modification apportée à la grammaire (fichier ''alf.g4''), vous devez generer de nouveau les fichiers de ANTLR, avec clic-droit sur le fichier de grammaire > ''Generate ANTLR Recognizer'' | ||
</note> | </note> | ||
===== Exercices ===== | ===== 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)** | + | - Téléchargez la structure du TP depuis le github repository donné, téléchargez et importez l'archive GSON comme dit dans le laboratoire, et inspectez la structure de la grammaire. **(1p)** |
- | - 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; | + | - 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 être 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> | 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; | + | - 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 maintenant 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érez 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> | 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> | + | - 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: **(3p)** <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)** | + | |