Chapter 7 Interprétation JaJaCode
7.1 Qu'est-ce qu'un interpréteur ?
Programme qui traite les lignes du code source d'un programme les unes
après les autres, au fur et à mesure qu'il les parcourt.
7.2 Présentation
Ce module doit permettre d'interpréter du code JaJaCode, c'est-à-dire
de réaliser l'exécution d'un programme JaJaCode spécifié en entrée.
7.3 Réalisation
Le développement de ce module s'est fait en collaboration avec le
groupe du module de compilation.
En effet, la principale question avant de commencer était de définir
ce qu'il y avait à la sortie du compilateur afin de pouvoir adapter
l'entrée de l'interpréteur JaJaCode.
Nous avons décidé de faire sortir du compilateur un vecteur
d'instructions JaJaCode.
7.3.1 Pourquoi un vecteur d'instructions ?
Avant d'aller plus loin dans la réalisation de l'interpréteur
JaJaCode, attardons-nous un peu sur le choix d'un vecteur
d'instructions plutôt qu'un tableau ou un arbre.
Lors de la réunion pour décider de la structure de données à utiliser
pour interfacer le compilateur et l'interpréteur JaJaCode, trois idées
ont retenu notre attention.
-
utiliser un arbre d'instructions JaJaCode;
- utiliser un tableau d'instructions JaJaCode;
- utiliser un vecteur d'instructions JaJaCode.
L'idée de l'arbre a été abandonnée car celui-ci se serait résumé à un
enchaînement de noeuds sur une seule branche.
Pour ce qui est du tableau, la principale difficulté était de
connaître la longueur du tableau et donc un nombre précis
d'instructions JaJaCode. Or, un vecteur possède les mêmes
caractéristiques qu'un tableau à ceci près qu'il n'est pas
borné. Cette structure nous a donc semblée être la plus adéquate pour
stocker les instructions JaJaCode générées.
7.3.2 Le développement de l'interpréteur JaJaCode
Dans un souci de simplification en vue de l'assemblage des différents
modules, un package interpretationJaJaCode a été mis en place.
Ce package regroupe plusieurs classes dont la classe
InterpretationJaJaCode, qui peut être qualifiée de classe
principale pour ce module, et trente-quatre autres classes
représentant chacune une instruction JaJaCode. Chacune de ces
trente-quatre classes est composée d'une méthode interprete qui
permet d'interpréter l'instruction du point de vue mémoire.
Le seul point délicat de ce module fut de gérer les instructions de
déclaration.
En effet, que ce soit la déclaration d'une variable, d'une constante
ou d'une méthode, l'instruction JaJaCode est toujours la même :
-
new(nom, entier, var, 1)
- pour une variable ;
- new(nom, cst, var, 1)
- pour une constante ;
- new(nom, meth, var, 1)
- pour une méthode.
Il a fallu trouver le moyen de distinguer et d'orienter ces
déclarations vers leurs instructions respectives.
La solution adoptée est de faire une classe
JJCNew qui, en fonction du troisième argument (var,
cst ou meth) oriente l'instruction de déclaration désirée.
Une autre facette de la réalisation de cet interpréteur est
l'utilisation d'une classe abstraite.
Elle regroupe un ensemble de méthodes qui n'ont pas
d'implémentation. Chaque instruction JaJaCode est définie dans une
classe héritant de cette classe abstraite. Elle possède deux méthodes
qui sont toutes les deux abstraites. La première s'appelle
toString et la seconde se nomme interprete. Elles
permettent respectivement d'afficher l'instruction exécutée et
d'interpréter l'instruction souhaitée du point de vue de la mémoire.
La méthode toString ne prend aucun argument et retourne un
objet de type String contenant l'instruction en chaîne de
caractères de l'instance courante.
La seconde méthode quant à elle prend deux arguments en paramètres. Le
premier est l'état mémoire et le second est l'adresse de l'instruction
à exécuter. En retour, cette fonction rend l'adresse de la prochaine
instruction à exécuter.
Voici les entêtes de ces fonctions :
public abstract String toString();
public abstract int interprete(EtatMemoire mem, int addr);
Les classes représentant les instructions JaJaCode
sont des classes filles de la classe abstraite décrite au paragraphe
ci-dessus.
Elles contiennent donc chacune l'implémentation des méthodes
précédentes en fonction de l'instruction JaJaCode qui les
appelle.
La classe InterpretationJaJaCode permet de jouer le rôle
d'interface entre les trente-cinq classes présentées précédemment (trente-quatre classes
d'instruction JaJaCode et la classe abstraite JJCInstr) et les autres
modules du projet. Par interface, on entend que c'est cette classe qui
sera instanciée depuis un programme pour communiquer avec le module
interprétationJaJaCode.
Cette classe regroupe six méthodes :
-
Le constructeur
- prend un vecteur d'instructions en paramètre.
- nextInstruction()
- exécute la prochaine instruction qui
se trouve dans le vecteur d'instructions passé en paramètre du
constructeur. Cette méthode retourne l'adresse de la prochaine
instruction. Cela sert essentiellement à savoir si l'on est à la fin
du vecteur d'instructions (fin du programme d'instructions).
- getEtatMemoire()
- retourne l'état mémoire actuel.
- setBreakPoint(String buff)
- permet d'initialiser les
points d'arrêt qui ont été choisis. Cette méthode prend en argument
un objet String qui contient la liste des adresses ou il y a un
point d'arrêt. Cette liste se présente sous cette forme :
Exemple : 1,5,6,10,15 .
- gotoNextBreakPoint()
- permet d'aller directement
jusqu'au prochain point d'arrêt. Cela signifie que cette méthode va
interpréter les instructions JaJaCode jusqu'à rencontrer un point d'arrêt.
- InitInterpreteurJaJaCode()
- permet d'initialiser la
mémoire à vide et de replacer le pointeur d'instruction au début du
vecteur d'instructions.
- termineProgramme()
- permet de savoir si l'on est à la fin du programme JaJaCode.
- getNumeroLigne()
- retourne l'emplacement du pointeur
d'instruction, c'est-à-dire le numéro de ligne de la prochaine instruction à exécuter.
7.4 Problèmes de types
Bien que l'interpréteur JaJaCode fonctionne correctement, il n'en
reste pas moins que quelques défauts indépendants de la réalisation
subsiste. Ces défauts sont des problèmes de type de donnée.
Par exemple, pour les instructions add, sub, mul, div et
sup, on ne peut les effectuer que si les deux opérandes qui
vont subir cette opération sont du même type, à savoir des entiers.
Il en est de même pour les instructions or, and et
not qui ne peuvent s'effectuer que si les opérandes sont de
type booléen.
La seule opération qui peut être effectuée sans se soucier du type de
l'opérande est l'instruction cmp car si les deux opérandes
sont de types différents ou n'ont pas la même valeur, cette instruction
retourne faux.
Cette charge de tester le type des opérandes est déléguée au contrôleur de type.
7.5 Jeux de test
7.5.1 But des tests
-
Vérification du respect des règles (respect de la sémantique);
- vérification de la consommation du sommet de pile (vérifier qu'une règle sensée consommer un sommet de pile et son sous-sommet, par exemple, l'effectue bien).
7.5.2 Données nécessaires au passage des tests
Ci-dessous la liste des données nécessaires pour passer les tests
7.5.3 Réalisation
Pour réaliser ces tests, plusieurs techniques ont été abordées. La première a été d'utiliser un fichier pour écrire l'état de la pile d'exécution. Cette opération a été rendue possible grâce aux fonctions d'affichage en mode texte de la pile d'exécution.
Une fois le moyen de visualiser l'état mémoire déterminé, il a fallu trouver une façon de lire et d'exécuter les instructions JaJaCode une à une de manière à voir ce qui se passait dans la pile d'exécution après chaque instruction.
Cette étape est de loin la plus cruciale puisqu'elle permet de visualiser le bon fonctionnement et le respect des règles d'interprétation.
Pour ce faire, une classe Clavier a été développée. Cette classe dispose de fonctions statiques capables de faire des saisies au clavier.
La fonction utilisée pour les tests a été celle nommée
saisie_string() qui retourne une chaîne de caractères. Ainsi, cette classe a permis de créer une boucle qui demandait une saisie au clavier à chaque tour. Ce procédé a permis de faire du mode pas-à-pas et donc d'exécuter les instructions une à une avec tout le temps nécessaire entre deux instructions pour visualiser la pile d'exécution.
Cette boucle est présentée ci-dessous :
do {
System.out.println(addr + " : " + vect.get(addr).toString());
addr = ((JJCInstr)vect.get(addr)).interprete(mem, addr); (1)
fichier.ecrire(" debugger.txt", mem.toString()); (2)
try{
chaine = Clavier.saisie_string(); (3)
}
catch(IOException e) {
System.out.println(e);
}
}while (!chaine.equals(" s ")); (4)
Ont peut voir dans cette boucle les différentes techniques décrites plus haut.
L'instruction (1) interprète une instruction JaJaCode, l'instruction
(2) écrit dans un fichier nommé debugger.txt l'état de la mémoire et l'instruction (3) est une instruction bloquante d'une saisie de clavier.
Pour sortir de la boucle, il suffit de saisir la chaîne de caractères s (s pour sortir) (4).
Forts de cet outil, les tests ont pu commencer.
Le premier programme testé fut celui du TD n°7 puisque nous disposions
de la correction. Nous avons ainsi pu étudier les différents états de la
pile pour vérifier que ceux-ci correspondaient à la correction du TD.
Sont ensuite venus les différents programmes générés par le compilateur. Ces différentes listes d'instructions offraient une panoplie de jeux de test pour valider le bon fonctionnement de l'interpréteur JaJaCode.