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 ; }