440 likes | 557 Views
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:
E N 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 • Yaccgera 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: E E + 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
E E + 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 E + E E * E 5 E * E E + E 3 8 3 5 8
Programa com regras para eliminação de ambiguidades e comandos de escrita para mostrar as reduções feitas: %{ #include <stdio.h> #include <ctype.h> #define YYSTYPE double %} %token NUM %left '+' '-' %left '*' '/' %right MENUN %% Declarações para solução de ambiguidades
line : expr '$' {printf ( "Valor: %g\n", $1); return 0;} ; expr : 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);}
Dispositivo para solução de ambiguidades | expr '/' expr {$$ = $1 / $3; printf ("Regra4: %g = %g / %g\n", $$, $1, $3);} | '(' expr ')' {$$ = $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: 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) • '+' 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 provocaria o deslocamento do ‘+’ para o topo da pilha A soma seria realizada antes da multiplicação Diferentes
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: haverá redução, pois a produção expr ‘*’ exprtem a precedência de ‘*’ que é maior que a de ‘+’
Na declaração %left ‘-’, o operador ‘-’ é associativo à esquerda; a seguinte forma sentencial causa redução: • 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 ‘+’, ‘-’, ‘*’, ‘/’
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:
4) Entrada: -3+8$ Regra7: 3 = 3 Regra6: -3 = - 3 Regra7: 8 = 8 Regra1: 5 = -3 + 8 Valor: 5 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 • Exemplo: o programa visto produziu os seguintes resultados experimentais: 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çõesde 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 0 Programa analisador LR Estado com item do tipo A . error Saída Pilha Tabelas LR
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 0 Programa analisador LR Estado com item do tipo A . error Saída Pilha Tabelas LR
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 . erro Entrada 0 error Programa analisador LR Estado com item do tipo A . error Saída Pilha Tabelas LR
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 . erro Entrada 0 error Programa analisador LR Estado com item do tipo A . error Saída Pilha Tabelas LR
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 0 A Programa analisador LR Saída Pilha Tabelas LR
Se não for vazia: erro Entrada 0 A Programa analisador LR Saída Pilha Tabelas LR
redutível para Se não for vazia: o analisador observa os próximos símbolos de entrada, até encontrar uma sub-cadeiaredutível para Estado com item do tipo A error . erro Entrada 0 error Programa analisador LR Estado com item do tipo A . error Saída Pilha Tabelas LR
redutível para Essa sub-cadeia é deslocada para a pilha provocando a redução para A Estado com item do tipo A error . erro Entrada 0 error Programa analisador LR Estado com item do tipo A . error Saída Pilha Tabelas LR
redutível para Essa sub-cadeia é deslocada para a pilha provocando a redução para A O caso mais comum é o de ser uma sequência de terminais erro Entrada 0 A Programa analisador LR Saída Pilha Tabelas LR
redutível para Também uma eventual ação semântica pode ocorrer (notificação e tratamento de erros programado) O caso mais comum é o de ser uma sequência de terminais erro Entrada 0 A Programa analisador LR Saída Pilha Tabelas LR
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 : ε | ListDeclDeclaração Declaração : TipoListElemDecl ; Tipo : INT | FLOAT | CHAR | BOOL ListElemDecl : ElemDecl | ListElemDecl , ElemDecl ElemDecl : ID
Analisador léxico: delim [ \t\n\r] ws {delim}+ letra [A-Za-z] digito [0-9] id {letra}({letra}|{digito})* %% {ws} { ;} bool {return BOOL;} char {return CHAR;} float {return FLOAT;} int {return INT;} {id} {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]; charcarac; }
%token BOOL %token CHAR %token FLOAT %token INT %token <cadeia> ID %token ABPAR %token FPAR %token ABCHAV %token FCHAV %token PVIRG %token VIRG %token <carac> INVAL %%
Funcao : CabecalhoDeclaracoes ; 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 : | ListDeclDeclaracao ; 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 testese seus resultados
Resultado: main ( ) { int i , j , k , fat ; bool m , n ; float x ; char a ; } 1) Programa para teste: (sem erros) main ( ) { inti, j, k, fat; bool m, n; float x; char a; }
Resultado: syntax error ***** Esperado: Identificador () { int i , j , k , fat ; bool m , n ; float x ; char a ; } 2) Programa para teste: ( ) { inti, 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 ; } 3) Programa para teste: abc ) { inti, 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 ( { inti, 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 { inti, 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 ; } 6) Programa para teste: abc ( ) { intbooli, j, k, fat; bool m, n float x; char a; }