Générateurs de compilateurs Pr ZEGOUR DJAMEL EDDINE Ecole Supérieure d’Informatique (ESI) www.zegour.uuuq.com email: [email protected] Générateurs de compilateurs Introduction Yacc Lex Coco/R Fonctionnement des générateurs de compilateurs Ils génèrent les parties d’un compilateur à partir d’une spécification concise (Parties générées : scanner, analyseur syntaxico-sémantique, générateur de code , ...) générateur scanner Spécification du scanner Ex. grammaire régulière) Spécification sémantique (Ex. grammaire d’attribut) générateur de l’analyseur Générateur De compilateurs Exemples Yacc Lex Coco/R ... scanner Analyseur compilateur & éditeur de liens Classes utilisateur • Table des symboles • Générateur de code • Programme principal • ... générateur d’analyseur syntaxique et sémantique pour C et Java générateur de scanner pour C, Java et C# générateur de scanner et d’analyseur pour Java, C#, Modula-2, Oberon, ... compilateur généré Générateurs de compilateurs Introduction Yacc Lex Coco/R Yacc - Yet another compiler compiler Histoire 1975 développé aux laboratoires Bell ( ensemble avec C et Unix) • Génère des analyseurs LALR(1) • À l’origine sous Unix, Aujourd'hui aussi sous Windows, Linux • A l’origine pour C, Aujourd'hui pour Java Utilisation sample.y Nous décrivons ici la version de Java Yacc parser.java javac Versions actuelles Bison version GNU de Yacc http://www.gnu.org/software/bison/bison.html Byacc Berkeley Yacc http://byaccj.sourceforge.net/ parser.class Langage d’entrée pour Yacc Format général %{package Java et les lignes ‘import’ %} Déclarations Yacc (unités, règles de précédence des opérateurs, ...) %% productions %% Déclarations Java (champs, méthodes) Traduit vers class parser { ... public void yyparse() { ... parser ... } } Yacc — Productions et actions sémantiques Productions Grammar Production Alternative SemAction NT T = {Production}. = NT ":" Alternative {"|" Alternative} ";" = {NT | T} [SemAction]. = "{" ... arbitrary Java statements ... "}". = ident. = ident | charConst. Exemple expr: term { $$ = $1; } | expr '+' term { $$.ival = $1.ival + $3.ival; } ; Actions Sémantiques • Peuvent contenir des instructions Java • Peuvent apparaître seulement à la fin d’une alternative • Les attributs sont dénotés par des noms spéciaux: $$ attribut du coté gauche NTS $i attribut du i-ème symbole du coté droit ($1 = attr. du premier symbole, $2 = attr. du second symbole, ...) Yacc — Attributs Pour les symboles terminaux • Sont délivrés par le scanner (scanner développé manuellement ou généré avec Lex) • Chaque unité lexicale a un attribut de type parserval class parserval { int ival; // token value if the token should have an int attribute double dval; // token value if the token should have a double attribute String sval; // token value, e.g. for ident and string Object obj; // token value for more complex tokens parserval(int val) {...} // constructors parserval(double val) {...} parserval(String val) {...} parserval(Obj val) {...} } • Le scanner retourne les attributs dans la variable globale yylval Scanner yylval = new parserval(n); Accès dans une action sémantique { ... $1.ival ... } Pour les symboles non terminaux • Chaque NTS a un attribut $$ de type parserval ( valeurs plus complexes rangées dans $$.obj) • Chaque affectation à $$ empile l’attribut du NTS dans une pile d’attributs. Les accès à $1, $2 se font en dépilant les attributs de la pile Yacc — Variables et Méthodes Java Sont déclarées après le second %% %{ ... imports ... %} ... Unités ... %% ... Productions ... %% ... Déclarations Java ... • Deviennent des champs et des méthodes pour l’analyseur • Peuvent être utilisées dans les actions sémantiques Au moins les méthodes suivantes doivent être implémentées dans les déclarations Java void yyerror(String msg) {...} Pour afficher les messages d’erreur int yylex() {...} Scanner (retourne les codes des unités et remplit yylval) public static void main(String[] arg) { ... initializations for yylex ... yyparse(); } Programme principal Exemple: Compilateur pour les expressions /* declaration of all tokens which are not strings */ %token number %% /* productions: first NTS is the start symbol */ input: expr { System.out.println($1.ival); } ; expr: term | expr '+' term { $$ = $1; } { $$.ival = $1.ival + $3.ival; } ; term: factor | term '*' factor { $$ = $1; } { $$.ival = $1.ival * $3.ival; } ; factor: number | '(' expr ')' { $$ = $1; } { $$ = $2; } ; %% int yylex() {...} void yyerror(string msg) {...} public static void main(String[] arg) {...} arithmétiques Conventions de codage des unités • eof == 0 • Code des unités ‘caractère’ : leurs valeurs Ascii (Par exemple '+' == 43) • YYERRCODE == 256 • Autres unités sont numérotées consécutivement commençant par 257 (Par exemple. nombre == 257); elles peuvent être accédées dans les productions et dans yylex() utilisant leur nom déclaré. Yacc — Précédence des opérateurs La grammaire suivante spécifie explicitement la précédence des opérateurs expr: term | expr '+' term ; term: factor | term '*' factor ; factor: number | '(' expr ')' ; • '*' a une précédence sur '+' • Les opérateurs sont associatifs à gauche: a*b*c == (a*b)*c On peut aussi l’exprimer comme suit en Yacc: %token number %left '+' %left '*' • %left : l’opérateur est associatif gauche a+b+c == (a+b)+c %% input: expr { System.out.println($1.ival); } ; expr: number | expr '+' expr | expr '*' expr | '(' expr ')' { $$ = $1; } { $$.ival = $1.ival + $3.ival; } { $$.ival = $1.ival * $3.ival; } { $$ = $2; } %% ... • Les opérateurs sont déclarés en ordre ascendant de priorité: '*' a une précédence sur '+' • Cette grammaire ne spécifie aucune précédence d’opérateurs • La précédence est spécifiée par %left ou %right Yacc — Traitement des erreurs Les alternatives ‘error’ Pour certains NTS (EX: Instructions, Expression, ...) l’utilisateur doit spécifier Les alternatives ‘error’ A: ... | ... | error a {...} ; a ... Séquence quelconque de symboles T et NT Signification: S’il existe une erreur dans A l’analyseur effectue les actions suivantes: • Il dépile des états de la pile jusqu’à l’obtention d’un état dans lequel une action ‘décaler’ avec l’unité error est valide • ‘Décaler’ error • Il saute les unités d’entrée jusqu’à ce qu’il détecte une séquence d’unités qui peut être réduite à a ( le sommet de pile contient alors : error a) • Il réduit error a à A et exécute l’action sémantique correspondante Exemple Statement = ... | error ';' ; Saute tout jusqu’au prochain ';' Générateurs de compilateurs Introduction Yacc Lex Coco/R Lex — Générateur de scanner Histoire 1975 développé aux laboratoires Bell • génère un scanner en forme de DFA • A l’origine un outil de Unix, aujourd'hui aussi pour Windows • A l’origine pour C, Aujourd’hui aussi pour Java • Coopère généralement avec Yacc Utilisation Nous decrivons ici la version C sample.l Lex sample.yy.c sample.y Yacc sample.tab.c include C-Compiler Versions actuelles flex version GNU de Lex (pour C) http://www.gnu.org/software/flex/ JLex version Java avec légère différence dans la syntaxe d’entrée; incompatible avec Bison ou Byacc http://www.cs.princeton.edu/~appel/modern/java/JLex/ version C# , dérivé de JLex http://www.cybercom.net/~zbrad/DotNet/Lex CsLex sample.o Exemple de description Lex %{ ... e.g. include directives for token numbers exported by the parser ... %} /* macros */ delim [ \t\n] /* blank, tab, eol */ ws {delim}+ /* {...} ... use of a macro */ letter [A-Za-z] digit [0-9] id {letter} ({letter} | {digit})* number {digit}+ %% /* token declarations described as regular expressions */ {ws} {} /* no action */ if { return IF; } /* constants like IF are imported from the parser */ then { return THEN;} else { return ELSE; } {id} { yylval = storeId(yytext, yyleng); return ID; } {number} { yylval = convert(yytext, yyleng); return number; } < { return yytext[0]; } > { return yytext[0]; } . {} /* . denotes any character */ %% /* semantic routines */ int storeId(char* text, int len) {...} int convert(char* text, int len) {...} Scanner généré La spécification du scanner est convertie en une fonction int yylex() {...} qui est incluse dans l’analyseur yylex() retourne aussi les attributs d’unités comme variables globales int yylval; char* yytext; int yyleng; int yylineno; /* attribute if the token has a numeric value */ /* token text (attribute of ident, string, ...) */ /* lengh of the token text */ /* line number of the token */ L’analyseur déclare (et exporte) les codes des unités %token IF %token THEN ... Expressions régulières dans Lex Éléments des expressions régulières abc . x* x+ x? (...|...) [...] {...} ^ $ \udddd la chaîne "abc"; tout caractère sauf ()[]{}*+?|^$.\ dénote lui-même Tout caractère sauf \n (fin de ligne) 0 ou plusieurs répétitions de x 1 ou plusieurs répétitions de x 0 ou 1 occurrence de x ( occurrence optionnelle) pour grouper des alternatives ensemble de tous les caractères entre les crochets (Ex. [A-Za-z0-9$]) Utilise d’une macro ligne début ligne fin caractère en Unicode Générateurs de compilateurs Introduction Yacc Lex Coco/R Coco/R – Compilateur de compilateur / Descente Récursive Histoire 1980 développé à l’université de Linz (Rechenberg, Mössenböck) • génère un scanner et un analyseur à partir d’une grammaire d’attribut - scanner comme un DFA - Analyseur sous forme de ‘descente récursive’ • Il existe des versions pour C#, Java, C/C++, Delphi, Modula-2, Oberon, Python, ... • Publié sous GPL: http://www.ssw.uni-linz.ac.at/Research/Projects/Coco/ Utilisation main grammaire d’attribut parser Coco/R scanner Classes utilisateurs (Ex. Table des symboles csc Exemple: Compilateur pour les Expressions arithmétiques COMPILER Calc /* grammar name = start symbol */ CHARACTERS /* character sets used in token declarations */ digit = '0' .. '9'. tab = '\t'. cr = '\r'. lf = '\n'. TOKENS /* declaration of all tokens which are not literals */ number = digit {digit}. COMMENTS /* declaration of comments */ FROM "//" TO cr lf FROM "/*" TO "*/" NESTED IGNORE tab cr lf /* these characters are ignored as white space */ PRODUCTIONS Calc (. int x; .) = "CALC" Expr<out x> (. System.Console.WriteLine(x); .) . Expr<out int x> = Term<out x> { '+' Term<out y> }. (. int y; .) Term<out int x> = Factor<out x> { '*' Factor<out y> }. (. int y; .) Factor<out int x> = number | '(' Expr<out x> ')'. END Calc. (. x = x + y; .) (. x = x * y; .) (. x = Convert.ToInt32(t.val); .) Coco/R — les attributs Les symboles terminaux • Les symboles terminaux n’ont pas d’attribut explicite • Leurs valeurs peuvent être accédées dans les actions sémantiques utilisant les variables suivantes Token t; l’unité la plus récente reconnue Token la; la prochaine unité (lookahead) (non encore reconnue) Exemple Factor<out int x> = number class Token { int kind; string val; int pos; int line; int col; } (. x = Convert.ToInt32(t.val); .) // token code // token value // token position in the source text (starting at 0) // token line (starting at 1) // token column (starting at 0) Les symboles non terminaux • Les NTS peuvent avoir des attributs d’entrée Attr. formels: A<int x, char c> = ... . Attr réels.: ... A<y, 'a'> ... • Les NTS peuvent avoir des attributs de sortie B<out int x, out int y> = ... . ... B<out z, out n> ... Coco/R — Traitement sémantique Actions sémantiques • code Java entre (. et .) • Peuvent apparaître n’importe où dans les productions • Dans le coté gauche d’une production elles sont considérée comme des déclarations Term<out int x> = Factor<out x> { '*' Factor<out y> }. (. int y; .) déclaration (. x = x * y; .) action sémantique Déclarations sémantiques • Apparaissent au début de la spécification du compilateur • Sont utilisées pour déclarer les champs et les méthodes de l’analyseur • Les ‘import’ peuvent aussi être spécifiés COMPILER Sample using System.Collections; static IList myList; static void AddToList (int x) {...} CHARACTERS ... Bien sûr, les actions sémantiques peuvent aussi accéder aux champs et méthodes de classes autre que ceux de l'analyseur. Coco/R – les méthodes d’analyse Chaque production est traduite en une méthode de l’analyseur Expr<out int x> = Term<out x> { '+' Term<out y> }. (. int y; .) (. x += y; .) devient static void Expr (out int x) { int y; Term(out x); while (la.kind == plus) { Scan(); Term(out y); x += y; } } Coco/R – Traitement des erreurs syntaxiques L’analyseur utilise la technique des ‘ancres spéciaux’ Point de synchronisation Doivent être marqués par SYNC Statement = SYNC ( Assignment | IfSatement | ... ). if la.kind dans Suivant(SYNC) une erreur est reportée et des unités sont sautées jusqu’à la.kind dans Suivant(SYNC) Inter {eof} Les faux messages d'erreur sont supprimés si moins de 3 unités ont été reconnues depuis la dernière erreur. Séparateurs faibles Les séparateurs au début d’une itération peuvent être marqués comme Weak (faibles) FormalPars = "(" Param { WEAK ',' Param } ')'. Si le séparateur manque ou est mal écrit, la boucle n'est pas terminée prématurément, mais l'analyseur synchronise avec Premier(Param) Inter Suivant({...}) Inter {eof} Coco/R — Tests de grammaire Test LL(1) A = a [B] C d | B a. B = a b. C = a [d]. Coco/R affiche les avertissements suivants LL1 warning in A: a is start & successor of deletable structure LL1 warning in A: a is start of several alternatives LL1 warning in C: d is start & successor of deletable structure Test de complétude Existe-il une production pour chaque NTS? Test de non-redondance Est-ce que que la grammaire contient des productions non atteintes? Test de Dérivabilité Est-ce que chaque NTS peut être dérivé en une chaîne de symboles terminaux? Test de Non-circularité Y a t-il des NTS qui peuvent être dérivé (directement ou indirectement)en eux-mêmes? Coco/R — Pragmas (Directives de compilation) Pragmas sont des symboles terminaux • Qui peuvent apparaître n’importe où dans l’entrée • Qui ne font pas partie de la syntaxe • Qui doivent être traités sémantiquement Ex. options du compilateur COMPILER X CHARACTERS ... TOKENS ... PRAGMAS PrintOption = "$print". DbgOption = "$debug". ... (. option[print] = true; .) (. option[debug] = true; .) Quand la string $print apparaît dans le texte d’entrée l’action sémantique option[print] = true; est exécutée Coco/R — le symbole ‘ANY’ Dans la déclaration des ensembles de caractères Il décrit des ensemble de caractères complémentaires CHARACTERS letter = 'A' .. 'Z' + 'a' .. 'z'. noLetter = ANY - letter. ... Tous les caractères qui ne sont pas des lettres Dans les productions Il décrit tout unité qui ne peut être produite par les autres alternatives PlaceHolder = ident | ANY. Tout unité qui n’est pas ident ou eof SemAction = "(." { ANY } ".)". Tout unité qui n’est pas ".)" ou eof Coco/R — Résolution des conflits LL(1) Résolution de conflit par un by a multi-symbol lookahead Statement = IF (IsAssignment()) Designator "=" Expr ";" | Designator "(" ActualParams ")" ";" | ... . static boolean IsAssignment () { Token x = la; while (x.kind != _assign && x.kind != _lpar) x = Scanner.Peek(); return x.kind == _assign; } Scanner.Peek() ... Lit les unités sans les enlever de l’entrée Les noms d’unités (_assign, _lpar, ...) sont générés à partir des sections TOKENS Résolution de conflit par une vérification sémantique Factor = IF (IsCast()) '(' ident ')' Factor /* type cast */ | '(' Expr ')' /* nested expression */ | ... . static boolean IsCast () { Token x = Scanner.Peek(); if (x.kind == _ident) { Symbol s = Tab.Find(x.val); return s.kind == Symbol.Kinds.Type; } else return false; } Coco/R — les frames Le scanner et l’analyseur sont générés à partir de frames (fichier texte ordinaire) Ex. Scanner.frame public class Scanner { const char EOL = '\n'; const int eofSym = 0; -->declarations ... static Token NextToken () { while (ignore[ch]) NextCh(); -->scan1 t = new Token(); t.pos = pos; t.col = pos - lineStart + 1; t.line = line; int state = start[ch]; StringBuilder buf = new StringBuilder(16); -->scan2 ... } ... } Coco/R insère le code à ces positions En modifiant les frames le scanner et l’analyseur peuvent être adaptés aux besoins de l’utilisateur (à un certain degré) Coco/R — Interfaces Scanner public class Scanner { public static void Init (string sourceFileName) {...} public static void Init (Stream s) {...} public static Token Scan () {...} public static Token Peek () {...} public static void ResetPeek () {...} } Parser public class Parser { public static Token public static Token public static void public static void } t; la; Parse () {...} SemErr (string msg) {...} Error message class public class Errors { public static int public static string public static void public static void public static void public static void } count = 0; errMsgFormat = "-- line {0} col {1}: {2}"; SynErr (int line, int col, int n); SemErr (int line, int col, int n); Error (int line, int col, string msg); Exception (string msg);