Schéma d'ensemble du compilateur
Pour présenter le schéma d'ensemble d'un compilateur,
on donne en général les différentes phases de compilation
(analyse lexicale, syntaxique, sémantique, production de code intermédiaire,
etc ...). Je préfère adopter un point de vu orthogonal, c'est-à-dire
donner d'abord les différentes représentations internes
successives du programme.
Les phases de compilation sont les ''flêches'' entre ces représentations.
Voici donc ces représentations :
Code source
Arbre syntaxique
Code intermédiaire (Lambda-code)
Code symbolique (Labels non résolus)
Code définitif
Voyons cela plus en détails :
L'arbre syntaxique est construit depuis le code source
par l'analyseur syntaxique qui s'appuie sur l'analyseur lexical.
L'arbre syntaxique est décoré :
on détermine les variables
d'environnements de chaque expression (une variable d'environnement
est un identificateur non global utilisé dans une expression; cette variable
doit être disponible au moment de l'execution, soit sur la pile,
soit dans l'environnement actif);
on effectue ensuite le typage
des expressions, en utilisant un algorithme d'unification
à la Milner. En fait, le typage est une phase de vérification
statique, les informations obtenues ne sont plus utilisées
dans les phases suivantes du compilateur.
Le code intermédiaire est produit à partir de l'arbre
syntaxique par la phase de précompilation, qui consiste à
affecter les positions des variables (dans la pile,
dans l'environnement).
On peut voir ce code intermédiaire
comme une sorte de programme en Lambda-calcul avec la notation
de De Bruijn, d'où le nom de Lambda-code.
Cette représentation est encore arborescente.
La phase centrale de la compilation, le passage du code
intermédiaire au code symbolique, est celle où l'on voit apparaître
une structure linéaire; en effet, jusque là, le code
est encore représenté sous forme arborescente.
Le choix
des instructions de la machine abstraite se fait dans cette phase.
En fait, le code est produit ''en partant de la fin'', ce qui
permet de savoir quelle instruction suit celle que l'on génère
et cela facilite ainsi la détection, par exemple,
de la récursivité terminale.
Le passage du code symbolique au code définitif est trivial :
il s'agit juste de résoudre les noms symboliques des labels
(utilisé pour faire des sauts dans le code)
en des adresses physiques dans la mémoire de la machine.
En fait, le code n'est pas écrit dans un fichier : il est
prêt à être executé; ainsi je n'ai pas eu à choisir une vraie
représentation physique des OpCode de la machine, mais
cela ne poserait aucun problème.
Une fois ces différents traitements effectués,
le code définitif est prêt à être executé.
En fait, le code n'est pas écrit dans un fichier;
ainsi je n'ai pas eu à choisir une vraie
représentation physique des OpCodes de la machine, mais
cela ne poserait aucun problème.
| |