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 %% productions %% code additionneldans lequel aucune partie n'est obligatoire. Cependant, le premier
%%
l'est, afin d'indiquer la séparation entre les déclarations et
les productions.
Cette partie d'un fichier Lex peut contenir :
%{
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.
notion expression_reguliereLes 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 :
%{ #include "calc.h" #include <stdio.h> #include <stdlib.h> %} /* Expressions regulieres */ /* ---------------------- */ blancs [\t\n ]+ lettre [A-Za-z] chiffre10 [0-9] /* base 10 */ chiffre16 [0-9A-Fa-f] /* base 16 */ identificateur {lettre}(_|{lettre}|{chiffre10})* entier10 {chiffre10}+L'exemple en lui-même est clair, mais regardons d'un peu plus près comment sont formées les expressions régulières.
Symbole | Signification -------------+------------------- x | Le caractere 'x' . | N'importe quel caractere sauf \n [xyz] | Soit x, soit y, soit z [^bz] | Tous les caracteres, SAUF b et z [a-z] | N'importe quel caractere entre a et z [^a-z] | Tous les caracteres, SAUF ceux compris entre a et z R* | Zero R ou plus, ou R est n'importe quelle expression reguliere R+ | Un R ou plus R? | Zero ou un R (c'est-a-dire un R optionnel) R{2,5} | Entre deux et cinq R R{2,} | Deux R ou plus R{2} | Exactement deux R "[xyz\"foo" | La chaine '[xyz"foo' {NOTION} | L'expansion de la notion NOTION definie plus haut \X | Si X est un 'a', 'b', 'f', 'n', 'r', 't', ou | 'v', represente l'interpretation ANSI-C de \X. \0 | Caractere ASCII 0 \123 | Caractere ASCII dont le numero est 123 EN OCTAL \x2A | Caractere ASCII en hexadecimal RS | R suivi de S R|S | R ou S R/S | R, seulement s'il est suivi par S ^R | R, mais seulement en debut de ligne R$ | R, mais seulement en fin de ligne <<EOF>> | Fin de fichier
Ainsi, la définition
identificateur {lettre}(_|{lettre}|{chiffre10})*reconnaitra comme identificateur les mots 'integer', 'une_variable', 'a1', mais pas '_ident' ni '1variable'. Facile, non ?
Enfin, comme dernier exemple, voici la définition d'un réel :
chiffre [0-9] entier {chiffre}+ exposant [eE][+-]?{entier} reel {entier}("."{entier})?{exposant}?
Cette partie sert à indiquer à Lex ce qu'il devra faire lorsqu'il rencontrera telle ou telle notion. Celle-ci peut contenir :
%{
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.
expression_reguliere actionSi
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...).
Tu peux mettre dans cette partie facultative tous le code que tu veux. Si tu ne mets rien, Lex considère que c'est juste :
main() { yylex(); }
Comme tu as pu le voir, Lex est très simple d'emploi (en tout cas dans les cas simples tels que ceux présentés). Pourtant, nous n'avons pas fait le tour de toutes les fonctionnalités de Lex, et je t'invite donc à consulter la page de manuel pour une information plus détaillée.