-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrapport.txt
More file actions
133 lines (101 loc) · 6.18 KB
/
rapport.txt
File metadata and controls
133 lines (101 loc) · 6.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
### Projet de Compilation : Compilateur MiniADA ###
{ Choix du langage }
J'ai choisi le Java simplement pour la modularité apportée par l'aspect Orienté
Objet et parce qu'il m'est agréable de programmer en Java.
{ Utilisation }
Pour utiliser ce compilateur, un JRE (Java Runtime Environement) avec une
version supérieure à 1.8 est supposé.
{ Analyse Lexicale }
Sources : lexer.flex, package token, fonction lexicalAnalysis de Compiler
Il n'y rien de spécial ici si ce n'est la nécessite de définir
LocalizedException (la classe définissant les erreurs du compilateur) comme une
sous classe de IOException pour que la fonction générée par JFlex puisse lever
de telles exceptions.
On reconnait "Character'Val" comme un identifier et à la casse près, c'est la
seule manière d'introduire une apostrophe.
{ Analyse Syntaxique }
Sources : package syntax, fonctions syntaxAnalysis, matchSymbol, matchKeyword,
matchSymbol de Compiler
Ayant commencé cette partie avant la fin du cours la concernant et l'outil
correspondant pour Java semblant contraignant, j'ai décidé d'écrire le parseur
sans outil.
Le parseur prend en entrée la liste des lexèmes (TokenList) et construit l'arbre
syntaxique en la parcourant.
Cet arbre est constitué d'objets de types Instruction, Declaration et
Expression. Dans ceux-ci, la partie relative à l'analyse syntaxique est
constituée des méthodes "match****".
Dans chacune des classes concernant l'arbre syntaxique, on trouve des méthodes
"type****" concernant le typage, "execute****" ou "value****" concerant un
interpréteur réalisé à des fins de vérifications et "buildASM" concernant
la production de code.
La seule difficulté que j'ai rencontrée dans cette partie a été de concevoir dés
le début les conventions par lesquelles agir sur la TokenList. En effet, un
manque d'uniformité peut vite provoquer des comportements imprévus.
A ce niveau, un appel de fonction sans paramètre et l'accès à une variable sont
indifférentiables ainsi on convient que les deux soient représentés par un
Access.
{ Analyse de Type }
Sources : package type, functions typeAnalysis de Compiler, functions type****
des noeuds de l'arbre de syntaxe.
Un Environnement contient toutes les informations locales concernant les
variables, fonctions, procédures et types déclarés. Il est alors propre à un
niveau de déclaration. Pour pouvoir accéder aux déclaration précédentes,
l’environnement des sous fonctions-procédures est en fait un SubEnvironement.
L’environnement racine est particulier puisque qu'il contient les types
primitifs Integer, Boolean et Character et les fonctions primitives Put, NewLine
et Character'Val.
Chaque type est représenté par une unique instance d'une sous classe de Type.
Un type défini par une déclaration est une instance de TypeDefined qui possède
un membre étant la définition du type en question représentée par un autre objet
Type.
Une difficulté rencontrée a été de concevoir cette structure de type. Il fallait
une structure permettant de construire l'objet d'un type avant de le définir
et ensuite de lui affecter une définition.
En dehors de ça, il n'y a rien de très spécifique à ce typeur.
{ Production de code }
Sources : package execution, fonctions buildAsm
La partie "Executer" peut être ignorée, elle permet seulement d'effectuer des tests
d'execution sur un serveur situé sous Linux depuis Windows où je n'ai pas réussi à
compiler le code assembleur tel que l'on l'écrit.
L'écriture à proprement parler de l'assembleur est gérée par ASMBuilder (similaire au
module OCaml fourni).
Détaillons maintenant quelques points de détail.
* Expressions
Lors du calcul d'une expression, tout résultat intermédiaire est stocké sur la pile.
* Registres temporaires
Certaines actions nécessitent parfois des registres. Ceux-ci sont alloués avec
getTmpReg(), ce qui permet de faire abstraction, lors du développement, des registres
déjà utilisés ou non. Cependant le procédé de compilation assure qu'il n'est pas possible
d'allouer une quantité arbitraire de ces registres.
* Appels de fonction
Le déroulement d'un appel est classique si ce n'est l'ajout dans chaque frame d'un
frameID unique pour chaque fonction déclarée. Celui-ci m'est nécessaire pour chercher
une variable qui est locale est une fonction ancêtre. En effet, on ne sait pas à
quelle profondeur on se trouve par rapport à elle dans la pile d'appel. Il suffit alors
de trouver la frame la plus profonde avec le bon frameID.
* Passage in-out
Il a été choisi pour ce mode de passage de fonctionner comme spécifier par ADA (sauf
erreur de ma part) c'est-à-dire de passer les paramètres par valeur puis, une fois
l'appel terminé, de recopier les valeurs éventuellement altérées.
* Entiers 32 bits sur une machine 64 bits
Ma première idée était de n'utiliser que des instructions sur 32 bits (addl, %eax, pushl...)
cependant, je n'ai pas réussi à compiler les pushl et les popl. Ainsi, l'efficacité hors
de préoccupation, j'ai ajouté des instructions movslq %eax, %rax après chaque opération
arithmétique. Cette solution est particulièrement déplaisante...
* Allocation
Tous les variables sont allouées sur la pile. Les records alloués par un "new" le sont
sur le tas, sans préoccupation de libération de cette mémoire.
{ Remarques }
Certaines structures du code sont peu agréables en conséquence des aspects peu naturels d'ADA.
Par exemple la distinction entre Fonction et Procédure entraîne la duplication de certains
segments de code.
En lançant les tests avec le script fourni, le compilateur semble très lent. En réalité,
c'est surtout le coup fixe de l'initialisation de la JVM qui se fait sentir. En effet,
j'ai effectué mes tests directement dans le compilateur (runTests) et ceux-ci, comme
le programme n'est lancé qu'une fois, sont bien plus rapides (spécifiquement sur l'aspect
syntaxe et typage).
Certains relativement rares fragments de code sont probablement obsolètes. Je m'en excuse,
il faudrait procéder à un nettoyage mais l'échéance approche.
Ce projet réalisé m'a apporté une compréhension bien plus profonde de l'enjeu de la compilation
et des outils dont dispose un compilateur.
### Marc Ducret - 2016/2017 ###