traduction dirigée par la syntaxe
DESCRIPTION
Traduction dirigée par la syntaxe. Grammaires attribuées Attributs synthétisés Attributs hérités Arbres syntaxiques Grammaires S-attribuées Grammaires L-attribuées - schémas de traduction - traduction descendante - traduction ascendante. Objectifs. - PowerPoint PPT PresentationTRANSCRIPT
Traduction dirigée par la syntaxe
Grammaires attribuées
Attributs synthétisés
Attributs hérités
Arbres syntaxiques
Grammaires S-attribuées
Grammaires L-attribuées
- schémas de traduction
- traduction descendante
- traduction ascendante
Objectifs
Faire la traduction pendant l'analyse syntaxique
Ajouter des actions portant sur les attributs des symboles
Obtenir un module qui fait à la fois l'analyse syntaxique et la traduction en une seule passe
Analyse descendante
Utiliser des fonctions qui ont des paramètres et renvoient des valeurs : les attributs
Analyse ascendante
La méthode est applicable avec Bison
Exemple
Grammaire attribuée pour une calculette
règle action
L --> E '\n' print(E.val)
E --> E + T E.val := E1.val + T.val
E --> T E.val := T.val
T --> T * F T.val := T1.val * F.val
T --> F T.val := F.val
F --> ( E ) F.val := E.val
F --> chiffre F.val := chiffre.val
Numérotation des non-terminaux
E --> E + T E.val := E1.val + T.val
Si la règle comporte plusieurs fois un même non-terminal
Une occurrence avec un indice dans l'action correspond à l'occurrence correspondante dans le membre droit de la règle
Une occurrence sans indice correspond au membre gauche de la règle
Arbres décorésEn ajoutant à un arbre de dérivation les attributs et leurs
valeurs, on obtient un arbre décoré
E
E+
\n
T
*F
nombre
T
F
nombre
T
F
nombre
.val=4
.val=4
.val=4
.val=5
.val=5
.val=3
.val=3
.val=3
.val=15
.val=15
.val=19
L
Grammaires attribuées
Une grammaire attribuée est définie par
- une grammaire
- des attributs associés à chaque symbole terminal ou non-terminal
- une action associée à chaque règle
X --> expr b := f(c1, c2, ... ck)
Une action peut avoir des entrées c1, c2, ... ck et des sorties b
qui sont des attributs de X et des symboles formant expr
Attributs synthétisés ou hérités
X --> expr b := f(c1, c2, ... ck)
L'attribut b est un attribut synthétisé si
dans toutes les actions où il est calculé, c'est un attribut de X
C'est un attribut hérité si
dans toutes les actions où il est calculé, c'est un attribut d'un des symboles formant expr
Exemple d'attributs synthétisés
Pour calculer les attributs synthétisés, on monte dans l'arbre
E
E+
\n
T
*F
nombre
T
F
nombre
T
F
nombre
.val=4
.val=4
.val=4
.val=5
.val=5
.val=3
.val=3
.val=3
.val=15
.val=15
.val=19
L
Exemple d'attributs hérités
Déclaration de variables en C
D --> T L L.type := T.type
T --> int T.type := integer
T --> float T.type := real
L --> L , id L1.type := L.type ;
ajouterType(id.entree, L.type)
L --> id ajouterType(id.entree, L.type)
L'attribut L.type est hérité
Construction d'un arbreUne grammaire attribuée qui construit un arbre représentant
une expression arithmétique
-
+
id num 4
id
entrée pour a
entrée pour c
. .
. .
.
.
Construction d'un arbreFonctions utilisées
makeNode(op, left, right) : crée un noeud dont l'étiquette est l'opérateur op et avec deux champs pour les pointeurs left et right
makeLeaf(id, entree) : crée un noeud dont l'étiquette est id et avec un champ pour un pointeur vers une entrée de la table des symboles
makeLeaf(num, val) : crée un noeud dont l'étiquette est num et avec un champ pour la valeur de la constante
Construction d'un arbre
E --> E + T E.ptr := makeNode('+', E1.ptr, T.ptr)
E --> E - T E.ptr := makeNode('-', E1.ptr, T.ptr)
E --> T E.ptr := T.ptr
T --> ( E ) T.ptr := E.ptr
T --> id T.ptr := makeLeaf(id, id.entree)
T --> num T.ptr := makeLeaf(num, num.val)
Les deux attributs E.ptr et T.ptr contiennent des pointeurs sur des arbres construits
Grammaires S-attribuées
Grammaires dont tous les attributs sont synthétisés
Le calcul des attributs peut se faire dans la pile de l'analyseur ascendant
état ... X Y Z
valeur ... X.x Y.y Z.z
Exemple
S --> E $
E --> E + T
E --> T
T --> T * F
T --> F
F --> ( E )
F --> N
Donnée Pile Valeurs Règle
3*5+4$ -
*5+4$ N 3
*5+4$ F 3 F --> N
*5+4$ T 3 T --> F
5+4$ T * 3 -
+4$ T * N 3 - 5
+4$ T * F 3 - 5 F --> N
+4$ T 15 T --> T * F
Grammaires S-attribuées
Calculer les attributs pendant les réductions
A --> X Y Z A.a := f(X.x, Y.y, Z.z)
Réduction :
- calculer A.a en fonction des valeurs contenues dans la pile
- dépiler X Y Z
- empiler A
- sauvegarder A.a dans la pile
état ... A
valeur ... A.a
état ... X Y Z
valeur ... X.x Y.y Z.z
Exemple
L --> E '\n' print(val[top-1])
E --> E + T val[ntop] := val[top - 2] + val[top]
E --> T /* inutile de recopier */
T --> T * F val[ntop] := val[top - 2] * val[top]
T --> F
F --> ( E ) val[ntop] := val[top - 1]
F --> chiffre
val[] : pile des valeurs d'attributs
top : taille actuelle de la pile
ntop : taille de la pile après la réduction en cours (se déduit de top et de la longueur de la règle)
Grammaires L-attribuées
Grammaire dans laquelle le calcul des attributs peut être fait lors d'un parcours en profondeur de l'arbre de dérivation
parcours(noeud n) {
pour chaque fils m de n {
calculer les attributs hérités de m ;
parcours(m) ; }
calculer les attributs synthétisés de n ; }
Définition formelle
Une grammaire est L-attribuée si
- tout attribut est synthétisé ou hérité ;
- dans une règle A --> X1 X2 ...Xn, si un attribut Xi.b est calculé dans l'action associée, il ne dépend que des attributs des variables X1 X2 ...Xi-1 ou des attributs hérités de A
Exemple : le langage EQNEQN est un langage de composition de texte permettant
d'écrire des formules mathématiques
Chaque formule est contenue dans une boîte virtuelle
Une boîte peut être en indice d'une autre
Dans ce cas le corps de caractères (taille) de la boîte indice est plus petit
De même pour une boîte en exposant
La hauteur d'une boîte (B.ht) dépend
- de la hauteur normale des caractères (texte.hn)
- du corps de caractères (B.cc)
- des indices ou exposants à l'intérieur
Exemple : le langage EQN
S --> B B.cc := 10 ;
S.ht := B.ht
B --> B B B1.cc := B.cc ;
B2.cc := B.cc ;
B.ht := max(B1.ht, B2.ht)
B --> B sub B B1.cc := B.cc ;
B2.cc := diminue(B.cc) ;
B.ht := position(B1.ht, B2.ht)
B --> texte B.ht := texte.hn * B.cc
diminue() applique un facteur d'échelle
position() modifie la hauteur à cause de l'indice
Schémas de traductionComme une grammaire attribuée mais précise quand on fait
les actions pendant un parcours de l'arbre en profondeur
Elles sont insérées dans les membres droits des règles
Si une action calcule un attribut d'un non-terminal du membre droit, elle doit être placée avant lui
Si une action utilise un attribut d'un non-terminal du membre droit, elle doit être placée après lui
Exemple : traduction postfixe des expressions additives
E --> T R
R --> addop T { print(addop.lexeme) } R | T --> num { print(num.val) }
Un schéma de traduction mal formé
S --> A A { A1.val := 1 ; A2.val := 2 }
A --> a { print(A.val) }
La deuxième condition n'est pas satisfaite
L'action qui calcule A1.val et A2.val est placée après
Le langage EQN
S --> { B.cc := 10 }
B { S.ht := B.ht }
B --> { B1.cc := B.cc }
B { B2.cc := B.cc }
B { B.ht := max(B1.ht, B2.ht) }
B --> { B1.cc := B.cc }
B sub { B2.cc := diminue(B.cc) }
B { B.ht := position(B1.ht, B2.ht) }
B --> texte { B.ht := texte.hn * B.cc }
Une grammaire L-attribuée peut toujours être mise sous la forme d'un schéma de traduction
Schémas de traduction en Bison
Dans une spécification Bison, on peut insérer les actions dans le membre droit des règles
Le traducteur engendré par Bison fera les actions au moment correspondantE : T R
;
R : addop T { printf($1) ; } R |
;
T : num { print($1) ; };
Traduction descendante
Pour l'analyse descendante, on élimine la récursivité à gauche dans la grammaire
Il faut aussi adapter les attributs
Exemple
E --> E + T E.val := E1.val + T.val
E --> E - T E.val := E1.val - T.val
E --> T E.val := T.val
T --> ( E ) T.val := E.val
T --> chiffre E.val := chiffre.val
Elimination de la récursivité à gauche
E --> T { E'.he := T.val } E' {E.val := E'.sy }
E' --> + T { E'1.he := E'.he + T.val } E' {E'.sy := E'1.sy }
E' --> - T { E'1.he := E'.he - T.val } E' {E'.sy := E'1.sy }
E' --> {E'.sy := E'.he }
T --> ( E ) {T.val := E.val }
T --> N {T.val := N.val }
L'attribut E'.he sert à transmettre la valeur de l'expression située à gauche
Elimination de la récursivité à gauche
E
T
nombre
E' .he=4
.val=9
.val=9E' .he=6
T
nombre .val=5
.val=5 T
nombre
.val=2
.val=2
+
E'
-
.he=9
Traduction descendante
Donnée : un schéma de traduction non récursif à gauche
Résultat : le code d'un traducteur descendant
Pour chaque non-terminal A, construire une fonction dont les paramètres sont les attributs hérités de A et qui renvoie comme valeur les attributs synthétisés de A (on suppose qu'il n'y en a qu'un)
Le code pour A décide quelle règle appliquer en fonction du symbole courant dans la donnée
Pour chaque attribut d'une variable du membre droit, déclarer une variable locale
Traduction descendante
Le code associé à une règle parcourt le membre droit et fait les actions suivantes :
- pour un symbole terminal X avec un attribut x, sauvegarder la valeur de x dans une variable locale et lire X
- pour un non-terminal B, faire c := B(b1, b2, ... bk) où c est l'attribut synthétisé de B et b1, b2, ... bk sont les attributs hérités de B
- pour une action, faire l'action
Traduction ascendante
Le problème est de calculer les attributs hérités
On effectue les actions seulement au moment où on réduit
Dans une règle A --> X1 X2 ...Xn, au moment où on passe Xi, on a dans la pile X1 X2 ...Xi-1 mais pas A
Si un attribut hérité de Xi dépend d'un attribut de A, quand et comment le calculer ? on ira le chercher dans la pile et non dans la règle
La méthode présentée est applicable à certaines grammaires L-attribuées dont la grammaire sous-jacente est LR(1)
Elimination des actions inséréesOn remplace le schéma de traduction par une grammaire L-attribuée en
remplaçant certaines actions par de nouveaux non-terminaux appelés marqueurs
Exemple
E --> T R
R --> + T { print('+') } R | - T { print('-') } R | T --> num { print(num.val) }
devient :
E --> T R
R --> + T M R | - T N R | T --> num { print(num.val) }
M --> {print('+') }
N --> {print('-') }
Schémas de traduction en Bison
Pour traiter un schéma de traduction Bison fait de même
R : addop T { printf($1) ; } R
|
;
devient :
R : addop T M R
|
;
M : { printf($1) ; }
;
Schémas de traduction en Bison
La numérotation des symboles dans le membre droit des règles tient compte des actions
Chaque action compte comme un symbole
R : addop T { printf($1) ; } R
|
;
L'attribut de R est dans $4
Trouver un attribut dans la pile
A --> X1 X2 ...Xn Quand on analyse Xi, les attributs hérités de A sont parfois
calculables à partir d'autres attributs qui sont déjà dans la pile
Exemple
D --> T L L.type := T.type
T --> int T.type := integer
T --> float T.type := real
L --> L , id L1.type := L.type ;
ajouterType(id.entree, L.type)
L --> id ajouterType(id.entree, L.type)
Trouver un attribut dans la piledonnée pile règle
int p , q , r $ p , q , r $ int
p , q , r $ T T --> int
, q , r $ T id
, q , r $ T L L --> id
, r $ T L , id
, r $ T L L --> L , id
r $ T L ,
$ T L , id
$ T L L --> L , id
$ D D --> T L
L.type vient de T.type
Le T en question est toujours juste au-dessous du L dans la pile
Trouver un attribut dans la pile
On peut donc accéder à une case de la pile au-dessous de la règle en cours
D --> T L /* inutile de recopier ici */
T --> int val[ntop] := integer
T --> float val[ntop] := real
L --> L , id ajouterType(val[top], val[top-3])
/* dans la pile : T L , id */
L --> id ajouterType(val[top], val[top-1])
/* dans la pile : T id */
L'action de la première règle est effectuée à la fin : trop tard pour recopier l'attribut
Trouver un attribut dans la pileCette méthode est applicable avec Bison
D : T L
;
T : int { $$ := INTEGER ; }
;
T : float { $$ := REAL ; }
;
L : L , id { ajouterType($3, $0) ;
/* dans la pile : T L , id */ }
| id { ajouterType($1, $0) ;
/* dans la pile : T id */ }
;
Pour descendre dans la pile : $0, $-1, $-2...
Trouver un attribut dans la pileS --> a A C C.he := A.sy
S --> b A B C C.he := A.sy
C --> c C.sy := f(C.he)
Quand on réduit c vers C, A.sy peut se trouver en val[top-1]ou en val[top-2]
Il faut modifier la grammaire
S --> a A C C.he := A.sy
S --> b A B M C M.he := A.sy ; C.he := M.sy
M --> M.sy := M.he
C --> c C.sy := f(C.he)
Quand on réduit c vers C, A.sy est toujours en val[top-1]
Trouver un attribut dans la pile
S --> a A C C.he := g(A.sy)
S --> b A B C C.he := A.sy
C --> c C.sy := f(C.he)
On modifie la grammaire
S --> a A N C N.he := A.sy ; C.he := N.sy
N --> N.sy := g(N.he)
S --> b A B M C M.he := A.sy ; C.he := M.sy
M --> M.sy := M.he
C --> c C.sy := f(C.he)
Quand on réduit c vers C, C.he est toujours en val[top-1]
Exemple : le langage EQN
S --> L B B.cc := L.cc ; S.ht := B.ht
L --> L.cc := 10
B --> B M B B1.cc := B.cc ; M.he := B.cc ;
B2.cc := M.sy ;
B.ht := max(B1.ht, B2.ht)
.cc corps de caractères : 10 points, 12 points...
.ht hauteur : distance entre la ligne de pied et le haut de la
boîte
.hn hauteur normale d'un caractère : hauteur du caractère
lorsqu'il est composé en corps 1
Exemple : le langage EQN
S --> L B B.cc := L.cc ; S.ht := B.ht
L --> L.cc := 10
B --> B M B B1.cc := B.cc ; M.he := B.cc ;
B2.cc := M.sy ;
B.ht := max(B1.ht, B2.ht)
M --> M.sy := M.he
B --> B sub N B B1.cc := B.cc ; N.he := B.cc ;
B2.cc := N.sy ;
B.ht := position(B1.ht, B2.ht)
N --> N.sy := diminue(N.he)
B --> texte B.ht := texte.hn * B.cc
Exemple : le langage EQN
S --> L B val[ntop] := val[top]
L --> val[ntop] := 10
B --> B M B val[ntop] := max(val[top-2], val[top])
/* dans la pile : L B M B */
M --> val[ntop] := val[top-1]
/* dans la pile : L B */
B --> B sub N B val[ntop] := position(val[top-3], val[top])
N --> val[ntop] := diminue(val[top-2])
/* dans la pile : L B sub */
B --> texte val[ntop] := val[top] * val[top-1]
/* dans la pile : L B */
Algorithme général
Donnée : une grammaire L-attribuée
Résultat : un traducteur ascendant
On suppose que chaque non-terminal A a un attribut hérité A.he et que chaque symbole X a un attribut synthétisé X.sy
Remplacer chaque règle A --> X1 X2 ...Xn par
A --> M1 X1 M2 X2 ... Mn Xn
Associer les Xi.he aux Mi
Quand on réduit vers Mi, la position de A.he, X1.he X2.he ... dans la pile se déduit de la nouvelle grammaire
Résumé
Les schémas de traduction permettent d'incorporer la traduction à l'analyse syntaxique pour obtenir un traducteur en une passe
Les attributs synthétisés sont faciles à calculer
- analyse descendante : valeurs des fonctions associées aux non-terminaux de la grammaire
- analyse ascendante : dans la pile
Les attributs hérités sont calculés
- en analyse descendante : comme paramètres des fonctions
- en analyse ascendante : en remontant dans la pile, et s'il le faut en introduisant des non-terminaux "marqueurs"