Compiladores: Bison
DEFINICION Y USO: BISON
Bisón es un generador de analizadores sintacticos LALR. Si la gramatica es ambigüa Bison es capaz de tratar de solucionarla siguiendo una cierta prioridad en las reglas que conoceremos mas adelante.
La estructura del fichero sera:
Programa file.y ->(bison)-> file.tab.c file.tab.c ->(compilador de C)->a.out entrada ->(a.out)-> salida
FORMATO Y DECLARACIONES
Al igual que FLEX, Bison se divide en tres partes en un fichero.y:
{declaraciones}
%%
{reglas}
%%
{rutinas de apoyo en C}
Declaraciones
Las declaraciones en C: Se realizan entre los simbolos '%{' y '}%'. Todo lo que esté en dichos simbolos se tratan como codigo C puro.
Declaraciones de componentes lexicos: El simbolo inicial, los tokens...
Otras declaraciones: Asociatividad y precedencia para que, en caso de una gramatica ambigüa puedan definirse que reglas van antes que otras.
Reglas: Cada una de ellas consta de una produccion de la gramatica y una accion semantica a dicha regla.
Si definimos una regla como <lado_izquierdo> -> <p1>|<p2> en bisón se traduciría como
<lado_izquierdo>: <p1> {accion semantica 1} | <p2>; {accion semantica 2}
En la seccion de reglas, un caracter simple entre comillas 'c' se considera como un simbolo terminal. No obstante es mejor hacer uso de declaraciones de tokens para poder devolverlos por la salida y seguir la traza de ejecución. Un caracter sin comillas de letras y digitos y no declarados como componentes lexicos, son simbolos no terminales.
El primer simbolo no terminal del lado izquierdo se considera el inicial, pero podria declararse en la parte de declaraciones cualquier otro como %start simbolo.
Asi pues: E: E '+' E; podria verse como E: E MAS E si tenemos el token MAS. Bison, además, con los tokens genera el fichero .h con todos los defines que necesitaria el léxico y el propio sintáctico por lo que podemos ahorrarnos ese fichero simplemente haciendo usando el fichero "nom.tab.h"
ACCIONES SEMANTICAS
Las acciones semánticas se realizan en C, donde $$ se refiere al valor del atributo asociado con el simbolo que este en el <lado_izquierdo>. Por otra parte $i hace referencia al numero de otros simbolos que estan en la parte derecha. Asi para la regla:
T: E '+' D;
$$ Hace referencia al valor de T
$1 Hace referencia al valor de E
$3 hace referencia al valor de D
$2 Hace referencia al valor de '+', al ser un token, no se tiene en cuenta porque no nos hace falta en este caso.
Si dijeramos que es una regla para sumar dos numeros podriamos decir que:
T: E '+' D; {$$ = $1+$3;}
Por defecto, si no ponemos nada, estamos diciendo $$=$1; no obstante pueden colocarse reglas en medio de una declaracion si fuera necesario. Pero esto 'crea' un nuevo estado intermedio vacio.
T: E {$$=$1;} '+' E {$$=$1;}; T: E A'{$$=$1;} '+' EB'{$$=$1;} A': λ {$$=$1;} B': λ {$$=$1;}
RUTINAS DE APOYO EN C
Hay que proporcionar un analizador lexico (yylex()) que produzca los componentes lexicos, cuyos valores (de los que hablamos anteriormente) se comunican con la variable yylval.
Los blancos, tabuladores y saltos de linea son ignorados, y pueden haber comentarios entre /* */.
EJEMPLO DE FICHERO BISON
Dada la gramatica siguiente gramatica, construir las reglas basicas para que sea valido por bison.
%token digito mas por pi pd
%%
entrada : /*vacio*/
| entrada linea /*empiezo*/
;
linea: E /*leo la linea y traduzco la gramatica*/
;
E: E mas T
| T
;
T: T por F
| F
;
F: pi E pd
| digito
;
FLEX Y BISON
Flex genera una funcion yylex(), que es llamada por el programa principal de Bison, por lo tanto, cada regla flex que devuelva tokens validos tiene que terminar con "return(token);". La interaccion se realiza de la siguiente forma:
Tambien puede compilarse el fichero de salida de Bison poniendo "#include "lex.yy.c" " en la primera seccion de Bison entre '%{' y '}%'. Sería importante usar la opcion -d para que bison genere el 'fichero.tab.h' que contiene los defines de todos los tokens, por lo que tendriamos que poner en el fichero lex '%{ #include "fichero.tab.h" }%'
EL PARSER
Bison trabaja como una maquina de estados finitos con una pila. El analizador léxico es capaz de leer y recordar el siguiente token de entrada. El estado actual es siempre el tope d ela pila, los estados vienen etiquetados con enteros pequeños e inicialmente está el 0.
La maquina puede hacer cuatro acciones: desplaza (shift), reduce (reduce), acepta (accept) y error(error).
Ademas nos diría los errores que se producen, si hay conflictos y todo. Respecto a las ambiguedades hay de dos tipos, la que se basan en precedencias y las que se basan en conflictos en la tabla.
CONFLICTOS EN LA TABLA
Cuando hay conflictos shift/reduce Bison siempre trata de desplazar, pues reducir no solucionara el conflicto. Sin embargo, ante reduce/reduce escogerá la regla primera que tenga.
Por lo que tecnicamente no tenemos que preocuparnos de estos errores pero si de la ambigüedad por preferencia. Si tuvieramos una gramatica que dado un termino puede ser una suma, una resta, una multiplicacion o una división y todas tienen las mismas prioridades, nos daremos cuenta de que nos interesa (cuando hacemos el arbol sintáctico) que la precedencia va por el lado izquierdo por convenio.
Asi pues si tengo operaciones de suma, resta, multiplicacion, division...
%left MAS MENOS %left POR DIV
Asi pues las que estan mas abajo son las que mas prioridad tienen, ¿pero que pasaria si tuviera un menos pero que no fuera una regla?
E: MENOS E;
Tendria que tener mas prioridad porque segun nuestras reglas aritmeticas el -3 ha de arrastrarse. Pero no podemos cambiar la gramatica, asi que podemos darle, manualmente mas precedencia.
%left MAS MENOS %left POR DIV %left UMENOS
E: MENOS E; %prec UMENOS;
Al leer si bison encuentra la misma precedencia, se fija en su asociatividad. Si la precedencia es por la izquierda, reduzco, si es por la derecha desplazo. Si la entrada tiene mas preferencia desplazo y si no, reduzco.
Las declaraciones de asociatividad eliminan los fallos de Bison por la salida por lo que, si hay fallos, no lo sabremos.
CARACTERITICAS AVANZADAS
Dada la sentencia X=4+3 bison analiza y ve que es correcta, sin embargo, si no se definió antes el identificador, es capaz de devolver el error con yyerror o abortar con yyabort.
ACCESO A LAS VARIABLES DESDE LAS REGLAS
Suponiendo que tengo una gramatica para el lenguaje tal que:
sent: adj noun verb adj noun
Y quiero prohibir que el adjetivo "young" vaya con el nombre "crowe" (es decir, que una bruja no pueda ser joven) puede manipularse el contenido de la pila con un puntero. Si sabemos exactamente que hay en la pila podemos saber que puntero usar. El actual es siempre $1 pues es el primero de la regla, si quisiera referirme a lo que hay tras el tendria que ver cuantos hay en la pila, el segundo sería $0, el tercero $-1...
Asi que suponiendo que la pila está vacio y meto YOUNG CROWE tendriamos:
YOUNG -> adj(YOUNG) -> CROWE adj(YOUNG) -> noun(CROWE) adj(YOUNG)-> ...
Podria ponerlo en bison como:
noun: CROWE | {if ($0==YOUNG) printf("A crowe cannot be young\n"); $$=CROWE;}
TIPOS ARBITRARIOS
Por defecto bison acpeta en el valor de los atributos yylval enteros. Pero podriamos poner lo que quisieramos. Para eso está el tipo %union.
En la seccion de declaraciones podemos decir:
%union { char * nombre; int ent;} %%
Si quisieramos declarar los tokens 'CADENA' 'NUM' 'C' y 'B' habria que especificar, o bien en la declaracion del token o bien en la regla, el tipo particular que tendrá, no obstante para $$ siempre habra que especificarlo:
%union { char * nombre; int ent;} %token<nombre> CADENA %token<ent> NUM %token<ent> C %token<ent> B %% A: B C; {printf("Cadena %s, numero %d", $2, $1} B: NUM; {$<ent>$= $<ent>1;} c: CADENA; {$<cad>$= $<cad>1;}








