Département d'informatique
Laboratoire des Méthodes de Programmation (LAMP)

Compilation     semestre d'hiver 2002-2003
Ecole Polytechnique Fédérale de Lausanne

Partie 3

À présenter lors de la séance du 13 décembre 2002



But

Le but de cette partie est de modifier l'analyseur syntaxique pour qu'il construise l'arbre syntaxique correspondant au programme source. Cet arbre syntaxique sera ensuite utilisé par toutes les phases suivantes du compilateur (l'analyse des noms, la vérification des types et la production de code).

Travail à effectuer

La première chose à faire est de choisir une syntaxe abstraite pour le langage misc. Une syntaxe vous est proposée ici, mais vous êtes libres de choisir la vôtre si la nôtre ne vous convient pas. Notez toutefois que les fichiers que nous distribuons pour cette partie utilisent cette syntaxe abstraite.

Une fois la grammaire abstraite choisie, il importe de la traduire en code Java, en écrivant une sous-classe de la classe Tree pour chaque construction de la grammaire abstraite. Ensuite, l'analyseur syntaxique doit être augmenté afin de construire effectivement l'arbre. Finalement, pour vérifier que l'arbre construit est correct, il faut un moyen de l'imprimer sous forme textuelle.

Sucre syntaxique

Nous avons introduits dans le langage misc une certaine quantité de sucre syntaxique. On nomme ainsi les constructions d'un langage qui sont strictement équivalentes à d'autres, mais qui ont une syntaxe différente. Le sucre syntaxique n'apporte donc aucune expressivité au langage, mais permet par contre souvent de rendre la vie du programmeur plus douce, en allégeant certaines notations, d'où son nom. Il faut bien sûr se garder d'ajouter une trop grande quantité de sucre syntaxique à un langage car, comme Alan Perlis l'a joliment dit dans un de ses aphorismes, syntactic sugar causes cancer of the semicolon.

Un exemple de sucre syntaxique en Java est la boucle for. En effet, tout ce qui est exprimable au moyen de la boucle for est exprimable au moyen de la boucle while, puisqu'on a l'équivalence suivante :

  for (e1; e2; e3)
    e4
est équivalent à
  {
    e1;
    while (e2) {
      e4;
      e3;
    }
  }

La boucle for n'est donc pas strictement nécessaire en Java, puisqu'elle ne permet pas d'exprimer des choses qui ne sont pas exprimables sans elle, mais elle permet parfois de simplifier et de clarifier les programmes.

Le sucre syntaxique de misc est défini dans la table ci-dessous. La manière correcte (et simple) de traiter le sucre syntaxique dans un compilateur est de le supprimer totalement, au moment de la construction de l'arbre. Le sucre syntaxique existe donc dans la grammaire concrète, mais pas dans la grammaire abstraite. Cela simplifie grandement les phases ultérieures du compilateur, qui doivent gérer uniquement les constructions de base du langage. Par exemple, les phases de vérification de type et de production de code d'un compilateur Java n'ont pas besoin de connaître l'existence de la boucle for, et peuvent se contenter de traiter la boucle while.

Désignation Notation Equivalence
1 Valeur false false 0
2 Valeur true true 1
3 if sans else if (cond) expr if (cond) expr else ()
4 "ou" logique expr1 | expr2 if (expr1) true 1 else expr2
5 "et" logique expr1 & expr2 if (expr1) expr2 else false 0
6 "non" logique ! expr if (expr) 0 else 1
7 Négation - expr 0 - expr
8 Listes [expr1, expr2, ..., exprn] expr1::expr2::...::exprn::[]
9 Chaînes de caractères "c1c2...cn" code(c1)::code(c2)::...::code(cn)::[]
10 Bloc { stat1; stat2; ...; statn; } { stat1; stat2; ...; statn; () }

Dans l'équivalence du sucre syntaxique pour les chaînes de caractères, l'expression code(c) représente le code ASCII du caractère c.

Le tableau ci-dessous donne quelques exemples de transformation de sucre syntaxique.

L'expression ... est transformée en ... d'après la (les) règle(s) n°...
if (c) a if (c) a else () 3
c | d if (c) 1 else d 4, 2
[109, 105, 115, 99] 109::105::115::99::[] 8
"misc" 109::105::115::99::[] 9
{} { () } 10
{ printInt(42); } { printInt(42); () } 10
{ printInt(42) } { printInt(42) } aucune (ce n'est pas du sucre syntaxique)

Tableaux d'arbres

En écrivant le code de construction des arbres, vous constaterez qu'il est souvent nécessaire de construire des tableaux d'arbres de taille non connue d'avance. Les tableaux Java n'étant pas redimensionnables, il est nécessaire d'utiliser une autre structure de données durant la collecte des données, et de la transformer ensuite en un tableau.

La bibliothèque standard de Java propose, dans le paquetage java.util, des structures de données parfaitement adaptées au problème : les listes. L'exemple suivant montre comment construire une liste de chaînes, la remplir, et finalement la transformer en un tableau. Pour plus d'information, reportez-vous à la documentation de Sun.

  List strList = new LinkedList ();
  strList.add ("salut");
  strList.add ("ça va ?");
  String[] strArray = (String[]) strList.toArray (new String[strList.size ()]);

Fichiers

Nous vous fournissons les fichiers suivants pour vous aider dans cette partie.

  • Tree.java contient, comme auparavant, la définition de la classe abstraite Tree, classe mère de toutes les classes des nœuds de l'arbre. Nous avons ajouté à ce fichier la définition de plusieurs sous-classes concrètes de Tree, en fonction de notre grammaire abstraite. Il ne vous reste que quelques sous-classes à ajouter.
  • Printer.java contient la définition d'un visiteur qui imprime l'arbre syntaxique. Comme pour Tree.java, la plus grosse partie du travail est faite, il ne vous reste qu'à ajouter les fonctions pour les nœuds que vous rajoutez dans Tree.java.
  • PrinterTest.java contient un programme qui vous permet de tester votre analyseur syntaxique. Le programme lit un fichier source, l'analyse et imprime dans un fichier objet l'arbre retourné par l'analyseur.

Bien entendu, le gros du travail consiste, encore une fois, à modifier votre analyseur syntaxique (fichier Parser.java) afin qu'il construise l'arbre.

Projet en Scala

Différences

La réalisation en Scala est très semblable à celle en Java. La principale différence est qu'au lieu d'utiliser la technique du visiteur, on utilise la reconnaissance de motifs (pattern matching). C'est pourquoi dans Tree.scala toutes les classes de nœuds sont des classes cas et la classe Printer n'implémente pas de visiteur, mais contient un appel à la méthode match de la classe Tree avec en argument les différents motifs de nœuds.

Une autre différence est qu'on utilise pas (ou très peu) les tableaux java et les listes du paquetage java.util. On préfère utiliser les listes Scala (type scala.List). Celles-ci sont documentées ici. Si vous préférez, vous pouvez aussi consulter leur code source. Celui-ci se trouve dans le fichier List.scala dans le répertoire share/scala/library/scala de la distribution. Sur les machines de la salle INF 3, il se trouve ici :

/home/iclamp/local/packages/scala/share/scala/library/scala/List.scala

Fichiers

Ci-dessous, les fichiers à utiliser pour le projet en Scala.

  • Tree.scala contient, comme auparavant, la définition de la classe abstraite Tree, classe mère de toutes les classes des nœuds de l'arbre. Nous avons ajouté à ce fichier la définition de plusieurs sous-classes concrètes de Tree, en fonction de notre grammaire abstraite. Il ne vous reste que quelques sous-classes à ajouter.
  • Printer.scala contient une classe qui permet d'imprimer l'arbre syntaxique. Comme pour Tree.scala, la plus grosse partie du travail est faite, il ne vous reste qu'à ajouter les fonctions pour les nœuds que vous rajoutez dans Tree.scala.
  • PrinterTest.scala contient un programme qui vous permet de tester votre analyseur syntaxique. Le programme lit un fichier source, l'analyse et imprime dans un fichier objet l'arbre retourné par l'analyseur.

Dernière modification: Tuesday, 24-Oct-2006 17:28:28 CEST