Content-type: text/html
Description un bref résumé de l'outil Quelques Exemples Simples Format du Fichier d'Entrée Motifs les expressions rationnelles étendues utilisées par flex Comment l'Entrée est Reconnue les règles pour déterminer ce qui a été identifié Actions comment spécifier ce qu'il faut faire quand un motif est reconnu L'Analyseur Généré détails à propos de l'analyseur produit par flex ; comment contrôler la source d'entrée Conditions de Démarrage introduction de contexte dans vos analyseurs, et gestion de « mini-analyseurs » Tampons d'Entrée Multiples comment manipuler de multiples sources d'entrée ; comment analyser à partir de chaînes de caractères plutôt qu'à partir de fichiers Règles de Fin-De-Fichier (End-Of-File, EOF) règles spéciales pour la fin de l'entrée Macros Diverses un résumé des macros disponibles dans les actions Valeurs Disponibles pour l'Utilisateur une résumé des valeurs disponibles dans les actions Interface avec Yacc connecter des analyseur lexicaux flex à des analyseurs syntaxiques yacc Options options de ligne de commandes de flex, et la directive « %option » Considérations de Performance comment rendre votre analyseur le plus rapide possible Générer des Analyseurs C++ La fonctionnalité (expérimentale) de génération de classes d'analyseurs C++ Incompatibilités avec Lex et POSIX comment flex diffère du lex de AT&T et du lex POSIX standard Diagnostics les messages d'erreur produits par flex (ou les analyseurs lexicaux qu'il génère) dont la signification pourrait ne pas être évidente Fichiers fichiers utilisés par flex Défectuosités / Bogues problèmes connus de flex Voir Aussi autre documentation, outils apparentés Auteur inclut une information de contact
Tout d'abord, voici quelques exemples simples, pour avoir une idée de la façon d'utiliser flex. L'entrée flex suivante spécifie un analyseur qui, à chaque fois qu'il rencontre la chaîne de caractères « nomUtilisateur », la remplace par le nom de connexion de l'utilisateur :
%% nomUtilisateur printf( "%s", getlogin() );Par défaut, tout le texte non reconnu par un analyseur flex est copié sur la sortie, et ainsi l'effet de bord de cet analyseur est de copier son fichier d'entrée sur sa sortie où chaque occurrence de « nomUtilisateur » est développée. Dans cette entrée, il n'y a qu'une seule règle. « nomUtilisateur » est le motif et « printf » est l' action. Le « %% » marque le début des règles.
Voici un autre exemple simple :
int nombre_lignes = 0, nombre_cars = 0; %% \n ++nombre_lignes; ++nombre_cars; . ++nombre_cars; %% main() { yylex(); printf( "# de lignes = %d, # de caractères = %d\n", nombre_lignes, nombre_cars ); }Cet analyseur compte le nombre de caractères et le nombre de lignes de son entrée (il ne produit pas d'autre sortie que le rapport final d'occurrences). La première ligne déclare deux variables globales, « nombre_lignes » et « nombre_cars », qui sont accessibles à la fois à l'intérieur de yylex() et de la routine main() déclarée après le second « %% ». Il y a deux règles, l'une qui reconnaît le retour à la ligne (« \n ») et qui incrémente à la fois le nombre de lignes et le nombre de caractères, et l'autre qui reconnaît tous les autres caractères (ce qui est indiqué par l'expression rationnelle « . »).
Un exemple un peu plus compliqué :
/* analyseur pour un langage de type Pascal */ %{ /* besoin de ceci pour l'appel à atof() plus bas */ #include <math.h> %} CHIFFRE [0-9] ID [a-z][a-z0-9]* %% {CHIFFRE}+ { printf( "Un entier : %s (%d)\n", yytext, atoi(yytext)); } {CHIFFRE}+"."{CHIFFRE}* { printf( "Un nombre flottant : %s (%g)\n", yytext, atof(yytext)); } if|then|begin|end|procedure|function { printf( "Un mot-clé : %s\n", yytext ); } {ID} printf( "Un identificateur : %s\n", yytext ); "+"|"-"|"*"|"/" printf( "Un opérateur : %s\n", yytext ); "{"[^}\n]*"}" /* manger les commentaires d'une ligne */ [ \t\n]+ /* manger les blancs */ . printf( "Caractère non reconnu : %s\n", yytext ); %% main( argc, argv ) int argc; char **argv; { ++argv, --argc; /* passer le nom du programme */ if ( argc > 0 ) yyin = fopen( argv[0], "r" ); else yyin = stdin; yylex(); }C'est l'ébauche d'un simple analyseur pour un langage comme Pascal. Il identifie différents types d'éléments lexicaux (tokens) et rapporte ce qu'il a vu.
Les détails de cet exemple seront expliqués dans les sections suivantes.
définitions %% règles %% code utilisateurLa section de définitions contient les déclarations de simples définition de noms qui servent à simplifier la spécification de l'analyseur, et les déclarations de conditions de démarrage, qui sont expliquées dans une section ultérieure.
Les définitions de noms ont la forme :
nom définitionLe « nom » est un mot commençant par une lettre ou un caractère de soulignement (« _ ») suivi de 0 à n lettres, chiffres, « _ » ou « - » (tiret). La définition est supposée débuter au premier caractère non d'espacement suivant le nom et continue jusqu'à la fin de la ligne. La définition peut être référencée plus tard en utilisant « {nom} », qui sera développé en « (définition) ». Par exemple,
CHIFFRE [0-9] ID [a-z][a-z0-9]*définit « CHIFFRE » comme étant une expression rationnelle correspondant à un chiffre unique, et « ID » comme étant une expression correspondant à une lettre suivie de zéro-ou-plusieurs lettres-ou-chiffres. Une référence ultérieure à
{CHIFFRE}+"."{CHIFFRE}*sera identique à
([0-9])+"."([0-9])*et correspond à un-ou-plusieurs chiffres suivis d'un point, et de zéro-ou-plusieurs chiffres.
La section de règles dans l'entrée de flex contient une série de règles de la forme :
motif actionoù le motif ne peut pas être indenté, et où l'action doit commencer sur la même ligne.
Voyez plus bas pour une description plus précise des motifs et des actions.
Finalement, la section du code utilisateur est simplement copiée telle quelle dans lex.yy.c. Elle est utilisée pour des routines d'accompagnement qui appellent ou sont appelées par l'analyseur. La présence de cette section est optionnelle ; si elle est manquante, le deuxième %% du fichier d'entrée peut également être omis.
Dans les sections de définitions et de règles, tout texte indenté ou compris entre %{ et %} est copié tel quel dans la sortie (sans les %{}). Les %{} ne peuvent eux-mêmes être indentés sur leur ligne.
Dans la section de règles, tout texte indenté ou entre %{} qui apparaît avant la première règle peut être utilisé pour déclarer des variables qui sont locales à la routine d'analyse lexicale, et (après les déclarations) au code qui doit être exécuté à chaque fois que l'on entre dans cette même routine. Un autre texte indenté ou entre %{} présent dans la section de règles est toujours copié sur la sortie, mais sa signification n'est pas bien définie et pourrait provoquer des erreurs à la compilation (cette caractéristique est présente pour la conformité POSIX ; voyez plus bas pour d'autres caractéristiques similaires).
Dans la section de définitions (mais pas dans la section de règles), un commentaire non indenté (c.-à-d. une ligne commençant par « /* ») est également copiée telle quelle dans la sortie jusqu'au « */ » suivant.
x correspond au caractère 'x' . n'importe quel caractère (octet) sauf le retour à la ligne [xyz] une « classe de caractères » ; dans ce cas, le motif convient pour un 'x', un 'y', ou un 'z' [abj-oZ] une « classe de caractères » contenant un intervalle ; convient pour un 'a', un 'b', n'importe quelle lettre allant de 'j' à 'o', ou un 'Z' [^A-Z] une « classe de caractères niée », c.-à-d. tout caractère sauf ceux dans la classe. Dans cet exemple, tout caractère SAUF une lettre majuscule. [^A-Z\n] tout caractère SAUF une lettre majuscule ou un retour à la ligne r* zéro ou plusieurs r, où r est une expression rationnelle quelconque r+ un ou plusieurs r r? zéro ou un r (c.-à-d. un r optionnel) r{2,5} entre deux et cinq r r{2,} deux r ou plus r{4} exactement 4 r {nom} le développement de la définition de « nom » (voir au dessus) "[xyz]\"foo" la chaîne de caractères littérale : [xyz]"foo \X si X est 'a', 'b', 'f', 'n', 'r', 't' ou 'v', alors la représentation C ANSI de \x. Sinon, un 'X' littéral (utilisé pour protéger des opérateurs comme '*') \0 un caractère NUL (de code ASCII 0) \123 le caractère de valeur octale 123 \x2a le caractère de valeur hexadécimale 2a (r) reconnaît un r ; les parenthèses sont utilisées pour surcharger la priorité (voir plus bas) rs l'expression rationnelle r suivie de l'expression rationnelle s ; appelé « concaténation » r|s un r ou un s r/s un r mais seulement s'il est suivi par un s. Le texte reconnu par s est inclus quand on détermine si cette règle est la « correspondance la plus longue », mais est ensuite renvoyé sur l'entrée avant que l'action ne soit exécutée. Ainsi, l'action ne voit que le texte auquel correspond r. Ce type de motif est appelé « contexte de queue ». (trailing context) (Il y a certaines combinaisons de r/s que flex ne peut détecter correctement ; voyez les notes consacrées aux contextes de queue dangereux dans la section Défectuosités/Bogues.) ^r un r, mais uniquement au début d'une ligne (c.-à-d. au début de l'analyse, ou juste après qu'un saut de ligne ait été détecté). r$ un r, mais seulement à la fin d'une ligne (c.-à-d. juste avant un saut de ligne). Équivalent à « r/\n ». Notez que la notion qu'a flex d'un « saut de ligne » est exactement celle dont le compilateur C utilisé pour compiler flex interprète '\n' ; en particulier, sur certains systèmes DOS, vous devez soit filtrer vous-même les \r de l'entrée vous-même, soit utiliser explicitement r/\r\n pour « r$ ». <s>r un r, mais seulement dans la condition de démarrage s (voyez en dessous pour une discussion sur les conditions de démarrage) <s1,s2,s3>r idem, mais dans une des conditions de démarrage s1, s2 ou s3 <*>r un r dans n'importe quelle condition de démarrage, même une exclusive. <<EOF>> un end-of-file (fin de fichier) <s1,s2><<EOF>> un end-of-file quand on se trouve dans la condition de démarrage s1 ou s2Notez qu'à l'intérieur d'une classe de caractères, tous les opérateurs d'expressions rationnelles perdent leur signification spéciale sauf l'échappement ('\') et les opérateurs de classes de caractères, « - », « ] » et, au début de la classe, « ^ ».
Les expressions rationnelles listées plus haut sont groupées en fonction de leur priorité, allant de la plus haute au sommet à la plus basse en bas. Celles regroupées ensemble ont une priorité égale. Par exemple,
foo|bar*est identique à
(foo)|(ba(r*))puisque l'opérateur « * » a une plus grande priorité que la concaténation, et que la concaténation a une plus grande priorité que l'alternative ('|'). Ce motif convient par conséquent soit à la chaîne de caractères « foo », soit à la chaîne de caractères « ba » suivie de zéro-ou-plusieurs r. Pour reconnaître « foo » ou zéro-ou-plusieurs « bar », utilisez :
foo|(bar)*et pour reconnaître zéro-ou-plusieurs « foo ou bar » :
(foo|bar)*
En plus des caractères et des intervalles de caractères, les classes de caractères peuvent également contenir des expressions de classes de caractères. Ce sont des expressions enfermées dans des délimiteurs [: et :] (qui doivent elles-mêmes apparaître entre le '[' et le ']' de la classe de caractères ; d'autres éléments peuvent également être présents dans la classe de caractères). Les expressions valides sont :
[:alnum:] [:alpha:] [:blank:] [:cntrl:] [:digit:] [:graph:] [:lower:] [:print:] [:punct:] [:space:] [:upper:] [:xdigit:]Ces expressions désignent toutes un groupe de caractères équivalent à la fonction C standard correspondante isXXX. Par exemple, [:alnum:] désigne les caractères pour lesquels isalnum() renvoie vrai - c.-à-d. tout caractère alphabétique ou numérique. Certains systèmes ne fournissent pas isblank(), et flex définit donc [:blank:] comme étant un blanc ou une tabulation.
Par exemple, les classes de caractères suivantes sont toutes équivalentes :
[[:alnum:]] [[:alpha:][:digit:] [[:alpha:]0-9] [a-zA-Z0-9]Si votre analyseur est insensible à la casse (option -i), alors [:upper:] et [:lower:] sont équivalents à [:alpha:].
Quelques notes sur les motifs :
foo/bar$ <sc1>foo<sc2>barNotez que la première ligne peut être écrite « foo/bar\n ».
foo|(bar$) foo|^barSi ce qu'on veut est un « foo » ou un bar-suivi-par-un-saut-de-ligne, on pourrait utiliser ceci (l'action spéciale '|' est expliquée plus bas) :
foo | bar$ /* l'action vient ici */Un truc similaire fonctionnera pour la détection d'un foo ou d'un bar-au-début-d-une-ligne.
Une fois que la correspondance est déterminée, le texte correspondant (appelé élément lexical) est mis à disposition dans le pointeur de caractère global yytext, et sa longueur dans l'entier global yyleng. L'action correspondant au motif reconnu est ensuite exécutée (une description plus détaillée des actions suit), et ensuite l'entrée restante est analysée afin de trouver une autre correspondance.
Si aucune correspondance n'est trouvée, alors la règle par défaut est exécutée : le caractère suivant dans l'entrée est considéré comme reconnu et est copié sur la sortie standard. Ainsi, l'entrée légale la plus simple pour flex est :
%%qui génère un analyseur qui copie simplement son entrée (un caractère à la fois) sur sa sortie.
Notez que yytext peut être défini de deux façons différentes : soit comme un pointeur de caractère, soit comme un tableau de caractères. Vous pouvez contrôler quelle définition utilise flex en incluant une des directives spéciales %pointer ou %array dans la première section (définitions) de votre entrée flex. Le défaut est %pointer, à moins que vous n'utilisiez l'option de compatibilité de lex -l , auquel cas yytext sera un tableau. L'avantage à utiliser %pointer est une analyse substantiellement plus rapide et aucun débordement de tampon lors de la reconnaissance de très gros éléments lexicaux (à moins que vous ne tombiez en manque de mémoire dynamique). Le désavantage est que vous êtes limités dans la façon dont vos actions peuvent modifier yytext (voyez la section suivante), et que les appels à la fonction unput() détruisent le contenu actuel de yytext, ce qui peut être un casse-tête de portage important lors d'une migration entre différentes versions de lex.
L'avantage de %array est que vous pouvez ensuite modifier yytext comme vous le souhaitez, et que les appels à unput() ne détruisent pas yytext (voir plus bas). De plus, les programmes lex existants accèdent parfois à yytext de façon externe en utilisant des déclarations de la forme :
extern char yytext[];Cette définition est erronée quand elle est utilisée avec %pointer, mais correcte pour %array.
%array définit yytext comme étant un tableau de YYLMAX caractères, qui est une valeur assez grande par défaut. Vous pouvez modifier la taille en #define-issant YYLMAX à une valeur différente dans la première section de votre entrée flex. Comme mentionné plus haut, avec %pointer, yytext grandit dynamiquement pour pouvoir traiter les grands éléments lexicaux. Bien que cela signifie que votre analyseur %pointer puisse traiter les très grands éléments lexicaux (comme la reconnaissance de blocs entiers de commentaires), gardez à l'esprit qu'à chaque fois que l'analyseur doit redimensionner yytext, il doit également réexaminer l'élément lexical complet à partir du début, et la reconnaissance de tels éléments lexicaux peut être lente. yytext ne grandit actuellement pas dynamiquement si un appel à unput() résulte en trop de texte repoussé ; à la place, une erreur à l'exécution en résulte.
Notez également que vous ne pouvez pas utiliser %array avec des classes d'analyseurs C++ (l'option c++; voir plus bas).
%% « effacez-moi »(Cela copiera tous les autres caractères de l'entrée en sortie puisqu'il seront reconnus par la règle par défaut.)
Voici un programme qui compresse de multiples blancs et tabulations en une simple espace blanche, et jette les caractères d'espacement trouvés à la fin d'une ligne :
%% [ \t]+ putchar( ' ' ); [ \t]+$ /* ignorer cet élément lexical */
Si l'action contient un « { », alors l'action continue jusqu'à ce que la « } » équilibrante soit trouvée, et l'action peut recouvrir plusieurs lignes. flex connaît les chaînes de caractères C et les commentaires, et ne sera pas dupé par des accolades trouvées à l'intérieur d'entre eux, mais permet également aux actions de commencer par %{ et considérera que l'action sera constituée de tout le texte allant jusqu'au %} suivant (qu'il y ait des accolades ordinaires à l'intérieur de l'action ou non).
Une action consistant uniquement en une barre verticale ('|') signifie « la même chose que l'action pour la règle suivante. » Voyez plus bas pour une illustration.
Les actions peuvent inclure du code C arbitraire, ce qui inclut les instructions return pour renvoyer une valeur à la routine qui a appelé yylex(). Chaque fois que yylex() est appelée, elle continue le traitement des éléments lexicaux à partir d'où elle s'était arrêtée en dernier lieu jusqu'à ce qu'elle atteigne la fin du fichier, ou jusqu'à ce qu'elle exécute un return.
Les actions sont libres de modifier yytext sauf pour l'allonger (ajouter des caractères à sa fin -- ceux-ci écraseront les caractères venant par après dans le flux d'entrée). Cela ne s'applique néanmoins pas lors de l'utilisation de %array (voir au-dessus); dans ce cas, yytext peut être librement modifié de n'importe quelle façon.
Les actions sont libres de modifier yyleng mis à part qu'elles ne devraient pas faire cela si l'action inclut également l'utilisation de yymore() (voir plus bas).
Il y a un certain nombre de directives spéciales qui peuvent être incluses à l'intérieur d'une action :
int nombre_mots = 0; %% frob special(); REJECT; [^ \t\n]+ ++nombre_mots;Sans le REJECT, tout « frob » dans l'entrée ne sera pas compté comme mot, car l'analyseur n'exécute normalement qu'une action par élément lexical. Des REJECT multiples sont permis, chacun trouvant le meilleur choix suivant la règle active à ce moment. Par exemple, quand l'analyseur suivant détecte l'élément lexical « abcd », il écrira « abcdabcaba » sur la sortie :
%% a | ab | abc | abcd ECHO; REJECT; .|\n /* manger tous les caractères non reconnus */(Les trois premières règles partagent l'action de la quatrième car elles utilisent l'action spéciale '|'.) REJECT est une fonctionnalité particulièrement coûteuse en terme de performance de l'analyseur ; si elles est utilisée dans toutes les actions de l'analyseur, elles ralentira toutes les correspondances effectuées par l'analyseur. De plus, REJECT ne peut être utilisé avec les options -Cf ou -CF (voir plus bas).
%% mega- ECHO; yymore(); kludge ECHO;Le premier « mega- » est détecté et transmis sur la sortie. Ensuite « kludge » est détecté, mais le « mega- » précédent traîne toujours au début de yytext, et donc l'ECHO pour la règle « kludge » sera en fait « mega-kludge ».
Deux notes concernant l'utilisation de yymore(). Primo, yymore() dépend du fait que yyleng reflète correctement la taille de l'élément lexical courant, et vous ne devriez donc pas modifier yyleng si vous utilisez yymore(). Secundo, la présence de yymore() dans l'action de l'analyseur engendre une pénalité de performance mineure dans la vitesse de reconnaissance de l'analyseur.
%% foobar ECHO; yyless(3); [a-z]+ ECHO;Un argument de 0 pour yyless provoquera le réexamen entier de la chaîne de caractères d'entrée courante. À moins que vous n'ayez modifié la façon dont l'analyseur va traiter ultérieurement son entrée (en utilisant BEGIN par exemple), cela résultera en une boucle sans fin.
Notez que yyless est une macro et ne peut être utilisée que dans le fichier d'entrée de flex, et pas depuis d'autres fichiers source.
{ int i; /* Copier yytext car unput() détériore yytext */ char *yycopy = strdup( yytext ); unput( ')' ); for ( i = yyleng - 1; i >= 0; --i ) unput( yycopy[i] ); unput( '(' ); free( yycopy ); }Notez que puisque chaque unput() repousse le caractère donné au début du flux d'entrée, le repoussage de chaîne de caractères doit être fait de la fin vers le début.
Un problème potentiel important lors de l'utilisation de unput() est que si vous utilisez %pointer (le défaut), un appel à unput() détruit le contenu de yytext, en commençant par son caractère le plus à droite, et en dévorant un caractère sur la gauche à chaque appel. Si vous avez besoin que la valeur de yytext soit préservée après un appel à unput() (comme dans l'exemple ci-dessus), vous devez soit la copier ailleurs, soit construire votre analyseur en utilisant %array à la place (voir Comment l'Entrée est Reconnue).
Finalement, notez que vous ne pouvez pas repousser EOF pour essayer de marquer le flux d'entrée avec un end-of-file.
%% "/*" register int c; for ( ; ; ) { while ( (c = input()) != '*' && c != EOF ) ; /* manger le texte du commentaire */ if ( c == '*' ) { while ( (c = input()) == '*' ) ; if ( c == '/' ) break; /* fin trouvée */ } if ( c == EOF ) { error( "EOF dans un commentaire" ); break; } } }(Notez que si l'analyseur a été compilé en utilisant C++, alors input() est référencé sous le nom de yyinput(), afin d'éviter un conflit de noms avec le flux C++ de nom input.)
int yylex() { ... diverses définitions et actions ici ... }(Si votre environnement supporte les prototypes de fonctions, alors il sera « int yylex( void ) ».) Cette définition peut être modifiée en définissant la macro « YY_DECL ». Par exemple, vous pourriez utiliser :
#define YY_DECL float lexscan( a, b ) float a, b;pour donner à la routine d'analyse le nom lexscan, renvoyant un flottant, et prenant deux flottants comme arguments. Notez que si vous donnez des arguments à la routine d'analyse en utilisant une déclaration de fonction de style K&R/non-prototypée, vous devez terminer la définition par un point-virgule (;).
À chaque fois que yylex() est appelée, elle analyse les éléments lexicaux à partir du fichier d'entrée global yyin (qui vaut stdin par défaut). Elle continue jusqu'à ce qu'elle atteigne soit une fin de fichier EOF (où elle renvoie la valeur 0) ou jusqu'à ce que l'une de ses actions exécute une instruction return.
Si l'analyseur atteint un EOF, les appels ultérieurs seront non définis à moins que soit yyin pointe sur un nouveau fichier d'entrée (auquel cas l'analyse continue à partir de ce fichier), soit yyrestart() soit appelée. yyrestart() prend un argument, un pointeur FILE * (qui peut être zéro, si vous avez réglé YY_INPUT afin d'analyser à partir d'une source différente de yyin), et initialise yyin pour une analyse à partir de ce fichier. Il n'y a essentiellement aucune différence entre affecter yyin à un autre fichier d'entrée, ou utiliser yyrestart() pour faire cela ; cette dernière possibilité est disponible pour assurer la compatibilité avec des versions précédentes de flex, et parce qu'elle peut être utilisée pour changer de fichier d'entrée au milieu de l'analyse. Elle peut également être utilisée pour jeter le tampon d'entrée courant, en l'appelant avec un argument de yyin, mais il vaut mieux utiliser YY_FLUSH_BUFFER (voir au-dessus). Notez que yyrestart() ne réinitialise pas la condition de démarrage à INITIAL (voir Conditions de Démarrage, plus bas).
Si yylex() arrête l'analyse du fait de l'exécution d'une instruction return dans l'une des actions, l'analyseur peut ensuite être ré-appelé et il reprendra l'analyse là où il l'avait laissée.
Par défaut (et à des fins d'efficacité), l'analyseur utilise des lectures de blocs plutôt que de simples appels getc() pour lire des caractères à partir de yyin. La façon dont il obtient son entrée peut être contrôlée en définissant la macro YY_INPUT. La séquence d'appel de YY_INPUT est « YY_INPUT(tampon,résultat,taille_max) ». Son action est de placer jusqu'à taille_max caractères dans le tableau de caractères tampon et de renvoyer dans la variable entière résultat soit le nombre de caractères lus, soit la constante YY_NULL (0 sur les systèmes Unix) pour indiquer EOF. Le YY_INPUT par défaut lit à partir du pointeur de fichier global « yyin ».
Un exemple de définition de YY_INPUT (dans la section de définitions du fichier d'entrée) :
%{ #define YY_INPUT(tampon,résultat,taille_max) \ { \ int c = getchar(); \ résultat = (c == EOF) ? YY_NULL : (buf[0] = c, 1); \ } %}Cette définition modifiera le traitement de l'entrée pour qu'il se produise pour un caractère à la fois.
Quand l'analyseur reçoit une indication de fin de fichier de YY_INPUT, il teste ensuite la fonction yywrap(). Si yywrap() renvoie faux (zéro), alors on suppose que la fonction a poursuivi son chemin et réglé yyin pour qu'elle pointe sur un autre fichier d'entrée, et l'analyse continue. Si elle renvoie vrai (non-zéro), alors l'analyseur se termine, en renvoyant 0 à son appelant. Notez que la condition de démarrage reste inchangée dans tous les cas ; elle ne revient pas à INITIAL.
Si vous ne fournissez pas votre propre version de yywrap(), alors vous devez soit utiliser %option noyywrap auquel cas l'analyseur se comporte comme si yywrap() renvoyait 1), soit éditer les liens avec -lfl pour obtenir la version par défaut de la routine, qui renvoie toujours 1.
Trois routines sont disponibles pour effectuer l'analyse sur des tampons présents en mémoire plutôt que sur des fichiers : yy_scan_string(), yy_scan_bytes()et yy_scan_buffer(). Voyez la discussion à leur sujet plus bas dans la section Tampons d'Entrée Multiples.
L'analyseur écrit sa sortie ECHO dans le yyout global (par défaut stdout), qui peut être redéfini par l'utilisateur simplement en l'affectant à un autre pointeur de FILE.
<STRING>[^"]* { /* manger le corps de la chaîne de caractères ... */ ... }ne sera active que lorsque l'analyseur est dans la condition de démarrage « STRING », et
<INITIAL,STRING,QUOTE>\. { /* traiter un escape ... */ ... }ne sera active que lorsque la condition de démarrage courante est « INITIAL », « STRING » ou « QUOTE ».
Les conditions de démarrage sont déclarées dans la section de définitions (la première) de l'entrée en utilisant des lignes non indentées commençant soit par %s, soit par %x, suivi d'une liste de noms. %s déclare des conditions de démarrage inclusives, %x des conditions de démarrage exclusives. Une condition de démarrage est activée en utilisant l'action BEGIN. Jusqu'au moment où la prochaine section BEGIN est exécutée, les règles possédant la condition de démarrage donnée seront actives et les règles avec d'autres conditions de démarrage seront inactives. Si la condition de démarrage est inclusive, alors les règles ne possédant aucune condition de démarrage seront également actives. Si elle est exclusive, alors seules les règles qualifiées par la condition de démarrage seront actives. Un ensemble de règles contingentes à la même condition de démarrage exclusive décrit un analyseur qui est indépendant de n'importe quelle autre règle de l'entrée de flex. De ce fait, les conditions de démarrage exclusives facilitent la spécification de « mini-analyseurs » qui analysent des portions de l'entrée qui sont syntaxiquement différentes du reste (p.ex. les commentaires).
Si la distinction entre les conditions de démarrage inclusives et exclusives est toujours un peu vague pour vous, voici un exemple simple illustrant les relations entre les deux. L'ensemble de règles
%s exemple %% <exemple>foo faire_quelque_chose(); bar quelque_chose_d_autre();est équivalent à
%x exemple %% <exemple>foo faire_quelque_chose(); <INITIAL,exemple>bar quelque_chose_d_autre();Sans le qualificatif <INITIAL,exemple>, le motif bar du second exemple ne serait pas actif (c.-à-d. ne pourrait être reconnu) lorsqu'on se trouve dans la condition de démarrage exemple. Pourtant, si nous avions juste utilisé <exemple> pour qualifier bar, alors il ne serait actif que dans exemple et pas dans INITIAL, alors que dans le premier exemple il serait actif dans les deux, car dans le premier exemple la condition de démarrage exemple est inclusive (%s).
Notez également que le spécificateur spécial de condition de démarrage <*> convient pour n'importe quelle condition de démarrage. Ainsi, l'exemple au-dessus aurait également pu être écrit
%x exemple %% <exemple>foo faire_quelque_chose(); <*>bar quelque_chose_d_autre();
La règle par défaut (pour mettre en ECHO tout caractère non reconnu) reste active dans les conditions de démarrage. Elle est équivalente à :
<*>.|\n ECHO;
BEGIN(0) revient à l'état original quand seules les règles sans conditions de démarrage sont actives. Cet état peut également être référencé par la condition de démarrage « INITIAL », et BEGIN(INITIAL) est par conséquent équivalent à BEGIN(0). (Les parenthèses autour du nom de la condition de démarrage ne sont pas requises mais on estime que cela fait partie d'un bon style à adopter.)
Les actions BEGIN peuvent également être fournies en tant que code indenté au début de la section de règles. Par exemple, le code suivant forcera l'analyseur à entrer dans la condition de démarrage « SPECIAL » à chaque fois que yylex() est appelée et que la variable globale enter_special est vraie :
int entrer_special; %x SPECIAL %% if ( entrer_special ) BEGIN(SPECIAL); <SPECIAL>blablabla ...d'autres règles suivent...
Pour illustrer l'utilisation des conditions de démarrage, voici un analyseur qui fournit deux interprétations différentes d'une chaîne de caractères comme « 123.456 ». Par défaut, il la traitera comme trois éléments lexicaux, l'entier « 123 », un point (« . »), et l'entier « 456 ». Mais si la chaîne est précédée plus tôt dans la ligne par la chaîne de caractères « flottants-attendus », il la traitera comme un unique élément lexical, le nombre flottant 123.456 :
%{ #include <math.h> %} %s attendu %% flottants-attendus BEGIN(attendu); <attendu>[0-9]+"."[0-9]+ { printf( "flottant trouvé, = %f\n", atof( yytext ) ); } <attendu>\n { /* c'est la fin de la ligne, ainsi * nous avons besoin d'un autre « nombre-attendu » * avant que nous ne reconnaissions d'autres * nombres */ BEGIN(INITIAL); } [0-9]+ { printf( "entier trouvé, = %d\n", atoi( yytext ) ); } "." printf( "point trouvé\n" );Voici un analyseur qui reconnaît (et élimine) les commentaires C tout en maintenant un comptage de la ligne d'entrée courante.
%x commentaire %% int num_ligne = 1; "/*" BEGIN(commentaire); <commentaire>[^*\n]* /* manger tout ce qui n'est pas un '*' */ <commentaire>"*"+[^*/\n]* /* manger les '*' non suivis d'un '/' */ <commentaire>\n ++num_ligne; <commentaire>"*"+"/" BEGIN(INITIAL);Cet analyseur a du mal à reconnaître le plus de texte possible avec chaque règle. En général, lorsque vous essayez d'écrire un analyseur très rapide, essayez de détecter le plus possible dans chaque règle, puisque cela offre un grand gain.
Notez que les noms des conditions de démarrage sont réellement des valeurs entières et peuvent être stockées en tant que telles. Ainsi, le code précédent pourrait être étendu de la manière suivante :
%x commentaire foo %% int num_ligne = 1; int appelant_commentaire; "/*" { appelant_commentaire = INITIAL; BEGIN(commentaire); } ... <foo>"/*" { appelant_commentaire = foo; BEGIN(commentaire); } <commentaire>[^*\n]* /* manger tout ce qui n'est pas un '*' */ <commentaire>"*"+[^*/\n]* /* manger les '*' non suivis d'un '/' */ <commentaire>\n ++num_ligne; <commentaire>"*"+"/" BEGIN(appelant_commentaire);En outre, vous pouvez accéder à la condition de démarrage courante en utilisant la macro à valeurs entières YY_START. Par exemple, les affectations ci-dessus pour appelant_commentaire pourraient à la place être écrites
appelant_commentaire = YY_START;Flex fournit YYSTATE en tant qu'alias pour YY_START (puisque c'est ce qui est utilisé par le lex de AT&T).
Notez que les conditions de démarrage n'ont pas leur propre espace de noms ; les %s et %x déclarent des noms de la même manière que les #define.
Finalement, voici un exemple de la façon de détecter les chaînes de caractères protégées de style C en utilisant des conditions de démarrage exclusives, qui inclut les séquences d'échappement développées (mais pas la vérification de longueur d'une chaîne de caractères) :
%x str %% char string_buf[MAX_STR_CONST]; char *string_buf_ptr; \" string_buf_ptr = string_buf; BEGIN(str); <str>\" { /* guillemets de terminaison détectés - fini */ BEGIN(INITIAL); *string_buf_ptr = '\0'; /* renvoie le type d'élément lexical « chaîne constante » * et sa valeur à l'analyseur syntaxique */ } <str>\n { /* erreur - constante chaîne de caractères non terminée */ /* générer un message d'erreur */ } <str>\\[0-7]{1,3} { /* séquence d'échappement octale */ int résultat; (void) sscanf( yytext + 1, "%o", &résultat ); if ( résultat > 0xff ) /* erreur, la constante est hors limite */ *string_buf_ptr++ = résultat; } <str>\\[0-9]+ { /* générer une erreur - mauvaise séquence d'échappement ; * quelque chose comme '\48' ou '\0777777' */ } <str>\\n *string_buf_ptr++ = '\n'; <str>\\t *string_buf_ptr++ = '\t'; <str>\\r *string_buf_ptr++ = '\r'; <str>\\b *string_buf_ptr++ = '\b'; <str>\\f *string_buf_ptr++ = '\f'; <str>\\(.|\n) *string_buf_ptr++ = yytext[1]; <str>[^\\\n\"]+ { char *yptr = yytext; while ( *yptr ) *string_buf_ptr++ = *yptr++; }
Souvent, comme dans certains des exemples plus haut, vous finissez par écrire un tas de règles qui sont toutes précédées par la(les) même(s) condition(s) de démarrage. Flex rend cela plus facile et plus propre en introduisant une notion de portée de condition de démarrage. Une portée de condition de démarrage commence par :
<CDDs>{où CDDs est une liste d'une ou de plusieurs conditions de démarrage. À l'intérieur de la portée de la condition de démarrage, chaque règle se voit automatiquement appliquer le préfixe <CDDs> , jusqu'à un '}' qui correspond au '{' initial. Ainsi, par exemple,
<ESC>{ "\\n" return '\n'; "\\r" return '\r'; "\\f" return '\f'; "\\0" return '\0'; }est équivalent à :
<ESC>"\\n" return '\n'; <ESC>"\\r" return '\r'; <ESC>"\\f" return '\f'; <ESC>"\\0" return '\0';Les portées de conditions de démarrage peuvent être imbriquées.
Trois routines sont disponibles pour la manipulation de conditions de démarrage :
La pile de conditions de démarrage grandit dynamiquement et ne souffre ainsi aucune limitation de taille intégrée. Si la mémoire est épuisée, l'exécution du programme échoue.
Pour utiliser des piles de conditions de démarrage, votre analyseur doit inclure une directive %option stack (voir Options plus bas).
Pour régler ces types de problèmes, flex fournit un mécanisme pour la création et la commutation entre de multiples tampons d'entrée. Un tampon d'entrée est créé en utilisant :
YY_BUFFER_STATE yy_create_buffer( FILE *fichier, int taille )qui prend un pointeur vers un FILE et une taille et crée un tampon associé au fichier donné et assez grand pour contenir taille caractères (en cas de doute, utilisez YY_BUF_SIZE pour la taille). Il renvoie un gestionnaire YY_BUFFER_STATE, qui peut ensuite être passé à d'autres routines (voir plus bas). Le type YY_BUFFER_STATE est un pointeur vers une structure opaque struct yy_buffer_state, et vous pouvez ainsi initialiser sans danger les variables YY_BUFFER_STATE à ((YY_BUFFER_STATE) 0) si vous le souhaitez, et également vous référer à la structure opaque afin de déclarer correctement les tampons d'entrée présents dans des fichiers sources différents de ceux de votre analyseur. Notez que le pointeur de FILE dans l'appel à yy_create_buffer n'est utilisé qu'en tant que valeur pour yyin vue par YY_INPUT ; si vous redéfinissez YY_INPUT de sorte qu'elle n'utilise plus yyin, alors vous pouvez passer sans danger un pointeur de FILE nul à yy_create_buffer. Vous pouvez sélectionner un tampon particulier à examiner en utilisant :
void yy_switch_to_buffer( YY_BUFFER_STATE nouveau_tampon )substitue le tampon d'entrée de l'analyseur de sorte que les éléments lexicaux suivants proviendront du nouveau_tampon. Notez que yy_switch_to_buffer() peut être utilisée par yywrap() pour tout régler pour une analyse ininterrompue, au lieu d'ouvrir un nouveau fichier et d'y faire pointer yyin. Notez également que la commutation de sources d'entrée via soit yy_switch_to_buffer(), soit yywrap(), ne change pas la condition de démarrage.
void yy_delete_buffer( YY_BUFFER_STATE tampon )est utilisée pour réclamer l'espace de stockage associé à un tampon ( tampon peut être nul, auquel cas la routine ne fait rien). Vous pouvez également effacer le contenu actuel d'un tampon en utilisant :
void yy_flush_buffer( YY_BUFFER_STATE tampon )Cette fonction élimine le contenu du tampon, de sorte que la prochaine fois que l'analyseur essaiera de reconnaître un élément lexical à partir du tampon, il remplira d'abord le tampon en utilisant YY_INPUT.
yy_new_buffer() est un alias pour yy_create_buffer(), fourni pour la compatibilité avec l'utilisation C++ de new et de delete pour la création et la destruction d'objets dynamiques.
Finalement, la macro YY_CURRENT_BUFFER renvoie un gestionnaire YY_BUFFER_STATE vers le tampon actuel.
Voici un exemple de l'utilisation de ces fonctionnalités pour l'écriture d'un analyseur qui développe les fichiers d'inclusion (la caractéristique <<EOF>> est traitée plus bas) :
/* l'état « incl » est utilisé pour prendre le nom * d'un fichier d'inclusion */ %x incl %{ #define PROFONDEUR_INCLUSION_MAX 10 YY_BUFFER_STATE pile_inclusion[PROFONDEUR_INCLUSION_MAX]; int ptr_pile_inclusion = 0; %} %% include BEGIN(incl); [a-z]+ ECHO; [^a-z\n]*\n? ECHO; <incl>[ \t]* /* manger les blancs */ <incl>[^ \t\n]+ { /* nom du fichier include obtenu */ if ( ptr_pile_inclusion >= PROFONDEUR_INCLUSION_MAX ) { fprintf( stderr, "Includes imbriqués trop profondément"); exit( 1 ); } pile_inclusion[ptr_pile_inclusion++] = YY_CURRENT_BUFFER; yyin = fopen( yytext, "r" ); if ( ! yyin ) error( ... ); yy_switch_to_buffer( yy_create_buffer( yyin, YY_BUF_SIZE ) ); BEGIN(INITIAL); } <<EOF>> { if ( --ptr_pile_inclusion < 0 ) { yyterminate(); } else { yy_delete_buffer( YY_CURRENT_BUFFER ); yy_switch_to_buffer( pile_inclusion[ptr_pile_inclusion] ); } }Trois routines sont disponibles pour faire en sorte que les tampons d'entrée examinent des chaînes de caractères présentes en mémoire plutôt que des fichiers. Elle créent toutes un nouveau tampon d'entrée pour l'examen de la chaîne de caractères, et renvoie un gestionnaire YY_BUFFER_STATE correspondant (que vous devriez effacer avec yy_delete_buffer() quand vous en avez terminé avec lui). Elles passent également dans le nouveau tampon en utilisant yy_switch_to_buffer(), de sorte que le prochain appel à yylex() débutera l'examen de la chaîne de caractères.
Notez que ces deux fonctions créent et examinent une copie de la chaîne de caractères ou des octets. (Cela peut être préférable, puisque yylex() modifie le contenu du tampon qu'il examine). Vous pouvez éviter la copie en utilisant
Les règles <<EOF>> ne peuvent être utilisées avec d'autres motifs ; elles ne peuvent être qualifiées qu'avec une liste de conditions de démarrage. Si une règle <<EOF>> non qualifiée est fournie, elle s'applique à toutes les conditions de démarrage qui ne possèdent pas encore d'actions <<EOF>>. Pour spécifier une règle <<EOF>> uniquement pour la condition de démarrage initiale, utilisez
<INITIAL><<EOF>>
Ces règles sont utiles pour capturer des choses comme des commentaires non fermés. Un exemple:
%x quote %% ...d'autres règles pour le traitement des guillemets... <quote><<EOF>> { error( "guillemet non terminé" ); yyterminate(); } <<EOF>> { if ( *++listefichiers ) yyin = fopen( *listefichiers, "r" ); else yyterminate(); }
#define YY_USER_ACTION ++ctr[yy_act]où ctr est un tableau qui conserve le nombre d'occurrences des différentes règles. Notez que la macro YY_NUM_RULES fournit le nombre total de règles (incluant la règle par défaut, même si vous utilisez -s), et ainsi une déclaration correcte pour ctr est :
int ctr[YY_NUM_RULES];
La macro YY_USER_INIT peut être définie pour fournir une action qui est toujours exécutée avant la première analyse (et avant que les initialisations internes de l'analyseur ne soient effectuées). Par exemple, elle pourrait être utilisée pour appeler une routine afin de lire à partir d'une table de données, ou ouvrir un fichier journal.
La macro yy_set_interactive(est_interactif) peut être utilisée pour contrôler si le tampon actuel est considéré interactif. Un tampon interactif est traité plus lentement, mais doit être utilisé lorsque la source d'entrée de l'analyseur est effectivement interactive pour éviter des problèmes dus à l'attente pour remplir les tampons (voyez la discussion sur l'attribut -I plus bas). Une valeur non nulle dans l'invocation de macro marque le tampon comme étant interactif, une valeur nulle non interactif. Notez que l'utilisation de cette macro surcharge %option always-interactive et %option never-interactive (voir Options en dessous). yy_set_interactive() doit être invoquée avant de commencer à examiner le tampon qui doit (ou pas) être considéré interactif.
La macro yy_set_bol(at_bol) peut être utilisée pour contrôler si le contexte d'analyse du tampon courant pour la prochaine mise en correspondance d'un élément lexical est effectuée comme si l'on était au début d'une ligne. Un argument de macro non nul ancre les règles avec '^' actif, alors qu'un argument rend les règles '^' inactives.
La macro YY_AT_BOL() renvoie vrai si le prochain élément lexical détecté à partir du tampon courant a les règles '^' actives, ou faux sinon.
Dans l'analyseur généré, les actions sont toutes regroupées dans une grande instruction switch, et séparées en utilisant YY_BREAK, qui peut être redéfinie. Par défaut, c'est simplement un « break », pour séparer chaque action de règle des règles suivantes. Redéfinir YY_BREAK permet, par exemple, aux utilisateurs C++ de faire en sorte que des #define YY_BREAK ne fassent rien (tout en étant très attentif au fait que chaque règle se termine par un « break » ou un « return » !) pour éviter d'avoir à souffrir d'avertissements dus à des instructions hors d'atteinte lorsqu'une action de règle se termine par un « return », le YY_BREAK est inaccessible.
%{ #include "y.tab.h" %} %% [0-9]+ yylval = atoi( yytext ); return TOK_NUMBER;
--accepting rule at line 53 ("le texte reconnu")Le numéro de ligne se réfère à l'emplacement de la règle dans le fichier définissant l'analyseur (c.-à-d. le fichier qui nourrissait flex). Les messages sont également générés quand l'analyseur revient en arrière, accepte la règle par défaut, atteint la fin de son tampon d'entrée (ou rencontre un NUL ; à ce moment, les deux se ressemblent du point de vue de l'analyseur), ou atteint un EOF.
"case" return TOK_CASE; "switch" return TOK_SWITCH; ... "default" return TOK_DEFAULT; [a-z]+ return TOK_ID;alors vous devriez plutôt utiliser la représentation complète de la table. Si seule la règle « identificateur » est présente et que vous utilisez ensuite une table de hachage ou quelque chose du genre pour détecter les mots-clés, vous devriez plutôt utiliser -F.
plus lent et plus court -Cem -Cm -Ce -C -C{f,F}e -C{f,F} -C{f,F}a plus rapide et plus grosNotez que les analyseurs avec les plus petites tables sont habituellement générés et compilés le plus rapidement, et que, durant le développement, vous devriez utiliser la compression maximale (par défaut).
yy_create_buffer yy_delete_buffer yy_flex_debug yy_init_buffer yy_flush_buffer yy_load_buffer_state yy_switch_to_buffer yyin yyleng yylex yylineno yyout yyrestart yytext yywrap(Si vous utilisez un analyseur C++, alors seuls yywrap et yyFlexLexer sont affectés.) À l'intérieur de votre analyseur lui-même, vous pouvez toujours vous référer aux variables et fonctions globales en utilisant n'importe quelle version de leur nom ; mais vu de l'extérieur, elles possèdent le nom modifié.
flex fournit également un mécanisme pour contrôler les options à l'intérieur même de la spécification de l'analyseur, plutôt qu'à partir de la ligne de commandes de flex. C'est fait en incluant des directives %option dans la première section de la spécification de l'analyseur. Vous pouvez spécifier de multiples options à l'intérieur d'une même directive %option, et de multiples répertoires dans la première section de votre fichier d'entrée de flex.
La plupart des options sont fournies simplement comme des noms, précédés facultativement du mot « no » (sans caractères d'espacement entre les deux) pour nier leur signification. Un nombre est équivalent à un drapeau de flex, ou à sa négation :
7bit option -7 8bit option -8 align option -Ca backup option -b batch option -B c++ option -+ caseful ou case-sensitive opposé de -i (défaut) case-insensitive ou caseless option -i debug option -d default opposé de l'option -s ecs option -Ce fast option -F full option -f interactive option -I lex-compat option -l meta-ecs option -Cm perf-report option -p read option -Cr stdout option -t verbose option -v warn opposé de l'option -w (utilisez "%option nowarn" pour -w) array équivalent à "%array" pointer équivalent "%pointer" (défaut)Certaines %option fournissent des fonctionnalités habituellement non disponibles :
flex examine vos actions de règles pour déterminer si vous utilisez les fonctionnalités REJECT ou yymore(). Les options reject et yymore sont disponibles pour surcharger sa décision d'utilisation ou non des options, soit en les définissant (p.ex. %option reject ) pour indiquer que la fonctionnalité est effectivement utilisée, soit en les in-définissant pour indiquer qu'elle n'est en fait pas utilisée (p.ex. %option noyymore).
Trois options prennent des valeurs délimitées par une chaîne de caractères, décalée avec '=':
%option outfile="ABC"est équivalent à -oABC, et
%option prefix="XYZ"est équivalent à -PXYZ. Finalement,
%option yyclass="foo"ne s'applique que lors de la génération d'un analyseur C++ (option -+). Elle informe flex que vous avez dérivé foo comme une sous-classe de yyFlexLexer, de sorte que flex placera vos actions dans la fonction membre foo::yylex() au lieu de yyFlexLexer::yylex(). Elle génère également une fonction membre yyFlexLexer::yylex() qui émet une erreur à l'exécution (en invoquant yyFlexLexer::LexerError()) si elle est appelée. Voyez Générer des Analyseurs C++, plus bas, pour des informations additionnelles.
Certaines options sont disponibles pour les puristes de lint qui veulent supprimer l'apparition de routines non nécessaires dans l'analyseur généré. Chacune des options suivantes, si elle est in-définie (p.ex., %option nounput), résulte en la non-apparition de la routine correspondante dans l'analyseur généré :
input, unput yy_push_state, yy_pop_state, yy_top_state yy_scan_buffer, yy_scan_bytes, yy_scan_string(bien que yy_push_state() et ses amis n'apparaîtront de toute façon pas à moins que vous n'utilisiez %option stack).
REJECT %option yylineno contexte de queue arbitraire jeux de motifs qui requièrent une sauvegarde %array %option interactive %option always-interactive '^' opérateur début-de-ligne yymore()les trois premières étant toutes assez chères et les deux dernières étant assez bon marché. Notez également que unput() est implémenté en tant qu'appel de routine qui fait potentiellement beaucoup de travail, alors que yyless() est une macro assez bon marché ; ainsi, si vous repoussez simplement un peu de texte en excès que vous avez analysé, utilisez yyless().
REJECT devrait être évité à tout prix quand la performance est importante. C'est une option particulièrement coûteuse.
Se débarrasser de la sauvegarde est une saleté et peut représenter une grosse charge de travail pour un analyseur compliqué. En principe, on commence par utiliser le drapeau -b pour générer un fichier lex.backup. Par exemple, sur l'entrée
%% foo return TOK_KEYWORD; foobar return TOK_KEYWORD;le fichier ressemble à ceci :
State #6 is non-accepting - associated rule line numbers: 2 3 out-transitions: [ o ] jam-transitions: EOF [ \001-n p-\177 ] State #8 is non-accepting - associated rule line numbers: 3 out-transitions: [ a ] jam-transitions: EOF [ \001-` b-\177 ] State #9 is non-accepting - associated rule line numbers: 3 out-transitions: [ r ] jam-transitions: EOF [ \001-q s-\177 ] Compressed tables always back up.Les toutes premières lignes nous disent qu'il y a un état de l'analyseur dans lequel il peut faire une transition sur un 'o' mais pas sur aucun autre caractère, et que dans cet état, le texte actuellement examiné ne correspond à aucune règle. L'état se produit quand on essaie de reconnaître les règles trouvées aux lignes 2 et 3 du fichier d'entrée. Si l'analyseur est dans cet état et lit ensuite quelque chose d'autre qu'un 'o', il devra revenir en arrière pour trouver une règle qui correspond. Avec un peu de prise de tête, on peut voir que cela doit être l'état dans lequel il est quand il a vu « fo ». Quand cela s'est passé, si quelque chose d'autre qu'un 'o' est vu, l'analyseur devra revenir en arrière pour reconnaître simplement le 'f' (par la règle par défaut).
Le commentaire concernant State #8 indique qu'il y a une problème quand « foob » a été détecté. En effet, pour chaque caractère différent d'un 'a', l'analyseur devra revenir en arrière pour accepter « foo ». De la même façon, le commentaire pour State #9 (état 9) concerne le moment où « fooba » a été détecté et qu'un 'r' ne suit pas.
Le commentaire final nous rappelle que il n'est pas nécessaire de passer par la difficulté de la suppression du retour en arrière des règles à moins d'utiliser -Cf ou -CF, puisqu'il n'y a pas de gain de performance à faire ceci avec des analyseurs compressés.
Le moyen de supprimer le retour arrière est d'ajouter des règles « erreur » :
%% foo return TOK_KEYWORD; foobar return TOK_KEYWORD; fooba | foob | fo { /* fausse alarme, pas réellement un mot-clé */ return TOK_ID; }
Éliminer le retour arrière parmi une liste de mots-clés peut également être effectué en utilisant une règle « attrape-tout » :
%% foo return TOK_KEYWORD; foobar return TOK_KEYWORD; [a-z]+ return TOK_ID;C'est habituellement la meilleure solution quand c'est approprié.
Les messages de retour arrière ont tendance à s'amonceler. Avec un jeu de règles compliqué, il n'est pas rare d'obtenir des centaines de messages. Si quelqu'un parvient à les déchiffrer, il ne faut qu'une douzaine de règles pour éliminer le retour arrière (bien qu'il soit facile de commettre une faute et d'avoir une règle d'erreur convenant accidentellement à un élément lexical valide. Une fonctionnalité future possible de flex sera d'ajouter automatiquement des règles pour éliminer le retour arrière).
Il est important de garder à l'esprit que vous ne bénéficierez des avantages de l'élimination du retour arrière que si vous éliminez chaque instance de retour arrière. En laisser ne serait-ce qu'une seule ne vous fait rien gagner.
Un contexte de queue variable (où à la fois les parties de tête et de queue n'ont pas une taille fixe) occasionne presque la même perte de performance que REJECT (c.-à-d. substantielle). Dès lors, quand c'est possible, une règle comme :
%% mouse|rat/(cat|dog) run();est mieux écrite :
%% mouse/cat|dog run(); rat/cat|dog run();ou comme
%% mouse|rat/cat run(); mouse|rat/dog run();Notez qu'ici l'action spéciale '|' ne fournit aucune économie, et peut même aggraver la situation (voyez Défectuosités / Bogues plus bas).
Un autre endroit où l'utilisateur peut améliorer la performance d'un analyseur (et qui est le plus facile à implémenter) provient du fait que le plus long les éléments lexicaux correspondent, le plus vite tournera l'analyseur. C'est parce qu'avec les longs éléments lexicaux, le traitement de la plupart des caractères d'entrée a lieu dans la boucle (courte) d'analyse interne, et ne doit pas souvent effectuer le travail additionnel de réglage de l'environnement d'analyse lexicale (p.ex. yytext) pour l'action. Rappelez-vous l'analyseur pour les commentaires C :
%x commentaire %% int num_ligne = 1; "/*" BEGIN(commentaire); <commentaire>[^*\n]* <commentaire>"*"+[^*/\n]* <commentaire>\n ++num_ligne; <commentaire>"*"+"/" BEGIN(INITIAL);Il pourrait être accéléré en l'écrivant comme ceci :
%x commentaire %% int num_ligne = 1; "/*" BEGIN(commentaire); <commentaire>[^*\n]* <commentaire>[^*\n]*\n ++num_ligne; <commentaire>"*"+[^*/\n]* <commentaire>"*"+[^*/\n]*\n ++num_ligne; <commentaire>"*"+"/" BEGIN(INITIAL);Maintenant, au lieu que chaque saut de ligne requière le traitement d'une autre action, la reconnaissance des sauts de ligne est « distribuée » sur les autres règles pour que le texte correspondant reste aussi long que possible. Notez que l'ajout de règles ne ralentit pas l'analyseur ! La vitesse de l'analyseur est indépendante du nombre de règles ou (modulo les considérations formulées au début de cette section) de la complexité des règles en ce qui concerne les opérateurs comme '*' et '|'.
Un exemple final de l'accélération d'un analyseur : supposez que vous vouliez examiner un fichier contenant des identificateurs et des mots-clés, un par ligne avec aucun autre caractère supplémentaire, et reconnaître tous les mots-clés. Une première approche naturelle est :
%% asm | auto | break | ... etc ... volatile | while /* c'est un mot-clé */ .|\n /* ce n'est pas un mot-clé */Pour éliminer le mouvement « à contre-courant », introduisez une règle attrape-tout :
%% asm | auto | break | ... etc ... volatile | while /* c'est un mot-clé */ [a-z]+ | .|\n /* ce n'est pas un mot-clé */Maintenant, s'il est garanti qu'il y a exactement un mot par ligne, alors nous pouvons réduire le nombre total de correspondances de moitié en fusionnant la reconnaissance des sauts de ligne avec celle des autres éléments lexicaux :
%% asm\n | auto\n | break\n | ... etc ... volatile\n | while\n /* c'est un mot-clé */ [a-z]+\n | .|\n /* ce n'est pas un mot-clé */Il faut faire très attention ici, car nous avons maintenant réintroduit du retour arrière dans l'analyseur. En particulier, alors que nous savons qu'il n'y aura jamais de caractères dans le flux d'entrée autres que des lettres ou des sauts de ligne, flex ne peut s'en rendre compte, et il planifiera le besoin éventuel de retour en arrière quand il a examiné un élément lexical comme « auto » et qu'ensuite le caractère suivant est quelque chose d'autre qu'un retour à la ligne ou une lettre. Précédemment, il ne reconnaîtrait que la règle « auto » et aurait fini, mais maintenant il n'a pas de règle « auto », seulement une règle « auto\n ». Pour éliminer la possibilité de retour arrière, nous devrions soit dupliquer toutes les règles mais sans les sauts de ligne finaux, soit, puisque nous ne nous attendons pas à rencontrer une telle entrée et par conséquent ne savons pas comment elle est classifiée, introduire une règle attrape-tout de plus, celle-ci n'incluant pas de saut de ligne :
%% asm\n | auto\n | break\n | ... etc ... volatile\n | while\n /* c'est un mot-clé */ [a-z]+\n | [a-z]+ | .|\n /* ce n'est pas un mot-clé */Compilé avec -Cf, c'est à peu près le plus (rapide) que l'on peut obtenir d'un analyseur flex pour traiter ce problème particulier.
Une note finale : flex est lent lors de la détection des NUL, en particulier quand un élément lexical contient de multiples NUL. Il vaut mieux écrire des règles qui détectent des petites petites quantités de texte si l'on s'attend à ce que le texte inclue souvent des NUL.
Une autre note finale concernant les performances : comme mentionné en haut de la section Comment l'Entrée est Reconnue, le redimensionnement dynamique de yytext pour s'adapter aux immenses éléments lexicaux est un processus lent car il requiert à l'heure actuelle que l'élément lexical (immense) soit réexaminé à partir du début. Par conséquent, si la performance est vitale, vous devriez essayer de détecter de « grandes » quantités de texte, mais pas de quantités « immenses », où la frontière entre les deux se situe environ à 8000 caractères/élément lexical.
Vous pouvez également utiliser flex pour générer une classe analyseur C++, en utilisant l'option -+ (ou, de façon équivalente, %option c++), qui est spécifiée automatiquement si le nom de l'exécutable flex se termine par un '+', comme flex++. Quand vous utilisez cette option, flex génère par défaut l'analyseur dans le fichier lex.yy.cc au lieu de lex.yy.c. L'analyseur généré inclut le fichier d'en-tête FlexLexer.h, qui définit l'interface de deux classes C++.
La première classe, FlexLexer, fournit une classe de base abstraite définissant l'interface générale de classe d'analyseur. Elle fournit les fonctions membres suivantes :
D'autres fonctions également fournies sont les fonctions membres équivalentes à yy_switch_to_buffer(), yy_create_buffer() (bien que le premier argument soit un pointeur d'objet istream* et pas un FILE*), yy_flush_buffer(), yy_delete_buffer(), et yyrestart() (à nouveau, le premier argument est un pointeur d'objet istream*).
La seconde classe définie dans FlexLexer.h est yyFlexLexer, qui est dérivée de FlexLexer. Elle définit les fonctions membres additionnelles suivantes :
En plus, yyFlexLexer définit les fonctions virtuelles protégées suivantes que vous pouvez redéfinir dans des classes dérivées pour configurer finement l'analyseur :
Notez qu'un objet yyFlexLexer contient son état d'analyse entier. Par conséquent, vous pouvez utiliser de tels objets pour créer des analyseurs réentrants. Vous pouvez instancier de multiples instances de la même classe yyFlexLexer, et vous pouvez également combiner de multiples classes d'analyseurs C++ dans le même programme en utilisant l'option -P discutée plus haut.
Finalement, notez que la fonctionnalité %array n'est pas disponible pour les classes d'analyseurs C++ ; vous devez utiliser %pointer (le défaut).
Voici un exemple d'un simple analyseur C++ :
// Un exemple de l'utilisation de la classe d'analyseurs C++ // de flex. %{ int mon_no_ligne = 0; %} chaîne \"[^\n"]+\" espbl [ \t]+ alpha [A-Za-z] chiffre [0-9] nom ({alpha}|{chiffre}|\$)({alpha}|{chiffre}|[_.\-/$])* num1 [-+]?{chiffre}+\.?([eE][-+]?{chiffre}+)? num2 [-+]?{chiffre}*\.{chiffre}+([eE][-+]?{chiffre}+)? nombre {num1}|{num2} %% {espbl} /* passer les blancs et les tabulations */ "/*" { int c; while((c = yyinput()) != 0) { if(c == '\n') ++mon_no_ligne; else if(c == '*') { if((c = yyinput()) == '/') break; else unput(c); } } } {nombre} cout << "nombre " << YYText() << '\n'; \n mon_no_ligne++; {nom} cout << "nom " << YYText() << '\n'; {chaine} cout << "chaîne " << YYText() << '\n'; %% int main( int /* argc */, char** /* argv */ ) { FlexLexer* lexer = new yyFlexLexer; while(lexer->yylex() != 0) ; return 0; }Si vous voulez créer de multiples classes (différentes) de lexer, vous devez utilisez l'attribut -P (ou l'option prefix=) pour renommer chaque yyFlexLexer en un autre xxFlexLexer. Vous pouvez ensuite inclure <FlexLexer.h> dans vos autres sources une fois par classe de lexer, en renommant d'abord yyFlexLexer comme ceci :
#undef yyFlexLexer #define yyFlexLexer xxFlexLexer #include <FlexLexer.h> #undef yyFlexLexer #define yyFlexLexer zzFlexLexer #include <FlexLexer.h>si, par exemple, vous avez utilisé %option prefix=xx pour l'un de vos analyseurs et %option prefix=zz pour l'autre.
IMPORTANT : la forme actuelle de la classe d'analyse est expérimentale et peut changer considérablement d'une version majeure de flex à l'autre.
Dans cette section, nous discutons de toutes les zones d'incompatibilité connues entre flex, lex AT&T, et la spécification POSIX.
L'option -l de flex active la compatibilité maximale avec l'implémentation originale de lex par AT&T, au prix d'une perte majeure dans la performance de l'analyseur généré. Nous notons ci-dessous quelles incompatibilités peuvent se produire lors de l'utilisation de l'option -l.
flex est entièrement compatible avec lex avec les exceptions suivantes :
fatal flex analyseur internal error--end of buffer missed(erreur fatale interne à flex -- fin du tampon manquée) Pour rendre l'analyseur réentrant, utilisez d'abord
yyrestart( yyin );Notez que cet appel jettera toute entrée mise en mémoire tampon ; habituellement ce n'est pas un problème avec un analyseur interactif.
NOM [A-Z][A-Z0-9]* %% foo{NOM}? printf( "Trouvé\n" ); %%ne reconnaîtra pas la chaîne de caractères « foo » car, quand la macro est développée, la règle est équivalente à « foo[A-Z][A-Z0-9]*? » et la priorité est telle que le '?' est associé avec « [A-Z0-9]* ». Avec flex, la règle sera développée en « foo([A-Z][A-Z0-9]*)? » et ainsi la chaîne de caractères « foo » conviendra.
%% foo|bar<espace ici> { foobar_action(); }flex ne supporte pas cette fonctionnalité.
Les fonctionnalités suivantes de flex ne sont pas incluses dans lex ou la spécification POSIX:
analyseurs C++ %option portée de condition de démarrage piles de conditions de démarrage analyseurs interactifs/non-interactifs yy_scan_string() et amies yyterminate() yy_set_interactive() yy_set_bol() YY_AT_BOL() <<EOF>> <*> YY_DECL YY_START YY_USER_ACTION YY_USER_INIT directives #line %{}'s autour des actions de multiples actions sur une ligneplus presque tous les drapeaux de flex. La dernière fonctionnalité de la liste se réfère au fait qu'avec flex, vous pouvez placer de multiples actions sur la même ligne, séparées par des points-virgules, alors qu'avec lex, le code suivant
foo handle_foo(); ++nombre_de_foos_vus;est (de façon plutôt surprenante) tronqué en
foo handle_foo();flex ne tronque pas l'action. Les actions qui ne sont pas enfermées dans des accolades sont simplement terminées à la fin de la ligne.
warning, rule cannot be matched indique que la règle donnée n'a pas pu être reconnue car elle suit d'autres règles qui reconnaîtront toujours le même texte. Par exemple, dans le code suivant, « foo » ne peut être reconnu car il vient après une règle « attrape-tout » d'identificateur :
[a-z]+ got_identifier(); foo got_foo();Utiliser REJECT dans un analyseur supprime cet avertissement.
warning, -s option given but default rule can be matched signifie qu'il est possible (peut-être seulement dans une condition de démarrage particulière) que la règle par défaut (reconnaître tout caractère unique) soit la seule qui corresponde à une entrée particulière. Puisque -s a été donné, ce n'est probablement pas ce qui était prévu.
reject_used_but_not_detected undefined ou yymore_used_but_not_detected undefined - Ces erreurs peuvent se produire au moment de la compilation. Elles indiquent que l'analyseur utilise REJECT ou yymore() mais que flex n'a pas réussi à remarquer cela, ce qui signifie que flex a recherché des occurrences de ces actions dans les deux premières sections, mais n'a pas réussi à en trouver, mais d'une façon ou d'une autre vous en ayez glissées quelques unes (via un fichier #include, par exemple). Utilisez %option reject ou %option yymore pour indiquer à flex que vous utilisez réellement ces fonctionnalités.
flex analyseur jammed - Un analyseur compilé avec -s a rencontré une chaîne de caractères d'entrée qui n'a été reconnue par aucune de ses règles. Cette erreur peut également se produire à cause de problèmes internes.
token too large, exceeds YYLMAX - votre analyseur utilise %array et une de ses règles a reconnu une chaîne de caractères plus longue que la constante YYLMAX (8 Ko octets par défaut). Vous pouvez augmenter la valeur en définissant via un #define la macro YYLMAX dans la section de définitions de votre entrée flex.
scanner requires -8 flag to use the character 'x' - La spécification de votre analyseur inclut la reconnaissance du caractère 8 bits 'x' et vous n'avez pas spécifié le drapeau -8, et votre analyseur utilisait les caractères 7 bits par défaut car vous avez utilisé les options de compression de table -Cf ou -CF. Voyez la discussion sur le drapeau -7 pour les détails.
flex analyseur push-back overflow - vous avez utilisé unput() pour repousser en entrée tellement de texte que le tampon de l'analyseur ne pouvait pas contenir à la fois le texte repoussé et l'élément lexical courant dans yytext. Idéalement, l'analyseur devrait redimensionner dynamiquement le tampon dans ce cas, mais actuellement il ne le fait pas.
input buffer overflow, can't enlarge buffer because parser uses REJECT - l'analyseur travaillait sur la mise en correspondance d'un élément lexical extrêmement grand et a eu besoin d'étendre le tampon d'entrée. Cela ne fonctionne pas avec les analyseurs qui utilisent REJECT.
fatal flex analyseur internal error--end of buffer missed Cela peut se produire dans le cas d'un analyseur où on réentre après qu'un saut lointain ait sauté en dehors du cadre d'activation de l'analyseur. Avant de réentrer dans l'analyseur, utilisez :
yyrestart( yyin );ou, comme mentionné au-dessus, utilisez la classe d'analyseurs C++.
too many start conditions in <> construct ! - vous avez listé plus de conditions de démarrage dans une construction <> qu'il n'en existe (vous devez donc avoir listé deux fois au moins l'une d'entre elles).
Certains motifs de contexte de queue ne peuvent être proprement reconnus et génèrent des messages d'avertissement ("dangerous trailing context"). Ce sont des motifs où la fin de la première partie de la règle correspond au début de la seconde partie, comme « zx*/xy* », où le 'x*' correspond au texte reconnu par de tels motifs n'est pas défini.)
Pour certaines règles de contexte de queue, les parties qui sont réellement de longueur fixe ne sont pas reconnues en tant que telles, ce qui mène à la baisse de performance mentionnée au-dessus. En particulier, les parties utilisant '|' ou {n} (comme « foo{3} ») sont toujours considérées comme ayant une longueur variable.
Combiner le contexte de queue avec l'action spéciale « | » peut résulter en ce qu'un contexte de queue fixe soit converti en un contexte de queue variable plus coûteux, comme par exemple dans le code suivant :
%% abc | xyz/def
L'utilisation de unput() invalide yytext et yyleng, à moins que la directive %array ou l'option -l n'ait été utilisée.
La reconnaissance des motifs NUL est substantiellement plus lente que la reconnaissance d'autres caractères.
Le redimensionnement du tampon d'entrée est lent, car il occasionne le réexamen de tout le texte reconnu jusqu'ici par l'élément lexical courant (généralement immense).
À cause à la fois de la mise en mémoire tampon de l'entrée, et de la lecture en avance, vous ne pouvez pas mélanger des appels à des routines de <stdio.h>, comme par exemple getchar(), avec les règles flex et vous attendre à ce que cela fonctionne. Appelez input() à la place.
L'ensemble des entrées de table listées par le drapeau -v exclut le nombre d'entrées de table nécessaire pour déterminer quelle règle a été reconnue. Le nombre d'entrées est égal au nombre d'états DFA si l'analyseur n'utilise pas REJECT, et un peu plus que le nombre d'état s'il le fait.
REJECT ne peut pas être utilisé avec les options -f ou -F.
Les algorithmes internes de flex ont besoin de documentation.
lex(1), yacc(1), sed(1), awk(1).
John Levine, Tony Mason, and Doug Brown, Lex & Yacc, O'Reilly and Associates. Assurez-vous d'avoir la 2ème édition.
M. E. Lesk and E. Schmidt, LEX - Lexical Analyzer Generator.
Alfred Aho, Ravi Sethi and Jeffrey Ullman, Compilers: Principles, Techniques and Tools, Addison-Wesley (1986). Décrit les techniques de reconnaissance de motifs utilisées par flex (automates finis déterministes).
Merci aux nombreux bêta-testeurs, aux gens qui ont fourni une rétroaction, et aux contributeurs de flex , en particulier à Francois Pinard, Casey Leedom, Robert Abramovitz, Stan Adermann, Terry Allen, David Barker-Plummer, John Basrai, Neal Becker, Nelson H.F. Beebe, benson@odi.com, Karl Berry, Peter A. Bigot, Simon Blanchard, Keith Bostic, Frederic Brehm, Ian Brockbank, Kin Cho, Nick Christopher, Brian Clapper, J.T. Conklin, Jason Coughlin, Bill Cox, Nick Cropper, Dave Curtis, Scott David Daniels, Chris G. Demetriou, Theo Deraadt, Mike Donahue, Chuck Doucette, Tom Epperly, Leo Eskin, Chris Faylor, Chris Flatters, Jon Forrest, Jeffrey Friedl, Joe Gayda, Kaveh R. Ghazi, Wolfgang Glunz, Eric Goldman, Christopher M. Gould, Ulrich Grepel, Peer Griebel, Jan Hajic, Charles Hemphill, NORO Hideo, Jarkko Hietaniemi, Scott Hofmann, Jeff Honig, Dana Hudes, Eric Hughes, John Interrante, Ceriel Jacobs, Michal Jaegermann, Sakari Jalovaara, Jeffrey R. Jones, Henry Juengst, Klaus Kaempf, Jonathan I. Kamens, Terrence O Kane, Amir Katz, ken@ken.hilco.com, Kevin B. Kenny, Steve Kirsch, Winfried Koenig, Marq Kole, Ronald Lamprecht, Greg Lee, Rohan Lenard, Craig Leres, John Levine, Steve Liddle, David Loffredo, Mike Long, Mohamed el Lozy, Brian Madsen, Malte, Joe Marshall, Bengt Martensson, Chris Metcalf, Luke Mewburn, Jim Meyering, R. Alexander Milowski, Erik Naggum, G.T. Nicol, Landon Noll, James Nordby, Marc Nozell, Richard Ohnemus, Karsten Pahnke, Sven Panne, Roland Pesch, Walter Pelissero, Gaumond Pierre, Esmond Pitt, Jef Poskanzer, Joe Rahmeh, Jarmo Raiha, Frederic Raimbault, Pat Rankin, Rick Richardson, Kevin Rodgers, Kai Uwe Rommel, Jim Roskind, Alberto Santini, Andreas Scherer, Darrell Schiebel, Raf Schietekat, Doug Schmidt, Philippe Schnoebelen, Andreas Schwab, Larry Schwimmer, Alex Siegel, Eckehard Stolz, Jan-Erik Strvmquist, Mike Stump, Paul Stuart, Dave Tallman, Ian Lance Taylor, Chris Thewalt, Richard M. Timoney, Jodi Tsai, Paul Tuinenga, Gary Weik, Frank Whaley, Gerhard Wilhelms, Kent Williams, Ken Yap, Ron Zellar, Nathan Zelle, David Zuhn, et ceux dont le nom a échappé à mes facultés marginales d'archivage de mails mais dont les contributions ont été appréciées de la même manière.
Merci à Keith Bostic, Jon Forrest, Noah Friedman, John Gilmore, Craig Leres, John Levine, Bob Mulcahy, G.T. Nicol, Francois Pinard, Rich Salz, et Richard Stallman pour leur aide relative à divers maux de tête dus à des problèmes de distribution.
Merci à Esmond Pitt et Earle Horton pour le support des caractères 8 bits ; à Benson Margulies et Fred Burke pour le support de C++ ; à Kent Williams et Tom Epperly pour le support de classes C++ ; à Ove Ewerlid pour le support des NUL ; et à Eric Hughes pour le support des tampons multiples.
Ce travail a été principalement effectué lorsque je faisais partie du Real Time Systems Group au Lawrence Berkeley Laboratory à Berkeley, CA. Merci beaucoup à tous ceux-ci pour leur aide.
Envoyez vos commentaires à vern@ee.lbl.gov.