Académique Documents
Professionnel Documents
Culture Documents
Analyse Lexicale Flex
Analyse Lexicale Flex
Introduction
Un compilateur est un programme qui prend en entrée un code source écrit
généralement dans un langage de programmation haut niveau et le
transforme en un code objet, ce dernier permettant de générer un
programme exécutable.
Analyse lexicale
L’analyse lexicale représente la première étape de la compilation, elle consiste
à vérifier si les caractères/mots du code source en entrée appartiennent au
langage auquel est associé le compilateur.
Un analyseur lexical doit:
Générer une suite d’entités lexicales (appelées tokens également) à
partir du code source lu, cette suite sera passée à l’analyseur syntaxique
afin de poursuivre le processus de compilation.
Ignorer les parties superflues telles que les commentaires, les espaces
et les sauts de lignes …
Gérer les numéros de lignes, cela permet d’associer à chaque erreur
rencontrée le numéro de la ligne dans laquelle elle apparue.
Signaler les erreurs lexicales lorsqu’elles surviennent, en précisant leur
emplacement dans le code source.
Ainsi, l’analyseur lexical que nous allons concevoir doit réaliser tout ce qui a été
mentionné en supra. Nous verrons de manière plus détaillée comment cela se
fait dans les prochains paragraphes.
Afin de pouvoir générer la suite des tokens, l’analyseur lexical doit d’abord lire
le code source et n’accepter que ce qui a été décrit par les règles lexicales du
langage. Pour définir ces règles lexicales, nous utilisons des expressions
régulières qui sont l’équivalent des automates.
Une expression régulière est une suite de caractères qui permet de contrôler
les caractères d’un texte dans le but de s’assurer de sa validité. Dans une
expression régulière chaque caractère est significatif et correspond à sa valeur
littérale, y compris les espaces. Exemple : l’expression régulière hello world
correspond à la chaine de caractère hello world.
Cependant certains caractères sont réservés, et ont chacun une certaine
fonctionnalité. Pour indiquer que nous souhaitons avoir leur valeur littérale,
nous devons les précéder du caractère spéciale \ ou bien les mettre entre deux
côtes " ".
" " et \ : comme nous l’avons mentionné ci-dessus, ces deux caractères
permettent d’avoir la valeur littérale d’un caractère, cependant le
symbole \ est utilisé uniquement avant les caractères réservés ou bien
certain autres caractères comme par exemple le n dans \n pour indiquer
le saut de ligne.
Exemple : \\ représente la valeur littérale de \ et "+" représente la
valeur littérale de +.
Il faut noter que \ et plus prioritaire que ", ainsi \" nous donnera la
valeur littérale de ", mais "\ est une expression incomplète car un autre
caractère doit suivre le \ et que nous devons ajouter un autre ", pour les
même raisons "\" et incomplète également.
. : le point indique que l’expression régulière accepte tous les caractères
possibles mis à part le saut de ligne \n.
[…] : Permet de déterminer des ensembles, cette notation indique que
l’expression régulière accepte un des caractères écrits entre les deux
crochets.
Exemple : [abc] représente le caractère a ou le b ou le c et qui
équivalente à l’expression (a|b|c).
[aeiouy]+ correspond à une suite d’un ou plusieurs caractères
appartenant chacun à l’ensemble [aeiouy] sans que ça ne soit le même
caractère sur toute la chaîne bien sûr.
- : lorsqu’il est utilisé entre deux crochet il correspond à un ensemble de
caractères dont les valeurs sont comprise dans un intervalle, il faut
noter que le – doit être écrit entre les deux bornes de l’intervalle et que
les codes ASCII de tous les éléments de l’intervalle sont contigus.
Exemple : [0-9] correspond à l’expression (0|1|2|3|4|5|6|7|8|9)
^ : Lorsqu’il est utilisé entre deux crochet il correspond à la négation,
autrement dit l’expression régulière acceptera tous les caractères
hormis ceux qui viennent après le ^. Notons que le ^ doit être suivi par
au moins un autre caractère, ainsi l’expression [^] n’est pas correcte.
Exemple : [^ab] signifie que nous allons accepter tous les caractères à
part le a et le b.
Note : à l’intérieur des crochets tous les caractères correspondent à leur
valeurs littérales sauf le \ le – s’il est compris entre deux valeurs et le ^
s’il est suivi de caractères. Exemple : [.+] veut dire que l’expression
accepte un caractère dont la valeur est soit le point soit le plus (valeurs
littérales) et non pas une suite d’un ou plusieurs caractères acceptant
tous les caractères sauf le saut de ligne.
Un analyseur lexical Flex est donc un fichier dont l’extension est .l, dans lequel
nous décrivons les règles lexicales du langage à analyser ainsi que les actions
associées à ces règles. Dans les paragraphes suivants nous présenterons la
structure générale d’un fichier .l ainsi qu’un exemple d’analyseur lexical
simple.
Dans un premier temps, étant donné que nous n’allons effectuer que l’analyse
lexicale, nous simulerons le résultat de cette étape qui est la suite des entités
lexicales en créant une chaine de caractère qui sera initialement vide et à
laquelle nous ajouterons les noms des entités lexicales au fur et à mesure que
nous les rencontrions. A la fin si aucune erreur lexicale n’a été détectée, la
chaîne représentant les tokens sera affichée et nous indiquerons que le code
est lexicalement correct.
Dans ce qui suit, nous allons écrire l’analyseur lexicale associé au langage Lp
que nous venons de définir. Le code Flex correspondant est le suivant :
%{
#include <string.h>
int ligne=1;
char Entite_Lexicale []="";
int nb_err_lex=0;
%}
Mc_Int int
Mc_Print printf
Mc_For for
STR ["].*["]
IDF [a-zA-Z][a-zA-Z0-9_]*
Affect =
PV ;
Par_Ouv \(
Par_Fer \)
CST_Int [1-9][0-9]*|0
Opr_Plus \+
Opr_Inf <
%%
{Mc_Int} strcat(Entite_Lexicale,"Mc_Int ");
{Mc_Print} strcat(Entite_Lexicale,"Mc_Print ");
{Mc_For} strcat(Entite_Lexicale,"Mc_For ");
{STR} strcat(Entite_Lexicale,"STR ");
{IDF} strcat(Entite_Lexicale,"IDF ");
{Affect} strcat(Entite_Lexicale,"Affect ");
{PV} strcat(Entite_Lexicale,"PV ");
{Par_Ouv} strcat(Entite_Lexicale,"Par_Ouv ");
{Par_Fer} strcat(Entite_Lexicale,"Par_Fer ");
{CST_Int} strcat(Entite_Lexicale,"CST_Int ");
{Opr_Plus} strcat(Entite_Lexicale,"Opr_Plus ");
{Opr_Inf} strcat(Entite_Lexicale,"Opr_Inf ");
[ \t]
[\n] ligne++;
. {nb_err_lex++;printf("erreur lexicale a la ligne %d generee par %s\n",ligne,yytext);}
%%
int yywrap(){}
int main()
{
yylex();
if (nb_err_lex==0)
{
printf("Code lexicalement correct \n");
printf("Tokens : %s\n",Entite_Lexicale);
}
return 1;
}
Une fois que l’analyseur lexical est écrit en lex, nous devons le compiler en
utilisant la commande flex.
Pour cela il faut ouvrir l’invité de commande là ou se trouve le fichier contenant
votre analyseur lexical et tapper :
flex nom_fichier.l
Cette commande va nous générer un fichier toujours nommé lex.yy.c, nous
compilerons ce fichier à l’aide de la commande gcc
gcc lex.yy.c
par défaut le nom du fichier exécutable générée sera nommé a.exe, si vous
voulez changer ce nom veuillez ajouter à la fin de la commande précédente ce
qui suit : -o nom_ fichier.exe.
Pour tester votre analyseur lexical, vous aller soit le lancer en tapant son nom
dans l’invité de commande et ensuite saisir le code que vous voulez tester
manuellement. Soit lui passer en paramètre un fichier .txt contenant le code à
analyser en utilisant la commande suivante :
nom_ fichier.exe<nom_fichier.txt
Notons qu’il est préférable de regrouper toute les instructions que vous allez
utiliser dans l’invité de commande, dans un seul fichier .bat et exécuter ce
fichier à chaque fois que vous aurez besoin de recompiler votre analyseur
lexical. Cela vous évitera de devoir écrire ces commandes plusieurs fois et
toutes les erreurs qui vont avec.
BON COURAGE