Explorer les Livres électroniques
Catégories
Explorer les Livres audio
Catégories
Explorer les Magazines
Catégories
Explorer les Documents
Catégories
HEMDANI
L’outil Lex
Lex est un générateur automatique d’analyseurs lexicaux disponible dans tous les environ-
nements de développement en C sous U nix 1 . Il lit en entrée un fichier ”fichier.l” décrivant
l’analyseur lexical du langage à compiler et produit en sortie un fichier ”lex.yy.c” contenant le
code C de l’analyseur lexical décrit dans ”fichier.l”.
Partie déclarations
%%
Partie règles
%%
Partie fonctions auxiliaires
1. On convient ici de regrouper sous le même terme ”Unix” toutes les variantes de ce système y compris Linux.
2. Ne pas utiliser cet entier pour coder un token autre que F IN .
1
Exemple 1
Le code suivant donne un exemple de partie déclarations.
%{
#include <stdio.h>
enum TOKEN {FIN=0, PV, IF, EGAL, ASSIGN, OP, PARG, PARD, ID, NUM};
%}
blancs [ \t\n]+
lettre [A-Za-z]
chiffre [0-9]
ident {lettre}({lettre}|{chiffre})*
entier {chiffre}+
e1 action1
e2 action2
.. ..
. .
en actionn
où chaque (ei )i=1,n est une expression régulière et chaque (actioni )i=1,n est un fragment de
code C séparé de l’expression régulière par au moins un blanc ou une tabulation.
L’interaction entre l’analyseur lexical généré par Lex et un analyseur syntaxique (souvent généré
par yacc 3 ) se fait comme suit : quand il est activé (par l’analyseur syntaxique), l’analyseur lexical
lit le reste du texte source caractère par caractère jusqu’à reconnaître le plus long préfixe filtré
par un modèle ei ; il exécute alors l’action actioni qui, typiquement, rend le contrôle à l’analyseur
syntaxique via une instruction return token;. Si elle ne lui rend pas le contrôle, l’analyseur
lexical continue à reconnaître d’autres lexèmes jusqu’à ce qu’une action rende le contrôle ; ceci
permet par exemple, d’éliminer les blancs et les commentaires.
Expressions simples
a : le caractère a.
”a” : le caractère a même si c’est un marqueur prédéfini.
\a : idem.
\nnn : le caractère de code octal nnn.
[abc] : a, b ou c.
[a-c] : idem.
[^a-c] : tout caractère n’appartenant pas à [a-c].
\n, \t, \r, \v ... : caractères prédéfinis du langage C.
. : tout caractère différent de \n.
Expressions composées
2
(e) : e
^e : e au début d’une ligne.
e$ : e en fin de ligne.
e? : e est optionnel.
e* : 0 ou plusieurs fois e.
e+ : 1 ou plusieurs fois e.
e{n,m} : entre n et m occurrences consécutives de e.
e|f : e ou f.
e/f : e seulement s’il est suivi par f.
{d} : la notion d telle que définie dans la partie déclarations.
return token;
Pour transmettre une valeur d’attribut, on positionne la variable globale yylval (lval pour last
value). Le dernier lexème reconnu est accessible par les variables :
— yytext : un tableau de caractères contenant le lexème,
— yyleng : une variable entière contenant la longueur du lexème.
Remarque 1
1. Si actioni est absente, l’action par défaut consiste à afficher le lexème reconnu.
2. Si une action comporte plus d’une instruction ou qu’elle ne peut pas tenir sur une ligne, elle
doit être placée entre { et }.
3. On peut utiliser le symbole | à la place d’une action pour indiquer que l’action est la même
que celle de la règle suivante.
4. Si une action est réduite à ’;’, le lexème reconnu est simplement ignoré. 2
2 Un exemple complet
Soit à implémenter un analyseur lexical pour le mini-langage décrit par la grammaire
P −→ P; P | I
I −→ if (E == E) I | id = E | ε
E −→ E op E | (E) | id | num
3
Token Modèle Attribut
F IN \0 | EOF
PV ;
IF if
EGAL ==
ASSIGN =
OP +|− COP ∈{P LU S, M OIN S}
P ARG (
P ARD )
ID lettre(lettre | chif f re)∗ nom
NUM chif f re+ valeur entière
%{
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
void erreur_lexicale();
%}
%option yylineno
blancs [ \t\n]+
lettre [A-Za-z]
chiffre [0-9]
ident {lettre}({lettre}|{chiffre})*
entier {chiffre}+
%%
{blancs} ;
; return PV;
if return IF;
== return EGAL;
= return ASSIGN;
[-+] { if (yytext[0]==’+’)
yylval.cop=PLUS;
else
yylval.cop=MOINS;
return OP;
}
\( return PARG;
\) return PARD;
{entier} { yylval.valeur=atoi(yytext);
return NUM;
}
{ident} { yylval.nom=(char*)malloc(yyleng+1);
4
strcpy(yylval.nom,yytext);
return ID;
}
"//".*$ ;
. erreur_lexicale();
%%
/* Avec la définition suivante de la fonction yywrap(), l’analyse s’arrête à
la fin de l’unique fichier accepté en argument.
* /
int yywrap(){
return 1;
}
5
printf("<FIN, >\n");
return 0;
}
L’option yylineno apparaissant juste avant la définition régulière, signifie à Lex qu’il doit main-
tenir la valeur d’une variable de même nom indiquant le numéro de la ligne en cours d’analyse.
Après génération et compilation du code source de l’analyseur lexical 5 , l’analyse du programme
de test suivant :
max=100; // Initialisation
if (x1==max) // Test pour l’égalité
y=ifs+0013-max
REJECT; : quand REJECT; est placée à la fin d’une action, elle force l’analyseur à soumettre
la même entrée aux règles suivantes.
Exemple 2
Pour calculer les nombres d’occurrences des mots ”compiler” et ”pile” dans un texte, on peut
utiliser les règles suivantes :
compiler { nombre_compiler++; REJECT; }
pile { nombre_pile++; }
\n |
. ;
Ces règles se justifient par le fait que le mot ”pile” apparaît dans le mot ”compiler”. 2
6
4 Fonctions offertes par Lex
Plusieurs fonctions sont utilisées par l’analyseur lexical généré par Lex et sont disponibles pour
l’utilisateur. Les plus importantes sont :
int yylex() : c’est la fonction qui réalise l’analyse lexicale ; à chaque appel, elle
retourne un nombre entier positif indiquant un token, ou 0 à la fin
du fichier.
int yymore() : indique que le lexème reconnu lors du prochain appel à yylex()
sera concaténé au contenu de yytext au lieu de le remplacer.
int yyless(int n) : indique que seuls les n premiers caractères de yytext seront gar-
dés ; ceux qui restent (s’il y en a) seront considérés comme non
encore lus lors du prochain appel à yylex().
int yywrap() : elle est appelée par yylex() à la fin du fichier d’entrée. Si la va-
leur retournée par yywrap() est 1, l’analyse s’arrête. Pour pou-
voir analyser plusieurs fichiers, il faut définir la fonction yywrap()
pour permettre :
− d’ouvrir le fichier suivant et l’associer à la variable FILE * yyin ;
− de retourner la valeur 0.
Remarque 2
Ce n’est que lorsque yywrap() retourne 1 que yylex() retourne 0. 2
5 Commandes de compilation
Supposons qu’une spécification Lex soit contenue dans un fichier de nom ”analex.l” ; la com-
mande
$ lex analex.l
produit un fichier de nom ”lex.yy.c” contenant le programme source en C de l’analyseur lexical
décrit par la spécification.
Pour compiler le programme, il suffit de saisir la commande
$ gcc lex.yy.c
qui produira un fichier exécutable de nom ”a.out”.
Au lieu d’utiliser les noms par défaut, on peut nommer les fichiers générés en utilisant l’option
”-o” (de output) ; ainsi, la commande
$ lex -o analex.c analex.l
ordonne à Lex de placer le programme source de l’analyseur lexical qu’il produit, dans un fichier
de nom ”analex.c”, et la commande
$ gcc -o analex analex.c
donne le nom ”analex” au programme exécutable produit par la chaîne de développement gcc.