Vous êtes sur la page 1sur 5

Université Ibn Tofaïl A.

U : 2017/2018
Faculté des sciences SMI : S5
Département d’Informatique Compilation

TP1
Introduction à la compilation

Exercice 1 : Traduction.
Ecrire un programme C qui traduit les entiers décimaux en nombres écrits en chiffres
romains. Puis, inversement traduit les nombres écrits en chiffres romains en entiers
décimaux.

Exercice 2 : Evaluation d’une expression arithmétique.


Considérons des expressions arithmétiques comportant en plus des opérandes et
opérateurs, des parenthèses gauches et droites. Pour les opérateurs, supposons qu’ils seront
seulement binaires à savoir des multiplications(*), des divisions(/), des additions(-) et des
soustractions(-) et qu’ils obéiront aux règles courantes de priorité. Pour les opérandes, on se
limitera aux entiers.
1. Ecrire une fonction qui permet de vérifier si une expression est bien
parenthèsée (ses parenthèses sont-elles équilibrées ?).
2. Ecrire une fonction qui transforme une expression infixée en son
expression équivalente postfixée (en notation polonaise).
3. Ecrire, alors, une fonction qui permet l’évaluation d’une expression
arithmétique.
Université Ibn Tofaïl A.U : 2017/2018
Faculté des sciences SMI : S5
Département d’Informatique Compilation

TP2
Analyse lexicale

L'analyseur lexical se présente comme une fonction qui à chaque appel renvoie
comme résultat une unité lexicale.

Unités lexicales :
Les unités lexicales de notre langage sont de plusieurs sortes :
- les mots-clés (si, alors, ...),
- les symboles simples (+, *; ...),
- les symboles doubles (<=, !=, ...),
- les identificateurs et
- les nombres, uniquement entiers.
Chaque unité lexicale est représentée par un nombre entier. Les unités dont le lexème
n'est pas un caractère unique sont représentées par la valeur d'une constante symbolique,
définie comme ceci #define TANTQUE 300. Ces valeurs sont toutes supérieures ou égales
à 256. Les unités lexicales dont le lexème est un caractère unique sont codées par [le code
interne de] ce caractère lui-même, c'est-à-dire un entier inférieur à 256.

Mots clés :
La reconnaissance d'un mot-clé est traitée de la même manière que celle d’un
identificateur. Une fois le lexème formé, celui-ci est recherché dans une table des mots clés
qui associe à chaque [lexème d'un] mot-clé [le code de] l'unité lexicale correspondante, définie
comme ceci : struct { char* lexeme,
int uniteLexicale ;
} MotRes[ ] = {{‘‘si’’ ,SI },{‘‘alors’’,ALORS},{ ‘‘sinon’’,SINON} … } ;
int nbMotRes = sizeof MotRes/sizeof MotRes[0] ;
Identificateurs :
Lorsque l'analyseur lexical rencontre un identificateur son travail doit se limiter à :
- déterminer qu'il s'agit d'un identificateur (ce qui correspond à l'échec de sa recherche
dans la table des mots-clés),
- donner comme résultat la valeur IDENTIFICATEUR (si c'est comme cela qu'on a
nommé la constante correspondante),
- affecter une variable globale, nommée par exemple lexeme, avec la chaîne de
caractères reconnue, a fin que d'autres couches du compilateur puissent l’utiliser
ultérieurement…

Lecture du texte source :


L'analyseur lexical accède au texte source caractère à caractère. Pour la clarté des idées,
on s'interdira de lire le texte source par lignes.
Un petit problème posé par les analyseurs qu'on écrit soi-même est celui des unités
dont la reconnaissance implique la lecture d'un caractère en ‘‘trop’’ : les nombres, les
identificateurs, les opérateurs à deux caractères. On pourra utiliser la fonction ungetc de la
bibliothèque standard.
S'agissant de développer un compilateur il faudra rapidement que les lectures se
fassent dans un fichier au lieu de l'entrée standard. Pour éviter que cela entraîne des
modifications un peu partout dans le programme, on remplacera dès le début les appels de
getchar() par des appels d'une fonction spécialisée (dont la première version est triviale) :
int lireCar() {return getchar();}
Puis, après avoir tester l’analyseur lexical, modifions cette fonction pour permettre la lecture
dans un fichier à la place de l'entrée standard.
Université Ibn Tofaïl A.U : 2017/2018
Faculté des sciences SMI : S5
Département d’Informatique Compilation

TP3
Analyse syntaxique

Pour obtenir un compilateur il faut d'abord posséder un analyseur syntaxique correct


et complet auquel on ajoute ensuite des fonctions de production de code.
Un analyseur syntaxique lit, à l'aide d'un analyseur lexical, le texte source et répond à la
question ‘‘ce texte appartient-il au langage engendré par la grammaire donnée ? ’’.
Notre analyseur syntaxique sera écrit selon la technique de la descente récursive, qu'on
peut présenter comme une méthode presque mécanique pour produire un analyseur
syntaxique à partir d'une grammaire.

Idée de base :
Correspondre chaque non-terminal à un appel de fonction et chaque terminal à la
consommation d’un lexème dans un flux. A l’opposé de l’analyseur non récursif, cet
analyseur est écrit en un ensemble de fonctions mutuellement récursives, et est donc
étroitement lié à la grammaire analysée.

Principe :
1. Chaque groupe de productions ayant le même membre gauche S donne lieu à une fonction
reconnaître_S(). Le corps de cette fonction se déduit des membres droits des productions en
question.
2. Lorsque plusieurs productions ont le même membre gauche, le corps de la fonction
correspondante est une conditionnelle (if) ou un aiguillage (switch) qui, d’après le symbole
terminal visible à la fenêtre, sélectionne l’exécution des actions correspondant au membre
droit de la production pertinente.
3. Une séquence de symboles S1S2… Sn dans le membre droit d’une production donne lieu,
dans la fonction correspondante, à une séquence d’instructions traduisant les actions
‘‘reconnaissance de S1’’ ‘‘reconnaissance de S2’’ . . ‘‘reconnaissance de Sn’’.
4. Si Si est un symbole non terminal, l’action ‘‘reconnaissance de Si’’ se réduit à l’appel de
fonction reconnaître_Si().
5. Si Si est un symbole terminal, l’action ‘‘reconnaissance de Si’’ consiste à considérer le
symbole terminal ‘a’ visible à la fenêtre et
– s’ils sont égaux, faire passer la fenêtre sur le symbole suivant,
– sinon, annoncer une erreur (par exemple, afficher ‘‘a attendu’’) .
L’initialisation consiste à :
Positionner la fenêtre sur le premier terminal de la chaîne d’entrée.
On lance l’analyse en appellant la fonction associée à l’axiome de la grammaire.
Terminaison :
– si la fenêtre à terminaux montre la marque de fin de chaîne, l’analyse a réussi,
– sinon la chaîne est erronée.

Algorithme :
si A possède les productions suivantes :
A ! ® A 1 j B1 B2 j ²
avec ® 2 VT et A1; B1; B2 2 VN alors le code de la fonction pour A aura la forme
suivante :

trouverA()
si (motCourant = ®) alors
motCourant = prochainMot() ;
return (trouverA1()) /* A ! ®A1 choisi */
sinon si (motCourant 2 Premier(B1)-f²g )alors
return (trouverB1() ^ trouverB2()) /* A ! B1 B2 choisi */
sinon si (motCourant 2 Suivant(A))
alors return True /* A ! ² choisie */
sinon return False /* erreur */

Travail demandé :
Un analyseur descendant récursif pour la grammaire suivante des expressions
arithmétiques récursive à droite :

expression ! terme finexpression


finexpression ! + terme finexpression j - terme finexpression j "
terme ! facteur finterme
finterme ! * facteur finterme j / facteur finterme j "
facteur ! entier j identificateur j ( expression )

Vous aimerez peut-être aussi