You are on page 1of 5

PC PRCTICO

Compiladores (y II)

Anlisis sintctico de un compilador


Aprendemos el sentido de la herramienta Bison
En esta nueva entrega sobre compiladores para lenguajes de programacin, repasaremos cmo tratar los tokens que devolvamos con la herramienta Flex, usando para ello Bison, lo cual ser una gran ayuda. A continuacin, tras realizar el anlisis sintctico de un fichero fuente, habremos construido nuestro primer compilador.
l igual que ocurra con la anterior entrega del mes de abril (n129, pag 241-243), nos centraremos en los aspectos ms prcticos y olvidaremos un poco la siempre aburrida teora (aunque sentimos deciros que una vez acabada la serie, si queris profundizar ms en este mundillo, deberis trabajar tambin la parte terica). Bison no es otra cosa que una herramienta mejorada de una aplicacin anterior llamada Yacc. De esta forma, el cdigo escrito para Yacc es compatible con Bison, aunque no a la inversa. Para empezar, el programa va a tomar un fichero de texto con el formato mostrado en el Cdigo 1.

Intentaremos verlo ms claro con un ejemplo. Para ello construiremos un analizador para un lenguaje que define expresiones aritmticas. Una posible entrada, al igual que en la entrega anterior, se puede ver en el Cdigo 2.

En primer lugar, debemos escribir nuestro fichero fuente en Flex para realizar el anlisis lxico, es decir, detectar las piezas relevantes del lenguaje y pasarlas al anali-

zador sintctico. Para ello, si observamos con detenimiento el fuente del Cdigo 3, advertiremos varias diferencias con respecto al incluido en el captulo anterior. Aun-

PC PRCTICO

Compiladores (y II)

que el resultado prctico sigue siendo el mismo, el lenguaje C generado por Flex utilizando esta versin va a ser ms eficiente.

I El primer fuente Ahora viene el momento de meterle mano al Bison. La parte que realiza el anlisis semntico de nuestro lenguaje la encontraremos en el Cdigo 4.

No hemos de olvidar dnde nos encontramos en este momento: nuestro fichero fuente ha pasado por el analizador lxico (Flex) y ste lo est troceando en tokens para pasrselos al sintctico, es decir, Bison. As pues, este ltimo est recibiendo, en forma de tokens, cadenas como LEER, ESCRIBIR, IDENT, etc. Por otra lado, es necesario diferenciar los smbolos terminales de los no terminales. Ello es posible gracias a que, por convenio, los primeros aparecen en mayscula (LEER, por ejemplo) o, si estn formados por un slo carcter, encerrados entre comillas simples (...). Los segundos se presentan en minscula. Por otra parte, mientras que stos pueden derivar en otros smbolos, de cualquiera de los dos tipos, los primeros (como su propio nombre indica) no pueden generar otros nuevos. Ahora ya estamos preparados para estudiar nuestro cdigo. En el argot de compiladores, al conjunto de reglas que hemos escrito se le denomina gramtica del lenguaje. En primer lugar veremos, en la lnea 6 del Cdigo 4, cmo se le indica a Bison cules son los smbolos terminales de nuestra gramtica. A partir de estas definiciones, Bison genera automticamente, en un fichero de cabecera .h (Cdigo 5), una serie de macros en las que asocia un nmero entero a cada token. De esta forma, podremos olvidarnos de crear (y, lo que es mejor, de mantener) dicho fichero nosotros mismos.

calculadora se compone de una instruccin (lnea 12) o de una entrada y una instruccin (lnea 11). Pero, qu es una entrada? Si aplicamos la definicin recursiva veremos que en esas tres simples lneas, sencillamente, establecemos que un programa est formado por una o ms instrucciones. Si proseguimos, en las lneas 14 a 17, contemplaremos el significado de instruccin. Hemos decidido que habr tres tipos de instrucciones: de lectura (leer(y)), de escritura (escribir(var2*y)) o de asignacin (x = (1+2)*3). Detengmonos ahora, por ejemplo, en la instruccin asignacin en las lneas 22 y 23. En ellas queda reflejado que sta es un IDENT (identificador) en este punto debemos recordar que es Flex quien decide qu parte del cdigo fuente es un identificador y lo retorna, segn lo definido en el Cdigo 1 seguido de un signo igual (=) y de una expresin. Por ltimo, tenemos expresin (olvidmosnos de momento de la lnea 28, del smbolo MENOS y de la instruccin %prec). En ella, especificamos que una expresin puede estar formada por dos expresin separadas por un signo +, *, etc. Pero adems de esto tambin puede tratarse de un IDENT o un NMERO (ambos definidos en Flex). De nuevo estamos utilizando aqu las propiedades de la recursividad.

Flex en accin procesando el fichero de entrada y pasndole tokens a Bison. I Gramticas y producciones Pero vayamos al meollo de la cuestin, al comienzo de la gramtica. Observemos atentamente las lneas 11, 12 y 13 del Cdigo 4. Comprendiendo la sintaxis aqu utilizada habremos entendido gran parte del funcionamiento de Bison. Como podis ver, estamos ante una herramienta realmente sencilla pero a la vez extremadamente potente. Estas lneas son las que designan una regla, compuesta a su vez por varias producciones. No debemos preocuparnos demasiado por estos nuevos trminos, simplemente hemos de saber que la gramtica de un lenguaje est formada por un conjunto de producciones o reglas. Volviendo a dichas lneas, en ellas estamos definiendo el principio de nuestro lenguaje de forma recursiva. En esas dos producciones (separadas por el smbolo |) estamos diciendo que el lenguaje de nuestra I Ensayo y error Para terminar de aclarar el funcionamiento de Flex y Bison acudiremos a un ejemplo prctico de una entrada a nuestro programa. Imaginemos que el fichero de entrada contiene la siguiente lnea: x = 5 + 3;. Recordemos que, en primer lugar, actuar el cdigo C creado por Flex, el cual, gracias al cdigo que hemos escrito, pasar a Bison los siguientes tokens: un IDENT (por el carcter x), el signo igual (=), un IDENT (caracter 5), el signo ms (+), otro IDENT (caracter 3) y, por ltimo, el punto y coma (;). Cuando se empieza a ejecutar el cdigo C creado por Bison, en primer lugar recibe el token IDENT. Bison comenzar su recorrido recursivo por la lnea 11 e intentar encajar los tokens que recibe en las reglas que hemos definido. Hemos de aadir que, aunque Bison es muy potente y eficaz, ste se gua por el mtodo de ensayo y error.

Pero antes de profundizar en ste, debemos hacer un inciso con el fin de aclarar algunos conceptos. La funcin del cdigo anterior es describir la estructura que presenta el lenguaje que queremos reconocer. Debemos recordar que, una vez compilado el fichero anterior con la herramienta Bison, se genera un archivo en lenguaje C. Tanto Flex como Bison son slo utilidades intermedias cuyo resultado pasaremos a cdigo mquina para construir nuestro compilador.

PC PRCTICO

Compiladores (y II)

En concreto, utiliza un autmata de pila para intentar encajar los tokens en nuestras reglas. Si observamos la grfica de lo que intenta hacer la herramienta (ver recuadro aclaratorio sobre cmo funcionan los autmatas de pila), constataremos que en primera instancia intenta comprobar si estamos ante una instruccin de lectura. Como la prueba fracasa, vuelve a insistir con la siguiente posibilidad, una instruccin de escritura. Evidentemente tampoco tiene xito, continuando con una de asignacin para concluir.

Los autmatas de pila


Como hemos indicado en el texto de este artculo, Bison tiene su fundamento terico en los autmatas de pila. En concreto implementa un mtodo llamado LR(1), que viene a utilizar una cadena de entrada que se recorre siempre de izquierda a derecha (Left to Right). Estos analizadores son del tipo bottom-up, pues parten de la cadena de entrada (en nuestro ejemplo, x = 5 + 3;) e intentan reducirla a lo que se denomina axioma de la gramtica. En nuestro ejemplo el axioma principal de la gramtica lo podemos ver en la lnea 11 del codigo 4 y es el smbolo no terminal entrada. En la imagen que ilustra este recuadro, vemos una representacin de un autmata de pila. Esquema de un autmata de pila. Y el esquema de funcionamiento es el siguiente: a) Continuamente mira si lo que hay en las ltimas casillas de la pila concuerda con la parte derecha de alguna de las reglas de produccin de la gramtica analizada. b) Si existe concordancia, elimina de la cima de la pila esa cadena y la cambia por la parte izquierda de la regla de produccin. A esto se le llama reduccin. c) Si no existe concordancia alguna, lee un carcter ms de la entrada y lo apila. A esta accin se le suele denominar desplazamiento. S usando este proceso conseguimos agotar el contenido de la cinta de entrada y en la cima de la pila queda el axioma de la gramtica, la palabra es reconocida. En otro caso no lo es. Veamos al autmata en accin intentando reconocer la sentencia if-then-else que proporcionan la mayora de los lenguajes de programacin. As observaremos cul es la estructura de esta instruccin y cmo se comporta ante una posible entrada. Hemos llegado al axioma de la gramtica y hemos agotado la cinta de entrada, por lo que la cadena introducida pertenece a nuestro lenguaje. Para tomar la decisin en cada momento sobre qu accin realizar (segn lo que tengamos en la cinta de entrada y en la pila) existen una serie de algoritmos no demasiado complejos pero que van ms all de la intencin de este artculo. As pues, Bison hace uso de estos algoritmos para procesar los tokens de entrada e intentar llegar al axioma de la gramtica. En el caso de nuestro lenguaje calculadora de ejemplo, intentar llegar a entrada (Cdigo 4, lnea 11).

Es la entrada x = 5 + 3; una instruccin de lectura?

Es la entrada x = 5 + 3; una instruccin de asignacin? Una vez llegados a este punto, podramos preguntarnos cul es la salida por pantalla del programa. En el caso de que nuestro fichero fuente sea correcto (es decir, si no hemos infringido las reglas del lenguaje) no habr ninguna salida. Recordemos que Bison slo comprueba si una determinada

PC PRCTICO

Compiladores (y II)

entrada es acorde a una gramtica. Suponiendo que el reconocedor generado por Bison detecte un error, automticamente llama a la funcin yyerror(). Su implementacin suele incluirse en la zona del archivo de entrada, justo detrs de los %% que finalizan la zona de reglas. En nuestro modelo usamos la variable yytext que contiene la ltima cadena reconocida, averiguando de esta forma dnde est el error. I Gramticas con atributos Hasta ahora hemos conseguido usar Bison y Flex para reconocer si una entrada se ajusta o no a unas determinadas reglas. A continuacin, descubriremos cmo son capaces de evaluar ciertos atributos asociados a los elementos reconocidos. De momento, Flex tan slo le pasa a Bison nuestros tokens, pero nicamente como un nmero identificador y, a efectos prcticos, todos los nmeros e identificadores son iguales. Por tanto, vamos a intentar recuperar esa informacin que habamos perdido en Flex y que denominaremos atributos. Lo primero que tendremos que especificar es de qu tipos podrn ser. Para ello, crearemos el fichero yystype.h (Cdigo 9) donde definiremos un tipo llamado YY_parse_STYPE.

Direcciones de inters
Cumplido nuestro objetivo de iniciar a los lectores en este apasionante mundo de los compiladores, hemos credo oportuno desechar una nueva entrega con los aspectos referentes al anlisis semntico. El marcado carcter prctico que caracteriza esta seccin, incompatibiliza la espesa teora necesaria para comprender a fondo este tema. Por eso, nos damos por satisfechos al concluir con esta ltima entrega que contiene material ms que suficiente para crear nuestras propias aplicaciones de manera sencilla y sobre todo prctica. Sin embargo, si queris ampliar informacin, hay varias pginas web que se pueden visitar para conocer ms sobre cmo realizar un compilador. Recomendamos la de la asignatura Procesadores de Lenguaje II de 4 de Ingeniera Informtica de la Facultad de Informtica y Estadstica de la Universidad de Sevilla, impartida por el departamento de Lenguajes y Sistemas Informticos, en www.lsi.us.es/~corchu/doc/teaching/pl2.html. Ah podris curiosear las prcticas del curso, as como bajaros algunas de las herramientas citadas. Otras pginas interesantes son www.compilerconnection.com y www.goof.com/pcg.

ste tan slo sirve para indicar que los atributos de los smbolos podrn ser bien valor de tipo int (ideal para asociarlo a un nmero entero), o bien nombre de tipo char*. Podremos especificar cualquier tipo vlido en C e incluso los incluidos en libreras propias. Para que el analizador lxico y el sintctico sean conscientes de la existencia del tipo YY_parse_STYPE, incluiremos el fichero yystype.h en las secciones %header del analizador lxico y sintctico. Adems, en la zona de definiciones del fuente Bison ser necesario aadir la siguiente lnea: %define STYPE YY_parse_STYPE (Cdigo 10). Bison interpretar que el YY_parse_STYPE definido contiene los posibles tipos de los atributos de los smbolos.

Una vez hecho esto, debemos especificar qu smbolos tienen atributos y de qu tipo son (de entre los definidos en YY_parse_STYPE). Bison slo permite definir un atributo por smbolo, apoyndose para ello en los nombres de los atributos definidos en la instruccin unin (en nuestro primer ejemplo valor y texto). Existen dos formas de establecer los atributos de los smbolos, dependiendo de si stos son terminales o no. Para los primeros utilizaremos la instruccin %token asignando a una serie de smbolos un determinado atributo. As, por ejemplo, en el Cdigo 11 indicamos que el terminal NMERO tiene un atributo valor de tipo int, mientras el smbolo IDENT tiene un atributo nombre de tipo char*. Para los smbolos no terminales utilizaremos la instruccin %type. De esta forma, la lnea %type expresin indica que el smbolo no terminal expresin tiene un atributo valor tipo int.

*yylval. Con ella conseguimos que yylex tenga un parmetro nuevo denominado yylval del tipo YY_parse_STYPE *. El nuevo prototipo de la funcin ser ahora yylex(YY_parse_STYPE * ). Desde este momento, ya podemos devolver atributos asociados a los tokens. Para ello, justo antes de hacerlo (accin return asociada a una expresin regular) debemos asignarle al nuevo parmetro (yylval) el valor que queremos asociar (al token). As, para calcular adecuadamente los atributos valor y texto de los smbolos terminales NMERO e IDENT, respectivamente, tendramos que hacer algo similar a lo efectuado en el Cdigo 12.

I Clculo de atributos Para calcular los atributos de los que hemos hablado debemos distinguir si se trata de smbolos terminales o no. En el caso de los primeros, el clculo debe hacerse mediante Flex, el anlisis lxico. Para ello, necesitamos una va de comunicacin, que se consigue definiendo un parmetro extra para la funcin yylex(). Recordemos que sta era la funcin principal creada por Flex y a la que llambamos desde nuestro main(). Esta definicin se hace con la siguiente instruccin situada en la zona reservada a tal efecto en el cdigo fuente interpretado por Flex: %define LEX_PARAM YY_parse_STYPE

Tan slo una observacin importante: cuando queramos devolver la cadena de caracteres yytext, hemos de asegurarnos antes de duplicarla ya que, si devolvisemos directamente yytext, el atributo de IDENT ira variando a medida que se modificase la cadena yytext (lo que ocurre con el reconocimiento de nuevos tokens). Por tanto, ahora hemos conseguido que, al reconocer cada uno de los tokens anteriores, no perdamos informacin importante como el lexema o el valor del nmero que hemos reconocido.

PC PRCTICO

Compiladores (y II)

La especificacin del clculo de los atributos de los smbolos no terminales se vincula a las producciones de la gramtica. Antes que nada, necesitamos aclarar la notacin para nombrar los atributos de los smbolos de una produccin. sta se ajusta a dos normas: el atributo del smbolo de la parte izquierda de una regla se nombra con $$, mientras que el del smbolo n-simo de la parte derecha se nombra con $n. Con esta notacin, ya podemos especificar las acciones a llevar a cabo para calcular los atributos (denominadas acciones semnticas por los tericos). stas se colocarn entre llaves ({...}) al final de cada produccin gramatical y se escriben en lenguaje C. Veamos un pequeo ejemplo (Cdigo 13) donde calcularemos los atributos de las expresiones. Con el fin de guardar los valores de las distintas variables y recuperarlas posteriormente, hemos usado una pequea biblioteca de almacenamiento. As, en la lnea 40 del Cdigo 15 utilizamos CambiaValor($1,$3);. sta se encarga de almacenar en una variable (cuyo nombre vendr dado por el valor del atributo char * del smbolo IDENT) el valor del atributo int del smbolo expresin. I Conclusiones Ya hemos conseguido construir nuestro primer pequeo compilador. Lo ms destacable es la sencillez y longitud del cdigo que hemos necesitado escribir para llegar a nuestro objetivo gracias a las dos herramientas utilizadas. Pensemos por un momento la cantidad de lneas de cdigo C que hubieran sido necesarias para llegar al mismo resultado sin la ayuda de Flex y Bison . Adems, el mantenimiento de este cdigo permite incluir nuevas instrucciones a nuestro lenguaje mucho ms fcilmente que si hubiramos escrito cdigo C directamente. Aun con esto, escribir un compilador para un lenguaje algo ms complejo no es tan directo como los ejemplos aqu mostrados. Se requiere el uso de tcnicas ms sofisticadas, como por ejemplo el clculo de los atributos de forma que sea posible construir una representacin en forma de rbol del cdigo de entrada. Algo que nunca dijimos que fuera sencillo. Eduardo Villalobos Fernndez (eduardo@imaginatica.us.es)

En primer lugar, ste debe comprender la notacin anteriormente citada. As, en la lnea 5 (la produccin expresin : NMERO ) el atributo de la izquierda de la regla es $$ y el de NMERO $1. Este ltimo es un smbolo terminal, al que Flex habr calculado un atributo llamado valor de tipo int para l (lneas 19 y 20 del Cdigo 12). En esta lnea 5 estamos, en definitiva, reconociendo, por ejemplo, una expresin del tipo x = 5;. Por tanto, para calcular el atributo de expresin (smbolo no terminal cuyo atributo tambin se llama valor) simplemente le asignamos el contenido del atributo valor de NMERO, como podemos observar en la lnea 6. Veamos ahora las lneas 1 y 2. En ellas estamos reconociendo expresiones del tipo x = 3 + 7; o y = x + 4;. Recordemos que a la parte izquierda de la produccin se la nombra como $$. A la primera expresin se la nombra como $1 y a la segunda como $3 al ocupar el smbolo + el lugar de $2. El tratamiento, como podremos observar, es extremadamente simple: slo necesitamos asignarle a $$ la suma de las dos expresiones. I Lenguaje calculadora Ya estamos listos para escribir el cdigo de nuestro lenguaje calculadora no slo para que lea nuestro fichero de entrada realizando el anlisis lxico y sintctico, sino para que tambin vaya realizando los clculos oportunos (Cdigo 14 para el analizador lxico, 15 para el sintctico y 16 para el programa principal con la funcin main).

You might also like