Il existe deux contrôles de type. Le contrôle de type statique qui
analyse l'arbre de syntaxe abstraite (ASA) notamment pour rechercher
les éventuelles incompatibilités de type entre les opérandes, avant
que celui-ci ne soit parcouru par le module de compilation. Le
contrôle de type dynamique qui collabore avec l'interpréteur lors de
l'exécution du code, pour éviter les débordements de pile ou
l'adressage de parcelles de mémoire non allouées.
Seul le contrôle de type statique a été mis en place dans le cadre de
ce projet.
5.1 Idée générale
Le contrôle de type intervient dans le cadre de l'analyse sémantique
du programme. Pendant cette phase, on effectue un certain nombre de tests
sur l'arbre de syntaxe abstraite (ASA) afin d'examiner si
l'assemblage des constituants du programme a un sens.
Afin d'explorer l'ASA, nous utilisons le paradigme de programmation
visiteur . Au fur et à mesure du parcours, nous réalisons
certaines opérations ayant pour but de construire une table des
symboles et de contrôler le type des noeuds.
5.2 Réalisation
5.2.1 Choix des structures de données
Le module contrôle de type doit stocker deux types d'informations : la
table des symboles et la liste des méthodes d'une part, une liste des
différentes incohérences rencontrées pendant son exécution d'autre
part.
L'emploi du type Vector proposé par Java est parfaitement
adapté puisque son utilisation se rapproche de celle des tableaux,
tout en proposant une taille variable.
5.2.2 Utilisation du paradigme de programmation Visiteur
Dans le module contrôle de type, une chaîne de caractères est retournée
par le visiteur lorsque celui-ci visite un noeud. Par
exemple, le visiteur retourne la chaîne de caractères
booleen lorsqu'il est dans le noeud = (ASTegal), si
les deux fils du noeud sont du même type. Lorsque les fils du noe
ud sont des feuilles, on consulte la table des symboles pour vérifier
d'une part si les symboles ont bien été déclarés, et d'autre part, si
leurs types concordent avec ce que le noeud exige.
Les différentes chaînes retournées sont les suivantes :
-
Ok : pour les listes d'instructions
- entier : pour les noeuds entiers
- booleen : pour les noeuds booléens
- erreur : pour les noeuds contenant des erreurs.
Exemple : le visiteur du noeud Si
public Object visit(ASTsi node, Object data){
String typNoeud1=null; // Type du premier noeud
String typNoeud2=null; // Type du second noeud
boolean err=false; // booleen qui passe à false si il y a une erreur
String retour; // Chaine de caractere retournée par le noeud
boolean trouve; /* booléen qui passe à true, si la
variable,constante a été trouvée */
// On récupère le type du premier noeud
typNoeud1=(String) node.jjtGetChild(0).jjtAccept(this,data);
// Si typNoeud1==null, le premier noeud est une feuille
if (typNoeud1==null) {
// On récupère le nom de la feuille
String noeud1=new String(node.jjtGetChild(0).toString());
// On regarde si le nom correspond à une variable
trouve=chercherVar(noeud1,node.getLigne());
if(trouve){
// Si c'est le cas, on recupère le type de la variable
typNoeud1=getTypeVar(node.jjtGetChild(0).toString());
} else {
// Sinon, on regarde si le nom correspond à une constante
trouve=chercherTab(noeud1,node.getLigne());
if(trouve){
// Si c'est le cas, on récupère le type de la constante
typNoeud1=getTypeTab(node.jjtGetChild(0).toString());
} else {
// Sinon, on ajoute le nom de la feuille comme étant non déclaré
ajouteNonDeclare(node.jjtGetChild(0).toString(),node.getLigne());
}
}
}
// Si le 1er noeud n'a pas de type (feuille non déclarée)
if(typNoeud1==null){
err=true;
// On ajoute une erreur
erreur e1 = new erreur(TYPE_INCORRECT,
"if : booleen attendu",
node.getLigne());
listeErreur.addElement(e1);
} else {
// Si le 1er noeud n'est pas de type booléen
if(typNoeud1.compareTo("booleen")!=0){
err=true;
// On ajoute aussi une erreur
erreur e2 = new erreur(TYPE_INCORRECT,
"if : booleen attendu",
node.getLigne());
listeErreur.addElement(e2);
}
}
// On recupère le second noeud
typNoeud2=((String) node.jjtGetChild(1).jjtAccept(this,data));
if(typNoeud1==null){
err=true;
} else {
// On vérifie qu'il s'agit bien d'un noeud d'instruction
if(typNoeud1.compareTo("ok")!=0){
err=true;
}
}
// Si tout est OK, on renvoie "ok", "erreur" dans les autres cas
if(err==true){
retour=new String("erreur");
} else {
retour=new String("ok");
}
return retour;
}
5.2.3 Gestion des variables
Portée des variables
Lorsque le visiteur rencontre des noeuds correspondant à des déclarations, il met à jour la table des symboles mais aussi l'ASA. En effet pour prendre en compte la portée des variables, on renomme les variables de cette façon :
-
Variable globale
- : la variable a devient globalea.
- Variable locale à la fonction test
- : la variable a devient testa.
Table des symboles
On utilise trois Vector Java pour représenter la table des symboles.
-
listeVar
- contient les variables.
- listeTab
- contient les tableaux.
- listeCst
- contient les constantes.
Chacun de ces Vector contient deux chaînes de caractères par enregistrement, le type du symbole et le nom de celui-ci.
5.2.4 Table des méthodes
Là aussi on utilise un Vector : listeMethode. Il sert à
enregistrer les différentes fonctions déclarées. Le type de retour, le
nom, puis les types des paramètres de la fonction sont enregistrés
dans un premier vecteur. Ce vecteur sera ensuite ajouté dans
listeMethode.
Figure 5.1: Le vecteur listeMethode
Lors des appels de fonction, on parcourt listeMethode pour
trouver les informations inhérentes à la fonction et ainsi vérifier la
pertinence des appels.
5.2.5 Les erreurs
Lorsque le module rencontre des incohérences sémantiques dans l'ASA,
il crée une nouvelle erreur. La structure erreur est composée de trois
champs :
-
Un entier représentant le type de l'erreur : NON_DECLARE pour
les symboles non déclarés. TYPE_INCORRECT lorsque les types ne
concordent pas.
- Une chaîne de caractères correspondant à la description de l'erreur.
Exemple : if : booleen attendu
- Un entier représentant la ligne à laquelle l'erreur s'est produite.
5.3 Jeux de test
Voici la liste des tests effectués sur le module contrôle de type :
Tests sur la portée des variables :
-
Test avec deux variables a (une locale, l'autre globale).
- Test avec deux constantes c (une locale, l'autre globale).
- Test avec deux tableaux t (le premier local, l'autre global).
Test concernant la table des symboles :
-
Tentative d'affectation/d'utilisation d'une variable non déclarée.
- Tentative d'affectation/d'utilisation d'un tableau non déclaré.
- Tentative d'utilisation d'une constante non déclarée.
- Tentative d'affectation d'une constante après sa déclaration.
Tests sur l'incompatibilité de type des opérandes :
-
Déclaration d'un entier, en essayant de lui affecter true. int a=true
- Affectation entre variables de types différents.
- Test d'égalité entre deux noeuds de types différents. if(entier==boolean)
- Test d'incrémentation d'une variable booléenne.
- Test si l'indice d'un tableau est bien de type entier.
- Test d'opérations booléennes sur des noeuds de type entier.
- Test d'opérations arithmétiques sur des noeuds de type booléen.
Tests sur les instructions :
-
Tests sur le type des noeuds des instructions (par exemple, le premier noeud d'une instruction if doit être un booléen).
Tests sur les fonctions :
-
Appel d'une fonction non déclarée.
- Tests concernant le retour des fonctions.
- Tests concernant le nombre et le type des paramètres des fonctions.