Вы находитесь на странице: 1из 23

Teoría de la computación

Sesión 08: Recuperación de errores en Bison

I. OBJETIVOS

- Mostrar la recuperación de errores en Bison


- Utilizar el token error en las gramaticas

II. TEMAS A TRATAR

● Token error
● Expresión reservada yyerrok

III. MARCO TEORICO

En un analizador ordinario, los valores semánticos del token deben almacenarse en la variable
global yylval. Cuando esté usando un solo tipo de valores semánticos, yylval tiene ese tipo. Así,
si el tipo es int (por defecto), podría escribir esto en yylex:

...
yylval = valor; /* Pone valor en la pila de Bison. */
return INT; /* Devuelve el tipo del token. */
...

Cuando esté utilizando varios tipos de datos, el tipo de yylval es una union compuesta a partir
de la declaración %union. Así cuando almacene un valor de token, debe utilizar el miembro
apropiado de la union. Si la declaración %union tiene el siguiente aspecto:

%union {
int intval;
double val;
symrec *tptr;
}

Entonces el código en yylex podría ser así:

...
yylval.intval = valor; /* Pone el valor en la pila de Bison. */
return INT; /* Devuelve el tipo del token. */
...

Errores y debug
Para poder resolver los conflictos de recursividad infinita, o saber cómo está actuando la pila
interna de nuestro compilador, es necesario hacer un proceso de debugging. Sobre la
calculadora que programamos, modificaremos el parser para que nos muestre cómo está
haciendo las reducciones y shiftings. Para lo cual debemos usar la macro YYDEBUG así:

Nuestra sección de reglas la va a definir:

%{
Teoría de la computación

#include <stdio.h>
#define YYDEBUG 1
extern int yylex(void);
extern char *yytext;
void yyerror(char *s);
%}
%token NUM
%token VAR
%token MAS
%token EOL

%left '+'
%left '-'
%left '*'
%left '/'

%union{
char cadena[100];
int valor;
}

%%
El resultado de la operación 5*6+3 sería entonces:

El resultado de la operación 5*6+3 sería entonces:


Starting parse
Entering state 0
Reading a token: 5*6+3
Next token is token NUM ()
Shifting token NUM ()
Entering state 1
Reducing stack by rule 8 (line 36):
$1 = token NUM ()
-> $$ = nterm expnum ()
Stack now 0
Entering state 5
Reading a token: Next token is token '*' ()
Error sintactico syntax errorError: popping nterm expnum ()
Stack now 0
Cleanup: discarding lookahead token '*' ()
Stack now 0

Recuperación de errores

Un programa no debería terminar su análisis ante un error. Por ejemplo, un compilador debe
recuperarse lo necesario como para que analice el resto del archivo de entrada y mostrar los
demás errores.
Teoría de la computación

Usando el yyparse, devolviendo 1 ante un error, haría que la función invocadora ignore el resto
de la línea de entrada cuando suceda (y entonces llamar a yyparse de nuevo).
Esto es inadecuado para un compilador, debido a que se obvia todo el contexto sintáctico
desde el comienzo hasta donde se encontró el error. Un error de sintaxis profundo denbro de
una función del fichero de entrada del compilador no debe producir que el compilador trate la
línea siguiente como el principio de un archivo.

Puede definir cómo recuperarse de un error de sintaxis escribiendo reglas para reconocer el
token especial error. Este es un símbolo terminal que siempre se define (no necesita
declararlo) y reservado para tratamiento de errores.
El analizador de Bison genera un token error siempre que ocurra un error de sintaxis; si ha
facilitado una regla que reconozca este token en el contexto actual, el analizador puede
continuar.

Por ejemplo:

stmnts: /* cadena vacía */


| stmnts '\n'
| stmnts exp '\n'
| stmnts error '\n'

La cuarta regla en este ejemplo dice que un error seguido de una nueva línea forma una
adición válida a cualquier stmnts.

La regla de recuperación de errores, interpretada estrictamente, cualquier error que ocurra en


medio de una expresión, este aplica la secuencia precisa de una stmnts, un error y una nueva
línea. Si aparece un error en medio de una expresión, probablemente existan tokens
adicionales y subexpresiones por leer antes de la nueva línea. De manera que la regla no es
aplicable de la forma habitual.

Pero Bison puede forzar la situación para que se ajuste a la regla, descartando parte del
contexto semántico y parte de la entrada. Primero descarta estados y objetos de la pila hasta
que regrese a un estado en el que el token error sea aceptable. (Esto quiere decir que las
subexpresiones ya analizadas son descartadas, retrocediendo hasta el último stmnts
completo.) En este punto el token error puede desplazarse. Entonces, si el antiguo token de
preanálisis no es aceptable para ser desplazado, el analizador lee tokens y los descarta hasta
que encuentre un token que sea aceptable. En este ejemplo, Bison lee y descarta entrada
hasta la siguiente línea de manera que la cuarta regla puede aplicarse.

La elección de reglas de error en la gramática es una elección de estrategias para la


recuperación de errores. Una estrategia simple y útil es sencillamente saltarse el resto de la
línea de entrada actual o de la sentencia actual si se detecta un error:

stmnt: error ';' /* ante un error, saltar hasta que se lea ';' */

También es útil recuperar el delimitador de cierre que empareja con un un delimitador de


apertura que ya ha sido analizado. De otra manera el delimitador de cierre probablemente
aparecerá como sin emparejar, y generará otro, mensaje de error:
Teoría de la computación

primary: '(' expr ')'


| '(' error ')'
...
;

Las estrategias de recuperación de errores son por fuerza adivinanzas. Cuando estas adivinan
mal, un error de sintaxis a menudo provoca otro. En el ejemplo anterior, la regla de
recuperación de errores sospecha que el error es debido a una mala entrada en un stmnt.
Suponga que en su lugar se inserta un punto y coma accidental en medio de un stmt válido.
Después de recuperarse del primer error con la regla de recuperación de errores, se
encontrará otro error de sintaxis directamente, ya que el texto que sigue al punto y coma
accidental también es una stmnt inválida.

Para prevenir una cascada de mensajes de error, el analizador no mostrará mensajes de error
para otro error de sintaxis que ocurra poco después del primero; solamente después de que se
hayan desplazado con éxito tres tokens de entrada consecutivos se reanudarán los mensajes
de error.

Note que las reglas que aceptan el token error podrían tener acciones, al igual que cualquiera
de las otras reglas pueden tenerlas.

Puede hacer que los mensajes de error se reanuden inmediatamente usando la macro yyerrok
en una acción. Si lo hace en la acción de la regla de error, no se suprimirá ningún mensaje de
error. Esta macro no requiere ningún argumento; `yyerrok;' es una sentencia válida de C.

El token de preanálisis anterior se reanaliza inmediatamente después de un error. Si este no es


aceptable, entonces la macro yyclearin podría utilizarse para limpiar ese token. Escriba la
sentencia `yyclearin;' en la acción de la regla de error.

Por ejemplo, suponga que ante un error de análisis, se llama a una rutina de manejo de errores
que avanza el flujo de entrada en algún punto donde el análisis prodría comenzar de nuevo. El
próximo símbolo devuelto por el analizador léxico será probablemente correcto. El token de
preanálisis anterior convendría que se descartara con `yyclearin;'.

La macro YYRECOVERING representa una expresión que tiene el valor 1 cuando el analizador se
está recuperando de un error de sintaxis, y 0 durante el resto del tiempo. Un valor de 1 indica
que actualmente los mensajes de error se están suprimiendo para nuevos errores de sintaxis.

IV. ACTIVIDADES (La práctica tiene una duración de 2 horas)


1. Considera la siguiente gramática

Archivo: sintáctico.y
%{
#include <stdio.h>
#define YYDEBUG 1
extern int yylex(void);
Teoría de la computación

extern char *yytext;


void yyerror(char *s);
%}
%token YY ZZ

%%
slist : slist stmt ';' { printf("slist stmt\n"); }
| stmt ';' { printf("stmt\n"); }
| error ';' { printf("ERROR!!!\n"); yyerrok; }
;
stmt : ZZ stmt
| ZZ
;
%%
void yyerror(char *s)
{
printf("Error sintactico %s",s);
}
int main(int argc,char **argv)
{
yydebug=1;
yyparse();
return 0;
}

Con la entrada “zz zz yy zz zz”, tiene un error en el medio. Esperamos a que Bison pueda reducir
las primeras “zz’s” y llegar al tocken yy, luego pondrá el token error hasta que encuentre un
carácter ; Si lo hay. Ejecutar el debug sobre esta gramática para verificar como lo interpreta

Archivo: léxico.l

%{
#include"sintactico.tab.h"
/*
externt yylval
*/

%}
%%
YY { return(YY);}
ZZ { return(ZZ);}
"" ;
%%
int yywrap(){ return 0;}

2. Considera la siguiente gramática:

%%
Teoría de la computación

slist : slist stmt ';' { printf("stmt\n"); }


| stmt ';' { printf("stmt\n"); }
;

stmt : ZZ
| '(' stmt ')'
| '(' error ')'
;
%%

El archivo sintáctico.y sería:

%{
#include <stdio.h>
#define YYDEBUG 1
extern int yylex(void);
extern char *yytext;
void yyerror(char *s);
%}
%token YY ZZ

%%
slist : slist stmt ';' { printf("stmt\n"); }
| stmt ';' { printf("stmt\n"); }
;

stmt : ZZ
| '(' stmt ')'
| '(' error ')'
;
%%
void yyerror(char *s)
{
printf("Error sintactico %s",s);
}
int main(int argc,char **argv)
{
yydebug=1;
yyparse();
return 0;
}

Se ingresa los siguientes elementos:

zz ;
( zz ) ;
( zz ;
zz ;
Teoría de la computación

zz ;
zz );
zz ;

¿Qué errores se están manejando?, advertimos que la gramática espera siempre el cierre de
paréntesis si encuentra un error, y de esta forma muchos statements son descartados, si el
usuario olvida cerrar los paréntesis

3. ¿Qué errores se están manejando?. Si el input fuera:

zz ;
Starting parse
Entering state 0
Reading a token: Next token is 258 (ZZ)
Shifting token 258 (ZZ), Entering state 1
Reducing via rule 3 (line 32), ZZ -> stmt
state stack now 0
Entering state 4
Reading a token: Next token is 41 (')')
ERROR lineno(1):parse error, expecting `';''. I got: )
Error: state stack now 0
The error on the closing parenthesis is the last thing the parser does.

Pero si fuera “zz );”. No se cubrirían los errores que no estén entre paréntesis ¿qué pasaría
entonces?

4. Utilizando bison, crear las siguientes reglas de producción de gramáticas A => aA, A=>a y
adicionar el token error a la parte no terminal

lexico.l

%{
#include "sintactico.tab.h"
%}

%%

[aA] {return A;}


\n {return NL;}
. {return yytext[0];}

%%
int yywrap(){ return 1;}

sintactico.y
%{

#include <stdio.h>
Teoría de la computación

int yylex();
int yyerror (char *s);

%}

/* Declaraciones de BISON */

%token A
%token NL

%%

cadena: S NL {printf("Se imprimio una cadena \n");


exit(0);}
|error {printf("Error!! \n");exit(0);}
;
S: S A |
;

%%
int yyerror (char *s)
{

printf ("%s\n", s);


return 0;
}
void main(){
yyparse();
yylex();
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c

5. Utilizando bison, crear las siguientes reglas de producción de gramáticas A => aA, A=>a y
adicionar el token error a la parte terminal
lexico.l
%{
#include "sintactico.tab.h"
%}

%%
Teoría de la computación

[aA] {return A;}


\n {return NL;}
. {return yytext[0];}

%%
int yywrap(){ return 1;}

sintactico.y

%{

#include <stdio.h>
int yylex();
int yyerror (char *s);

%}

/* Declaraciones de BISON */

%token A
%token NL

%%

cadena: S NL {printf("Se imprimio una cadena \n");


exit(0);}
;
S: S A | error {printf("Error!! \n");exit(0);}
;

%%
int yyerror (char *s)
{
Teoría de la computación

printf ("%s\n", s);


return 0;
}
void main(){
yyparse();
yylex();
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c

6. Utilizando bison, crear las siguientes reglas de producción de gramáticas A => aA, A=>AA, A=>a,
adicionar el token error sin la palabra “exit(0)” verificar si se sigue tomando error

léxico.l
%{
#include "sintactico.tab.h"
%}

%%

[aA] {return A;}


[ \t] ;
quit |
q |
\n {return NL;}
. {return yytext[0];}

%%
int yywrap(){ return 1;}

sintáctico.y
%{

#include <stdio.h>
int yylex();
int yyerror (char *s);

%}
Teoría de la computación

/* Declaraciones de BISON */

%token A
%token NL

%%

cadena: S NL {printf("Se imprimio una cadena \n");


exit(0);}
|error {printf("Error!! \n");}
;
S: S A A |
;

%%
int yyerror (char *s)
{

printf ("%s\n", s);


return 0;
}
void main(){
yyparse();
yylex();
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c

7. Utilizando bison, crear las siguientes reglas de producción de gramáticas A => aA, A=>AA, A=>a,
adicionar el token error sin la palabra “exit(0)” y en la primera producción al lado del token NL
verificar si se sigue tomando error

léxico.l
%{
#include "sintactico.tab.h"
%}

%%

[aA] {return A;}


[ \t] ;
quit |
q |
\n {return NL;}
. {return yytext[0];}
Teoría de la computación

%%
int yywrap(){ return 1;}

sintáctico.y
%{

#include <stdio.h>
int yylex();
int yyerror (char *s);

%}

/* Declaraciones de BISON */

%token A
%token NL

%%

cadena: S error NL {printf("Se imprimio una cadena \n");


exit(0);}
|error {printf("Error!! \n");}
;
S: S A A |
;

%%
int yyerror (char *s)
{

printf ("%s\n", s);


return 0;
}
void main(){
yyparse();
yylex();
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c

8. Utilizando bison, crear las siguientes reglas de producción de gramáticas A => aA, A=>AA, A=>a,
adicionar el token error sin la palabra “exit(0)” y en la primera producción al principio de la
cadena verificar si se sigue tomando error
lexico.l
%{
Teoría de la computación

#include "sintactico.tab.h"
%}

%%

[aA] {return A;}


[ \t] ;
quit |
q |
\n {return NL;}
. {return yytext[0];}

%%
int yywrap(){ return 1;}

sintactico.y
%{

#include <stdio.h>
int yylex();
int yyerror (char *s);

%}

/* Declaraciones de BISON */

%token A
%token NL

%%

cadena: S NL error {printf("Se imprimio una cadena \n");


exit(0);}
|error {printf("Error!! \n");}
;
S: S A A |
;

%%
int yyerror (char *s)
{

printf ("%s\n", s);


return 0;
}
void main(){
yyparse();
Teoría de la computación

yylex();
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c

9. Utilizando bison, crear las siguientes reglas de producción de gramáticas A => aA, A=>AA, A=>a,
adicionar el token error sin la palabra “exit(0)” como una producción de cadena, al final escribir
la palabra reservada yyerrok

lexico.l
%{
#include "sintactico.tab.h"
%}

%%

[aA] {return A;}


[ \t] ;
quit |
q |
\n {return NL;}
. {return yytext[0];}

%%
int yywrap(){ return 1;}

sintactico.y
%{

#include <stdio.h>
int yylex();
int yyerror (char *s);

%}

/* Declaraciones de BISON */

%token A
%token NL

%%

cadena: error S NL {printf("Se imprimio una cadena \n");


exit(0);}
|error {printf("Error!! \n");}
Teoría de la computación

;
S: S A A |
;

%%
int yyerror (char *s)
{

printf ("%s\n", s);


return 0;
}
void main(){
yyparse();
yylex();
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c

10. Utilizando bison, crear las siguientes reglas de producción de gramáticas A => aA, A=>AA, A=>a,
adicionar el token error como una producción de cadena, al final escribir la palabra reservada
yyerrok y la función “exit(0)”

lexico.l
%{
#include "sintactico.tab.h"
%}

%%

[aA] {return A;}


[ \t] ;
quit |
q |
\n {return NL;}
. {return yytext[0];}

%%
int yywrap(){ return 1;}

sintactico.y
%{

#include <stdio.h>
int yylex();
int yyerror (char *s);
Teoría de la computación

%}

/* Declaraciones de BISON */

%token A
%token NL

%%

cadena: S NL {printf("Se imprimio una cadena \n");


exit(0);}
|error {printf("Error!! \n");yyerrok;}
;
S: S A A |
;

%%
int yyerror (char *s)
{

printf ("%s\n", s);


return 0;
}
void main(){
yyparse();
yylex();
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c

11. Utilizando bison, crear las siguientes reglas de producción de gramáticas A => aA, A=>AA, A=>a,
adicionar el token error junto con el token NL como una producción de cadena, al final escribir la
palabra reservada yyerrok y la función “exit(0)”

lexico.l
%{
#include "sintactico.tab.h"
%}

%%

[aA] {return A;}


[ \t] ;
quit |
q |
Teoría de la computación

\n {return NL;}
. {return yytext[0];}

%%
int yywrap(){ return 1;}

sintactico.y
%{

#include <stdio.h>
int yylex();
int yyerror (char *s);

%}

/* Declaraciones de BISON */

%token A
%token NL

%%

cadena: S NL {printf("Se imprimio una cadena \n");


exit(0);}
|error NL {printf("Error!! \n");yyerrok;exit(0);}
;
S: S A A |
;

%%
int yyerror (char *s)
{

printf ("%s\n", s);


return 0;
}
void main(){
yyparse();
yylex();
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c
12. Utilizando bison, crear reglas de producción de gramáticas que reconozcan la expresión número
+ número, adicionar el token error como una producción de cadena
Teoría de la computación

lexico.l
%{
#include "sintactico.tab.h"
#include "string.h"
int yyparse();

%}
numero [0-9]+
%%
{numero} {return(NUM);};
"+" {return('+');};
"\n" {return('\n');};
.;
%%
int yywrap(){ return 0;}

void main(){
yyparse();
}

sintactico.y
%{

#include <stdio.h>
extern int yylex(void);
extern int linea;
void yyerror(char *s);
%}

%union {
char cadena[100];
int numero;
}
%token <numero> NUM
%token '+'
%token '\n'

%%
entrada: expnum '\n' {printf("Expresion reconocida \n");}
|expnum '+' expnum '\n' {printf("Es una operacion entre numeros\n");}
|error '\n' {printf("Error!!\n");}
;
expnum: NUM {printf("Es un numero \n");}
;

%%

void yyerror(char *s)


Teoría de la computación

{
printf("%s",s);
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c

13. Utilizando bison, crear reglas de producción de gramáticas que reconozcan la expresión número
+ número, adicionar el token error como una producción de cadena y al final escribir la palabra
reservada yyerrok

léxico.l
%{
#include "sintactico.tab.h"
#include "string.h"
int yyparse();

%}
numero [0-9]+
%%
{numero} {return(NUM);};
"+" {return('+');};
"\n" {return('\n');};
.;
%%
int yywrap(){ return 0;}

void main(){
yyparse();
}

sintactico.y
%{

#include <stdio.h>
extern int yylex(void);
extern int linea;
void yyerror(char *s);
%}

%union {
char cadena[100];
int numero;
}
%token <numero> NUM
%token '+'
%token '\n'
Teoría de la computación

%%
entrada: expnum '\n' {printf("Expresion reconocida \n");}
|expnum '+' expnum '\n' {printf("Es una operacion entre numeros\n");}
|error '\n' {printf("Error!!\n");yyerrok;}
;
expnum: NUM {printf("Es un numero \n");}
;

%%

void yyerror(char *s)


{
printf("%s",s);
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c

14. Utilizando bison, crear reglas de producción de gramáticas que reconozcan la expresión número
+ número, adicionar el token error dentro de la suma de números

lexico.l
%{
#include "sintactico.tab.h"
#include "string.h"
int yyparse();

%}
numero [0-9]+
%%
{numero} {return(NUM);};
"+" {return('+');};
"\n" {return('\n');};
.;
%%
int yywrap(){ return 0;}

void main(){
yyparse();
}

sintactico.y
%{

#include <stdio.h>
extern int yylex(void);
Teoría de la computación

extern int linea;


void yyerror(char *s);
%}

%union {
char cadena[100];
int numero;
}
%token <numero> NUM
%token '+'
%token '\n'

%%
entrada: expnum '\n' {printf("Expresion reconocida \n");}
|expnum '+' expnum '\n' {printf("Es una operacion entre numeros\n");}
|expnum '+' error '\n' {printf("Error en la suma\n");}
;
expnum: NUM {printf("Es un numero \n");}
;

%%

void yyerror(char *s)


{
printf("%s",s);
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c

15. Utilizando bison, crear reglas de producción de gramáticas que reconozcan la expresión número
+ número, adicionar el token error como una producción de cadena y al final escribir la palabra
reservada yyerrok

lexico.l
%{
#include "sintactico.tab.h"
#include "string.h"
int yyparse();

%}
numero [0-9]+
%%
{numero} {return(NUM);};
"+" {return('+');};
"\n" {return('\n');};
.;
Teoría de la computación

%%
int yywrap(){ return 0;}

void main(){
yyparse();
}

sintactico.y
%{

#include <stdio.h>
extern int yylex(void);
extern int linea;
void yyerror(char *s);
%}

%union {
char cadena[100];
int numero;
}
%token <numero> NUM
%token '+'
%token '\n'

%%
entrada: expnum '\n' {printf("Expresion reconocida \n");}
|expnum '+' expnum '\n' {printf("Es una operacion entre numeros\n");}
|expnum '+' error '\n' {printf("Error en la suma\n");yyerrok;}
;
expnum: NUM {printf("Es un numero \n");}
;

%%

void yyerror(char *s)


{
printf("%s",s);
}

lex lexico.l
bison -d sintactico.y
gcc lex.yy.c sintactico.tab.c

VII. Ejercicios
1. Crear un ejemplo donde se use yyclearin
2. Crear una gramática con la palabra error para una calculadora que sume, reste, multiplique y
divida usando paréntesis
Teoría de la computación

3. Se puede usar el token error en el analizador lexico, defina porque


4. Agregar una producción a la suma para producir varias sumas y agregar el token error junto
con yyerrok
2.Hacer un informe de los ejercicios
VII. Bibliografía y referencias

1. https://www.gnu.org/software/bison/

Вам также может понравиться