Cours Compilation
Cours Compilation
Introduction 2
À l’attention des lecteurs . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Contexte d’apparition . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Structure générale d’un compilateur . . . . . . . . . . . . . . . . . . . . 4
2 Analyse lexicale 28
2.1 Rôle de l’analyseur lexical . . . . . . . . . . . . . . . . . . . . . . 28
2.2 Table des symboles . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3 Spécification lexicale . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.1 Classes des unités lexicales . . . . . . . . . . . . . . . . . . 30
2.3.2 Expressions régulières notation étendue . . . . . . . . . . . 31
2.4 Reconnaissance des unités lexicales . . . . . . . . . . . . . . . . . 33
2.4.1 Quelque problèmes dans la reconnaissance des tokens . . . 33
2.4.2 Approche de reconnaissance . . . . . . . . . . . . . . . . . 34
2.4.3 Conventions . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.5 Implémentation d’analyseurs lexicaux . . . . . . . . . . . . . . . . 36
ii Contents
3 Analyse syntaxique 47
3.1 Rôle de l’analyseur syntaxique . . . . . . . . . . . . . . . . . . . . 47
3.2 Spécification syntaxique . . . . . . . . . . . . . . . . . . . . . . . 47
3.2.1 Grammaires hors contexte et dérivation vs réduction . . . . 49
3.2.2 Qualités et formes particulières des grammaires . . . . . . 50
3.3 Analyse syntaxique descendante . . . . . . . . . . . . . . . . . . . 55
3.3.1 Analyse par la descente récursive . . . . . . . . . . . . . . 55
3.3.2 Analyse LL(k) . . . . . . . . . . . . . . . . . . . . . . . . 59
3.4 Analyse syntaxique ascendante . . . . . . . . . . . . . . . . . . . . 66
3.4.1 Analyse LR(0) . . . . . . . . . . . . . . . . . . . . . . . . 69
3.4.2 Analyse SLR . . . . . . . . . . . . . . . . . . . . . . . . . 72
3.4.3 Analyse LR(1) canonique . . . . . . . . . . . . . . . . . . 75
3.4.4 Analyse LALR(1) . . . . . . . . . . . . . . . . . . . . . . 78
3.5 Générateurs d’analyseurs syntaxiques avec Bison . . . . . . . . . . 79
3.5.1 Partie déclaration C . . . . . . . . . . . . . . . . . . . . . . 81
3.5.2 Partie déclaration Bison . . . . . . . . . . . . . . . . . . . 81
3.5.3 Partie règles de la grammaire . . . . . . . . . . . . . . . . . 81
3.5.4 Partie code additionnel . . . . . . . . . . . . . . . . . . . . 82
3.6 Gestion des erreurs . . . . . . . . . . . . . . . . . . . . . . . . . . 83
3.6.1 Récupération en mode panique . . . . . . . . . . . . . . . . 85
3.6.2 Récupération au niveau du syntagme . . . . . . . . . . . . . 85
3.6.3 Production d’erreurs . . . . . . . . . . . . . . . . . . . . . 85
Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Travaux pratiques 90
Bibliography vi
Liste des figures
Contexte d’apparition
La révolution industrielle de la moitie du 20e siècle à fait naître les premiers
instruments et calculateurs triviaux. Ces machines sont caractérisées par leurs coût
et taille souvent prohibitifs mais aussi par des capacités de mémorisation et de calcul
simples. Un autre aspect qui compliqua leur exploitation est leur mode d’exploitation
qui reste soit manuel ou repose sur des langages machines (suite de bits) très difficile
à mener et à maintenir. La figure suivante montre un code machine allégé et codé en
héxadécimal pour un Pentium x86.
1 55 89 e5 53 83 ec 04 83 e4 f0 e8 31 00 00 00 89 c3 e8 2a 00
2 00 00 39 c3 74 10 8d b6 00 00 00 00 39 c3 7e 13 29 c3 39 c3
3 75 f6 89 1c 24 e8 6e 00 00 00 8b 5d fc c9 c3 29 d8 eb eb 90
Les gens ont rapidement constaté que les programmes en codes machines sont
difficiles à lire, écrire et maintenir car ils sont sujet à l’erreur. Le besoin de
l’introduction d’une notation plus gérable est devenu une nécessité. Ainsi les
langages d’assemblage sont créés, qui permettent de surpasser le code machine vers
des codes mnémoniques plus simples.
programme source
Analyseur lexical
Analyseur syntaxique
Analyseur sémantique
Optimiseur de code
programme cible
Chaque phase est a son tour décortiquée en tâches plus spécifiques. Nous présentons
brièvement dans cette section un aperçu général des différentes phases. Considérons
le code de calcul du PGCD de deux entiers montré dans la Figure 5, et expliquons les
différents traitements qu’il (ou ses transformés) va subir durant ce long processus.
1 int main () {
2 int i = getint () , j = getint () ;
3 while (i != j) {
4 if (i > j) i = i - j;
5 else j = j - i;
6 }
7 putint (i);
8 }
Analyse lexicale
La mission de l’analyse lexicale est de regrouper les caractères du texte source du
programme en unités ayant une signification du point de vue spécification lexicale
du langage. Ainsi la succession de i,n,t, et espace par exemple est perçue comme une
unité lexicale mots-clé "int". De cette manière, l’analyse lexicale transforme le code
source d’une suite de caractères en une suite d’unités lexicales qui constitue l’entrée
de la phase suivante. Il est du ressort de cette phase aussi l’insertion et le codage des
unités lexicales reconnues dans une structure centrale appelée la table de symboles,
la suppression des espaces, blancs et commentaires et en fin de signaler les erreurs
d’ordre lexical détectées (la mauvaise formation des unités lexicales).
Analyse syntaxique
À la réception des unités lexicales de la phase précédente, le rôle de l’analyse
syntaxique est de vérifier la bonne disposition des unités reçues pour former des
instructions valides du langage c-à-d conformes à sa définition syntaxique donnée
7
< if >
...
par une grammaire (collection de règles du genre A → α). Pour justifier la validité
syntaxique du code, cette phase crée un arbre dit syntaxique dont les nœuds internes
sont les opérations et les fils associés les arguments. En cas d’incohérence des
erreurs d’ordre syntaxiques seront émises. La figure ci-dessous montre l’arbre de
l’instruction while du code précédent.
Analyse sémantique
Ici l’objectif est de vérifier des aspects ayant trait à la signification du code. Cette
phase utilise l’arbre syntaxique fournit par la précédente et la table de symboles
pour l’annoter avec les attributs consignées pour effectuer des vérifications liées
au contexte comme : les déclarations, la compatibilité des types, le respect des
portés, du nombre/types des arguments dans les fonctions, etc. Souvent ces
aspects sont complexes à traiter avec des grammaires hors contextes comme dans
la phase précédente. On fait recours à des techniques (grammaires attribuées)
permettant d’opérer des actions sémantiques par des routines à invoquer dans des
endroits/moments appropriés. L’analyse sémantique transforme l’arbre syntaxique
en arbre abrégé et annoté ou en une forme intermédiaire pour une machine abstraite
et la transmet à la suite du processus de compilation. Les erreurs d’ordre sémantiques
sont signalées dans cette phase moyennant le module de gestion des erreurs.
8 Introduction
Optimisation
Dans la première opération d’optimisation (indépendante de la machine), le code
intermédiaire subi plusieurs transformations, qui préservent son logique, permettant
d’améliorer son efficacité. Le gain obtenu concerne divers aspects tel que : le temps
d’exécution du programme, sa taille, voire même les communications et l’énergie
qu’il dispense. Après la génération du code machine, ce dernier fera l’objet d’une
deuxième phase d’optimisation mais cette fois dépendant de l’architecture cible.
Génération de code
Cette phase produit le code objet en prenant en compte l’architecture et le jeux
d’instruction du processeur cible. C’est une traduction du code intermédiaire
optimisé vers une séquence d’instructions machine qui réalisent le même travail. Des
questions à résoudre sont par exemple : attribuer des adresses (ou un mécanisme
d’adressage) pour les objets manipulés, allouer judicieusement les registres de la
machine souvent en nombre limité, bien sélectionner/ordonner les instructions, etc.
C HAPTER 1
1.1 Fondements
La théorie des langages formels se fonde sur plusieurs autres théories
mathématiques. On fait usage ici notamment de la théorie des ensembles, de
la logique mathématique et de quelques aspects de l’algèbre et ses structures
élémentaires. Des notions de la théorie des graphes sont souvent exploitées
également dans ce cours. Le lecteur avide est invité à consulter les diapositifs du
cours dispensé en présentiel en deuxième année licence informatique ou d’exploiter
les références bibliographiques sur chaque volet sus-mentionné. Le livre [20] est un
bon survol de ces concepts.
Nous rappelons succinctement dans ce chapitre les base afférentes directement
aux langages formels. Plus particulièrement, nous nous focalisons sur les langages
réguliers. Pour plus d’exemples, de démonstrations et d’exercices veuillez consulter
le livre du groupe du professeur Jeff Ullman [16]
chaîne 2020.
w, x, y étant trois mots sur un alphabet Σ. On dit que :
Un préfixe, (resp. suffixe ou facteur) est dit propre lorsque il est différent du mot
vide ε et du mot w lui même
Pour un alphabet Σ l’ensemble Σk inclut tous les mots sur Σ de longueur k. À
tire d’exemple sur l’alphabet Σ = {a, b}, Σ2 = {aa, ab, ba, bb}. Par convention, pour
tout alphabet Σ, nous admettons que Σ0 = {ε}.
L’ensemble de tous les mots sur un alphabet Σ sera noté Σ∗ . Ainsi, nous avons :
[
Σ∗ = Σ0 ∪ Σ1 ∪ Σ2 ∪ . . . = Σk
k≥0
Sur Σ∗ est défini une opération dite de concaténation entre des mots. si u et
v sont deux mots de Σ la concaténation de u et de v donne le mot uv formé des
symboles de u suivis de ceux de v. Cette opération est associative et admet ε comme
élément neutre. Notez bien que cette opération n’est pas en général commutative.
Un langage sur un alphabet Σ est tout ensemble de mots sur cet alphabet. Ceci
dit, un langage L sur Σ est un sous ensemble de Σ∗ . Voici quelques remarques à bien
avaler sur les langages :
2. Il n’est pas exigé que les mots d’un langage sur un alphabet inclut tous les
symboles de cet alphabet
3. Tout langage d’un alphabet est aussi un langage sur son super-alphabet
Les langages étant des ensembles, les opérations ensemblistes usuelles (union,
intersection, complément, etc.) y sont définies de la même manière Nous insistons
ici en particulier sur les opérations de concaténation et d’itération.
La concaténation de deux langages M et N est le langage formé des mots
obtenus par concaténation des mots des deux langages en respectant l’ordre de
concaténation. Cette opération est associative mais, comme pour les mots, n’est
pas commutative. Elle admet le langage vide comme absorbant et le langage formé
seulement du mot vide comme élément neutre.
L’itération ou l’étoile d’un langage offre un moyen de formé des mots du même
langage par plusieurs succession de concaténation. En général on note :
L0 = {ε}
Ln = L.Ln−1
Il existe plusieurs propriétés et résultats sur les opérations sur les langage, consultez
la bibliographie pour plus de détail. Nous donnons ci-après des propriétés
remarquables de l’opération d’itération.
S i
L∗ = i≥0 L
L∗ = L∗ ∗
L∗ = L∗ L∗
L(ML)∗ = (LM)∗ L
(L ∪ M)∗ = (M ∗ L)∗ M ∗
0/ ∗ = {ε}
{ε}∗ = {ε}
L+ = LL∗ = L∗ L
1.3 Grammaires
Les grammaires sont un outil permettant de générer des mots en offrant un système
de réécriture sous la forme de règles dites de production. Elles permettent ainsi de
définir des langages. Une grammaire est spécifiée comme un quadruplet
G =< T, N, S, P > constituée d’un ensemble T de symboles terminaux entrant dans
la composition des mots appelé aussi alphabet, d’un ensemble N de symboles non
12 Chapter 1. Rappel sur les langages formels
+
G génère f ssi S ⇒ f
Ceci nous conduit au concept du langage généré par une grammaire qui est donc
l’ensemble de mots générés par cette grammaire. On écrit:
+
L(G) = {w ∈ T ∗ | S ⇒ w}
Durant son étude sur les langage et leur structures syntaxiques Noam Chomsky
aboutira à une classification des langages et des grammaires associées selon le degré
de complexité connue sous le terme hiérarchie de Chomsky [9]. Cette hiérarchie
inclut quatre classes de langages, les voici :
Langages contextuels
de type 1
Langages hors-contexte
de type 2
Langages réguliers
de type 3
• Forme une brique de base qui aide dans la synthèse des circuits numériques
Ci-après nous présentons de façon formelle les plus communs des automates finis.
Un automate est qualifié par déterministe (AFD) s’il existe au plus une transition
par état et par lettre de l’alphabet. Dans le cas contraire, il est dit non déterministe.
Notons qu’un automate est représenté soit en donnant sa table de transitions qui
élucide le lien entre états et symboles de l’alphabet ou sous la forme d’un graphe qui
montre explicitement cette fonction de transition.
• Q = {q0 , q1 , q2 }
• Σ = {a, b}
• F = {q1 }
a b a a,b
→ q0 q0 q1 b b
∗ q1 q0 q2 q0 q1 q2
q2 q2 q2 a
• s0 = q0
• sn ∈ F
Dans l’exemple précédent, l’automate accepte bien le mot abab mais rejette abb.
La fonction de transitions δ est aisément étendue au mots de la manière suivante :
δ̂ : Q × Σ∗ → Q
1.4. Automates finis 17
δ̂ (q, ε) = q
δ̂ (q, aw) = δ̂ (δ (q, a), w)
L’ensemble des mots acceptés par un automate forme le langage accepté par cet
automate. Formellement, on écrit :
L(A) = {w ∈ Σ∗ | δ̂ (q0 , w) ∈ F}
Un automate est complet si pour tout état il existe exactement une transition pour
chaque symbole de l’alphabet Σ. Deux automates sont équivalents s’ils acceptent le
même langage.
∆ : Q × Σ → 2Q
Exemple 2. Soit l’AFN définit ainsi A =< {q0 , q1 , q2 }, {0, 1}, ∆, q0 , F > avec la
fonction ∆ et l’ensemble F schématisés dans la table et la figure ci-dessous :
0 1 0,1
→ q0 {q0 , q1 } {q0 }
0 1
q1 0/ {q2 }
q0 q1 q2
∗ q2 0/ 0/
Le langage d’un AFN est l’ensemble des mots dont l’exécution par l’automate
conduit à un ensemble contenant au moins un état final. i.e :
ˆ 0 , w) ∩ F 6= 0}
L(A) = {w ∈ Σ∗ | ∆(q /
Cette dernière définition n’est correcte qu’en étendant la fonction de transitions aux
mots de la manière suivante :
ˆ ε) = {q} ∀q ∈ Q cas de base
∆(q,
ˆ u) = {p1 , . . . pk } Pour pi ∈ Q, a ∈ Σ et
∆(q,
Sk
ˆ
i=1 ∆(pi , a) = {r} . . . rm } alors :
ˆ w) = {r1 , . . . rm } et w = ua
∆(q,
• QD = 2QN c-à-d les parties de QN sans les états inaccessibles (ne possédant
pas un chemin de l’état initial)
• q1 = {q0 }
S
• δ (S,a) = p∈S ∆(p, a) avec S ∈ QN
• FD = {S ∈ QN | S ∩ FN 6= 0}
/
0
1
0 1
0 1
→ {q0 } {q0 , q1 } {q0 }
{q0 , q1 } {q0 , q1 } {q0 , q2 } {q0 } {q0 , q1 } {q0 , q2 }
0
∗ {q0 , q2 } {q0 , q1 } {q0 }
Figure 1.4: AFD obtenu par la construction des sous-ensemble appliquée à l’AFN
précédent
1 ε
q r s
0 ε
ε-clôture
l’ε-clôture d’un état p, noté Cε (p), est l’ensemble de tous les états atteignables à
partir de p par zéro, un ou plusieurs ε-transitions. Formellement de façon récursive
la fermeture en ε est spécifiée comme suit :
p ∈ Cε (p)
si q ∈ Cε (p) & q0 ∈ ∆ε (q, ε) Alors q0 ∈ Cε (p)
S
Cε (E) = s∈E Cε (s)
Dans l’ε-AFN précédent, nous avons Cε (q) = {q} ; et Cε (r) = Cε (s) = {r, s}
• Refaire ces transitions (en gardant les mêmes étiquettes) cette fois émanant de
p
4. (r) est une expressions régulière et L((r)) = L(r). cette dernière clause
n’est pas un opérateur, mais permet de définir des expressions régulières
parenthésées.
Dans les expressions complexes, il faut tenir compte de l’ordre de priorité des
opérateurs réguliers. Voici cet ordre du plus fort vers le plus faible :
1. () : les parenthèses
2. * : l’étoile de Kleene
3. . : la concaténation
4. + : l’union
22 Chapter 1. Rappel sur les langages formels
E +F = F +E
E +E = E
(E + F)G = EG + EF
E(F + G) = EF + EG
E + 0/ = 0/ + E = 0/
E 0/ = 0E
/ = 0/
Eε = εE = ε
0/ ∗ = ε∗ = ε
E(FE)∗ = (EF)∗ E
(E + F)∗ = (E ∗ F)∗ E ∗ = (F ∗ E)∗ F
b a
a
0 1
Figure 1.6: Un AFD qui reconnaît le langage dénoté par l’expression (a + b)∗ a
Par exemple, le langage sur {a, b} des mots se terminant par un a est reconnaissable
(reconnu par l’automate de la Figure 1.6) et aussi régulier (dénoté par l’expression
régulière suivante : (a + b)∗ a).
S.C. Kleene [18] introduit le théorème d’équivalence entre automates finis et
expressions régulières qui porte son nom. Il prouva que la classe des langages
reconnaissables est égale à la classe des langages réguliers. La preuve donne
une méthode pour passer d’un automate à une expressions régulière équivalente et
inversement. Cette preuve introduit pour le premier sens une construction dite de
Thompson. Le deuxième sens est possible via le lemme d’Arden. Afin d’alléger ce
support ne nous détaillons pas ces constructions ici. Pour de plus ample information,
veuillez vous référez aux slides du cours et les références bibliographiques.
En résumé les modèles présentés dans ce chapitre sont équivalent : tout ce que
peut être reconnu par un AFD l’est aussi par : un AFN, ε-AFN, une expression
régulière et évidement une grammaire régulière.
Théorème 1.6.1. Les automates finis, les expressions régulières et les grammaires
linéaires spécifient la même classe de langages (les langages réguliers)
• Union
• Intersection
• Concaténation
24 Chapter 1. Rappel sur les langages formels
• Itération
• Complémentation
• Différence
• Transposé
• Homomorphisme
Enfin, notons qu’il existe des langages qui ne sont pas réguliers. Le lemme de
pompage est un outil qui valide la régularité d’un langage, veuillez vous référer au
référence bibliographiques pur plus de détail [16].
Dans la partie analyse lexicale nous utiliserons les langages réguliers, par contre
nous nous recourons aux langages hors contextes dans la partie analyse syntaxique.
Les langages contextuels trouvent une application dans l’analyse sémantique.
Exercices du chapitre 1
L3 Info. Série de TD N
RAPPEL
Exercice 1
L = {ab, aa, baa}un langage. Lesquels des mots suivants sont dans L : ∗
, , ,
abaabaaabaa aaaabaaaa baaaaabaaaab baaaaabaa
Exercice 2
Soit l'AFN A suivant :
a, b a, b a, b
a a b
start 0 1 2 3
Exercice 4
Concevoir un AFN ayant :
Trois états pour {ab, abc} ∗
Exercice 5
Donner un AFD pour chacun des langages sur {0, 1} suivants :
Tout 00 est suivi immédiatement par un 1 (01, 0010,0010011001 sont dans ce
langage et 00100 et 0001 ne le sont pas)
Les mots où le symbole le plus à gauche est diérent de celui le plus à droite.
Le langage dénoté par l'ER : aa + aba b ∗ ∗ ∗
Exercice 6
Donner les grammaires sur {a, b} qui génèrent les langages suivants :
Les mots avec exactement un seul a L = L L
Les mots ayant au moins un a L =L ∪L
3 1 2
L = {a b | n ≥ 0}
1 6
n 2n
2
1/2
Série de TD
Exercice 7
Soit l'expression régulière suivante : (a bc + aacb )
∗ + + +
(a) Donner une grammaire régulière à droite qui engendre le même langage
(b) Déduire l'AFD équivalent
(c) Analyser les chaînes aabcaacb et bcacb
Exercice 8
Utiliser la construction de Thompson pour concevoir un AFN correspondant à l'ex-
pression : what|who|why, puis donner l'équivalent déterministe et minimal.
Exercice 9
Donner une grammaire régulière à gauche qui engendre le langage formé de 0 et de
1 contenant au moins une séquence 010 ou une séquence 000, puis déduire l'AFD
équivalent et analyser les chaînes 0110101 et 01101100.
Exercice 10
Considérons le langage sur {a, b} formé de mots n'ayant pas deux caractères consé-
cutifs identiques.
(a) Ce langage est-t-il régulier? justier
(b) Trouver une expression rationnelle qui le dénote
Exercice 11
Décrire les langages dénotés par les expressions rationnelles suivantes :
1. a(a + b) a
∗
3. (a + b) a(a + b)(a + b)
∗
2. ((ε + a)b )
∗ ∗
4. a ba ba ba
∗ ∗ ∗ ∗
Exercice 12
Minimiser l'automate ci-dessous déni par sa table de transitions (0 est l'état initial
et 2 l'unique état nal). Donner l'ER du langage reconnu par cet automate.
a b
→0 1 5
1 6 2
←2 0 2
3 2 6
4 7 5
5 2 6
6 6 4
7 6 2
2/2
C HAPTER 2
Analyse lexicale
Tokens
(chaîne,classe)
Texte source Vers la suite
Analyseur lexical Analyseur syntaxique
de la compilation
Age := 25; Token suivant ?
("Age", ID), (":=",OP), ("25",NB), (";", SEP), ...
Figure 2.3: Le code précédent vu par l’analyseur lexical comme un flot de caractères
Une implémentation d’un analyseur lexical devrait donc prendre en charge les
tâches ci-dessous :
2. Interagir avec la table de symboles pour la gestion des unités lexicales (codage,
insertion, recherche et mise à jour éventuelle dans la suite du processus de
compilation)
4. Opérateurs : +,-,*,/,<,<=,>,>=,==,!=,&&,...
3. Nombres : les chaînes non vides de chiffres pour les entiers, et un motif plus
élaborés pour les réels (voir plus loin)
S i
4. (0 + 1)∗ = i>=0 (0 + 1) = {0, 1, 00, 01, 10, 11, 000, . . .} Toutes les chaînes de
0 ou 1
5. (0(0 + 1))∗ = {ε, 00, 01, 0000, 0001, 0100, 0101, . . .} = {ω ∈ {0, 1}∗ | |ω| =
2k, k ∈ N et 1 en positions paires}
3. (1 + 0) ∗ 1(1 + 0)∗
4. (0 + 1) ∗ (0 + 1)(0 + 1)∗
Depuis les trois opérations régulières de Kleene [18], plusieurs autres extensions
et implémentations ont été proposées afin de simplifier et améliorer l’usage des
expressions régulières. Voici, ci-dessous, les notations les plus fréquentes [4, 21].
Pour une exploration des expression régulières dans des langages variés tel que :
Java, PHP, Python, etc., le lecteur est invité à consulter le livre [30].
Exercice
[a − zA − Z][a − zA − Z0 − 9]∗
2.4. Reconnaissance des unités lexicales 33
programmation. Le but est de cerner les problèmes qui peuvent compliqués la tâche
des scanners [6].
1. Fortran
Les blancs sont non significatifs. Il en résulte que par exemple l’identificateur
val1 et v al 1 sont identiques. Par ailleurs, l’analyseur lexical ne peut
distinguer les deux instructions suivantes qu’après avoir lu le (. ou la ,).
• DO 5 I = 1,25
• DO 5 I = 1.25
2. PL/I
Les mots clés dans cet ancien langage ne sont pas réservés. Ceci dit, un
programmeur peut coder :
if else then then = else; else else = then
3. C++ :
Des conflit peuvent surgir avec l’usage des templates imbriqués et l’opérateurs
de flux ».
4. Python :
Les blocs sont définis par indentations, ce qui exige de mémoriser les
augmentations et retraits de celles-ci.
1. Écrire les expressions régulières pour les lexèmes de chaque classe (mots clés
(Rkw ), identificateurs (Rid ), nombres (Rnb ),...)
5. Supprimer a1 . . . ai et recommencer à 3.
2.4.3 Conventions
Il est aisé de constater des ambiguïtés dans l’approche précédente. Voici quelque
une et les solutions adoptées.
implémenté soit itérativement ou par des procédures (ou fonctions selon le langage
utilisé) mutuellement récursives associées aux différents états de l’automate (lente
dû à la récursion entre appels de modules). Un pseudo-code est montré dans
l’Algorithme 2.
Cette construction [31] suit la définition récursive des expressions régulières et donne
pour chacun des six cas l’automate associé. Les automates des cas de base sont
schématisés dans la Figure 2.5, et ceux des cas d’induction dans la figure 2.6.
La Figure 2.7, quant à elle, montre l’automate obtenu après application de la
2.5.2.2 Dérivées
La dérivée (dûe à Janusz A. Brzozowski) [8, 25] d’une expression régulière E sur un
alphabet Σ par rapport à un symbole a ∈ Σ, notée a−1 (E) est définie comme suit :
a−1 (φ ) = a−1 (ε) = a−1 (b) = φ pour b 6= a
a−1 (a) = ε
a−1 (E + F) = a−1 (E) + a−1 (F)
a−1 (EF) = a−1 (E)(F) + ε(E)a−1 (F)
−1
a (E∗) = a−1 (E)(E∗)
Avec ε(E) est une fonction qui aide dans le calcul des dérivées. Elle exprime
l’appartenance ou non du mot vide à E.
ε si ε ∈ E
ε(E) =
φ sinon
2.5. Implémentation d’analyseurs lexicaux 39
1 E 3
E|F ε ε
0 5
ε ε
2 F 4
EF
ε
0 E 1 3 F 5
ε
E∗
ε ε
0 1 E 3 5
q2 a q3
ε ε
q0 ε q1 ε q4 ε q5 c q8
ε b ε
q6 q7
Les états de l’automate résultat sont les différentes dérivées. Une transition,
par le symbole a est crée entre un état correspondant à l’expression s et celui
correspondant à l’expression dérivée a−1 (s). Un état est final si l’expression qu’il
représente inclut le mot vide ε. Le lecteur intéressé pourra trouver les détails et
les preuves (correction, terminaison) de cette construction dans [8]. Un exemple de
cette construction pour l’expression (a + b) ∗ bab est montré dans la Figure 2.8
(a + b) ∗ bab + ab
a b b
(a + b) ∗ bab a (a + b) ∗ bab + ab + ε
a
a
(a + b) ∗ bab + b
Flex [21], dont le principe est exhibé par la Figure 2.9, génère un code en C (dans
le fichier lex.yy.c par défaut où est définie la routine yylex) à partir d’un fichier texte
de spécification lexicale (souvent ayant l ou lex comme extension). Le code produit
est à compiler pour obtenir l’executable de l’analyseur. Le fichier d’entrée de flex
est composé des parties ci-dessous comme l’illustre l’Algorithme 4.
Elles associent des noms à des expressions qui permettent en suite de simplifier
l’écriture du code. Elles ont la forme suivante :
nom exp
Voici des exemples :
lettre [a-zA-Z]
chiffre [0-9]
Une expression précédemment définie est ensuite utilisée dans la partie suivante en
l’entourant entre une paire d’accolades {}.
C’est la partie principale d’un code flex. Elle définie des expressions régulières
(partie gauche) et les actions associées ( partie droite en C) à effectuer dans le cas de
leur reconnaissance selon la forme ci-dessous.
1 exp_1 { action_1 }
2 exp_2 { action_2 }
3 ...
• yyin et yyout fichier d’entrée/de sortie de flex (stdin et stdout par default).
2.5. Implémentation d’analyseurs lexicaux 43
1 %{
2 /* pour la fonction atof () */
3 # include < math .h >
4 %}
5 NB [0 -9]
6 ID [a -z ][a -z0 -9]*
7 %%
8 { NB }+ { printf ( " Un Entier : %s (% d)\n", yytext , atoi ( yytext ) ) ;}
9 { NB }+"."{ NB }* { printf (" Un flottant : %s (% g)\n", yytext , atof (
yytext )) ;}
10 if | then | begin | end | procedure | function { printf ( " Mot clef : %s\n",
yytext ) ;}
11 { ID } printf ( " Un identificateur : %s\n", yytext );
12 "+"|" -"|"*"|"/" printf ( " Un Operateur : %s\n", yytext );
13 "{"[\^{ $ \; $ }}\ n ]*"}" /* ignorer les commantaires sur une ligne */
14 [ \t\n ]+ /* ignorer les espaces et les blanc */
15 . printf ( " Caractere invalide : %s\n", yytext );
16 %%
17 main ( argc , argv )
18 int argc ;
19 char ** argv ;
20 {
21 if ( argc > 0 )
22 yyin = fopen ( argv [0] , "r" );
23 else
24 yyin = stdin ;
25 yylex () ;
26 }
On peut faire plusieurs tâche une fois une chaîne suivant un motif donné a été
reconnue. Cela permet d’exploiter Flex pour effectuer non seulement de l’analyse
lexicale, mais aussi des actions variées telles que : recopier, supprimer, convertir,
etc.
Exercices du chapitre 2
Série de TD
ANALYSE LEXICALE
Exercice 1
Soit la portion du code source suivante :
x = 0 ;\n\twhile (x < 10) { \n\tx++;\n }
Déterminer le nombre d'unités lexicales pour chaque classe de tokens (Mots-clés,
Identicateurs, Nombres, Opérateurs, Blancs, Sep, etc.)
Exercice 2
Soit le code C ci-dessous
++
float limitedSquare(float x) {
/* return x-squared, but never more than 100 */
return (x <= -10.0 || x >= 10.0)? 100 : x*x; }
(a) Diviser ce code à ses tokens appropriés
(b) À quels lexèmes doit-t-on associer des valeurs? Quelles sont ces valeurs?
Exercice 3
Proposer des spécications de l'heure donner au format 12H exemple : "05 :14 PM".
Les minutes sont toujours des nombres à deux chires, mais l'heure peut être un
chire unique.
Exercice 4
Donner des expressions régulières pour dénoter :
(a) Une chaîne quotée en C (exemple : "compilation"), sachant qu'une chaîne quotée
ne peut s'étendre sur plusieurs lignes
(b) Un commentaire en C introduit par //
++
texte du commentaire
Exercice 5
Un commentaire en PASCAL est une suite de 80 caractères au plus délimitée par (*
et *) ne contenant pas la chaine *) à moins que celle-ci n'apparaisse dans une chaîne
bien quotée.
(a) Construire un automate déterministe qui accepte un commentaire en PASCAL
(b) Écrire un algorithme de reconnaissance d'un commentaire
Exercice 6
Écrire un analyseur lexical qui reconnaît les unités X, Y et Z suivantes. On supposera
que les unités du langage sont séparées par un ou plusieurs blancs.
(a) L'unité X est une suite de chires décimaux qui peut commencer par le caractère
'-'. Sa valeur est inférieur à 32768.
(b) L'unité Y commence par une lettre suivie par une suite de lettres, de chires et
de tirets. Elle ne se termine jamais par un tiret et ne comporte pas de tirets qui
se suivent. Sa longueur maximale est de 31 caractères.
1/2
L3 Info. Série de TD N
(c) L'unité Z est une suite de chires qui peut commencer par le caractère '-' et qui
comporte le point décimal. Le nombre de chires est inférieur ou égal à 9.
Exercice 7
En SQL, les mots clés et les identicateurs ne sont pas sensibles à la casse. Écrire un
programme Flex qui reconnait les mots clés SELECT, FROM, WHERE (avec n'im-
porte quelle combinaison de minuscules et de majuscules), ainsi que l'unité lexicale
ID.
Exercice 8
Considérons la chaîne abbbaacc. Quelle(s) spécication(s) lexicale(s) produi(sen)t la
tokenisation : ab/bb/a/aac :
b+ c∗ a(b|c∗) ab
ab∗ b+ b+ b+
ac∗ ab ac∗
ac∗
Exercice 9
Pour les entrées ci-après énumérées, donner la liste des tokens reconnus par un ana-
lyseur implémentant la spécication suivante :
a(ba)∗
b ∗ (ab)∗
abd
d+
1. dddabbabab
2. ababddababa
3. babad
4. ababdddd
Exercice 10
(a) Étant donné la spécication lexicale ci-dessous.
%%
aa printf("1");
b?a+b? printf("2");
b?a*b? printf("3");
. printf("4");
Indiquer pour les chaînes suivantes la sortie de l'analyseur et les tokens produits :
bbbabaa
aaabbbbbaabanalyse
(b) Donner un exemple d'une entrée pour laquelle cet analyseur produit la sortie
123, ou justier, le cas échéant, l'inexistence de tel exemple.
(c) Quelle sera pour les mêmes chaînes données en (1) la sortie de l'analyseur, si on
remplace seulement la partie expression régulière de la dernière règle par .+
2/2
C HAPTER 3
Analyse syntaxique
Figure 3.1: Interaction entre les différents modules de la partie analyse d’un
compilateur
Exemple
• L = {(n )n | n ≥ 0} langage des expressions bien parenthésées. (), (()), tel que
dans ((1 + 2) ∗ 3) . . .
Pour que l’analyseur syntaxique puisse détecter les suites valides d’unités lexicales
de celles invalides, il a besoin de :
E E
E + E E * E
id(a) E * E E + E id(c)
Exemple 8.
E → E + E | E ∗ E | id et la chaîne : w = a + b ∗ c
3.2.2.1 Ambiguité
Pour une grammaire G, s’il existe un mot w de L(G) ayant plusieurs arbres
syntaxiques, on dit que G est ambiguë. Nous pouvons rencontré l’ambiguïté dans
plusieurs cas. Voici deux exemples typiques de grammaires ambiguës indispensables
dans tous les langages de programmation.
E → E + E | E ∗ E | (E) | id
if C then I
y < 0 a := 1 a := 0
if C then I else I
x > 10 if C then I a := 0
y < 0 a := 1
Figure 3.3: Deux arbres syntaxiques de la même chaîne dans la grammaire des
instructions conditionnelles
E → T +E | T T → id ∗ T | id | (E) ∗ T | (E)
Dans les langages de programmation les productions sont souvent définies en terme
d’elles mêmes. par exemple la production utilisée dans les déclarations d’une liste
de variables :
L → v | L, v
Une grammaire est dite récursive à gauche (RG) de façon directe si elle contient une
règle de la forme :
A → Aα | β A ∈ N; α, β ∈ (T ∪ N)∗
Parfois la RG n’est pas explicite ou apparente. Une grammaire est dite récursive
gauche de façon indirecte s’il existe une règle de la forme :
A → Bα | β A, B ∈ N B →+ Aγ | Γ α, β , γ, Γ ∈ (T ∪ N)∗
A → β A0
A → Aα | β ⇔
A0 → αA0 | ε
RGI: Faire des substitutions de manière à faire apparaître une RGD puis appliquer
les règles:
S → Ac | b
S → Ac | b
S → Ac | b
S → Ac | b
A → Bd | a ⇒ A → Acd | dd | a ⇒ A → aA0 | ddA0
A → Acd | dd | a
B → Ac | d
B → Ac | d
A0 → cdA0 | ε
3.2.2.3 Factorisation
L’analyseur syntaxique lit de gauche à droite la liste des unités lexicales fournies par
l’analyseur lexical. Il est souhaitable qu’il puisse décider à la rencontre d’une unité
lexicale quelle production doit utiliser. Ceci est impossible si les membres droits des
productions ont les mêmes préfixes.
L’idée de la factorisation est de réécrire la grammaire de sorte à différer
l’indéterminisme jusqu’à avoir lu suffisamment d’unités lexicales de l’entrée pour
pouvoir faire le bon choix.
Exemple 11.
I → if C then I else I
| if C then I
Ici. On préfère réécrire la grammaire :
54 Chapter 3. Analyse syntaxique
I → if C then I T
T → else I | ε
En général,
A → aB | α | β
A → aX | aY | aZ | α | β ⇒
B → X | Y | Z
Une grammaire est ε-libre si aucune production ne contient ε sauf l’axiome, qui ne
doit pas apparaître en partie droite d’aucune production.
S→a|A
S→a|ε
G = A → AB | ε ⇔ A → AB | B
B → aAbB | b
B → aAbB | abB | b
Les production vides posent souvent des difficultés dans l’analyse syntaxique; les
isoler est en général un remède aux difficultés qu’elles génèrent.
3.3. Analyse syntaxique descendante 55
Dans une grammaire sous cette forme normale, toute les règles sont de la forme
A → BC A, B,C ∈ N
A→a a∈T
S → ε
Les arbres de dérivation des grammaires sous la forme normale de Chomsky sont
binaires ce qui peut offrir une souplesse dans l’analyse.
Une grammaire est sous forme normale de Greibach si elle est ε-libre et toutes les
règles sont de la forme :
A → aα A ∈ N, α ∈ N∗ , a ∈ T ou S → ε
Remarques
• Factoriser
Table 3.2: Exemple non concluant d’une analyse par descente récursive d’une chaîne
apartenant au langage de la grammaire
Pile des appels chaîne
Z bcaa#
ZS bcaa#
ZSS caa#
ZSSAA aa#
ZSSAAS a#
ZSSAASASA #
ZSSAASAS #
ZSSAASA #
ZSSAASA #
ZSSAAS #
ZSSAA #
ZSSA # ⇒ erreur ? pourtant la chaîne appartient au langage
Fonctionnement
Nous disposons de la chaîne d’entrée terminée par # et une table d’analyse à deux
dimensions. Initialement, la pile contient les symboles de la grammaire avec # au
fond au dessous de l’axiome. Les lignes de la table pour les non terminaux, et les
colonnes représentent les terminaux avec #. La case à la ligne de N et la colonne de
60 Chapter 3. Analyse syntaxique
Pile
b b a ... c a #
Chaîne à analyser
3. Si la lecture est terminée (terme courant est #), et le sommet est aussi #
l’analyse est réussie et terminée.
DÉBUT
Définition 6.
Pour calculer les ensembles DEB d’une chaîne X, appliquer les règles ci-après
récursivement jusqu’à stabilité des ensembles.
Il est aisé d’étendre cet ensemble à un préfixe de longueur au plus k (on dit un
k-préfixe). Ainsi :
SUIVANT
De même, l’ensemble SUIV est la collection des terminaux qui peuvent suivre
(apparaître immédiatement à droite) un symbole non terminal.
Définition 7.
Suivre les règles suivantes pour calculer les ensembles SUIVANT de manière
récursive jusqu’à stabilité des ensembles SUIVANT:
+
SUIVk (X) = {w ∈ T∗ | S ⇒ β Xγ et w ∈ DEBk (γ#)}
Dans les exemples suivants, nous calculons les ensembles DEB et SUIV.
Exemple 16.
S → d | XY S
G4 : Y → c | ε
X → a | Y
3.3. Analyse syntaxique descendante 63
DEB SUIV
S a,c,d #
X a,c,ε a,c,d
Y c,ε a,c,d
Corollaire 3.3.3. Une grammaire est LL(1) si elle est non récursive à gauche et si
pour toute production A → α1 | . . . | αn , nous avons pour tout i 6= j :
• DEB(αi ) ∩ DEB(α j ) 6= 0/
• Si αi →∗ ε alors α j 6→∗ ε
Pour l’élaboration de la table d’analyse LL, suivre le pseudo algorithme suivant. Les
entrées vides de la table correspondent à des erreurs. La Table 3.3 montre celle
associée à la grammaire G7 de l’Exemple 18.
La table d’analyse étant multi-définie (case T [R, e]), cette grammaire n’est pas
LL.
méthodes dites LR(k) [19] (pour Left to right scanning performing the Reverse of the
right most derivations using k symbols of lookahead) réalisent l’inverse d’une suite
de dérivations la plus à droite et effectuent deux actions primitives : Décaler (empiler
un symbole dans la pile) ou Réduire (remplacer un membre droit d’une production
présent au sommet de la pile par le membre gauche correspondant) (Shift/reduce in
english). Elles exploitent donc une pile et un automate déterministe (l’ensemble est
en fait un automate à pile) dans la recherche de membres droits candidats (sous-
chaînes susceptibles à conduire à une suite valide de réductions). Ces chaînes
candidates sont appelées manches ou handles. l’entier k désigne le nombre de
caractères de prédiction à utiliser pour effectuer la bonne action. Ce nombre est
rarement supérieur à 1, en raison de la complexité impliquée avec l’augmentation de
la taille de cette information sur le fonctionnement de l’analyseur. Commençons par
décrire la base de ces méthodes en ne regardant aucun symbole de prédiction, c-à-d
avec k nul, après avoir examiné un exemple.
Exemple introductif
Avant d’aller en profondeur, donnons un exemple illustratif de cette famille
d’approches et le genre de décisions qu’elles sont confrontées à prendre.
Exemple 26.
Z→E
G13 : E → E + T | T
T → T ∗ i | i
Définition 9 (Préfixe viable). Un préfixe viable est un préfixe d’une chaîne résultat
d’une dérivation la plus à droite qui n’excède pas (ne va pas au delà) le handle de
cette chaîne.
Définition 10 (Item). Un item pour une grammaire G est une production dont le
membre droit est marqué par un point. Si A → αβ ∈ P alors [A → α.β ] est un item.
Dans cette notation les crochets servent à distinguer les items et la règle
de production associée. Généralement à une règle de production correspondent
plusieurs items. Par exemple, quatre items sont associés à la production : E → E − T
qui sont : [E → .E − T ], [E → E. − T ], [E → E − .T ], [E → E − T.]. La production
A → ε quant à elle donne un seul item : [A → .]. Un ensemble d’items est qualifié
de final (terminal) s’il contient un item dans la marque termine le membre droit de
l’item (un point à la fin).
L’intuition derrière un item est simple, à titre d’exemple pour l’item [E → E.−T ]
: l’analyseur vient juste de reconnaître une chaîne dérivable de E et espère rencontrer
le symbole − suivi d’une chaîne dérivable de T .
Afin de construire notre AFD, l’objectif est d’associer items et préfixes viables.
Ainsi [A → α.β ] est valide pour un préfixe viable φ γ si et seulement si il existe
+
une dérivation la plus à droite S ⇒ φ Aw ⇒ φ αβ w où w est un mot terminal [13].
70 Chapter 3. Analyse syntaxique
Nous donnons une construction d’un AFD à partir des règles de production de
la grammaire qui reconnaît les préfixes viables. Les items seront regroupés en
ensembles qui forment les états de cet AFD. Définissons les opérations suivantes
pour achever cette construction : fermeture qui donne les états de l’automate et
successeur qui en spécifie les transitions entre ces états.
La fermeture d’un ensemble d’items I est l’ensemble d’items C(I) construit par
application de l’algorithme suivant :
Successeur δ
Lemme 3.4.1. Une grammaire G est LR(0) si l’automate LR(0) associé ne contient
aucun état ambiguë
La grammaire G14 ci-dessous est LR(0), son automate est montré dans la Figure 3.8:
G14 : S → aSb | c
72 Chapter 3. Analyse syntaxique
2 4
E → T. ∗
T → T ∗ .i
E → T. ∗ i
i
T
7
0
Z → .E T → T ∗ i. ∗
E → .E + T
3
i
E → .T T → i.
T → .T ∗ i
T → .i
i
E
1 5
6
E → E + .T T
Z → E. E → E + T.
E → .T ∗ i
E → E. + T + E → T. ∗ i
E → .i
SLR( for Simple LR) [12] est une amélioration légère sur la méthode précédente
dans les cas de conflits décaler/réduire. L’heuristique stipule que devant un état
ambiguë à cause d’un conflit décaler/réduire il faut choisir de réduire si le caractère
de prédiction fait partie des suivants du non terminal de l’item final, sinon il faut
opter pour le décalage. Si malgré cette astuce le conflit persiste, la grammaire est
donc non SLR et le recours à d’autre méthodes plus puissante s’impose alors.
3.4. Analyse syntaxique ascendante 73
S → a.Sb S
S → .aSb S → aS.b
S → .c c
S → c. b
a c
S → aSb.
Z → .S
S → .aSb Z → S.
S → .c S
Exemple 28. La grammaire G15 présente des conflits décaler/réduire dans deux
états.
G15 :→ aSb | ε
Comme Suiv(S) = {b, #}, l’ambiguïté est levée : On décale à la rencontre d’un
a et on réduit à la lecture de b ou # et donc G15 est SLR.
S → a.Sb S
S → .aSb S → aS.b
S → .
b
a
S → aSb.
Z → .S S
S → .aSb Z → S.
S → .
défini‘e.
Exemple 29. La grammaire ci-dessous n’est pas SLR. Son automate comprend un
état ambiguë ayant un conflit décaler/réduire. L’analyseur ne sait pas comment agir
à la lecture d’un b, car l’état a un item de réduction [A → c.] et un autre de décalage
[S → c.b]. L’heuristique SLR ne résout pas ce conflit étatnt donnée que les suivants
de A inclut b.
Z→S
G16 : S → A | cb
A → aAb | c
3.4. Analyse syntaxique ascendante 75
Pour doter les méthodes LR d’avantage de puissance, on étend l’ensemble des items
LR(0) par l’information de prévision (le caractère lookahead). Un item LR(1) inclut
désormais deux composants : L’item de la règle comme dans LR(0) et le symbole de
prévision.
Définition 11. Un item LR(1) est est un objet à deux composants : une production
marquée et un caractère de prévision [A → α.β , a] où a ∈ T ∪ {#}
S → c.b b
Z → S. S → cb.
A → c.
c
S
Z → .S
S → .A A
S → .cb S → A.
A → .aAb
A → .c
A b
A → a.Ab A → aA.b A → aAb.
I7
I8
A → a.A, # A
A → .aA, # A → aA., #
I9 b
A → .b, #
A → b., #
b I2
I1 I6
S → A.A, # A
Z → S., # A → .aA, # S → AA., #
A → .b, #
S A
I0
Z → .S, # I3
S → .AA, # b
A → b., a|b
A → .aA, a|b
A → .b, a|b
b
a
I5
I4
A → a.A, a|b A
a A → .aA, a|b A → aA., a|b
A → .b, a|b
Bison [21] est une version améliorer et libre du fameux générateur Yacc
d’Unix [17]. C’est un générateur d’analyseurs syntaxiques qui reçoit la spécification
syntaxique et produit le code source de l’analyseur en langage C. L’entrée de Bison
est la spécification syntaxique du langage c-à-d une grammaire à contexte libre
LALR en format BNF facile à lire par un programme. Bison compile les règles
et les déclarations dans le fichier d’entrée afin de produire la table d’analyse LALR
codée en langage C sous la forme d’instructions switch dans la routine yyparse().
Similairement à Flex, un fichier (d’extension .y par convention) d’entrée à Bison
inclut, comme illustre la Figure 3.12, quatre parties : une pour les déclarations
3.5. Générateurs d’analyseurs syntaxiques avec Bison 81
globales, une deuxième pour les déclarations propres à Bison, la partie règles de
production de la grammaire et en fin un code suplémentaire.
1 %{
2 Declaration C
3 %}
4 Declaration Bison
5 %%
6 Regles de la grammaire
7 %%
8 Code C additionnel
Pour assurer une sémantique au langage analysé, des actions sémantiques écrites
en langage C peuvent être insérées après chaque règle de production. Un exemple
serait d’additionner les valeurs associées aux symboles pour la première règle.
• L’ordre de précédence est déterminé par l’ordre d’apparition des tokens (les
premiers listés ont la priorité la plus faible). Si plusieurs tokens possèdent le
même ordre de précédence, il seront déclarés en même temps sur la même
ligne.
Ces types peuvent être utilisés avec les directives %token et %type afin
d’indiquer les types pour les symboles de la grammaire.
Voici comme résumé un exemple complet d’un code Bison pour la grammire des
expressions arithmétiques :
ANALYSE SYNTAXIQUE
Exercice 1
Quel est le nombre
de mots et d'arbres syntaxiques distincts générés par la grammaire
S → A1 | 1B
A → 10 | C | ε
ci-contre : G:
B → C1 | ε
C →0|1
Exercice 2
Ci-après une grammaire des expressions lisp simpliées :
lexp → atom | list
atom → number | identif ier
G1 :
list → (lexpseq)
lexpseq → lexpseq lexp | lexp
Exercice 4
Soit la grammaire : G1 : S → 0S1S | 2S | ε
(a) Écrire un analyseur syntaxique pour G1 en utilisant la descente récursive.
(b) Analyser la chaîne : 012
Exercice 5
S → aB | bA | c
Soit la grammaire G1 ci-contre. G1 : A → aS | bA
B →b
(a) Montrer que G1 est LL(1)
(b) Écrire un analyseur syntaxique pour G1 en utilisant la descente récursive.
(c) Analyser la chaîne : babbac
Exercice 6
S → a | b | (T )
Soit la grammaire : G1 :
T → T, S | S
1/3
L3 Info. Série de TD N
Exercice 8
On s'intéresse à la construction d'un analyseur
syntaxique pour la grammaire G ci-
S → N @N .id
après, où T = {., id, @}, N = {S, N } : G:
N → id | id.N
(a) Donner l'arbre de dérivation pour la chaîne [email protected]
(b) Peut-on analyser cette grammaire par une méthode descendante ? Pourquoi ? Si
votre réponse est négative, la transformer donc.
(c) La grammaire transformée obtenue dans la question précédente est-elle LL(1) ?
Justier.
(d) En étudiant le langage généré par G, donner une grammaire équivalente qui soit
LL(1), puis tracer sa table d'analyse LL.
(e) Analyser la chaîne introduite en (a).
Exercice 9
Soit la grammaire G ci-après, où T = {a, b}, N = {S, N } :
S → abN | ε
G2 :
N → Saa | b
2/3
L3 Info. Série de TD N
Exercice 11
On s'intéresse à la construction d'un analyseur syntaxique pour la grammaire G ci-
contre :
S → aAS | bA
G:
A → cA | d
(a) Trouver les ensembles DEB et SUIV des non terminaux de G
(b) Calculer la collection des items LR[0]. G est-elle SLR(1) ? Justier.
(c) Construire la table d'analyse SLR(1) pour G
(d) Simuler votre analyseur sur la chaîne : acdbd
Exercice 12
Soit la grammaire G ci-contre :
E → (L) | a
G:
A → EL | E
(a) Construire l'automate des items LR(0) et la table d'analyse SLR, puis analyser
((a)a(a a)).
(b) Construire l'automate des items LALR(1) puis la table correspondante en pro-
pageant les caractères de prévision via l'automate des items LR(0)
Exercice 13
Montrer que la grammaire suivante est LR(1) mais non LALR(1) :
S → aAd | bBd | aBe | bAe
G: A →c
B →c
Exercice 14
Soit la grammaire G suivante :
S → AS | b
G:
A → SA | a
3/3
Travaux pratiques
Mini projet 1 de Compilation
1 Objectif
Le but de ce TP est de réaliser la première phase de la compilation (analyse lexicale) pour un
mini-langage de programmation de deux manières différentes.
2 Spécification lexicale
Nous avons défini le mini-langage de programmation jad composé des éléments suivants :
• Les mots clés (KWD) insensibles à la casse : si, sino, is, tq, qt, rpt, jsq, lre, ecr, vrai,
faux
• Les entiers naturels (INT) non signés composés par des suites non vides de chiffres
• Les identificateurs (ID) : les suites de lettres ou de chiffres commençant par une lettre
• Séparateurs (SP) : ; , ( ) :
• Les blancs (BLC): les suites non vides de : espace, tabulation et fin de ligne
3 Travail à faire
1. Écrire un analyseur lexical ad-hoc (codé en dur) pour jad Vous êtes libre dans le choix
du langage de réalisation (C, Java, Python, etc.). L’analyseur lexical doit recevoir le code
source dans un fichier texte et fournir son résultat, après avoir retourner le token rencontré,
dans un autre fichier de sortie en respectant le format suivant :
Nous enrichissons maintenant notre mini-langage par les nouveaux tokens ci-après :
(a) Les flottants (FLT) : les nombre réels signés en notation scientifique
Page 1/3
(b) Les chaı̂nes de caractères (CHN) : elles sont incluses entre double quotes ”...”. Dans
une chaı̂ne une séquence ’\c’ dénote le caractère c avec les exceptions suivantes :
• \b backspace
• \t tab
• \n newline
• \f formfeed
Vous devez respecter les restrictions suivantes sur les chaı̂nes :
• Une chaı̂ne ne peut contenir le nul (le caractère \0) ni le EOF
• Une chaı̂ne ne peut dépasser les limites des fichiers
• Une chaı̂ne ne peut inclure de nouvelles lignes non escapées. Par exemple :
”Cette chaı̂ne \
est correcte ”
par contre la suivante est invalide :
”Cette chaı̂ne
est incorrecte ”
(c) les commentaires (CMT) au style du C : sur une seule ligne introduits par ”//” ou
ceux multi-lignes encadrés par les couples ”/*” et ”*/”. Cette dernière forme peut
être imbriquée.
2. Réécrire le même analyseur lexical en utilisant cette-fois l’outil flex (même entrée/sortie
en fichiers textes).
Page 2/3
5 Construction de l’analyseur
1. Pour les codages en dur utiliser votre IDE favori
2. Compiler la spécification Flex par : Flex -o scanner.c scanner.l : produira le fichier scan-
ner.c à partir de scanner.l
3. Compiler le tout par : gcc -o analyseur scanner.c -lfl : produira l’exécutable anal-
yseur à partir de l’ensemble des fichiers
4. Pour le codage des tokens vous pouvez soit les définir comme de simples entiers, ou utiliser
l’outil Bison. Dans ce dernier, on définit un token en le précédant de l’option %token.
Compiler le fichier bison en utilisant l’option -d pour produire la définition de vos tokens
en fichier entête .h à inclure dans votre fichier flex.
5. Invoquer votre analyseur par : ./analyseur : où vous pouvez taper votre code source en
jad ou en le passant à travers un fichier texte et invoquer l’analyseur par la commande
./analyseur < votre-fichier-source
Page 3/3
3eme
1 Objectif
Après avoir exploré les différentes techniques d’analyse syntaxique. On vous propose de réaliser
un petit analyseur syntaxique pour les expressions arithmétiques en utilisant les outils Bison
et Flex. le point de démarrage sera deux fichiers de spécification lexicale(Flex) et syntax-
ique(Bison) à enrichir graduellement par des constructions simples.
2 Bison
Bison est la version gnu (libre) du fameux générateur automatique d’analyseurs syntaxique yacc.
Ce dernier crée un analyseur LALR(1) extensible par des actions sémantiques et des règles de
désambiguïsation pour les grammaires non LALR(1). Le format du fichier de spécification est
semblable à celui de Flex à savoir : une partie déclaration, une deuxième de définition de règles
et une dernière pour le code supplémentaire.
(b) Les directives %left (resp. %right) permettent de définir une règle d’associativité gauche
(resp. droite), par exemple %left + : signifie que l’expression a+b+c sera évaluée : (a+b)+c.
Noter qu’un token déclaré par %left (resp. %right) n’a pas besoin d’être introduit par une
directive %token. %nonassoc : pour les opérateurs non associatifs.
(c) L’ordre de précédence est déterminé par l’ordre d’apparition des tokens(les premiers listés
ont la priorité la plus faible). Si plusieurs tokens possèdent le même ordre de précédence, il
seront déclarés en même temps sur la même ligne.
(d) On introduit la grammaire avec les actions sémantiques associées aux différentes produc-
tions. Ces dernières seront séparées sur différentes lignes par ’|’ comme dans la notation
BNF. Une ligne vide remplace l’alternative vide (ε)
(e) Si une règle de production a été reconnue l’action associée sera exécutée. Comme en Flex,
une action est un bloc de code en C. les $n désignent les numéros des symboles comme
ils apparaissent dans le MDP et $$ fait référence au MGP. Exemple : E −→ E + E {$$ =
$1 + $3 ; }
Page 1 sur 3
Compilation 3eme Année Info. (S5) TP N o 2
(f) La fonction main() de l’outil appelle yyparse() pour traiter l’entrée, qui appelle à son tour
la fonction yylex() afin de récupérer les tokens. Si une erreur est rencontrée, l’analyseur
appelle yyerror(m), où m est le message d’erreur devant être renvoyé.
(g) Bison maintient 2 piles séparées : une première pour l’analyse syntaxique qui contient
les symboles de la grammaire et une deuxième de valeurs contenant les attributs de ces
symboles.
(h) La communication entre Bison et Flex est assurée via la variable yyval, son type est le même
que celui des éléments de la pile des valeurs
3. Lier le tout par : gcc calc.c calc.tab.y -o calculateur : produira calculateur à partir de
l’ensemble des fichiers .c et.h
4. Invoquer votre analyseur par : ./calculateur : où vous pouvez taper vos expressions.
Page 2 sur 3
Compilation 3eme Année Info. (S5) TP N o 2
1. Permettre cette fois-ci des noms de variables quelconques dont la longueur n’excède
pas 5 caractères.
2. Ajouter la structure conditionnelle simple : Si (condition) Alors Action.
3 Exemples
> 1+1
> 2
> (2+5)
> 7
> −6−3
> −9
> x=y =12
> 12
> x+y
> 24
> x + 3 * ( y /2 −1)
> 27
> 1+2−
> syntax error
> 1+2:5
> caractere invalide syntaxe error
> Q
> Au r e v o i r !
Page 3 sur 3
Mini projet 3 : Parser pour jad
1 Objectif
Le but de ce TP est de réaliser l’analyseur syntaxique pour le mini-langage de programmation
jad de deux manières différentes.
• Les mots clés (KWD) insensibles à la casse : si, sinon, is, tq, qt, rpt, jsq, lre, ecr, vrai,
faux
• Les entiers naturels (INT) non signés composés par des suites non vides de chiffres
• Les identificateurs (ID) : les suites de lettres ou de chiffres commençant par une lettre
• Séparateurs (SP) : ; , ( ) :
• Les commentaires (CMT) au style du C : sur une seule ligne introduits par ”//” ou ceux
multi-lignes encadrés par les couples ”/*” et ”*/”. Cette dernière forme peut être imbriquée.
• Les blancs (BLC): les suites non vides de : espace, tabulation et fin de ligne
Page 1/3
3 Spécification syntaxique
La grammaire du langage jad est donnée ci-après : les symboles en gras sont des mots clés ou
des terminaux, les non terminaux sont entre les couples < et >
4 Travail à faire
1. Écrire l’analyseur syntaxique pour ce mini-langage par la descente récursive (Vous serez
amener à transformer certaines instructions !). Pour cette partie utiliser le langage de votre
choix.
2. Réécrire le même analyseur syntaxique en utilisant l’outil Bison. Dans ce cas, vous pouvez
ne pas altérer votre grammaire (Bison est un analyseur ascendant).
Page 2/3
5 Construction des l’analyseurs
1. Compiler la spécification Flex par : Flex -o scanner.c scanner.l : produira le fichier scan-
ner.c à partir de scanner.l
4. Invoquer votre analyseur par : ./analyseur : où vous pouvez taper vos codes sources
en jad Vous pouvez également introduire votre code dans un fichier texte et invoquer
l’analyseur par la commande ./analyseur < votre-fichier-source
Page 3/3
Bibliography
[1] (2020). Beazley, d. ply (python lex-yacc). https://www.dabeaz.com/ply/. (Cité
en pages 41 et 79.)
[2] (2020). Klein, g., rowe, s., and décamps, r., jflex : https://jflex.de/. (Cité en
page 41.)
[3] (2020). Paxson, v., estes, w., and millaway, j. flex : fast lexer ; main page :
https://www.gnu.org/software/flex/. (Cité en page 41.)
[4] Aho, A., Sethi, R., and Ullman, J. (1986). Compilers: Principles, techniques,
and tools. In Addison-Wesley series in computer science / World student series
edition. (Cité en pages 2, 4, 28, 32, 47, 51 et 55.)
[5] Aho, A. V., Lam, M. S., Sethi, R., and Ullman, J. D. (2007). Compilers:
Principles, Techniques, and Tools (2Nd Edition). Addison-Wesley Longman
Publishing Co., Inc., Boston, MA, USA. (Non cité.)
[7] Ananian C. S., Flannery F., e. a. (2020). Cup : Construction of useful parsers.
http://www2.cs.tum.edu/projects/cup/. (Cité en page 79.)
[9] Chomsky, N. (1956). Three models for the description of language. IRE Trans.
Inf. Theory, 2(3):113–124. (Cité en page 13.)
[14] F., D. and J., P. T. (1979). Efficient computation of lalr(1) look-ahead sets.
pages 176–187. In Johnson, S. C., editor, Proceedings of the 1979 SIGPLAN
Symposium on Compiler Construction, Denver, Colorado, USA. (Cité en
page 78.)
[15] Hanspeter Mössenböck, Markus Löberbauer, A. W. (2020). The compiler
generator coco/r. http://www.ssw.uni-linz.ac.at/coco/. (Cité en page 79.)
[16] Hopcroft, J. E., Motwani, R., and Ullman, J. D. (2007). Introduction to
automata theory, languages, and computation, 3rd Edition. Pearson international
edition. Addison-Wesley. (Cité en pages 9, 24, 36, 51, 73 et 85.)
[17] Johnson, S. C. (1979). Yacc : Yet another compiler compiler. Technical report.
(Cité en page 80.)
[18] Kleene, S. C. (1956). Representation of events in nerve nets and finite
automata. In Shannon, C. and McCarthy, J., editors, Automata Studies, pages
3–41. Princeton University Press, Princeton, NJ. (Cité en pages 23 et 32.)
[19] Knuth, D. E. (1965). On the translation of languages from left to right. Inf.
Control., 8(6):607–639. (Cité en pages 68 et 69.)
[20] Lehman, E. (2017). Mathematics for Computer Science. Samurai Media
Limited, London, GBR. (Cité en page 9.)
[21] Levine, J. R. (2009). flex and bison - Unix text processing tools. O’Reilly. (Cité
en pages 32, 41, 79 et 80.)
[22] Lewis, P. M. and Stearns, R. E. (1968). Syntax-directed transduction. J. ACM,
15(3):465–488. (Cité en page 59.)
[23] Lucas, P. (1961). The structure of formula-translators. ALGOL Bull.,
(Suppl.16):1–27. (Cité en page 55.)
[24] M. E. Lesk, E. S. (1975). Lex - a lexical analyzer generator. Technical report,
AT&T Bell Laboratories, Murray Hill, New Jersey 07974. (Cité en page 41.)
[25] Owens, S., Reppy, J. H., and Turon, A. (2009). Regular-expression derivatives
re-examined. J. Funct. Program., 19(2):173–190. (Cité en page 38.)
[26] Rabin, M. O. and Scott, D. S. (1959). Finite automata and their decision
problems. IBM J. Res. Dev., 3(2):114–125. (Cité en page 18.)
[27] Reps, T. W. (1998). "maximal-munch" tokenization in linear time. ACM Trans.
Program. Lang. Syst., 20(2):259–273. (Cité en page 35.)
[28] Rosenkrantz, D. J. and Stearns, R. E. (1970). Properties of deterministic top-
down grammars. Inf. Control., 17(3):226–256. (Cité en page 59.)
viii Bibliography
[32] Tremblay, J. and Sorenson, P. (1985). The theory and practice of compiler
writing. (Cité en page 2.)
[33] Viswanadha, S. and Sankar, S. (2020). Javacc, the most popular parser
generator for use with java applications: https://javacc.github.io/javacc/faq.html.
(Cité en page 79.)