370 likes | 544 Views
Analyse Syntactique . ( Compilers, Principles, Techniques and Tools , Aho, Sethi et Ullman, 1986). Compilateurs. Un compilateur est un programme qui lit un programme écrit dans une langue (la langue source) et produit un programme écrit dans une autre langue (la langue cible).
E N D
Analyse Syntactique (Compilers, Principles, Techniques and Tools, Aho, Sethi et Ullman, 1986)
Compilateurs • Un compilateur est un programme qui lit un programme écrit dans une langue (la langue source) et produit un programme écrit dans une autre langue (la langue cible). • Un compilateur utilise plusieurs étapes. Chaque étape transforme le programme source en une autre représentation. • Programme source Analyseur lexical Analyseur Syntactique Analyseur Sémantique Générateur de Code Intermédiaire Optimiseur de Code Générateur de Code Programme objectif • Pour cette partie de cours, nous allons nous concentrer sur l’Analyseur Syntactique
Analyse Syntactique • L’analyse syntactique est le processus qui détermine si une chaine de jetons peut être produite par une grammaire. • Il y a deux types principaux de méthodes d’ analyse syntactique – ascendant (de bas en haut) et descendent (de haut en bas). • Pour l’analyse syntactique descendante, on commence à la racine et procède vers les noeuds terminaux. • Pour analyse syntactique ascendante, on commence aux noeuds terminaux et procède vers la racine. • Il est facile de construire des analyseurs descendant efficaces à la main. • L’Analyse syntactique ascendante est plus flexible et on peut l’utiliser avec plus de classes de grammaires. Mais il est plus difficile de la développer. Par contre, il y a des outils disponible pour générer des parseurs directement, a partir de la grammaire.
Partie IAnalyse Syntactique Descendante • Idées fondamentales de l’Analyse Syntactique Descendante • L’Analyse Syntactique Descendante prédictive • Grammaires recursives a gauche • Factorisation sur la gauche • Construire un Analyseur Syntactique • Grammaires LL(1)
Idées fondamentales de l’Analyse Syntactique Descendante • Pour l’Analyse Syntactique Descendante, on essaye de trouver un dérivation a gauche pour la chaîned'entrée. • Exemple: S cAd Trouvez un dérivation A ab | a pour w cad S S Backtrack S / | \ / | \ / | \ c A d c A d c A d / \ | a b a
Analyseur Syntactique Prédictif : Généralités • Dans la majorité des cas, si on écrit la grammaire très soigneusement, on peut éliminer la recursion degauche et faire une factorisation a gauche de la grammaire qui en résulte. Ceci nous permet d’obtenir une grammaire que l'on peut analyser syntactiquement avec un analyseur syntactique récursif descendent qui n'a pas besoin de backtracking. • Cette classe de compilateur s’appelle un analyseur syntactique prédictif.
Grammaires Récursives aGauche I • Une grammaire est récursive a gauche si il y a un noeud non terminal A et il y a une dérivation A Aα pour n’importe quelle chaîne α. • Les Analyseurs Syntactiques descendants peuvent entrer dans une boucle infinie en presence de règles récursives a gauche. Donc, on doit les éliminer. • On peut éliminer une règle récursive a gauche comme A A α | β si on peut la remplacer par : • A β R ou R est un nouveau noeud non terminal R α R | є et є est la chaîne vide. • La nouvelle grammaire est récursive a droite.
Grammaires Récursives aGauche II • Si vous avez une récursion a gauche directe—c’est-à-dire, si la récursion se produit a l’interieure d’une seule règle—On peut l’ éliminer de la manière suivante: • Groupez les règles A Aα1 |… | Aαm | β1 | β2 |…| βn Assurez-vous qu’aucun des β’s commence par A • Remplacez les A-règles originales par • A β1A’ | β2 A’ | … | βn A’ • A’ α1 A’ | α2 A’ | … | αm A’ • Ce procédé n'éliminera pas la récursion a gauche indirecte de la sorte • A BaA • B Ab [Il y a un autre procédé mais nous ne le discutons pas ici] • La récursion a gauche directe ou indirecte est problématique pour tous les analyseurs descendents. Cependant, ce n'est pas un problème pour des algorithmes d'analyse ascendants.
Grammaires Récursives aGauche III • Voici un exemple d'une grammaire avec récursion a gauche directe: E E + T | T T T * F | F F ( E ) | id • Cette grammaire peut être réécrite comme la grammaire non récursive a gauche suivante: E T E’ E’ + TE’ | є T F T’ T’ * F T’ | є F (E) | id
Factorisation a Gauche d'une Grammaire I • La récursion a gauche n'est pas le seul trait qui empêche l'analyse descendante. • Un autre est la question de savoir si l'analyseur peut toujours choisir le côté droit correct par seule analyse du jeton d'entrée suivant; c’est a dire, en utilisant seulement le premier jeton produit par le non terminal d’extrême gauche dans la dérivation courante. • Pour s'assurer que c'est possible, nous avons besoin de factoriser a gauche la grammaire produite a l'étape précédente (qui n’est plus récursive a gauche).
Gauche-Factorisation d'une Grammaire II • Voici le procédé utilisé pour factoriser une grammaire a gauche: • Pour chacun des non terminaux A, trouvez le plus long préfixe α commun à deux ou à plus de ses alternatives. • Remplacez toutes les A-productions: A αβ1 | αβ2 … | αβn | γ (ou γreprésente toutes les alternatives qui ne commencent pas avec α) • par: A α A’ | γ A’ β1 | β2 | … | βn
Gauche-Factorisation d'une Grammaire III • Voici un exemple d'une grammaire qui a besoin d’etre factorisee a gauche: S iEtS | iEtSeS | a E b ( i = “if”; t = “then”; et e = “else”) • Apres la factorisation a gauche cette grammaire devient: S iEtSS’ | a S’ eS | є E b
Analyseur Syntactique Prédictif : Détails • Le problème principal pendant l'analyse prédictive est celui de déterminer la production à appliquer pour un non terminal. • Ceci est fait en employant une table d'analyse. • Une table d'analyse est un tableau bidimensionnel M[A,a] où A est un non terminal, et a est un terminal ou le symbole $ qui signifie “fin de chaîne d’entrée”. • Les autres entrées d’un Analyseur Syntactique Prédictif sont : • La mémoire tampon d’entrée, qui contient la chaîne à analyser suivie de $. • La pile qui contient une sequence de symboles de grammaire avec, au depart, simplement $S (la fin de la chaîne d’entrée et le symbole de départ).
Analyseur Syntactique Prédictif: Procédé Informel • L’Analyseur Syntactique Prédictif utilise X, le symbole sur le dessus de la pile, et a, le symbole d’entrée suivant. Il utilise egalement M, la table d'analyse. • Si X=a=$ arrêter et retourner: succès • Si X=a≠$ Enlever X de la pile et avancer le pointeur d’entrée vers le prochain symbole • Si X est un non terminal consulter M[X,a] • Si l’élément est un regle de production, remplacer X sur la pile avec le côté droit de la production • Si l’élément est vide, arrêter et retourner: echec
Analyseur Syntactique Prédictif : Un Exemple Table d'analyse Trace de l’Algorithme
Construire la Table D'analyse I: First et Follow • First(α)est l'ensemble des terminaux qui sont a la tete de la chaîne α. Follow(A)est l'ensemble des terminaux a qui peuvent être tout de suite à la droite de A. First et Follow sont employés dans la construction de la table d'analyse. • Construire First: • Si X est un terminal, alors First(X) est {X} • Si X є est une production, alors ajoutez є à First(X) • Si X est un non terminal et X Y1 Y2 … Yk est une production, alors placez a dans First(X) si, pour quelque i, a est dans First(Yi) et є est dans tous les First(Y1)… First(Yi-1)
Construire le Table D'analyse II: First et Follow • Construire Follow: • Mettre $ dans Follow(S), où S est le symbole de départ et $ est le symbole de fin de chaîne. • Si il y a une production A αBβ, alors tout le contenu de First(β) sauf є est mis dans Follow(B). • Si il y a un production A αB ou une production A αBβ et First(β) contient є, alors tout le contenu de Follow(A) est dans Follow(B) Exemple: E TE’ E’ +TE’ | є T FT’ T’ *FT’ | є F (E) | id First(E) = First(T) = First(F) = {(, id} First(E’) = {+, є} First(T’) = {*, є} Follow(E) = Follow(E’) = {),$} Follow(F)={+,*,),$} Follow(T) = Follow(T’) = {+,),$}
Construire la Table D'analyse III • Algorithme pour construire une table d'analyse prédictive: • Pour chaque A α de la grammaire, fait étapes 2 et 3 • Pout chaque terminal a dans First(α), ajouter A α à M[A, a] • Si є est dans First(α), ajouter A α à M[A, b] pour chaque terminal b dans Follow(A). Si є est dans First(α), ajouter A α à M[A,b] pour chaque terminal b dans Follow(A). Si є est dans First(α) et $ est dans Follow(A), ajouter A α à M[A, $]. • Chaque élément indéfini de M est une erreur.
LL(1) Grammaires • Une grammaire avec un table d'analyse qui n'a aucune entrée contenant plus d’une définition s'appelle LL(1) • Aucune grammaire ambiguë ou récursive a gauche ne peut être LL(1). • Une grammaire G est LL(1) si et seulement si dans tous les cas ou A α | β sont deux productions distinctes de G, les conditions suivantes sont verifiees: • Il n’y a aucun terminal a pour lequelα ainsi que βdérivent des chaînes commençant par a • α ou β peut dériver la chaîne vide, mais les deux ne peuvent pas le faire simultanement. • Si β peut (directement ou indirectement) dériver є, alors α ne peut pas dériver de chaîne qui commence avec un terminal appartenant a Follow(A).
Partie IIAnalyseur Syntactique Ascendant • Il y a différentes approches à l'analyse ascendante . Une approche s’appelle Shift-Reduce parsing. Cette approche peut, en outre, prendre des formes diverses. • L’Une de ces méthode s’appelle Operator-precedence parsing. Cependent, elle n’est pas tres generale. Une autre methode: LR-Parsing l’est beaucoup plus. • Dans ce cours, nous nous concentrerons sur le LR-Parsing. LR-Parsing prend, lui-meme, trois formes : Simple LR-Parsing (SLR) une version simple mais limitée; le LR canonique, la version la plus puissante, mais la plus chère ; et LALR qui est intermédiaire en coût et puissance. Nous allons nous concentrer, ici, sur le SLR.
LR-Parsing : Avantages • Le LR Parsing peut identifier n'importe quelle langue pour laquelle une grammaire libre de contexte peut être écrite. • Le LR parsing est la methode la plus générale de parsing. Cependant, elle est aussi efficace que toute autre approche Shift-Reduce. • La classe de grammaires qui peuvent être analysées parle LR Parsing contient la classe de grammaires qui peut être analysé par un analyseur prédictif et est plus vaste. • Le LR-Parsing peut détecter une erreur syntactique dès qu'il sera possible de le faire pendant un scan de gauche à droite de l'entrée.
LR-Parsing: Inconvénient/Solutions • L'inconvénient principal du LR-Parsing est que, pour une grammaire typique de langage de programmation, sa construction à la main demande trop de travail. • Heureusement, des outils spécialisés pour construire des parseur LR automatiquement ont été conçus. • Avec de tels outils, un utilisateur peut écrire une grammaire libre de contexte et la passer dans un générateur de compilateur automatique afin de produire un parseur pour cette grammaire. • Un exemple d'un tel outil est Yacc “Yet Another Compiler-Compiler"
Algorithmes de LR-Parsing : Détails I • Un Parseur LR se compose d'une entrée, d’une sortie, d'une pile, d'un programme pilote et d'une table d'analyse a deux parties : action et goto. • Le programme pilote est le même pour tous les parseurs LR. Seule la table d'analyse change d'un parseur a l’autre. • Le programme emploie la pile pour stocker une chaîne de la forme s0X1s1X2…Xmsm, ou sm est le dessus de la pile. Les Sk‘s sont des symboles d’état et les Xi‘s sont des symboles de grammaire . Ensemble, les symboles d'état et de grammaire déterminent les décisions des parseurs LR.
Algorithmes de LR-Parsing : Détails II • La table d'analyse se compose de deux parties : une fonction d'action du Parseur et une fonction goto. • Le programme de Parsing LR détermine sm, l'état sur le dessus de la pile et ai, l'entrée courante. Il consulte alors l'action[sm, ai] ce qui peut prendre une des quatre valeurs suivantes: • Shift • Reduce • Accept • Error
Algorithmes de LR-Parsing : Détails III • Si l’action[sm, ai] = Shift s, Décalez s, où s est un état et poussezai et s sur le haut de la pile. • Si l’action[sm, ai] = Reduce A β, alors ai et sm sont remplaces par A, et, si s était l'état au-dessous de aidans la pile , alors goto[s, A] est consulté et l'état qu'il stocke est poussé sur la pile. • Si l’action[sm, ai] = Accept, alors l’analyse est accomplie. • Si action[sm, ai] = Error, alors l'analyseur a découvert une erreur.
Algorithmes de LR-Parsing Exemple: La Grammaire • E E + T • E T • T T * F • T F • F (E) • F id
Algorithmes de LR-Parsing Exemple : Trace du programme
Analyse SLR I • Definition: Un article LR(0) d'une grammaire G est une production de G avec un point à une position particuliere du côté droit. • Exemple: A XYZ produit les quatre articles suivants: • A .XYZ • A X.YZ • A XY.Z • A XYZ. • La production A єproduit un seul article , A . • Intuitivement, un article indique quel montant de la production nous avons vu à un point precis dans le processus d'analyse.
Analyse SLR II • Pour créer une table d'analyse de SLR, nous définissons trois nouveaux éléments: • Une grammaire augmentée pour G, la grammaire initiale. Si S est le symbole de départ de G, nous ajoutons la production S’ .S . Le but de cette nouvelle production est d'indiquer à l'analyseur quand il doit cesser l’analyze et accepter l'entrée. • L'opération de fermeture (The closure operation) • la fonction goto
Analyse SLR II : L'opération de fermeture • Si I est un ensemble d'articles pour une grammaire G, alors closure(I) est l'ensemble d'articles construits a partir de I par les deux règles suivantes: • Au commencement, chaque article de I est ajouté a closure(I) • Si A α . B β est dans closure(I) et B γ est une production, alors ajouter l’article B . γ à I, s'il n'est pas déjà present. Nous appliquerons cette règle jusqu'à ce qu’il n’y ait plus de nouveaux articles a ajouter a closure(I).
Analyse SLR : L'opération de fermeture– Example Grammaire Originale Grammaire augmentée 0. E’ E • E E + T 1. E E + T • E T 2. E T • T T * F 3. E T * F • T F 4. T F • F (E) 5. F (E) • F id 6. F id Si I = {[E’ E]} alors Closure(I)= { [E’ .E], [E .E + T], [E .T], [E .T*F], [T .F], [F .(E)] [F .id] }
Analyse SLR :L'opération Goto • Goto(I,X), où I est un ensemble d'articles et X est un symbole de grammaire, est défini comme la fermeture de l'ensemble de tous les articles [A αX.β] tels que[A α.Xβ] est dans I. • Exemple: Si I est l'ensemble de deux articles {E’ E.], [E E.+T]}, alors goto(I, +) se compose de E E + .T T .T * F T .F F .(E) F .id
Analyse SLR : Construction d’ensembles d'articles procedure articles(G’) C = {Closure({[S’ .S]})} Répétez Pour chaque ensemble de points I en C et chaque symbole X de grammaire tels que goto(I,X) n’est pas vide et pas en C, faites: ajoutez goto(I,X) à C Jusqu'à qu’aucun nouvel ensemble d'articles ne peut être ajouté à C
Exemple: La collection canonique LR(0) pour la grammaire G I0: E’ .E I4: F (.E) I7: T T * .F E .E + T E .E + T F .(E) E .T E .T F .id T .T * F T .T * F I8: F (E.) T .F T .F E E.+T F .(E) F .(E) I9: E E + T. F .id F .id T T.* F I1: E’ E. I5: F id. I10: T T*F. E E.+T I6: E E+.T I11: F (E). I2: E T. T .T*F T T. * F T .F I3: T F. F .(E) F .id
Construction d'une Table d'analyse de SLR • Construire C={I0, I1, … In} la collection d'ensembles de d’ articles LR(0) pour G’ • L'état i est construit a partir de Ii. Les actions d'analyse pour l'état i sont déterminées comme suit: • Si [A α.aβ] est dans Ii et goto(Ii,a) = Ij, alors action[i,a] devient “shift j”. Ici a doit être un terminal. • Si [A α.] est dans Ii, alors action[i, a] devient “réduce A α” pour tout les a dans Follow(A); Ici A ne peut pas être S’. • Si [S’ S.] est dans Ii, alors action[i,$] devient “accept” Si des actions contradictoires sont produites par les règles ci-dessus, nous disons que la grammaire n'est pas SLR(1). L'algorithme alors ne produit pas d’analyseur.
Construction d'une Table d'analyse de SLR (cont’d) 3. Les transitions goto pour l'état i sont construites pour tous les nonterminaux A en utilisant la règle: Si goto(Ii, A) = Ij, alors goto[i, A] = j. 4. Toutes les entrées non définies par les règles (2) et (3) deviennent des erreurs. 5. L'état initial de l'analyseur est celui qui a ete construit a partir de l'ensemble d'articles contenant [S’ S]. Voir l'exemple en classe