Puisque vous voulez savoir comment fonctionnent les lexers, je suppose que vous voulez réellement savoir comment fonctionnent les générateurs de lexers.
Un générateur de lexer prend une spécification lexicale, qui est une liste de règles (paires d'expression régulière-jeton), et génère un lexer. Ce lexer résultant peut ensuite transformer une chaîne d'entrée (caractère) en une chaîne de jeton selon cette liste de règles.
La méthode la plus couramment utilisée consiste principalement à transformer une expression régulière en automates finis déterministes (DFA) via un automate non déterministe (NFA), plus quelques détails.
Un guide détaillé de cette transformation peut être trouvé ici . Notez que je ne l'ai pas lu moi-même, mais il semble assez bon. De plus, à peu près n'importe quel livre sur la construction d'un compilateur présentera cette transformation dans les premiers chapitres.
Si vous êtes intéressé par des diapositives de cours sur le sujet, il y en a sans aucun doute une quantité infinie parmi les cours sur la construction de compilateurs. De mon université, vous pouvez trouver de telles diapositives ici et ici .
Il y a peu d'autres choses qui ne sont pas couramment utilisées dans les lexers ou traitées dans les textes, mais qui sont néanmoins très utiles:
Premièrement, la gestion d'Unicode est quelque peu banale. Le problème est que l'entrée ASCII ne fait que 8 bits de large, ce qui signifie que vous pouvez facilement avoir une table de transition pour chaque état dans le DFA, car ils n'ont que 256 entrées. Cependant, Unicode, ayant une largeur de 16 bits (si vous utilisez UTF-16), nécessite 64k tables pour chaque entrée dans le DFA. Si vous avez des grammaires complexes, cela peut commencer à prendre beaucoup de place. Remplir ces tableaux commence également à prendre un peu de temps.
Alternativement, vous pouvez générer des arbres d'intervalles. Un arbre de plage peut contenir les tuples ('a', 'z'), ('A', 'Z') par exemple, ce qui est beaucoup plus efficace en mémoire que d'avoir la table complète. Si vous conservez des intervalles sans chevauchement, vous pouvez utiliser n'importe quel arbre binaire équilibré à cet effet. Le temps d'exécution est linéaire dans le nombre de bits dont vous avez besoin pour chaque caractère, donc O (16) dans le cas Unicode. Cependant, dans le meilleur des cas, ce sera généralement un peu moins.
Un autre problème est que les lexers générés couramment ont en fait une performance quadratique dans le pire des cas. Bien que ce comportement du pire des cas ne soit pas communément observé, il pourrait vous mordre. Si vous rencontrez le problème et souhaitez le résoudre, un article décrivant comment obtenir un temps linéaire peut être trouvé ici .
Vous voudrez probablement pouvoir décrire les expressions régulières sous forme de chaînes, telles qu'elles apparaissent normalement. Cependant, l'analyse de ces descriptions d'expressions régulières dans des NFA (ou éventuellement une structure intermédiaire récursive en premier) est un peu un problème d'oeuf de poule. Pour analyser les descriptions d'expressions régulières, l'algorithme Shunting Yard est très approprié. Wikipédia semble avoir une longue page sur l'algorithme .