2. Reseña Historica
Diseño y construcción de un compilador
En el proceso de construcción de compiladores se integran muchos conceptos
diferentes de las Ciencias de la Computación:
Algoritmos de búsqueda.
Árboles, Hashing.
Programación modular.
Lenguaje Assembly.
Análisis: Se trata de la comprobación de la corrección del programa fuente, e incluye
las fases correspondientes al Análisis léxico (que consiste en la descomposición del
programa fuente en componentes léxicos), Análisis sintáctico (agrupación de los
componentes léxicos en frases gramaticales) y Análisis
3. semántico (comprobación de la validez semántica de las sentencias aceptadas
en la fase de Análisis Sintáctico).
Síntesis: Su objetivo es la generación de la salida expresada en el lenguaje
objeto y suele estar formado por una o varias combinaciones de fases de
Generación de Código (normalmente se trata de código intermedio o de código
objeto) y de Optimización de Código (en las que se busca obtener un código lo
más eficiente posible).
Que es Flex y Bison
Son dos herramientas útiles para crear programas que reaccionen a una entrada de
datos con una estructura y un lenguaje predeterminado, como por ejemplo, podemos
crear compiladores, intérpretes y analizadores de línea de comando.
Flex: El Flex define las reglas de reconocimiento de símbolos (Tokens) a partir de
expresiones regulares. Cuando un Token es reconocido por uno de estos patrones de
agrupamiento se le define una acción, por lo general esta acción es devolver el Tipo y
el valor (lexema).
El Flex cuando se utiliza combinado con el Bison, utiliza las definiciones de los Tokens
realizadas en el Bison para la comunicación entre ellos,
Bison: GNU bison es un programa generador de analizadores sintácticos de propósito
general perteneciente al proyecto GNU disponible para prácticamente todos los
sistemas operativos, se usa normalmente acompañado de flex aunque los analizadores
léxicos se pueden también obtener de otras formas.
Bison convierte la descripción formal de un lenguaje, escrita como una gramática libre
de contexto LALR, en un programa en C, C++, o Java que realiza análisis sintáctico. Es
utilizado para crear analizadores para muchos lenguajes, desde simples calculadoras
hasta lenguajes complejos. Para utilizar Bison, es necesaria experiencia con la sintaxis
usada para describir gramáticas.
Como se instala Flex y Bison
1. Descarga el software disponible en el sitio de la cátedra.
2. Instalar el software en la unidad C: (para explicar a partir del punto 4 se tendrá
como hipótesis de que flex y bison han sido instalados en la ruta:
C:GnuWin32 donde contiene una subcarpeta llamada bin donde se
encuentran los programas respectivos)
3. Flex y bison son aplicaciones de consola, por lo que se deberá entrar al Símbolo
del sistema y tipear líneas de comando para ejecutar Flex. Una alternativa es
crear un archivo de proceso por lotes (*.bat) que contenga las líneas de
4. comando para la ejecución de Flex y Bison y/o la compilación del archivo
generado.
5. 4. Si deseas que flex y bison se integren al conjunto de variables del entorno (esto
te va a permitir llamar a flex/bison desde cualquier ubicación en la línea de
comandos) debes hacer lo siguiente:
Clic derecho en “Mi PC”.
Selecciona “Propiedades”
Clic en la pestaña “Opciones Avanzadas”
Presiona el botón “Variables de entorno”
En la ventana de variables de entorno, ubicarse en la sección “Variables del
sistema” luego haz clic en PATH y luego en el botón “Modificar” (si no está
hacer clic en “Nueva” y agregar PATH) • En la nueva ventana, escribir la ruta
completa al directorio “bin” de la aplicación flex/bison. Si existe otro valor,
separarlos con comas.
Aceptar los cambios y luego reiniciar el sistema operativo.
5. Si deseas instalar un compilador de C como MinGwin, deberás integrar la ruta
de acceso al compilador a las variables de entorno para facilitar la llamada al
programa. Por ejemplo si se instaló MingWin en “C:Mingw” y dentro de la
carpeta “bin” se encuentra “gcc.exe” que es el ejecutable, entonces de deberá
agregar (análogo a los pasos anteriores) lo siguiente:
6. Cuando tengas listo podrás llamar a flex/bison desde el símbolo del sistema sin
necesidad de ubicarte en la carpeta donde ha sido instalado flex/bison.
Como se compila con Flex y Bison
Luego de escribir las especificaciones de flex y bison realizar lo siguiente. Si se desea
invocar a flex:
Si se desea invocar a bison (recordar que bison trabaja en conjunto con flex):
6. Para invocar a Bison en conjunción con flex realizar lo siguiente:
Para compilar los archivos generados. Flex: MinGW
Una alternativa es utilizar un compilador para windows como DevC++ o Borland C++
4.5
Abriendo el archivo lex.yy.c y luego compilándolo se generará el ejecutable
“lex.yy.exe”
BISON y FLEX en conjunción:
7. Ejemplos de la creación de un compilador utilizando Flex y Bison
Vamos a realizar un ejemplo de una calculadora sencilla que reconocerá las principales
operaciones aritmética (+,-,* y /).
Abrimos un editor de texto y pegamos el siguiente código que será nuestro scanner
/*****************
Definiciones
Se colocan las cabeceras, variables y expresiones regulares
********************/
%{
#include <stdio.h>
#include <stdlib.h>
#include "sintactico.tab.h"
int linea=0;
%}
/*
Creamos todas las expresiones regulares
Creamos la definición llamada DIGITO, podemos acceder esta definición
usando {DIGITO}*/
DIGITO [0-9]
NUMERO {DIGITO}+("."{DIGITO}+)?
%%
/***************
Reglas
*****************/
/* Creamos las reglas que reconocerán las cadenas que acepte
Nuestro scanner y retornaremos el token a bison con la
8. funcion return. */
{NUMERO} {yylval.real=atof(yytext); return(NUMERO);}
"=" {return(IGUAL);}
"+" {return(MAS);}
"-" {return(MENOS);}
";" {return(PTOCOMA);}
"*" {return(POR);}
"/" {return(DIV);}
"(" {return(PAA);}
")" {return(PAC);}
"n" {linea++;}
[trf] {}
" " {}
/* Si en nuestra entrada tiene algún caracter que no pertenece a
las reglas anteriores, se genera un error léxico */
. {printf("Error lexico en linea %d",linea);}
%%
/*
Código de Usuario
Aquí podemos realizar otras funciones, como por ejemplo ingresar
símbolos a nuestra tabal de símbolos o cualquier otra accione
del usuario.
Todo lo que el usuario coloque en esta sección se copiara al
archvi lex.yy.c tal y como esta.
*/
Guardamos el archivo como lexico.l. Luego creamos un nuevo archivo y colocamos el
siguiente código.
%{
/********************
Declaraciones en C
**********************/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
extern int yylex(void);
extern char *yytext;
extern int linea;
extern FILE *yyin;
9. void yyerror(char *s);
%}
/************************
Declaraciones de Bison
*************************/
/* Especifica la coleccion completa de tipos de datos para poder usar
varios tipos de datos en los terminales y no terminales*/
%union
{
float real;
}
/* Indica la produccion con la que inicia nuestra gramatica*/
%start Exp_l
/* Especificacion de termines, podemos especificar tambien su tipo */
%token <real> NUMERO
%token MAS
%token MENOS
%token IGUAL
%token PTOCOMA
%token POR
%token DIV
%token PAA
%token PAC
/* No Terminales, que tambien podemos especificar su tipo */
%type <real> Exp
%type <real> Calc
%type <real> Exp_l
/* Definimos las precedencias de menor a mayor */
%left MAS MENOS
%left POR DIV
%%
/**********************
Reglas Gramaticales
***********************/
Exp_l: Exp_l Calc
|Calc
;
Calc : Exp PTOCOMA {printf ("%4.1fn",$1)}
;
10. /* Con el símbolo de $$ asignamos el valor semántico de toda la acción de la derecha
y se la asignamos al no terminal de la izquierda, en la siguiente regla, se la asigna a Exp.
Para poder acceder al valor de los terminales y no terminales del lado derecho usamos
el símbolo $ y le concatenamos un número que representa la posición en la que se
encuentra es decir si tenemos
A --> B NUMERO C
Si queremos usar le valor que tiene el no terminal B usamos $1, si queremos
Usar el valor que tiene NUMERO usamos $2 y así sucesivamente.
*/
Exp : NUMERO {$$=$1;}
|Exp MAS Exp {$$=$1+$3;}
|Exp MENOS Exp {$$=$1-$3;}
|Exp POR Exp {$$=$1*$3;}
|Exp DIV Exp {$$=$1/$3;}
|PAA Exp PAC {$$=$2;}
;
%%
/********************
Codigo C Adicional
**********************/
void yyerror(char *s)
{
printf("Error sintactico %s",s);
}
int main(int argc,char **argv)
{
if (argc>1)
yyin=fopen(argv[1],"rt");
else
yyin=stdin;
yyparse();
return 0;
}
Guardamos este archivo con el nombre sintáctico.y y con eso ya tenemos nuestro
scanner y nuestro parser terminado. Para compilar estos archivos usamos los
comandos
Compilando sintactico.y
~> bison -d sintactico.y
11. El parámetro –d, crea el fichero t.tab.h, que contiene los identificadores de los tokens
de bison usados por flex
Compilando lexico.l
~> flex lexico.l
Compilando arhivos generados y crear ejecutable
~> cc lex.yy.c sintactico.tab.c -o analizador -lfl -lm
Esto nos genera un ejecutable llamado analizador.
Muchas veces necesitamos modificar nuestro archivo sintáctico.y o lexico.l y tenemos
que compilar todo cada vez que hacemos un cambio, para no estar escribiendo los
comandos cada vez que realizamos un cambio, crearemos un script, que al ejecutarlo
realizara todos los comandos de compilación. Para eso creamos un nuevo archivo en
blanco y escribimos
#!/bin/bash
bison -d sintactico.y
flex lexico.l
cc lex.yy.c sintactico.tab.c -o analizador -lfl –lm
Guardamos este archivo con cualquier nombre, por ejemplo compilar.sh. Ahora
cambiaremos las propiedades de este archivo para poder ejecutar. Le damos clic
derecho sobre este archivo y en la pestaña permisos elegimos la opción de “Permitir
ejecutar el archivo como un programa”, cerramos esa ventana.
12. Para poder compilar, desde consola nos ubicamos donde se encuentra este archivo .sh
y escribimos
./compilar.sh
Esto nos genera nuestro ejecutable que podemos correr para poder probar nuestra
calculadora. Para ejecutar este ejemplo usamos el comando
./analizador
Ingresamos algunas expresiones y el resultado que obtenemos es:
EJEMPLO 2
Archivo para FLEX
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
%{
#include <stdio.h>
#include <conio.h>
#include "parser.h"
%}
%option noyywrap
%option yylineno
letra [a-zA-Z]
digito [0-9]
binario [0-1]
ignora " "|t|n
operarit *|+|-|/
operlog &|$
comparador <|>|<=|>=|==|!=
%%
13. 16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
{ignora}+ {;}
"Entero" {printf("Palabra reservada para tipo de dato enteron");return
PRENTERO;}
"Real" {printf("Palabra reservada para tipo de dato realn");return
PRREAL;}
"Booleano" {printf("Palabra reservada para tipo de dato
booleanon");return PRBOOLEANO;}
"Caracter" {printf("Palabra reservada para tipo de dato caractern");return
PRCARACTER;}
"Si" {printf("Palabra reservada para condicionaln");return PRSI;}
"Sino" {printf("Palabra reservada para otro condicionaln");return
PRSINO;}
"SinoSi" {printf("Palabra reservada para definir condicionales
secundariasn");return PRSINOSI;}
"Entonces" {printf("Palabra reservada para definir accion a
realizarn");return PRENTONCES;}
"FinSi" {printf("Palabra reservada finalizar condicionaln");return
PRFINSI;}
"Para" {printf("Palabra reservada para bucle de tipo Paran");return
PRPARA;}
"FinPara" {printf("Palabra reservada para fin de bucle de tipo
Paran");return PRFINPARA;}
"Mientras" {printf("Palabra reservada para bucle de tipo
Mientrasn");return PRMIENTRAS;}
"Hacer" {printf("Palabra reservada para indicar que se empieza
algon");return PRHACER;}
"FinMientras" {printf("Palabra reservada fin de bucle de tipo
Mientrasn");return PRFINMIENTRAS;}
"FinHacerMientras" {printf("Palabra reservada para indicar fin de bucle Hacer-
Mientrasn");return PRFINHACERMIENTRAS;}
"Funcion" {printf("Palabra reservada para declaracion de
funcionesn");return PRFUNCION;}
"Estructura" {printf("Palabra reservada para declaracion de
estructurasn");return PRESTRUCTURA;}
"FinFuncion" {printf("Palabra reservada para finalizar funcionn");return
PRFINFUNCION;}
"Retorna" {printf("Palabra reservada para retorno de funcionn");return
PRRETORNA;}
"SinValor" {printf("Palabra reservada para funcion sin valor de
retornon");return PRSINVALOR;}
"Definir" {printf("Palabra reservada para definir funcionesn");return
PRDEFINIR;}
"Constante" {printf("Palabra reservada para definir constantesn");return
PRCONSTANTE;}
"Entrada" {printf("Palabra reservada para definir entradasn");return
PRENTRADA;}
"Salida" {printf("Palabra reservada para definir salidasn");return