Vous êtes sur la page 1sur 51

Analyse syntaxique

Pr ZEGOUR DJAMEL EDDINE


Ecole Supérieure d’Informatique (ESI)
www.zegour.uuuq.com
email: d_zegour@esi.dz
Analyse syntaxique
Grammaires contexte-libre et Automates à pile(PDA)
Analyse descendante récursive
Propriétés LL(1)
Traitement des erreurs
Grammaires à contexte libre
Problème
Les grammaires régulières ne traitent pas la récursion centrale
E = x | "(" E ")".
On utilise alors les grammaires à contexte libre

Définition
Une grammaire est dite à contexte libre (CFG) si toutes ses productions ont la forme:
A = . A est un NTS,  séquence non vide de TS et NTS
En EBNF le coté droit  peut aussi contenir les meta-symboles |, (), [] and {}

Exemple
Expr = Term { ( "+" | "-" ) Term }.
Term = Factor { ( "*" | "/" ) Factor }.
Factor = id | "(" Expr ")". Récursion centrale indirecte

Les grammaires à contexte libre peuvent être reconnues par les automates à pile (PDA)
Automates à pile :Push-Down Automaton(PDA)
Caractéristiques
• Permet les transitions avec des symboles terminaux et des symboles non terminaux
• Utilise une pile pour sauvegarder les états visités

Exemple
E = x | "(" E ")".
E reconnu
revenir 1 arc État lecture
x
E/1 continuer à partir de là avec E
État réduire
E
( E )
E/3
x
stop E/1
Appel récursif
( E ) de l‘automate E
E/3
x
E/1

( E )

...
Automates à pile (suite)
x
E/1
E
( E )
E/3
x
stop E/1

( E )
E/3
...

Peut être simplifié comme suit

x
E/1
Utilise une pile pour trouver le chemin
E x
( E ) de retour des états visités
E/3
(
stop
Comment fonctionne un PDA?
Exemple: ((x))
x
0 1 E/1
E x
( E )
2 3 4 E/3
(
stop

Les états visités sont rangés dans une pile


Pile Reste à analyser ( ( x
0 2 2 1 E/1
0. ((x))
E E )
02. (x)) 3 4 E/3
022. x)) stop
0221. ))
E )
022. E)) 3 4 E/3
0223. ))
02234. )
02. E)
023. )
0234.
0. E
Grammaires régulières et Grammaires à contexte libre

Grammaires régulières Grammaires à contexte libre

Utilisées pour Lexique Syntaxe

Reconnues par DFA (sans pile) PDA (avec pile)


Entrée Entrée

DFA DFA
(état) (état)

pile

Productions A = a | b C. A = .
Analyse syntaxique
Grammaires contexte-libre et Automates à pile(PDA
Analyse descendante récursive
Propriétés LL(1)
Traitement des erreurs
Analyse descendante récursive
• Technique d‘analyse Top-down (de haut en bas)
• L‘arbre syntaxique est construit du symbole initial(axiome) vers la phrase (top-down)

Exemple grammaire Entrée


A = a A c | b b. abbc

Symbole de départ A A A

?
Quelle est a A c a A c
L‘alternative
qui convient? ? b b

Entrée a b b c a b b c a b b c

La bonne alternative est sélectionnée utilisant ...


• L‘unité lexicale courante de l‘entrée
• Les premiers symboles terminaux des alternatives d‘une production
Variables statiques de l‘analyseur syntaxique
Unité lexicale courante

A tout moment l‘analyseur connaît la prochaine unité lexicale

static int la; // code de l‘unité lexicale courante

Il utilise deux variables pour les unités lexicales (pour la phase sémantique)

static Token token; // unité déjà reconnue


static Token laToken; // unité courante non encore reconnue

Ces variables sont mises à jour dans la méthode Scan()


static void Scan () { token laToken
token = laToken; Entrée ident assign ident plus ident
laToken = Scanner.Next(); Déjà reconnues la
la = laToken.kind;
}

Scan() est appelée au début de l‘analyse. La première unité est dans la


Comment analyser les symboles terminaux?
Modèle
Symbole à analyser: a
Action de l‘analyseur: Check(a);
On a besoin des méthodes suivantes
static void Check (int expected) {
if (la == expected) Scan(); // recognized => read ahead
else Error( Token.names[expected] + " expected" );
}

public static void Error (string msg) {


Console.WriteLine("– line {0}, col {1}: {2}", laToken.line, laToken.col,
msg);
throw new Exception("Panic Mode");
}
Dans la class Token:
Ordonné par
public static string[] names = {"?", "identifier", "number", ..., "+", "-", ...};
code
Les noms des symboles terminaux sont déclarés comme des constantes dans la classe Token
public const int NONE = 0,
IDENT = 1, NUMBER = 2, ...,
PLUS = 4, MINUS = 5, ... ;
Comment analyser les symboles non terminaux?
Modèle
Symbole à analyser : A
Action de l‘analyseur : A(); // Appel à la méthode d‘analyse de A

Chaque symbole non terminal est reconnu par une méthode avec le même nom

private static void A() {


... parsing actions for the right-hand side of A ...
}

Initialisation de l‘analyseur

public static void Parse () {


Scan(); // initialise token, laToken et la
Program(); // appelle la méthode d’analyse de l’axiome
Check(Token.EOF); // à la fin, l’entrée doit être vide
}
Comment analyser les séquences?
Modèle
production: A = a B c.
Méthode de l‘analyseur: static void A () {
// la contains a terminal start symbol of A
Check(a);
B();
Check(c);
// la contains a follower of A
}

Simulation
Entrée restante
A = a B c. static void A () {
abbc
B = b b. Check(a);
bbc
B();
c
Check(c);
}

static void B() {


bbc
Check(b);
bc
Check(b);
c
}
Comment analyser les alternatives
Modèle || , ,  sont des expressions EBNF

Action de l‘analyseur if (la in First()) { ... parse  ... }


else if (la in First()) { ... parse  ... }
else if (la in First()) { ... parse  ... }
else Error("..."); // find a meaninful error message

Exemple
A = a B | B b. First(aB) = {a}
B = c | d. First(Bb) = First(B) = {c, d}

static void A () { static void B () {


if (la == a) { if (la == c) Check(c);
Check(a); else if (la == d) Check(d);
B(); else Error ("invalid start of B");
} else if (la == c || la == d) { }
B();
Check(b); exemples: analyser a d et c b
} else Error ("invalid start of A");
analyser b b
}
Comment analyser les Options EBNF
Modèle [ ] est une expression EBNF

Action de l‘analyseur if (la in First()) { ... parse  ... } // no error branch!

Exemple
A = [ a b ] c.

static void A () {
if (la == a) {
Check(a);
Check(b);
}
Check(c);
}

Exemple: analyser a b c
analyser c
Comment analyser les itérations EBNF
Modèle { } est une expression EBNF

Action de l‘analyseur while (la in First()) { ... parse  ... }

Exemple
A = a { B } b.
B = c | d.
Ou bien ...
static void A () { static void A () {
Check(a); Check(a);
while (la == c || la == d) B(); while (la != b && la != Token.EOF) B();
Check(b); check(b);
} }

Exemple: analyser a c d c b Sans EOF: danger d‘une boucle infinie,


analyser a b si b n‘existe pas dans l‘entrée
Cas des ensembles ‘First’ grands
Si l‘ensemble First a plus de 4 : utiliser la classe BitArray
Exemple: First(A) = {a, b, c, d, e}
First(B) = {f, g, h, i, j}

Les ensembles First sont initialisés au début du programme

using System.Collections;
static BitArray firstA = new BitArray(Token.names.Length);
firstA[a] = true; firstA[b] = true; firstA[c] = true; firstA[d] = true; firstA[e] = true;
static BitArray firstB = new BitArray(Token.names.Length);
firstB[f] = true; firstB[g] = true; firstB[h] = true; firstB[i] = true; firstB[j] = true;

Exemple
C = A | B. static void C () {
if (firstA[la]) A();
else if (firstB[la]) B();
else Error("invalid C");
}
Cas des ensembles ‘First’ grands
Si l‘ensemble a moins de 4 éléments: utiliser des vérifications explicites (plus rapide)
Exemple : First(A) = {a, b, c}

if (la == a || la == b || la == c) ...

Si l‘ensemble est un intervalle: utiliser un test d‘intervalle


if (a <= la && la <= c) ...

Les codes des unités lexicales sont souvent choisis de telle sorte qu‘ils forment des intervalles

Exemple

First(A) = { a, c, d } const int


First(B) = { a, d } a = 0, First(B)
First(C) = { b, e } d = 1, First(A)
c = 2,
b = 3, First(C)
e = 4,
Optimisations
Éviter les multiples vérifications
A = a | b. A = { a | B d }.
B = b | c.
static void A () {
if (la == a) Scan(); // no Check(a); static void A () {
else if (la == b) Scan(); while (la == a || la == b || la == c) {
else Error("invalid A"); if (la == a) Scan();
} else { // no check any more
B(); Check(d);
} // no error case
}
}

Schéma plus efficace pour analyser les alternatives dans une itération
A = { a | B d }.

static void A () {
for (;;) {
if (la == a) Scan();
else if (la == b || la == c) { B(); Check(d); }
else break;
}
}
Optimisations
Modèle d’une itération fréquente
Exemple
 { separator } ident { "," ident }

for (;;) { for (;;) {


... parse  ... Check(ident);
if (la == separator) Scan(); else break; if (la == Token.COMMA) Scan(); else break;
} }

Exemple d‘entrée: a , b , c :
Déterminer correctement les ensembles ‘First’
Grammaire
First
A = B a. C= De d et e (D est ‘annulable’!)
B = {b}c b et c | f. f
| [d] d et a (!) D = { d }.
| e.
e

Méthodes d‘analyse
static void A () { static void C () {
B(); Check(a); if (la == d || la == e) {
} D(); Check(e);
} else if (la == f) {
static void B () { Scan();
if (la == b || la == c) { } else Error("invalid C");
while (la == b) Scan(); }
Check(c);
} else if (la == d || la == a) { static void D () {
if (la == d) Scan(); while (la == d) Scan();
} else if (la == e) { }
Scan();
} else Error("invalid B");
}
Descente récursive et arbre syntaxique
L‘arbre syntaxique est construit implicitement
• Représente les méthodes actives à un moment donné
• Représente les productions utilisées

Exemple A = a B c.
B = d e.
call A()
A
static void A () {
Check(a); B(); Check(c); A en exécution
} a B c

reconnaît a
call B() A
static void B () {
A en exécution
" pile"
Check(d); Check(e); a B c B en exécution
}
d e

A
reconnaît d et e A en exécution
Retour de B() a B c
Analyse syntaxique
Grammaires contexte-libre et Automates à pile(PDA
Analyse descendante récursive
Propriétés LL(1)
Traitement des erreurs
Propriétés LL(1)
Pré condition pour l‘analyse descendante récursive

LL(1) ... peut être analysé de gauche ( Left) à droite


avec des dérivations canoniques gauche (Left) ( le NTS le plus à gauche est dérivé en
premier )
et utilise une seule unité lexicale (1) de l‘entrée
Définition
1. Une grammaire est LL(1) si toutes ses productions sont LL(1).
2. Une production A est LL(1) si pour toutes ses alternatives
1 | 2 | ... | n
la condition suivante est vérifiée:
First(i) Intersection First(j) = {} ( pour tout i et j)
[ Au plus un k peut être vide. Et dans ce cas First(i) Intersection Follow() = {} ( pour tout i #k)]

En d‘autres termes
• Les symboles terminaux de début de toutes les alternatives d‘une production doivent être
disjoints deux à deux.
• L‘analyseur doit être capable de choisir l‘une des alternatives par la consultation de la
l‘unité lexicale courante.
Comment éliminer les conflits LL(1) ?
Factorisation
IfStatement = "if" "(" Expr ")" Statement
| "if" "(" Expr ")" Statement "else" Statement.
Extraire la séquence commune de début
IfStatement = "if" "(" Expr ")" Statement (
| "else" Statement
).
... Ou en EBNF
IfStatement = "if" "(" Expr ")" Statement [ "else" Statement ].

Quelquefois les non terminaux doivent être remplacés avant factorisation


Statement = Designator "=" Expr ";"
| ident "(" [ ActualParameters ] ")" ";".
Designator = ident { "." ident }.
Remplacer Designator dans Statement
Statement = ident { "." ident } "=" Expr ";"
| ident "(" [ ActualParameters ] ")" ";".
ensuite factoriser
Statement = ident ( { "." ident } "=" Expr ";"
| "(" [ ActualParameters ] ")" ";"
).
Comment éliminer la Récursion gauche
La récursion à gauche est toujours un conflit LL(1)
Par exemple
IdentList = ident | IdentList "," ident.

génère les phrases suivantes


ident
ident "," ident
ident "," ident "," ident
...

Peut toujours être remplacée par une itération


IdentList = ident { "," ident }.
Conflits LL(1) cachés
Les itérations et les options EBNF sont des alternatives cachées

A = [ ] . Idem A =   | .  et sont des expressions EBNF

Règles

A = [ ] . First() Inter First() doit être {}


A = { } . First() Inter First() doit être {}

A =  [ ]. First() Inter Follow(A) doit être {}


A =  { }. First() Inter Follow(A) doit être {}

A=|. First() Inter Follow(A) doit être {}


Éliminer les conflits LL(1) cachés
Name = [ ident "." ] ident.

Où est le conflit et comment l‘éliminer?

Name = ident [ "." ident ].

Cette nouvelle production est elle LL(1) ?


Nous devons vérifier si First("." ident) Intersection Follow(Name) = {}

Prog = Declarations ";" Statements.


Declarations = D { ";" D }.

Où est le conflit et comment l‘éliminer?

Remplacer Declarations dans Prog


Prog = D { ";" D } ";" Statements.
First(";" D) Inter First(";" Statements) # {}
Prog = D ";" { D ";" } Statements.
Nous devons encore vérifier si First(D ";") Inter First(Statements) = {}
Problème des ‘Else’
L‘ instruction If en Java
Statement = "if" "(" Expr ")" Statement [ "else" Statement ]
| ... .

C‘est un conflit LL(1) !

First("else" Statement) Inter Follow(Statement) = {"else"}

C‘est même une ambiguïté qui ne peut être éliminée

Statement

Statement
On peut construire 2 arbres syntaxiques
if (expr1) if (expr2) stat1; else stat2; différents!
Statement

Statement
Problème des ‘Else’

Solution
Statement = "if" "(" Expr ")" Statement [ "else" Statement ]
| ... .

Si la prochaine unité est "else"


L‘analyseur prend comme option:
le "else" est associé au dernier "if"
if (expr1) if (expr2) stat1; else stat2;

Statement

Statement
Autres exigences pour une grammaire
(Pré conditions pour les analyseurs syntaxiques)

Complétude
Pour chaque NTS il doit y avoir une production
A=aBC. erreur!
B=bb. pas de production pour C

Dérivabilité
Chaque NTS doit être dérivable (directement ou indirectement) en une chaîne de TS
A=aB|c. erreur!
B=bB. B ne peut être dérivé en une chaîne de TS

Non-circularité
Un NTS ne doit pas être dérivable (directement ou pas) en lui-même (A => B 1 => B2 => ... => A)

A=ab|B. erreur!
B=bb|A. cette grammaire est circulaire car A => B => A
Analyse syntaxique
Grammaires contexte-libre et Automates à pile(PDA
Analyse descendante récursive
Propriétés LL(1)
Traitement des erreurs
Objectifs du traitement des erreurs syntaxiques

Exigences
1. Déterminer le maximum d‘erreurs en une seule compilation
2. Pas de bug (quelque soit l‘erreur)
3. Ne pas ralentir l‘exécution en traitant les erreurs
4. Ne pas gonfler le code

Techniques pour l‘analyse descendante récursive


• Mode panique
• Utilisation des symboles de reprise (ancres)
• Utilisation des symboles spéciaux de reprise
Mode panique
L'analyseur abandonne après la première erreur

static void Error (string msg) {


Console.WriteLine("-- line {0}, col {1}: {2}", laToken.line, laToken.col, msg);
throw new Exception("Panic Mode - exiting after first error");

Avantages
• économique
• suffisant pour les langages de commandes ou pour les interpréteurs

Inconvénients
• Non appropriée pour la production des compilateurs de qualité
Traitement des erreurs utilisant les ancres
Exemple
Entrée attendue: a b c d ...
Entrée réelle: a x y d ...

Récupération(synchronise l‘entrée restante avec la grammaire)


1. Trouver l’ "unité de reprise " (ancre) avec laquelle l‘analyseur peut continuer après l‘erreur
Quelles sont les unités avec lesquelles l‘analyseur peut continuer dans la situation donnée?
c successeur de b (qui était attendu à la position de l‘erreur)
d successeur de b c
...
Les unités de reprise (ancre) à cette position sont {c, d, ...}

2. Sauter les unités jusqu‘à ce que une unité de reprise soit trouvée .
x et y sont sautées dans l‘exemple, mais d est un ancre; l‘analyseur peut continuer avec.

3. Conduire l'analyseur à la position dans la grammaire où il peut continuer.


(remonter dans l‘arbre syntaxique)
Détermination des ancres
Chaque méthode d‘analyse d‘un non terminal A possède les successeurs
courant de A comme paramètres

static void A (BitArray sux) { sux ... successeurs de tous les NTS, qui sont
... en exécution
}

Dépendant du contexte courant, suxA peut dénoter différents ensembles

B eof B eof

a A b b C d e
... ...
a A f
... ...

suxA = {b, eof} suxA = {f, d, e, eof}

sux contient toujours eof (le successeur du symbole de départ)


Traitement des symboles terminaux
Grammaire Action de l‘analyseur
A = ... a s1 s2 ... sn . check(a, suxA Union First(s1) Union First(s2) ... Union First(sn));

si : TS ou NTS Peut être déterminé au moment de la compilation

Doit être déterminé au moment de l‘exécution

static void Check (int expected, BitArray sux) {...}

Exemple
A = a b c. static void A (BitArray sux) {
Check(a, Add(sux, fs1)); static BitArray fs1 = new BitArray();
Check(b, Add(sux, fs2)); fs1[b] = true; fs1[c] = true;
Check(c, sux);
}
Rempli au début du programme

static BitArray Add (BitArray a, BitArray b) {


BitArray c = (BitArray) a.Clone();
c.Or(b);
return c;
}
Traitement des symboles non terminaux
Grammaire Action de l‘analyseur
A = ... B s1 s2 ... sn . B(suxA Union First(s1) Union First(s2) ... Union First(sn));

Exemple
A = a B c. static void A (BitArray sux) {
B = b b. Check(a, Add(sux, fs3)); fs3 = {b, c}
B(Add(sux, fs4)); fs4 = {c}
Check(c, sux);
}

static void B (BitArray sux) {


Check(b, Add(sux, fs5)); fs5 = {b}
Check(b, sux);
}

La méthode d‘analyse pour l‘axiome S est appelée S(fs0); où fs0 = {eof}


Sauter les unités invalides
Les erreurs sont détectées dans check()
static void Check (int expected, BitArray sux) {
if (la == expected) Scan();
else Error(Token.names[expected] + " expected", sux);
}

Après l‘affichage du message les unités sont sautées jusqu‘à la rencontre


d‘une unité de reprise

static void Error (string msg, BitArray sux) {


Console.WriteLine("-- line {0}, col {1}: {2}", laToken.line, laToken.col,
msg);
errors++;
while (!sux[la]) Scan(); // while (la # sux) Scan();
}

static int errors = 0; // number of syntax errors detected


Synchronisation avec la grammaire
Exemple A eof suxA = {eof}
A = a B e. suxB = {e, eof}
a B e
B = b c d.
b c d
static void A (BitArray sux) {
Check(a, Add(sux, fs1)); Entrée a b x e eof
B(Add(sux, fs2));
Check(e, sux);
}
static void B (BitArray sux) {
Check(b, Add(sux, fs3));
Check(c, Add(sux, fs4)); L‘erreur est détecté ici; ancres = {d, e, eof}
Check(d, sux);
} 1. x est sauté; la == e ( dans ancres)
2. L‘analyseur continue: Check(d, sux);
3. Détecte de nouveau une erreur; ancres = {e, eof}
4. aucune unité est sautée, car la == e (dans ancres)
5. L‘analyseur retourne de B() et lance Check(e, sux);
6. Recouvrement réussi!

Une fois l‘erreur détectée l‘analyseur avance jusqu‘à trouver un endroit dans la grammaire qui
concorde avec l‘unité courante.
Supprimer les faux messages d‘erreur
Durant le recouvrement de l‘erreur l‘analyseur produit des faux messages d‘erreur

Résolu par une simple heuristique


Si moins de 3 unités sont reconnues correctement depuis la dernière erreur,
l'analyseur suppose que la nouvelle erreur est une fausse erreur.
Les fausses erreurs ne sont pas affichées

static int errDist = 3; // next error should be reported

static void Scan () {


...
errDist++; // one more token recognized
}

public static void Error (string msg, BitArray sux) {


if (errDist >= 3) {
Console.WriteLine("-- line {0}, col {1}: {2}", laToken.line, laToken.col, msg);
errors++;
}
while (!sux[la]) Scan();
errDist = 0; // counting is restarted
}
Traitement des alternatives
A=||. , ,  sont des expressions EBNF

static void A (BitArray sux) {


// the error check is already done here so that the parser can synchronize with
// the starts of the alternatives in case of an error
if (la not in (First() Or First() Or First()))
Error("invalid A", sux Or First() Or First() Or First());
// la matches one of the alternatives or is a legal successor of A
if (la in First()) ... parse  ...
else if (la in First()) ... parse  ...
else ... parse  ... // no error check here; any errors have already been reported
}

First() Or First() Or First()peut être déterminé au moment de la compilation


sux Or ... est déterminé au moment de l‘exécution
Traitement des Options et Itérations EBNF
Options
static void A (BitArray sux) {
A = [ ] .
// error check already done here, so that the parser can
// synchronize with the start of  in case of an error
if (la not in (First() Or First()))
Error("...", sux Or First() Or First());
// la matches  or  or is a successor of A
if (la in First()) ... parse  ...;
... parse  ...
}

Itérations
static void A (BitArray sux) {
A = { } .
for (;;) {
// the loop is entered even if la not in First()
if (la in First()) ... parse  ...; // correct case 1
else if (la in First() Or sux) break; // correct case 2
else Error("...", sux Or First(a) Or First(b));// error case
}
... parse  ...
}
Exemple
A = a B | b {c d}.
B = [b] d.

static void A (BitArray sux) { static void B (BitArray sux) {


if (la != a && la != b) if (la != b && la != d)
Error("invalid A", Add(sux, fs1)); // fs1 = {a, b} Error("invalid B", Add(sux, fs3)); // fs3 = {b, d}
if (la == a) { if (la == b) Scan();
Scan(); B(sux); Check(d, sux);
} else if (la == b) { }
Scan();
for (;;) {
if (la == c) {
Scan();
Check(d, Add(sux, fs2)); // fs2 = {c}
} else if (sux[la]) {
break;
} else {
Error("c expected", Add(sux, fs2));
}
}
}
}
En résumé
Traitement des erreurs avec des symboles de reprises (ancres)

Avantage
+ applicable automatiquement

Inconvénients
- Ralentit l‘analyse
- gonfle le code de l‘analyseur
- complexe
Traitement des erreurs avec des ancres spéciaux
Le traitement est seulement fait à des positions particulières
Les mot-clés apparaissent à des positions uniques dans la grammaire

Exemple Ensemble d‘ancres


• Début d‘instruction: if, while, do, ...
• Début de déclaration: public, static, void, ...
Problème: ident peut figurer à plusieurs endroits!
ident n‘est pas un ancre sûr. Il est donc omis de l‘ensemble des ancres

Code à insérer aux points de synchronisation


... L‘ensemble des ancres à ce point de synchronisation
if (la not in expectedSymbols) {
Error("..."); // no successor sets; no skipping of tokens in Error()
while (la not in (expectedSymbols Or {eof})) Scan();
}
... Pour éviter la boucle infinie

• Pas besoin de passer les ensembles successeur aux méthodes de l‘analyseur


• Les ensembles d‘ancres sont connus avant l‘exécution
• Après une erreur l‘analyseur ignore des unités jusqu‘au prochain point de synchronisation
Exemple
Synchronisation au début d‘une instruction
static void Statement () { static BitArray firstStat = new BitArray();
if (!firstStat[la]) { firstStat[Token.WHILE] = true;
Error("invalid start of statement"); firstStat[Token.IF] = true;
while (!firstStat[la] && la != Token.EOF) Scan(); ...
errDist = 0;
}
if (la == Token.IF) { Scan();
Check(Token.LPAR); Conditions(); le reste de l‘analyseur
Check(Token.RPAR); reste inchangé
Statement(); (comme s‘il n‘ y a pas
if (la == Token.ELSE) { Scan(); Statement(); } de traitement d‘erreur)
} else if (la == Token.WHILE) {
...
}

Pas de synchronisation dans Error()


public static void Error (string msg) {
if (errDist >= 3) {
Console.WriteLine("-- line {0}, col {1}: {2}", laToken.line, laToken.col, msg);
errors++;
}
errDist = 0;
heuristics with errDist can also
} be applied here
Exemple de recouvrement
static void Statement () { static void Check (int expected) {
if (!firstStat[la]) { if (la == expected) Scan();
Error("invalid start of statement"); else Error(...);
while (!firstStat[la] && la != Token.EOF) Scan(); }
errDist = 0;
} public static void Error (string msg) {
if (la == Token.IF) { Scan(); if (errDist >= 3) {
Check(Token.LPAR); Condition(); Console.WriteLine(...);
Check(Token.RPAR); errors++;
Statement(); }
if (la == Token.ELSE) { Scan(); Statement(); } errDist = 0;
... }
}
Entrée erronée: if a > b then max = a;
la action
if Scan(); if dans firstStat , ok
a Check(LPAR); message d‘erreur: ‘(‘ attendue
Condition(); reconnaît a > b
then check(RPAR); message d‘erreur: ‘)’ attendue
Statement(); then ne correspond pas, donc erreur, mais pas de message d‘erreur
then est sauté; synchronisation avec ident (si dans firstStat)
max
Synchronisation au début d‘une itération
Exemple
Block = "{" { Statement } "}".

Modèle standard dans ce cas


static void Block () {
Check(Token.LBRACE);
while (firstStat[la])
Statement();
Check(Token.RBRACE);
}

Problème: si la prochaine unité ne correspond pas à une instruction la boucle n‘est pas exécutée.
Le point de synchronisation dans Statement n‘est jamais atteint.
Synchronisation au début d‘une itération
Exemple
Block = "{" { Statement } "}".

C‘est meilleur de synchroniser au début de l‘itération


static void Block() {
Check(Token.LBRACE);
for (;;) {
if (la in First(Statement)) Statement(); // correct case 1
else if (la in {rbrace, eof}) break; // correct case 2
else { // error case
Error("invalid start of Statement");
do Scan(); while (la  (First(Statement) union {rbrace, eof}));
errDist = 0;
}
}
Check(Token.RBRACE);
}

Pas de synchronisation dans Statement()


static void Statement () {
if (la == Token.IF) { Scan(); ...
}
En résumé
Traitement des erreurs avec des symboles de reprises spéciaux

Avantages
+ ne ralentit pas l‘exécution de l‘analyseur
+ ne gonfle pas le code de l‘analyseur
+ simple

Inconvénients
- demande plus d‘expérience

Vous aimerez peut-être aussi