Section: User Commands (1)
Updated: Abril 1995
Index Return to Main
Contents
flex - generador de analizadores léxicos rápidos
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
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.
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 "*/".
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:
foo/bar$ <sc1>foo<sc2>barFíjese que la primera regla se puede escribir como "foo/bar\n".
foo|(bar$) foo|^barSi 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.
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).
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:
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).
%% 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.
%% 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.
{ 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.
%% "/*" { 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.)
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.
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:
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).
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.
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:
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:
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(); }
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.
Esta sección resume los diferentes valores disponibles al usuario en las acciones de la regla.
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;
tiene las siguientes opciones:
--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.
"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.
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 grandeFí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.
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.
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:
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).
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.
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óx, 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:
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:
Además, yyFlexLexer define las siguientes funciones virtuales protegidas que puede redefinir en clases derivadas para adaptar el analizador:
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.
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:
fatal flex scanner internal error--end of buffer missedPara 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.
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á.
%% foo|bar<espacio aquí> { foobar_action(); }flex no dispone de esta propiedad.
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.
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).
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.
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).
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, [email protected], 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, [email protected], 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 [email protected].
Sobre esta traducción enviar comentarios a Adrián Pérez Jorge ([email protected]).
This document was created by man2html, using
the manual pages.
Time: 20:28:06 GMT, January 21, 2005