Subsecciones

Expresiones Regulares en Flex

Puede encontrar los ejemplos de este capítulo en https://github.com/crguezl/flex-examples.

Un lenguaje regular es aquel que puede ser descrito mediante expresiones regulares como las que se utilizan en ex, vi, sed, perl y en tantas otras utilidades UNIX. Dado un lenguaje regular, un analizador léxico es un programa capaz de reconocer las entradas que pertenecen a dicho lenguaje y realizar las acciones semánticas que se hayan asociado con los estados de aceptación. Un generador de analizadores léxicos es una herramienta que facilita la construcción de un analizador léxico. Un generador de analizadores léxicos parte, por tanto, de un lenguaje adecuado para la descripción de lenguajes regulares (y de su semántica) y produce como salida una función (en C, por ejemplo) que materializa el correspondiente analizador léxico. La mayor parte de los generadores producen a partir del conjunto de expresiones regulares los correspondientes tablas de los autómatas finitos deterministas. Utilizando dichas tablas y un algoritmo de simulación genérico del autómata finito determinista se obtiene el analizador léxico. Una vez obtenido el estado de aceptación a partir de la entrada es posible, mediante una sentencia switch ejecutar la acción semántica asociada con la correspondiente expresión regular.


Estructura de un programa LEX

Estructura de un programa

LEX y FLEX son ejemplos de generadores léxicos. Flex lee desde la entrada estándar si no se especifica explícitamente un fichero de entrada. El fichero de entrada reglen.l (se suele usar el tipo l) debe tener la forma:

%{
declaration C1
.
.
.

declaration CM
%}
macro_name1 regular_definition1
.
.
.

macro_nameR regular_definitionR

%x exclusive_state
%s inclusive_state
%%

regular_expression1 { action1(); }
.
.
.

regular_expressionN { actionN(); }

%%
support_routine1() {
}
.
.
.

support_routineS() {
}

Como vemos, un programa LEX consta de 3 secciones, separadas por %%. La primera sección se denomina sección de definiciones, la segunda sección de reglas y la tercera sección de código. La primera y la última son opcionales, ası el programa legal LEX mas simple es:

%%

que genera un analizador que copia su entrada en stdout.

Compilación

Una vez compilado el fichero de entrada regleng.l mediante la correspondiente orden:

flex reglen.l

obtenemos un fichero denominado lex.yy.c. Este fichero contiene la rutina yylex() que realiza el análisis léxico del lenguaje descrito en regleng.l. Supuesto que una de las support_routines es una función main() que llama a la función yylex(), podemos compilar el fichero generado con un compilador C para obtener un ejecutable a.out:

cc lex.yy.c -lfl

La inclusión de la opción -fl enlaza con la librerıa de flex, que contiene dos funciones: main y yywrap().

Ejecución

Cuando ejecutamos el programa a.out, la función yylex() analiza las entradas, buscando la secuencia mas larga que casa con alguna de las expresiones regulares (regular_expressionK) y ejecuta la correspondiente acción (actionK()). Si no se encuentra ningun emparejamiento se ejecuta la regla por defecto, que es:

(.|\n) { printf("%s",yytext); }

Si encuentran dos expresiones regulares con las que la cadena mas larga casa, elige la que figura primera en el programa lex. Una vez que yylex() ha encontrado el token, esto es, el patrón que casa con la cadena mas larga, dicha cadena queda disponible a través del puntero global yytext , y su longitud queda en la variable entera global yyleng .

Una vez que se ha ejecutado la correspondiente acción, yylex() continúa con el resto de la entrada, buscando por subsiguientes emparejamientos. Asi continúa hasta encontrar un end of file, en cuyo caso termina, retornando un cero o bien hasta que una de las acciones explicitamente ejecuta una sentencia return.

Sección de definiciones

La primera sección contiene, si las hubiera, las definiciones regulares y las declaraciones de los estados de arranque.

Las definiciones tiene la forma:

name regular_definition

donde name puede ser descrito mediante la expresión regular:

[a-zA-Z_][a-zA-Z_0-9-]*

La regular_definition comienza en el primer carácter no blanco que sigue a name y termina al final de la lınea. La definición es una expresión regular extendida. Las subsiguientes definiciones pueden ``llamar'' a la macro {name} escribiéndola entre llaves. La macro se expande entonces a (regular_definition) en flex y a regular_definition en lex.

El código entre los delimitadores %{ y %} se copia verbatim al fichero de salida, situándose en la parte de declaraciones globales. Los delimitadores deben aparecer (sólos) al comienzo de la lınea.

El Lenguaje de las Expresiones Regulares Flex

La sintáxis que puede utilizarse para la descripción de las expresiones regulares es la que se conoce como ``extendida'':

Los operadores han sido listados en orden de precedencia, de la mas alta a la mas baja. Por ejemplo foo|bar+ es lo mismo que (foo)|(ba(r)+).

Las Acciones Semánticas

Cada patrón regular tiene su correspondiente acción asociada. El patrón termina en el primer espacio en blanco (sin contar aquellos que están entre comillas dobles o prefijados de secuencias de escape). Si la acción comienza con {, entonces se puede extender a través de multiples líneas, hasta la correspondiente }. El programa flex no hace un análisis del código C dentro de la acción. Existen tres directivas que pueden insertarse dentro de las acciones: BEGIN, ECHO y REJECT. Su uso se muestra en los subsiguientes ejemplos.

La sección de código se copia verbatim en lex.yy.c. Es utilizada para proveer las funciones de apoyo que se requieran para la descripción de las acciones asociadas con los patrones que parecen en la sección de reglas.

Versión Utilizada

Todos los ejemplos que aparecen en este documento fueron preparados con la versión 2.5.4 de flex en un entorno Linux
$ uname -a
Linux nereida.deioc.ull.es 2.2.12-20 #10 Mon May 8 19:40:16 WEST 2000 i686 unknown
$ flex --version   
flex version 2.5.4
y con la versión 2.5.2 en un entorno Solaris
> uname -a
SunOS fonil 5.7 Generic_106541-04 sun4u sparc SUNW,Ultra-5_10
> flex --version
flex version 2.5.2


Espacios en blanco dentro de la expresión regular

La expresión regular va desde el comienzo de la línea hasta el primer espacio en blanco no escapado. Todos los espacios en blanco que formen parte de la expresión regular deben ser escapados o protegidos entre comillas. Así, el siguiente programa produce un error en tiempo de compilación C:
> cat spaces.l
%%
one two { printf("spaces\n"; }
%%
nereida:~/public_html/regexpr/lex/src> flex spaces.l
nereida:~/public_html/regexpr/lex/src> gcc lex.yy.c
spaces.l: In function `yylex':
spaces.l:2: `two' undeclared (first use in this function)
spaces.l:2: (Each undeclared identifier is reported only once
spaces.l:2: for each function it appears in.)
spaces.l:2: parse error before `{'
spaces.l:4: case label not within a switch statement
lex.yy.c:632: case label not within a switch statement
lex.yy.c:635: case label not within a switch statement
lex.yy.c:757: default label not within a switch statement
lex.yy.c: At top level:
lex.yy.c:762: parse error before `}'
Deberíamos escapar el blanco entre one y two o bien proteger la cadena poniéndola entre comillas: "one two".


Ejemplo Simple

Este primer ejemplo sustituye las apariciones de la palabra username por el login del usuario:
$ cat subst.l
%option main
%{
#include <unistd.h>
%}
%%
username    printf( "%s",  getlogin());
%%
$ flex -osubst.c subst.l
$ gcc -o subst subst.c
$ subst
Dear username:
Dear pl:
He presionado CTRL-D para finalizar la entrada.
Observe el uso de la opción %option main en el fichero subst.l para hacer que flex genere una función main. También merece especial atención el uso de la opción -osubst para cambiar el nombre del fichero de salida, que por defecto será lex.yy.c.


Suprimir

Al igual que en sed y awk, es muy sencillo suprimir las apariciones de una expresión regular.
$ cat delete.l
/* delete all entries of zap me */
%%
"zap me"
$ flex delete.l ; gcc lex.yy.c -lfl; a.out
this is zap me a first zap me phrase
this is  a first  phrase


Declaración de yytext

En la sección de definiciones es posible utilizar las directivas %pointer o %array. Estas directivas hacen que yytext se declare como un puntero o un array respectivamente. La opción por defecto es declararlo como un puntero, salvo que se haya usado la opción -l en la línea de comandos, para garantizar una mayor compatibilidad con LEX. Sin embargo, y aunque la opción %pointer es la mas eficiente (el análisis es mas rápido y se evitan los buffer overflow), limita la posible manipulación de yytext y de las llamadas a unput().
$ cat yytextp.l
%%
hello {
        strcat(yytext, " world");
        printf("\n%d: %s\n",strlen(yytext),yytext);
      }
$ flex yytextp.l ; gcc lex.yy.c -lfl ; a.out
hello
 
11: hello world
 
fatal flex scanner internal error--end of buffer missed
Este error no aparece si se utiliza la opción %array:
$ cat yytext.l
%array
%%
hello {
        strcat(yytext, " world");
        printf("\n%d: %s\n",strlen(yytext),yytext);
      }
$ flex yytext.l; gcc lex.yy.c -lfl; a.out
hello
 
11: hello world

Además, algunos programs LEX modifican directamente yytext, utilizando la declaración:

extern char yytext[]

que es incompatible con la directiva %pointer (pero correcta con %array). La directiva %array define yytext como un array de tamaño YYLMAX. Si deseamos trabajar con un mayor tamaño, basta con redefinir YYLMAX.


Declaración de yylex()

Por defecto la función yylex() que realiza el análisis léxico es declarada como int yylex(). Es posible cambiar la declaración por defecto utilizando la macro YY_DECL. En el siguiente ejemplo la definición:
#define YY_DECL char *scanner(int *numcount, int *idcount)
hace que la rutina de análisis léxico pase a llamarse scanner y tenga dos parametros de entrada, retornando un valor de tipo char *.
$ cat decl.l
%{
#define YY_DECL char *scanner(int *numcount, int *idcount)
%}
 
num [0-9]+
id [a-z]+
%%
{num} {(*numcount)++;}
halt  {return ((char *) strdup(yytext));}
{id}  {(*idcount)++;}
%%
main() {
  int a,b;
  char *t;
 
  a = 0; b = 0;
  t = scanner(&a, &b);
  printf("numcount = %d, idcount = %d, yytext = %s\n",a,b,t);
  t = scanner(&a, &b);
  printf("numcount = %d, idcount = %d, yytext = %s\n",a,b,t);
}
 
int yywrap() {
  return 1;
}
La ejecución del programa anterior produce la siguiente salida:
$ decl
a b 1 2 3 halt
     numcount = 3, idcount = 2, yytext = halt
 
e 4 5 f
 
numcount = 5, idcount = 4, yytext = (null)
$ decl
a b 1 2 3 halt
     numcount = 3, idcount = 2, yytext = halt
 
e 4 f 5 halt
    numcount = 5, idcount = 4, yytext = halt

yywrap()

Cuando el analizador léxico alcanza el final del fichero, el comportamiento en las subsiguientes llamadas a yylex resulta indefinido. En el momento en que yylex() alcanza el final del fichero llama a la función yywrap, la cual retorna un valor de 0 o 1 según haya mas entrada o no. Si el valor es 0, la función yylex asume que la propia yywrap se ha encargado de abrir el nuevo fichero y asignarselo a yyin . Otra manera de continuar es haciendo uso de la función yyrestart(FILE *file). El siguiente ejemplo cuenta el número de lıneas, palabras y caracteres en una lista de ficheros proporcionados como entrada.

%{
unsigned long charCount = 0, wordCount = 0, lineCount = 0;
%}

word [^ \t\n]+ 
eol \n 

%%
{word} { wordCount++; charCount += yyleng; }
{eol} { charCount++; lineCount++; }
. charCount++;

%%

char **fileList;
unsigned nFiles;
unsigned currentFile = 0;
unsigned long totalCC = 0;
unsigned long totalWC = 0;
unsigned long totalLC = 0;

main ( int argc, char **argv) {
  FILE *file;

  fileList = argv + 1; nFiles = argc - 1;

  if (nFiles == 0) {
    fprintf(stderr,"Usage is:\n%s file1 file2 file3 ...\n",argv[0]);
    exit(1);
  }
  file = fopen (fileList[0], "r");
  if (!file) {
      fprintf (stderr, "could not open %s\n", argv[1]);
      exit (1);
  }
  currentFile = 1; yyrestart(file);
  yylex ();
  printf ("%8lu %8lu %8lu %s\n", lineCount, wordCount,
    charCount, fileList[currentFile - 1]);
  if (argc > 2) {
      totalCC += charCount; totalWC += wordCount; totalLC += lineCount;
      printf ("%8lu %8lu %8lu total\n", totalLC, totalWC, totalCC);
  }
  return 0;
}

int yywrap () {
  FILE *file;

  if (currentFile < nFiles) {
     printf ("%8lu %8lu %8lu %s\n", lineCount, wordCount,
       charCount, fileList[currentFile - 1]);
     totalCC += charCount; totalWC += wordCount; totalLC += lineCount;
     charCount = wordCount = lineCount = 0;
     fclose (yyin);

     while (fileList[currentFile] != (char *) 0) {
       file = fopen (fileList[currentFile++], "r");
       if (file != NULL) { yyrestart(file); break; }
	  fprintf (stderr, "could not open %s\n", fileList[currentFile - 1]);
     }
     return (file ? 0 : 1);
    }
    return 1;
}

La figura muestra el proceso de compilación y la ejecución:

$ flex countlwc.l;gcc lex.yy.c; a.out *.l
      58      179     1067 ape-05.l
      88      249     1759 countlwc.l
      11       21      126 magic.l
       9       17      139 mgrep.l
       9       16      135 mlg.l
       5       15      181 ml.l
       7       12       87 subst.l
     187      509     3494 total
La diferencia esencial entre asignar yyin o llamar a la función yyrestart es que esta última puede ser utilizada para conmutar entre ficheros en medio de un análisis léxico. El funcionamiento del programa anterior no se modifica si se se intercambian asignaciones a yyin (yyin = file) y llamadas a yyrestart(file).

unput()

La función unput(c) coloca el carácter c en el flujo de entrada, de manera que será el primer carácter leıdo en próxima ocasión.
$ cat unput2.l
%array
%%
[a-z] {unput(toupper(yytext[0]));}
[A-Z] ECHO;
%%
$ flex unput2.l ; gcc lex.yy.c -lfl;a.out
abcd
ABCD
Un problema importante con unput es que, cuando se utiliza la opción %pointer, las llamadas a unput destruyen los contenidos de yytext. Es por eso que, en el siguiente ejemplo se hace una copia de yytext. La otra alternativa es, por supuesto, usar la opción %array.
$ cat unput.l
%%
[0-9]+ {
  int i;
  char *yycopy = (char *) strdup(yytext);
 
  unput(')');
  for(i=strlen(yycopy)-1; i>=0; --i)
    unput(yycopy[i]);
  unput('(');
  free(yycopy);
}
\([0-9]+\) printf("Num inside parenthesis: %s\n",yytext);
.|\n
$ flex unput.l ; gcc lex.yy.c -lfl ; a.out
32
Num inside parenthesis: (32)
(43)
Num inside parenthesis: (43)


input()

La función input() lee desde el flujo de entrada el siguiente carácter. Normalmente la utilizaremos si queremos tomar ``personalmente el control'' del análisis. El ejemplo permite ``engullir'' los comentarios (no anidados):
$ cat input.l
%%
"/*" {
        int c;
        for(;;) {
          while ((c=input()) != '*' && c != EOF)
            ;
          if (c == '*')  {
            while ((c = input()) == '*')
              ;
            if (c == '/') break;
          }
          if (c == EOF) {
            fprintf(stderr,"Error: EOF in comment");
            yyterminate();
          }
        }
      }
La función yyterminate() termina la rutina de análisis léxico y devuelve un cero indicándole a la rutina que llama que todo se ha acabado. Por defecto, yyterminate() es llamada cuando se encuentra un final de fichero. Es una macro y puede ser redefinida.
$ flex input.l ; gcc lex.yy.c -lfl ; a.out
hello /* world */
hello
unfinished /* comment
unfinished Error: EOF in comment
He presionado CTRL-D después de entrar la palabra comment.

REJECT

La directiva REJECT le indica al analizador que proceda con la siguiente regla que casa con un prefijo de la entrada. Como es habitual en flex, se elige la siguiente regla que casa con la cadena mas larga. Consideremos el siguiente ejemplo:
$ cat reject.l
%%
a    |
ab   |
abc  |
abcd ECHO; REJECT; printf("Never seen\n");
.|\n
La salida es:
$ gcc lex.yy.c -lfl;a.out
abcd
abcdabcaba
Observe que REJECT supone un cambio en el flujo de control: El código que figura después de REJECT no es ejecutado.

yymore()

La función yymore() hace que, en vez de vaciar yytext para el siguiente matching, el valor actual se mantenga, concatenando el valor actual de yytext con el siguiente:
$ cat yymore.l
%%
mega- ECHO; yymore();
kludge ECHO;
 
$ flex yymore.l ; gcc lex.yy.c -lfl ; a.out
mega-kludge
mega-mega-kludge
La variable yyleng no deberıa ser modificada si se hace uso de la función yymore().

yyless()

La función yyless(n) permite retrasar el puntero de lectura de manera que apunta al carácter n de yytext. Veamos un ejemplo:
$ cat yyless.l
%%
foobar ECHO; yyless(4);
[a-z]+ ECHO;
 
$ flex yyless.l; gcc lex.yy.c -lfl; a.out
foobar
foobarar
Veamos un ejemplo mas ``real''. supongamos que tenemos que reconocer las cadenas entre comillas dobles, pero que pueden aparecer en las mismas secuencias de escape \". La estrategia general del algoritmo es utilizar la expresión regular \"[^"]+\" y examinar si los dos últimos carácteres en yytext son \". En tal caso, se concatena la cadena actual (sin la " final) como prefijo para el próximo emparejamiento (utilizando yymore). La eliminación de la " se hace a través de la ejecución de yyless(yyleng-1), que al mismo tiempo garantiza que el próximo emparejamiento tendrá lugar con este mismo patrón \"[^"]+\".
$ cat quotes.l
%%
\"[^"]+\" {
            printf("Processing string. %d: %s\n",yyleng,yytext);
            if (yytext[yyleng-2] =='\\') {
             yyless(yyleng-1); /* so that it will match next time */
             yymore(); /* concatenate  with current yytext */
             printf("After yyless. %d: %s\n",yyleng,yytext);
            } else {
              printf("Finished. The string is: %s\n",yytext);
            }
          }
El ejemplo no puede entenderse si no se tiene en cuenta que yyless(yyleng-1) actualiza los valores de yyleng y yytext, como muestra la salida.
¿Qué ocurre si intercambiamos las posiciones de yymore() e yyless(yyleng-1) en el código? >Cambiara la salida? La respuesta es que no. Parece que la concatenación se hace con el valor final de yytext y no con el valor que este tenıa en el momento de la llamada a yymore.
Otra observación a tener en cuenta es que yyless() es una macro y que, por tanto, sólo puede ser utilizada dentro del fichero lex y no en otros ficheros fuentes.

En general, el uso de estas funciones nos puede resolver el problema de reconocer lımites que de otra forma serıan difıciles de expresar con una expresión regular.

$ flex quotes.l ; gcc lex.yy.c -lfl ; a.out
"Hello \"Peter\", nice to meet you"
Procesing string. 9: "Hello \"
After yyless. 8: "Hello \
Procesing string. 16: "Hello \"Peter\"
After yyless. 15: "Hello \"Peter\
Procesing string. 35: "Hello \"Peter\", nice to meet you"
Finished. The string is: "Hello \"Peter\", nice to meet you"

Estados

Las expresiones regulares pueden ser prefijadas mediante estados. Los estados o condiciones de arranque, se denotan mediante un identificador entre ángulos y se declaran en la parte de las definiciones. Las declaraciones se hacen mediante %s para los estados ``inclusivos'' o bien %x para los estados ``exclusivos'', seguidos de los nombres de los estados. No pueden haber caracteres en blanco antes de la declaración. Un estado se activa mediante la acción BEGIN estado. A partir de ese momento, las reglas que esten prefijadas con el estado pasan a estar activas. En el caso de que el estado sea inclusivo, las reglas no prefijadas también permanecen activas. Los estados exclusivos son especialmente útiles para especificar ``sub analizadores'' que analizan porciones de la entrada cuya estructura ``sintáctica'' es diferente de la del resto de la entrada.

El ejemplo ``absorbe'' los comentarios, conservando el numero de lıneas del fichero en la variable linenum

$ cat comments.l
%option noyywrap
%{
  int linenum = 0;
%}
%x comment
%%
 
"/*" BEGIN(comment); printf("comment=%d, YY_START = %d, YYSTATE = %d",comment,YY_START,YYSTATE);
<comment>[^*\n]* /* eat anything that is not a star * /
<comment>"*"+[^*/\n]* /* eat up starts not followed by / */
<comment>\n ++linenum; /* update number of lines */
<comment>"*"+"/" BEGIN(0);
 
\n ECHO; linenum++;
.  ECHO;
%%
main() {
  yylex();
  printf("\n%d lines\n",linenum);
}
La opción noyywrap hace que yylex() no llame a la función yywrap() al final del fichero y que asuma que no hay mas entrada por procesar.

Los estados se traducen por enteros, pudiendo ser manipulados como tales. La macro INITIAL puede utilizarse para referirse al estado 0. Las macros YY_START y YYSTATE contienen el valor del estado actual.

$ flex comments.l ; gcc lex.yy.c ; a.out < hello.c
main() <%
int a<:1:>; comment=1, YY_START = 1, YYSTATE = 1
  a<:0:> = 4; comment=1, YY_START = 1, YYSTATE = 1
  printf("hello world! a(0) is %d\n",a<:0:>);
%>
 
6 lines     
$ cat hello.c
main() <%
int a<:1:>; /* a comment */
  a<:0:> = 4; /* a comment in
                 two lines */
  printf("hello world! a(0) is %d\n",a<:0:>);
%>                                  
En flex es posible asociar un ámbito con los estados o condiciones iniciales. Basta con colocar entre llaves las parejas patrón acción gobernadas por ese estado. El siguiente ejemplo procesa las cadenas C:
$ cat ststring.l
%option main
%x str
%{
#define MAX_STR_CONST 256
 
  char string_buffer[MAX_STR_CONST];
  char *string_buf_ptr;
%}
 
%%
\"  string_buf_ptr  = string_buffer; BEGIN(str);
<str>{
\"             {BEGIN (INITIAL); *string_buf_ptr = '\0'; printf("%s",string_buffer); }
\n             { printf("Error: non terminated string\n"); exit(1); }
\\[0-7]{1,3}   { int result; /* octal escape sequence */
                        (void) sscanf(yytext+1,"%o",&result);
                         if (result > 0xff) {printf("Error: constant out of bounds\n"); exit(2); }
                         *string_buf_ptr++ = result;
                    }
\\[0-9]+       { printf("Error: bad escape sequence\n"); exit(2); }
\\n            {*string_buf_ptr++ = '\n';}
\\t            {*string_buf_ptr++ = '\t';}
\\b            {*string_buf_ptr++ = '\b';}
\\r            {*string_buf_ptr++ = '\r';}
\\f            {*string_buf_ptr++ = '\f';}
\\(.|\n)       {*string_buf_ptr++ = yytext[1];}
[^\\\n\"]+     {char *yptr = yytext; while(*yptr) *string_buf_ptr++ = *yptr++; }
}
(.|\n)
%%                            
$ flex ststring.l ; gcc lex.yy.c ; a.out < hello.c
        hello
world! a(0) is %d
$ cat hello.c
main() <%
int a<:1:>; /* a comment */
  a<:0:> = 4; /* a comment in
                 two lines */
  printf("\thell\157\nworld! a(0) is %d\n",a<:0:>);
%>                                   
Obsérve la conducta del programa ante las siguientes entradas:

La pila de estados

Mediante el uso de la opción

%option stack

tendremos acceso a una pila de estados y a tres rutinas para manipularla:

Ejemplo

El siguiente programa flex utiliza las funciones de la pila de estados para reconocer el lenguaje (no regular) $ \{a^n b^n / n \in N\}$

%option main
%option noyywrap
%option stack
%{
#include <stdio.h>
#include  <stdlib.h>
%}
%x estado_a
%%
^a { yy_push_state(estado_a);}
<estado_a>{
a      { yy_push_state(estado_a); }
b      { yy_pop_state();  }
b[^b\n]+    {   printf ("Error\n"); 
        while (YYSTATE != INITIAL)
          yy_pop_state();
        while (input() != '\n') ;
      }
(.|\n)  {  printf ("Error\n"); 
        while (YYSTATE != INITIAL)
          yy_pop_state();
        while (input() != '\n') ;
      }
}
.      {  printf ("Error\n");
        while (input() != '\n') ;
      }
\n      { printf("Aceptar\n");}
%%

Final de Fichero

El patrón <<EOF>> permite asociar acciones que se deban ejecutar cuando se ha encontrado un end of file y la macro yywrap() ha devuelto un valor no nulo.

Cualquiera que sea, la acción asociada deberá de optar por una de estas cuatro alternativas:

El patrón <<EOF>> no puede usarse con otras expresiones regulares. Sin embargo, es correcto prefijarlo con estados.

Si <<EOF>> aparece sin condiciones de arranque, la regla se aplica a todos los estados que no tienen una regla <<EOF>> específica. Si lo que se quiere es que la regla se restringa al ámbito del estado inicial se deberá escribir:

<INITIAL><<EOF>>

Sigue un programa que reconoce los comentarios anidados en C. Para detectar comentarios incacabados usaremos <<EOF>>.

%option stack
%x comment
%%
"/*"   { yy_push_state(comment); }
(.|\n) ECHO;
<comment>"/*"     { yy_push_state(comment); } 
<comment>"*/"     { yy_pop_state(); } 
<comment>(.|\n)   ;
<comment><<EOF>>  { fprintf(stderr,"Error\n"); exit(1); }
%%

$ cat hello.c
main() {
int a[1]; /* a /* nested comment */. */
  a[0] = 4; /* a  /* nested comment in
                 /* two */ lines  */ *****/
}                                                   
$ flex nestedcom.l ; gcc lex.yy.c -lfl ; a.out < hello.c
main() {
int a[1];
  a[0] = 4;
}
$ cat hello4.c
main() {
int a[1]; /* a /* nested comment */. */
  a[0] = 4; /* an  /* incorrectly nested comment in
                 /* two  lines  */ *****/
}
$ a.out < hello4.c
main() {
int a[1];
Error
  a[0] = 4;

Uso de Dos Analizadores

La opción -Pprefix de flex cambia el prefijo por defecto yy para todas las variables globales y funciones. Por ejemplo -Pfoo cambia el nombre de yytext footext. También cambia el nombre del fichero de salida de lex.yy.c a lex.foo.c. Sigue la lista de identificadores 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
Desde dentro del analizador léxico puedes referirte a las variables globales y funciones por cualquiera de los nombres, pero externamente tienen el nombre cambiado. Esta opción nos permite enlazar diferentes programas flex en un mismo ejecutable.

Sigue un ejemplo de uso de dos analizadores léxicos dentro del mismo programa:

$ cat one.l
%%
one {printf("1\n"); return 1;}
.   {printf("First analyzer: %s\n",yytext);}
%%
 
int onewrap(void) {
  return 1;
}
 
$ cat two.l
%%
two {printf("2\n"); return 2;}
.   {printf("Second analyzer: %s\n",yytext);}
%%
int twowrap(void) {
  return 1;
}
$ cat onetwo.c
main() {
  onelex();
  twolex();
}
Como hemos mencionado, la compilación flex se debe realizar con el opción -P, que cambia el prefijo por defecto yy de las funciones y variables accesibles por el usuario. El mismo efecto puede conseguirse utilizando la opción prefix, escribiendo %option prefix="one" y %option prefix="two" en los respectivos programas one.l y two.l.
$ flex -Pone one.l
$ flex -Ptwo two.l
$ ls -ltr | tail -2
-rw-rw----   1 pl       casiano     36537 Nov  7 09:52 lex.one.c
-rw-rw----   1 pl       casiano     36524 Nov  7 09:52 lex.two.c
$ gcc onetwo.c lex.one.c lex.two.c
$ a.out
two
First analyzer: t
First analyzer: w
First analyzer: o
 
one
1
one
Second analyzer: o
Second analyzer: n
Second analyzer: e
 
two
2
$

La Opción outfile

Es posible utilizar la opción -ooutput.c para escribir el analizador léxico en el fichero output.c en vez de en lex.yy.c. El mismo efecto puede obtenerse usando la opción outfile="output.c" dentro del programa lex.

Leer desde una Cadena: YY_INPUT

En general, la rutina que hace el análisis léxico, yylex(), lee su entrada a través de la macro YY_INPUT. Esta macro es llamada con tres parámetros
YY_INPUT(buf,result,max)
el primero, buf es utilizado para guardar la entrada. el tercero max indica el número de caracteres que yylex() pretende leer de la entrada. El segundo result contendrá el número de caracteres realmente leıdos. Para poder leer desde una cadena (string) basta con modificar YY_INPUT para que copie los datos de la cadena en el buffer pasado como parámetro a YY_INPUT. Sigue un ejemplo:
$ cat string.l
%{
#undef YY_INPUT
#define YY_INPUT(b,r,m) (r = yystringinput(b,m))
#define min(a,b) ((a<b)?(a):(b))
%}
 
%%
[0-9]+ printf("Num-");
[a-zA-Z][a-zA-Z_0-9]* printf("Id-");
[ \t]+
. printf("%c-",yytext[0]);
%%
 
extern char string[];
extern char *yyinputptr;
extern char *yyinputlim;
 
int yystringinput(char *buf, int maxsize) {
  int n = min(maxsize, yyinputlim-yyinputptr);
 
  if (n > 0) {
    memcpy(buf, yyinputptr, n);
    yyinputptr += n;
  }
  return n;
}
 
int yywrap() { return 1; }
Este es el fichero conteniendo la función main:
$ cat stringmain.c
char string[] = "one=1;two=2";
char *yyinputptr;
char *yyinputlim;
 
main() {
  yyinputptr = string;
  yyinputlim = string + strlen(string);
  yylex();
  printf("\n");
}
Y esta es la salida:
$ a.out
Id-=-Num-;-Id-=-Num-
La cadena string = "one=1;two=2" definida en la línea 2 ha sido utilizada como entrada para el análisis léxico.

El operador de ``trailing context'' o ``lookahead'' positivo

En el lenguaje FORTRAN original los ``blancos'' no eran significativos y no se distinguía entre mayúsculas y minúsculas. Así pues la cadena do i = 1, 10 es equivalente a la cadena DOI=1,10. Un conocido conflicto ocurre entre una cadena con la estructura do i = 1.10 (esto es DOI=1.10) y la cadena anterior. En la primera DO e I son dos ``tokens'' diferentes, el primero correspondiendo a la palabra reservada que indica un bucle. En la segunda, DOI constituye un único ``token'' y la sentencia se refiere a una asignación. El conflicto puede resolverse utilizando el operador de ``trailing'' r/s. Como se mencionó, el operador de ``trailing''r/s permite reconocer una r pero sólo si va seguida de una s. El texto casado con s se incluye a la hora de decidir cual es el emparejamiento mas largo, pero se devuelve a la entrada cuando se ejecuta la acción. La acción sólo ve el texto asociado con r. El fichero fortran4.l ilustra una posible solución:
 cat fortran4.l
%array
%{
#include <string.h>
#undef YY_INPUT
#define YY_INPUT(buf,result,max) (result = my_input(buf,max))
%}
number [0-9]+
integer [+-]?{number}
float ({integer}\.{number}?|\.{number})(E{integer})?
label [A-Z0-9]+
id   [A-Z]{label}*
%%
DO/{label}={number}\, { printf("do loop\n"); }
{id} { printf("Identifier %s\n",yytext); }
{number} { printf("Num %d\n",atoi(yytext)); }
{float} { printf("Float %f\n",atof(yytext)); }
(.|\n)
%%

int my_input(char *buf, int max)
{
  char *q1, *q2, *p = (char *) malloc(max);
  int i;
  if ('\0' != fgets(p,max,yyin)) {
    for(i=0, q1=buf, q2=p;(*q2 != '\0');q2++) {
      if (*q2 != ' ') { *q1++ = toupper(*q2); i++; };
    }
    free(p);
    return i;
  }
  else exit(1);
}
La función

char *fgets(char *s, int size, FILE *stream)

lee a lo mas uno menos que size caracteres desde stream y los almacena en el buffer apuntado por s. La lectura termina después de un EOF o un retorno de carro. Si se lee un \n, se almacena en el buffer. La función pone un carácter nulo \0 como último carácter en el buffer.

A continuación, puedes ver los detalles de una ejecución:

$ flex fortran4.l; gcc lex.yy.c -lfl; a.out
do j = 1 . 10
Identifier DOJ
Float 1.100000
do k = 1, 5
do loop
Identifier K
Num 1
Num 5


Manejo de directivas include

El analisis léxico de algunos lenguajes requiere que, durante la ejecución, se realice la lectura desde diferentes ficheros de entrada. El ejemplo tıpico es el manejo de las directivas include file existentes en la mayorıa de los lenguajes de programación.
¿Donde está el problema? La dificultad reside en que los analizadores generados por flex proveen almacenamiento intermedio (buffers) para aumentar el rendimiento. No basta con reescribir nuestro propio YY_INPUT de manera que tenga en cuenta con que fichero se esta trabajando. El analizador sólo llama a YY_INPUT cuando alcanza el final de su buffer, lo cual puede ocurrir bastante después de haber encontrado la sentencia include que requiere el cambio de fichero de entrada.
$ cat include.l
%x incl
%{
#define yywrap() 1
#define MAX_INCLUDE_DEPTH 10
YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];
int include_stack_ptr = 0;
%}
%%
include          BEGIN(incl);
.                ECHO;
<incl>[ \t]*
<incl>[^ \t\n]+  { /* got the file name */
                   if (include_stack_ptr >= MAX_INCLUDE_DEPTH) {
                     fprintf(stderr,"Includes nested too deeply\n");
                     exit(1);
                   }
                   include_stack[include_stack_ptr++] = YY_CURRENT_BUFFER;
                   yyin = fopen(yytext,"r");
                   if (!yyin) {
                     fprintf(stderr,"File %s not found\n",yytext);
                     exit(1);
                   }
                   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]);
          }
        }
%%
main(int argc, char ** argv) {
 
  yyin = fopen(argv[1],"r");
  yylex();
}
La función yy_create_buffer(yyin, YY_BUF_SIZE)); crea un buffer lo suficientemente grande para mantener YY_BUF_SIZE caracteres. Devuelve un YY_BUFFER_STATE, que puede ser pasado a otras rutinas. YY_BUFFER_STATE es un puntero a una estructura de datos opaca (struct yy_buffer_state) que contiene la información para la manipulación del buffer. Es posible por tanto inicializar un puntero YY_BUFFER_STATE usando la expresión ((YY_BUFFER_STATE) 0).

La función yy_switch_to_buffer(YY_BUFFER_STATE new_buffer); conmuta la entrada del analizador léxico. La función void yy_delete_buffer( YY_BUFFER_STATE buffer ) se usa para recuperar la memoria consumida por un buffer. También se pueden limpiar los contenidos actuales de un buffer llamando a: void yy_flush_buffer( YY_BUFFER_STATE buffer )

La regla especial <<EOF>> indica la acción a ejecutar cuando se ha encontrado un final de fichero e yywrap() retorna un valor distinto de cero. Cualquiera que sea la acción asociada, esta debe terminar con uno de estos cuatro supuestos:

  1. Asignar yyin a un nuevo fichero de entrada.
  2. Ejecutar return.
  3. Ejecutar yyterminate().
  4. Cambiar a un nuevo buffer usando yy_switch_to_buffer().

    La regla <<EOF>> no se puede mezclar con otros patrones.

Este es el resultado de una ejecución del programa:

$ cat hello.c
#include hello2.c
main() <%
int a<:1:>; /* a comment */
  a<:0:> = 4; /* a comment in
                 two lines */
  printf("\thell\157\nworld! a(0) is %d\n",a<:0:>);
%>
$ cat hello2.c
#include hello3.c
/* file hello2.c  */
$ cat hello3.c
/*
third file
*/
$ flex include.l ; gcc lex.yy.c ; a.out hello.c
##/*
third file
*/
 
/* file hello2.c  */
 
main() <%
int a<:1:>; /* a comment */
  a<:0:> = 4; /* a comment in
                 two lines */
  printf("\thell\157\nworld! a(0) is %d\n",a<:0:>);
%>                                                            
Una alternativa a usar el patrón <<EOF>> es dejar la responsabilidad de recuperar el buffer anterior a yywrap(). En tal caso suprimiríamos esta parajea patrón-acción y reescribiríamos yywrap():
%x incl
%{
#define MAX_INCLUDE_DEPTH 10
YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];
int include_stack_ptr = 0;
%}
%%
include          BEGIN(incl);
.                ECHO;
<incl>[ \t]*
<incl>[^ \t\n]+  { /* got the file name */
                   if (include_stack_ptr >= MAX_INCLUDE_DEPTH) {
		     fprintf(stderr,"Includes nested too deeply\n");
		     exit(1);
		   }
		   include_stack[include_stack_ptr++] = YY_CURRENT_BUFFER;
		   yyin = fopen(yytext,"r");
		   if (!yyin) {
		     fprintf(stderr,"File %s not found\n",yytext);
		     exit(1);
		   }
		   yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
		   BEGIN(INITIAL);
		 }
%%
main(int argc, char ** argv) {

  yyin = fopen(argv[1],"r");
  yylex();
}

int yywrap() {
  if ( --include_stack_ptr < 0) { 
    return 1;
  } else {
    yy_delete_buffer(YY_CURRENT_BUFFER);
    yy_switch_to_buffer(include_stack[include_stack_ptr]);
    return 0;
  }
}

Análisis Léxico desde una Cadena: yy_scan_string

El objetivo de este ejercicio es mostrar como realizar un análisis léxico de los argumentos pasados en la lınea de comandos. Para ello flex provee la función yy_scan_string(const char * str). Esta rutina crea un nuevo buffer de entrada y devuelve el correspondiente manejador YY_BUFFER_STATE asociado con la cadena str. Esta cadena debe estar terminada por un carácter \0. Podemos liberar la memoria asociada con dicho buffer utilizando yy_delete_buffer(BUFFER). La siguiente llamada a yylex() realizará el análisis léxico de la cadena str.

$ cat scan_str.l
%%
[0-9]+     printf("num\n");
[a-zA-Z]+  printf("Id\n");
%%
main(int argc, char ** argv) {
int i;
 
  for(i=1;i<argc;i++) {
    yy_scan_string(argv[i]);
    yylex();
    yy_delete_buffer(YY_CURRENT_BUFFER);
  }
}
 
int yywrap() { return 1; }
$ flex scan_str.l ; gcc lex.yy.c ; a.out Hello World! 1234
Id
Id
!num
Alternativamente, la función main() podría haber sido escrita asi:
main(int argc, char ** argv) {
int i;
YY_BUFFER_STATE p;

  for(i=1;i<argc;i++) {
    p = yy_scan_string(argv[i]);
    yylex();
    yy_delete_buffer(p);
  }
}
La función yy_scan_bytes(const char * bytes, int len) hace lo mismo que yy_scan_string pero en vez de una cadena terminada en el carácter nulo, se usa la longitud len. Ambas funciones yy_scan_string(const char * str) y yy_scan_bytes(const char * bytes, int len) hacen una copia de la cadena pasada como argumento.

Estas dos funciones crean una copia de la cadena original. Es mejor que sea asi, ya que yylex() modifica los contenidos del buffer de trabajo. Si queremos evitar la copia, podemos usar

yy_scan_buffer(char *base, yy_size_t size),

la cual trabaja directamente con el buffer que comienza en base, de tamaño size bytes, los últimos dos de los cuáles deben ser YY_END_OF_BUFFER_CHAR (ASCII NUL). Estos dos últimos bytes no son ``escaneados''. El área de rastreo va desde base[0] a base[size-2], inclusive. Si nos olvidamos de hacerlo de este modo y no establecemos los dos bytes finales, la función yy_scan_buffer() devuelve un puntero nulo y no llega a crear el nuevo buffer de entrada. El tipo yy_size_t es un tipo entero. Como cabe esperar, size se refiere al tamaño del buffer.

Análisis de la Lınea de Comandos y 2 Analizadores

El objetivo de este ejercicio es mostrar como realizar un análisis léxico de los argumentos pasados en la lınea de comandos. Para ello diseñaremos una librerıa que proporcionará un función yylexarg(argc,argv) que hace el análisis de la lınea de acuerdo con la especificación flex correspondiente. En el ejemplo, esta descripción del analizador léxico es proporcionada en el fichero fl.l. Para complicar un poco mas las cosas, supondremos que queremos hacer el análisis léxico de un fichero (especificado en la lınea de comandos) según se especifica en un segundo analizador léxico trivial.l. El siguiente ejemplo de ejecución muestra la conducta del programa:
$ fl -v -V -f tokens.h
verbose mode is on
version 1.0
File name is: tokens.h
Analyzing tokens.h
#-id-blanks-id-blanks-int-blanks-#-id-blanks-id-blanks-int-blanks-#-id-blanks-id-blanks
-int-blanks-#-id-blanks-id-blanks-int-blanks-#-id-blanks-id-blanks-int-blanks-
Los contenidos del fichero Makefile definen las dependencias y la estructura de la aplicación:
$ cat Makefile
LIBS=-lflarg
CC=gcc -g
LIBPATH=-L. -L~/lib
INCLUDES=-I. -I~/include
 
fl: main.c lex.arg.c lex.yy.c libflarg.a tokens.h
        $(CC) $(LIBPATH) $(INCLUDES) main.c lex.arg.c lex.yy.c $(LIBS) -o fl
lex.arg.c: fl.l
        flex -Parg fl.l
lex.yy.c: trivial.l tokens.h
        flex trivial.l
libflarg.a: flarg.o
        ar r libflarg.a flarg.o
flarg.o: flarg.c
        $(CC) -c flarg.c
clean:                                 
$ make clean;make
rm lex.arg.c lex.yy.c *.o  fl
flex -Parg fl.l
flex trivial.l
gcc -g -c flarg.c
ar r libflarg.a flarg.o
gcc -g -L. -L~/lib -I. -I~/include main.c lex.arg.c lex.yy.c -lflarg -o fl
Observa el uso de la opción -Parg en la traducción del fichero fl.l. Ası no solo el fichero generado por flex, sino todas las variables y rutinas accesibles estarán prefijadas por arg en vez de yy. La librerıa la denominamos libflarg.a. (flarg por flex arguments). El correspondiente fichero cabecera será flarg.h. Los fuentes de las rutinas que compondrán la librerıa se mantendrán en el fichero flarg.c.

Lo que haremos será redefinir YY_INPUT(buf, result, max) para que lea su entrada desde la lınea de argumentos.

$ cat flarg.h
int yyarglex(int argc, char **argv);
int YY_input_from_argv(char *buf, int max);
int argwrap(void);
 
#undef YY_INPUT
#define YY_INPUT(buf,result,max) (result = YY_input_from_argv(buf,max))
La función int YY_input_from_argv(char *buf, int max) utiliza los punteros char **YY_targv y char **YY_arglim para moverse a través de la familia de argumentos. Mientras que el primero es utilizado para el recorrido, el segundo marca el lımite final. Su inicialización ocurre en

yyarglex(int argc, char **argv)
con las asignaciones:
  YY_targv = argv+1;
  YY_arglim = argv+argc;

despues, de lo cual, se llama al analizador léxico generado, arglex .

$ cat flarg.c
char **YY_targv;
char **YY_arglim;
 
int YY_input_from_argv(char *buf, int max)
{
  static unsigned offset = 0;
 
  int len, copylen;
 
    if (YY_targv >= YY_arglim) return 0;        /* EOF */
    len = strlen(*YY_targv)-offset;     /* amount of current arg */
    if(len >= max) {copylen = max-1; offset += copylen; }
    else copylen = len;
    if(len > 0) memcpy(buf, YY_targv[0]+offset, copylen);
    if(YY_targv[0][offset+copylen] == '\0') {   /* end of arg */
      buf[copylen] = ' '; copylen++; offset = 0; YY_targv++;
    }
    return copylen;
}
 
int yyarglex(int argc, char **argv) {
  YY_targv = argv+1;
  YY_arglim = argv+argc;
  return arglex();
}
 
int argwrap(void) {
  return 1;
}
El fichero fl.l contiene el analizador léxico de la lınea de comandos:
$ cat fl.l
%{
unsigned verbose;
unsigned thereisfile;
char *progName;
char fileName[256];
#include "flarg.h"
#include "tokens.h"
%}
 
%%
-h      |
"-?"    |
-help   { printf("usage is: %s [-help | -h | -? ] [-verbose | -v]"
         " [-Version | -V]"
         " [-f filename]\n", progName);
        }
 
-v      |
-verbose { printf("verbose mode is on\n"); verbose = 1; }
 
-V      |
-version { printf("version 1.0\n"); }
 
-f[[:blank:]]+[^ \t\n]+ {
              strcpy(fileName,argtext+3);
              printf("File name is: %s\n",fileName);
              thereisfile = 1;
            }
.
 
\n
Observe el uso de la clase [:blank:] para reconocer los blancos. Las clases son las mismas que las introducidas en gawk.

El análisis léxico del fichero que se lee después de procesar la lınea de comandos es descrito en trivial.l. Partiendo de trivial.l, la ejecución del Makefile da lugar a la construcción por parte de flex del fichero lex.yy.c conteniendo la rutina yylex.

$ cat trivial.l
%{
#include "tokens.h"
%}
digit [0-9]
id [a-zA-Z][a-zA-Z0-9]+
blanks [ \t\n]+
operator [+*/-]
%%
{digit}+ {return INTTOKEN; }
{digit}+"."{digit}+ {return FLOATTOKEN; }
{id} {return IDTOKEN;}
{operator} {return OPERATORTOKEN;}
{blanks} {return BLANKTOKEN;}
. {return (int) yytext[0];}
%%
int yywrap() {
  return 1;
}
El fichero tokens.h contiene la definición de los tokens y es compartido con main.c.
$ cat tokens.h
#define INTTOKEN  256
#define FLOATTOKEN 257
#define IDTOKEN 258
#define OPERATORTOKEN 259
#define BLANKTOKEN 260
Nos queda por presentar el fichero main.c:
$ cat main.c
#include <stdio.h>
#include "flarg.h"
#include "tokens.h"
extern unsigned verbose;
extern unsigned thereisfile;
extern char *progName;
extern char fileName[256];
extern FILE * yyin;
 
main(int argc, char **argv) {
  unsigned lookahead;
  FILE * file;
 
  progName = *argv;
  yyarglex(argc,argv);
  if (thereisfile) {
    if (verbose) printf("Analyzing %s\n",fileName);
    file = (fopen(fileName,"r"));
    if (file == NULL) exit(1);
    yyin = file;
    while (lookahead = yylex()) {
      switch (lookahead) {
        case INTTOKEN:
            printf("int-");
            break;
          case FLOATTOKEN:
            printf("float-");
            break;
          case IDTOKEN:
            printf("id-");
            break;
          case OPERATORTOKEN:
            printf("operator-");
            break;
          case BLANKTOKEN:
            printf("blanks-");
            break;
          default: printf("%c-",lookahead);
       }
    } /* while */
    printf("\n");
  } /* if */
}

Declaraciones pointer y array

Como se comentó, las opciones %pointer y %array controlan la definición que flex hace de yytext. en el caso en que eligamos la opción %array la variable YYLMAX controla el tamaño del array. Supongamos que en el fichero trivial.l del ejemplo anterior introducimos las siguientes modificaciones:
$ cat trivial.l
%array
%{
#undef YYLMAX
#define YYLMAX 4
#include "tokens.h"
%}
digit [0-9]
id [a-zA-Z][a-zA-Z0-9]+
blanks [ \t\n]+
operator [+*/-]
%%
{digit}+ {return INTTOKEN; }
{digit}+"."{digit}+ {return FLOATTOKEN; }
{id} {return IDTOKEN;}
{operator} {return OPERATORTOKEN;}
{blanks} {return BLANKTOKEN;}
. {return (int) yytext[0];}
%%
int yywrap() {
  return 1;
  }
En tal caso, la definición excesivamente pequeña de YYLMAX provoca un error en tiempo de ejecución:
$ fl -V -f tokens.h
version 1.0
File name is: tokens.h
token too large, exceeds YYLMAX

Las Macros YY_USER_ACTION, yy_act e YY_NUM_RULES

La macro YY_USER_ACTION permite ejecutar una acción inmediatamente después del ``emparejamiento'' y antes de la ejecución de la acción asociada. cuando se la invoca, la variable yy_act contiene el número de la regla que ha emparejado (las reglas se numeran a partir de uno). La macro YY_NUM_RULES contiene el número de reglas, incluyendo la regla por defecto.

El siguiente programa aprovecha dichas macros para mostrar las frecuencias de uso de las reglas.

$ cat user_action.l
%array
%{
#include <string.h>
 
int ctrl[YY_NUM_RULES];
#undef YY_USER_ACTION
#define YY_USER_ACTION { ++ctrl[yy_act]; }
%}
number [0-9]+
id   [a-zA-Z_]+[a-zA-Z0-9_]*
whites [ \t\n]+
%%
{id}
{number}
{whites}
.
%%
 
int yywrap() {
  int i;
 
  for(i=1;i<YY_NUM_RULES;i++)
    printf("Rule %d: %d occurrences\n",i,ctrl[i]);
}
                                                  
$ flex user_action.l ; gcc lex.yy.c -lfl ; a.out
a=b+2*(c-4)
Rule 1: 3 occurrences
Rule 2: 2 occurrences
Rule 3: 1 occurrences
Rule 4: 6 occurrences

Las opciones interactive

La opción option always-interactive hace que flex genere un analizador que considera que su entrada es ``interactiva''. Concretamente, el analizador para cada nuevo fichero de entrada, intenta determinar si se trata de un a entrada interactiva o desde fichero haciendo una llamada a la función isatty(). Vea un ejemplo de uso de esta función:

$ cat isatty.c
#include <unistd.h>
#include <stdio.h>
main() {
 
  if (isatty(0))
    printf("interactive\n");
  else
    printf("non interactive\n");
}
$ gcc isatty.c; a.out
interactive
$ a.out < isatty.c
non interactive
$
cuando se usa la opción option always-interactive, se elimina esta llamada.

La macro YY_BREAK

Las acciones asociadas con los patrones se agrupan en la rutina de análisis léxico yylex() en una sentencia switch y se separan mediante llamadas a la macro YY_BREAK. Asi, al compilar con flex el siguiente fichero .l
$ cat interactive.l
%%
. printf("::%c",yytext[0]);
\n printf("::%c",yytext[0]);      
tenemos el fichero de salida lex.yy.c que aparece a continuación (hemos omitido las líneas de código en las que estamos menos interesados, sustituyendolas por puntos suspensivos)
/* A lexical scanner generated by flex */
....
#define YY_NUM_RULES 3
#line 1 "interactive.l"
#define INITIAL 0
#line 363 "lex.yy.c"
....
YY_DECL {
 ....
#line 1 "interactive.l"
#line 516 "lex.yy.c"
 ....
 if ( yy_init ) {
   yy_init = 0;
#ifdef YY_USER_INIT
   YY_USER_INIT;
#endif
   if ( ! yy_start ) yy_start = 1;  /* first start state */
   if ( ! yyin ) yyin = stdin;
   if ( ! yyout ) yyout = stdout;
   if ( ! yy_current_buffer ) yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE );
   yy_load_buffer_state();
 }
 while ( 1 )    /* loops until end-of-file is reached */ {
   ............................
yy_match:
   do {
    .....
   }
   ............
yy_find_action:
   ............
   YY_DO_BEFORE_ACTION;
   do_action:  /* This label is used only to access EOF actions. */
     switch ( yy_act ) { /* beginning of action switch */
       case 0: 
         ...................
         goto yy_find_action;
     case 1:
     YY_RULE_SETUP
     #line 2 "interactive.l"
     printf("::%c",yytext[0]);
       YY_BREAK
     case 2:
     YY_RULE_SETUP
     #line 3 "interactive.l"
     printf("::%c",yytext[0]);
       YY_BREAK
     case 3:
     YY_RULE_SETUP
     #line 4 "interactive.l"
     ECHO;
       YY_BREAK
     #line 614 "lex.yy.c"
     case YY_STATE_EOF(INITIAL):
       yyterminate();
       case YY_END_OF_BUFFER:
         { .....  }
     default:
       YY_FATAL_ERROR("fatal flex scanner internal error--no action found");
  } /* end of action switch */
 } /* end of scanning one token */
} /* end of yylex */

#if YY_MAIN
int main()
  { yylex(); return 0; }
#endif
#line 4 "interactive.l"
Por defecto, la macro

Casiano Rodríguez León
2013-04-23