Previous Up Next

Chapter 6  Compilation

L'objectif du module de compilation est de traduire les instructions MiniJaJa contenues dans l'arbre de syntaxe abstraire en instructions JaJaCode.

6.1  Idée générale

Pour réaliser la traduction nous utilisons le paradigme de programmation visiteur, qui nous permet d'être totalement indépendants des modifications qui ont lieu sur l'analyseur lexical et syntaxique. Nous avons donc un visiteur qui parcourt l'arbre de syntaxe abstraite, à chaque noeud qu'il rencontre, il effectue les actions mentionnées dans les règles de traduction du MiniJaJa vers le JaJaCode.

6.2  Réalisation

6.2.1  Choix des structures de données

Le premier problème que nous avons eu a résoudre fut le choix de la structure de données à adopter pour stocker les instructions JaJaCode. Nous avons fait ce travail avec l'équipe du module Interprétation JaJaCode  afin de trouver la structure la mieux adaptée pour la traduction et l'interprétation. L'interprétation du JaJaCode est linéaire, avec des sauts conditionnels donc du point de vue de l'interprétation c'est sous forme de tableau d'instructions qu'il faudrait stocker le JaJaCode, les indices du tableau représentant les adresses des instructions. Toutefois le tableau a un inconvénient : sa taille fixe, et il est impossible de prévoir à l'avance le nombre d'instructions que va générer la traduction. Du point de vue de la traduction c'est la liste chaînée qui est la plus facile à manipuler. Java a coupé la poire en deux puisqu'il met à disposition le type Vector qui permet l'utilisation de tableaux non bornés. C'est naturellement cette structure de données que nous avons choisi d'utiliser.

6.2.2  Le paradigme de programmation visiteur

Ce paradigme a été utilisé dans beaucoup de modules du projet. Nous allons voir en quelques lignes en quoi il consiste. Le principe du motif Visiteur consiste à réaliser une classe agissant sur les données issues d'autres classes. Imaginons un ensemble de classes dérivées d'une classe mère, chacune implémentant une méthode draw(). Le principe du Visiteur va consister à déplacer toutes les méthodes draw() dans le Visiteur pour ne pas dupliquer de code entre les différentes classes. La réalisation d'une opération de visite se déroule suivant un protocole particulier. Chaque classe susceptible de se voir visitée doit contenir une méthode intitulée accept(Visitor v). Le code de cette méthode exécutera en retour l'instruction v.visit(this) pour signifier au Visiteur qu'il possède le droit de venir voir ce qu'il se passe dans cette classe.

6.2.3  Gestion des adresses

La gestion des adresses est modifiée par rapport au règles de traduction. En effet, dans les règles, les instructions manipulant des adresses (if et goto) se basent sur l'adresse n qui représente l'adresse de début de la règle courante. Cependant les instructions de branchement ont besoin de connaître le nombre d'instructions générées par l'analyse de noeud qui interviennent après le branchement. Par exemple: [ctantque]: n |- e Þ {pe, ne}, n+ne+2 |- iss Þ {piss, niss}/n |- tantque(e, iss) Þ {((pe ÅD not) ÅD (if(n + ne + niss + 3)) Å piss ÅD goto(n)), ne + niss + 3 }
Dans cette règle on a l'instruction if qui est ajoutée à la liste d'instructions avant les instructions piss. Or if a besoin de niss, le nombre d'instructions associées à piss, et il ne peut l'avoir à ce moment là puisqu'il ne sera connu qu'après l'analyse du noeud de l'ASA correspondant (iss). Pour résoudre ce problème, nous appliquons le proverbe Il faut remettre à plus tard ce que l'on peut faire maintenant. C'est à dire qu'au moment d'ajouter l'instruction if à la liste, c'est une instruction nop qui est ajoutée (on prend alors soin de sauvegarder l'emplacement de cette instruction). Puis, lorsque l'on a toutes les informations pour constituer le if, on remplace l'instruction nop à l'adresse sauvegardée par le if dûment rempli. Dans notre cas c'est après l'analyse du noeud iss que l'on procède à l'opération. A ce moment là, l'adresse de l'instruction courante est n + ne + niss + 2, donc on remplace le nop par un if(adresse_instruction_courant + 1).

6.2.4  Gestion des retraits

Pour gérer les règles de retraits, nous passons en argument de la fonction de visite un tag , qui est une chaîne de caractères qui indique au noeud appelé qu'il doit utiliser les règles de retrait pour ce noeud au lieu des règles traditionnelles. Exemple de méthode :
  public Object visit(ASTcst node, Object data) {
    if((String) data == "retrait") {
      // swap
      vectjjc.addElement(new JJCSwap());
      
      // pop
      vectjjc.addElement(new JJCPop());
    }
    else {
      // t - type
      String type = (String) node.jjtGetChild(0).toString();
      
      // i - identificateur
      String ident = (String) node.jjtGetChild(1).toString();
      
      // pe - expression
      node.jjtGetChild(2).jjtAccept(this,data);
      
      // new(i, t, cst, 0)
      vectjjc.addElement(new JJCNew(ident, type, "cst", 0));
    }
    return null;
  }
Nous avons ici un noeud (cst) qui a une règle de retrait. Si la chaîne de caractères retrait est passée en paramètre de la méthode ce seront les instructions de retrait qui seront ajoutées à la liste d'instructions JaJaCode. Sinon c'est la règle normale  qui s'applique. Exemple d'appel :
  public Object visit(ASTclasse node, Object data) {
    // init
    vectjjc.addElement(new JJCInit());
    
    // pdss - liste de declarations
    node.jjtGetChild(1).jjtAccept(this, data);
    
    // pmma - methode main
    node.jjtGetChild(2).jjtAccept(this, data);
    
    // prdss - retrait liste de declarations
    node.jjtGetChild(1).jjtAccept(this, "retrait");
    
    // pop
    vectjjc.addElement(new JJCPop());
    
    // jcstop
    vectjjc.addElement(new JJCJcstop());
    
    return null;
  }
Dans cette exemple on voit que pour retirer les déclarations dans le noeud classe on demande le parcours du noeud déclarations mais avec comme paramètre la chaîne de caractères retrait

6.2.5  Problème du un noeud - deux règles

Ce problème est de même nature que celui du retrait. Il provient du fait que l'arbre de syntaxe abstraite ne fait pas la différence entre certains noeuds. Par exemple pour une affectation, l'ASA renvoie un noeud ASTaffectation pour les variables et les tableaux. Or, le compilateur n'effectue pas les mêmes actions s'il doit traduire une affectation de variable ou de tableau. Afin de faire la différence entre les deux cas nous utilisons la méthode Java instanceOf qui nous indique quel est le type du noeud que nous traitons et donc nous permet d'appliquer les règles adéquates lorsque l'ASA n'est plus assez précis.

6.2.6  Traitement des erreurs

Le module de compilation ne génère pas d'erreurs. En effet, il se limite à l'application de règles qui transforment un code en un autre. Il n'a cependant pas de moyens de savoir si ces codes sont justes ou faux. S'il y a un problème syntaxique il sera levé lors de l'analyse lexicale et syntaxique. S'il y a un problème sémantique il ne pourra apparaître que lors de l'interprétation JaJaCode.

6.3  Jeux de test

Les tests effectués sur le module compilateur sont les suivants : Tests sur la génération de code : Tests sur les structures de données utilisées :
Previous Up Next