Guía 6

Anuncio
1
Compiladores / Guía VI / Ciclo 02 - 2016
Centro de Investigación y
Transferencia de Tecnología
Uso de la herramienta YACC
Facultad: Ingeniería
Escuela: Computación
Asignatura: Compiladores
Contenido
En la presente práctica se hace una introducción a la herramienta yacc, la cual servirá para generar analizadores sintácticos
en el desarrollo de los compiladores.
Objetivos Específicos


Utilizar una distribución de Linux que proporcione una herramienta de generación de análisis sintáctico.
Construir un analizador sintáctico haciendo uso de la herramienta yacc.
Material y Equipo



Guía de laboratorio N° 5.
Máquina Virtual Linux, distribución Bodhi.
LXTerminal, Flex, yacc y Bison.
Introducción Teórica
Introducción
YACC es una herramienta que sirve para generar un programa, capaz de analizar gramaticalmente una entrada dada por
lex, a partir de una especificación. Esta especificación, debe contener los tokens reconocidos y los tipos de datos de los
mismos si es que se ocupan para realizar operaciones sobre ellos, y una especificación de gramática en un formato similar
a BNF (Backus Naus Form), que va desde el símbolo no terminal más general a cada una de las opciones terminales.
Ejemplo:
<Expresion>
Numero + <Expresion>
Numero - <Expresion>
Numero
En el ejemplo podemos ver que <Expresion> es un símbolo no terminal que está compuesto por un "Numero" terminal
seguido de un símbolo '+' o '-' terminales seguido por un <Expresion> no terminal, que a su vez puede ser otro número u
otra expresión más compleja.
Una especificación yacc se divide en tres secciones diferentes de manera similar a lex, la de definiciones, la de reglas, y
la de subrutinas, que van igualmente separadas por un '%%', mismas que pueden incluir código de C encerrado entre un
%{ y un %}.
%token y %type
2
Compiladores / Guía VI / Ciclo 02 - 2016
%token sirve para definir los tokens que hay, y si es necesario, el tipo de dato que usan, todos los tokens son tomados
como símbolos terminales,
%left y %right
El siguiente paso, es definir el tipo de precedencia de nuestros tokens operadores, en este punto tenemos dos factores, la
precedencia por sí misma, y la agrupación de los operadores.
Precedencia
La precedencia es asignada en orden inverso al que aparecen, es decir, el último operador declarado, tiene mayor
precedencia que el anterior y así sucesivamente.
Asociatividad
%left y %right indican si el operador se agrupa a la derecha o a la izquierda, por ejemplo, en el caso de EXPO (Exponente)
debe asociarse a la derecha, por que buscamos que se resuelva de ese modo, de derecha a izquierda, por ejemplo:
Buscamos que
4^3^5^2^9
Sea evaluado asi :
4^(3^(5^(2^9)))
Por lo contrario, las sumas y restas queremos resolverlas de izquierda a derecha:
Buscamos que
4-3+5+2-9
sea evaluado así:
(((4-3) +5) +2)-9
Usar este tipo de declaraciones es importante para disminuir la posibilidad de ambigüedades en el lenguaje generado.
%start
En algunos casos es conveniente indicarle a yacc cuál es el símbolo (no terminal) inicial a la hora de hacer el parseo, es
decir, el símbolo que se trata de reducir, si esta opción no es especificada, yacc toma al primer símbolo de la sección de
reglas como símbolo inicial.
En nuestro ejemplo, se presentan ambos casos, nuestro símbolo inicial "Input" se encuentra al inicio del archivo y también
está declarado como símbolo inicial.
%start Input
Reglas
En esta parte finalmente llegamos a la definición de la gramática, acá podemos observar que cada símbolo se define con
su nombre, seguido de dos puntos ":" seguidos de varios símbolos que conformaran su composición gramatical que, en
caso de tener varias opciones, son separados por "|" (OR) indicando que se tienen varias opciones para reducir ese símbolo
y para terminar cada regla, un ";".
Ejemplo:
Si tomamos la gramática que definimos al principio de esta sección:
<Expresión>
Numero + <Expresión>
3
Compiladores / Guía VI / Ciclo 02 - 2016
Numero - <Expresión>
Numero
Y la transformamos a una regla de yacc, se vería como esto:
Expresión: NUMERO '+' Expresión
{$$ = $1 + $3;}
| NUMERO '-' Expresión
{$$ = $1 - $3;}
| NUMERO
{$$ = $1;}
;
En el ejemplo ya transformado a una regla gramatical de yacc, podemos ver que ya se especifica que hacer con los
símbolos de la gramática una vez que son resueltos en la parte de código de C. En este ejemplo, Expresión es el símbolo
no terminal que se está definiendo de manera recursiva, el valor que tomara una vez resuelto es el valor asignado a la
variable $$, que traducida a C es una variable mapeada al símbolo no terminal, $1, $2 y $3 son variables que son mapeadas
al valor de los símbolos de cada línea de cada regla, estas son numeradas de izquierda a derecha.
Ejemplo:
En este segmento de regla:
Expresión: NUMERO '+' Expresión
Expresión equivale a $$
NUMERO equivale a $1
'+' equivale a $2
Y Expresión (la parte recursiva) equivale a $3
Procedimiento
Ejemplo 1. Realizaremos una calculadora sencilla:
Utilizando el mismo principio de la guía anterior vamos a crear el archivo example1.l
%{
#include <string.h>
#include "y.tab.h"
extern int intval;
%}
integer [0-9]+
nl
\n
%%
[\t]+ ;
{integer}
{nl}
{sscanf(yytext, "%d", &intval);
{return NEWLINE;}
return INTEGER;}
4
Compiladores / Guía VI / Ciclo 02 - 2016
.
%%
{return yytext[0];}
Ahora, crearemos el archivo example.y
%{
#include <stdio.h>
int intval;
extern char *yytext;
%}
%token INTEGER
%token NEWLINE
%left '+' '-'
%left '*'
%nonassoc UMINUS
%start line
/* simbolo sentencial */
%%
line:
| line term
;
term: NEWLINE
| expr NEWLINE
| error NEWLINE
{printf("%d\n", $1);}
{yyerror("Error en la expresion");
}
;
expr: INTEGER {$$ = intval;}
| expr '+' expr {$$ = $1 +
| expr '-' expr {$$ = $1 | expr '*' expr {$$ = $1 *
| '(' expr ')' {$$ = $2;}
| '-' expr %prec UMINUS {$$
;
$3;}
$3;}
$3;}
= - $2;}
%%
main()
{
yyparse();
}
yyerror(char *s)
{
printf("Hubo un error : %s en el simbolo %c\n", s, yytext[0]);
}
5
Compiladores / Guía VI / Ciclo 02 - 2016
Para compilar el archivo, se hace lo siguiente:
NOTA: El ejercicio da una advertencia, pero funciona correctamente.
Ejemplo 2. Calculadora con expresiones matemáticas complejas.
Creando el archivo example2.l
%{
#include "y.tab.h"
#include <stdlib.h>
#include <stdio.h>
%}
white
digit
integer
[ \t]+
[0-9]
{digit}+
%%
{white}
{ /* Ignoramos espacios en blanco */ }
"exit"|"quit"|"bye" {printf("Terminando programa\n");exit(0);}
{integer}
{
yylval.dval=atof(yytext);
return(NUMBER);
}
"+"
"-"
"*"
"/"
"^"
"("
")"
"\n"
%%
return(SUMA);
return(RESTA);
return(MULTI);
return(DIVIDE);
return(EXPO);
return(IZQ_PAREN);
return(DERE_PAREN);
return(END);
6
Compiladores / Guía VI / Ciclo 02 - 2016
Creando el archivo example2.y
%{
#include <stdio.h>
#include <math.h>
%}
%union{
double dval;
}
%token
%token
%token
%token
<dval> NUMBER
SUMA
RESTA
IZQ_PAREN
END
%left
%left
%left
%right
SUMA
MULTI
NEG
EXPO
MULTI
DIVIDE
DERE_PAREN
EXPO
RESTA
DIVIDE
%type <dval> Expression
%start Input
%%
Input:
Line
| Input Line
;
Line:
END
| Expression END
;
Expression:
|
|
|
|
|
|
|
;
NUMBER
{ printf("Result: %f\n",$1); }
{ $$=$1; }
Expression SUMA Expression
{ $$=$1+$3; }
Expression RESTA Expression
{ $$=$1-$3; }
Expression MULTI Expression
{ $$=$1*$3; }
Expression DIVIDE Expression { $$=$1/$3; }
RESTA Expression %prec NEG
{ $$=-$2; }
Expression EXPO Expression
{ $$=pow($1,$3); }
IZQ_PAREN Expression DERE_PAREN { $$=$2; }
%%
int yyerror(char *s) {
printf("%s\n",s);
}
7
Compiladores / Guía VI / Ciclo 02 - 2016
int main(void) {
yyparse();
}
Para compilar:
Ejercicios propuestos
1. Modificar el ejemplo 2 de manera que se pueda dividir. Además, se debe validar la división entre cero. Cuando
exista una operación de ese tipo, por ejemplo, 8/0, el programa debe enviar un mensaje: “Error al dividir”.
Investigación Complementaria
1. Agregar a la calculadora las funciones trigonométricas del seno y el coseno.
Bibliografía

Manuel Alfonseca Moreno, Marina de la Cruz Echeandía, Alfonso Ortega de la Puente, Estrella Pulido Cañabate,
Compiladores: Teoría y Práctica, Pearson Educación, S.A., Madrid, 2006
Descargar