Analyse lexical
Transcription
Analyse lexical
Compilation Analyse en deux phases Analyse lexicale ("lexer" ou "scanner") : transforme une suite de caractère en suite de lexèmes Analyse lexical Analyse syntaxique ou grammaticale ("parser") : transforme une suite de lexèmes en arbre Expressions régulières Reconnaissance des unités lexicales Génération automatique (lex) Automates finis Les deux analyses sont donc exécutées de façon entremêlée. 1 2 Enjeux Unités lexicales et lexèmes Définition unité lexicale : certaine information associé à une suite de caractères. C'est un symbole terminal. Domaine d'application plus large que celui de la compilation : analyses des commandes, des requêtes, etc. Exemples : – Les chaînes <=, >=, <, > sont des opérateurs relationnels. – Les chaînes toto, ind, tab, ajouter sont des identificateurs. – Les chaînes if, else, while sont des mots clefs. – Les symboles , . ; ( ) sont des séparateurs. Un analyseur lexical est spécifié à l’aide d'une grammaire régulière, tandis qu’un analyseur syntaxique est spécifié à l’aide d’une grammaire non contextuelle. Les expressions régulières sont un langage de description d'automates ; elles sont utilisées dans de nombreux outils Unix (emacs, grep...), et fournies en bibliothèque dans beaucoup des langages de programmation (javascript, java, php, etc). Définition. Un modèle ou motif est une règle associée à une unité lexicale qui décrit l'ensemble des chaînes qui correspond à l'unité lexicale. Définition. Un lexème est la suite de caractère correspondant au modèle. 3 4 Unités lexicales et lexèmes Théorie des langages : notions. Soit l'alphabet Σ un ensemble fini non vide de caractères. Un mot est une séquence de caractères (de Σ). On note : Exemples : • L'unité lexicale IDENT (identificateurs) a pour modèle : toute suite non vide de caractères composée de chiffres, lettres ou du symbole "_" et qui commencent par une lettre. Exemples : truc, i, a3, ajouter_valeur – – – – – – • L'unité lexicale NOMBRE (entier signé) a pour modèle : toute suite non vide de chiffres précédée éventuellement d'un seul caractère parmi {+,-\}. Exemples : 12, 83204, +054, 57 • L'unité lexicale REEL a pour modèle : tout lexème correspondant à l'unité lexicale NOMBRE suivi éventuellement d'un point et d'une suite (vide ou non) de chiffres, le tout suivi éventuellement du caractère E ou e et d'un lexème correspondant à l'unité lexicale NOMBRE. Cela peut également être un point suivi d'une suite de chiffres, et éventuellement du caractère E ou e et d'un lexème correspondant à l'unité lexicale NOMBRE. Exemples : 12.4, 0.5e3, 10., -4e-1, -.103e+2 Un langage sur Σ est un sous-ensemble L de Σ*. Exemples. Soit l'alphabet Σ ={a,b,c} • Soit L1 l'ensemble des mots de Σ ayant autant de a que de b. L1 est le langage infini {є, ab, ba, c, ccc, abccc, accbccbccccca, aabb, baab, bbccccaccbcabcccacac, . . .} Pour décrire le modèle ou motif d'une unité lexicale, on utilise des expressions régulières. 5 є le mot vide, uv la concaténation des mots u et v (є est l'élément neutre). Σ* est l'ensemble infini de tous les mots sur Σ Σ+ est l'ensemble infini des mots non vides, c'est à dire = Σ* - {є} |m| est la longueur du mot m. Σn l'ensemble des mots de longueur n. 6 • Soit L2 l'ensemble de tous les mots de Σ ayant exactement 4 a. L2 est le langage infini {aaaa, abcabbbaacc, . . .} Théorie des langages Expressions régulières Opérations sur les langages : Les langages réguliers peuvent se décrire facilement à l'aide d'expressions régulières. Chaque expression régulière (ER) r dénote un langage L(r). Les expressions régulières sont construites récursivement comme suit : Supposons que r et s soient des expressions régulières dénotant respectivement les langages L(r) et L(s). On peut construire d'autres expressions régulières de trois manières : 1. (r) | (s) est une ER dénotant le langage L(r) union L(s) 2. (r) (s) est une ER dénotant le langage L(r) L(s) 3. (r)* est une ER dénotant le langage (L(r))* Etant donné un langage, comment décrire tous les mots acceptables ? Comment décrire un langage ? Il existe plusieurs types de langage, certains étant plus facile à décrire que d'autres. On s'intéresse ici aux langages réguliers (type 3 de Chomsky). 7 Remarques : pour économiser des parenthèses, les priorités sont : priorité( * ) > priorité(concaténation) > priorité( | ). Par exemple ab*|c = ((a)((b)*))|(c) La concaténation est distributive par rapport à | . Par exemple r(s|t) = rs|rt 8 Expressions régulières Expressions régulières Exercices : On ajoute à la notation : • [abc] pour (a|b|c) • [a1-a2] pour {c ∈ Σ, a1 ≤ c ∧ c ≤ a2} • [^…] pour le complément • r? pour r|є • r+ pour rr* • . pour n'importe quel caractère • Ecrire l'expression régulière pour représenter l'ensemble de tous les mots formés de a et de b, ou le mot vide. C'est à dire { є, a, b, ab, ba, aab, bbab, …} • Ecrire l'expression régulière correspondant à : soit le mot a, soit les mots formés de 0 ou plusieurs b suivi d'un c. C'est à dire {a, c, bc, bbc, bbbc, bbbbc, . . } • Ecrire une ou deux expressions équivalentes à ((є | a)b*) * Exercices, écrire les expressions régulières pour représenter : • Une suite de chiffres, exemples : {0, 15, 128, . . } • Entiers hexadécimaux, exemples : {0xF14, 0x05, 0xff, . . } • Nombres entiers, exemples : {58,+5, -99, . . } • Nombres réels, exemples : {58, 3.14, 5E22, -3.14e-10, . . } • Ecrire l'expression régulière pour représenter l'ensemble des mots sur {a,b} ayant le facteur abb • Ecrire l'expression régulière pour représenter l'ensemble des mots sur {a,b} ayant exactement 3 a 9 10 Définitions régulières Définitions régulières Pour simplifier les notations, on peut donner des noms à certaines expressions régulières. Exemple : lettre chiffre id Une définition régulière est une séquence de définitions de la forme : d1 r1 d2 r2 ... dn rn Dans la quelle : 1. Chaque dj est un nouveau symbole qui n'est ni Σ, ni identique à autre dk; 2. Chaque ri est une expression régulière sur l'alphabet Σ {d1,d2,…,di-1} 11 [A-Za-z_] [0-9] {lettre} ({lettre} | {chiffre})* Autre exemple : chiffre chiffres nbr [0-9] {chiffre}+ {chiffres} ("."{chiffres})?(E[+-]? {chiffres})? Attention méta-symbole !! 12 Compilation Reconnaissance des unités lexicales Programme "analyseur lexical" reconnaît les unités lexicales. Le principal client de l'analyseur lexical est l'analyseur syntaxique. L'interface entre ces deux analyseurs est "uniteSuivante()", qui renvoie à chaque appel l'unité lexicale suivante. Reconnaissance des unités lexicales L'analyseur lexical envoie vers l'analyseur syntaxique : • la dernière unité lexicale reconnue, • le (ou les) attribut(s) de l'unité lexicale, • le lexème correspondant. Intérêt des attributs. Par exemple pour : <=, >=, <, >, =, <> . l'analyseur syntaxique a juste besoin de savoir que cela correspond à l'unité lexicale OPREL. C'est lors de la génération de code que l'on aura besoin de distinguer entre < et >= par exemple. 13 14 Reconnaissance des unités lexicales Reconnaissance des unités lexicales Exemple : grammaire de l'instruction if (de Pascal) En plus l'analyseur lexical et l'analyseur syntaxique partagent certaines données : les définitions des constantes définissant les unités lexicales, la table de symboles, etc. instr Quelques considérations supplémentaires : • L'analyseur lexical est "glouton" : prendre le lexème est le plus long possible. • Seul l'analyseur lexical accède au texte source. • L'analyseur lexical acquiert le texte source un caractère à la fois. • L'analyseur syntaxique n'acquiert ses données qu'à travers de l'analyseur lexicale (uniteSuivante()) exp_bool terme | | | | if exp_bool then instr if exp_bool then instr else instr terme oprel terme terme id nbre Partie syntaxique Exemple de fragment de source : if vitesse > 65 then 15 16 Reconnaissance des unités lexicales Reconnaissance des unités lexicales Les terminaux de la grammaire (unités lexicales) sont : if, then, else, oprel, id et nbre. L'analyseur lexicale doit reconnaître les mots clés if, then et else, ainsi que les lexèmes de oprel, id et nbre. Les motifs de ces unités lexicales (décrits par les définitions régulières) sont : chiffre chiffres nbr lettre id if then else oprel 17 De plus, l'analyseur lexical doit ignorer les blancs, en utilisant la définition régulière : [0-9] Partie lexicale {chiffre}+ {chiffres} ("."{chiffres})?(E[+-]? {chiffres})? [A-Za-z] {lettre} ({lettre} |{chiffre})* if then else < | > | <= | >= | = | <> blanc (" " | \t | \n) + 18 Analyseurs Reconnaissance des unités lexicales Récapitulatif des lexèmes, unités lexicales et attributs à reconnaître : LEXEME UNITE LEXICALE VALEUR ATTRIBUT Un blanc --- --- if if --- then then --- else else --- id id Pointeur vers table nbre nbre Valeur du nombre < oprel INF <= oprel INFEGAL = oprel EGAL <> oprel NONEGAL > oprel SUP >= oprel SUPEGAL 19 Ecriture d'un analyseur lexicale. Deux alternatives : 1. Analyse lexicale prédictive. Analyseur fait "à la main". On peut éventuellement utiliser des diagrammes de transition. 2. Utiliser un générateur automatique d'analyseur lexicaux tel que JFlex. Alternative la plus fréquente. 20 Diagrammes de transition Diagrammes de transition Exemple : diagramme de transition qui reconnaît les lexèmes de l'unité lexicale oprel : Une expression régulière peut se convertir (à la main ou automatiquement) en un diagramme de transition. Diagramme de transition : lors de la reconnaissance d'un lexème, l'analyseur lexical passe par divers états. De chaque état e sont issues une ou plusieurs flèches étiquetées par des caractères. Une flèche étiquetée par c relie e à l'état e1 dans lequel l'analyseur passera si le caractère c est lu dans le texte source. c e e1 Un état particulier représente l'état initial de l'analyseur. Les états finaux (doubles cercles), correspondant à la reconnaissance d'une unité lexicale. 21 22 Analyseurs lexicaux "en dur" Analyseurs lexicaux programmés "en dur" Les diagrammes de transition sont une aide pour l'écriture d'analyseurs lexicaux. Par exemple, à partir du diagramme précédent on peut obtenir un analyseur lexical de l'unité lexicale oprel. Analyseurs lexicaux "en dur" Code d'un fragment de l'analyseur lexicale (unité lexicale oprel) : public Yytoken uniteSuivante() { /* le char c est "global" c = carac_suivant */ while (estBlanc(c)) /* etat = 0 */ c = lireCar(); if (c == '<') { /* etat = 1 */ c = lireCar(); /* on avance la lecture */ if (c == '='){ /* etat = 2 */ c = lireCar(); return(new Yytoken(Common.OPREL,yytext()); } else if (c == '>') { /* etat = 3 */ c = lireCar(); return (new Yytoken(Common.OPREL,yytext()); } else /* etat = 4 */ return (new Yytoken(Common.OPREL,yytext()); else if (c == '=') { /* etat = 5 */ c = lireCar(); return (new Yytoken(Common.OPREL,yytext()); } /* suite page suivante */ Une manière de traiter le caractère lu en trop consiste en avoir une variable contenant toujours le caractère prochain du texte source. Exemple de fragment des fonctions auxiliaires : public boolean estBlanc(char c) { return c == ' ' || c == '\t' || c == '\n'; } public boolean estLettre(char c) { return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z'; } public boolean estChiffre(char c) { return '0' <= c && c <= '9'; } 23 L'étoile signifie que la reconnaissance requiert la lecture d'un caractère au-delà de la fin du lexème 24 Analyseurs lexicaux "en dur" Mots clés else if (c == '>') { /* etat = 6 */ c = lireCar(); if (c == '=') { /* etat = 7 */ c = lireCar(); return (new Yytoken(Common.OPREL,yytext()); } else /* etat = 8 */ return (new Yytoken(Common.OPREL,yytext()); } /* else autres lexemes ou erreur */ } Reconnaissance des mots clés. Les mots clés appartiennent au langage défini par l'expression régulière lettre(lettre|chiffre)*, tout comme les identificateurs. Leur reconnaissance peut se faire de deux manières : 1. Soit on incorpore les mots clés au diagrammes de transition. Exemple diagramme de transition pour le mot clé then : début 25 t h e n ni lettre ni chiffre * 26 Mots clés Compilation Méthode plus utilisé 2. Soit on traite de la même manière les mots clés et les identificateurs. Dans ce cas on initialise la table de symboles avec les mots clés. Ainsi le diagramme de transition pour les id et les mots clés est : Génération automatique (lex) lettre ou chiffre début lettre autre * return(consultTableSymb()) Consulte la table de symboles et retourne l'unité lexicale si le lexème n'est pas dans la table, alors il le place 27 28 Constructeur d'analyseurs lexicaux Lex Constructeur d'analyseurs lexicaux Lex Le programme Lex permet de générer des analyseurs lexicaux. Différentes versions existent selon le langage de programmation utilisé : Lex, Flex, Ocamllex, JFlex, etc. Un programme en JFlex se divise en trois parties séparées par %% 1. La première contient des définitions et initialisations utiles pour le programme généré ; ce qui est écrit là sera inséré tel quel dans ce programme généré. 2. La seconde contient des options, des définitions régulières et des déclarations. 3. La troisième contient les règles et actions à appliquer lors de la lecture du texte à analyser. JFlex prend en entrée un ensemble d'expressions et produit le source d'un programme Java qui est l'analyseur lexical correspondant aux expressions régulières. Forme d'un programme en JFlex : définitions initialisations et classes en Java %% options, définitions régulières et déclarations %% règles {actions} Par exemple "monAnalyseur.flex" 29 30 Constructeur d'analyseurs lexicaux Lex Constructeur d'analyseurs lexicaux Lex Classe produite par Jflex : Un premier exemple Compter le nombre de "a" et "e" dans un texte. monAnalyseur1.flex monAnalyseur1.java (fragment) Nom de la classe générée. package testJFlex; Par défaut : Yylex %% %class MonAnalyseur1 %standalone %{ code inséré directement dans la classe %} %{ // On désire compter le nb de "a" et "e" // s'insere dans la classe MonAnalyseur1 public static int nbA = 0, nbE = 0; %} %% a {nbA++; System.out.print(yytext()+"("+nbA+")");} e {nbE++; System.out.print(yytext()+"("+nbE+")");} Notez la partie : règle {action} règle est une exp. régulière, le motif. action est exécutée si le motif est reconnu String yytex() représente le lexème reconnu 31 32 package testJFlex; /** * This class is a scanner generated by JFlex 1.4.3 * .... */ class MonAnalyseur1 { . . . // On désire compter le nb de "a" et "e" // s'insere dans la classe MonAnalyseur1 public static int nbA = 0, nbE = 0; .... public static void main(String argv[]) { . . . MonAnalyseur1 scanner = null; try { scanner=new MonAnalyseur1(new java.io.FileReader(argv[i])); while ( !scanner.zzAtEOF ) scanner.yylex(); } catch (java.io.FileNotFoundException e) { .... } .... } } Constructeur d'analyseurs lexicaux Lex Constructeur d'analyseurs lexicaux Lex Le fonctionnement du filtrage. La troisième partie de JFlex contient des lignes sous la forme Ri {Ai}. Où Ri est une expression régulière et Ai une action : Source à analyser : monAnalyseur1.txt le test est important. Aussi la réussite, le courage, le bon sens. R1 R2 ... Rn Console d'exécution : {A1} {A2} {An} L'analyseur considère le texte à analyser comme une chaîne f, il cherche le facteur gauche le plus long qui appartient à un Ri. • Si f = gh et g Ri, alors il effectue l'action Ai et recommence le procédé avec h. • Les caractères non "filtrés" sont affichés tels quels. 33 34 Si le facteur gauche g appartient à Ri et à Rj c'est l'action correspondante au plus petit des deux indices i, j qui est effectuée. • Si aucun facteur gauche non vide appartient à un Ri alors une lettre de f est lue (consommée) puis recopiée en sortie. Le processus est répété jusqu'à la fin du texte. Constructeur d'analyseurs lexicaux Lex Constructeur d'analyseurs lexicaux Lex Expressions régulières en Lex : 35 x Filtre le caractère 'x' \x Filtre le caractère 'x' littéralement . [xyz] Expressions régulières en Lex : {DEF} où "DEF" est une définition régulière définie dans la première partie Tout caractère (byte) sauf newline (R) R. Les parenthèses déterminent une priorité. "ensemble de caractères"; ici, l'expression filtre un 'x', ou un 'y', ou un 'z' RS R suivie par l'expression S. Concaténation R|S R ou S. Union" [abj-oZ] "ensemble de caractères" contenant un intervalle ; filtre 'a', 'b', ou n'importe quelle lettre entre de 'j' et 'o', ou 'Z' R/S R seulement si suivie par S. S n'est pas "consommé" par l'analyseur. [^A-Z] Complément d'un "ensemble de caractères", i.e., un caractère qui n'est pas dans l'ensemble A-Z. Tout caractère SAUF une majuscule. ^R R au début d'une ligne. ~R [^A-Z\n] (tout caractère sauf une majuscule) ou (un newline) filtre tout jusqu'à trouver R. Le lexeme termine par R. Exemple : ~(ab) filtre cdefaxab R* Zéro une ou plusieurs R, où R est une expression rationnelle "s" chaîne de caractères s. R+ Une ou plusieurs R <<EOF>> fin du fichier R? Zéro ou une R R{m,n} E répété entre m et n fois 36 Exercice Exercice: :compter compter lelenb nbde dechaînes chaînes ("xx") ("xx") Constructeur d'analyseurs lexicaux Lex Constructeur d'analyseurs lexicaux Lex Console d'exécution 2ème exemple Compter le nombre de minuscules, majuscules, blancs et autres. 3ème exemple Analyser le fragment Pascal suivant : monAnalyseur2.flex if vitesse > 65 then Et pour compter le nb de mots package testJFlex; commençant par majuscule ? %% %class MonAnalyseur2 %standalone %{ static int minis = 0,majus = 0,autres =0,blancs =0; %} A exécuter à la fin %eof{ System.out.println("Minuscules : "+minis+" Majuscules : " +majus+" Autre : "+autres+" Blancs : "+blancs); %eof} Notez les motifs %% [a-z] {minis++;} [A-Z] {majus++;} [\ ] {blancs++;} Console d'exécution pour le \n {} source monAnalyseur1.txt . {autres++;} 37 Définitions régulières impliquées dans l'analyse : sep blanc lettre chiffre id nbre [ \t\n] {sep}+ [A-Za-z] [0-9] {lettre} ({lettre}|{chiffre})* {chiffre}+ (\.{chiffre}+)?(E[+-]? {chiffre}+)? 38 Constructeur d'analyseurs lexicaux Lex monAnalyseur3.flex Constructeur d'analyseurs lexicaux Lex Pour lancer JFlex avec Eclipse : package testJFlex; %% %class MonAnalyseur3 %standalone /* définitions régulières */ sep = [ \t\n] blanc = {sep}+ lettre = [a-zA-Z] chiffre = [0-9] id = {lettre}({lettre}|{chiffre})* nbre = {chiffre}+(\.{chiffre}+)?(E[+\-]?{chiffre}+)? %% {blanc} { /*pas d'action*/ } if | then | else {System.out.println("MR"+yytext());} {id} {System.out.println("ID"+yytext());} {nbre} {System.out.println("NBRE"+yytext());} "<" | "<=" | ">" | "<>" | "=" {System.out.println("OPREL"+yytext());} 39 A la place de println, l'utilisation avec un analyseur syntaxique requiert : return(<unité lexicale>) 1. Télécharger jflex. Adresse : http://www.jflex.de/ 2. Dans les propriétés de votre "projet" java d'Eclipse, faire "AddExternalJAR" pour pointer vers "JFlex.jar" 3. Pour lancer jflex, exécuter la classe "Main" de "JFlex.jar". Vous obtiendrez une fenêtre comme ceci : 40 Constructeur d'analyseurs lexicaux Lex Constructeur d'analyseurs lexicaux Lex Exercice : Évaluation d’expressions postfixées. Si la génération de l'analyseur lexicale a réussi, alors vous pouvez lancer la classe générée (si elle comporte le main). Écrire un programme JFlex permettant d’évaluer des expressions postfixées. Vous devez spécifier le nom du fichier source dans l'argument du main. Par exemple : No ligne : %line, yyline Exemple. Source : 7 20 * 13 1 - 7 * + 9 2 / 10 + 9 + 2 9 1 1 1 + 3 a 5 + // commentaire reste 55 /* commentaire */ /* plus */ 14 /* compliqué */ 41 42 Faire une deuxième version acceptant des nombres réels. Ex : 3.14 5 + 1.3e-10 4.5 * 6 - Ajouter les commentaires plus tard Troisième version, continuer le traitement en cas d'erreur. Ex : "3 a 5 +" signale erreur mais retourne 8 Compilation Automates finis Comment JFlex fonctionne ? au moyen des automates finis (implémentation des diagrammes de transition). Les automates finis sont des "reconnaisseurs" ; il disent oui ou non à propos d'une chaîne d'entrée. Automates finis Les automates finis (déterministes et non déterministes) reconnaissent les langages réguliers décrits par des expressions régulières. Le schéma traditionnel d'un analyseur : 1. Chaque expression régulière est compilée en un automate, 2. L'ensemble des automates est fusionné en un seul automate déterministe. 43 44 Automates finis Automates finis Un automate peut être représenté graphiquement par un diagramme de transition. Automate fini non déterministe (AFN) ou déterministe (AFD) : • Ensemble fini d’états E ; • Alphabet d’entrée fini ; • Fonction de transition ; • État initial q0 ; • Ensemble d’états terminaux F ; Remarquez que dans un AFN : 1. Le même symbole peut étiqueter des arcs sortant d'un état ; 2. Un arc peut être étiqueté par , la chaîne vide. Exemple de langage : (a|b)*abb AFD : : E E • au plus une transition par couple état-lettre ; AFN : AFN : : E sous-ensemble de E ; • plusieurs transitions possibles par couple ; • possibilités de transitions vides ou -transitions. début Table de transition : 46 Automates finis pour : N() pour a : N(a) b a 0 1 b Table de transition : 47 a b b 2 b b 2 3 Exemple de mot accepté par l'automate : ababb chemin : 0,0,1,2,3 Etat \ Symbole a b 0 {0,1} {0} 1 {2} 2 {3} Exercice : écrire la grammaire équivalente Construction d’un AFN à partir d’une expression régulière (construction de Thompson) Exemple même langage : (a|b)*abb début 1 Automates finis Remarquez que dans un AFD : 1. Il n'y a aucun arc étiqueté par ; 2. Pour chaque état s et chaque symbole d'entrée a, il y a un seul arc sortant de s étiqueté a. AFD : a 0 b On dit qu'un automate fini accepte une chaîne s = c1 c2 … ck ss’il existe un chemin entre l'état initail et un état terminal, composé de k flèches étiquetées c1, c2, …, ck. 45 Représentation graphique sous la forme d'un diagramme de transition a pour r | s : N(r | s ) 3 a a pour rs : N(rs) Etat \ Symbole a b 0 1 0 1 1 2 2 1 3 3 1 0 pour r* : N(r*) 48 Automates finis Automates finis On réalise d’abord une analyse syntaxique de l’expression régulière, puis construction de Thompson. Utilisation d’un AFN pour reconnaître un mot. Il faut construire tous les chemins correspondants dans l’AFN. Exemple : ababb Exemple : r = (a|b)*abb b b trans(T,a) transitions partant de l'ensemble d'états T sur l'entrée a -fermeture(T) transitions partant de l'ensemble d'états T par des transitions États numérotés suivant l’ordre de construction de l’automate. 49 50 Automates finis Automates et Lex Conversion d'un AFN en AFD. Méthode : construire toutes les transitions possibles, complétées par leur -fermeture. Exemple : (a|b)*abb Démarche d'analyse de Lex : • Convertir en AFN chacune des expressions régulières à l'aide de l'algorithme de Thompson. • Fusionner les AFN en un seul AFN. On utilise les transitions. • Convertir le AFN en AFD. b AFN AFD 51 52