Este documento describe cómo construir un compilador simple utilizando las herramientas Flex y Bison. Se explica cómo crear archivos léxicos (.l) y sintácticos (.y) para definir las expresiones regulares, tokens y gramática de un lenguaje, y cómo compilarlos en un ejecutable utilizando Flex, Bison y un compilador C. También incluye dos ejemplos completos de calculadoras sencillas creadas con Flex y Bison para ilustrar el proceso de desarrollo de un compilador.
Reporte de simulación de flujo del agua en un volumen de control MNVA.pdf
Herramientas Flex Bison
1. PONTIFICIA UNIVERSIDAD CATÓLICA DEL ECUADOR SEDE IBARRA
ESCUELA DE INGENIERÍA EN SISTEMAS
TALLER DE COMPILADORES
Nombre: Marcos Ruales
Fecha: 21/06/2017
UTILIZANDO LA HERRAMIENTA DE BÚSQUEDA DE LA WEB, INVESTIGAR LOS
SIGUIENTES TEMAS:
Herramientas para la construcción de procesadores de lenguaje.
Seguidamente mostraremos las herramientas disponibles que pueden
utilizarse para la realización de la Práctica de Procesadores de Lenguajes.
Estas herramientas funcionan bajo Windows, aunque se puede utilizar, si se
desea, cualquier otra herramienta.
2. Aplicación de los lenguajes:
Hoy en día los lenguajes de programación tienen una infinidad de
aplicaciones, básicamente cualquier objeto electrónico tiene cierto grado de
programación. Algunos de los más comunes son C++ y JAVA, también
existe HTML, HTTP, XML, XAML y C#, este último actualmente es el más
utilizado en todos los dispositivos y redes basados en MICROSOFT (Xbox
350, Windows Mobile, Windows Phone, Windows Cloud, Zune, etc.).
Ya que los lenguajes de programación son informáticamente un puente
entre el Hardware y el Software estos permiten que las computadoras
puedan establecer conexión con un celular, una cámara o una consola
portátil de videojuego.
Reseña Histórica:
Los primeros lenguajes de programación surgieron de la idea de Charles
Babagge, la cual se le ocurrió a este hombre a mediados del siglo XIX. Era
un profesor matemático de la universidad de Cambridge e inventor inglés,
que al principio del siglo XIX predijo muchas de las teorías en que se basan
los actuales ordenadores. Consistía en lo que él denominaba la maquina
analítica, pero que por motivos técnicos no pudo construirse hasta mediados
del siglo XX. Con él colaboro Ada Lovedby, la cual es considerada como la
primera programadora de la historia, pues realizo programas para aquélla
supuesta máquina de Babagge, en tarjetas perforadas. Como la maquina no
llego nunca a construirse, los programas de Ada, lógicamente, tampoco
llegaron a ejecutarse, pero si suponen un punto de partida de la
programación, sobre todo si observamos que en cuanto se empezó a
programar, los programadores utilizaron las técnicas diseñadas por Charles
Babagge, y Ada, que consistían entre otras, en la programación mediante
tarjetas perforadas. A pesar de ello, Ada ha permanecido como la primera
programadora de la historia. Se dice por tanto que estos dos genios de
antaño, se adelantaron un siglo a su época, lo cual describe la inteligencia
de la que se hallaban dotados.
En 1823 el gobierno Británico lo apoyo para crear el proyecto de una
máquina de diferencias, un dispositivo mecánico para efectuar sumas
repetidas. Pero Babagge se dedicó al proyecto de la máquina analítica,
abandonando la máquina de diferencias, que se pudiera programar con
tarjetas perforadas, gracias a la creación de Charles Jacquard (francés).
Este hombre era un fabricante de tejidos y había creado un telar que podía
reproducir automáticamente patrones de tejidos, leyendo la información
codificada en patrones de agujeros perforados en tarjetas de papel rígido.
3. Entonces Babagge intento crear la máquina que se pudiera programar con
tarjetas perforadas para efectuar cualquier cálculo con una precisión de 20
dígitos. Pero la tecnología de la época no bastaba para hacer realidad sus
ideas. Si bien las ideas de Babagge no llegaron a materializarse de forma
definitiva, su contribución es decisiva, ya que los ordenadores actuales
responden a un esquema análogo al de la máquina analítica. En su diseño,
la máquina constaba de cinco unidades básicas:
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
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:
4. 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 comando para la ejecución de Flex y Bison y/o la compilación del
archivo generado.
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
5. 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):
Para invocar a Bison en conjunción con flex realizar lo siguiente:
Para compilar los archivos generados. Flex: MinGW
6. 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:
2 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
7. 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 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.
*/
8. 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;
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
9. %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)}
;
/* 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;}
10. |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
(a
r
g
c
>
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/bas
h 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:
13. EJEMPLO 2
Archivo para FLEX
?
1 %{
2 #include <stdio.h>
3 #include <conio.h> 4 #include
"parser.h"
%}
5 %option noyywrap
6 %option yylineno
7 letra [a-zA-Z]
8 digito [0binario [0-9]-1]
9 ignora " "|t|n
10operarit *|+|-|/
11operlog &|$
12 comparador <|>|<=|>=|==|!=
13%% {ignora }+ {;}
14"Entero" {printf("Palabra reservada para tipo de dato
15enteron");return PRENTERO;}
16"Real" {printf("Palabra reservada para tipo de dato 17
realn");return PRREAL;}
18"Booleano" {printf("Palabra reservada para tipo de dato
19booleanon");return PRBOOLEANO;}
20"Caracter" {printf("Palabra reservada para tipo de dato
21caractern");return PRCARACTER;}
22"Si" {printf("Palabra reservada para 23condicional
n");return PRSI;}
24"Sino" condicional n");return {printf("Palabra
reservada para otro PRSINO;}
25"SinoSi" {printf("Palabra reservada para definir
26condicionales secundariasn");return PRSINOSI;}
14. 27"Entonces" {printf("Palabra reservada para definir
accion a
28realizarn");return PRENTONCES;}
29"FinSi" {printf("Palabra reservada finalizar
30condicionaln");return PRFINSI;}
31"Para" {printf("Palabra reservada para bucle de tipo
32Paran");return PRPARA;}
33"FinPara" {printf("Palabra reservada para fin de bucle
de
34tipo Paran");return PRFINPARA;}
35"Mientras" {printf("Palabra reservada para bucle de tipo
36Mientras n");return PRMIENTRAS;}
37"Hacer" empieza algo n");return {printf("Palabra
reservada para indicar que se PRHACER;}
38"FinMientras" {printf("Palabra reservada fin de bucle de
tipo
39Mientrasn");return PRFINMIENTRAS;}
40"FinHacerMientras" {printf("Palabra reservada para indicar
fin
41de bucle Hacer-Mientrasn");return PRFINHACERMIENTRAS;}
42"Funcion" {printf("Palabra reservada para declaracion
de
43funcionesn");return PRFUNCION;}
44"Estructura" {printf("Palabra reservada para declaracion
de
45estructurasn");return PRESTRUCTURA;}
46"FinFuncion" {printf("Palabra reservada para finalizar
47funcionn");return PRFINFUNCION;}
48"Retorna" {printf("Palabra reservada para retorno de
49funcionn");return PRRETORNA;}
50"SinValor" {printf("Palabra reservada para funcion sin
valor
51de retorno n");return PRSINVALOR;}
52"Definir" funciones n");return {printf("Palabra
reservada para definir PRDEFINIR;}