Vous êtes sur la page 1sur 343

Cours de M1

Cours de compilation
MASTER M1

Introduction à la compilation
Cours de M1

Pourquoi écrire un compilateur

La réalisation d’un compilateur nécessite une bonne


maîtrise de la programmation (programmation
modulaire, récursivité, exemples de grand système
complexe…)
Les techniques de compilation sont nécessaires pour
lire, modifier, écrire des données en format
interchangeable
HTML, XML
Cours de M1

Rôle d’un compilateur


Le but d’un compilateur est de traduire des
programmes écrits dans un langage source en un
langage destination
Souvent, le source est un langage de
programmation de haut et la destination est en
langage machine
Parfois, traduction de source en source
LaTeX en HTML (HEVEA)
Pascal en C, FORTRAN en C
Manipulation de données en XML
Le compilateur doit détecter si le source est
conforme aux règles de programmation
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

Phases d’un compilateur


Un compilateur opère généralement en phases, chacune
d’elles transformant le programme source d’une
représentation en une autre
Les phases ne sont pas toujours clairement définies
Certaines phases peuvent être absentes
Cours de M1

Structure d’un compilateur


Analyse lexicale Séquence de lexèmes

Analyse syntaxique Structure en arbre

Analyse sémantique Structure en arbre avec attributs

Génération de code Code intermédiaire


intermédiaire

Optimisation Code intermédiaire optimisé

Génération de code Séquence de code objet


Cours de M1

Structure d’un compilateur


a = b + 60;
Analyse lexicale
id1 = id2 + 60 ;
=
Analyse syntaxique
+
id1
id2 60
Analyse sémantique =
+
Génération de code id1
id2EntierVersReel
intermédiaire
60
Optimisation temp1 = EntierVersReel(60)
temp2 = id2 + temp1
id1 = temp2
Génération de code
mov id2, r2
add 60, r2
mov r2, id1
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

Exemple : règles syntaxiques


EXP EXP PLUS EXP |
EXP PLUS EXP
Cours de M1

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

Gestion de la table des symboles


Une table des symboles est une structure de données
contenant un enregistrement pour chaque identificateur

Lexème Catégorie Type Paramètres


Position VAR REAL
Somme FCT ENTIER A ENTIER
B ENTIER
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

Cousins des compilateurs


Les préprocesseurs produisent ce qui sera l’entrée
d’un compilateur
inclusion de fichiers
macro-expansion
Éditeur de liens (chargement et reliure)
le chargement consiste à prendre du code machine
translatable et à modifier les adresses translatables
le relieur constitue un unique programme à partir de
plusieurs fichiers contenant du code machine translatable
(compilation séparée ou bibliothèque)
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é lexicale Lexèmes Modèles


Identificateur Position Lettre suivie de Lettre et de
Vitesse chiffres
I1
Somme
OPAFFACT = =
Nombre -2,50 Constante numérique : Signe
-9,10 facultatif suivi d'une Suite de
123 chiffres
SI Si Si
Alors Alors Alors
Sinon Sinon Sinon
OPREL < <= < ou <= ou
> >= > ou >= ou
= <> = ou <>
Cours de M1

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

Formalisme des expressions régulières


a,b, etc... des lettres de , M et N des expressions
régulières, 〚 M 〛 le langage associé à M

une lettre de l’alphabet a désigne le langage {a}


 désigne le langage {}
concaténation : MN désigne le langage 〚 M 〛〚 N 〛
M|N désigne le langage 〚 M〛 U〚 N 〛
Répétition : M* désigne le langage 〚 M 〛 *
Cours de M1

Expression et langage réguliers


Chaque E.R. E définir un langage régulier L(e) de la façon
suivante :

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

Formalisme des expressions régulières


Autres constructions obtenues à partir des précédentes
[abc] pour (a|b|c) et [a1-a2] pour tout caractère entre a1 et a2
(alphabet supposé ordonné)
M+ pour MM*
[^abc] désigne le complémentaire de {a,b,c} dans 
. pour  (tout caractère)
a? est une abréviation de a|
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

variable int a,b,c


 interne peut aussi être reconnu comme un
début de déclaration de variables

On choisit par priorité :


 Le lexème le plus long ;
 l’ordre de définition.
Cours de M1

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.

Théorème : tout langage régulier est reconnu par un


automate fini
Cours de M1

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

AFD (définition formelle)


Un automate fini déterministe M est un quintuplet
(,Q,,q0,F) où :
 est un alphabet ;
Q est un ensemble fini d’états ;
:Qx  Q est la fonction de transition ;
q0 est l’état initial ;
F est un ensemble d’états finaux
  ( q,  )  q
  (q, aw )     ( q, a), w)
On peut étendre  sur :Qx   Q par

Le langage L(M) reconnu par l'automate M est
l'ensemble q0,w)F des mots
permettant d'atteindre un état final à
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}

L’automate reconnaît le langage {aab,bbb}


Cours de M1

Traduction d'un AFD en code


e := q0 ;
c := CarSuiv();
tant que c ≠ EOF faire
e := Transiter(e,c);
c := CarSuiv();
fin;
si e  F alors
retourner ok
sinon retourner non;
Cours de M1

Automates finis non-déterministes


La définition est la même que celle des automates
déterministes, compte tenu des deux détails suivants :
les transitions sont définies par une relation et non plus
par une fonction, c’est-à-dire que plusieurs transitions
issues d’un état donné peuvent porter la même étiquette
il existe des transitions « spontanées » qui portent une
étiquette spéciale, classiquement 
Cours de M1

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

Définition formelle des AFND


La définition est la même que pour les AFD, excepté
 : Qx(U{})2Q

On étend  sur Qx  Q par :


 q   ( q,  )
  (q, aw )    ( q ' , w)
 q '   ( q , a )

Le langage L(M) reconnu par un automate



non déterministe est : w  (q0 , w)  F  0 
Cours de M1

Définition formelle - Précisions


Un AFND reconnaît un mot x ssi il existe un chemin
dans le graphe de transitions entre l'état initial et un
état d'acceptation tel que les étiquettes le long de ce
chemin épellent le mot x.
Le langage défini par un AFND est l'ensemble des
mots qu'il accepte
Propriété : tout langage reconnu par un AFND peut
être décrit par une E.R. et réciproquement
Cours de M1

Intérêt des AFND ?


L’intérêt des automates finis non-déterministes est
qu’il est facile d’associer un automate (Q,,s,F) à
une expression régulière M

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

Pour a appartenant à , construire l’AFND :

début a F
i cet automate reconnaît {a}
1
Cours de M1

Méthode de Thompson (suite)


Supposons que N(s) et N(t) soient les AFND pour
les expressions régulières s et t, l’automate
suivant reconnaît s|t

N(s) 

début
i F


N(t)
Cours de M1

Méthode de Thompson (suite)


l’automate suivant reconnaît st

début
i N(s) N(t) F

L’état de départ de N(s) devient l’état de départ de l’AFND composé


Cours de M1

Méthode de Thompson (suite)


l’automate suivant reconnaît, l’expression
régulière s*

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

Comparaisons AFD - AFND


AFD : il est très facile de déterminer si un mot est
reconnu par un AFD  pas de choix multiples au
départ d'un état
AFND : l'AFD contient plus d'états (ou plus de
transitions) que l'AFND correspondant
Un AFD est minimal s'il n'existe pas d'AFD
reconnaissant le même langage avec un nombre
d'états inférieur.
Résultat : tout langage régulier est reconnu par un AFD
minimal qui est unique
Cours de M1

Déterminisation d'un automate


L'automate déterministe associé à Q a 2|Q| états, mais en
général, seulement les états atteignables depuis {q0}
nous intéressent.

Note : en pratique, cet ensemble est relativement petit,


souvent de l'ordre |Q|
Cours de M1

Déterminisation d'un automate

A partir d'un état et en lisant une lettre, la table de


transition d'un AFND nous donne l'ensemble des états
accessibles.
On identifie cet ensemble à un état de l'AFD associé et
on construit ainsi de proche en proche l'AFD.
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

b Les états finaux sont


L'état initial ceux qui contiennent
a b b
est l'ensemble A B C D au moins un état
des états de a final de l'AFND
départ de a
l'AFND
b
a
Cours de M1

(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

Minimisation d'un AFD


1. Construire une partition initiale  de l'ensemble des
états en 2 groupes : les états finaux et les autres
2. Pour chaque groupe G de  : partitionner G en sous-
groupes tels que tous les états de ce sous-groupe
aient le même comportement (introduire un état
« puits » si nécessaire (n=n-1)
3. On choisit un représentant pour chaque sous-groupe
qui sera l'état de l'AFD minimal
Cours de M1

Minimisation d'un automate


L'état de départ est le représentant du groupe
contenant l'état initial de l'AFD initial
Les états d'acceptation sont les représentants des
groupes formés d'états d'acceptation
a b b
A NF NF
B NF NF
NF
C NF NF C b
D NF F b
F E NF NF a
A NF1 NF1 A a B b D b E
NF1 B NF1 NF2
C NF1 NF1 a
a
NF2 D NF1 F
F E NF1 NF1 a
A NF2 NF1
NF1
C NF2 NF1
NF2 B NF2 NF3
NF3 D NF2 F
F E NF2 NF1 b
b
a
A a B b D b E
a

a
Cours de M1

Notre outil de travail JFlex


Outil libre
Génère du Java
Est programmé en Java
Récupérable sur http://jflex.de
Langage de spécification très proche de lex (outil
standard générant du C ou C++) et flex
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

Expressions régulières de JFlex


| ( ) { } [ ] < > \ . * + ? ^ $ / " ~ ! sont des
caractères spéciaux
a|b pour a ou b
a b pour a suivie de b
[abc] pour a ou b ou c
(a|b)* pour une répétition d'un nombre quelconque de fois
(éventuellement nul) de a ou b
(a|b)+ pour une répétition un nombre quelconque de fois (non
nul) de a ou b
a{n} a répété n fois
~a tout jusqu'au caractère a
( a ) la même chose que a
. pour un caractère quelconque sauf \n
Cours de M1

Expressions régulières en JFlex


Si on a défini une macro par mamacro = <exprReg>,
alors on peut utiliser mamacro en l'entorant de { et } :
{mamacro}
Il existe des classes de caractères prédéfinies :
[:letter:] les lettres de l'alphabet
[:digit:] tout chiffre
Remarque : les blancs et les tabulations sont ignorés par
JFlex, sauf quand ils sont entre guillemets ou crochets.
Par exemple, [ \n] représente soit l'espace, soit la fin
de ligne.
Cours de M1

Règles lexicales de JFlex


Les règles de traduction d’un programme jflex sont
des instructions de la forme :

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

Méthodes utilisables dans les actions


JFlex fournit l'API suivante :
String yytext() retourne la portion d'entrée qui a
permis de reconnaître le symbole courant ;
yylength() retourne la longueur de la chaîne
yytext() ;
yyline retourne le numéro de la ligne du premier
caractère de la portion de texte reconnue (disponible si
%line a été utilisée) ;
yycolumn retourne le numéro de la colonne du
premier caractère de la portion de texte reconnue
(option %column)
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

Ambiguïtés dans le flot d'entrée


Bool = bool
Ident = [:letter:][:letterdigit:]*
%%
{Bool} { System.out.println("Bool"); }
{Ident} { System.out.println("Ident"); }

Comment est reconnu BoolTmp ?


BoolTmp sera reconnu comme un symbole boolTmp de
la classe Ident et non comme le symbole bool de la
classe Bool suivi du symbole Tmp de la classe Ident
Cours de M1

Comment fonctionne JFlex ?


1. chaque expression régulière est compilée en un
automate ;
2. l'ensemble des automates est fusionné en un seul ;
3. l'automate résultant est déterminisé ;
4. l'automate est minimisé.
Cours de M1

Exemple : filtrage de texte


Ce deuxième exemple supprime les lignes qui commencent par p ainsi
que tous les entiers, remplace les o par des * et retourne le reste
inchangé .

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

Récupération des erreurs


En cas d'erreur, il est nécessaire d'indiquer un minimum
d'informations, telles que :
la position de l'erreur
le dernier lexème traité
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

Exemple 2 bloc de code en Java


En Pascal, est une suite d’instructions séparées par des
points-virgules entre une { et }

bloc -> { liste_opt_instr }


liste_opt_instr -> liste_instr | 
liste_instr -> liste_instr ; instr | instr

liste des symboles vides


Cours de M1

Grammaire non contextuelle


Une grammaire non contextuelle comprend quatre
composants G=(, V, P, S) :
un ensemble d’unités lexicales appelées symboles
terminaux  (lexèmes ou tokens) ;
un ensemble de non-terminaux V (disjoint de ) ;
un ensemble de règles de production P où chaque
règle est constituée d’un non-terminal, appelé
partie gauche, d’une flèche appelée partie droite
de la forme
 avec V, et UV*
La désignation d’un des non-terminaux en tant que
symbole de départ S.
Cours de M1

Grammaire non contextuelle (suite)


les terminaux sont les symboles de base à partir
desquels les chaînes sont formées (unités
lexicales)
les non-terminaux sont les variables syntaxiques qui
dénotent un ensemble de chaînes
l'axiome est un non-terminal particulier. L'ensemble
des chaînes que l'on peut obtenir à partir de l'axiome
est le langage défini par la grammaire
Cours de M1

Grammaire non contextuelle (suite)


Une grammaire définit un langage formé par
l’ensemble des séquences finies de symboles
terminaux qui peuvent être dérivées du symbole
initial par des applications successives de règles

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

Cela peut également se représenter sous la


forme d'un arbre que l'on nomme (arbre de
dérivation)
Cours de M1

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

Article Nom Subord. Verbe GN

Le chat regarde Article Nom

Pron. GN la souris
relatif

GN
qui Verbe
prép.

est Prép. GN

sur Article Nom

le chat que regarde la souris est les commode la commode


Cours de M1

Phrase

GN GV

Article Nom Subord. Verbe GN

Le chat regarde Article Nom

Pron. GN la souris
relatif

GN
qui Verbe
prép.

est Prép. GN

sur Article Nom

le chat que regarde la souris est les commode la commode


Cours de M1

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∈ LG ⇔ S  w
Cours de M1

Dérivation à gauche – à droite


Une dérivation est dite à gauche si le non terminal le plus
à gauche est remplacé

g

Une dérivation est dite à droite si le non-terminal le plus à


droite est remplacé

d
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

A chaque arbre d'analyse,


- E est associé une unique dérivation
gauche (ou droite)

Les deux suites différentes de


( E )
dérivations donnent le même arbre de
dérivation.
E + E

Id Id
Cours de M1

Ambiguïté

Une grammaire est ambiguë si elle produit plus d'un arbre


d'analyse (ou d'une dérivation à gauche) pour une phrase
donnée.

Remarque : la grammaire précédente est ambiguë


Cours de M1

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

Exemples : expressions arithmétiques


Les terminaux (lexèmes) sont NUM, ID, + et -
SE
E  NUM
E  ID
EE+E
EE–E

Pour reconnaître l'expression 1 – 1 + x, plusieurs


dérivations sont possibles (on parle d'ambiguïté)
Cours de M1

Dérivations possibles

  E  NUM 
  
 E 

  E  NUM  E  NUM
  
S E  SE 
   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

On obtient une grammaire non ambiguë mais moins


lisible
Cours de M1

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

S  aSb∣aSc∣d avec le mot w=aaaaaaadbbcbbbc

Pour savoir quelle règle utiliser, il faut cette fois-ci


connaître aussi la dernière lettre du mot.
Conclusion : ce qui serait pratique, ça serait d'avoir
une table qui nous dit : quand je lis tel caractère et que
j'en suis à dériver tel symbole non-terminal, alors
j'applique telle règle et je ne me pose pas de
questions. Ça existe, et ça s'appelle une table
d'analyse.
Cours de M1

Analyse descendante

Analyse prédictive
Cours de M1

Implémentation à l'aide d'une pile explicite

flot d'entrée Id + Id * Id $

Programme Flot de sortie


axiome d'analyse (indique la suite des
S
prédictive règles utilisées)
$

Pile M Table d'analyse


a b c $
A R1 Erreur
B R2
Cours de M1

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 XUVW
$ 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é

E ⇒ T E ' ⇒ F T ' E ' ⇒ id T ' E ' ⇒ id E ' ⇒


⇒ idTE ' ⇒ id F T ' E ' ⇒ id id T ' E ' ⇒ id id∗F T ' E '
⇒ id id∗id T ' E ' ⇒ idid∗id E ' ⇒ id id∗id
Cours de M1

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

On définit pour AV :


follow ( A)  x 
  S   *
  A  avec x  first ( )
ensemble des terminaux qui peuvent se trouver immédiatement à droite

de A après dérivation. Si A est le symbole le plus à droite, alors
$follow(A)
Cours de M1

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 XY1Y2...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

Pour l'axiome S $∈ follow S 


si A  B  first  −{}⊂ follow  B
si A  B

ou A   B  avec  ⇒  } follow  A⊂ follow  B
Cours de M1

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')

first(S)={i,a} règles B ou B avec *


first(S')={e,} r1 =iEt B=S =S'
first(E)={b} rajout de follow(S) à follow(S)
follow(S)={$,e} r1 =iEtS B=S'
follow(E)={t} rajout de follow(S) à follow(S')
follow(S')={$,e} r3 =e B=S
rajout de follow(S')
Cours de M1

Construction de la table d'analyse


1.Pour chaque règle ri  :
2.Pour chaque afirst(), mettre ri dans T[,a]
3.Si en plus  est nullable (first()), pour
chaque afollow() (aU$), mettre ri dans
T[,a]

Remarque : nous avons un analyseur prédictif


si chaque case la table contient au plus une
règle de production (pas de conflit)
Cours de M1

a b e i t $

S r2 r1

S' r3 r4
r4
E r5

ambiguïté de la grammaire
Cours de M1

Les grammaires LL(k)


Il existe une classe de grammaires adaptée à l'analyse
descendante déterministe ( sans rebroussement)
nommée LL(k)
Inspection des k symboles terminaux
suivants pour choisir la bonne production
LL(k) à appliquer
k symboles de prévision
Analyse du flot d'entrée de Construction d'une
gauche (Left) à droite dérivation gauche
(Left to Right Scanning) (Leftmost Derivation)

En pratique, si k>1, la réalisation de l'analyseur correspondant est difficile


et peu performante. Si k=0, la grammaire est trop restrictive.
Si k=1, notre analyseur est prédictif
LL(1)aucune case de la table d'analyse n'a plusieurs alternatives
Cours de M1

Grammaires prédictives LL(1)

Une grammaire G est LL(1) si et seulement si :

Pour A ∣

il n'existe pas de terminal a tel que :


afirst() et afirst()
 et ∗ ne peuvent pas se dériver tous les deux en 
Si  ⇒  ,  ne se dérive pas en une chaîne
commençant par un terminal de follow(A)
On appelle grammaire LL(1) une grammaire pour laquelle la table d'analyse
décrite précédemment n'a aucune case définie de façon multiple.
Cours de M1

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

Suppression des ambiguïtés


Soit la grammaire suivante :
instr  si expr alors instr
| si expr alors instr sinon instr
| autre

Cette grammaire est ambiguë car la chaîne :


si E1 alors si E2 alors S1 sinon S2
peut être analysée de deux façons différentes
Cours de M1

Exemple

phrase : if cond1 then if E2 then I1 else I2


instr

if cond then instr

if cond then instr else instr

instr

if cond then instr else instr

if cond then instr


Cours de M1

Désambiguïser une grammaire


Il est parfois possible d'enlever les ambiguïtés d'une
grammaire. Dans le cas précédent, il suffit d'ajouter
des règles de réécriture permettant d'appliquer le
principe : "chaque sinon doit être associé au si le
plus près".

instruction  instr_close | instr_non_close


instr_close  si expr alors instr_close sinon
instr_close | autre
instr_non_close  si expr alors instr |
si expr alors instr_close sinon
instr_non_close
Cours de M1

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 

Les méthodes d'analyse descendante ne peuvent pas


fonctionner avec des grammaires récursives à gauche
A ⇒ A ⇒ A   ⇒ A   ⇒
Cours de M1

Cas d'une récursivité à gauche immédiate

A   A'
A  A ∣
A '   A '∣

La récursivité à gauche est transformée en récursivité à droite

Ex 1 : A  Aa∣b
A⇒ Aa ⇒ Aaa ⇒⇒ baaa

{A bA '
A'  aA '∣
A⇒ bA' ⇒ baA' ⇒ baaA' ⇒⇒ baa
Cours de M1

Récursivité à gauche

A  A 1 A 2 A  m 1  2 n
remplacé par :

A  1 A'  2 A'  n A'


A'  1 A'  2 A'  m A' 
Cours de M1

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

Éliminer les récursivités gauches immédiates des Ai


Cours de M1

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

• S n'est pas immédiatement récursif


On obtient G'

pour A2=A S  Aa∣b


• remplacer A =S dans A =A
1 2
A  bdA '∣A'
A '  cA'∣adA '∣
A Ac inchangé
A Aad∣bd
A  inchangé
Cours de M1

Ex 2 : expressions arithmétiques

{ E  ET∣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
EE+T |T boucle infinie
récursivité à gauche

A  A |   ,  = + T
à remplacer par
A  R ATR
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

Remarque : la factorisation ne supprime pas l'ambiguïté


Cours de M1

Factorisation à gauche (suite)


Si les règles de production sont de la forme :

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

On espère obtenir une grammaire LL(1)


Note : les grammaires ambiguës ou récursives à gauche ne
sont pas LL(1)
Si la grammaire est ambiguë, on peut choisir arbitrairement
la règle à utiliser en cas de conflit mais on affecte le
langage
Cours de M1

Implémentation à l'aide de
procédures récursives

On considère que chaque symbole non-terminal du


langage représente une procédure, et on implémente
directement cette procédure.
La règle appelée correspond à l’élément présent dans
l’ensemble premier
Cours de M1

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 ∣n1 }
G∥A aAb∣ab

Impossible de construire un AFND qui « compte » le nombre de


a pour avoir autant de b après.
Cours de M1

LC ?
Certains langages ne peuvent être engendrés par des
grammaires non contextuelles

∗ Ce type de langage pourrait contrôler que


L1={wcw∣w∈a∣b } les identificateurs sont déclarés avant d'être
utilisés
n m n m Cette phase est laissée à l’analyse sémantique
L 2={a b c d ∣n1, m1 }
Ce type de langage pourrait vérifier que
le nombre de paramètres formels correspond
au nombre de paramètres effectifs
(pour deux procédures)
Remarque : des langages très proches de L1 et L2 peuvent être engendrés par
une grammaire non contextuelle
Cours de M1

LC ? (suite)

R ∗
L ' 1={wow ∣w∈a∣b }
A  aAa∣bAb∣c

n m m n
L ' 2={a b c d ∣n1 m1 }

{A  aAd∣aA ' d
A '  bA ' c∣bc
Cours de M1

Analyse ascendante

Analyse par décalage réduction


Cours de M1

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
Bd
S

S ⇒ aABe ⇒ aA d e ⇒ a Abc de ⇒ a b bcde


B
A
On recherche les sous-chaînes
correspondants à la partie droite
A d'une règle de production

b b c d e On construit ainsi un arbre d'analyse des


a feuilles vers la racine
Cours de M1

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 :

aAAcde ⇒ aA b cde ⇒ a b bcde


bloqué
Cours de M1

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

Mise en place de l'analyse par


décalage - réduction
Cours de M1

Implémentation à l'aide d'une pile


au départ : en cours :

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écalages­ré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

Conflits possibles au cours de l'analyse


Conflit décaler/réduire : en cours d'analyse, on ne
peut pas décider s'il faut réduire le manche
présent en sommet de pile ou décaler encore
quelques symboles pour faire apparaître un autre
manche
(exemple grammaire précédente)
Conflit réduire/réduire : le manche au sommet de
pile peut être réduit par plusieurs productions
(exemple : grammaire où l'appel des procédures
avec paramètres à la même syntaxe que les
tableaux ou leur indice)
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

•A l'analyse de l'entrée A(i,j)


•le flot d'unités lexicales est ID(ID,ID)
•Au cours de l'analyse
• ID représente-t-il un indice de tableau ou un paramètre effectif ?

Pour éviter ces conflits, nous allons définir un classe de grammaire


LR(k) pour lesquelles l'analyseur décalage/réduction se déroule correctement
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

•A l'analyse de l'entrée A(i,j)


•le flot d'unités lexicales est ID(ID,ID)
•Au cours de l'analyse
• ID représente-t-il un indice de tableau ou un paramètre effectif ?

Pour éviter ces conflits, nous allons définir un classe de grammaire


LR(k) pour lesquelles l'analyseur décalage/réduction se déroule correctement
Cours de M1

Les analyseurs LR
Analyseurs par décalage réduction sans rebroussement

LR(k)
Indique le nombre de

LR(k) symboles de pré-vision


pour prendre les décisions
d'analyse

Analyse du flot d'entrée Construction d'une dérivation


de Gauche à Droite Droite inverse
(Left to Right scanning) (Rightmost derivation in reverse)

Note : quand k est omis, il est supposé être égal à 1


Cours de M1

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  ET 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

E ⇒ ET ' ⇒ EF ⇒ E Id ⇒ T  Id


T  Id ⇒ T ∗F  Id ⇒ T∗Id∗Id ⇒ F∗Id  Id ⇒ Id∗Id Id
Cours de M1

Conclusion sur les grammaires LR


Une grammaire pour laquelle nous pouvons construire
les tables Action et Successeur d'analyse LR(k) est
une grammaire LR(k)
Il existe des grammaires non contextuelles qui ne sont
pas LR, mais tous les langages de programmation
classique sont reconnus par des grammaires LR
Les analyseurs LR sont plus puissants que les
analyseurs prédictifs, mais s'il est possible de
« programmer à la main » un analyseur prédictif, nous
devons utiliser un constructeur d'analyseurs pour les
analyseurs LR (par exemple yacc ou bison) car la
quantité de travail est trop importante
Cours de M1

Construction des tables d'analyse


Il existe 3 méthodes pour construire ces tables
d'analyse :
1. SLR (simple LR) : la plus facile mais la moins puissante
2. LR : la plus puissante et la plus coûteuse
3. LALR (Look Ahead LR) : puissante et coût intermédiaire entre
les 2 précédentes
Cours de M1

Définition
Un item est une production avec un point repérant une
position dans la partie droite

Soit A une règle de production avec X,Y,ZV


4 items possibles


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

Construction des tables SLR


L'idée est de construire un AFD pour reconnaître les préfixes
possibles pour une analyse par décalage – réduction.
Méthode :
1. on augmente la grammaire de la production S'→S et S'
devient le nouvel axiome (à la place de S)
2. pour construire l'automate, on définit 2 fonctions
Fermeture(I) I étant un ensemble d'items

I⊂FermetureI

si A  . B ∈Fermeture  I  B . ∈Fermeture  I 
et B   est une production
Transition(I,X) = (I,X) X ∈T ∪V 
est la fermeture de l'ensemble des items de la forme
 A  X.  tel que  A  . X ∈ I
Cours de M1

Exemple de fermeture
Soit la grammaire : Et soit l'ensemble d'items


E  ET 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  ET 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

Construction des tables SLR (suite)


On part de l'état I 0= Fermeture {S '  . S }
Pour chaque symbole X de la grammaire, on effectue la
transition (I0,X) en créant ainsi de nouveaux états I1, I2...
On recommence ainsi tant que tous les états n'ont pas été
étudiés.
Application :


E  ET 1
E T 2
G T  T ∗F 3
T  F 4
F  E  5
F  Id 6
Cours de M1

Construction des tables SLR (suite)

∣ ∣
F . E 


S ' . E T T ∗. F
E . ET E . ET 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  ET .


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

Construction de la table d'analyse


1. Construire la collection d'items {I0,I1,...,In}
2. L'état i est construit à partir de Ii :
1. Pour chaque (Ii,a)=Ij : mettre décaler j dans M[i,a]
2. Pour chaque (Ii,A)=Ij : mettre aller en j dans M[i,A]

3. Pour chaque A→. (sauf A=S') contenu dans Ii :


mettre réduire A→dans chaque M[i,a] où afollow(A)
4. Si S'→S.Ii : mettre accepter dans M[i,$]
Cours de M1

Construction des tables SLR (suite)


3. Les tables ACTION et SUCCESSEUR

}
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

si S '  S.∈I i } Action[i ,$ ]= Accepter


Toutes les cases d'ACTION non définies sont à « Erreur »

∀ A∈V si Transition  I i , A= I j alors Successeur [i , a]= j


Cours de M1

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

Construction des tables LR


La méthode est similaire à la construction des tables SLR
Fermeture(I)
I ⊂Fermeture  I 

}
si [ A . B  , a]∈ I
B ⇒[ B .  , b ]∈Fermeture  I 
b∈Premier  a
Transition(I,X) est la fermeture des Items de la forme

[ A  X .  , a ]tel que [ A   . X  , a ]∈I

Pour l'automate, on part de I0= Fermeture([S'→.S,$])


Cours de M1

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 ,$ ]

identification de [S .CC ,$ ] avec [ A  . B  , a]


A=S = B=C et a=$
puisque C ne dérive pas en la chaîne vide, first C $ = first C 
on ajoute donc [C . cC , c ] ,[C . cC , d ] ,[C . d , d ]
Cours de M1

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. , $

Sur c nous fermons {[C → c.C,$]}


C  c.C , $
I 6 C . cC ,$
C . d , $
Cours de M1

Exemple (suite)
Transition (I2,d)
I 7 :C  d. , $

Transition(I3,c)=I3, Transition(I3,d)=I4, Transition(I3,C)=I8


I 8 :C  cC. , c∣d

Transition pour I6 sur c et d sont I6 et I7 et


Transition(I6,C)
I 9 :C  cC. , $
Cours de M1

Graphe de la fonction Transition


Cours de M1

Remplissage de la table ACTION


si [ A . , a]∈I i A≠S ' } Action[i , a]= réduire A

État ACTION SUCCESSEUR


c d $ S C
0 d3 d4 1 2
1 Acc
2 d6 d7 5
3 d3 d4 8
4 C→d C→d
5 S→CC
6 d6 d7 9
7 C→d
8 C→cC C→cC
9 C→cC
Cours de M1

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

Construction des tables LALR


LALR : Look Ahead LR
On construit l'automate d'analyse LR et on regroupe
les états ayant un « cœur » commun (les mêmes items
indépendamment des terminaux de pré-vision)
Si aucun conflit ne se produit, la grammaire est LALR
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

Cette grammaire est LALR

Il existe un algorithme pour construire efficacement les tables d'analyse LALR


(sans passer par les tables LR)
Cours de M1

CUP

Un générateur d'analyseurs syntaxiques


Cours de M1

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 
;
%%

cup indique les conflits


shift/reduce conflict : conflit décaler – réduire
reduce/reduce conflict : conflit réduire - réduire
Cours de M1

Analyse syntaxique avec CUP


Installer et tester CUP
Exemple de programmes CUP
Utiliser CUP avec Jflex
Cours de M1

Explications générales sur cup


Fichier exemple1.cup
4 parties principales dans le fichiers :
Déclarations générales : importations, initialisation et mode de
récupération des tokens ;
Déclaration des terminaux et des non terminaux. Les
terminaux peuvent être déclarés avec ou sans type (ici Integer)
;
Précédences et associativités des règles ;
Règles de grammaire proprement dites.
Cours de M1

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
|

terminal INT,PLUS,MOINS, expr FOIS expr

FOIS,DIV,PARENG,PAREND; |

non terminal expr ; expr DIV expr


|
PARENG expr PAREND
|
INT

;
Cours de M1

Résultats

java -jar java-cup11a.jar calcul.cup


Error : *** More conflicts encountered than expected -- parser generation aborted
------- CUP v0.11a beta 20060608 Parser Generation Summary -------
1 error and 16 warnings
9 terminals, 1 non-terminal, and 7 productions declared,
producing 15 unique parse states.
0 terminals declared but not used.
0 non-terminals declared but not used.
0 productions never reduced.
16 conflicts detected (0 expected).
No code produced.
---------------------------------------------------- (v0.11a beta 20060608)

ERREUR car notre grammaire est ambiguë,


Problème de priorité des opérateurs
Cours de M1

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é

3 + 4 * 8 (un décalage est réalisé lors de l’analyse du *)


Cours de M1

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

java -jar java-cup-11a.jar calcul2.cup


------- CUP v0.11a beta 20060608 Parser Generation Summary -------
0 errors and 0 warnings
8 terminals, 1 non-terminal, and 6 productions declared,
producing 13 unique parse states.
0 terminals declared but not used.
0 non-terminals declared but not used.
0 productions never reduced.
0 conflicts detected (0 expected).
Code written to "parser.java", and "sym.java".
---------------------------------------------------- (v0.11a beta 20060608
Cours de M1
Cours de M1

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

Fichier jflex pour CUP


/* calcul.flex */
import java_cup.runtime.*; //Pour travailler avec cup

%%
%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

L'analyseur syntaxique complet


Un Main utilisant le parseur
Compilation :
cup calcul.cup ou java -jar java-cup-11a.jar calcul.cup
→ parser.java et sym.java
jflex calcul.jflex → Yylex.java
javac -cp .:java-cup-11a.jar:. Yylex.java
javac -cp .:java-cup-11a.jar:. parser.java
javac -cp .:java-cup-11a.jar:. sym.java
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;

System.out.println ( "\nSyntaxe correcte" ) ;


} catch ( Exception e ) {

System.out.println ( "\nSyntax Error " ) ;


e.printStackTrace ( ) ;
}
}
}
Cours de M1

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;

expr ::= expr:e1 PLUS expr:e2


{: RESULT = new Integer(e1.intValue() +
e2.intValue()); :}
;
|
INT:n
terminal Integer INT;
{: RESULT = new Integer(n.intValue()); :}
terminal PLUS, MOINS, FOIS, DIV, PARENG, PAREND;
|expr:e1 FOIS expr:e2
{: RESULT = new
Integer(e1.intValue()*e2.intValue()); :}
;
Cours de M1

Attention – fichier attribut.flex


%{
// fonction pour manipuler des tokens avec ligne, colonne, nombre

%}

// 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

Notre langage Scheme simplifié


Comme nous avons la chance de disposer d'une
grammaire LL(1) (à vérifier) :
nous pouvons construire un analyseur syntaxique
descendant récursif
un seul token est nécessaire pour notre analyse
syntaxique
Cours de M1

Analyseur syntaxique de Scheme


L'analyseur lexical sera vu comme un procédure appelée
par l'analyseur syntaxique. Chaque appel à la procédure
lexical next_token() mettra à jour la variable
globale token
token contient le dernier token lu

Une fonction teste(tokens t) teste si le prochain


token est bien celui passé en paramètre ; on
s'arrête sur une erreur (fonction erreur)
Cours de M1

fonction teste
public int teste(Yytoken t) throws IOException{
variable globale
if (t.index == token.index){

System.out.println("token suivant "+token.chaine+" "+token.index);


}
else {
System.err.println("Erreur d'analyse syntaxique\n token lu --> "+token.chaine + "\t token attendu --> "+t.chaine);
return Lexer.T_EOF;
}
return 0;
}
public void next_token() throws IOException {
token = scanner.yylex();

}
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

Table des symboles, variables, …


Cours de M1

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

Aperçu de l'analyse sémantique


Contrôle des types
Affectation d'une variable d'un certain type avec une
expression de type différent
Référence à une variable inappropriés :
A[I] : erreur si A n'est pas un tableau
A + I : erreur si A n'est pas un entier ou un réel
I <= 10 : erreur si I n'est pas une variable numérique
Contrôle d'existence et d'unicité
Un identificateur doit être déclaré avant d'être utilisé
Ne pas déclarer 2 fois le même identificateur dans le même
contexte
Gérer la portée des identificateurs
Cours de M1

Aperçu de l'analyse sémantique


Contrôle du flux d'exécution
On peut re-déclarer en local un identificateur déjà déclaré en
global
L'instruction retourner doit se trouver à l'intérieur d'une
fonction et doit concerner une donnée de type attendu
Cours de M1

Traduction dirigée par la syntaxe


A chaque symbole de la grammaire, on associe un
ensemble d'attributs et, à chaque production, un
ensemble de règles sémantiques pour calculer la valeur
des attributs associés.
➢ Chaque symbole (terminal ou non) possède un ensemble
d'attributs (i.e. des variables)
➢ Chaque règle possède un ensemble de règles sémantiques
➢ Une règle sémantique est une suite d'instructions algorithmiques

La grammaire et l'ensemble des règles sémantiques


constituent la traduction dirigée par la syntaxe.
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 . nba1
0 1
S . nbc :=S . nbc
0 1
S  aS S . nba :=S . nba1
0 1
S . nbc :=S . nbc
0 1 2
S  cSacS S . nba :=S . nba1 S . nba1
0 1 2
S . nbc :=S . nbcS . nbc2
0
S  0 . nba :=0
S
S . nbc :=0
S '  S ≤résultat final est dans S.nba et S.nbc
Cours de M1

Calcul de nba / nbc pour le mot acaacabb

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 sémantique et ambiguïtés


E→E+E E(0).val = E(1).val + E(2).val
E→E*E E(0).val = E(1).val * E(2).val
E→i E.val = i.val

Analyse de i + i + i et i + i * i
Cours de M1

Grammaire attribuée et ambiguë

On obtiendrait la même valeur pour les 2 arbres admis par

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.

Dans l'exemple page suivante, les attributs sont


synthétisés
Cours de M1

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

On a affaire à des attributs synthétisés.


Cours de M1

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  SS∣

Règles Action sémantique


S'→S S.nb := 0
S →(S)S S(1).nb := S(0).nb+1
S(2).nb := S(0).nb
S→ écrire S.nb
Cours de M1

Attributs hérités

arbre décoré pour le mot (( )) ( ( ) ( )) ()


Niveau d'imbrication des ) dans un système de parenthèses
Cours de M1

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

Ordre d'évaluation et graphe de dépendances


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.

Les interdépendances peuvent être décrites par un graphe


orienté appelé graphe de dépendances.
Cours de M1

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 mimj 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.

Des problèmes peuvent apparaître s'il existe des cycles


dans le graphe de dépendance.
Cours de M1

Ordre d'évaluation (suite)


Étudier les méthodes d'évaluation des graphes de
dépendance des règle sémantiques dépassent le niveau
de ce cours. Les techniques font appel à des techniques
complexes :
construction d'arbres abstraits
les graphes orientés acycliques

Cours de M1

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

cycle dans le graphe de


dépendance
Cours de M1

Évaluation des attributs


Après l'analyse syntaxique
Lors de l'analyse syntaxique, on construit (en dur) l'arbre
syntaxique, puis ensuite, lorsque l'analyse syntaxique
est terminée, le calcul des attributs s'effectue sur cet
arbre par des parcours de cet arbre.
:-( Cette méthode est très coûteuse en mémoire
:-) Ne dépend pas du parcours imposé dans l'arbre
syntaxique
Cours de M1

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

Pas de dépendances de la droite vers la gauche


Cours de M1

Exemple de grammaire L-attribuée


Cours de M1

Exemple de grammaire non L-attribuée


Cours de M1

Grammaire LL(1) attribuée


Grammaire attribuée et LL(1) : LL-attribuée
Cours de M1

Grammaire LL(1)-attribuée

La valeur de E'.opg dépend de valeurs :


de l'attribut hérité opg du père E' ;
et éventuellement de l'attribut v du frère gauche T
La grammaire est LL-attribuée.
Cours de M1

Grammaires LL(1)-attribuées avec dépendances


Cours de M1

Implantation dans un analyseur récursif


pour la règle X → AbC :
X → { calcul Her(A) } A { calcul Synth(A) }
b { calcul Synth(b) }
{ calcul Her(C) } C { calcul Synth(C) }
{ calcul Synth(X) }
Cours de M1

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.

En Java : SynthX x(h1x, ..., hnX)


avec SynthX type objet regroupant les attributs de
Synth(X).
Cours de M1

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

Exemple : compter le nombre de 1


A partir d'un mot binaire { SB
B  0B∣1B∣
DDS avec attribut hérité :
règle action
S→B B.nb := 0
B→0B B(1).nb := B(0).nb
B→1B B(1).nb := B(0).nb+1
B→ écrire B.nb

DDS avec attribut synthétisé : règle action


S→B écrire B.nb
B→0B B(0).nb := B(1).nb
B→1B B(0).nb := B(1).nb+1
B→ B.nb := 0
Cours de M1

Arbres décorés

hérité synthétisé
Cours de M1

DDS de typage de variable en Pascal


Production Règle sémantique

DL:T L.typeh := T.type

T  integer T.type := entier

T  real T.type := réel

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

L.typeh = real type hérité


T.type = real

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

Problèmes de l'analyse sémantique


Portée des identificateurs
Contrôle de type
Surcharge d'opérateurs et de fonctions
Fonctions polymorphes
Cours de M1

Portée des identificateurs


On appelle portée d'un identificateur la(es) partie(s) du
programme où il est valide et a la signification qui lui a
été donné lors de sa déclaration.
Cette notion de validité et visibilité dépend bien sûr des
langages. Par exemple, en Cobol tous les
identificateurs sont partout visibles. En Fortran, C,
Pascal, ..., les identificateurs qui sont définis dans un
bloc ne sont visibles qu'à l'intérieur de ce bloc; un
identificateur déclaré dans un sous-bloc masque un
identificateur de même nom déclaré dans un bloc de
niveau inférieur.
Cours de M1

La table des symboles


Par exemple, l'analyseur sémantique doit vérifier si chaque
utilisation d'un identificateur est légale, ie si cet identificateur a été
déclaré et cela de manière unique dans son bloc. Il faut donc
mémoriser tous les symboles rencontrés au fur et à mesure de
l'avancée dans le texte source. Pour cela on utilise une structure
de données adéquate que l'on appelle une table des symboles.
La table des symboles contient toutes les informations
nécessaires sur les symboles (variables, fonctions, ...) du
programme :
identificateur (le nom du symbole dans le programme)
son type (entier, chaîne, fonction qui retourne un réel,...)
son emplacement mémoire
si c'est une fonction : la liste de ses paramètres et leurs types
...
Cours de M1

Opérations sur la table des symboles


Lors de la déclaration d'une variable, elle est stockée
dans cette table. Il faut regarder si cette variable n'est
pas déjà contenue dans la table. Si c'est le cas, c'est
une erreur. Lors de l'utilisation d'une variable, il faut
vérifier qu'elle fait partie de la table (on peut alors
récupérer son type, donner ou modifier une valeur, ...).
Il faut donc munir la table des symboles de
procédures :
ajout ;
suppression ;
recherche.
Cours de M1

Résolution du problème de portée


Utilisation d'une pile pour la gestion de la TS

entier i
caractère c

entier j i entier c caractère


entier i
j entier j entier

caractère c c caractère c caractère

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

Deux types de contrôle


On appelle statique un contrôle de type effectué lors
de la compilation, et dynamique un contrôle de type
effectué lors de l'exécution du programme cible.
Le contrôle dynamique est à éviter car il est alors très
difficile pour le programmeur de voir d'où vient l'erreur
(quand il y en a une). Malheureusement, certains
contrôles ne peuvent être fait que dynamiquement :
int tab[10];
int i;
... tab[i]
Le compilateur ne pourra pas garantir en général qu'à
l'exécution la valeur de i sera comprise entre 0 et 9.
Cours de M1

TDS de contrôle de type


Règle de production action sémantique
I →Id = E I.type := si Id.type=E.type alors vide
sinon erreur_type_incompatible(ligne,Id.type,E.type)
I→si E alors I I(0).type := si E.type=booléen alors I(1).type
sinon erreur_type(ligne,...)
E→ Id E.type := Recherche_Table(Id)
E→ E+E E .type := si E .type=entier et E(2).type=entier alors entier
(0) (1)

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,...)

Remarque : comment vérifier le type s->t.f(p[*i])-&j en C ?


Cours de M1

Surcharge des opérateurs


Des opérateurs (ou des fonctions) peuvent être
surchargés c'est à dire avoir des significations
différentes suivant le contexte.
En C, la division est la division entière si les deux
opérandes sont de type entier, réelle sinon.
En ADA, on peut redéfinir un opérateur standard :
function "*" (x,y : complexe) return complexe;
Comment prendre en compte une opération de type
z*(3*5)
Cours de M1

Une solution possible


Prendre en compte la totalité des types dans la TDS y
compris les types créés par l'utilisateur
Travail complexe
Génération de code difficile
Cours de M1

Génération de code intermédiaire


Cours de M1

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.

Par exemple, le langage Pascal – UCSD produit un


code intermédiaire appelé P-code. Nous allons
également utiliser une version simplifiée du P-code.
Cours de M1

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

Passer par un langage intermédiaire est la méthode


moderne pour écrire un compilateur.

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

Le source est correct → compilable

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

Petit aperçu du format ELF


Cours de M1

Note sur le format ELF


ELF (Executable and Linkable Format) ;
Format binaire spécifié en 1999 ;
Très répandu :
GNU/Linux ;
BSD
Solaris
BeOS
Sony Playstation
Nintendo DS et WII
Cours de M1

Principe du format ELF


Assembler des unités de compilation entre elles dans
le but de créer un programme exécutable (édition de
liens) ;
Créer une image mémoire d'un programme en vue de
son exécution (chargement).

La première opération est réalisée par l'éditeur de liens


ld, la seconde lorsque l'exécutable est invoqué.
Cours de M1

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

Du code source à l'exécutable


#include <stdio.h>
int var_init = 10;
float global_init ;
int main(int argc, char *argv[])
{
Int local_init = 12 ;

printf("Hello, ELF: %d\n", global_init + local_init);


return 0;
}

gcc –verbose -o essai essai.c


Cours de M1
fonlupt@moria:~/langages/essai$ gcc --verbose essai.c -o test
Using built-in specs.
Target: x86_64-linux-gnu
...
gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)
...
/usr/lib/gcc/x86_64-linux-gnu/4.4.5/cc1 -quiet -v essai.c -D_FORTIFY_SOURCE=2 -quie
-dumpbase essai.c -mtune=generic -auxbase essai -version -fstack-protector -o
/tmp/cc7WvXCG.s
...
as -V -Qy -o /tmp/ccEzGJCI.o /tmp/cc7WvXCG.s
GNU assembler version 2.20.51 (x86_64-linux-gnu) using BFD version (GNU Binutils for
Ubuntu) 2.20.51-system.20100908
...
/usr/lib/gcc/x86_64-linux-gnu/4.4.5/collect2 --build-id --eh-frame-hdr -m elf_x86_64
--hash-style=gnu -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o test -z relro
/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crt1.o /usr/lib/gcc/x86_64-linux-
gnu/4.4.5/../../../../lib/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtbegin.o
-L/usr/lib/gcc/x86_64-linux-gnu/4.4.5 -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5
-L/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib -L/lib/../lib -L/usr/lib/../lib
-L/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../.. -L/usr/lib/x86_64-linux-gnu /tmp/ccEzGJCI.o
-lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed
/usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtend.o /usr/lib/gcc/x86_64-linux-
gnu/4.4.5/../../../../lib/crtn.o
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

Point de vue de l'exécution


Cours de M1
fonlupt@moria:~/enseignement/compilation$ readelf --sections -W test
There are 30 section headers, starting at offset 0x1150:

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

test: file format elf64-x86-64

Disassembly of section .text:

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

test: file format elf64-x86-64

Disassembly of section .data:

...

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

test: file format elf64-x86-64


Disassembly of section .bss:
0000000000601028 <completed.7424>:
0000000000601038 <global_init>:

Dans notre exemple, variable globale global_init non


initialisée. Sous Linux, elles sont initialisées à 0
fonlupt@moria:~/enseignement/compilation$ readelf --symbols test
Cours de M1
Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)

La table des symboles


3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)

Symbol table '.symtab' contains 66 entries:


Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND

[...]
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

PHT : Program Header Table


L'OS (Linux) dans notre cas ne voit pas des sections
mais des segments ;
Dans notre cas, 9 « program headers » commençant à
l'offset 64 ;
Plusieurs sections sont associées aux segments ;
R accessible en lecture, W en écriture et X
exécutable ;
VirtAddr indique l'adresse virtuelle de ces segments ;
Sous Linux, on peut ignorer PhysAddr car Linux
travaille en mode protégé
fonlupt@moria:~/enseignement/compilation$ readelf --segments test Cours de M1
Elf file type is EXEC (Executable file)
Entry point 0x400410
There are 9 program headers, starting at offset 64

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

Section to Segment mapping:


Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .te
.fini .rodata .eh_frame_hdr .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .ctors .dtors .jcr .dynamic .got
Cours de M1

Quelques infos sur les segments


LOAD : le contenu du segment est chargé lors de
l'exécution. « offset » indique l'offset du fichier à partir
duquel le noyau doit lire le contenu, « FileSiz » le
nombre d'octets à lire ;
Par exemple le segment 2 est le contenu du fichier
commençant d 0x000 à 0x6bc
STACK : pile du segment (taille dépendante du noyau)
Cours de M1

Exécution direct
gcc -g -o test test.c
gdb test Option de débogage

(gdb) b 1 → break à la ligne 1


(gdb) r → exécution
ps -elf | grep test → récupération du numéro du
processus
cat /proc/numéro du processus/maps
→ exécution des segments
Cours de M1

fonlupt@moria:/proc/26899$ cat maps


00400000-00401000 r-xp 00000000 09:02 27134605 /home/fonlupt/enseignement/compilation/test
00600000-00601000 r--p 00000000 09:02 27134605 /home/fonlupt/enseignement/compilation/test
00601000-00602000 rw-p 00001000 09:02 27134605 /home/fonlupt/enseignement/compilation/test
7ffff7a59000-7ffff7bd3000 r-xp 00000000 09:00 1573198 /lib/libc-2.12.1.so
7ffff7bd3000-7ffff7dd2000 ---p 0017a000 09:00 1573198 /lib/libc-2.12.1.so
7ffff7dd2000-7ffff7dd6000 r--p 00179000 09:00 1573198 /lib/libc-2.12.1.so
7ffff7dd6000-7ffff7dd7000 rw-p 0017d000 09:00 1573198 /lib/libc-2.12.1.so
7ffff7dd7000-7ffff7ddc000 rw-p 00000000 00:00 0
7ffff7ddc000-7ffff7dfc000 r-xp 00000000 09:00 1572888 /lib/ld-2.12.1.so
7ffff7fda000-7ffff7fdd000 rw-p 00000000 00:00 0
7ffff7ff9000-7ffff7ffb000 rw-p 00000000 00:00 0
7ffff7ffb000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 00020000 09:00 1572888 /lib/ld-2.12.1.so
7ffff7ffd000-7ffff7ffe000 rw-p 00021000 09:00 1572888 /lib/ld-2.12.1.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Cours de M1

Quelques explications
17 segments aka VMA (Virtual Memory Address)
VMA n°1 correspond au segment n°2
Cours de M1

Les langages intermédiaires


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

Exemple de traduction de code


fragment de programme C a=b*c+b*(b-c)*(-c) devient
(1) t1 := b*c (5) t5 := t3*t4
(2) t2 := b-c (6) t6 := t1+t5
(3) t3 := b*t2 (7) a := t6
(4) t4 := -c

ou encore, en optimisant le nombre de registres utilisés


(1) t1 := b*c (5) t2 := t3*t4
(2) t2 := b-c (6) t1 := t1+t5
(3) t2 := b*t2 (7) a := t1
(4) t3 := -c
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

Un exemple plus complet


a := x * -y + c

op arg1 arg2 résultat


moinsu y t1
* x t1 t2
+ t2 c t3
:= t3 a

Remarque : t1, t2 et t3 seront stockés dans la table des


symboles
Cours de M1

Génération de quadruplets dirigée par la syntaxe


Cours de M1

Production de code à 3 adresses


Le code à trois adresses est (assez) facile à traduire.
Il suffit d'insérer des actions sémantiques dans la
grammaire du langage
On peut donc utiliser une TDS
Cours de M1

Génération de code pour les expressions arithmétiques

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

Génération de code (suite)


Aff → id:= E
{gen(:=,E.place, ,place(id.lex,TS)}
E→E+T
{ E.place ← newtemp() ; gen(+,E(1).place, T.place, E.place)}
E →T { E.place ← T.place }
T → T*F
{ T.place ← newtemp() ; gen(*, T(1).place, F.place, T.place)
T → F { T.place ← F.place}
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

Traduction d'une expression


x = 2+3*5
Id = E (:=E.place, , place(x,TS))

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

Les expressions booléennes


Il existe plusieurs méthodes d'évaluation des expressions
booléennes.
Pour le langage C, JAVA
xb1 opb exb2 :
On évalue exb2 seulement si nécessaire
Vrai  exb2  exb2 et Faux  exb2 
Cours de M1

Machine / Langage à pile


Le langage à pile est associé à une machine abstraite
appelée P-machine. Elle est caractérisée de la manière
suivante :
zone mémoire gérée uniquement sous forme de pile ;
pas de registres sauf le pointeur de pile (SP) et le compteur
d'instructions (PC) ;
la plupart des instructions du langage prennent leurs opérandes
sur la pile et laissent leur résultat sur cette pile.
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.

/* mnémoniques disponibles dans notre P-


code */
typedef enum mnemoniques {ADD, SUB,
MUL, DIV, EQL, NEQ, GTR, LSS,LEQ,GEQ,
PRN, INN, INT, LDI, LDA, LDV, STO, BRN,
BZE, HLT,INPUTI,
PUSH,POP,DUP,STV} mnemoniques;
Cours de M1

Instructions de la P-machine (1ère partie)


ADD additionne le sous-sommet et le sommet, laisse
le résultat au sommet (idem SUB, MUL, DIV).
Dépile 2 fois, empile 1 fois.
EQL laisse 1 au sommet si sommet=sous-sommet,
0 sinon (idem NEQ, GTR, LSS, LEQ, GEQ).
Dépile 2 fois.
PRN Imprime le sommet. Dépile 1 fois.

INN lit un entier, stocke cet entier à l'adresse


trouvée au sommet, dépile 1 fois
INT c incrémente de la constance c le pointeur de
pile (SP)
LDI v empile la valeur v
Cours de M1

Instructions de la P-machine (2ème partie)


LDA a empile l'adresse de a

LDV remplace le sommet par la valeur trouvée à


l'adresse indiquée par le sommet
(déréférencement). Dépile une fois, empile une
STO fois
stocke la valeur au sommet à l'adresse
indiquée par le sous-sommet. Dépile 2 fois.
BRN i branchement inconditionnel à l'instruction i

BZE i branchement à l'instruction i si sommet = 0,


dépile 1 fois (cf inverse de BNZ)
HLT halte
Cours de M1

Instructions de la P-machine (3ème partie)


INPUTI Lit un entier et le place au sommet de la pile

PUSH x Empile la valeur fournie en opérande

POP Dépile le sommet de la pile (il est perdu)

DUP Duplique le sommet de pile (erreur si pile vide)

STV x Stocke dans la variable x, la valeur en sommet


de pile (dépile une fois)
HLT halte
Cours de M1

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

Les tests bze, brn, label


Si cond alors instr1 sinon instr2
Traduction de la condition laissant le résultat sur la pile
bze sinon
Traduction de instr1
brn fsi
label sinon
Traduction de instr2
label fsi
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.

C'est le rôle du compilateur d'allouer ces symboles en


mémoire. A chaque symbole rencontré, le
compilateur doit associer un emplacement mémoire
dont la taille dépend du type de symbole.
Cours de M1

Allocation de la mémoire (suite)


Une manière simple et naturelle de faire est de choisir les
adresses au fur et à mesure de l'analyse des
déclarations en incrémentant un offset qui indique la
place occupée par les déclarations précédentes.

 A la fin de l'analyse, on connaît la taille mémoire exacte


réservée aux déclarations