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.
La structure de l'AST dépend beaucoup de la complexité de la grammaire:
Le but de ce TP est de vous habituer a travailler plus avec le parser, le visiteur et la generation de l'arbre de syntaxe abstraite, en continuant le dévéloppement du TP passé.
Etant donnée la grammaire suivante:
grammar Ex1; start : (statement SEMICOLON NEWLINE*)* #multilineProg | statement SEMICOLON NEWLINE* #singlelineProg ; statement : declaration #declarationRule | expression #expressionRule | attribution #attributionRule ; declaration : type VARIABLE EQ expression #variableDeclaration ; type : INT #typeInt | FLOAT #typeFloat | STRING #typeString ; value : INT_NUMBER #valueInt | FLOAT_NUMBER #valueFloat | STRING_TEXT #valueString | VARIABLE #valueVariable ; expression : left=expression op=MUL right=expression #expressionMultiply | left=expression op=DIV right=expression #expressionDivision | left=expression op=REM right=expression #expressionRem | left=expression op=ADD right=expression #expressionAddition | left=expression op=SUB right=expression #expressionSubtraction | LP expression RP #expressionParanthesis | value #expressionValue ; /** TODO 1: Add the tokens (in the Lexer part) and the rules (with aliases) for boolean expressions * Operators: OR, AND, NOT * Values: true, false, variables */ attribution : VARIABLE EQ expression #variableAttribution ; /** TODO 4: Add the tokens (in the Lexer part) and the rules (with aliases) for lists declaration * Keyword: list * Name: any variable name * Values: any value separated by comma */ /** TODO 5: Add the tokens (in the Lexer part) and the rules (with aliases) for functions declaration * Keyword: function * Name: any variable name * Parameters: any declaration separated by comma * Instructions: any statement separated by a semicolon and one or more new lines * Return: "return" keyword + any statement ending with a semicolon */ /** BONUS: Add the tokens (in the Lexer part) and the rules (with aliases) for function calls * Function name: any variable name * Parameters: any value separated by comma * Add the function call to the variable declaration */ WS : (' ') -> skip; NEWLINE : ([\r\n]+) -> skip; VARIABLE : ('_'[a-zA-Z0-9]+); ADD : '+'; SUB : '-'; MUL : '*'; DIV : '/'; REM : '%'; INT : 'int'; FLOAT : 'float'; STRING : 'string'; LP : '('; RP : ')'; EQ : '='; SEMICOLON : ';'; INT_NUMBER : ([0-9]+); FLOAT_NUMBER : ([0-9]+'.'[0-9]+); STRING_TEXT : ('"'~["]+'"'|'\''~[']+'\'');
Vous pouvez aussi observer la grammaire et aussi les implementations des classes et de visiteur dans le repository Github donné pour le TP d'aujourd'hui.
Le résultat généré aura le format suivant:
{ "id": "statements", "statements": [ { "id": "declaration", "variable_type": "int", "variable": "_var1", "value": { "id": "expression", "left": { "id": "expression", "left": { "id": "value", "value": 2 }, "right": { "id": "value", "value": 5 }, "op": "+" }, "right": { "id": "expression", "left": { "id": "value", "value": 7 }, "right": { "id": "value", "value": 3 }, "op": "*" }, "op": "/" } } ] }
Au niveau de l'anayse on peut retrouver 2 types d'erreurs:
Pour pouvoir traiter ces erreurs, on doit tout d'abord éliminer les écouteurs d'erreurs par défaut d'ANTLR (ErrorListeners)
lexer.removeErrorListeners (); parser.removeErrorListeners ();
Ensuite, il faut créer un nouvel outil qui puisse attraper et afficher les erreurs. Pour cela, on peut définir une nouvelle classe:
public class ErrorPrinter implements ANTLRErrorListener { @Override public void syntaxError(Recognizer<?, ?> recognizer, Object o, int i, int i1, String s, RecognitionException e) { if (e != null) { System.out.println("line: "+i+" position: "+i1+" message: "+s); } } }
Finalament, on doit indiquer au parser
et au lexer
quels sont les nouveaux écouteurs:
lexer.addErrorListener (new ErrorPrinter()); parser.addErrorListener (new ErrorPrinter());
bool _var1 = _var2 ||_var3 && !_var4;
String _var2 = "FILS " + "ALF";
list _var1 = [10, 5.5, 'alf', true, _var2];
function _functionName (int _var1=0, String _var2="alf", bool _var3=true) { _var1 = _var2 + "2021"; _var4 = false; return 3+5/7; };