Télécharger la présentation
1
Analyse lexicale Pr ZEGOUR DJAMEL EDDINE
Ecole Supérieure d’Informatique (ESI)
2
Analyse lexicale Taches d‘un scanner
Grammaires Régulières et Automates finis Implémentation des scanners
3
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 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 = "=" "=". ...
4
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 ";" ) 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. Les unités lexicales peuvent être decrites à l‘aide de grammaires régulières (Plus simple et plus efficace que les grammaires à contexte libre)
5
Analyse lexicale Taches d‘un scanner
Grammaires Régulières et Automates finis Implémentation des scanners
6
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 = a. A = b B. a, b des TS A, B des NTS 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 Autre définition Une grammaire est dite régulière si elle peut être décrite par une simple non récursive production EBNF. Exemple Grammaire pour les identificateurs Ident = letter { letter | digit }.
7
Exemples Peut-on transformer la grammaire suivante en une grammaire régulière? Après substitution de F dans T T = id { "*" id }. E = T { "+" T }. T = F { "*" F }. F = 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? E = F { "*" F }. F = id | "(" E ")". Après substitution de F dans 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.
8
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. Les structures lexicales sont généralement régulières nom letter { letter | digit } nombres digit { digit } Exception: commentaires emboîtés /* /* ... */ */ Le scanner doit les traiter comme cas spécial chaînes "\"" { pas de quote } "\"" Mot-clés letter { letter } opérateurs ">" "="
9
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 | ... Exemples "w" "h" "i" "l" "e" while letter ( letter | digit )* names digit+ numbers
10
Automate Fini Déterministique (DFA)
Peut être utilisé pour analyser les langages réguliers Exemple Table de transition letter digit s0 s1 d error "fini", car d Peut être défini explicitement letter État final letter 1 État initial est 0 par convention digit 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 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
11
Le Scanner comme un DFA Le scanner peut être vu comme un grand DFA
" " letter Exemple Entrée: max >= 30 letter 1 ident digit s0 s1 m a x Pas de transition avec " " dans s1 ident reconnu digit 2 digit number > = s0 s5 Saute les blancs au début Ne s‘arrête pas en s4 Pas de transition avec " " dans s5 geq reconnu ( 3 lpar > = 4 5 s0 s2 3 0 Saute les blancs au début Pas de transition avec " " dans s2 number reconnu gtr geq ... Après chaque unité lexicale reconnue le scanner recommence à l‘état initial s0
12
Transformation: grammaire régulière vers DFA
Une grammaire régulière peut être transformée en un DFA comme suit A = b C. b A C A = d. d A stop Exemple Grammaire A = a B | b C | c. B = b B | c. C = a C | c. Automate b c A B a C b stop c a c
13
Automate Fini Non Déterministique (NDFA)
Exemple intNum intNum = digit { digit }. hexNum = digit { hex } "H". digit = "0" | "1" | ... | "9". hex = digit | "A" | ... | "F". digit Non déterministique car Il y a 2 possibles transitions avec digit dans s0 1 digit digit H 2 3 hexNum hex Chaque NDFA peut être transformé en un DFA équivalent 1 digit 2 A,B,C,D,E,F hex H 3 intNum hexNum
14
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]; Exemple pour d 2 a 1 c b A = a { b } c. A d a b c F int[,] delta = { {1, -1, -1}, {-1, 1, 2}, {-1, -1, -1} }; Cette implémentation pourrait être très inefficace pour un véritable scanner
15
Implémentation d‘un DFA (Variante 2)
c 1 2 A b Coder les états dans le code source 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; } case 2: return A; } return errorToken; en Java c‘est plus fatiguant: 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; } s2: return A; err: return errorToken;
16
Analyse lexicale Taches d‘un scanner
Grammaires Régulières et Automates finis Implémentation des scanners
17
Interface du Scanner Initialisation du scanner
class Scanner { static void Init (TextReader r) {...} static Token Next () {...} } Scanner.Init(new StreamReader("myProg.zs")); Initialisation du scanner Token t; for (;;) { t = Scanner.Next(); ... } Lecture des unités lexicales
18
Unités lexicales Exemple de codage pour un langage particulier
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) } 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, /* || */ Exemple de codage pour un langage particulier const int NONE = 0, IDENT = 1, NUMBER = 2, CHARCONST = 3, ASSIGN = 17, /* = */ PPLUS = 18, /* ++ */ MMINUS = 19, /* -- */ SEMICOLON = 20, /* ; */ COMMA = 21, /* , */ PERIOD = 22, /* . */ LPAR = 23, /* ( */ RPAR = 24, /* ) */ LBRACK = 25, /* [ */ RBRACK = 26, /* ] */ LBRACE = 27, /* { */ RBRACE = 28, /* } */ BREAK = 29, CLASS = 30, CONST = 31, ELSE = 32, IF = 33, NEW = 34, READ = 35, RETURN = 36, VOID = 37, WHILE = 38, WRITE = 39, EOF = 40; Erreur Classes des unités Opérateurs et caractères spéciaux Mot-clés Fin de fichier
19
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 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() 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; } } ch = prochain caractère d‘entrée retourne EOF si fin de fichier incrémente line et col
20
Method Next() nom, mot-clés nombres unité simple unités composée
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; case '/': NextCh(); if (ch == '/') { do NextCh(); while (ch != '\n' && ch != EOF); t = Next(); // call scanner recursively } else t.kind = Token.SLASH; 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
21
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 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 la valeur dans t.val. Si débordement: Erreur t.kind = Token.NUMBER; A la fin ch contient le premier caractère après le nombre
22
Considérations d‘efficacité
Taille d‘un programme modeste Aux environs de 1000 instructions environ 6000 unités environ caractères La phase lexicale consomme un temps important prend % du temps total de compilation 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);
Présentations similaires
© 2024 SlidePlayer.fr Inc.
All rights reserved.