Langages et grammaires algébriques: résumé Licence info S5 Cours COMPIL – 2012 - 2013 Le but de ce cours est de présenter les outils pour l’analyse syntaxique : les langages algébriques, décrits par des grammaires algébriques. À l’issue de l’UE, vous devriez être capable de concevoir une grammaire algébrique pour un dsl et d’utiliser divers générateurs automatiques. 1 But de l’analyse syntaxique Comme vu en cours d’introduction, l’analyseur syntaxique récupère en entrée le flot de symboles produit par l’analyseur lexical. L’analyseur syntaxique connaı̂t la structure des textes corrects, et reconnaı̂t la structure du flot de symbole (ou le rejette si le mot d’entrée est incorrect). Cette structure apparaı̂t sous la forme d’un arbre, appelé arbre syntaxique. Exemple : programme PROGRAM IDENT(”pgm”) FININSTR DECL → IDENT(”x”) SEP IDENT(”y”) FININSTR listeDeclarations listeInstructions declaration type DECLINT listeIdent IDENT SEP listeIdent IDENT On commence par expliquer pourquoi les langages réguliers ne permettent pas de faire une analyse syntaxique. 2 Limite des langages réguliers L’exemple typique est celui des parenthèses imbriquées : il suffit de penser aux accolades ou parenthèses ouvrantes et fermantes de Java. On peut considérer le langage {an bn |n ≥ 0} dans lequel a joue le rôle de parenthèse ouvrante et b de parenthèse fermante. Si on n’arrive déjà pas à décrire ce langage, on n’arrivera pas non plus à décrire Java. Il se trouve que {an bn |n ≥ 0} n’est pas un langage régulier. En effet, pour le reconnaı̂tre, il faut mémoriser un nombre non borné de a et de b (pour vérifier qu’il y a autant de b que de a). Or les AFND qui reconnaissent les langages réguliers ont une ”mémoire” finie (leur nombre fini d’état jouant le rôle de ”mémoire”). 3 Les langages et grammaires algébriques La première notion abordée est celle de grammaire algébrique, que nous pratiquerons dès le TP3. 3.1 Les grammaires algébriques Voici un exemple de grammaire algébrique : phrase → sujet verbe complement sujet → JE | IL verbe → JOUE | TRAVAILLE complement → AVECLECHAT | SURLORDINATEUR Cours COMPIL Langages et grammaires algébriques: résumé Cette grammaire est constituée de 7 productions. ”phrase → sujet verbe complement” est une production qui indique qu’une phrase est un sujet suivi d’un verbe suivi d’un complément. ”verbe → JOUE | TRAVAILLE” est en fait 2 productions de même membre gauche, qui indiquent qu’un verbe est soit JOUE, soit TRAVAILLE (le symbole | joue donc le rôle de choix, comme dans les expressions régulières). L’axiome (le point de départ) de cette grammaire est phrase. Le vocabulaire terminal (l’ensemble des terminaux) est constitué des symboles { JE, IL, JOUE, TRAVAILLE, AVECLECHAT, SURLORDINATEUR}, c’est à dire que les mots engendrés par cette grammaire seront des suites de ces symboles. Le vocabulaire non-terminal (l’ensemble des non-terminaux) est constitué des symboles {phrase, sujet, verbe, complement}. 3.2 Langage engendré, dérivations Pour engendrer un mot à partir d’une grammaire algébrique, on part de l’axiome, et on procède par récritures successives jusqu’à avoir complètement éliminé les non-terminaux (comme leur nom l’indique, on a terminé quand on se retrouve avec uniquement des terminaux). On dit qu’on effectue des dérivations. Exemple : phrase ⇒ sujet verbe complement ⇒ sujet TRAVAILLE complement ⇒ IL TRAVAILLE complement ⇒ IL TRAVAILLE AVECLECHAT est une suite de dérivations directes qui part de l’axiome, et permet d’engendrer le mot IL TRAVAILLE AVECLE CHAT. Effectuer une dérivation directe consiste à choisir un non-terminal et une règle dans laquelle ce nonterminal apparaı̂t en partie gauche. Ensuite on remplace le non-terminal par la partie droite de la règle. Comme on choisit le non-terminal et la production, plusieurs choix sont possibles. On peut ainsi dériver le même mot avec la suite de dérivations : phrase ⇒ sujet verbe complement ⇒ IL verbe complement ⇒ IL TRAVAILLE complement ⇒ IL TRAVAILLE AVECLECHAT On peut aussi dériver les mots JE TRAVAILLE AVECLECHAT, JE TRAVAILLE SURLORDINATEUR et IL TRAVAILLE SURLORDINATEUR. Sans surprise, le langage engendré par une grammaire est l’ensemble des mots composés des terminaux qu’on peut dériver à partir de l’axiome. 3.3 Langages algébriques, comparaison avec les réguliers Les langages engendrés par les grammaires algébriques sont appelés langages algébriques. Les langages réguliers sont inclus dans les langages algébriques. Donc tout langage régulier est aussi algébrique, mais l’inverse n’est pas vrai (cf {an bn |n ≥ 0} qui est algébrique mais pas régulier). En conséquence, tout langage qu’on peut décrire par une expression régulière peut aussi être décrit par une grammaire algébrique. 3.4 Outils pour l’analyse syntaxique On parle souvent de ”parseur” au lieu d’analyseur syntaxique. Reconnaı̂tre un texte se dit alors ”parser”. Un générateur d’analyseur syntaxique est un logiciel qui prend en entrée une grammaire algébrique et qui produit en sortie un module d’analyse syntaxique (un autre logiciel) correspondant à cette grammaire. Nous verrons deux générateurs en TP : Cup et antLR, qui utilisent des techniques de parsing différentes. 4 Arbres syntaxiques La figure 1 montre un arbre syntaxique pour le mot : IL TRAVAILLE AVECLECHAT. On constate que les 2 dérivations données plus haut pour ce mot permettent de construire cet arbre. Dans le cas de cette grammaire, ce mot admet un seul arbre syntaxique, qui fixe son interprétation. Cours COMPIL Langages et grammaires algébriques: résumé phrase sujet verbe IL complement TRAVAILLE AVECLECHAT Figure 1 – Un arbre syntaxique Voici une autre grammaire qui engendre des expressions arithmétiques construites sur les terminaux {id, +, *, (, )} : E → E + E | E * E | ( E ) | id Le mot id * id + id admet deux arbres syntaxiques (donc 2 interprétations), correspondant à 2 dérivations différentes, cf figure 2. (1) E ⇒ E * E ⇒ E * E + E ⇒∗ id * id + id (2) E ⇒ E + E ⇒ E * E + E ⇒∗ id * id + id E (1) E (2) E E E id * id E E + E id id E E * id + id Figure 2 – Un mot admettant 2 arbres syntaxiques 5 Grammaires ”pathologiques” Quand on donne une grammaire algébrique à un générateur de parseur, plusieurs cas sont possibles : – génération de code sans warning (cas favorable) ; – rejet de la grammaire car elle ne convient pas à l’algorithme de parsing de l’outil (cas qu’on verra plus tard, pas facile) ; – génération de code avec warning : il faut bien comprendre le message car ce n’est pas bon signe ; – rejet de la grammaire pour une autre raison : il faut comprendre pourquoi la grammaire est pathologique. On s’intéresse ici aux 2 derniers cas : grammaires pathologiques car contenant des productions qui ne servent pas (cas du warning) et grammaire ambiguës qui admettent plusieurs interprétations pour un mot et sont cause de non déterminisme (cas du rejet). 5.1 Ambiguı̈té Le mot de la figure 2 est ambigü car il admet plusieurs arbres syntaxiques, donc plusieurs interprétations. Pour faire simple, le générateur de parseur ne sait pas quel code générer pour prévoir la reconnaissance d’un tel mot. Une grammaire dite ambiguë, qui engendre au moins un mot ambigü, sera donc rejetée par le générateur. Quand une grammaire est ambiguë, il n’y a plus qu’à la retravailler pour obtenir une grammaire équivalente (= qui engendre le même langage) et qui n’est pas ambiguë. Parfois on a juste une grammaire mal fichue, il suffit de réfléchir un peu (mais ce n’est pas si simple quand on n’a pas d’expérience). Cours COMPIL 5.1.1 Langages et grammaires algébriques: résumé Le cas des grammaires à opérateurs C’est un cas bien connu qu’on sait ”désambiguer”. Cette grammaire à opérateurs présente des productions récursives et symétriques (par rapport aux opérateurs) qui conduisent fatalement à une ambiguı̈té. E → E + E | E * E | ( E ) | id Le problème est double : – l’associativité des opérateurs binaires n’est pas spécifiée. Le mot id + id + id admet 2 arbres syntaxiques correspondant à un associativité gauche ou droite de l’opérateur + ; – la priorité des opérateurs n’est pas spécifiée, les 2 interprétations du mot id * id + id données figure 2 correspondent à un opérateur + prioritaire sur *, ou à l’inverse. Il suffit d’intégrer ces informations de priorité et d’associativité dans une nouvelle grammaire plus structurée, en suivant la recette suivante : – on ajoute un non terminal par niveau de priorité (S pour somme - +, P pour produit - *, F pour facteur, etc) ; – les moins prioritaires en haut de l’arbre, proches de l’axiome ; – les plus prioritaires en bas de l’arbre, proches des feuilles ; – les atomes tout en bas ; – associativité gauche/droite ⇒ récursivité gauche/droite. On obtient pour notre exemple (d’axiome S) : S→S+P | P P →P *F | F F → ( S ) | id La grammaire obtenue est nettement moins lisible. Des outils comme Cup automatisent la transformation de la grammaire : on donne à l’outil une grammaire ambiguë et des indications d’associativité et de priorité, et il génère par derrière la version non ambiguë. L’outil antLR ne le fait pas, il faut lui donner directement une grammaire non ambiguë. 5.2 Les grammaires dont des productions ne servent à rien Ces grammaires contiennent : – des non-terminaux improductifs qui, même s’ils sont membre gauches de production, ne permettent pas d’engendrer un mot ; – des non-terminaux inaccessibles qu’on ne voit jamais apparaı̂tre dans une dérivation issue de l’axiome. Il s’ensuit que toute une partie de la grammaire ne sert à rien alors que ce n’était pas l’intention de celui qui l’a écrite ! Le message d’erreur des outils n’étant pas toujours des plus clairs, il faut savoir détecter les improductifs et les inaccessibles, et savoir réparer sa grammaire en conséquence. 6 Les algébriques. . . et les autres On a vu que les algébriques contiennent les réguliers. Mais tous les langages ne sont pas algébriques. L’exemple type est le langage {an bn cn | n ≥ 0}, qu’on ne peut pas décrire par une grammaire algébrique. Noam Chomsky a introduit dans sa célèbre classification d’autres grammaires plus générales que les algébriques : régulière ⊂ algébrique ⊂ contextuelle ⊂ quelconque