NOMBRE¶
flex - generador de analizadores léxicos rápidos
SINOPSIS¶
flex [-bcdfhilnpstvwBFILTV78+? -C[aefFmr] -osalida -Pprefijo
-Sesqueleto] [--help --version] [nombrefichero ...]
INTRODUCCIÓN¶
Este manual describe
flex, una herramienta para la generación de
programas que realizan concordancia de patrones en texto. El manual incluye a
la vez secciones de tutorial y de referencia:
Descripción
una breve introducción a la herramienta
Algunos Ejemplos Simples
Formato del Fichero de Entrada
Patrones
las expresiones regulares extendidas que utiliza flex
Cómo se Empareja la Entrada
las reglas para determinar lo que ha concordado
Acciones
cómo especificar qué hacer cuando concuerde un patrón
El Escáner Generado
detalles respecto al escáner que produce flex;
cómo controlar la fuente de entrada
Condiciones de Arranque
la introdución de contexto en sus escáneres, y
conseguir "mini-escáneres"
Múltiples Buffers de Entrada
cómo manipular varias fuentes de entrada; cómo
analizar cadenas en lugar de ficheros.
Reglas de Fin-de-Fichero
reglas especiales para reconocer el final de la entrada
Macros Misceláneas
un sumario de macros disponibles para las acciones
Valores Disponibles para el Usuario
un sumario de valores disponibles para las acciones
Interfaz con Yacc
conectando escáneres de flex junto con analizadores de yacc
Opciones
opciones de línea de comando de flex, y la directiva
"%option"
Consideraciones de Rendimiento
cómo hacer que sus analizadores vayan tan rápido
como sea posible
Generando Escáneres en C++
la facilidad (experimental) para generar analizadores
léxicos como clases de C++
Incompatibilidades con Lex y POSIX
cómo flex difiere del lex de AT&T y del lex estándar
de POSIX
Diagnósticos
esos mensajes de error producidos por flex (o por
los escáneres que este genera) cuyo significado podría
no ser evidente
Ficheros
los ficheros usados por flex
Deficiencias / Errores
problemas de flex conocidos
Ver También
otra documentación, herramientas relacionadas
Autor
incluye información de contacto
DESCRIPCIÓN¶
flex es una herramienta para generar
escáneres: programas
que reconocen patrones léxicos en un texto.
flex lee los
ficheros de entrada dados, o la entrada estándar si no se le ha
indicado ningún nombre de fichero, con la descripción de un
escáner a generar. La descripción se encuentra en forma de
parejas de expresiones regulares y código C, denominadas
reglas.
flex genera como salida un fichero fuente en C,
lex.yy.c, que
define una rutina
yylex(). Este fichero se compila y se enlaza con la
librería
-lfl para producir un ejecutable. Cuando se arranca el
fichero ejecutable, este analiza su entrada en busca de casos de las
expresiones regulares. Siempre que encuentra uno, ejecuta el código C
correspondiente.
ALGUNOS EJEMPLOS SIMPLES¶
En primer lugar veremos algunos ejemplos simples para una toma de contacto con
el uso de
flex. La siguiente entrada de
flex especifica un
escáner que siempre que encuentre la cadena "username" la
reemplazará por el nombre de entrada al sistema del usuario:
%%
username printf( "%s", getlogin() );
Por defecto, cualquier texto que no reconozca el analizador léxico de
flex se copia a la salida, así que el efecto neto de este
escáner es copiar su fichero de entrada a la salida con cada
aparición de "username" expandida. En esta entrada, hay
solamente una regla. "username" es el
patrón y el
"printf" es la
acción. El "%%" marca el
comienzo de las reglas.
Aquí hay otro ejemplo simple:
int num_lineas = 0, num_caracteres = 0;
%%
\n ++num_lineas; ++num_caracteres;
. ++num_caracteres;
%%
main()
{
yylex();
printf( "# de líneas = %d, # de caracteres. = %d\n",
num_lineas, num_caracteres );
}
Este analizador cuenta el número de caracteres y el número de
líneas en su entrada (no produce otra salida que el informe final de la
cuenta). La primera línea declara dos variables globales,
"num_lineas" y "num_caracteres", que son visibles al mismo
tiempo dentro de
yylex() y en la rutina
main() declarada
después del segundo "%%". Hay dos reglas, una que empareja
una línea nueva ("\n") e incrementa la cuenta de
líneas y la cuenta de caracteres, y la que empareja cualquier caracter
que no sea una línea nueva (indicado por la expresión regular
".").
Un ejemplo algo más complicado:
/* escáner para un lenguaje de juguete al estilo de Pascal */
%{
/* se necesita esto para la llamada a atof() más abajo */
#include <math.h>
%}
DIGITO [0-9]
ID [a-z][a-z0-9]*
%%
{DIGITO}+ {
printf( "Un entero: %s (%d)\n", yytext,
atoi( yytext ) );
}
{DIGITO}+"."{DIGITO}* {
printf( "Un real: %s (%g)\n", yytext,
atof( yytext ) );
}
if|then|begin|end|procedure|function {
printf( "Una palabra clave: %s\n", yytext );
}
{ID} printf( "Un identificador: %s\n", yytext );
"+"|"-"|"*"|"/" printf( "Un operador: %s\n", yytext );
"{"[^}\n]*"}" /* se come una linea de comentarios */
[ \t\n]+ /* se come los espacios en blanco */
. printf( "Caracter no reconocido: %s\n", yytext );
%%
main( argc, argv )
int argc;
char **argv;
{
++argv, --argc; /* se salta el nombre del programa */
if ( argc > 0 )
yyin = fopen( argv[0], "r" );
else
yyin = stdin;
yylex();
}
Esto podría ser los comienzos de un escáner simple para un
lenguaje como Pascal. Este identifica diferentes tipos de
tokens e
informa a cerca de lo que ha visto.
Los detalles de este ejemplo se explicarán en las secciones siguientes.
El fichero de entrada de
flex está compuesto de tres secciones,
separadas por una línea donde aparece únicamente un
%% en
esta:
definiciones
%%
reglas
%%
código de usuario
La sección de
definiciones contiene declaraciones de definiciones
de
nombres sencillas para simplificar la especificación del
escáner, y declaraciones de
condiciones de arranque, que se
explicarán en una sección posterior.
Las definiciones de nombre tienen la forma:
nombre definición
El "nombre" es una palabra que comienza con una letra o un subrayado
('_') seguido por cero o más letras, dígitos, '_', o '-'
(guión). La definición se considera que comienza en el primer
caracter que no sea un espacio en blanco siguiendo al nombre y continuando
hasta el final de la línea. Posteriormente se puede hacer referencia a
la definición utilizando "{nombre}", que se expandirá
a "(definición)". Por ejemplo,
DIGITO [0-9]
ID [a-z][a-z0-9]*
define "DIGITO" como una expresión regular que empareja un
dígito sencillo, e "ID" como una expresión regular que
empareja una letra seguida por cero o más letras o dígitos. Una
referencia posterior a
{DIGITO}+"."{DIGITO}*
es idéntica a
([0-9])+"."([0-9])*
y empareja uno o más dígitos seguido por un '.' seguido por cero o
más dígitos.
La sección de
reglas en la entrada de
flex contiene una
serie de reglas de la forma:
patrón acción
donde el patrón debe estar sin sangrar y la acción debe comenzar
en la misma línea.
Ver más abajo para una descripción más amplia sobre
patrones y acciones.
Finalmente, la sección de código de usuario simplemente se copia a
lex.yy.c literalmente. Esta sección se utiliza para rutinas de
complemento que llaman al escáner o son llamadas por este. La presencia
de esta sección es opcional; Si se omite, el segundo
%% en el
fichero de entrada se podría omitir también.
En las secciones de definiciones y reglas, cualquier texto
sangrado o
encerrado entre
%{ y
%} se copia íntegramente a la salida
(sin los %{}'s). Los %{}'s deben aparecer sin sangrar en líneas
ocupadas únicamente por estos.
En la sección de reglas, cualquier texto o %{} sangrado que aparezca
antes de la primera regla podría utilizarse para declarar variables que
son locales a la rutina de análisis y (después de las
declaraciones) al código que debe ejecutarse siempre que se entra a la
rutina de análisis. Cualquier otro texto sangrado o %{} en la
sección de reglas sigue copiándose a la salida, pero su
significado no está bien definido y bien podría causar errores
en tiempo de compilación (esta propiedad se presenta para conformidad
con
POSIX ; ver más abajo para otras características
similares)
En la sección de definiciones (pero no en la sección de reglas),
un comentario sin sangría (es decir, una línea comenzando con
"/*") también se copia literalmente a la salida hasta el
próximo "*/".
PATRONES¶
Los patrones en la entrada se escriben utilizando un conjunto extendido de
expresiones regulares. Estas son:
x empareja el caracter 'x'
. cualquier caracter (byte) excepto una línea nueva
[xyz] una "clase de caracteres"; en este caso, el patrón
empareja una 'x', una 'y', o una 'z'
[abj-oZ] una "clase de caracteres" con un rango; empareja
una 'a', una 'b', cualquier letra desde la 'j'
hasta la 'o', o una 'Z'
[^A-Z] una "clase de caracteres negada", es decir, cualquier
caracter menos los que aparecen en la clase. En
este caso, cualquier caracter EXCEPTO una letra
mayúscula.
[^A-Z\n] cualquier caracter EXCEPTO una letra mayúscula o
una línea nueva
r* cero o más r's, donde r es cualquier expresión regular
r+ una o más r's
r? cero o una r (es decir, "una r opcional")
r{2,5} donde sea de dos a cinco r's
r{2,} dos o más r's
r{4} exactamente 4 r's
{nombre} la expansión de la definición de "nombre"
(ver más abajo)
"[xyz]\"foo"
la cadena literal: [xyz]"foo
\X si X es una 'a', 'b', 'f', 'n', 'r', 't', o 'v',
entonces la interpretación ANSI-C de \x.
En otro caso, un literal 'X' (usado para
indicar operadores tales como '*')
\0 un caracter NUL (código ASCII 0)
\123 el caracter con valor octal 123
\x2a el caracter con valor hexadecimal 2a
(r) empareja una r; los paréntesis se utilizan para
anular la precedencia (ver más abajo)
rs la expresión regular r seguida por la expresión
regular s; se denomina "concatenación"
r|s bien una r o una s
r/s una r pero sólo si va seguida por una s. El
texto emparejado por s se incluye cuando se
determina si esta regla es el "emparejamiento
más largo", pero se devuelve entonces a la
entrada antes que se ejecute la acción. Así
que la acción sólo ve el texto emparejado
por r. Este tipo de patrones se llama
"de contexto posterior".
(Hay algunas combinaciones de r/s que flex
no puede emparejar correctamente; vea las notas
en la sección Deficiencias / Errores más abajo
respecto al "contexto posterior peligroso".)
^r una r, pero sólo al comienzo de una línea (es
decir, justo al comienzo del análisis, o a la
derecha después de que se haya analizado una
línea nueva).
r$ una r, pero sólo al final de una línea (es decir,
justo antes de una línea nueva). Equivalente
a "r/\n".
Fíjese que la noción de flex de una "línea nueva"
es exáctamente lo que el compilador de C utilizado
para compilar flex interprete como '\n'; en
particular, en algunos sistemas DOS debe filtrar
los \r's de la entrada used mismo, o explícitamente
usar r/\r\n para "r$".
<s>r una r, pero sólo en la condición de arranque s
(ver más abajo para una discusión sobre las
condiciones de arranque)
<s1,s2,s3>r
lo mismo, pero en cualquiera de las condiciones
de arranque s1, s2, o s3
<*>r una r en cualquier condición de arranque, incluso
una exclusiva.
<<EOF>> un fin-de-fichero
<s1,s2><<EOF>>
un fin-de-fichero en una condición de arranque s1 o s2
Fíjese que dentro de una clase de caracteres, todos los operadores de
expresiones regulares pierden su significado especial excepto el caracter de
escape ('\') y los operadores de clase de caracteres, '-',
Las expresiones regulares en el listado anterior están agrupadas de
acuerdo a la precedencia, desde la precedencia más alta en la cabeza a
la más baja al final. Aquellas agrupadas conjuntamente tienen la misma
precedencia. Por ejemplo,
foo|bar*
es lo mismo que
(foo)|(ba(r*))
ya que el operador '*' tiene mayor precedencia que la concatenación, y la
concatenación más alta que el operador '|'. Este patrón
por lo tanto empareja
bien la cadena "foo"
o la cadena
"ba" seguida de cero o más r's. Para emparejar
"foo" o, cero o más "bar"'s, use:
foo|(bar)*
y para emparejar cero o más "foo"'s o "bar"'s:
(foo|bar)*
Además de caracteres y rangos de caracteres, las clases de caracteres
pueden también contener
expresiones de clases de caracteres. Son
expresiones encerradas entre los delimitadores
[: y
:] (que
también deben aparecer entre el '[' y el ']' de la clase de caracteres;
además pueden darse otros elementos dentro de la clase de caracteres).
Las expresiones válidas son:
[:alnum:] [:alpha:] [:blank:]
[:cntrl:] [:digit:] [:graph:]
[:lower:] [:print:] [:punct:]
[:space:] [:upper:] [:xdigit:]
Todas estas expresiones designan un conjunto de caracteres equivalentes a la
correspondiente función estándar
isXXX de C. Por ejemplo,
[:alnum:] designa aquellos caracteres para los cuales
isalnum()
devuelve verdadero - esto es, cualquier caracter alfabético o
numérico. Algunos sistemas no ofrecen
isblank(), así que
flex define
[:blank:] como un espacio en blanco o un tabulador.
Por ejemplo, las siguientes clases de caracteres son todas equivalentes:
[[:alnum:]]
[[:alpha:][:digit:]]
[[:alpha:]0-9]
[a-zA-Z0-9]
Si su escáner ignora la distinción entre mayúsculas y
minúsculas (la bandera
-i ), entonces
[:upper:] y
[:lower:] son equivalentes a
[:alpha:].
Algunas notas sobre los patrones:
- -
- Una clase de caracteres negada tal como el ejemplo "[^A-Z]"
anterior emparejará una línea nueva a menos que
"\n" (o una secuencia de escape equivalente) sea uno de los
caracteres presentes explícitamente en la clase de caracteres
negada (p.ej., "[^A-Z\n]"). Esto es diferente a cómo
muchas de las otras herramientas de expresiones regulares tratan las
clases de caracteres negadas, pero desafortunadamente la inconsistencia
está fervientemente enrraizada históricamente. Emparejar
líneas nuevas significa que un patrón como [^"]* puede
emparejar la entrada completa a menos que haya otra comilla en la
entrada.
- -
- Una regla puede tener lo más una instancia del contexto posterior
(el operador '/' o el operador '$'). La condición de arranque, los
patrones '^', y "<<EOF>>" pueden aparecer solamente
al principio de un patrón, y, al igual que con '/' y '$', no pueden
agruparse dentro de paréntesis. Un '^' que no aparezca al principio
de una regla o un '$' que no aparezca al final de una regla pierde sus
propiedades especiales y es tratado como un caracter normal.
- Lo siguiente no está permitido:
foo/bar$
<sc1>foo<sc2>bar
Fíjese que la primera regla se puede escribir como
"foo/bar\n".
- En el siguiente ejemplo un '$' o un '^' es tratado como un caracter
normal:
foo|(bar$)
foo|^bar
Si lo que se desea es un "foo" o un "bar" seguido de una
línea nueva, puede usarse lo siguiente (la acción especial
'|' se explica más abajo):
foo |
bar$ /* la acción va aquí */
Un truco parecido funcionará para emparejar un "foo" o, un
"bar" al principio de una línea.
CÓMO SE EMPAREJA LA ENTRADA¶
Cuando el escáner generado está funcionando, este analiza su
entrada buscando cadenas que concuerden con cualquiera de sus patrones. Si
encuentra más de un emparejamiento, toma el que empareje más
texto (para reglas de contexto posterior, se incluye la longitud de la parte
posterior, incluso si se devuelve a la entrada). Si encuentra dos o más
emparejamientos de la misma longitud, se escoge la regla listada en primer
lugar en el fichero de entrada de
flex.
Una vez que se determina el emparejamiento, el texto correspondiente al
emparejamiento (denominado el
token) está disponible en el
puntero a caracter global
yytext, y su longitud en la variable global
entera
yyleng. Entonces la
acción correspondiente al
patrón emparejado se ejecuta (una descripción más
detallada de las acciones viene a continuación), y entonces la entrada
restante se analiza para otro emparejamiento.
Si no se encuentra un emparejamiento, entonces se ejecuta la
regla por
defecto: el siguiente caracter en la entrada se considera reconocido y se
copia a la salida estándar. Así, la entrada válida
más simple de
flex es:
%%
que genera un escáner que simplemente copia su entrada (un caracter a la
vez) a la salida.
Fíjese que
yytext se puede definir de dos maneras diferentes: bien
como un
puntero a caracter o como un
array de caracteres. Usted
puede controlar la definición que usa
flex incluyendo una de las
directivas especiales
%pointer o
%array en la primera
sección (definiciones) de su entrada de flex. Por defecto es
%pointer, a menos que use la opción de compatibilidad
-l,
en cuyo caso
yytext será un array. La ventaja de usar
%pointer es un análisis substancialmente más
rápido y la ausencia de desbordamiento del buffer cuando se emparejen
tokens muy grandes (a menos que se agote la memoria dinámica). La
desventaja es que se encuentra restringido en cómo sus acciones pueden
modificar
yytext (vea la siguiente sección), y las llamadas a la
función
unput() destruyen el contenido actual de
yytext,
que puede convertirse en un considerable quebradero de cabeza de portabilidad
al cambiar entre diferentes versiones de
lex.
La ventaja de
%array es que entoces puede modificar
yytext todo lo
que usted quiera, las llamadas a
unput() no destruyen
yytext
(ver más abajo). Además, los programas de
lex existentes
a veces acceden a
yytext externamente utilizando declaraciones de la
forma:
extern char yytext[];
Esta definición es errónea cuando se utiliza
%pointer, pero
correcta para
%array.
%array define a
yytext como un array de
YYLMAX caracteres,
que por defecto es un valor bastante grande. Usted puede cambiar el
tamaño símplemente definiendo con #define a
YYLMAX con un
valor diferente en la primera sección de su entrada de
flex.
Como se mencionó antes, con
%pointer yytext crece
dinámicamente para acomodar tokens grandes. Aunque esto signifique que
con
%pointer su escáner puede acomodar tokens muy grandes (tales
como emparejar bloques enteros de comentarios), tenga presente que cada vez
que el escáner deba cambiar el tamaño de
yytext
también debe reiniciar el análisis del token entero desde el
principio, así que emparejar tales tokens puede resultar lento. Ahora
yytext no crece dinámicamente si una llamada a
unput() hace que se deba devolver demasiado texto; en su lugar, se
produce un error en tiempo de ejecución.
También tenga en cuenta que no puede usar
%array en los
analizadores generados como clases de C++ (la opción
c++; vea
más abajo).
ACCIONES¶
Cada patrón en una regla tiene una acción asociada, que puede ser
cualquier sentencia en C. El patrón finaliza en el primer caracter de
espacio en blanco que no sea una secuencia de escape; lo que queda de la
línea es su acción. Si la acción está
vacía, entonces cuando el patrón se empareje el token de entrada
simplemente se descarta. Por ejemplo, aquí está la
especificación de un programa que borra todas las apariciones de
"zap me" en su entrada:
%%
"zap me"
(Este copiará el resto de caracteres de la entrada a la salida ya que
serán emparejados por la regla por defecto.)
Aquí hay un programa que comprime varios espacios en blanco y tabuladores
a un solo espacio en blanco, y desecha los espacios que se encuentren al final
de una línea:
%%
[ \t]+ putchar( ' ' );
[ \t]+$ /* ignora este token */
Si la acción contiene un '{', entonces la acción abarca hasta que
se encuentre el correspondiente '}', y la acción podría entonces
cruzar varias líneas.
flex es capaz de reconocer las cadenas y
comentarios de C y no se dejará engañar por las llaves que
encuentre dentro de estos, pero aun así también permite que las
acciones comiencen con
%{ y considerará que la acción es
todo el texto hasta el siguiente
%} (sin tener en cuenta las llaves
ordinarias dentro de la acción).
Una acción que consista sólamente de una barra vertical ('|')
significa "lo mismo que la acción para la siguiente regla."
Vea más abajo para una ilustración.
Las acciones pueden incluir código C arbitrario, incuyendo sentencias
return para devolver un valor desde cualquier rutina llamada
yylex(). Cada vez que se llama a
yylex() esta continúa
procesando tokens desde donde lo dejó la última vez hasta que o
bien llegue al final del fichero o ejecute un return.
Las acciones tienen libertad para modificar
yytext excepto para alargarla
(añadiendo caracteres al final--esto sobreescribirá más
tarde caracteres en el flujo de entrada). Sin embargo esto no se aplica cuando
se utiliza
%array (ver arriba); en ese caso,
yytext
podría modificarse libremente de cualquier manera.
Las acciones tienen libertad para modificar
yyleng excepto que estas no
deberían hacerlo si la acción también incluye el uso de
yymore() (ver más abajo).
Hay un número de directivas especiales que pueden incluirse dentro de una
acción:
- -
- ECHO copia yytext a la salida del escáner.
- -
- BEGIN seguido del nombre de la condición de arranque pone al
escáner en la condición de arranque correspondiente (ver
más abajo).
- -
- REJECT ordena al escáner a que proceda con la "segunda
mejor" regla que concuerde con la entrada (o un prefijo de la
entrada). La regla se escoge como se describió anteriormente en
"Cómo se Empareja la Entrada", y yytext e
yyleng se ajustan de forma apropiada. Podría ser una que
empareje tanto texto como la regla escogida originalmente pero que viene
más tarde en el fichero de entrada de flex, o una que
empareje menos texto. Por ejemplo, lo que viene a continuación
contará las palabras en la entrada y llamará a la rutina
especial() siempre que vea "frob":
int contador_palabras = 0;
%%
frob especial(); REJECT;
[^ \t\n]+ ++contador_palabras;
Sin el REJECT, cualquier número de "frob"'s en la
entrada no serían contados como palabras, ya que el escáner
normalmente ejecuta solo una acción por token. Se permite el uso de
múltiples REJECT's, cada uno buscando la siguiente mejor
elección a la regla que actualmente esté activa. Por
ejemplo, cuando el siguiente escáner analice el token
"abcd", este escribirá "abcdabcaba" a la
salida:
%%
a |
ab |
abc |
abcd ECHO; REJECT;
.|\n /* se come caracteres sin emparejar */
(Las primeras tres reglas comparten la acción de la cuarta ya que
estas usan la acción especial '|'.) REJECT es una propiedad
particularmente cara en términos de rendimiento del escáner;
si se usa en cualquiera de las acciones del escáner esta
ralentizará todo el proceso de emparejamiento del
escáner. Además, REJECT no puede usarse con las
opciones -Cf o -CF (ver más abajo).
- Fíjese también que a diferencia de las otras acciones
especiales, REJECT es una bifurcación; el
código que la siga inmediatamente en la acción no
será ejecutado.
- -
- yymore() dice al escáner que la próxima vez que
empareje una regla, el token correspondiente debe ser
añadido tras el valor actual de yytext en lugar de
reemplazarlo. Por ejemplo, dada la entrada "mega-klugde" lo que
viene a continuación escribirá "mega-mega-kludge"
a la salida:
%%
mega- ECHO; yymore();
kludge ECHO;
El primer "mega-" se empareja y se repite a la salida. Entonces se
empareja "kludge", pero el "mega-" previo aún
está esperando al inicio de yytext asi que el ECHO
para la regla del "kludge" realmente escribirá
"mega-kludge".
Dos notas respecto al uso de
yymore(). Primero,
yymore() depende
de que el valor de
yyleng refleje correctamente el tamaño del
token actual, así que no debe modificar
yyleng si está
utilizando
yymore(). Segundo, la presencia de
yymore() en la
acción del escáner implica una pequeña
penalización de rendimiento en la velocidad de emparejamiento del
escáner.
- -
- yyless(n) devuelve todos excepto los primeros n caracteres
del token actual de nuevo al flujo de entrada, donde serán
reanalizados cuando el escáner busque el siguiente emparejamiento.
yytext e yyleng se ajustan de forma adecuada (p.ej.,
yyleng no será igual a n ). Por ejemplo, con la
entrada "foobar" lo que viene a continuación
escribirá "foobarbar":
%%
foobar ECHO; yyless(3);
[a-z]+ ECHO;
Un argumento de 0 para yyless hará que la cadena de entrada
actual sea analizada por completo de nuevo. A menos que haya cambiado la
manera en la que el escáner procese de ahora en adelante su entrada
(utilizando BEGIN, por ejemplo), esto producirá un bucle sin
fin.
Fíjese que
yyless es una macro y puede ser utilizada solamente en
el fichero de entrada de flex, no desde otros ficheros fuente.
- -
- unput(c) pone el caracter c de nuevo en el flujo de entrada.
Este será el próximo caracter analizado. La siguiente
acción tomará el token actual y hará que se vuelva a
analizar pero encerrado entre paréntesis.
{
int i;
/* Copia yytext porque unput() desecha yytext */
char *yycopia = strdup( yytext );
unput( ')' );
for ( i = yyleng - 1; i >= 0; --i )
unput( yycopia[i] );
unput( '(' );
free( yycopia );
}
Fíjese que ya que cada unput() pone el caracter dado de nuevo
al principio del flujo de entrada, al devolver cadenas de
caracteres se debe hacer de atrás hacia delante.
Un problema potencial importante cuando se utiliza
unput() es que si
está usando
%pointer (por defecto), una llamada a
unput()
destruye el contenido de
yytext, comenzando con su caracter
más a la derecha y devorando un caracter a la izquierda con cada
llamada. Si necesita que se preserve el valor de yytext después de una
llamada a
unput() (como en el ejemplo anterior), usted debe o bien
copiarlo primero en cualquier lugar, o construir su escáner usando
%array
(ver Cómo se Empareja la Entrada).
Finalmente, note que no puede devolver
EOF para intentar marcar el flujo
de entrada con un fin-de-fichero.
- -
- input() lee el próximo caracter del flujo de entrada. Por
ejemplo, lo que viene a continuación es una manera de comerse los
comentarios en C:
%%
"/*" {
register int c;
for ( ; ; )
{
while ( (c = input()) != '*' &&
c != EOF )
; /* se come el texto del comentario */
if ( c == '*' )
{
while ( (c = input()) == '*' )
;
if ( c == '/' )
break; /* encontró el final */
}
if ( c == EOF )
{
error( "EOF en comentario" );
break;
}
}
}
(Fíjese que si el escáner se compila usando C++,
entonces a input() se le hace referencia con yyinput(), para
evitar una colisión de nombre con el flujo de C++ por el
nombre input.)
- -
- YY_FLUSH_BUFFER vacía el buffer interno del escáner
de manera que la próxima vez que el escáner intente
emparejar un token, este primero rellenará el buffer usando
YY_INPUT (ver El Escáner Generado, más abajo). Esta
acción es un caso especial de la función más general
yy_flush_buffer(), descrita más abajo en la sección
Múltiples Buffers de Entrada.
- -
- yyterminate() se puede utilizar en lugar de una sentencia de
retorno en una acción. Esta hace que finalice el escáner y
retorne un 0 a quien haya llamado al escáner, indicando que
"todo está hecho". Por defecto, también se llama a
yyterminate() cuando se encuentra un fin-de-fichero. Esta es una
macro y podría ser redefinida.
El Escáner Generado¶
La salida de
flex es el fichero
lex.yy.c, que contiene la rutina
de análisis
yylex(), un número de tablas usadas por esta
para emparejar tokens, y un número de rutinas auxiliares y macros. Por
defecto,
yylex() se declara así
int yylex()
{
... aquí van varias definiciones y las acciones ...
}
(Si su entorno acepta prototipos de funciones, entonces este será
"int yylex( void )"). Esta definición podría
modificarse definiendo la macro "YY_DECL". Por ejemplo,
podría utilizar:
#define YY_DECL float lexscan( a, b ) float a, b;
para darle a la rutina de análisis el nombre
lexscan, que devuelve
un real, y toma dos reales como argumentos. Fíjese que si pone
argumentos a la rutina de análisis usando una declaración de
función no-prototipada/tipo-K&R, debe hacer terminar la
definición con un punto y coma (;).
Siempre que se llame a
yylex(), este analiza tokens desde el fichero de
entrada global
yyin (que por defecto es igual a stdin). La
función continúa hasta que alcance el final del fichero (punto
en el que devuelve el valor 0) o una de sus acciones ejecute una sentencia
return.
Si el escáner alcanza un fin-de-fichero, entonces el comportamiento en
las llamadas posteriores está indefinido a menos que o bien
yyin
apunte a un nuevo fichero de entrada (en cuyo caso el análisis
continúa a partir de ese fichero), o se llame a
yyrestart().
yyrestart() toma un argumento, un puntero
FILE * (que puede ser
nulo, si ha preparado a
YY_INPUT para que analice una fuente distinta a
yyin), e inicializa
yyin para que escanee ese fichero.
Esencialmente no hay diferencia entre la asignación a
yyin de un
nuevo fichero de entrada o el uso de
yyrestart() para hacerlo; esto
último está disponible por compatibilidad con versiones
anteriores de
flex, y porque puede utilizarse para conmutar ficheros de
entrada en medio del análisis. También se puede utilizar para
desechar el buffer de entrada actual, invocándola con un argumento
igual a
yyin; pero mejor es usar
YY_FLUSH_BUFFER (ver más
arriba). Fíjese que
yyrestart() no reinicializa la
condición de arranque a
INITIAL (ver Condiciones de Arranque,
más abajo).
Si
yylex() para el análisis debido a la ejecución de una
sentencia
return en una de las acciones, el analizador podría
ser llamado de nuevo y este reanudaría el análisis donde lo
dejó.
Por defecto (y por razones de eficiencia), el analizador usa lecturas por
bloques en lugar de simples llamadas a
getc() para leer caracteres
desde
yyin. La manera en la que toma su entrada se puede controlar
definienfo la macro
YY_INPUT. La secuencia de llamada para YY_INPUT es
"YY_INPUT(buf,result,max_size)". Su acción es poner hasta
max_size caracteres en el array de caracteres
buf y devolver en
la variable entera
result bien o el número de caracteres
leídos o la constante YY_NULL (0 en sistemas Unix) para indicar EOF.
Por defecto YY_INPUT lee desde la variable global puntero a fichero
"yyin".
Una definición de ejemplo para YY_INPUT (en la sección de
definiciones del fichero de entrada) es:
%{
#define YY_INPUT(buf,result,max_size) \
{ \
int c = getchar(); \
result = (c == EOF) ? YY_NULL : (buf[0] = c, 1); \
}
%}
Esta definición cambiará el procesamiento de la entrada para que
suceda un caracter a la vez.
Cuando el analizador reciba una indicación de fin-de-fichero desde
YY_INPUT, entonces esta comprueba la función
yywrap(). Si
yywrap() devuelve falso (cero), entonces se asume que la función
ha ido más allá y ha preparado
yyin para que apunte a
otro fichero de entrada, y el análisis continúa. Si este retorna
verdadero (no-cero), entonces el analizador termina, devolviendo un 0 a su
invocador. Fíjese que en cualquier caso, la condición de
arranque permanece sin cambios; esta
no vuelve a ser
INITIAL.
Si no proporciona su propia versión de
yywrap(), entonces debe
bien o usar
%option noyywrap (en cuyo caso el analizador se comporta
como si
yywrap() devolviera un 1), o debe enlazar con
-lfl para
obtener la versión por defecto de la rutina, que siempre devuelve un 1.
Hay disponibles tres rutinas para analizar desde buffers de memoria en lugar de
desde ficheros:
yy_scan_string(), yy_scan_bytes(), e
yy_scan_buffer(). Las trataremos en la sección Múltiples
Buffers de Entrada.
El analizador escribe su salida con
ECHO a la variable global
yyout (por defecto, stdout), que el usuario podría redefinir
asignándole cualquier otro puntero a
FILE.
CONDICIONES DE ARRANQUE¶
flex dispone de un mecanismo para activar reglas condicionalmente.
Cualquier regla cuyo patrón se prefije con "<sc>"
únicamente estará activa cuando el analizador se encuentre en la
condición de arranque llamada "sc". Por ejemplo,
<STRING>[^"]* { /* se come el cuerpo de la cadena ... */
...
}
estará activa solamente cuando el analizador esté en la
condición de arranque "STRING", y
<INITIAL,STRING,QUOTE>\. { /* trata una secuencia de escape ... */
...
}
estará activa solamente cuando la condición de arranque actual sea
o bien "INITIAL", "STRING", o "QUOTE".
Las condiciones de arranque se declaran en la (primera) sección de
definiciones de la entrada usando líneas sin sangrar comenzando con
%s o
%x seguida por una lista de nombres. Lo primero declara
condiciones de arranque
inclusivas, lo último condiciones de
arranque
exclusivas. Una condición de arranque se activa
utilizando la acción
BEGIN. Hasta que se ejecute la
próxima acción
BEGIN, las reglas con la condición
de arranque dada estarán activas y las reglas con otras condiciones de
arranque estarán inactivas. Si la condición de arranque es
inclusiva, entonces las reglas sin condiciones de arranque
también estarán activas. Si es
exclusiva, entonces
sólamente las reglas calificadas con la condición de
arranque estarán activas. Un conjunto de reglas dependientes de la
misma condición de arranque exclusiva describe un analizador que es
independiente de cualquiera de las otras reglas en la entrada de
flex.
Debido a esto, las condiciones de arranque exclusivas hacen fácil la
especificación de "mini-escáneres" que analizan
porciones de la entrada que son sintácticamente diferentes al resto
(p.ej., comentarios).
Si la distinción entre condiciones de arranque inclusivas o exclusivas es
aún un poco vaga, aquí hay un ejemplo simple que ilustra la
conexión entre las dos. El conjunto de reglas:
%s ejemplo
%%
<ejemplo>foo hacer_algo();
bar algo_mas();
es equivalente a
%x ejemplo
%%
<ejemplo>foo hacer_algo();
<INITIAL,ejemplo>bar algo_mas();
Sin el calificador
<INITIAL,example>, el patrón
bar
en el segundo ejemplo no estará activo (es decir, no puede emparejarse)
cuando se encuentre en la condición de arranque
example. Si
hemos usado
<example> para calificar
bar, aunque, entonces
este únicamente estará activo en
example y no en
INITIAL, mientras que en el primer ejemplo está activo en ambas,
porque en el primer ejemplo la condición de arranque
example es
una condición de arranque
inclusiva (%s).
Fíjese también que el especificador especial de la
condición de arranque
<*> empareja todas las condiciones
de arranque. Así, el ejemplo anterior también pudo haberse
escrito;
%x ejemplo
%%
<ejemplo>foo hacer_algo();
<*>bar algo_mas();
La regla por defecto (hacer un
ECHO con cualquier caracter sin emparejar)
permanece activa en las condiciones de arranque. Esta es equivalente a:
<*>.|\n ECHO;
BEGIN(0) retorna al estado original donde solo las reglas sin condiciones
de arranque están activas. Este estado también puede referirse a
la condición de arranque "INITIAL", así que
BEGIN(INITIAL) es equivalente a
BEGIN(0). (No se requieren los
paréntesis alrededor del nombre de la condición de arranque pero
se considera de buen estilo.)
Las acciones
BEGIN pueden darse también como código
sangrado al comienzo de la sección de reglas. Por ejemplo, lo que viene
a continuación hará que el analizador entre en la
condición de arranque "ESPECIAL" siempre que se llame a
yylex() y la variable global
entra_en_especial sea verdadera:
int entra_en_especial;
%x ESPECIAL
%%
if ( entra_en_especial )
BEGIN(ESPECIAL);
<ESPECIAL>blablabla
...más reglas a continuación...
Para ilustrar los usos de las condiciones de arranque, aquí hay un
analizador que ofrece dos interpretaciones diferentes para una cadena como
"123.456". Por defecto este la tratará como tres tokens, el
entero "123", un punto ('.'), y el entero "456". Pero si
la cadena viene precedida en la línea por la cadena
"espera-reales" este la tratará como un único token,
el número en coma flotante 123.456:
%{
#include <math.h>
%}
%s espera
%%
espera-reales BEGIN(espera);
<espera>[0-9]+"."[0-9]+ {
printf( "encontró un real, = %f\n",
atof( yytext ) );
}
<espera>\n {
/* este es el final de la línea,
* así que necesitamos otro
* "espera-numero" antes de
* que volvamos a reconocer más
* números
*/
BEGIN(INITIAL);
}
[0-9]+ {
printf( "encontró un entero, = %d\n",
atoi( yytext ) );
}
"." printf( "encontró un punto\n" );
Aquí está un analizador que reconoce (y descarta) comentarios de C
mientras mantiene una cuenta de la línea actual de entrada.
%x comentario
%%
int num_linea = 1;
"/*" BEGIN(comentario);
<comentario>[^*\n]* /* come todo lo que no sea '*' */
<comentario>"*"+[^*/\n]* /* come '*'s no seguidos por '/' */
<comentario>\n ++num_linea;
<comentario>"*"+"/" BEGIN(INITIAL);
Este analizador se complica un poco para emparejar tanto texto como le sea
posible en cada regla. En general, cuando se intenta escribir un analizador de
alta velocidad haga que cada regla empareje lo más que pueda, ya que
esto es un buen logro.
Fíjese que los nombres de las condiciones de arranque son realmente
valores enteros y pueden ser almacenados como tales. Así, lo anterior
podría extenderse de la siguiente manera:
%x comentario foo
%%
int num_linea = 1;
int invocador_comentario;
"/*" {
invocador_comentario = INITIAL;
BEGIN(comentario);
}
...
<foo>"/*" {
invocador_comentario = foo;
BEGIN(comentario);
}
<comentario>[^*\n]* /* se come cualquier cosa que no sea un '*' */
<comentario>"*"+[^*/\n]* /* se come '*'s que no continuen con '/'s */
<comentario>\n ++num_linea;
<comentario>"*"+"/" BEGIN(invocador_comentario);
Además, puede acceder a la condición de arranque actual usando la
macro de valor entero
YY_START. Por ejemplo, las asignaciones
anteriores a
invocador_comentario podrían escribirse en su lugar
como
invocador_comentario = YY_START;
Flex ofrece
YYSTATE como un alias para
YY_START (ya que es lo que
usa
lex de AT&T).
Fíjese que las condiciones de arranque no tienen su propio espacio de
nombres; los %s's y %x's declaran nombres de la misma manera que con
#define's.
Finalmente, aquí hay un ejemplo de cómo emparejar cadenas entre
comillas al estilo de C usando condiciones de arranque exclusivas, incluyendo
secuencias de escape expandidas (pero sin incluir la comprobación de
cadenas que son demasiado largas):
%x str
%%
char string_buf[MAX_STR_CONST];
char *string_buf_ptr;
\" string_buf_ptr = string_buf; BEGIN(str);
<str>\" { /* se vio la comilla que cierra - todo está hecho */
BEGIN(INITIAL);
*string_buf_ptr = '\0';
/* devuelve un tipo de token de cadena constante y
* el valor para el analizador sintáctico
*/
}
<str>\n {
/* error - cadena constante sin finalizar */
/* genera un mensaje de error */
}
<str>\\[0-7]{1,3} {
/* secuencia de escape en octal */
int resultado;
(void) sscanf( yytext + 1, "%o", &resultado );
if ( resultado > 0xff )
/* error, constante fuera de rango */
*string_buf_ptr++ = resultado;
}
<str>\\[0-9]+ {
/* genera un error - secuencia de escape errónea;
* algo como '\48' o '\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++;
}
A menudo, como en alguno de los ejemplos anteriores, uno acaba escribiendo un
buen número de reglas todas precedidas por la(s) misma(s)
condición(es) de arranque. Flex hace esto un poco más
fácil y claro introduciendo la noción de
ámbito de
la condición de arranque. Un ámbito de condición de
arranque comienza con:
<SCs>{
Donde
SCs es una lista de una o más condiciones de arranque.
Dentro del ámbito de la condición de arranque, cada regla
automáticamente tiene el prefijo
<SCs> aplicado a esta,
hasta un
'}' que corresponda con el
'{' inicial. Así, por
ejemplo,
<ESC>{
"\\n" return '\n';
"\\r" return '\r';
"\\f" return '\f';
"\\0" return '\0';
}
es equivalente a:
<ESC>"\\n" return '\n';
<ESC>"\\r" return '\r';
<ESC>"\\f" return '\f';
<ESC>"\\0" return '\0';
Los ámbitos de las condiciones de arranque pueden anidarse.
Están disponibles tres rutinas para manipular pilas de condiciones de
arranque:
- void yy_push_state(int new_state)
- empuja la condición de arranque actual al tope de la pila de las
condiciones de arranque y cambia a new_state como si hubiera
utilizado BEGIN new_state (recuerde que los nombres de las
condiciones de arranque también son enteros).
- void yy_pop_state()
- extrae el tope de la pila y cambia a este mediante un BEGIN.
- int yy_top_state()
- devuelve el tope de la pila sin alterar el contenido de la pila.
La pila de las condiciones de arranque crece dinámicamente y por ello no
tiene asociada ninguna limitación de tamaño. Si la memoria se
agota, se aborta la ejecución del programa.
Para usar pilas de condiciones de arranque, su analizador debe incluir una
directiva
%option stack (ver Opciones más abajo).
MÚLTIPLES BUFFERS DE ENTRADA¶
Algunos analizadores (tales como aquellos que aceptan ficheros
"incluidos") requieren la lectura de varios flujos de entrada. Ya
que los analizadores de
flex hacen mucho uso de buffers, uno no puede
controlar de dónde será leída la siguiente entrada
escribiendo símplemente un
YY_INPUT que sea sensible al contexto
del análisis. A
YY_INPUT sólo se le llama cuando el
analizador alcanza el final de su buffer, que podría ser bastante
tiempo después de haber analizado una sentencia como un
"include" que requiere el cambio de la fuente de entrada.
Para solventar este tipo de problemas,
flex provee un mecanismo para
crear y conmutar entre varios buffers de entrada. Un buffer de entrada se crea
usando:
YY_BUFFER_STATE yy_create_buffer( FILE *file, int size )
que toma un puntero a
FILE y un tamaño "size" y crea un
buffer asociado con el fichero dado y lo suficientemente grande para mantener
size caracteres (cuando dude, use
YY_BUF_SIZE para el
tamaño). Este devuelve un handle
YY_BUFFER_STATE, que
podría pasarse a otras rutinas (ver más abajo). El tipo de
YY_BUFFER_STATE es un puntero a una estructura opaca
struct
yy_buffer_state, de manera que podría inicializar de forma segura
variables YY_BUFFER_STATE a
((YY_BUFFER_STATE) 0) si lo desea, y
también hacer referencia a la estructura opaca para declarar
correctamente buffers de entrada en otros ficheros fuente además de los
de su analizador. Fíjese que el puntero a
FILE en la llamada a
yy_create_buffer se usa solamente como el valor de
yyin visto
por
YY_INPUT; si usted redefine
YY_INPUT de manera que no use
más a
yyin, entonces puede pasar de forma segura un puntero
FILE nulo a
yy_create_buffer. Se selecciona un buffer en
particular a analizar utilizando:
void yy_switch_to_buffer( YY_BUFFER_STATE nuevo_buffer )
conmuta el buffer de entrada del analizador de manera que los tokens posteriores
provienen de
nuevo_buffer. Fíjese que
yy_switch_to_buffer() podría usarlo yywrap() para arreglar las
cosas para un análisis continuo, en lugar de abrir un nuevo fichero y
que
yyin apunte a este. Fíjese también que cambiar las
fuentes de entrada ya sea por medio de
yy_switch_to_buffer() o de
yywrap() no cambia la condición de arranque.
void yy_delete_buffer( YY_BUFFER_STATE buffer )
se usa para recuperar el almacenamiento asociado a un buffer. (El
buffer
puede ser nulo, en cuyo caso la rutina no hace nada.) Puede también
limpiar el contenido actual de un buffer usando:
void yy_flush_buffer( YY_BUFFER_STATE buffer )
Esta función descarta el contenido del buffer, de manera que la
próxima vez que el analizador intente emparejar un token desde el
buffer, este primero rellenará el buffer utilizando
YY_INPUT.
yy_new_buffer() es un alias de
yy_create_buffer(), que se ofrece
por compatibilidad con el uso en C++ de
new y
delete para crear
y destruir objetos dinámicos.
Finalmente, la macro
YY_CURRENT_BUFFER retorna un handle
YY_BUFFER_STATE al buffer actual.
Aquí hay un ejemplo del uso de estas propiedades para escribir un
analizador que expande ficheros incluidos (la propiedad
<<EOF>> se comenta más abajo):
/* el estado "incl" se utiliza para obtener el nombre
* del fichero a incluir.
*/
%x incl
%{
#define MAX_INCLUDE_DEPTH 10
YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];
int include_stack_ptr = 0;
%}
%%
include BEGIN(incl);
[a-z]+ ECHO;
[^a-z\n]*\n? ECHO;
<incl>[ \t]* /* se come los espacios en blanco */
<incl>[^ \t\n]+ { /* obtiene el nombre de fichero a incluir */
if ( include_stack_ptr >= MAX_INCLUDE_DEPTH )
{
fprintf( stderr, "Demasiados include anidados" );
exit( 1 );
}
include_stack[include_stack_ptr++] =
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 ( --include_stack_ptr < 0 )
{
yyterminate();
}
else
{
yy_delete_buffer( YY_CURRENT_BUFFER );
yy_switch_to_buffer(
include_stack[include_stack_ptr] );
}
}
Se dispone de tres rutinas para preparar buffers de entrada para el
análisis de cadenas en memoria en lugar de archivos. Todas estas crean
un nuevo buffer de entrada para analizar la cadena, y devuelven el
correspondiente handle
YY_BUFFER_STATE (que usted debería borrar
con
yy_delete_buffer() cuando termine con él). Estas
también conmutan el nuevo buffer usando
yy_switch_to_buffer(),
de manera que la próxima llamada a
yylex() comenzará
analizando la cadena.
- yy_scan_string(const char *str)
- analiza una cadena terminada en nulo.
- yy_scan_bytes(const char *bytes, int len)
- analiza len bytes (incluyendo posibles NUL's) comenzando desde el
punto bytes.
Fíjese que ambas de estas funciones crean y analizan una
copia de
la cadena o bytes. (Esto podría ser deseable, ya que
yylex()
modifica el contenido del buffer que está analizado.) Usted puede
evitar la copia utilizando:
- yy_scan_buffer(char *base, yy_size_t size)
- que analiza in situ el buffer comenzando en base, que consiste de
size bytes, donde los dos últimos bytes deben ser
YY_END_OF_BUFFER_CHAR (ASCII NUL). Estos dos últimos bytes
no se analizan; así, el análisis consta de base[0]
hasta base[size-2], inclusive.
- Si se equivoca al disponer base de esta manera (es decir, olvidar
los dos YY_END_OF_BUFFER_CHAR bytes finales), entonces
yy_scan_buffer() devuelve un puntero nulo en lugar de crear un
nuevo buffer de entrada.
- El tipo yy_size_t es un tipo entero con el que puede hacer una
conversión a una expresión entera para reflejar el
tamaño del buffer.
REGLAS DE FIN-DE-FICHERO¶
La regla especial "<<EOF>>" indica las acciones que deben
tomarse cuando se encuentre un fin-de-fichero e yywrap() retorne un valor
distinto de cero (es decir, indica que no quedan ficheros por procesar). La
acción debe finalizar haciendo una de estas cuatro cosas:
- -
- asignando a yyin un nuevo fichero de entrada (en versiones
anteriores de flex, después de hacer la asignación
debía llamar a la acción especial YY_NEW_FILE; esto
ya no es necesario);
- -
- ejecutando una sentencia return;
- -
- ejecutando la acción especial yyterminate();
- -
- o, conmutando a un nuevo buffer usando yy_switch_to_buffer() como
se mostró en el ejemplo anterior.
Las reglas <<EOF>> no deberían usarse con otros patrones;
estas deberían calificarse con una lista de condiciones de arranque. Si
se da una regla <<EOF>> sin calificar, esta se aplica a
todas las condiciones de arranque que no tengan ya acciones
<<EOF>>. Para especificar una regla <<EOF>> solamente
para la condición de arranque inicial, use
<INITIAL><<EOF>>
Estas reglas son útiles para atrapar cosas tales como comentarios sin
final. Un ejemplo:
%x comilla
%%
...otras reglas que tengan que ver con comillas...
<comilla><<EOF>> {
error( "comilla sin cerrar" );
yyterminate();
}
<<EOF>> {
if ( *++filelist )
yyin = fopen( *filelist, "r" );
else
yyterminate();
}
MACROS MISCELÁNEAS¶
La macro
YY_USER_ACTION puede definirse para indicar una acción
que siempre se ejecuta antes de la acción de la regla emparejada. Por
ejemplo, podría declararse con #define para que llame a una rutina que
convierta yytext a minúsculas. Cuando se invoca a
YY_USER_ACTION, la variable
yy_act da el número de la
regla emparejada (las reglas están numeradas comenzando en 1). Suponga
que quiere medir la frecuencia con la que sus reglas son emparejadas. Lo que
viene a continuación podría hacer este truco:
#define YY_USER_ACTION ++ctr[yy_act]
donde
ctr en un vector que mantiene la cuenta para las diferentes reglas.
Fíjese que la macro
YY_NUM_RULES da el número total de
reglas (incluyendo la regla por defecto, incluso si usted usa
-s),
así que una declaración correcta para
ctr es:
int ctr[YY_NUM_RULES];
La macro
YY_USER_INIT podría definirse para indicar una
acción que siempre se ejecuta antes del primer análisis (y antes
de que se haga la inicialización interna del analizador). Por ejemplo,
este podría usarse para llamar a una rutina que lea una tabla de datos
o abrir un fichero de registro.
La macro
yy_set_interactive(is_interactive) se puede usar para controlar
si el buffer actual se considera
interactivo. Un buffer interactivo se
procesa más lentamente, pero debe usarse cuando la fuente de entrada
del analizador es realmente interactiva para evitar problemas debidos a la
espera para el llenado de los buffers (ver el comentario de la bandera
-I más abajo). Un valor distinto de cero en la invocación
de la macro marcará el buffer como interactivo, un valor de cero como
no-interactivo. Fíjese que el uso de esta macro no tiene en cuenta
%option always-interactive o
%option never-interactive (ver
Opciones más abajo).
yy_set_interactive() debe invocarse antes
del comienzo del análisis del buffer que es considerado (o no)
interactivo.
La macro
yy_set_bol(at_bol) puede usarse para controlar si el contexto
del buffer de análisis actual para el próximo emparejamiento de
token se hace como si se encontrara al principio de una línea. Un
argumento de la macro distinto de cero hace activas a las reglas sujetas a
'^', mientras que un argumento igual a cero hacer inactivas a las reglas con
'^'.
La macro
YY_AT_BOL() devuelve verdadero si el próximo token
analizado a partir del buffer actual tendrá activas las reglas '^', de
otra manera falso.
En el analizador generado, las acciones están recogidas en una gran
sentencia switch y separadas usando
YY_BREAK, que puede ser redefinida.
Por defecto, este es símplemente un "break", para separar la
acción de cada regla de las reglas que le siguen. Redefiniendo
YY_BREAK permite, por ejemplo, a los usuarios de C++ que #define
YY_BREAK no haga nada (¡mientras tengan cuidado para que cada regla
finalice con un "break" o un "return"!) para evitar que
sufran los avisos de sentencias inalcanzables cuando debido a que la
acción de la regla finaliza con un "return", el
YY_BREAK es inaccesible.
VALORES DISPONIBLES AL USUARIO¶
Esta sección resume los diferentes valores disponibles al usuario en las
acciones de la regla.
- -
- char *yytext apunta al texto del token actual. Este puede
modificarse pero no alargarse (no puede añadir caracteres al
final).
- Si aparece la directiva especial %array en la primera
sección de la descripción del analizador, entonces
yytext se declara en su lugar como char yytext[YYLMAX],
donde YYLMAX es la definicion de una macro que puede redefinir en
la primera sección si no le gusta el valor por defecto
(generalmente 8KB). El uso de %array produce analizadores algo
más lentos, pero el valor de yytext se vuelve inmune a las
llamadas a input() y unput(), que potencialmente destruyen
su valor cuando yytext es un puntero a caracter. El opuesto de
%array es %pointer, que se encuentra por defecto.
- Usted no puede utilizar %array cuando genera analizadores como
clases de C++ (la bandera -+ ).
- -
- int yyleng contiene la longitud del token actual.
- -
- FILE *yyin es el fichero por el que flex lee por defecto.
Este podría redefinirse pero hacerlo solo tiene sentido antes de
que el análisis comience o después de que se haya encontrado
un EOF. Cambiándolo en medio del análisis tendrá
resultados inesperados ya que flex utiliza buffers en su entrada;
use yyrestart() en su lugar. Una vez que el análisis termina
debido a que se ha visto un fin-de-fichero, puede asignarle a yyin
el nuevo fichero de entrada y entonces llamar al analizador de nuevo para
continuar analizando.
- -
- void yyrestart( FILE *new_file ) podría ser llamada para que
yyin apunte al nuevo fichero de entrada. El cambio al nuevo fichero
es inmediato (cualquier entrada contenida en el buffer previamente se
pierde). Fíjese que llamando a yyrestart() con yyin
como argumento de esta manera elimina el buffer de entradda actual y
continúa analizando el mismo fichero de entrada.
- -
- FILE *yyout es el fichero sobre el que se hacen las acciones
ECHO. Este puede ser reasignado por el usuario.
- -
- YY_CURRENT_BUFFER devuelve un handle YY_BUFFER_STATE al
buffer actual.
- -
- YY_START devuelve un valor entero correspondiente a la
condición de arranque actual. Posteriormente puede usar este valor
con BEGIN para retornar a la condición de arranque.
INTERFAZ CON YACC¶
Uno de los usos principales de
flex es como compañero del
generador de analizadores sintácticos
yacc. Los analizadores de
yacc esperan invocar a una rutina llamada
yylex() para encontrar
el próximo token de entrada. La rutina se supone que devuelve el tipo
del próximo token además de poner cualquier valor asociado en la
variable global
yylval. Para usar
flex con
yacc, uno
especifica la opción
-d de
yacc para intruirle a que
genere el fichero
y.tab.h que contiene las definiciones de todos los
%tokens que aparecen en la entrada de
yacc. Entonces este
archivo se incluye en el analizador de
flex Por ejemplo, si uno de los
tokens es "TOK_NUMERO", parte del analizador podría parecerse
a:
%{
#include "y.tab.h"
%}
%%
[0-9]+ yylval = atoi( yytext ); return TOK_NUMERO;
OPCIONES¶
flex tiene las siguientes opciones:
- -b
- Genera información de retroceso en lex.backup. Esta es una
lista de estados del analizador que requieren retroceso y los caracteres
de entrada con los que la hace. Añadiendo reglas uno puede eliminar
estados de retroceso. Si todos los estados de retroceso se eliminan
y se usa -Cf o -CF, el analizador generado funcionará
más rápido (ver la bandera -p). Únicamente los
usuarios que desean exprimir hasta el último ciclo de sus
analizadores necesitan preocuparse de esta opción. (Ver la
sección sobre Consideraciones de Rendimiento más
abajo.)
- -c
- es una opción que no hace nada, incluída para cumplir con
POSIX.
- -d
- hace que el analizador generado se ejecute en modo de
depuración. Siempre que se reconoce un patrón y la
variable global yy_flex_debug no es cero (que por defecto no lo
es), el analizador escribirá en stderr una línea de
la forma:
--accepting rule at line 53 ("el texto emparejado")
El número de línea hace referencia al lugar de la regla en el
fichero que define al analizador (es decir, el fichero que se le introdujo
a flex). Los mensajes también se generan cuando el analizador
retrocede, acepta la regla por defecto, alcanza el final de su buffer de
entrada (o encuentra un NUL; en este punto, los dos parecen lo mismo en lo
que le concierne al analizador), o alcance el fin-de-fichero.
- -f
- especifica un analizador rápido. No se realiza una
compresión de tablas y se evita el uso de stdio. El resultado es
grande pero rápido. Esta opción es equivalente a -Cfr
(ver más abajo).
- -h
- genera un sumario de "ayuda" de las opciones de flex por
stdout y entonces finaliza. -? y --help son
sinónimos de -h.
- -i
- indica a flex que genere un analizador case-insensitive. Se
ignorará si las letras en los patrones de entrada de flex
son en mayúsculas o en minúsculas, y los tokens en la
entrada serán emparejados sin tenerlo en cuenta. El texto
emparejado dado en yytext tendrá las mayúsculas y
minúsculas preservadas (es decir, no se convertirán).
- -l
- activa el modo de máxima compatibilidad con la
implementación original de lex de AT&T. Fíjese
que esto no significa una compatibilidad completa. El uso de esta
opción cuesta una cantidad considerable de rendimiento, y no puede
usarse con las opciones -+, -f, -F, -Cf, o -CF. Para los
detalles a cerca de la compatibilidad que se ofrece, vea la sección
"Incompatibilidades con Lex y POSIX" más abajo. Esta
opción también hace que se defina el nombre
YY_FLEX_LEX_COMPAT en el analizador generado.
- -n
- es otra opción que no hace nada, incluída para cumplir con
POSIX.
- -p
- genera un informe de rendimiento en stderr. El informe consta de
comentarios que tratan de las propiedades del fichero de entrada de
flex que provocarán pérdidas serias de rendimiento en
el analizador resultante. Si indica esta bandera dos veces, también
obtendrá comentarios que tratan de las propiedades que producen
pérdidas menores de rendimiento.
- Fíjese que el uso de REJECT, %option yylineno, y el
contexto posterior variable (vea la sección Deficiencias / Errores
más abajo) supone una penalización substancial del
rendimiento; el uso de yymore(), el operador ^, y la bandera
-I supone penalizaciones del rendimiento menores.
- -s
- hace que la regla por defecto (que la entrada sin emparejar del
analizador se repita por stdout) se suprima. Si el analizador
encuentra entrada que no es reconocida por ninguna de sus reglas, este
aborta con un error. Esta opción es útil para encontrar
agujeros en el conjunto de reglas del analizador.
- -t
- indica a flex que escriba el analizador que genera a la salida
estándar en lugar de en lex.yy.c.
- -v
- especifica que flex debería escribir en stderr un
sumario de estadísticas respecto al analizador que genera. La
mayoría de las estadísticas no tienen significado para el
usuario casual de flex, pero la primera línea identifica la
versión de flex (la misma que se informa con -V), y
la próxima línea las banderas utilizadas cuando se genera el
analizador, incluyendo aquellas que se encuentran activadas por
defecto.
- -w
- suprime los mensajes de aviso.
- -B
- dice a flex que genere un analizador batch, que es lo
opuesto al analizador interactivo generador por -I (ver
más abajo). En general, use -B cuando esté
seguro de que su analizador nunca se usará de forma
interactiva, y quiere con esto exprimir un poco más el
rendimiento. Si por el contrario su objetivo es exprimirlo mucho
más, debería estar utilizando la opción -Cf o
-CF (comentadas más abajo), que activa -B
automáticamente de todas maneras.
- -F
- especifica que se debe utilizar la representación de la tabla
rápida (y elimina referencias a stdio). Esta representación
es aproximadamente tan rápida como la representación
completa de la tabla (-f), y para algunos conjuntos de patrones
será considerablemente más pequeña (y para otros,
mayor). En general, si el conjunto de patrones contiene "palabras
clave" y una regla "identificador" atrápalo-todo,
como la del conjunto:
"case" return TOK_CASE;
"switch" return TOK_SWITCH;
...
"default" return TOK_DEFAULT;
[a-z]+ return TOK_ID;
entonces será mejor que utilice la representación de la tabla
completa. Si sólo está presente la regla
"identificador" y utiliza una tabla hash o algo parecido para
detectar palabras clave, mejor utilice -F.
- Esta opción es equivalente a -CFr (ver más abajo).
Esta opción no puede utilizarse con -+.
- -I
- ordena a flex que genere un analizador interactivo Un
analizador interactivo es uno que solo mira hacia delante para decidir que
token ha sido reconocido únicamente si debe hacerlo. Resulta que
mirando siempre un caracter extra hacia delante, incluso si el analizador
ya ha visto suficiente texto para eliminar la ambigüedad del token
actual, se es un poco más rápido que mirando solamente
cuando es necesario. Pero los analizadores que siempre miran hacia delante
producen un comportamiento interactivo malísimo; por ejemplo,
cuando un usuario teclea una línea nueva, esta no se reconoce como
un token de línea nueva hasta que introduzca otro token, que
a menudo significa introducir otra línea completa.
- Los analizadores de flex por defecto son interactivos a
menos que use la opción -Cf o -CF de
compresión de tablas (ver más abajo). Esto es debido a que
si está buscando un rendimiento alto tendría que estar
utilizando una de estas opciones, así que si no lo ha hecho
flex asume que prefiere cambiar un poco de rendimiento en tiempo de
ejecución en beneficio de un comportamiento iteractivo intuitivo.
Fíjese también que no puede utilizar -I
conjuntamente con -Cf o -CF. Así, esta opción
no se necesita realmente; está activa por defecto para todos esos
casos en los que se permite.
- Usted puede forzar al analizador que no sea interactivo usando
-B (ver más arriba).
- -L
- ordena a flex que no genere directivas #line. Sin esta
opción, flex acribilla al analizador generado con directivas
#line para que los mensajes de error en las acciones estén
localizadas correctamente respecto al fichero original de flex (si
los errores son debidos al código en el fichero de entrada), o a
lex.yy.c (si los errores son fallos de flex --
debería informar de este tipo de errores a la dirección de
correo dada más abajo).
- -T
- hace que flex se ejecute en modo de traza. Este
generará un montón de mensajes en stderr relativos a
la forma de la entrada y el autómata finito no-determinista o
determinista resultante. Esta opción generalmente es para usarla en
el mantenimiento de flex.
- -V
- imprime el número de la versión en stdout y sale.
--version es un sinónimo de -V.
- -7
- ordena a flex que genere un analizador de 7-bits, es decir, uno que
sólo puede reconocer caracteres de 7-bits en su entrada. La ventaja
de usar -7 es que las tablas del analizador pueden ser hasta la
mitad del tamaño de aquellas generadas usando la opción
-8 (ver más abajo). La desventaja es que tales analizadores
a menudo se cuelgan o revientan si su entrada contiene caracteres de
8-bits.
- Fíjese, sin embargo, que a menos que genere su analizador
utilizando las opciones de compresión de tablas -Cf o
-CF, el uso de -7 ahorrará solamente una
pequeña cantidad de espacio en la tabla, y hará su
analizador considerablemente menos portable. El comportamiento por defecto
de flex es generar un analizador de 8-bits a menos que use
-Cf o -CF, en cuyo caso flex por defecto genera
analizadores de 7-bits a menos que su sistema siempre esté
configurado para generar analizadores de 8-bits (a menudo este será
el caso de los sistemas fuera de EEUU). Puede decir si flex generó
un analizador de 7 u 8 bits inspeccionando el sumario de banderas en la
salida de -v como se describió anteriormente.
- Fíjese que si usa -Cfe o -CFe (esas opciones de
compresión de tablas, pero también el uso de clases de
equivalencia como se comentará más abajo), flex genera
aún por defecto un analizador de 8-bits, ya que normalmente con
estas opciones de compresión las tablas de 8-bits completas no son
mucho más caras que las tablas de 7-bits.
- -8
- ordena a flex que genere un analizador de 8-bits, es decir, uno que
puede reconocer caracteres de 8-bits. Esta bandera sólo es
necesaria para analizadores generados usando -Cf o -CF, ya
que de otra manera flex por defecto genera un analizador de 8-bits de
todas formas.
- Vea el comentario sobre -7 más arriba a cerca del
comportamiento por defecto de flex y la discusión entre los
analizadores de 7-bits y 8-bits.
- -+
- especifica que quiere que flex genere un analizador como una clase de C++.
Vea la sección Generando Escáners en C++ más abajo
para los detalles.
- -C[aefFmr]
- controla el grado de compresión de la tabla y, más
generalmente, el compromiso entre analizadores pequeños y
analizadores rápidos.
- -Ca ("alinea") ordena a flex que negocie tablas
más grandes en el analizador generado para un comportamiento
más rápido porque los elementos de las tablas están
mejor alineados para el acceso a memoria y computación. En algunas
arquitecturas RISC, la búsqueda y manipulación de palabras
largas es más eficiente que con unidades más pequeñas
tales como palabras cortas. Esta opción puede doblar el
tamaño de las tablas usadas en su analizador.
- -Ce ordena a flex que construya clases de
equivalencia, es decir, conjunto de caracteres que tienen identicas
propiedades léxicas (por ejemplo, si la única
aparición de dígitos en la entrada de flex es en la
clase de caracteres "[0-9]" entonces los dígitos '0',
'1', ..., '9' se pondrán todos en la misma clase de equivalencia).
Las clases de equivalencia normalmente ofrecen notables reducciones en los
tamaños de los ficheros finales de tabla/objeto (típicamente
un factor de 2-5) y son juiciosamente bastante baratos en cuanto al
rendimiento (una localización en un vector por caracter
analizado).
- -Cf especifica que se deben generar las tablas del analizador
completas - flex no debería comprimir las tablas
tomando ventaja de las funciones de transición similares para
diferentes estados.
- -CF especifica que debería usarse la representación
del analizador rápido alternativo (descrito anteriormente en la
bandera -F ) Esta opción no puede usarse con -+.
- -Cm ordena a flex a que construya clases de
meta-equivalencias, que son conjuntos de clases de equivalencia (o
caracteres, si las clases de equivalencia no se están usando) que
comunmente se usan de forma conjunta. Las clases de meta-equivalencias son
a menudo un gran ahorro cuando se usan tablas comprimidas, pero tienen un
impacto moderado en el rendimiento (uno o dos tests "if" y una
localización en un array por caracter analizado).
- -Cr hace que el analizador generado elimine el uso de la
librería de E/S estándar para la entrada. En lugar de llamar
a fread() o getc(), el analizador utilizará la
llamada al sistema read(), produciendo una ganancia en el
rendimiento que varía de sistema en sistema, pero en general
probablemente es insignificante a menos que también esté
usando -Cf o -CF. El uso de -Cr puede producir un
comportamiento extraño si, por ejemplo, lee de yyin usando
stdio antes de llamar al analizador (porque el analizador perderá
cualquier texto que sus lecturas anteriores dejaron en el buffer de
entrada de stdio).
- -Cr no tiene efecto si usted define YY_INPUT (ver El
Escáner Generado más arriba).
- Con solamente -C se especifica que las tablas del analizador
deberían comprimirse pero no debería utilizarse ni las
clases de equivalencia ni las clases de meta-equivalencias.
- Las opciones -Cf o -CF y -Cm no tienen sentido juntas
- no hay oportunidad para las clases de meta-equivalencias si la tabla no
está siendo comprimida. De otra forma las opciones podrían
mezclarse líbremente, y son acumulativas.
- La configuración por defecto es -Cem, que especifica que
flex debería generar clases de equivalencia y clases de
meta-equivalencias. Esta configuración provee el mayor grado de
compresión. Puede llegarse a un compromiso entre analizadores de
ejecución más rápida con el coste de tablas mayores
siendo generalmente verdadero lo siguiente:
lo más lento y pequeño
-Cem
-Cm
-Ce
-C
-C{f,F}e
-C{f,F}
-C{f,F}a
lo más rápido y grande
Fíjese que los analizadores con tablas más pequeñas
normalmente se generan y compilan de la forma más rápida
posible, así que durante el desarrollo usted normalmente
querrá usar como viene por defecto, compresión
máxima.
- -Cfe a menudo es un buen compromiso entre velocidad y tamaño
para la producción de analizadores.
- -osalida
- ordena a flex que escriba el analizador al fichero salida en lugar
de a lex.yy.c. Si combina -o con la opción -t,
entonces el analizador se escribe en stdout pero sus directivas
#line (vea la opción -L más arriba) hacen
referencia al fichero salida.
- -Pprefijo
- cambia el prefijo yy usado por defecto por flex para todas
las variables visibles globalmente y nombres de funciones para que sea
prefijo. Por ejemplo, -Pfoo cambia el nombre de
yytext a footext. Este también cambia el nombre por
defecto del fichero de salida de lex.yy.c a lex.foo.c.
Aquí están todos los nombres afectados:
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 usted está utilizando un analizador en C++, entonces
únicamente yywrap y yyFlexLexer se ven afectados.)
Dentro de su analizador, puede aún hacer referencia a las variables
globales y funciones usando cualquier versión de su nombre; pero
externamente, estas tienen el nombre modificado.
- Esta opción le deja enlazar fácilmente múltiples
programas flex conjuntamente en el mismo ejecutable. Fíjese,
sin embargo, que usando esta opción también se renombra
yywrap(), de manera que ahora debe o bien proveer su propia
versión de la rutina (con el nombre apropiado) para su analizador,
o usar %option noyywrap, ya que enlazar con -lfl no
podrá proveerle una por defecto.
- -Sfichero_esqueleto
- ignora el fichero de esqueleteo por defecto con el que flex
construye sus analizadores. Usted probablemente nunca necesitará
utilizar esta opción a menos que este haciendo mantenimiento o un
desarrollo de flex.
flex también ofrece un mecanismo para controlar las opciones
dentro de la propia especificación del analizador, en vez de a partir
de la línea de comando. Esto se hace incluyendo las directivas
%option en la primera sección de la especificación del
analizador. Usted puede especificar varias opciones con una sola directiva
%option, y varias directivas en la primera sección de su fichero
de entrada de flex.
La mayoría de las opciones vienen dadas simplemente como nombres,
opcionalmente precedidos por la palabra "no" (sin intervenir un
espacio) para negar su significado. Las banderas de flex o su negación
son equivalentes a un número:
7bit opción -7
8bit opción -8
align opción -Ca
backup opción -b
batch opción -B
c++ opción -+
caseful o
case-sensitive opuesto de -i (por defecto)
case-insensitive o
caseless opción -i
debug opción -d
default opuesto de la opción -s
ecs opción -Ce
fast opción -F
full opción -f
interactive opción -I
lex-compat opción -l
meta-ecs opción -Cm
perf-report opción -p
read opción -Cr
stdout opción -t
verbose opción -v
warn opuesto de la opción -w
(use "%option nowarn" para -w)
array equivalente a "%array"
pointer equivalente a "%pointer" (por defecto)
Algunas directivas
%option ofrecen propiedades que de otra manera no
están disponibles:
- always-interactive
- ordena a flex que genere un analizador que siempre considere su entrada
como "interactiva". Normalmente, sobre cada fichero de entrada
nuevo el analizador llama a isatty() como intento para determinar
si la entrada del analizador es interactiva y por lo tanto debería
leer un caracter a la vez. Cuando esta opción se utilice, sin
embargo, entonces no se hace tal llamada.
- main
- ordena a flex que facilite un programa main() por defecto para el
analizador, que simplemente llame a yylex(). Esta opción
implica noyywrap (ver más abajo).
- never-interactive
- ordena a flex que genere un analizador que nunca considere su entrada como
"interactiva" (de nuevo, no se hace ninguna llamada a
isatty()). Esta es la opuesta a always-interactive.
- stack
- activa el uso de pilas de condiciones de arranque (ver Condiciones de
Arranque más arriba).
- stdinit
- si se establece (es decir, %option stdinit) inicializa yyin
e yyout a stdin y stdout, en lugar del que viene por
defecto que es nil. Algunos pogramas de lex existentes
dependen de este comportamiento, incluso si no sigue el ANSI C, que no
requiere que stdin y stdout sean constantes en tiempo de
compilación.
- yylineno
- ordena a flex a generar un analizador que mantenga el número
de la línea actual leída desde su entrada en la variable
global yylineno. Esta opción viene implícita con
%option lex-compat.
- yywrap
- si no se establece (es decir, %option noyywrap), hace que el
analizador no llame a yywrap() hasta el fin-de-fichero, pero
simplemente asume que no hay más ficheros que analizar (hasta que
el usuario haga apuntar yyin a un nuevo fichero y llame a
yylex() otra vez).
flex analiza las acciones de sus reglas para determinar si utiliza las
propiedades
REJECT o
yymore() Las opciones
reject e
yymore están disponibles para ignorar sus decisiones siempre que
use las opciones, o bien estableciendolas (p.ej.,
%option reject) para
indicar que la propiedad se utiliza realmente, o desactivándolas para
indicar que no es utilizada (p.ej.,
%option noyymore).
Tres opciones toman valores delimitados por cadenas, separadas por '=':
%option outfile="ABC"
es equivalente a
-oABC, y
%option prefix="XYZ"
es equivalente a
-PXYZ. Finalmente,
%option yyclass="foo"
sólo se aplica cuando se genera un analizador en C++ (opción
-+). Este informa a
flex que ha derivado a
foo como una
subclase de
yyFlexLexer, así que
flex pondrá sus
acciones en la función miembro
foo::yylex() en lugar de
yyFlexLexer::yylex(). Este también genera una función
miembro
yyFlexLexer::yylex() que emite un error en tiempo de
ejecución (invocando a
yyFlexLexer::LexerError()) si es llamada.
Ver Generando Escáners en C++, más abajo, para
información adicional.
Están disponibles un número de opciones para los puristas de lint
que desean suprimir la aparición de rutinas no necesarias en el
analizador generado. Cada una de la siguientes, si se desactivan (p.ej.,
%option nounput ), hace que la rutina correspondiente no aparezca en el
analizador generado:
input, unput
yy_push_state, yy_pop_state, yy_top_state
yy_scan_buffer, yy_scan_bytes, yy_scan_string
(aunque
yy_push_state() y sus amigas no aparecerán de todas manera
a menos que use
%option stack).
CONSIDERACIONES DE RENDIMIENTO¶
El principal objetivo de diseño de
flex es que genere analizadores
de alto rendimiento. Este ha sido optimizado para comportarse bien con
conjuntos grandes de reglas. Aparte de los efectos sobre la velocidad del
analizador con las opciones de compresión de tablas
-C
anteriormente introducidas, hay un número de opciones/acciones que
degradan el rendimiento. Estas son, desde la más costosa a la menos:
REJECT
%option yylineno
contexto posterior arbitrario
conjunto de patrones que requieren retroceso
%array
%option interactive
%option always-interactive
'^' operador de comienzo de línea
yymore()
siendo las tres primeras bastante costosas y las dos últimas bastante
económicas. Fíjese también que
unput() se
implementa como una llamada de rutina que potencialmente hace bastante
trabajo, mientras que
yyless() es una macro bastante económica;
así que si está devolviendo algún texto excedente que ha
analizado, use
yyless().
REJECT debería evitarse a cualquier precio cuando el rendimiento
es importante. Esta es una opción particularmente cara.
Es lioso deshacerse del retroceso y a menudo podría ser una cantidad de
trabajo enorme para un analizador complicado. En principio, uno comienza
utilizando la bandera
-b para generar un archivo
lex.backup. Por
ejemplo, sobre la entrada
%%
foo return TOK_KEYWORD;
foobar return TOK_KEYWORD;
el fichero tiene el siguiente aspecto:
El estado #6 es no-aceptar -
números de línea asociados a la regla:
2 3
fin de transiciones: [ o ]
transiciones de bloqueo: fin de archivo (EOF) [ \001-n p-\177 ]
El estado #8 es no-aceptar -
números de línea asociados a la regla:
3
fin de transiciones: [ a ]
transiciones de bloqueo: fin de archivo (EOF) [ \001-` b-\177 ]
El estado #9 es no-aceptar -
números de línea asociados a la regla:
3
fin de transiciones: [ r ]
transiciones de bloqueo: fin de archivo (EOF) [ \001-q s-\177 ]
Las tablas comprimidas siempre implican un retroceso.
Las primeras líneas nos dicen que hay un estado del analizador en el que
se puede hacer una transición con una 'o' pero no sobre cualquier otro
caracter, y que en ese estado el texto recientemente analizado no empareja con
ninguna regla. El estado ocurre cuando se intenta emparejar las reglas
encontradas en las líneas 2 y 3 en el fichero de entrada. Si el
analizador está en ese estado y entoces lee cualquier cosa que no sea
una 'o', tendrá que retroceder para encontrar una regla que empareje.
Con un poco de análisis uno puede ver que este debe ser el estado en el
que se está cuando se ha visto "fo". Cuando haya ocurrido, si
se ve cualquier cosa que no sea una 'o', el analizador tendrá que
retroceder para simplemente emparejar la 'f' (por la regla por defecto).
El comentario que tiene que ver con el Estado #8 indica que hay un problema
cuando se analiza "foob". En efecto, con cualquier caracter que no
sea una 'a', el analizador tendrá que retroceder para aceptar
"foo". De forma similar, el comentario para el Estado #9 tiene que
ver cuando se ha analizado "fooba" y no le sigue una 'r'.
El comentario final nos recuerda que no mecere la pena todo el trabajo para
eliminar el retroceso de las reglas a menos que estemos usando
-Cf o
-CF, y que no hay ninguna mejora del rendimiento haciéndolo con
analizadores comprimidos.
La manera de quitar los retrocesos es añadiendo reglas de
"error":
%%
foo return TOK_KEYWORD;
foobar return TOK_KEYWORD;
fooba |
foob |
fo {
/* falsa alarma, realmente no es una palabra clave */
return TOK_ID;
}
La eliminación de retroceso en una lista de palabras clave también
puede hacerse utilizando una regla "atrápalo-todo":
%%
foo return TOK_KEYWORD;
foobar return TOK_KEYWORD;
[a-z]+ return TOK_ID;
Normalmente esta es la mejor solución cuando sea adecuada.
Los mensajes sobre retrocesos tienden a aparecer en cascada. Con un conjunto
complicado de reglas no es poco común obtener cientos de mensajes. Si
uno puede descifrarlos, sin embargo, a menudo sólo hay que tomar una
docena de reglas o algo así para eliminar los retrocesos (ya que es
fácil cometer una equivocación y tener una regla de error que
reconozca un token válido. Una posible característica futura de
flex será añadir reglas automáticamente para
eliminar el retroceso).
Es importante tener en cuenta que se obtienen los beneficios de eliminar el
retroceso sólo si elimina
cada instancia del retroceso. Dejar
solamente una significa que no ha ganado absolutamente nada.
El contexto posterior
variable (donde la parte delantera y posterior no
tienen una longitud fija) supone casi la misma pérdida de rendimiento
que
REJECT (es decir, substanciales). Así que cuando sea posible
una regla como esta:
%%
raton|rata/(gato|perro) correr();
es mejor escribirla así:
%%
raton/gato|perro correr();
rata/gato|perro correr();
o así
%%
raton|rata/gato correr();
raton|rata/perro correr();
Fíjese que aquí la acción especial '|'
no ofrece
ningún ahorro, y puede incluso hacer las cosas peor (ver Deficiencias /
Errores más abajo).
Otro área donde el usuario puede incrementar el rendimiento del
analizador (y una que es más fácil de implementar) surge del
hecho que cuanto más tarde se empareje un token, más
rápido irá el analizador. Esto es debido a que con tokens
grandes el procesamiento de la mayoría de los caracteres de entrada
tiene lugar en el (corto) bucle de análisis más interno, y no
tiene que ir tan a menudo a hacer el trabajo de más para constituir el
entorno del analizador (p.ej.,
yytext) para la acción. Recuerde
el analizador para los comentarios en C:
%x comentario
%%
int num_linea = 1;
"/*" BEGIN(comentario);
<comentario>[^*\n]*
<comentario>"*"+[^*/\n]*
<comentario>\n ++num_linea;
<comentario>"*"+"/" BEGIN(INITIAL);
Esto podría acelerarse escribiéndolo como:
%x comentario
%%
int num_linea = 1;
"/*" BEGIN(comentario);
<comentario>[^*\n]*
<comentario>[^*\n]*\n ++num_linea;
<comentario>"*"+[^*/\n]*
<comentario>"*"+[^*/\n]*\n ++num_linea;
<comentario>"*"+"/" BEGIN(INITIAL);
Ahora en lugar de que cada línea nueva requiera el procesamiento de otra
regla, el reconocimiento de las líneas nuevas se "distribuye"
sobre las otras reglas para mantener el texto reconocido tan largo como sea
posible. ¡Fíjese que el
añadir reglas
no
ralentiza el analizador! La velocidad del analizador es independiente del
número de reglas o (dadas las consideraciones dadas al inicio de esta
sección) cuán complicadas sean las reglas respecto a operadores
tales como '*' y '|'.
Un ejemplo final sobre la aceleración de un analizador: suponga que
quiere analizar un fichero que contiene identificadores y palabras clave, una
por línea y sin ningún caracter extraño, y reconocer
todas las palabras clave. Una primera aproximación natural es:
%%
asm |
auto |
break |
... etc ...
volatile |
while /* es una palabra clave */
.|\n /* no es una palabra clave */
Para eliminar el retroceso, introduzca una regla atrápalo-todo:
%%
asm |
auto |
break |
... etc ...
volatile |
while /* es una palabra clave */
[a-z]+ |
.|\n /* no es una palabra clave */
Ahora, si se garantiza que hay exáctamente una palabra por línea,
entonces podemos reducir el número total de emparejamientos por la
mitad mezclando el reconocimiento de líneas nuevas con las de los otros
tokens:
%%
asm\n |
auto\n |
break\n |
... etc ...
volatile\n |
while\n /* es una palabra clave */
[a-z]+\n |
.|\n /* no es una palabra clave */
Uno tiene que ser cuidadoso aquí, ya que hemos reintroducido retroceso en
el analizador. En particular, aunque
nosotros sepamos que ahí
nunca habrán otros caracteres en el flujo de entrada que no sean letras
o líneas nuevas,
flex no puede figurarse eso, y planeará
la posible necesidad de retroceder cuando haya analizado un token como
"auto" y el próximo caracter sea algo distinto a una
línea nueva o una letra. Previamente este podría entonces
emparejar la regla "auto" y estar todo hecho, pero ahora este no
tiene una regla "auto", solamente una regla "auto\n". Para
eliminar la posibilidad de retroceso, podríamos o bien duplicar todas
las reglas pero sin línea nueva al final, o, ya que nunca esperamos
encontrar tal entrada y por lo tanto ni cómo es clasificada, podemos
introducir una regla atrápalo-todo más, esta que no incluye una
línea nueva:
%%
asm\n |
auto\n |
break\n |
... etc ...
volatile\n |
while\n /* es una palabra clave */
[a-z]+\n |
[a-z]+ |
.|\n /* no es una palabra clave */
Compilado con
-Cf, esto es casi tan rápido como lo que uno puede
obtener de un analizador de
flex para este problema en particular.
Una nota final:
flex es lento cuando empareja NUL's, particularmente
cuando un token contiene múltiples NUL's. Es mejor escribir reglas que
emparejen
cortas cantidades de texto si se anticipa que el texto
incluirá NUL's a menudo.
Otra nota final en relación con el rendimiento: tal y como se
mencionó en la sección Cómo se Reconoce la Entrada, el
reajuste dinámico de
yytext para acomodar tokens enormes es un
proceso lento porque ahora requiere que el token (inmenso) sea reanalizado
desde el principio. De esta manera si el rendimiento es vital, debería
intentar emparejar "grandes" cantidades de texto pero no
"inmensas" cantidades, donde el punto medio está en torno a
los 8K caracteres/token.
GENERANDO ESCÁNERES EN C++¶
flex ofrece dos maneras distintas de generar analizadores para usar con
C++. La primera manera es simplemente compilar un analizador generado por
flex usando un compilador de C++ en lugar de un compilador de C. No
debería encontrarse ante ningún error de compilación (por
favor informe de cualquier error que encuentre a la dirección de correo
electrónico dada en la sección Autores más abajo). Puede
entonces usar código C++ en sus acciones de las reglas en lugar de
código C. Fíjese que la fuente de entrada por defecto para su
analizador permanece como
yyin, y la repetición por defecto se
hace aún a
yyout. Ambos permanecen como variables
FILE *
y no como
flujos de C++.
También puede utilizar
flex para generar un analizador como una
clase de C++, utilizando la opción
-+ (o, equivalentemente,
%option c++), que se especifica automáticamente si el nombre del
ejecutable de flex finaliza con un '+', tal como
flex++. Cuando se usa
esta opción, flex establece por defecto la generación del
analizador al fichero
lex.yy.cc en vez de
lex.yy.c. El
analizador generado incluye el fichero de cabecera
FlexLexer.h, que
define el interfaz con las dos clases de C++.
La primera clase,
FlexLexer, ofrece una clase base abstracta definiendo
la interfaz a la clase del analizador general. Este provee las siguientes
funciones miembro:
- const char* YYText()
- retorna el texto del token reconocido más recientemente, el
equivalente a yytext.
- int YYLeng()
- retorna la longitud del token reconocido más recientemente, el
equivalente a yyleng.
- int lineno() const
- retorna el número de línea de entrada actual (ver %option
yylineno), o 1 si no se usó %option
yylineno.
- void set_debug( int flag )
- activa la bandera de depuración para el analizador, equivalente a
la asignación de yy_flex_debug (ver la sección
Opciones más arriba). Fíjese que debe construir el
analizador utilizando %option debug para incluir información
de depuración en este.
- int debug() const
- retorna el estado actual de la bandera de depuración.
También se proveen funciones miembro equivalentes a
yy_switch_to_buffer(), yy_create_buffer() (aunque el primer
argumento es un puntero a objeto
istream* y no un
FILE*),
yy_flush_buffer(), yy_delete_buffer(), y
yyrestart() (de
nuevo, el primer argumento es un puntero a objeto
istream* ).
La segunda clase definida en
FlexLexer.h es
yyFlexLexer, que se
deriva de
FlexLexer. Esta define las siguientes funciones miembro
adicionales:
- yyFlexLexer( istream* arg_yyin = 0, ostream* arg_yyout = 0 )
- construye un objeto yyFlexLexer usando los flujos dados para la
entrada y salida. Si no se especifica, los flujos se establecen por
defecto a cin y cout, respectivamente.
- virtual int yylex()
- hace el mismo papel que yylex() en los analizadores de flex
ordinarios: analiza el flujo de entrada, consumiendo tokens, hasta que la
acción de una regla retorne un valor. Si usted deriva una subclase
S a partir de yyFlexLexer y quiere acceder a las funciones y
variables miembro de S dentro de yylex(), entonces necesita
utilizar %option yyclass="S" para informar a flex
que estará utilizando esa subclase en lugar de yyFlexLexer.
Es este caso, en vez de generar yyFlexLexer::yylex(), flex
genera S::yylex() (y también genera un substituto
yyFlexLexer::yylex() que llama a yyFlexLexer::LexerError()
si se invoca).
- virtual void switch_streams(istream* new_in = 0,
- ostream* new_out = 0) reasigna yyin a new_in (si no
es nulo) e yyout a new_out (idem), borrando el buffer de
entrada anterior si se reasigna yyin.
- int yylex( istream* new_in, ostream* new_out = 0 )
- primero conmuta el flujo de entrada via switch_streams( new_in, new_out
) y entonces retorna el valor de yylex().
Además,
yyFlexLexer define las siguientes funciones virtuales
protegidas que puede redefinir en clases derivadas para adaptar el analizador:
- virtual int LexerInput( char* buf, int max_size )
- lee hasta max_size caracteres en buf y devuelve el
número de caracteres leídos. Para indicar el
fin-de-la-entrada, devuelve 0 caracteres. Fíjese que los
analizadores "interactivos" (ver las banderas -B y
-I ) definen la macro YY_INTERACTIVE. Si usted redefine
LexerInput() y necesita tomar acciones distintas dependiendo de si
el analizador está analizando una fuente de entrada interactivo o
no, puede comprobar la presencia de este nombre mediante
#ifdef.
- virtual void LexerOutput( const char* buf, int size )
- escribe a la salida size caracteres desde el buffer buf,
que, mientras termine en NUL, puede contener también NUL's
"internos" si las reglas del analizador pueden emparejar texto
con NUL's dentro de este.
- virtual void LexerError( const char* msg )
- informa con un mensaje de error fatal. La versión por defecto de
esta función escribe el mensaje al flujo cerr y
finaliza.
Fíjese que un objeto
yyFlexLexer contiene su estado de
análisis
completo. Así puede utilizar tales objetos para
crear analizadore reentrantes. Puede hacer varias instancias de la misma clase
yyFlexLexer, y puede combinar varias clases de analizadores en C++
conjuntamente en el mismo programa usando la opción
-P comentada
anteriormente.
Finalmente, note que la característica
%array no está
disponible en clases de analizadores en C++; debe utilizar
%pointer
(por defecto).
Aquí hay un ejemplo de un analizador en C++ simple:
// Un ejemplo del uso de la clase analizador en C++ de flex.
%{
int mylineno = 0;
%}
string \"[^\n"]+\"
ws [ \t]+
alpha [A-Za-z]
dig [0-9]
name ({alpha}|{dig}|\$)({alpha}|{dig}|[_.\-/$])*
num1 [-+]?{dig}+\.?([eE][-+]?{dig}+)?
num2 [-+]?{dig}*\.{dig}+([eE][-+]?{dig}+)?
number {num1}|{num2}
%%
{ws} /* evita los espacios en blanco y tabuladores */
"/*" {
int c;
while((c = yyinput()) != 0)
{
if(c == '\n')
++mylineno;
else if(c == '*')
{
if((c = yyinput()) == '/')
break;
else
unput(c);
}
}
}
{number} cout << "número " << YYText() << '\n';
\n mylineno++;
{name} cout << "nombre " << YYText() << '\n';
{string} cout << "cadena " << YYText() << '\n';
%%
int main( int /* argc */, char** /* argv */ )
{
FlexLexer* lexer = new yyFlexLexer;
while(lexer->yylex() != 0)
;
return 0;
}
Si desea crear varias (diferentes) clases analizadoras, use la bandera
-P
(o la opción
prefix= ) para renombrar cada
yyFlexLexer a
algún otro
xxFlexLexer. Entonces puede incluir
<FlexLexer.h> en los otros ficheros fuente una vez por clase
analizadora, primero renombrando
yyFlexLexer como se presenta a
continuación:
#undef yyFlexLexer
#define yyFlexLexer xxFlexLexer
#include <FlexLexer.h>
#undef yyFlexLexer
#define yyFlexLexer zzFlexLexer
#include <FlexLexer.h>
si, por ejemplo, usted utilizó
%option prefix="xx" para
uno de sus analizadores y
%option prefix="zz" para el otro.
IMPORTANTE: la forma actual de la clase analizadora es
experimental y
podría cambiar considerablemente entre versiones principales.
INCOMPATIBILIDADES CON LEX Y POSIX¶
flex es una reescritura de la herramienta
lex del Unix de AT&T
(aunque las dos implementaciones no comparten ningún código),
con algunas extensiones e incompatibilidades, de las que ambas conciernen a
aquellos que desean escribir analizadores aceptables por cualquier
implementación. Flex sigue completamente la especificación POSIX
de
lex, excepto que cuando se utiliza
%pointer (por defecto),
una llamada a
unput() destruye el contenido de
yytext, que va en
contra de la especificación POSIX.
En esta sección comentaremos todas las áreas conocidas de
incompatibilidades entre flex, lex de AT&T, y la especificación
POSIX.
La opción
-l de
flex activa la máxima compatibilidad
con la implementación original de
lex de AT&T, con el coste
de una mayor pérdida de rendimiento en el analizador generado.
Indicamos más abajo qué incompatibilidades pueden superarse
usando la opción
-l.
flex es totalmente compatible con
lex con las siguientes
excepciones:
- -
- La variable interna del analizador de lex sin documentar
yylineno no se ofrece a menos que se use -l o %option
yylineno.
- yylineno debería gestionarse por buffer, en lugar de por
analizador (simple variable global).
- yylineno no es parte de la especificación POSIX.
- -
- La rutina input() no es redefinible, aunque podría invocarse
para leer los caracteres que siguen a continuación de lo que haya
sido reconocido por una regla. Si input() se encuentra con un
fin-de-fichero se realiza el procesamiento de yywrap() normal.
input() retorna un fin-de-fichero ``real'' como EOF.
- La entrada en su lugar se controla definiendo la macro
YY_INPUT.
- La restricción de flex de que input() no puede
redefinirse va de acuerdo a la especificación POSIX, que
simplemente no especifica ninguna manera de controlar la entrada del
analizador que no sea haciendo una asignación inicial a
yyin.
- -
- La rutina unput() no es redefinible. Esta restricción va de
acuerdo a POSIX.
- -
- Los analizadores de flex no son tan reentrantes como los
analizadores de lex. En particular, si tiene un analizador
interactivo y un gestor de interrupción con long-jumps fuera del
analizador, y el analizador a continuación se invoca de nuevo,
podría obtener el siguiente mensaje:
fatal flex scanner internal error--end of buffer missed
Para volver al analizador, primero utilice
yyrestart( yyin );
Vea que esta llamada eliminará cualquier entrada en el buffer;
normalmente esto no es un problema con un analizador interactivo.
- Dese cuenta también de que las clases analizadoras en C++
son reentrantes, así que si usar C++ es una opción
para usted, debería utilizarla. Vea "Generando Escáners
en C++" más arriba para los detalles.
- -
- output() no se provee. La salida desde la macro ECHO se hace
al puntero de fichero yyout (por defecto a stdout).
- output() no es parte de la especificación POSIX.
- -
- lex no acepta condiciones de arranque exclusivas (%x), aunque
están en la especificación POSIX.
- -
- Cuando se expanden las definiciones, flex las encierra entre
paréntesis. Con lex, lo siguiente:
NOMBRE [A-Z][A-Z0-9]*
%%
foo{NOMBRE}? printf( "Lo encontró\n" );
%%
no reconocerá la cadena "foo" porque cuando la macro se
expanda la regla es equivalente a "foo[A-Z][A-Z0-9]*?" y la
precedencia es tal que el '?' se asocia con "[A-Z0-9]*". Con
flex, la regla se expandirá a
"foo([A-Z][A-Z0-9]*)?" y así la cadena "foo" se
reconocerá.
- Fíjese que si la definición comienza con ^ o finaliza
con $ entonces no se expande con paréntesis, para
permitir que estos operadores aparezcan en las definiciones sin perder su
significado especial. Pero los operadores <s>, /, y
<<EOF>> no pueden utilizarse en una definición
de flex.
- El uso de -l produce en el comportamiendo de lex el no poner
paréntesis alrededor de la definición.
- La especificación de POSIX dice que la definición debe ser
encerrada entre paréntesis.
- -
- Algunas implementaciones de lex permiten que la acción de
una regla comience en una línea separada, si el patrón de la
regla tiene espacios en blanco al final:
%%
foo|bar<espacio aquí>
{ foobar_action(); }
flex no dispone de esta propiedad.
- -
- La opción %r de lex (generar un analizador Ratfor) no
se ofrece. No es parte de la especificación de POSIX.
- -
- Después de una llamada a unput(), el contenido de
yytext está indefinido hasta que se reconozca el
próximo token, a menos que el analizador se haya construido usando
%array. Este no es el caso de lex o la especificación
de POSIX. La opción -l elimina esta incompatibilidad.
- -
- La precedencia del operador {} (rango numérico) es
diferente. lex interpreta "abc{1,3}" como "empareja
uno, dos, o tres apariciones de
'abc'", mientras que flex lo interpreta como "empareja
'ab' seguida de una, dos o tres apariciones de 'c'". Lo último
va de acuerdo con la especificación de POSIX.
- -
- La precedencia del operador ^ es diferente. lex interpreta
"^foo|bar" como "empareja bien 'foo' al principio de una
línea, o 'bar' en cualquier lugar", mientras que flex
lo interpreta como "empareja 'foo' o 'bar' si vienen al principio de
una línea". Lo último va de acuerdo con la
especificación de POSIX.
- -
- Las declaraciones especiales del tamaño de las tablas tal como
%a que reconoce lex no se requieren en los analizadores de
flex; flex los ignora.
- -
- El identificador FLEX_SCANNER se #define de manera que los analizadores
podrían escribirse para ser procesados con flex o con
lex. Los analizadores también incluyen
YY_FLEX_MAJOR_VERSION y YY_FLEX_MINOR_VERSION indicando
qué versión de flex generó el analizador (por
ejemplo, para la versión 2.5, estas definiciones serán 2 y 5
respectivamente).
Las siguientes propiedades de
flex no se incluyen en
lex o la
especificación POSIX:
analizadores en C++
%option
ámbitos de condiciones de arranque
pilas de condiciones de arranque
analizadores interactivos/no-interactivos
yy_scan_string() y sus amigas
yyterminate()
yy_set_interactive()
yy_set_bol()
YY_AT_BOL()
<<EOF>>
<*>
YY_DECL
YY_START
YY_USER_ACTION
YY_USER_INIT
directivas #line
%{}'s alrededor de acciones
varias acciones en una línea
más casi todas las banderas de flex. La última propiedad en la
lista se refiere al hecho de que con
flex puede poner varias acciones
en la misma línea, sepradas con punto y coma, mientras que con
lex, lo siguiente
foo handle_foo(); ++num_foos_seen;
se trunca (sorprendentemente) a
foo handle_foo();
flex no trunca la acción. Las acciones que no se encierran en
llaves simplemente se terminan al final de la línea.
DIAGNÓSTICOS¶
aviso, la regla no se puede aplicar indica que la regla dada no puede
emparejarse porque sigue a otras reglas que siempre emparejarán el
mismo texto que el de esta. Por ejemplo, en el siguiente ejemplo
"foo" no puede emparejarse porque viene después de una regla
"atrápalo-todo" para identificadores:
[a-z]+ obtuvo_identificador();
foo obtuvo_foo();
El uso de
REJECT en un analizador suprime este aviso.
aviso, se ha especificado la opción -s pero se
puede aplicar la regla por defecto significa que es posible (tal vez
únicamente en una condición de arranque en particular) que la
regla por defecto (emparejar cualquier caracter simple) sea la única
que emparejará una entrada particular. Ya que se indicó
-s, presumiblemente esto no es lo que se pretendía.
definición no definida {reject_used_but_not_detected} o
definición no definida {yymore_used_but_not_detected} - Estos
errores pueden suceder en tiempo de compilación. Indican que el
analizador usa
REJECT o
yymore() pero que
flex
falló en darse cuenta del hecho, queriendo decir que
flex
analizó las dos primeras secciones buscando apariciones de estas
acciones y falló en encontrar alguna, pero que de algún modo se
le han colado (por medio de un archivo #include, por ejemplo). Use
%option
reject o
%option yymore para indicar a flex que realmente usa esta
funcionalidad.
flex scanner jammed - un analizador compilado con
-s ha encontrado
una cadena de entrada que no fue reconocida por niguna de sus reglas. Este
error puede suceder también debido a problemas internos.
token too large, exceeds YYLMAX - su analizador usa
%array y una
de sus reglas reconoció una cadena más grande que la constante
YYLMAX (8K bytes por defecto). Usted puede incrementar el valor
haciendo un #define
YYLMAX en la sección de definiciones de su
entrada de
flex.
el analizador requiere la opción -8 para poder usar el
carácter 'x' - La especificación de su analizador incluye el
reconocimiento del caracter de 8-bits
'x' y no ha especificado la
bandera -8, y su analizador por defecto está a 7-bits porque ha usado
las opciones
-Cf o
-CF de compresión de tablas. Vea el
comentario de la bandera
-7 para los detalles.
flex scanner push-back overflow - usted utilizó
unput()
para devolver tanto texto que el buffer del analizador no pudo mantener el
texto devuelto y el token actual en
yytext. Idealmente el analizador
debería ajustar dinámicamente el buffer en este caso, pero
actualmente no lo hace.
input buffer overflow, can't enlarge buffer because scanner uses REJECT -
el analizador estaba intentando reconocer un token extremadamente largo y
necesitó expandir el buffer de entrada. Esto no funciona con
analizadores que usan
REJECT.
fatal flex scanner internal error--end of buffer missed - Esto puede
suceder en un analizador que se reintroduce después de que un long-jump
haya saltado fuera (o sobre) el registro de activación del analizador.
Antes de reintroducir el analizador, use:
yyrestart( yyin );
o, como se comentó más arriba, cambie y use el analizador como
clase de C++.
too many start conditions in <> construct! - ha listado más
condiciones de arranque en una construcción <> que las que
existen (así que tuvo que haber listado al menos una de ellas dos
veces).
FICHEROS¶
- -lfl
- librería con la que los analizadores deben enlazarse.
- lex.yy.c
- analizador generado (llamado lexyy.c en algunos sistemas).
- lex.yy.cc
- clase generada en C++ con el analizador, cuando se utiliza -+.
- <FlexLexer.h>
- fichero de cabecera definiendo la clase base del analizador en C++,
FlexLexer, y su clase derivada, yyFlexLexer.
- flex.skl
- esqueleto del analizador. Este fichero se utiliza únicamente cuando
se construye flex, no cuando flex se ejecuta.
- lex.backup
- información de los retrocesos para la bandera -b (llamada
lex.bck en algunos sistemas).
DEFICIENCIAS / ERRORES¶
Algunos patrones de contexto posterior no pueden reconocerse correctamente y
generan mensajes de aviso ("contexto posterior peligroso"). Estos
son patrones donde el final de la primera parte de la regla reconoce el
comienzo de la segunda parte, tal como "zx*/xy*", donde el 'x*'
reconoce la 'x' al comienzo del contexto posterior. (Fíjese que el
borrador de POSIX establece que el texto reconocido por tales patrones no
está definido.)
Para algunas reglas de contexto posterior, partes que son de hecho de longitud
fija no se reconocen como tales, resultando en la pérdida de
rendimiento mencionada anteriormente. En particular, las partes que usan '|' o
{n} (tales como "foo{3}") siempre se consideran de longitud
variable.
La combinación de contexto posterior con la acción especial '|'
puede producir que el contexto posterior
fijo se convierta en contexto
posterior
variable que es más caro. Por ejemplo, en lo que viene
a continuación:
%%
abc |
xyz/def
El uso de
unput() invalida yytext e yyleng, a menos que se use la
directiva
%array o la opción
-l.
La concordancia de patrones de NUL's es substancialmente más lento que el
reconocimiento de otros caracteres.
El ajuste dinámico del buffer de entrada es lento, ya que conlleva el
reanálisis de todo el texto reconocido hasta entonces por el
(generalmente enorme) token actual.
Debido al uso simultáneo de buffers de entrada y lecturas por adelantado,
no puede entremezclar llamadas a rutinas de <stdio.h>, tales como, por
ejemplo,
getchar(), con reglas de
flex y esperar que funcione.
Llame a
input() en su lugar.
La totalidad de las entradas de la tabla listada por la bandera
-v
excluye el número de entradas en la tabla necesarias para determinar
qué regla ha sido emparejada. El número de entradas es igual al
número de estados del DFA si el analizador no usa
REJECT, y algo
mayor que el número de estados si se usa.
REJECT no puede usarse con las opciones
-f o
-F.
El algoritmo interno de
flex necesita documentación.
VER TAMBIÉN¶
lex(1),
yacc(1),
sed(1),
awk(1).
John Levine, Tony Mason, and Doug Brown,
Lex & Yacc, O'Reilly and
Associates. Esté seguro de obtener la 2ª edición.
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) (Edición en castellano:
Compiladores: Principios, Técnicas y Herramientas,
Addison-Wesley Iberoamericana, S.A. (1990)) Describe las técnicas de
concordancia de patrones usadas por
flex (autómata finito
determinista).
AUTOR¶
Vern Paxson, con la ayuda de muchas ideas e inspiración de Van Jacobson.
Versión original por Jef Poskanzer. La representación de tablas
rápidas es una implementación parcial de un diseño hecho
por Van Jacobson. La implementación fue hecha por Kevin Gong y Vern
Paxson.
Agradecimientos a los muchos
flex beta-testers, feedbackers, y
contribuidores, especialmente a 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, y aquellos cuyos nombres han caído bajo mis escasas dotes
de archivador de correo pero cuyas contribuciones son apreciadas todas por
igual.
Agradecimientos a Keith Bostic, Jon Forrest, Noah Friedman, John Gilmore, Craig
Leres, John Levine, Bob Mulcahy, G.T. Nicol, Francois Pinard, Rich Salz, y a
Richard Stallman por la ayuda con diversos quebraderos de cabeza con la
distribución.
Agradecimientos a Esmond Pitt y Earle Horton por el soporte de caracteres de
8-bits; a Benson Margulies y a Fred Burke por el soporte de C++; a Kent
Williams y a Tom Epperly por el soporte de la clase de C++; a Ove Ewerlid por
el soporte de NUL's; y a Eric Hughes por el soporte de múltiples
buffers.
Este trabajo fue hecho principalmente cuando yo estaba con el Grupo de Sistemas
de Tiempo Real en el Lawrence Berkeley Laboratory en Berkeley, CA. Muchas
gracias a todos allí por el apoyo que recibí.
Enviar comentarios a vern@ee.lbl.gov.
Sobre esta traducción enviar comentarios a Adrián Pérez
Jorge (alu1415@csi.ull.es).