IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Automates à pile LR Notion de poignée
IFT313© Froduald Kabanza2 Sujets Automate à pile LR –Définition –Simulation Notion de poignée (handle) –Définition Ébauche dun pilote (driver) LR –Définition –Simulation
IFT313© Froduald Kabanza3 Objectifs Pouvoir définir et simuler un automate à pile LR. Pouvoir définir la notion de poignée (handle). Introduction aux défis fondamentaux de lanalyse LR.
IFT313© Froduald Kabanza4 Références [2] Appel, A. and Palsberg. J. Modern Compiler Implementation in Java. Second Edition. Cambridge, – Section 3.3 [4] Aho, A., Lam, M., Sethi R., Ullman J. Compilers: Principles, Techniques, and Tools, 2 nd Edition. Addison Wesley, – Section 4.5
IFT313© Froduald Kabanza5 Rappel Un générateur danalyseurs syntaxiques LL(1) fonctionne comme suit : Son entrée est une grammaire. Le générateur LL(1) a accès à un driver LL(1). À partir de la grammaire, le générateur calcule une table danalyse capable de déterminer (prédire) la production à appliquer, en fonction du non-terminal (partie gauche de la règle de production) au sommet de la pile et du prochain token. La sortie est juste une combinaison du driver LL(1) et de la table danalyse. La sortie est équivalente à un automate à pile LL(1).
IFT313© Froduald Kabanza6 Rappel Un analyseur LL(1) effectue la dérivation la plus à gauche, dans ce sens que: à chaque instant, la chaîne restant à lire est dérivable du contenu de la pile par une dérivation la plus à gauche. Par conséquent les étapes dun analyseur LL(1) consistent à : Développer le non terminal au sommet de la pile en le remplaçant par la partie droite dune règle de production commençant par ce non terminal (prédiction dune étape de dérivation à gauche). Enlever le terminal au sommet de la pile lorsquil correspond au prochain token (reconnaître le préfixe de la chaîne dérivée jusquà cet étape par une dérivation à gauche)
IFT313© Froduald Kabanza7 Rappel Lanalyse LL(1) détermine la production à appliquer au non-terminal au sommet de la pile juste en lisant le prochain token sur lentrée. Toutefois, ceci est faisable seulement si la grammaire ne contient pas des productions récursives à gauche, partageant un préfixe, ou ambigües. Si une grammaire ne remplit pas ces conditions, nous devons la transformer en une grammaire équivalente les satisfaisant. Très souvent, cela donne lieu à une grammaire moins intuitive, ne reflétant pas naturellement la grammaire de notre langage de programmation. Une telle grammaire peut être difficile à déboguer et à mettre à jour. La méthode danalyse LR évite ces problèmes.
IFT313© Froduald Kabanza8 LL vs LR Un analyseur LL Commence avec une chaîne de tokens et un symbole de départ de la grammaire. Répétitivement, il détermine la production à utiliser pour dériver le reste de la chaîne de tokens : on remplace le symbole au sommet de la pile correspondant à la la partie gauche dune règle de production, par la partie droite. Cest une approche de haut en bas (du symbole de départ vers la chaîne des tokens). Cela donne une dérivation la plus à gauche de la chaîne de tokens à partir du symbole de départ. Un analyseur LR Commence aussi par la chaîne de tokens, mais le symbole de départ de la grammaire nest pas impliqué dès le départ. Répétitivement, on met autant de tokens que possible sur la pile, jusquà avoir une sous- chaîne au sommet de la pile correspond à la partie droite dune production; on peut alors remplacer la sous-chaîne sur la pile par la partie gauche de la production. Cest une approche de bas en haut (de la chaîne de tokens vers le symbole de départ). Cela donne la dérivation la plus à droite à partir du symbole de départ.
IFT313© Froduald Kabanza9 Automate à pile LR : description informelle La technique utilisée par les analyseurs LR est appelée shift-reduce (avance- réduit) par opposition à predict-match des analyseurs LL. Lidée est de lire les tokens, un à un, en les mettant sur la pile. À chaque étape, on analyse la pile pour vérifier si elle contient une sous-chaîne au sommet correspondant à une partie droite dune production. Si oui, on remplace la sous-chaîne par la partie gauche. Ceci équivaut à une étape de dérivation. Cette étape est appelée reduire (la pile). Sinon, on continue à lire les tokens, en les déplaçant au sommet de la pile, jusquà avoir une sous-chaîne correspondant à la partie droite dune production. Cette étape est appelée « shifting (avancer) (la tête de lecture des tokens) » (après avoir déplacé le token courant sur la pile). Cest ça un automate LR …
IFT313© Froduald Kabanza10 Exemple Entrée: abbcde (pile, entrée) action 1. ($, abbcde$) Initialization 2. ($a, bbcde$) Shift a 3. ($ab, bcde$) Shift b 4. ($aA, bcde$) Reduce b ( A b) 5. ($aAb, cde$) Shift b 6. ($aAbc, de$) Shift c 7. ($aA, de$) Reduce Abc ( A Abc) 8. ($aAd, e$) Shift d 9. ($aAB, e$) Reduce d ( B d) 10. ($aABe, $) Shift e 11. ($S, $) Reduce aABe ( S aABe) 12. ($, $) Finish : Accept Grammaire G = (V,A,R,S), avec V = {S, A, B} A = {a, b, c, d, e} R = { S aABe, A Abc | b B d }
IFT313© Froduald Kabanza11 Observation importante A chaque étape, le contenu de la pile ( ) concaténé avec le reste de lentrée (v), est une forme sententielle pour une dérivation la plus à droite : c.-à-d., S => v R * 1. ($, abbcde$) Initialization 2. ($a, bbcde$) Shift a 3. ($ab, bcde$) Shift b 4. ($aA, bcde$) Reduce b (A b) 5. ($aAb, cde$) Shift b 6. ($aAbc, de$) Shift c 7. ($aA, de$) Reduce Abc (A Abc) 8. ($aAd, e$) Shift d 9. ($aAB, e$) Reduce d (B d) 10. ($aABe, $) Shift e 11. ($S, $) Reduce aABe (S aABe) 12. ($, $) Finish : Accept abbcde <= abbcde <= abbcde <= aAbcde <= aAbcde <= aAbcde <= aAde <= aAde <= aABe <= aABe <= S G = (V,A,R,S), avec V = {S, A, B} A = {a, b, c, d, e} R = {S aABe, A Abc | b B d }
IFT313© Froduald Kabanza12 Conflits shift-reduce et reduce-reduce Lautomate LR est non déterministe parce quon peut avoir des conflits shift-reduce et reduce-reduce. Par exemple, à létape 6 on a la possibilité de déplacer (shift) c et de réduire b (avec A b). Dans lexemple précédent, on a choisi shift. Un choix de reduce aurait donné lieu à une exécution différente. Entrée: abbcde (pile, entrée) action 1. ($, abbcde$) Initialization 2. ($a, bbcde$) Shift a 3. ($ab, bcde$) Shift b 4. ($aA, bcde$) Reduce b ( A b) 5. ($aAb, cde$) Shift b 6. ($aAA, cde$) Reduce b ( A b). Rejet Grammaire G = (V,A,R,S), avec V = {S, A, B} A = {a, b, b, c, d, e} R = { S aABe, A Abc | b B d }
IFT313© Froduald Kabanza13 Analyseurs LR déterministes Les questions fondamentales dans lanalyse LR sont : Quand faire shift, quand faire reduce ? Sil y a plusieurs productions dont les parties droites correspondent à des chaînes au sommet de la pile, quelle production utiliser pour faire reduce. Pour une exécution déterministe, on ne devrait avoir aucun conflit entre les choix dactions possibles (shift ou reduce). En fait, plus tard nous allons imposer des restrictions sur les grammaires acceptable pour une approche LR, afin déviter de tels conflits. Différentes restrictions nous donnerons différentes versions danalyseurs LR déterministes: SLR(1), LR(1) and LALR(1).
IFT313© Froduald Kabanza14 Doù viennent les termes Shift et Reduce? Laction de lire un token est de le déplacer au sommet de la pile est communément connu sous le vocable « shift » (on avance la tête de lecture des tokens, après avoir déplacé le token actuel sur la pile). Laction de remplacer une chaînes de symboles au sommet de la pile par un non terminal (partie gauche dune règle de production) est appelée reduce (on réduit la longueur de la pile puisque la partie droite dune règle a toujours plus de symboles que la partie gauche pour une GHC; la seule exception est le cas des productions avec une partie droite qui est la chaîne vide). Par conséquent, lautomate à pile LR est aussi connu sous le nom dautomate à pile shift-reduce.
IFT313© Froduald Kabanza15 Poignées (handles) 1/ 2 Une sous-chaîne au sommet de la pile qui correspond à une partie droite dune production est appelée poignée (handle), pourvu quelle mène à une exécution qui accepte. En effet, nous avons déjà vu une situation où il y avait une sous-chaîne au sommet de la pile, correspondant à une partie droite dune règle de production, mais ne menant pas à lacceptation de lentrée. La sous-chaîne nétait pas une poignée. Plus précisément, une poignée dune forme sententielle droite γ est une règle de production A et une position γ à la quelle la chaîne peut être trouvée et remplacée par A afin de générer la forme sententielle précédente dans une dérivation de droite de γ. Autrement dit, si S => w => w, alors A dans la position juste après est une poignée de w RR *
IFT313© Froduald Kabanza16 Poignées (handles) 1/ 2 Si S => w => w, alors A dans la position juste après est une poignée de w Par abus de langage, si A dans la position juste après est une poignée de w, nous allons aussi dire que est une poignée pour en fait dire la partie droite dune poignée A Lorsque est le contenu de la pile et w le restant de lentrée, si est une poignée, on va réduire par la production correspondante A Vous remarquerez que jusque là lon parle dune poignée et non de la poignée. Cest parce, si la grammaire est ambiguë, il peut y avoir plusieurs poignées pour une dérivation la plus droite. Si la grammaire est non ambigüe, on a toujours une seule dérivation la plus à droite, donc, une seule poignée à chaque étape. RR *
IFT313© Froduald Kabanza17 Automate à pile LR : description formelle On peut maintenant expliquer formellement la méthode pour construire un automate à pile LR (non déterministe) pour une GHC. Il faut se rappeler que selon notre convention, une transition de lautomate a la forme : (état, prochain-token, sommet-de-pile) (nouvel-état, nouveau-sommet) On aura une transition pour enlever le symbole de départ S de la pile lorsque lautomate a fini de lire toute lentrée. Nous aurons aussi des transitions pour réduire la pile (reduce); c.-à-d., remplacer une partie droite dune règle de production par la partie gauche, sur la pile. Finalement, nous aurons des transitions pour déplacer les tokens sur la pile (shift).
IFT313© Froduald Kabanza18 Générer un automate LR pour une GHC Étant donné une grammaire G = (Nonterminaux, Terminaux, Productions, S), lautomate à pile LR correspondant est M=({p,q}, Nonterminaux U Terminaux, Terminaux, R, p, {q}, $), tel que la relation des transitions R est définie comme suit : (p, $, S$) (q, ε ) (Accept) (p, ε, Y k …Y 1 ) (p, A) pour chaque production A Y 1 … Y k (Reduce) (Rappel : Y k va être au sommet de la pile). (p, a, ε) (p, a) pour chaque terminal a (Shift) Toutes les configurations vont avoir létat p, sauf éventuellement la dernière qui aura létat q.
IFT313© Froduald Kabanza19 Exemple Grammaire: G = (V,A,R,S), avec V = {S, A, B} A = {a, b, c, d, e} R = {S aABe, A Abc | b B d } Automate à pile M = ({p,q}, {S,A,B}, {a, b, c, d, e}, R, p,{q},$) R = { 1. (p, $, S$) (q, ε ) 2. (p, ε, eBAa) (p, S) 3. (p, ε, cbA) (p, A ) 4. (p, ε, b) (p, A ) 5. (p, ε, d) (p, B ) 6. (p, a, ε) (p, a ) 7. (p, b, ε) (p, b ) 8. (p, c, ε) (p, c ) 9. (p, d, ε) (p, d ) 10. (p, e, ε) (p, e ) }
IFT313© Froduald Kabanza20 Exemple, suite Transitions 1. (p, $, S$) (q, ε ) 2. (p, ε, eBAa) (p, S) 3. (p, ε, cbA) (p, A ) 4. (p, ε, b) (p, A ) 5. (p, ε, d) (p, B ) 6. (p, a, ε) (p, a ) 7. (p, b, ε) (p, b ) 8. (p, c, ε) (p, c ) 9. (p, d, ε) (p, d ) 10. (p, e, ε) (p, e ) } Trace sexécution. Je montre seulement (pile, entrée). Toutes les configurations sont dans létat p, sauf la dernière qui est dans létat q. 1. ($, abbcde$) Transition (Shift) 6 2. ($a, bbcde$) Transition (Shift) 7 3. ($ab, bcde$) Transition (Reduce) 4 4. ($aA, bcde$) Transition ( Shift) 7 5. ($aAb, cde$) Transition (Shift) 8 6. ($aAbc, de$) Transition (Reduce) 3 7. ($aA, de$) Transition (Shift) 9 8. ($aAd, e$) Transition (Reduce) 5 9. ($aAB, e$) Transition (Shift) ($aABe, $) Transition (Reduce) ($S, $) Transition ( Accept) ($, $) Dérivation correspondante: abbcde <= abbcde <= abbcde <= aAbcde <= aAbcde <= aAde <= aAde <= aABe <= aABe <= S
IFT313© Froduald Kabanza21 Grammaire augmentée Pour définir un driver LR, nous avons besoin du concept de « grammaire augmentée ». Étant donné une grammaire G, la grammaire augmentée correspondante est obtenue en ajoutant : un nouveau symbole de départ S et une production S S. Ceci permet dindiquer au driver LR quand arrêter lanalyse et annoncer lacceptation de lentrée. Lorsque la pile contient seulement S et que lentrée est vide, la production précédente provoque un remplacement de S par S sur la pile. Contrairement à notre automate à pile, le driver LR naccepte pas quand la pile et lentrée sont vides. Au contraire, il accepte quand la pile contient S. Il est possible de définir une version de driver LR qui accepte sur une pile vide, mais la formulation présente est la plus fréquente dans les manuels académiques.
IFT313© Froduald Kabanza22 Algorithme LRDriver Algorithm LRDriver variables : stack, handle (sous-chaîne au sommet de la pile), a (token), in (entrée) initialement la pile est vide ($) et lentrée est w$ (une chaîne w). while (true) { if (symbol on top of stack is S ) return (a = = $); //accepte handle = stack.findHandle(); //trouver une poignée. Étape cruciale ! if handle != void { // void si la poignée nest pas trouvée soit A handle la règle correspondante // reduce (nondeterminisme si plusieurs règles) pop handle from the stack; push A on the stack; print out the production A handle; // pour imprimer la séquence de dérivation } else { a = in.nextToken(); // shift if a = $ exit with error(); // erreur si ni reduce ni shift ne sont possibles push a on the stack; continue; }
IFT313© Froduald Kabanza23 Exemple revisité Trace dexécution 1. ($, abbcde$) Shift 2. ($a, bbcde$) Shift 3. ($ab, bcde$) Reduce 4 4. ($aA, bcde$) Shift 5. ($aAb, cde$) Shift 6. ($aAbc, de$) Reduce 3 7. ($aA, de$) Shift 8. ($aAd, e$) Reduce 5 9. ($aAB, e$) Shift 10. ($aABe, $) Reduce ($S, $) Accept 12. ($S, $) Dérivation correspondante: abbcde <= abbcde <= abbcde <= aAbcde <= aAbcde <= aAbcde <= aAde <= aAde <= aABe <= aABe <= S <= S Grammaire G = (V,A,R,S), avec V = {S, S, A, B} A = {a, b, c, d, e} R = { 1. S S, 2. S aABe, 3. A Abc | 4. b 5. B d }
IFT313© Froduald Kabanza24 Contexte LR(0) 1/2 On aurait pu formuler lanalyser LR(0) à laide du concept de «contexte LR(0)» plutôt que celui « poignée » (Sudkamp [1], page 596). Nous avons introduit la « poignée » comme suit: Si S => w => w, alors A dans la position juste après est une poignée de w Étant donné une grammaire (V, Σ, P, S), la chaîne est un contexte LR(0) pour la règle de production A sil existe une dérivation S => w => w, avec et (V U Σ)* et w Σ*. Autrement dit, est un contexte LR(0) pour la règle de production A ssi A est un poignée pour w, avec et (V U Σ)* et w Σ*. R * R R * R
IFT313© Froduald Kabanza25 Contexte LR(0) 2/2 Tout comme pour un poignée, un contextes LR(0) détermine quand faire une réduction durant lanalyse LR, sinon quand faire « shift ». 1.Si A est une poignée (c.-à-d., si le contenu de la pile ( ) est un contexte LR(0) de la règle A on réduit avec A 1.Sil ny a pas de poignée au sommet de la pile, mais le sommet de la pile contient un préfixe dun poignée (c.-à-d., si le contenu de la pile ( ) est nest un contexte LR(0) mais est un préfixe dun contexte LR(0) on met (shit) le prochain token sur la pile. 1.Si le sommet de la pile nest un préfixe pour une poignée quelconque (c.à-d., le contenu de la pile nest pas un préfixe pour un contexte LR(0) quelconque), on rejète la chaîne dentrée.
IFT313© Froduald Kabanza26 Récapitulation Le défi majeur pour implémenter un driver LR efficace est de trouver la poignée, c-à-d., létape handle = stack.findHandle(). Une approche naïve pour implémenter stack.findHandle() serait de comparer le contenu de la pile avec les parties droites des règles de production pour trouver une règle dont la partie droite est au sommet de la pile. Par contre, il faudra faire backtracking si on se trompe est que cette partie droite ne mène pas à lacceptation (c.-à-d., ne savère pas être une poignée). Pour des sous-classes intéressantes des GHC, on peut éviter de scanner la pile et de faire backtracking pour trouver une poignée. Dans ce cours, on verra les grammaires SLR(1), LR(1) et LALR(1).
IFT313© Froduald Kabanza27 Récapitulation Le problème est donc de trouver une poignée sur la pile (partie droite dune production menant à lacceptation) et de déterminer avec quel non terminal la remplacer (avec quelle production réduire). Cette décision est plus difficile à faire que la décision correspondante dans lanalyse LR (prédire une production). Par conséquent, elle plus difficile à implémenter. Néanmoins, cette approche nous permet de mettre moins de restrictions sur les langages (et leurs grammaires) pouvant être analysés. Plus précisément, on a plus de chance davoir à spécifier une grammaire simple avec une approche LR quavec une approche LL, pour le même langage.