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
.
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()
.
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
.
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.
La sintáxis que puede utilizarse para la descripción de las expresiones regulares es la que se conoce como ``extendida'':
x
Casa con 'x'
.
Cualquier carácter, excepto \n
.
[xyz]
Una ``clase''; en este caso una de las letras x
, y
, z
[abj-oZ]
Una ``clase'' con un rango; casa con a
, b
, cualquier letra desde j
hasta o
, o una Z
[^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. Obsérvese que el retorno de carro \n
casa
con esta expresion. Así es posible que, <un patrón como [^"]+
pueda casar con todo el fichero!.
[^A-Z\n]
Cualquier carácter, excepto las letras mayúsculas o un \n
.
[[:alnum:]]
Casa con cualquier caracter alfanumérico. Aqui [:alnum:]
se refiere a una de las clases predefinidas. Las otras clases son: [:alpha:]
[:blank:]
[:cntrl:]
[:digit:]
[:graph:]
[:lower:]
[:print:]
[:punct:]
[:space:]
[:upper:]
[:xdigit:]
. Estas clases designan el mismo conjunto de caracteres que la correspondiente función C isXXXX
.
r*
Cero o mas r
.
r+
Una o mas r
.
r?
Cero o una r
.
r{2,5}
Entre 2 y 5 r
.
r{2,}
2 o mas r
.
r{4}
Exactamente 4 r
.
{macro_name}
La expansión de macro_name
por su regular_definition
"[xyz]\"foo"
Exactamente la cadena: [xyz]"foo
\X
Si X
is an a
, b
, f
, n
, r
, t
, o v
, entonces, la interpretación ANSI-C de \x
. En cualquier otro caso X
.
\0
El carácter NUL (ASCII 0).
\123
El carácter cuyo código octal es 123.
\x2a
El carácter cuyo código hexadecimal es 2a.
(r)
Los paréntesis son utilizados para cambiar la precedencia.
rs
Concatenation
r|s
Casa con r
o s
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 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
. Este tipo de patrón se denomina trailing context o lookahead positivo.
^r
Casa con r
, al comienzo de una línea.
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. Asi: foo|(bar$)
se empareja con bar$
. Si lo que se quería es la otra interpretación, es posible escribir foo|(bar\n)
, o bien:
foo | bar$ { /* action */ }
r$
Casa con r
, al final de una línea. Este es también un operador de trailing context. Una regla no puede tener mas de un operador de trailing context. Por ejemplo, la expresión foo/bar$
es incorrecta.
<s>r
Casa con r
, pero sólo si se está en el estado s
.
<s1,s2,s3>r
Idem, si se esta en alguno de los estados s1
, s2
, or s3
<*>r
Casa con r
cualquiera que sea el estado, incluso si este es exclusivo.
<<EOF>>
Un final de fichero.
<s1,s2><<EOF>>
Un final de fichero, si los estados son s1
o s2
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)+)
.
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.