Compiladores. Simão Melo de Sousa



Documentos relacionados
Nº horas ESTRATÉGIAS RECURSOS AVALIAÇÃO

AULA 3 Alocação dinâmica de memória: Ponteiros

Linguagens de Programação:

Programação de Computadores I. Linguagem C Função

Aula 01 Introdução Custo de um algoritmo, Funções de complexidad e Recursão

Orientação a Objetos

3. COMPILAÇÃO E ESTRUTURA BÁSICA DE UM PROGRAMA EM C

Algoritmos e Estruturas de Dados I. Variáveis Indexadas. Pedro O.S. Vaz de Melo

Introdução. Software Básico Aula 3. Prof. Dr. Rogério Vargas.

Conceitos básicos de programação

Montadores e Compiladores

Prova Modelo. Nome: Número:

COMPUTAÇÃO. O estudante deve ser capaz de apontar algumas vantagens dentre as seguintes, quanto à modalidade EaD:

Proporcionar a modelagem de sistemas utilizando todos os conceitos da orientação a objeto;

Recursos Humanos Recursos Humanos 2007

INFORMÁTICA PARA GESTÃO II Curso Superior de Gestão de Marketing

Algoritmos e Estruturas de Dados I. Recursividade. Pedro O.S. Vaz de Melo

Web Browser como o processo cliente. Servidor web com páginas estáticas Vs Aplicações dinâmicas para a Web: HTTP porto 80

Os conceitos aprendidos são fundamentais, essenciais para escrever um programa macro. Mas o que é um programa macro?

Maratona de Programação - Dicas Para Iniciantes

Aplicações Informáticas A

Algoritmos e Programação : Conceitos e estruturas básicas. Hudson Victoria Diniz

Documento Geral Explicativo. GS1 Portugal

Exame de Equivalência à Frequência do Ensino Secundário

- Campus Salto. Disciplina: Sistemas de Arquivos Docente: Fernando Santorsula

Metodologias de Programação

Programação de Computadores - I. Profª Beatriz Profº Israel

1.1. Definição do Problema

INTRODUÇÃO À PROGRAMAÇÃO II VARIÁVEIS COMPOSTAS HOMOGÊNEAS UNIDIMENSIONAIS

Capítulo VI Circuitos Aritméticos

ESTUDO DE UM CIRCUITO RC COMO FILTRO

Universidade Federal do ABC Disciplina: Natureza da Informação Lista de Exercícios 02 Códigos e conversão A/D Prof. João Henrique Kleinschmidt

Módulo IV Programação Visual Basic. Programação

Modelo de Entidade-Relação (ER)

Informática Aplicada

Verificação e validação

O AMBIENTE DELPHI. Programação de Computadores.

Implementação do Relatório de Falhas em SAP-PM

7. Funções de Lógica Combinacional. 7. Funções de Lógica Combinacional 1. Somadores Básicos. Objetivos. Objetivos. Circuitos Digitais 03/11/2014

2. Tipos Abstratos de Dados

Ficheiros binários 1. Ficheiros binários

Tipos de problemas de programação inteira (PI) Programação Inteira. Abordagem para solução de problemas de PI. Programação inteira

SISTEMAS DIGITAIS MEMÓRIAS E CIRCUITOS DE LÓGICA PROGRAMÁVEL

Árvores Parte 1. Aleardo Manacero Jr. DCCE/UNESP Grupo de Sistemas Paralelos e Distribuídos

Algoritmo e Pseudo-código

Informação-Prova de Equivalência à disciplina de: Aplicações Informáticas B. 1. Introdução. Ensino Secundário. Ano letivo de 2011/12

LINGUAGEM SQL Linguagem usada em SGBD para: Definir estrutura de dados; Modificar dados em um banco de dados; Especificar restrições de segurança; Rea

INSTITUTO SUPERIOR TÉCNICO Análise e Síntese de Algoritmos. RESOLUÇÃO DA RESPESCAGEM DO 2 o TESTE

Teste de Funções por Cobertura do Grafo de Fluxo de Controle

Universidade Federal da Paraíba Centro de Informática Departamento de Informática

Arquitetura TCP/IP. Apresentado por: Ricardo Quintão

MANUAL DO USUÁRIO SIMPLEX. Prof. Erico Fagundes Anicet Lisboa, M. Sc.

Introdução a Funções

Sistemas Digitais Ficha Prática Nº 7

Tipos de Banco de Dados - Apresentação

Forma Normal de Boyce-Codd

M A N U A L D O ADMINISTRADOR DO PORTAL

PROGRAMAÇÃO LINEAR. Formulação de problemas de programação linear e resolução gráfica

Programação de CPLDs no ambiente ISE 4.2i da Xilinx

Métodos Formais. Agenda. Relações Binárias Relações e Banco de Dados Operações nas Relações Resumo Relações Funções. Relações e Funções

Edição de Tabelas no QGIS

Notas de aula de Lógica para Ciência da Computação. Aula 11, 2012/2

3 Informações para Coordenação da Execução de Testes

Compiladores II. Fabio Mascarenhas

Do alto-nível ao assembly

Fundamentos de Arquitetura e Organização de Computadores

Análise e Projeto de Sistemas OO

CAMPUS DE GUARATINGUETÁ FACULDADE DE ENGENHARIA. Introdução à Programação em C. Algoritmos: Estruturas de Repetição. Prof. Dr. Galeno.J.

Versão Referenciais de FORMAÇÃO. Padel. Menção de formação. Grau

Geração e Otimização de Código (continuação)

Um Tradutor Dirigido por Sintaxe

Unidade 10 Análise combinatória. Introdução Princípio Fundamental da contagem Fatorial

Sistema Operacional. Implementação de Processo e Threads. Prof. Dr. Márcio Andrey Teixeira Sistemas Operacionais

Backup. O que é um backup?

Registo de Representantes Autorizados e Pessoas Responsáveis

Versão Portuguesa. Introdução. Instalação de Hardware. Adaptador Ethernet Powerline LC202 da Sweex de 200 Mbps

Soluções de gestão de clientes e de impressão móvel

FOLHA DE CÁLCULO ELABORAÇÃO DE GRÁFICOS

Normas para a elaboração de um relatório. para a disciplina de projecto integrado. 3.º ano 2.º semestre. Abril de 2004

Algoritmos e Programação II

Modelagem de Dados com UML. Modelagem de Dados com UML. Modelagem de Dados com UML. Modelagem de Dados com UML. Diagrama de Classes

Plano de Ensino PROBABILIDADE E ESTATÍSTICA APLICADA À ENGENHARIA - CCE0292

FERRAMENTAS DA QUALIDADE

1. Estrutura de Dados

Gramáticas Livres de Contexto

Herança. Prof. Leonardo Barreto Campos 1

A Dança do Embaralhamento. Série Matemática na Escola. Objetivos 1. Introduzir a noção de grupo de permutação; 2. Mostrar uma aplicação de MMC.

03. [Sebesta, 2000] Descreva a operação de um gerador de linguagem geral.

Introdução à Programação. Funções e Procedimentos. Prof. José Honorato F. Nunes honoratonunes@gmail.com

Curso Tecnológico de Administração/12.º Nº de anos: 1 Duração: 120 minutos Decreto-Lei n.º 139/2012, de 5 de julho

Sistemas Distribuídos Capítulo 4 - Aula 5

Primeira Prova de Análise e otimização de Código - DCC888 -

Programação WEB I Funções

Programação em JAVA. Subtítulo

Guia do Controlador Universal de Impressão

BANCO DE DADOS. Professor: André Dutton

LINHAS MESTRAS; FASES; DISCIPLINAS; PRINCÍPIOS E MELHORES PRÁTICAS.

Aula Extra. Depurador Code::Blocks. Monitoria de Introdução à Programação

No arquivo Exames e Provas podem ser consultados itens e critérios de classificação de provas desta disciplina.

1. Noção de algoritmo

Transcrição:

Compiladores Análise Semântica versão α 0.001 Simão Melo de Sousa Este documento é uma tradução adaptada do capítulo Analyse Sémantique da sebenta Cours de Compilation de Christine Paulin-Morhing e Marc Pouzet (http://www.lri.fr/~paulin). 1 Introdução A análise semântica trata a entrada sintáctica e transforma-a numa representação mais simples e mais adaptada à geração de código. Esta camada do compilador fica igualmente encarregue de analisar a utilização dos identificadores e de ligar cada uma delas a sua declaração. Nesta situação verificar-se-á que o programa respeita as regras de visibilidade e de porte dos identificadores. É também esperado que esta fase da compilação verifique que cada expressão definida tenha um tipo adequado conforme as regras próprias à linguagem. Neste parte da lição iremos estudar a gestão da tabela dos símbolos que serve para a ligação dos nomes manipulados aos objectos que estes de facto designam. Estudaremos igualmente a tipificação dos programas. Por fim definiremos as noções de gramáticas de atributos que permitam associar valores aos nodos da árvore de derivação sintáctica. 2 Tabela dos símbolos 2.1 Introdução As linguagens de programação manipulam identificadores que são essencialmente símbolos que servem para designar objectos conteúdo dum endereço memória, no caso por exemplo duma variável, pedaços de código no caso de nomes de procedimentos, tipos, etc... A tabela de símbolos arquiva as informações sobre os objectos designados por nomes na linguagem em questão. Esta é actualizada de cada vez que é analisada uma declaração dum novo identificador. De forma semelhante, a tabela é consultada de cada vez que é utilizado um identificador no programa analisado. A tabela de símbolos permite para cada identificador o arquivo das informações associadas ao objecto identificado. Estas podem ser de natureza diversa, como o tipo do 1

objecto, uma posição na lista das variáveis declaradas (com a finalidade de calcular o endereço relativo aquando da geração de código), um valor... É igualmente possível coexistirem várias tabelas de símbolos, por exemplo quando existem vários espaços de nomes, como é o caso para linguagens orientadas a objecto (e.g. os packages do java). Classicamente, encontraremos nestas linguagens uma tabela de símbolos para cada espaço de nome. Por exemplo arquivaremos nos diferentes espaços o nome das classes e o nome dos métodos associados. Em linguagens de tipo ML, os tipos, os módulos e os valores podem ser agrupados em tabelas de símbolos diferentes. Um mesmo identificador pode ser utilizado para representar diferentes objectos. Este objectos podem estar arquivados ou referenciados em diferentes tabelas de símbolos. Este situação obriga conhecer a natureza do objecto para determinar em que tabela procurar os seus dados. Este conhecimento é em geral adquirido. Assim este aparente conflito pode ser facilmente resolvido. Um mesmo identificador (variável, procedimento) pode igualmente estar declarado mais do que uma vez na mesma tabela. Aquando da compilação será necessário conhecer precisamente o objecto referenciado por cada identificador. As regras de porte (scope em inglês) dos identificadores que permitirá resolver os conflitos subjacentes. 2.2 Porte dos identificadores Os programas manipulam vários identificadores. Por razões de eficácia, e com a finalidade de melhorara a robustez do código, as linguagens permitam indicar sintácticamente que a utilização de certas variáveis será confinada a uma parte bem determinada do programa, designado de bloco. Assim, fora deste bloco, não será necessário alocar espaço para as variáveis atribuídas ao bloco em questão. Durante a análise semântica, o compilador assegurar-se-á que todas as variáveis utilizadas foram declaradas de forma adequada e são bem visíveis durante as suas utilizações (i.e. as suas utilizações tem lugar no bloco ao qual pertencem). As regras de porte/alcance dos identificadores são específicas a cada linguagem. Por exemplo em C, uma variável é local ao procedimento em que foi declarada, ou é global a todo o programa. Em Pascal, qualquer identificador tem de ser declarado de forma centralizada antes da sua utilização (daí a necessidade da instrução forward para as funções mutuamente recursivas). O corpo dum procedimento pode utilizar variáveis declaradas em qualquer procedimento que o engloba (ele próprio incluído). Em ML, qualquer identificador deve ter sido previamente declarado numa expressão let id =... in... ou let id =... O porte de tal declaração está restrito a expressão associada a expressão a direita do in no caso da declaração local, ou ao resto do programa/módulo em que está definido no segundo caso (ficheiro = módulo em OCaml). Fora do módulo um identificador exportado (i.e. cuja visibilidade fora do módulo é permitida) pode ser acedido através do que se designa por nome qualificado ou seja Nome_do_módulo.identificador. Se existirem directivas de abertura de módulos (por exemplo open em OCaml, import em Java) a qualificação pode ser omitida. Se um identificador é declarado mais do que uma vez o objecto acedido pelo nome qualificado é o ultimo declarado. É no entanto possível aceder a um objecto via o seu nome completamente qualificado. No caso de um nome completamente qualificado poder 2

referir dois objectos diferentes, então este fica por designar o último declarado. Uma declaração de função em ML não é por defeito recursiva. Para tal é preciso juntar a palavra chave rec ao let. Em Java, o corpo dum método pode utilizar outros métodos da mesma classe mesmo se a definição destes métodos ocorre posteriormente. Isto obriga ao processamento em bloco das definições de classes. Mais, podemos definir numa classe vários métodos com o mesmo nome. O tipo dos parâmetros permite determinar de forma estática (i.e. em tempo de compilação) o método por aplicar. De uma forma similar, várias classes podem redefinir os mesmos métodos. Se as classes são disjuntas, esta ambiguidade de nome pode ser resolvida por uma verificação dos tipos (i.e. type checking). Se um mesmo identificador está definido numa classe e redefinido numa das suas sub-classes então devese impor coerência entre os tipos dos objectos assim definidos, como no caso dos métodos por exemplos. A escolha do método por executar é geralmente feita de forma dinâmica (na altura da execução). Em algumas situações é possível indicar sintácticamente que método se pretende chamar, como no caso do uso das palavras chaves super ou this. No primeiro caso é assim indicado explicitamente que se pretende invocar o método da classe mãe mesmo se essa foi redefinida na classe activa. 2.3 Representação da tabela de símbolos Deve ser possível após a análise dum programa encontrar toda a informação associada a um identificador. Isto pode ser feito enfeitando (analogia feita ao enfeito da árvore de natal que fica após este trabalho com todo o significado a semântica de natal) a árvore de sintaxe abstracta (ASA). É possível associar a cada utilização dum identificador um apontador para parte da ASA que corresponde a declaração do identificador. Esta associação pode (costuma) igualmente incluir outras informações úteis como, por exemplo, o tipo do objecto referenciado. De facto o cuidado por ter aqui é o compromisso entre utilidade da informação arquivada, o seu tamanho e a frequência da sua utilização. Sem cuidado, a gestão duma tabela de símbolo pode se tornar pesada. Uma outra solução consiste em arquivar as informações sobre um objecto numa tabela e associar um endereço para esta tabela a cada utilização do identificador. Este endereço pode ser um apontador, um inteiro ou um nome único. Ao lado desta estrutura persistente, é necessário gerir uma tabela para a verificação do porte de cada identificador. Esta tabela deve poder informar em qualquer instante desta fase de análise dos dados dos identificadores visíveis. deve ser igualmente possível juntar novos identificadores, determinar rapidamente e facilmente se um dado identificador é visível. De forma semelhante deve ser possível retirar identificadores desta estrutura quando o objecto referenciado deixa de existir ou de ser acessível (um identificador de uma variável local quando se atinge o fim do bloco em que está definido). Consideremos a analise do seguinte programa: 1 let x = ( let y = 2 in y*y + 2*y +1) in x+y Quando começamos a analisar esta expressão, construímos uma tabela dos símbolos visíveis T. Primeiro analisa-se a construção let x = e in e que declara o novo identificador x. Para encontrar informação sobre este identificador, analisa-se o corpo e da definição (aqui let y = 2 in y*y + 2*y +1). Nesta situação devemos então analisar o corpo da 3

definição de y ou seja 2. Este processo leva assim a actualização da tabela T em T na qual se acrescentou a entrada y com a informação de que este é, por exemplo, inteiro. Esta declaração torna qualquer informação prévia sobre y em T invisível. Utilizando a tabela T analisamos a expressão y*y + 2*y +1 que é determinada como sendo do tipo inteiro. Ao sair do bloco y*y + 2*y +1, a entrada y na tabela de símbolos deve desaparecer e assim voltamos de T para T. Neste ponto preciso sabe-se que x é inteiro e podemos então juntar esta informação a tabela de símbolos T e proceder a analise de x+y. Com se vê na expressão por analisar e pelas regras de porte em OCaml a ocorrência de y não faz referência à definição interna à definição de x mas sim a uma declaração prévia. No fim desta análise, as diferentes utilizações dos identificadores na ASA devem estar associadas à boa declaração. Estas tabelas devem ser optimizadas porque o número de identificadores pode ser importante e o acesso a informação deve ser rápido. Podem ser implementadas de forma imperativa ou funcional No caso duma representação funcional a analise de visilibilidade poderia se escrever da seguinte forma: function visivel : tabela * asa -> asa_tipada visivel (T,let(x,e,e )) = seja f = visivel(t,e), tip = tipo_de(t,f), T = add ((x,tip),t), f = visivel(t,e ), let(x:tip,f,f ) Este processo funciona se a representação da tabela é funcional, ou seja se a construção de T (junção de (x, tip)) não altere de facto a tabela T. Não é o caso, por exemplo se T for uma tabela de Hash (como é o caso em OCaml). A copia e arquivo da cópia da tabela de hash com o objectivo de a repor caso necessário seria aqui altamente ineficiente. Se optarmos por uma tabela de hash, é assim necessário retirar explicitamente os identificadores dos quais pretendemos apagar o registo. Assim sendo esta tabela por ser global: function visible_imp : asa -> asa_tipada visival_imp (let(x,e,e )) = seja f = visivel_imp(e), tip = tipo_de(f), add x; seja f = visivel_imp(e ), del (x); let(x:tip,f,f ) As tabelas de hash com ligações para listas de entradas (onde os identificadores pertencendo a mesma lista correspondem aos valores com a mesma chave de dispersão (hash key)) são em regra geral boas estruturas para implementar tabelas de símbolos visíveis. De facto quando identificadores com o mesmo nome estão declarados o último esconde naturalmente os anteriores. Isto porque a lista de entradas é gerida como uma pilha. Para retirar um identificador basta eliminar a primeira ocorrência encontrada na tabela (que corresponde ao último inserido). 4

É possível introduzir em cada bloco do programa analisado um número arbitrário de identificadores. Na saída de cada bloco devemos poder identificar e remover todos os identificadores da tabela que nele foram introduzidos (estes deixam de ser visíveis). É assim necessário conservar uma pilha dos blocos abertos com a informação, para cada bloco aberto, dos identificadores que este introduz. Este processo pode ser feito de forma funcional arquivando uma pilha de listas de identificadores ou de forma mais imperativa interligando o conjunto de identificadores introduzidos num mesmo bloco na tabela dos símbolos visíveis e guardando num estrutura (uma pilha por exemplo) o endereço na tabela do último identificador introduzido no bloco activo. 2.4 Representação dos símbolos Quando se analisa os símbolos do programa, é necessário fazer numerosas comparações (igualdades, desigualdades se se utiliza árvores binárias de pesquisa). Para tal é pertinente utilizar uma representação eficiente dos identificadores com por exemplo uma codificação com base em inteiros. Neste caso é interessante dispor duma tabela de dispersão (hash table) onde se arquiva a associação entre inteiro - identificador. Em termos de requisitos, (a) esta associação deve ser única, isto é, um inteiro representa um único identificador; (b) esta tabela deve poder devolver o identificador representado por um inteiro e viceversa; (c) a gestão desta tabela não deve sobrecarregar computacionalmente o processo de analise. 5