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 utilisateur
La 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éfinition
Le « 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 action
où 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 s2
Notez 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>bar
Notez que la première ligne peut être écrite « foo/bar\n ».
foo|(bar$)
foo|^bar
Si 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 gros
Notez 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 ligne
plus 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.