• Nenhum resultado encontrado

Compiladores-11

N/A
N/A
Protected

Academic year: 2021

Share "Compiladores-11"

Copied!
52
0
0

Texto

(1)

Usando Esquemas de

Tradução

“Na presença de Deus e de Cristo Jesus, que há de julgar os vivos e os mortos, por ocasião da sua manifestação pessoal e mediante seu Reino"

(2)

Revisão

Esquemas de Tradução são um tipo de

Tradução Dirigida por Sintaxe

 Associam ações semânticas (trechos de código) às

produções

 Podem ser realizados de duas maneiras

 Junto com a análise sintática

 Veremos no CUP e em parsers descendentes

(3)

Sumário da Aula

1. Esquemas de Tradução no CUP

2. Esquemas de Tradução em Parsers

Descendentes

3. Construção da Árvore

(4)

1. Esquemas de Tradução no

CUP

(5)

Tradução na Análise Sintática

 As ações semânticas são disparadas sempre

que o parser identifica uma produção

 Veremos como é feita em dois casos

Esquema de tradução em parsers ascedentes

(como o CUP)

 Esquema de tradução em parsers de descida

(6)

Tradução Usando o CUP

 Em geral, basta colocar um trechos de código

em meio às produções delimitando-os por {: e :}

comand ::= expr PT_VIRG

{: System.out.println("Expressão!"); :} ;

(7)

Tradução Usando o CUP

 Em alguns casos, é possível colocar a ação semântica

em meio aos símbolos

 Ação executada antes de reconhecer o PT_VIRG

 Em outros casos, o CUP pode indicar que é incapaz de

identificar o momento em que a ação deve ser executada

comand ::= expr

{: System.out.println("Expressão!"); :} PT_VIRG

(8)

Tradução Usando o CUP

 A tradução permitida pelo CUP pode ser

entendida como se

 Cada símbolo tivesse um único atributo

 O tipo desse atributo é uma classe de sua escolha

Funcionam como atributos sintetizados

(valores do pai definidos a partir dos filhos)

Não permite atributos herdados (filhos definidos a

(9)

Tradução Usando o CUP

Parar associar classes aos símbolos terminais e

não-terminais:

 Indicar na seção de declaração desses símbolos

Ocorrências do símbolo terminal NUMERO serão associadas a

um valor String (como se fosse um atributo de NUMERO)

Ocorrências do não-terminal expr serão associadas a um valor

Integer

terminal String NUMERO; non terminal Integer expr;

(10)

Integração Lexer/CUP

 Atenção: o tipo com que você declara um

símbolo terminal deve ser o mesmo tipo

retornado pelo lexer no segundo parâmetro do construtor de “Symbol”

 Ok, pois yytext() retorna uma String

[0-9]+ {

return new Symbol(sym.NUMERO, yytext()); }

(11)

Tradução Usando o CUP

 Para ter acesso aos valores (atributos) dos

símbolos filhos, basta colocar dois pontos seguido de um nome qualquer

 Esse nome conterá o valor (atributo) desejado

É como se val fosse o atributo de expr

comand ::= expr:val PT_VIRG

{: System.out.println("Valor: " + val); :} ;

(12)

Tradução Usando o CUP

 Para definir (“setar”) o valor associado ao

não-terminal pai, use a variável RESULT

 RESULT é como se fosse o valor do atributo do nó

pai expr (lado esquerdo da produção)

expr ::= expr:e1 MAIS expr:e2

{: int soma = e1.intValue() + e2.intValue(); RESULT = new Integer(soma);

(13)

Tradução Usando o CUP

 Para um exemplo completo de uso do CUP junto

com ações semânticas, vejam o projeto de

exemplo disponível no site

 Implementa um Esquema de Tradução para

interpretar expressões

 Veremos agora como usar um Esquema de

(14)

2. Esquemas de Tradução

em Parsers Descendentes

(15)

Tradução em Parsers de

Descida Recursiva

 Relembrando a técnica

O reconhecimento de cada não-terminal X está

associado a um procedimento parseX()

Dentro de parseX() é feita a discriminação entre as

diferentes produções de X

 Como fazer para associar ações semânticas a

(16)

Tradução em Parsers de

Descida Recursiva

 O caso mais simples é se o não-terminal tem

uma só produção e a ação deve ser executada no final da produção

 Por exemplo, para essa tradução...

(17)

Tradução em Parsers de

Descida Recursiva

 ...o método parseComando() ficaria assim

void parseComand() { parseExpr();

acceptToken(PT_VIRG);

printf("Expressão!");

(18)

Tradução em Parsers de

Descida Recursiva

 Se o não-terminal tem apenas uma produção e

a ação tiver que ser executada no meio da produção...

(19)

Tradução em Parsers de

Descida Recursiva

 ...o resultado é este

void parseComand() { parseExpr();

printf("Expressão!");

PT_VIRG }

(20)

Tradução em Parsers de

Descida Recursiva

 Se tiver mais de uma produção...

comand ::= "while" {printf("While!");} "(" expr ")" comand

| "do" comand {printf(“Do-While!");} "while (" expr ");" | ...

(21)

Tradução em Parsers de

Descida Recursiva

 ...basta posicionar a ação semântica dentro do

bloco que trata a produção desejada

void parseLoopComand() {

if (token.getType() == WHILE) { acceptToken();

System.out.println(“While!"); ...

} else if (token.getType() == DO) { acceptToken(); parseComand(); System.out.println(“Do-While!"); ... } }

(22)

Tradução em Parsers de

Descida Recursiva

 Em alguns casos, pode não ser possível

posicionar a ação adequadamente, pois ainda não se sabe qual é a produção

 Por exemplo: se tiver mais de uma produção e a

ação de alguma delas acontece antes do primeiro símbolo

(23)

Tradução em Parsers de

Descida Recursiva

 Como trabalhar com atributos para os símbolos?

 Por exemplo, implementar a tradução abaixo

 Primeiro veremos como associar um objeto a

um símbolo (representando o atributo val)

expr ::= termo + expr1 {expr.val = termo.val + expr1.val}

(24)

Tradução em Parsers de

Descida Recursiva

Para associar uma classe ao não-terminal X,

basta fazer o método parseX() retornar a classe

 Para os terminais, é só retornar um atributo da

classe desejada junto com o Token

Integer parseExpr() {

... }

(25)

Tradução em Parsers de

Descida Recursiva

 Para acessar os valores dos filhos basta receber

(e guardar) os valores de retorno

 Atributos sintetizados

Integer parseExpr() {

Integer termoVal = parseTermo(); if (toke.getType() == MAIS) { acceptToken();

Integer expr1Val = parseExpr(); return termoVal + expr1Val;

}

return termoVal; }

(26)

Tradução em Parsers de

Descida Recursiva

 Em alguns casos, pode ser necessário passar

valores por parâmetro

 Atributos herdados

 Isso acontece, por exemplo, na gramática obtida

(27)

Tradução em Parsers de

Descida Recursiva

 Gramática original

 Seria fácil criar uma tradução para calcular o valor de

expressões aqui...

expr ::= expr “*” termo | termo

(28)

Tradução em Parsers de

Descida Recursiva

 Gramática sem recursão

 É preciso passar o valor do “termo” do lado esquerdo

para o “restoExpr” fazer a multiplicação

expr ::= termo restoExpr

restoExpr ::= “*” termo restoExpr | ε

(29)

Tradução em Parsers de

Descida Recursiva

 A tradução ficaria assim

Integer parseExpr() {

Integer termoVal = parseTermo(); parseRestoExpr(termoVal);

}

Integer parseRestoExpr(Integer esq) { if (token.getType() == VEZES) { Integer dir = parseTermo(); Integer produto = esq * dir; return parseRestoExpr(produto); } else {

return esq; }

(30)
(31)

Construção da Árvore

 Na verdade, podemos falar de árvores de dois tipos

Árvore de derivação – guarda todos os símbolos, inclusive

sinais especiais, de cada produção

Árvore sintática – guarda só o que é relevante de cada

produção

 Por exemplo, não precisa guardar tokens de ponto-e-vírgula,

“begin”, “end”, etc.

Geralmente, é mais adequado construir a árvore

(32)

Exemplo

 Gramática para os exemplos a seguir

expressão ::= expressão + expressão | expressão - expressão | expressão * expressão | expressão / expressão | ( expressão ) | IDENTIFICADOR | INTEIRO-LITERAL

(33)

Árvore de Derivação

 Árvore de derivação de “x*(i+i)”

expressão expressão x * i + x expressão expressão expressão ( expressão )

(34)

Árvore Sintática

 Árvore sintática de “x*(i+i)”

produto x i x var soma var var

(35)

Construção da Árvore

Sintática

 Não são árvores homogêneas, como as que são

vistas em Algoritmos e Estruturas de Dados

 Recomendo criar, para cada nó dessa árvore,

um tipo diferente

 Classes específicas para cada símbolo não-terminal  Classes específicas para cada produção

(36)

Construção da Árvore

1. Se o não-terminal tiver uma só produção

 Usar uma só classe e colocar como atributos dela os

símbolos importantes que aparecem no lado direito

 Exemplo de produção no CUP

Criar classe Atribuicao com o atributo identificador (do tipo

String) e o atributo expressao (do tipo que for associado a esse não-terminal)

(37)

Construção da Árvore

1. Se o não-terminal tiver uma só produção

(cont.)

 Por fim, colocar a ação no CUP para instanciar esse

objeto

atrib ::= IDENTIFIER:id EQ expr:e

(38)

Construção da Árvore

2. Não-terminal com mais de uma produção

 Criar uma classe abstrata ou interface para

representar o não-terminal da esquerda

 Criar uma subclasse para cada produção  Exemplos de produções no CUP

expr ::= expr MAIS expr | expr VEZES expr | NUM

(39)

Construção da Árvore

2. Não-terminal com mais de uma produção

(cont.)

 Criar interface Expr

 Criar classes filhas Soma, Produto e ExprNum

Expr

(40)

Construção da Árvore

2. Não-terminal com mais de uma produção

(cont.)

 Ações para instanciar as classes

expr ::= expr:e1 MAIS expr:e2

{: RESULT = new Soma(e1,e2); :}

| expr:e1 VEZES expr:e2

{: RESULT = new Produto(e1,e2); :}

| NUM:n

(41)

Construção da Árvore

 Em alguns casos, uma mesma classe pode ser

usada para mais de uma produção

 Uma só classe para "if" sem "else" e para "if" com

"else"

 Uma só classe para todas as expressões binárias  Uma só classe para todas as expressões unárias  Usar o bom senso...

(42)

4. Esquemas de Tradução na

Árvore de Derivação

(43)

Tradução na Análise Sintática

 Vimos antes como usar Esquemas de Tradução

junto com a análise sintática

 Esquemas desse tipo têm algumas limitações

 Parsers ascendentes (como o CUP) permitem

(diretamente) apenas atributos sintetizados

 Parser descendentes permitem atributos sintetizados

(44)

Tradução na Árvore

Outra maneira de se usar Esquemas de

Tradução é usando a árvore já construída

 Essa estratégia é mais geral do que traduções

que acontecem durante a análise sintática

 Permite implementar qualquer tradução, mesmo com

(45)

Esquema de Tradução na

Árvore de Derivação

A primeira etapa é construir a árvore sintática

 Acabamos de ver como fazer isso durante a análise

sintática...

Em seguida, basta percorrer a árvore,

executando as ações semânticas no momento desejado

(46)

Esquema de Tradução na

Árvore de Derivação

 Uma maneira simples de implementar uma

tradução é criando métodos em cada classe da árvore

 Tudo inicia chamando o método do nó raiz

 Na ordem adequada, cada classe da árvore

Realiza ações

 E chama métodos dos seus atributos (conceitualmente,

(47)

Exemplo 1: Tradução para a

Forma Pré-fixada

 Este é o Esquema de Tradução desejado

 Visa passar a expressão para a forma prefixada

 Vamos supor que a árvore tenha sido criada

com as classes Soma, Produto e ExprNum, todas filhas de Expr

expr → { print("+"); } expr + expr | { print("*"); } expr * expr | NUM { print(NUM.lexval); }

(48)

Exemplo 1: Tradução para a

Forma Pré-fixada

 A tradução é feita assim

class ExprNum extends Expr { int lexval;

public void toPrefixed() { print(lexval);

}

(49)

Exemplo 1: Tradução para a

Forma Pré-fixada

 E assim

 Fazer análogo na classe Produto

class Soma extends Expr {

private Expr expr1, expr2;

public void toPrefixed() { print("+");

expr1.toPrefixed(); expr2.toPrefixed(); }

(50)

Exemplo 2: Verificação

Semântica

Criar um método verificaSemantica() em cada classe

da árvore

O início seria no método verificarSemantica() da classe

Program

 Esse método iria chamar o método

verificarSemantica() da classe ListaDecl e, depois, o da

classe Bloco

 O método da classe ListaDecl iria chamar o método

verificarSemantica() de cada objeto Declaracao

(51)

Esquema de Tradução na

Árvore de Derivação

 Outra maneira de percorrer a árvore é usando o

design pattern "Visitor"

 A árvore implementa apenas as chamadas a

métodos de um Visitor genérico

 Classes Visitor de próposito específico podem

ser criadas para implementar uma tradução

(52)

Projeto

 Fazer um Esquema de Tradução durante a

análise sintática apenas para construir a árvore

 Baseie-se no projeto base criado

 Fazer dois Esquemas de Tradução na árvore,

para realizar:

 a análise semântica

Referências

Documentos relacionados

No Experimento 1, cinco estudantes universitários aprenderam as relações de identidade AA, BB e CC com três consequências específicas (rf1, rf2 e rf3), mas

• Como um exemplo nos programas de alimentação escolar, o governo nacional pode gerenciar parte ou todos os fundos e/ou algumas mercadorias fornecidas para o programa (desde reservas

Substituir o símbolo não-terminal situado mais à esquerda da árvore de derivação pelos símbolos do lado direito da produção cujo lado esquerdo é o

ConversorGLC::Terminal?( s ) Indica se s é símbolo terminal ConversorGLC::NaoTerminal?( s ) Idem, para não terminal. GramaticaLivreContexto.listarProducoes Apresenta o conteúdo

Após a dificuldade de escolher e adquirir um par de sapatos, a maioria das usuárias ainda precisam transformá-los - levar ao sapateiro para realizar as modificações

O documento descreve a configuração e a verificação de dispositivos IP que se move através do centro de dados na rede permitida do protocolo da separação da identidade do

MOSFET Tipo Depleção  Símbolo gráfico Terminal do substrato disponível Terminal do substrato conectado ao terminal fonte Não há ligação entre a.. Porta e

O fato é que no escopo extenso das estratégias de manipulação e mutação comportamental, assim como de manutenção da atenção dos usuários, pela vigilância permanente,