La construcción de un analizador léxico es un paso fundamental en el desarrollo de compiladores y procesadores de lenguajes. LEX y FLEX son ejemplos de generadores léxicos, herramientas que facilitan la creación de analizadores léxicos a partir de un fichero de entrada que describe las reglas del lenguaje. Estos analizadores se basan en autómatas finitos deterministas para identificar emparejamientos en el texto de entrada.
Flujo de Trabajo Básico con FLEX
Cuando trabajamos con FLEX, el proceso general implica tomar un fichero de entrada (generalmente con extensión .l, como regleng.l) que describe el lenguaje y, tras su procesamiento, obtenemos un fichero denominado lex.yy.c. Este fichero C contiene el código fuente de nuestro analizador léxico. Asumiré que el fichero lex.yy.c tiene un main, para ahorrarme explicaciones.
Para compilar y enlazar este código, utilizamos el compilador GCC. La información detallada sobre GCC la tienes en las páginas man e info (man gcc, info gcc).
Compilación y Enlace de la Librería FLEX
Para compilar el código generado por FLEX y enlazar con las librerías necesarias, utilizamos GCC. La opción -o hace que el siguiente argumento sea el nombre del fichero de salida. Así pues, le estás indicando al GCC que debe hacer una compilación de algún fichero y guardarlo en el nombre de ejecutable especificado.
A menudo, te encontrarás con la necesidad de enlazar una librería específica. Si se produce un error como "No conozco ninguna biblioteca que se llame 'l'", esto indica que el compilador no encuentra la librería de la biblioteca de utilidades de FLEX, que es -ll. ¿Quizá te refieres a la biblioteca matemática ("m") o se trate de una biblioteca que has creado/recibido tú (el fichero deberá llamarse libl.a)?
Lea también: IVA 21% Excel
Si se trata de una librería personalizada o una que no está en las rutas de búsqueda estándar del sistema, tendrás que indicarle al GCC la ruta en la que debe buscar el fichero. Para eso tienes la opción -L.
Un ejemplo de comando de compilación podría ser:
gcc -o programita lex.yy.c -L. -ll
En este caso, -L. indica que el compilador debe buscar librerías en el directorio actual, y -ll especifica que debe enlazar con libl.a (la librería de FLEX).
Otro ejemplo que muestra el proceso completo, desde la generación del analizador léxico hasta la ejecución, es:
$ flex scan_str.l ; gcc lex.yy.c -o a.out -ll ; ./a.out Hello World!
Este comando primero ejecuta flex sobre scan_str.l para generar lex.yy.c, luego compila lex.yy.c y lo enlaza con la librería de FLEX para crear el ejecutable a.out, y finalmente ejecuta a.out pasando "Hello World!" como entrada.
Lea también: Guía IVA reducido
Si deseas cambiar el prefijo de las variables globales y rutinas accesibles generadas por FLEX (por defecto yy), puedes usar la opción -P. Por ejemplo, -Pfoo cambia el nombre de yytext a footext. La librería resultante para esta configuración la denominaríamos libflarg.a (por "flex arguments").
Un comando de compilación que incluye rutas de librerías y directorios de inclusión podría ser:
gcc -g -L. -L~/lib -I. lex.yy.c -o mi_analizador -ll
Aquí -L. y -L~/lib añaden directorios al camino de búsqueda de librerías, y -I. añade el directorio actual al camino de búsqueda de ficheros de inclusión.
Estructura de un Fichero de Entrada FLEX
Un fichero de entrada FLEX se divide en tres secciones, separadas por %%:
- Definiciones: En esta sección se pueden definir macros, constantes, opciones de configuración de FLEX y estados de inicio.
- Reglas: Esta es la sección principal donde se especifican los patrones regulares y sus acciones asociadas. Cada patrón regular tiene su correspondiente acción asociada.
- Código de Usuario: La tercera sección de código se copia verbatim en lex.yy.c y es donde se pueden definir funciones auxiliares o la función main para probar el analizador léxico.
Expresiones Regulares en FLEX
FLEX utiliza una sintaxis rica para expresiones regulares extendidas. Aquí algunos ejemplos:
Lea también: ¿Cómo localizar tus XML del SAT?
- [^A-Z]: Una ``Clase complementada'', esto es, todos los caracteres que no están en la clase. Cualquier carácter, excepto las letras mayúsculas.
- [[:alnum:]]: Casa con cualquier carácter alfanumérico. Aquí [:alnum:] se refiere a una de las clases predefinidas. Las otras clases son: [:alpha:], [:blank:], [:cntrl:], [:digit:], [:graph:], [:lower:], [:print:], [:punct:], [:space:], [:upper:], [:xdigit:].
- r{2,}: 2 o más r.
- r{4}: Exactamente 4 r.
- r/s: Una r pero sólo si va seguida de una s. El texto casado con s se incluye a la hora de decidir cuál es el emparejamiento más largo, pero se devuelve a la entrada cuando se ejecuta la acción. La acción sólo ve el texto asociado con r. Este tipo de patrón se denomina trailing context o lookahead positivo.
- r$: Casa con r, al final de una línea. Este es también un operador de trailing context. Una regla no puede tener más de un operador de trailing context.
Un ^ que no aparece al comienzo de la línea o un $ que no aparece al final de la línea, pierde su naturaleza de ``ancla'' y es tratado como un carácter ordinario. Así: foo|(bar$) se empareja con bar$.
La sección de reglas contiene patrones en orden de prioridad, de la más alta a la más baja, para resolver conflictos entre emparejamientos.
Funciones y Macros Internas de FLEX
FLEX genera la función principal de análisis léxico, declarada como int yylex() por defecto utilizando la macro YY_DECL. Esta función devuelve un valor de 0 o 1 según haya más entrada o no.
Manejo de la Entrada
- La función yyrestart(FILE *file) permite conmutar entre ficheros en medio de un análisis léxico, apuntando a yyin.
- La macro YY_INPUT se pretende leer de la entrada. El buffer pasado como parámetro a YY_INPUT debe ser lo suficientemente grande para mantener YY_BUF_SIZE caracteres.
- Los contenidos de yytext son la cadena que ha emparejado. La función yymore() es usada para el próximo emparejamiento.
Control del Análisis
- 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, generalmente al alcanzar un final de fichero.
- La macro yywrap() ha devuelto un valor no nulo, indicando que no hay más entrada.
- 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 una entrada interactiva o desde fichero haciendo una llamada a la función isatty().
Estados de Inicio
Los estados de inicio se definen en la parte de las definiciones, seguidos de los nombres de los estados. Al usar la acción BEGIN estado, las reglas prefijadas con el estado pasan a estar activas, lo que permite un control más granular sobre el análisis léxico.
