CES-41 COMPILADORES Aulas Práticas - 2015 Capítulo II A Ferramenta Yacc Yacc é um gerador de analisadores sintáticos: Têm como entrada a gramática livre de contexto da linguagem-fonte do compilador e implementa uma função de nome yyparse, que responde se o programa analisado tem ou não erros sintáticos Yacc (Yet Another Compiler-Compiler) do sistema UNIX também possui diversas versões para o sistema DOS (o Yacc do Mingw é uma delas) O analisador gerado é um programa em Linguagem C Programa 2.1: Saída de dados Criar na pasta MinGW/bin um arquivo extensão .y (saida.y, por exemplo) com o seguinte programa: %% prod:{printf ("hello friends!\n");} ; %% Colocar pelo menos uma produção yylex () { Não pode haver função main return 0; Tem de haver função yylex } Executar os seguintes comandos: yacc saida.y gcc y.tab.c main.c yyerror.c -o saida -lfl saida %% prod:{printf ("hello friends!\n");} ; %% yylex () { return 0; } Executar: saida > ppp Abrir o arquivo ppp (No DOS: more ppp) Abrir o arquivo main.c (só executa yyparse e retorna) yyparse é o analisador sintático produzido pelo Yacc Abrir o arquivo y.tab.c e localizar yylex, prod e o printf acima Estrutura de um programa em Yacc: Tal como em Lex, um programa em Yacc é dividido em três partes, a saber: Declarações %% Produções da gramática %% Rotinas auxiliares Produções da gramática: É a parte principal de um programa em Yacc O programa deve conter pelo menos uma produção As produções podem vir acompanhadas de ações escritas na Linguagem C, inseridas em qualquer posição de seu lado direito Produções da gramática: Exemplo: Sejam as produções: Expr Expr OPAD Termo | Termo Termo ID | CTE Em Yacc, com ações opcionais: expr : | ; termo: | ; expr OPAD {comandos em C} termo {comandos em C} termo ID {comandos em C} CTE {comandos em C} Produções da gramática: Ações podem aparecer no lado direito de uma produção vazia Exemplo: yyy : | ; {comandos em C} xxx {comandos em C} zzz A primeira produção de yyy é vazia Declarações: Nelas estão inclusas: Em C, delimitadas por %{ e %}: Declarações de variáveis, tipos, protótipos, etc. - Definições de constantes e macros (define’s) - Inclusão de arquivos sem definição de funções - Declarações do Yacc (fora de %{ e %}) usadas para definir terminais, não-terminais, precedência de operadores, tipos dos atributos, etc. Declarações: Exemplo: para as produções anteriores expr : | ; termo: | ; expr OPAD {comandos em C} termo {comandos em C} termo ID {comandos em C} CTE {comandos em C} os átomos são assim declarados: %token OPAD %token ID %token CTE Rotinas auxiliares: São definições de funções em C, referenciadas nas ações das produções da gramática Podem trazer a inclusão de arquivos com extensão .c; por exemplo, o arquivo lex.yy.c, do Flex Não devem conter a função main, pois essa já vem inclusa no ambiente da ferramenta Devem incluir a função yylex; quando usado com Flex, essa função já vem contida no arquivo lex.yy.c %% prod : {printf ("hello friends!\n");} friends!\n"); return;} ; %% yylex () { 0; return 50; } Fazer yylex retornar algo diferente de zero Colocar um return dentro da ação depois do printf Retomando o programa saida.y %% prod : {printf ("hello friends!\n");} ; %% yylex () { Explicando os fatos return 0; } O analisador gerado pelo Yacc é bottom-up Vai detectando lados direitos de produções e reduzindo-os para seus lados esquerdos Tudo acaba bem quando se consegue chegar ao símbolo inicial %% prod : {printf ("hello friends!\n");} ; %% yylex () { Explicando os fatos return 0; } A única produção desta gramática (prod) é vazia Antes de yyparse pedir um átomo para yylex, ele casa a falta de átomo em suas mãos com seu lado direito yyparse faz a redução para o lado esquerdo e executa a ação no final da produção Chegou ao símbolo inicial %% prod : {printf ("hello friends!\n");} ; %% yylex () { Explicando os fatos return 0; } Em seguida, chama yylex que lhe retorna zero Recebendo zero, ele aceita e retorna main então se encerra %% prod : {printf ("hello friends!\n");} friends!\n"); return;} ; %% yylex () { Explicando os fatos return 50; } Depois de chegar ao símbolo inicial, yyparse só aceita o átomo zero Se yylex lhe retornar algo diferente de zero, ele rejeitará Com o return dentro da ação, yyparse retorna para main antes de chamar yylex Programa 2.2: Entrada de dados Criar um arquivo extensão .y (entra.y, por exemplo) com o seguinte programa: %% ppp: {int i, n; printf ("Digite o numero de repeticoes: "); scanf ("%d", &n); for (i = 1; i <= n; i++) printf ("\nhello friends!"); } ; %% yylex () {return 0;} Executar os seguintes comandos: yacc entra.y gcc y.tab.c yyerror.c main.c -o entra -lfl entra Criar um arquivo entra.dat, colocando nele o número 10 Executar os comandos: entra < entra.dat entra < entra.dat > ppp Abrir o arquivo ppp Esquema de produção de um programa executável usando Yacc, sem auxilio do Flex: Programa 2.3: Reconhecimento de frase Rodar yacc e gcc para um arquivo recfrase.y com o seguinte programa: %% prod : ; 'C' 'O' 'M' 'P' ' ' '1' '6' {printf ("Reconheco!\n"); return;} %% yylex () { return getchar (); } Executar recfrase com: COMP COMP COMP COMP 15 16 17 163 O lado direito das produções tem apenas terminais (tokens) Um caractere entre apóstrofos é considerado um token Programa 2.3: Reconhecimento de frase Rodar yacc e gcc para um arquivo recfrase.y com o seguinte programa: %% prod : ; 'C' 'O' 'M' 'P' ' ' '1' '6' {printf ("Reconheco!\n"); return;} %% yylex () { return getchar (); } Executar recfrase com: COMP COMP COMP COMP 15 16 17 163 Executar com arquivo de dados (recfrase.dat, por exemplo): uma frase de cada vez Executar: recfrase < recfrase.dat > ppp Programa 2.4: Gramática S → ε | a S b %token a Experimente Tokens são convertidos tirar o return; em %token b sem e com defines peloarquivo Yacc de dados %token dolar %token erro %% SS : S dolar {printf ("Fim da analise\n"); return;} ; S : yylex () { | aSb char x; ; x = getchar (); %% while (x == ' ' || x == '\n' || x == '\t' || x == '\r') x = getchar (); printf ("Caractere lido: %c\n", x); if (x == 'a') return a; if (x == 'b') return b; if (x == '$') return dolar; return erro; } Rodar com para arquivo várias cadeias, de dados uma contendo: por vez: aabb$ $ aaabbb$ a$ b$ aaabb$ aabbb$ aba$ ba$ Programa 2.4: Gramática S → ε | a S b %token a Experimente tirar o return; %token b sem e com arquivo de dados %token dolar %token erro %% SS : S dolar {printf ("Fim da analise\n"); return;} ; S : yylex () { Chegando ao símbolo inicial, yyparse | aSb char x; sempre chama yylex, esperando zero ; x = getchar (); %% while (x == ' ' || x == '\n' || x == '\t' || x == '\r') x = getchar (); printf ("Caractere lido: %c\n", x); if (x == 'a') return a; if (x == 'b') return b; if (x == '$') return dolar; return erro; } yylex só retorna quando um caractere é digitado e nunca retorna zero Com fim de arquivo, yylex retorna erro Exercício 2.1: Escrever em Yacc, um analisador sintático para a seguinte gramática geradora da linguagem L abaixo: L = { (a b)n cn (d d)* | n 1 } Gramática: SabAcD AabAc | ε DddD |ε Exercício 2.2: Escrever em Yacc, um analisador sintático para a seguinte gramática geradora da linguagem L abaixo: L = { ai bj | j i 0 } Gramática: SAB AaAb |ε BbB |ε Exercício 2.3: Escrever em Yacc, um analisador sintático para a seguinte gramática geradora de expressões contendo só pares de parêntesis balanceados e nenhum outro caractere Exemplos: ( ), ( ( ) ), ( ) ( ), ( ) ( ( ) ( ( ) ) ) ( ( ) ) Gramática: S (A) | S(A) A ε| S Programa 2.5: Yacc auxiliado por Lex Programa em Flex no arquivo expr01.l delim ws digit num %% {ws} {num} "+" "-" "*" "/" "(" ")" "$" . %% [ \t\n\r] {delim}+ [0-9] {digit}+ { ;} {return {return {return {return {return {return {return {return {return CTE;} OPAD;} OPAD;} OPMULT;} OPMULT;} ABPAR;} FPAR;} DOLAR;} INVAL;} yylex retorna tokens declarados no programa em Yacc Programa em Yacc no arquivo expr01.y %{ #include #include %} %token %token %token %token %token %token %token %% <stdio.h> <stdlib.h> DOLAR CTE OPAD OPMULT ABPAR FPAR INVAL Tokens a serem retornados por yylex line : expr DOLAR {printf("Fim da analise\n"); return;} ; expr : expr OPAD term | term ; term : term OPMULT fat | fat ; fat : CTE | ABPAR expr FPAR ; %% #include "lex.yy.c" Tokens aparecem do lado direito das produções yylex está em lex.yy.c Executar os seguintes comandos: flex expr01.l yacc expr01.y gcc y.tab.c main.c yyerror.c -o expr01 -lfl expr01 (digitar uma expressão correta terminada por ‘$’) expr01 (digitar uma expressão incorreta terminada por ‘$’) Usando arquivo de dados de entrada, yylex retorna zero ao ler fim de arquivo, quando criado pelo Flex Então, dispensa-se o return da produção line Esquema de produção de um programa executável usando Yacc, com auxilio do Flex: Programa 2.6: Uso de atributos (calculadora simples) Programa em Flex no arquivo calc01.l delim ws digit num %% {ws} {num} "+" "-" "*" "/" "(" ")" "$" . %% [ \t\n\r] {delim}+ [0-9] {digit}+ { ;} {yylval {yylval {yylval {yylval {yylval {return {return {return {yylval yylval guarda o atributo de um token yylval é declarado pelo Yacc = atoi(yytext); return CTE;} = MAIS; return OPAD;} = MENOS; return OPAD;} = VEZES; return OPMULT;} = DIV; return OPMULT;} ABPAR;} FPAR;} DOLAR;} = yytext[0]; return INVAL;} Programa em Yacc no arquivo calc01.y %{ #include #include #define #define #define #define %} %token %token %token %token %token %token %token %% <stdio.h> <stdlib.h> MAIS 1 MENOS 2 VEZES 3 DIV 4 DOLAR CTE OPAD OPMULT ABPAR FPAR INVAL Atributos para os tokens OPAD e OPMULT Por default, yylval é inteiro line : ; expr : { expr DOLAR{ printf("valor: %d\n", $1); } expr OPAD term Não-terminais também têm atributos switch ($2) { case MAIS : $$ = $1 + $3; break; case MENOS : $$ = $1 - $3; break; } } | ; term Numa produção qualquer: $$ é o atributo do não-terminal do lado esquerdo $1, $2, $3 ... são atributos dos 1o, 2o, 3o ... símbolos do lado direito term : { fat } | ; : | ; term OPMULT fat Rodar o executável para um arquivo com a expressão switch ($2) 10 * (5 + 3)$ { case VEZES: $$ = $1 * $3; break; case DIV: $$ = $1 / $3; break; } O atributo de um terminal é fornecido por yylex, em yylval fat Os atributos dos não-terminais devem ser calculados nas ações CTE ABPAR expr FPAR {$$ = $2;} %% #include "lex.yy.c" Por default, a ação tomada no final de uma produção é: $$ = $1; No Yacc, a análise é bottom-up (por deslocamento e redução) Os átomos são obtidos e deslocados para uma pilha (deslocamento) Quando no topo da pilha se formar o lado-direito de uma produção, tem-se uma ocasião para reduzir O analisador verifica se a redução é válida Isso será estudado no tópico sobre Análise Bottom-Up do capítulo sobre Análise Sintática Em caso positivo, substitui, na pilha, o lado-direito pelo lado-esquerdo da produção (redução) Então, a ação no final da produção é executada Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha # expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada 10*(5+3)$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha # expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada 10*(5+3)$# Operação d Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #C10 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada *(5+3)$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #C10 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada *(5+3)$# Operação r: F → C Ação $$ = $1 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #F10 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada *(5+3)$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #F10 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada *(5+3)$# Operação r: T → F Ação $$ = $1 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada *(5+3)$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada *(5+3)$# Operação ddd Por que não reduz segundo E → T ? A resposta virá no estudo de Análise Bottom-Up Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(C5 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada +3)$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(C5 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada +3)$# Operação r: F → C Ação $$ = $1 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(F5 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada +3)$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(F5 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada +3)$# Operação r: T → F Ação $$ = $1 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(T5 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada +3)$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(T5 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada +3)$# Operação r: E → T Ação $$ = $1 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E5 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada +3)$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E5 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada +3)$# Operação dd Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E5+C3 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada )$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E5+C3 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada )$# Operação r: F → C Ação $$ = $1 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E5+F3 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada )$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E5+F3 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada )$# Operação r: T → F Ação $$ = $1 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E5+T3 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada )$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E5+T3 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada )$# Operação r: E → E+T Ação $$ = $1+$3 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E8 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada )$# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E8 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada )$# Operação d Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E8) expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada $# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*(E8) expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada $# Operação r: F → (E) Ação $$ = $2 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*F8 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada $# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T10*F8 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada $# Operação r: T → T*F Ação $$ = $1*$3 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T80 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada $# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #T80 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada $# Operação r: E → T Ação $$ = $1 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #E80 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada $# Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #E80 expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada $# Operação d Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #E80$ expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada # Operação Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #E80$ expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada # Operação r: L → E$ Ação Imprimir $1 Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #L expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada Operação # Escrito no vídeo: valor 80 Ação Análise e cálculo da expressão 10 * (5 + 3)$ line : expr : term : fat : Pilha #L expr DOLAR expr OPAD term | term term OPMULT fat | fat CTE | ABPAR expr FPAR Entrada # Operação aceitar Escrito no vídeo: valor 80 Ação Árvore sintática de 10*(5+3)$ com atributos: Programa 2.7: Alteração no tipo dos atributos Programa em Flex no arquivo calc02.l: delim ws digit cte %% {ws} {cte} "+" "*" "(" ")" "$" %% [ \t\n] {delim}+ [0-9] {digit}+(\.{digit}*)? { ;} {yylval {return {return {return {return {return = atof(yytext); return CTE;} MAIS;} VEZES;} Para simplificar: ABPAR;} Expressões somente com FPAR;} somas e multiplicações DOLAR;} Programa em Yacc no arquivo calc02.y %{ #include #include #define %} %token %token %token %token %token %token %% <stdio.h> <stdlib.h> YYSTYPE float DOLAR CTE MAIS VEZES ABPAR FPAR Alteração no tipo de yylval e dos atributos dos nãoterminais line : expr DOLAR { printf("valor: %g\n",$1); return 0;} ; expr : expr MAIS term {$$ = $1 + $3;} | term ; term : term VEZES fat {$$ = $1 * $3;} | fat ; fat : CTE | ABPAR expr FPAR {$$ = $2;} ; Rodar o executável para um arquivo %% com uma expressão tal como #include "lex.yy.c" 10.5 * (5.2 + 3.3)$ Programa 2.8: Atributos de tipos alternativos Programa em Flex no arquivo calc03.l delim ws digit ctint ctfloat %% [ \t\n] {delim}+ [0-9] {digit}+ {digit}+\.{digit}* Programa com constantes inteiras e reais {ws} {ctint} { ;} {yylval.valint = atoi(yytext); return CTINT;} {ctfloat} {yylval.valfloat = atof(yytext); return CTFLOAT;} "+" {yylval.atr = MAIS; return OPAD;} "-" {yylval.atr = MENOS; return OPAD;} "*" {yylval.atr = VEZES; return OPMULT;} "/" {yylval.atr = DIV; return OPMULT;} "(" {return ABPAR;} ")" {return FPAR;} "$" {return DOLAR;} yylval não é mais declarado no programa Flex %% Deve ser uma union com os Agora yylval está sendo usado campos: como apresentado no capítulo sobre Flex valint, valfloat e atr Programa em Yacc no arquivo calc03.y %{ #include <stdio.h> #include <stdlib.h> Os campos da union podem ser #define MAIS 1 struct’s ou até #define MENOS 2 outras union’s #define VEZES 3 #define DIV 4 %} %union { int valint, atr; Declaração da estrutura e dos campos de yylval e de outras variáveis de float valfloat; mesmo tipo, usadas na função yyparse } %type %token %token %token %token %token %token %token %% <valfloat> <valint> <valfloat> <atr> <atr> expr term DOLAR CTINT CTFLOAT OPAD OPMULT ABPAR FPAR fat %type: para os não-terminais %token: para os terminais Especificação dos atributos dos tokens e dos nãoterminais line : ; expr : { Aqui, $1 corresponde ao campo valfloat de alguma variável de yyparse expr DOLAR { printf("valor: %g\n",$1); } expr OPAD term Mudança no tipo do valor a ser escrito switch ($2) { case MAIS : $$ = $1 + $3; break; case MENOS : $$ = $1 - $3; break; } } | ; Aqui: term $1, $3 e $$ correspondem ao campo valfloat $2 corresponde ao campo atr term : { term OPMULT fat switch ($2) { case VEZES: $$ = $1 * $3; break; case DIV: $$ = $1 / $3; break; } fat } | ; : | | ; fat Fator de conversão CTINT {$$ = (float)$1;} CTFLOAT ABPAR expr FPAR {$$ = $2;} %% #include "lex.yy.c" Rodar o executável para um arquivo com uma expressão tal como 10.5 * (5 + 3.3)$ Programa 2.9: Ações no início e meio das produções %{ Arquivo acaomeio.y #include <stdio.h> #include <stdlib.h> int v, w, x, y, z; %} %% Caracteres entre A : {w = 10; $$ = 5*w;} B apóstrofos são {$$ = 1; v = $1; y = $2;} C tokens { x = $3; z = $4; printf ("v = %d; w = %d; x = %d; y = %c; z = %c;", v, w, x, y, z); return 0; yylex () { } char x; ; x = getchar (); B : 'b' while (x != 'b' && x != 'c') ; x = getchar (); C : 'c' yylval = x; O tipo e o atributo ; return x; são iguais %% } A : ; B : ; C : ; {w = 10; $$ = 5*w;} B {$$ = 1; v = $1; y = $2;} C { x = $3; z = $4; printf ("- - -", v, w, x, y, z); return 0; } 'b' Ação no início ou meio de uma produção: 'c' Yacc cria uma produção vazia para um não-terminal fictício A ação fica no final dessa produção vazia Tal não-terminal fica no lugar da ação, na produção original A A Produção: : {w = 10; $$ = 5*w;} B {$$ = 1; v = $1; y = $2;} C {x = $3; z = $4; printf ("... ", v, w, x, y, z); return 0;} ; Torna-se: $$1 $$2 A : : : ; {w = 10; $$ = 5*w;} ; {$$ = 1; v = $1; y = $2;} ; $$1 B $$2 C { x = $3; z = $4; printf ("…", v, w, x, y, z); return 0; } Nas produções de $$1 e $$2, $$ é o atributo do lado esquerdo Na produção de $$2, $1 é o atributo de $$1 e $2 é o de B, da produção de A Na produção de A, $3 é o atributo de $$2 e $4 é o de C Cuidado com a numeração dos atributos da produção original $$1 $$2 A : : : ; {w = 10; $$ = 5*w;} ; {$$ = 1; v = $1; y = $2;} ; $$1 B $$2 C { x = $3; z = $4; printf ("…", v, w, x, y, z); return 0; } Para entrada bc: $$1 $$2 A : : : B C ; : : {w = 10; $$ = 5*w;} ; {$$ = 1; v = $1; y = $2;} ; $$1 B $$2 C { x = $3; z = $4; printf ("…", v, w, x, y, z); return 0; } Resultado no vídeo: v = 50; w = 10; x = 1; y = b; z = c; 'b'; 'c'; Programa 2.10: Pretty-Printer Dada uma entrada desorganizada do tipo: { i = 1; i + 1; i {j = 0;{ = j + 1; - (x-3) + c; } i 1; } } i = = 0; j a = b Obter uma saída organizada do tipo: { i = 1 ; i = i + 1 ; i = 0 ; { j = 0 ; { j = j + 1 ; a = b - ( x - 3 ) + c ; } i = i - 1 ; } = i - } É necessário mudar de linha e tabular oportunamente Programa em Flex no arquivo pretty2015.l delim ws digito letra intct id %% {ws} {id} {intct} "+" "-" "(" ")" "{" "}" ";" "=" . %% [ \t\n] {delim}+ [0-9] [A-Za-z] {digito}+ {letra}({letra}|{digito})* { ;} {strcpy (yylval.cadeia, yytext); return ID;} {yylval.valint = atoi(yytext); return INTCT;} {yylval.atr = MAIS; return ADOP;} {yylval.atr = MENOS; return ADOP;} {return OPPAR;} {return CLPAR;} {return OPBRACE;} {return CLBRACE;} {return SCOLON;} {return ASSIGN;} {yylval.carac = yytext[0]; return INVAL;} Programa em Yacc no arquivo pretty2015.y %{ #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAIS #define MENOS int tab = 0; %} %union { char cadeia[50]; int atr, valint; char carac; } 7 8 tab: no de tabulações a serem dadas por uma função de nome tabular %token %token %token %token %token %token %token %token %token %token %% <cadeia> <valint> <atr> <carac> ID INTCT ADOP OPPAR CLPAR OPBRACE CLBRACE SCOLON ASSIGN INVAL CompStat : OPBRACE {tabular (); printf ("\{\n"); tab++;} StatList CLBRACE {tab--; tabular (); printf ("}\n");} ; StatList : Statement | StatList Statement ; Statement : AssignStat | CompStat ; AssignStat : tabular: dá tantas tabulações quanto for o valor de tab Depois de todo token imprime seu texto ID {tabular (); printf ("%s ", $1);} ASSIGN {printf ("= ");} Expression SCOLON {printf(";\n");} ; Para alguns tokens, imprime ‘\n’ Expression Term : | ; : | | Term Expression ADOP { if ($2 == MAIS) printf ("+ "); else printf ("- "); } Term ID {printf ("%s ", $1);} INTCT {printf ("%d ", $1);} OPPAR {printf("\( ");} Expression CLPAR {printf (") ");} ; %% #include "lex.yy.c" tabular () { int i; for (i = 1; i <= tab; i++) printf ("\t"); } Rodar o executável para um arquivo contendo o programa desorganizado ilustrativo Outra forma de apresentar a gramática (com abreviaturas): Comp : StatL : Stat : AsStat : Expr : Term : | ‘{’ $$1 StatL ‘}’ {tab--; tabular (); write("}\n");} ; Stat | StatL Stat ; AsStat | Comp ; ID $$2 “=” $$3 Expr ‘;’ {write(";\n");} ; Term | Expr OPAD $$4 Term ; ID {write($1);} | INTCT {write($1);} ‘(’ $$5 Expr ‘)’ {write (")");} ; $$4: {if ($2 == MAIS) write (‘+’); $$1: {tabular (); else printf (‘-’);} write ("{\n"); tab++;} $$2: {tabular (); write ($1);} $$5: {write (‘(’);} $$3: {write ("=");} A análise bottom-up por deslocamento e redução, feita pelo Yacc, simula o caminhamento em pós-ordem, na árvore sintática de uma sentença correta Isso é usado a seguir para ilustrar o resultado das ações do pretty-printer para produzir o efeito desejado Seja a seguinte sentença bem simples: { i = 1 ; j = 2 ; } A seguir, sua árvore sintática Seja a pós-ordem: Comp { tab $$1 StatL StatL } AsStat AsStat $$2 Os elementos dos retângulos formam a pilha Stat Stat ID(i) 01 ID(j) = Reduzir: Deslocar Ação: vazio { tabular (); write ("{\n"); tab++;} $$3 Term $$2 Expr {_ _ INTCT(1) = ; $$3 Expr ; Term INTCT(2) Tela do vídeo Seja a pós-ordem: Comp { tab $$1 StatL StatL } Stat Stat AsStat AsStat ID(i) $$2 1 ID(j) = Reduzir: Deslocar Ação: vazio {tabular (); write ($1);} $$3 Term $$2 Expr { _ INTCT(1) = ; $$3 i_ Expr ; Term INTCT(2) Seja a pós-ordem: Comp { tab $$1 StatL StatL } Stat Stat AsStat AsStat ID(i) $$2 1 ID(j) = $$3 $$2 Expr = ; $$3 Expr ; Term { Term Reduzir: Ação: Deslocar vazio {write (“=");} INTCT(1) ii_=_ INTCT(2) Seja a pós-ordem: Comp { tab $$1 StatL StatL } Stat Stat AsStat AsStat ID(i) $$2 1 ID(j) = $$3 $$2 Expr = ; $$3 Expr ; Term { Term Reduzir: Ação: Deslocar {write {$$ = $1);} ($1);}irrelevante INTCT(1) i= =_1_ INTCT(2) Seja a pós-ordem: Comp { tab $$1 StatL StatL } Stat Stat AsStat AsStat ID(i) $$2 1 ID(j) = $$3 $$2 Expr = ; $$3 Expr ; Term { Term i=1 1_; _ Reduzir: Ação: Deslocar {write {$$ = $1);} (“;\n”);} irrelevante INTCT(1) INTCT(2) Seja a pós-ordem: Comp { tab $$1 StatL StatL } Stat Stat AsStat AsStat ID(i) 1 ID(j) $$2 = $$3 $$2 Expr = ; $$3 Expr ; Term { Term _ Reduzir: Ação: Deslocar vazio {tabular(); write ($1);} INTCT(1) i=1; j_ INTCT(2) Seja a pós-ordem: Comp { tab $$1 StatL StatL } Stat Stat AsStat AsStat ID(i) $$2 1 ID(j) = $$3 $$2 Expr = ; $$3 Expr ; Term { Term Reduzir: Ação: Deslocar vazio {write (“=”);} INTCT(1) i=1; jj_=_ INTCT(2) Seja a pós-ordem: Comp { tab $$1 StatL StatL } Stat Stat AsStat AsStat ID(i) $$2 1 ID(j) = $$3 $$2 Expr = ; $$3 Expr ; Term { Term Reduzir: Ação: Deslocar {write {$$ = $1);} ($1);}irrelevante INTCT(1) i=1; j= =_2_ INTCT(2) Seja a pós-ordem: Comp { tab $$1 StatL StatL } Stat Stat AsStat AsStat ID(i) $$2 1 ID(j) = $$3 $$2 Expr = ; $$3 Expr ; Term { Reduzir: Ação: Deslocar {write {$$ = $1);} (“;\n”);} irrelevante i=1; j=2 2_; Term _ INTCT(1) INTCT(2) Seja a pós-ordem: Comp { tab $$1 StatL StatL } Stat Stat AsStat AsStat ID(i) $$2 01 ID(j) = $$3 $$2 Expr = ; $$3 Expr ; Term { i=1; j=2; Term Reduzir: Ação: Deslocar Aceitar {tab--; tabular(); write (“}\n”);} } _ _ INTCT(1) INTCT(2) Comp { As ações no prettyprinter produziram os resultados desejados $$1 StatL StatL } Stat Stat AsStat AsStat ID(i) $$2 ID(j) = $$3 $$2 Expr = ; $$3 Expr ; Term { Processamento de um não-terminal: é uma redução para ele i=1; j=2; Term } _ INTCT(1) INTCT(2) Resultado Programa 2.11: Conflitos no Yacc %token a %token b %token c %token dolar %token erro %% SS: S dolar {printf ; S : X C | A Y ; A : /* vazia */ | X : /* vazia */ | Y : /* vazia */ | C : /* vazia */ | %% Arquivo conflito.y com uma gramática geradora da linguagem L = {aibjck | i=j ou j=k} ("Fim da analise\n"); return;} A a b C a ; X b ; Y c ; c ; A gramática é ambígua: Sentença aaabbbccc tem mais de uma árvore sintática yylex () { char x; Arquivo conflito.y (continuação) x = getchar (); while (x == ' ' || x == '\n' || x == '\t' || x == '\r') x = getchar (); printf ("Caractere lido: %c\n", x); if (x == 'a') return a; O analisador gerado pelo if (x == 'b') return b; Yacc não consegue tratar if (x == 'c') return c; certas gramáticas if (x == '$') return dolar; return erro; Solução: Método mais } geral Executar: yacc conflito.y Resultado: yacc: 1 shift/reduce conflict, 1 reduce/reduce conflict. yylex () { char x; Arquivo conflito.y (continuação) x = getchar (); while (x == ' ' || x == '\n' || x == '\t' || x == '\r') x = getchar (); printf ("Caractere lido: %c\n", x); if (x == 'a') return a; O analisador gerado pelo if (x == 'b') return b; Yacc não consegue tratar if (x == 'c') return c; certas gramáticas if (x == '$') return dolar; return erro; Solução: Método mais } geral Executar: yacc –v conflito.y Abrir arquivo y.output Arquivo y.output: Contém um relatório da montagem do analisador Conflito shift-reduce: indecisão entre deslocar o átomo para a pilha ou fazer uma redução no topo da pilha Conflito reduce-reduce: indecisão na escolha de uma produção para reduzir no topo da pilha Arquivo y.output - as produções são numeradas: 0 1 2 3 4 5 6 7 8 9 10 11 Produção arfificial do Yacc $accept : SS $end SS : S dolar 0: shift/reduce conflict (shift 1, reduce 4) S : X C on a | A Y No estado 0 (zero – início da análise), A : diante de um ‘a’ na entrada, ele não sabe se | A a o empilha e vai para o estado 1, ou se faz X : uma redução usando a produção 4 | a X b Y : 0: reduce/reduce conflict (reduce 4, reduce | b Y c 6) on dolar C : No estado 0 (zero – início da análise), | C c diante de um ‘$’ na entrada, ele não sabe se faz uma redução usando a produção 4 ou 6 Há gramáticas ambíguas que têm equivalentes não ambíguas Exemplo: gramática S S S | ( S ) | ε geradora expressões com pares de parêntesis balanceados, tais como: ( ), ( ( ) ), ( ) ( ), ( ) ( ( ) ( ( ) ) ) ( ( ) ) Gramática equivalente: S ε | S ( S ) Programa 2.12: Conflito por ações no início ou meio de produções %% SS : S '$' {printf ("Fim da analise\n"); return;} ; S : | S ; '(' S ')' Programa correto para S ε| S(S) %% yylex () { char x; x = getchar (); while (x == ' ' || x == '\n' || x == '\t' || x == '\r') x = getchar (); printf ("Caractere lido: %c\n", x); if (x == '\(' || x == ')' || x == '$') return x; return '#'; Rodar para: ( ) ( ( ) ( ) ) ( ( ) ) $ } Inserindo uma ação: %% SS : S '$' {printf ("Fim da analise\n"); return;} ; S : | {printf ("\nyyy");} S '(' S ')' ; %% yacc: 1 reduce/reduce conflict. yylex () { char x; x = getchar (); while (x == ' ' || x == '\n' || x == '\t' || x == '\r') x = getchar (); printf ("Caractere lido: %c\n", x); if (x == '\(' || x == ')' || x == '$') return x; return '#'; } Arquivo y.output: 0 1 2 3 4 $accept : SS $end SS : S '$' S : $$1 : S : $$1 S '(' S ')' O conflito é em usar a produção 2 ou 3 para reduzir diante do ‘(’ $$1 é um não-terminal fictício Cada caso tem sua solução particular Aqui, pode-se colocar a ação depois do S Observações finais: Apesar do conflito, o Yacc gera o analisador O problema é que os resultados produzidos podem não ser corretos No projeto da disciplina, as produções do comando condicional irão gerar um conflito shift/reduce O analisador gerado resolverá o conflito de forma a obedecer à regra usual para o caso if-else