Browse Source

Rapport mis à jour

Phyks (Lucas Verney) 5 years ago
parent
commit
43dfda214f
1 changed files with 172 additions and 28 deletions
  1. 172
    28
      README.md

+ 172
- 28
README.md View File

@@ -1,15 +1,22 @@
1
-Projet de compilation : MiniC++ (Lucas Verney)
2
-====
1
+Projet de compilation : MiniC++ (Lucas Verney) ====
3 2
 
4 3
 ## Présentation
5 4
 
6
-Cette archive `tar.gz` contient tous les codes sources relatifs à mon projet de compilation du fragment de C++, MiniC++, pour le cours "Langages de programmation et compilation".
5
+Cette archive `tar.gz` contient tous les codes sources relatifs à mon projet de
6
+compilation du fragment de C++, MiniC++, pour le cours "Langages de
7
+programmation et compilation".
7 8
 
8
-La commande ``make`` dans le répertoire du projet compile les codes sources en un exécutable `minic++` et la commande `make clean` efface les fichiers générés par `make`.
9
+La commande ``make`` dans le répertoire du projet compile les codes sources en
10
+un exécutable `minic++` et la commande `make clean` efface les fichiers générés
11
+par `make`.
9 12
 
10
-Le compilateur `minic++` accepte l'option éventuelle `--parse-only` sur sa ligne de commande pour ne réaliser que l'analyse syntaxique du fichier et exactement un fichier MiniC++ portant l'extension `.cpp`.
13
+Le compilateur `minic++` accepte l'option éventuelle `--parse-only` sur sa
14
+ligne de commande pour ne réaliser que l'analyse syntaxique du fichier et
15
+exactement un fichier MiniC++ portant l'extension `.cpp`.
11 16
 
12
-Le compilateur termine avec le code de sortie 0 si le fichier est syntaxiquement correct, 1 si une erreur lexicale ou syntaxique a été identifiée et 2 si le compilateur a rencontré une erreur interne.
17
+Le compilateur termine avec le code de sortie 0 si le fichier est
18
+syntaxiquement correct, 1 si une erreur lexicale ou syntaxique a été identifiée
19
+et 2 si le compilateur a rencontré une erreur interne.
13 20
 
14 21
 ## Structure de l'archive
15 22
 
@@ -27,38 +34,175 @@ Les fichiers suivants sont présents dans l'archive :
27 34
 
28 35
 ## Choix techniques
29 36
 
37
+### Analyse lexicale et syntaxique
38
+
30 39
 Quatre principales difficultés ont été rencontrées :
31 40
 
32 41
 * La mise en place du _lexer hack_.
33 42
 * Le traitement des parenthèses.
34
-* Le traitement des opérateurs unaires et notamment leur distinction des opérateurs unaires.
43
+* Le traitement des opérateurs unaires et notamment leur distinction des
44
+  opérateurs unaires.
35 45
 * La résolution des conflits.
36 46
 
37
-Pour la mise en place du _lexer hack_, j'ai opté pour l'utilisation d'une référence dans un module annexe (`lexhack.ml`). Un stockage de la liste des _tident_ sous forme de listes me paraissait suffisant. Cette référence est modifiée par l'analyseur syntaxique quand il rencontre une nouvelle déclaration de classes et lue par l'analyseur lexicale afin de déterminer le type d'un _ident_ (_ident_ ou _tident_). Pour pouvoir utiliser le nouveau type introduit directement dans la classe, j'ai dû séparer la déclaration de classes en deux étapes dans l'analyseur syntaxique.
38
-
39
-Pour le traitement des parenthèses, et de leurs priorités notamment, j'ai dû créer un nouveau non-terminal dans l'analyseur syntaxique, en assignant une priorité à ce non-terminal et au _token_ des parenthèses ouvrantes.
40
-
41
-Pour le traitement spécifique des opérateurs unaires, la distinction entre opérateur unaire et binaire ne pouvant être réalisée au moment de l'analyse lexicale, j'ai dû définir des priorités particulières pour les règles unaires dans l'analyseur syntaxique.
42
-
43
-Enfin, pour la résolution des conflits, et notamment des conflits avec la structure 
44
-
45
-    if () 
46
-    if ()
47
-    ...
48
-    else ()
49
-    ...
50
-
51
-j'ai assigné une priorité supérieure au _token_ `ELSE` qu'au _token_ `IF` afin de reproduire le comportement du C++ et résoudre les conflits.
52
-
53
-Pour le typage, j'utilise des Hashtbl pour stocker toute donnée utile ultérieurement, et j'ai adapté l'arbre de syntaxe abstraite pour compiler plus facilement.
54
-
55
-## Bugs connus et fonctions non implémentées
56
-
47
+Pour la mise en place du _lexer hack_, j'ai opté pour l'utilisation d'une
48
+référence dans un module annexe (`lexhack.ml`). Un stockage de la liste des
49
+_tident_ sous forme de listes me paraissait suffisant. Cette référence est
50
+modifiée par l'analyseur syntaxique quand il rencontre une nouvelle déclaration
51
+de classes et lue par l'analyseur lexicale afin de déterminer le type d'un
52
+_ident_ (_ident_ ou _tident_). Pour pouvoir utiliser le nouveau type introduit
53
+directement dans la classe, j'ai dû séparer la déclaration de classes en deux
54
+étapes dans l'analyseur syntaxique.
55
+
56
+Pour le traitement des parenthèses, et de leurs priorités notamment, j'ai dû
57
+créer un nouveau non-terminal dans l'analyseur syntaxique, en assignant une
58
+priorité à ce non-terminal et au _token_ des parenthèses ouvrantes.
59
+
60
+Pour le traitement spécifique des opérateurs unaires, la distinction entre
61
+opérateur unaire et binaire ne pouvant être réalisée au moment de l'analyse
62
+lexicale, j'ai dû définir des priorités particulières pour les règles unaires
63
+dans l'analyseur syntaxique.
64
+
65
+Enfin, pour la résolution des conflits, et notamment des conflits avec la
66
+structure 
67
+
68
+    if () if () ...  else () ...
69
+
70
+j'ai assigné une priorité du _token_ `ELSE` à celle du _token_ `IF` afin de
71
+reproduire le comportement du C++ et résoudre les conflits.
72
+
73
+### Typage
74
+
75
+Pour le typage, j'ai défini une fonction par structure à compiler. Pour chaque
76
+expression, je renvoie le nouvel arbre de syntaxe abstraite et le type de
77
+l'expression. Conformément au sujet, un certain nombre de remplacements sont
78
+effectués pour faciliter la compilation :
79
+* `Null` est remplacé par l'entier 0, avec un type `ATNull`
80
+* `true` et `false`sont remplacés respectivement par les entiers 1 et 0
81
+* l'opérateur flèche est remplacée par le déréférencement d'un pointeur et
82
+  l'opérateur point.
83
+
84
+J'utilise des Hashtbl pour stocker toute donnée utile ultérieurement, et j'ai
85
+adapté l'arbre de syntaxe abstraite pour compiler plus facilement. En
86
+particulier, l'arbre de syntaxe abstraite a été modifié pour ne pas conserver
87
+les informations de type, inutiles, après le typage. Je stocke également dans
88
+l'arbre de syntaxe abstraite les variables locales propres à chaque instruction
89
+ou à chaque déclaration ainsi que la taille de la _frame_ à allouer pour ces
90
+instructions. J'ai également séparé nettement dans le nouvel arbre de syntaxe
91
+abstraite les déclarations de classe des déclarations de variables, pour un
92
+traitement facilité.
93
+
94
+En particulier, j'utilise les Hashtbl suivants :
95
+* `globals_ pour stocker les variables globales (nom de la variable et type)
96
+* `decl\_class` pour stocker les déclarations de classe
97
+* `decl\_fonction` pour stocker les déclarations de fonctions
98
+* `glob\_objects` pour stocker les objets déclarés en-dehors de toute fonction
99
+* `refs\_` pour stocker les identifiants de variables qui sont des références
100
+* `constructors` pour stocker les constructeurs des classes (correspondance
101
+  entre le nom de la classe et le nom effectivement attribué au constructeur
102
+  lors de la résolution de la surcharge)
103
+
104
+D'autres Hashtbl sont utilisés localement pour garder une trace des éléments
105
+propres à chaque construction. En particulier, un Hashtbl `locals` est utilisé
106
+pour garder en mémoire les variables locales, leur type et leur position sur la
107
+pile.
108
+
109
+Des références globales permettent de garder une trace de :
110
+* la fonction actuellement compilée (`current\_function`) pour traiter
111
+  facilement les instructions `return`
112
+* l'objet actuellement compilé (`current\_object`) pour traiter facilement le
113
+  mot-clé `this`
114
+* le nombre de fonctions rencontrées, pour donner un nom unique au moment de la
115
+  résolution de la surcharge
116
+
117
+Pour le typage des fonctions, on conserve dans l'arbre de syntaxe abstraite un
118
+Hashtbl contenant les identifiants de variables, leur taille et leur position
119
+sur la pile. Cette position peut être :
120
+* ou bien une position effective sur la pile
121
+* ou bien un _label_ de variable globale, dans le cas d'une référence vers une
122
+  variable globale
123
+* ou bien la position sur la pile d'un argument passé par référence
124
+
125
+La surcharge des fonctions est implémentée en modifiant l'identifiant de la
126
+fonction, pour lui assigner un identifiant unique. Une table réalise alors la
127
+correspondance entre les identifiants dans le code et les identifiants
128
+modifiés. Cette table est consultée pour trouver les fonctions à appeler, en
129
+fonction des arguments.
130
+
131
+Pour générer des identifiants uniques, j'ai exploité les caractères interdits
132
+dans la spécification de MiniC++. En particulier, un identifiant ne pouvant
133
+commencer par un symbole underscore, il est très facile d'obtenir un
134
+identifiant dont on est sûr qu'il ne sera pas utilisé par ailleurs en
135
+commençant par un underscore.
136
+
137
+Enfin, la vérification qu'il existe une et une seule fonction `main()` est
138
+effectuée grâce à une référence globale de type booléen. Cette variable est
139
+fausse initialement et passée à vraie si on rencontre une fonction `main()`. Si
140
+on rencontre une deuxième fonction `main()`, une erreur est levée. Avant de
141
+passer la main au compilateur, on vérifie que cette variable est bien vraie.
142
+J'utilise également une référence globale de type booléen pour stocker la
143
+présence ou non d'une directive de préprocesseur pour inclure `iostream` et
144
+ainsi pouvoir utiliser `std::cout`.
145
+
146
+### Compilateur
147
+
148
+J'utilise le schéma de compilation suggéré par l'énoncé. Je passe les arguments
149
+sur la pile et n'utilise que 4 registres :
150
+
151
+* _a0_ pour les valeurs de retour
152
+* _a1_, _t0_ et _t1_ pour des étapes de calcul intermédiaires
153
+
154
+Pour la compilation des références au sein d'une instruction, je crée juste un
155
+alias de l'ancienne variable. Ainsi, dans le cas d'une référence vers une
156
+variable globale, la nouvelle variable aura le même _label_ que l'ancien
157
+variable. Dans le cas d'une référence vers une variable locale, la position
158
+stockée pour la nouvelle variable sera la même que celle de l'ancienne
159
+variable.
160
+
161
+Pour la compilation du passage par référence, j'ai regardé le code produit par
162
+`g++`. Celui-ci traite les références comme du ``sucre syntaxique'' pour du
163
+passage de pointeurs par valeur. C'est donc le comportement que j'ai reproduit.
164
+J'identifie les arguments passés par référence au moment du typage et passe
165
+alors, non pas la valeur de la variable, mais un pointeur vers cette variable
166
+en argument de la fonction. Je déréférence alors ce pointeur à chaque
167
+utilisation dans la fonction.
168
+
169
+Pour l'assignation de variables, la structure de mon code m'a obligé à
170
+distinguer les assignations possibles pour les traiter différemment, ce qui ne
171
+semble pas optimal et a ralenti l'implémentation de certaines structures,
172
+notamment les fonctions qui renvoient une référence.
173
+
174
+Les objets déclarés localement sont alloués sur la pile, comme les variables
175
+locales. La taille de chaque variable est gardée, aux côtés de sa position,
176
+afin de pouvoir calculer facilement les _offsets_ et allouer assez de mémoire.
177
+Les objets et les variables globales sont alloués sur le tas. Le mot-clé `new`
178
+alloue de la mémoire sur le tas, par un appel système. Cette mémoire n'est pas
179
+récupérée, faute d'appel système MIPS pour le faire.
180
+
181
+La compilation des boucles et des tests est réalisée de la façon décrite dans
182
+le cours.
183
+
184
+Pour l'instruction `return`, il convient de vérifier si cette instruction est
185
+dans la fonction `main()` ou dans une autre fonction. En effet, l'appel système
186
+à effectuer est différent selon qu'on est dans la fonction `main()` ou pas.
187
+
188
+## Bugs connus et structures non implémentées
189
+
190
+Le typeur et le compilateur ont été codés en parallèle et donc tout ce qui est
191
+correctement typé devrait compiler. Le code du typeur et du compilateur est
192
+assez lourd, ayant été écrit pour un sous-ensemble du langage que j'ai étendu
193
+au fur et à mesure. Ainsi, certains choix initiaux n'ont vraiment pas été
194
+optimaux et ont fortement ralenti l'implémentation des dernières structures du
195
+langage. En particulier, certains morceaux de code sont redondants et
196
+pourraient être regroupés en un seul bout de code global pour traiter tous les
197
+cas (notamment pour la distinction de traitement fonction / méthode ou de
198
+l'assignation de variables, dans le compilateur). Certaines de ces structures
199
+n'ont pas pu être implémentées, faute de temps :
57 200
 * Fonctions qui renvoient un référence (int &f())
58 201
 * Classes
59 202
     * Instanciation de classes globales (objet global)
60 203
     * Passage d'arguments au constructeur
61
-    * Vérification que le constructeur a bien le bon nombre d'argument et de bons types
204
+    * Vérification que le constructeur a bien le bon nombre d'argument et de
205
+      bons types
62 206
     * Méthodes
63 207
     * Héritage (multiple)
64 208