Você está na página 1de 37

4o Ingenier a Inform atica

II26 Procesadores de lenguaje


Analizador l exico Esquema del tema
1. Introducci on 2. Repaso de conceptos de lenguajes formales 3. Categor as l exicas 4. Especicaci on de las categor as l exicas 5. Aut omatas de estados nitos 6. Implementaci on del analizador l exico 7. Algunas aplicaciones de los analizadores l exicos 8. Resumen del tema

1.

Introducci on

Vimos que la primera fase del an alisis es el an alisis l exico. El principal objetivo del analizador l exico es leer el ujo de caracteres de entrada y transformarlo en una secuencia de componentes l exicos que utilizar a el analizador sint actico. Al tiempo que realiza esta funci on, el analizador l exico se ocupa de ciertas labores de limpieza. Entre ellas est a eliminar los blancos o los comentarios. Tambi en se ocupa de los problemas que pueden surgir por los distintos juegos de caracteres o si el lenguaje no distingue may usculas y min usculas. Para reducir la complejidad, los posibles s mbolos se agrupan en lo que llamaremos categor as l exicas. Tendremos que especicar qu e elementos componen estas categor as, para lo que emplearemos expresiones regulares. Tambi en ser a necesario determinar si una cadena pertenece o no a una categor a, lo que se puede hacer ecientemente mediante aut omatas de estados nitos.

2.
2.1.

Repaso de conceptos de lenguajes formales


Por qu e utilizamos lenguajes formales

Como acabamos de comentar, para transformar la secuencia de caracteres de entrada en una secuencia de componentes l exicos utilizamos aut omatas de estados nitos. Sin embargo, estos aut omatas los especicaremos utilizando expresiones regulares. Tanto unos como otras son ejemplos de utilizaci on de la teor a de lenguajes formales. Es natural preguntarse si es necesario dar este rodeo. Existen varias razones que aconsejan hacerlo. La primera raz on para emplear herramientas formales es que nos permiten expresarnos con precisi on y, generalmente, de forma breve. Por ejemplo, para describir la categor a de los enteros, podemos intentar utilizar el castellano y decir algo as como que son secuencias de d gitos. Pero entonces no queda claro cuales son esos d gitos (por ejemplo, en octal, los d gitos van del cero al siete). Cambiamos entonces a secuencias de d gitos, cada uno de los cuales puede ser un 0, un 1, un 2, un 3, un 4, un 5, un 6, un 7, un 8 o un 9. Todav a queda otro problema m as, valen las cadenas vac as? Normalmente no, as que llegamos a un n umero entero consiste en una secuencia de uno o m as d gitos, cada uno de los cuales puede ser un 0, un 1, un 2, un 3, un 4, un 5, un 6, un 7, un 8 o un 9. Sin embargo, con expresiones regulares, podemos decir lo mismo con [09]+ .

II26 Procesadores de lenguaje

Otra ventaja de las herramientas formales, es que nos permiten razonar sobre la correcci on de nuestros dise nos y permiten conocer los l mites de lo que podemos hacer. Por ejemplo, el lema de bombeo nos permite saber que no podremos utilizar expresiones regulares para modelar componentes l exicos que tengan el mismo n umero de par entesis abiertos que cerrados, por lo que averiguar si algo est a bien parentizado ser a tarea del analizador sint actico. Como u ltima ventaja del empleo de lenguajes formales, comentaremos la existencia de herramientas para automatizar la implementaci on. Por ejemplo, el paso de las expresiones regulares a un programa que las reconozca se puede hacer mediante un generador de analizadores l exicos como flex.

2.2.

Alfabetos y lenguajes

Al trabajar con lenguajes formales, utilizaremos una serie de conceptos b asicos. En primer lugar, un alfabeto es un conjunto nito de s mbolos. No nos interesa la naturaleza de los s mbolos. Dependiendo de la aplicaci on que tengamos en mente, estos pueden ser: caracteres, como al especicar el analizador l exico; letras o palabras, si queremos trabajar con lenguaje natural; categor as l exicas, al especicar el analizador sint actico; direcciones en el plano, al hacer OCR, etc. Justamente esta abstracci on es lo que hace que los lenguajes formales se puedan aplicar ampliamente. Una cadena es una secuencia nita de s mbolos del alfabeto. Nuevamente, no estamos interesados en la naturaleza precisa de las cadenas. Si estamos especicando el analizador l exico, podemos ver la entrada como una cadena; si trabajamos con lenguaje natural, la cadena puede ser una pregunta a una base de datos; para el analizador sint actico, una cadena es el programa una vez pasado por el analizador l exico; en OCR, una cadena es la descripci on de una letra manuscrita, etc. La cadena de longitud cero se denomina cadena vac a y se denota con . Para referirnos al conjunto de cadenas de longitud k , utilizamos k . Si nos referimos al conjunto de todas las cadenas que se pueden escribir con s mbolos del alfabeto, usamos . Se cumplir a que:

=
k=0

k = 0 1 2 . . .

Date cuenta de que es un conjunto innito, pero que cualquiera de sus cadenas es nita. Finalmente, un lenguaje formal es un subconjunto de . Tal como est a denido, puede ser nito o innito. Es m as, la denici on no impone la necesidad de que el lenguaje tenga alg un sentido o signicado; un lenguaje formal es simplemente un conjunto de cadenas. Una vez hemos decidido que vamos a utilizar un lenguaje formal, nos enfrentaremos a dos problemas: especicarlo y reconocerlo. En nuestro caso, las especicaciones ser an de dos tipos: por un lado expresiones regulares para los lenguajes que emplearemos en el an alisis l exico y por otro gram aticas incontextuales en el an alisis sint actico. Una vez especicado el lenguaje ser a necesario encontrar la manera de reconocerlos, esto es, resolver la pregunta si una cadena dada pertenece al lenguaje. Veremos que los m etodos de an alisis que emplearemos ser an capaces de responder a la pregunta de forma eciente: con un coste lineal con la talla de la cadena.

3.
3.1.

Categor as l exicas
Conceptos b asicos

Para que el analizador l exico consiga el objetivo de dividir la entrada en partes, tiene que poder decidir por cada una de esas partes si es un componente separado y, en su caso, de qu e tipo. De forma natural, surge el concepto de categor a l exica, que es un tipo de s mbolo elemental del lenguaje de programaci on. Por ejemplo: identicadores, palabras clave, n umeros enteros, etc.

Analizador l exico

Los componentes l exicos (en ingl es, tokens ) son los elementos de las categor as l exicas. Por ejemplo, en C, i es un componente l exico de la categor a identicador, 232 es un componente l exico de la categor a entero, etc. El analizador l exico ir a leyendo de la entrada y dividi endola en componentes l exicos. En general, no basta con saber la categor a a la que pertenece un componente, en muchos casos es necesaria cierta informaci on adicional. Por ejemplo, ser a necesario conocer el valor de un entero o el nombre del identicador. Utilizamos los atributos de los componentes para guardar esta informaci on. Un u ltimo concepto que nos ser au til es el de lexema : la secuencia concreta de caracteres que corresponde a un componente l exico. Por ejemplo, en la sentencia altura=2; hay cuatro componentes l exicos, cada uno de ellos de una categor a l exica distinta: Categor a l exica identicador asignaci on entero terminador Lexema altura = 2 ; Atributos valor: 2

3.2.

Categor as l exicas m as usuales

Algunas familias de categor as l exicas t picas de los lenguajes de programaci on son: Palabras clave Palabras con un signicado concreto en el lenguaje. Ejemplos de palabras clave en C son while, if, return. . . Cada palabra clave suele corresponder a una categor a l exica. Habitualmente, las palabras clave son reservadas. Si no lo son, el analizador l exico necesitar a informaci on del sint actico para resolver la ambig uedad. Identicadores Nombres de variables, nombres de funci on, nombres de tipos denidos por el usuario, etc. Ejemplos de identicadores en C son i, x10, valor_leido. . . Operadores S mbolos que especican operaciones aritm eticas, l ogicas, de cadena, etc. Ejemplos de operadores en C son +, *, /, %, ==, !=, &&. . . Constantes num ericas Literales1 que especican valores num ericos enteros (en base decimal, octal, hexadecimal. . . ), en coma otante, etc. Ejemplos de constantes num ericas en C son 928, 0xF6A5, 83.3E+2. . . Constantes de car acter o de cadena Literales que especican caracteres o cadenas de caracteres. Un ejemplo de literal de cadena en C es "una cadena"; ejemplos de literal de car acter son x, \0. . . S mbolos especiales Separadores, delimitadores, terminadores, etc. Ejemplos de estos s mbolos en C son {, }, ;. . . Suelen pertenecer cada uno a una categor a l exica separada. Hay tres categor as l exicas que son especiales: Blancos En los denominados lenguajes de formato libre (C, Pascal, Lisp, etc.) los espacios en blanco, tabuladores y saltos de l nea s olo sirven para separar componentes l exicos. En ese caso, el analizador l exico se limita a suprimirlos. En otros lenguajes, como Python, no se pueden eliminar totalmente. Comentarios Informaci on destinada al lector del programa. El analizador l exico los elimina. Fin de entrada Se trata de una categor a cticia emitida por el analizador l exico para indicar que no queda ning un componente pendiente en la entrada.
1 Los

literales son secuencias de caracteres que representan valores constantes.

c Universitat Jaume I 2010-2011

II26 Procesadores de lenguaje

4.

Especicaci on de las categor as l exicas

El conjunto de lexemas que forman los componentes l exicos que podemos clasicar en una determinada categor a l exica se expresa mediante un patr on. Algunos ejemplos son: Categor a l exica entero identicador operador asignaci on while Lexemas 12 5 0 192831 x x0 area + := while Patr on Secuencia de uno o m as d gitos. Letra seguida opcionalmente de letras y/o d gitos. Caracteres +, -, * o /. Car acter : seguido de =. La palabra while.

Ya hemos comentado las dicultades de especicar correctamente las categor as mediante lenguaje natural, as que emplearemos expresiones regulares.

4.1.

Expresiones regulares

Antes de describir las expresiones regulares, recordaremos dos operaciones sobre lenguajes. La concatenaci on de dos lenguajes L y M es el conjunto de cadenas que se forman al tomar una del primero, otra del segundo y concatenarlas. M as formalmente: la concatenaci on de los lenguajes L y M es el lenguaje LM = {xy | x L y M }. La notaci on Lk se utiliza para representar la concatenaci on de L consigo mismo k 1 veces, con los convenios L0 = {} y L1 = L. La otra operaci on que deniremos es la clausura de un lenguaje L, representada como L y que es el conjunto de las cadenas que se pueden obtener mediante la concatenaci on de un n umero arbitrario de cadenas de L. M as formalmente L = i=0 Li (recuerda que los lenguajes son conjuntos y por tanto tiene sentido hablar de su uni on).
Ejercicio 1

Sean L = {a, aa, b} y M = {ab, b}. Describe LM y M 3 por enumeraci on. Observa que M 3 no es lo mismo que {xxx | x M }.

4.2.

Expresiones regulares b asicas

Deniremos las expresiones regulares de manera constructiva. Dada una expresi on regular r, utilizaremos L(r) para referirnos al lenguaje que representa. Como base, emplearemos las expresiones para el lenguaje vac o, la cadena vac a y las cadenas de un s mbolo: La expresi on regular denota el lenguaje vac o: L() = . La expresi on regular denota el lenguaje que u nicamente contiene la cadena vac a2 L() = {}. La expresi on regular a, donde a , representa el lenguaje L(a) = {a}. La verdadera utilidad de las expresiones regulares surge cuando las combinamos entre s . Para ello disponemos de los operadores de clausura, uni on y concatenaci on: Sea r una expresi on regular, la expresi on regular (r) representa el lenguaje L((r) ) = (L(r)) . Sean r y s dos expresiones regulares. Entonces: La expresi on regular (rs) representa el lenguaje L((rs)) = L(r)L(s). La expresi on regular (r|s) representa el lenguaje L((r|s)) = L(r) L(s).
2. . . y

que no tiene nada que ver con el lenguaje vac o, como ya sabr as.

Analizador l exico

A partir de estas deniciones, vamos a ver qu e signica la expresi on regular ((a|b) )(ab). En primer lugar, vemos que es la concatenaci on de otras dos expresiones: (a|b) y ab. La primera designa el lenguaje que es la clausura de la disyunci on de los lenguajes formados por una a y una b, respectivamente. Podemos expresarlo m as claramente como las cadenas formadas por cualquier n umero de aes y bes. La segunda expresi on corresponde al lenguaje que u nicamente contiene la cadena ab. Al concatenar ambas, obtenemos cadenas formadas por un n umero arbitrario de aes y bes seguidas de ab. Dicho de otra forma, son cadenas de aes y bes que terminan en ab. Para describir los literales enteros, podr amos utilizar la expresi on regular: ((((((((((0|1)|2)|3)|4)|5)|6)|7)|8)|9)((((((((((0|1)|2)|3)|4)|5)|6)|7)|8)|9)) ) El exceso de par entesis hace que esta expresi on sea bastante dif cil de leer. Podemos simplicar la escritura de expresiones regulares si establecemos prioridades y asociatividades. Vamos a hacer que la disyunci on tenga la m nima prioridad, en segundo lugar estar a la concatenaci on y nalmente la clausura tendr a la m axima prioridad. Adem as, disyunci on y concatenaci on ser an asociativas por la izquierda. Cuando queramos que el orden sea distinto al marcado por las prioridades, utilizaremos par entesis. Con este convenio, podemos representar los enteros as : (0|1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)

4.3.

Expresiones regulares extendidas

Aun cuando las expresiones regulares resultan m as legibles al introducir las prioridades, sigue habiendo construcciones habituales que resultan inc omodas de escribir, por ejemplo la expresi on que hemos utilizado para los enteros. Veremos en este apartado algunas extensiones que, sin aumentar el poder descriptivo de las expresiones regulares, permiten escribirlas m as c omodamente. Un aviso: as como las expresiones regulares b asicas pueden considerarse una notaci on est andar, las extensiones que vamos a presentar ahora no lo son. Esto quiere decir que, seg un el programa que se emplee, algunas se considerar an v alidas y otras no; tambi en puede suceder que haya otras extensiones disponibles. Tambi en hay que advertir que, mientras no digamos otra cosa, trataremos con el conjunto de caracteres ASCII imprimibles ( representa el espacio en blanco): ! 1 A Q a q " 2 B R b r # 3 C S c s $ 4 D T d t % 5 E U e u & 6 F V f v 7 G W g w ( 8 H X h x ) 9 I Y i y * : J Z j z + ; K [ k { , < L \ l | = M ] m } . > N ^ n ~ / ? O _ o

0 @ P p

y los caracteres salto de l nea \n y tabulador \t. Tambi en utilizaremos eof para referirnos al n de entrada (seguiremos la tradici on de llamarlo n de chero end of le aunque en ocasiones no se corresponda con el n de ning un chero). Clases de caracteres La primera extensi on que vamos a ver nos permite escribir clases de caracteres de manera c omoda. En su forma m as simple, si queremos representar la elecci on de uno entre varios caracteres, basta con encerrarlos entre corchetes. As [abd] representa lo mismo que a|b|d. Cuando los caracteres son contiguos, se puede utilizar un guion entre el primero y el u ltimo para indicar todo el rango. De esta manera, [az] es el conjunto de las letras min usculas. Tambi en podemos combinar varios rangos y conjuntos, de modo que [azAZ09_] es el conjunto de las letras, los d gitos y el subrayado. Si necesitamos que el propio guion est e en el rango, debemos ponerlo bien al principio bien al nal. As , los operadores aritm eticos son [-+*/]. El ejemplo anterior tambi en nos muestra que, dentro de un rango, los caracteres especiales pierden su signicado. Por otro lado, si lo que queremos incluir es el corchete cerrado, debemos ponerlo en la primera posici on, como en []16].
c Universitat Jaume I 2010-2011

II26 Procesadores de lenguaje

Otra posibilidad que nos ofrecen las clases de caracteres es especicar los caracteres que no est an en un conjunto. Para ello, se incluye en la primera posici on un circunejo, as [09] es el conjunto de los caracteres que no son d gitos. En cualquier otra posici on, el circunejo se representa a s mismo. Finalmente, el punto (.) representa cualquier car acter, incluyendo el n de l nea. Otra advertencia m as: seg un del sistema, el punto puede incluir o no el n de l nea. En particular, la librer a re de Python considera el punto equivalente a [\n], salvo que se empleen algunas opciones especiales. Para flex, el punto tampoco incluye el n de l nea. Nuevos operadores Enriqueceremos el conjunto b asico de operadores regulares con dos nuevos operadores: la clausura positiva y la opcionalidad. El primero se representa mediante el signo m as e indica una o m as repeticiones de la expresi on regular a la que afecta. Le otorgamos la misma prioridad que a la clausura normal. De manera simb olica, decimos que r+ equivale a rr , donde r es una expresi on regular arbitraria. El segundo operador, representado mediante un interrogante, representa la aparici on opcional de la expresi on a la que afecta. De esta manera, r? es equivalente a r|. Su prioridad es tambi en la de la clausura. Car acter de escape Hemos visto que hay una serie de caracteres que tienen un signicado especial. A estos caracteres se les llama metacaracteres. Si queremos referirnos a ellos podemos utilizar el car acter de escape, en nuestro caso la barra \. As , la expresi on 4 representa una secuencia de cero o m as cuatros mientras que 4\* representa un cuatro seguido por un asterisco. Para representar la propia barra utilizaremos \\. Sin embargo, dentro de las clases de caracteres, no es necesario escapar, as 4[*] y 4\* son equivalentes (y muy distintas de [4*]). La excepci on son los caracteres n de l nea, tabulador y la propia barra: [\n\t\\]. Nombres La u ltima extensi on que permitiremos es la posibilidad de introducir nombres para las expresiones. Estos nombres luego podr an utilizarse en lugar de cualquier expresi on. La u nica restricci on que impondremos es que no deben provocar ninguna recursividad, ni directa ni indirectamente.

4.4.

Ejemplos

Vamos a ver algunos ejemplos de expresiones regulares. En primer lugar, revisitaremos los n umeros enteros. Si utilizamos clases de caracteres, podemos escribirlos como [09][09] . D andonos cuenta de que esto representa una clausura positiva, podemos escribirlo como [09]+ . Otra posibilidad es darle el nombre d gito a [09] y escribir los enteros como d gito+ . Supongamos que tenemos un lenguaje en el que los comentarios comienzan por << y terminan por >>, sin que pueda aparecer ninguna secuencia de dos mayores en el cuerpo del comentario. C omo podemos escribir la expresi on regular en este caso? Un primer intento ser a <<. >>. Esta expresi on tiene un problema ya que aceptar a << a >> a >> como comentario. Podemos resolver el problema si observamos que entre la apertura y el cierre del comentario puede aparecer cualquier car acter que no sea un mayor o, si aparece un mayor, debe aparecer seguido de algo que no lo sea. Es decir, que el interior del comentario es una secuencia de elementos de [>]|>[>]. Para lograr las repeticiones, utilizamos la clausura, con lo que, una vez a nadidos el comienzo y nal, obtenemos la expresi on <<([>]|>[>]) >>. Como renamiento, podemos utilizar el operador de opcionalidad para obtener <<(>?[>]) >>.
Ejercicio 2

Por qu e no vale la expresi on regular <<[(>>)] >> en el caso anterior?

Analizador l exico

Ejercicio 3

La expresi on regular que hemos visto antes acepta 007 y similares como literales enteros. Escribe una expresi on regular para los enteros de modo que se admita o el n umero cero escrito como un u nico d gito o una secuencia de d gitos comenzada por un n umero distinto de cero.
Ejercicio 4

Escribe una expresi on regular para el conjunto de las palabras reservadas integer, real y char escritas en min usculas y otra que permita escribirlas con cualquier combinaci on de may usculas y min usculas.
Ejercicio 5

En muchos lenguajes de programaci on, los identicadores se denen como secuencias de letras, d gitos y subrayados que no empiecen por un d gito. Escribe la expresi on regular correspondiente.

Ejercicio 6

Escribe una expresi on regular para las cadenas de dos o m as letras min usculas que empiezan por a o por b tales que la u ltima letra coincide con la primera.
Ejercicio 7

Las siguientes expresiones regulares son intentos fallidos de modelar los comentarios en C. Da un contraejemplo para cada una de ellas: /\*. \*/ /\*[*/] \*/ /\*([*]|\*[/]) \*/ Como ves por el ejercicio anterior, escribir correctamente las expresiones para los comentarios en C puede ser complicado. Vamos a intentar escribirla de dos formas distintas. En primer lugar, observa que la expresi on empezar a por /\* y acabar a por \*/, as que tendr a la forma /\*\*/, donde es una expresi on regular que representa las cadenas que no contienen la secuencia asteriscobarra. Para construir , podemos jarnos tanto en los asteriscos como en las barras. Empecemos j andonos en los asteriscos. La expresi on ser a una clausura en cuyo interior podremos encontrar tanto s mbolos que no sean asteriscos ([*]) como secuencias de uno o m as asteriscos (\*+ ) seguidas de algo que no sea ni una barra ni un asterisco ([*/]). Con esto tenemos ([*]|\*+ [*/]) . Nos queda el detalle de que pueden aparecer cero o m as asteriscos al nal de la cadena. Si los a nadimos, tenemos =([*]|\*+ [*/]) \* Y la expresi on resultante es3 /\*([*]|\*+ [*/]) \*+ / La otra opci on es mirar las barras. En este caso, podemos ver las cadenas generadas por como una secuencia de segmentos que terminan en barra (y s olo tienen una barra) seguidos de una secuencia de no barras. Es decir, un comentario tiene la forma: /*<no barras> /<no barras> /<no barras> /...<no barras> */ donde los <no barras> pueden estar vac os y puede haber cero o m as barras. Llamemos a una expresi on regular que genere un <no barras> seguido de una barra. Se cumplir a que = [/] .
3 Recuerda

que \* \* es equivalente a \*+ .

c Universitat Jaume I 2010-2011

II26 Procesadores de lenguaje

Desgraciadamente, no podemos hacer =[/] / porque permitir amos la secuencia */. Lo que haremos es que lo que genere pueda ser simplemente una barra o consistir en una secuencia de dos o m as s mbolos de modo que todos menos los dos u ltimos son distintos de barra, el pen ultimo no es ni una barra ni un asterisco y el u ltimo es una barra. Es decir =/|[/] [*/]/ o, jugando con la opcionalidad, ([/] [*/])?/. Si expandimos en , tenemos que = (([/] [*/])?/) [/] Y la expresi on resultante es /\*(([/] [*/])?/) [/] \*/
Ejercicio* 8

Intenta demostrar que las siguientes expresiones para los comentarios en C son correctas. /\*([*] \*+ [*/]) [*] \*+ / /\*(\* [*/]|/) \*+ / Pista: para la segunda, puede resultarte util intentar ver c omo transformarla en una de las explicadas en el texto.
Ejercicio 9

Escribe una expresi on regular para los comentarios en C++. Estos tienen dos formas: una es la misma que en C y la otra abarca desde la secuencia // hasta el nal de la l nea.
Ejercicio 10

Dise na expresiones regulares para los siguientes lenguajes regulares: a) Secuencias de caracteres encerradas entre llaves que no incluyen ni el car acter | ni la llave cerrada. b) Secuencias de caracteres encerradas entre llaves en las que el car acter | s olo puede aparecer si esta escapado por una barra invertida; la llave cerrada no puede aparecer y la barra invertida s olo puede aparecer para escapar una barra vertical u otra barra invertida. c) N umeros enteros entre 0 y 255 sin ceros iniciales.

Ejercicio 11

Qu e lenguajes representan las siguientes expresiones regulares? 0(0|1) 0 (0|1) 0(0|1)(0|1) 0 10 10 10 (0?0?1) 0?0?
Ejercicio* 12

Las expresiones regulares tambi en son cadenas de un lenguaje. Dado que tienen que estar bien parentizadas, este lenguaje no es regular. Sin embargo partes de el s lo son, por ejemplo, las clases de caracteres. Dise na una expresi on regular que exprese el conjunto de clases de caracteres que podemos utilizar en una expresi on regular.

5.

Aut omatas de estados nitos

Las expresiones regulares permiten describir con cierta comodidad los lenguajes regulares. Sin embargo, no es f acil decidir a partir de una expresi on regular si una cadena pertenece al

Analizador l exico

correspondiente lenguaje. Para abordar este problema utilizamos los aut omatas de estados nitos. Estos son m aquinas formales que consisten en un conjunto de estados y una serie de transiciones entre ellos. Para analizar una frase, el aut omata se sit ua en un estado especial, el estado inicial. Despu es va cambiando su estado a medida que consume s mbolos de la entrada hasta que esta se agota o no se puede cambiar de estado con el s mbolo consumido. Si el estado que alcanza es un estado nal, decimos que la cadena es aceptada; en caso contrario, es rechazada. Distinguimos dos tipos de aut omatas seg un c omo sean sus transiciones. Si desde cualquier estado hay como mucho una transici on por s mbolo, el aut omata es determinista. En caso de que haya alg un estado tal que con un s mbolo pueda transitar a m as de un estado, el aut omata es no determinista. Nosotros trabajaremos u nicamente con aut omatas deterministas.

5.1.

Aut omatas nitos deterministas


Q es un conjunto nito de estados. es un alfabeto. E Q Q es un conjunto de arcos tal que si (p, a, q ) y (p, a, q ) pertenecen a E , se tiene on de determinismo). que q = q (condici q0 Q es el estado inicial. F Q es el conjunto de estados nales.

Un aut omata nito determinista es una qu ntupla (Q, , E, q0 , F ) donde:

Para denir el funcionamiento del AFD debemos empezar por el concepto de camino. Un camino en un AFD es una secuencia p1 , s1 , p2 , s2 , . . . , sn1 , pn tal que para todo i entre 1 y n 1 (pi , si , pi+1 ) E . Esto quiere decir que podemos ver el camino como una secuencia de arcos en la que el estado al que llega un arco es el de partida del siguiente. Decimos que el camino parte de p1 y llega a pn . Llamamos entrada del camino a la cadena s1 . . . sn1 , que ser a vac a si n = 1. Una cadena x es aceptada por un AFD si existe un camino que parta de q0 , tenga como entrada x y llegue a un estado q F . El lenguaje aceptado o reconocido por un AFD es el conjunto de las cadenas aceptadas por el. Los aut omatas se pueden representar gr acamente. Para ello se emplea un grafo en el que cada estado est a representado por un nodo y cada arco del aut omata se representa mediante un arco en el grafo. Los estados nales se marcan mediante un doble c rculo y el estado inicial mediante una echa. Por ejemplo, el AFD A = ({A, B, C }, {0, 1}, E, A, {A}), con E = {(A, 0, A), (A, 1, B ), (B, 0, C ), (B, 1, A), (C, 0, B ), (C, 1, C )}, se puede representar gr acamente as : 0 1 A 1 B 0 0 C 1 (1)

Es interesante plantearse qu e lenguaje reconoce este aut omata. Podemos hacer un listado de algunas de las cadenas que acepta: Longitud 0 1 2 3 4 Cadenas 0 00, 11 000, 011, 110 0000, 0011, 0110, 1001, 1100, 1111

c Universitat Jaume I 2010-2011

10

II26 Procesadores de lenguaje

Si interpretamos la cadena vac a como cero y las restantes como n umeros escritos en binario, podemos ver que todas las cadenas representan m ultiplos de tres. Para convencernos de que no es casualidad, podemos reescribir los estados como 0, 1 y 2: 0 1 0 1 1 0 0 2 1

Ahora, puedes demostrar por inducci on que si una cadena x lleva al estado i, el resto de dividir el n umero representado por x entre tres es justamente i. Como aceptamos s olo aquellas cadenas que terminan en el estado cero, una cadena es aceptada si y solo s representa un n umero que dividido por tres da de resto cero, que es lo que quer amos.
Ejercicio 13

Escribe el AFD que acepta los m ultiplos de tres escritos en binario pero no la cadena vac a ni n umeros de la forma 00+ .
Ejercicio* 14

Demuestra que para cualquier n, el lenguaje de los m ultiplos de n en base 2 es regular. Idem para base 10. Cuando representemos aut omatas, habr a muchas veces en las que encontremos estructuras como la siguiente: a b c . . . . . . x y z Este tipo de estructuras las simplicaremos mediante clases de caracteres4 . As la estuctura anterior quedar a: [az]

Vamos a intentar ahora escribir un aut omata para los comentarios en C. En primer lugar podemos crear la parte del comienzo y el nal del comentario: / A B * C * D / E

Ahora nos queda decir que entre estas dos partes cabe cualquier cosa. Tendremos que poner un arco desde C a s mismo con los s mbolos que no son el asterisco. Observemos ahora qu e pasa si tras ver un asterisco en C vemos un car acter que no es una barra (que terminar a el comentario). Hay que distinguir dos casos: si el nuevo car acter es tambi en un asterisco, debemos permanecer en D; si el nuevo car acter no es un asterisco ni una barra, debemos retroceder al estado C .
4 Date cuenta de que estamos utilizando un m etodo para simplicar el dibujo, pero no podemos escribir en los arcos expresiones regulares cualesquiera.

Analizador l exico

11

Teniendo todo esto en cuenta, el aut omata nos queda as : [*] * / A B * C [*/] * D / E

Ejercicio 15

Dise na un aut omata para los comentarios en C++. 5.1.1. Memoria y AFDs

Una manera de interpretar el funcionamiento de los AFDs es imaginarse que los estados son la memoria del aut omata. Fij emonos en el aut omata de los comentarios en C. Podemos interpretar los estados de la siguiente manera: Estado A: estado inicial. Estado B : hemos le do la primera barra. Estado C : estamos en mitad del comentario. Estado D: hemos le do el asterisco del n del comentario. Estado E : hemos llegado al nal del comentario. Esta misma idea est a en el aut omata para los m ultiplos de 3: cada estado recuerda el resto de la parte del n umero que se lleva le da al dividirla por tres. Dado que hay un n umero nito de estados, la memoria esta limitada a priori. Esto quiere decir que si para reconocer un determinado lenguaje la cantidad de memoria no se puede acotar, no ser a posible hacerlo con un AFD. Esta es la raz on, por ejemplo, de que no se puedan reconocer pal ndromos o cadenas bien parentizadas con AFDs. Supongamos que queremos construir u aut omata para las secuencias de dos o m as d gitos del conjunto {1, 3, 5} tales que el u ltimo es mayor que todos los anteriores. Para esto, necesitar amos un estado inicial, por ejemplo I , y luego recordar los n umeros le dos mediante dos grupos de estados: Los estados m1 , m3 y m5 reejar an que el m aximo le do ha sido el 1, 3 o 5, respectivamente, y que la cadena no puede terminar ah porque est a repetido o ha sido el u nico le do. Los estados u1 , u3 y u5 reejar an el n umero m aximo le do y que adem as es el u ltimo y sin repetir. L ogicamente, ser an nales los estados u1 , u3 y u5 . Si reexionamos un poco, podemos ver que los estados m5 y u1 sobran: m5 porque desde el no ser a posible alcanzar ning un estado nal ya que necesitaremos un n umero mayor que cinco, lo que no es posible; u1 sobra porque no habr a manera de llegar a el ya que para que uno sea el m aximo la cadena s olo puede tener unos y, entonces, no puede tener dos o m as d gitos si repetir el uno. Las transiciones del estado inicial ir an con un uno al m1 y con un tres al m3 , con un cinco no habr a transici on porque corresponder a al estado m5 que hemos eliminado. Desde el estado m1 habr a un bucle con un uno, y transiciones a u3 y u5 con el tres y el cinco, respectivamente. Desde el estado u3 nos moveremos al m3 con un uno o un tres y al u5 con un cinco. Finalmente, en el estado m3 habr a un bucle con un uno o un tres y una transici on a u5 con un cinco. El aut omata queda pues:
c Universitat Jaume I 2010-2011

12

II26 Procesadores de lenguaje

1 m1 1 3

5
u3 [13] 3 m3 [13]
5

u5

Ejercicio 16

Escribe una expresi on regular para el lenguaje anterior.


Ejercicio 17

Escribe una expresi on regular y un AFD para el lenguaje de las cadenas de dos o m as d gitos del conjunto {1, 3, 5, 7} tales que el u ltimo es mayor que todos los anteriores. Vamos intentar ahora construir el AFD para el reconocimiento de comentarios un poco m as complejos que los que vimos para C. Estos comienzan por una cadena del lenguaje denido por la expresi on regular <-+ \| y terminan con la primera aparici on de una cadena del lenguaje denido por la expresi on regular \|-+ >. No es necesario que coincidan las longitudes de la cadenas de inicio y n del comentario. Como antes, creamos la parte inicial y nal del comentario, pero tendremos que tener en cuenta que puede aparecer m as de un gui on: < A B C | D | E F > G

Para a nadir las echas que faltan, tendremos en cuenta c omo se interpretan los estados: En el estado D estaremos siempre que no estemos dentro de una secuencia que pueda ser el nal del comentario. As que tendremos que llegar a el desde E si no vemos ni un gui on ni una barra y desde F si no vemos ni un gui on, ni una barra, ni un mayor. Adem as, permaneceremos en un bucle con cualquier s mbolo que no sea una barra. En el estado E ya hemos visto la barra del comienzo del comentario, as que llegaremos a el desde el estado F si vemos una barra. Adem as, permaneceremos en un bucle con la barra. Con esto, tenemos: < A B C | D [-|] [-|>] Para obtener la expresi on regular, necesitamos hacer algo m as de trabajo. Siguiendo el esquema de los comentarios en C, la expresi on regular tendr a la forma <-+ \|\|-+ >. Ahora representa cadenas que no tienen en su interior ninguna subcadena del lenguaje \|-+ >. Fij emonos en los s mbolos mayor que. Empezamos por escribir = [>] donde son cadenas que terminan en >, [|] | E | | F > G

Analizador l exico

13

s olo tienen un > y no est an en \|-+ >. Si una cadena termina en > y no est a en \|-+ > puede ser porque: Tenga una barra delante del >, es decir porque est e en [>] \|>. Hemos puesto [>] porque antes de la barra puede venir cualquier cosa. Tenga delante del > una secuencia de cero o m as guiones que no tenga delante una barra, es decir porque est e en ([>] [-|>])?- >. Es interesante analizar la expresi on con cuidado. Por un lado, acepta la cadena formada s olo por un >. Por otro, si la cadena es de la forma - ->, la aceptamos por la opcionalidad; nalmente si la opcionalidad no es vac a, seguro que la secuencia de guiones parte despu es de algo que no es una barra. Juntando ambas expresiones tenemos: =([>] \||([>] [-|>])?- )> con lo que =(([>] \||([>] [-|>])?- )>) [>] y la expresi on buscada es <-+ \|(([>] \||([>] [-|>])?- )>) [>] \|-+ >
Ejercicio* 18

Aqu hay varias expresiones regulares propuestas por estudiantes para comentarios como los mencionados. S olo la expresi on d) es correcta. Comprueba que lo es y piensa por qu e fallan las dem as. Intenta arreglar la c). a) <-+ \|([|]|\|[-]|\|-+ [>]) [|] \|-+ > b) <-+ \|([|]|\|[-]|\|-+ [->]) [|] \|-+ > c) <-+ \|- ([-]|- [->]|[-|]- ) - \|-+ > d) <-+ \|([|]|\|(\||-+ \|) ([-|]|-+ [-|>])) \|(\||-+ \|) -+ > Pista: es u til averiguar la losof a detr as de cada expresi on: las dos primeras buscan modelar prejos del cierre de la expresi on seguidos de algo que los interrumpe; la tercera modela secuencias de guiones no precedidas por | o no seguidas por >.

5.2.

Dicultad de dise no

Viendo el ejemplo anterior y los de los comentarios en C es posible que te plantees la cuesti on de si es m as f acil escribir expresiones regulares o dise nar AFDs. En realidad, ambos tienen sus puntos fuertes y d ebiles. El caso de los comentarios ejemplica lo dif cil que es expresar la negaci on con expresiones regulares y lo f acil que es mediante AFDs. El punto fuerte de las expresiones regulares es su facilidad para modelar no determinismo, especialmente el vinculado a modelar la disyunci on. Por ejemplo el lenguaje de las cadenas de dos o m as vocales en las que la u ltima no est a repetida se puede modelar con la expresi on [aeio]+ u|[aeiu]+ o|[aeuo]+ i|[aiou]+ e|[eiou]+ a sin embargo, el AFD correspondiente tiene m as de cincuenta estados (b asicamente, tiene que llevar la cuenta en los estados de que letras ha visto). Algo parecido sucede cuando tenemos que modelar cualquier conjunto nito (por ejemplo, las palabras clave de los lenguajes de programaci on, como veremos en la p agina 26). El dominar las expresiones regulares tambi en es importante porque se pueden utilizar en gran cantidad de contextos (por ejemplo en editores de texto, metacompiladores, validadores de entrada, etc.) y porque tienen extensiones que las hacen muy potentes. Algunas de esas extensiones permiten escribir m as f acilmente las expresiones de los comentarios que tanto nos han costado y otras hacen que los lenguajes generados sean m as potentes que los regulares (y que los incontextuales).
c Universitat Jaume I 2010-2011

14

II26 Procesadores de lenguaje

5.2.1.

Reconocimiento con AFDs

Como puedes imaginar, el algoritmo de reconocimiento con AFDs es bastante sencillo. Simplemente necesitamos llevar la cuenta del estado en que estamos y ver c omo podemos movernos: Algoritmo ReconoceAFD (x : , A = (Q, , E, q0 , F ) : AF D); q := q0 ; i := 1; mientras i |x| y existe (q, xi , q ) E hacer q := q ; i := i + 1; n mientras si i > |x| y q F entonces devolver s ; si no devolver no; n si Fin ReconoceAFD. Como puedes comprobar f acilmente, el coste de reconocer la cadena x es O(|x|), que incluso es independiente del tama no del aut omata!
Ejercicio* 19

Obviamente, la independencia del coste del tama no del aut omata depende de su implementaci on. Piensa en posibles implementaciones del aut omata y c omo inuyen en el coste.

5.3.

Construcci on de un AFD a partir de una expresi on regular

Podemos construir un AFD a partir de una expresi on regular. La idea b asica es llevar en cada estado del aut omata la cuenta de en qu e parte de la expresi on estamos. Para esto, emplearemos lo que llamaremos tems, que ser an expresiones regulares con un punto centrado ( ). El punto indicar a qu e parte de la expresi on corresponde con la entrada le da hasta alcanzar el estado en el que est a el tem. Por ejemplo, dada la expresi on regular (ab) cd(ef) , un posible tem ser a (ab) c d(ef) . Este tem lo podr amos encontrar tras haber le do, por ejemplo, la entrada ababc. Si el tem tiene el punto delante de un car acter, de una clase de caracteres o al nal de la expresi on diremos que es un tem b asico. Los estados de nuestro aut omata ser an conjuntos de tems b asicos sobre la expresi on regular. Utilizamos conjuntos porque en un momento dado podemos estar en m as de un punto en la expresi on regular. Por ejemplo, en la expresi on regular (a) b tras leer una a, podemos estar esperando otra a o una b, as pues, estar amos en el estado {( a) b, (a) b}. 5.3.1. Algunos ejemplos

Empezaremos por un ejemplo muy sencillo. Vamos a analizar la cadena b2 con la expresi on regular b2. Al comienzo, no hemos analizado nada y el tem que muestra esto es b2. El primer car acter es la b, que es trivialmente aceptado por b, as que avanzamos el punto para llegar a b 2. Ahora leemos el 2 y avanzamos a b2 . Dado que hemos terminado la entrada y hemos llegado al nal de la expresi on, podemos concluir que aceptamos la cadena. Podemos resumir el proceso en la siguiente tabla: Estado { b2} {b 2} {b2 } Entrada b 2 Observaciones Estado inicial Aceptamos

Cosas que hemos aprendido con este ejemplo: que si al leer un car acter, el punto est a delante de el, podemos moverlo detr as. Por otro lado, hemos visto que aceptaremos la cadena de entrada

Analizador l exico

15

si tenemos el punto al nal de la expresi on y se ha terminado la entrada. Dicho de otro modo, los estados que tengan tems con el punto en la u ltima posici on ser an nales. Construir un aut omata a partir de aqu es trivial: Los estados ser an: { b2}, {b 2} y {b2 }. Como estado inicial usamos { b2}. Con la b pasamos de { b2} a {b 2}. Con el 2 pasamos de {b 2} a {b2 }. El u nico estado nal es {b2 }. As obtenemos el aut omata siguiente5 : b b2 b2 2 b2

L ogicamente, las cosas se ponen m as interesantes cuando la expresi on regular no es u nicamente una cadena. Vamos a ver c omo podemos trabajar con clases de caracteres. Como ejemplo, analizaremos la cadena b2 con la expresi on regular [az][az09]. Al comienzo, no hemos analizado nada, as que el tem que muestra esto es [az][az09]. El primer car acter es la b, que es aceptado por [az], as que avanzamos el punto para llegar a [az] [az09]. Ahora leemos el 2 y avanzamos a [az][az09] . Como antes, aceptamos b2 ya que hemos llegado al nal de la expresi on y se ha terminado la entrada. En forma de tabla, hemos hecho: Estado { [az][az09]} {[az] [az09]} {[az][az09] } Entrada b 2 Observaciones Estado inicial Aceptamos

Como vemos, el punto se puede mover sobre una clase de caracteres si el car acter en la entrada es uno de los de la clase. Podemos construir ahora un AFD de la manera siguiente: Los estados ser an: { [az][az09]}, {[az] [az09]} y {[az][az09] }. Como estado inicial usamos { [az][az09]}. Con cada una de las letras de [az] pasamos del estado inicial a {[az] [az09]}. Con los d gitos pasamos de {[az] [az09]} a {[az][az09] }. El u nico estado nal es {[az][az09] }. As , obtenemos el aut omata [az] [az][az09] [az] [az09] [az09] [az][az09]

Aunque es tentador suponer que si el punto est a delante de una clase de caracteres tendremos un arco que sale con esa clase, no te dejes enga nar, es s olo una casualidad. En general no es as , mira, por ejemplo, el ejercicio 21. Vamos ahora a a nadir clausuras. Supongamos que tenemos la expresi on regular [az]([az0 9]) . Hemos puesto par entesis para tener m as claro d onde est a el punto; tambi en tendremos que hacerlo con las disyunciones. Hagamos como antes, vamos a analizar b2. Empezamos con el tem que tiene el punto delante de la expresi on regular. Con esto tenemos [az]([az09]) . Al aceptar b con [az] nos queda [az] ([az09]) Dado que el punto no est a delante de ning un car acter o clase de caracteres, tendremos que encontrar qu e tems b asicos constituyen nuestro siguiente
5 Aunque

en los estados tendremos conjuntos de tems, no pondremos las llaves para no sobrecargar las guras.

c Universitat Jaume I 2010-2011

16

II26 Procesadores de lenguaje

estado. Como el punto est a delante de la clausura, podemos bien leer una letra o d gito o bien terminar. Lo que vamos a hacer es reejar las dos posibilidades en nuestro estado mediante dos tems: [az]( [az09]) y [az]([az09]) . Con el 2 s olo podemos mover el punto del primer tem con lo que pasamos a [az]([az09] ) . Nuevamente tenemos un tem no b asico, as que volvemos a buscar los correspondientes tems b asicos. En este caso, podemos volver al comienzo de la clausura o salir de ella. Es decir, tenemos que pasar al estado que contiene [az]( [az09]) y [az]([az09]) , que es el que ya ten amos. Como el estado tiene un tem con punto al nal, aceptamos. Si resumimos el proceso en forma de tabla, tenemos:

Estado { [az]([az09]) } {[az]( [az09]) , [az]([az09]) } {[az]( [az09]) , [az]([az09]) }

Entrada b 2

Observaciones Estado inicial

Aceptamos

As pues, un punto situado inmediatamente antes de una clausura puede atravesar esta lo que equivale a decir que ha generado la cadena vac a o ponerse al comienzo de su interior para generar una o m as copias de ese interior. As mismo, cuando el punto est a delante del par entesis de cierre de la clausura puede salir de ella o volver al principio. Para ver c omo se permiten las repeticiones, vamos a analizar ab4:

Estado { [az]([az09]) } {[az]( [az09]) , [az]([az09]) } {[az]( [az09]) , [az]([az09]) } {[az]( [az09]) , [az]([az09]) }

Entrada a b 4

Observaciones Estado inicial

Aceptamos

Puedes darte cuenta de que, como era de esperar, tras movernos con a no cambiamos de estado. Podemos construir el aut omata correspondiente: [az09] [az] [az]([az09])

[az]( [az09]) [az]([az09])

Por u ltimo, vamos a ver c omo trabajar con las disyunciones. Vamos a intentar construir el aut omata para t(a|alo|re)n. El estado inicial no tiene problemas: { t(a|re|alo)n}. Con una t pasaremos al tem no b asico t (a|alo|re)n. A partir de el, se producen tres tems b asicos, que constituyen el siguiente estado de nuestro aut omata: {t( a|alo|re)n, t(a| alo|re)n, t(a|alo| re)n}. Desde este estado, con una a obtenemos dos tems: t(a |alo|re)n y t(a|a lo|re)n. El segundo es b asico, as que s olo tendremos que ver lo que pasa con el primer tem. Podemos ver que basta con pasar el punto detr as del par entesis para obtener t(a|alo|re) n. De manera similar, si tuvi eramos

Analizador l exico

17

Cuadro 1: Transformaciones de los tems no b asicos.

Item original ( ) ( ) ( ) ( )

Nuevos tems ( ) ( ) ( ) ( ) ( ) ( )

Item original (1 |2 |...|n )

Nuevos tems ( 1 |2 |...|n ) (1 | 2 |...|n ) ... (1 |2 |...| n ) (...|i |...)

(...|i |...)

el punto en t(a|alo |re)n o en t(a|alo|re )n, pasar amos a t(a|alo|re) n. Teniendo en cuenta esto, el aut omata nos queda: t(a|alo|re)n r t t( a|alo|re)n t(a| alo|re)n t(a|alo| re)n a l t(a|a lo|re)n t(a|alo|re) n 5.3.2. Formalizaci on t(a|al o|re)n n t(a|alo|re)n n o t(a|alo|re) n t(a|alo|r e)n e

Intentaremos ahora sistematizar lo que hemos ido haciendo. Empezaremos por el proceso de, a partir de un conjunto de tems, encontrar los tems b asicos que constituir an el estado del aut omata. Llamaremos clausura a este proceso y el algoritmo que seguiremos ser a el siguiente: Algoritmo clausura (S ) C := S ; v := ; // tems ya tratados mientras haya tems no b asicos en C hacer sea i un tem no b asico de C ; C := C {i}; v := v {i}; C := C transformaciones(i) v ; n mientras devolver C ; Fin clausura La idea b asica es sustituir cada tem no b asico por una serie de transformaciones que se corresponden con los movimientos que hac amos del punto durante los ejemplos. En el cuadro 1 puedes encontrar las transformaciones para las expresiones regulares b asicas. Es interesante darse cuenta de la diferencia entre los par entesis abiertos que encierran una subexpresi on y aquellos que corresponden a una disyunci on o una clausura. Una vez sabemos c omo calcular la clausura de un estado, podemos construir los movimientos del aut omata. Si estamos en un estado que contiene una serie de tems b asicos, el estado al que
c Universitat Jaume I 2010-2011

18

II26 Procesadores de lenguaje

iremos al consumir un car acter de la entrada ser a la clausura del conjunto resultante de avanzar el punto una posici on en aquellos tems que lo tengan bien delante del car acter, bien delante de una clase de caracteres que lo incluyan. El algoritmo para calcular el siguiente de un estado dado un s mbolo resulta muy sencillo: Algoritmo siguiente(q, x) devolver clausura({ | q x L( )}); Fin Ahora tenemos todas las herramientas para construir nuestro aut omata. El estado inicial ser a la clausura del tem formado anteponiendo el punto a la expresi on regular. Despu es iremos viendo todos los posibles movimientos desde este estado. Mantendremos un conjunto con los estados que vayamos creando y que nos queden por analizar. Como estados nales, pondremos aquellos que tengan el punto al nal. Con todo esto, el algoritmo resultante es: Algoritmo construcci on() q0 :=clausura( ); Q := {q0 }; E := := Q; // pendientes mientras = hacer q :=arbitrario(); := {q }; para todo x hacer si siguiente(q, x) = entonces q :=siguiente(q, x); si q Q entonces Q := Q {q }; := {q }; n si E := E {(q, x, q )}; n si n para todo n mientras F := {q Q | q }; devolver (Q, , E, q0 , F ); Fin
Ejercicio 20

Construye los AFDs correspondientes a las siguientes expresiones regulares: a) (a|)b b) (a|)b b

Ejercicio 21

Construye el AFD correspondiente a la expresi on regular [az] [az09]. Ten cuidado al elegir las clases de caracteres de los arcos.

Analizador l exico

19

Ejercicio 22

Completa el cuadro 1 para los operadores de opcionalidad y clausura positiva. Construye los AFDs correspondientes a las siguientes expresiones regulares: a) ab?c b) a[bc]?c c) ab[bc]+ d) a[bc]+ c

Ejercicio 23

Utiliza el m etodo de los tems para escribir AFDs para las siguientes categor as l exicas: a) Las palabras if, in e input. b) N umeros expresados en hexadecimal escritos como dos s mbolos de porcentaje seguidos de d gitos hexadecimales de modo que las letras est en todas, bien en may usculas, bien en min usculas. c) N umeros reales compuestos de una parte entera seguida de un punto y una parte decimal. Tanto la parte entera como la decimal son obligatorias.

Ejercicio* 24

Son m nimos los aut omatas obtenidos con el m etodo de los tems?
Ejercicio* 25

Construye los AFDs correspondientes a las expresiones del ejercicio 8.

5.4.

Compromisos entre espacio y tiempo

La construcci on anterior nos permite pasar de una expresi on regular a un AFD y emplear este para analizar cadenas. Sin embargo, esta puede no ser la soluci on optima para todos los casos en que se empleen expresiones regulares. Hay por lo menos dos maneras m as de trabajar con las expresiones regulares: emplear aut omatas nitos no deterministas (AFN) y utilizar backtracking. La construcci on de un aut omata no determinista es m as sencilla que la de uno determinista y proporciona aut omatas de menor n umero de estados que los deterministas (el n umero de estados de un AFD puede ser exponencial con la talla de la expresi on regular, el del AFN es lineal). A cambio, el an alisis es m as costoso (con el AFN el coste temporal es O(|x| |r|), por O(|x|) para el AFD, donde |x| es la talla de la cadena y |r| la de la expresi on regular). Esto hace que sea una opci on atractiva para situaciones en las que la expresi on se vaya a utilizar pocas veces. Cuando se utiliza backtracking, lo que se hace es ir probando a ver qu e partes de la expresi on se corresponden con la entrada y se vuelve atr as si es necesario. Por ejemplo, supongamos que tenemos la expresi on regular a(b|bc)c y la entrada abcc. Para analizarla, primero vemos que la a est a presente en la entrada. Llegamos a la disyunci on e intentamos utilizar la primera opci on. Ning un problema, avanzamos en la entrada a la primera c y en la expresi on a la c del nal. Volvemos a avanzar y vemos que la expresi on se ha terminado pero la entrada no. Lo que hacemos es volver atr as (es decir, retrocedemos en la entrada hasta el momento de leer la b) y escoger la segunda opci on de la disyunci on. El an alisis prosigue ahora normalmente y la cadena es aceptada. Podemos ver que el uso de backtracking minimiza la utilizaci on del espacio (s olo necesitamos almacenar la expresi on), pero puede aumentar notablemente el tiempo de an alisis (hasta hacerlo exponencial con el tama no de la entrada). Cuando se trabaja con compiladores e int erpretes, las expresiones se utilizan muchas veces, por lo que merece la pena utilizar AFDs e incluso minimizarlos (hay algoritmos de minimizaci on con
c Universitat Jaume I 2010-2011

20

II26 Procesadores de lenguaje

coste O(|Q| log(|Q|)), donde |Q| es el n umero de estados del aut omata). En otras aplicaciones, por ejemplo facilidades de b usqueda de un procesador de textos, la expresi on se emplear a pocas veces, as que es habitual que se emplee backtracking o aut omatas no deterministas si se quieren tener cotas temporales razonables. Como resumen de los costes, podemos emplear el siguiente cuadro. Recuerda que |r| es la talla de la expresi on regular y |x| la de la cadena que queremos analizar. M etodo AFD AFN Backtracking Memoria exponencial O(|r|) O(|r|) Tiempo O(|x|) O(|x| |r|) exponencial

De todas formas, en la pr actica los casos en los que los costes son exponenciales son raros, por lo que el n umero de estados del AFD o el tiempo empleado por backtracking suele ser aceptable.
Ejercicio* 26

Puedes dar ejemplos de expresiones regulares que requieran un coste exponencial para su transformaci on en AFD o su an alisis por backtracking?

6.

Implementaci on del analizador l exico

Hasta ahora hemos visto c omo especicar los lenguajes asociados a las diferentes categor as l exicas. Sin embargo, el analizador l exico no se utiliza para comprobar si una cadena pertenece o no a un lenguaje. Lo que debe hacer es dividir la entrada en una serie de componente l exicos, realizando en cada uno de ellos determinadas acciones. Algunas de estas acciones son: comprobar alguna restricci on adicional (por ejemplo que el valor de un literal entero est e dentro de un rango), preparar los atributos del componente y emitir u omitir dicho componente. As pues, la especicaci on del analizador l exico deber a incluir por cada categor a l exica del lenguaje el conjunto de atributos y acciones asociadas. Un ejemplo ser a: categor a entero expresi on regular [09]
+

acciones calcular valor comprobar rango emitir calcular valor comprobar rango emitir copiar lexema emitir emitir emitir omitir emitir

atributos valor

real

[09]+ \.[09]+

valor

identicador asignaci on rango blanco eof

[azAZ][azAZ09] := \.\. [ \t\n]+


e o f

lexema

6.1.

La estrategia avariciosa

Inicialmente puede parecer que podemos especicar completamente el analizador dando una lista de expresiones regulares para las distintas categor as. Sin embargo, debemos denir algo m as: c omo esperamos que se unan estas categor as para formar la entrada. Analicemos el siguiente fragmento de un programa utilizando las categor as anteriores: c3:= 10.2

Analizador l exico

21

En principio, esperamos encontrar la siguiente secuencia de componentes l exicos: id


lex: c3

asignaci on

real

valor: 10.2

Sin embargo, no hemos denido ning un criterio por el que la secuencia no pudiera ser esta: id
lex: c

entero

valor: 3

asignaci on

entero

valor: 1

real

valor: 0.2

Lo que se suele hacer es emplear la denominada estrategia avariciosa: en cada momento se busca en la parte de la entrada no analizada el prejo m as largo que pertenezca al lenguaje asociado a alguna categor a l exica. De esta manera, la divisi on de la entrada coincide con la que esperamos.

6.2.

M aquina discriminadora determinista

Para reconocer simult aneamente las distintas categor as l exicas utilizaremos un tipo de aut omata muy similar a los AFDs: la m aquina discriminadora determinista (MDD). El funcionamiento de las MDDs es muy similar al de los AFDs, las diferencias son dos: adem as de cadenas completas, aceptan prejos de la entrada y tienen asociadas acciones a los estados nales. Estudiaremos la construcci on a partir del ejemplo de especicaci on anterior. Empezamos por a nadir a cada expresi on regular un s mbolo especial: Categor a entero real identicador asignaci on rango blanco eof Expresi on regular [09]+ #entero [09]+ \.[09]+ #real [azAZ][azAZ09] #ident :=#asig \.\.#rango [ \t\n]+ #blanco e o #eof f

Despu es unimos todas las expresiones en una sola: [09]+ #entero |[09]+ \.[09]+ #real | [azAZ][azAZ09] #ident |:=#asig |\.\.#rango | [ \t\n]+ #blanco |eof#eof Ahora construimos el AFD asociado a esta expresi on regular. Este AFD lo puedes ver en la gura 1. Por construcci on, los u nicos arcos que pueden llegar a un estado nal ser an los etiquetados con s mbolos especiales. Lo que hacemos ahora es eliminar los estados nales (en realidad s olo habr a uno) y los arcos con s mbolos especiales. Como nuevos estados nales dejamos aquellos desde los que part an arcos con s mbolos especiales. A estos estados asociaremos las acciones correspondientes al reconocimiento de las categor as l exicas. Nuestro ejemplo queda como se ve en la gura 2. 6.2.1. Funcionamiento de la MDD

C omo funciona una MDD? La idea del funcionamiento es muy similar a la de los AFD, salvo por dos aspectos: La MDD no intenta reconocer la entrada sino segmentarla. La MDD act ua repetidamente sobre la entrada, empezando en cada caso en un punto distinto, pero siempre en su estado inicial. Sea x la cadena que queremos segmentar y M nuestra MDD. Queremos encontrar una secuencia de cadenas {xi }n i=1 tales que:
c Universitat Jaume I 2010-2011

22

II26 Procesadores de lenguaje

[09] 1 \. 2 [09]

[09] 3

# en
[09]

4 : \. 6

te ro

# real

# as ig

\. [azAZ09]

#r an

go

[az A

11

Z]

# ident
co

\t

\n ]

[ \t\n] 9

n la #b

10

Figura 1: AFD asociado a la expresi on regular completa.

Su concatenaci on sea la cadena original: x1 . . . xn = x. Cada una de ellas es aceptada por M, es decir, que para cada xi existe un camino en M que parte del estado inicial y lleva a uno nal. Queremos que sigan la estrategia avariciosa. Esto quiere decir que no es posible encontrar para ning un i una cadena y que sea aceptada por M, que sea prejo de xi . . . xn y que sea m as larga que xi . Afortunadamente, encontrar esta segmentaci on es muy sencillo. Empezamos por poner M en su estado inicial. Despu es actuamos como si M fuera un AFD, es decir, vamos cambiando el estado de la m aquina seg un nos indica la entrada. En alg un momento llegaremos a un estado q desde el que no podemos seguir avanzando. Esto puede suceder bien porque no existe ninguna transici on de q con el s mbolo actual, bien porque se nos haya terminado la entrada. Si q es nal, acabamos de encontrar un componente l exico. Ejecutamos las acciones asociadas a q y volvemos a empezar. Si q no es nal, tenemos que desandar lo andado, hasta que lleguemos al u ltimo estado nal por el que hayamos pasado. Este ser a el que indique el componente l exico encontrado. Si no existiera tal estado, estar amos ante un error l exico. La descripci on dada puede parecer un tanto liosa, vamos a ver un ejemplo. Supongamos que tenemos la entrada siguiente: a:= 9.1 Inicialmente la m aquina est a en el estado 0. Con los dos primeros espacios, llegamos al estado 9. El siguiente caracter (la a) no tiene transici on desde aqu , as que miramos el estado actual. Dado

# eo f

eo f

Analizador l exico

23

[09] \. [09]

[09]

emitir real emitir entero

[09]

4 : \. 6

emitir asig

\. [azAZ09]

emitir rango

[az A

Z]

8
[ \t

emitir ident

[ \t\n]
\n ]

omitir

Figura 2: MDD correspondiente al ejemplo. Hemos simplicado las acciones para evitar sobrecargar la gura.

que es nal, ejecutamos la acci on correspondiente, en este caso omitir. Volvemos al estado 0. Ahora estamos viendo la a, as que transitamos al estado 8. Nuevamente nos encontramos con un car acter que no tiene transici on. En este caso, la acci on es emitir, as que emitimos el identicador, tras haber copiado el lexema. De manera an aloga, se emitir a un asig, se omitir a el espacio y se emitir a un real. Vamos a ver ahora qu e pasa con la entrada siguiente: 9..12 Como antes, empezamos en el estado 0. Con el 9 llegamos al estado 1. Despu es, con el . vamos al estado 2. Ahora resulta que no podemos transitar con el nuevo punto. Lo que tenemos que hacer es retroceder hasta el u ltimo estado nal por el que hemos pasado, en este caso el 1. L ogicamente, al retroceder, debemos devolver los s mbolos no consumidos, en nuestro caso los dos puntos. De esta manera, hemos encontrado que 9 es un entero y lo emitimos tras calcular el valor y comprobar el rango. Qu e hacemos ahora? Volvemos al estado cero. El car acter que vamos a leer ahora es nuevamente el primero de los dos puntos. De esta manera transitamos al estado 6 y despu es al 7. Emitimos rango y volvemos a empezar para emitir otro entero. Por u ltimo, veamos qu e pasa con: a.3 En primer lugar, emitimos el identicador con lexema a. Despu es transitamos con el punto al estado 6. En este momento no podemos transitar con el tres. Pero adem as resulta que no hemos pasado por estado nal alguno. Lo que sucede es que hemos descubierto un error.
c Universitat Jaume I 2010-2011

f eo

10

emitir eof

24

II26 Procesadores de lenguaje

Ejercicio 27

Construye una MDD para la siguiente especicaci on: categor a n umero expresi on regular -?[09] (\.[09] )?
+

acciones calcular valor averiguar tipo emitir separar operador emitir copiar lexema emitir omitir omitir copiar lexema emitir emitir

atributos valor, tipo

asignacion operador comentario blanco id eof

[-+*/]?= [-+*/] \|[\n] \n [ \t\n] [azAZ_][azAZ09_]


e o f

operador lexema

lexema

C omo se comportar a ante las siguientes entradas? a) a1+= a+1 b) a 1+= a-1 c) a1+ = a- 1 d) a1+= a-1 |+1

6.3.

Tratamiento de errores

Hemos visto que si la MDD no puede transitar desde un determinado estado y no ha pasado por estado nal alguno, se ha detectado un error l exico. Tenemos que tratarlo de alguna manera adecuada. Desafortunadamente, es dif cil saber cu al ha sido el error. Si seguimos con el ejemplo anterior, podemos preguntarnos la causa del error ante la entrada a.3. Podr a suceder que estuvi esemos escribiendo un real y hubi esemos sustituido el primer d gito por la a. Por otro lado, podr a ser un rango mal escrito por haber borrado el segundo punto. Tambi en podr a suceder que el punto sobrase y quisi eramos escribir el identicador a3. Como puedes ver, a un en un caso tan sencillo es imposible saber qu e ha pasado. Una posible estrategia ante los errores ser a denir una distancia entre programas y buscar el programa m as pr oximo a la entrada encontrada. Desgraciadamente, esto resulta bastante costoso y las ganancias en la pr actica suelen ser m nimas. La estrategia que seguiremos es la siguiente: Emitir un mensaje de error y detener la generaci on de c odigo. Devolver al ujo de entrada todos los caracteres le dos desde que se detect o el u ltimo componente l exico. Eliminar el primer car acter. Continuar el an alisis. Siguiendo estos pasos, ante la entrada a.3, primero se emitir a un identicador, despu es se se nalar a el error al leer el 3. Se devolver an el tres y el punto a la entrada. Se eliminar a el punto y se seguir a el an alisis emitiendo un entero.

Analizador l exico

25

6.3.1.

Uso de categor as l exicas para el tratamiento de errores

La estrategia anterior garantiza que el an alisis contin ua y que nalmente habremos analizado toda la entrada. Sin embargo, en muchas ocasiones los mensajes de error que puede emitir son muy poco informativos y puede provocar errores en las fases siguientes del an alisis. En algunos casos, se puede facilitar la emisi on de mensajes de error mediante el uso de pseudo-categor as l exicas. Supongamos que en el ejemplo anterior queremos tratar los siguientes errores: No puede aparecer un punto seguido de un entero. En este caso queremos que se trate como si fuera un n umero real con parte entera nula. No puede aparecer un punto aislado. En este caso queremos que se asuma que en realidad era un rango. Para lograrlo, podemos a nadir a la especicaci on las siguientes l neas: categor a errorreal expresi on regular \.[09]
+

acciones informar del error calcular valor emitir real informar del error emitir rango

atributos valor

errorrango

\.

Con esta nueva especicaci on, la entrada v:=.3 har a que se emitiesen un identicador, una asignaci on y un real, adem as de informar del error. Pese a las ventajas que ofrecen estas categor as, hay que tener mucho cuidado al trabajar con ellas. Si en el caso anterior hubi eramos a nadido [0-9]+\. a la pseudo-categor a errorreal con la intenci on de capturar reales sin la parte decimal, habr amos tenido problemas. As , la entrada 1..2 devolver a dos errores (1. y .2). Una categor a que suele permitir de manera natural este tipo de estrategia es el literal de cadena. Un error frecuente es olvidar las comillas de cierre de una cadena. Si las cadenas no pueden abarcar varias l neas, es f acil (y un ejercicio deseable para ti) escribir una expresi on regular para detectar las cadenas no cerradas. 6.3.2. Detecci on de errores en las acciones asociadas

Otros errores que se detectan en el nivel l exico son aquellos que s olo pueden encontrarse al ejecutar las acciones asociadas a las categor as. Un ejemplo es el de encontrar n umeros fuera de rango. En principio es posible escribir una expresi on regular para, por ejemplo, los enteros de 32 bits. Sin embargo, esta expresi on resulta excesivamente complicada y se puede hacer la comprobaci on f acilmente mediante una simple comparaci on. Esta es la estrategia que hemos seguido en nuestro ejemplo. Aqu s que es de esperar un mensaje de error bastante informativo y una buena recuperaci on: basta con emitir la categor a con un valor predeterminado (por ejemplo cero).

6.4.

Algunas categor as especiales

Ahora comentaremos algunas categor as que suelen estar presentes en muchos lenguajes y que tienen ciertas peculiaridades. Categor as que se suelen omitir Hemos visto en nuestros ejemplos que los espacios generalmente no tienen signicado. Esto hace que sea habitual tener una expresi on del tipo [ \t\n]+ con omitir como acci on asociada. A un as , el analizador l exico s que suele tenerlos en cuenta para poder dar mensajes de error. En particular, es bueno llevar la cuenta de los \n para informar de la l nea del error. En algunos casos, tambi en se tienen en cuenta los espacios para poder dar la posici on del error dentro de la l nea.
c Universitat Jaume I 2010-2011

26

II26 Procesadores de lenguaje

Otra categor a que es generalmente omitida es el comentario. Como en el caso anterior, el analizador l exico suele analizar los comentarios para comprobar cu antos nes de l nea est an incluidos en ellos y poder informar adecuadamente de la posici on de los errores. El n de chero Aunque ha aparecido como un s mbolo m as al hablar de las expresiones regulares, lo cierto es que el n de chero no suele ser un car acter m as. Dependiendo del lenguaje en que estemos programando o incluso dependiendo de las rutinas que tengamos, el n de chero aparecer a de una manera u otra. En algunos casos es un valor especial (por ejemplo en la funci on getc de C), en otros debe consultarse expl citamente (por ejemplo en Pascal) y en otros consiste en la devoluci on de la cadena vac a (por ejemplo en Python). Como esto son detalles de implementaci on, nosotros mostraremos expl citamente el n de chero como si fuera un car acter eof. Es m as, en ocasiones ni siquiera tendremos un chero que nalice. Puede suceder, por ejemplo, que estemos analizando la entrada l nea a l nea y lo que nos interesa es que se termina la cadena que representa la l nea. Tambi en puede suceder que estemos en un sistema interactivo y analicemos una cadena introducida por el usuario. Para unicar todos los casos, supondremos que el analizador l exico indica que ya no tiene m as entrada que analizar mediante la emisi on de la categor a eof y no utilizaremos eof como parte de ninguna expresi on regular compuesta. Las palabras clave De manera impl cita, hemos supuesto que las expresiones regulares que empleamos para especicar las categor as l exicas denen lenguajes disjuntos dos a dos. Aunque esta es la situaci on ideal, en diversas ocasiones puede suponer una fuente de incomodidades. El caso m as claro es el que sucede con las palabras clave y su relaci on con los identicadores. Es habitual que los identicadores y las palabras clave tengan la misma forma. Por ejemplo, supongamos que los identicadores son cadenas no vac as de letras min usculas y que if es una palabra clave. Qu e sucede al ver la cadena if? La respuesta depende en primer lugar del lenguaje. En bastantes lenguajes, las palabras clave son reservadas, por lo que el analizador l exico deber a clasicar la cadena if en la categor a l exica if . Sin embargo, en lenguajes como PL/I, el siguiente fragmento if then then then = else ; else else = then; es perfectamente v alido. En este caso, el analizador l exico necesita informaci on sint actica para decidir si then es una palabra reservada o un identicador. Si decidimos que las palabras clave son reservadas, tenemos que resolver dos problemas: C omo lo reejamos en la especicaci on del analizador l exico. C omo lo reejamos al construir la MDD. Puede parecer que el primer problema est a resuelto; simplemente se escriben las expresiones regulares adecuadas. Sin embargo, las expresiones resultantes pueden ser bastante complicadas. Intentemos escribir la expresi on para un caso muy sencillo: identicadores consistentes en cadenas de letras min usculas, pero sin incluir la cadena if. La expresi on es: [ahjz][az] |i([aegz][az] )?|if[az]+
Ejercicio* 28

Sup on que en el lenguaje del ejemplo anterior tenemos tres palabras clave reservadas: if, then y else. Dise na una expresi on regular para los identicadores que excluya estas tres palabras clave.

Este sistema no s olo es inc omodo para escribir, facilitando la aparici on de errores, tambi en hace que a nadir una nueva palabra clave sea una pesadilla. Lo que se hace en la pr actica es utilizar un sistema de prioridades. En nuestro caso, cuando un lexema pueda clasicarse en dos categor as

Analizador l exico

27

l exicas distintas, escogeremos la categor a que aparece en primer lugar en la especicaci on. As para las palabras del ejercicio, nuestra especicaci on podr a ser: categor a if then else identicador expresi on regular if then else [az]+ acciones emitir emitir emitir copiar lexema emitir atributos

lexema

Ahora que sabemos c omo especicar el analizador para estos casos, nos queda ver la manera de construir la MDD. En principio, podemos pensar en aplicar directamente la construcci on de la MDD sobre esta especicaci on. Sin embargo este m etodo resulta excesivamente complicado. Por ello veremos otra estrategia que simplica la construcci on de la MDD con el precio de algunos c alculos extra. Para ver lo complicada que puede resultar la construcci on directa, volvamos al caso en que s olo tenemos identicadores formados por letras min usculas y una palabra reservada: if. Empezamos por escribir la expresi on regular con las marcas especiales: if#if |[az]+ #identificador Con esta expresi on construimos la MDD:

f i A
[a hj

B [aegz]
z]

emitir if

[az] emitir identicador

[az] Vemos que la ambig uedad aparece en el estado C, que tiene dos posibles acciones. Hemos decidido que if tiene preferencia sobre identicador, as que dejamos el aut omata de esta manera:

f i A
[a hj

B [aegz]
z]

emitir if

[az] emitir identicador

[az] Vemos que incluso para algo tan sencillo, el aut omata se complica. Evidentemente, a medida que crece el n umero de palabras reservadas, la complejidad tambi en aumenta.
c Universitat Jaume I 2010-2011

28

II26 Procesadores de lenguaje

La soluci on que podemos adoptar es la siguiente: [az] [az] A B comprobar lexema y emitir

De esta manera, cualquier lexema parecido a un identicador terminar a en el estado B. Bastar a entonces comprobar si realmente es un identicador o una palabra clave y emitir la categor a adecuada. De esta manera, tambi en resulta sencillo aumentar el n umero de palabras clave sin grandes modicaciones de la MDD. Se nos plantea ahora el problema de comprobar de manera eciente si la palabra encontrada efectivamente es una palabra clave. Para esto hay diversas opciones: Hacer busqueda dicot omica sobre una lista ordenada. Utilizar una tabla de dispersi on (hash ). Como la lista de palabras claves es conocida a priori, podemos utilizar una funci on de dispersi on perfecta. Utilizar un trie. Etc. . .

6.5.

El interfaz del analizador l exico

C omo ser a la comunicaci on del analizador l exico con el sint actico? Como ya comentamos en el tema de estructura del compilador, el analizador sint actico ir a pidiendo componentes l exicos a medida que los necesite. As pues, lo m nimo que deber a ofrecer el analizador l exico es una funci on (o m etodo en caso de que utilicemos un objeto para construirlo) que permita ir recuperando el componente l exico actual. Puede ser c omodo dividir esta lectura en dos partes: Una funci on que indique cu al es el u ltimo componente le do. Esta funci on no avanza la lectura. Una funci on que avance la lectura hasta encontrar otro componente y lo devuelva. Dado que la primera es generalmente trivial (bastar a con devolver el contenido de alguna variable local o de un atributo), veremos c omo implementar la segunda. Adem as de estas funciones, existen una serie de funciones auxiliares que debe proporcionar el analizador: Una funci on (o m etodo) para especicar cu al ser a el ujo de caracteres de entrada. Generalmente, esta funci on permitir a especicar el chero desde el que leeremos. En caso de que el analizador sea un objeto, la especicaci on del ujo de entrada puede ser uno de los par ametros del constructor. Una funci on (o m etodo) para averiguar en qu e l nea del chero nos encontramos. Esto facilita la emisi on de mensajes de error. Para dar mejores mensajes, puede tambi en devolver el car acter dentro de la l nea. Relacionada con la anterior, en algunos analizadores podemos pedir que escriba la l nea actual con alguna marca para se nalar el error. Si el analizador l exico necesita informaci on del sint actico, puede ser necesario utilizar funciones para comunicarla. La complejidad o necesidad de estas funciones variar a seg un las necesidades del lenguaje. As , si nuestro lenguaje permite la inclusi on de cheros (como las directivas #include de C), ser a necesario poder cambiar con facilidad el ujo de entrada (o, alternativamente, crear m as de un analizador l exico).

Analizador l exico

29

6.6.

El ujo de entrada

Hasta ahora hemos asumido m as o menos impl citamente que los caracteres que utilizaba el analizador l exico proven an de alg un chero. En realidad, esto no es necesariamente as . Puede suceder que provengan de la entrada est andar6 , o de una cadena (por ejemplo, en caso de que estemos procesando los argumentos del programa). Para evitar entrar en estos detalles, podemos suponer que existe un m odulo u objeto que utiliza el analizador l exico para obtener los caracteres. Este m odulo deber a ofrecer al menos los siguientes servicios: Un m etodo para especicar desde d onde se leen los caracteres (chero, cadena, dispositivo. . . ). Un m etodo para leer el siguiente car acter. Un m etodo para devolver uno o m as caracteres a la entrada. Seg un las caracter sticas del lenguaje para el que estemos escribiendo el compilador, la devoluci on de caracteres ser a m as o menos complicada. En su forma m as simple, bastar a una variable para guardar un car acter. En otros casos, ser a necesario incluir alguna estructura de datos, que ser a una pila o una cola, para poder guardar m as de un car acter. A la hora de leer un car acter, lo primero que habr a que hacer es comprobar si esta variable est a o no vac a y despu es devolver el car acter correspondiente. Un aspecto importante es la eciencia de la lectura desde disco. No es raro que una implementaci on eciente de las siguientes fases del compilador haga que la lectura de disco sea el factor determinante para el tiempo total de compilaci on. Por ello es necesario que se lean los caracteres utilizando alg un almac en intermedio o buer. Si se utiliza un lenguaje de programaci on de alto nivel, es normal que las propias funciones de lectura tengan ya implementado un sistema de buer. Sin embargo, suele ser necesario ajustar alguno de sus par ametros. Aprovechando que los ordenadores actuales tienen memoria suciente, la mejor estrategia puede ser leer completamente el chero en memoria y tratarlo como una gran cadena. En algunos casos esto no es posible. Por ejemplo, si se est a preparando un sistema interactivo o si se pretende poder utilizar el programa como ltro.

6.7.

Implementaci on de la MDD

Llegamos ahora a la implementaci on de la MDD propiamente dicha. Podemos dividir las implementaciones en dos grandes bloques: Implementaci on mediante tablas. Implementaci on mediante control de ujo. En el primer caso, se dise na una funci on que es independiente de la MDD que se est e utilizando. Esta se codica en una tabla que se pasa como par ametro a la funci on. Esta estrategia tiene la ventaja de su exibilidad: la funci on sirve para cualquier analizador y las modicaciones s olo afectan a la tabla. A cambio tiene el problema de la velocidad, que suele ser inferior a la otra alternativa. La implementaci on mediante control de ujo genera un c odigo que simula directamente el funcionamiento del aut omata. Esta aproximaci on es menos exible pero suele ser m as eciente. 6.7.1. Implementaci on mediante tablas

Esta es la implementaci on m as directa de la MDD. Se utilizan tres tablas: La tabla movimiento contiene por cada estado y cada s mbolo de entrada el estado siguiente o una marca especial en caso de que el estado no est e denido.
6 En UNIX esto no supone diferencia ya que la entrada est andar se trata como un chero m as, pero en otros sistemas s que es importante.

c Universitat Jaume I 2010-2011

30

II26 Procesadores de lenguaje

La tabla nal contiene por cada estado un booleano que indica si es o no nal. La tabla acci on contiene por cada estado las acciones asociadas. Asumiremos que la acci on devuelve el token que se tiene que emitir o el valor especial indenido para indicar que hay que omitir. Debemos tener en cuenta que la tabla m as grande, movimiento, ser a realmente grande. Con que tengamos algunos cientos de estados y que consideremos 256 car acteres distintos es f acil subir a m as de cien mil entradas. Afortunadamente, es una tabla bastante dispersa ya que desde muchos estados no podemos transitar con muchos caracteres. Adem as existen muchas las repetidas. Por esto existen diversas t ecnicas que reducen el tama no de la tabla a niveles aceptables, aunque no las estudiaremos aqu . Lo que tenemos que hacer para avanzar el analizador es entrar en un bucle que vaya leyendo los caracteres y actualizando el estado actual seg un marquen las tablas. A medida que avanzamos tomamos nota de los estados nales por los que pasamos. Cuando no podemos seguir transitando, devolvemos al ujo de entrada la parte del lexema que le mos despu es de pasar por el u ltimo nal y ejecutamos las acciones pertinentes. Las variables que utilizaremos son: q : el estado de la MDD. l: el lexema del componente actual. uf : u ltimo estado nal por el que hemos pasado. ul : lexema que ten amos al llegar a uf . Date cuenta de que al efectuar las acciones sem anticas, debemos utilizar como lexema ul . Teniendo en cuenta estas ideas, nos queda el algoritmo de la gura 3. Observa que hemos utilizado la palabra devolver con dos signicados distintos: por un lado es la funci on que nos permite devolver al ujo de entrada el o los caracteres de anticipaci on; por otro lado, es la sentencia que termina la ejecuci on del algoritmo. Deber a estar claro por el contexto cu al es cual. A la hora de implementar el algoritmo, hay una serie de simplicaciones que se pueden hacer. Por ejemplo, si el ujo de entrada es inteligente, puede que baste decirle cu antos caracteres hay que devolver, sin pasarle toda la cadena. La manera de tratar los errores depender a de la estrategia exacta que hayamos decidido. Puede bastar con activar un ag de error, mostrar el error por la salida est andar y relanzar el an alisis. Otra posibilidad es lanzar una excepci on y conar en que otro nivel la capture. 6.7.2. Implementaci on mediante control de ujo

En este caso, el propio programa reeja la estructura de la MDD. L ogicamente, aqu s olo podemos dar un esquema. La estructura global del programa es simplemente un bucle que lee un car acter y ejecuta el c odigo asociado al estado actual. Esta estructura la puedes ver en la gura 4. El c odigo asociado a un estado depender a de si este es o no nal. En caso de que lo sea, tenemos que guard arnoslo en la variable uf y actualizar ul . Despu es tenemos que comprobar las transiciones y, si no hay transiciones posibles, devolver c al ujo de entrada y ejecutar las acciones asociadas al estado. Si las acciones terminan en omitir, hay que devolver la m aquina al estado inicial, en caso de que haya que emitir, saldremos de la rutina. El c odigo para los estados nales est a en la gura 5. El c odigo para los estados no nales es ligeramente m as complicado, porque si no hay transiciones debemos retroceder hasta el u ltimo nal. El c odigo correspondiente est a en la gura 6. Ten en cuenta que, si se est a implementando a mano el analizador, suele ser muy f acil encontrar mejoras sencillas que aumentar an la eciencia del c odigo. Por ejemplo, muchas veces hay estados que transitan hacia s mismos, lo que se puede modelar con un bucle. En otros casos, no es necesario guardar el u ltimo estado nal porque s olo hay una posibilidad cuando no podamos transitar. Por todo esto, es bueno que despu es (o antes) de aplicar las reglas mec anicamente, reexiones acerca de las particularidades de tu aut omata y c omo puedes aprovecharlas.

Analizador l exico

31

Algoritmo siguiente; // Con tablas q := q0 ; // Estado actual l := ; // Lexema uf := indenido; // Ultimo estado nal ul := ; // Lexema le do hasta el u ltimo nal mientras 0 = 1 1 hacer acter; c := siguiente car l := l c; si movimiento[q, c] = indenido entonces q := movimiento[q, c]; si nal[q ] entonces uf := q ; ul := l n si si no si uf = indenido entonces devolver(ul1 l); // Retrocedemos en el ujo de entrada t := ejecutar(acci on[uf ], ul ); si t = indenido entonces // Omitir q := q0 ; l := ; // Reiniciamos la MDD uf := indenido; ul := ; si no devolver t; n si si no // Tratar error n si n si n mientras n siguiente.
Figura 3: Algoritmo para encontrar el siguiente componente utilizando tablas. La expresi on ul1 l representa la parte de l que queda despu es de quitarle del principio la cadena ul.

Algoritmo siguiente; // Con control de ujo q := q0 ; // Estado actual l := ; // Lexema uf := indenido; // Ultimo estado nal ul := ; // Lexema le do hasta el u ltimo nal mientras 0 = 1 1 hacer acter; c := siguiente car l := l c; opci on q q0 : // c odigo del estado q0 q1 : // c odigo del estado q1 ... qn : // c odigo del estado qn n opci on n mientras n siguiente.
Figura 4: Estructura de un analizador l exico implementado mediante control de ujo.

c Universitat Jaume I 2010-2011

32

II26 Procesadores de lenguaje

// C odigo del estado qi (nal) uf := qi ; ul := lc1 ; // Ojo: eliminamos el u ltimo car acter opci on c a1 . . . an : q := qa ; // (q, c) = qa para c a1 . . . an b1 . . . bm : q := qb ; // (q, c) = qb para c b1 . . . bm ... z1 . . . zk : q := qz ; // (q, c) = qz para c z1 . . . zk si no // no hay transiciones con c devolver(c); // Acciones de qi // Si las acciones incluyen omitir: q := q0 ; l := ; uf := indenido; ul := ; // Si no incluyen omitir: devolver t; n opci on
Figura 5: C odigo asociado a los estados nales.

// C odigo del estado qi (no nal) opci on c a1 . . . an : q := qa ; // (q, c) = qa para c a1 . . . an b1 . . . bm : q := qb ; // (q, c) = qb para c b1 . . . bm ... z1 . . . zk : q := qz ; // (q, c) = qz para c z1 . . . zk si no // no hay transiciones con c si uf = indenido entonces devolver(ul1 l); // Retrocedemos en el ujo de entrada t := ejecutar(acci on[uf ], ul ); si t = indenido entonces q := q0 ; l := ; uf := indenido; ul := ; si no devolver t; n si si no // Tratar error n si n opci on
Figura 6: C odigo asociado a los estados no nales.

Analizador l exico

33

Bucles en los estados Supongamos que nuestro aut omata tiene un fragmento como este: [azAZ09] [azAZ] A B emitir id

Las acciones correspondientes al estado B ser an: uf := B ; ul := lc1 ; opci on c a. . . z, A. . . Z, 0. . . 9: q := B ; si no devolver(c); t := id(ul); devolver t; n opci on Sin embargo, es m as eciente hacer: mientras c en [a. . . z, A. . . Z, 0. . . 9] hacer l := l c; acter; c := siguiente car n mientras devolver(c); devolver id(l); No se guarda el estado nal Si nuestro aut omata tiene un fragmento como: [09] \. A B [09] C

no es necesario en el estado A guardar el u ltimo estado nal por el que se pasa: en B , sabemos que, si no se puede transitar, se ha venido de A y en C ya estamos en un estado nal.

7.

Algunas aplicaciones de los analizadores l exicos

Adem as de para construir compiladores e int erpretes, los analizadores l exicos se pueden emplear para muchos programas convencionales. Los ejemplos m as claros son aquellos programas que tienen alg un tipo de entrada de texto donde hay un formato razonablemente libre en cuanto espacios y comentarios. En estos casos es bastante engorroso controlar d onde empieza y termina cada componente y es f acil liarse con los ndices de los caracteres. Un analizador l exico simplica notablemente la interfaz y si se dispone de un generador autom atico, el problema se resuelve en pocas l neas de c odigo. Vamos a ver c omo utilizar metacomp, el metacompilador que utilizaremos en pr acticas, para resolver un par de problemas: obtener el vocabulario de un chero de texto y eliminar los comentarios de un programa en C.

c Universitat Jaume I 2010-2011

34

II26 Procesadores de lenguaje

7.1.

Vocabulario

Supongamos que tenemos uno o varios cheros de texto y queremos obtener una lista de las palabras que contienen junto con su n umero de apariciones. Deniremos las palabras como aquellas secuencias de caracteres que se ajustan a la expresi on [azAZ]+ . Adem as, pasaremos las palabras a min uscula. Omitiremos el resto de caracteres de la entrada. Con esto, podemos escribir nuestra especicaci on l exica: categor a palabra otro expresi on regular [azAZ] .
+

acciones calcular pal emitir omitir

atributos pal

Para crear un analizador l exico con metacomp, basta con escribir un chero de texto con dos partes separadas por el car acter %. La primera parte contiene una especicaci on l exica. Esta consiste en una serie de l neas con tres partes cada una: Un nombre de categor a o la palabra None si la categor a se omite. Un nombre de funci on de tratamiento o None si no es necesario ning un tratamiento. Una expresi on regular. Los analizadores l exicos generados por metacomp dividen la entrada siguiendo la estrategia avariciosa y, en caso de conicto, preriendo las categor as que aparecen en primer lugar. Por cada lexema encontrado en la entrada se llama a la correspondiente funci on de tratamiento, que normalmente se limita a calcular alg un atributo adicional a los tres que tienen por defecto todos los componentes: lexema, nlinea y cat, que representan, respectivamente, el lexema, el n umero de l nea y la etiqueta de categor a del componente. Par nuestro ejemplo, la especicaci on l exica tendr a las l neas:
1 2 3 4

palabra procesaPalabra None None %

[a-zA-Z]+ .

Despu es del car acter %, viene la parte de c odigo de usuario, que puedes ver en la gura 7. Aqu tenemos que especicar las funciones de tratamiento (en nuestro caso procesaPalabra), otras funciones que queramos emplear (en nuestro caso tendremos la funci on que procesa un chero) y la funci on main a la que se llamar a al ejecutar el programa generado por metacomp. Veamos las tres funciones una por una. La funci on procesaPalabra se limita a copiar en el atributo pal de la componente el lexema pasado a min usculas. La funci on procesaFichero es m as interesante. Le pasamos como par ametro un chero (f). Con el, construimos un objeto de tipo AnalizadorLexico, que nos proporciona metacomp. Cada vez que llamemos al m etodo avanza del analizador l exico, nos devolver a un componente. Para averiguar a qu e categor a pertenece, utilizamos el atributo cat. Si hemos terminado, la categor a sera mc_EOF, en caso contrario, entramos en el bucle. Como s olo puede ser una palabra, actualizamos el diccionario con las cuentas. Finalmente, al salir del bucle, mostramos las cuentas que hemos recogido. Por u ltimo la funci on main se ocupa de llamar a procesaFichero bien con la entrada est andar, bien con los cheros que se le pasen como argumentos. Para compilar el programa, si el chero se llama vocabulario.mc hacemos: metacomp -S -s vocabulario vocabulario.mc La opci on -S indica que s olo queremos generar el analizador l exico (por defecto, metacomp genera tambi en un analizador sint actico con acciones sem anticas, lo veremos m as adelante). La opci on -s con un nombre de chero dice d onde se debe guardar el resultado; si no la especicamos, se muestra por la salida est andar.

Analizador l exico

35

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

def procesaPalabra(c): c.pal = c.lexema.lower() def procesaFichero(f): cuentas = {} A = AnalizadorLexico(f) c = A.avanza() while c.cat != "mc_EOF": cuentas[c.pal] = cuentas.get(c.pal, 0)+1 c = A.avanza() for pal in sorted(cuentas.keys()): print "%s\t%s" % (pal, cuentas[pal]) def main(): if len(sys.argv) == 1: procesaFichero(sys.stdin) else: for n in sys.argv: try: f = open(n) except: sys.stderr.write("Error, no he podido abrir %s\n" % f) sys.exit(1) print n print "="*len(n) print procesaFichero(f)
Figura 7: Zona de c odigo de usuario para la aplicaci on de vocabulario.

7.2.

Eliminar comentarios

Vamos ahora a hacer un ltro que lea un programa en C o C++ por su entrada est andar, elimine los comentarios y muestre el resultado por la salida est andar. En la gura 8 puedes ver una versi on inicial. Como ves, omitimos los comentarios y tenemos una categor a para recoger cualquier car acter. El bucle de procesa los componentes se limita a escribirlos en la salida est andar. Desgraciadamente, tenemos dos problemas con esta estrategia. Por un lado, si eliminamos un comentario sin m as, se pueden juntar dos componentes l exicos que estuvieran separados y cambiar el signicado del programa. Por otro lado, si el comentario est a dentro de un literal de cadena, no es realmente un comentario. Para solucionar el primer problema, sustituiremos los comentarios del primer tipo por un espacio. Esto har a que sean una categor a que se emite y que tengamos que tenerla en cuenta en procesaFichero. Para resolver el segundo problema, tendremos que incluir una categor a para las cadenas, que tambi en habr a que tener en cuenta en procesaFichero. La nueva versi on est a en la gura 9. Observa que el programa completo tiene menos de veinte l neas. Imagina cu antas l neas tendr as que escribir en un programa equivalente sin utilizar el concepto de analizador l exico y, lo que es m as importante, piensa cu anto te costar a asegurarte de que tu implementaci on es correcta.

c Universitat Jaume I 2010-2011

36

II26 Procesadores de lenguaje

1 2 3 4 5 6 7 8 9 10 11 12 13

None None otro

None None None

/\*([^*]|\*+[^*/])*\*+/ //[^\n]* .

% def procesaFichero(f): A = AnalizadorLexico(f) c = A.avanza() while c.cat != "mc_EOF": sys.stdout.write(c.lexema) def main(): procesaFichero(sys.stdin)
Figura 8: Aplicaci on inicial para eliminar los comentarios de un programa en C o C++.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

comentario None cadena otro None %

None None None .

/\*([^*]|\*+[^*/])*\*+/ //[^\n]* "([\n\\"]|\\[\n])*"

def procesaFichero(f): A = AnalizadorLexico(f) c = A.avanza() while c.cat != "mc_EOF": if c.cat == "cadena" or c.cat == "otro": sys.stdout.write(c.lexema) else: sys.stdout.write( ) c = A.avanza() def main(): procesaFichero(sys.stdin)
Figura 9: Aplicaci on para eliminar los comentarios de un programa en C o C++.

Analizador l exico

37

8.

Resumen del tema


El analizador l exico divide la entrada en componentes l exicos. Los componentes se agrupan en categor as l exicas. Asociamos atributos a las categor as l exicas. Especicamos las categor as mediante expresiones regulares. Para reconocer los lenguajes asociados a las expresiones regulares empleamos aut omatas de estados nitos. Se pueden construir los AFD directamente a partir de la expresi on regular. El analizador l exico utiliza la m aquina discriminadora determinista. El tratamiento de errores en el nivel l exico es muy simple. Podemos implementar la MDD de dos formas: con tablas, mediante control de ujo. Hay generadores autom aticos de analizadores l exicos, por ejemplo metacomp. Se pueden emplear las ideas de los analizadores l exicos para facilitar el tratamiento de cheros de texto.

c Universitat Jaume I 2010-2011

Você também pode gostar