Université Laval Faculté des sciences et de génie Département d'informatique et de génie logiciel IFT-3101 Danny Dubé Hiver 2014 Version : 11 avril Questions Travail pratique #2 Traduction orientée-syntaxe Code intermédiaire 1. (25 points.) Dénitions orientées-syntaxe. En vous basant sur l'une ou l'autre des grammaires suivantes, montrez comment reconnaître les langages donnés à l'aide d'une dénition orientée-syntaxe. Dans chaque dénition orientée-syntaxe que vous concevez, l'attribut S.ok devrait être un booléen qui indique si le mot est dans le langage ou pas. S A A a A 1 b A 1 c A 1 ɛ S A A a A 1 B B b B 1 C C c C 1 ɛ (a) Le langage 1 régulier L 1. Théoriquement, L 1 est simple parce qu'il est reconnaissable à l'aide d'un automate ni mais il serait terriblement pénible à décrire à l'aide d'une expression régulière. L 1 = { } w {a, b, c} 24 w 2 a + 4 w b + 14 w 3 c 1000 (mod 2014). (b) Le langage hors-contexte L 2. Bien que hors-contexte, L 2 serait pénible à décrire à l'aide d'une grammaire. L 2 = { } a i b j c k 7i + 3j = 18k + 2014. (c) Le langage L 3, lequel n'est pas hors-contexte. L 3 = {w {a, b, c} w a w b w c }. (d) Le langage L 4, lequel n'est pas hors-contexte. L 4 = {u c v c v c w v {a, b} et u, w {a, b, c} }. 1. L'expression w c dénote le nombre d'apparition du symbole c dans la chaîne w. C'est un peu comme l'opérateur de longueur mais spécialisé pour le comptage d'un symbole spécique.
2. (15 points.) Dénitions orientées-syntaxe. Supposons que nous avons un programme qui manipule des arbres AVL. Il s'agit d'une sorte d'arbres binaires de recherche qui sont quasi-balancés. Notre programme a la capacité de sauver des arbres sur disque et de les relire. La syntaxe utilisée pour la représentation externe des arbres est donnée par la grammaire hors-contexte ci-bas. Un arbre vide est dénoté par empty. Un arbre non-vide est dénoté par la description de sa racine, [t 1, x, t 2 ], où x est appelée une clé et où t 1 et t 2 sont des sous-arbres. Ici, une clé est un entier. An de nous assurer de l'intégrité des données, les invariants propres aux arbres AVL doivent être vériés, lesquels sont : en traversant un arbre en profondeur d'abord de gauche à droite, les clés que l'on retrouve dans les noeuds sont ordonnées et les deux sous-arbres de tout noeud interne ont des hauteurs qui dièrent d'au plus 1. La hauteur d'un arbre t est dénie comme la distance entre la racine de t et sa feuille la plus éloignée. Par exemple, empty a pour hauteur 0, [empty, x, empty] a pour hauteur 1, [empty, x, [empty, y, empty]] a pour hauteur 2, etc. Ajoutez des règles sémantiques à la grammaire hors-contexte suivante an que les deux invariants soient vériés. Le non-terminal S doit synthétiser un attribut booléen `S.ok' indiquant le respect ou non des invariants. S T T [ T 1, num, T 2 ] empty Par exemple, l'arbre suivant n'est pas valide car ses clés ne sont pas ordonnées : [[empty, 14, empty], 12, [empty, 18, empty]] et l'arbre suivant n'est pas valide car il est trop débalancé : [empty, 1, [empty, 2, [empty, 3, empty]]]. 3. (15 points.) Systèmes de traduction. Considérons le système de traduction suivant. Il eectue un certain calcul sur les éléments d'une liste. Vous devez éliminer la récursion à gauche de la grammaire sous-jacente et, bien entendu, adapter les calculs faits par le système de traduction, an que le nouveau système de traduction eectue les mêmes calculs. S [ L ] { S.s := L.s } L L 1, num { L.i := L 1.i + 1 ; L.s := L 1.s + L.i num.lexval } L num { L.i := 1 ; L.s := L.i num.lexval }
4. (25 points.) Typage. Cet exercice consiste à concevoir une dénition orientée-syntaxe qui eectue le typage pour un langage de programmation hypothétique. Il s'agit d'un langage de programmation fonctionnel. Nous nous intéressons principalement aux expressions du langage et ignorons presque entièrement les problématiques de la gestion des variables. Le langage est décrit plus bas. La syntaxe des expressions et des types est décrite à la fois à l'aide d'une grammaire hors-contexte et d'explications informelles. La grammaire ne spécie ni la priorité ni l'associativité des opérateurs du langage et n'est absolument pas adaptée à l'analyse prédictive. On peut voir cette grammaire comme étant celle de la syntaxe abstraite. Examinons le langage de programmation. Dans ce langage, des données des types suivants sont disponibles : les nombres, les booléens et les listes. À l'intérieur du compilateur, le type d'un nombre est number, celui d'un booléen est boolean et celui d'une liste est [t] où les éléments de la liste sont du type t. E id Référence à une variable num Constante numérique true Constantes booléennes false [ ] : T Liste vide [ L ] Liste non-vide if E then E else E end Expression conditionnelle - E Opération unaire - & E Opération unaire & E @ E Opération binaire @ E * E Opération binaire * E / E Opération binaire / E + E Opération binaire + E - E Opération binaire - E < E Opération binaire < E = E Opération binaire = L E Suite d'une ou... L, L... de plusieurs expressions T number boolean [ T ] Type des listes Figure 1 Grammaire hors-contexte pour la syntaxe abstraite du langage. À la gure 1, on retrouve la grammaire. L'essentiel de la grammaire nous donne la forme des expressions ordinaires ainsi que celle des expressions de types. Trois sortes de symboles sont utilisés : les non-terminaux, écrits sous la forme de lettres majuscules, comme E, les terminaux qui sont leur propre catégorie lexicale, écrits avec une fonte de dactylo, comme true, et les terminaux variables, écrits en gras, comme num. Parmi tous les terminaux, seul le terminal id a un attribut signicatif,
appelé id.entry, lequel est le lien vers la table des symboles pour l'identicateur. En premier lieu, on retrouve les E-productions, lesquelles génèrent les expressions du programme. Ensuite, on retrouve les L-productions, lesquelles génèrent les suites d'expressions qui apparaissent dans les formes syntaxiques de construction de listes. Enn, on retrouve les T -productions, lesquelles génèrent les expressions de types au niveau du langage source. Voici maintenant les règles de typage des expressions, donnée informellement. Référence à une variable. Le type de la variable lue par l'expression x est donnée par la table des symboles. Nous convenons que le type de l'expression est donnée par lookup(id.entry). Constante numérique. Le type de l'expression est number. Constante booléenne. Le type de l'expression est boolean. Liste vide. Manifestement, cette expression a le type liste mais, faute d'éléments, une liste vide ne peut pas spécier par elle-même le type de ses éléments. Donc, le type de l'expression est donné explicitement par T (où T dénote le type de la liste, pas celui des éléments). Liste non-vide. Tous les éléments qu'elle contient doivent être du même type t et la liste elle-même est du type [t]. Expression conditionnelle. Cette expression ressemble à l'expression e 1? e 2 : e 3 des langages C et Java. La condition e 1 doit avoir le type boolean, les deux branches e 2 et e 3 doivent avoir le même type t et l'expression complète a le type t. Opération unaire -. Cette opération peut s'appliquer à un nombre ou à un booléen. Dans le premier cas, elle agit comme la négation arithmétique et produit un nombre. Dans le second cas, elle agit comme la négation logique et produit un booléen. Opération unaire &. Cette opération s'applique à une liste et retourne sa longueur. Opération binaire @. Cette opération sert à extraire un élément d'une liste. Elle prend une liste du type [t] à gauche et un nombre i à droite et retourne le ième élément. Opération binaire *. Cette opération calcule le produit de deux nombres ou effectue un et logique entre deux booléens. Opération binaire /. Cette opération calcule le quotient entre deux nombres. Opération binaire +. Sur deux nombres, + calcule la somme. Sur deux booléens, + eectue un ou logique. Enn, sur deux listes du même type, + eectue la concaténation. Opération binaire -. Cette opération calcule la diérence entre deux nombres. Opération binaire <. Cette opération compare soit deux nombres, soit deux booléens, et elle retourne un booléen. Opération binaire =. Cette opération compare deux valeurs du même type et retourne un booléen. Lorsque les règles de typage ne sont pas respectées au niveau d'une certaine expression, il faut déclarer une erreur de types. En de telles circonstances, votre dénition orientée-syntaxe devrait produire le type spécial type_error, lequel existe en sus des types normaux du langage source. Ce type est produit lorsqu'une erreur de types
est détectée et il doit ensuite être propagé jusqu'à la racine de l'arbre de syntaxe du programme. Vous pouvez faire la supposition que les programmes reçus en entrée sont syntaxiquement valides ; i.e. ils respectent la grammaire. Toutefois, les programmes ne respectent pas nécessairement les règles de typage. 5. (20 points.) Génération de code intermédiaire. Dans le chapitre 6, nous avons abordé la génération du code intermédiaire pour l'énoncé de contrôle switch. Syntaxiquement, les énoncés switch comportent un certain nombre de clauses ordinaires et une clause nale obligatoire. L'exemple (a) est l'énoncé présenté au chapitre 6. On pourrait généraliser la syntaxe des énoncés switch en permettant d'attacher plus qu'une clé à chaque clause et en rendant la clause nale facultative. Les exemples (b) et (c) sont des énoncés généralisés. Exemple (a) : case 14 : S 2 ; else S 3 Exemple (b) : case 3, 5, 7, 11 : S 2 ; else S 3 Exemple (c) : case 3, 5, 7, 11 : S 2 ; case 2, 4, 6, 8 : S 3 ; end En partant de la grammaire hors-contexte suivante, concevez une dénition orientéesyntaxe qui produit du code intermédiaire pour les énoncés switch généralisés. S C C case K S ; C 1 else S end K num, K 1 num : Remise des travaux Vous devez remettre le travail via Pixel. Les autres modalités de remise sont inscrites dans le plan de cours.