Está en la página 1de 12

UNIVERSIDAD NACIONAL DE JUJUY FACULTAD DE INGENIERA

Compiladores
APUNTES DE CTEDRA

EL METACOMPILADOR BISON INTERFAZ FLEX - BISON

Facultad de Ingeniera UNJu

Ctedra de Compiladores

1. Qu es BISON? BISON, al igual que FLEX, permite generar programas de forma automtica. Esta herramienta se usa en consonancia con la herramienta FLEX y sirve para especificar analizadores sintcticos. De la misma forma que FLEX tiene como base las expresiones regulares, la herramienta BISON tambin se basa en otro formalismo para describir lenguajes, en este caso sern las gramticas independientes del contexto las que constituirn el ncleo de las especificaciones que procesar BISON.

2. El formato del fichero de entrada La herramienta BISON es una versin mejorada de una herramienta anterior denominada YACC. BISON ha sido desarrollada con la intencin de ser compatible con las especificaciones que procesaba YACC, de manera que el lenguaje que acepta BISON es bsicamente el lenguaje YACC ms algunas modificaciones o mejoras. La herramienta BISON va a tomar como entrada un fichero de texto que, bsicamente, tiene el siguiente formato: %header{ <cdigo C de cabecera> %} %{ <cdigo C de declaracin> %} <definiciones> %% <producciones y acciones> %% <cdigo C de implementacin> Seccin de cdigo C de cabecera: Se incluyen declaraciones en C que queremos que aparezcan dentro de la interfaz del analizador sintctico que vamos a generar. La mayora de las veces tendremos suficiente con la interfaz por defecto, as que al igual que ocurra con la herramienta FLEX utilizaremos esta seccin en contadas ocasiones. Seccin de cdigo C de declaracin: Se incluyen declaraciones en C de elementos que vayan a ser utilizados en el resto de la especificacin. Estos elementos no aparecern dentro de la interfaz del analizador que vamos a generar. Seccin de definiciones: Se incluye una serie de definiciones que completan la gramtica que se incluir en la seccin de producciones y acciones (Por ejemplo las definiciones de tokens, es decir la definicin del alfabeto de smbolos terminales de la gramtica). Seccin de producciones y acciones: Constituye el ncleo de la especificacin y se compone de una serie de producciones gramaticales. Seccin de cdigo C de implementacin: Permite introducir las implementaciones en C de los elementos declarados en la segunda seccin. Su uso no es del todo recomendable ya que no favorece la creacin de mdulos reutilizables.

3. Definicin de tokens Los tokens, o smbolos terminales de la gramtica, se van a implementar a travs de nmeros enteros (int). A la hora de definir estos tokens, BISON ofrece dos posibilidades:

Facultad de Ingeniera UNJu

Ctedra de Compiladores

1. Si el token encaja con una palabra de un solo carcter se puede representar por ese mismo carcter encerrado entre comillas simples. Por ejemplo: 'a' 2. Si por el contrario decidimos dar un nombre a un determinado token (cosa que es obligatoria si el token encaja con un lexema de ms de un carcter) debemos incluirlo en la seccin de definiciones con la orden %token. Por ejemplo: %token NUMERO IDENT define dos tokens llamados NUMERO e IDENT. A partir de estas definiciones, BISON generar automticamente en la interfaz (fichero "*.h") una serie de macros que asociarn un nmero entero a cada token: #define NUMERO 256 #define IDENT 257

4. Smbolos no terminales No es necesario especificar qu nombres se corresponden con smbolos no terminales, BISON deducir esta informacin por eliminacin: todos los nombres de smbolos que no se declaren explcitamente como tokens y se utilicen en la gramtica se considerarn directamente no terminales. Habitualmente se sigue el convenio de definir los smbolos terminales con maysculas y los no terminales con minsculas. No es obligatorio, pero permite leer con ms claridad las especificaciones gramaticales.

5. Producciones gramaticales A diferencia de la notacin utilizada por FLEX, que incluye una gran variedad de operadores, el lenguaje de la herramienta BISON para definir gramticas es bastante simple. En el siguiente ejemplo se encuentran todos los elementos que pueden aparecer en una gramtica: lista : lista '-' ELEMENTO | /* lambda */ | '-' ELEMENTO ; Los dos puntos (:) constituyen el smbolo de produccin, es decir el que separa la parte izquierda de la regla de la parte derecha. Si tenemos varias producciones que tienen la misma parte izquierda las podemos agrupar en una sola con la barra vertical (|). El punto y coma (;) indica el final de una regla. En la parte derecha de la regla podemos colocar cualquier combinacin de smbolos no terminales (en el ejemplo lista) y terminales (en el ejemplo '-' y ELEMENTO). O incluso ninguno, como ocurre en la segunda regla, interpretndose en este caso como la palabra vaca (). Se pueden incluir comentarios en cualquier parte de la zona de reglas, para ello se utilizarn los mismos delimitadores que en C.

6. La funcin yyerror Cuando el reconocedor generado por BISON detecta un error sintctico automticamente llama a la funcin yyerror. La implementacin de esta funcin queda a cargo del programador. Habitualmente, la definicin de esta funcin suele incluirse en la zona de implementacin del

Facultad de Ingeniera UNJu

Ctedra de Compiladores

archivo de entrada, justo detrs de los %% que finalizan la zona de reglas. La implementacin ms simple es: %{... %} ... %% ... %% void yyerror(const char *s) { printf("%s",s); } El mensaje que obtenemos con esta implementacin es poco informativo, ya que slo nos muestra en la salida estndar el siguiente mensaje: parse error Con poco esfuerzo, podemos conseguir un mensaje un poco ms orientativo, que nos diga cul ha sido el lexema que ha provocado el error. Para ello utilizaremos la variable yytext que se encuentra declarada en la interfaz de analizador lxico generado por FLEX ("analex.h"). Tendremos que hacer visible dicha interfaz incluyndola en la seccin de declaraciones C de la especificacin BISON: %{ ... #include "analex.h" %} ... %% ... %% void yyerror(const char *s) { printf("%s",yytext); }

7. Un ejemplo simple Veamos un ejemplo muy simple de aplicacin conjunta de las herramientas FLEX y BISON. Se trata de reconocer la siguiente entrada: valor1 valor2 z valor1 valor2 = = = = = 10; 12; valor1; valor2; z;

Como se ve, se trata de una lista de asignaciones. Cada asignacin tiene una variable en su parte izquierda y una expresin muy simple (variable o nmero) en su parte derecha. Antes de escribir el analizador sintctico debemos concretar los aspectos lxicos. He aqu su especificacin: /* Fichero : analex.l" */ %{ #include "anasint.h" %}

Facultad de Ingeniera UNJu

Ctedra de Compiladores

blanco letra digito ident numero %% {blanco} {numero} {ident} . %%

" "|\t|\n [a-zA-Z] [0-9] {letra}({letra}|{digito})* {digito}+ {;} {return NUMERO;} {return IDENT;} {return yytext[0];}

Con la ltima regla se procesan todos los tokens de un slo carcter (en el ejemplo slo '=' y ';'). Las definiciones de NUMERO e IDENT son importadas del fichero "anasint.h". Estas definiciones son generadas automticamente por BISON a partir de las instrucciones %token del fichero "anasint.y". La especificacin sintctica es la siguiente: /* Fichero: anasint.y */ %{ #include <stdio.h> #include "analex.h" void yyerror(const char *); %} %token NUMERO IDENT %% entrada : asignaciones ; asignaciones : asignacion |asignaciones asignacion ; IDENT '=' expr ';' ; IDENT |NUMERO ;

asignacion : expr :

%% void yyerror(const char *s) { printf("\n Error: %s",yytext); }

8. Listas, agregados y elecciones La gramtica del ejemplo anterior contiene los tres esquemas de reglas ms usuales en la descripcin de lenguajes. Estos tres esquemas son: Esquema lista: se aplica cuando la entrada que se quiere reconocer consta de una secuencia de elementos de una misma categora. Por ejemplo una lista de nmeros, una lista de instrucciones, una lista de identificadores. Bastan dos reglas para definir este tipo de construcciones, una recursiva y otra que sirve de caso base: lista : elemento |lista elemento ;

Facultad de Ingeniera UNJu

Ctedra de Compiladores

En el lenguaje anterior, la definicin del smbolo asignaciones es un claro ejemplo de este tipo de esquema. En algunas ocasiones las listas incluyen en su sintaxis un elemento separador. En estos casos el esquema vara un poco para contemplar esta caracterstica pero es sustancialmente el mismo. Por ejemplo si el separador es una coma, el esquema sera: lista : elemento |lista ',' elemento ; Esquema agregado: se aplica cuando la entrada que se quiere reconocer consta siempre de un nmero fijo de elementos. Por ejemplo una instruccin de asignacin o un programa compuesto por tres secciones. Las reglas que definen este tipo de construcciones sintcticas son del tipo: agregado : componente1 componente2 componente3 ; Con tantos componentes como sean necesarios. En el lenguaje de la seccin anterior, la definicin del smbolo asignacin es un claro ejemplo de un agregado de cuatro componentes, tres de los cuales son smbolos terminales (IDENT, '=', ';') y otro un smbolo no-terminal (expr). Esquema eleccin: se aplica para agrupar distintas opciones en la definicin de la estructura sintctica de una determinada entrada. Por ejemplo los distintos tipos de instrucciones en un lenguaje de programacin o los distintos tipos de datos en la declaracin de una variable. Las reglas que definen una eleccin son del tipo: eleccion : opcion1 |opcion2 |opcion3 ; Con tantas opciones como sean necesarias. En la gramtica de la seccin anterior la definicin del smbolo expr constituye una eleccin entre dos opciones (IDENT y NUMERO).

9. Cmo se compila una especificacin? Con la siguiente orden: bison -oanasint.c -d anasint.y se generan los siguientes ficheros de salida: anasint.c anasint.h El primero de ellos contiene la implementacin de la funcin yyparse que es la responsable de analizar la entrada. El segundo contiene una serie de declaraciones, entre las que destacan por su importancia las macros que asignan valores numricos a los smbolos terminales con nombre. Por ejemplo: #define NUMERO 256 Antes de ejecutar BISON, debemos copiar en nuestro directorio de trabajo los ficheros bison.h y bison.cc proporcionados junto con la herramienta. Si no queremos copiarlos podemos indicarle a BISON dnde se encuentran estos dos ficheros con las opciones -H y -S, respectivamente. Por ejemplo, si tenemos BISON instalado en la carpeta c:\bison, podramos ejecutarlo desde cualquier punto con la siguiente orden: c:\bison\bison -Hc:\bison\bison.h -Sc:\bison\bison.cc -oanasint.c -d anasint.y

Facultad de Ingeniera UNJu

Ctedra de Compiladores

Al igual que ocurre con los ficheros escritos en FLEX, los fuentes BISON tambin se pueden integrar en el entorno VisualC++ estableciendo en su opcin Settings la forma en la que se procesan.

10. Cmo llamar a yyparse? (inicio.c) El fichero princip.c contiene la llamada a la funcin yyparse. #include <stdio.h> #include "analex.h" #include "anasint.h" main() { yyin=fopen("entrada.txt","r"); yyparse(); fclose(yyin); } La funcin yyparse se encarga de llamar a la funcin yylex.

11. Ejecucin Para ejecutar nuestro analizador sintctico an nos quedan por seguir los siguientes pasos: 1. 2. Crear un Workspace en el que se incluyan los ficheros analex.l, anasint.y, inicio.c, anasint.c y analex.c. Editar un fichero de texto que se llame entrada.txt, que contenga una secuencia de caracteres susceptibles de ser procesados por el analizador.

12. Particularidades del cdigo generado Para que el cdigo generado pueda ser compilado correctamente debemos definir ciertas macros en el entorno VisualC++ 1. 2. 3. Activar la opcin settings del workspace Sealar la pestaa C/C++ En preprocessor definitions incluir la definicin _MSDOS

13. Ejercicio Propuesto Compilar y ejecutar el ejemplo anterior.

14. Desarrollando reconocedores sintcticos ms complejos Con las herramientas BISON y FLEX se pueden desarrollar reconocedores sintcticos, que adems de decirnos si una entrada se ajusta o no a unas determinadas reglas lxicas y sintcticas, sean capaces de evaluar ciertos atributos asociados a los elementos reconocidos. Este tipo de reconocedores se especifican con una ampliacin de las gramticas independientes del contexto denominadas gramticas con atributos.

Facultad de Ingeniera UNJu

Ctedra de Compiladores

15. Comunicacin adicional entre los analizadores lxico y sintctico Hasta el momento, la comunicacin entre el analizador lxico y el sintctico se ha limitado al nmero entero que sirve para codificar los tokens. Esta informacin es generada por la funcin yylex y utilizada por la funcin yyparse cada vez que necesita procesar un nuevo fragmento del fichero de entrada. Esquemticamente esta comunicacin se refleja as: void yyparse () { int token; ... token = yylex(); ... } Con la introduccin de las gramticas con atributos, adems del token, los analizadores lxico y sintctico debern compartir otra informacin, la referente a los atributos. Estos atributos podrn ser tan complejos como queramos, por lo que para implementarlos nos harn falta tipos de datos igualmente complejos. Utilizaremos el fichero "yystype.h" para establecer los tipos de atributos que podrn compartirse entre ambos analizadores. Dicho fichero de incluirse en las especificaciones "analex.l" y "anasint.y":

16. Tipos de los atributos Cada gramtica asignar distintos tipos de atributos a sus smbolos, por lo que ser necesario escribir una versin del fichero "yystype.h" ajustada a cada caso. No obstante, el contenido de este fichero tendr siempre una estructura muy parecida y los cambios ms importantes afectarn a los campos de la union que en l se declaran (sealados en gris en el siguiente ejemplo): #ifndef _YYSTYPE_H #define _YYSTYPE_H typedef union{ int valor; char *texto;} YY_parse_STYPE; #endif La definicin anterior sirve para indicar que los atributos de los smbolos podrn ser o bien uno denominado valor de tipo int, o bien uno denominado nombre de tipo char*. Se pueden utilizar tanto tipos y constructores de tipos de C, como tipos definidos en otros mdulos, en estos casos ser necesario incluir la cabecera del mdulo correspondiente para que

Facultad de Ingeniera UNJu

Ctedra de Compiladores

los nombres de los nuevos tipos sean conocidos (tambin se sealan en gris los fragmentos especficos de este nuevo ejemplo): #ifndef _YYSTYPE_H #define _YYSTYPE_H #include "conjunto.h" typedef union{ int valor; Conjunto numeros;} YY_parse_STYPE; #endif

17. Clculo de los atributos de los smbolos terminales (en "analex.l") En el caso de los smbolos terminales (tokens) los valores de los atributos se calcularn en el momento de devolver el token correspondiente y el encargado de realizar ese clculo ser el anlisis lxico, estar por tanto especificado en el fichero "analex.l". Para poder transmitir esa informacin hacia el analizador sintctico es necesaria una va de comunicacin, que se consigue con un parmetro del tipo YY_parse_STYPE para la funcin yylex. Esta definicin se hace con la siguiente instruccin que se coloca en la zona de macros del fuente FLEX: %define LEX_PARAM YY_parse_STYPE *yylval Con ella conseguimos que la funcin yylex tenga un parmetro llamado yylval de tipo YY_parse_STYPE *. Ahora ya podemos devolver atributos asociados a los tokens, para ello justo antes de devolver el token (accin return asociada a una expresin regular) debemos asignarle al parmetro yylval el valor del atributo que queremos asociarle (al token). As, para calcular adecuadamente los atributos valor y texto de los smbolos terminales NUMERO e IDENT, respectivamente, tendramos que hacer lo siguiente ("analex.l"): %header{ #include "yystype.h" %} %{ #include <string.h> #include <stdlib.h> #include "anasint.h" %} ... letra [a-zA-Z] digito [0-9] ident {letra}({letra}|{digito})* numero {digito}+ %define LEX_PARAM YY_parse_STYPE *yylval %% {ident} {yylval->texto = strdup(yytext); return (IDENT);} {numero} {yylval->valor = atoi(yytext); return(NUMERO);} ... %% Hay dos cosas importantes en la especificacin, que si no se tienen en cuenta suelen provocar bastantes problemas: El fichero "anasint.h" no se incluye en la seccin %header del fichero "analex.h" sino que se incluye en la seccin de declaraciones locales. Esto se hace para evitar que las cabeceras del analizador lxico y sintctico contengan definiciones comunes.

Facultad de Ingeniera UNJu

Ctedra de Compiladores

Cuando queremos devolver la cadena de caracteres yytext, nos aseguramos antes de duplicarla ya que si devolvisemos directamente yytext el atributo de IDENT ira variando a medida que variase la cadena yytext (con el reconocimiento de nuevos tokens). Si se dispone de una librera de manipulacin del tipo abstracto cadena esto no sera necesario, porque las funciones de creacin de cadenas ya se encargaran de gestionar adecuadamente la manipulacin de la memoria para la ubicacin de cadenas.

18. Definicin de los atributos de los smbolos (en "anasint.y") Una vez que se han definido los posibles tipos de los atributos de los smbolos, el siguiente paso es determinar qu smbolos tienen atributos y de qu tipo son (de entre las posibilidades definidas en YY_parse_STYPE). BISON slo permite definir un atributo por smbolo. Para ello se apoyar en los nombres de atributos definidos en la instruccin union (en nuestro primer ejemplo valor y texto). Tendremos dos formas de establecer los atributos de los smbolos dependiendo de si stos son terminales o no terminales: 1. Para los smbolos terminales: Aprovecharemos la instruccin %token para establecer que una serie de smbolos tienen un determinado atributo. Por ejemplo: %token <valor> NUMERO %token <texto> IDENT CADENA indica que el smbolo terminal NUMERO tiene un atributo valor de tipo int (por la definicin de la union) y los smbolos IDENT y CADENA tienen un atributo texto de tipo char *. 2. Para los smbolos no terminales: Utilizaremos la instruccin %type. Por ejemplo: %type <valor> expresion indica que el smbolo no terminal expresion tiene un atributo valor tipo int. Adems de indicarle a BISON los tipos de los atributos de los smbolos, tambin hay que incluir en la zona de macros del fuente "anasint.y" las siguientes definiciones: %header{ ... %} ... %define PURE %define STYPE YY_parse_STYPE %% ... La primera indica que la comunicacin de atributos entre yylex e yyparse se hace a travs de parmetros, y la segunda informa a BISON de que los tipos disponibles estn enumerados en la declaracin de YY_parse_STYPE.

19. Clculo de los atributos de los smbolos no terminales (en "anasint.y") La especificacin del clculo de los atributos de los smbolos no terminales se vincula a las producciones de la gramtica. Antes que nada, necesitamos una notacin para nombrar los atributos de los smbolos de una produccin. Esta notacin se ajusta a las siguientes normas: El atributo del smbolo de la parte izquierda de una regla se nombra con $$.

10

Facultad de Ingeniera UNJu

Ctedra de Compiladores

El atributo del smbolo i-simo de la parte derecha de una regla se nombra con $i, con i 1. Con esta notacin ya podemos asociar a las reglas de la gramtica las acciones de clculo de atributos (tambin denominadas acciones semticas). Estas acciones se colocarn entre llaves al final de cada produccin gramatical. El lenguaje en el que se expresarn estas acciones ser C. Por ejemplo: expresion '+' expresion {$$=$1+$3;} | expresion '-' expresion {$$=$1-$3;} | NUMERO {$$=$1;} ; En las acciones semnticas slo podremos calcular el atributo del smbolo de la parte izquierda ($$) en funcin de los atributos de los smbolos de la parte derecha ($1, $2, ...). expresion :

20. Un ejemplo simple Veamos todos los fuentes necesarios para procesar un simple lenguaje. Una entrada a este lenguaje constar de una serie de llamadas a la instruccin escribir. Esta instruccin tomar como argumento una expresin aritmtica, la evaluar y mostrar el resultado por la salida estndar. Una posible entrada ("entrada.txt") sera: escribir(1+2); escribir((1+2)*3); escribir(3/4); escribir((3*3)-(2*2));

La descripcin de los atributos de los smbolos es la siguiente: /* Fichero "yystype.h" */ #ifndef _YYSTYPE_H #define _YYSTYPE_H typedef union{ int valor;} YY_parse_STYPE; #endif El analizador lxico para dicho reconocedor es: /* Fichero analex.l */ %header{ #include "yystype.h" %} %{ #include <stdlib.h> #include "anasint.h" %} letra [A-Za-z] digito [0-9] numero {digito}+ blanco [ \t\n] %define LEX_PARAM YY_parse_STYPE *yylval %% {blanco}* {;} {numero} {yylval->valor=atoi(yytext); return(NUMERO);} escribir {return(ESCRIBIR);} . {return(yytext[0]);} %%

11

Facultad de Ingeniera UNJu

Ctedra de Compiladores

El analizador sintctico es: /* Fichero: anasint.y */ %header{ #include "yystype.h" %} %{ #include<stdio.h> #include"analex.h" void yyerror(const char *); %} %token ESCRIBIR %token <valor> NUMERO %type <valor> expr term fact %define PURE %define STYPE YY_parse_STYPE %% entrada : instrucciones ; instrucciones : instruccion | instrucciones instruccion ; instruccion : escritura ; escritura : ESCRIBIR '(' expr ')' ';' {printf("La expresion vale: %i\n",$3);} ; expr : expr '+' term {$$ = $1+$3;} | expr '-' term {$$ = $1-$3;} | term {$$ = $1;} ; term : term '*' fact {$$=$1*$3;} | term '/' fact {$$ = $1/$3;} | fact {$$=$1;} ; fact : '(' expr ')' {$$=$2;} | NUMERO {$$=$1;} ; %% void yyerror(char *s) { printf("ERROR: (%s)\n",yytext); }

21. Ejercicio Propuesto Compilar y ejecutar el ltimo ejemplo de este enunciado.

12

También podría gustarte