Previous Up Next

Chapter 8  Interprétation MiniJaJa

L'objectif de l'interpréteur MiniJaJa est d'interpréter le code MiniJaJa préalablement transformé en Arbre de Syntaxe Abstraite (ASA) par le parser MiniJaJa.

8.1  Idée générale

8.1.1  Utilisation de l'ASA et application des règles d'interprétation

Afin d'interpréter un code MiniJaJa, il est nécessaire d'utiliser un arbre de syntaxe abstraite. Si ce code est lexicalement et sémantiquement correct, c'est-à-dire que l'ASA aura pu être généré correctement, il suffit qu'il n'y ait pas d'erreur de typage pour qu'il puisse être interprété. C'est pourquoi, une fois l'ASA généré, il doit subir le contrôle de type. Sur cet ASA, il sera possible d'appliquer toutes les règles d'interprétation étudiées pendant les cours de compilation. Ces règles doivent s'appliquer sur chaque noeud de l'arbre concerné par une règle d'interprétation. Plus globalement, cette interprétation est basée sur un parcours en profondeur de l'ASA.

8.1.2  Objectif de l'interpréteur

L'objectif de l'interpréteur MiniJaJa est de voir le résultat de l'exécution d'un code MiniJaJa. Toutefois, le langage MiniJaJa ne permet pas de faire d'entrées/sorties sur les périphériques d'entrées/sorties standards. Il est donc nécessaire pour constater le bon fonctionnement du code à interpréter de visionner l'état de la mémoire pour observer les modifications des variables éventuellement déclarées. Étant donné qu'à la fin de l'interprétation le contexte de la mémoire doit être complètement libéré (ie : la pile et le tas doivent être vides), il est nécessaire de regarder l'évolution de l'état mémoire pendant l'interprétation. C'est pourquoi nous avons dû mettre en place un système de points d'arrêt.

8.2  Réalisation

Un certains nombre de points particulièrement intéressants nous ont obligés a faire des choix techniques lors du développement, c'est ce que nous allons détailler maintenant.

8.2.1  Parcours de l'ASA, paradigme visiteur

Nous avons vu que le développement de l'interpréteur MiniJaJa reposait essentiellement sur le développement du parser générant l'ASA. Il aurait été tout-à-fait envisageable d'intégrer le développement de cet interpréteur dans celui du parser étant donné que le travail s'effectue sur la même structure. Toutefois, une des contraintes du développement de l'ASA est que chaque fois qu'une amélioration y est apportée, il faut régénérer à l'aide des outils JJTree et JavaCC les fichiers constituant le parser. Or c'est sur ces mêmes fichiers qu'il nous fallait travailler pour développer l'interpréteur MiniJaJa. Au vu de ces contraintes, nous avons choisis d'utiliser le paradigme de programmation visiteur. Grâce à cela, le développement des deux parties a pu être complètement indépendant et les évolutions de l'ASA ont pu être possibles même après le commencement du développement de l'interpréteur MiniJaJa. L'utilisation de cette méthode nécessite l'implantation de l'interface ParserVisitor générée lors de la génération du parser MiniJaJa. Le fonctionnement du paradigme visiteur est détaillé dans le chapitre compilation (cf. 6.2.2 page ??).

8.2.2  Utilisation la pile

Pendant l'interprétation, la pile est constamment modifiée, que ce soit pour déclarer des méthodes ou des variables ou encore pour modifier ces dernières. Ces ajouts, consultations ou modifications dans la pile sont basées sur les méthodes publiques disponibles dans la classe EtatMemoire. Les modifications de tableaux, donc dans les tas, sont transparentes à l'utilisation et sont gérées directement par la pile.

8.2.3  Retraits dans la pile

Durant l'interprétation d'un programme MiniJaJa, la pile est utilisée pour stocker les variables et les fonctions. Cela dit, lorsqu'un élément de pile est devenu inutile il faut le supprimer. Par exemple après l'exécution d'une fonction, il faut supprimer les variables locales. De même, à la fin programme, il faut supprimer de la pile toutes les déclarations de variables, les déclarations de fonctions et la déclaration de la classe. Ces retraits s'effectuent à la fin des noeuds classe, appelI et appelE, en utilisant la méthode retirerDecl() de la pile.

8.2.4  Fonctions

Dans un programme MiniJaJa, la déclaration des fonctions est placée avant l'exécution du programme principal. Aussi, il ne faut pas exécuter ce code avant que la fonction soit appelée sinon le résultat sera incorrect. La solution technique employée pour résoudre ce problème est qu'au moment de la déclaration de la fonction, le noeud correspondant est empilé afin d'être réutilisé au moment de l'appel. Au moment de l'appel d'une fonction, c'est-à-dire lorsque l'on rencontre à l'interprétation le noeud appelI ou appelE, il faut effectuer les actions suivantes : Il y a une limitation dans l'utilisation des fonctions avec retour. En effet, si la fonction comporte plusieurs instructions return, l'interpréteur rendra un résultat erroné puisqu'il fera le retour seulement avec la dernière instruction return. Ainsi le code suivant :
if (a==1) {
  return 1 ;
}else{
  return 2 ;
} ;
Doit être remplacé par un code de la sorte :
if (a==1) {
  temp=1 ;
}else{
  temp=2 ;
} ;
return temp ;

8.2.5  Choix du bon fils

Dans certains cas, il faut savoir quelle règle utiliser étant donné qu'à un noeud peut correspondre deux règles d'interprétation. C'est le cas des noeuds somme et affectation. Il est en effet possible de les utiliser soit avec des variables simples, soit avec des vecteurs. Il faut donc anticiper et regarder si le fils concerné du noeud affectation ou somme est de type tableau ou variable. Pour cela, il y a deux méthodes. La première consiste à afficher le noeud fils en chaîne de caractères et à regarder la valeur retournée. Cette valeur correspond au nom du noeud dans l'ASA. Par exemple :
if ((node.jjtGetChild(0).toString())=="tab"){
  //code a exécuter
}
Dans cet exemple on regarde si le fils est un noeud tab, ce qui nous assure que le traitement à effectuer par la suite concerne un tableau. Il est aussi possible d'utiliser la méthode Java instanceOf qui permet de voir si un objet est une instance d'une classe. L'exemple précédent donnerait le code suivant :
if ((node.jjtGetChild(0) instanceOf  ASTtableau){
  //code à exécuter
}

8.2.6  Récupérer l'identifiant ou la valeur d'une variable

Un problème qu'il est fréquent de rencontrer est que pour une variable on veuille récupérer son identificateur et d'autres fois sa valeur. Pour résoudre ce problème, nous utilisons le second champ (data) de la méthode visit() (cette méthode provient de l'implantation de l'interface ParserVisitor). En effet, lorsque nous voulons que le fils retourne l'identificateur nous passons le paramètre Integer(0) et Interger(1) s'il nous faut la valeur.

8.2.7  Points d'arrêt

Pour visionner correctement l'état mémoire en cours d'interprétation, il a fallu développer un système de points d'arrêt. Pour cela nous avons choisi d'utiliser le système des threads. Le principe est que la classe contenant l'interpréteur MiniJaJa est une classe héritant de la classe Java Thread, ainsi il est possible de suspendre et de redémarrer l'exécution du processus. Les points d'arrêt sont matérialisés dans chaque noeud de l'ASA par une variable booléenne. Cette variable est positionnée à vrai ou à faux au moment de l'instanciation de la classe contenant l'interpréteur MiniJaJa. En effet, l'interface passe en paramètre une liste de numéros correspondant aux lignes où il faut mettre un point d'arrêt et ensuite l'interpréteur lance une méthode qui va parcourir l'ASA et positionner la valeur booléenne aux bons endroits. Tous les noeuds ne sont pas concernés par les points d'arrêt. En effet, il n'y a pas d'intérêt à mettre un point d'arrêt sur un noe ud qui concerne plus d'une ligne dans le programme MiniJaJa ou encore sur un noeud terminal. C'est par exemple le cas des noeuds decls, vars ou encore enil. De même, il ne faut pas mettre plusieurs points d'arrêt sur une même ligne. La solution ici employée est de mettre le point d'arrêt sur le noeud de plus haut niveau dans l'ASA. Au moment de l'interprétation du programme, si un point d'arrêt est trouvé, il faut effectuer les opérations suivantes : L'attente de l'événement de l'interface est réalisé dans une boucle tant que . Cependant, pour éviter de surcharger la machine, un sleep de 20 ms est utilisé à l'intérieur de la boucle. Le délai de 20 ms permet un bon équilibre entre le temps de réaction vis-à-vis de l'interface et la charge de la machine. L'utilisation de cette méthode pour suspendre l'exécution du thread est préférable à l'emploi des méthodes suspend() et resume() qui sont dépréciées depuis la version 1.1 du JDK et qui peuvent rendre le système instable.

8.3  Jeux de test

Les tests ont été concentrés sur des programmes MiniJaJa contenant des instructions qui modifient ou utilisent la mémoire, qui font des boucles ou encore qui appellent des fonctions. Les noeuds de l'ASA concernés sont donc : classe, tableau, main, cst, var, methode, affectation, affectationT, increment, incrementT, appelI, appelE, retour, somme,sommeT, si, tantque, non, moins, op2.
Previous Up Next