Esquemas de Tradução Prof. Alexandre Monteiro

Slides:



Advertisements
Apresentações semelhantes
IFTO ESTRUTURA DE DADOS AULA 05 Prof. Manoel Campos da Silva Filho
Advertisements

Programação em Java Prof. Maurício Braga
Programação em Java Prof. Maurício Braga
Software Básico Silvio Fernandes
UNICAMP Universidade Estadual de Campinas Centro Superior de Educação Tecnológica Divisão de Telecomunicações Propagação de Ondas e Antenas Prof.Dr. Leonardo.
INFORMAÇÕES COMPLEMENTARES
A busca das mulheres para alcançar seu espaço dentro das organizações
Vamos contar D U De 10 até 69 Professor Vaz Nunes 1999 (Ovar-Portugal). Nenhuns direitos reservados, excepto para fins comerciais. Por favor, não coloque.
IV – Análise semântica DEI Associação de regras semânticas a produções
III – Análise sintáctica
Operadores e Funções do LINGO
Introdução à Programação usando Processing Programação Gráfica 2D Animações Exercício Animações 14/10/09 Bruno C. de Paula 2º Semestre 2009 > PUCPR >
14/10/09 Uma animação possui: Início; Passo; Fim; 1.
Exercício do Tangram Tangram é um quebra-cabeças chinês no qual, usando 7 peças deve-se construir formas geométricas.
Nome : Resolve estas operações começando no centro de cada espiral. Nos rectângulos põe o resultado de cada operação. Comprova se no final.
Interação entre objetos
Ludwig Krippahl, 2007 Programação para as Ciências Experimentais 2006/7 Teórica 3.
Software Básico Silvio Fernandes
Curso de ADMINISTRAÇÃO
Árvores.
EXPRESSÕES ARITMÉTICAS
EXPRESSÕES ARITMÉTICAS
JavaCC e JJTree Geração de compiladores implementados em Java
FUNÇÃO MODULAR.
Curso Sistemas de Informação Disciplina: Arquitetura de Software
Estruturas de Dados II Prof.: Sergio Pacheco Prof.: Sergio Pacheco 1 1.
YACC.
Construção de Compiladores
Aula 4 Nomes, Vinculações, Tipos e Escopos
Aula 5 Tipos de Dados Universidade do Vale do Rio dos Sinos
EXEMPLOS DE ESTRUTURAS PROTENDIDAS
Capítulo 9 Herança 1.
Programação Baseada em Objectos Desenho de TAD
Classes e objetos Arrays e Sobrecarga
Classes e objetos P. O. O. Prof. Grace.
Prof. Bruno Moreno Aula 4 – 11/03/2011
JAVA: Conceitos Iniciais
Programação Orientada a Objetos com Java
CATÁLOGO GÉIA PÁG. 1 GÉIA PÁG. 2 HESTIA PÁG. 3.
Ferramentas para a Construção de Compiladores: Lex & Yacc
Lemas (Sudkamp)  .
Plataforma Brasil – Submissão de pesquisa
Programação I Aula 2 (Métodos)
Sintaxe e Semântica Prof.: Gláucya Carreiro Boechat
Semântica de Linguagens de Programação
Prof. Alexandre Monteiro
1 JavaCC Prof. Alexandre Monteiro Baseado em material cedido pelo Prof. Euclides Arcoverde Recife.
Geração de Código Intermediário
1 Aplicações do Fecho Regular. 2 A interseção de uma linguagem livre de contexto e uma linguagem regular é uma linguagem livre de contexto livre de contexto.
Olhe fixamente para a Bruxa Nariguda
Capítulo II Gramáticas e Linguagens
Máquina de Turing Universal
Analise sintática aula-07-analise-sintática.pdf.
INTRODUÇÃO À ORIENTAÇÃO A OBJETOS EM JAVA
Esquemas L-atribuídos
Análise Sintática LR Prof. Alexandre Monteiro
Wagner Santos C. de Jesus
Analise Semântica aula-10-analise-semântica.pdf.
Analisador Léxico Prof. Alexandre Monteiro
Projeto de Tradutor Preditivo. Introdução Introdução Esquemas L-atribuidos são reconhecidos por analisadores Top-Down. Entre estes analisadores, se encontra.
Análise Sintática Prof. Alexandre Monteiro
Aula Prática 4 Monitoria IP/CC (~if669).
Análise Léxica Prof. Alexandre Monteiro
Tradução Dirigida por Sintaxe
Revisão Compiladores – AP2
Análise Sintática de Descida Recursiva
Faculdade Pernambucana - FAPE Setembro/2007
COMPILADORES 04 Prof. Marcos.
Sintaxe de uma Linguagem
Faculdade Pernambuca - FAPE Compiladores Abril/2007 Compiladores Abril/2007.
Transcrição da apresentação:

Esquemas de Tradução Prof. Alexandre Monteiro Baseado em material cedido pelo Prof. Euclides Arcoverde Recife

Contatos Prof. Guilherme Alexandre Monteiro Reinaldo Apelido: Alexandre Cordel E-mail/gtalk: alexandrecordel@gmail.com greinaldo@fbv.edu.br Site: http://www.alexandrecordel.com.br/fbv Celular: (81) 9801-1878

Traduções Dirigidas por Sintaxe As produções de uma gramática são suas “regras sintáticas” e definem construções relevantes da linguagem Traduções Dirigidas por Sintaxe associam regras semânticas a cada uma dessas regras sintáticas, com algum objetivo As regras semânticas definem a saída correspondente a cada produção

Traduções Dirigidas por Sintaxe Possíveis aplicações Criação da árvore sintática em memória Verificação de tipos Geração de código intermediário Etc. Enfim, ela pode ser usada em todo o restante das etapas do front-end...

Traduções Dirigidas por Sintaxe Existem dois tipos Definição Dirigida por Sintaxe – assumindo que cada nó da árvore possui atributos, define os valores que eles devem receber Esquemas de Tradução – associam código qualquer a cada produção, para ser executado durante a análise sintática (parsing) A distinção é um pouco confusa...

Traduções Dirigidas por Sintaxe “Definição Dirigida por Sintaxe” é uma descrição mais abstrata e formal de uma tradução Especifica a tradução Já um “Esquema de Tradução” é a descrição concreta do código que vai ser executado para cada produção Implementa uma definição dirigida por sintaxe Usa trechos de código

Definição Dirigida por Sintaxe

Definição Dirigida por Sintaxe É a gramática livre de contexto da linguagem acrescida de Descrição dos atributos que cada símbolo (terminal ou não-terminal) possui Regras semânticas, associadas a cada produção, para definir os valores dos atributos Chamaremos SDD (Syntax-Directed Definition)

Exemplo 1 L → E ; E → T + E1 E → T T → F * T1 T → F F → digit Considere a seguinte gramática: L → E ; E → T + E1 E → T T → F * T1 T → F F → digit

Exemplo 1 Com base em uma gramática de expressões, o objetivo do exemplo é avaliar o valor numérico das expressões Para os não-terminais L, E, T e F, vamos considerar que possuem o atributo “val” O símbolo terminal digit terá um atributo “lexval”, com o seu valor numérico O símbolo terminal ; (ponto-e-vírgula) não tem importância nesta tradução

Exemplo 1 Produção Regra Semântica L → E ; E → T + E1 E → T T → F * T1 L.val = E.val E → T + E1 E.val = T.val + E1.val E → T E.val = T.val T → F * T1 T.val = F.val * T1.val T → F T.val = F.val F → digit F.val = digit.lexval

Exemplo 2 E → E1 + T E → E1 - T E → T T → ( E ) T → num T → identifier Considere a seguinte gramática: E → E1 + T E → E1 - T E → T T → ( E ) T → num T → identifier

Exemplo 2 Este exemplo usa uma gramática de expressões com soma e subtração apenas O objetivo da SDD neste exemplo é construir a árvore sintática Cada não-terminal tem um atributo “node” que representa o nó da árvore que representa aquela ocorrência do não-terminal

Exemplo 2 Produção Regra Semântica E → E1 + T E → E1 - T E → T E.node = new Op(‘+’, E1.node, T.node) E → E1 - T E.node = new Op(‘-’, E1.node, T.node) E → T E.node = T.node T → ( E ) T.node = E.node T → num T.node = new Leaf(num) T → identifier T.node = new Leaf(identifier)

Definição Dirigida por Sintaxe Não se preocupa com detalhes, como a ordem de definição dos atributos Sua principal aplicação é para especificar traduções mais simples Mais abstrata

Esquema de Tradução

Esquema de Tradução É uma extensão do conceito de SDD Possui trechos de código como regras semânticas (que passam a ser chamadas ações semânticas) Controla a ordem de execução das ações semânticas Implementação da tradução (ou da SDD) Mais concreta

Esquema de Tradução Assim, um Esquema de Tradução é uma gramática livre acrescida de Descrição dos atributos que cada símbolo (terminal ou não-terminal) possui Ações semânticas, associadas a cada produção Descritas na forma de trechos de código de uma linguagem de programação real Posicionadas dentro da produção como se fossem um símbolo, para indicar o momento exato de executar a ação

Esquema de Tradução Um Esquema de Tradução pode ser implementado junto com a análise sintática Ações semânticas disparadas sempre que o parser identifica uma produção Ou percorrendo a árvore de derivação Primeiro, a árvore é construída por um Esquema de Tradução realizado durante a análise sintática Depois, a árvore é percorrida, disparando as ações semânticas no momento adequado

Usando Esquemas de Tradução

Introdução Esquemas de Tradução são um tipo de Tradução Dirigida por Sintaxe Ações semânticas (trechos de código) associados às produções Pode ser realizada de duas maneiras Junto com a análise sintática Veremos no CUP e em parsers descendentes Percorrendo a árvore de derivação

Sumário Esquemas de Tradução no CUP Esquemas de Tradução em Parsers Descendentes Construção da Árvore Esquemas de Tradução na Árvore Sintática

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 recursiva

Esquemas de Tradução no CUP

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!"); :} ; expr ::= ...

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 ;

Tradução Usando o CUP O CUP permite associar classes aos símbolos (terminais ou não-terminais) Pensando no conceito de Definição Dirigida por Sintaxe, é como se Cada símbolo tivesse um único atributo O tipo desse atributo é a classe em questão Funcionam como atributos sintetizados Não permite atributos herdados

Tradução Usando o CUP Para associar classes aos símbolos terminais e não-terminais, basta indicá-las na seção de declaração de símbolos Ocorrências do símbolo terminal NUMERO serão representadas por objetos da classe String Ocorrências do não-terminal expr representadas pela classe Integer terminal String NUMERO; non terminal Integer expr;

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

Tradução Usando o CUP Para ter acesso aos objetos que representam os atributos dos símbolos filhos, basta colocar dois pontos seguido de um nome qualquer A ação poderá usar esse nome como uma variável daquele tipo É como se val fosse o atributo de expr comand ::= expr:val PT_VIRG {: System.out.println("Valor: " + val); :} ;

Tradução Usando o CUP Para atribuir valor ao objeto que representa o não-terminal pai (lado esquerdo), é preciso usar a variável RESULT O RESULT é como um atributo sintetizado (que depende apenas dos filhos) de expr expr ::= expr:e1 MAIS expr:e2 {: int soma = e1.intValue() + e2.intValue(); RESULT = new Integer(soma); :}

Tradução Usando o CUP Para um exemplo completo de uso do CUP junto com ações semânticas será disponibilizado no oro-aro Implementa um Esquema de Tradução para interpretar expressões, retornando seus valores Veremos agora como usar um Esquema de Tradução com um parser de descida recursiva

Esquemas de Tradução em Parsers Descendentes

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 cada produção?

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... comand ::= expr ";" { System.out.println("Expressão!"); }

Tradução em Parsers de Descida Recursiva ...o método parseComando() ficaria assim void parseComand() { parseExpr(); acceptToken(PT_VIRG); System.out.println("Expressão!"); }

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... comand ::= expr {System.out.println("Expressão!"); } ";"

Tradução em Parsers de Descida Recursiva ...o resultado é este void parseComand() { parseExpr(); System.out.println("Expressão!"); PT_VIRG }

Tradução em Parsers de Descida Recursiva Se tiver mais de uma produção... comand ::= "while" {System.out.println("While!");} "(" expr ")" comand | "do" comand {System.out.println(“Do-While!");} "while (" expr ");" | ...

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) { parseComand(); System.out.println(“Do-While!"); }

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 Mesma limitação do CUP

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} | termo {expr.val = termo.val}

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() { ... }

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 (token.getType() == MAIS) { acceptToken(); Integer expr1Val = parseExpr(); return termoVal + expr1Val; }

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, em uma gramática recursiva à esquerda, após transformá-la em uma sem a recursão

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 termo ::= NUMBER

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 | ε termo ::= NUMBER

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;

Construção da Árvore

Construção da Árvore Relembrando... Árvore de derivação – guarda todos os símbolos, inclusive sinais especiais, de cada produção Feita com base na gramática concreta Árvore sintática – guarda só o que é importante de cada produção Feita com base na gramática abstrata É mais adequado construir a árvore sintática

Construção da Árvore Classes diferentes para cada símbolo não-terminal Não são árvores homogêneas, como as que são vistas nas disciplinas de estruturas de dados Cada nó dessa árvore pode ser de um tipo diferente Classes diferentes para cada símbolo não-terminal Classes diferentes para cada produção

Construção da Árvore 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) atrib ::= IDENTIFIER EQ expr

Construção da Árvore 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 {: RESULT = new Atribuicao(id,e); :}

Construção da Árvore Não-terminal com mais de uma produção Criar uma classe abstrata ou interface para representar o não-terminal Criar uma classe (herdada) para cada produção Exemplos de produções no CUP expr ::= expr MAIS expr | expr VEZES expr | NUM

Construção da Árvore Criar interface Expr Não-terminal com mais de uma produção (cont.) Criar interface Expr Criar classes filhas Soma, Produto e ExprNum Expr Soma Produto ExprNum

Construção da Árvore Não-terminal com mais de uma produção (cont.) Colocar as ações no CUP 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 {: RESULT = new ExprNum(n); :}

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...”

Esquemas de Tradução na Árvore de Derivação

Tradução na Análise Sintática Já vimos 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 apenas atributos sintetizados Parser descendentes permitem atributos sintetizados e herdados, mas com algumas restrições

Tradução na Árvore Outra maneira de se usar Esquemas de Tradução é usando a árvore Essa estratégia é mais geral do que traduções que acontecem durante a análise sintática Permite implementar qualquer tradução, mesmo com atributos herdados

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

Esquema de Tradução na Árvore de Derivação Uma maneira simples de implementar uma tradução é criar métodos em cada classe da árvore que representar uma produção (ou um não-terminal) Tudo inicia chamando o método do nó raiz No momento adequado, cada nó chama os métodos equivalentes dos filhos

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

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

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

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 E assim sucessivamente...

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 Ver http://en.wikipedia.org/wiki/Visitor_pattern

Visitor Visitor é um padrão de projeto catalogado pelos “quatro” mosqueteiros da engenharia de software, Gamma, Helm, Johnson e Vlissides (Gang of Four - GoF), como um padrão de projeto comportamental Seu objetivo principal é permitir que sejam adicionadas novas funcionalidades a classes existentes, de maneira plugável, sem que haja a necessidade de se alterar a hierarquia dessas classes

Bibliografia AHO, A., LAM, M. S., SETHI, R., ULLMAN, J. D., Compiladores: princípios, técnicas e ferramentas. Ed. Addison Wesley. 2a Edição, 2008 (Capítulo 4)