CES-41 Teoria Cap 5-d
Download
Report
Transcript CES-41 Teoria Cap 5-d
5.6 – Complementos de Yacc
5.6.1 – Usando Yacc com gramáticas ambíguas
Gramáticas ambíguas causam conflitos durante a
construção de um analisador sintático pelo Yacc
Os tipos de conflitos são:
Deslocamento-redução: o analisador tanto pode
deslocar um átomo para a pilha, como pode reduzir o
topo da pilha por uma produção
Redução-redução: o analisador pode usar mais de
uma produção para reduzir o topo da pilha
Há regras para a solução de ambiguidades
Yacc gera analisadores mais eficientes para gramáticas
ambíguas do que para gramáticas não ambíguas
Exemplo 5.34: Programa para análise da seguinte gramática
ambígua para cálculo de expressões:
EE+E | E-E | E*E | E/E | (E) | -E
| num
Com a seguinte precedência de operadores:
‘*’ e ‘/’ tem precedência sobre ‘+’ e ‘-’
Menos-unário tem precedência sobre ‘*’ e ‘/’
O programa deve calcular expressões de valor real
EE+E | E-E | E*E | E/E | (E) | -E
| num
A gramática é ambígua pois para a sentença 5 + 8 * 3 tem
duas árvores sintáticas
E
E
5
E
+
E
E
8
*
E
E
E
3
5
+
*
E
8
E
3
Programa com regras para eliminação de ambiguidades e
comandos de escrita para mostrar as reduções feitas:
%{
#include
#include
#define
%}
%token
%left
%left
%right
%%
<stdio.h>
<ctype.h>
YYSTYPE double
NUM
'+' '-'
'*' '/'
MENUN
Declarações para
solução de
ambiguidades
line
:
;
expr :
|
|
expr '$'
{printf ( "Valor: %g\n", $1);
return 0;}
expr '+' expr
{$$ = $1 + $3;
printf ("Regra1: %g = %g + %g\n", $$, $1, $3);}
expr '-' expr
{$$ = $1 - $3;
printf ("Regra2: %g = %g - %g\n", $$, $1, $3);}
expr '*' expr
{$$ = $1 * $3;
printf ("Regra3: %g = %g * %g\n", $$, $1, $3);}
|
|
|
|
;
%%
expr '/' expr
{$$ = $1 / $3;
printf ("Regra4: %g = %g / %g\n", $$, $1, $3);}
Dispositivo para solução
'(' expr ')'
de ambiguidades
{$$ = $2;
printf ("Regra5: %g = ( %g )\n", $$, $2);}
'-' expr
%prec MENUN
{$$ = -$2;
printf ("Regra6: %g = - %g \n", $$, $2);}
NUM
{$$ = $1; printf ("Regra7: %g = %g\n", $$, $1);}
yylex () {
int c;
do
c = getchar ();
while (c == ' ');
if (c == '.' || isdigit (c)) {
ungetc (c, stdin);
scanf ("%lf", &yylval) ;
return (NUM);
}
return c;
}
A seguir, são descritas regras e dispositivos de programação
do Yacc para solucionar ambiguidades
Por default, o conflito redução-redução é resolvido em
favor da produção que aparece primeiro na lista de
produções
Por default, o conflito deslocamento-redução é resolvido
em favor do deslocamento
No exemplo anterior, não fosse pelas regras de precedência
a serem comentadas logo a seguir, a forma sentencial:
Pilha
expr ‘*’ expr
Entrada
‘+’ 3
provocaria o deslocamento do ‘+’ para o topo da pilha
A soma seria realizada antes da multiplicação
Nas declarações do programa Yacc, pode-se atribuir
precedências e associatividades aos terminais da gramática;
sejam as declarações:
%left
%left
%right
'+' '-'
'*' '/'
'~' (menos unário)
Diferentes
'+' e '-' têm mesma precedência
'*' e '/' têm mesma precedência
A precedência cresce de cima para baixo
No programa aparece:
%right
MENUN
Cada produção em Yacc também tem associada a si uma
precedência e uma associatividade:
Por default, é a precedência e associatividade de seu
terminal mais a direita
Exemplo: a precedência e associatividade da produção
A a B c D e F G será a de ‘e’
Na decisão (deslocar ‘a’) ou (reduzir por A) haverá
deslocamento se ‘a’ tiver maior precedência que A, e
vice-versa
Na forma sentencial:
Pilha
expr ‘*’ expr
Entrada
‘+’ 3
haverá redução, pois a produção
expr ‘*’ expr tem a precedência de ‘*’
que é maior que a de ‘+’
Na declaração %left ‘-’, o operador ‘-’ é associativo à
esquerda; a seguinte forma sentencial causa redução:
Pilha
expr ‘-’ expr
Entrada
‘-’ 3
A expressão 5-3-1 = 2-1 = 1
Se fosse %right ‘-’, causaria deslocamento
O cálculo de 5-3-1 seria 5-3-1 = 5-2 = 3
Pode-se também forçar uma produção a ter uma
determinada precedência
O programa visto apresenta:
%left
'+' '-'
%left
'*' '/'
%right
MENUN
-------expr | '-' expr %prec MENUN
Esta produção é forçada a ter precedência maior que ‘+’, ‘-’,
‘*’, ‘/’
Exemplo: o programa visto produziu os seguintes resultados
experimentais:
1) Entrada: 5-2-2$
Regra7: 5 = 5
Regra7: 2 = 2
Regra2: 3 = 5 - 2
Regra7: 2 = 2
Regra2: 1 = 3 - 2
Valor: 1
2) Trocando %left '+' '-' por
%right '+' '-':
Entrada: 5-2-2$
Regra7: 5 = 5
Regra7: 2 = 2
Regra7: 2 = 2
Regra2: 0 = 2 - 2
Regra2: 5 = 5 - 0
Valor: 5
Exemplo: o programa visto produziu os seguintes resultados
experimentais:
3) Destrocando:
Entrada: 5*-(3+2)$
Regra7: 5 = 5
Regra7: 3 = 3
Regra7: 2 = 2
Regra1: 5 = 3 + 2
Regra5: 5 = ( 5 )
Regra6: -5 = - 5
Regra3: -25 = 5 * -5
Valor: -25
4) Entrada: -3+8$
Regra7: 3 = 3
Regra6: -3 = - 3
Regra7: 8 = 8
Regra1: 5 = -3 + 8
Valor: 5
5) Trocando %right MENUN
%left '+' '-'
%left '*' '/'
Entrada: -3+8$
Regra7: 3 = 3
Regra7: 8 = 8
Regra1: 11 = 3 + 8
Regra6: -11 = - 11
Valor: -11
5.6.2 – Notificação e tratamento de erros no Yacc
Em Yacc, o tratamento de erros pode ser feito usando
produções de erros entre as produções da gramática
Primeiramente deve-se decidir quais não-terminais da
gramática terão essas produções
Esses podem ser não-terminais que possuam átomos em
suas produções normais, para que mensagens notificando a
falta deles possam ser emitidas
Ou então outros estratégicos geradores de expressões,
comandos, sub-programas, etc.
Seja A um não-terminal escolhido; introduz-se, na gramática,
produções da forma A error
e são cadeias de terminais e ou não-terminais, vazias ou
não
error é uma palavra reservada do Yacc (um token especial)
Na montagem do analisador, as produções de erros são
tratadas como produções normais da gramática
No entanto, quando o analisador produzido encontra um
erro, a manipulação dos estados e da pilha é diferente do
normal
Encontrando um erro, desempilha-se símbolos e estados, até
encontrar um estado no topo da pilha, cujo conjunto de
itens contenha um item do tipo A . error
erro
Entrada
Estado com
item do tipo
A . error
Programa
analisador
LR
0
Pilha
Tabelas LR
Saída
Encontrando um erro, desempilha-se símbolos e estados, até
encontrar um estado no topo da pilha, cujo conjunto de
itens contenha um item do tipo A . error
erro
Entrada
Estado com
item do tipo
A . error
Programa
analisador
LR
0
Pilha
Tabelas LR
Saída
Desloca-se o átomo fictício error para o topo da pilha, como se
tivesse visto um erro na entrada
Estado com
item do tipo
A error .
Estado com
item do tipo
A . error
erro
Entrada
err
or
Programa
analisador
LR
0
Pilha
Tabelas LR
Saída
Se for vazia, reduz-se imediatamente para A e uma eventual
ação semântica pode ocorrer (notificação e tratamento de
erros programado)
Estado com
item do tipo
A error .
Estado com
item do tipo
A . error
erro
Entrada
err
or
Programa
analisador
LR
0
Pilha
Tabelas LR
Saída
Se for vazia, reduz-se imediatamente para A e uma eventual
ação semântica pode ocorrer (notificação e tratamento de
erros programado)
erro
Entrada
A
0
Pilha
Programa
analisador
LR
Tabelas LR
Saída
Se não for vazia:
erro
Entrada
A
0
Pilha
Programa
analisador
LR
Tabelas LR
Saída
Se não for vazia:
o analisador observa os próximos símbolos de entrada, até
encontrar uma sub-cadeia redutível para
redutível
para
Estado com
item do tipo
A error .
Estado com
item do tipo
A . error
erro
Entrada
err
or
Programa
analisador
LR
0
Pilha
Tabelas LR
Saída
Essa sub-cadeia é deslocada para a pilha provocando a redução
para A
redutível
para
Estado com
item do tipo
A error .
Estado com
item do tipo
A . error
erro
Entrada
err
or
Programa
analisador
LR
0
Pilha
Tabelas LR
Saída
Essa sub-cadeia é deslocada para a pilha provocando a redução
para A
redutível
para
O caso mais
comum é o de
ser uma sequência
de terminais
erro
Entrada
A
0
Pilha
Programa
analisador
LR
Tabelas LR
Saída
Também uma eventual ação semântica pode ocorrer
(notificação e tratamento de erros programado)
redutível
para
O caso mais
comum é o de
ser uma sequência
de terminais
erro
Entrada
A
0
Pilha
Programa
analisador
LR
Tabelas LR
Saída
O analisador deixa o estado de erro e volta ao normal,
através da execução de uma macro: yyerrok
O exemplo a seguir é experimental e relativamente simples:
Mostra os casos de e serem vazios e compostos de
um e dois terminais
Mostra também o uso da macro yyerrok
A literatura (livros, Internet, etc.) apresenta outros dispositivos
para recuperação de erros no Yacc
Exemplo: programa para a seguinte gramática:
Função : Cabeçalho Declarações
Cabeçalho : ID ( )
Declarações : { ListDecl }
ListDecl : ε | ListDecl Declaração
Declaração : Tipo ListElemDecl ;
Tipo : INT | FLOAT | CHAR | BOOL
ListElemDecl : ElemDecl
| ListElemDecl , ElemDecl
ElemDecl : ID
Analisador léxico:
delim
ws
letra
digito
id
%%
{ws}
bool
char
float
int
{id}
"("
")"
"{"
"}"
";"
","
.
%%
[ \t\n\r]
{delim}+
[A-Za-z]
[0-9]
{letra}({letra}|{digito})*
{ ;}
{return BOOL;}
{return CHAR;}
{return FLOAT;}
{return INT;}
{strcpy (yylval.cadeia, yytext); return ID;}
{return ABPAR;}
{return FPAR;}
{return ABCHAV;}
{return FCHAV;}
{return PVIRG;}
{return VIRG;}
{yylval.carac = yytext[0]; return INVAL;}
Analisador sintático:
%{
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
void Esperado (char *);
%}
%union {
char cadeia[50];
char carac;
}
%token
%token
%token
%token
%token
%token
%token
%token
%token
%token
%token
%token
%%
BOOL
CHAR
FLOAT
INT
<cadeia>
ABPAR
FPAR
ABCHAV
FCHAV
PVIRG
VIRG
<carac>
ID
INVAL
Funcao
: Cabecalho Declaracoes
;
Cabecalho : ID ABPAR {printf ("%s \( ", $1);} FPAR
{printf (")\n");}
| ID error {printf ("%s ", $1);
Esperado ("(");}
FPAR {yyerrok; printf (")\n");}
| ID error {printf ("%s\n", $1);
Esperado ("( )"); yyerrok;}
| error {Esperado ("Identificador");}
ABPAR FPAR {yyerrok; printf ("() ");}
| ID ABPAR error {printf ("%s (\n", $1);
Esperado (")"); yyerrok;}
;
Declaracoes :
ABCHAV {printf ("\{\n");} ListDecl
FCHAV {printf ("}\n");}
;
ListDecl
:
| ListDecl Declaracao
;
Declaracao : Tipo ListElemDecl PVIRG
{printf ("; \n");}
| Tipo ListElemDecl error PVIRG
{Esperado("';' "); yyerrok;}
;
Tipo
: INT
{printf ("int ");}
| FLOAT {printf ("float ");}
| CHAR {printf ("char ");}
| BOOL {printf ("bool ");}
;
ListElemDecl : ElemDecl
| ListElemDecl VIRG {printf (", ");}
ElemDecl
;
ElemDecl : ID {printf ("%s ", $1);}
| error ID
{Esperado("Identificador"); yyerrok;}
;
%%
#include "lex.yy.c"
void Esperado (char *s) {
printf ("\n***** Esperado: %s \n", s);
}
A seguir alguns testes e seus resultados
1) Programa para teste: (sem erros)
main ( )
{
int i, j, k, fat;
bool m, n;
float x;
char a;
}
Resultado:
main ( )
{
int i , j , k , fat ;
bool m , n ;
float x ;
char a ;
}
2) Programa para teste:
()
syntax error
{
int i, j, k, fat;
bool m, n;
float x;
char a;
}
Resultado:
***** Esperado: Identificador
() {
int i , j , k , fat ;
bool m , n ;
float x ;
char a ;
}
3) Programa para teste:
abc )
{
int i, j, k, fat;
bool m, n;
float x;
char a;
}
Resultado:
syntax error
abc
***** Esperado: (
)
{
int i , j , k , fat ;
bool m , n ;
float x ;
char a ;
}
4) Programa para teste:
abc (
{
int i, j, k, fat;
bool m, n;
float x;
char a;
}
Resultado:
syntax error
abc (
***** Esperado: )
{
int i , j , k , fat ;
bool m , n ;
float x ;
char a ;
}
5) Programa para teste:
abc
{
int i, j, k, fat;
bool m, n;
float x;
char a;
}
Resultado:
syntax error
abc
***** Esperado: ( )
{
int i , j , k , fat ;
bool m , n ;
float x ;
char a ;
}
6) Programa para teste:
abc ( )
{
int bool i, j, k, fat;
bool m, n
float x;
char a;
}
Resultado:
abc ( )
{
int syntax error
***** Esperado: Identificador
, j , k , fat ;
bool m , n syntax error
***** Esperado: ';'
char a ;
}