230 likes | 401 Vues
Analyse lexicale. Pr ZEGOUR DJAMEL EDDINE Ecole Supérieure d’Informatique (ESI) www.zegour.uuuq.com email: d_zegour@esi.dz. Analyse lexicale Taches d‘un scanner Grammaires Régulières et Automates finis Implémentation des scanners. 2. Saute les caractères non significatifs. blancs
E N D
Analyse lexicale Pr ZEGOUR DJAMEL EDDINE Ecole Supérieure d’Informatique (ESI) www.zegour.uuuq.com email: d_zegour@esi.dz
Analyse lexicale Taches d‘un scanner Grammaires Régulières et Automates finis Implémentation des scanners
2. Saute les caractères non significatifs • blancs • Caractères de tabulation • Caractères de fin de lignes (CR, LF) • Commentaires Les unités lexicales ont une structure syntaxique ident = letter { letter | digit }. number = digit { digit }. if = "i" "f". eql = "=" "=". ... Taches d’un scanner 1. Délivre les symboles terminaux (unités lexicales) scanner IF, LPAR, IDENT, EQ, NUMBER, RPAR, ..., EOF i f ( x = = 3 ) Source Unités lexicales
Le scanner doit éliminer les caractères non significatifs Ces caractères peuvent apparaître partout => conduiraient vers des grammaires très complexes) Statement = "if" {Blank} "(" {Blank} Expr {Blank} ")" {Blank} ... . Blank = " " | "\r" | "\n" | "\t" | Comment. Pourquoi la phase lexicale est séparée de la phase syntaxique? Entrainerait une analyse syntaxique plus compliquée (Ex. distinction difficile entre les mots clés et les identificateurs) Statement = ident "=" Expr ";" | "if" "(" Expr ")" ... . On doit écrire ceci comme suit Statement = "i" ( "f" "(" Expr ")" ... | letter {letter | digit} "=" Expr ";" ) Les unités lexicales peuvent être decrites à l‘aide de grammaires régulières (Plus simple et plus efficace que les grammaires à contexte libre)
Analyse lexicale Taches d‘un scanner Grammaires Régulières et Automates finis Implémentation des scanners
Exemple Grammaire pour les identificateurs Ident = letter | letter Rest. Rest = letter | digit | letter Rest | digit Rest. Ex., dérivation de xy3 Ident => letter Rest => letter letter Rest => letter letter digit Exemple Grammaire pour les identificateurs Ident = letter { letter | digit }. Grammaires régulières Définition Une grammaire est dite régulière si elle peut être décrite par des productions de la forme: a, b des TS A, B des NTS A = a. A = b B. Autre définition Une grammaire est dite régulière si elle peut être décrite par une simple non récursive production EBNF.
Après substitution de F dans T T = id { "*" id }. Après substitution de T dans E E = id { "*" id } { "+" id { "*" id } }. La grammaire est régulière Peut-on transformer la grammaire suivante en une grammaire régulière? Après substitution de F dans E E = F { "*" F }. F = id | "(" E ")". E = ( id | "(" E ")" ) { "*" ( id | "(" E ")" ) }. Substituer E dans E ne mène à rien. Récursion centrale ne peut être éliminée. La grammaire est non régulière. Exemples Peut-on transformer la grammaire suivante en une grammaire régulière? E = T { "+" T }. T = F { "*" F }. F = id.
Les structures lexicales sont généralement régulières Exception:commentaires emboîtés nom letter { letter | digit } /* ..... /* ... */ ..... */ Le scanner doit les traiter comme cas spécial Limitations des grammaires régulières Les grammaires régulières ne traitent pas les structures emboîtées Raison : ne peut éliminer la récursion centrale! La récursion centrale est importante dans les langages de programmation. • Expressions emboîtées • Instructions emboîtées • Classes emboîtées Expr => ... "(" Expr ")" ... Statement => "do" Statement "while" "(" Expr ")" Class => "class" "{" ... Class ... "}" Solution : utilisation des grammaires à contexte libre. nombres digit { digit } chaînes "\"" { pas de quote } "\"" Mot-clés letter { letter } opérateurs ">" "="
Exemples "w" "h" "i" "l" "e" while letter ( letter | digit )* names digit+ numbers Expressions régulières Autre notation pour les grammaires régulières Définition 1. e (la chaîne vide) est une expression régulière 2. Un symbole terminal est une expression régulière 3. Si a et b sont des expressions régulières, les expressions suivantes sont régulières: a b (a | b) (a)? e | a (a)* e | a | aa | aaa | ... (a)+ a | aa | aaa | ...
Table de transition d "fini", car d Peut être défini explicitement letter digit s0 s1 error s1 s1 s1 Définition • Un automate fini déterministique est un 5 tuple (S, I, d, s0, F) • S Ensemble des états • I Ensemble des symboles d‘entrée • d: S x I S Fonction de transition des états • s0 État initial • F Ensemble des états finaux Automate Fini Déterministique (DFA) Peut être utilisé pour analyser les langages réguliers Exemple État final letter letter 0 1 État initial est 0 par convention digit Le langage reconnu par une DFA est l‘ensemble de toutes les séquences de symboles pouvant être générées de l‘état initial vers l‘un des états finaux. • Un DFA reconnaît une phrase (sentence) • S‘il est dans un état final • Et il n‘y a plus de symboles d‘entrée Ou il n‘ y a pas de transition possible • avec le prochain symbole d‘entrée
Exemple Entrée:max >= 30 m a x • Pas de transition avec " " dans s1 • ident reconnu s0 s1 > = • Saute les blancs au début • Ne s‘arrête pas en s4 • Pas de transition avec " " dans s5 • geq reconnu s0 s5 3 0 • Saute les blancs au début • Pas de transition avec " " dans s2 • number reconnu s0 s2 Le Scanner comme un DFA Le scanner peut être vu comme un grand DFA " " letter letter 0 1 ident digit digit digit 2 number ( 3 lpar > = 4 5 gtr geq ... Après chaque unité lexicale reconnue le scanner recommence à l‘état initial s0
Exemple Grammaire Automate A = a B | b C | c. B = b B | c. C = a C | c. b c a A B a b c C c stop Transformation: grammaire régulière vers DFA Une grammaire régulière peut être transformée en un DFA comme suit b A = b C. A C d A = d. A stop
Chaque NDFA peut être transformé en un DFA équivalent H intNum digit A,B,C,D,E,F H 0 1 2 3 hexNum hex digit Automate Fini Non Déterministique (NDFA) Exemple intNum Non déterministique car Il y a 2 possibles transitions avec digit dans s0 intNum = digit { digit }. hexNum = digit { hex } "H". digit = "0" | "1" | ... | "9". hex = digit | "A" | ... | "F". digit digit 0 1 digit H 2 3 hexNum hex
Exemple pour d d a b c 0 1 - - 1 - 1 2 2 - - - F A = a { b } c. a c 0 1 2 int[,] delta = { {1, -1, -1}, {-1, 1, 2}, {-1, -1, -1} }; A b Cette implémentation pourrait être très inefficace pour un véritable scanner Implémentation d‘un DFA (Variante 1) Implémentation de d comme une matrice int[,] delta = new int[maxStates, maxSymbols]; int lastState, state = 0; // DFA starts in state 0 do { int sym = next symbol; lastState = state; state = delta[state, sym]; } while (state != undefined); assert(lastState in F); // F is set of final states return recognizedToken[lastState];
en Java c‘est plus fatiguant: int state = 0; loop: for (;;) { char ch = read(); switch (state) { case 0: if (ch == 'a') { state = 1; break; } else break loop; case 1: if (ch == 'b') { state = 1; break; } else if (ch == 'c') { state = 2; break; } else break loop; case 2: return A; } } return errorToken; Implémentation d‘un DFA (Variante 2) a c 0 1 2 A b Coder les états dans le code source char ch = read(); s0: if (ch == 'a') { ch = read(); goto s1; } else goto err; s1: if (ch == 'b') { ch = read(); goto s1; } else if (ch == 'c') { ch = read(); goto s2; } else goto err; s2: return A; err: return errorToken;
Analyse lexicale Taches d‘un scanner Grammaires Régulières et Automates finis Implémentation des scanners
Initialisation du scanner Scanner.Init(new StreamReader("myProg.zs")); Lecture des unités lexicales Token t; for (;;) { t = Scanner.Next(); ... } Interface du Scanner class Scanner { static void Init (TextReader r) {...} static Token Next () {...} }
Exemple de codage pour un langage particulier Erreur Classes des unités Opérateurs et caractères spéciaux Mot-clés Fin de fichier const int NONE = 0, IDENT = 1, NUMBER = 2, CHARCONST = 3, BREAK = 29, CLASS = 30, CONST = 31, ELSE = 32, IF = 33, NEW = 34, READ = 35, RETURN = 36, VOID = 37, WHILE = 38, WRITE = 39, EOF = 40; ASSIGN = 17, /* = */ PPLUS = 18, /* ++ */ MMINUS = 19, /* -- */ SEMICOLON = 20, /* ; */ COMMA = 21, /* , */ PERIOD = 22, /* . */ LPAR = 23, /* ( */ RPAR = 24, /* ) */ LBRACK = 25, /* [ */ RBRACK = 26, /* ] */ LBRACE = 27, /* { */ RBRACE = 28, /* } */ PLUS = 4, /* + */ MINUS = 5, /* - */ TIMES = 6, /* * */ SLASH = 7, /* / */ REM = 8, /* % */ EQ = 9, /* == */ GE = 10, /* >= */ GT = 11, /* > */ LE = 12, /* <= */ LT = 13, /* < */ NE = 14, /* != */ AND = 15, /* && */ OR = 16, /* || */ Unités lexicales class Token { public const int NONE = 0, IDENT = 1, …. int kind; // token code int line; // token line (for error messages) int col; // token column (for error messages) int val; // token value (for number and charCon) string str; // token string (for numbers and identifiers) }
Init() public static void Init (TextReader r) { input = r; line = 1; col = 0; NextCh(); // reads the first character into ch and increments col to 1 } NextCh() • ch = prochain caractère d‘entrée • retourne EOF si fin de fichier • incrémente line et col static void NextCh() { try { ch = (char) input.Read(); col++; if (ch == '\n') { line++; col = 0; } else if (ch == '\uffff') ch = EOF; } catch (IOException e) { ch = EOF; } } Implémentation des Scanner Variables statiques dans le scanner static TextReader input; // input stream static char ch; // next input character (still unprocessed) static int line, col; // line and column number of the character ch const int EOF = '\u0080'; // character that is returned at the end of the file
Method Next() public static Token Next () { while (ch <= ' ') NextCh(); // skip blanks, tabs, eols Token t = new Token(); t.line = line, t.col = col; switch (ch) { case 'a': ... case 'z': case 'A': ... case 'Z': ReadName(t); break; case '0': case '1': ... case '9': ReadNumber(t); break; case ';': NextCh(); t.kind = Token.SEMICOLON; break; case '.': NextCh(); t.kind = Token.PERIOD; break; case EOF: t.kind = Token.EOF; break; // no NextCh() any more ... case '=': NextCh(); if (ch == '=') { NextCh(); t.kind = Token.EQ; } else t.kind = Token.ASSIGN; break; case '&': NextCh(); if (ch == '&') { NextCh(); t.kind = Token.AND; } else t.kind = NONE; break; ... case '/': NextCh(); if (ch == '/') { do NextCh(); while (ch != '\n' && ch != EOF); t = Next(); // call scanner recursively } else t.kind = Token.SLASH; break; default: NextCh(); t.kind = Token.NONE; break; } return t; } // ch holds the next character that is still unprocessed nom, mot-clés nombres unité simple unités composée commentaire caractère invalide
static void ReadNumber (Token t) • Au début ch contient le premier chiffre du nombre • Lit les prochains digits, les range dans t.str; puis convertit la chaîne en un nombre et range lavaleur dans t.val.Si débordement: Erreur • t.kind = Token.NUMBER; • A la fin ch contient le premier caractère après le nombre Autres méthodes static void ReadName (Token t) • Au début ch contient la première lettre du nom • Lit les prochaines lettres, digits et '_' et les range dans t.str • Chercher le nom dans la table des mots clés ( hash-code ou arbre de recherche binaire )si trouvé: t.kind =code du mot clé;sinon: t.kind = Token.IDENT; • À la fin, ch contient le premier caractère après le nom
Faire attention à la programmation Utiliser les variables globales : Ex.: ch est global et non un paramètre de NextCh() Pour les grands fichiers d‘entrée, c‘est judicieux d‘utiliser des lectures bufférisées Stream file = new FileStream("MyProg.zs"); Stream buf = new BufferedStream(file); TextReader r = new StreamReader(buf); Scanner.Init(r); Considérations d‘efficacité Taille d‘un programme modeste Aux environs de 1000 instructions environ 6000 unités environ 60000 caractères La phase lexicale consomme un temps important prend 20-30% du temps total de compilation