ISBN 0-321-49362-1
Chapitre 4
Analyse lexicale et syntaxique
Copyright © 2007 Addison-Wesley. All rights reserved. 1-2
Chapitre 4: Sujets
• Introduction• Analyse lexicale• Analyse syntaxique
– Méthode descendante– Méthode ascendante
Copyright © 2007 Addison-Wesley. All rights reserved. 1-3
Introduction
• 3 principales méthodes pour implémenter un langage de programmation:– compilation– interprétation– méthode hybride
Dans tous les cas, il est nécessaire de se référer au code source pour détecter les erreurs de syntaxe.
• Pratiquement toutes les méthodes d'analyse de la syntaxe reposent sur une description formelle de la syntaxe du langage (grammaire hors-contexte)
Copyright © 2007 Addison-Wesley. All rights reserved. 1-4
Analyse syntaxique
• Deux parties:
– Au niveau inférieur il y a l'analyseur lexical (automates finis)
– Au niveau supérieur il y a l'analyseur syntaxique ou parseur (automates à pile)
Copyright © 2007 Addison-Wesley. All rights reserved. 1-6
Avantages de la séparation
• Simplicité – méthodes moins complexes peuvent être utilisées pour l'analyse lexicale; simplifie le parseur
• Efficacité- séparation permet l'optimisation de l'analyseur lexical.
• Portabilité – l'analyseur lexical dépend souvent de la plateforme (lecture de fichiers) alors que l'analyseur syntaxique est toujours portable.
Copyright © 2007 Addison-Wesley. All rights reserved. 1-7
Analyse lexicale
• Agit comme interface pour l'analyseur syntaxique
• Identifie les parties du programme source correspondant aux lexèmes– Un lexème est un mot correspondant à une
catégorie de mots (token) – Par exemple, la variable somme est un lexème
appartenant au token identificateur
Copyright © 2007 Addison-Wesley. All rights reserved. 1-8
Analyse lexicale (suite)
• L'analyseur syntaxique appelle l'analyseur lexical lorsqu'il a besoin d'un autre token
• 3 approches pour construire un analyseur lexical:
– Écrire une description formelle des tokens (expressions régulière) et utiliser un outil logiciel (ex. lex) pour transformer ces descriptions en un analyseur lexical.
– Concevoir un diagramme d'état pour décrire les tokens et implémenter ce diagramme sous forme de programme.
– Concevoir un diagramme d'état pour décrire les tokens et implémenter ce diagramme à l'aide d'une table.
Copyright © 2007 Addison-Wesley. All rights reserved. 1-9
Construire un analyseur lexical
• Par exemple, si on veut lire trois types de tokens:– identificateurs: lettre suivie de lettres ou de
chiffres.– mots clefs: suite de lettres– entiers littéraux: chiffre suivit de chiffres
• Approche naïve: Une transition pour chaque état et chaque caractère – trop gros!
Copyright © 2007 Addison-Wesley. All rights reserved. 1-10
Construire un analyseur lexical (suite)
• Plusieurs transition peuvent être combinées pour simplifier le diagramme– Une seule classe de caractères pour toutes les
lettres majuscules et minuscule (LETTER)– Une seule classe de caractères pour tous les
chiffres (DIGIT)
Copyright © 2007 Addison-Wesley. All rights reserved. 1-11
Construire un analyseur lexical (suite)
• Plutôt que d'avoir des états distincts pour les mots réservés, on traite ceux-ci de la même manière que les identificateurs– On consulte une table pour déterminer si un
lexème est un mot clef
Copyright © 2007 Addison-Wesley. All rights reserved. 1-12
Diagramme d'état
Copyright © 2007 Addison-Wesley. All rights reserved. 1-13
Construire un analyseur lexical (suite)
• Variables globales et utilitaires:– char nextChar: contient le dernier caractère lu– int charClass: LETTER (0), DIGIT (1), UNKNOWN (-1)– char lexeme[100]: chaîne de caractère– getChar() – lit le prochain caractère et le place dans nextChar. Met la classe du caractère dans charClass.
– addChar() – Ajoute nextChar à la fin de lexeme– int lookup(char*) – détermine si lexeme est un mot
clef
Copyright © 2007 Addison-Wesley. All rights reserved. 1-14
Implémentation
int lex() { getChar(); switch (charClass) { case LETTER: addChar(); getChar(); while (charClass == LETTER ||
charClass == DIGIT) { addChar(); getChar(); } return lookup(lexeme); break;
…
Copyright © 2007 Addison-Wesley. All rights reserved. 1-15
Implémentation
…case DIGIT:
addChar();
getChar();
while (charClass == DIGIT) {
addChar();
getChar();
}
return INT_LIT;
break;
} /* End of switch */
} /* End of function lex */
Copyright © 2007 Addison-Wesley. All rights reserved. 1-16
L'analyseur syntaxique
• Objectifs:– Trouver toutes les erreurs de syntaxe et
envoyer un message approprié. – Produire l'arbre syntaxique ou, du moins,
l'information nécessaire pour le construire.
Copyright © 2007 Addison-Wesley. All rights reserved. 1-17
L'analyseur syntaxique (suite)
• Deux catégories d'analyseurs syntaxiques:
– Descendant – produit l'arbre syntaxique en commençant par la racine
– Ascendant – commence par les feuilles
Dans les deux cas on utilise une grammaire hors-contexte comme description du langage.
Copyright © 2007 Addison-Wesley. All rights reserved. 1-18
L'analyseur syntaxique (suite)
• Complexité– À partir de toute grammaire non ambiguë, il
est possible de construire un analyseur syntaxique fonctionnant en temps O(n3), où n est la taille de l'entrée.
– On utilise plutôt un type restreint de grammaire permettant d'effectuer l'analyse syntaxique en temps O(n)
Copyright © 2007 Addison-Wesley. All rights reserved. 1-19
L'analyseur syntaxique (suite)
• Parseurs descendants– On commence avec le symbole de départ et on
détermine la séquence de règles nécessaires pour dériver l'entrée (suite de token)
<départ> entrée
• Algorithme LL: – Left-to-right scan (lecture de l'entrée de
gauche à droite)– Leftmost derivation (dérivation par la gauche)
Copyright © 2007 Addison-Wesley. All rights reserved. 1-20
L'analyseur syntaxique (suite)
• Parseurs ascendants– On commence avec l'entrée et on applique à
rebours les règles de la grammaire afin d'obtenir la symbole de départ.
entrée <départ>
• Algorithme LR:• Left-to-right scan• Rightmost derivation
Copyright © 2007 Addison-Wesley. All rights reserved. 1-21
Analyseur récursif-descendant (LL)
• Un sous-programme pour chaque non-terminal
• Les sous-programmes sont mutuellement récursifs
• Les grammaires hors-contextes étendues sont appropriées pour ce type d'analyseur syntaxique car elles minimisent le nombre de sous-programmes.
• On suppose l'existence d'un analyseur lexical lex, qui met le prochain token dans la variable globale nextToken
Copyright © 2007 Addison-Wesley. All rights reserved. 1-22
Analyseur récursif-descendant (suite)
• Exemple:
<expr> <term> {(+ | -) <term>}<term> <factor> {(* | /) <factor>}<factor> id | ( <expr> )
Copyright © 2007 Addison-Wesley. All rights reserved. 1-23
Analyseur récursif-descendant (suite)
// //Sous-programme pour le non-terminal expr//void expr() {
term(); // Sous-programme pour le non-terminal term
while (nextToken == PLUS_CODE || nextToken == MINUS_CODE){ lex(); term(); }}
Copyright © 2007 Addison-Wesley. All rights reserved. 1-24
Analyseur récursif-descendant (suite)
// // Sous-programme pour le non-terminal term//void term() {
factor(); // Sous-programme pour le non-terminal factor
while (nextToken == MULT_CODE || nextToken == DIV_CODE){ lex(); factor(); }}
Copyright © 2007 Addison-Wesley. All rights reserved. 1-25
Analyseur récursif-descendant (suite)
void factor() {if (nextToken) == ID_CODE)
lex();else if (nextToken == LEFT_PAREN_CODE) {
lex(); expr();
if (nextToken == RIGHT_PAREN_CODE) lex();
else error(); } else error(); }
Copyright © 2007 Addison-Wesley. All rights reserved. 1-26
Analyseur récursif-descendant (suite)
• Grammaires LL– Forme particulière
• Le problème de la récursion à gauche:A → Abou
A → BbB → Ac
– De telles grammaires ne peuvent pas être utilisée par un analyseur syntaxique descendant
– On peut cependant toujours remplacer une telle grammaire par une autre n'ayant pas ce problème
Copyright © 2007 Addison-Wesley. All rights reserved. 1-27
Analyseur récursif-descendant (suite)
Autre problème:– Deux règles: A→α et A→β tels que
α génère aα' et β génère aβ'
– Comment choisir entre ces deux règles?– La grammaire doit être modifiée mais cela
n'est pas toujours possible
Copyright © 2007 Addison-Wesley. All rights reserved. 1-28
Analyseurs ascendants
• Forme sententielle droite: On développe le non-terminal le plus à droite en premier
• Exemple:E → E + T | T
T → T * F | F
F → (E) | id
E E+T E+T*F E+T*id E+F*id ⇒ ⇒ ⇒ ⇒ ⇒ E+id*id F+id*id id+id*id⇒ ⇒
• On veut partir de id+id*id et remonter vers E
Copyright © 2007 Addison-Wesley. All rights reserved. 1-29
Analyseurs ascendants (suite)
• Handle: segment d'une forme sententielle (dérivée par la droite) correspondant à la partie à droite de la dernière règle utilisée
• Exemple:E E+T E+F E+id E+T+id⇒ ⇒ ⇒ ⇒
E+T est le handle de E+T+id
• Remarque: Si la grammaire est non-ambiguë alors le handle est unique
• Problème: Comment trouver le handle?
Copyright © 2007 Addison-Wesley. All rights reserved. 1-30
Analyseurs ascendants (suite)
• Les algorithmes Shift-Reduce:
– Utilisent une pile
– Déplacement (Shift): Action de placer le prochain token sur le dessus de la pile
– Reduction: Action de remplacer le handle sur le dessus de la pile par la partie de gauche (non-terminal) de la règle correspondante
Copyright © 2007 Addison-Wesley. All rights reserved. 1-31
Analyseurs ascendants (suite)
• Parseurs LR– Type particulier d'analyseurs ascendants
conçus par D. Knuth en 1965– Ils fonctionnent pour pratiquement toutes les
grammaires décrivant des langages de programmation
– Ils peuvent détecter les erreurs de syntaxe aussitôt que possible.
– Plus généraux que les parseurs LL
Copyright © 2007 Addison-Wesley. All rights reserved. 1-32
Analyseurs ascendants (suite)
• Un parseur LR est un automate fini augmenté d'une pile
• Une configuration de la machine a la forme:
(S0 X1 S1 X2 S2 … Xm Sm, aiai+1…an$)
1. aiai+1…an : Partie de l'entrée qui reste à lire
2. Si : États de la machine
3. Xi : Symboles de la grammaire
Copyright © 2007 Addison-Wesley. All rights reserved. 1-33
Structure d'un parseur LR
Copyright © 2007 Addison-Wesley. All rights reserved. 1-34
Analyseurs ascendants (suite)
• Le comportement d'un parseur LR est indiqué par deux tables: – la table ACTION (lignes=états, colonnes=terminaux)– la table GOTO (lignes=états, colonnes=non-
terminaux)
• La table ACTION indique si un déplacement ou une réduction doit avoir lieu étant donné l'état courant et le prochain token (terminal)
• La table GOTO indique quel état placer sur le dessus de la pile après qu'une réduction ait eu lieu.
Copyright © 2007 Addison-Wesley. All rights reserved. 1-35
Analyseurs ascendants (suite)
• Configuration initiale: (S0, a1…an$)• Configuration courante: (S0X1S1X2S2…
XmSm, aiai+1…an$)• Comportement du parseur:
– Si ACTION[Sm, ai] = Shift S, la prochaine configuration sera:
(S0X1S1X2S2…XmSmaiS, ai+1…an$)– Si ACTION[Sm, ai] = Reduce A et que
S = GOTO[Sm-r, A], où r = ||, alors la prochaine configuration sera:
(S0X1S1X2S2…Xm-rSm-rAS, aiai+1…an$)
Copyright © 2007 Addison-Wesley. All rights reserved. 1-36
Analyseurs ascendants (suite)
• Comportement du parseur (suite):– Si ACTION[Sm, ai] = Accept, alors l'analyse est
complété et aucune erreur n'a été trouvée.
– Si ACTION[Sm, ai] = Error, alors le parseur appelle un utilitaire de gestion des erreurs.
Copyright © 2007 Addison-Wesley. All rights reserved. 1-37
Analyseurs ascendants (suite)
• Exemple:1. E → E + T2. E → T3. T → T * F4. T → F5. F → ( E )6. F → id
• Les tables ACTION et GOTO sont habituellement construite à l'aide d'un programme telle que yacc ou bison
Copyright © 2007 Addison-Wesley. All rights reserved. 1-38
Les tables ACTION et GOTO
Copyright © 2007 Addison-Wesley. All rights reserved. 1-39
Analyseurs ascendants (suite)
Pile Entrée Action
0 id + id * id $ Shift 5
0 id 5 + id * id $ Reduce 6 (GOTO[0,F])
0 F 3 + id * id $ Reduce 4 (GOTO[0,T])
0 T 2 + id * id $ Reduce 2 (GOTO[0,E])
0 E 1 + id * id $ Shift 6
0 E 1 + 6 id * id $ Shift 5
0 E 1 + 6 id 5 * id $ Reduce 6 (GOTO[6,F])
0 E 1 + 6 F 3 * id $ Reduce 4 (GOTO[6,T])
0 E 1 + 6 T 9 * id $ Shift 7
0 E 1 + 6 T 9 * 7 id $ Shift 5
0 E 1 + 6 T 9 * 7 id 5 $ Reduce 6 (GOTO[7,F])
0 E 1 + 6 T 9 * 7 F 10 $ Reduce 3 (GOTO[6,T])
0 E 1 + 6 T 9 $ Reduce 1 (GOTO[0,E])
0 E 1 $ Accept
1) E → E + T2) E → T3) T → T * F4) T → F5) F → ( E )6) F → id
1-40
Comprendre les tables: les items
(0) S → T$ (1) T → 0T0 (2) T → 1
Items: S → _T$ T → _0T0 T → _1 S → T_$ T → 0_T0 T → 1_ S → T$_ T → 0T_0
T → 0T0_Items complets:
S → T$_T → 0T0_ T → 1_
1-41
T
S→T_$
Signification des items
0 T 0
0 T 0
S
S→_T$
$
S→T$_
T
T→0T_0
T
T→0T_0
0
T→0T0_
1
T→1_
0
T→0T0_
0
T→0_T0
0
T→0_T0
• Un item de la forme A→α_β indique que l'on est en train de traiter la règle A→αβ alors que α est sur les dessus de la pile
• Un item complet de la forme A→γ_ indique que γ est sur le dessus de la pile et qu'on peut le remplacer par A (réduction)
1-42
T
S→T_$
Signification des items (suite)
0 T 0
0 T 0
S
S→_T$
$
S→T$_
T
T→0T_0
T
T→0T_0
0
T→0T0_
1
T→1_
0
T→0T0_
0
T→0_T0
0
T→0_T0
1, T→1_
0, T→0_T0
0, T→0_T0
S, S→_T$
S, accept
S, S→_T$
$, S→T$_
T, S→T_$
S, S→_T$T, S→T_$
S, S→_T$
0, T→0T0_
T, T→0T_0
0, T→0_T0
S, S→_T$
T, T→0T_0
0, T→0_T0
0, T→0_T0
S, S→_T$
0, T→0T0_
T, T→0T_0
0, T→0_T0
0, T→0_T0
S, S→_T$T, T→0T_0
0, T→0_T0
S, S→_T$
Shifts (3 fois) Reduce Shift Reduce
Shift Reduce Shift Reduce
Analyse du mot 00100$
1-43
Automate fini
0
S→_T$
1
S→T_$
<accept>
S→T$_
4
T→0T_0
2
T→0_T0
5
T→0T0_
3
T→1_
0
0
T 0
T
$
11
0) S → T$1) T → 0T02) T → 1
• Cet automate sert à décrire toutes les formes sententielles ne contenant pas de handle.
• Chaque état correspond à un item
• Seules les formes sententielles conduisant à un item complet possèdent un handle.
1-44
Construction des tables à partir de l'automate
ACTION GOTO
0 1 $ T
0 S2 S3 1
1 accept
2 S2 S3 4
3 R2 R2
4 S5
5 R1 R1
1-45
Autre exemple
• Ajoutons 3 règles à la grammaire précédente:
(0) S → T$ (1) T → 0T0 (2) T → 1 (3) T → 0V (4) V → 2V (5) V →2
Note: Ceci n'est pas une grammaire LL mais c'est une grammaire LR
1-46
Automate fini non-déterministe
0
S→_T$
1
S→T_$
<accept>
S→T$_
4
T→0T_0
2
T→0_T0
5
T→0T0_
3
T→1_
0
0
T 0
T
$
11
6
T→0_V
8
V→2_V
7
V→2_
9
V→2V_
2
2
2
V
0
2
10
T→0V_
V
1-47
Automate fini déterministe
0
S→_T$
1
S→T_$
<accept>
S→T$_
4
T→0T_02
T→0_T0
5
T→0T0_
3
T→1_
0
0
T
$
11
2,6
T→0_T0T→0_V 7,8
V→2_V→2_V
9
V→2V_
2
2V
0
0
T
10
T→0V_
V
• On peut toujours transformer un automate fini non-déterministe en un automate fini déterministe équivalent.• Chaque état correspond à un ensemble d'items.
1-48
Les conflits
• Il existe deux types de conflits possibles lors de la construction des tables à partir de l'automate:
1. Shift-Reduce
1. Reduce-Reduce
1-49
Conflits Shift-Reduce
• S → 0 S 0 | 0
– Configuration initiale: (S0, 000$)– Shift: (S0 0 S1, 00$)– Shift ou Reduce ?
• Bison choisit toujours le Shift dans ce cas. • Remarque: Le mot ne sera donc pas
accepté alors qu'il devrait l'être.
1-50
Conflits Reduce-Reduce
1. S → 0 S 0 2. S → T3. T → S 0 4. T → 1
– Configuration initiale: (S0, 010$)– Shift: (S0 0 S1, 10$)– Shift: (S0 0 S1 1 S2, 0$)– Reduce: (S0 0 S1 T S3, 0$)– Reduce: (S0 0 S1 S S3, 0$)– Shift: (S0 0 S1 S S3 0 S4, $)– Réduce: on utilise la règle 1 ou 3 ?
• Yacc choisit toujours la première des règles dans ce cas.• Remarque: La grammaire n'est pas ambiguë.
Copyright © 2007 Addison-Wesley. All rights reserved. 1-51
Résumé
• L'analyse syntaxique est une partie essentielle de L'implémentation d'un langage
• Un analyseur lexical traduit une séquence de caractère (le programme source) en une séquence de tokens (et de lexèmes)
• Un parseur récursif-descendant est un parseur LL implémenté en écrivant le code directement à partir de la grammaire du langage
• Un parseur LR est l'approche la plus commune pour implémenter les analyseurs syntaxiques ascendants; c'est approche utilisé par Yacc.