Accueil🇫🇷Chercher

Analyse lexicale

En informatique, l’analyse lexicale, lexing, segmentation ou tokenization est la conversion d’une chaîne de caractères (un texte) en une liste de symboles (tokens en anglais). Elle fait partie de la première phase de la chaîne de compilation. Ces symboles sont ensuite consommés lors de l'analyse syntaxique. Un programme réalisant une analyse lexicale est appelé un analyseur lexical, tokenizer[1] ou lexer. Un analyseur lexical est généralement combiné à un analyseur syntaxique pour analyser la syntaxe d'un texte.

Lexème

« Un lexème est une suite de caractères dans un programme source qui correspond au motif d'un symbole lexical et qui est analysé par l'analyseur lexical comme une instance de ce symbole lexical »[2].

Certains auteurs utilisent le terme « token » pour représenter à la fois la chaîne de caractère en cours de traitement par l'analyseur lexical et la structure de donnée produite en sortie de ce traitement[3].

Le terme « lexème » en informatique n'a pas le même sens que lexème en linguistique. En informatique sa définition se rapproche de celle de morphème en linguistique.

Unité lexicale

Une unité lexicale ou token lexical ou plus simplement token est un couple composé d'un nom et d'une valeur optionnelle. Le nom de l’unité lexicale est une catégorie d'unité lexicale[2].

Quelques exemples de noms d'unités lexicales qu'on rencontre le plus souvent :

  • identifiers (identifiants) : nom de variables ou de fonctions choisis par le programmeur du code analysĂ©. Ne peut ĂŞtre un mot rĂ©servĂ© du langage ;
  • keywords (mots-clefs) : mots rĂ©servĂ©s du langage ;
  • separators (ponctuation) : caractères de ponctuation simples comme les points et les virgules ou composĂ©s comme les parenthèses et les crochets ;
  • operators (opĂ©rateurs) : symboles s'appliquant sur des arguments et produisant un rĂ©sultat ;
  • literals (littĂ©raux) : suite de caractères reprĂ©sentant une valeur numĂ©rique, logique, etc. ;
Exemples d'unités lexicales
Nom Valeurs
identifiants x, color, UP​
mots-clefs if, while, return​
ponctuation }, (, ;
opérateurs +, <, =​
littéraux true, 6.02e23, "music"​

Un nom d'unité lexicale peut être comparé au concept de nature grammaticale.

Grammaire lexicale

Les langages de programmation définissent souvent des règles, sous la forme d'une grammaire, définissant la syntaxe à adopter. Ces règles prennent souvent la forme d'expressions rationnelles et définissent les séquences de caractères utilisées pour former des lexèmes.

Les langages reconnus par une expression rationnelle sont appelés les langages rationnels. À toute expression rationnelle, on peut associer un automate fini. Il existe également des langages non rationnels.

Deux des plus importantes catégories d'unités lexicales sont les caractères d'espacement et les commentaires. Elles sont toutes deux définies dans la grammaire de la plupart des langages et traités par les lexers, mais sont le plus souvent considérées comme non-signifiantes ; dans le meilleur des cas séparant deux tokens et n'en générant pas par elles-mêmes. Il y a deux exceptions majeures à cela. Tout d'abord les langages dits à indentation comme syntaxe comme le Python qui délimitent les blocs de code par l'indentation et pour qui les caractères d'espacement sont signifiants et peuvent donc générer des tokens. Ensuite dans certaines utilisations des lexers, notamment certains outils de débogage ou d'impression élégante (pretty-printers en anglais) il peut être nécessaire de conserver le code source original pour pouvoir l'afficher plus tard à l'utilisateur.

Analyseur lexical

On appelle analyseur lexical (lexer en anglais) tout programme effectuant une analyse lexicale.

L'analyseur lexical sert Ă  :

  • Ă©liminer les « bruits Â» du texte source : commentaires, espaces ;
  • reconnaĂ®tre les opĂ©rateurs et les mots-clĂ©s : +, >, if, return, … ;
  • reconnaĂ®tre les identificateurs, les chaĂ®nes de caractères littĂ©rales et les nombres littĂ©raux.

Lorsque l'analyseur lexical dĂ©tecte un lexème invalide, il rapporte une erreur. En revanche, les combinaisons de lexèmes sont gĂ©nĂ©ralement laissĂ©es Ă  l'analyseur syntaxique : par exemple, un analyseur lexical typique reconnaĂ®t les parenthèses comme Ă©tant des lexèmes mais ne vĂ©rifie pas qu'une parenthèse ouvrante « ( Â» est forcĂ©ment associĂ©e Ă  une parenthèse fermante « ) Â».

Un analyseur lexical peut ĂŞtre Ă©crit :

  • « Ă  la main Â» : il faut construire l'automate fini non dĂ©terministe Ă  partir d'une expression rationnelle E, puis l'exĂ©cuter pour dĂ©terminer si une chaĂ®ne d'entrĂ©e appartient au langage reconnu par E ;
  • par une table dĂ©crivant l'automate et un programme exploitant cette table ;
  • par un gĂ©nĂ©rateur d'analyseurs lexicaux : Lex, Flex., ANTLR, etc.

Bien que les lexers soient principalement utilisés pour écrire des compilateurs, ils entrent dans la conception d'autres outils de traitement des langages informatiques comme les lint ou les systèmes d'impression élégante (troff).

Processus

L'analyse lexicale constitue la première phase des processus de compilation modernes. L'analyse est généralement réalisée en parcourant le texte source une seule fois.

Elle consiste en la démarcation, et si possible caractérisation de segments de la chaîne de caractères d'entrée en une suite de lexèmes qui seront passés à une autre forme d'analyse.

Par exemple, l'instruction sum = 2 + 3; en langage C produit après analyse lexicale la liste de symboles suivante :

Valeur Catégorie lexicale
sum identifiant
= opérateur d'affectation
2 entier littéral
+ opérateur d'addition
3 entier littéral
; fin de déclaration

La suite de caractères d'entrée, implicitement segmentée par des espaces comme dans les langages naturels a été transformée en une liste de six unités lexicales :

[(identifiant, sum), (opérateur d'affectation, =), (entier littéral, 2), (operator, +), (entier littéral, 3), (fin de déclaration, ;)]

Généralement, quand une unité lexicale représente plusieurs lexèmes, l’analyseur lexical sauvegarde suffisamment d'informations pour pouvoir reproduire le lexème original et l'utiliser lors de l'analyse sémantique. L’analyseur syntaxique récupère ces informations et les stocke sous la forme d'un arbre de syntaxe abstraite (AST). Cela est nécessaire pour éviter la perte d'information dans le cas des nombres et des identifiants.

Les lexèmes sont identifiés en fonction de règles établies par l’analyseur lexical. Parmi les méthodes utilisées pour identifier les lexèmes on trouve : les expressions régulières, une suite spécifique de caractères que l'on nomme drapeau, des caractères appelés séparateurs et un dictionnaire.

L'analyseur lexical ne traite en général pas les combinaisons d’unités lexicales, cette tâche étant laissée à l’analyseur syntaxique. Par exemple, un analyseur lexical typique peut reconnaître et traiter les parenthèses mais est incapable de les compter et donc de vérifier si chaque parenthèse fermante « ) » correspond à une parenthèse ouvrante « ( » précédente.

L'analyse lexicale d'une chaîne de caractères se déroule en trois étapes :

  1. Le balayage qui segmente la chaîne de caractères d'entrée en lexèmes [4] et leur associe une catégorie lexicale (entier littéral, opérateur d'addition, identifiant…) ;
  2. L'évaluation qui convertit chaque lexème en une valeur.

Balayage

La première Ă©tape, le balayage, est gĂ©nĂ©ralement rĂ©alisĂ©e par un automate fini. Cet automate possède les informations nĂ©cessaires pour traiter toutes les sĂ©quences de caractères pouvant ĂŞtre contenues dans un lexème (chaque caractère de ces sĂ©quences Ă©tant un lexème). Par exemple un int peut contenir toutes les suites possibles de chiffres. Dans de nombreux cas le premier caractère signifiant lu peut ĂŞtre utilisĂ© pour dĂ©duire la nature du lexème courant et chaque caractère suivant sera ajoutĂ© Ă  la valeur du token jusqu'Ă  la lecture d'un caractère non acceptable. Dans certains langages cependant, la règle peut ĂŞtre plus complexe et nĂ©cessiter du retour sur trace sur des caractères dĂ©jĂ  lus. Par exemple en C, un caractère « L Â» n'est pas suffisant pour diffĂ©rencier un identifiant commençant par « L » d'un littĂ©ral composĂ© de ce seul caractère.

Évaluation

Un lexème n'est qu'une suite de caractères caractérisés par un type. Pour construire une unité lexicale l'analyseur lexical nécessite une seconde étape, l'évaluation, qui produit une valeur. Le type du lexème combiné avec sa valeur est ce qui constitue un token, qui peut ensuite être livré à un analyseur syntaxique. Certains lexèmes, comme ceux représentant la ponctuation n'ont pas réellement de valeur, leur fonction d'évaluation peut donc rendre une valeur nulle ; seul leur type est nécessaire. De la même manière, l'évaluation peut supprimer complètement un lexème, le dissimulant à l’analyseur syntaxique comme cela peut être le cas pour les caractères d'espacement ou les commentaires. L'évaluation des identifiants est souvent simple, en passant directement la chaîne de caractères les constituant en valeur, mais pour les valeurs numériques l’analyseur lexical peut faire le choix de les transformer en unité lexicale sous forme de chaînes de caractères (différant leur traitement à l'analyse sémantique) ou de les évaluer lui-même.

Même s'il peut être possible, voire nécessaire en cas de petit nombre de lexèmes, d'écrire un analyseur lexical « à la main », ceux-ci sont souvent générés par des outils automatiques. Ces outils acceptent généralement les expressions régulières décrivant les lexèmes autorisés. Chaque expression régulière est associée à une règle de production de la grammaire formelle du langage évalué. Ces outils peuvent générer du code source qui peut être compilé et exécuté ou construire une table de transition d'états pour un automate-fini.

Une expression régulière représente une version compacte du motif que les lexèmes doivent suivre pour constituer une unité lexicale valide. Par exemple, pour un langage basé sur la langue française, un identifiant peut être n'importe quel caractère alphabétique ou un tiret bas suivi par n'importe quelle suite de chiffres ou de caractères ASCII alphanumériques et/ou de tirets bas. Cette suite peut être représentée par la chaîne de caractères suivante [a-zA-Z_][a-zA-Z_0-9]*.

Les expressions régulières et les automates finis qu'elles génèrent ne sont pas assez puissants pour traiter des motifs récursifs comme ce qu'on trouve dans les langages de Dyck[5]. Ces traitements sont laissés à l’analyseur syntaxique.

Dans des langages anciens comme l'ALGOL, la première étape de la compilation était appelée la reconstruction de ligne qui consistait à parcourir le texte pour y identifier des mots-clefs et supprimer les caractères d'espacement et les commentaires. Les analyses lexicales et syntaxiques étaient réalisées par un seul programme parser-lexer[6].

Limites

Typiquement, l'analyse lexicale agit au niveau des mots. Il peut cependant parfois ĂŞtre difficile de diffĂ©rencier ce qu'est un « mot Â». Souvent les analyseurs lexicaux se basent sur des heuristiques simples, par exemple :

  • caractères d'espacement et de ponctuation peuvent ou pas faire partie de la liste de lexèmes valides ;
  • toutes les suites continues de caractères alphanumĂ©riques peuvent ĂŞtre interprĂ©tĂ©es comme un seul et mĂŞme lexème ;
  • les lexèmes sont sĂ©parĂ©s par des caractères d'espacement ou de ponctuation ;

Dans des langages qui utilisent des espaces inter-mots (comme la plupart des langages de programmation et des langages naturels utilisant l'alphabet latin) cette approche est facile à mettre en œuvre. Malgré cela, il existe de nombreux cas (contractions, émoticônes, mots composés, URI, etc.) qui nécessitent des heuristiques plus complexes pour être traités par l'analyseur lexical. Un exemple classique est la suite de caractères « New York-based » qui en anglais forme un seul mot mais qui naïvement peut être séparé en deux voire trois lexèmes.

L'analyse lexicale peut être particulièrement difficile en ce qui concerne les langues naturelles écrites en scriptio continua pour lesquelles il n'existe aucun symbole de ponctuation ou de séparation des lexèmes comme en grec ancien ou en chinois.

Indentation comme syntaxe

Certains langages, comme Python, utilise l'indentation pour dĂ©limiter les blocs de code. L’analyseur lexical doit donc gĂ©nĂ©rer une unitĂ© lexicale INDENT Ă  l'augmentation de l'indentation et une unitĂ© lexicale DEDENT Ă  sa rĂ©duction[7]. Ces unitĂ©s lexicales correspondent Ă  ceux gĂ©nĂ©rĂ©s Ă  la lecture de crochets ouvrant « { Â» et fermant « } » dans des langages comme le C. Pour pouvoir ĂŞtre pris en compte par l’analyseur syntaxique, celui-ci doit pouvoir conserver l'Ă©tat courant de l'indentation (puisque les blocs peuvent ĂŞtre imbriquĂ©s les uns dans les autres) ce qui rend donc la grammaire du langage analysĂ© contextuelle. INDENT-DEDENT dĂ©pendent du contexte (en l'occurrence du niveau d'indentation prĂ©cĂ©dent).

Générateur d'analyseur lexical

Les analyseurs lexicaux sont souvent générés par des outils appelés générateurs d'analyseurs lexicaux. Un des plus communément utilisé est Lex, conjointement avec le générateur d'analyseur syntaxique Yacc et leurs équivalents libres Flex et Bison. Ces générateurs sont une forme de langage dédié, prenant en entrée une spécification lexicale (généralement des expressions régulières et quelques balises) et produisent en sortie un lexer.

Ces outils permettent le développement rapide d'un analyseur lexical fonctionnel.

Liste de générateurs d'analyseurs lexicaux

  • JavaCC :  compilateur de compilateur Ă©crit en Java
  • JFLex[9] : gĂ©nĂ©rateur d'analyseur lexical pour Java Ă©crit en Java
  • AnnoFlex[10] : un autre gĂ©nĂ©rateur d'analyseur lexical pour Java Ă©crit en Java
  • RE/flex[11] : variante rapide de lex/flex pour C++
  • Quex[12] : gĂ©nĂ©rateur de lexer C and C++ Ă©crit en Python
  • FsLex[13] : gĂ©nĂ©rateur de lexer reconnaissant les caractères ASCII et Unicode pour F#
  • PLY[14] : implĂ©mentation de Lex en Python

Complexité algorithmique

La performance des lexers, en particulier dans les langages stables dans lesquels le lexer est très souvent appelé (comme C ou HTML) est une préoccupation majeure. Les lexers générés grâce à Lex/Flex sont considérés comme raisonnablement rapides mais peuvent dans certains cas être deux à trois fois plus lents que les lexers écrits « à la main » ou des outils comme re2c[15].

Algorithme naĂŻf

  1. On génère pour chaque symbole un automate qui reconnaît l'expression rationnelle associée au symbole. Cet automate sera identifié par le symbole.
  2. Tant que le mot n'a pas été entièrement analysé et qu'il n'y a pas d'erreur :
    1. On lit le mot lettre par lettre en faisant avancer les automates en parallèle pour chaque lettre.
    2. Lorsqu'un automate entre dans un état final, on conserve le sous-mot trouvé et l'identificateur de l'automate.
    3. Si tous les automates sont dans un état puits ou que le mot a été entièrement analysé :
      1. Si aucun automate n'a atteint d'Ă©tat final : on renvoie une erreur.
      2. Sinon, on ajoute le couple (plus grand sous-mot avec un automate en état final, type de l'automate l'ayant trouvé) à la liste des entités lexicales. On se replace alors juste après ce sous-mot, on remet les automates à zéro et on continue la lecture du mot.

Analyse de la complexité

Dans le cas des langages de programmation, cet algorithme s’exécute souvent en temps linéaire par rapport à la taille du mot d'entrée. Cependant, il existe des cas pathologiques où l'algorithme s'exécute en temps quadratique, tel que celui-ci : avec deux lexèmes : a et a…ab, l'entrée an nécessite que l'algorithme aille jusqu'à la fin du mot pour chaque a qu'il reconnait. La complexité est alors quadratique.

Autres algorithmes

Il existe d'autres algorithmes capables d'analyser un mot en temps linéaire[16].

Notes et références

Notes

    Références

    1. « Anatomy of a Compiler and The Tokenizer », sur www.cs.man.ac.uk (consulté le ).
    2. (en) Aho, Lam, Sethi et Ullman, Compilers Principles, Techniques, & Tools, 2nd Ed., WorldCat, page 111.
    3. (en) « Perl 5 Porters », sur perldoc.perl.org.
    4. Termes normalisés par ISO/CEI 2382-15:1999 Technologies de l'information — Vocabulaire — Partie 15 : Langages de programmation
    5. « Structure and Interpretation of Computer Programs », sur mitpress.mit.edu (consulté le ).
    6. (en) Visser, E, Scannerless Generalized-LR Parsing, University of Amsterdam, .
    7. « 2. Lexical analysis — Python 3.6.4 documentation », sur docs.python.org (consulté le ).
    8. re2c.
    9. JFLex.
    10. AnnoFlex.
    11. RE/flex.
    12. Quex.
    13. FsLex.
    14. PLY.
    15. Peter Bumbulis et Donald D. Cowan, « RE2C: A More Versatile Scanner Generator », ACM Lett. Program. Lang. Syst., vol. 2, nos 1-4,‎ , p. 70–84 (ISSN 1057-4514, DOI 10.1145/176454.176487, lire en ligne, consulté le ).
    16. (en) Thomas Reps, « “Maximal-Munch” Tokenization in Linear Time », ACM Transactions on Programming Languages and Systems, Vol. 20, No. 2,‎ (lire en ligne).

    Annexes

    Articles connexes

    Liens externes

      Cet article est issu de wikipedia. Text licence: CC BY-SA 4.0, Des conditions supplémentaires peuvent s’appliquer aux fichiers multimédias.