Précédent Index Suivant

Chapitre 7 :   Variables de type enregistrement

7.1   Grammaire et sémantique

On définit le langage PP3 comme une (nouvelle) extension de PP1 autorisant la déclaration et la manipulation de structures (enregistrements) simples ; on abandonne donc temporairement les tableaux. On modifie la règle VARS pour autoriser la déclaration de structures ;
VARS ::= var DECLS ; | e
DECLS ::= DECL {, DECL}
DECL ::= ID | ID ( DECLS )
Sémantiquement, cela permet la déclaration de structures composées d'enregistrements qui sont eux-mêmes soit des variables simples, soit des structures.

Par exemple,
 var A, B (C, D), E, F (A, B (C, D)) ;  
définit deux variables simples A et E, et deux variables structures B et F. La structure B déclare deux variables entières simples tandis que l'enregistrement F déclare une variable simple et un enregistrement de deux variables.

On modifie les règles dans lesquelles figure le non-terminal ID (à savoir AFFEC, LIRE et FACT) pour autoriser la référence à un champ d'enregistrement par une notation pointée (règle VAR) :
VAR ::= ID {. ID}
AFFEC ::= VAR := EXPR
LIRE ::= read ( VAR { , VAR } )
FACT ::= VAR | NUM | ( EXPR )

Sémantiquement, une variable VAR doit toujours référencer une variable simple ; en particulier, les affectations directes entre structures ne sont pas permises.

7.2   Table des symboles

Les déclarations sont des arbres. Aussi, on les mémorisera dans la table des symboles sous forme d'arbres. Pour faciliter l'implantation dans la table des symboles, on associe à chaque variable un lien horizontal et un lien vertical. Le lien horizontal référence un frère dans la déclaration, le lien vertical référence le fils aîné. La figure 7.1 est une représentation de l'exemple de déclaration donné ci-dessus.

        A ---> B --------------> E ---> F ---> *  
        |      |                 |      | 
        V      V                 V      V
        *      C ---> D ---> *   *      A ---> B ---> * 
               |      |                 |      | 
               V      V                 V      V 
               *      *                 *      C ---> D ---> * 
                                               |      |  
                                               V      V 
                                               *      * 
Figure 7.1 : Représentation d'une déclaration d'enregistrements.


On remarque que les feuilles n'ont pas de lien vertical. On code cette structure dans la table des symboles par l'ajout de deux champs LV et LH.
var
   TABLESYM : array [TABLEINDEX] of record 
      NOM : ALFA ; 
      CLASSE : CLASSES ; 
      ADRESSE : integer ;
      LV : integer ; 
      LH : integer 
   end ; 
On prend comme convention que le champ ADRESSE représente un déplacement par rapport à l'adresse du père dans l'arbre et représente une adresse absolue (en fait relative à 0 !) pour le premier niveau de déclaration (dont les variables simples). Pour l'exemple
 var A, B (C, D), E, F (A, B (C, D)) ;  
on aura la table des symboles de la figure 7.2.

  NOM ADRESSE LH LV
0 A 0 1 *
1 B 1 4 2
2 C 0 3 *
3 D 1 * *
4 E 3 5 *
5 F 4 * 6
6 A 0 7 *
7 B 1 * 8
8 C 0 9 *
9 D 1 * *

Figure 7.2 : Table des symboles pour une déclaration d'enregistrements.


Dans cette table, une * signifie qu'un lien est terminal. On le codera par une valeur invalide, par exemple -1. La déclaration de l'exemple nécessite une allocation de 7 entiers (variable OFFSET).

Il faut écrire les deux procédures d'analyse des déclarations DECLS et DECL. Ces procédures retournent par un paramètre BASE l'adresse dans la table des symboles de la première entrée analysée. Cette adresse est utilisée pour rattacher la déclaration à son père dans l'arbre. Le code des deux procédures est donné aux figures 7.4 et 7.3.

 procedure DECLS (var BASE:integer) ; 
 var START : integer ; TBASE : integer ; 
 begin
    START := DERNIERSYMB ;
    DECL (TBASE) ; 
    while TOKEN = VIRG_TOKEN do 
       begin
          NEXT_TOKEN ; 
          DECL (TBASE)
       end ; 
    TABLESYM [TBASE]. LH := -1 ; (* fin du niveau de declaration *) 
    BASE := START 
 end ;  
Figure 7.3 : Procédure DECLS, analyse de déclarations d'enregistrements.



 procedure DECL (var BASE:integer) ; 
 var START : integer ; OLD_OFFSET : integer ; 
 begin
    if DERNIERSYM = INDEXMAX then ERREUR ;
    START := DERNIERSYMB ;
    DERNIERSYMB := DERNIERSYMB + 1 ; 
    if TOKEN <> ID_TOKEN then ERREUR ;
    with TABLESYM [START] do begin 
       NOM := SYM ; 
       CLASSE := VARIABLE ;
       ADRESSE := OFFSET ; 
       LH := -1 ; (* provisoirement *)
       LV := -1 (* provisoirement *)  
    end ; 
    NEXT\_TOKEN ; 
    if TOKEN = PAR_OUV_TOKEN then begin
    (* les offset sont comptes a partir du pere *)
       OLD_OFFSET := OFFSET ; OFFSET := 0 ;
       NEXT_TOKEN ;  
       DECLS (TABLESYM [START]. LV) ; 
       if TOKEN <> PAR_FER_TOKEN then ERREUR 
       else NEXT_TOKEN ; 
       OFFSET := OLD_OFFSET + OFFSET  
    end else 
       OFFSET := OFFSET + 1 ;
    TABLESYM [START]. LH := DERNIERSYMB ; 
    BASE := START 
 end ;  
Figure 7.4 : Procédure DECL, analyse de déclarations d'enregistrements.


7.3   Génération de code

La génération de code pour accéder à un champ de structure utilise l'adresse de ce champ. Cette adresse peut être définie lors de la compilation. Étant donnée la manière dont on a construit la table des symboles, pour obtenir l'adresse d'une variable il suffit d'additionner les champs ADRESSE des ID rencontrés.

On utilisera la procédure CHERCHERSYM modifiée :
 procedure CHERCHERSYM (var INDEX: TABLEINDEX ; 
                        PERMIS: CLASSET ;
                        DEBUT: TABLEINDEX) ;  
Le paramètre DEBUT indique l'entrée dans la table des symboles à partir de laquelle rechercher le symbole en suivant les liens LH. Comme par le passé, cette procédure retourne dans son premier paramètre INDEX l'indice dans la table des symboles du symbole trouvé.

On écrit ensuite la procédure VAR d'analyse d'une variable qui laisse sur la pile P-Code l'adresse de la variable :
 procedure VAR ; 
 var BASE : integer ; ADR : integer ;  
 begin 
    if TOKEN <> ID_TOKEN then ERREUR ; 
    else NEXT_TOKEN ; 
    CHERCHERSYM (INDEX, [VARIABLE], 0) ; (* niveau 0 *)
    ADR := TABLESYM [INDEX]. ADRESSE ; 
    while TOKEN = PT_TOKEN do 
       begin
          NEXT_TOKEN ; 
          if TOKEN <> ID_TOKEN then ERREUR 
          else NEXT_TOKEN ; 
          CHERCHESYM (INDEX, [VARIABLE], INDEX) ; 
          ADR := ADR + TABLESYM [INDEX]. ADRESSE        
       end ;
    GENERER2 (LDA, ADR)
 end ; 
Cette procédure est appelée par les trois procédures FACT, AFFEC, et LIRE que l'on modifiera en conséquence.

7.4   Travaux dirigés et travaux pratiques

  1. Construire le compilateur pp3 comme une modification de pp1 implantant la manipulation d'enregistrements telle qu'elle est décrite dans ce chapitre. On veillera de plus à modifier la production de la table des symboles (exercice 4).

  2. Les procédures DECL et DECLS données dans ce chapitre ne testent pas la présence éventuelle de doubles déclarations. Quelle information doit passer entre ces procédures pour détecter de telles déclarations. Implanter ce mécanisme de détection des déclarations fautives.

  3. La manipulation d'enregistrements peut être la source d'autres erreurs. Dresser une liste de ces erreurs et en ajouter la détection dans le compilateur.

    Modifier le code de recouvrement d'erreurs pour l'étendre au langage PP3.

    De nouveaux cas de figures de corrections d'erreurs apparaissent-ils ? Les prendre en compte.

Précédent Index Suivant