Plan
- 
=8em
Arbres de syntaxe abstraite (arbres binaires, termes)
 - Grammaires formelles
 - BNF
 - Arbres syntaxiques
 - Analyse récursive descendante
 - Evaluation d'expressions arithmétiques
 - Associativité
 
cf. les cours Automates et Calculabilité en majeure M1 pour les
grammaires formelles, et Compilation en majeure M2 pour les analyseurs
syntaxiques
Schéma général
Texte
ß
ß
Flot de lexèmes
ß
ß
Arbre Syntaxe Abstraite (ASA) 
Arbres de syntaxe abstraite
- 
Le texte est non structuré
 - ASA = structure de donnée abstraite pour traiter un problème
 - Exemples de problèmes: 
- ·lire/écrire des arbres binaires
 - ·calculer des expressions arithmétiques
 - ·interpréter un petit langage de commandes
 - ·calculer symboliquement (algèbre, logique)
 - ·générer du code machine (compilation)
 
 - Exemples d'ASAs: 
- ·arbres binaires
 - ·termes représentant une expression arithmétique
 
 
rbreerme
Arbres binaires (1/2)
 
class Arbre {
  int val;
  Arbre a1, a2;
  Arbre (int v, Arbre a, Arbre b) {val = v; a1 = a; a2 = b; }
  Arbre (int v) {val = v; a1 = null; a2 = null; }
}
 
  Arbre a = new Arbre (7,
              new Arbre (8, new Arbre(4), null),
              new Arbre (9, new Arbre(10), new Arbre (11)));
Arbres binaires (2/2)
ou encore plus abstraitement
Surcharge -- Overloading
- Les fonctions (et les constructeurs) peuvent être surchargées.
 - Surcharge (Java) ¹ polymorphisme (ML)
- ·Une fonction polymorphe est la même pour une collection de types de
ses arguments. Exemple: l'image mirroir d'une liste (sans préciser
le type de ses éléments).
 - ·Une fonction surchargée change de sens selon le
type de ses arguments.
 
 - Les deux notions sont statiques. Elles n'interviennent qu'à
la phase de compilation (et non à l'exécution).
 
Les principes des langages de programmation modernes sont enseignés
en majeure 1.
Termes (1/3)
But: calculer. Les termes sont représentés par des arbres. Par
exemple:
 
class Terme {
  final static int ADD = 0, SUB = 1, MUL = 2, DIV = 3, MINUS = 4,
                   VAR = 5, CONST = 6;
  int nature;
  Terme a1, a2;
  String nom;
  int val;
  Terme (int t, Terme a) {nature = t; a1 = a; }
  Terme (int t, Terme a, Terme b) {nature = t; a1 = a; a2 = b; }
  Terme (String s) {nature = VAR; nom = s; }
  Terme (int v) {nature = CONST; val = v; }
}
Termes (2/3)
 
Terme t = new Terme (MUL,
             new Terme (ADD, new Terme ("x"), new Terme (1)),
             new Terme (ADD,
                new Terme (MUL, new Terme (3), new Terme ("x")),
                new Terme (2)));
On a choisi ici de faire l'union de tous les champs. Seuls les
plus significatifs figurent sur cette figure. D'autres
représentations seront vues plus tard.
Termes (3/3)
ou encore pour (x+1)*(3*x+2)
Exercice 1 Dessiner les ASA pour les termes x+y+z,
x*y*z, x*y+z, x*(y+z), (a+a*a)*(a*a+a*a).
Grammaires formelles (1/4)
On fait appel aux grammaires algébriques (context-free).
G = (S, V, S, P) où
| S | 
 | 
alphabet terminal | 
| V | 
 | 
ensemble fini des variables non terminales 
(V Ç S = Ø) | 
| S | 
 | 
axiome (S Î V) | 
| P | 
 | 
ensemble fini de règles de productions Xi
® wi (Xi Î V, wi Î (S È V)*) | 
Langage reconnu par G
- 
· v ® v' implique uvw ® uv'w
 - · u ®* v ssi u=u0 ® u1 ® u2 ® ··· un=v (n ³ 0)
 - · L(G) = { w | S ®* w,  w Î S* }
 
Grammaires formelles (2/4)
 | 
 | 
| Représentation linéaire d'arbres | 
 
| S={[, ], nb }
 ,  V = { A } | 
 
 | 
   | 
   | 
 
| Expressions arithmétiques 1 | 
 
| S={(, ), +, -, *, /, id, nb }
 ,  V = { E } | 
 
| E ® E + E | 
 | 
E ® E - E | 
 | 
E ® E * E | 
 | 
E ® E / E | 
 | 
 
| E ® id | 
 | 
E ® nb | 
 | 
E ®  ( E ) | 
 | 
   | 
   | 
   | 
|   | 
 | 
 
| Expressions arithmétiques 2 | 
 
| S={(, ), +, -, *, /, id, nb }
 ,  V = { E, P, F }, E axiome | 
 
| E ® P + E | 
 | 
E ® P - E | 
 | 
E ® P | 
 
| P ® F * P | 
 | 
P ® F / P | 
 | 
P ® F | 
 
| F ® id | 
 | 
F ® nb | 
 | 
F ®  ( E ) | 
   | 
   | 
   | 
Grammaires formelles (3/4)
 
| S | 
 | 
® S S ® S aSb ® S aSSb ® S aSb ® S aaSbb ® S aabb
 ® aSbaabb ® abaabb | 
| S | 
 | 
® aSb ® aS Sb ® aSSSb ® aSaSbSb ® aSaSSbSb ® aaSbaSSbSb ® 
aaaSbbaSSbSb | 
|   | 
 | 
® aaaSbbaSSbaSbb ® aaabbaSSbaSbb ® aaabbaSbaSbb 
® aaabbabaSbb ® aaabbababb | 
a correspond à parenthèse ouvrante '(',
b correspond à parenthèse fermante ')'.
Grammaires formelles (4/4)
| Représentation linéaire des arbres binaires | 
| S={[, ], nb }
 ,  V = { A } | 
 
 | 
   | 
| A | 
 | 
®[ A 7 A] 
®[[ A 8 A] 7 A] 
®[[[ A4A] 8A] 7A] 
®[[[ 4A] 8A] 7A] | 
|   | 
 | 
®[[[ 4] 8A] 7A] 
®[[[ 4] 8] 7A] 
®[[[ 4] 8] 7[ A9A]] | 
|   | 
 | 
®[[[ 4] 8] 7[[ A10A] 9A]] 
®[[[ 4] 8] 7[[ 10A] 9A]] | 
|   | 
 | 
®[[[ 4] 8] 7[[ 10] 9A]] 
®[[[ 4] 8] 7[[ 10] 9[ A11A]]] | 
|   | 
 | 
®[[[ 4] 8] 7[[ 10] 9[ 11A]]] 
®[[[ 4] 8] 7[[ 10] 9[ 11]]] | 
Long mais précis !
On peut abstraire les dérivations par un arbre de dérivations ou
encore arbre syntaxique.
 
Arbre syntaxique (1/5)
 | 
mot | 
[[[4]8]7[[10]9[11]]] | 
 | 
grammaire | 
 | 
 | 
 | 
arbre syntaxique | 
 
 | 
(arbre de syntaxe | 
 
 | 
concrète) | 
   | 
 | 
 | 
 | 
arbre de syntaxe | 
 
 | 
abstraite | 
   | 
 | 
Arbre syntaxique (2/5)
Mot aabbabab
Grammaire 
etc.
Langage parenthésé
Une grammaire est ambigue si elle génère plus d'un arbre syntaxique
pour un même mot reconnu.
Exercice 2 Trouver une grammaire non ambigue générant le langage
parenthésé précédent.
Arbre syntaxique (3/5)
Mot 3*x+y
Grammaire Expressions arithmétiques 1
Grammaire ambigue, car deux arbres syntaxiques pour 3*x+y
Þ deux ASAs pour 3*x+y
Arbre syntaxique (4/5)
Mot (x+3*y)*(2*x+10*y)Expressions arithmétiques 2
| E ® P + E | 
 | 
E ® P - E | 
 | 
E ® P | 
 | 
P ® F * P | 
 | 
P ® F / P | 
| P ® F | 
 | 
F ® id | 
 | 
F ® nb | 
 | 
F ®  ( E ) | 
 | 
Arbre syntaxique (5/5)
ASA associé à (x+3*y)*(2*x+10*y)
Exercice 3 Dans les quatre grammaires précédentes, donner les grammaires
non ambigues. Démontrer le!
Grammaires BNF (Backus-Naur Form) (1/2)
La syntaxe des langages de programmation est aussi décrite par une
grammaire formelle. Les nombreuses variables non-terminales sont
décrites par des identificateurs. Il y a souvent des raccourcis
pour simplifier la notation. Un bout de la BNF de Java:
ForStatement:
        for ( ForInitopt ; Expressionopt ; ForUpdateopt )
                Statement
ForInit:
        StatementExpressionList
        LocalVariableDeclaration
ForUpdate:
        StatementExpressionList
StatementExpressionList:
        StatementExpression
        StatementExpressionList , StatementExpression
StatementExpression:
        Assignment
        PreIncrementExpression
        PreDecrementExpression
        PostIncrementExpression
        PostDecrementExpression
        MethodInvocation
        ClassInstanceCreationExpression
AssignmentExpression:
        ConditionalExpression
        Assignment
Assignment:
        LeftHandSide AssignmentOperator AssignmentExpression
LeftHandSide:
        Name
        FieldAccess
        ArrayAccess
AssignmentOperator:  one of
        = *= /= %= += -= <<= >>= >>>= &= ^= |=
Grammaires BNF (Backus-Naur Form) (2/2)
 
for (x = 1, y = 3; x < 100; ++x, ++y) {
   Statement
}
Parfois, on écrit les BNFs avec des
diagrammes en chemin de fer.
Analyse syntaxique
Donnés: grammaire G et un mot w
But: w Î L(G) ? Si oui, construire l'ASA de w.
2 grandes méthodes:
- 
analyse descendante. On part de S pour atteindre
w.
 - analyse ascendante. On part de w pour atteindre
 S.
 
déterministes (sans backtracking) pour des grammaires spéciales:
- 
grammaires LL(k) pour analyse descendante
javacc ou Pascal [Wirth, 71]
 - grammaires LR(k) pour analyse ascendante
yacc [S. Johnson, 73] 
Ici on verra l'analyse récursive descendante pour les grammaires
LL(1).
Méthode récursive descendante (1/4)
Mot [[[4]8]7[[10]9[11]]]
On part de l'axiome A en choisissant l'une des 2 règles de production
en fonction du premier lexème (terminal) de la chaîne d'entrée.
 
A la fin, on obtient l'arbre syntaxique de la planche ??.
1
ireArbrexpressionroduitacteuraleurDeystemutrintrintlneriverrintlnrintlnrintln
Méthode récursive descendante (2/4)
Le lexème courant dans la variable globale lc. 
 
static Lexeme lc;
static void avancer() {lc = Lexeme.suivant(); }
static Arbre lireArbre () {
  if (lc.nature == Lexeme.L_CroG) {
    avancer(); Arbre a = lireArbre();
    if (lc.nature == Lexeme.L_Nombre) {
      int x = lc.val;
      avancer(); Arbre b = lireArbre(); 
      if (lc.nature == Lexeme.L_CroD) {
        avancer(); return new Arbre (x, a, b);
      } else
        throw new Error ("Erreur de syntaxe. Il manque \"]\".");
    } else
      throw new Error ("Erreur de syntaxe. Il manque un nombre.");
  else 
    return null;
  }
}
Méthode récursive descendante (3/4)
Le lexème courant dans la variable globale lc.
 
static Lexeme lc;
static void avancer() {lc = Lexeme.suivant(); }
static Terme expression() {
  Terme t = produit(); switch (lc.nature) {
  case Lexeme.L_Plus: avancer(); return new Terme (ADD, t, expression());
  case Lexeme.L_Moins: avancer(); return new Terme (MINUS, t, expression());
  default: return t;
 }
}
- Une fonction (récursive) par variable
 non terminale.
 - Au début, on appelle la fonction correspondant à l'axiome.
 
2
Méthode récursive descendante (4/4)
 
static Terme produit() {
  Terme t = facteur(); switch (lc.nature) {
  case Lexeme.L_Mul: avancer(); return new Terme (MUL, t, produit());
  case Lexeme.L_Div: avancer(); return new Terme (DIV, t, produit());
  default: return t;
 }
}
static Terme facteur() {
  Terme t; switch (lc.nature) {
  case Lexeme.L_ParG: avancer(); t = expression();
    if (lc.nature != Lexeme.L_ParD) throw new Error ("Il manque ')'");
    break;
  case Lexeme.L_Nombre: t = new Terme (lc.val); break;
  case Lexeme.L_Id: t = new Terme (lc.nom); break;
  default: throw new Error ("Erreur de syntaxe");
  }
  avancer();
  return t;
}
On décide toujours avec pas plus d'un caractère d'avance LL(1).
Opérations sur les ASAs
- 
belle impression (pretty-print), i.e.  sans
parenthèses superflues.
 - interprétation (évaluation d'expressions
arithmétiques ou booléennes)
 - calcul formel (dérivation formelle, intégration, etc.)
 - compilation (génération de code)
 - transformations (passer en notation polonaise postfixe ou
préfixe).
 - analyses statiques (vérifier 
 l'absence de débordements flottants dans le logiciel embarqué d'Ariane
 5 avant son exécution)
 
Evaluation d'expressions arithmétiques
Données: t terme, e table des
valeurs des variables.
But: calcul la valeur du terme t dans
l'environnement e.
 
static int evaluer(Terme t, Environnement e) {
  switch (t.nature) {
  case ADD: return evaluer(t.a1, e) + evaluer(t.a2, e);
  case SUB: return evaluer(t.a1, e) - evaluer(t.a2, e);
  case MUL: return evaluer(t.a1, e) * evaluer(t.a2, e);
  case DIV: return evaluer(t.a1, e) / evaluer(t.a2, e);
  case CONST: return t.val;
  case VAR: return valeurDe(t.nom, e);
  default: throw new Error ("Erreur dans evaluation");
  }
}
static int valeurDe(String s, Environnement e) {
  if (e == null) throw new Error ("Variable non définie");
  if (e.nom.equals(s)) return e.val;
  else return valeurDe(s, e.suivant);
}
Dérivation
Données: t terme, x nom de variable.
But: calcul la dérivée du terme t par rapport à x.
 
static int deriver(Terme t, String x) {
  switch (t.nature) {
  case ADD: return new Terme(ADD, deriver(t.a1, x), deriver(t.a2, x));
  case SUB: return new Terme(SUB, deriver(t.a1, x), deriver(t.a2, x));
  case MUL: return new Terme(ADD,
       new Terme(MUL, deriver(t.a1, x), t.a2),
       new Terme(MUL, t.a1, deriver(t.a2, x)));
  case DIV: return new Terme(DIV,
       new Terme(SUB,
              new Terme(MUL, deriver(t.a1, x), t.a2),
              new Terme(MUL, t.a1, deriver(t.a2, x))),
       new Terme(MUL, t.a2, t.a2);
  case CONST: return new Terme(CONST, 0);
  case VAR: if (t.nom.equals (x)) return new Terme(CONST, 1)
            else return new Terme(CONST, 0);
  }
}
Remarque: le résultat peut être un dag (dû au partage de
sous-termes)
Belle impression
 
static void impExp (Terme t) {
  switch (t.nature) {
  case ADD: impProd(t.a1); System.out.print("+"); impExp(t.a2); break;
  case SUB: impProd(t.a1); System.out.print("-"); impExp(t.a2); break;
  default: impProd(t);
  }
}
static void impProd (Terme t) {
  switch (t.nature) {
  case MUL: impFact(t.a1); System.out.print("*"); impProd(t.a2); break;
  case DIV: impFact(t.a1); System.out.print("/"); impProd(t.a2); break;
  default: impFact(t);
  }
}
static void impFact (Terme t) {
  switch (t.nature) {
  case CONST: System.out.print(t.val); break;
  case VAR: System.out.print(t.nom); break;
  default: System.out.print("("); impExp(t); System.out.print(")"); break;
  }
}
Belle symétrie par rapport à l'analyse syntaxique (opération inverse).
Opérateurs non associatifs
La méthode récursive descendante parenthèse mal les opérateurs
non associatifs. 
| x+y+z | 
analysé comme | 
x+(y+z) | 
| x-y-z | 
  | 
x-(y-z) | 
Pour revenir au parenthésage naturel, implicitement à gauche, il faut
transformer la grammaire en:
| E ® E + P | 
 | 
E ® E - P | 
 | 
E ® P | 
 
| P ® P * F | 
 | 
P ® P / F | 
 | 
P ® F | 
 
| F ® id | 
 | 
F ® nb | 
 | 
F ®  ( E ) | 
   | 
Impossible à analyser en récursif descendant (récursivité
gauche dans la grammaire), puisque E ®* Eu.
Avec des expressions régulières, on écrit la grammaire comme 
| E ® P  (+ P)* | 
 | 
E ® P  (- P)* | 
 | 
  | 
 
| P ® F  (* F)* | 
 | 
P ® P  (/ F)* | 
 | 
 
| F ® id | 
 | 
F ® nb | 
 | 
F ®  ( E ) | 
   | 
et on utilise un while dans la programmation.
Analyse syntaxique
- théorie des langages formels
[Chomsky,
Schutzenberger, 1960]
 - analyse syntaxique
[Aho,
Sethi,
Ullman, 1980]
 - compilation
[Appel],
[Caml, Ocaml, ...Leroy]
 - analyse de langues naturelles
 
Les grammaires formelles Þ
Automates et Calculabilité en majeure M1;
les analyseurs syntaxiques Þ Compilation en majeure M2.
Exercices
Exercice 4 Programmer la belle impression.
Exercice 5 Essayer d'imaginer ce que pourrait être l'analyse
ascendante.
Exercice 6 Trouver une solution grammaticale LL(1) pour l'analyse syntaxique des
opérateurs non associatifs.
Exercice 7 Donner des exemples où l'analyse descendante a des
difficultés, mais pas une analyse ascendante.
Exercice 8 Faire une calculette logique
Exercice 9 Faire une calculette Texas Instrument (sans notation polonaise).
This document was translated from LATEX by
HEVEA.