Info
Info
Pierre-Yves Schobbens
Bureau 409
081/72 49 90
rue Grandgagnage 21
5000 Namur
[email protected]
Plan
0. Introduction
1. Analyse lexicale : Langages réguliers
2. Analyse syntaxique : Langages non contextuels
3. Analyse statique (p.ex. typage): Grammaires attribuées
4. Eléments de génération de code
5. Sémantique
Références
• Grune, Bal, Jacobs, Langendoen « Compilateurs »,
Dunod, 2002.
• Aho, Sethi, Ullman : « Compilateurs : principes,
techniques et outils », InterEditions, 1998, BUMP
# 1 412/037
• R. Wilhem, D. Maurer : « Les compilateurs :
théorie, construction, génération », Masson, 1994,
BUMP # 1 412/030
Chapitre 0. Introduction
Types de langages
• Les techniques de ce cours s’appliquent à:
– des langages de programmation : Pascal, Ada, …
– des langages de commande : JCL, Sh, Csh, …
– des langages de description de données: SQL (DDL), XML, ...
– des langages de spécification : VDM, Z, Albert, ...
– des langages de formulaires : EDIFACT, CGI (WWW), ...
– des formats de messages (réseaux)
– des langages de documents : EQN, HTML,
Paradigmes de langages de programmation
1. impératifs : Algol, Pascal, Ada, Fortran, Cobol, Modula, C
axés sur l’affectation
2 extrêmes
1. Les interpréteurs
- lisent le programme au fur et à mesure
les programmes peuvent être créés dynamiquement
2. Les compilateurs
- traduisent l’ensemble du programme
soit - en langage d’une machine concrète 486, P6, Power
ou - en langage d’une machine abstraite SECD, WAM, JVM,
P-MACHINE
Rôle d’un compilateur
Code source compilateur Code cible
compilateur
d’implémentation
Code d’implémentation du compilateur
Générateur de
compilateur
(Flex, Bison)
Spécification
D25
1. analyse lexicale
reconnaître les « mots »
2. crible
avant
C Ada
Front-ends (1-4) C++ Pascal
ANSI 95
partie avant
Middle-end (5)
Partie centrale
Automate fini
générateur
d’analyseur
lexical
Expressions régulières
Plan
1. Langages et expressions régulières
3. Régulier ou pas?
D112
Définitions
Note : convient pour les langages séquentiels (parlé, écrit) mais pas
pour des langages graphiques
Propriétés
• x.=x=.x neutre
• x . (y. z) = (x . y) . z associativité
• Ces propriétés sont complètes
D113
Ordres
• X est un préfixe de Y : Y commence par X
– « 12 » est un préfixe de « 1234 »
• X est un suffixe de Y : Y finit par X
– « 34 » est un suffixe de « 1234 »
• X est une sous-chaîne de Y : X s’obtient par
suppression d’un suffixe et d’un préfixe
– « 23 » est une sous-chaîne de « 1234 »
D114
L1 . L ⎜ ∈ L1
2 = {x . y x et ∈ yL2} concaténation
L = ∪
n≥0
a= { " a " } singleton
Propriétés
D115
Expressions régulières
= les expressions écrites avec
aussi noté ou +
souvent omis
une constante décimale de C est, soit un 0 seul, soit un chiffre significatif suivi d’autant
Ln = L. L. ... L
} où n est un naturel n
n fois
L+ = L L*
L? = L
[n-m] n n+1 m
L = L L ... L
D135
2. Automates finis
caractère courant
bae mot à reconnaître
q0 b q1 a q2 b q3 q4
a
état initial état courant
c état final
ou accepteur
D136
Définition
Fonctionnement
• Un automate accepte un mot m si on peut trouver un
chemin c:
– Qui commence dans l’état initial
– Qui finit dans un état final
– Qui suit des transitions
– La concaténation des étiquettes des transitions donne m
• Les mots acceptés forment le langage de l’automate L(A)
Exemple: constante naturelle décimale de C
[1-9]
[0-9]
Exemple : multiples de 2 en notation binaire
0
1
a
[a-z 0-9]
a z
[a-z]
z notation :
0
9
Exemple : Multiples de 7
a
r s
ssi
début
début a
Pour R2
début
Pour R1 Pour R2
début
Pour R1
(c) Construction de l’automate de fermeture pour deux expressions régulières
Exemple : constante décimale de C
0
1 2
0 F
9 9
4
3
5 ...
0
1
Problèmes
• 1. L’automate contient des -transitions
• 2. Il n’est pas déterministe: moins efficace
• 3. Il y a beaucoup trop d’états (80 dans l’exemple)
D138
Automate déterministe
• Un automate fini est déterministe, intuitivement
s’il ne peut se comporter que d’une façon pour un
mot donné :
– Il n’a pas de transition-
– Étant donné un état de départ et un caractère lu, il y a au plus 1
état d’arrivée
• Il est complet si, étant donné un état de départ et un
caractère lu, il y a au moins un état d’arrivée
Idée de la déterminisation
début 1 2 ... k
S1
1 2 ... k
S2
Déterminisation
9 9
4
3
5 ...
0
1
État initial
Constantes décimales de C déterminisé
0 0
9
0
0
9 9
9
Exemple
Reconnaître une suite de 1, 2, 3 où le dernier chiffre est apparu précédemment.
Expression régulière : * (1 * 1 | 2 * 2 | 3 * 3), où
1,2,3
1
1,2,3 1 1
1,2,3
2 2
Automate : 0 2 4
1,2,3
3 3
3
Exemple : déterminisation
1
0,1,4
3 1/2
1 0,1
2 1/2
2,4
0,1 2
0,1,2 3 3
1 3 2 0,1 1/2/3 1/2/3
1 2,3
0,1,3 0,1
2 0,2 1 1/3 2,3,4
0 2
1/3
0,2,4 1 0,1 2
3 3 1 3,4
1 3 1
2
0,3 0,2,3
2/3 2/3
3 2 0,2
3,4
0,3,4
Intuition : Les états marqués 1 (ou 2, 3) signifient «on a rencontré un 1 (ou 2,3)».
L’état 4 signifie : on a rencontré un 2° caractère identique, donc
on peut terminer.
Algorithme de minimisation
retirer les états non accessibles
compléter l’automate
Idée : On met les états dans des classes différentes quand on voit qu’ils ont
un comportement différent.
: partition courante les états sont groupés en classes
:= {F, Q \ F} au départ, on crée deux classes: les finaux et les non-finaux
répéter
pour chaque K,
K’
:= ( \ {K}) {{Ki}}
tous les états de Ki arrivent dans la même classe pour chaque caractère
Non finaux
0 Finaux
0 0
9
0
0
9 9
9
: partition des états
Exemple de minimisation
« Constante décimale de C » Fusion d’états
1 0
9
9
S5
si S3 est final
Note: ces équations ont plusieurs solutions, c'est la plus petite qui nous intéresse.
automate fini expression régulière (2)
S2 0-9
S0 = 0 . S1 | (1 | 2 | … | 9) . S2
S1 =
S2 = | (0 | 1 | ... | 9) . S2
Exemple (2)
2° étape : résolution par triangulation
1) éliminer S1 :
S0 = 0 | [1-9] . S2
S2 = | [0-9] . S2
2) résoudre S2 :
S2 = [0-9]* .
3) éliminer S2 :
Complémentation
• On calcule un automate qui accepte le complément de A.
• Algorithme:
1. Déterminiser A;
2. Le compléter:
1. Ajouter un état mort;
2. Ajouter des transitions vers cet état (si car. manque).
3. Propriété: chaque chaîne amène dans un seul état.
3. Inverser les états finaux et non-finaux.
Exemple de complémentation
complété : il y a une flèche pour
chaque caractère de = [0-9]
Automate déterministe
[0-9]
0 [0-9]
S0 S1
[1-9]
[0-9]
S2
z
si k est le nombre d’états
une chaîne plus longue
doit faire une boucle.
Exemple
• Le langage {0n 1n } n’est pas régulier :
– Il faut retenir le nombre de 0, qui peut être grand : l’information
n’est pas finie.
– S’il était régulier, on aurait un automate; Soit k son nombre
d’états. 0k 1k doit passer par une boucle :
• Si la boucle ne contient que des 0, on accepte des chaînes avec plus
de 0 que de 1.
• Si la boucle ne contient que des 1, on accepte des chaînes avec plus
de 1 que de 0.
• Si la boucle contient des 0 et 1, alors on accepte des chaînes où 0 suit
1.
Chapitre 2 : Analyse Syntaxique
Rôle : L’analyseur syntaxique retrouve la structure (arbre)
grammaticale du programme.
Grammaire non contextuelle
Pour le langage C la catégorie < programme > n’existe pas, le symbole de départ
(ce qu’on peut compiler) est <déclarations>.
D192 W254
Notations
• Terminaux: les minuscules, chiffres, symboles
d’opérateurs, symboles en gras
• Non-terminaux: majuscules: A,B,C,S ; entre
<crochets>
• Symboles: X, Y, Z
• Chaînes de terminaux: u,v,w
• Chaînes de symboles:
Historique
- Pourquoi « non contextuel » ? (context-free)
Comp : < instr. composée > : : = begin < suite d’instructions > end
produit
< programme > ⇒ * program tutu
begin
end.
Simplifications
• Un non-terminal est inaccessible s’il ne fait
partie d’aucune proto-phrase.
• Il est improductif s’il ne produit aucune
chaîne de terminaux.
• On peut retirer ces non-terminaux de la
grammaire sans changer le langage : ils ne
servent à rien.
Algorithme: Productif
Arbre de dérivation : S
p id R ; D C .
bIe
Ambiguité
Déf. : une grammaire est ambiguë s’il y a une phrase qui a plusieurs arbres de dérivation.
A éviter quand on veut générer un analyseur.
Peut être utile comme grammaire abstraite.
< instruction > : : = if < expr > then < instruction > else < instruction>
| if < expr > then < instruction >
a
fenêtre de lecture (avance d’un caractère)
a sommet
= état courant
•
états à caractère à états à empiler
dépiler du trouver dans sur la pile
sommet de la fenêtre
la pile
Fonctionnement
< instr > : : = if < expr > then < instr > else < instr > fi
while < expr > do < instr > od
begin < instr > end
PREMIER( ) PREMIER( ) =
pour tout qui peut suivre A dans une dérivation gauche: S g* wA
Algorithme : PREMIER
PREMIER(A) donne les possibilités de début de A,
tronqués à k caractères.
Pour chaque non-terminal A, le tableau P[A] contient
un sous-ensemble de PREMIER(A), initialement vide.
pour i := 1..n
pour j := 1..i-1
remplacer Aj par sa définition dans Ai ::= Aj
remplacer Ai ::= Ai par Ai ::=Ai’ , Ai’ ::=Ai’|
D202
Exemple
Élimination de la récursivité gauche
Grammaire d’expressions: Résultat
i=1 Ai::=Ai |
E ::= E + T | T E ::= T E’
i=2 Ai::=Ai | E’ ::= +T E’ |
T ::= T * F | F T ::= F T ’
T’ ::= * F T’ |
F ::= ( E ) | id F ::= ( E ) | id
Ordre: 1: E, 2: T, 3: F
D203
Exemple 2
Élimination de la récursivité gauche
Grammaire : calcul : Résultat
A ::= B b | C e A ::= B b | C e
i=2 Ai::=Aj Ai::=Ai |
B ::= A a | d B ::= B b a | C e a | d B::= CeaB’|dB’
B’::=baB’|
C ::= A c | d C ::= B b c | C e c | d C ::= dB’bcC’|dC’
C ::= CeaB’bc|dB’bc|Cec|d C’::=eaB ’bcC’|ecC’|
Ordre: 1: A, 2: B, 3: C
Factorisation
• Idée : postposer le choix en regroupant les
parties communes (facteurs)
• Exemple: I ::= if E then I | if E then I else I
devient I ::= if E then I C
C::= | else I
Exemple : PREMIER
Pour la grammaire des expressions sans récursivité gauche:
0 : S ::= E 3 : E’ ::= + E 6 : T’ ::= * T
1 : E ::= T E’ 4 : T ::= F T’ 7 : F ::= (E)
2 : E’ ::= 5 : T’ ::= 8 : F ::= id
k vaut 1. Les itérations de PREMIER sont listées en colonnes. On visite les productions en
commençant par le fin (de 8 à 1). Par exemple, on peut lire dans la deuxième entrée de la
première colonne, qu’après la visite des productions 8 puis 7, l’ensemble P du non-terminal
F contient les symboles id et (.
P ne donne pas le début des productions 5 et 2: il faut connaître le début de ce qui est
derrière E’ et T’, calculé par SUIVANT.
Suivant
• Quand PREMIER() est trop court, on regarde derrière
avec SUIVANT(A) : ce qui peut suivre A.
• Algorithme :
Le tableau des suivants est initialisé à vide
SUIVANT[S] := {$} /* le marqueur de fin */
tant que SUIVANT change
pour chaque règle A ::= B
ajouter PREMIER() . SUIVANT[A](tronqué à k) à SUIVANT[B]
Si ceci élimine les conflits, on dit que la grammaire est fortement LL(k).
Une grammaire LL(1) est fortement LL(1).
Exemple : SUIVANT
Pour la grammaire des expressions sans récursivité gauche:
0 : S ::= E 3 : E’ ::= + E 6 : T’ ::= * T
1 : E ::= T E’ 4 : T ::= F T’ 7 : F ::= (E)
2 : E’ ::= 5 : T’ ::= 8 : F ::= id
PREMIER ne suffit pas pour E’, T’
On calcule SUIVANT:
S: {$}
0: E: {$}
1: T: {+,$} E’:{$}
4: F: {*,+,$} T’:{+,$}
7: E: { ),$}
1: T: {+,),$} E’:{ ),$}
4: F: {*,+,),$} T’:{+,),$}
Analyseur descendant à pile
• Si la grammaire est LL(k) : à partir du non-terminal à
développer (derrière le • en sommet de pile) et des k
caractères d’entrée, on sait quelle règle appliquer. On le
met dans une table.
• L’analyseur fait :
– une lecture si le • est devant un terminal:
On avance la fenêtre et le point dans l'item.
– une réduction si le • est en fin de d'item:
On dépile l'item.
– une expansion si le • est devant un non-terminal:
On empile l'item d’après la table.
D215 Exemple
table LL(1) pour expressions
( ) + * id #
E (E → TE') erreur erreur erreur (E → TE') erreur
E' erreur (E'→ ε) (E'→ + E) erreur erreur (E'→ ε)
T (T → FT') erreur erreur erreur (T → FT') erreur
T' erreur (T'→ ε) (T'→ ε) (T'→ * T') erreur (T'→ ε)
F (F → (E)) erreur erreur erreur (F → id) erreur
S (S → E) erreur erreur erreur (S → E) erreur
D216 Trace de l’analyse LL(1) d’une expression
Contenu de la pile Chaîne d'entrée
$ [S •E] id * id $
$ [S •E] [E •TE'] id * id $
$ [S •E] [E •TE'] [T •FT'] id * id $
$ [S •E] [E •TE'] [T •FT'] [F •id] id * id $
$ [S •E] [E •TE'] [T •FT'] [F id•] * id $
$ [S •E] [E •TE'] [T F•T'] * id $
$ [S •E] [E •TE'] [T F•T'] [T' •*T] * id $
$ [S •E] [E •TE'] [T F•T'] [T' *•T] id $
$ [S •E] [E •TE'] [T F•T'] [T' *•T] [T •FT'] id $
$ [S •E] [E •TE'] [T F•T'] [T' *•T] [T •FT'] [F •id] id $
$ [S •E] [E •TE'] [T F•T'] [T' *•T] [T •FT'] [F id•] $
$ [S •E] [E •TE'] [T F•T'] [T' *•T] [T F•T'] $
$ [S •E] [E •TE'] [T F•T'] [T' *•T] [T F•T'] [T' •] $
$ [S •E] [E •TE'] [T F•T'] [T' *•T] [T FT'•] $
$ [S •E] [E •TE'] [T F•T'] [T' *T•] $
$ [S •E] [E •TE'] [T FT'•] $
$ [S •E] [E T•E'] $
$ [S •E] [E T•E'] [E' •] $
$ [S •E] [E TE'•] $
$ [S E•] $
$ $
D215
Gestion de la pile
On peut représenter les items en mettant les non-terminaux
non encore lus en ordre inverse sur la pile.
L’analyseur fait :
– une lecture si le sommet est un terminal:
On avance la fenêtre et on dépile.
– une expansion si le sommet est un non-terminal:
On empile en ordre inverse la partie droite de la règle trouvée
d’après la table.
– les réductions deviennent implicites.
Analyseur récursif prédictif
• On peut employer la pile implicite de
récursion :
– le non-terminal de gauche = procédure appelée
– règles = corps de la procédure
– prédiction = test du fichier d’entrée
– point = adresse de retour
Exemple : expression
T E’
F T’ E
+
T E’
x y
F T’
id + id
x + y
x 1
*
if < expr > then < instr > else < var > := < expr > end if
manche
if < expr > then < instr > else < instr > end if
...
E + T
[E → .E + T] → [E → E. + T] → [E → E + .T] → [E → E + T. ]
S1
T
[E → .T] → [E → T. ]
T * F
[T → .T * F] → [T → T. * F] → [T → T * .F] → [T → T * F. ]
S2
F
[T → .F] → [T → F. ]
S3
( E )
[F → .(E)] → [F → (.E)] → [F → (E.)] → [F → (E).]
id
[F → .id] → [F → id.]
S5
S
Préfixe viable
ssi
Exemple : expressions
L’automate LR(0) donne :
+ T
S1 S6 S9 Les états S1, S2, S9 sont impropres :
id
ils contiennent des conflits LR(0) :
F
E
S5
cette grammaire n’est pas LR(0).
( +
id
F id *
S0 S3
id
( F
E )
T ( S4 S8 S11
T (
* F
S2 S7 S10
Exemple : expressions : états LR(0)
S0 = { [S → .E], S5 = {[F → id.]}
[E → .E + T],
[E → .T], S6 = {[E → E + .T],
[T → .T * F], [T → .T * F],
[T → .F], [T → .F],
[F → .(E)], [F → .(E)],
[F → .id] } [F → .id] }
S1 = { [S → E.], S7 = {[T → T * .F],
[E → E. + T] [F → .(E)],
[F → .id] }
S2 = { [E → T.], S8 = {[F → (E.)],
[T → T. * F] } [E → E. + T] }
S3 = { [T → F.] } S9 = {[E → E + T.],
[T → T. * F] }
S4 = { [F → (.E)] { →
S10 = [T T * F.] }
[E → .E + T],
[E → .T], { → (E).]
S11 = [F }
[T → .T * F],
[T → .F],
[F → .(E)], Les états S1, S2, S9 sont impropres :
[F → .id] }
ils contiennent des conflits LR(0) :
cette grammaire n’est pas LR(0).
Exemple C q5
q1
a
b D
q2
La grammaire:
S ::= C | D
b e q7
C ::= a C | b
q0 e q3
D ::= a D | e
C
n’est pas LL(k) : on ne peut pas choisir entre C et D q4
car il peut y avoir des a d’abord dans les deux cas.
Elle est LR(0) car on peut choisir après lecture. S D
q6
Son automate déterministe caractéristique q8
ne contient pas de conflits LR(0).
q0 = {[S’ ::= •S], [S::= •C], [C::= •aC], [C::= •b], [S::= •D], [D::= •aD], [D::= •e]}
q1 = {[C::= a•C], [C::= •aC], [C::= •b], [D::= a•D], [D::= •aD], [D::= •e]}
q2 = {[C::= b•]}
q3 = {[D::= e•]}
q4 = {[S::= C•]}
q5 = {[C::= aC•]}
q6 = {[S::= D•]}
q7 = {[D::= aD•]}
q7 = {[S’::= S•]}
D256 W352
SLR
• Pour résoudre les conflits LR(0), on regarde
SUIVANT(A), où A est le côté gauche de la
règle à réduire.
D237 W353
Exemple : Affectations en C
S::=L=R| R
L : : = *R | id
R::=L
Exemple : Affectations en C :
automate SLR(1)
S [S’ S • ]
[S’ • S]
[S • L = R]
[S • R] S3
[L • * R] R [S R • ]
[L • id]
[R • L] *
id S4
S2 L *
S5 [L * • R]
[S L • = R] id [R • L]
[R L • ] [L id • ]
[L • *R]
[L • id]
S6 = id *
L R
[S L = • R] S8 S7
[R • L] [R L • ] [L *R • ]
[L • * = R] L
[L • id]
S9 R S2 est
conflictuel : SUIVANT(R) contient =.
[S L = R • ] Cette grammaire n’est pas SLR(1).
D260 W350
Fermeture
fonction Fermeture(I);
répéter
pour chaque item LR(k) [A•B, u] de I
et chaque chaîne v de PREMIER(u)
pour chaque règle B
ajouter [B •, v] à I
jusqu’à stabilisation
Exemple : expressions : états LR(1)
+ T
S14 S20
id +
+ T id
S1 S6 S9
id S12
(
F
E *
S5 id
F S18
id F id
F
F ( *
S0 S3 E )
id ( S15 S13 S19
( (
T
E )
S11 F
S4 S8 *
T T S17 S16 S21
(
* F
S2 S7 S10
Notation: Les prévisions des items LR(1) qui ont le même item sans prévision sont regroupés.
D270 W
LALR
• on garde les états LR(0)
• les prévisions sont la fusion des prévisions LR
ayant le même cœur (état LR(0) )
• Pour calculer les prévisions de [B •] :
– trouver les origines de la règle : expansions [B •]
– trouver les sources de l’expansion [A•B, u]
– calculer ce qui se trouve derrière B: PREMIER( u)
– faire l’union
– en cas de boucle: répéter jusqu’à stabilisation
Exemple : affectations en C
S0
S [S’ S • ]
[S’ • S , {$} ]
[S • L = R]
[S • R , {$} ] S3
[L • * R] R [S R • ]
[L • id]
[R • L , {$} ] *
id S4
S2 L *
S5 [L * • R]
[S L • = R] id [R • L]
[R L •, {$} ] [L id • ]
[L • *R]
[L • id]
S6 = id *
L R
[S L = • R] S8 S7
[R • L] [R L • ] [L *R • ]
[L • * = R] L
[L • id]
[S → S7 = {[T →
( S1 = { E.], {$} T * .F],
Etats impropres
[E → E. + T] {$, } +} [F → .(E)],
[F → .id] }
S2 = { [E → T.], {$, +, ) } S8 = {[F → (E.)],{), +}
( [T → T. * F] } [E → E. + T] }
S3 = { [T → F.] } S9 = {[E → E + T.],{$, +, )}
( [T → T. * F] }
S4 = { [F → (.E)] { →
S10 = [T T * F.] }
[E → .E + T],{ ), +}
[E → .T], { ), +} { → (E).] }
S11 = [F
[T → .T * F],
[T → .F],
[F → .(E)], Tous les conflits sont :résolus
[F → .id] } la grammaire est LALR(1).
Grammaires ambiguës
• Une grammaire ambiguë peut être plus
naturelle
• Elle provoque toujours des conflits
• Les conflits peuvent être résolus par des
règles de précédence et d’associativité, sans
augmenter le nombre d’états
Exemple : expressions
La grammaire la plus naturelle est :
E ::= E + E | E * E | ( E ) | id
qui est ambiguë, mais peut servir de grammaire abstraite.
En Pascal, on a :
<conditionnelle > ::= if E then I else I | if E then I
cette grammaire est ambiguë, mais sera analysée correctement
grâce à la préférence au décalage.
Langages contextuels
• Certains langages ne peuvent pas être traités avec
les grammaires non-contextuelles.
• Méthodes:
– 1. un langage est non-contextuel ssi il peut être reconnu
par un programme dont la seule structure de données
infinie est une pile.
Lemme de pompage
– 2. Si un langage est non-contextuel, alors il a une longueur k :
toute chaîne plus longue que k se décompose en 5 parties u v w
x y, et les parties v et x peuvent être répétées le même nombre de
fois: u vn w xn y fait aussi partie du langage. Les parties v w x
ensemble ont une longueur de k au plus.
• En pratique on l’emploie dans l’autre sens : on trouve une série de
phrases (une pour chaque k) dans le langage et, quelle que soit la
S décomposition, le lemme nous obligerait à inclure aussi des phrases
incorrectes : on sait alors que le langage ne peut être décrit par une
grammaire BNF.
A
A
u v w x y
Exemple
ak bk ck se décomposerait en u v w x y
v w x est de longueur k au plus
Sémantique statique
Rôle :
if2
grt
E E Ass Ass
E E T E E E E
T F F T T
F F F
1. Typage
• Portée
• Systèmes de types
– équivalence
– coercition
– polymorphisme
– surcharge
Portée
• Les types se déduisent des déclarations
• Les déclarations sont valides dans leur portée
• Elles peuvent être cachées par d’autres
déclarations
• Chaque langage a ses règles de portée / visibilité.
Portée : exemple de Pascal
• En Pascal, la portée d’une déclaration est le bloc
dans lequel elle se trouve
• Il ne peut y avoir qu’une déclaration de ce nom
dans un bloc
• Une nouvelle déclaration du même nom dans un
bloc imbriqué cache la déclaration extérieure
• Les paramètres sont traités comme des variables
locales
Portée : exemple d’Ada
• La portée d’une déclaration commence à la
déclaration et se termine à la fin du bloc
• Si le bloc est un package, elle comprend de
plus les packages qui importent celui-ci
• Une nouvelle déclaration ne cache un nom
que si le type est le même
Portée : implémentation
• Pour chaque bloc on crée une table de symboles
• Chaque table a un lien vers le bloc englobant
• Chaque déclaration ajoute une entrée dans le bloc
(souvent avec des infos: type, taille, déplacement
dans le bloc d’activation, etc.)
• Chaque occurrence d’identificateur est recherchée
dans la liste des blocs en appliquant les règles du
langage.
D381
Expressions de types
• expressions de type : typiquement :
– des types de base :
• booléen, entier, réel, …
– des noms de types
– des constructeurs de type :
• tableaux
• records (structures, enregistrement)
• fonctions
• pointeurs
D383
Règles de typage
• Chaque langage a ses règles
• Parfois les règles demandent une
vérification dynamique (à l’exécution)
• Un système de typage est sain si toutes les
valeurs utilisées à l’exécution sont du type
calculé statiquement
• Un langage est typé si le typage impose des
conditions sur les programmes.
Exemple : Pascal
• le typage de Pascal n’est pas sain :
– on n’est pas obligé de vérifier que les indices de
tableaux restent dans les bornes prévues
– les variants peuvent être modifiés sans contrôle
– les valeurs initiales ne sont pas nécessairement
du bon type
Exemple de règles
• Si a est un tableau, alors dans indexation sur
a, les expressions des indices doivent avoir
le même type de base que le type d’indices
apparaissant dans la déclaration de a.
– exemple:
var a : array[1..10] of integer;
i : -10.. 0 ;
i := a[i] (* correct car le type de base est integer , mais pas recommand
D389
Equivalence structurelle
• deux types sont équivalents si leur structure
est la même (intuitivement, s’ils admettent
les mêmes valeurs)
• vérification en parcourant la structure des
types
• si cycles possibles, attention à la
terminaison
Algorithme
structure globale : partition des nœuds du graphe,
notée t1 == t2 (les types déjà prouvés équivalents)
equiv(t1, t2)
si t1 == t2 alors renvoyer vrai
sinon si constructeur(t1) <> constructeur(t2)
alors renvoyer faux
sinon si pour tout i, equiv (fils(i,t1),fils(i,t2)))
alors union(t1, t2); renvoyer vrai
Particularités de typage
• surcharge
• coercition
• polymorphisme
Surcharge (ou « polymorphisme ad hoc »)
Résolution de la surcharge
Soit vis(k) l’ensemble des définitions de k visibles, avec leurs types
L’algorithme élimine les types d’abord en montant dans l’arbre puis en descendant.
La contrainte est que les arguments doivent avoir le type attendu par la fonction.
Coercition
Def : Conversion implicite entre types (insérée par le compilateur)
Polymorphisme (paramétrique)
Une définition est polymorphique si elle peut s’appliquer de la même
façon à tout type.
Exemple en ML
head : ‘a list ‘a renvoie le 1er élément d’une liste
où ‘a est une variable qui représente n'importe quel type
en Pascal :
nil : ‘a
Résolution du polymorphisme
• Pour chaque occurrence d’une fonction polymorphe, on introduit une
copie de son type avec de nouvelles variables de type
• Pour chaque variable (de type inconnu), on introduit une nouvelle
variable de type. La portée des variables est locale à une ligne.
• Les variables de type peuvent recevoir des valeurs particulières dans
une substitution
• On résout les contraintes par unification, en calculant la substitution
qui permet de les satisfaire. Il y a un résultat le plus général, le type
principal.
D412
Exemple 2 (ML)
‘ta ‘a list‘tl2 ‘tl2
fun append ( [ ] ) (l2) = l2
ajout (table, id, def) : table - crée une erreur si id est déjà dans table
- dans cet exemple, def est le type de la variable ;
on peut mettre d ’autres infos dans la table (le déplacement, etc.)
vide : table
La table sera synthétisée par les déclarations et héritées par les instructions.
D316
Exemple : types dans les
déclarations
typeh: attribut hérité de L
tab: attribut synthétisé de L: table de symboles (nom, type)
typeh L tab : T
id , typeh L tab
id
x, y, z : bool;
Exemple : typage Pascal
Calcul du type d’une expression numérique en Pascal
(avec surcharge à la place de coercition) dans la grammaire abstraite
then int
else real
| (E1)
E0 . type = E1 . type
Exemple : arbre abstrait
Les grammaires attribuées peuvent aussi servir à calculer
l’arbre abstrait comme attribut de l’arbre concret.
cons(e,g,d) construit un nœud d’arbre à 2 fils, g et d.
a est un attribut synthétisé représentant l'arbre abstrait
Règles de grammaire Règles d’attribution
E ::= E1 +T E.a = cons(+, E1 .a , T.a)
E ::= T E.a = T.a
T ::= ( E ) T.a = E.a
T ::= id T.a = id
Pour un DAG, cons renvoie le nœud (si) existant
Ordres d’évaluation des attributs
• ordre fixe : donné a priori (p.ex. suit la méthode
d’analyse syntaxique : LL ou LR)
• ordre statique : d’après la grammaire attribuée, on
précalcule un ordre
• ordre dynamique : le graphe de dépendance est
construit avec l’arbre; les attributs sont évalués
dans un ordre compatible avec le graphe
D326
Ordre fixe : grammaire S-
attribuées
• Une grammaire est S-attribuée si elle ne
contient que des attributs synthétisés
• Implémenté dans les analyseurs ascendants
(Yacc, Bison, …)
• Syntaxe: ex: E : E '+' E { $$ = $1 + $3 } ;
– l’évaluation de l’attribut synthétisé $$ se fait
dans l’action de réduction
– $i désigne l’attribut du ième symbole
D328
Grammaires S-attribuées :
Implémentation
• L’analyseur LR conserve une pile des non-
terminaux déjà réduits
• Il leur associe leur attribut synthétisé
• Lors de la réduction, les $i sont trouvés en
sommet de pile, utilisés pour calculer $$, et
remplacés par $$.
Exemple : calculatrice
Règle de grammaire Action
d’attribution(Yacc)
S:E;
E : E ‘+’ E { $$ = $1+$3 }
| E ‘*’ E { $$ = $1*$3 }
| ‘ (‘ E ‘ ) ’ { $$ = $2} ;
F : nombre { $$ = $1} ;
D329
nb.val = 3
L-attributs dans LR
• On ajoute des actions à l’endroit où les attributs
doivent être calculés
• les actions dans un règle (mais pas à la fin) seront
traduites par des marqueurs : des non-terminaux à
définition vide, avec une action en fin.
• On accède aux attributs des non-terminaux
précédents en remontant la pile ($0, $-1, etc.)
Exemple : marqueur
Une règle
S : {$$ = 5} B ;
est traduite par
S : M B;
M : {$$ = 5};
Dans B la valeur de M est accessible comme $0
(c’est l’élément précédent sur la pile).
D344
D: T {$$=$1} L {$$ = $3 };
T : int {$$ = integer() };
L: L ',' id {$$ = add($3, $<ty>0, $1)}
| id {$$ = add($1, $<ty>0,
empty() ) };
D23, D333, D347
Postfixe : 3 5 * 7 4 * + 3 5 + /
Parenthèses inutiles
chiffres empiler
opération prendre les données sur la pile
les remplacer par le résultat
4 5
} 28 } 8
5
}* 7 *
15
}+ 3 * }
43 / 5
3 15 43
Ce principe est employé dans les langages à pile PostScript et FORTH,
et dans les machines abstraites: P-machine (Pascal), JVM (Java)
Registres
Allocation de registres
Technique simplifiée pour les arbres :
1. réorganisation des calculs
2. allocation des registres par simulation de pile
Réordonnancement
Idée : on évalue d’abord l’expression qui emploie le plus de regist
car 1 registre sert pour le résultat.
+
Exemple :
t1 t2
4 reg 3 reg
R4
R3
R2 RES 2
R1 RES 1 RES 1 RES 1 }+ RES
calcul de t1 ; calcul de t2 ; addition
Calcul du meilleur ordre
r ( t1 op t2) =
{ max (r1, r2) si r1 r2
r1 + 1 si r1 = r2
où r1 = r(t1)
r2 = r(t2)
_ 3
+ 2 _ 2
a b c + 2
1 1 1
d e
1 1
Note: diffère de D617 car prévu pour une machine load/store (p.ex.
MIPS)
Algorithme : cas 3
• s’il est plus avantageux de respecter
l’ordre : r1r2, r>r2
ProdCode(n1);
R := dépiler(pilereg);
ProdCode(n2);
émettre(op, R, R, sommet(pilereg)) ;
empiler(pilereg, R);
Algorithme : cas 2
s’il est plus avantageux d’inverser l’ordre
càd si r2r1, r>r1:
ProdCode(n2);
R := dépiler(pilereg);
ProdCode(n1);
émettre( op, R, sommet(pilereg), R);
empiler(pilereg, R);
Algorithme : cas 4
si les deux sous-termes utilisent plus de registres qu’il n’y en
a: r1, r2 r
ProdCode(n1);
T := dépiler(piletemp);
émettre(sw sommet(pilereg), T ); // stocke le résultat en T
ProdCode(n2);
émettre(lw sommet2(pilereg), T ) ; // charge T
émettre(op sommet(pilereg), sommet2(pilereg), sommet(pilereg) );
empiler(piletemp, T)
Exemple
pour (a + b) - (c - (d + e)) avec 2 registres :
lw R1 , a
lw R2, b _ 3
add R1, R1, R2
sw R1,T // T := a+b _
+ 2 2
lw R1, d
lw R2, e
a b c + 2
add R1,R1,R2 // R1 = d+e 1 1 1
lw R2, c
sub R1, R2, R1 // R1 = c-(d+e) d e
1 1
lw R2, T
sub R1, R2, R1 // R1 = (a+b)-(c-(d+e))
D579 W563
Bloc de base
• le graphe de flot de contrôle (ou ordinogramme) donne les
successions possibles d’instructions
ex: while B do S B
S
• un bloc de base est une suite d’instructions sans
branchements entrants ni sortants.
D598 W575
1 [] 1
a i
D619
Pipelines
• Dans les processeurs à pipeline, le résultat n’est
disponible qu’après un délai
• Il faut insérer des instructions indépendantes
• Algorithme glouton:
– tri topologique du DAG
– choix d’une instruction parmi les minimales
– calcul des collisions
• Augmente l’emploi des registres
Algorithme pour pipelines
Simplification : le délai est 1 cycle.
Candidats := minimaux(DAG);
Collisions := {instruction précédente };
répéter
b := ChoixHeuristique(Candidat \ enCollision(Candidats, Collisions );
si b = erreur alors insérer NOP; Collisions := vide;
sinon enlever b de Candidats; Collisions := {b};
ajouter dans candidats les nœuds dont b était le seul prédécesseur
jusqu’à Candidats vide
2. Instructions
• 2.1 conditionnelles
• 2.2 boucles
• 2.3 case
Instructions conditionnelles
exemple : MIPS
while repeat
e codeD(e) code(st) st
beq $r, $zero until
do
codeD(e) e
st code(st)
j beq $r, $zero
(a) boucle while (b) boucle repeat
var < nom > : array [l1 .. u1, ..., lk .. uk] of < type >
Rangement
la ième dimension di = ui - li + 1 (si ui > li)
• par ligne:
le dernier indice varie d’abord: a[1,1] a[1,2] a[1,3] a[2,1] a[2,2] a[2,3]
• par colonne
le premier indice varie d’abord: a[1,1] a[2,1] a[1,2] a[2,2] a[1,3] a[2,3]
Tableaux : indices
• Pour accéder à un élément a[e,f,g] on
calcule l’adresse a+ (e-l1)* d2 *d3 + (f-l2)*d3
+ (g-l3)
• en Pascal seuls e,f,g sont dynamiques
• on peut précalculer la partie statique
• Le schéma de Horner limite les
multiplications : e* d2 *d3 + f *d3 + g = (e*
d2 + f) *d3 + g
Records
En Pascal tous les types ont une taille statique
pour chaque champ on a un déplacement relatif
Ex. record p.ex.
a : array [1 .. 5, 1 .. 5] of boolean 1 byte par bool.0
x : integer 1 byte par int. 25
y : real 1 byte par real26
end 27
Les machines ont souvent des contraintes d’alignement :
p.ex. un réel doit avoir une adresse multiple de 4
on doit laisser du remplissage
ou réordonner les champs
Pointeurs
Les cases créées par new ne peuvent pas être allouées sur la pile car
elles vivent plus longtemps que la procédure qui les a créées.
On réserve une zone spéciale, le TAS.
PILE TAS
SP
La gestion du tas
Pile de contrôle
L ’exécution des procédures (récursives) est imbriquée représentable par une pile
Le contenu de la pile représente une branche de l ’arbre d ’activation.
La pile contient des blocs d ’activation.
Le registre FP (frame pointer) point sur le bloc courant, sommet de la pile.
Par exemple:
1. l ’appelant crée le bloc d ’activation
2. l ’appelant évalue les arguments et les met dans les registres ($a) ou dans le bloc
3. l ’appelant calcule le lien d ’accès en suivant le sien.
3. l ’appelant sauve le PC et saute au début de l ’appelé
4. l ’appelé alloue et initialise les variables locales
5. l ’appelé commence son exécution (qui calcule la valeur de retour)
Au retour:
1. l ’appelé restaure les registres, en dernier le PC (saut de retour)
D453
procédure p
var x : integer
procedure q 1 niveau d’imbrication statique
... x ...
end
program h ;
h
var i : integer ;
proc p
var a p p
proc r
var i : real p r
i := i/2 ; a := 5
if i > 0 p s
then i := i - 1 ; p i en cès
L ac
else r r d’
fi
i := 2 ;
p;
p
end.
* *
p r r
MP
* *
p q
q SP
MP
*
p
q SP
Passage de paramètres
• Diffère d’un langage à l’autre
• Principaux types :
– par valeur
– par référence (ou par variable)
– par copie (in out)
– par nom
Passage par valeur
• Le paramètre est comme une variable locale
• L’appelant évalue la valeur de l’argument et
le place comme valeur initiale du paramètre
Passage par référence
• L’adresse de l’argument est passée.
• En Pascal: noté par var
Exemple
procedure Troquer(var x, y : integer);
var t : integer;
begin
t := x;
x := y;
y := t
end;
N’aurait aucun effet en passage par valeur !
D470
Exemple
program ValeurResultat(output);
var a : integer;
procedure Hasardeuse(var x:integer);
begin x:=2 ; a := 0 end;
begin
a:=1; Hasardeuse(a) ; writeln(a)
end.
a=0 si passage par référence, a=2 si passage par copie.
D471
h
q q
proc q
func f
p p
p(g)
p(f)
f f
func g
q
(b) p (c)
situation de la pile
situation de la pile
(a) après appel de p(f)
après appel de p(g)
Un programme avec et (en pointillé)
g et (en pointillé)
fonction en paramètre après appel de h
après appel de h
Sémantique dénotationnelle
Idée :
x. x * x
3 4
1 x. x + ((x. x + x) (x + 2)) * (x. x + x) (x + 2)
4
3 x. x + ((x + 2) + (x + 2)) * (x. x + x) (x + 2))
4 x. x + (((x+ 2) + (x + 2)) * ((x + 2) + (x + 2)))
= x. 4x2 + 9x + 16
si on emploie les simplifications arithmétiques.
1
Ex. : 2 x. x + (x. x * x) (x+2 + x+2)
1 x. x + (x+2 + x+2) * (x+2 + x+2)
Domaines sémantiques
Quel objet mathématique représente le sens d’un programme ?
le sens est une fonction des états initiaux vers les états finaux.
Ex. : Pour les langages concurrents (Ada, LOTOS, CSP, CCS,...) les opérations
intermédiaires visibles comptent aussi :
le sens est un objet plus compliqué (p.ex. des arbres)
Compositionnalité
On veut donner un sens à chaque partie de façon à ce que le sens d’un
composé se déduise du sens de ses composants.
Avantages :
« bouclage » ou « bottom »
• [[ skip ]] = x. x
else [[ S2 ]] (x)
else x
Récursivité
• la définition du while est récursive, mais pas bien fondée.
• Comment calculer son sens ?
• Ce doit être une solution de:
W = x. if [[ E ]] (x) then W ° [[ S ]](x) else x.
• Cette équation peut avoir plusieurs solutions, mais une
seule est intuitive.
• Les solutions sont appellées points fixes de:
F = W. x. if [[ E ]] (x) then W ° [[ S ]](x) else x.
Ordre d’information
• Pour comparer les solutions et choisir la
plus naturelle, on définit un ordre partiel
• X Y se lit
« Y contient au moins l’info de X »
ou
« X approxime Y »
Bouclage
Considérons le programme f(x) = f(x)+1
Cette équation n’a pas de solution dans les nombres
Le programme boucle à l’exécution
On ajoute une valeur spéciale qui représente le fait qu’un
programme boucle.
= +1 est alors la seule solution.
Ordre partiel
Un ordre partiel est une relation:
• Réflexive: X X
• Transitive: X Y et YZ implique X Z
• Antisymétrique: XY et YX implique X=Y
Supremum
• Un majorant de Y est un élément b plus grand
que tous ceux d'Y:
bMaj(Y) = yY, yb
• Un majorant plus petit que les autres est le
supremum de Y
s=sup(Y)
ssi
s Maj(Y) et m Maj(Y), sm
Chaîne
Une chaîne est une suite croissante
(xi)i N telle que xixi+1
Domaine
Un domaine ou ordre partiel complet (CPO)
est un ensemble X muni d'un ordre partiel
• X contient un minorant
• Toute chaîne de X a un supremum
Intuition:
• Un programme qui boucle ne fournit aucune info: X
• On peut représenter l’info accumulée par un programme
par le supremum de ses calculs partiels
CPO plat
Exercice: est-ce bien un CPO?
CPO Union disjointe
Si on a deux CPOs A, B on forme le type
union disjointe de A et B en rajoutant
encore un en-dessous:
A B
Interprétation Abstraite
L’interprétation abstraite est une technique pour calculer des propriétés
d’un programme.
En principe ces propriétés se déduisent de la sémantique du programme,
mais celle-ci est trop coûteuse à calculer.
On ne calcule donc que les propriétés qui nous intéressent;
Le plus souvent ceci amène à faire des approximations.
La sémantique sert à trouver et prouver ces approximations.
W470
2. On veut construire une sémantique abstraite compositionnelle qui retient les propriétés à calculer
avec une relation «est représenté par » entre les deux domaines :
+ p 0 n ? p 0 n ? * p 0 n ? / p 0 n ?
p p p ? ? p ? p p ? p p 0 n ? p p err n ?
0 p 0 n ? 0 n 0 p ? 0 0 0 0 0 0 0 err 0 0
nn n ? nn n ? ? nn 0 p ? nn err p ?
? ? ? ? ? ? ? ? ? ? ? ? 0 ? ? ? ? err ? ?
on peut l'utiliser dans les règles de la grammaire pour calculer un nouveau sens. Le plus petit point
fixe donne la sémantique de chaque non-terminal. La sémantique du symbole initial donne le
langage de la grammaire. L'ordre utilisé est l'ordre sur les vecteurs d'ensembles:
+ Avantages :
+ intuition claire
+ facilité de construire un interpréteur
+ traitement du non-déterminisme
Inconvénients fréquents :
F1 ... Fn prémisses
F0 conclusion
Une règle peut contenir des méta-variables càd des variables qui
représentent n’importe quel texte d’une catégorie syntaxique donnée.
On l’appelle alors un schéma de règle.
E1 : expr(entier) E2 : expr(réel)
E1 + E1 : expr(réel)
X : variable(T) E : expr(T)
X := E : instruction
Sémantique Opérationnelle Structurée :
Définition
'
(ei , m) → (ei , m' )
'
• e
de calcul :( f ( 1 ,.., ei ,.., en), m) → ( f (e1 ,.., ei ,.., e ), m' )
n
calcul *
(4 * 5, m) → (4 * 20, m)
congruence
((4 * 5) + (5* y), m) → (20 + (5* y), m)
recherche
(y, m) → (2, m)
(5 * y, m ) → (5 * 2, m) congruence
(20 + (5 * y), m ) → (20 + (5 * 2), m) congruence
calcul +
(20 + 10, m) → (30, m)
Instructions
• affectation :
– d’abord évaluer l’expression
– ensuite modifier l’état pour la variable avec la constante ainsi calculée
• conditionnelle :
– d’abord évaluer l’expression
– ensuite évaluer la branche choisie (2 cas)
• boucle :
– créer une copie de l’expression
– l’évaluer (en gardant l ’expression du while pour les itérations)
• séquence :
– évaluer la première instruction
– lorsqu’elle se termine exécuter la 2ème instruction
Instructions
Affectation : 1. (e, m) (e’, m ’)
e : expr congruence
(id := e, m) (id := e’, m ’)
id : identificateur calcul :=
n : const
2. (id := n, m) (skip, update (id, n, m))
instruction vide
Conditionnelle : 1. (e, m) (e’, m’)
(S1, m’)
pre post
Exemple de règles
• règle pour l’affectation:
{ post [e/x] } x := e {post }
la règle est valide si x ne peut être accédée que par ce nom
(pas de synonymie)
• règle pour le if:
{ pre et b} t {post }
{ pre et non b} e {post }
{ pre } if b then t else e {post }