TP 6 - AST: Continuation

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:

ast.jpg

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         :   ('"'~["]+'"'|'\''~[']+'\'');

Et l'écrasement des méthodes de visiteur:

import { CharStreams, CodePointCharStream, CommonTokenStream, Token } from 'antlr4ts';
import { Ex1Lexer } from './Ex1Lexer.js';
import { Ex1Parser, ExpressionAdditionContext, ExpressionDivisionContext, ExpressionMultiplyContext, ExpressionParanthesisContext, ExpressionRemContext, ExpressionSubtractionContext, MultilineProgContext, SinglelineProgContext, TypeFloatContext, TypeIntContext, TypeStringContext, ValueFloatContext, ValueIntContext, ValueStringContext, VariableDeclarationContext, ExpressionValueContext, ValueVariableContext, VariableAttributionContext } from './Ex1Parser.js';
import { Ex1Visitor } from './Ex1Visitor.js';
import * as fs from 'fs';
import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor';
 
let input: string = fs.readFileSync('./sample.txt').toString();
let inputStream: CodePointCharStream = CharStreams.fromString(input);
let lexer: Ex1Lexer = new Ex1Lexer(inputStream);
let tokenStream: CommonTokenStream = new CommonTokenStream(lexer);
let parser: Ex1Parser = new Ex1Parser(tokenStream);
 
let tree = parser.start();
 
abstract class ASTNode {
    constructor(){};
}
 
class StatementsNode extends ASTNode {
    constructor(public readonly statements: ASTNode[], public readonly line: number) {
        super();
    }
    toJSON() {
        return {
            id: "statements",
            statements: this.statements,
        }
    }
}
class DeclarationNode extends ASTNode {
    constructor(public readonly variable_type: string, public readonly variable: string, public readonly op: string, public readonly value: Expression|ValueNode,  public readonly line: number) {
        super();
    }
    toJSON() {
        return {
            id: "declaration",
            variable_type: this.variable_type,
            variable: this.variable,
            value: this.value,
        }
    }
}
 
class ValueNode extends ASTNode {
    constructor(public readonly value: number|string|boolean, public readonly line: number) {
        super();
    }
    toJSON() {
        return {
            id: "value",
            value: this.value,
        }
    }
}
class TypeNode extends ASTNode {
    constructor(public readonly type_name: string, public readonly line: number) {
        super();
    }
    toJSON() {
        return  {
            type: this.type_name,
        }
    }
}
class Expression extends ASTNode {
    constructor(public readonly op: string, public readonly left: Expression, public readonly right: Expression, public readonly line: number) {
        super();
    }
    toJSON() {
        return {
            id: "expression",
            left: this.left,
            right: this.right,
            op: this.op,
        }
    }
}
 
class AttributionNode extends ASTNode {
    constructor(public readonly variable: string, public readonly value: Expression, public readonly line: number) {
        super();
    }
    toJSON() {
        return {
            id: "attribution",
            to: this.variable,
            from: this.value,
        }
    }
}
 
/** TODO 1: Create a class for List Nodes which will return a Node with the following properties
     * id => list_declaration
     * list_type => the actual type, which will be set in the Visitor as list
     * list_name => the name of the list
     * values => an array of values
*/
 
 
/** TODO 5: Create a class for Function Nodes which will return a Node with the following properties
 * id => function
 * function_name: the actual name of the function, taken from the variable
 * parameters: the list of the parsed nodes set as parameters
 * instructions: the list of the parsed nodes set as statements in the function
 * return: the parsed return node
 */
 
/** TODO 5: Recommandations for solving this exercice
 * You should create a class for the Parameter Node, with the following properties: 
        * id: function_parameter,
        * type: the type of the parameter from the declaration
        * value: the parsed value assigned to the parameter
 * You should create a class for the Return Node, with the following properties:
        * id: return_node
        * return_statement: the statement that will be parsed in the Visitor 
 */
 
class MyEx1Visitor extends AbstractParseTreeVisitor<ASTNode> implements Ex1Visitor<ASTNode> {
    defaultResult() {
        return new StatementsNode([], 0);
    }
    visitMultilineProg(ctx: MultilineProgContext): StatementsNode {
        let statements = [];
        for(let i = 0; i < ctx.statement().length; i++)
            statements[i] = this.visit(ctx.statement(i));
        if(statements) {
            return new StatementsNode(statements, 1);
        } else {
            throw new Error();
        }
    }
    visitSinglelineProg(ctx: SinglelineProgContext):ASTNode {
        return new StatementsNode([this.visit(ctx.statement())], 1);
    }
    visitVariableDeclaration(ctx: VariableDeclarationContext): DeclarationNode {
        return new DeclarationNode(
            (this.visit(ctx.type()) as TypeNode).type_name,
            ctx.VARIABLE().text,
            ctx.EQ().text,
            this.visit(ctx.expression()) as Expression,
            ctx.VARIABLE().symbol.line
        );
    }
    visitValueInt(ctx: ValueIntContext): ValueNode {
        return new ValueNode(
            parseInt(ctx.INT_NUMBER().text),
            ctx.INT_NUMBER().symbol.line
        );
    }
    visitValueVariable(ctx: ValueVariableContext): ValueNode {
        return new ValueNode(
            ctx.VARIABLE().text,
            ctx.VARIABLE().symbol.line
        );
    }
    visitValueFloat(ctx: ValueFloatContext): ValueNode {
        return new ValueNode(
            parseFloat(ctx.FLOAT_NUMBER().text),
            ctx.FLOAT_NUMBER().symbol.line
        );
    }
    visitValueString(ctx: ValueStringContext): ValueNode {
        return new ValueNode(
            ctx.STRING_TEXT().text,
            ctx.STRING_TEXT().symbol.line
        );
    }
    /** TODO 1: Visit the boolean value */
 
    visitTypeInt(ctx: TypeIntContext): TypeNode {
        return new TypeNode(
            ctx.INT().text,
            ctx.INT().symbol.line
        )
    }
    visitTypeString(ctx: TypeStringContext): TypeNode {
        return new TypeNode(
            ctx.STRING().text,
            ctx.STRING().symbol.line
        )
    }
    visitTypeFloat(ctx: TypeFloatContext): TypeNode {
        return new TypeNode(
            ctx.FLOAT().text,
            ctx.FLOAT().symbol.line
        )
    }
    /** TODO 1: Visit the boolean type */
 
    visitExpressionMultiply(ctx: ExpressionMultiplyContext): Expression {
		const left = this.visit(ctx.expression(0));
		const right = this.visit(ctx.expression(1));
		const op = ctx._op;
 
		if(op.text) {
			return new Expression(op.text, left as Expression, right as Expression, ctx._op.line);
		} else throw new Error();
	}
    visitExpressionDivision(ctx: ExpressionDivisionContext): Expression {
		const left = this.visit(ctx.expression(0));
		const right = this.visit(ctx.expression(1));
		const op = ctx._op;
 
		if(op.text) {
			return new Expression(op.text, left as Expression, right as Expression, ctx._op.line);
		} else throw new Error();
	}
    visitExpressionRem(ctx: ExpressionRemContext): Expression {
		const left = this.visit(ctx.expression(0));
		const right = this.visit(ctx.expression(1));
		const op = ctx._op;
 
		if(op.text) {
			return new Expression(op.text, left as Expression, right as Expression, ctx._op.line);
		} else throw new Error();
	}
    visitExpressionAddition(ctx: ExpressionAdditionContext): Expression {
		const left = this.visit(ctx.expression(0));
		const right = this.visit(ctx.expression(1));
		const op = ctx._op;
 
		if(op.text) {
			return new Expression(op.text, left as Expression, right as Expression, ctx._op.line);
		} else throw new Error();
	}
    visitExpressionSubtraction(ctx: ExpressionSubtractionContext): Expression {
		const left = this.visit(ctx.expression(0));
		const right = this.visit(ctx.expression(1));
		const op = ctx._op;
 
		if(op.text) {
			return new Expression(op.text, left as Expression, right as Expression, ctx._op.line);
		} else throw new Error();
	}
    visitExpressionParanthesis(ctx: ExpressionParanthesisContext) {
        return this.visit(ctx.expression());
    }
    visitExpressionValue(ctx: ExpressionValueContext): ValueNode {
        let value = this.visit(ctx.value());
        if(value !== undefined) {
		    return new ValueNode((this.visit(ctx.value()) as ValueNode).value, ctx.value()._start.line);
        } else throw new Error();
	}
 
    /**TODO 1: Visit every type of boolean expression */
 
 
    /**TODO 4: Visit list declaration */
    visitVariableAttribution(ctx: VariableAttributionContext): AttributionNode {
        return new AttributionNode(
            ctx.VARIABLE().text,
            this.visit(ctx.expression()) as Expression,
            ctx.VARIABLE().symbol.line
        );
    }
 
    /**TODO 5: Visit function declaration and return node */
 
}
const visitor = new MyEx1Visitor();
console.log(JSON.stringify(visitor.visit(tree), null, 4));

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": "/"
            }
        }
    ]
}

Erreurs

Au niveau de l'anayse on peut retrouver 2 types d'erreurs:

  • Erreurs lexicales - le Parser trouve des jetons qu'il ne peut pas identifier dans le Lexer
  • Erreurs syntaxiques - le texte ne respecte pas les règles indiquées dans la grammaire

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:

class ErrorPrinter implements ANTLRErrorListener<any> {
    syntaxError (recognizer: Recognizer<any, any>, offendingSymbol: any | undefined, line: number, charPositionInLine: number, msg: string, e: RecognitionException | undefined) {
        if (e) {   
            console.log ("line: "+line+" positon: "+charPositionInLine+" message: "+msg);
 
        }
    } 
}

Finalament, on doit indiquer au parser et au lexer quels sont les nouveaux écouteurs:

lexer.addErrorListener (new ErrorPrinter());
parser.addErrorListener (new ErrorPrinter());

Exercices

  1. Téléchargez la structure du TP et utilisez la grammaire Ex1.g4 comme support pour les exercices. En suivant les lignes marquées par TODO 1, ajoutez les règles de grammaire, les classes et les méthodes nécessaires pour pouvoir déclarer aussi des variables et des expressions booléennes. Testez la fonctionnalité du programme pour l'exemple suivant: (2p)
     bool _var1 = _var2 ||_var3 && !_var4; 
  2. Apportez les modifications nécessaires de sorte que chaque noeud de l'AST comprend aussi le numéro de la ligne ou il a été trouvé. (1.5p)
  3. Faites que la grammaire accepte aussi la concaténation des chaines de caracteres. Vérifiez pour l'exemple suivant: (1.5p)
     string _var2 = "FILS " + "ALF"; 
  4. En suivant les lignes marquées par TODO 4, ajoutez à la grammaire les règles, classes et méthodes nécessaires pour que vous puissiez déclarer des listes. Les listes peuvent inclure n'importe quel type de données . Vérifiez pour la déclaration suivante: (1.5p)
     list _var1 = [10, 5.5, 'alf', true, _var2]; 
  5. En suivant les lignes marquées par TODO 5, ajoutez les règles, les classes et les méthodes necessaires pour la déclaration des fonctions. Une fonction peut avoir un ou plusieurs paramètres et son contenu peut inclure une seule ou plusieurs instructions. Vérifiez la correctitude de votre programme pour l'exemple suivant: (3.5p)
    function _functionName (int _var1=0, string _var2="alf", bool _var3=true)
    {
        _var1 = _var2 + "2021";
        _var4 = false;
    
        return 3+5/7;
    };
  6. BONUS: Traitez les erreurs de lexer et de syntaxe pour votre arbre et affichez les messages nécessaires. (1.5p)
alf/laboratoare/06.txt · Last modified: 2021/04/09 23:33 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