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' ó `%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 '/' */
<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' (see section
Opciones∞).