chiffre

publicité
Analyse lexicale
Généralités
Expressions rationnelles
Automates finis
Analyseurs lexicaux
Rôle d'un analyseur lexical
lexèmes
code source
analyseur
lexical
analyseur
syntaxique
...
table des
symboles
Séparer l'analyse lexicale de l'analyse syntaxique
Réduire la complexité du compilateur et la complexité de
conception de ces deux modules
Augmenter la flexibilité du compilateur : portabilité,
maintenabilité
Augmenter l'efficacité du compilateur
Méthodes de construction
Utiliser un générateur d'analyseurs lexicaux
(Flex)
Ecrire l'analyseur lexical dans un langage
évolué
Lecture du code source (1/2)
On utilise un tampon de lecture cyclique en deux moitiés
Anticiper = lire quelques caractères d'avance pour décider la valeur du
lexème en cours
...
a
=
4 * i + 1
...
fin
debut
Une anticipation de quelques caractères suffit
Un langage où il n'y avait pas de limite à la taille de l'anticipation : PL/1
DECLARE ( ARG1, ARG2, ... , ARGn )
Il faut attendre la fin de l'expression pour savoir si DECLARE est un motclé ou un nom de tableau
Lecture du code source (2/2)
...
a
=
4 * i + 1
fin
debut
si fin atteint la fin de la première moitié
alors { charger la deuxième moitié
fin := fin + 1 }
sinon si fin atteint la fin de la deuxième moitié
alors { charger la première moitié
fin := 0 }
sinon fin := fin + 1
...
Mots et langages formels
Un mot sur un alphabet A est une suite finie de
symboles pris dans A
A peut être l'ensemble des caractères, dans ce cas les
lexèmes sont des mots
code ASCII
alphabet
{ 0, 1 }
mots
caractères
analyse lexicale
caractères
lexèmes
analyse syntaxique
lexèmes
programmes
Le mot vide est noté ε, sa longueur est 0
Un langage formel est un ensemble de mots
Expressions rationnelles
On forme des expressions à partir des symboles (éléments de l'alphabet) et
de ε en utilisant
- l'union notée | (ou +)
- la concaténation (pas de symbole)
- l'itération notée *
On utilise souvent des opérations supplémentaires (abréviations) :
+ pour l'itération au moins une fois
? pour l'option
. pour tout l'alphabet
[...] pour une union de symboles
Priorités entre opérateurs : itération, concaténation, union
Expressions rationnelles
Identificateurs en C
lettre = A | B | ... | Z | a | b | ... | z
chiffre = 0 | 1 | ... | 9
id = (lettre | _) (lettre | _ | chiffre)*
Nombres sans signe en C
chiffre = 0 | 1 | ... | 9
chiffres = chiffre chiffre*
frac = . chiffres
exp = (E | e) (+ | - | ε) chiffres | ε
num = (chiffres (frac | . | ε) | frac) exp
Les mêmes avec les abréviations
chiffre = 0 | 1 | ... | 9
chiffres = chiffre+
frac = . chiffres
exp = ((E | e) (+ | -) ? chiffres)?
num = (chiffres (frac | .)? | frac) exp
Lexèmes
Définition des espaces blancs
delim = \b | \t | \n
ws = delim*
On dit habituellement lexème pour "catégorie de lexèmes" :
expression
lexème
attribut
expression
lexème
attribut
ws
-
-
<
relop
lt
if
if
-
<=
relop
le
else
else
-
==
relop
eq
id
id
pointeur
!=
relop
ne
num
num
valeur
>
relop
gt
>=
relop
ge
etc.
Automates finis
Les analyseurs lexicaux utilisent des graphes appelés automates finis
- les sommets sont appelés états
- les transitions sont orientées et étiquetées
- certains états sont finaux et comportent une action
lettre | _ | chiffre
11
lettre
_
12
autre
Identificateurs en C
13
return(gettoken(), install)
Automates finis
0
autre
1
<
2
return(relop, lt)
3
return(relop, le)
=
>
autre
4
5
return(relop, gt)
=
6
=
7
=
return(relop, ge)
8
return(relop, eq)
!
9
=
10
Opérateurs de comparaison
return(relop, ne)
Automates finis
chiffre
1
4
chiffre
15
E|e
chiffre
+|-
chiffre
16
17
18
autre
chiffre
.
.
20
chiffre
chiffre
21
E|e
chiffre
2
2
23
autre
chiffre
24
chiffre
.
.
25
chiffre
Nombres sans signe en C
26
autre
19
Implémentation en C
Un programme C qui reconnaît dans le début d'une chaîne de caractères un des
lexèmes définis par les automates
/* Recherche de l'état initial du prochain automate */
int state = 0, start = 0 ;
int lexical_value ;
int fail() {
forward = token_debut ;
switch(start) {
case 0 : start = 11 ; break ;
case 11 : start = 14 ; break ;
case 14 : start = 22 ; break ;
case 22 : recover() ; break ;
default : /* erreur */ }
return start ; }
token nexttoken() {
while (1) {
switch(state) {
case 0 : c = nextchar() ;
if (c==blank||c==tab||c==newline) {
state = 0 ; debut ++ ; }
else if (c == '<') state = 1 ;
else if (c == '>') state = 4 ;
else if (c == '=') state = 7 ;
else if (c == '!') state = 9 ;
else state = fail() ;
break ;
/* ... cas 1 - 10 ... */
case 11 : c = nextchar() ;
if (isletter(c)||c=='_') state = 12 ;
else state = fail() ;
break ;
case 12 : c = nextchar() ;
if (isletter(c)||c=='_') state = 12 ;
else if (isdigit(c)) state = 12 ;
else state = 13 ;
break ;
case 13 : retract(1) ; install_id() ;
return(gettoken()) ;
/* ... cas 14-26 ... */
}
}
}
Automates déterministes
Un automate fini est déterministe si :
- il possède au plus un état initial
- de chaque état part au plus une flèche étiquetée par un symbole donné
Exemple d'automate non déterministe :
a|b
a
1
2
a
Exemple d'automate déterministe :
a
b
a
2
1
b
Automates déterministes
Un automate déterministe représentant un ensemble de mots
peut être utilisé commodément pour reconnaître l'un des
mots dans une chaîne de caractères : il suffit de partir de
l'état initial, de suivre les flèches et de voir si l'état dans
lequel on arrive est final.
Tout automate fini est équivalent à un automate fini
déterministe : il existe un automate fini déterministe qui
reconnaît le même langage.
Déterminisation
Algorithme : on part d'un automate fini A = (Q, I, F, ). On
construit l'automate déterministe D ayant
1. comme états les parties de Q
2. comme état initial I
3. comme états finaux les parties de Q contenant au moins un
élément de F
4. comme transitions les (U, a, V) si V est l'ensemble des états
atteignables depuis U par une transition étiquetée a
Complexité : si A a n états, D a au plus 2n états.
Exemple
a|b
1
a
b
2
3
Un automate non déterministe
b
1
a
a
b
12
b
a
Le résultat de l'algorithme
13
Généralisation
On peut admettre des transitions d'étiquette  (transitions
spontanées).
Pour être déterministe, un automate ne doit pas contenir de
transitions spontanées.
Pour déterminiser, on adapte l'algorithme précédent :
- à l'étape 2 on prend l'ensemble des états atteignables depuis
I par un chemin de transitions spontanées ;
- à l'étape 3, V est l'ensemble des états atteignables depuis U
par un chemin étiqueté a
La construction récursive
On peut construire un automate fini équivalent à une
expression rationnelle : il reconnaît le même langage.
L'automate construit est tel que :
- il a un seul état initial i
- il a un seul état final f  i
- aucune transition n'entre dans i
- aucune transition ne sort de f.
Pour  et a, on prend:

a

union



concaténation



itération



Utilisation de Flex
spécification
Flex
flex
lex.yy.c
compilateur C
caractères
(code source)
a.out
lexèmes
4 étapes :
- créer sous éditeur une spécification Flex (expressions
rationnelles)
- traiter cette spécification par la commande flex
- compiler le programme source C obtenu
- exécuter le programme exécutable obtenu
...
Spécifications Flex
Un programme Flex est fait de trois parties :
déclarations
%%
règles de traduction
%%
fonctions auxiliaires en C
Les règles de traduction sont de la forme
p1
{ action1 }
p2
{ action2 }
...
pn
{ actionn }
où chaque pi est une expression rationnelle et chaque action une suite
d'instructions en C.
Exemple
%{
/* Partie en langage C : définitions de constantes,
déclarations de variables globales, commentaires... */
%}
delim [ \t\n]
letter [a-zA-Z]
%%
{delim}*
{ /* pas d'action */ }
if
{ return IF ; }
then
{ return THEN ; }
else
{ return ELSE ; }
{letter}({letter}|[0-9])* { yyval = install_id() ;
return ID ; }
([0-9]+(\.[0-9]*)?|\.[0-9]+)((E|e)(\+|-)?[0-9]+)?
{
yyval = install_num() ; return NUMBER ; }
Exemple
"<"
{ yyval = LT ; return RELOP ; }
"<="
{ yyval = LE ; return RELOP ; }
%%
install_id() {
/* fonction installant dans la table des symboles le
lexème vers lequel pointe yytext et dont la longueur
est yylength. Renvoie un pointeur sur l'entrée dans
la table */
}
install_num() {
/* fonction calculant la valeur du lexème */
}
Spécifications Flex
Les commentaires /* ... */ ne peuvent être insérés que dans une portion
en C :
- dans la partie déclaration, seulement entre %{ et %} ;
- dans la partie règles, seulement dans les actions ;
- dans la partie fonctions auxiliaires, n'importe où.
Dans les règles
pi
{ actioni }
les expressions rationnelles pi ne peuvent pas contenir d'espaces blancs
(ou alors dé-spécialisés).
Dans la partie règles, si une règle commence par un espace blanc, elle
est interprétée comme du langage C et est insérée dans lex.yy.c au
début de la fonction qui renvoie le prochain lexème (utilisable pour
déclarer des variables locales).
Segmentation du code source
par l'analyseur lexical (1/3)
L'analyseur lexical produit par Flex
- commence à chercher les lexèmes au début du code source ;
- après chaque lexème reconnu, recommence à chercher les
lexèmes juste après
Exemple : si piraté est reconnu, raté n'est pas reconnu
Si aucun lexème n'est reconnu à partir d'un point du code
source
l'analyseur affiche le premier caractère sur la sortie standard
et recommence à chercher à partir du caractère suivant
Segmentation du code source
par l'analyseur lexical (2/3)
Si plusieurs lexèmes sont reconnus à partir d'un point
du code source (conflit)
1. Deux lexèmes de longueurs différentes
C'est le plus long qui gagne
Exemple : [a-zA-Z_] [a-zA-Z_0-9]*
Segmentation du code source
par l'analyseur lexical (3/3)
2. Deux lexèmes de même longueur
Ils commencent au même point et terminent au même
point : s'il y a conflit, c'est qu'ils sont issus de deux
règles différentes. C'est la règle qui apparaît la première
dans la spécification Flex qui gagne
Exemple : conflit entre [a-zA-Z_] [a-zA-Z_0-9]* et while
Mettre l'exception avant la règle
En dehors de ce cas, l'ordre des règles ne joue pas
Téléchargement