TD Flex Bison [PDF]

  • 0 0 0
  • Gefällt Ihnen dieses papier und der download? Sie können Ihre eigene PDF-Datei in wenigen Minuten kostenlos online veröffentlichen! Anmelden
Datei wird geladen, bitte warten...
Zitiervorschau

Maitrise Informatique Lyon-1 Compilation

1

T dCompilation Sujet 2007-2008

Analyse lexicale

1.1

Flex

Le but de l’analyse lexicale est de transformer une suite de symboles en terminaux (un terminal peut être par exemple un nombre, un signe ’+’, un identificateur, etc...). Une fois cette transformation effectuée, la main est repassée à l’analyseur syntaxique (voir ci-dessous). Le but de l’analyseur lexical est donc de ’consommer’ des symboles et de les fournir à l’analyseur syntaxique. Un fichier de description pour Lex est formé de trois parties, selon le schéma suivant : declarations %% définition des symboles %% code additionnel dans lequel aucune partie n’est obligatoire. Cependant, le premier %% l’est, afin d’indiquer la séparation entre les déclarations et les productions.

1.1.1

les déclarations

Cette partie d’un fichier Lex peut contenir : – Du code écrit dans le langage cible, encadré par %{ et %}, qui se retrouvera au début du fichier synthétisé par Lex. C’est ici que l’on va spécifier les fichiers à inclure. Lex recopie tel quel tout ce qui est écrit entre ces deux signes, qui devront toujours être placés en début de ligne. – Des expressions régulières définissant des notions non terminales, telles que lettre, chiffre et nombre. Ces spécifications sont de la forme : notion expression_reguliere Les notions ainsi définies pourront alors être utilisées dans la suite de la première partie du fichier, ainsi que dans la deuxième partie, en les parenthésant par et . Exemple : blancs lettre chiffre10 chiffre16 entier10

[\t\n ]+ [A-Za-z] [0-9] /* base 10 */ [0-9A-Fa-f] /* base 16 */ {chiffre10}+

1.1.2

Les expressions régulières Symbole x . [xyz] [∧bz] [a − z] [∧a − z] R∗ R+ R? R{2, 5} R{2, } R{2} ”[xyz\”f oo” {N OT ION } \X \0 \123 \x2A RS R&S R/S R

R$ > 1.1.3

Signification Le caractere ’x’ N’importe quel caractere sauf le retour à la ligne Soit x, soit y, soit z Tous les caracteres, SAUF b et z N’importe quel caractere entre a et z Tous les caracteres, SAUF ceux compris entre a et z Zero R ou plus, ou R est n’importe quelle expression reguliere Un R ou plus Zero ou un R (c’est-a-dire un R optionnel) Entre deux et cinq R Deux R ou plus Exactement deux R La chaine ’[xyz\”f oo’ L’expansion de la notion NOTION definie plus haut Si X est un ’a’, ’b’, ’f’, ’n’, ’r’, ’t’. Caractere ASCII 0 Caractere ASCII dont le numero est 123 EN OCTAL Caractere ASCII en hexadecimal R suivi de S R ou S R, seulement s’il est suivi par S R, mais seulement en debut de ligne R, mais seulement en fin de ligne Fin de fichier

les définitions des symboles

Cette partie sert à indiquer à Lex ce qu’il devra faire lorsqu’il rencontrera tel ou tel symbole. Celle-ci peut contenir : – Des spécifications écrites dans le langage cible, encadrées par %{ et %} (toujours placés en début de ligne), qui seront placées au début de la fonction yylex(), la fonction chargée de consommer les terminaux, et qui renvoit un entier. – Des productions de la forme : expression_reguliere action Si action est absente, Lex recopiera les caractères tels quels sur la sortie standard. Si action est présente, elle sera écrite en du code du langage cible. Si celle-ci comporte plus d’une seule instruction ou ne peut tenir sur une seule ligne, elle devra être parenthésée par et . Il faut de plus savoir que les commentaires tels que /* ... */ ne peuvent être présents dans la deuxième partie d’un fichier Lex que s’il sont placés dans les actions parenthésées. Dans le cas contraire, ceux-ci seraient considérés par Lex comme des expressions régulières ou des actions, ce qui donnerait lieu à des messages d’erreur, ou, au mieux, à un comportement inattendu. Enfin, la variable yytext désigne dans les actions les caractères acceptés par expression_régulière. Il s’agit d’un tableau de caractère de longueur yyleng (donc défini comme char yytext[yyleng]). Exemple : %% [ \t]+$ ; [ \t] printf(" ");

%% Ce programme supprime tous les espaces inutiles dans un fichier. Tu auras d’ailleurs noté que Lex permet de faire énormément de choses, et pas seulement des interpréteurs et des compilateurs (Il peut par exemple servir à la recherche/remplacement dans un texte, etc...). 1.1.4

Le code additionnel

Cette section du fichier flex contient les implémentations C des fonctions nécessaires. main() { yylex(); } 1.1.5

La compilation

La génération de l’exécutable se fera à l’aide des commandes suivantes : flex -olexer.c lexer.l gcc -o lexer.o -c lexer.c gcc -o main lexer.o -lfl

1.2

Exercices – Ecrire l’expression régulière définissant les identificateurs (lettres, chiffres et _, ne commençant ni par un chiffre ni par _) identificateur {lettre}(_|{lettre}|{chiffre10})* reconnaitra comme identificateur les mots ’integer’, ’une_variable’, ’a1’, mais pas ’_ident’ ni ’1variable’. – Ecrire la définition d’un réel (partie entière et flottante obligatoire, signe optionnel, sans exposant) chiffre [0-9] entier {chiffre}+ reel -?{entier}"."{entier} – Ecrire la définition d’un réel (partie entière obligatoire, partie flottante optionnelle, signe optionnel, avec/sans exposant) chiffre [0-9] entier {chiffre}+ exposant [eE][+-]?{entier} reel -?{entier}("."{entier})?{exposant}?

2

Analyse syntaxique

2.1

Bison

2.1.1

Principe de fonctionnement

L’outil bison permet de générer des analyseurs syntaxiques. Un fichier d’entrée décrit la grammaire à analyser ainsi que les attributs et actions sémantiques associés. Un fichier .c est généré à partir de cette description, celui-ci contient la mise en oeuvre de l’automate à pile pour une analyse ascendante (voir figure 1). L’appel de l’analyseur se fait par le biais de la fonction int yyparse () créée par bison.

fichier .c

description de la grammaire

Bison

fichier .h

F IG . 1 – principe de fonctionnement de bison

2.1.2

Les fichiers de description

Les fichiers de description de grammaire pour bison ont un formalisme similaire à ceux de flex (voir figure 2).

} } }

Définitions

%% Règles de production

%% Code C/C++

F IG . 2 – fichier de description de bison

2.1.3

Les définitions

La section de définition permet de décrire certaines parties de la grammaire (ensemble des symboles terminaux, axiomes, attributs sémantiques...). Il est possible d’y inclure du code C en l’encadrant avec %{ et %}. Le mot-clé %start permet de définir l’axiome de la grammaire. Le mot-clé %token permet de définir les éléments du vocabulaire terminal. Les mot-clés %left,%right et %nonassoc permettent de définir la priorité (ordre de spécification des éléments) et l’associativité des opérateurs. 2.1.4

Les règles de production

Les règles de production sont données sous la forme Symbôle : Règle (voir figure 3). Le symbole ’ :’ permet de séparer les parties gauche et droite de la règle. Le ’ ;’ permet de terminer une règle. Dans le cas où un symbole accèpte plusieurs dérivations possibles, on utilisera le formalisme le caractère ’|’ pour différencier les règles. %% A : a b c ... z ; %%

%% A:abc |def ; %%

F IG . 3 – description de règles pour bison

2.1.5

Le code C

La dernière section du fichier de description contient du code C (fonctions, variables globales...).

2.1.6

Exemple

Soit la grammaire G-2.a des expressions arithmétiques (addition et multiplication) entières. Grammaire G-2.a = < VN = { E } , VT = { cste + ∗ } , E,  E → E+E P = >

|

E∗E

|

cste



Cette grammaire sera décrite sous la forme décrite dans la figure 4. %start E %token + %token * %token cste %% E : E + E | E * E | cste ; %% int main ( int argc, char** argv ) { yyparse (); }

F IG . 4 – description de règles pour bison

2.2

Lien entre l’analyseur syntaxique et un analyseur lexical

L’analyseur syntaxique fait appel à l’analyseur lexical pour connaître le prochain symbole dans la chaine à analyser. Pour bison l’appel à l’analyseur lexical se fait par le biais de la fonction int yylex (). Dans la majeure partie des cas, cet analyseur lexical sera généré à l’aire de flex (voir figure 5). parser .c yyparse () description de la grammaire

gcc

parser.o

Bison

gcc

executable

parser .h

gcc

description des symboles

lexer.o

lexer .c Flex yylex ()

F IG . 5 – principe de fonctionnement de bison et flex

La génération de l’exécutable se fera à l’aide des commandes suivantes : flex -olexer.c lexer.l bison -d -o parser.c parser.y gcc -o parser.o -c parser.c gcc -o lexer.o -c lexer.c gcc -o main parser.o lexer.o -lfl L’option -d de bison permet de dire à ce dernier de générer un fichier .h contenant la définition des constantes associées aux différents symboles terminaux de la grammaire. Ce fichier est à include

dans le fichier lexer.l pour que l’analyseur lexical puisse renvoyer la valeur de ces constantes en résultat. Dans les actions de l’analyseur lexical, on aura un return «symbole» où «symbole» est l’une des valeurs définies dans les %token. 2.2.1

Exemple

Soit le langage décrit par an bn . Malgré le fait que ce langage soit décrit par une expression régulière, il est difficile de le reconnaître en n’utilisant que l’outil flex. 2.2.2

Version simple

Pour simplifier, nous le décrivons à l’aide de la grammaire hors-contexte G-2.b. Grammaire G-2.b = < VN = { E } , VT = { a b } , E,  E → P = >

aEb

|

ab



Dans ce cas, l’analyseur sera généré à l’aide des fichiers de description suivants de la figure 6. lexer.l

parser.y

%{ #include "parser.h" int yyerror (char*); %} %% a return TOKEN_A; b return TOKEN_B; (.|\n) yyerror ( "symbole non reconnu" ); %% int yyerror ( char* m ) { printf ( "%s\n", m ); return 1; }

%token TOKEN_A %token TOKEN_B %start E %% E : TOKEN_A E TOKEN_B | TOKEN_A TOKEN_B ; %% int main ( int argc, char** argv ) { yyparse (); }

F IG . 6 – analyseur du langage an bn

En utilisant cette mise en oeuvre de la grammaire, l’analyse d’une chaine se fait directement. En fin d’analyse, on est sur que le nombre de ’a’ est égal au nombre de ’b’. 2.2.3

Version avec actions

Dans cette deuxième version, nous allons mettre en place une grammaire reconnaissant le langage a+ b+ : la grammaire G-2.c. Les actions permettront de restreindre le champs d’application de la grammaire au langage an bn .

On défini alors 2 variables globales nba et nbb qui permettront de comptabiliser le nombre de ’a’ et de ’b’ rencontrés au cours de l’analyse. Les actions sont décrites ainsi : Dans ce cas, l’analyseur sera généré à l’aide des fichiers de description de la figure 7.

Grammaire G-2.c = < VN = { E A B } , VT = { a b } , E, ( E → AB A → Aa P = B → Bb >

→ → → → →

A B E

Aa a Bb b AB

) | |

a b

nba + +; nba + +; nbb + +; nbb + +; si nba nbb alors erreur;

parser.y

lexer.l %{ #include "parser.h" int yyerror (char*); %} %% a return TOKEN_A; b return TOKEN_B; (.|\n) yyerror ( "symbole non reconnu" ); %% int yyerror ( char* m ) { printf ( "%s\n", m ); return 1; }

%{ int nba, nbb; %} %token TOKEN_A %token TOKEN_B %start E %% E : A B { if ( nba != nbb ) yyerror ( "erreur" ); ; A : A TOKEN_A { nba ++; } | TOKEN_A { nba ++; } ; B : B TOKEN_B { nbb ++; } | TOKEN_B { nbb ++; } ; %% int main ( int argc, char** argv ) { nba = nbb = 0; yyparse (); }

F IG . 7 – analyseur du langage an bn

2.3

Exercices

Considérons la grammaire G-2.d décrivant les expressions arithmétiques entières. Grammaire G-2.d = < VN = { E } , VT = { cste ( ) + ∗ − / } , E,  E → E+E E+E P = >

E+E

E+E

|

(E)

|

cste



La mise en oeuvre de cette grammaire est décrite dans la figure 8. Cette mise en oeuvre pose un certain nombre de problèmes avec bison (des conflits sur la résolution de l’automate à pile).

parser.y

lexer.l %{ #include "parser.h" %} %% "+" return ADD; "-" return SUB; "*" return MUL; "/" return DIV; [0-9]+ return CSTE; "(" return POUV; ")" return PFER; \n [ \t] . yyerror ( "symbole non reconnu" ); %%

%token ADD %token SUB %token MUL %token DIV %token POUV %token PFER %token CSTE %start E %% E : E ADD E | E SUB E | E MUL E | E DIV E | POUV E PFER | CSTE ; %% int main ( int argc, char** argv ) { yyparse (); }

F IG . 8 – analyseur d’expressions

Afin de résoudre ces problèmes sans modifier la grammaire nous allons utiliser les mots-clé : 1. %left qui permet de spécifier un opérateur associatif à gauche 2. %right qui permet de spécifier un opérateur associatif à droite 3. %nonassoc qui permet de spécifier un opérateur non associatif Ces mot-clé seront utilisés suivant l’ordre de priorité des opérateurs : du moins prioritaire au plus prioritaire (voir figure 9). parser.y

lexer.l %{ #include "parser.h" %} %% "+" return ADD; "-" return SUB; "*" return MUL; "/" return DIV; [0-9]+ return CSTE; "(" return POUV; ")" return PFER; \n [ \t] . yyerror ( "symbole non reconnu" ); %%

%token %token %token %token %token %token

ADD SUB MUL DIV POUV PFER

%token CSTE %start E %left ADD SUB %left MUL DIV

%% E

: | | | | | ;

E ADD E E SUB E E MUL E E DIV E POUV E PFER CSTE

%% int main ( int argc, char** argv ) { yyparse (); }

F IG . 9 – analyseur d’expressions