Está en la página 1de 37

4o Ingenier Informtica a a

II26 Procesadores de lenguaje


Analizador lxico e Esquema del tema
1. Introduccin o 2. Repaso de conceptos de lenguajes formales 3. Categor lxicas as e 4. Especicacin de las categor lxicas o as e 5. Autmatas de estados nitos o 6. Implementacin del analizador lxico o e 7. Algunas aplicaciones de los analizadores lxicos e 8. Resumen del tema

1.

Introduccin o

Vimos que la primera fase del anlisis es el anlisis lxico. El principal objetivo del analizador a a e lxico es leer el ujo de caracteres de entrada y transformarlo en una secuencia de componentes e lxicos que utilizar el analizador sintctico. e a a Al tiempo que realiza esta funcin, el analizador lxico se ocupa de ciertas labores de limpieza. o e Entre ellas est eliminar los blancos o los comentarios. Tambin se ocupa de los problemas que a e pueden surgir por los distintos juegos de caracteres o si el lenguaje no distingue maysculas y u minsculas. u Para reducir la complejidad, los posibles s mbolos se agrupan en lo que llamaremos categor as lxicas. Tendremos que especicar qu elementos componen estas categor para lo que empleae e as, remos expresiones regulares. Tambin ser necesario determinar si una cadena pertenece o no a e a una categor lo que se puede hacer ecientemente mediante autmatas de estados nitos. a, o

2.
2.1.

Repaso de conceptos de lenguajes formales


Por qu utilizamos lenguajes formales e

Como acabamos de comentar, para transformar la secuencia de caracteres de entrada en una secuencia de componentes lxicos utilizamos autmatas de estados nitos. Sin embargo, estos e o autmatas los especicaremos utilizando expresiones regulares. Tanto unos como otras son ejemplos o de utilizacin de la teor de lenguajes formales. Es natural preguntarse si es necesario dar este o a rodeo. Existen varias razones que aconsejan hacerlo. La primera razn para emplear herramientas formales es que nos permiten expresarnos con o precisin y, generalmente, de forma breve. Por ejemplo, para describir la categor de los enteros, o a 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 queda otro problema ms, valen las a a cadenas vac as? Normalmente no, as que llegamos a un nmero entero consiste en una secuencia u de uno o ms d a 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 correccin o de nuestros diseos y permiten conocer los l n mites de lo que podemos hacer. Por ejemplo, el lema de bombeo nos permite saber que no podremos utilizar expresiones regulares para modelar componentes lxicos que tengan el mismo nmero de parntesis abiertos que cerrados, por lo que e u e averiguar si algo est bien parentizado ser tarea del analizador sintctico. a a a Como ultima ventaja del empleo de lenguajes formales, comentaremos la existencia de herra mientas para automatizar la implementacin. Por ejemplo, el paso de las expresiones regulares o a un programa que las reconozca se puede hacer mediante un generador de analizadores lxicos e como flex.

2.2.

Alfabetos y lenguajes

Al trabajar con lenguajes formales, utilizaremos una serie de conceptos bsicos. En primer a lugar, un alfabeto es un conjunto nito de s mbolos. No nos interesa la naturaleza de los s mbolos. Dependiendo de la aplicacin que tengamos en mente, stos pueden ser: caracteres, como o e al especicar el analizador lxico; letras o palabras, si queremos trabajar con lenguaje natural; e categor lxicas, al especicar el analizador sintctico; direcciones en el plano, al hacer OCR, as e a etc. Justamente esta abstraccin es lo que hace que los lenguajes formales se puedan aplicar amo pliamente. 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 lxico, podemos e 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 sintctico, una cadena es el programa una vez a pasado por el analizador lxico; en OCR, una cadena es la descripcin de una letra manuscrita, e o etc. La cadena de longitud cero se denomina cadena vac y se denota con . Para referirnos al a 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 que: a

=
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 denido, puede ser a nito o innito. Es ms, la denicin no impone la necesidad de que el lenguaje tenga algn sentido a o u 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 sern de dos tipos: por a un lado expresiones regulares para los lenguajes que emplearemos en el anlisis lxico y por otro a e gramticas incontextuales en el anlisis sintctico. a a a Una vez especicado el lenguaje ser necesario encontrar la manera de reconocerlos, esto es, a resolver la pregunta si una cadena dada pertenece al lenguaje. Veremos que los mtodos de anlisis e a que emplearemos sern capaces de responder a la pregunta de forma eciente: con un coste lineal a con la talla de la cadena.

3.
3.1.

Categor lxicas as e
Conceptos bsicos a

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

Analizador lxico e

Los componentes lxicos (en ingls, tokens) son los elementos de las categor lxicas. Por e e as e ejemplo, en C, i es un componente lxico de la categor identicador, 232 es un componente e a lxico de la categor entero, etc. El analizador lxico ir leyendo de la entrada y dividindola en e a e a e componentes lxicos. e En general, no basta con saber la categor a la que pertenece un componente, en muchos a casos es necesaria cierta informacin adicional. Por ejemplo, ser necesario conocer el valor de un o a entero o el nombre del identicador. Utilizamos los atributos de los componentes para guardar esta informacin. o Un ultimo concepto que nos ser util es el de lexema: la secuencia concreta de caracteres que a corresponde a un componente lxico. e Por ejemplo, en la sentencia altura=2; hay cuatro componentes lxicos, cada uno de ellos de e una categor lxica distinta: a e Categor lxica a e identicador asignacin o entero terminador Lexema altura = 2 ; Atributos valor: 2

3.2.

Categor lxicas ms usuales as e a

Algunas familias de categor lxicas t as e picas de los lenguajes de programacin son: o 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 lxica. a e Habitualmente, las palabras clave son reservadas. Si no lo son, el analizador lxico necesitar e a informacin del sintctico para resolver la ambigedad. o a u Identicadores Nombres de variables, nombres de funcin, nombres de tipos denidos por el o usuario, etc. Ejemplos de identicadores en C son i, x10, valor_leido. . . Operadores S mbolos que especican operaciones aritmticas, lgicas, de cadena, etc. Ejemplos e o de operadores en C son +, *, /, %, ==, !=, &&. . . Constantes numricas Literales1 que especican valores numricos enteros (en base decimal, e e octal, hexadecimal. . . ), en coma otante, etc. Ejemplos de constantes numricas en C son e 928, 0xF6A5, 83.3E+2. . . Constantes de carcter o de cadena Literales que especican caracteres o cadenas de caraca teres. Un ejemplo de literal de cadena en C es "una cadena"; ejemplos de literal de carcter a 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 lxica separada. a e Hay tres categor lxicas que son especiales: as e Blancos En los denominados lenguajes de formato libre (C, Pascal, Lisp, etc.) los espacios en blanco, tabuladores y saltos de l nea slo sirven para separar componentes lxicos. En ese o e caso, el analizador lxico se limita a suprimirlos. En otros lenguajes, como Python, no se e pueden eliminar totalmente. Comentarios Informacin destinada al lector del programa. El analizador lxico los elimina. o e Fin de entrada Se trata de una categor cticia emitida por el analizador lxico para indicar a e que no queda ningn componente pendiente en la entrada. u
1 Los

literales son secuencias de caracteres que representan valores constantes.

c Universitat Jaume I 2010-2011

II26 Procesadores de lenguaje

4.

Especicacin de las categor lxicas o as e

El conjunto de lexemas que forman los componentes lxicos que podemos clasicar en una e determinada categor lxica se expresa mediante un patrn. Algunos ejemplos son: a e o Categor lxica a e entero identicador operador asignacin o while Lexemas 12 5 0 192831 x x0 area + := while Patrn o Secuencia de uno o ms d a gitos. Letra seguida opcionalmente de letras y/o d gitos. Caracteres +, -, * o /. Carcter : seguido de =. a La palabra while.

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

4.1.

Expresiones regulares

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

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

4.2.

Expresiones regulares bsicas a

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

que no tiene nada que ver con el lenguaje vac como ya sabrs. o, a

Analizador lxico e

A partir de estas deniciones, vamos a ver qu signica la expresin regular ((a|b) )(ab). e o En primer lugar, vemos que es la concatenacin de otras dos expresiones: (a|b) y ab. La primera o designa el lenguaje que es la clausura de la disyuncin de los lenguajes formados por una a y una b, o respectivamente. Podemos expresarlo ms claramente como las cadenas formadas por cualquier a nmero de aes y bes. La segunda expresin corresponde al lenguaje que unicamente contiene la u o cadena ab. Al concatenar ambas, obtenemos cadenas formadas por un nmero arbitrario de aes y u 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 expresin regular: o ((((((((((0|1)|2)|3)|4)|5)|6)|7)|8)|9)((((((((((0|1)|2)|3)|4)|5)|6)|7)|8)|9)) ) El exceso de parntesis hace que esta expresin sea bastante dif de leer. Podemos simplicar e o cil la escritura de expresiones regulares si establecemos prioridades y asociatividades. Vamos a hacer que la disyuncin tenga la m o nima prioridad, en segundo lugar estar la concatenacin y nalmente a o la clausura tendr la mxima prioridad. Adems, disyuncin y concatenacin sern asociativas a a a o o a por la izquierda. Cuando queramos que el orden sea distinto al marcado por las prioridades, utilizaremos parntesis. Con este convenio, podemos representar los enteros as e : (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 ms legibles al introducir las prioridades, sigue a habiendo construcciones habituales que resultan incmodas de escribir, por ejemplo la expresin o o que hemos utilizado para los enteros. Veremos en este apartado algunas extensiones que, sin aumentar el poder descriptivo de las expresiones regulares, permiten escribirlas ms cmodamente. a o Un aviso: as como las expresiones regulares bsicas pueden considerarse una notacin estndar, a o a las extensiones que vamos a presentar ahora no lo son. Esto quiere decir que, segn el programa u que se emplee, algunas se considerarn vlidas y otras no; tambin puede suceder que haya otras a a e extensiones disponibles. Tambin hay que advertir que, mientras no digamos otra cosa, trataremos e 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. Tambin utilizaremos eof para referirnos al n de e entrada (seguiremos la tradicin de llamarlo n de chero end of le aunque en ocasiones no o se corresponda con el n de ningn chero). u Clases de caracteres La primera extensin que vamos a ver nos permite escribir clases de cao racteres de manera cmoda. En su forma ms simple, si queremos representar la eleccin de uno o a o 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 ultimo para indicar todo el rango. De esta manera, [az] es el conjunto de las letras minsculas. Tambin u e 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 en el rango, debemos ponerlo bien al principio bien e al nal. As los operadores aritmticos son [-+*/]. El ejemplo anterior tambin nos muestra que, , e e 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 posicin, como en []16]. o
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 estn en un conjunto. Para ello, se incluye en la primera posicin un circunejo, as [09] es el a o conjunto de los caracteres que no son d gitos. En cualquier otra posicin, el circunejo se representa o a s mismo. Finalmente, el punto (.) representa cualquier carcter, incluyendo el n de l a nea. Otra advertencia ms: segn del sistema, el punto puede incluir o no el n de l a u nea. En particular, la librer re a 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 bsico de operadores regulares con dos nuevos a operadores: la clausura positiva y la opcionalidad. El primero se representa mediante el signo ms e indica una o ms repeticiones de la expresin a a o regular a la que afecta. Le otorgamos la misma prioridad que a la clausura normal. De manera simblica, decimos que r+ equivale a rr , donde r es una expresin regular arbitraria. o o El segundo operador, representado mediante un interrogante, representa la aparicin opcional o de la expresin a la que afecta. De esta manera, r? es equivalente a r|. Su prioridad es tambin o e la de la clausura. Carcter de escape Hemos visto que hay una serie de caracteres que tienen un signicado espea cial. A estos caracteres se les llama metacaracteres. Si queremos referirnos a ellos podemos utilizar el carcter de escape, en nuestro caso la barra \. As la expresin 4 representa una secuencia a , o de cero o ms cuatros mientras que 4\* representa un cuatro seguido por un asterisco. Para rea presentar 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 excepcin son los o caracteres n de l nea, tabulador y la propia barra: [\n\t\\]. Nombres La ultima extensin que permitiremos es la posibilidad de introducir nombres para o las expresiones. Estos nombres luego podrn utilizarse en lugar de cualquier expresin. La unia o ca restriccin que impondremos es que no deben provocar ninguna recursividad, ni directa ni o indirectamente.

4.4.

Ejemplos

Vamos a ver algunos ejemplos de expresiones regulares. En primer lugar, revisitaremos los nmeros enteros. Si utilizamos clases de caracteres, podemos escribirlos como [09][09] . Dnu a donos 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. Cmo podemos escribir la expresin regular en este caso? Un primer intento ser <<. >>. Esta o o a expresin tiene un problema ya que aceptar << a >> a >> como comentario. Podemos resolver el o a problema si observamos que entre la apertura y el cierre del comentario puede aparecer cualquier carcter que no sea un mayor o, si aparece un mayor, debe aparecer seguido de algo que no lo sea. a 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 aadidos el comienzo y nal, obtenemos n la expresin <<([>]|>[>]) >>. Como renamiento, podemos utilizar el operador de opcionalidad o para obtener <<(>?[>]) >>.
Ejercicio 2

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

Analizador lxico e

Ejercicio 3

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

Escribe una expresin regular para el conjunto de las palabras reservadas integer, real y char o escritas en minsculas y otra que permita escribirlas con cualquier combinacin de maysculas y u o u minsculas. u
Ejercicio 5

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

Ejercicio 6

Escribe una expresin regular para las cadenas de dos o ms letras minsculas que empiezan o a u por a o por b tales que la ultima 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 expresin empezar por /\* y acabar por \*/, as que tendr la forma /\*\*/, o a a a donde es una expresin regular que representa las cadenas que no contienen la secuencia asteriscoo barra. Para construir , podemos jarnos tanto en los asteriscos como en las barras. Empecemos jndonos en los asteriscos. La expresin ser una clausura en cuyo interior podremos encontrar a o a tanto s mbolos que no sean asteriscos ([*]) como secuencias de uno o ms asteriscos (\*+ ) seguidas a de algo que no sea ni una barra ni un asterisco ([*/]). Con esto tenemos ([*]|\*+ [*/]) . Nos queda el detalle de que pueden aparecer cero o ms asteriscos al nal de la cadena. Si los aadimos, a n tenemos =([*]|\*+ [*/]) \* Y la expresin resultante es3 o /\*([*]|\*+ [*/]) \*+ / La otra opcin es mirar las barras. En este caso, podemos ver las cadenas generadas por o como una secuencia de segmentos que terminan en barra (y slo tienen una barra) seguidos de o 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 y puede haber cero o ms barras. Llamemos a una os a expresin regular que genere un <no barras> seguido de una barra. Se cumplir que = [/] . o a
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 ms s a mbolos de modo que todos menos los dos ultimos son distintos de barra, el penltimo u no es ni una barra ni un asterisco y el ultimo es una barra. Es decir =/|[/] [*/]/ o, jugando con la opcionalidad, ([/] [*/])?/. Si expandimos en , tenemos que = (([/] [*/])?/) [/] Y la expresin resultante es o /\*(([/] [*/])?/) [/] \*/
Ejercicio* 8

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

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

Disea expresiones regulares para los siguientes lenguajes regulares: n a) Secuencias de caracteres encerradas entre llaves que no incluyen ni el carcter | ni la llave a cerrada. b) Secuencias de caracteres encerradas entre llaves en las que el carcter | slo puede aparecer si a o esta escapado por una barra invertida; la llave cerrada no puede aparecer y la barra invertida slo puede aparecer para escapar una barra vertical u otra barra invertida. o c) Nmeros enteros entre 0 y 255 sin ceros iniciales. u

Ejercicio 11

Qu lenguajes representan las siguientes expresiones regulares? e 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 tambin son cadenas de un lenguaje. Dado que tienen que estar bien e parentizadas, este lenguaje no es regular. Sin embargo partes de l s lo son, por ejemplo, las clases e de caracteres. Disea una expresin regular que exprese el conjunto de clases de caracteres que podemos n o utilizar en una expresin regular. o

5.

Autmatas de estados nitos o

Las expresiones regulares permiten describir con cierta comodidad los lenguajes regulares. Sin embargo, no es fcil decidir a partir de una expresin regular si una cadena pertenece al a o

Analizador lxico e

correspondiente lenguaje. Para abordar este problema utilizamos los autmatas de estados nitos. o Estos son mquinas formales que consisten en un conjunto de estados y una serie de transiciones a entre ellos. Para analizar una frase, el autmata se sita en un estado especial, el estado inicial. o u Despus va cambiando su estado a medida que consume s e 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 autmatas segn cmo sean sus transiciones. Si desde cualquier o u o estado hay como mucho una transicin por s o mbolo, el autmata es determinista. En caso de que o haya algn estado tal que con un s u mbolo pueda transitar a ms de un estado, el autmata es no a o determinista. Nosotros trabajaremos unicamente con autmatas deterministas. o

5.1.

Autmatas nitos deterministas o


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 o que q = q (condicin de determinismo). q0 Q es el estado inicial. F Q es el conjunto de estados nales.

Un autmata nito determinista es una qu o 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 vac si n = 1. a a 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 l. e Los autmatas se pueden representar grcamente. Para ello se emplea un grafo en el que cada o a estado est representado por un nodo y cada arco del autmata se representa mediante un arco a o 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 grcamente as a : 0 1 A 1 B 0 0 C 1 (1)

Es interesante plantearse qu lenguaje reconoce este autmata. Podemos hacer un listado de e o 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 como cero y las restantes como nmeros escritos en binario, a u podemos ver que todas las cadenas representan mltiplos de tres. Para convencernos de que no es u casualidad, podemos reescribir los estados como 0, 1 y 2: 0 1 0 1 1 0 0 2 1

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

Escribe el AFD que acepta los mltiplos de tres escritos en binario pero no la cadena vac ni u a nmeros de la forma 00+ . u
Ejercicio* 14

Demuestra que para cualquier n, el lenguaje de los mltiplos de n en base 2 es regular. u Idem para base 10. Cuando representemos autmatas, habr muchas veces en las que encontremos estructuras o a 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 autmata para los comentarios en C. En primer lugar o 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 pasa si e tras ver un asterisco en C vemos un carcter que no es una barra (que terminar el comentario). a a Hay que distinguir dos casos: si el nuevo carcter es tambin un asterisco, debemos permanecer a e en D; si el nuevo carcter no es un asterisco ni una barra, debemos retroceder al estado C. a
4 Date cuenta de que estamos utilizando un mtodo para simplicar el dibujo, pero no podemos escribir en los e arcos expresiones regulares cualesquiera.

Analizador lxico e

11

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

Ejercicio 15

Disea un autmata para los comentarios en C++. n o 5.1.1. Memoria y AFDs

Una manera de interpretar el funcionamiento de los AFDs es imaginarse que los estados son la memoria del autmata. Fijmonos en el autmata de los comentarios en C. Podemos interpretar o e o los estados de la siguiente manera: Estado A: estado inicial. Estado B: hemos le la primera barra. do Estado C: estamos en mitad del comentario. Estado D: hemos le el asterisco del n del comentario. do Estado E: hemos llegado al nal del comentario. Esta misma idea est en el autmata para los mltiplos de 3: cada estado recuerda el resto a o u de la parte del nmero que se lleva le al dividirla por tres. u da Dado que hay un nmero nito de estados, la memoria esta limitada a priori. Esto quiere decir u que si para reconocer un determinado lenguaje la cantidad de memoria no se puede acotar, no ser posible hacerlo con un AFD. Esta es la razn, por ejemplo, de que no se puedan reconocer a o pal ndromos o cadenas bien parentizadas con AFDs. Supongamos que queremos construir u autmata para las secuencias de dos o ms d o a gitos del conjunto {1, 3, 5} tales que el ultimo es mayor que todos los anteriores. Para esto, necesitar amos un estado inicial, por ejemplo I, y luego recordar los nmeros le u dos mediante dos grupos de estados: Los estados m1 , m3 y m5 reejarn que el mximo le ha sido el 1, 3 o 5, respectivamente, a a do y que la cadena no puede terminar ah porque est repetido o ha sido el unico le a do. Los estados u1 , u3 y u5 reejarn el nmero mximo le y que adems es el ultimo y sin a u a do a repetir. Lgicamente, sern nales los estados u1 , u3 y u5 . Si reexionamos un poco, podemos ver que los o a estados m5 y u1 sobran: m5 porque desde l no ser posible alcanzar ningn estado nal ya que e a u necesitaremos un nmero mayor que cinco, lo que no es posible; u1 sobra porque no habr manera u a de llegar a l ya que para que uno sea el mximo la cadena slo puede tener unos y, entonces, no e a o puede tener dos o ms d a gitos si repetir el uno. Las transiciones del estado inicial irn con un uno al m1 y con un tres al m3 , con un cinco no a habr transicin porque corresponder al estado m5 que hemos eliminado. Desde el estado m1 a o a habr un bucle con un uno, y transiciones a u3 y u5 con el tres y el cinco, respectivamente. Desde a 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 un bucle con un uno o un tres y una transicin a u5 con un cinco. El autmata a o o 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 expresin regular para el lenguaje anterior. o


Ejercicio 17

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

Para aadir las echas que faltan, tendremos en cuenta cmo se interpretan los estados: n o 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 l desde E si no vemos ni un guin e o ni una barra y desde F si no vemos ni un guin, ni una barra, ni un mayor. Adems, o a 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 l e desde el estado F si vemos una barra. Adems, permaneceremos en un bucle con la barra. a Con esto, tenemos: < A B C | D [-|] [-|>] Para obtener la expresin regular, necesitamos hacer algo ms de trabajo. Siguiendo el esquema o a de los comentarios en C, la expresin regular tendr la forma <-+ \|\|-+ >. Ahora representa o a cadenas que no tienen en su interior ninguna subcadena del lenguaje \|-+ >. Fijmonos en los e s mbolos mayor que. Empezamos por escribir = [>] donde son cadenas que terminan en >, [|] | E | | F > G

Analizador lxico e

13

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

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

5.2.

Dicultad de diseo n

Viendo el ejemplo anterior y los de los comentarios en C es posible que te plantees la cuestin de o si es ms fcil escribir expresiones regulares o disear AFDs. En realidad, ambos tienen sus puntos a a n fuertes y dbiles. El caso de los comentarios ejemplica lo dif que es expresar la negacin con e cil o expresiones regulares y lo fcil que es mediante AFDs. El punto fuerte de las expresiones regulares a es su facilidad para modelar no determinismo, especialmente el vinculado a modelar la disyuncin. o Por ejemplo el lenguaje de las cadenas de dos o ms vocales en las que la ultima no est repetida a a se puede modelar con la expresin o [aeio]+ u|[aeiu]+ o|[aeuo]+ i|[aiou]+ e|[eiou]+ a sin embargo, el AFD correspondiente tiene ms de cincuenta estados (bsicamente, tiene que llevar a a 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 programacin, como o veremos en la pgina 26). a El dominar las expresiones regulares tambin es importante porque se pueden utilizar en gran e 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 ms fcilmente las expresiones de los comentarios que tanto nos han costado y otras hacen a a que los lenguajes generados sean ms potentes que los regulares (y que los incontextuales). a
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 cmo podemos movernos: o 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 fcilmente, el coste de reconocer la cadena x es O(|x|), que incluso a es independiente del tamao del autmata! n o
Ejercicio* 19

Obviamente, la independencia del coste del tamao del autmata depende de su implementan o cin. Piensa en posibles implementaciones del autmata y cmo inuyen en el coste. o o o

5.3.

Construccin de un AFD a partir de una expresin regular o o

Podemos construir un AFD a partir de una expresin regular. La idea bsica es llevar en cada o a estado del autmata la cuenta de en qu parte de la expresin estamos. Para esto, emplearemos o e o lo que llamaremos tems, que sern expresiones regulares con un punto centrado ( ). El punto a indicar qu parte de la expresin corresponde con la entrada le hasta alcanzar el estado en a e o da el que est el a tem. Por ejemplo, dada la expresin regular (ab) cd(ef) , un posible o 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 carcter, de una clase de caracteres o al nal de la expresin a o diremos que es un tem bsico. Los estados de nuestro autmata sern conjuntos de a o a tems bsicos a sobre la expresin regular. Utilizamos conjuntos porque en un momento dado podemos estar en o ms de un punto en la expresin regular. Por ejemplo, en la expresin regular (a) b tras leer una a o o 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 expresin o regular b2. Al comienzo, no hemos analizado nada y el tem que muestra esto es b2. El primer carcter es la b, que es trivialmente aceptado por b, as que avanzamos el punto para llegar a b 2. a Ahora leemos el 2 y avanzamos a b2 . Dado que hemos terminado la entrada y hemos llegado al nal de la expresin, podemos concluir que aceptamos la cadena. Podemos resumir el proceso en o 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 carcter, el punto est delante a a de l, podemos moverlo detrs. Por otro lado, hemos visto que aceptaremos la cadena de entrada e a

Analizador lxico e

15

si tenemos el punto al nal de la expresin y se ha terminado la entrada. Dicho de otro modo, los o estados que tengan tems con el punto en la ultima posicin sern nales. Construir un autmata o a o a partir de aqu es trivial: Los estados sern: { b2}, {b2} y {b2 }. a Como estado inicial usamos { b2}. Con la b pasamos de { b2} a {b2}. Con el 2 pasamos de {b 2} a {b2 }. El unico estado nal es {b2 }. As obtenemos el autmata siguiente5 : o b b2 b 2 2 b2

Lgicamente, las cosas se ponen ms interesantes cuando la expresin regular no es unicamente o a o una cadena. Vamos a ver cmo podemos trabajar con clases de caracteres. Como ejemplo, analizao remos la cadena b2 con la expresin regular [az][az09]. Al comienzo, no hemos analizado nada, o as que el tem que muestra esto es [az][az09]. El primer carcter es la b, que es aceptado por a [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 expresin y se ha o 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 carcter en la entrada a es uno de los de la clase. Podemos construir ahora un AFD de la manera siguiente: Los estados sern: { [az][az09]}, {[az] [az09]} y {[az][az09] }. a 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 unico estado nal es {[az][az09] }. As obtenemos el autmata , o [az] [az][az09] [az] [az09] [az09] [az][az09]

Aunque es tentador suponer que si el punto est delante de una clase de caracteres tendremos a un arco que sale con esa clase, no te dejes engaar, es slo una casualidad. En general no es as n o , mira, por ejemplo, el ejercicio 21. Vamos ahora a aadir clausuras. Supongamos que tenemos la expresin regular [az]([az0 n o 9]) . Hemos puesto parntesis para tener ms claro dnde est el punto; tambin tendremos que e a o a e hacerlo con las disyunciones. Hagamos como antes, vamos a analizar b2. Empezamos con el tem que tiene el punto delante de la expresin regular. Con esto tenemos [az]([az09]) . Al aceptar o b con [az] nos queda [az] ([az09]) Dado que el punto no est delante de ningn carcter a u a o clase de caracteres, tendremos que encontrar qu e tems bsicos constituyen nuestro siguiente a
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 delante de la clausura, podemos bien leer una letra o d a 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 slo podemos mover el punto del primer o tem con lo que pasamos a [az]([az09] ) . Nuevamente tenemos un tem no bsico, as que a volvemos a buscar los correspondientes tems bsicos. En este caso, podemos volver al comienzo a 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 ms copias de ese interior. As mismo, cuando el punto est delante del parntesis a a e de cierre de la clausura puede salir de ella o volver al principio. Para ver cmo se permiten las o 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 autmata correspondiente: o [az09] [az] [az]([az09])

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

Por ultimo, vamos a ver cmo trabajar con las disyunciones. Vamos a intentar construir el o autmata para t(a|alo|re)n. El estado inicial no tiene problemas: { t(a|re|alo)n}. Con una t pao saremos al tem no bsico t(a|alo|re)n. A partir de l, se producen tres a e tems bsicos, que consa tituyen el siguiente estado de nuestro autmata: {t( a|alo|re)n, t(a| alo|re)n, t(a|alo| re)n}. o Desde este estado, con una a obtenemos dos tems: t(a |alo|re)n y t(a|a lo|re)n. El segundo es bsico, as que slo tendremos que ver lo que pasa con el primer a o tem. Podemos ver que basta con pasar el punto detrs del parntesis para obtener t(a|alo|re) n. De manera similar, si tuviramos a e e

Analizador lxico e

17

Cuadro 1: Transformaciones de los tems no bsicos. a

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 autmata nos queda: o 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. Formalizacin o 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 bsicos que constituirn el estado del autmata. a a o Llamaremos clausura a este proceso y el algoritmo que seguiremos ser el siguiente: a Algoritmo clausura (S) C := S; v := ; // tems ya tratados mientras haya tems no bsicos en C hacer a sea i un tem no bsico de C; a C := C {i}; v := v {i}; C := C transformaciones(i) v; n mientras devolver C; Fin clausura La idea bsica es sustituir cada a tem no bsico por una serie de transformaciones que se coa rresponden con los movimientos que hac amos del punto durante los ejemplos. En el cuadro 1 puedes encontrar las transformaciones para las expresiones regulares bsicas. Es interesante darse a cuenta de la diferencia entre los parntesis abiertos que encierran una subexpresin y aquellos que e o corresponden a una disyuncin o una clausura. o Una vez sabemos cmo calcular la clausura de un estado, podemos construir los movimientos o del autmata. Si estamos en un estado que contiene una serie de o tems bsicos, el estado al que a
c Universitat Jaume I 2010-2011

18

II26 Procesadores de lenguaje

iremos al consumir un carcter de la entrada ser la clausura del conjunto resultante de avanzar a a el punto una posicin en aquellos o tems que lo tengan bien delante del carcter, bien delante de a 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 autmata. El estado inicial ser o a la clausura del tem formado anteponiendo el punto a la expresin regular. Despus iremos viendo o e 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 construccin() o 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 expresin regular [az] [az09]. Ten cuidado al elegir o las clases de caracteres de los arcos.

Analizador lxico e

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 mtodo de los e tems para escribir AFDs para las siguientes categor lxicas: as e a) Las palabras if, in e input. b) Nmeros expresados en hexadecimal escritos como dos s u mbolos de porcentaje seguidos de d gitos hexadecimales de modo que las letras estn todas, bien en maysculas, bien en minsculas. e u u c) Nmeros reales compuestos de una parte entera seguida de un punto y una parte decimal. u Tanto la parte entera como la decimal son obligatorias.

Ejercicio* 24

Son m nimos los autmatas obtenidos con el mtodo de los o e tems?


Ejercicio* 25

Construye los AFDs correspondientes a las expresiones del ejercicio 8.

5.4.

Compromisos entre espacio y tiempo

La construccin anterior nos permite pasar de una expresin regular a un AFD y emplear este o o para analizar cadenas. Sin embargo, esta puede no ser la solucin ptima para todos los casos o o en que se empleen expresiones regulares. Hay por lo menos dos maneras ms de trabajar con las a expresiones regulares: emplear autmatas nitos no deterministas (AFN) y utilizar backtracking. o La construccin de un autmata no determinista es ms sencilla que la de uno determinista y o o a proporciona autmatas de menor nmero de estados que los deterministas (el nmero de estados o u u de un AFD puede ser exponencial con la talla de la expresin regular, el del AFN es lineal). A o cambio, el anlisis es ms costoso (con el AFN el coste temporal es O(|x| |r|), por O(|x|) para el a a AFD, donde |x| es la talla de la cadena y |r| la de la expresin regular). Esto hace que sea una o opcin atractiva para situaciones en las que la expresin se vaya a utilizar pocas veces. o o Cuando se utiliza backtracking, lo que se hace es ir probando a ver qu partes de la expresin e o se corresponden con la entrada y se vuelve atrs si es necesario. Por ejemplo, supongamos que a tenemos la expresin regular a(b|bc)c y la entrada abcc. Para analizarla, primero vemos que la o a est presente en la entrada. Llegamos a la disyuncin e intentamos utilizar la primera opcin. a o o Ningn problema, avanzamos en la entrada a la primera c y en la expresin a la c del nal. u o Volvemos a avanzar y vemos que la expresin se ha terminado pero la entrada no. Lo que hacemos o es volver atrs (es decir, retrocedemos en la entrada hasta el momento de leer la b) y escoger la a segunda opcin de la disyuncin. El anlisis prosigue ahora normalmente y la cadena es aceptada. o o a Podemos ver que el uso de backtracking minimiza la utilizacin del espacio (slo necesitamos o o almacenar la expresin), pero puede aumentar notablemente el tiempo de anlisis (hasta hacerlo o a exponencial con el tamao de la entrada). n Cuando se trabaja con compiladores e intrpretes, las expresiones se utilizan muchas veces, por e lo que merece la pena utilizar AFDs e incluso minimizarlos (hay algoritmos de minimizacin con o
c Universitat Jaume I 2010-2011

20

II26 Procesadores de lenguaje

coste O(|Q| log(|Q|)), donde |Q| es el nmero de estados del autmata). En otras aplicaciones, por u o ejemplo facilidades de bsqueda de un procesador de textos, la expresin se emplear pocas veces, u o a as que es habitual que se emplee backtracking o autmatas no deterministas si se quieren tener o cotas temporales razonables. Como resumen de los costes, podemos emplear el siguiente cuadro. Recuerda que |r| es la talla de la expresin regular y |x| la de la cadena que queremos analizar. o Mtodo e AFD AFN Backtracking Memoria exponencial O(|r|) O(|r|) Tiempo O(|x|) O(|x| |r|) exponencial

De todas formas, en la prctica los casos en los que los costes son exponenciales son raros, por a lo que el nmero de estados del AFD o el tiempo empleado por backtracking suele ser aceptable. u
Ejercicio* 26

Puedes dar ejemplos de expresiones regulares que requieran un coste exponencial para su transformacin en AFD o su anlisis por backtracking? o a

6.

Implementacin del analizador lxico o e

Hasta ahora hemos visto cmo especicar los lenguajes asociados a las diferentes categor o as lxicas. Sin embargo, el analizador lxico no se utiliza para comprobar si una cadena pertenece e e o no a un lenguaje. Lo que debe hacer es dividir la entrada en una serie de componente lxicos, e realizando en cada uno de ellos determinadas acciones. Algunas de estas acciones son: comprobar alguna restriccin adicional (por ejemplo que el valor de un literal entero est dentro de un rango), o e preparar los atributos del componente y emitir u omitir dicho componente. As pues, la especicacin del analizador lxico deber incluir por cada categor lxica del o e a a e lenguaje el conjunto de atributos y acciones asociadas. Un ejemplo ser a: categor a entero expresin regular o [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 asignacin o 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 Sin embargo, debemos denir algo ms: as. a cmo esperamos que se unan estas categor para formar la entrada. o as Analicemos el siguiente fragmento de un programa utilizando las categor anteriores: as c3:= 10.2

Analizador lxico e

21

En principio, esperamos encontrar la siguiente secuencia de componentes lxicos: e id


lex: c3

asignacin o

real

valor: 10.2

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

entero

valor: 3

asignacin o

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 ms largo que pertenezca al lenguaje asociado a a alguna categor lxica. De esta manera, la divisin de la entrada coincide con la que esperamos. a e o

6.2.

Mquina discriminadora determinista a

Para reconocer simultneamente las distintas categor lxicas utilizaremos un tipo de auta as e o mata muy similar a los AFDs: la mquina discriminadora determinista (MDD). El funcionamiento a de las MDDs es muy similar al de los AFDs, las diferencias son dos: adems de cadenas completas, a aceptan prejos de la entrada y tienen asociadas acciones a los estados nales. Estudiaremos la construccin a partir del ejemplo de especicacin anterior. Empezamos por o o aadir a cada expresin regular un s n o mbolo especial: Categor a entero real identicador asignacin o rango blanco eof Expresin regular o [09]+ #entero [09]+ \.[09]+ #real [azAZ][azAZ09] #ident :=#asig \.\.#rango [ \t\n]+ #blanco e o #eof f

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

Cmo funciona una MDD? La idea del funcionamiento es muy similar a la de los AFD, salvo o por dos aspectos: La MDD no intenta reconocer la entrada sino segmentarla. La MDD acta repetidamente sobre la entrada, empezando en cada caso en un punto distinto, u 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 tales que: i=1
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 expresin regular completa. o

Su concatenacin sea la cadena original: x1 . . . xn = x. o 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 ningn i una cadena y que sea aceptada por M, que sea prejo de xi . . . xn y que sea u ms larga que xi . a Afortunadamente, encontrar esta segmentacin es muy sencillo. Empezamos por poner M en o su estado inicial. Despus actuamos como si M fuera un AFD, es decir, vamos cambiando el estado e de la mquina segn nos indica la entrada. En algn momento llegaremos a un estado q desde el a u u que no podemos seguir avanzando. Esto puede suceder bien porque no existe ninguna transicin o de q con el s mbolo actual, bien porque se nos haya terminado la entrada. Si q es nal, acabamos de encontrar un componente lxico. Ejecutamos las acciones asociadas a q y volvemos a empezar. e Si q no es nal, tenemos que desandar lo andado, hasta que lleguemos al ultimo estado nal por el que hayamos pasado. Este ser el que indique el componente lxico encontrado. Si no existiera a e tal estado, estar amos ante un error lxico. e La descripcin dada puede parecer un tanto liosa, vamos a ver un ejemplo. Supongamos que o tenemos la entrada siguiente: a:= 9.1 Inicialmente la mquina est en el estado 0. Con los dos primeros espacios, llegamos al estado 9. a a El siguiente caracter (la a) no tiene transicin desde aqu as que miramos el estado actual. Dado o ,

# eo f

eo f

Analizador lxico e

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 accin correspondiente, en este caso omitir. Volvemos al estado 0. o Ahora estamos viendo la a, as que transitamos al estado 8. Nuevamente nos encontramos con un carcter que no tiene transicin. En este caso, la accin es emitir, as que emitimos el identicador, a o o tras haber copiado el lexema. De manera anloga, se emitir un asig, se omitir el espacio y se a a a emitir un real. a Vamos a ver ahora qu pasa con la entrada siguiente: e 9..12 Como antes, empezamos en el estado 0. Con el 9 llegamos al estado 1. Despus, con el . vamos e al estado 2. Ahora resulta que no podemos transitar con el nuevo punto. Lo que tenemos que hacer es retroceder hasta el ultimo estado nal por el que hemos pasado, en este caso el 1. Lgicamente, o 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 hacemos ahora? Volvemos al estado cero. El carcter que vamos a leer ahora es e a nuevamente el primero de los dos puntos. De esta manera transitamos al estado 6 y despus al 7. e Emitimos rango y volvemos a empezar para emitir otro entero. Por ultimo, veamos qu pasa con: e a.3 En primer lugar, emitimos el identicador con lexema a. Despus transitamos con el punto al e estado 6. En este momento no podemos transitar con el tres. Pero adems resulta que no hemos a 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 especicacin: o categor a nmero u expresin regular o -?[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

Cmo se comportar ante las siguientes entradas? o a 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 lxico. Tenemos que tratarlo de alguna manera e adecuada. Desafortunadamente, es dif saber cul ha sido el error. Si seguimos con el ejemplo cil a anterior, podemos preguntarnos la causa del error ante la entrada a.3. Podr suceder que esa tuvisemos escribiendo un real y hubisemos sustituido el primer d e e gito por la a. Por otro lado, podr ser un rango mal escrito por haber borrado el segundo punto. Tambin podr suceder que a e a el punto sobrase y quisiramos escribir el identicador a3. Como puedes ver, an en un caso tan e u sencillo es imposible saber qu ha pasado. e Una posible estrategia ante los errores ser denir una distancia entre programas y buscar el a programa ms prximo a la entrada encontrada. Desgraciadamente, esto resulta bastante costoso a o y las ganancias en la prctica suelen ser m a nimas. La estrategia que seguiremos es la siguiente: Emitir un mensaje de error y detener la generacin de cdigo. o o Devolver al ujo de entrada todos los caracteres le dos desde que se detect el ultimo como ponente lxico. e Eliminar el primer carcter. a Continuar el anlisis. a Siguiendo estos pasos, ante la entrada a.3, primero se emitir un identicador, despus se sealar a e n a el error al leer el 3. Se devolver el tres y el punto a la entrada. Se eliminar el punto y se seguir an a a el anlisis emitiendo un entero. a

Analizador lxico e

25

6.3.1.

Uso de categor lxicas para el tratamiento de errores as e

La estrategia anterior garantiza que el anlisis contina y que nalmente habremos analizado a u 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 anlisis. En algunos casos, a se puede facilitar la emisin de mensajes de error mediante el uso de pseudo-categor lxicas. o as e 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 nmero real con parte entera nula. u No puede aparecer un punto aislado. En este caso queremos que se asuma que en realidad era un rango. Para lograrlo, podemos aadir a la especicacin las siguientes l n o neas: categor a errorreal expresin regular o \.[09]
+

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

atributos valor

errorrango

\.

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

Otros errores que se detectan en el nivel lxico son aquellos que slo pueden encontrarse al e o ejecutar las acciones asociadas a las categor Un ejemplo es el de encontrar nmeros fuera de as. u rango. En principio es posible escribir una expresin regular para, por ejemplo, los enteros de o 32 bits. Sin embargo, esta expresin resulta excesivamente complicada y se puede hacer la como probacin fcilmente mediante una simple comparacin. Esta es la estrategia que hemos seguido en o a o nuestro ejemplo. Aqu s que es de esperar un mensaje de error bastante informativo y una buena recuperacin: basta con emitir la categor con un valor predeterminado (por ejemplo cero). o a

6.4.

Algunas categor especiales as

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

26

II26 Procesadores de lenguaje

Otra categor que es generalmente omitida es el comentario. Como en el caso anterior, el anaa lizador lxico suele analizar los comentarios para comprobar cuntos nes de l e a nea estn incluidos a en ellos y poder informar adecuadamente de la posicin de los errores. o El n de chero Aunque ha aparecido como un s mbolo ms al hablar de las expresiones regulares, a lo cierto es que el n de chero no suele ser un carcter ms. Dependiendo del lenguaje en que a a 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 funcin getc o de C), en otros debe consultarse expl citamente (por ejemplo en Pascal) y en otros consiste en la devolucin de la cadena vac (por ejemplo en Python). Como esto son detalles de implementacin, o a o nosotros mostraremos expl citamente el n de chero como si fuera un carcter eof. a Es ms, en ocasiones ni siquiera tendremos un chero que nalice. Puede suceder, por ejemplo, a 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. Tambin puede suceder que estemos en un sistema interactivo y analicemos e una cadena introducida por el usuario. Para unicar todos los casos, supondremos que el analizador lxico indica que ya no tiene ms e a entrada que analizar mediante la emisin de la categor eof y no utilizaremos eof como parte de o a ninguna expresin regular compuesta. o Las palabras clave De manera impl cita, hemos supuesto que las expresiones regulares que empleamos para especicar las categor lxicas denen lenguajes disjuntos dos a dos. Aunque esta as e es la situacin ideal, en diversas ocasiones puede suponer una fuente de incomodidades. El caso o ms claro es el que sucede con las palabras clave y su relacin con los identicadores. a o Es habitual que los identicadores y las palabras clave tengan la misma forma. Por ejemplo, supongamos que los identicadores son cadenas no vac de letras minsculas y que if es una as u palabra clave. Qu sucede al ver la cadena if? La respuesta depende en primer lugar del lenguaje. e En bastantes lenguajes, las palabras clave son reservadas, por lo que el analizador lxico deber e a clasicar la cadena if en la categor lxica if. Sin embargo, en lenguajes como PL/I, el siguiente a e fragmento if then then then = else ; else else = then; es perfectamente vlido. En este caso, el analizador lxico necesita informacin sintctica para a e o a decidir si then es una palabra reservada o un identicador. Si decidimos que las palabras clave son reservadas, tenemos que resolver dos problemas: Cmo lo reejamos en la especicacin del analizador lxico. o o e Cmo lo reejamos al construir la MDD. o Puede parecer que el primer problema est resuelto; simplemente se escriben las expresiones a regulares adecuadas. Sin embargo, las expresiones resultantes pueden ser bastante complicadas. Intentemos escribir la expresin para un caso muy sencillo: identicadores consistentes en cadenas o de letras minsculas, pero sin incluir la cadena if. La expresin es: u o [ahjz][az] |i([aegz][az] )?|if[az]+
Ejercicio* 28

Supn que en el lenguaje del ejemplo anterior tenemos tres palabras clave reservadas: if, then o y else. Disea una expresin regular para los identicadores que excluya estas tres palabras clave. n o

Este sistema no slo es incmodo para escribir, facilitando la aparicin de errores, tambin o o o e hace que aadir una nueva palabra clave sea una pesadilla. Lo que se hace en la prctica es utilizar n a un sistema de prioridades. En nuestro caso, cuando un lexema pueda clasicarse en dos categor as

Analizador lxico e

27

lxicas distintas, escogeremos la categor que aparece en primer lugar en la especicacin. As e a o para las palabras del ejercicio, nuestra especicacin podr ser: o a categor a if then else identicador expresin regular o if then else [az]+ acciones emitir emitir emitir copiar lexema emitir atributos

lexema

Ahora que sabemos cmo especicar el analizador para estos casos, nos queda ver la manera o de construir la MDD. En principio, podemos pensar en aplicar directamente la construccin de la o MDD sobre esta especicacin. Sin embargo este mtodo resulta excesivamente complicado. Por o e ello veremos otra estrategia que simplica la construccin de la MDD con el precio de algunos o clculos extra. a Para ver lo complicada que puede resultar la construccin directa, volvamos al caso en que slo o o tenemos identicadores formados por letras minsculas y una palabra reservada: if. Empezamos u por escribir la expresin regular con las marcas especiales: o if#if |[az]+ #identificador Con esta expresin construimos la MDD: o

f i A
[a hj

B [aegz]
z]

emitir if

[az] emitir identicador

[az] Vemos que la ambigedad aparece en el estado C, que tiene dos posibles acciones. Hemos u decidido que if tiene preferencia sobre identicador, as que dejamos el autmata de esta manera: o

f i A
[a hj

B [aegz]
z]

emitir if

[az] emitir identicador

[az] Vemos que incluso para algo tan sencillo, el autmata se complica. Evidentemente, a medida o que crece el nmero de palabras reservadas, la complejidad tambin aumenta. u e
c Universitat Jaume I 2010-2011

28

II26 Procesadores de lenguaje

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

De esta manera, cualquier lexema parecido a un identicador terminar en el estado B. Bastar a a entonces comprobar si realmente es un identicador o una palabra clave y emitir la categor a adecuada. De esta manera, tambin resulta sencillo aumentar el nmero de palabras clave sin e u 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 dicotmica sobre una lista ordenada. o Utilizar una tabla de dispersin (hash). Como la lista de palabras claves es conocida a priori, o podemos utilizar una funcin de dispersin perfecta. o o Utilizar un trie. Etc. . .

6.5.

El interfaz del analizador lxico e

Cmo ser la comunicacin del analizador lxico con el sintctico? Como ya comentamos en o a o e a el tema de estructura del compilador, el analizador sintctico ir pidiendo componentes lxicos a a a e medida que los necesite. As pues, lo m nimo que deber ofrecer el analizador lxico es una funcin a e o (o mtodo en caso de que utilicemos un objeto para construirlo) que permita ir recuperando el e componente lxico actual. Puede ser cmodo dividir esta lectura en dos partes: e o Una funcin que indique cul es el ultimo componente le o a do. Esta funcin no avanza la o lectura. Una funcin que avance la lectura hasta encontrar otro componente y lo devuelva. o Dado que la primera es generalmente trivial (bastar con devolver el contenido de alguna variable a local o de un atributo), veremos cmo implementar la segunda. o Adems de estas funciones, existen una serie de funciones auxiliares que debe proporcionar el a analizador: Una funcin (o mtodo) para especicar cul ser el ujo de caracteres de entrada. Geo e a a neralmente, esta funcin permitir especicar el chero desde el que leeremos. En caso de o a que el analizador sea un objeto, la especicacin del ujo de entrada puede ser uno de los o parmetros del constructor. a Una funcin (o mtodo) para averiguar en qu l o e e nea del chero nos encontramos. Esto facilita la emisin de mensajes de error. Para dar mejores mensajes, puede tambin devolver el o e carcter dentro de la l a nea. Relacionada con la anterior, en algunos analizadores podemos pedir que escriba la l nea actual con alguna marca para sealar el error. n Si el analizador lxico necesita informacin del sintctico, puede ser necesario utilizar fune o a ciones para comunicarla. La complejidad o necesidad de estas funciones variar segn las necesidades del lenguaje. As a u , si nuestro lenguaje permite la inclusin de cheros (como las directivas #include de C), ser o a necesario poder cambiar con facilidad el ujo de entrada (o, alternativamente, crear ms de un a analizador lxico). e

Analizador lxico e

29

6.6.

El ujo de entrada

Hasta ahora hemos asumido ms o menos impl a citamente que los caracteres que utilizaba el analizador lxico proven de algn chero. En realidad, esto no es necesariamente as Puede e an u . suceder que provengan de la entrada estndar6 , o de una cadena (por ejemplo, en caso de que a estemos procesando los argumentos del programa). Para evitar entrar en estos detalles, podemos suponer que existe un mdulo u objeto que utiliza o el analizador lxico para obtener los caracteres. Este mdulo deber ofrecer al menos los siguientes e o a servicios: Un mtodo para especicar desde dnde se leen los caracteres (chero, cadena, dispositie o vo. . . ). Un mtodo para leer el siguiente carcter. e a Un mtodo para devolver uno o ms caracteres a la entrada. e a Segn las caracter u sticas del lenguaje para el que estemos escribiendo el compilador, la devolucin de caracteres ser ms o menos complicada. En su forma ms simple, bastar una variable o a a a a para guardar un carcter. En otros casos, ser necesario incluir alguna estructura de datos, que a a ser una pila o una cola, para poder guardar ms de un carcter. A la hora de leer un carcter, a a a a lo primero que habr que hacer es comprobar si esta variable est o no vac y despus devolver a a a e el carcter correspondiente. a Un aspecto importante es la eciencia de la lectura desde disco. No es raro que una implementacin eciente de las siguientes fases del compilador haga que la lectura de disco sea el factor o determinante para el tiempo total de compilacin. Por ello es necesario que se lean los caracteo res utilizando algn almacn intermedio o buer. Si se utiliza un lenguaje de programacin de u e o 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 parmetros. Aprovechando que los a 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 preparando un sistema interactivo o si se pretende poder utilizar el programa a como ltro.

6.7.

Implementacin de la MDD o

Llegamos ahora a la implementacin de la MDD propiamente dicha. Podemos dividir las imo plementaciones en dos grandes bloques: Implementacin mediante tablas. o Implementacin mediante control de ujo. o En el primer caso, se disea una funcin que es independiente de la MDD que se est utilizando. n o e Esta se codica en una tabla que se pasa como parmetro a la funcin. Esta estrategia tiene a o la ventaja de su exibilidad: la funcin sirve para cualquier analizador y las modicaciones slo o o afectan a la tabla. A cambio tiene el problema de la velocidad, que suele ser inferior a la otra alternativa. La implementacin mediante control de ujo genera un cdigo que simula directamente el o o funcionamiento del autmata. Esta aproximacin es menos exible pero suele ser ms eciente. o o a 6.7.1. Implementacin mediante tablas o

Esta es la implementacin ms directa de la MDD. Se utilizan tres tablas: o a 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 denido. e
6 En UNIX esto no supone diferencia ya que la entrada estndar se trata como un chero ms, pero en otros a a 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 accin contiene por cada estado las acciones asociadas. Asumiremos que la accin o o 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 ms grande, movimiento, ser realmente grande. Con a a que tengamos algunos cientos de estados y que consideremos 256 carcteres distintos es fcil subir a a a ms de cien mil entradas. Afortunadamente, es una tabla bastante dispersa ya que desde muchos a estados no podemos transitar con muchos caracteres. Adems existen muchas las repetidas. Por a esto existen diversas tcnicas que reducen el tamao de la tabla a niveles aceptables, aunque no e n 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 segn marquen las tablas. A medida que avanzamos u 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 despus de pasar por el ultimo nal e y ejecutamos las acciones pertinentes. Las variables que utilizaremos son: q: el estado de la MDD. l: el lexema del componente actual. uf : ultimo estado nal por el que hemos pasado. ul : lexema que ten amos al llegar a uf . Date cuenta de que al efectuar las acciones semnticas, debemos utilizar como lexema ul . a 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 funcin que nos o permite devolver al ujo de entrada el o los caracteres de anticipacin; por otro lado, es la sentencia o que termina la ejecucin del algoritmo. Deber estar claro por el contexto cul es cual. o a a 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 cuntos caracteres hay a que devolver, sin pasarle toda la cadena. La manera de tratar los errores depender de la estrategia exacta que hayamos decidido. Puede a bastar con activar un ag de error, mostrar el error por la salida estndar y relanzar el anlisis. a a Otra posibilidad es lanzar una excepcin y conar en que otro nivel la capture. o 6.7.2. Implementacin mediante control de ujo o

En este caso, el propio programa reeja la estructura de la MDD. Lgicamente, aqu slo o o podemos dar un esquema. La estructura global del programa es simplemente un bucle que lee un carcter y ejecuta el cdigo asociado al estado actual. Esta estructura la puedes ver en la gura 4. a o El cdigo asociado a un estado depender de si este es o no nal. En caso de que lo sea, o a tenemos que guardrnoslo en la variable uf y actualizar ul . Despus tenemos que comprobar las a e 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 mquina al estado a inicial, en caso de que haya que emitir, saldremos de la rutina. El cdigo para los estados nales o est en la gura 5. a El cdigo para los estados no nales es ligeramente ms complicado, porque si no hay transio a ciones debemos retroceder hasta el ultimo nal. El cdigo correspondiente est en la gura 6. o a Ten en cuenta que, si se est implementando a mano el analizador, suele ser muy fcil encontrar a a mejoras sencillas que aumentarn la eciencia del cdigo. Por ejemplo, muchas veces hay estados a o que transitan hacia s mismos, lo que se puede modelar con un bucle. En otros casos, no es necesario guardar el ultimo estado nal porque slo hay una posibilidad cuando no podamos transitar. Por o todo esto, es bueno que despus (o antes) de aplicar las reglas mecnicamente, reexiones acerca e a de las particularidades de tu autmata y cmo puedes aprovecharlas. o o

Analizador lxico e

31

Algoritmo siguiente; // Con tablas q := q0 ; // Estado actual l := ; // Lexema uf := indenido; // Ultimo estado nal ul := ; // Lexema le hasta el ultimo nal do mientras 0 = 1 1 hacer a c := siguiente carcter; 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(accin[uf ], ul ); o 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 expresin ul1 l repreo senta la parte de l que queda despus de quitarle del principio la cadena ul. e

Algoritmo siguiente; // Con control de ujo q := q0 ; // Estado actual l := ; // Lexema uf := indenido; // Ultimo estado nal ul := ; // Lexema le hasta el ultimo nal do mientras 0 = 1 1 hacer a c := siguiente carcter; l := l c; opcin q o q0 : // cdigo del estado q0 o q1 : // cdigo del estado q1 o ... qn : // cdigo del estado qn o n opcin o n mientras n siguiente.
Figura 4: Estructura de un analizador lxico implementado mediante control de ujo. e

c Universitat Jaume I 2010-2011

32

II26 Procesadores de lenguaje

// Cdigo del estado qi (nal) o uf := qi ; ul := lc1 ; // Ojo: eliminamos el ultimo carcter a opcin c o 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 opcin o
Figura 5: Cdigo asociado a los estados nales. o

// Cdigo del estado qi (no nal) o opcin c o 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(accin[uf ], ul ); o si t = indenido entonces q := q0 ; l := ; uf := indenido; ul := ; si no devolver t; n si si no // Tratar error n si n opcin o
Figura 6: Cdigo asociado a los estados no nales. o

Analizador lxico e

33

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

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

no es necesario en el estado A guardar el ultimo 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 lxicos e

Adems de para construir compiladores e intrpretes, los analizadores lxicos se pueden emplear a e e para muchos programas convencionales. Los ejemplos ms claros son aquellos programas que a tienen algn tipo de entrada de texto donde hay un formato razonablemente libre en cuanto u espacios y comentarios. En estos casos es bastante engorroso controlar dnde empieza y termina o cada componente y es fcil liarse con los a ndices de los caracteres. Un analizador lxico simplica e notablemente la interfaz y si se dispone de un generador automtico, el problema se resuelve en a pocas l neas de cdigo. o Vamos a ver cmo utilizar metacomp, el metacompilador que utilizaremos en prcticas, para reo a solver 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 nmero de apariciones. Deniremos las palabras como aquellas u secuencias de caracteres que se ajustan a la expresin [azAZ]+ . Adems, pasaremos las palabras o a a minscula. Omitiremos el resto de caracteres de la entrada. Con esto, podemos escribir nuestra u especicacin lxica: o e categor a palabra otro expresin regular o [azAZ] .
+

acciones calcular pal emitir omitir

atributos pal

Para crear un analizador lxico con metacomp, basta con escribir un chero de texto con e dos partes separadas por el carcter %. La primera parte contiene una especicacin lxica. Esta a o e consiste en una serie de l neas con tres partes cada una: Un nombre de categor o la palabra None si la categor se omite. a a Un nombre de funcin de tratamiento o None si no es necesario ningn tratamiento. o u Una expresin regular. o Los analizadores lxicos generados por metacomp dividen la entrada siguiendo la estrategia e avariciosa y, en caso de conicto, preriendo las categor que aparecen en primer lugar. Por as cada lexema encontrado en la entrada se llama a la correspondiente funcin de tratamiento, que o normalmente se limita a calcular algn atributo adicional a los tres que tienen por defecto todos u los componentes: lexema, nlinea y cat, que representan, respectivamente, el lexema, el nmero de u l nea y la etiqueta de categor del componente. a Par nuestro ejemplo, la especicacin lxica tendr las l o e a neas:
1 2 3 4

palabra procesaPalabra None None %

[a-zA-Z]+ .

Despus del carcter %, viene la parte de cdigo de usuario, que puedes ver en la gura 7. Aqu e a o tenemos que especicar las funciones de tratamiento (en nuestro caso procesaPalabra), otras funciones que queramos emplear (en nuestro caso tendremos la funcin que procesa un chero) y o la funcin main a la que se llamar al ejecutar el programa generado por metacomp. o a Veamos las tres funciones una por una. La funcin procesaPalabra se limita a copiar en el o atributo pal de la componente el lexema pasado a minsculas. La funcin procesaFichero es ms u o a interesante. Le pasamos como parmetro un chero (f). Con l, construimos un objeto de tipo a e AnalizadorLexico, que nos proporciona metacomp. Cada vez que llamemos al mtodo avanza del e analizador lxico, nos devolver un componente. Para averiguar a qu categor pertenece, utilizae a e a mos el atributo cat. Si hemos terminado, la categor sera mc_EOF, en caso contrario, entramos en a el bucle. Como slo puede ser una palabra, actualizamos el diccionario con las cuentas. Finalmente, o al salir del bucle, mostramos las cuentas que hemos recogido. Por ultimo la funcin main se ocupa de llamar a procesaFichero bien con la entrada estndar, o a 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 opcin -S indica que slo queremos generar el analizador lxico (por defecto, metacomp genera o o e tambin un analizador sintctico con acciones semnticas, lo veremos ms adelante). La opcin e a a a o -s con un nombre de chero dice dnde se debe guardar el resultado; si no la especicamos, se o muestra por la salida estndar. a

Analizador lxico e

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 cdigo de usuario para la aplicacin de vocabulario. o o

7.2.

Eliminar comentarios

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

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: Aplicacin inicial para eliminar los comentarios de un programa en C o C++. o

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: Aplicacin para eliminar los comentarios de un programa en C o C++. o

Analizador lxico e

37

8.

Resumen del tema


El analizador lxico divide la entrada en componentes lxicos. e e Los componentes se agrupan en categor lxicas. as e Asociamos atributos a las categor lxicas. as e Especicamos las categor mediante expresiones regulares. as Para reconocer los lenguajes asociados a las expresiones regulares empleamos autmatas de o estados nitos. Se pueden construir los AFD directamente a partir de la expresin regular. o El analizador lxico utiliza la mquina discriminadora determinista. e a El tratamiento de errores en el nivel lxico es muy simple. e Podemos implementar la MDD de dos formas: con tablas, mediante control de ujo. Hay generadores automticos de analizadores lxicos, por ejemplo metacomp. a e Se pueden emplear las ideas de los analizadores lxicos para facilitar el tratamiento de cheros e de texto.

c Universitat Jaume I 2010-2011

También podría gustarte