Académique Documents
Professionnel Documents
Culture Documents
JFlex PDF
JFlex PDF
Cours de compilation
MASTER M1
Introduction à la compilation
Cours de M1
Langages
Un langage est un ensemble de caractères alpha-
numériques que l’on appelle phrase du langage
Les règles définissant la structure des phrases sont
définies par une grammaire
Exemple :
Une affectation en Java est de la forme
a=b+5;
Terminateur d’instruction
Symbole d’affectation
Cours de M1
Analyse lexicale
Parcourt le programme source de gauche à droite,
caractère par caractère
Ignore les caractères superflus (espaces, tab, RC,
commentaires)
Convertit les unités lexicales en tokens ({, id, =, ==,
abstract ...)
Envoie ces informations à l'analyse syntaxique
Exemple :
position = initiale + vitesse*60 ;
ID OPAFFACT ID PLUS ID MULT NB PTVIRG
Cours de M1
Analyse syntaxique
Vérifie si la séquence d'unités lexicales fournie par
l'analyse lexicale respecte les
« règles »syntaxiques du langage
Génération de code
Produit le code
Le code généré peut être :
Code intermédiaire : facile à produire et à traduire par la suite
Code machine translatable
Cours de M1
Analyse Sémantique
Contrôle si le programme est sémantiquement correct
(contrôle des types, portée des identificateurs,
correspondance entre paramètres formels et paramètres
effectifs)
Collecte des informations pour la production de code
(dans la table des symboles)
Cours de M1
Interpréteur
Les interpréteurs s’arrêtent à la phase de production
de code intermédiaire
L’exécution du programme source consiste alors en
une interprétation du code intermédiaire par un
programme appelé interpréteur
Souvent, il exécute un code intermédiaire (par
exemple le P-Code fabriqué par le Pascal UCSD)
Avantages : portabilité et exécution
immédiate
Désavantage : exécution plus lente qu’avec un
compilateur complet
Cours de M1
Culture : bytecode
Le compilateur ne produit pas de code pour un
processeur réel, mais pour un processeur d’une
machine virtuelle
Portabilité : pour une nouvelle architecture, il n’y a
pas besoin de modifier le compilateur, il suffit a priori
de porter le programme qui implémente la machine
virtuelle
JAVA : « Compile once, run everywhere »
Un peu exagéré car dans le cas de Java,
l’environnement ne se compose pas seulement d’un
processeur, mais aussi de nombreuses librairies
Cours de M1
Analyse lexicale
Code source
Analyse lexical
Suite de lexèmes
Cours de M1
Introduction
L’analyse lexicale se trouve au tout début de la chaîne
de compilation
Elle collabore avec l’analyse syntaxique pour analyser la
structure du langage
Elle transforme une suite de caractères en une suite de
mots, dit aussi lexèmes (tokens)
L’analyse lexicale utilise essentiellement des automates
pour reconnaître des expressions régulières
Cours de M1
Principe
Transforme une suite de caractère en une suite de
lexèmes
somme = total + 5.25 ;
L’analyseur lexical retournera :
IDENTIFICATEUR
OPPAFFECT
IDENTIFICATEUR
PLUS
NOMBREREEL
PTVIRG
Cours de M1
Tâches secondaires
L'analyse lexicale peut également effectuer un certain
nombre de tâches :
Élimination des caractères superflus (espaces, tabulations, RC,
commentaires, ...)
Compter le nombre de RC afin de repérer le numéro de la ligne si
une erreur est détectée
Conversion des lettres en majuscules (pour un langage comme
PASCAL par exemple)
Cours de M1
Enjeux
Les analyses lexicales et grammaticales ont un domaine
d’application bien plus large que la compilation (analyse
de requêtes…)
Ces deux analyses utilisent principalement les
automates et les expressions régulières (utilisées dans
de nombreux programmes unix, comme sed, grep,
…)
Cours de M1
Note
L’étude détaillée des automates et des grammaires
formelles pourrait constituer un cours à part entière
(cours très théorique au demeurant !!!)
Nous nous contenterons pour ce cours d’une description
minimale avec comme but :
se familiariser avec les expressions régulières
comprendre les principes de base du fonctionnement
des automates
Cours de M1
Quelques définitions
Un modèle est une « règle » qui décrit l'ensemble des
lexèmes pouvant représenter une unité lexicale
Unités lexicales
Quand différents lexèmes désignent la même unité lexicale,
un attribut est associé à l'unité lexicale
Exemple :
ID chaîne de caractères « position »
NB valeur : 60
OPREL différentes constantes numériques
PPQ, PPE, PGQ, PGE, DIF, EGA
Cours de M1
Langages formels
Ensemble fini appelé alphabet, dont les
éléments sont appelés caractères
Un mot (sur ) est une séquence de caractères
(sur )
le mot vide
uv la concaténation des mots u et v
ensemble des mots sur
Un langage sur est un sous ensemble L de
Si U et V sont deux langages sur
UV ensemble concaténation
U* (resp U+) ensemble des mots obtenus par la
concaténation arbitraire, éventuellement nul (resp.
Cours de M1
Exemples
1 est l’alphabet français et L1 l’ensemble des
mots du dictionnaire français avec toutes leurs
déclinaisons
2 est l’ensemble des caractères ASCII, et L2 est
l’ensemble des mots-clefs de JAVA l’ensemble des
symboles, l’ensemble des identificateurs…
Cours de M1
L'opérateur de Kleene *
si a L(a)={a} est le plus prioritaire, puis
L()={} la concaténation, puis
l'union
et L(e1|e2)=L(e1)UL(e2)
L(e1e2)=L(e1)L(e2)
L(e*)=U(L(e))i avec (L(e))0={}
={}UL(e)U(L(e))2U...
Cours de M1
Exemple
Soit ={a,b,c}
a|b définit le langage {a,b}
(a|b)(a|b) {aa,ab,ba,bb}
a* {,a,a2,a3,...}
(a|b)* {,a,b,aa,ab,ba,bb,..}
a*|b* {,a,a2,a3,...,b,b2,b3,...}
tous les mots formés de a ou b
a(b|c)* tous les mots commençant par a
suivi de b et de c
Cours de M1
Remarques
Deux E.R. Sont équivalentes si elles définissent le même
langage
Ex : (a|b)*=a*(a|b)*
On utilise parfois la notation e+ pour ee*
L(e+) = Ui=1(L(e))i
Pour des raisons de lisibilité, il est possible de
nommer certaines E.R. Et d'utiliser ces noms dans
d'autres E.R.
DEC (0|1|...|9)+(|.(0|1|...9|)+)
Chiffre (0|1|...|9)
DEC Chiffre+(|.Chiffre+)
Cours de M1
Règles de priorité
Problème : il peut y avoir des ambiguïtés avec les
expressions régulières
int peut être comme la déclaration de la
Récapitulation
Le rôle d'un analyseur lexical est la reconnaissance des
unités lexicales. Une unité lexicale peut (la plupart du
temps) être exprimée sous forme d'expression
régulière.
Diagrammes de transition
Les expressions régulières permettent de délimiter les
éléments lexicaux du langages
Comment lire facilement les éléments lexicaux d’un
langage (moins facile qu’il n’y paraît !)
Utilisation des diagrammes de transition
Cours de M1
Diagrammes de transition
Ce sont des automates finis déterministes
On se déplace de position en position dans le
diagramme au fur et à mesure de la lecture des
caractères
Les positions dans l’automate sont appelées états
Les états sont reliés par des arcs qui ont des
étiquettes indiquant les caractères lus
Les automates sont déterministes (aucun symbole
ne peut apparaître comme étiquette de deux arcs
quittant un nœud )
Un état appelé départ est l’état initial
Cours de M1
Exemple
début > =
0 6 7
recul d’entrée
autre
*
8
début < =
0 1 2 retourne(oprel,PPE)
>
3 retourne(oprel,DIF)
*
4 retourne(oprel,PPQ)
autre
=
5 retourne(oprel,EGA)
> =
6 7 retourne(oprel,PGE)
autre
* retourne(oprel,PGQ)
8
Cours de M1
Exemple
q
a 1 a
q q b
F
0 3
b q b
2
a b
q0 q1 q2
Q={q0,q1,q2,q3,F} q1 q3
q2 q3
={a,b} q3 F
F={F}
Exemple
a
F q a q b F
2 0 1 1
L’automate reconnaît le langage des mots d’au moins une lettre formés
avec a et b. On note que le mot ab peut être reconnu de deux façons
différentes
Cours de M1
Exemples
a,b
L1=(a|b)*
F a,b
a,b L2=(a|b)+
F
a,b
L3=(a|b)*abb(a|b)*
a,b b
a b
F
Cours de M1
Construction de Thompson
Donnée : une expression régulière r
Résultat : une AFND qui reconnaît L(r)
Cours de M1
Méthode de Thompson
Pour , construire l’AFND :
cet automate reconnaît
début
i F
début a F
i cet automate reconnaît {a}
1
Cours de M1
N(s)
début
i F
N(t)
Cours de M1
début
i N(s) N(t) F
début
i N(s) F
Cours de M1
Exemple r=(a|b)*abb
pour le premier a pour b
début a début b
2 3 4 5
Cours de M1
a|b
a
2 3
début
1 6
b
4 5
Cours de M1
(a|b)*
a
2 3
début
0 1 6 7
b
4 5
Cours de M1
(a|b)*abb
a
2 3
début
0 1 6 7 a
b 8
4 5
b
9
b
F
Cours de M1
Exemple
L=(a|b)*abb
a,b b
a b
0 1 2 3
a b
A=0 01 0
B = 01 01 02
C = 02 01 03
D = 03 01 0
(a|b)*abb
a
2 3
début
0 1 6 7 a
b 8
4 5
b
9
b
1
Cours de M1
Déterminisation
Etat Initial a b
A 01247 3671248 567124
B 1234678 3671248 5671249
C 124567 3671248 567124
D 1245679 3671248 567124 10
E 124567 10 3671248 567124
Cours de M1
Exemple
b
C b
b
début a b b
A B D E
a
a
a
Cours de M1
a
Cours de M1
Principe de fonctionnement
programme source
jflex compilateur Yylex.java
toto.jflex jflex
compilateur Yylex.class
Yylex.java
java
flot
Java Yylex lexèmes
d’entrée
Cours de M1
Spécifications en JFlex
Un programme jflex comprend trois parties séparées par
des %% :
code utilisateur
%%
options et déclarations
%%
règles et actions
corps du programme lex
Cours de M1
Code utilisateur
Le code de l'utilisateur est copié tel quel
En général, cette partie ne comprend que :
Des importations de packages ou de bibliothèques
Ce code se retrouvera en tête de la classe générée par
jflex
Cours de M1
Les options
Les options de jflex sont précisées en commençant
chaque ligne par le symbole %
%class Lexer : indique que la classe générée se
nommera Lexer.java
%unicode : le jeu de caractères unicode sera
reconnu lors de l'analyse
%cup : indique que l'analyseur syntaxique cup sera
utilisé (non utilisé dans notre cas)
%line : les lignes seront comptées dans l'analyse (la
ligne est récupérée par la variable yyline)
%column : idem pour les colonnes (variable
Cours de M1
Les options
%standalone : indique que la classe générée par jflex
sera exécutable (contient un main)
Du code peut être rajouté entre %{ et }%. Ce code est
en général utilisé pour déclarer des méthodes ou des
objets utiles pour l'analyse
De nombreuses autres options sont disponibles, à
vous de consulter la documentation de jflex
Cours de M1
Déclarations de macros
Une définition de macro est de la forme :
<idMacro> = <exprReg>
Les identificateurs de macro ont la même syntaxe que
les identificateurs Java
Remarque : une macro n'est pas un non terminal et ne
peut être utilisée récursivement ! Elle peut être utilisée
dans d'autres macros
Les expressions régulières définies par Jflex sont très
proches de celles vues en cours
Cours de M1
m1 {action1}
m2 {action2}
…
mn {actionn}
où chaque mi est une expression régulière et chaque
actioni est un fragment de programme qui décrit
quelle action l’analyseur lexical doit réaliser quand
le lexème concorde avec mi. En JFlex, les actions
sont écrites en Java.
Cours de M1
Fonctionnement de JFlex
JFlex travaille de concert avec un analyseur
syntaxique.
recherche du plus long préfixe correspond à une
des regexp mi
actioni correspondante exécutée
Si deux expressions régulières reconnaissent une
portion de l'entrée de même longueur, alors
l'analyseur choisit la première expression
rencontrée dans le fichier
Cours de M1
Exemple 1
// Calcul-V1.flex // règles
%%
// jflex options {entier}
{ System.out.println("Nom
%class Lexi bre("+yytext()+")"); }
%unicode \n { ; }
%line . { ;}
%column
%standalone
// macros
entier = [0-9]+
%%
Cours de M1
Exemple 2
// Calcul-V2.flex // macros
%% entier = [0-9]+
// jflex options %%
%class Lexi // règles
%unicode
%line {entier}
%column { System.out.println("Nom
bre("+yytext()+")"); }
%standalone
\n { ; }
%{eof
. { ;}
System.out.println("\nFin
du programme");
%eof}
Cours de M1
Exemple 3
// Calcul-V3.flex
%%
%class Lexi // macros
%unicode entier = [0-9]+
%line %%
%column // règles
%standalone {entier}
%{ { System.out.println("Nombre
("+yytext()+")");
public int n, somme = 0;
n++;
%}
somme +=
%{eof if (n > 0) Integer.valueOf(yytext()).in
tValue();
System.out.println("\nValeur }
moyenne des nombres " +
((somme + 0.0)/n)); \n { ; }
Cours de M1
Exemple 4
// Calcul-V4.flex // macros
%% entier = [0-9]+
// jflex options ope = ["+-*/"]
%class Lexi %%
%unicode // règles
%line
%column {entier}
%standalone { System.out.println("Nombre
("+yytext()+")"); }
%{ public int n, somme = 0;
{ope}
%} { System.out.println("Ope("+
%{eof yytext()+")"); }
if (n > 0) \n { ; }
System.out.println("\nFin"); . { ;}
%eof}
Cours de M1
chiffre = [0-9]
entier = {chiffre}+
%%
{entier} { System.out.print(" xxxx "); }
^p(.)*\n { System.out.println(" j'enlève les lignes qui
commencent par p"); }
. { if ( yytext().equals("o") )
System.out.print("*")
else
System.out.print(yytext());
}
Cours de M1
Analyse syntaxique
Grammaires
Cours de M1
Langage et syntaxe
Les phrases d’un langage ont une structure déterminée par une
grammaire
Exemple : une phrase correcte est constituée par un sujet suivi
d’un verbe alternative
if (expression) instruction else instruction
Ceci peut être exprimé par une grammaire
phrase sujet verbe
instr if (expr) instr else instr
Si on complète avec deux nouvelle règles
sujet pierre | claire
verbe court | marche
4 phrases possibles :
pierre court | pierre marche | claire court | claire marche
Cours de M1
Exemple 1
Nombre -> Chiffre | Chiffre Nombre
Chiffre -> 0|1|2|3|4|5|6|7|8|9
génère
0
17
547
7350
etc…
Cours de M1
Convention :
les chiffres, les signes ou les chaînes en gras
sont des terminaux
un nom en italique désigne un non-terminal
Cours de M1
Dérivations
Pour montrer qu'un mot w est dans le langage L,
il faut exhiber une suite finie d'applications de
règles de production partant de S et
aboutissant à w, appelée dérivation
On peut représenter une dérivation par
S w0 w1
P1 P2
wn w
Pn
Exemple de grammaire
phrase GN GV
GN Article Nom | Article Nom Subord
Subord Pron_relatif GV
GV Verbe GN | Verbe GN_prep
GN_prep Prep GN
Article le | la | les
Nom chat | commode | souris
Verbe est | regarde
Prep sur | sous
Pron_relatif qui | que | dont | ou
Cours de M1
Phrase
GN GV
Pron. GN la souris
relatif
GN
qui Verbe
prép.
est Prép. GN
Phrase
GN GV
Pron. GN la souris
relatif
GN
qui Verbe
prép.
est Prép. GN
Langage engendré
Le langage engendré par une grammaire G est
l'ensemble de terminaux que l'on peut dériver à partir
de l'axiome
∗
∀ w ∈ w∈ LG ⇔ S w
Cours de M1
Exemple
G:
E E + E / E * E / (E) / - E / Id
-(Id + Id) appartient L(G)
E -E -(E) -(E + E)
1er cas :
- (Id + E) -(Id + Id)
2ème cas :
-(E + Id) -(Id + Id)
Cours de M1
Arbre de dérivation
Un arbre de dérivation est une représentation graphique
d'une dérivation.
E
Id Id
Cours de M1
Ambiguïté
Exemple
Soit la grammaire G Il existe deux arbres d'analyse
E E + E syntaxique pour id+id*id
E E * E E E + E
E (E) id + E
E -E id + E * E
E id id + id * E
id + id * id
ou
E E * E
E + E * E
id + E * E
id + id * E
id + id * id
Cours de M1
Dérivations possibles
E NUM
E
E NUM E NUM
S E SE
E NUM
E ID
E
E ID
Cours de M1
Exercice
Trouvez une grammaire G non ambiguë engendrant le
même langage que G (* plus prioritaire que +)
E E + T | T
T T * id | id
Processus d'analyse
Le but est reconstruire l'arbre syntaxique d'une phrase
à partir d'une grammaire non ambiguë. Il existe
deux processus :
l'analyse descendante. Cette analyse correspond
à un parcours descendant gauche (on lit de
gauche à droite). Il s'agit de deviner à chaque
étape de deviner la règle qui sert à engendrer le
mot qu'on lit.
l'analyse ascendante. Dans cette analyse, on
essaye d'appliquer le membre droit d'une règle et
à le remplacer par le non-terminal gauche
correspondant. C'est l'opération inverse de la
dérivation.
Cours de M1
L'analyse descendante
Tente de construire un arbre syntaxique pour une chaîne
d'entrée de la racine vers les feuilles
ou
Tente de déterminer une dérivation gauche associée à
une chaîne d'entrée
Cours de M1
Exemple
∣
S cAd
A ab ∣ a
w=cad S
cad c A d
S ⇒c Ad
mémorisé
S
⇒cabd
cad c A d
échec
a b retour au choix précédent
Cours de M1
Exemple (suite)
cad c A d S ⇒c Ad ⇒cad
retour
a
Dans cet exemple, nous traitons une forme générale d'analyse descendante où des
retours arrières sont possibles avec passages répétés sur le texte source. Dans
la pratique, le rebroussement est rarement nécessaire, on peut adapter les grammaires
pour effectuer une analyse descendante sans rebroussement
Cours de M1
Remarque
Analyse descendante
Analyse prédictive
Cours de M1
flot d'entrée Id + Id * Id $
Algorithme
au départ : si X=a alors Dépiler(X);
Avancer;
si X est un non terminal
terminal ou
S si M[X,a] contient une non-terminal
$ production XUVW
$ w Remplacer X pour WVU
si M[X,a] vide au fond
Routine d'erreur
en cours : si X=a=$ alors succès
X
... a ... $
..
.
$
Cours de M1
Exemple
Soit la grammaire suivante :
E TE' [r1]
E' +TE' [r2] | [r3]
T FT' [r4]
T' *FT' [r5] | [r6]
F (E) [r7] | nb [r8]
Cours de M1
Table d'analyse
nb + * ( ) $
E r1 r1
E' r2 r3 r3
T r4 r4
T' r6 r5 r6 r6
F r8 r7
Cours de M1
E 2+3*4$ r1
TE' 2+3*4$ r4
FT'E' 2+3*4$ r8
T'E' 2+3*4$ r6
E' 2+3*4$ r2
TE' 2+3*4$ r4
FT'E' 2+3*4$ r8
T'E' 2+3*4$ r5
FT'E' 2+3*4$ r8
T'E' 2+3*4$ r6
E' 2+3*4$ r3
0 succès
Cours de M1
Fonctionnement de la table
Elle permet d'expliciter les dérivations à effectuer pour
accepter une phrase d'une grammaire
Exemple :
dérivation de w = 2 + 3 * 4 $
symbole de fin
Cours de M1
Résumé
Un peu de théorie
les terminaux avant
grosso modo
définition : pour (UV)*
first ( ) x * x si est une chaîne nullable
ensemble des terminaux qui commencent
les chaînes qui se dérivent de
les terminaux après A
Définition de first
si X est un terminal Pour une chaîne
first(X)={X}
X1X2...Xn
Si X
first(X) first(X1X2...Xn)=first(X1)
si XY1Y2...Yk si first(X1) + first(X2)
first(Y1) first(X), si
∗
si Y 1 ⇒ , first Y 2 ⊂ first x
∗
si Y 2 ⇒
Cours de M1
Application
first(E)={ (, nb }
first(E')={ +, }
first(T)={ (, nb }
first(T')={ *, }
first(F)={ (, nb }
Cours de M1
Calcul de follow
1.s'il y a une production A B, le contenu de
first(), excepté , est ajouté à follow(B)
2.mettre $ dans follow(S), où S est l'axiome et $ le
marqueur de fin de texte
3.Pour calculer follow(A) pour tous les non-terminaux
A, appliquer les règles suivantes jusqu'à ce
qu'aucun terminal ne puisse être ajouté
4.s'il existe une production A B ou A B
tq first() contient (*), les éléments de
follow(A) sont ajoutés à follow(B)
Cours de M1
Définition de follow
Exemple
E TE' [r1] règles B
E' +TE' [r2] | [r3] r1 B=T =E' rajout de +
T FT' [r4] r2 idem
T' *FT' [r5] | [r6] r4 B=F =T' rajout de *
F (E) [r7] | nb [r8] r5 =* B=F =T' idem
r7 =( B=E =) rajout de )
first(E)=first(T)=first(F)={(,nb}
first(E')={+,}
règles B ou B nullable
first(T')={*,}
follow(E)={$,)} r1 =T B=E' rajout de follow(E)
follow(T)={+,$,)} r1 B=T =E' rajout de follow(E)
follow(F)={*,+,$,)} r2 =+ B=T =E' rajout de
follow(E')={$,)} follow(E')
follow(T')={+,$,)} r4 =F =T' rajout de follow(T)
r4 B=F =T' rajout de follow(T)
r5 =*F B=T'
r5 =* B=F =T' rajout de
follow(T')
Cours de M1
Exemple
S iEtSS' (r1) | a (r2) règles B
S' eS (r3) | (r4) r1 B=E, =tSS' rajout de t
E b (r5) r1 B=S, =S' rajout de first(S')
a b e i t $
S r2 r1
S' r3 r4
r4
E r5
ambiguïté de la grammaire
Cours de M1
Pour A ∣
Remarque
Une grammaire ambiguë ou récursive à gauche ou
non factorisée à gauche n'est pas LL(1)
Cours de M1
Transformation de grammaires
Deux grammaires sont équivalentes si elles
engendrent le même langages
Selon la méthode d'analyse (ascendante,
descendante...) la grammaire doit vérifier certaines
contraintes et par conséquent subir éventuellement
quelques transformations
Cours de M1
Exemple
instr
Exemple en Java
if (i>0) if (i>0)
if (i>5) {
System.out.print if (i>5)
ln("erreur") System.out.print
else ln("erreur");
System.out.println }
("négatif") else
System.out.println("
négatif")
Cours de M1
Récursivité à gauche
Une grammaire est récursive à gauche si elle contient un
non terminal A tel qu'il existe une dérivation .
A⇒ A
A A'
A A ∣
A ' A '∣
Ex 1 : A Aa∣b
A⇒ Aa ⇒ Aaa ⇒⇒ baaa
{A bA '
A' aA '∣
A⇒ bA' ⇒ baA' ⇒ baaA' ⇒⇒ baa
Cours de M1
Récursivité à gauche
A A 1 A 2 A m 1 2 n
remplacé par :
Cas général
A⇒ A
1.Classer les non-terminaux A1, A2, ..., An
2.Pour i de 1 à n
Pour j de 1 à i - 1
Remplacer Ai A j par Ai 1 ∣ 2 ∣∣ k
où A j 1∣ 2∣ k
Exemple
S Aa∣b Éliminer la récursivité gauche
A Ac∣Sd∣ immédiate
A bdA '∣A '
• S est récursif à gauche S Aa Sda
A' cA'∣adA '∣
• A est immédiatement récursif à gauche
1) classer A1=S, A2=A
2) pour A1=S
• vide
Ex 2 : expressions arithmétiques
{ E ET∣T
T T ∗Id∣Id
{
E TE '
E ' TE '∣
T Id T '
T ' ∗Id T '∣
Cours de M1
Récursivité à gauche
Il faut enlever les ambiguïtés
EE+T |T boucle infinie
récursivité à gauche
A A | , = + T
à remplacer par
A R ATR
R R | R + T |
Cours de M1
Factorisation à gauche
C'est une transformation utile pour avoir une
grammaire permettant l'analyse prédictive. L'idée
est que pour développer un non-terminal A, quand il
n'est pas évident de choisir l'alternative à utiliser, on
ré-écrit les règles de productions
Exemple :
instr si expr alors instr sinon instr
quelle alternative choisir
| si expr alors instr après lecture du si ?
Cours de M1
Exemple
instr si expr alors instr-suite
instr-suite sinon instr |
A A'
A 1∣ 2 A' 1∣ 2
A 1 2 n
on transforme en
A A'
A' 1 2 n
Cours de M1
Problèmes rencontrés
On enlève les ambiguïtés
On élimine les récursivités à gauche
On factorise à gauche
Implémentation à l'aide de
procédures récursives
Grammaire LL(1)
T → R | aTc
R → | bR
first(T) = {a,b,} first(R) = {,b}
follow(T) = {$,c} follow(R)= {$,c}
a b c $
T r2 r1 r1 r1
R r4 r3 r3
Analyse de aabbbcc$
Cours de M1
Exemple
fonction parseR()
si next = 'b' alors
consomme('b') ; parseR()
sinon si next = 'c' ou next = '$' alors
(* ne rien faire *)
sinon ErreurSyntaxique()
fonction parseT()
si next = 'a' alors
consomme('a') ; parseT(); consomme('c')
sinon si next = 'b' ou next = 'c' ou next = '$' alors
parseR()
sinon ErreurSyntaxique()
Cours de M1
Conclusions
En transformant la grammaire de façon à ce qu'elle soit :
non ambiguë. Il n'y a pas de méthodes. Une grammaire ambiguë
est une grammaire qui a été mal conçue
factorisée à gauche
sans récursivité à gauche
Il ne reste plus qu'a espérer que ça soit LL(1). Sinon, il faut
concevoir une autre méthode pour l'analyse syntaxique
On parle d'analyseur prédictif.
Cours de M1
LR LNC
A partir d'un AFN, on peut facilement définir une grammaire
non contextuelle engendrant le même langage
Ex : L=(a|b)*abb
A0 aA0∣bA0∣aA1
a b b A1 bA2
0 1 2 3
A2 bA3
A3
•Note :
a,b • LR = Langages réguliers
• LNC = Langages non contextuels
• LC = Langages contextuels
Cours de M1
LNC LR ?
Un AFND ne sait pas compter
n n
L={a b ∣n1 }
G∥A aAb∣ab
LC ?
Certains langages ne peuvent être engendrés par des
grammaires non contextuelles
LC ? (suite)
R ∗
L ' 1={wow ∣w∈a∣b }
A aAa∣bAb∣c
n m m n
L ' 2={a b c d ∣n1 m1 }
{A aAd∣aA ' d
A ' bA ' c∣bc
Cours de M1
Analyse ascendante
Principes
Tente de construire un arbre d'analyse par une
chaîne d'entrée des feuilles (bas) vers la racine (haut)
Tente de déterminer une dérivation droite en « sens
inverse »
Il s'agit de « réduire » une chaîne w vers l'axiome de la
grammaire. On regarde si dans la chaîne à analyser,
il existe une sous-chaîne correspondant à la partie
droite d'une production et on remplace par le non-
terminal de la partie gauche de cette production
Cours de M1
Remarques
Malgré les apparences, les analyseurs ascendants
sont plus efficaces que les analyseurs descendants.
En outre, ils acceptent une classe de langage plus
large
Le principal inconvénient de ces analyseurs est qu'ils
nécessitent des tables qu'il est extrêmement difficile de
construire à la main
Cours de M1
Exemple
∣
S aABe
A Abc∣b et w=abbcde
Bd
S
Exemple (suite)
La suite des réductions lue à l'envers représente une dérivation droite de l'axiome
S ⇒ aA B e ⇒ a A de ⇒ a A bc de ⇒ abbcde
Remarque : le choix de la sous-chaîne à réduire ne doit pas être quelconque. Dans notre
exemple :
Définition
On appelle manche de chaîne, une sous-chaîne
correspondant à la partie droite d'une production et
dont la réduction vers le non-terminal de la partie
gauche représente une étape le long de la dérivation
droite inverse.
∣
∗
si S ⇒ Aw ⇒ w
d d
alors est un manche de w
{
∗
avec w∈
A ∈P Si la grammaire est ambiguë, il peut exister plusieurs
manches dans une chaîne
Cours de M1
On décale de l'entrée vers la pile
0, 1 ou plusieurs symboles jusqu'à
ce qu'un manche se trouve en sommet
$ de pile. On le remplace par la partie
gauche de la production.
$ w
Flot d'entrée
pile
On recommence ces « décalagesréductions »
jusqu'à ce que l'on détecte une erreur ou un succès :
S
$
$
Cours de M1
Exemple
id ] E
∣
E E E
E E∗E ∗ ∗ ∗
E E E E E E E succès
1
$ $ $ $ $
as
E id
C
w=id id∗id
id ] E
id ] E E E E id ] E
∗ ∗ ∗
$ $ $ $ $ E E E E
C
as
2
E E E E E
succès
Note : comme la grammaire est ambiguë, $ $ $ $ $
il existe 2 séquences possibles pour l'analyseur
Cours de M1
Exemple
Proc → id(liste_paramètres)
liste_paramètres → liste_paramètres,paramètre
paramètre → id
tab → id(liste_exprs)
liste_exprs → liste_exprs,expr
expr → id
Exemple
Proc → id(liste_paramètres)
liste_paramètres → liste_paramètres,paramètre
paramètre → id
tab → id(liste_exprs)
liste_exprs → liste_exprs,expr
expr → id
Les analyseurs LR
Analyseurs par décalage réduction sans rebroussement
LR(k)
Indique le nombre de
Algorithme d'analyse LR
Sm a1 a2 $
symbole Xm
sm-1
Programme Flot de sortie
état ¦ d'analyse LR
(résume l'état
des piles)
s0
pile
Table Table
ACTION SUCCESSEUR
Cours de M1
Principe
Au départ :
$
¦ w
Si Action[s,a]= Décaler s'
s0
[
pile empiler a
empiler s '
En cours :
avancer p
si Action[s,a]= Réduire A ->
... a ... $
[
s Dépiler 2∗ symboles
Soit s ' l'état au sommet
¦ empiler A
P empiler Succ [ s ' , A]
pile s0
si Action[s,a] = Accepter
=> Succès
si Action[s,a] = Erreur
=> Routine de récupération
sur erreur
Cours de M1
Exemple :
grammaire des expressions arithmétiques
notation :
di décaler et empiler l'état i
rj réduire avec la règle j
Tables d'analyse acc accepter => succès
0 erreur
Etat ACTION SUCC
Id + * ( ) $ E T F
∣
E ET 1 0
1
d5
d6
d4
acc
1 2 3
E T 2 2
3
r2
r4
d7
r4
r2
r4
r2
r4
T T ∗F 3 4
5
d5
r6 r6
d4
r6 r6
8 2 3
T F 4 6
7
d5
d5
d4
d4
9 3
10
F E 5 8
9
d6
r1 d7
d11
r1 r1
F id 6 10
11
r3
r5
r3
r5
r3
r5
r3
r5
Cours de M1
Exemple (suite)
id * id + id $
r6 r3 r1
0 T 2 * 7 F 10
0 E 1 + 6 Id 5
0E1+6F3
0 T 2 * 7 Id 5
0E1+6T9
r6 r4 r2
0F3
0E1
0 id 5
ac
0T2
Définition
Un item est une production avec un point repérant une
position dans la partie droite
∣
A . XYZ
A X.YZ
A XY.Z
A XYZ.
interprétation : A X.YZ
Nous venons de lire une chaîne dérivée de X
Nous attendons une chaîne dérivée de YZ
Cours de M1
Exemple de fermeture
Soit la grammaire : Et soit l'ensemble d'items
∣
E ET 1 I ={T T ∗. F , E E.T }
E T 2
G T T ∗F 3 La fermeture de cet ensemble
T F 4
F E 5 d'items est :
F Id 6 {T T ∗. F , E E.T , F . Id , F . E }
Cours de M1
Exemple de transition
Sur l'exemple de la On aura :
grammaire
∣
E ET 1 I , F ={T T ∗F. }
E T 2
G T T ∗F 3
T F 4
F E 5
F Id 6
I , ={E E. T , T .T ∗F ,T . F , F . Id , F . E }
Soit l'ensemble d'items :
{ T T ∗. F , E E.T ,
F . Id , F . E }
Cours de M1
∣
E ET 1
E T 2
G T T ∗F 3
T F 4
F E 5
F Id 6
Cours de M1
∣ ∣
F . E
∣
S ' . E T T ∗. F
E . ET E . ET I 7 : F . E
E .T E . T F .id
I 0 : T . T ∗F I 4 : T .T ∗F
T . F T . F
F . E
F .id
F . E
F . id ∣
I 8 : F E .
E E .T
∣
I 1: S ' E .
E E .T
I 5 : F id . 9 ∣ T T .∗F
I : E ET .
∣
E E.T
T . T ∗F I 10 :T T ∗F .
∣
I 2: E T .
T T .∗F
I 6: T . F
F . E I 11 :T E .
F .id
I 3 :T F .
Cours de M1
Cours de M1
}
si A . a ∈I i
et transition I i , a= I j Action[i , a]=décaler j
avec a∈T
et A≠S ' }
si A .∈I i Action[i , a]=réduire par A
pour A∈suivant A
ACTION SUCCESSEUR
Id + * ( ) $ E T F
0 d5 d4 1 2 3
1 d6
2 E→T d7 E→T E→T
3 T→F T→F T→F T→F
4 d5 d4 8 2 3
5 F→Id F→Id F→Id F→Id
6 d5 d4 9 3
7 d5 d4 10
8 d6 d11
9 E→E+T d7 E→E+T E→E+T
10 T→T*F T→T*F T→T*F T→T*F
11 F→(E) F→(E) F→(E) F→(E)
avec suivant(E)={+,),$}
suivant(T)={*,+,),$}
suivant(F)={*,+,),$}
Cours de M1
Remarques
Des conflits peuvent apparaître dans la table d'analyse
(grammaire non SLR dans ce cas)
Pour certaines grammaires, l'analyse SLR n'est pas
assez puissante pour se rappeler suffisamment le
contexte gauche pour décider de l'action à faire. Pour
un item A→. , on place cette production dans la table
ACTION pour chaque terminal a de follow(A). Or, il se
peut que le manche situé sur la pile à cet état de
l'automate ne puisse jamais être suivi de a. L'analyse
LR résout ce problème. Elle précise après chaque
item, l'ensemble des terminaux qui peuvent
effectivement suivre le manche en sommet de pile.
Cours de M1
}
si [ A . B , a]∈ I
B ⇒[ B . , b ]∈Fermeture I
b∈Premier a
Transition(I,X) est la fermeture des Items de la forme
Exemple
∣
G S CC
C cC∣d
first S ={c , d }
first C ={c , d }
Cours de M1
Fermeture de ([S'→.S,$])
identification avec [ A . B , a ]
∣
S ' . S ,$
A=S ' = B=S = et a=$
I 0 S . CC , $
Ajout de [ B . , b] C . cC , c∣d
C . d , c∣d
est S CC
on ajoute donc [ S .CC ,$ ]
Calcul de transition(I0,X)
pour X =S nous fermons {[ S ' S . , $]}
I 1 : S ' S. , $
pour X =C nous fermons {[C C.C , $]}
∣
S C.C ,$
I 2 C . cC , $
C . d , $
pour X =c nous fermons {[C c.C , c∣d ]}
∣
C c.C , c∣d
I 3 C . cC , c∣d
C . d , c∣d
pour X =d
I 4 :C d. , c∣d
Cours de M1
Exemple (suite)
Aucun nouvel ensemble sur I1
I2 a des transitions sur C, c et d
I 5 : S CC. , $
∣
C c.C , $
I 6 C . cC ,$
C . d , $
Cours de M1
Exemple (suite)
Transition (I2,d)
I 7 :C d. , $
Remarques
Des conflits peuvent encore apparaître ! Même pour des
grammaires non ambiguës. Dans ce cas, la grammaire n'est
pas LR (rare pour les langages)
Toute grammaire SLR est LR mais le nombre d'états de
l'analyseur est supérieur au nombre d'états de l'analyseur
SLR (la grammaire de l'exemple est SLR et ne nécessite
que 7 états dans un analyseur SLR)
Dans la pratique, un langage comme PASCAL engendre
plusieurs centaines d'états en SLR et plusieurs milliers en
LR !
L'analyse LALR évite certains conflits sans engendrer
autant d'états que l'analyse (Quelques centaines quand
même)
Cours de M1
Exemple
Dans l'exemple précédent, on fusionne :
I4 et I7 en I47
c d $ S C
I3 et I6 en I36 0 d36 d47 1 2
1 Acc
I8 et I9 en I89 2 d36 d47 5
36 d36 d47 89
47 C→d C→d C→d
5 S→CC
89 C→cC C→cC C→cC
CUP
Principe
sym.java
Grammaire non
contextuelle
cup
parser.java
sym.java
parser.java javac monProg
yylex.java
Résultat de
Source à analyser java monProg l'analyse
Cours de M1
Conflits et ambiguïtés
Si on utilise la grammaire suivante :
%%
session : session expression '=' { printf ... }
| ;
expression : expression '+' expression ;
| expression '' expression ;
| expression '*' expression ;
| nombre
;
%%
Communication CUP/JFlex
L'analyseur syntaxique (obtenu avec CUP) reçoit les
"tokens" de l'analyseur lexical
L'analyseur lexical (obtenu avec JFlex) doit envoyer les
bons "tokens"
À partir de calcul.cup deux classes générées :
sym.java parser.java
L'analyseur syntaxique attend des valeurs de type
Symbol
Cours de M1
Exécution de cup
Compilation
cup exemple1.cup ou java -jar java-cup-11a.jar
exemple1.cup
génération des fichiers parser.java et sym.java
sym.java contient les déclarations de constantes, une
pour chaque terminal
parser.java implémente le parseur proprement dit
Cours de M1
Exemple de grammaire
/* calcul.cup */
expr ::= expr PLUS expr
import java_cup.runtime.*;
|
expr MOINS expr
|
FOIS,DIV,PARENG,PAREND; |
;
Cours de M1
Résultats
Ambiguïté
Une expression arithmétique comme 3 + 6 * 4 est
ambiguë dans notre grammaire
Elle peut s'évaluer 3+ 6 et ensuite multiplier 9 par 4
Elle peut aussi multiplier 6 * 4 et ajouter 3 à 24
Les anciennes versions de cup obligeaient une
grammaire sans ambiguïté
Les nouvelles versions permettent de lever un certain
nombre d'ambiguïtés en spécifiant l'associativité et la
priorité des opérateurs
En cas de conflit décalage/réduction, on regarde la
priorité du terminal par rapport à la priorité de la règle
de production
Cours de M1
Explications
Pour l'expression 3 + 4 * 8
Quand l'analyseur arrive au symbole *, il ne sait pas s'il
doit décaler ou réduire l'expression (ici il faut décaler
car * est prioritaire par rapport au + et ainsi la
multiplication sera prioritaire par rapport au +)
Une priorité est donnée à tous les terminaux. S'il n'y a
pas d'indication avec le mot-clef precedence, la
priorité la plus faible est donnée au terminal
Une priorité est donnée à chaque règle qui correspond
au dernier terminal de la règle (expr FOIS expr a la
priorité de FOIS)
Cours de M1
Explications (suite)
En cas de conflit décalage/réduction,si le terminal en
cours d'analyse, l’analyseur syntaxique provoque :
un décalage si le terminal à analyser à une priorité plus
importante
une réduction si la règle a une priorité plus importante
la règle d’associativité est appliquée en cas d’égalité
calcul2.cup
/* calcul2.cup */ /* priorités */
precedence left PLUS, MOINS;
import java_cup.runtime.*; precedence left FOIS;
expr ::= expr PLUS expr
| De plus en
plus prioritaire
expr MOINS expr
terminal INT,PLUS,MOINS, |
expr FOIS expr
FOIS,PARENG,PAREND; | PARENG expr PAREND
|
non terminal expr ;
INT
;
Cours de M1
Résultat
Les 2 fichiers
parser.java est sym.java suite de constantes pour
chaque terminal
l’analyseur syntaxique /** CUP generated class containing
symbol constants. */
public class sym {
/* terminals */
public static final int error = 1;
public static final int PLUS = 3;
public static final int MOINS = 4;
public static final int PARENG = 6;
public static final int PAREND = 7;
public static final int FOIS = 5;
public static final int EOF = 0;
public static final int INT = 2;
}
Cours de M1
%%
%unicode
%line
%column
%cup
Cours de M1
// modèles
entier = [0-9]+
%%
//Règles
{entier} {System.out.print(yytext()); return new Symbol (sym.INT); }
\+ {System.out.print(yytext()); return new Symbol (sym.PLUS); }
- {System.out.print(yytext()); return new Symbol (sym.MOINS); }
\* {System.out.print(yytext()); return new Symbol (sym.FOIS); }
\( {System.out.print(yytext()); return new Symbol (sym.PARENG); }
\) {System.out.print(yytext()); return new Symbol (sym.PAREND); }
\n { System.out.print(yytext()); }
. { System.out.print(yytext()); }
Cours de M1
Le programme Main
import java.io.*;
public class Main {
public static void main( String args [ ]) {
try {
parser p = new parser ( new Yylex ( new FileReader (args[0])));
Object result = p.parse( ).value;
Test
javac Main.java -cp .:java-cup-11a.jar:.
utilisation
java -cp .:java-cup-11a.jar Main exemple.txt
15 + 15 * 9
file OK
Cours de M1
Applications
2 tp sur le couplage jflex et cup
vérification d’une expression arithmétique
vérification de la validité syntaxique d’une facture
Cours de M1
Attribut synthétisé
But : calculer la valeur de chaque expression
Calcul d'un attribut synthétisé val sur la grammaire
Définition de val :
Règle expr ::= INT
val(expr) = val(INT)
Règle expr ::= expr(1) PLUS expr(2)
val(expr) = val(expr1) + val(expr2)
Val est synthétisé : la valeur sur le membre gauche
d'une règle est fonction des valeurs sur les membres
de droite
Cours de M1
Exemple de grammaire
/* attribut.cup */
non terminal list_expr;
import java_cup.runtime.*; non terminal Integer expr;
%}
// modèles
chiffre = [0-9]
entier = {chiffre}+
espace = [ \t\n]
%%
// Règles
{entier} { return new Symbol (sym.INT, new Integer(yytext())); }
\+ {
return new Symbol (sym.PLUS); }
Cours de M1
Analyse syntaxique
Application au mini-Scheme
Cours de M1
fonction teste
public int teste(Yytoken t) throws IOException{
variable globale
if (t.index == token.index){
}
Cours de M1
Conclusion
Fini la théorie
Théorie des langages domaine très complexe (du plus
simple au plus complexe)
grammaires régulières
grammaires LL(1)
Grammaires SLR
grammaires LALR (peu étudiées)
grammaires LR(1)
…
Cours de M1
Analyse sémantique
Introduction
Le compilateur effectue un contrôle statique du
programme source : syntaxique et sémantique
Exemple : T tableau de 10 entiers de 1..10, I entier
Contrôle syntaxique : Contrôle sémantique :
correct incorrect correct incorrect
T[I] T I T[I] T[I][1]
T[I][1] T[2] T + I
T+I T[0.5]
T[0.5] T < 1
T <
Cours de M1
Définitions
On notera X.a l'attribut a du symbole X. S'il y a plusieurs
symboles X dans une production, on notera X(0) s'il est
en partie gauche, X(1) si c'est le plus à gauche de la
partie droite, ..., X(n) si c'est le plus à droite de la partie
droite
Cours de M1
Exemple
Grammaire S aSb | aS | cSacS |
attributs : nba (calcul du nombre a), nbc (calcul du nombre c)
règles sémantiques
0 1
S aSb S . nba :=S . nba1
0 1
S . nbc :=S . nbc
0 1
S aS S . nba :=S . nba1
0 1
S . nbc :=S . nbc
0 1 2
S cSacS S . nba :=S . nba1 S . nba1
0 1 2
S . nbc :=S . nbcS . nbc2
0
S 0 . nba :=0
S
S . nbc :=0
S ' S ≤résultat final est dans S.nba et S.nbc
Cours de M1
4 S 2
a b
3 S2
c a 1 S 0
1 S 0 c
a 0 S 0 a 0S 0 b
Sur cet arbre syntaxique, on voit que l'on peut calculer les attributs d'un noeud
dès que les attributs de tous ces fils ont été calculés
Cours de M1
Remarques
On appelle arbre syntaxique décoré un arbre
syntaxique sur les nœuds duquel on rajoute la valeur
de chaque attribut
Dans une DDS (Définition Dirigée par la Syntaxe), il n'y
a aucun ordre spécifique imposé pour l'évaluation des
attributs. Tout ordre d'évaluation qui calcule sur l'arbre
syntaxique l'attribut a après tous les attributs dont a
dépend est possible. Plus généralement, il n'y a aucun
ordre spécifique pour l'exécution des actions
sémantiques
Cours de M1
Analyse de i + i + i et i + i * i
Cours de M1
i + i + i. . .
. . . mais
Le mot i + i * i a deux significations, E .val a 2 valeurs
possibles.
⇒ attribuer une grammaire ambiguë n’a aucun sens pratique.
Cours de M1
Remarques
Cette grammaire spécifie comment calculer des
valeurs associés à ses symboles
Mais elle ne dit pas quand effectuer ces calculs
… ni dans quel ordre effectuer les actions
Grammaire attribuée = formalisme de spécification, pas
d'exécution
Cours de M1
Attributs synthétisés
Un attribut est synthétisé si sa valeur, à un nœud d'un
arbre syntaxique, est déterminée à partir de valeurs
d'attributs des fils de ce nœud (utilisés intensivement en
pratique). L'attribut de la partie gauche est calculé en
fonction des attributs de la partie droite. Le calcul des
attributs se fait des feuilles vers la racine.
Exemple
Règle Action sémantique
E→E+T E(0).val:=E(1).val+T.val
E→T E.val:=T.val
T→(E) T.val := E.val
T→i T.val := i.val
Visualisation
Cours de M1
Attributs hérités
Un attribut hérité est un attribut dont la valeur à un
nœud d'un arbre syntaxique est définie en terme des
attributs du père et/ou des frères de ce nœud C'est-à-
dire le calcul est effectué à partir du non terminal de la
partie gauche et éventuellement d'autres non
terminaux de la partie droite
Si les attributs d'un nœud donné ne dépendent pas
des attributs de ses frères droits, alors les attributs
hérités peuvent être facilement évalués lors d'une
analyse descendante
Note : l'axiome de la grammaire doit être de la forme
A→B pour pouvoir initialiser le calcul
Cours de M1
Grammaire
Grammaire
{ S ' S
S SS∣
Attributs hérités
Ordre d'évaluation
Dans quel ordre évaluer les attributs ?
les actions induisent des dépendances de données ;
construction d'un graphe de dépendances.
Cours de M1
Ordre d'évaluation
Si ce graphe est cyclique : grammaire mal formée.
sinon (grammaire bien formée) : on peut trouver un
ordre d'évaluation, une numérotation des actions
Cours de M1
Autre exemple
Cours de M1
Graphes de dépendances
Si un attribut b à un nœud d'un arbre syntaxique dépend
d'un attribut c, la règle sémantique définissant b en ce
nœud doit être évaluée après la règle sémantique qui
définit c.
Exemple
Soit une règle de production A XY possédant une règle
sémantique A.a:=f(X.x,Y.y), le graphe de dépendance
sera : A a
X Y
Si la règle sémantique est X.h:=g(A.a,Y.y), le graphe de
dépendance sera :
A
X Y
h
Cours de M1
Ordre d'évaluation
Une fois le graphe de dépendance donné, un tri
topologique (si mimj est un arc de mi à mj, alors mi doit
apparaître avant mj) du graphe permet d'obtenir l'ordre
d'évaluation des règles sémantiques.
DDS problématique
production action sémantique
S'→S S.a:=S.b
S→ S S T S(0).b:=S(1).b+S(2).b+T.b
S(1).a:=S(0).a
S(2).a:=S(0).a+1
T.a:=S(0).a+S(0).b+S(1).a
T→ P T T(0).b:=P.a+P.b
P.a:=T(0).a+3
T(1).a:=...
a hérité et b synthétisé
Cours de M1
Graphe de dépendance
Analyse descendante
Analyse descendante : parcours de l'arbre en ordre
préfixé en profondeur d'abord :
père puis fils gauche à droite, récursivement
X →X1X2X3 et la pile
Cours de M1
Restriction
X2 doit par définition :
avoir à sa disposition ses attributs hérités ;
calculer ses attributs synthétisés.
Les attributs hérités de X2 ne peuvent venir de ses frères
droits.
Cours de M1
Restriction
Les attributs de X2 sont calculés en fonction :
de ses attributs hérités ;
des attributs calculés dans le sous-arbre dont il est
racine.
Cours de M1
Grammaire L-attribuée
Pour permettre une évaluation des attributs lors de
l'analyse descendante, une grammaire attribuée doit
être L-attribuée. Pour toute règle :
X →X1X2...Xn
Les attributs hérités de Xi (1 ≤ i ≤ n) dépendent
uniquement de la valeur :
d'attributs hérités du père X ;
d'attributs des frères Xj, avec j ≤ i
Grammaire LL(1)-attribuée
Analyseur récursif
Une méthode X( ) par non-terminal X.
Les valeurs des attributs :
hérités de X sont disponibles pour X avant sa reconnaissance ;
synthétisés de X sont calculées après que X a été reconnu.
Donc :
les attributs hérités Her(X) dont des entrées de X ;
les attributs synthétisés Synth(X) sont des sorties de X.
Exemple 1
pour la production X → Ab
public SynthX X(h1X, ..., hnX) {
...
eval des Her(A); // en fonction de Her(X)
if (courant == Type.b) {
Synthb synth_b = (Synthb) courant.getValue();
this.consommer(b);
} else throw ... ;
eval des Synth(X); // en fonction de Her(X),
// Synth(A), Synth(b) }
return Synth(X);
Cours de M1
Résumé
Attributs synthétisés bien adaptés pour une analyse
ascendante et descendante récursive
Attributs hérités bien adaptés pour une analyse
descendante
Question : comment faire avec des attributs
synthétisés et hérités ?
on peut souvent utiliser des attributs synthétisés qui font la
même chose que les attributs hérités qu'on voulait
Cours de M1
Arbres décorés
hérité synthétisé
Cours de M1
L L , id L(1).typeh := L(0).typeh
mettre dans la table des
symboles
L id
le type L(0).type pour ID
mettre dans la table des
symboles le type L.type pour
a, b, c : integer Id
Cours de M1
real
(1)
L .type = real
, Id
L(1).type = real , Id c
a b
Cours de M1
Modification de la grammaire
production action sémantique
D→ Id L mettre dans la table des symboles le type
L.type pour Id
L→ , Id L L(0).type:=L(1).type
mettre dans la table des symboles le type
L(1).type pour Id
L→ : T L.type:=T.type
T→ integer T.type:=entier
T→ real T.type:=réel
Cours de M1
Illustration
D mettre a dans la TS
de type entier
Id L L(0).type := entier
mettre b de type entier
dans la TS
, Id L L.type := entier
a
b : T T.type := entier
integer
Cours de M1
E.val = 19
E.val = 15 T.val = 4
+
T.val = 15 F.val = 4
F.val = 5
T.val = 3 * chiffre.vallex = 4
F.val = 3 chiffre.vallex = 5
chiffre.vallex = 3
Cours de M1
Remarques
Ce n'est pas toujours évident de concevoir une DDS
n'utilisant que des attributs hérités ou de concevoir une
autre grammaire permettant de n'avoir que des synthétisés.
Heureusement, il existe des méthodes automatiques qui
transforment une DDS avec des attributs hérités et
synthétisés en une DDS équivalente n'ayant que des
attributs synthétisés.
Une autre idée peut être de faire plusieurs passes
d'analyses, suivant le graphe de dépendances de la DDS.
Une passe ascendante permettant d'évaluer un certain
attribut synthétisé s, puis une passe descendante pour
évaluer des attributs hérités h et r, puis enfin un passe
ascendante pour évaluer un attribut synthétisé t.
Cours de M1
entier i
caractère c
i entier i entier
Cours de M1
Contrôle de type
Lorsque le langage source est un langage typé, il faut
vérifier la pertinence des types des objets manipulés
dans les phrases du langage.
En C, on ne peut pas additionner un double et un char
*, multiplier un int et un struct, indicer un tableau avec
un float, affecter un struct * à un char ... Certaines
opérations sont par contre possibles : affecter un int à
un double, un char à un int, ... (conversions implicites).
Cours de M1
sinon
si (E(1).type ≠reel et E(1).type≠entier)
ou (E(2).type≠reel et E(2).type≠entier) alors
erreur_type_incompatible(ligne,E(1).type,E(2).type)
sinon réel
E→ E mod E E(0).type := si E(1).type=entier et E(2).type=entier alors entier
sinon erreur_type(ligne,...)
Présentation
E x é c u tio n
Cours de M1
Grâce à un interpréteur
Chaque instruction du source est analysée puis
exécutée :
Chaque exécution entraîne une analyse → lent
On doit avoir un interpréteur en mémoire
Exemples :
PostScript
Scheme
Certains LISP
Prolog
Cours de M1
Grâce à un compilateur
Avant l'exécution, le source en L est transformé en
langage machine
Programme exécuté directement par le microprocesseur →
très rapide, encombrement mémoire minimal
Il faut un compilateur par microprocesseur → non portable
Certains langages qui autorisent l'auto-modification sont
impossibles à compiler
Exemples :
Pascal, ADA
C
Cours de M1
Grâce à un pseudo-compilateur
On utilise un langage intermédiaire très rudimentaire, ne
dépendant pas du microprocesseur.
Programme
en Compilation
Exécution
langage L Programme en LI
interprétation
Cours de M1
Langage intermédiaire
Dans la plupart des compilateurs, un code
intermédiaire est produit. Ce code intermédiaire est
en général un langage simple. L'avantage immédiat
d'une production de code intermédiaire est qu'il
permet de s'affranchir de la machine.
Avantages
Plus rapide que l'interprétation, moins que la
compilation
Nécessité d'avoir un interpréteur de langage
intermédiaire en mémoire
Le compilateur est portable
Le compilateur et l'interpréteur « ne sont pas très
difficiles » à écrire
Solution pour les langages difficiles/impossibles à
compiler : Lisp, Smalltalk
Cours de M1
Remarque
On peut également écrire un compilateur en passant par
un langage intermédiaire
Compilation Compilation
Programme en LI Programme en LM
Ces deux phases de compilation sont plus faciles à mettre en oeuvre que la
compilation directe en LM
Cours de M1
Programme
L1 en LM1
traduction
compilation Programme en LI
Programme
en LMn
Ln
Cours de M1
Schéma récapitulatif
Analyse lexicale
Analyse syntaxique
Analyse sémantique
Génération en LI
Optimisation en LI
Génération en LM
fichier.o
Édition des liens statiques : les
fonctions des librairies sont ajoutées
Exécution avec
édition de liens
dynamiques
Exécution
Cours de M1
Application à Linux
Le format ELF
3 types dans le format ELF :
Un ensemble de fonctions et de variables (symboles)
dont l'adresse n'est pas fixe. Ne dispose ni d'adresse
fixe, ni de point d'entrée et est relocalisable. Fichiers .o
créés par gcc ou archives .a ;
Un programme prêt à l'exécution, adresses mémoires
des symboles et son point d'entrée fixés. Fichier dit
exécutable.
Les bibliothèques partagées avec très peu d'adresses
absolues et une table d'indirection permettant de
reloger les composants en mémoire. Fichier dit objet
partagé.
Cours de M1
Exemples
fonlupt@moria:~/enseignement/compilation$ readelf -h
/usr/lib/libcurl.so.4.2.0 | grep -i type
Type: DYN (Shared object file)
fonlupt@moria:~/enseignement/compilation$ readelf -h /usr/lib/crti.o | grep -i
type
Type: REL (Relocatable file)
fonlupt@moria:~/enseignement/compilation$ readelf -h /bin/ls | grep -i type
Type: EXEC (Executable file)
Cours de M1
Chaine de compilation
3 programmes pour produire le binaire :
cc1 compilateur C (ce que nous faisons dans le
cours) ! Transforme un fichier c en assembleur .s ;
as assembleur. Transforme un fichier texte assembleur
en binaire. Le fichier objet produit .o au format ELF
n'est cependant pas encore prêt pour l'exécution.
Référencement des éléments se trouvant dans
d'autres unités ou dans des bibliothèques ;
collect2 éditeur de liens. Prend en entrée une série de
fichiers .o et les combine, les réorganise, leur affecte
une adresse définitive en mémoire. (cf option -l).
Cours de M1
Analyse de l'exécutable
fonlupt@moria:~/enseignement/compilation$ readelf -h test
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400410
Start of program headers: 64 (bytes into file)
Start of section headers: 4432 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 27
Cours de M1
Les informations
L'exécutable est créé pour une architecture x86 à 64
bits
A l'exécution le programme commencera à l'adresse
virtuelle 0x400410. Ce n'est pas l'adresse de main()
mais d'une procédure _start créé par l'édition de liens ;
Le programme a 30 sections et 9 segments
La section est un objet qui contient des informations pour
l'édition de liens : code, variables, informations de
relocalisation... ;
Le noyau linux prépare des (PMV) pages mémoires virtuelles
où sont « copiées » les sections. Chaque PMV représente un
segment. La PHT (Program Header Table) associe sections
aux segments.
Cours de M1
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 0000000000400238 000238 00001c 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 000254 000020 00 A 0 0 4
[ 3] .note.gnu.build-id NOTE 0000000000400274 000274 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 000298 00001c 00 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 0002b8 000060 18 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 000318 00003d 00 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400356 000356 000008 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 000360 000020 00 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 000380 000018 18 A 5 0 8
[10] .rela.plt RELA 0000000000400398 000398 000030 18 A 5 12 8
[11] .init PROGBITS 00000000004003c8 0003c8 000018 00 AX 0 0 4
[12] .plt PROGBITS 00000000004003e0 0003e0 000030 10 AX 0 0 4
[13] .text PROGBITS 0000000000400410 000410 0001e8 00 AX 0 0 16
[14] .fini PROGBITS 00000000004005f8 0005f8 00000e 00 AX 0 0 4
[15] .rodata PROGBITS 0000000000400608 000608 000011 00 A 0 0 4
[16] .eh_frame_hdr PROGBITS 000000000040061c 00061c 000024 00 A 0 0 4
[17] .eh_frame PROGBITS 0000000000400640 000640 00007c 00 A 0 0 8
[18] .ctors PROGBITS 0000000000600e28 000e28 000010 00 WA 0 0 8
[19] .dtors PROGBITS 0000000000600e38 000e38 000010 00 WA 0 0 8
[20] .jcr PROGBITS 0000000000600e48 000e48 000008 00 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000600e50 000e50 000190 10 WA 6 0 8
[22] .got PROGBITS 0000000000600fe0 000fe0 000008 08 WA 0 0 8
[23] .got.plt PROGBITS 0000000000600fe8 000fe8 000028 08 WA 0 0 8
[24] .data PROGBITS 0000000000601010 001010 000014 00 WA 0 0 8
[25] .bss NOBITS 0000000000601028 001024 000010 00 WA 0 0 8
[26] .comment PROGBITS 0000000000000000 001024 00002b 01 MS 0 0 1
[27] .shstrtab STRTAB 0000000000000000 00104f 0000fe 00 0 0 1
[28] .symtab SYMTAB 0000000000000000 0018d0 000618 18 29 46 8
[29] .strtab STRTAB 0000000000000000 001ee8 0001f9 00 0 0 1
Cours de M1
La section .text
.text : section qui contient le code (partie exécutable X)
fonlupt@moria:~/enseignement/compilation$ objdump -d --section .text test
0000000000400410 <_start>:
400410: 31 ed xor %ebp,%ebp
400412: 49 89 d1 mov %rdx,%r9
400415: 5e pop %rsi
400416: 48 89 e2 mov %rsp,%rdx
400419: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
40041d: 50 push %rax
40041e: 54 push %rsp
40041f: 49 c7 c0 20 05 40 00 mov $0x400520,%r8
400426: 48 c7 c1 30 05 40 00 mov $0x400530,%rcx
40042d: 48 c7 c7 f4 04 40 00 mov $0x4004f4,%rdi
400434: e8 c7 ff ff ff callq 400400 <__libc_start_main@plt>
Cours de M1
La section .data
La section .data contient toutes les variables créées à
l'initialisation
fonlupt@moria:~/enseignement/compilation$ objdump -d --section
.data test
...
0000000000601020 <var_init>:
601020: 0a 00 00 00 → 10
Cours de M1
La section .bss
BSS signifie Block Started Symbol. C'est une section
où sont définies toutes les variables non initialisées.
objdump -d --section .bss test
[...]
37: 0000000000600e30 0 OBJECT LOCAL DEFAULT 18 __CTOR_END__
38: 00000000004006b8 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__
39: 0000000000600e48 0 OBJECT LOCAL DEFAULT 20 __JCR_END__
40: 00000000004005c0 0 FUNC LOCAL DEFAULT 13 __do_global_ctors_aux Value =
41: 0000000000000000 0 FILE LOCAL DEFAULT
[..;] adresse
48: 0000000000400410 0 FUNC GLOBAL DEFAULT 13 _start
49: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
50: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
51: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
52: 00000000004005f8 0 FUNC GLOBAL DEFAULT 14 _fini
53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
54: 0000000000400608 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
55: 0000000000601010 0 NOTYPE GLOBAL DEFAULT 24 __data_start
56: 0000000000601020 4 OBJECT GLOBAL DEFAULT 24 var_init
57: 0000000000601038 4 OBJECT GLOBAL DEFAULT 25 global_init
58: 0000000000601018 0 OBJECT GLOBAL HIDDEN 24 __dso_handle
59: 0000000000600e40 0 OBJECT GLOBAL HIDDEN 19 __DTOR_END__
60: 0000000000400530 137 FUNC GLOBAL DEFAULT 13 __libc_csu_init
61: 0000000000601024 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
62: 0000000000601040 0 NOTYPE GLOBAL DEFAULT ABS _end
63: 0000000000601024 0 NOTYPE GLOBAL DEFAULT ABS _edata
64: 00000000004004f4 34 FUNC GLOBAL DEFAULT 13 main
Cours de M1
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000006bc 0x00000000000006bc R E 200000
LOAD 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001fc 0x0000000000000218 RW 200000
DYNAMIC 0x0000000000000e50 0x0000000000600e50 0x0000000000600e50
0x0000000000000190 0x0000000000000190 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME [..] manque d'autres segments
Exécution direct
gcc -g -o test test.c
gdb test Option de débogage
Quelques explications
17 segments aka VMA (Virtual Memory Address)
VMA n°1 correspond au segment n°2
Cours de M1
Code à 3 adresses
Un programme écrit en code à 3 adresses est une
séquence d'instructions numérotées
affectation binaire x := y op z op : + - * ET OU ...
affectation unaire x := opu z opu : not, -, ...
Affectation simple x:= y
copie (num) x:=y
branchement inconditionnel (num1) aller a (num2)
branchement conditionnel (num1) si x oprel y aller a (num2)
lecture (num) lire x
écriture (num) écrire y
Le nombre de registres est illimité
Cours de M1
Représentation du code
Un quadruplet est une structure composée de 4
champs appelés op, arg1, arg2 et résultat
Par exemple :
x := y + z +,y,z,x
x := -y moinsu, y, , x
x := y :=,y, , x
aller_a 10 aller_a, , 10
Si a<b aller_a 10 <, a, b, 10
Cours de M1
Aff → id:= E
E→E+T|T
T → T*F | F
F → (E) | cst | id
Sémantique de id:= E
On évalue (calcule) la valeur de E avec l'état des
variables avant l'affectation
On place cette valeur dans la variable id.
Il faut que la valeur de E soit rangée quelque part : un emplacement
mémoire est nécessaire pour stocker la valeur de l'expression.
Cours de M1
Génération de code
Attribut pour une expression E, T, F : E.place, T.place : un
emplacement mémoire qui contiendra la valeur de E, T ou F.
Nous supposons défini les 4 fonctions suivantes :
newtemp() : revoie un emplacement mémoire libre ;
place(id.lex,TS) : renvoie l'entrée correspondant à l'identificateur
id.lex dans la table des symboles
ajoute(id.lex,T.type,TS) : alloue un emplacement mémoire à id.lex
et crée l'entrée correspondante
gen(op,a,b,r) : ajoute le quadruplet (op,a,b,) à une liste
initialement vide
Cours de M1
F → (E)
{F.place ← E.place}
F → id
{ F.place ← (id.lex, TS)}
F → cst
{ F.place ← newtemp() ;
gen(:=,cst, , F.place)}
Cours de M1
E.place ← newtemp()
E+T gen(+, E(1).place, T.place, E.place)
E.place := T.place
T
T.place := F.place
F
cst T.place ← newtemp()
F.place ← newtemp() T*F gen(*, T(1).place, F.place, T.place)
T.place := F.place
gen(:=, 2, , F.place) F F.place ← newtemp()
cst
gen(:=, 5, , F.place)
F.place ← newtemp()
gen(:=, 3, , F.place)
Cours de M1
Code généré
En supposant que les T.place:= 103
[103]:= [101]*[102] //15
adresses commencent à
E.place:= 104
100
[104]:= [100] + [103] //17
F.place:= 100
place(x,TS):= 104
[100] := 2
T.place:= 100
E.place:= 100
F.place:= 101
[101]:= 3
T.place:= 101 T(1)
F.place:= 102
[102]:= 5
Cours de M1
Exemple
Traduction de a := b*c/(2-d)
empiler b
d
empiler c
2
c empiler 2
b empiler d
a sub
div
mul
:=
a *
b /
c -
2 d
Cours de M1
Remarques
Pour les langages de triplet ou quadruplet, il faut
construire l'arbre syntaxique complet avant de générer
le code (parcours en profondeur d'abord) tandis que
pour les langages à pile, on construit et on génère le
code au fur et à mesure : il s'agit d'une traduction
dirigée par la syntaxe (parcours postfixé)
Ce concept de machine à pile est très utilisé de nos
jours (introduction en 1972 par N. Wirth)
Permet de calculer une expression sans variables temporaires
Facile à transformer en assembleur
Cours de M1
Structuration de la mémoire
Le bas de la pile est réservé pour l'allocation de
variables globales, c'est-à-dire que la première
instruction d'un programme P-code réserve une partie de
la pile (incrémentation de SP en début de programme)
Les éléments de la pile sont des simples entiers
Les adresses sont des adresses absolues dans la pile
Cours de M1
Structures de la P-machine
Mémoire de la P-machine code de la P-machine
libre
SP
(Stack Pointer)
adresse croissante
réservé aux
variables ADD PC
(program
counter)
Cours de M1
Instructions de la P-machine
Les instructions de la P-machine sont très simples. En fait,
ce sont pratiquement des instructions en assembleur ;
on les appelle généralement mnémoniques.
Instructions en séquence
Programme qui lit deux entiers et affiche le carré de
leur somme
INPUTI
INPUTI
ADD
DUP
MUL
PRN
HLT
Cours de M1
Exemple
Écrire un programme qui lit un entier n et affiche n2 si n>=0 et -n si n<0
INPUTI
DUP
SGN – laisse 1 si <0, 0 si >=0
PUSH 1
EQL
BZE caspositif
PRINTI
BRN fin
label caspositif
DUP
MUL
PRINTI
label fin
PUSH 10
PRINTI
HLT
Cours de M1
Allocation de la mémoire
Au niveau de notre langage PP, on ne se préoccupe
pas de la gestion de la mémoire (comme dans tous
les langages de haut niveau). On manipule la
mémoire par l'intermédiaire de symboles.