IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO...

109
UNIVERSIDADE REGIONAL DE BLUMENAU CENTRO DE CIÊNCIAS EXATAS E NATURAIS CURSO DE CIÊNCIAS DA COMPUTAÇÃO – BACHARELADO IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO AMBIENTE FURBOL ANDRÉ LUÍS GARLINI BLUMENAU 2008 2008/1-04

Transcript of IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO...

Page 1: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

UNIVERSIDADE REGIONAL DE BLUMENAU

CENTRO DE CIÊNCIAS EXATAS E NATURAIS

CURSO DE CIÊNCIAS DA COMPUTAÇÃO – BACHARELADO

IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO

AMBIENTE FURBOL

ANDRÉ LUÍS GARLINI

BLUMENAU 2008

2008/1-04

Page 2: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

ANDRÉ LUÍS GARLINI

IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO

AMBIENTE FURBOL

Trabalho de Conclusão de Curso submetido à Universidade Regional de Blumenau para a obtenção dos créditos na disciplina Trabalho de Conclusão de Curso II do curso de Ciências da Computação — Bacharelado.

Prof. José Roque Voltolini da Silva - Orientador

BLUMENAU 2008

2008/1-04

Page 3: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO

AMBIENTE FURBOL

Por

ANDRÉ LUÍS GARLINI

Trabalho aprovado para obtenção dos créditos na disciplina de Trabalho de Conclusão de Curso II, pela banca examinadora formada por:

______________________________________________________ Presidente: Prof. José Roque Voltolini da Silva – Orientador, FURB

______________________________________________________ Membro: Prof. Mauro Marcelo Mattos, Doutor – FURB

______________________________________________________ Membro: Prof. Miguel Alexandre Wisintainer, Mestre – FURB

Blumenau, 10 de julho de 2008

Page 4: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

Dedico este trabalho à minha família.

Page 5: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

AGRADECIMENTOS

Agradeço o meu orientador José Roque Voltolini da Silva e a minha família. Todos

foram de extrema importância para a realização deste trabalho.

Agradeço também os meus colegas e professores do curso de BCC, especialmente o

amigo Adilson Hasckel, por todos esses anos de luta e de amizade.

Page 6: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

Toda a nossa ciência, comparada a realidade, é primitiva e infantil – e, no entanto, é a coisa mais preciosa que temos.

Albert Einstein

Page 7: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

RESUMO

Este trabalho apresenta a implementação de Tipos Abstratos de Dados (TADs) no ambiente de programação FURBOL. O trabalho é baseado no Trabalho de Conclusão de Curso (TCC) de Paulo Henrique da Silva (SILVA, 2002), estendendo-o para oferecer suporte a TADs definidos pelos usuários da Linguagem de Programação (LP) FURBOL. Para a especificação formal da sintaxe é utilizada a notação Backus-Naur Form (BNF) e para a semântica são utilizados esquemas de tradução.

Palavras-chave: Tipos abstratos de dados. Esquemas de tradução. Linguagens de programação.

Page 8: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

ABSTRACT

This work presents the abstract data type´s implementation in the FURBOL´s programming environment. The work is based on (SILVA, 2002), extending it to support abstract data types defined by the FURBOL´s programming language users. For the formal syntax specification is used de BNF notation and translation schemes for specification of semantics.

Key-words: Abstract data types. Translation schemes. Programming languages.

Page 9: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

LISTA DE ILUSTRAÇÕES

Figura 1 - Paradigmas de LPs...................................................................................................19

Quadro 1 – Exemplo de uma função em Scheme.....................................................................21

Quadro 2 - Exemplo de código em PROLOG..........................................................................21

Quadro 3 - Exemplo de um TAD definido em C++.................................................................22

Quadro 4 - Trecho de uma gramática apta a análise recursiva preditiva..................................25

Quadro 5 - Exemplo de um esquema de tradução....................................................................27

Quadro 6 - Exemplo de uma gramática de atributos ................................................................27

Quadro 7 - Algoritmo para a construção de um tradutor preditivo dirigido pela sintaxe.........28

Quadro 8 - Instruções do código de três endereços ..................................................................29

Quadro 9 - Registradores do 8086/8088...................................................................................34

Quadro 10 - Propósito dos sinalizadores do registrador FLAGS .............................................36

Quadro 11 - Modos de endereçamento do 8086/8088..............................................................37

Quadro 12 - Conjunto de instruções do 8086/8088..................................................................39

Quadro 13 - Layout de memória de um arquivo .COM............................................................40

Quadro 14 - Estrutura completa de um arquivo .COM ............................................................40

Figura 2 - Interface do FURBOL .............................................................................................41

Quadro 15 - Programa em FURBOL com unidades de processos concorrentes......................42

Quadro 16 - Exemplo de programa na linguagem implementada por Tomazelli (2004).........43

Quadro 17 - Exemplo da definição de uma classe na linguagem especificada em Leyendecker

(2005).....................................................................................................................43

Quadro 18 - Definição do programa principal e blocos ...........................................................46

Quadro 19 - Definição de declarações de variáveis .................................................................47

Quadro 20 - Definição dos tipos de dados................................................................................48

Quadro 21 - Definição de sub-rotinas e procedimentos ...........................................................49

Quadro 22 - Definição de tarefas..............................................................................................49

Quadro 23 - Definição de parâmetros formais .........................................................................50

Quadro 24 - Definição dos comandos da linguagem................................................................51

Quadro 25 - Definição dos comandos da linguagem (continuação).........................................52

Quadro 26 - Definição dos comandos de atribuição, estrutura de chamada de procedimento e

parâmetros atuais ...................................................................................................53

Quadro 27 - Definição do comando de repetição e comandos condicionais............................54

Page 10: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

Quadro 28 - Definição dos comandos de entrada e saída.........................................................55

Quadro 29 - Definição dos comandos incremento e decremento.............................................56

Quadro 30 - Definição dos comandos de criação e exclusão de semáforos .............................56

Quadro 31 - Definição dos comandos de operação P e V em semáforos.................................57

Quadro 32 - Definição dos comandos de criação e exclusão de objetos..................................58

Quadro 33 - Definição da estrutura de controle de expressões ................................................59

Quadro 34 - Definição da estrutura de controle de expressões (continuação) .........................59

Quadro 35 - Definição da estrutura de controle de expressões (continuação) .........................60

Quadro 36 - Definição da estrutura de controle de expressões (continuação) .........................61

Quadro 37 - Definição da estrutura de controle de expressões (continuação) .........................62

Quadro 38 - Definição da estrutura de controle de expressões (continuação) .........................63

Quadro 39 - Especificação dos TADs definidos pelos usuários...............................................64

Quadro 40 - Especificação de um método................................................................................65

Quadro 41 - Especificação do acesso aos membros dos TADs................................................66

Quadro 42 - Exemplo do uso do parâmetro oculto esse ........................................................67

Quadro 43 - Exemplo de um TAD definido conforme a nova especificação da linguagem

FURBOL ...............................................................................................................68

Quadro 44 - Exemplo de um programa que usa o TAD definido no Quadro 43 .....................69

Quadro 45 - Exemplo de um programa que define e usa um TAD..........................................70

Figura 3 - Esboço da tabela de símbolos para o programa do Quadro 45................................71

Quadro 46 - Exemplo de variáveis do tipo objeto....................................................................72

Quadro 47 - Exemplo da criação de um objeto ........................................................................72

Quadro 48 - Exemplo da criação de um objeto com chamada de método construtor ..............73

Quadro 49 – Uso da função 48h do MS-DOS..........................................................................74

Quadro 50 - Trecho do procedimento criarobj ..................................................................74

Quadro 51 – Localização do parâmetro esse na pilha ...........................................................75

Quadro 52 - Exemplo de programa em FURBOL com chamada de método...........................76

Quadro 53 - Código gerado para a chamada de um método ....................................................76

Quadro 54 - Código para a chamada de um método por outro método....................................77

Quadro 55 - Definição de um TAD com quatro atributos........................................................78

Quadro 56 - Código do método constroi definido no Quadro 55 ......................................78

Quadro 57 - Trecho de programa que utiliza a palavra reservada nulo .................................79

Quadro 58 - Código gerado pelo enunciado da linha 14 do Quadro 57 ...................................79

Page 11: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

Quadro 59 - Exemplo de programa que exclui dois objetos ....................................................80

Quadro 60 - Exemplo de código gerado para exclusão de um objeto ......................................80

Quadro 61 - Exemplo de código gerado para exclusão de um objeto com chamada de destrutor

...............................................................................................................................81

Quadro 62 - Uso da função 49h do MS-DOS...........................................................................81

Quadro 63 - Trecho do procedimento destruirobj ...........................................................81

Figura 4 - Diagrama de casos de uso........................................................................................82

Quadro 64 - Caso de uso compilar ...........................................................................................83

Figura 5 - Diagrama de classes do FURBOL...........................................................................84

Figura 6 - Diagrama de seqüência para o caso de uso compilar...............................................86

Quadro 65 - Declaração das palavras reservadas do FURBOL ...............................................88

Quadro 66 - Implementação do não-terminal TAD...................................................................89

Quadro 67 - Implementação do não-terminal MetodoTAD ....................................................91

Quadro 68 - Implementação do não-terminal Esse ................................................................93

Quadro 69 - Código do método ProcurarSimboloNivel do gerenciador de símbolos..93

Quadro 70 - Exemplo de exceção gerada pelo compilador ......................................................94

Quadro 71 – Método que gera código que atribui ao registrador de segmento ES o conteúdo

do parâmetro esse ...............................................................................................94

Quadro 72 - Método que gera código assembly para acessar os atributos de um TAD...........95

Quadro 73 – Trecho de código do compilador que invoca os métodos apresentados no Quadro

71 e no Quadro 72..................................................................................................95

Figura 7 - Acesso as principais funcionalidades do FURBOL.................................................96

Figura 8 - Ambiente exibindo o código intermediário gerado pelo compilador ......................97

Figura 9 - Ambiente exibindo o código de montagem gerado pelo compilador ......................98

Figura 10 - Ambiente exibindo a saída do montador e do ligador ...........................................99

Quadro 74 - Atalhos no teclado do ambiente ...........................................................................99

Quadro 75 – Exemplo de problema com parâmetros por referência......................................100

Quadro 76 - Exemplo de uma expressão que apresenta um resultado incorreto....................100

Quadro 77 - Limitações do trabalho com relação aos arrays.................................................101

Quadro 78 - Limitações do trabalho com relação ao uso de símbolos ainda não definidos...101

Quadro 79 - Procedimento do núcleo criarobj .................................................................106

Quadro 80 - Procedimento do núcleo destruirobj ..........................................................107

Quadro 81 - Procedimento do núcleo imprime ...................................................................108

Page 12: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

LISTA DE SIGLAS

BCD – Binary-Coded Decimal

BNF – Backus-Naur Form

CLR – Commom Language Runtime

FURB – Universidade Regional de Blumenau

GLC – Gramática Livre de Contexto

IP – Instruction Point

LP – Linguagem de Programação

MS-DOS – Microsoft - Disk Operating System

MSIL – Microsoft Intermediate Language

PC – Personal Computer

PSP – Prefixo do Segmento do Programa

RF – Requisisto Funcional

RNF – Requisito Não-Funcional

SO – Sistema Operacional

TAD – Tipo Abstrato de Dado

TCC – Trabalho de Conclusão de Curso

UCP – Unidade Central de Processamento

UML – Unified Modeling Language

LISTA DE SÍMBOLOS

@ - arroba

Page 13: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

SUMÁRIO

1 INTRODUÇÃO..................................................................................................................15

1.1 OBJETIVOS DO TRABALHO ........................................................................................16

1.2 ESTRUTURA DO TRABALHO......................................................................................16

2 FUNDAMENTAÇÃO TEÓRICA....................................................................................17

2.1 LINGUAGENS DE PROGRAMAÇÃO...........................................................................17

2.1.1 Paradigmas de linguagens de programação ....................................................................18

2.1.1.1 Paradigma imperativo...................................................................................................19

2.1.1.2 Paradigma declarativo ..................................................................................................20

2.1.2 Tipos abstratos de dados .................................................................................................21

2.2 COMPILADORES............................................................................................................23

2.2.1 Análise léxica..................................................................................................................23

2.2.2 Análise sintática ..............................................................................................................23

2.2.2.1 Gramática......................................................................................................................24

2.2.2.1.1 Gramáticas livres do contexto..................................................................................25

2.2.2.2 Análise recursiva preditiva ...........................................................................................25

2.2.3 Análise semântica............................................................................................................26

2.2.4 Tradução dirigida pela sintaxe ........................................................................................26

2.2.4.1 Esquemas de tradução...................................................................................................26

2.2.4.1.1 Gramática de atributos .............................................................................................27

2.2.4.1.2 Esquemas de tradução L-atribuídos .........................................................................28

2.2.4.2 Projeto de um tradutor preditivo...................................................................................28

2.2.5 Geração de código intermediário ....................................................................................29

2.2.6 Código intermediário de três endereços..........................................................................29

2.2.7 Gerência de memória ......................................................................................................30

2.2.8 Geração de código-alvo...................................................................................................30

2.3 MICROPROCESSADOR 8088 ........................................................................................31

2.3.1 Memória ..........................................................................................................................32

2.3.1.1 Endereços segmentados ................................................................................................32

2.3.1.2 Pilha ..............................................................................................................................33

2.3.2 Registradores...................................................................................................................33

2.3.2.1 Registradores de propósito geral ..................................................................................34

Page 14: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

2.3.2.2 Registradores de ponteiros e de índices........................................................................34

2.3.2.3 Registradores de segmento ...........................................................................................35

2.3.2.4 Registrador de ponteiro da instrução ............................................................................35

2.3.2.5 Registrador de sinalizadores .........................................................................................36

2.3.3 Endereçando a memória por registradores......................................................................37

2.3.4 Interrupções.....................................................................................................................38

2.3.5 Conjunto de instruções do 8086/8088.............................................................................38

2.3.6 Layout de memória de um arquivo .COM.......................................................................39

2.4 FURBOL ...........................................................................................................................40

2.5 TRABALHOS CORRELATOS........................................................................................42

3 DESENVOLVIMENTO....................................................................................................44

3.1 REQUISITOS DO SISTEMA A SER DESENVOLVIDO...............................................44

3.2 ESPECIFICAÇÃO DA LINGUAGEM FURBOL ...........................................................45

3.2.1 Programa principal e bloco de comandos .......................................................................45

3.2.2 Declarações de variáveis e definições de tipos ...............................................................46

3.2.3 Procedimentos e tarefas...................................................................................................48

3.2.4 Comandos da linguagem.................................................................................................50

3.2.5 Expressões.......................................................................................................................59

3.2.6 Definições dos TADs ......................................................................................................63

3.2.7 Exemplo de um TAD definido conforme a nova especificação do FURBOL................67

3.2.8 Exemplo de uma tabela de símbolos para um programa que define e usa um TAD ......69

3.3 ASPECTOS RELEVANTES CONSIDERADOS PARA IMPLEMENTAÇÃO DA

EXTENSÃO DA LINGUAGEM PROPOSTA.................................................................72

3.3.1 Criação de objetos...........................................................................................................72

3.3.1.1 Procedimento criarobj ............................................................................................74

3.3.2 Parâmetro oculto esse ...................................................................................................74

3.3.3 Chamada de métodos ......................................................................................................75

3.3.4 Acesso aos atributos........................................................................................................77

3.3.5 Palavra reservada nulo ..................................................................................................79

3.3.6 Exclusão de objetos.........................................................................................................79

3.3.6.1 Procedimento destruirobj .....................................................................................81

3.4 ESPECIFICAÇÃO DO AMBIENTE................................................................................82

3.4.1 Diagrama de casos de uso ...............................................................................................82

Page 15: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

3.4.2 Diagrama de classes ........................................................................................................83

3.4.3 Diagrama de seqüência para o caso de uso compilar......................................................85

3.5 IMPLEMENTAÇÃO DO AMBIENTE............................................................................87

3.5.1 Analisador léxico ............................................................................................................87

3.5.2 Analisador sintático preditivo .........................................................................................88

3.5.2.1 Exemplos da implementação de não-terminais da nova especificação da LP..............89

3.5.2.2 Erros de compilação .....................................................................................................94

3.5.2.3 Acesso aos atributos do TAD .......................................................................................94

3.6 OPERACIONALIDADE DO AMBIENTE......................................................................95

3.7 RESULTADOS E DISCUSSÃO ......................................................................................99

4 CONCLUSÕES................................................................................................................103

4.1 EXTENSÕES ..................................................................................................................103

REFERÊNCIAS BIBLIOGRÁFICAS ...............................................................................104

APÊNDICE A – Procedimentos do núcleo para criação e exclusão de objetos..............106

APÊNDICE B – Procedimento do núcleo para imprimir inteiros...................................108

Page 16: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

15

1 INTRODUÇÃO

Uma Linguagem de Programação (LP) serve como meio de comunicação entre o

indivíduo que deseja resolver um determinado problema e o computador escolhido para

ajudá-lo na solução. Uma LP, portanto, deve fazer a ligação entre o pensamento humano e a

precisão requerida pelos computadores (PRICE; TOSCANI, 2001, p. 1). De acordo com Aho,

Sethi e Ullman (1995, p. 1), existem milhares de LPs, que vão desde linguagens tradicionais

até linguagens especializadas. O Java, o C++, o Pascal e o FURBOL são alguns exemplos de

LPs.

Para realizar a tradução de uma LP para um programa alvo ou para outra LP existem

programas especializados chamados de compiladores. “Posto de forma simples, um

compilador é um programa que lê um programa escrito numa linguagem – a linguagem fonte

– e o traduz num programa equivalente em outra linguagem – a linguagem alvo” (AHO;

SETHI; ULLMAN, 1995, p. 1).

Toda LP apresenta seu próprio conjunto de símbolos e regras para a formação e

interpretação de programas (VAREJÃO, 2004, p. 2). Algumas linguagens oferecem suporte a

Tipos Abstratos de Dados (TADs) definidos pelos usuários. Um TAD é um encapsulamento

que inclui a representação de dados de um tipo específico de dado e os subprogramas que

fornecem as operações para esse tipo. Uma instância de um TAD é chamada de objeto

(SEBESTA, 2000, p. 398).

Entre as milhares de LPs existentes pode-se citar o FURBOL. O FURBOL (acrônimo

de FURB e ALGOL) é uma LP que vem sendo desenvolvida por vários integrantes do curso

de Ciências da Computação da Universidade Regional de Blumenau (FURB) e que possui

como um dos seus grandes destaques as unidades de programas concorrentes (SILVA, 2002).

Contudo, o FURBOL atualmente ainda não oferece a possibilidade da criação de TADs

definidos pelos usuários.

Com o objetivo de ampliar a LP FURBOL, este trabalho propõe-se a estender a

mesma, a partir da implementação de Silva (2002), adicionando o suporte a TADs.

Page 17: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

16

1.1 OBJETIVOS DO TRABALHO

O objetivo deste trabalho é estender a LP FURBOL.

O objetivo específico do trabalho é adicionar a possibilidade da criação de TADs pelos

usuários do FURBOL.

1.2 ESTRUTURA DO TRABALHO

O trabalho está dividido em quatro capítulos. No capítulo 2 é feito um levantamento

sobre os assuntos relevantes para o desenvolvimento do trabalho. O terceiro capítulo

apresenta o desenvolvimento da extensão do FURBOL para a inclusão de TADs. O quarto

capítulo contém as conclusões do trabalho, as limitações e sugestões de extensões para o

mesmo.

Page 18: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

17

2 FUNDAMENTAÇÃO TEÓRICA

Neste capítulo são apresentadas informações sobre: LPs, compiladores, o

microprocessador 8088 e FURBOL. Na última seção são descritos alguns trabalhos correlatos.

2.1 LINGUAGENS DE PROGRAMAÇÃO

Segundo Varejão (2004, p. 1), uma LP é uma ferramenta utilizada para escrever

programas, os quais são conjuntos de instruções a serem seguidas pelo computador para

realizar um determinado processo.

Todas as LPs possuem as suas próprias características, como o seu conjunto de

símbolos e regras para a formação e interpretação de programas (VAREJÃO, 2004, p. 2).

Segundo Price e Toscani (2001, p. 1), se uma LP apresentar construções que estão mais

próximas do problema a ser resolvido, esta linguagem é considerada de alto nível. Por outro

lado, LPs que estão mais próximas das instruções entendidas pela máquina são chamadas de

linguagens de baixo nível. Como exemplos de linguagens de baixo nível podem-se citar as

linguagens de montagem (assembly) e como exemplo de LP de alto nível o Pascal.

Varejão (2004, p. 6-12) lista várias propriedades que são desejáveis em uma LP, as

quais são:

a) legibilidade: propriedade que diz respeito à facilidade para se ler e entender um

programa. Quanto mais fácil for seguir as instruções de um programa, mais fácil

será entender o que está sendo feito e descobrir erros de programação;

b) redigibilidade: essa propriedade refere-se a facilidade de escrita de um programa.

A redigibilidade de programas pode conflitar em alguns casos com a legibilidade.

Por exemplo, a LP C permite a redação de comandos complexos, mas que podem

não identificar de maneira muito clara a sua funcionalidade;

c) confiabilidade: essa propriedade relaciona-se aos mecanismos fornecidos pela LP

para incentivar a construção de programas confiáveis. LPs que possuem

mecanismos para detectar eventos indesejáveis e especificar respostas adequadas a

tais eventos permitem a construção de programas mais confiáveis. Uma LP pode,

por exemplo, verificar em tempo de execução se o índice de um vetor está dentro

Page 19: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

18

dos limites permitidos para o vetor indexado e caso o índice esteja fora dos limites

permitidos lançar uma exceção, interrompendo o fluxo normal de execução do

programa. Essa mesma LP pode fornecer um mecanismo para capturar a exceção

lançada, permitindo que o programador trate o evento indesejável adequadamente;

d) eficiência: muito embora, hoje em dia, boa parte da responsabilidade de gerar

código eficiente seja delegado ao compilador, as características de uma LP podem

determinar se os programas escritos naquela linguagem serão mais ou menos

eficientes;

e) facilidade de aprendizado: uma LP deve ser de fácil aprendizado pelo

programador. LPs como C++ que possuem múltiplas maneiras de realizar a mesma

funcionalidade tendem a ser mais difíceis de aprender;

f) ortogonalidade: essa propriedade diz respeito à capacidade da LP permitir ao

programador combinar seus conceitos básicos, sem que se produzam efeitos

atípicos nessa combinação. Uma LP é tão mais ortogonal quanto menor for o

número de exceções aos seus padrões regulares;

g) reusabilidade: essa propriedade diz respeito à possibilidade de reutilizar o mesmo

código para diversas aplicações. Quanto mais reusável for um código, maior será a

produtividade de programação;

h) modificabilidade: propriedade que se refere às facilidades oferecidas pelas LPs

para possibilitar a alteração do programa, sem que tais modificações impliquem

mudanças em outras partes dos programas. Exemplos de mecanismos que

proporcionam boa modificabilidade são o uso de constantes simbólicas e a

separação entre interface e implementação de subprogramas;

i) portabilidade: é desejável que programas escritos em uma LP comportem-se da

mesma maneira, independente da ferramenta utilizadas para traduzí-los para a

linguagem de máquina ou o hardware e sistema operacional onde serão

executados.

2.1.1 Paradigmas de linguagens de programação

Varejão (2004, p. 17) chama de paradigma “um conjunto de características que servem

para categorizar um grupo de linguagens.”. Varejão (2004, p. 17) classifica os paradigmas das

LPs em duas categorias principais: imperativo e declarativo e adota a classificação

Page 20: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

19

apresentada na Figura 1.

Fonte: Varejão (2004, p. 17).

Figura 1 - Paradigmas de LPs

2.1.1.1 Paradigma imperativo

Os programas escritos em uma LP que adota o paradigma imperativo especificam

como uma computação é realizada por uma seqüência de alterações no estado da memória do

computador. O foco dos programas nesse paradigma é especificar como um processamento

deve ser feito no computador (VAREJÃO, 2004, p. 17). Ainda, seguindo a classificação

apresentada na Figura 1, o paradigma imperativo é subdividido nos seguintes paradigmas

(VAREJÃO, 2004, p. 18-19):

a) paradigma estruturado: esse tipo de programação baseia-se na idéia de

desenvolvimento de programas por refinamentos sucessivos. A programação

estruturada desestimula o uso de comandos de desvio incondicional e incentiva a

divisão dos programas em subprogramas e em blocos aninhados de comandos.

Pascal é um exemplo de LP que adota o paradigma estruturado;

b) paradigma orientado a objetos: esse paradigma pode ser considerado a evolução do

paradigma estruturado. As linguagens que adotam o paradigma orientado a objetos

enfocam as abstrações de dados como elemento básico da programação. Nesse

paradigma existe o conceito de classes que são abstrações que definem uma

estrutura de dados e um conjunto de operações que podem ser realizadas sobre

elas. Outros conceitos importantes no paradigma orientado a objeto é o conceito de

herança e polimorfismo. Exemplos de LPs que adotam esse paradigma são o C++

e JAVA;

Page 21: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

20

c) paradigma concorrente: esse paradigma engloba LPs que oferecem facilidades para

o desenvolvimento de sistemas concorrentes. A programação concorrente ocorre

quando vários processos executam simultaneamente e concorrem por recursos, tais

como dados ou dispositivos periféricos. ADA é um exemplo de LP que oferece

suporte a concorrência.

2.1.1.2 Paradigma declarativo

“Em contraste com o paradigma imperativo, no qual os programas são especificações

de como o computador deve realizar uma tarefa, no paradigma declarativo os programas são

especificações sobre o que é essa tarefa.” (VAREJÃO, 2004, p. 19). A maior preocupação do

programador que utiliza uma LP que adota este paradigma é descrever de forma abstrata a

tarefa a ser resolvida.

O paradigma declarativo é subdivido em dois paradigmas, os quais são (VAREJÃO,

2004, p. 19):

a) paradigma funcional: linguagens funcionais operam sobre funções, as quais

recebem listas de valores e retornam um valor. Um programa funcional é uma

chamada de função, que por sua vez pode chamar outras funções, para gerar um

valor de retorno. LISP e Scheme são exemplos de LPs que seguem o paradigma

funcional. No Quadro 1 é apresentado um exemplo de uma função em Scheme que

compara duas listas simples;

b) paradigma lógico: um programa lógico é composto por cláusulas que definem

predicados e relações factuais. Um diferencial do paradigma lógico é que a

execução dos programas corresponde a um processo de dedução automática.

Assim, quando uma questão é formulada, um mecanismo de inferência tenta

deduzir novos fatos a partir dos já existentes para verificar a veracidade da

questão. Como exemplo de LP que segue o paradigma lógico pode-se citar o

PROLOG. No Quadro 2 é exibido um exemplo de código em PROLOG.

Page 22: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

21

(DEFINE (igualsimples lis1 lis2) (COND ((NULL? lis1) (NULL? lis2)) ((NULL? lis2) '()) ((EQ? (CAR lis1) (CAR lis2)) (igualsimples CDR(lis1) CDR(lis2))) (ELSE '()) )) Fonte: Sebesta (2000, p. 554).

Quadro 1 – Exemplo de uma função em Scheme

pais(X, Y) :- mãe(X, Y). pais(X, Y) :- pai(X, Y). avô(X, Z) :- pais(X, Y) , pais(Y, Z). irmãos(X, Y) :- mãe(M, X) , mãe(M, Y), pai(F, X) , pai(F, Y). Fonte: Sebesta (2000, p. 581).

Quadro 2 - Exemplo de código em PROLOG

2.1.2 Tipos abstratos de dados

A criação de tipos abstratos de dados é um recurso oferecido por várias linguagens de

programação. Para Varejão (2004, p. 168), tipos abstratos de dados “são conjuntos de valores

que apresentam um comportamento uniforme definido por um grupo de operações

(geralmente, um grupo de constantes iniciais e um conjunto de funções e procedimentos)”.

Segundo Sebesta (2000, p. 398) a abstração de dados é uma arma contra a

complexidade. É um meio de tornar programas grandes e/ou complicados mais manejáveis.

Para Varejão (2004, p. 168) com o uso de TADs o código fica mais confiável, pois o usuário

do TAD não pode efetuar livremente mudanças nos dados. Isso só pode ser feito pelas

operações do implementador do TAD. A representação interna do TAD fica “escondida” do

usuário do tipo. Além disso, desde que não haja uma alteração na interface do TAD, a sua

implementação pode ser modificada sem que o código do usuário seja também modificado.

Isso torna a manutenção dos programas menos complexa.

Para Sebesta (2000, p. 399), um tipo TAD (no contexto dos tipos definidos pelo

usuário) é um tipo de dado que satisfaz as duas condições seguintes:

a) a representação ou a definição do TAD e as operações sobre os objetos do tipo

estão contidas em uma única unidade sintática. Além disso, outras unidades de

programa podem ter permissão para criar variáveis do TAD definido;

b) a representação de objetos do TAD não é visível pelas unidades de programa que

usam o tipo, de modo que as únicas operações diretas possíveis sobre esses objetos

Page 23: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

22

são aquelas oferecidas na definição do tipo.

Segundo Varejão (2004, p. 168), existem quatro tipos de operações que podem ser

realizadas sobre um tipo abstrato de dado, as quais são:

a) operações construtoras: usadas para inicializar o TAD;

b) operações consultoras: usadas para obter informações relacionadas com os valores

do TAD;

c) operações atualizadoras: permitem a alteração dos valores do TAD;

d) operações destrutoras: responsáveis por realizar qualquer atividade de finalização

quando o TAD não for mais necessário.

C++ é uma das linguagens existentes que oferece suporte a TAD definidos pelo

usuário. Em C++ um TAD definido pelo usuário é chamado de classe. “Os dados definidos

em uma classe são chamados membros de dados; as funções definidas em uma classe são

chamadas funções-membro” (SEBESTA, 2000, p. 407, grifo do autor). No Quadro 3 é

apresentado um exemplo de um TAD definido em C++.

class pilha { private: //** Estes membros são visíveis somente a outros membros //** e amigos int *ptr_pilha; int tam_max; int top_ptr; public: //** Estes membros são visíveis aos clie ntes pilha() { //** Um construtor ptr_pilha = new int [ 100 ]; tam_max = 99; top_ptr = -1; } ~pilha() {delete [] ptr_pilha;}; //** Um destr utor void push(int numero) { if (top_ptr == tam_max) cout << “Erro no pop – a pilha esta cheia\n ”; else ptr_pilha[++top_ptr] = numero; } void pop() { if (top_ptr == -1) cout << “Erro no pop – a pilha esta vazia\n ”; else top_ptr--; } int top() {return (ptr_pilha[top_ptr]);} int empty() {return (top_ptr == -1);} } Fonte: adaptado de Sebesta (2000, p. 408-409).

Quadro 3 - Exemplo de um TAD definido em C++

No exemplo do Quadro 3, o identificador pilha que aparece logo após a palavra

reservada class é o nome do TAD definido. Já os identificadores ptr_pilha , tam_max e

top_ptr são os membros de dados do TAD. O identificador pilha é uma função-membro

construtora, ~pilha uma função destrutora, push e pop funções atualizadoras e top e empty

Page 24: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

23

funções consultoras.

2.2 COMPILADORES

Segundo Price e Toscani (2001, p. 4), no contexto de LPs, um tradutor é um sistema

que recebe como entrada um programa escrito em uma LP (linguagem fonte) e produz como

saída um programa equivalente em outra linguagem (linguagem objeto).

Um compilador é um tipo de programa tradutor que mapeia programas escritos em

uma LP de alto nível para programas equivalentes em linguagem simbólica (assembly) ou

linguagem de máquina (PRICE; TOSCANI, 2001, p. 4).

A tradução (compilação) de um programa é um processo que costuma ser dividido em

algumas etapas, as quais podem ser: análise léxica, análise sintática, análise semântica e

geração de código.

2.2.1 Análise léxica

A análise léxica é a primeira etapa do processo de compilação. Também denominado

de scanner, a função de um analisador léxico é “fazer a leitura do programa fonte, caractere a

caractere, e traduzí-lo para uma seqüência de símbolos léxicos, também chamados tokens.”

(PRICE; TOSCANI, 2001, p. 17).

Price e Toscani (2001, p. 17) citam como exemplos de tokens as palavras reservadas,

os identificadores, as constantes e os operadores da LP. Caracteres não significativos como

espaços em branco e comentários são desprezados pelo analisador léxico.

2.2.2 Análise sintática

Para Price e Toscani (2001, p. 29) a análise sintática é a segunda fase de um tradutor.

A sua função é verificar se as construções usadas no programa estão gramaticalmente

corretas. Segundo Louden (2004, p. 95) a sintaxe de uma LP é normalmente dada pelas regras

gramaticais de uma Gramática Livre de Contexto (GLC).

Page 25: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

24

Dada uma GLC G e uma sentença do programa fonte s , o objetivo do analisador

sintático é verificar se a sentença s pertence à linguagem gerada por G. O analisador sintático,

também chamado de parser, recebe do analisador léxico a seqüência de tokens que forma a

sentença s e produz como resultado uma árvore de derivação para s , se a sentença é válida

para a linguagem, ou emite uma mensagem de erro, caso contrário (PRICE; TOSCANI, 2001,

p. 29).

De acordo com Price e Toscani (2001, p. 29) há duas estratégias básicas para a análise

sintática:

a) estratégia top-down ou descendente: os métodos baseados nesta estratégia

constroem a árvore de derivação a partir do símbolo inicial da gramática (raiz da

árvore), fazendo a árvore crescer até atingir suas folhas. Nesta estratégia, em cada

passo, um lado esquerdo de produção é substituído por um lado direito (expansão);

b) estratégia bottom-up ou redutiva: essa estratégia realiza a análise no sentido

inverso, ou seja, a partir dos tokens do texto fonte (folhas da árvore de derivação)

constrói a árvore até o símbolo inicial da gramática.

2.2.2.1 Gramática

Segundo Price e Toscani (2001, p. 18) uma gramática G é um mecanismo para gerar

sentenças de uma linguagem e é definida pela quádrupla: (N, T, P, S), onde N é um conjunto

de símbolos não-terminais (variáveis sintáticas), T é um conjunto de símbolos terminais

(tokens) onde (T ∩ N = ∅), P é um conjunto de regras de produção (regras sintáticas) e S é o

símbolo inicial da gramática (S ∈ N).

As regras de produção de uma gramática são representadas por α → β e definem o

mecanismo de geração de sentenças da linguagem. Uma seqüência de regras da forma α →

β1, α → β2, ..., α → βn, pode ser representada de forma simplificada por: α → β1 | β2 | ... | βn.

A aplicação sucessiva de regras de produção, a partir do símbolo inicial da gramática, permite

derivar as sentenças válidas da linguagem representada pela gramática (PRICE; TOSCANI,

2001, p. 18).

Segundo Aho, Sethi e Ullman (1995, p. 12), uma gramática deriva cadeias começando

pelo símbolo inicial da gramática e, então, substituindo repetidamente um não-terminal pelo

lado direito de uma produção para aquele não-terminal. As cadeias de tokens que podem ser

Page 26: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

25

derivadas a partir do símbolo inicial formam a linguagem definida pela gramática.

2.2.2.1.1 Gramáticas livres do contexto

As gramáticas livres do contexto, popularizadas pela notação BNF, formam a base

para a análise sintática das LPs, pois permitem descrever a maioria das linguagens usadas

atualmente (PRICE; TOSCANI, 2001, p. 30).

De acordo com Price e Toscani (2001, p. 30) uma GLC é qualquer gramática cujas

produções são da forma: A → α onde A é um símbolo não-terminal e α é um elemento de (N ∪

T) * que é o conjunto de sentenças que podem ser formadas por N e T, incluindo a palavra

vazia Є. O nome livre do contexto deriva do fato de o não-terminal A poder ser substituído por

α em qualquer contexto, isto é, sem depender de qualquer análise dos símbolos que sucedem

ou antecedem A na forma sentencial em questão.

2.2.2.2 Análise recursiva preditiva

Um analisador sintático preditivo é um tipo de analisador sintático descendente

(PRICE; TOSCANI, 2001, p. 38).

Segundo Price e Toscani (2001, p. 41) os analisadores sintáticos preditivos exigem

que:

a) a gramática não tenha recursividade à esquerda, ou seja, um não-terminal não pode

derivar ele mesmo;

b) a gramática esteja fatorada à esquerda;

c) que para os não-terminais com mais de uma regra de produção, os primeiros

terminais deriváveis sejam capazes de identificar, univocamente, a produção que

deve ser aplicada a cada instante da análise.

O Quadro 4 apresenta o trecho de uma gramática apta a análise recursiva preditiva.

COMANDO � if EXPR then COMANDO | while EXPR do COMANDO | repeat LISTA until EXPR | id := EXPR Fonte: Price e Toscani (2001, p. 42).

Quadro 4 - Trecho de uma gramática apta a análise recursiva preditiva

Para as produções do Quadro 4, os primeiros terminais dos lados direitos (if , while ,

Page 27: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

26

repeat e id ) determinam, diretamente, a produção a ser aplicada.

2.2.3 Análise semântica

A principal atividade de um analisador semântico é determinar se as estruturas

sintáticas analisadas fazem sentido, ou seja, verificar se um identificador declarado como

variável é usado como tal; se existe compatibilidade entre operandos e operadores em

expressões; entre outros (PRICE; TOSCANI, 2001, p. 9).

Para Louden (2004, p. 259) a análise semântica pode ser dividida em duas categorias.

A primeira é a análise de um programa requerida pelas regras da LP, para verificar sua

correção e garantir sua execução. A outra categoria é aquela efetuada pelo compilador para

melhorar a eficiência de execução do programa traduzido (otimização).

2.2.4 Tradução dirigida pela sintaxe

Tradução dirigida pela sintaxe é uma técnica que permite realizar a tradução do

programa (geração de código) simultaneamente a análise sintática. Nesta técnica ações

semânticas (envolvidas entre chaves) são associadas às regras de produção da gramática de

modo que, quando uma dada produção é processada, essas ações são executadas (PRICE;

TOSCANI, 2001, p. 85).

2.2.4.1 Esquemas de tradução

Segundo Price e Toscani (2001, p. 86) um esquema de tradução é uma extensão de

uma GLC que associa atributos aos símbolos gramaticais e de ações semânticas às regras de

produção. As ações semânticas podem ser avaliações de atributos ou chamadas a

procedimentos e funções. No Quadro 5 é apresentado um exemplo de esquema para traduzir

expressões infixadas para expressões pós-fixadas.

Page 28: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

27

E � T R R � Op T { print(Op.simbol) } R | Є T � num { print(num.lexval)}

Fonte: Price e Toscani (2001, p. 88). Quadro 5 - Exemplo de um esquema de tradução

Louden (2004, p. 261) apresenta alguns exemplos típicos de atributos, os quais são:

a) tipo de dados de uma variável;

b) valor de uma expressão;

c) localização de uma variável na memória;

d) código-objeto de um procedimento;

e) quantidade de dígitos significativos em um número.

Os atributos de um esquema de tradução são classificados em:

a) atributos sintetizados: um atributo é classificado como sintetizado se todas as suas

dependências apontarem de filho para pai na árvore de análise sintática (estrutura

de dados utilizada para representação da estrutura sintática de uma linguagem)

(LOUDEN, 2004, p. 279);

b) atributos herdados: são aqueles cujo valor são computados a partir dos valores do

nó pai e/ou irmãos na árvore de análise sintática (AHO; SETHI; ULLMAN, 1995,

p. 120).

2.2.4.1.1 Gramática de atributos

As ações semânticas de um esquema de tradução podem produzir efeitos colaterais,

tais como, imprimir um valor, armazenar um literal em uma tabela ou atualizar uma variável

global. Quando o esquema de tradução não produz efeitos colaterais ele é dito ser uma

gramática de atributos. Nesse caso, as ações semânticas são atribuições ou funções

envolvendo, exclusivamente, os atributos do esquema de tradução (PRICE; TOSCANI, 2001,

p. 89). No Quadro 6 é apresentado um exemplo de uma gramática de atributos.

A � A 1 digit { A.val := A 1.val + digit.lexval }

A � digit { A.val := digit.lexval }

Fonte: Price e Toscani (2001, p. 90). Quadro 6 - Exemplo de uma gramática de atributos

No exemplo apresentado, val é um atributo do símbolo não-terminal A, enquanto

lexval é um atributo do símbolo terminal digit . A presença do índice 1 em A1 serve para

diferenciar as duas ocorrências de A. Isso é necessário, pois o atributo val será diferente para

Page 29: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

28

cada caso de A. O conteúdo entre chaves é a ação semântica a ser executada.

2.2.4.1.2 Esquemas de tradução L-atribuídos

Os esquemas de tradução L-atribuídos restringem o uso de atributos herdados, de

modo a permitir que as ações semânticas possam ser executadas em um único passo durante a

análise sintática. Basicamente a restrição imposta é “para cada símbolo X no lado direito de

uma regra de produção, a ação que calcula um atributo herdado de X deve aparecer à esquerda

de X” (PRICE; TOSCANI, 2001, p. 94).

2.2.4.2 Projeto de um tradutor preditivo

Um tradutor preditivo é um programa que possui um procedimento para cada não-

terminal. Cada procedimento decide que produção usar e implementa o lado direito das

produções (AHO; SETHI; ULLMAN, 1995, p. 21). O seu algoritmo é apresentado no Quadro

7.

Entrada: um esquema de tradução (o esquema deve ser baseado numa gramática apropriada para análise preditiva). Resultado: código de um tradutor dirigido por sintaxe. Método: 1) Para cada não-terminal A, construa uma função que tenha um parâmetro formal para cada atributo herdado de A e que retorne os valores dos atributos sintetizados de A. Além disso, a função para A deve ter uma variável local para cada atributo de símbolo gramatical que aparece nas produções de A. 2) O código para o não-terminal A deve decidir qual produção usar, baseado no símbolo corrente da entrada. Considerando o lado direito da produção a ser usada, da esquerda para a direita, o código associado deve implementar o seguinte: i) para cada token X, verificar se X é o token lido e avançar a leitura; ii) para cada não-terminal B, gerar uma atribuição c := B(b1, b2, ..., bk), sendo b1, b2, ..., bk variáveis para os atributos herdados de B, e c a variável para o atributo sintetizado de B; iii) para cada ação semântica, copiar o código correspondente, substituindo cada referência a atributo pela variável desse atributo. Fonte: adaptado de Price e Toscani (2001, p. 110).

Quadro 7 - Algoritmo para a construção de um tradutor preditivo dirigido pela sintaxe

Page 30: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

29

2.2.5 Geração de código intermediário

Price e Toscani (2001, p. 115) definem a geração de código intermediário como sendo

a transformação da árvore de derivação em um segmento de código que pode, eventualmente,

ser o código objeto final, mas que na maioria das vezes, constitui-se num código

intermediário.

De acordo com Aho, Sethi e Ullman (1995, p. 200), apesar de um compilador poder

traduzir o programa-fonte diretamente na linguagem-alvo, existem alguns benefícios em se

usar uma forma intermediária de código independente de máquina. Price e Toscani (2001, p.

115) listam alguns benefícios da geração de código intermediário, os quais são:

a) possibilita a otimização do código intermediário;

b) simplifica a implementação do compilador, resolvendo, gradativamente, as

dificuldades da passagem de código fonte para objeto (alto-nível para baixo-nível);

c) possibilita a tradução do código intermediário para diversas máquinas.

2.2.6 Código intermediário de três endereços

De acordo com Price e Toscani (2001, p. 117), no código intermediário de três

endereços, cada instrução faz referência no máximo a três variáveis (endereços de memória)

(Quadro 8).

A := B op C A := op B A := B goto L if A oprel B goto L

Fonte: Price e Toscani (2001, p. 117). Quadro 8 - Instruções do código de três endereços

No exemplo do Quadro 8, A, B e C representam endereços de variáveis, op representa

um operador binário (primeira instrução) ou unário (segunda instrução), oprel representa um

operador relacional e L representa o rótulo de uma instrução.

Page 31: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

30

2.2.7 Gerência de memória

De acordo com Price e Toscani (2001, p. 169-170) a memória para um programa

compilado deve conter:

a) o código objeto gerado;

b) espaço para a área estática;

c) a pilha para ativação dos procedimentos;

d) espaço para a memória dinâmica (heap).

A reserva de memória para os dados de um programa pode ser feita de três maneiras,

as quais são (PRICE; TOSCANI, 2001, p. 170):

a) alocação estática: quando o comprimento de um dado é conhecido durante a

compilação e não é alterado durante a execução do programa, o compilador pode

gerar diretivas para que a reserva de memória para esse dado seja feita na

inicialização do programa;

b) alocação em pilha: embora muitas vezes o espaço necessário para a chamada de

um procedimento, tais como, variáveis locais, parâmetros e endereço de retorno,

seja conhecido em tempo de compilação, a alocação desse espaço somente pode

ser feita em tempo de execução, pois a ordem de chamadas é determinada pela

execução do programa. Portanto, a alocação deve ser feita dinamicamente e, em

geral, é realizada numa estrutura em pilha, a qual pode ser a própria pilha de

execução da máquina;

c) alocação dinâmica: para as estruturas de dados referenciadas através de ponteiros,

as áreas também são reservadas dinamicamente. Essas áreas são alocadas e

liberadas através de comandos específicos da LP, tais como new e dispose da

linguagem Pascal. Normalmente, essas estruturas são alocadas em uma área

chamada de heap, que cresce no sentido contrário ao da pilha.

2.2.8 Geração de código-alvo

Segundo Aho, Sethi e Ullman (1995, p. 222-223), a geração de código alvo é a fase

final de um compilador. Essa recebe como entrada a representação intermediária do

programa-fonte e produz como saída um programa-alvo equivalente. Esta saída pode assumir

Page 32: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

31

uma variedade de formas, tais como, linguagem absoluta de máquina, linguagem relocável de

máquina ou linguagem de montagem (assembly).

Price e Toscani (2001, p. 186) apresentam quatro aspectos que devem ser considerados

no projeto de geradores de código, os quais são:

a) forma do código objeto a ser gerado: linguagem absoluta de máquina, relocável ou

de montagem;

b) seleção das instruções de máquina: a escolha da seqüência apropriada das

instruções pode resultar em um código mais otimizado;

c) alocação de registradores;

d) escolha da ordem de avaliação das instruções.

Para Aho, Sethi e Ullman (1995, p. 224), “Uma familiaridade com a máquina-alvo e

seu conjunto de instruções é um pré-requisito para o projeto de um bom gerador de código”.

A máquina-alvo para a qual o FURBOL gera código alvo é o microprocessador 8088 (SILVA,

2002), o qual é apresentado na seção seguinte.

2.3 MICROPROCESSADOR 8088

O 8088 é um microprocessador desenvolvido pela empresa Intel Corporation, sendo

uma versão do modelo 8086 (SANTOS; RAYMUNDI JÚNIOR, 1989, p. 8).

De acordo com Norton e Wilton (1991, p. 6), o microprocessador 8086 difere do 8088

em apenas um pequeno aspecto: ele usa o barramento de dados inteiro de 16 bits ao invés da

barramento de 8 bits usado pelo 8088. Praticamente tudo o que se lê sobre o 8086 também se

aplica ao 8088; para fins de programação pode-se considerá-los idênticos. Portanto, neste

trabalho 8086 e 8088 serão tratados como sinônimos.

Em todos os Personal Computers (PCs), o microprocessador é o chip que roda os

programas. O microprocessador, ou Unidade Central de Processamento (UCP), executa uma

série de cálculos, comparações numéricas e transferências de dados em respostas aos

programas armazenados na memória. A UCP controla a operação básica do computador,

emitindo e recendo sinais de controle, endereços de memória e dados de uma parte do

computador para outra, ao longo de uma rede de vias eletrônicas interconectadas chamada de

barramento. Ao longo do barramento encontram-se as portas de entrada e saída, que conectam

os vários dispositivos de memória e de suporte ao barramento (NORTON; WILTON, 1991, p.

Page 33: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

32

2).

2.3.1 Memória

Segundo Santos e Raymundi Júnior (1989, p. 8), a memória em um sistema baseado no

8086/8088 é uma seqüência de no máximo 1.048.576 bytes (1 Mbyte). A cada byte associa-se

um endereço, na faixa de 00000 a FFFFF, em notação hexadecimal.

Como o 8086/8088 é um microprocessador de 16 bits, esse não pode trabalhar

diretamente com números mais largos que 16 bits. Teoricamente, isso significa que o

8086/8088 é capaz de acessar somente 64 KB de memória. Contudo, na prática ele pode

acessar 1 Mbyte de memória. Isso é possível porque o esquema de endereçamento de 20 bits,

usado com o 8086/8088, pode trabalhar de 216 (65.536) para 220 (1.048.576). Ainda, o

8086/8088 é limitado por sua capacidade de armazenamento de 16 bits. Para acessar

endereços de 20 bits o 8086/8088 utiliza um método de endereçamento segmentado

(NORTON; WILTON, 1991, p. 23).

2.3.1.1 Endereços segmentados

Segundo Norton e Wilton (1991, p. 23-24), o 8086/8088 divide o espaço de memória

endereçável em segmentos, cada um contendo 64 KB de memória. Cada segmento começa em

um endereço de parágrafo – ou seja, um local de byte que é divisível exatamente por 16. Para

acessar bytes ou palavras (dois bytes consecutivos na memória) individuais, usa-se um

deslocamento (offset) que aponte para o local de byte exato dentro de um segmento particular.

O microprocessador converte um endereço segmentado qualquer em um endereço físico de 20

bits, usando o valor de segmento como número de parágrafo e somando o valor de

deslocamento a ele. Com efeito, o microprocessador desloca o valor de segmento à esquerda

por 4 bits e depois soma o valor de deslocamento para criar um endereço de 20 bits.

Page 34: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

33

2.3.1.2 Pilha

Para Santos e Raymundi Júnior (1989, p. 30-31) a pilha é uma característica inerente

aos microprocessadores. A pilha de um microprocessador é uma área de memória usada para

guardar dados, normalmente valores presentes nos registradores que precisam ser alterados,

mas que devem depois recuperar seu antigo valor. As instruções que fazem isso são PUSH

(adiciona um valor no topo da pilha), POP (recupera uma valor do topo da pilha), PUSHF

(adiciona o valor do registrador FLAGS no topo da pilha) e POPF (recupera o valor do

registrador FLAGS do topo da pilha). Uma característica curiosa e que deve ser notada é que a

pilha do 8086/8088 tem sua base em um endereço de memória alto dentro do segmento de

pilha, crescendo em direção à memória de endereço baixo.

Norton e Wilton (1991, p. 30) afirmam que o uso mais importante da pilha é na

manutenção do registro do local de onde as sub-rotinas foram chamadas e quais os parâmetros

passados a ela. A pilha também pode ser usada como área de armazenamento das variáveis

locais de uma sub-rotina.

2.3.2 Registradores

De acordo com Norton e Wilton (1991, p. 26), o 8086/8088 foi projetado para executar

instruções e operações lógicas e aritméticas na hora em que recebe as instruções e transfere

dados de ou para a memória. Para fazer isso ele usa quatorze registradores de 16 bits, cada

qual com uma finalidade especial. Santos e Raymundi Júnior (1989, p. 10) dividem os

registradores em cinco grupos, os quais são:

a) registradores de propósito geral;

b) registradores de ponteiros e de índice;

c) registradores de segmento;

d) registrador de ponteiro da instrução;

e) registrador de sinalizadores.

No Quadro 9 são apresentados os registradores do microprocessador 8086/8088.

Page 35: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

34

Registradores de propósito geral AX, BX, CX e DX

Registradores de ponteiros e de índices BP, SP, SI, DI

Registradores de segmento CS, DS, SS, ES

Registrador de ponteiro de instrução IP

Registrador de sinalizadores FLAGS

Quadro 9 - Registradores do 8086/8088

2.3.2.1 Registradores de propósito geral

As metades alta e baixa dos quatro registradores de propósito geral também podem ser

usadas como registradores de 8 bits. Assim têm-se os seguintes registradores de 8 bits: AL e AH

(parte baixa e alta do registrador AX), BL e BH, CL e CH, DL e DH (SANTOS; RAYMUNDI

JÚNIOR, 1989, p. 12).

Segundo Santos e Raymundi Júnior (1989, p. 12), com relação ao uso dos registradores

de propósito geral têm-se uma grande liberdade na escolha de qual registrador usar para

realizar operações aritméticas e lógicas. Contudo, existem instruções que os usam com certas

funções especializadas, como as instruções de multiplicação e divisão que se concentram nos

registradores AL e AX. Devido aos seus usos especializados em algumas instruções, os

registradores de propósito geral recebem os seguintes nomes descritivos:

a) AX é o acumulador, por concentrar resultados em algumas instruções aritméticas;

b) BX é chamado de base, pois é o único registrador de propósito geral que pode ser

usado para gerar um endereço de memória para acessar dados dentro do segmento

de dados;

c) CX é o contador, por conter, normalmente, o número de vezes que se quer repetir

um laço;

d) DX é chamado de registrador de dados, por ser usado para especificar o endereço

nas instruções de entrada e saída.

2.3.2.2 Registradores de ponteiros e de índices

Os registradores deste grupo são usados para armazenar endereços de deslocamento

Page 36: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

35

(offset) dentro dos segmentos, onde os dados devem ser acessados. O par SP e BP trabalha

dentro do segmento da pilha, enquanto o par SI e DI trabalha normalmente, no segmento de

dados (SANTOS; RAYMUNDI JÚNIOR, 1989, p. 13).

Santos e Raymundi Júnior (1989, p. 13-14) afirmam que embora os registradores de

ponteiros e de índices tenham seus usos especializados, pode-se empregar os registradores BP,

SI e DI em operações aritméticas e lógicas, sempre de 16 bits. Já o uso do SP nessas

operações não é recomendável, pois a perda do indicador do topo da pilha pode acarretar

efeitos desastrosos para um programa.

2.3.2.3 Registradores de segmento

Os registradores de segmento (CS, DS, SS e ES) são usados para gerar endereços de

memória de 20 bits, conforme visto na seção 2.3.1.1. Identificam os quatro segmentos

endereçáveis naquele momento pelo programa. Cada segmento identifica um bloco de

memória de 64 Kbytes (SANTOS; RAYMUNDI JÚNIOR, 1989, p. 14).

Segundo Norton e Wilton (1991, p. 28) cada registrador de segmento é usado para um

tipo especial de endereçamento, os quais são:

a) o registrador CS identifica o segmento de código, que contém o programa sendo

executado;

b) os registradores DS e ES identificam os segmentos de dados onde os dados usados

são armazenados;

c) o registrador SS identifica o segmento de pilha, que foi apresentada na seção

2.3.1.2.

2.3.2.4 Registrador de ponteiro da instrução

De acordo com Santos e Raymundi Júnior (1989, p. 14), os códigos de todas as

instruções são buscados no segmento de código. O registrador ponteiro da instrução

(Instruction Point – IP) indica o deslocamento dentro do segmento de código onde está a

instrução a ser executada.

Norton e Wilton (1991, p. 29-30) afirmam que “os programas não possuem acesso

Page 37: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

36

direto ao registrador IP , mas há uma série de instruções, como JMP e CALL, que alteram

indiretamente o valor de IP , ou salvam e recuperam o seu valor da pilha.”

2.3.2.5 Registrador de sinalizadores

Segundo Santos e Raymundi Júnior (1989, p. 15) o 8086/8088 contém em seu interior

um total de nove sinalizadores (flags). O seu propósito é indicar resultados obtidos sempre na

última operação aritmética ou lógica executada, ou para definir o comportamento do

microprocessador na execução de certas instruções. Esses 9 flags estão agrupados em um

registrador de 16 bits, sendo que apenas 9 bits são utilizados. Esse registrador é chamado de

FLAGS. O significado de cada bit do registrador é apresentador no Quadro 10.

FLAG Propósito

O (Overflow) Indica overflow do bit de alta ordem (bit mais significativo) após uma operação aritmética. Este overflow deve ser entendido como um “estouro” na capacidade do byte em armazenar um valor sinalizado.

D (Direction) Se o valor desse bit for 0, após a execução de uma instrução string, os registradores de índice envolvidos serão incrementados automaticamente e, em caso contrário, serão decrementados. Há apenas duas instruções que manipulam este flag (CLD e STD).

I (Interrupt) Indica se as interrupções estão ou não habilitadas. O valor 1 indica que as interrupções estão habilitadas, isto é, se ocorrerem serão atendidas. O valor 0 indica que as mesmas estão desabilitadas. Pode-se definir a opção desejada com as instruções CLI e STI.

T (Trap) Permite a operação “passo a passo”.

S (Sign) Contém o sinal resultante após uma operação aritmética (0 = positivo, 1 = negativo). Este sinalizador tem relevância quando usa-se números sinalizados.

Z (Zero) Indica o resultado da operação aritmética ou de comparação (0 = não zero, 1 = resultado zero, ou igual).

A (Aux. carry) Indica o transporte do bit 3 em um dado de 8 bits. Esse flag tem uso restrito a operações aritméticas com valores Binary-Coded Decimal (BCD, em português Código Binário Decimal).

P (Parity) Indica a paridade dos 8 bits de baixa ordem (1 = par, 0 = ímpar) de um resultado, após uma instrução aritmética ou lógica.

C (Carry) Indica o transporte do bit de mais alta ordem, após uma operação aritmética, ou contém o último bit, após uma operação de rotação ou deslocamento.

Fonte: adaptado de Santos e Raymundi (1989, p. 16-17). Quadro 10 - Propósito dos sinalizadores do registrador FLAGS

Page 38: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

37

2.3.3 Endereçando a memória por registradores

Em Norton e Wilton (1991, p. 32-33) são apresentadas várias formas de se endereçar

os dados da memória através dos registradores da UCP, as quais são apresentadas no Quadro

11.

Nome Endereço Efetivo Exemplo Comentários

imediato valor “endereçado” faz parte da instrução do 8086/8088

mov ax, 1234h Armazena 1234H em AX.

direto especificado como parte da instrução do 8086/8088

mov ax, [1234h] Copia o valor encontrado no endereço com deslocamento 1234h para o registrador AX. O registrador de segmento default é DS.

indireto contido em BX, SI, DI ou BP

mov ax, [bx] Copia o valor no deslocamento contido em BX para AX. O registrador de segmento default para [BX], [SI] e [DI] é DS; para [BP] o default é SS.

base a soma de um deslocamento (parte da instrução) e o valor em BX ou BP

mov ax, [bx+2] ou mov ax, 2[bx]

Copia o valor 2 bytes além do deslocamento contido em BX para AX. O registrador de segmento default para [BX] é DS; para [BP] o default é SS.

indexado a soma de um deslocamento e o valor em SI ou DI

mov ax, [si+2] ou mov ax, 2[si]

Copia o valor 2 bytes além do deslocamento contido em SI para AX. O registrador de segmento default é DS.

indexado na base

a soma de um deslocamento, o valor em SI ou DI e o valor em BX ou BP

mov ax, [bp+si+2] ou mov ax, 2[bp+si] ou mov ax, 2[bp][si]

O deslocamento é a soma dos valores em BP e SI, mais 2. Quando BX é usado, o registrador de segmento default é DS; quando BP é usado, o default é SS.

cadeia cadeia fonte: registrador indireto usando SI Cadeia destino: registrador indireto usando DI

movsb Copia a cadeia da memória em DS:[SI] para ES:[DI].

Fonte: adaptado de Norton e Wilton (1991, p. 32-33). Quadro 11 - Modos de endereçamento do 8086/8088

Page 39: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

38

2.3.4 Interrupções

De acordo com Norton e Wilton (1991, p. 37) uma interrupção é uma indicação para

UCP de que sua atenção imediata é necessária. O 8086/8088 pode responder a interrupções de

software e hardware. Quando uma interrupção é gerada (no caso da interrupção de software

através da instrução INT) a UCP salva na pilha o conteúdo corrente do registrador de flags, do

registrador CS e do registrador IP. Em seguida ela transfere o controle ao manipulador de

interrupção. O manipulador de interrupção termina a sua rotina com uma instrução IRET que

retorna CS:IP e os flags aos registradores correspondente, transferindo assim o controle de

volta ao programa interrompido.

2.3.5 Conjunto de instruções do 8086/8088

A linguagem de montagem (assembly) do 8086/8088 é composta por uma série de

códigos simbólicos de instrução (mnemônicos). Um montador (assembler) traduz essas

instruções e dados associados em forma binária, chamada linguagem de máquina, que pode

residir na memória e ser processada pelo 8086/8088 para realizar tarefas especificas

(NORTON; WILTON, 1991, p. 18). No Quadro 12 são apresentadas as instruções mais

usadas na implementação deste trabalho.

Page 40: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

39

Instrução Formato Propósito

CALL CALL alvo Chamar uma sub-rotina.

INT INT tipo Alterar o fluxo de execução do programa, desviando-se para uma rotina de interrupção.

LEA LEA destino, fonte Carregar o endereço de offset, ou deslocamento de um operando na memória para o registrador de 16 bits especificado em destino.

MOV MOV destino, fonte Copiar o conteúdo do operando fonte para o destino.

POP POP destino Retirar a palavra armazenada no topo da pilha colocando-a no registrador ou posição de memória especificada por destino.

PUSH PUSH fonte Colocar no topo da pilha o valor de fonte.

RET RET [dado] Encerrar uma sub-rotina, transferindo o fluxo do processamento para a instrução seguinte à chamada da sub-rotina. O valor [dado] é opcional. Nele pode-se especificar um valor que após o retorno da sub-rotina será adicionado ao SP.

Fonte: adaptado de Santos e Raymundi (1989, p. 33-92). Quadro 12 - Conjunto de instruções do 8086/8088

2.3.6 Layout de memória de um arquivo .COM

Segundo Santos e Raymundi Júnior (1989, p. 171) os arquivos .COM contêm apenas

um segmento definido. Esse contém todos os dados do programa, inclusive o Prefixo do

Segmento do Programa (PSP) e a pilha. Isto é feito criando-se um segmento para o código e,

através do pseudo-operador do montador Assume, fazer com que os quatros registradores de

segmento referenciem o mesmo bloco físico de 64 Kbytes. O Sistema Operacional (SO) cria o

PSP e o inclui nos primeiros 256 bytes do segmento. Deste modo, o ponto de entrada para o

programa deve ser definido em 100H.

O Quadro 13 apresenta o layout de memória de um arquivo .COM.

Page 41: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

40

CS:0000H PSP

CS:0100H código do programa e dados

pilha Quadro 13 - Layout de memória de um arquivo .COM

O Quadro 14 apresenta a estrutura completa de um arquivo .COM em linguagem de

montagem (assembly).

CODIGO SEGMENT PARA ‘CODE’ ASSUME CS:CODIGO, SS:CODIGO, DS:CODIGO, ES:CODIGO ORG 100H INICIO: JMP COMECO | :

definição de variáveis : : COMECO PROC NEAR : :

corpo de instruções : : ret COMECO ENDP CODIGO ENDS END INICIO

Fonte: adaptado de Santos e Raymundi (1989, p. 172). Quadro 14 - Estrutura completa de um arquivo .COM

2.4 FURBOL

O FURBOL é uma LP que adota o paradigma imperativo e vem sendo desenvolvida

por vários integrantes do curso de Ciências da Computação da FURB (VARGAS, 1992, 1993;

SILVA; SILVA, 1993; BRUXEL, 1996; RADLOFF, 1997; SCHMITZ, 1999; ANDRÉ, 2000;

ADRIANO, 2001; SILVA, 2002). Atualmente o FURBOL não é mais apenas uma LP. Ele

consiste de um ambiente integrado de desenvolvimento, onde é possível editar o código fonte

Page 42: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

41

em FURBOL e compilá-lo, gerando como saída um código de montagem (assembly)

compatível com microprocessadores 8086/8088.

Algumas características da linguagem FURBOL são:

a) comandos em língua portuguesa;

b) comando de condição e repetição;

c) unidades de programas concorrentes;

d) mecanismo de sincronização da comunicação entre unidades concorrentes do tipo

semáforo.

O compilador do FURBOL é um tradutor dirigido pela sintaxe que utiliza o método de

análise gramatical preditiva, onde a sintaxe é definida utilizando uma gramática livre de

contexto apresentada na BNF e a semântica utilizando gramática de atributos (SILVA, 2002).

Na Figura 2 é exibida a interface do FURBOL com um programa exemplo.

Fonte: Silva (2002, p. 137).

Figura 2 - Interface do FURBOL

Em Silva (2002) é descrito o desenvolvimento de unidades de processos concorrentes

para a linguagem de programação FURBOL. No trabalho é implementado um escalonador de

tarefas que é instalado na rotina de tratamento da interrupção de hardware 8H (timer). Para

permitir a sincronização das tarefas foi implementado o mecanismo de sincronização do tipo

semáforo. No Quadro 15 é apresentado um exemplo de programa em FURBOL que utiliza

Page 43: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

42

unidades de processos concorrentes.

programa produtor_consumidor; var cheio, vazio: semaforo; tarefa produtor; var c: inteiro; inicio c := 4; enquanto c > 0 faca inicio imprime("P -> Produzindo..."); p(vazio); imprime("P -> Armazenado!"); v(cheio); dec(c); fim ; fim ; tarefa consumidor; var c: inteiro; inicio c := 4; enquanto c > 0 faca inicio p(cheio); imprime("C -> Retirado!"); v(vazio); imprime("C -> Consumindo..."); dec(c); fim ; fim ; inicio criarsmf(cheio, 0); criarsmf(vazio, 2); produtor; consumidor; espera ; excluirsmf(cheio); excluirsmf(vazio); fim.

Fonte: Silva (2002, p. 109). Quadro 15 - Programa em FURBOL com unidades de processos concorrentes

2.5 TRABALHOS CORRELATOS

Tomazelli (2004) apresenta o desenvolvimento de um compilador para uma LP com

geração de código Microsoft Intermediate Language (MSIL). A LP apresentada é

simplificada, com comandos em português e herda algumas características estruturais da

linguagem C. O resultado do processo de compilação é um arquivo texto com código MSIL

(Quadro 16b), o qual é lido pelo montador ILAsm da Microsoft, gerando um módulo

gerenciado. Esse módulo gerenciado (arquivo “.EXE”) pode então ser executado pela

Page 44: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

43

Commom Language Runtime (CLR).

Exemplo Código MSIL programa teste { vazio principal() { inteiro n = 0; enquanto (n <= 10) { escreva(n * 2, "\n"); n += 1; } } }

(a)

.assembly extern mscorlib { }

.assembly teste { }

.module teste.exe .class public teste { .method public static void principal() { .entrypoint .locals (int32 V_0) ldc.i4 0 stloc V_0 lb000000: ldloc V_0 ldc.i4 10 cgt ldc.i4 0 ceq brfalse lb000001 { ldloc V_0 ldc.i4 2 mul call void [mscorlib]System.Console::Write(int32) ldstr "\n" call void [mscorlib]System.Console::Write(string) ldloc V_0 ldc.i4 1 add stloc V_0 } br lb000000 lb000001: ret } }

(b)

Fonte: Tomazelli (2004, p. 46). Quadro 16 - Exemplo de programa na linguagem implementada por Tomazelli (2004)

Leyendecker (2005) apresenta a especificação de uma LP orientada a objetos (Quadro

17) para a plataforma Microsoft .NET e o desenvolvimento de um compilador para esta

linguagem. Assim como em Tomazelli (2004), no final do processo de compilação o

montador de MSIL da Microsoft (o ILAsm) é invocado com o intuito de gerar um arquivo

executável ou uma biblioteca de classes para a plataforma .NET.

001 namespace org.furb.pacote1; 002 003 using org.furb.pacote2.*; 004 using org.furb.pacote3.Classe1; 005 006 public class ClasseQualquer 007 extends ClasseBase { 008 //Definição interna da classe 009 }

Fonte: Leyendecker (2005, p. 42). Quadro 17 - Exemplo da definição de uma classe na linguagem especificada em Leyendecker (2005)

Page 45: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

44

3 DESENVOLVIMENTO

Este capítulo tem como objetivo apresentar as etapas envolvidas no desenvolvimento

do trabalho proposto. Serão abordadas as seguintes etapas: requisitos do sistema,

especificação da LP FURBOL, aspectos relevantes considerados para implementação da

extensão da linguagem proposta, especificação, implementação, operacionalidade do sistema

e resultados e discussão.

Observa-se ainda que este capítulo limita-se a descrever o desenvolvimento de TADs.

Especificações usadas e não descritas aqui foram reaproveitadas de Silva (2002). A

documentação destas funções é encontrada também em Silva (2002).

3.1 REQUISITOS DO SISTEMA A SER DESENVOLVIDO

A ferramenta deverá:

a) manter os seguintes requisitos do ambiente de Silva (2002):

- possuir um editor para os arquivos fonte (Requisito Funcional – RF),

- compilar o código fonte FURBOL, gerando como saída código de montagem

(assembly) compatível com microprocessadores 8086/8088 (RF),

- emitir mensagens com os resultados da compilação (RF),

- permitir a visualização do código intermediário de três endereços gerado (RF),

- permitir a visualização do código de montagem (assembly) gerado (RF),

- invocar o montador Turbo Assembler e o ligador Turbo Link para a geração de

código de máquina executável em microprocessadores 8086/8088 (RF);

b) permitir a definição de TADs pelos usuários (RF);

c) usar esquemas de tradução para representar a semântica (Requisito Não-Funcional

- RNF);

d) ser implementado usando o ambiente Borland Delphi 7.0 (RNF).

Page 46: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

45

3.2 ESPECIFICAÇÃO DA LINGUAGEM FURBOL

Nesta seção é apresentada a especificação da LP FURBOL. Ela é uma extensão da

especificação apresentada em Silva (2002), na qual foram incluídas regras e ações semânticas

para oferecer suporte a TADs definidos pelos usuários. As extensões ou modificações feitas

na gramática estão sublinhadas, quando de sua apresentação em quadros.

Os símbolos em negrito são símbolos terminais da gramática. As palavras entre

apóstrofes (‘’) também são consideradas como símbolos terminais, podendo ser palavras

reservadas ou qualquer outro token. As palavras em itálico DeclaraDadosGlobais, EmiteInter

e EmiteObjeto geram diretivas para que seja realizada a reserva de memória para as variáveis

globais, a saída do compilador em código de três endereços e a saída do compilador em

linguagem de montagem (assembly), respectivamente.

A palavra Simbolos representa o gerenciador de símbolos (interface entre o analisador

sintático e os símbolos onde a instalação, a busca, a atualização dos símbolos e o

gerenciamento de escopo são realizados). A palavra CRLF (do inglês Carriage Return Line

Feed) representa uma quebra de linha ou nova linha. As demais palavras são consideradas

elementos não-terminais (como por exemplo, EstruturaDados, ComandoComposto¸ entre

outros), os quais possuem derivações. A palavra vazia é representada pelo símbolo

circunflexo (^).

A palavra GeraCodigoAssembly é uma função que calcula o endereço de um símbolo e

retorna-o num formato de operando assembly que endereça um dado na memória.

Os atributos codigo e codasm contêm o código intermediário e o código assembly

respectivamente. O símbolo “||” é utilizado para representar a concatenação dos enunciados e

atributos que armazenam os códigos na medida em que são gerados.

3.2.1 Programa principal e bloco de comandos

A definição do programa e dos blocos de comandos é mostrada no Quadro 18. As

palavras CarregaProcsNucleo e CarregaIniFinalNucleo representam a carga dos

procedimentos do núcleo e o código de inicialização e finalização, respectivamente. Ambas as

palavras são agrupamentos de chamadas seqüenciais da função CarregaRotina que carrega

um arquivo que contém o código da rotina passada no parâmetro.

Page 47: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

46

Programa → 'programa' EmiteObjeto('.8086'); EmiteObjeto('codigo_segmento segment'); EmiteObjeto('assume cs:codigo_segmento,' || 'ds:codigo_segmento,ss:nothing,es:nothing'); EmiteObjeto('org 100h');

id EmiteInter(id.nome || '0'); EmiteInter('goto ' || id.nome); EmiteObjeto('entrada: jmp ' || id.nome);

';' Simbolos.AbrirEscopo('Principal');

DefinicoesTADs

EstruturaDados

EstruturaSubRotinas

CComposto Programa.codigo := DefinicoesTADs.codigo || CRLF || EstruturaSubRotinas.codigo || CRLF || 'principal proc near' || CRLF || CComposto.codigo || CRLF || 'ret' || CRLF || 'endp' || CRLF || id.nome || ':' || CRLF || 'int 8h' || ':' || CRLF || 'int 20h' || CRLF Programa.codasm := DefinicoesTADs.codasm || CRLF || EstruturaSubRotinas.codasm || CRLF || 'principal_proc proc near' || CRLF || 'push bp' || CRLF || 'mov bp,sp' || CRLF || CComposto.codasm || CRLF || 'pop bp' || CRLF || 'principal_proc endp' || CRLF || CarregaProcsNucleo || CRLF || id.nome || ':' || CRLF || CarregaIniFinalNucleo || CRLF EmiteInter(Programa.codigo); EmiteObjeto(Programa.codasm);

'.' DeclaraDadosGlobais; EmiteObjeto('codigo_segmento ends'); EmiteObjeto('end entrada');

CComposto → 'inicio'

Comando CComposto.codigo := Comando.codigo; CComposto.codasm := Comando.codasm;

'fim'

Quadro 18 - Definição do programa principal e blocos

Com a extensão realizada neste trabalho tornou-se possível definir TADs logo após a

identificação do programa, acima das declarações das variáveis globais.

3.2.2 Declarações de variáveis e definições de tipos

A definição da declaração das variáveis é mostrada no Quadro 19.

Page 48: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

47

EstruturaDados → 'var'

id Se não Simbolos.SimboloRedeclarado(id.nome) então Simbolos.Instalar(TSimbolo.Create(id.nome));

ListaID (Matriz | ^)

Simbolos.AtualizarUltimosSimbolos(ListaID.tipo);

';'

Declaracoes

| ^

Declaracoes → id Se não Simbolos.SimboloRedeclarado(id.nome) então id.simbobj := TSimbolo.Create(id.nome); id.simbobj.TipoVariavel := Declaracoes.TipoVariavel; Símbolos.Instalar(id.simbobj);

ListaID (Matriz | )̂

Simbolos.AtualizarUltimosSimbolos(ListaID.tipo);

';'

Declaracoes

| ^

ListaID → ','

id Se não Simbolos.SimboloRedeclarado(id.nome) então id.simbobj := TSimbolo.Create(id.nome); id.simbobj.TipoVariavel := ListaID.TipoVariavel; Símbolos.Instalar(id.simbobj);

ListaID (Matriz | )̂

Simbolos.AtualizarUltimosSimbolos(ListaID.tipo);

| ':'

Tipo ListaID.tipo := Tipo.tipo;

Quadro 19 - Definição de declarações de variáveis

O objetivo do atributo TipoVarivel (atributo herdado dos não-terminais

Declaracoes e ListaID ) que é atribuído aos símbolos criados é diferenciar as variáveis

(globais, parâmetros, locais ou atributos). Esta diferenciação é necessária na hora da geração

de código.

No Quadro 20 é apresentada a definição dos tipos da linguagem FURBOL.

Page 49: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

48

Tipo → 'inteiro' Tipo.tipo := tsInteiro;

| 'logico' Tipo.tipo := tsLogico;

| 'matriz' Tipo.tipo := tsMatriz;

| 'semaforo' Tipo.tipo := tsSemaforo;

| id Simb := Símbolos.ProcurarSimbolo(id.nome); Se Simb.tipo = tsClasse então Tipo.tipo := tsObjeto; Tipo.TAD := Simb;

Matriz → LimitesM Simbolos.AtualizarUltimosSimbolos(LimitesM.tipo)

':'

ListaID Matriz.tipo := ListaID.tipo

LimitesM → '['

Dimensao

']'

| ^

Dimensao → num

Dimensao.liminf := num.valor

'..'

num Dimensao.limsup := num.valor

MaisDimensao

MaisDimensao → ','

Dimensao

| ^

Quadro 20 - Definição dos tipos de dados

O atributo Tipo passou a ser um registro com dois campos. O primeiro identifica o

tipo de símbolo que está sendo declarado. O segundo campo é um ponteiro para um símbolo

na tabela de símbolos e é usado quando uma referência de um TAD está sendo declarada.

Neste caso, o segundo campo do registro aponta para o TAD na tabela de símbolos que está

sendo referenciado.

3.2.3 Procedimentos e tarefas

No Quadro 21 é apresentada a definição de sub-rotinas e a definição de procedimentos.

No Quadro 22 é apresentada a definição da unidade de programa tarefa.

Page 50: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

49

EstruturaSubRotinas → 'procedimento'

EstruturaProcedimento EstruturaSubRotinas.codigo := EstruturaProcedimento.codigo; EstruturaSubRotinas.codasm := EstruturaProcedimento.codasm;

| 'tarefa' Se Simbolos.EscopoAtual.Nivel > 0 então erro;

EstruturaTarefa EstruturaSubRotinas.codigo := EstruturaTarefa.codigo; EstruturaSubRotinas.codasm := EstruturaTarefa.codasm;

| ^

EstruturaProcedimento → id Se não Simbolos.SimboloRedeclarado(id.nome) então id.simbobj := TSimbolo.Create(id.nome, tsProcedimento) Simbolos.Instalar(id.simbobj) id.simbobj.TabelaAgregada := Simbolos.AbrirEscopo(id.nome);

ParamFormais

';'

EstruturaDados

EstruturaSubRotinas

CComposto EstruturaProcedimento.codasm := (id.nome || 'proc near' || CRLF || 'push bp' || CRLF || 'mov bp,sp' || CRLF || 'sub sp,' || Simbolos.EscopoAtual.LarguraVariaveis || CRLF || CComposto.codasm || CRLF || 'mov sp,bp' || CRLF || 'pop bp' || CRLF || 'ret ' || Simbolos.EscopoAtual.LarguraParametros || CRLF || id.nome || 'endp' || CRLF || EstruturaSubRotinas.codasm; Simbolos.FecharEscopo;

';'

EstruturaSubRotinas EstruturaProcedimento.codigo := EstruturaProcedimento.codigo || EstruturaSubRotinas.codigo; EstruturaProcedimento.codasm := EstruturaProcedimento.codasm || EstruturaSubRotinas.codasm;

Quadro 21 - Definição de sub-rotinas e procedimentos

EstruturaTarefa → id Se não Simbolos.SimboloRedeclarado(id.nome) então id.simbobj := TSimbolo.Create(id.nome, tsTarefa) Simbolos.Instalar(id.simbobj) id.simbobj.TabelaAgregada := Simbolos.AbrirEscopo(id.nome);

';'

EstruturaDados

EstruturaSubRotinas

CComposto EstruturaTarefa.codasm := (id.nome || 'proc near' || CRLF || 'push bp' || CRLF || 'mov bp,sp' || CRLF || 'sub sp,' || Simbolos.EscopoAtual.LarguraVariaveis || CRLF || CComposto.codasm || CRLF || 'mov sp,bp' || CRLF || 'pop bp' || CRLF || 'ret ' || Simbolos.EscopoAtual.LarguraParametros || CRLF || id.nome || 'endp' || CRLF || EstruturaSubRotinas.codasm; Simbolos.FecharEscopo;

';'

EstruturaSubRotinas EstruturaTarefa.codigo := EstruturaTarefa.codigo || EstruturaSubRotinas.codigo; EstruturaTarefa.codasm := EstruturaTarefa.codasm || EstruturaSubRotinas.codasm;

Quadro 22 - Definição de tarefas

No Quadro 23 é apresentada a definição de parâmetros formais dos procedimentos do

FURBOL.

Page 51: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

50

ParamFormais → '('

(ParamValor | ParamRef)

SecaoParam

')'

| ^

SecaoParam → ';'

(ParamValor | ParamRef)

SecaoParam

| ^

ParamValor → id Se não Simbolos.SimboloRedeclarado(id.nome) então id.simbobj := TSimbolo.Create(id.nome); id.simbobj.TipoVariavel := tvValor; Símbolos.Instalar(id.simbobj);

ListaID Simbolos.AtualizarUltimosSimbolos;

ParamRef → 'ref'

id Se não Simbolos.SimboloRedeclarado(id.nome) então id.simbobj := TSimbolo.Create(id.nome); id.simbobj.TipoVariavel := tvReferencia; Símbolos.Instalar(id.simbobj);

ListaID Simbolos.AtualizarUltimosSimbolos;

Quadro 23 - Definição de parâmetros formais

O objetivo do atributo TipoVarivel (atributo herdado dos não-terminais ParamValor

e ParamRef ) que é atribuído aos símbolos criados é diferenciar os dois tipos de parâmetros

(por cópia valor e referência).

3.2.4 Comandos da linguagem

Do Quadro 24 ao Quadro 32 são apresentadas as definições das estruturas dos

comandos da linguagem. Os comandos são especificados nas definições do Quadro 24 e do

Quadro 25. O código para eles é gerado pelas definições nos quadros que vão do Quadro 26

até o Quadro 32.

Page 52: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

51

Comando → Identificador

('.' AcessaObjeto | ChamMetodo | ChamProc | L, Atribuição)

Se Identificador.Simb.tipo <> tsObjeto então erro; Comando.codigo := AcessaObjeto.codigo; Comando.codasm := AcessaObjeto.codasm; Se Identificador.Simb.tipo = tsMetodo então Se Identificador.Simb.TipoMetodo <> tmMetodo então erro; Comando.codigo := ChamMetodo.codigo; Comando.codasm := ChamMetodo.codasm; Senão Se Identificador.Simb.tipo = tsProcedimento então Comando.codigo := ChamProc.codigo || GeraCodigoEloEstatico || 'chamada ' || Identificador.Simb.nome; Comando.codasm := ChamProc.codasm || GeraCodigoEloEstatico || 'call ' || Identificador.Simb.nome; Senão Se Simb.tipo = tsTarefa então Comando.codigo := 'disparar(' || Identificador.Simb.nome || ')'; Comando.codasm := 'push ax' || CRLF || 'mov ax,offset ' || Identificador.Simb.nome || CRLF || 'push ax' || CRLF || 'call disparar_proc' || CRLF || 'pop ax' || CRLF; Senão Comando.codigo := Atribuicao.codigo; Comando.codasm := Atribuicao.codasm;

Virgula Comando.codigo := Comando.codigo || Virgula.codigo; Comando.codasm := Comando.codasm || Virgula.codasm;

| CCondicional

Virgula Comando.codigo := CCondicional.codigo || Virgula.codigo; Comando.codasm := CCondicional.codasm || Virgula.codasm;

| CRepeticao

Virgula Comando.codigo := CRepeticao.codigo || Virgula.codigo; Comando.codasm := CRepeticao.codasm || Virgula.codasm;

| CEntrada

Virgula Comando.codigo := CEntrada.codigo || Virgula.codigo; Comando.codasm := CEntrada.codasm || Virgula.codasm;

| CSaida

Virgula Comando.codigo := CSaida.codigo || Virgula.codigo; Comando.codasm := CSaida.codasm || Virgula.codasm;

| CInc

Virgula Comando.codigo := CInc.codigo || Virgula.codigo; Comando.codasm := CInc.codasm || Virgula.codasm;

| CDec

Virgula Comando.codigo := CDec.codigo || Virgula.codigo; Comando.codasm := CDec.codasm || Virgula.codasm;

| 'nl'

Virgula Comando.codigo := 'nl' || Virgula.codigo; Comando.codasm := 'nop' || Virgula.codasm;

Quadro 24 - Definição dos comandos da linguagem

Foram adicionados comandos que permitem a chamada dos métodos definidos nos

TADs. Somente os métodos atualizadores e consultores podem ser invocados diretamente. Os

métodos construtores e destrutores só podem ser chamados através dos comandos criarobj

e destruirobj respectivamente.

Page 53: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

52

No Quadro 25 é apresentado a definição para fazer o reconhecimento dos comandos de

criação e exclusão de objetos. As regras semânticas para geração de código destes comandos

são apresentadas mais adiante no Quadro 32.

Comando → 'morre'

Virgula Comando.codigo := 'morre' || Virgula.codigo; Comando.codasm := 'call morre_proc' || Virgula.codasm;

| 'espera'

Virgula Comando.codigo := 'espera' || Virgula.codigo; Comando.codasm := 'call espera_proc' || Virgula.codasm;

| 'repassa'

Virgula Comando.codigo := 'repassa' || Virgula.codigo; Comando.codasm := 'call repassa_proc' || Virgula.codasm;

| 'criarsmf'

CCriarSmf

Virgula Comando.codigo := CCriarSmf.codigo || Virgula.codigo; Comando.codasm := CCriarSmf.codasm || Virgula.codasm;

| 'excluirsmf'

CExcluirSmf

Virgula Comando.codigo := CExcluirSmf.codigo || Virgula.codigo; Comando.codasm := CExcluirSmf.codasm || Virgula.codasm;

| 'p'

CP

Virgula Comando.codigo := CP.codigo || Virgula.codigo; Comando.codasm := CP.codasm || Virgula.codasm;

| 'v'

CV

Virgula Comando.codigo := CV.codigo || Virgula.codigo; Comando.codasm := CV.codasm || Virgula.codasm;

| ‘criarobj’

CCriarObj

Virgula Comando.codigo := CCriarObj.codigo || Virgula.codigo; Comando.codasm := CCriarobj.codasm || Virgula.codasm;

| ‘destruirobj’

CDestruirObj

Virgula Comando.codigo := CDestruirObj.codigo || Virgula.codigo; Comando.codasm := CDestuirobj.codasm || Virgula.codasm;

| Virgula Comando.codigo := Virgula.codigo; Comando.codasm := Virgula.codasm;

Quadro 25 - Definição dos comandos da linguagem (continuação)

Nas produções que reconhecem os comandos criarobj e destruirobj é realizada a

chamada dos procedimentos que implementam os não-terminais CCriarObj e

CDestruirObj , respectivamente. Em seguida é invocado o método que implementa o não-

Page 54: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

53

terminal Virgula . Os código gerados são concatenados e atribuídos aos atributos codigo e

codasm do não-terminal Comando.

No Quadro 26 é apresentado a definição dos comandos de atribuição, estrutura de

chamada de procedimento e parâmetros atuais.

Virgula → ';'

Comando Virgula.codigo := Comando.codigo Virgula.codasm := Comando.codasm

| ^

Atribuicao → ':=' Se Atribuicao.deslocamento <> '' então RI := Novo_T; IndCodAsm := 'mov ' || RI || ',' || Ldesloca || CRLF || 'push ' || RI; PreIdAt := 'pop di';

Expressao E.local := Expresssao.local; Se não TiposCompativeis(Expressao.tipo, Atribuicao.tipo) então erro; Se Expressao.deslocamento <> ' ' então Atribuicao.codigo := gerar(idAT'['L.deslocamento']:='E.local); Senão Atribuicao.codigo := gerar(IdAT ':=' E.Local); RX := Registrador; Atribuicao.codasm := IndCodAsm || Expressao.codasm || AtPrelocal || CRLF || 'mov ' || RX || ',' || aTlocal || CRLF || PreIdAT || CRLF || 'mov ' || IdAT ',' RX;

ChamProc → '(' ParN := 0;

ParamAtuais Se ParN <> ChamProc.TabelaSimbolos.QuantidadeParametros então erro; ChamProc.codasm := ParamAtuais.codasm;

')'

| ^ Se 0 <> ChamProc.TabelaSimbolos.QuantidadeParametros então erro;

ParamAtuais → Identificador SimbVar := Identificador.Simb; ParN := ParN + 1; Se ParN > ParamAtuais.SimbProc..QuantidadeParametros então erro; SimbPar := ParamAtuais.SimbProc.TabelaSimbolos.[ParN - 1]; Se SimbVar.tipo <> SimbPar.tipo então erro('tipos incompatíveis'); Se SimbPar.TipoVariavel = tvValor então ParamAtuais.codasm := 'push ' || SimbVar.nome || CRLF || ParamAtuais.codasm; senão ParamAtuais.codasm := 'lea di,' || SimbVar.nome || CRLF || 'push di' || CRLF || ParamAtuais.codasm;

(',' ParamAtuais | ^)

| num ParN := ParN + 1; Se ParN > ParamAtuais.SimbProc..QuantidadeParametros então erro; SimbPar := ParamAtuais.SimbProc.TabelaSimbolos.[ParN - 1]; Se SimbPar.TipoVariavel <> tvValor ou SimbPar.Tipo <> tsInteiro então erro; ParamAtuais.codasm := 'push ' || num.valor || CRLF || ParamAtuais.codasm; (',' ParamAtuais | ^)

Quadro 26 - Definição dos comandos de atribuição, estrutura de chamada de procedimento e parâmetros atuais

Uma consistência na chamada dos procedimentos foi adicionada (Quadro 26). O

compilador passou a verificar se o número de parâmetros reais é o mesmo que o número de

parâmetros formais. Caso exista diferença é emitido um erro de compilação. As ações

semânticas sublinhadas no não-terminal ChamProc verificam se a quantidade de parâmetros é

suficiente. A terceira ação semântica do não-terminal ParamAtuais verifica se a quantidade

Page 55: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

54

de parâmetros não excedeu o permitido.

A chamada ao não-terminal Identificador (Quadro 26) permite que a palavra

reservada esse seja usada na chamada de um procedimento.

A segunda produção do não-terminal ParamAtuais (Quadro 26) permite que um valor

inteiro seja passado como parâmetro sem que um identificador seja utilizado.

O Quadro 27 apresenta a definição do comando de repetição e comandos condicionais.

CRepeticao → 'enquanto' CRepeticao.inicio := Novo_L; Expressao.v := Novo_L; Expressao.f := CRepeticao.prox;

Expressao

'faca'

CComposto CComposto.prox := CRpeticao.inicio; CRepeticao.codigo:= CRepeticao.inicio ':' || Expressao.codigo || E.v ':' || CRLF || CComposto.codigo || CRLF || 'goto ' || CRepeticao.inicio; CRepeticao.codasm := CRepeticao.inicio ':" || Expressao.codasm || E.v ':' || CRLF || CComposto.codasm || CRLF || 'jmp' || CRepeticao.inicio;

CCondicional → 'se' Expressao 'entao'

CComposto CComposto1.prox := CCondicional.prox; CCondicional.codigo := Expressao.codigo || E.v ':' || CRLF || CComposto.codigo; CCondicional.codasm := Expressao.codasm || E.v ':' || CRLF || CComposto.codasm;

CCondicional2 CCondicional.codigo := CCondicional.codigo || CCondicional2.codigo; CCondicional.codasm := CCondicional.codasm || CCondicional2.codasm;

CCondicional2 → 'senao'

CComposto fproximo := proximo; CCondicional2.codigo := Expressao.codigo || CRLF || 'goto ' || proximo || CRLF || f ':' || CRLF || CComposto.codigo; CCondicional2.codasm := Expressao.codasm || CRLF || 'jmp ' || proximo || CRLF || f ':' || CRLF || CComposto.codasm;

| ^ CCondicional2.codigo := CCondicional2.codigo || CRLF || f || ':'; CCondicional2.codasm := CCondicional2.codasm || CRLF || f || ':';

Quadro 27 - Definição do comando de repetição e comandos condicionais

No Quadro 28 é apresentado a definição dos comandos de entrada e saída.

Page 56: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

55

CEntrada → 'leitura'

'('

id Se Simbolos.ProcurarSimbolo(id.nome) = nil então erro('Identificador não declarado');

LeituraListaID

')' CEntrada.codigo := 'leitura(' || id.nome || ',' || LeituraListaID.codigo;

LeituraListaID → ','

id Se Simbolos.ProcurarSimbolo(id.nome) = nil então erro('Identificador não declarado'); LeituraListaID.codigo := LeituraListaID.codigo || ',' || id.nome;

LeituraListaID

| ^

CSaida → 'imprime'

'('

Expressao

ListaExpressao Se Expressao.tipo = tsinteiro CSaida.codasm := Expressao.Prelocal || CSaida.codasm || CRLF ‘push local’ || CRLF ‘call imprime’;

')' CSaida.codigo := 'imprime(' || Expressao.codigo || ',' || ListaExpressao.codigo ;

ListaExpressao → ','

Expressao ListaExpressao.codigo := ListaExpressao.codigo || Expressao.codigo;

ListaExpressao

| ^

Quadro 28 - Definição dos comandos de entrada e saída

O comando de saída imprime foi extendido para também imprimir expressões do tipo

inteiro. Antes o comando apenas imprimia cadeias de caracteres. As ações semânticas

sublinhadas no não-terminal CSaida verificam se foi enunciada a impressão de uma

expressão do tipo inteiro e em caso positivo gera código para a chamada do procedimento do

núcleo imprime (Quadro 28).

O Quadro 29 apresenta a definição dos comandos incremento e decremento.

Page 57: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

56

CInc → '('

id Se Simbolos.ProcurarSimbolo(id.nome) = nil então erro('Identificador não declarado'); Se id.tipo <> tsInteiro então erro('Tipo incopatível'); CInc.codigo := 'inc ' || '(' || id.nome || ')'; CInc.codasm := 'inc ' || id.nome;

')'

CDec → '('

id Se Simbolos.ProcurarSimbolo(id.nome) = nil então erro('Identificador não declarado'); Se id.tipo <> tsInteiro então erro('Tipo incopatível'); CDec.codigo := 'dec ' || '(' || id.nome || ')'; CDec.codasm := 'dec ' || id.nome;

')'

Quadro 29 - Definição dos comandos incremento e decremento

No Quadro 30 é apresentado a definição dos comandos de criação e exclusão de

semáforos.

CCriarSmf → '('

Identificador Se Identificador.Simb.tipo <> tsSemaforo então erro('Tipo incopatível');

','

Expressão Se Expressao.tipo <> tsInteiro então erro;

')' CCriarSmf.codigo := Expressao.codigo || 'criarsmf(' || Identificador.Simb.nome || Expressao.local || ')'; CCriarSmf.codasm := Expressao.codasm || 'push di' || CRLF || 'push cx' || CRLF || 'push ' || Expressao.local || CRLF || 'sub sp,2 || CRLF || 'call criar_smf_proc' || CRLF || 'pop cx' || CRLF || 'mov ' || GeraCodigoAssembly(Identificador.Simb) || ',cl' || CRLF || 'add sp,2' || CRLF || 'pop cx' || CRLF || 'pop di' || CRLF;

CExcluirSmf → '('

Identificador

')' CExcluirSmf.codigo := 'excluirsmf(' || Identificador.Simb.nome || ')'; CExcluirSmf.codasm := 'push di' || CRLF || 'push cx' || CRLF || 'xor cx,cx' || CRLF || 'mov cl,' || GeraCodigoAssembly(Identificador.Simb) || CRLF || 'push cx' || CRLF || 'call excluir_smf_proc' || CRLF || 'pop cx' || CRLF || 'pop di' || CRLF;

Quadro 30 - Definição dos comandos de criação e exclusão de semáforos

As chamadas ao não-terminal Identificador permitem que a palavra reservada

esse , que é reconhecida pelo não-terminal Identificador , seja usada nos comandos de

criação e exclusão de semáforos.

O Quadro 31 apresenta a definição dos comandos de operação P e V em semáforos.

Page 58: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

57

CP → '('

Identificador

')' CP.codigo := 'p(' || Identificador.Simb.nome || ')'; CP.codasm := 'push di' || CRLF || 'push cx' || CRLF || 'xor cx,cx' || CRLF || 'mov cl,' || GeraCodigoAssembly(Identificador.Simb) || CRLF || 'push cx' || CRLF || 'call p_smf_proc' || CRLF || 'pop cx' || CRLF || 'pop di' || CRLF;

CV → '('

Identificador

')' CV.codigo := 'v(' || Identificador.Simb.nome || ')'; CV.codasm := 'push di' || CRLF || 'push cx' || CRLF || 'xor cx,cx' || CRLF || 'mov cl,' || GeraCodigoAssembly(Identificador.Simb) || CRLF || 'push cx' || CRLF || 'call v_smf_proc' || CRLF || 'pop cx' || CRLF || 'pop di' || CRLF;

Quadro 31 - Definição dos comandos de operação P e V em semáforos

As chamadas ao não-terminal Identificador permitem que a palavra reservada esse

seja usada nos comandos P e V.

No Quadro 32 são apresentadas as ações semânticas para a geração de código dos

comandos de criação e exclusão de objetos.

O comando criarobj aceita como primeiro parâmetro uma variável do tipo objeto.

Aceita também, opcionalmente como segundo parâmetro a chamada de um método do TAD

do qual o primeiro parâmetro é uma referência. Esse método deve ser obrigatoriamente do

tipo construtor e é invocado logo após que a memória para objeto é alocada. O endereço da

memória alocada é atribuído a variável especificada no primeiro parâmetro.

O comando destruirobj é semelhante ao comando criarobj . A diferença é que o

método chamado deve ser do tipo destrutor. Esse método é invocado antes que a memória

alocada para o objeto (que é o conteúdo do primeiro parâmetro do comando) seja liberada.

Page 59: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

58

CCriarObj → '('

Identificador Se Identificador.Simb.Tipo <> tsObjeto então erro;

ChamConstrutor

')'

‘;’ CCriarObj.codigo := ‘criarobj(’ || Identificador.Simb.nome || ‘)’ || CRLF || ChamConstrutor.codigo; CriarObj.codasm := ‘push ax’ || CRLF || ‘push’ || Identificador.Simb.TamanhoAtributos || CRLF || ‘call criarobj’ || CRLF || ‘mov’ || GeraCodigoAssembly(Identificador.Simb) || ‘, ax’ || CRLF || ‘pop ax’ || CRLF || ChamConstrutor.codasm;

CDestruirObj → '('

Identificador Se Identificador.Simb.Tipo <> tsObjeto então erro;

ChamDestrutor

')'

‘;’ CDestruirObj.codigo := ChamDestrutor.codigo || ‘destruirobj(’ || Identificador.Simb.nome || ‘)’ || CRLF; CDestruirObj.codasm := ChamDestrutor.codasm || ‘push ax’ || CRLF || ‘mov ax, ’ || GeraCodigoAssembly(Identificador.Simb) || ‘push ax’ || CRLF || ‘call destruirobj’ || CRLF || ‘pop ax’ || CRLF;

Quadro 32 - Definição dos comandos de criação e exclusão de objetos

As chamadas ao não-terminal Identificador permitem que a palavra reservada esse

seja usada nos comandos de criação e exclusão de objetos.

As ações do não-terminal CCriarObj verificam se a variável usada como primeiro

parâmetro do comando criarobj é do tipo objeto. Em seguida é feita a chamada ao não-

terminal ChamConstrutor que se encarrega de gerar código para a chamada do método

construtor caso essa chamada seja enunciada pelo programador. Após a chamada ao não-

terminal ChamConstrutor é gerado código para a chamada do procedimento do núcleo

criarobj e para a atribuição do resultado do procedimento a variável do tipo objeto. Ao final

o código gerado pelas ações do não-terminal CCriarObj é concatenado ao código gerado

pela chamada ao não-terminal ChamConstrutor .

As ações do não-terminal CDestruirObj são semelhantes às ações do não-terminal

CCriarObj . A diferença é que neste caso são realizadas chamadas ao não-terminal

ChamDestrutor e ao procedimento do núcleo destruirobj . Outra diferença é que no final o

código gerado é concatenado ao contrário. Primeiro é realizado a chamada ao método

destrutor e depois a chamada ao procedimento do núcleo que exclui o objeto.

Page 60: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

59

3.2.5 Expressões

A estrutura de controle de expressões pode ser vista do Quadro 33 até o Quadro 38.

Esta definição foi construída a partir da definição de precedência de operadores definida em

Aho, Sethi e Ullman (1995).

Expressao → Expressao2 Relacao.v := Expressao2.v; Relacao.f := Expressao2.f; Relacao.local := Expressao2.local; Relacao.codigo := Expressao2.codigo; Relacao.codasm := Expressao2.codasm;

Relacao Expressao.local := Relacao.local; Expressao.codigo := Relacao.codigo; Expressao.codasm := Relacao.codasm; Expressao.v := Relacao.v; Expressao.f := Relacao.f;

Expressao2 → TC ELi.v := TC.v; ELi.f := TC.f; ELi.local := TC.local; ELi.codigo := TC.codigo; ELi.codasm := TC.codasm;

EL Expressao2.local := ELs.local; Expressao2.codigo := ELs.codigo; Expressao2.codasm := ELs.codasm;

Quadro 33 - Definição da estrutura de controle de expressões

Relacao → '=' Relacao.codigo := 'se ' || Relacao.local || ' = ' || Expressao2.local || 'goto ' || E.v || 'goto ' || E.f; Relacao.codasm := 'mov ' || R0 || ',' || Relacao.local || CRLF || 'cmp ' || R0 || ',' || local || CRLF || 'je ' || Rv || CRLF || 'jmp ' || Rf;

| '<>' Relacao.codigo := 'se ' || Relacao.local ' <> ' Expressao2.local || 'goto' || E.v || 'goto' || E.f; Relacao.codasm := 'mov ' || R0 || ',' || Relacao.local || CRLF || 'cmp ' || R0 || ',' || local || CRLF || 'jne ' || Rv || CRLF || 'jmp ' || Rf;

| '<' Relacao.codigo := 'se ' || Relacao.local || ' < ' || Expressao2.local || 'goto ' || E.v || 'goto ' || E.f; Relacao.codasm := 'mov ' || R0 || ',' || Relacao.local || CRLF || 'cmp ' || R0 || ',' || local || CRLF || 'jb ' || Rv || CRLF || 'jmp ' || Rf;

| '>' Relacao.codigo := 'se ' || Relacao.local || ' > ' || Expressao2.local || 'goto ' || E.v || 'goto ' || E.f; Relacao.codasm := 'mov ' || R0 || ',' || Relacao.local || CRLF || 'cmp ' || R0 || ',' || local || CRLF || 'ja ' || Rv || CRLF || 'jmp ' || Rf;

^

Quadro 34 - Definição da estrutura de controle de expressões (continuação)

Page 61: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

60

EL → '+'

TC EL1i.local := Novo_T; EL1i.codigo := EL.codigo || TC.codigo || CRLF || EL1i.local || ':=' || EL.local || '+' || TC.local; EL1i.codasm := EL.codigo || TC.codigo || CRLF || 'mov ' || RX || ',' || EL.local || CRLF || 'add ' || RX || ',' || Tc.local || CRLF || 'mov ' || EL1i.local || ',' || RX;

EL1 EL.local := EL1s.local; EL.codigo := EL1s.codigo; EL.codasm := EL1s.codasm;

| '-'

TC EL1i.local := Novo_T; EL1i.codigo := EL.codigo || TC.codigo || CRLF || EL1i.local || ':=' || EL.local || '-' || TC.local; EL1i.codasm := EL.codigo || TC.codigo || CRLF || 'mov ' || RX || ',' || EL.local || CRLF || 'sub ' || RX || ',' || Tc.local || CRLF || 'mov ' || EL1i.local || ',' || RX;

EL1 EL.local := EL1s.local; EL.codigo := EL1s.codigo; EL.codasm := EL1s.codasm;

| ^ EL.v := ELs.v; EL.f := ELs.f; EL.local := ELs.local; EL.codigo := ELs.codigo; EL.codasm := ELs.codasm;

TC → F TL1.v := F.v; TL1.f := F.f; TL1.local := F.local; TL1.codigo := F.codigo; TL1.codasm := F.codasm;

TL T.v := TLs.v; T.f := TLs.f; TC.local := TLs.local; TC.codasm := TLs.codasm; TC.codigo := TLs.codigo;

Quadro 35 - Definição da estrutura de controle de expressões (continuação)

Page 62: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

61

TL → '*'

F TL1.local := Novo_T; TL1.codigo := TL.codigo || F.codigo || CRLF || TL1.local ':=' TL.local || '*' || F.local; TL1.codasm : = TL.codigo || F.codigo || CRLF || 'mov ax,' || TLLOCAL || CRLF || 'mul ' || Local || CRLF || 'mov ' || TliLocal || ',ax';

TL1 TL.local := TL1s.local; TL.codigo := TL1s.codigo; TL.codasm := TL1s.codasm;

| '/'

F TL1.local := Novo_T; TL1.codigo := TL.codigo || F.codigo || CRLF || TL1.local ':=' TL.local || '/' || F.local; TL1.codasm : = TL.codigo || F.codigo || CRLF || 'mov ax, ' || TLLOCAL || CRLF || 'div ' || Local || CRLF || 'mov ' || TliLocal || ',ax' || CRLF ||;

TL1 TL.local := TL1s.local; TL.codigo := TL1s.codigo; TL.codasm := TL1s.codasm;

| 'E'

F TL.v := Novo_L; TL.f := TL1.f; F.v := TL1.v; F.f := TL1.f; TL1.codigo := TL.codigo || TL1.v ':' || F.codigo; TL1.codasm := TL.codasm || TL1.v ':' || F.codasm;

TL1 TL.v := TL1s.v; TL.f := TL1s.f; TL.codasm := TL1s.codasm; TL.codigo := TL1s.codigo;

| ^ TL.v := TLs.v; TL.f := TLs.f; TL.local := TLs.local; TL.codigo := TLs.codigo; TL.codasm := TLs.codasm;

Quadro 36 - Definição da estrutura de controle de expressões (continuação)

Page 63: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

62

F → '(' Expressao.v := F.v; Expressao.f := F.f;

Expressao

')' F.local := Expressao.local; F.codigo := Expressao.codigo; F.codasm := Expressao.codasm;

| '-'

Expressao F.local := Novo_T; F.codigo := Expressao.codigo || CRLF || gerar(F.local || ':=' || ' uminus ' || E.local);

| 'nao' Expressao.v := F.f; Expressao.f := F.v;

Expressao

| Identificador L Se L.deslocamento := 0 então F.local := L.local; senão F.local := Novo_T; gerar(F.local || ':=' || L.local || '[' || L.deslocamento || ']');

| num F.local := num.valor; F.codigo := ''; F.codasm := '';

| ‘nulo’ F.local := ‘0’ ; F.codigo := ''; F.codasm := '';

| ^

L → Lista_E ']'

L.local := Novo_T; L.deslocamento := Novo_T; L.codigo := gerar(L.local := c(Lista_E.array); L.codigo := gerar(L.deslocamento ':=' Lista_E.local '*' Largura(Lista_E.array)); L.codasm := Lista_E.codasm || CRLF || 'mov dx,' || Lista_E.local || CRLF || 'mov al,' || VerificaTipoMatriz(Lista_E.array) || CRLF || 'mul dx' || CRLF || 'mov ' || L.deslocamento || ',ax' || CRLF || 'add ' || L.deslocamento || ',' || Lc || CRLF || 'add ' || L.deslocamento || ',2';

| id L.local := L.Simb.local; L.deslocamento := '';

Quadro 37 - Definição da estrutura de controle de expressões (continuação)

A chamada ao não-terminal Identificador realizada no não-terminal F permite que

a palavra reservada esse possa ser usada em expressões.

A quinta produção do não-terminal F (Quadro 37) faz o reconhecimento da palavra

reservada nulo que para o compilador é equivalente ao valor 0. Essa palavra é compatível

com qualquer variável que seja uma referência a um TAD, podendo ser atribuída e comparada

a essas variáveis. Seu objetivo é permitir que os usuários possuam uma forma de indicar que

uma referência a um TAD é nula (não está apontando para uma área de memória previamente

alocada para o objeto).

O atributo do não-terminal L Simb agora é sintetizado pelo não-terminal

Identificador (o qual permite que um identificador seja acessado através da palavra esse ),

que precede L nos não-terminais Comando (Quadro 24) e F (Quadro 37). Antes a produção

de L analisava o token atual e sintetizava o atributo Simb . Agora, como a produção do não-

Page 64: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

63

terminal Identificador avança a analise léxica e altera o token atual, tornou-se necessário

que a própria produção de Identificador sintetizasse o atributo Simb , passando- o para L

como atributo herdado.

Lista_E → id

'['

Expressao Ri.matriz := id.local; Ri.local := Expressao.local; Ri.ndim := 1;

R Lista_E.matriz := Rs.matriz; Lista_E.local := Rs.local; Lista_E.ndim := Rs.ndim;

R → Expressao t := Novo_T; m := Ri.ndim + 1; gerar(t || ':=' || Lista_E.local) limite(Lista_E.matriz,m); R1i.matriz := Ri.matriz; R1i.local := t; R1i.ndim := m; R1i.codasm := Expressao.codasm || CRLF || 'mov cx,' || local || CRLF || 'mov al,' || limite(Lista_E.array,m) || CRLF || 'mul cx' || CRLF || 'mov ' || t || ',ax' || CRLF || 'add ' || t || ',' || Elocal;

R1 Rs.matriz := R1s.matriz; Rs.local := R1s.local; Rs.ndim := R1s.ndim; Rs.codasm := R1s.codasm;

| ^ Rs.matriz := Ri.matriz; Rs.local := Ri.local; Rs.ndim := Ri.ndim; Rs.codasm := Ri.codasm;

Quadro 38 - Definição da estrutura de controle de expressões (continuação)

3.2.6 Definições dos TADs

No Quadro 39 é apresentada a especificação dos TADs definidos pelos usuários.

Page 65: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

64

DefinicoesTADs → ‘classe’

TAD

DefinicoesTADs1 DefinicoesTADs.codigo := TAD.codigo || DefinicoesTADs1.codigo; DefinicoesTADs.codasm := TAD.codasm || DefinicoesTADs1.codasm;

| ^

TAD → id Se não Simbolos.SimboloRedeclarado(id.nome) então id.simbobj := TSimbolo.Create(id.nome, tsClasse); Simbolos.Instalar(id.simbobj); id.simbobj.TabelaMembros := Símbolos.AbrirEscopo(id.nome);

AtributosTAD

MetodosTAD

‘fim’ TAD.codigo := MetodosTAD.codigo; TAD.codasm := MetodosTAD.codasm; Simbolos.FecharEscopo;

';'

AtributosTAD → id Se não Símbolos.SimboloRedeclarado(id.nome) então id.simbobj := TSimbolo.Create(id.nome); id.simbobj.TipoVariavel := tvAtributo; Símbolos.Instalar(id.simbobj);

ListaID (Matriz | )̂

Simbolos.AtualizarUltimosSimbolos(ListaID.tipo);

‘;’

AtributosTAD

| ^

MetodosTAD → (‘procedimento’ | ‘construtor’ | ‘destrutor’)

MetodoTAD

MetodosTAD1 MetodosTAD.codigo := MetodoTAD.codigo || MetodosTAD1.codigo; MetodosTAD.codasm := MetodoTAD.codasm || MetodosTAD1.codasm;

| ^

Quadro 39 - Especificação dos TADs definidos pelos usuários

Um TAD é composto por atributos (membros de dados) e métodos (funções-

membros). Ambos são opcionais. Os atributos devem ser declarados obrigatoriamente antes

dos métodos.

Um atributo pode ser um tipo primitivo, um array finito ou uma referência para outro

TAD previamente definido. Arrays dinâmicos não são permitidos como atributos e um erro de

compilação é emitido caso sejam declarados como tal.

Um método é classificado como construtor quando é definido usando-se a palavra

reservada construtor . Para os métodos destrutores usa-se a palavra reservada destrutor e

para os métodos consultores e atualizadores a palavra procedimento .

No Quadro 40 é apresentada a especificação de um método definido pelos usuários.

Page 66: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

65

MetodoTAD → id Se não Simbolos.SimboloRedeclarado(id.nome) então id.simbobj := TSimbolo.Create(id.nome, tsMetodo); Simbolos.Instalar(id.simbobj); id.simbobj.TabelaAgregada := Símbolos.AbrirEscopo(id.nome); id.simbobj.TipoMetodo := MetodoTAD.TipoMetodo; ParamOculto := TSimbolo.Create(‘esse’, tsObjeto); Símbolos.Instalar(ParamOculto);

ParamFormais

‘;’

EstruturaDados

CComposto MetodoTAD.codigo := id.nome || ‘proc near’ || CRLF || CComposto.codigo || CRLF || ‘ret’ || CRLF || id.nome || ‘endp’ ; MetodoTAD.codasm := id.nome || ‘proc near’ || CRLF || ‘push bp’ || CRLF || ‘mov bp, sp’ || CRLF || ‘sub sp, ’ || Símbolos.EscopoAtual.LarguraVariaveis || CRLF || CComposto.codasm || ‘mov sp, bp’ || CRLF || ‘pop bp’ || CRLF || ‘ret’ || Símbolos.EscopoAtual.LarguraParametros + 2 || CRLF || id.nome || ‘endp’; Símbolos.FecharEscopo;

';'

Quadro 40 - Especificação de um método

A definição de um método é semelhante à definição de um procedimento. Contudo

existem algumas diferenças, as quais são:

a) métodos não podem ser aninhados;

b) o código de um método pode utilizar a palavra reservada esse que é usada para

acessar explicitamente um membro do TAD ao qual o método pertence ou quando

uma referência para esse TAD é esperada.

Quando um método é definido, o compilador instala na lista de símbolos do método

um parâmetro oculto (não definido pelo programador) chamado de esse . Este parâmetro é

sempre o primeiro da lista e é do tipo objeto. Ele é uma referência (ponteiro) para o local onde

encontram-se os atributos do TAD e será usado internamente pelo compilador para acessar

esses atributos.

No Quadro 41 é apresentada a especificação do acesso aos membros dos TADs.

Page 67: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

66

Identificador → ‘esse’ SimbMetodo := Símbolos.ProcurarSimboloNivel(Símbolos.EscopoAtual.Nome, Símbolos.EscopoAtual.Nivel-1); Se SimbMetodo = nil ou SimbMetodo.TipoMetodo = tmNenhum então erro;

Esse Identificador.Simb := Esse.Simb;

| id Identificador.Simb := Símbolos.ProcurarSimbolol(id.nome); Se Identificador.Simb = nil então erro;

Esse → ‘.’

id Esse.Simb := Símbolos.ProcurarSimboloNivel(id.nome, Símbolos.EscopoAtual.Nivel-1); Se Esse.Simb = nil então erro;

| ^ Esse.Simb := Símbolos.ProcurarSimbolo (‘esse’); Se Esse.Simb = nil então erro;

AcessaObjeto → id Simb := AcessaObjeto.Objeto.TAD.TabelaMembros.ProcurarSimbolo(id.nome); Se Simb = nil então erro; Se Simb.TipoMetodo <> tmMetodo então erro;

ChamMetodo AcessaObjeto.codigo := ChamMetodo.codigo; AcessaObjeto.codasm := ChamMetodo.codasm;

ChamMetodo → ChamMetodo2 ChamMetodo.codigo := ‘chamada ’ || ChamMetodo.Metodo.Nome; ChamMetodo.codasm := ‘push’ || GeraCodigoAssembly(ChamMetodo.Objeto) || CRLF || ‘push bp’ || CRLF || ‘call ’ ChamMetodo.Metodo.Nome; ChamMetodo.codigo := ChamMetodo2.codigo || ChamMetodo.codigo; ChamMetodo.codasm := ChamMetodo2.codasm || ChamMetodo.codigo;

ChamMetodo2 → ‘(‘ ParN := 1;

ParamFormais Se ParN <> ChamMetodo2.Metodo.TabelaAgregada.QuantidadeParametros então erro; ChamMetodo2.codigo := ParamAtuais.codigo; ChamMetodo2.codasm := ParamAtuais.codasm;

‘)’

| ^ Se 1 <> ChamMetodo2.Metodo.TabelaAgregada.QuantidadeParametros então erro;

ChamConstrutor → ','

id Simb := ChamConstrutor.Objeto.TAD.TabelaMembros.ProcurarSimbolo(id.nome); Se Simb = nil então erro; Se Simb.TipoMetodo <> tmConstrutor então erro;

ChamMetodo ChamConstrutor.codigo := ChamMetodo.codigo; ChamConstrutor.codasm := ChamMetodo.codasm;

| ^

ChamDestrutor → ','

id Simb := ChamDestrutor.Objeto.TAD.TabelaMembros.ProcurarSimbolo(id.nome); Se Simb = nil então erro; Se Simb.TipoMetodo <> tmDestrutor então erro;

ChamMetodo ChamDestrutor.codigo := ChamMetodo.codigo; ChamDestrutor.codasm := ChamMetodo.codasm;

| ^

Quadro 41 - Especificação do acesso aos membros dos TADs

Como já foi informado, a palavra reservada esse só pode ser usada dentro de um

método. Quando ela é usada seguida de um ponto e de um identificador, este identificador é

procurado pelo gerenciador de símbolos somente um nível acima do nível do método, ou seja,

o identificador deve ser um membro do TAD do qual o método pertence. Caso a palavra esse

não seja usada seguida de um ponto, o parâmetro oculto é localizado, pois esse é seu nome.

Page 68: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

67

Isso pode ser necessário quando uma referência para o objeto atual é esperada (Quadro 42).

01)classe TPonto 02) x, y: inteiro; 03) 04) (...) 05) 06) procedimento AutoDestruir; 07) inicio 08) destruirobj(esse); 09) fim; 10)fim;

Quadro 42 - Exemplo do uso do parâmetro oculto esse

Na linha 8 do exemplo apresentado, a palavra esse é usada sem um ponto em seguida.

Neste caso o compilador localizará o parâmetro oculto que é uma referência do objeto atual e

seu conteúdo será o parâmetro do procedimento do núcleo destruirobj . Como resultado

deste enunciado a memória alocada para o objeto será liberada.

Somente os métodos consultores e atualizadores de um TAD podem ser acessados

diretamente. Os atributos só podem ser acessados pelo código de um método e os métodos

construtores e destrutores pelos comandos criarobj e destruirobj respectivamente.

A chamada de um método é semelhante à chamada de um procedimento. A única

diferença é que depois que os parâmetros informados pelo programador são empilhados o

compilador também empilha o conteúdo da variável do tipo objeto que está sendo

referenciada (parâmetro esse do método) . Caso um método invoque outro método do

mesmo objeto o parâmetro esse é propagado.

3.2.7 Exemplo de um TAD definido conforme a nova especificação do FURBOL.

No Quadro 43 é apresentado o exemplo de um TAD definido, conforme a nova

especificação da linguagem FURBOL implementada neste trabalho.

Page 69: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

68

classe TPilha ftamanho: inteiro; ftopo: inteiro; fpilha: matriz[1..7]: inteiro; construtor constroi; inicio ftamanho := 7; ftopo := 0; fim; procedimento empilha(i: inteiro); inicio se (ftopo = ftamanho) entao inicio imprime("A pilha esta cheia"); fim senao inicio ftopo := ftopo + 1; fpilha[ftopo] := i; fim; fim; procedimento desempilha; inicio se (ftopo = 0) entao inicio imprime("A pilha esta vazia"); fim senao inicio ftopo := ftopo - 1; fim; fim; procedimento topo; inicio se (ftopo = 0) entao inicio imprime("A pilha esta vazia"); fim senao inicio imprime(fpilha[ftopo]); fim; fim; procedimento vazio; inicio se (ftopo = 0) entao inicio imprime("A pilha esta vazia"); fim senao inicio imprime("A pilha nao esta vazia"); fim; fim; procedimento listar; var i: inteiro; inicio imprime("Valores da pilha:"); i := ftopo; enquanto (i > 0) faca inicio imprime(fpilha[i]); i := i - 1; fim; fim; fim;

Quadro 43 - Exemplo de um TAD definido conforme a nova especificação da linguagem FURBOL

Page 70: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

69

No exemplo apresentado TPilha é o nome do TAD definido. Ftamanho , ftopo e

fpilha são os atributos do TAD. constroi é o método construtor. empilha e desempilha

são métodos atualizadores e topo , vazio e listar métodos consultores.

No Quadro 44 é apresentado o exemplo de um programa que cria uma instância do

TAD definido no Quadro 43 e usa essa instância.

programa pilha; classe TPilha (...) fim; var gi, gj: inteiro; gpilha: TPilha; inicio criarobj(gpilha, constroi); gi := 0; enquanto (gi < 10) faca inicio gi := gi + 1; gpilha.empilha(gi); fim; gpilha.listar; destruirobj(gpilha); fim.

Quadro 44 - Exemplo de um programa que usa o TAD definido no Quadro 43

3.2.8 Exemplo de uma tabela de símbolos para um programa que define e usa um TAD

O Quadro 45 apresenta um programa que define e usa um TAD.

Page 71: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

70

programa Retangulo; classe TRetangulo esquerda, topo, direita, fundo: inteiro; construtor constroi(esquerda, topo, direita, fun do: inteiro); inicio imprime("Construindo um retangulo"); esse.esquerda := esquerda; esse.topo := topo; esse.direita := direita; esse.fundo := fundo; fim; procedimento Area; var largura, altura, area: inteiro; inicio imprime("Area do Retangulo:"); largura := direita - esquerda; altura := fundo - topo; area := largura * altura; imprime(area); fim; destrutor destroi; inicio imprime("Destruindo um retangulo"); fim; fim; var gRet: TRetangulo; inicio criarobj(gRet, constroi(0, 0, 20, 20)); gRet.Area; destruirobj(gRet, destroi); fim.

Quadro 45 - Exemplo de um programa que define e usa um TAD

A Figura 3 apresenta o esboço da tabela de símbolos para o programa do Quadro 45.

Page 72: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

71

Escopo: TRetangulo Nível: 1 Símbolo: esquerda Tipo: tsInteiro TipoVariavel: tvAtributo Símbolo: topo Tipo: tsInteiro TipoVariavel: tvAtributo Símbolo: direita Tipo: tsInteiro TipoVariavel: tvAtributo Símbolo: fundo Tipo: tsInteiro TipoVariavel: tvAtributo Símbolo: constroi Tipo: tsMetodo TipoMetodo: tmConstrutor TabelaAgregada Símbolo: Area Tipo: tsMetodo TipoMetodo: tmMetodo TabelaAgregada Símbolo: destroi Tipo: tsMetodo TipoMetodo: tmDestrutor TabelaAgregada

Escopo: constroi Nível: 2 Símbolo: esse Tipo: tsObjeto (TRetangulo) TipoVariavel: tvValor Símbolo: esquerda Tipo: tsInteiro TipoVariavel: tvValor Símbolo: topo Tipo: tsInteiro TipoVariavel: tvValor Símbolo: direita Tipo: tsInteiro TipoVariavel: tvValor Símbolo: fundo Tipo: tsInteiro TipoVariavel: tvValor

Escopo: Principal Nível: 0 Símbolo: TRetangulo Tipo: tsClasse TabelaMembros Símbolo: gRet Tipo: tsObjeto (TRetangulo) TipoVariavel: tvGlobal

Escopo: Area Nível: 2 Símbolo: esse Tipo: tsObjeto (TRetangulo) TipoVariavel: tvValor Símbolo: largura Tipo: tsInteiro TipoVariavel: tvLocal Símbolo: altura Tipo: tsInteiro TipoVariavel: tvLocal Símbolo: area Tipo: tsInteiro TipoVariavel: tvLocal

Escopo: destroi Nível: 2 Símbolo: esse (TRetangulo) Tipo: tsObjeto TipoVariavel: tvValor

Figura 3 - Esboço da tabela de símbolos para o programa do Quadro 45

Page 73: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

72

3.3 ASPECTOS RELEVANTES CONSIDERADOS PARA IMPLEMENTAÇÃO DA EXTENSÃO DA LINGUAGEM PROPOSTA

Nessa seção são apresentados os aspectos relevantes considerados para implementação

da extensão da linguagem proposta.

3.3.1 Criação de objetos

O conteúdo de uma variável do tipo objeto é apenas uma referência (ponteiro) para o

segmento onde encontra-se a área de dados destinada aos atributos do objeto. A criação do

objeto (alocação de memória para os dados) é feita de forma explícita através do comando

criarobj . Uma variável do tipo objeto ocupa dois bytes, conforme pode ser observado no

Quadro 46.

Furbol assembly classe TPonto x, y: inteiro; construtor constroi(x, y: inteiro); inicio esse.x := x; esse.y := y; fim; fim; var ponto1: TPonto; ponto2: TPonto;

ponto1 dw ? ponto2 dw ?

Quadro 46 - Exemplo de variáveis do tipo objeto

As variáveis ponto1 e ponto2 são duas variáveis globais. São referências para o

TAD TPonto . Em assembly dw é uma diretiva para o montador alocar dois bytes para uma

variável (endereço de memória).

No Quadro 47 é apresentada a criação de um objeto do TAD TPonto referenciado pela

variável ponto1 que foi apresentado no Quadro 46.

Furbol assembly criarobj(ponto1);

1) push ax 2) push 4 3) call criarobj 4) mov ponto1,ax 5) pop ax

Quadro 47 - Exemplo da criação de um objeto

No Quadro 47 pode-se observar que o segundo parâmetro do comando criarobj

(chamada de um construtor) é opcional. A linha 1 em assembly salva o conteúdo do

registrador AX na pilha, pois o mesmo será usado a seguir. A linha 2 empilha o valor 4 na

Page 74: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

73

pilha, que será o parâmetro do procedimento criarobj , que espera um valor inteiro

indicando o tamanho da representação do TAD (atributos) em bytes. O compilador consulta a

tabela de símbolos agregada ao TAD TPonto para saber que o mesmo possui tamanho 4

(atributos x e y , ambos com tamanho 2). A linha 3 invoca o procedimento criarobj . A linha

4 atribui o resultado do procedimento criarobj que está no registrador AX a variável global

ponto1 . Após esse comando o conteúdo da variável ponto1 será o endereço do segmento

alocado para os atributos do objeto, que é o retorno do procedimento criarobj . A linha 5

restaura o conteúdo do registrador AX que foi salvo na linha 1.

No Quadro 48 é apresentada a criação de mais um objeto do TAD TPonto apresentado

no Quadro 46. Desta vez a variável usada será ponto2 e o método construtor constroi será

invocado. Quando no comando criarobj a chamada de um método construtor é enunciada,

primeiro é realizada a criação do objeto (linhas 1, 2, 3, 4 e 5 em assembly) e em seguida é

feita a chamada do construtor (linhas 6, 7, 8, 9 e 10 em assembly). Nas linhas 6 e 7 são

empilhados os parâmetros gj e gi respectivamente. Na linha 8 é empilhado o parâmetro

ponto2 (parâmetro oculto). Na linha 9 o registrador BP é empilhado. Na linha 10 o método

construtor é invocado. gi e gj são variáveis globais do tipo inteiro e parâmetros do

construtor. A chamada de um método construtor funciona da mesma forma que a chamada de

um método comum. Os aspectos relacionados a chamada de um método são apresentados

adiante, na seção 3.3.3.

Furbol assembly criarobj(ponto2, constroi(gi, gj)); 01) push ax

02) push 4 03) call criarobj 04) mov ponto2,ax 05) pop ax 06) push gj 07) push gi 08) push ponto2 09) push bp 10) call TPonto@constroi

Quadro 48 - Exemplo da criação de um objeto com chamada de método construtor

O registrador BP só é empilhado na linha 8 para manter um padrão entre as chamadas

de procedimentos e métodos no FURBOL que é possuir sempre a mesma quantidade de bytes

na pilha entre os parâmetros e as variáveis locais. O valor do registrador BP empilhado não é

utilizado pelo método chamado e só seria necessário caso fosse possível a definição de

métodos aninhados. Neste caso ele serviria como elo de acesso.

Page 75: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

74

3.3.1.1 Procedimento criarobj

O procedimento criarobj que é invocado pelo comando de mesmo nome é um

procedimento criado e adicionado aos outros procedimentos do núcleo do FURBOL.

O procedimento recebe como parâmetro o tamanho do TAD em bytes, aloca memória

na heap para o objeto e retorna no registrador AX o valor do segmento do bloco alocado para

o objeto. Para alocar memória é usada a função 48h (Quadro 49) do SO MicroSoft – Disk

Operating System (MS-DOS).

Entradas: AH = 48h BX = número de parágrafos (unidades de 16 bytes) que se deseja alocar Saída: CF = indica a ocorrência de erro (0 = ok) AX = contém o código do erro ou o valor do segmento do bloco alocado BX = em caso de erro por falta de memória, contém o tamanho máximo que pode ser alocado Fonte: adaptado de Santos e Raymundi Júnior (1989, p. 244).

Quadro 49 – Uso da função 48h do MS-DOS

O Quadro 50 apresenta o trecho do procedimento no qual a memória é alocada. O

código completo da rotina pode ser encontrado no Apêndice A.

1) mov bx, ax ; bx <- quantidade de parágrafos 2) mov ah, 48h ;função do DOS 3) int 21h

Quadro 50 - Trecho do procedimento criarobj

Na linha 1 é atribuído ao registrador BX o conteúdo do registrador AX que possui a

quantidade de parágrafos de memória que devem ser alocados para o objeto. Essa quantidade

foi previamente calculada pela rotina. A linha 2 atribui ao registrador AH o código da função

do MS-DOS que será invocada. A linha 3 gera a interrupção que transfere a execução para o

MS-DOS. Caso a alocação de memória falhe uma mensagem de erro é emitida e o registrador

AX é zerado.

3.3.2 Parâmetro oculto esse

Todo método possui um parâmetro oculto chamado esse . O conteúdo do parâmetro

esse é o endereço do segmento alocado para os atributos do objeto. O parâmetro oculto

esse é necessário, pois esta foi a solução criada para que o método chamado acesse os

atributos do objeto.

Page 76: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

75

O parâmetro esse é sempre o primeiro parâmetro de um método, portanto sua posição

na pilha é sempre a mesma e não é necessário nenhum cálculo por parte do compilador para

acessar o seu conteúdo. O seu conteúdo sempre será a palavra apontada pelo registrador BP

mais um deslocamento de 6 bytes (word ptr [bp+6] em assembly). Os 6 bytes que estão na

pilha entre o byte apontado por BP e o parâmetro esse são apresentados no exemplo do

Quadro 51.

Aumento da pilha

BP Exemplo de endereço

Conteúdo da pilha

Comentário

BP � 992 conteúdo salvo do registrador BP

O primeiro comando de um procedimento é salvar o conteúdo do registrador BP, pois o mesmo será modificado. O segundo é atribuir ao registrador BP o endereço do topo da pilha (neste exemplo 992).

994 endereço de retorno do procedimento

Endereço de retorno que é empilhado implicitamente pela instrução CALL (2 bytes).

996 elo de acesso Ponteiro usado para acessar as variáveis não-locais nos procedimentos aninhados (2 bytes).

998 parâmetro esse Parâmetro oculto esse (2 bytes).

1000 parâmetros normais

Parâmetros definidos pelo usuário.

Quadro 51 – Localização do parâmetro esse na pilha

3.3.3 Chamada de métodos

A chamada de um método é semelhante à chamada de um procedimento. Primeiro os

parâmetros definidos pelo programador são empilhados da direita para a esquerda. Em

seguida o conteúdo da variável do tipo objeto que está sendo referenciada também é

empilhado (parâmetro oculto esse) . Por ultimo é empilhado o registrador BP do

procedimento chamador e é feita a chamada do método.

Para o correto funcionamento do programa, no momento da chamada de um método, o

conteúdo da variável do tipo objeto que está sendo referenciada deve ser o endereço

(ponteiro) do segmento alocado para os atributos do objeto, ou seja, o objeto deve ter sido

previamente criado através do comando criarobj .

No Quadro 52 é apresentado um exemplo de um programa em FURBOL que faz a

Page 77: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

76

chamada de um método.

01)programa teste; 02) 03)classe TPonto 04) x, y: inteiro; 05) procedimento setXY(x, y: inteiro); 06) inicio 07) esse.x := x; 08) esse.y := y; 09) fim; 10) construtor constroi(x, y: inteiro); 11) inicio 12) setXY(x, y); 13) fim; 14)fim; 15) 16)var 17) ponto1: TPonto; 18) gi, gj: inteiro; 19)inicio 20) gi := 7; 21) gj := 7777; 22) criarobj(ponto1); 23) 24) ponto1.setXY(gi, gj); 25) 26) destruirobj(ponto1); 27)fim.

Quadro 52 - Exemplo de programa em FURBOL com chamada de método

A variável ponto1 é uma referência para o TAD TPonto . O método invocado chama-

se setXY . Nó código do método setXY pode-se observar o uso da palavra reservada esse

(Quadro 52, linhas 7 e 8), que foi usada neste caso para indicar ao compilador que os

símbolos x e y que estão sendo referenciados a esquerda das atribuições são os membros do

TAD e não os parâmetros x e y .

O código gerado para a chamada desse método é apresentado no Quadro 53.

Furbol assembly ponto1.setXY(gi, gj); 1) push gj

2) push gi 3) push ponto1 4) push bp 5) call TPonto@setXY

Quadro 53 - Código gerado para a chamada de um método

No Quadro 53 na linha 1 em assembly é empilhado o parâmetro gj . Na linha 2 o

parâmetro gi é empilhado. Na linha 3 é empilhado o conteúdo da variável do tipo objeto que

está sendo referenciada (ponto1 ). Na linha 4 é empilhado o registrador BP e finalmente o

método setXY é invocado.

Embora o método invocado chama-se setXY , em assembly o seu nome é formado pela

concatenação do nome do TAD, pelo símbolo @ e pelo seu nome em FURBOL. No exemplo

apresentado o nome em assembly do método é TPonto@setXY . Isso é necessário para evitar

conflitos no caso de outro TAD também possuir um método chamado setXY .

Um método também pode ser invocado por outro método do mesmo TAD. Isso pode

ser observado no exemplo do Quadro 52. Neste exemplo o método constroi invoca o

Page 78: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

77

método setXY . Nestes casos basta o compilador propagar o parâmetro oculto esse para o

método chamado. O Quadro 54 apresenta o código completo gerado para o método constroi

do Quadro 52.

Furbol assembly construtor constroi(x, y: inteiro); inicio setXY(x, y); fim;

01) TPonto@constroi proc near 02) push bp 03) mov bp, sp 04) sub sp, 0 05) push [bp+10] 06) push [bp+8] 07) push word ptr [bp+6] 08) push bp 09) call TPonto@setXY 10) mov sp, bp 11) pop bp 12) ret 8 13) TPonto@constroi endp

Quadro 54 - Código para a chamada de um método por outro método

A linha 1 em assembly declara o inicio de um novo procedimento chamado de

TPonto@constroi . A linha 2 salva o conteúdo do registrador BP, pois o mesmo será alterado

em seguida. A linha 3 atribui ao registrador BP o endereço do topo da pilha (conteúdo do

registrador SP) que será usado durante o procedimento para endereçar valores na pilha. A

linha 4 reserva memória na pilha para as variáveis locais. A linha 5 empilha o parâmetro y . A

linha 6 empilha o parâmetro x . Na linha 7 o parâmetro oculto esse é propagado para o

método setXY. Essa linha enuncia que o conteúdo da palavra apontada pelo registrador BP

mais um deslocamento de 6 bytes deve ser empilhada, que no caso de um método é o

conteúdo do parâmetro oculto esse , conforme visto na seção 3.3.2. A linha 8 empilha o

conteúdo do registrador BP que é o elo de acesso dos procedimentos aninhados. Na linha 9 o

método setXY é invocado. A linha 10 restaura o conteúdo o conteúdo do registrador SP. A

linha 11 restaura o conteúdo do registrador BP empilhado na linha 2. A linha 12 enuncia o

retorno do procedimento indicando que o valor 8 (quantidade de bytes empilhado pelo

chamador do método constroi ) deve ser adicionado ao registrador SP. A linha 13 declara o

fim do procedimento.

3.3.4 Acesso aos atributos

Para acessar os atributos de um objeto adotou-se a seguinte estratégia. Primeiro é

atribuído ao registrador de segmento ES o conteúdo do parâmetro oculto esse , que no caso de

um programa escrito corretamente é o valor do segmento alocado para os atributos do objeto.

Em seguida o atributo é acessado usando a forma de endereçamento

Page 79: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

78

ES:[deslocamento_do_atributo] , sendo deslocamento_do_atributo um número inteiro

calculado pelo compilador. Como o registrador de segmento de dados ES possui o valor do

segmento alocado previamente para o objeto, alterando-se o valor do deslocamento dentro do

segmento, pode-se acessar qualquer atributo do objeto.

Para calcular o deslocamento até um atributo o compilador consulta a tabela de

símbolos agregada ao TAD. O deslocamento de um determinado atributo é soma dos

tamanhos de todos os atributos que são definidos antes do atributo que está sendo calculado o

deslocamento.

O Quadro 55 apresenta a definição de um TAD que possui quatro atributos.

classe TRetangulo topo, esquerda, fundo, direita: inteiro; construtor constroi; inicio topo := 7; esquerda := 77; fundo := 777; direita := 7777; fim; fim;

Quadro 55 - Definição de um TAD com quatro atributos

No TAD definido no Quadro 55 os atributos topo , esquerda , fundo e direita

possuem respectivamente os deslocamentos 0, 2, 4 e 6.

No Quadro 56 é apresentado o código gerado para o método constroi do TAD

definido no Quadro 55.

Furbol assembly construtor constroi; inicio topo := 7; esquerda := 77; fundo := 777; direita := 7777; fim;

01) TRetangulo@constroi proc near 02) push bp 03) mov bp, sp 04) sub sp, 0 05) mov ax,7 06) mov es,word ptr [bp+6] 07) mov word ptr es:[0],ax 08) mov ax,77 09) mov es,word ptr [bp+6] 10) mov word ptr es:[2],ax 11) mov ax,777 12) mov es,word ptr [bp+6] 13) mov word ptr es:[4],ax 14) mov ax,7777 15) mov es,word ptr [bp+6] 16) mov word ptr es:[6],ax 17) mov sp, bp 18) pop bp 19) ret 4 20) TRetangulo@constroi endp

Quadro 56 - Código do método constroi definido no Quadro 55

Na linha 6 em assembly é atribuído ao registrador ES o conteúdo do parâmetro oculto

esse que é localizado na palavra apontada pelo registrador BP mais um deslocamento de 6

bytes, conforme visto na seção 3.3.2. Na linha 7 é atribuído para a palavra encontrada no

Page 80: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

79

segmento ES com deslocamento 0 (deslocamento do atributo topo ) o conteúdo do registrador

AX. Desta forma o primeiro atributo do objeto recebe o conteúdo de AX. Código similar é

gerado nas linhas seguintes para acessar os outros atributos, alterando apenas o deslocamento

dos mesmos.

3.3.5 Palavra reservada nulo

Para o compilador a palavra reservada nulo é equivalente ao valor 0. O Quadro 57

apresenta o trecho de um programa que utiliza a palavra nulo .

01)programa teste; 02) 03)classe TPonto 04)(...) 05)fim; 06) 07)var 08) ponto: TPonto; 09) 10)inicio 11) (...) 12) 13) destruirobj(ponto); 14) ponto := nulo; 15) 16) (...) 17)fim.

Quadro 57 - Trecho de programa que utiliza a palavra reservada nulo

No programa apresentado a palavra reservada nulo é atribuída a variável global ponto

na linha 14 para indicar que a referência para o TAD não é mais válida, pois o objeto foi

excluído na linha 13. Quando a linha 14 for executada o conteúdo da variável ponto será 0.

No Quadro 58 é apresentado o código gerado pelo enunciado da linha 14 do Quadro

57.

Furbol assembly ponto := nulo; 1) mov ax,0

2) mov ponto,ax

Quadro 58 - Código gerado pelo enunciado da linha 14 do Quadro 57

Na linha 1 em assembly o valor 0 é atribuído ao registrador AX. Na linha 2 o conteúdo

do registrador AX é atribuído a variável global ponto .

3.3.6 Exclusão de objetos

A exclusão de um objeto (liberação da memória alocada para os seus atributos) é feita

Page 81: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

80

de forma explicita através do comando destruirobj . O comando de exclusão espera como

primeiro parâmetro uma variável do tipo objeto e opcionalmente a chamada de um método

destrutor como segundo parâmetro.

No Quadro 59 é apresentado um programa que cria dois objetos do TAD TPonto e em

seguida exclui os dois objetos.

programa teste; classe TPonto x, y: inteiro; destrutor destroi; inicio imprime("Destruindo um ponto."); fim; fim; var ponto1, ponto2: TPonto; inicio criarobj(ponto1); criarobj(ponto2); destruirobj(ponto1); destruirobj(ponto2, destroi); fim.

Quadro 59 - Exemplo de programa que exclui dois objetos

Na primeira exclusão o destrutor do TAD não é invocado. Na segunda exclusão o

método destrutor destroi é invocado. No Quadro 60 é apresentado o código gerado para a

primeira exclusão.

Furbol assembly destruirobj(ponto1); 1) push ax

2) mov ax,ponto1 3) push ax 4) call destruirobj 5) pop ax

Quadro 60 - Exemplo de código gerado para exclusão de um objeto

Na linha 1 (Quadro 60) em assembly o conteúdo do registrador AX é salvo, pois o

mesmo será usado em seguida. Na linha 2 o conteúdo da variável global ponto1 é atribuído

ao registrador AX. A linha 3 empilha o conteúdo do registrador AX, que será o parâmetro do

procedimento destruirobj , que espera o valor do segmento do bloco previamente alocado

para o objeto que será liberado. A linha 4 invoca o procedimento destruirobj . Na linha 5 o

conteúdo do registrador AX é restaurado.

Quando a chamada de um método destrutor é enunciada, o compilador primeiro faz a

chamada do método e em seguida exclui o objeto. No Quadro 61 é apresentado o código

gerado para a segunda exclusão do programa do Quadro 59.

Page 82: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

81

Furbol assembly destruirobj(ponto2, destroi); 1) push ponto2

2) push bp 3) call TPonto@destroi 4) push ax 5) mov ax,ponto2 6) push ax 7) call destruirobj 8) pop ax

Quadro 61 - Exemplo de código gerado para exclusão de um objeto com chamada de destrutor

Na linha 1 (Quadro 61) em assembly é empilhado o conteúdo da variável do tipo

objeto que está sendo referenciada (ponto2 , parâmetro oculto). Na linha 2 é empilhado o

conteúdo do registrador BP. Na linha 3 o método destrutor destroi é invocado. Na linha 4 o

conteúdo do registrador AX é salvo. Na linha 5 é atribuído ao registrador AX o conteúdo da

variável ponto2 . Na linha 6 o conteúdo do registrador AX é empilhado. A linha 7 invoca o

procedimento destruirobj . A linha 8 restaura o conteúdo do registrador AX.

3.3.6.1 Procedimento destruirobj

O procedimento destruirobj que é invocado pelo comando de mesmo nome é um

procedimento criado e que foi adicionado aos outros procedimentos do núcleo do FURBOL.

O procedimento recebe como parâmetro o valor do segmento do bloco previamente

alocado para o objeto e libera essa memória. Para liberar a memória é usada a função 49h

(Quadro 62) do MS-DOS.

Entradas: AH = 49h ES = segmento do bloco que será liberado Saída: CF = indica a ocorrência de erro (0 = ok) AX = código do erro, se houver Fonte: adaptado de Santos e Raymundi Júnior (1989, p. 245).

Quadro 62 - Uso da função 49h do MS-DOS

O Quadro 63 apresenta o trecho do procedimento no qual a memória é liberada. O

código completo da rotina pode ser encontrado no Apêndice A.

1) mov ah, 49h ;função do DOS 2) mov es, word ptr [bp + 4] ; es <- blocoMemoria 3) int 21h

Quadro 63 - Trecho do procedimento destruirobj

A linha 1 (Quadro 63) atribui ao registrador AH o código da função do MS-DOS que

será invocada. A linha 2 atribui ao registrador ES o valor do bloco de memória que será

liberado (parâmetro do procedimento). A linha 3 gera a interrupção que transfere a execução

para o MS-DOS.

Page 83: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

82

3.4 ESPECIFICAÇÃO DO AMBIENTE

Nessa seção é apresenta a especificação do ambiente de programação FURBOL. Para

realizar a especificação do protótipo foi utilizada a Unified Modeling Language (UML),

através dos diagramas de casos de uso, classes e de seqüência.

3.4.1 Diagrama de casos de uso

Na Figura 4 é apresentado o diagrama de casos de uso do ambiente FURBOL.

Figura 4 - Diagrama de casos de uso

Os quatro casos de uso apresentados são:

a) Entrada do fonte : o usuário executa os comandos Abrir ou Novo da interface

do ambiente. Em seguida o sistema abre o arquivo escolhido ou limpa a tela de

edição do ambiente;

b) Salvar fonte : o usuário salva o fonte digitado ou modificado num arquivo .FUR;

c) Compilar : o usuário compila o fonte. No Quadro 64 este caso de uso é

apresentado com maiores detalhes;

d) Executar : o usuário executa o programa .COM gerado pelo ambiente.

Page 84: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

83

COMPILAR

Descrição: usuário solicita que o programa seja compilado. Ator principal : usuário. Cenário principal: compilar a) através da interface o usuário escolhe a opção Compilar; b) o sistema compila o programa fonte; c) o sistema invoca o montador Turbo Assembler; d) o sistema invoca o ligador Turbo Link; e) o sistema exibe a mensagem “Programa compilado com sucesso”. Cenário de exceção: erro de compilação Caso o programa fonte apresente algum erro o sistema encerra a compilação e apresenta qual é o problema para o usuário. Cenário de exceção: erro na geração de código de máquina Caso ocorra algum erro durante a geração do código de máquina o sistema apresenta uma mensagem de erro para o usuário. Pré-condição: programa escrito em LP FURBOL. Pós-condição: programa em código de máquina criado.

Quadro 64 - Caso de uso compilar

3.4.2 Diagrama de classes

Na Figura 5 é apresentado o diagrama de classes do FURBOL. O diagrama

apresentado é simplificado (não apresenta os atributos e métodos das classes), pois o seu

objetivo é apresentar as associações entre as classes.

Page 85: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

84

Figura 5 - Diagrama de classes do FURBOL

A função de cada classe do sistema é:

a) TFormPrincipal : representa a interface do sistema com o usuário;

b) TMontadorLigador : responsável por invocar o montador Turbo Assembler e o

ligador Turbo Link;

c) TMontadorLigadorSaidaFrm : responsável por apresentar a saída do montador e

do ligador para o usuário;

d) TAnalisadorLexico : implementa a parte de análise léxica do compilador.

Fornece o fluxo de tokens para o analisador sintático;

e) TAnalisadorSintatico : implementa um analisador sintático preditivo (seção

2.2.4.2) conforme a especificação da linguagem;

f) TGerenciadorSimbolos : representa a interface entre o analisador sintático e os

símbolos. A instalação, a busca, a atualização dos símbolos e o gerenciamento de

escopo são realizados através desta classe;

Page 86: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

85

g) TListaTabelas : armazena as tabelas de símbolos usadas durante a tradução em

uma lista. No fim da compilação a memória alocada para todas as tabelas da lista é

liberada, garantindo desta forma que a memória alocada para os símbolos durante

a compilação também seja desalocada, evitando perda de memória;

h) TPilhaTabelas : armazena as tabelas de símbolos em uma pilha. As tabelas

encontradas mais no topo desta pilha possuem nível de alinhamento maior que as

encontradas no fundo da pilha;

i) TTabelaSimbolos : responsável pelos símbolos encontrados dentro de um escopo;

j) TSimbolo : representa um símbolo. Armazena informações sobre o símbolo tais

como: nome e tipo;

k) TDimensaoMatriz : classe auxiliar usada quando um símbolo é do tipo matriz.

3.4.3 Diagrama de seqüência para o caso de uso compilar

Na Figura 6 é apresentado o diagrama de seqüência para o caso de uso compilar.

Page 87: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

86

Figura 6 - Diagrama de seqüência para o caso de uso compilar

O processo de compilação inicia com o usuário selecionando a opção Compilar na

interface do sistema. Em seguida o objeto FormPrincipal que é responsável pela interface do

sistema cria uma instância da classe TAnalisadorLexico (passando como parâmetro ao

construtor Create o programa fonte) e outra da classe TAnalisadorSintatico (passando a

instância do analisador léxico como parâmetro ao construtor Create ) que por sua vez cria

uma instância da classe TGerenciadorSimbolos .

Criadas as instâncias dos analisadores léxico e sintático o método Traduzir do

analisador sintático é invocado. Em seguida o analisador sintático entra em um laço até que o

fim do programa seja encontrado. É durante este laço que o programa em FURBOL é

traduzido. Enquanto o programa é traduzido o analisador sintático invoca repetidamente o

método ProximoToken do analisador léxico que se encarrega de fornecer o fluxo de tokens

para o analisador sintático. O analisador sintático também invoca métodos das classes

Page 88: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

87

TGerenciadorSimbolos e TSimbolo conforme necessário.

Encerrada a tradução do programa fonte uma instância da classe TMontadorLigador é

criada. Em seguida os métodos Montar e Ligar são invocados, o que resulta na criação do

código de máquina executável em microprocessadores 8086/8088.

3.5 IMPLEMENTAÇÃO DO AMBIENTE

Nessa seção é apresentada a implementação do ambiente de programação FURBOL.

Para realizar a implementação do protótipo foi utilizado o ambiente de desenvolvimento

Borland Delphi 7.0.

O código do ambiente apresentado por Silva (2002) foi totalmente reaproveitado. A

interface do ambiente não foi alterada, bem como o código responsável pela mesma. O código

do compilador foi extendido para oferecer suporte a TADs definidos pelos usuários do

FURBOL, conforme a nova especificação da LP apresentada na seção 3.2.

3.5.1 Analisador léxico

O analisador léxico do FURBOL passou a reconhecer novas palavras reservadas. Para

classificar um token como palavra reservada o analisador léxico verifica se o token encontra-

se em um array constante de strings. A declaração deste array é apresentado no Quadro 65.

Page 89: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

88

strLexReservadas : array [Low(TLexReservada)..High( TLexReservada)] of string = ( '', // prNenhum 'programa', // prPrograma 'inicio', // prInicio 'fim', // prFim 'var', // prVar 'inteiro', // prInteiro 'logico', // prLogico 'matriz', // prMatriz 'semaforo', // prSemaforo 'procedimento', // prProcedimento 'se', // prSe 'entao', // prEntao 'senao', // prSenao 'enquanto', // prEnquanto 'faca', // prFaca 'imprime', // prImprime 'leitura', // prLeitura 'verdadeiro', // prVerdadeiro 'falso', // prFalso 'nao', // prNao 'mod', // prMod 'div', // prDiv 'e', // prE 'ou', // prOu 'ref', // prRef 'inc', // prInc 'dec', // prDec 'nl', // prNl 'redimensiona', // prRedimensiona 'dimensoes', // prDimensoes, 'liminf', // prLiminf, 'limsup', // prLimsup, 'tarefa', // prTarefa 'morre', // prMorre 'espera', // prEspera 'repassa', // prRepassa 'criarsmf', // prCriarSmf 'excluirsmf', // prExcluirSmf 'p', // prP 'v', // prV 'criarobj', // prCriarObj 'destruirobj', // prDestruirObj 'classe', // prClasse 'construtor', // prConstrutor 'destrutor', // prDestrutor 'esse', // prEsse 'nulo'); // prNulo

Quadro 65 - Declaração das palavras reservadas do FURBOL

As palavras reservadas relacionadas com definição e uso de TADs são as sete últimas

palavras do array.

3.5.2 Analisador sintático preditivo

A classe que implementa o analisador sintático preditivo seguindo o algoritmo

apresentado na seção 2.2.4.2 é a classe TAnalisadorSintatico . Neste trabalho a classe foi

modificada para seguir a nova especificação da linguagem apresentada na seção 3.2. A classe

possui um método para cada não-terminal apresentado na especificação, além de outros

métodos auxiliares.

Page 90: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

89

3.5.2.1 Exemplos da implementação de não-terminais da nova especificação da LP

Nessa seção são apresentados exemplos da implementação de três não-terminais da

nova especificação da LP, dando ênfase aos atributos herdados e sintetizados.

No Quadro 66 é apresentado a implementação do não-terminal TAD.

01)function TAnalisadorSintatico._TAD(var CodigoAsm : string): string; 02)var 03) TADName: string; 04) MetodosTADCodigo, 05) MetodosTADCodAsm: string; 06) Simb: TSimbolo; 07)begin 08) if FLexico.Token = tkIdentificador then begin 09) 10) if FSimbolos.SimboloRedeclarado(FLexico.Lexe ma) then 11) raise ESinErro.CreateFmt(strErrSinIdentDuplic ado, [FLexico.Linha, FLexico.Coluna, 12)FLexico.Lexema]); 13) 14) TADName := FLexico.Lexema; 15) 16) FLexico.ProximoToken; 17) 18) Simb := TSimbolo.Create(TADName, TipoSimbolo Classe); 19) FSimbolos.Instalar(Simb); 20) Simb.TabelaMembros := FSimbolos.AbrirEscopo( TADName); 21) 22) _AtributosTAD; 23) 24) MetodosTADCodigo := _MetodosTAD(Simb, Metodo sTADCodAsm); 25) 26) if (FLexico.Token = tkReservada) and (FLexic o.Reservada = prFim) then begin 27) FLexico.ProximoToken; 28) end 29) else 30) raise ESinErro.CreateFmt(strErrSinTokenEsp erado, [FLexico.Linha, FLexico.Coluna, 31)strLexReservadas[prFim]]); 32) 33) FSimbolos.FecharEscopo; 34) 35) Result := MetodosTADCodigo; 36) CodigoAsm := MetodosTADCodAsm; 37) 38) if (FLexico.Token = tkEspecial) and (FLexico .Lexema = ';') then begin 39) FLexico.ProximoToken; 40) end 41) else 42) raise ESinErro.CreateFmt(strErrSinTokenEsp erado, [FLexico.Linha, FLexico.Coluna, 43)';']); 44) end 45) else 46) raise ESinErro.CreateFmt(strErrSinIdentEsper ado, [FLexico.Linha, FLexico.Coluna]); 47)end;

Quadro 66 - Implementação do não-terminal TAD

O parâmetro CodigoAsm e o retorno da função são os atributos sintetizados do não-

terminal TAD e representam respectivamente o código assembly e intermediário gerados pela

definição do TAD.

As variáveis locais MetodosTADCodigo e MetodosTADCodAsm representam os atributos

sintetizados do não terminal MetodosTAD .

Nas linhas 8 a 12 é verificado se o token atual é um identificador e se o mesmo não foi

Page 91: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

90

redeclarado dentro do espoco atual. Caso as condições não sejam satisfeitas um erro de

compilação é gerado. Detalhes sobre os erros de compilação são apresentados na seção

3.5.2.2.

A chamada do método ProximoToken da classe TAnalisadorLexico solicita ao

analisador léxico que o próximo token seja procurado. Esse token passará então a ser

referenciado como token atual.

Na linha 18 é criada uma instância da classe TSimbolo que manterá as informações do

TAD que está sendo definido. Na linha 19 o símbolo é instalado na tabela de símbolos do

escopo atual.

Na linha 20 é solicitado ao gerenciador de símbolos que um novo escopo seja aberto.

Esse novo escopo manterá os membros do TAD que está sendo definido. O método

AbrirEscopo do gerenciador de símbolos cria uma nova tabela de símbolos e empilha a nova

tabela na pilha de tabelas de símbolos. Esse escopo aberto passará então a ser referenciado

como escopo atual.

Na linha 22 é invocado o método que implementa o não-terminal AtributosTAD . Este

não-terminal não possui atributos herdados e sintetizados.

Na linha 24 o método que implementa o não-terminal MetodosTAD é invocado. É

passado como primeiro parâmetro a variável local que representa o atributo herdado do não-

terminal MetodosTAD (variável Simb ) e a variável que representa o atributo sintetizado do

mesmo (variável MetodosTADCodAsm) como segundo parâmetro. O outro atributo sintetizado

do não terminal MetodosTAD é o retorno do método invocado e é atribuído a variável local

MetodosTADCodigo que representa este atributo.

Na linha 33 é solicitado ao gerenciador de símbolos que o escopo aberto na linha 20

seja encerrado. O método FecharEscopo do gerenciador de símbolos desempilha uma tabela

de símbolos da pilha de tabelas.

Nas linhas 35 e 36 os atributos sintetizados do não-terminal TAD são produzidos.

No Quadro 67 é apresentado a implementação do não-terminal MetodoTAD.

Page 92: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

91

01)function TAnalisadorSintatico._MetodoTAD(TAD: TS imbolo; TipoMetodo: TTipoMetodo; 02) var Cod igoAsm: string): string; 03)var 04) MetodoName, MetodoNameAsm: string; 05) Simb, ParamOculto: TSimbolo; 06) TipoParamOculto: TTipoSimbolo; 07) Proximo, CCompostoCodigo, CCompostoCodAsm: str ing; 08)begin 09) if FLexico.Token = tkIdentificador then begin 10) if FSimbolos.SimboloRedeclarado(FLexico.Lexe ma) then 11) raise ESinErro.CreateFmt(strErrSinIdentDupli cado,[FLexico.Linha,FLexico.Coluna, 12)FLexico.Lexema]); 13) 14) MetodoName := FLexico.Lexema; 15) MetodoNameAsm := TAD.Nome + '@' + FLexico.Le xema; 16) FLexico.ProximoToken; 17) 18) Simb := TSimbolo.Create(MetodoName, TipoSimb oloMetodo); 19) Simb.NomeMetodoAsm := MetodoNameAsm; 20) FSimbolos.Instalar(Simb); 21) Simb.TabelaAgregada := FSimbolos.AbrirEscopo (MetodoName); 22) Simb.TipoMetodo := TipoMetodo; 23) 24) TipoParamOculto.Tipo := tsObjeto; 25) TipoParamOculto.TAD := TAD; 26) ParamOculto := TSimbolo.Create(strLexReserva das[prEsse], TipoParamOculto); 27) FSimbolos.Instalar(ParamOculto); 28) ParamOculto.TipoVariavel := tvValor; 29) 30) _ParamFormais; 31) 32) if (FLexico.Token = tkEspecial) and (FLexico .Lexema = ';') then begin 33) FLexico.ProximoToken; 34) 35) _EstruturaDados(tvLocal); 36) 37) CCompostoCodigo := _CComposto(Proximo, CCo mpostoCodAsm); 38) 39) Result := CRLF + 40) MontaLinha(MetodoNameAsm, 'proc' , 'near', true) + 41) CCompostoCodigo + 42) MontaLinha('', 'ret', '', true) + 43) MontaLinha(MetodoNameAsm, 'endp' , '', true); 44) 45) CodigoAsm := CRLF + 46) MontaLinha(MetodoNameAsm, 'proc', 'near' , true) + 47) MontaLinha('', 'push', 'bp', true) + 48) MontaLinha('', 'mov', 'bp, sp', true) + 49) MontaLinha('', 'sub', 'sp, ' + 50)IntToStr(FSimbolos.EscopoAtual.LarguraVariaveis) , true) + 51) CCompostoCodAsm + 52) MontaLinha('', 'mov', 'sp, bp', true) + 53) MontaLinha('', 'pop', 'bp', true) + 54) MontaLinha('', 'ret', IntToStr(FSimbolos .EscopoAtual.LarguraParametros + 2), 55)true) + 56) MontaLinha(MetodoNameAsm, 'endp', '', tr ue); 57) 58) FSimbolos.FecharEscopo; 59) 60) if (FLexico.Token = tkEspecial) and (FLexi co.Lexema = ';') then begin 61) FLexico.ProximoToken; 62) end 63) else 64) raise 65) ESinErro.CreateFmt(strErrSinTokenEsperado, [FLexico.Linha,FLexico.Coluna,';']); 66) end 67) else 68) raise 69)ESinErro.CreateFmt(strErrSinTokenEsperado,[FLexi co.Linha,FLexico.Coluna,';']); 70) end 71) else 72) raise ESinErro.CreateFmt(strErrSinIdentEsper ado, [FLexico.Linha, FLexico.Coluna]); 73)end;

Quadro 67 - Implementação do não-terminal MetodoTAD

Os parâmetros do método TAD e TipoMetodo representam os parâmetros herdados do

Page 93: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

92

não-terminal MetodoTAD (linha 1). O primeiro é uma referência para o TAD ao qual o método

que está sendo definido pertence. O segundo é o tipo do método o qual pode ser construtor,

procedimento (atualizador ou consultor) ou destrutor. O parâmetro CodigoAsm e o retorno da

função são os atributos sintetizados do não-terminal MetodoTAD e representam o código

assembly e intermediário gerados respectivamente.

As variáveis locais Proximo , CCompostoCodigo e CCompostoCodAsm representam os

atributos sintetizados do não terminal CComposto .

Na linha 15 é estabelecido o nome em assembly do método do TAD que está sendo

definido.

Na linha 18 é criada uma instância da classe TSimbolo que vai manter as informações

do método que está sendo definido. Na linha 20 o símbolo é instalado na tabela de símbolos

do escopo atual.

Na linha 21 é solicitado ao gerenciador de símbolos que um novo escopo seja aberto.

Este novo escopo vai manter os parâmetros e variáveis locais do método que está sendo

definido.

Nas linhas 24 a 28 o parâmetro oculto esse é criado e instalado no escopo aberto na

linha 21. O parâmetro oculto é instalado como primeiro parâmetro do método do TAD. O seu

tipo é objeto e aponta para o TAD ao qual o método que está sendo definido pertence.

Na linha 30 é invocado o método que implementa o não-terminal ParamFormais . Na

linha 35 o método que implementa o não-terminal EstruturaDados . Para este método é

passado um parâmetro indicando que as variáveis que estão sendo declaradas são variáveis

locais.

Na linha 37 o método que implementa o não-terminal CComposto é invocado. É

passado para o método as variáveis locais que representam os atributos sintetizados do não-

terminal CComposto .

Nas linhas 39 a 43 é montado o código intermediário do método do TAD que está

sendo definido. Nas linhas 45 a 56 é montado o código assembly do método.

Na linha 58 é solicitado ao gerenciador de símbolos que o escopo aberto na linha 21

seja encerrado.

No Quadro 68 é apresentado a implementação do não-terminal Esse .

Page 94: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

93

01)function TAnalisadorSintatico._Esse: TSimbolo; 02)begin 03) if (FLexico.Token = tkEspecial) and (FLexico.L exema = '.') then begin 04) FLexico.ProximoToken; 05) 06) if (FLexico.Token = tkIdentificador) then be gin 07) 08) // busca um nivel acima (atributo ou metod o) 09) Result := FSimbolos.ProcurarSimboloNivel(F Lexico.Lexema, 10)FSimbolos.EscopoAtual.Nivel-1); 11) if not Assigned(Result) then 12) raise ESinErro.CreateFmt(strErrSinIdentN aoDeclarado, [FLexico.Linha, 13)FLexico.Coluna, FLexico.Lexema]); 14) 15) FLexico.ProximoToken; 16) 17) end 18) else 19) raise ESinErro.CreateFmt(strErrSinIdentEsp erado, [FLexico.Linha, 20)FLexico.Coluna]); 21) end 22) else begin 23) Result := FSimbolos.ProcurarSimbolo(strLexRe servadas[prEsse]); 24) if not Assigned(Result) then 25) raise ESinErro.CreateFmt(strErrSinIdentNao Declarado, [FLexico.Linha, 26)FLexico.Coluna, FLexico.Lexema]); 27) end; 28)end;

Quadro 68 - Implementação do não-terminal Esse

O não-terminal Esse possui um atributo sintetizado que é o retorno do método.

Como o não-terminal Esse possui duas produções, o analisador sintático deve decidir

qual delas usar baseado no token atual. Esta tarefa é realizada na linha 3. Caso a condição

apresentada nessa linha seja satisfeita, a primeira produção é usada. Caso contrário a segunda.

Nas linhas 9 e 10 o método ProcurarSimboloNivel é invocado. Este método procura

um símbolo exclusivamente no nível passado como segundo parâmetro. Neste caso é

especificado que o nível deve ser um a menos que o nível atual. Como a palavra reservada

esse só pode ser usada dentro de um método, esta chamada ao método

ProcurarSimboloNivel garante que o símbolo será procurado exclusivamente no nível onde

os membros do TAD estão localizados.

Na linha 23 é solicitado ao gerenciador de símbolos que o símbolo com nome esse

seja localizado, ou seja, o parâmetro oculto esse é procurado.

No Quadro 69 é apresentado o código do método ProcurarSimboloNivel do

gerenciador de símbolos.

1)function TGerenciadorSimbolos.ProcurarSimboloNive l(const Nome: String; Nivel: Integer): 2)TSimbolo; 3)begin 4) Result := nil; 5) if (Nivel >= 0) and (Nivel < FEscopoAtivo.Quant idade) then 6) begin 7) Result := FEscopoAtivo.Tabelas[Nivel].Procura rSimbolo(Nome); 8) end; 9)end;

Quadro 69 - Código do método ProcurarSimboloNivel do gerenciador de símbolos

A procura é realizada exclusivamente na tabela encontrada no nível da pilha de tabelas

Page 95: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

94

(atributo FEscopoAtivo da classe) que foi especificado como parâmetro (parâmetro Nível -

Quadro 69, linha 1). Caso o nível especificado seja inválido, a linha 7 não é executada e nil é

retornado.

Na linha 7 é invocado o método ProcurarSimbolo da classe TTabelaSimbolos . Este

método verifica se existe um símbolo na tabela com o nome igual ao passado como

parâmetro.

3.5.2.2 Erros de compilação

O compilador do FURBOL não implementa recuperação de erros. Quando um erro de

compilação é encontrado o compilador lança uma exceção encerrando o processo de tradução.

A exceção é capturada pela classe TFormPrincipal que se encarrega de reportar o erro para

o usuário. O Quadro 70 apresenta um trecho do código do compilador que lança uma exceção.

1)if (FLexico.Token = tkEspecial) and (FLexico.Lexe ma = ';') then begin 2) FLexico.ProximoToken; 3)end 4)else 5)raise 6) ESinErro.CreateFmt(strErrSinTokenEsperado,[FLex ico.Linha,FLexico.Coluna,';']);

Quadro 70 - Exemplo de exceção gerada pelo compilador

No código apresentado é verificado na linha 1 se o token atual é o token especial ‘;’ .

Caso a condição não seja satisfeita o código das linhas 5 e 6 é executado, o que lança uma

exceção encerrando a compilação.

3.5.2.3 Acesso aos atributos do TAD

O método do compilador que gera código que atribui ao registrador de segmento ES o

conteúdo do parâmetro oculto esse é apresentado no Quadro 71.

function TAnalisadorSintatico.GerarAcessoAtributos: string; begin { carrregar o registrador ES com o primeiro paramet ro do método (parametro oculto que possui o valor do segmento on de estão os atributos do TAD) } Result := MontaLinha('', 'mov', 'es,word ptr [bp+' + IntToStr(FSimbolos.EspacoRes ervadoParametros) + ']', true); end;

Quadro 71 – Método que gera código que atribui ao registrador de segmento ES o conteúdo do parâmetro esse

Como o valor do atributo EspacoReservadoParametros não se altera durante toda a

compilação (é sempre 6), o código gerado para atribuir ao registrador de segmento ES o

Page 96: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

95

conteúdo do parâmetro esse é sempre o mesmo, conforme o que foi apresentado na seção

3.3.2.

O método que gera código para acessar os atributos do TAD usando o registrador ES

como registrador de segmento é apresentado no Quadro 72.

01)function TSimbolo.ObtemCodigoAssemblyAtributo(Re gDeslocInt: String): string; 02)begin 03) Result := ''; 04) 05) case Tipo.Tipo of 06) tsLogico, tsSemaforo: Result := Result + 'by te ptr '; 07) else Result := Result + 'word ptr '; 08) end; 09) 10) Result := Result + 'es:['; 11) 12) if (RegDeslocInt <> '') then begin 13) Result := Result + RegDeslocInt + '+'; 14) end; 15) 16) Result := Result + IntToStr(Deslocamento) + '] '; 17) 18)end;

Quadro 72 - Método que gera código assembly para acessar os atributos de um TAD

Nas linhas 5 a 8 (Quadro 72) é decidido se será gerado código para endereçar um ou

dois bytes, conforme o tipo do atributo. Na linha 16 é gerado o código para acessar o atributo.

Para isso é calculado o deslocamento do atributo dentro do TAD (seção 3.3.4) acessando a

property Deslocamento do símbolo. O parâmetro RegDeslocInt possui um deslocamento

adicional que quando não for vazio deve ser adicionado ao deslocamento do atributo. Esse

deslocamento adicional é usado quando o atributo é um array.

Os métodos apresentados no Quadro 71 e no Quadro 72 são usados em conjunto pelo

compilador. Primeiro é invocado o método do Quadro 71 para atribuir ao registrador ES o

conteúdo do parâmetro esse e em seguida o método do Quadro 72 para gerar código para

acessar o atributo. No Quadro 73 é apresentado um trecho de código do compilador que

invoca os métodos apresentados no Quadro 71 e no Quadro 72.

if (Simb.TipoVariavel = tvAtributo) then begin Prelocal := GerarAcessoAtributos; Local := Simb.ObtemCodigoAssemblyAtributo(''); End

Quadro 73 – Trecho de código do compilador que invoca os métodos apresentados no Quadro 71 e no Quadro 72

3.6 OPERACIONALIDADE DO AMBIENTE

O ambiente de programação FURBOL permite que o usuário abra, salve ou crie um

Page 97: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

96

novo arquivo fonte. Com o fonte criado o usuário pode compilar o programa, executá-lo,

visualizar o código intermediário e assembly gerado. A Figura 7 apresenta como acessar as

principais funcionalidades do FURBOL.

Figura 7 - Acesso as principais funcionalidades do FURBOL

As operações Novo, Abrir e Salvar também podem ser acessadas através do menu

Arquivo do ambiente. As operações Compilar e Executar também são encontradas no menu

Projeto .

Selecionando a segunda aba do editor, o usuário pode visualizar o código intermediário

gerado pelo processo de tradução. A Figura 8 apresenta o ambiente exibindo o código

intermediário gerado pelo compilador.

Page 98: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

97

Figura 8 - Ambiente exibindo o código intermediário gerado pelo compilador

Através da terceira aba do editor é exibido o código de montagem gerado ao usuário. A

Figura 9 apresenta o ambiente exibindo o código de montagem gerado durante a compilação.

Page 99: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

98

Figura 9 - Ambiente exibindo o código de montagem gerado pelo compilador

Outra funcionalidade disponibilizada pelo ambiente é visualizar qual foi a saída

apresentada pelo montador e pelo ligador (Figura 10). O usuário pode executar essa operação

através do menu Projeto , opção Saída montador/ligador .

Page 100: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

99

Figura 10 - Ambiente exibindo a saída do montador e do ligador

Algumas das funcionalidades do FURBOL também podem ser acessadas através de

atalhos no teclado. O Quadro 74 apresenta estas operações e seus respectivos atalhos.

Operação Atalho Novo arquivo Ctrl + N Abrir arquivo Ctrl + A Salvar arquivo Ctrl + S Compilar Ctrl + F9 Executar F9 Saída montador/ligador F5

Quadro 74 - Atalhos no teclado do ambiente

3.7 RESULTADOS E DISCUSSÃO

Desde o inicio da implementação percebeu-se que os testes seriam dificultados sem um

comando de saída que imprimisse inteiros. O comando de saída do FURBOL apenas imprimia

cadeias de caracteres. Com o objetivo de facilitar os testes e de ampliar a LP FURBOL foi

implementado um procedimento que imprime inteiros (Apêndice B) e que foi adicionado aos

outros procedimentos do núcleo do FURBOL.

Durante a implementação e dos testes percebeu-se problemas com a geração de código

de algumas construções já existentes no FURBOL e que acabaram não sendo corrigidas, os

quais são:

a) os parâmetros por referência só funcionam corretamente com variáveis globais. A

Page 101: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

100

saída do programa do Quadro 75, por exemplo, é indefinida, enquanto o correto

seria imprimir o valor 7777;

programa bug_param_referencia; procedimento proc1(ref i: inteiro); inicio i := 7777; fim; procedimento proc2; var j: inteiro; inicio proc1(j); imprime(j); (* resultado indefinido *) fim; inicio proc2; fim.

Quadro 75 – Exemplo de problema com parâmetros por referência

b) expressões que possuem parênteses apresentam em alguns casos um resultado

incorreto. O programa do Quadro 76, por exemplo, apresenta como saída o valor 4,

enquanto o correto seria 5.

programa bug_expressao; var i: inteiro; inicio i := (1 + 1) + 1 + (1 + 1); imprime(i); (* imprime 4 *) fim.

Quadro 76 - Exemplo de uma expressão que apresenta um resultado incorreto

Outro problema encontrado foi que o compilador não verificava durante a chamada de

um método se a quantidade de parâmetros reais era o mesmo que a quantidade de parâmetros

formais. Uma consistência foi adicionada e este problema foi corrigido.

A implementação existente de arrays não foi completamente adaptada a definição e

uso de TADs, deixando este trabalho com limitações, as quais são:

a) não é permitido declarar arrays dinâmicos como atributos dos TADs (Quadro 77,

linha 13);

b) é possível declarar um array de objetos, mas não é possível usar os elementos do

array individualmente nos comandos criarobj , destruirobj e na chamada de

métodos (Quadro 77, linhas 19, 20 e 21).

Page 102: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

101

01)programa limitacoes_arrays; 02) 03)classe TPonto 04) x, y: inteiro; 05) procedimento setXY(x, y: inteiro); 06) inicio 07) esse.x := x; 08) esse.y := y; 09) fim; 10)fim; 11) 12)classe TPilha 13) fPilha: matriz; (* não compila *) 14)fim; 15) 16)var 17) gPontos: matriz[1..120]: TPonto; 18)inicio 19) criarobj(gPontos[1]); (* não compila *) 20) gPontos[1].setXY(7, 120); (* não compila *) 21) destruirobj(gPontos[1]); (* não compila *) 22)fim.

Quadro 77 - Limitações do trabalho com relação aos arrays

Outra limitação do trabalho é que um símbolo não pode ser usado antes que o mesmo

seja definido. Por exemplo, um método não pode invocar outro método do mesmo TAD antes

que este método seja definido (Quadro 78, linha 15). Outro exemplo similar é que um TAD

não pode possuir como atributo um objeto de um TAD ainda não definido (Quadro 78, linha

5).

01)programa limitacao_simbolo_nao_definido; 02) 03)classe TRetangulo 04) topo_esquerda, 05) fundo_direita: TPonto; (* não compila – Tponto ainda não foi definido *) 06) 07) (...) 08)fim; 09) 10)classe TPonto 11) x, y: inteiro; 12) 13) construtor constroi(x, y: inteiro); 14) inicio 15) setXY(x, y); (* não compila – setXY ainda não foi definido *) 16) fim; 17) 18) procedimento setXY(x, y: inteiro); 19) inicio 20) esse.x := x; 21) esse.y := y; 22) fim; 24)fim; 23) 24)inicio 25) (...) 26)fim.

Quadro 78 - Limitações do trabalho com relação ao uso de símbolos ainda não definidos

Para validar a implementação, primeiro foram realizados testes nos procedimentos

adicionados ao núcleo (imprime , criarobj e destruirobj ). Quando os testes dos

procedimentos do núcleo foram concluídos partiu-se para a implementação do compilador.

Conforme os novos não-terminais ou as mudanças nas ações semânticas da

especificação da linguagem foram implementados, pequenos trechos de programas em

Page 103: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

102

FURBOL foram escritos e o código gerado analisado e validado. Quando tornou-se possível

definir TADs completos e código para usá-los, foram escritos pequenos programas que

testavam esses TADs. Novos programas foram escritos conforme a extensão do FURBOL era

implementada. Sempre que uma mudança significativa era implementada ou um erro

encontrado e corrigido, todos os programas de testes eram novamente verificados.

Page 104: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

103

4 CONCLUSÕES

O objetivo deste trabalho foi alcançado. A LP foi estendida e tornou-se possível a

criação de TADs pelos usuários do FURBOL. Esse pode definir os quatro tipos de operações

de um TAD (construtoras, consultoras, atualizadoras e destrutoras), declarar referências para

os TADs em outras unidades do programa e criar instâncias destes TADs.

Contudo, o trabalho apresenta algumas limitações, as quais são:

a) não é permitido declarar arrays dinâmicos como atributos dos TADs;

b) não é possível usar os elementos de um array de objetos individualmente nos

comandos criarobj , destruirobj e na chamada de métodos;

c) um símbolo não pode ser utilizado antes de ser definido.

As ferramentas utilizadas (Enterprise Architect para a especificação do ambiente e

Borland Delphi 7.0 para a implementação) mostraram-se adequadas.

Este trabalho é relevante para o curso de Ciência da Computação da FURB, pois

apresenta a extensão de um trabalho de pesquisa que vem sendo desenvolvida por vários

acadêmicos do curso e aprofunda vários temas abordados durante o curso de computação.

4.1 EXTENSÕES

Como possíveis extensões para o trabalho sugere-se:

a) ampliar a LP para oferecer suporte a orientação a objetos;

b) ampliar a LP para oferecer suporte a tratamento de exceções;

c) atualmente o compilador encerra a tradução quando um erro é encontrado (seção

3.5.2.2). Sugere-se implementar a recuperação de erros para que a compilação

continue até o final do arquivo fonte mesmo quando erros forem detectados;

d) criar uma biblioteca com funções de entrada e saída para vários dispositivos;

e) otimizar o código gerado;

f) ampliar a LP para permitir a definição de funções;

g) gerar código para outras plataformas (microcontroladores, ARM, entre outros).

Page 105: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

104

REFERÊNCIAS BIBLIOGRÁFICAS

ADRIANO, Anderson. Implementação de mapeamento finito (array’s) no ambiente FURBOL . 2001. 94 f. Trabalho de Conclusão de Curso (Bacharelado em Ciências da Computação) - Centro de Ciências Exatas e Naturais, Universidade Regional de Blumenau, Blumenau.

AHO, Alfred V.; SETHI, Ravi; ULMAN, Jeffrey D. Compiladores: princípios, técnicas e ferramentas. Tradução Daniel de Ariosto Pinto. Rio de Janeiro: Livros Técnicos e Científicos, 1995.

ANDRÉ, Geovânio B. Protótipo do gerador executável a partir do ambiente FURBOL. 2000. 65 f. Trabalho de Conclusão de Curso (Bacharelado em Ciências da Computação) - Centro de Ciências Exatas e Naturais, Universidade Regional de Blumenau, Blumenau.

BRUXEL, Jorge L. Definição de um interpretador para a linguagem Portugol, utilizando gramática de atributos. 1996. 67 f. Trabalho de Conclusão de Curso (Bacharelado em Ciências da Computação) - Centro de Ciências Exatas e Naturais, Universidade Regional de Blumenau, Blumenau.

LEYENDECKER, Gustavo Z. Especificação e compilação de uma linguagem de programação orientada a objetos para a plataforma Microsoft .NET. 2005. 88 f. Trabalho de Conclusão de Curso (Bacharelado em Ciências da Computação) - Centro de Ciências Exatas e Naturais, Universidade Regional de Blumenau, Blumenau.

LOUDEN, Kenneth C. Compiladores: princípios e práticas. Tradução Flávio Soares Corrêa da Silva. São Paulo: Thomson Pioneira, 2004.

NORTON, Peter; WILTON, Richard. Novo guia Peter Norton para programadores do IBM PC & OS/2. Tradução Daniel Vieira. Rio de Janeiro: Campus, 1991.

PRICE, Ana M. A.; TOSCANI, Simão S. Implementação de linguagens de programação: compiladores. 2. ed. Porto Alegre: Sagra Luzzatto, 2001.

RADLOFF, Marcelo. Protótipo de um ambiente para programação em uma linguagem bloco estruturada com vocabulário na língua portuguesa. 1997. 73 f. Trabalho de Conclusão de Curso (Bacharelado em Ciências da Computação) - Centro de Ciências Exatas e Naturais, Universidade Regional de Blumenau, Blumenau.

SANTOS, Jeremias P. dos; RAYMUNDI JÚNIOR, Edison. Programando em assembler: 8086/8088. São Paulo: McGraw-Hill, 1989.

Page 106: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

105

SCHMITZ, Héldio. Implementação de produto cartesiano e métodos de passagem de parâmetros no ambiente FURBOL. 1999. 86 f. Trabalho de Conclusão de Curso (Bacharelado em Ciências da Computação) - Centro de Ciências Exatas e Naturais, Universidade Regional de Blumenau, Blumenau.

SEBESTA, Robert W. Conceitos de linguagens de programação. Tradução José Carlos Barbosa dos Santos. Porto Alegre: Bookman, 2000.

SILVA, José R. V. da; SILVA, Joilson M. da. Desenvolvimento de um ambiente de programação para a linguagem Portugol. Dynamis, Blumenau, v. 1, n. 5, p. 99-114, dez. 1993.

SILVA, Paulo H. Implementação de unidades para processos concorrentes no ambiente FURBOL. 2002. 154 f. Trabalho de Conclusão de Curso (Bacharelado em Ciências da Computação) - Centro de Ciências Exatas e Naturais, Universidade Regional de Blumenau, Blumenau.

VAREJÃO, F. Linguagens de programação: Java, C, C++ e outras: conceitos e técnicas. Rio de Janeiro: Campus, 2004.

TOMAZELLI, Giancarlo. Implementação de um compilador para uma linguagem de programação com geração de código Microsoft .NET Intermediate Language. 2004. 83 f. Trabalho de Conclusão de Curso (Bacharelado em Ciências da Computação) - Centro de Ciências Exatas e Naturais, Universidade Regional de Blumenau, Blumenau.

VARGAS, Douglas N. Editor dirigido por sintaxe . Relatório de pesquisa n. 240 arquivado na Pró-Reitoria de Pesquisa da Universidade Regional de Blumenau, Blumenau, set. 1992.

______. Definição e implementação no ambiente windows de uma ferramenta para o auxílio no desenvolvimento de programas. 1993. 114 f. Trabalho de Conclusão de Curso (Bacharelado em Ciências da Computação) - Centro de Ciências Exatas e Naturais, Universidade Regional de Blumenau.

Page 107: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

106

APÊNDICE A – Procedimentos do núcleo para criação e exclusão de objetos

No Quadro 79 é apresentado o código completo do procedimento do núcleo criarobj .

;-------------------------------------------------- --------- ; NÚCLEO: Alocar memória para o objeto - FURBOL: criarobj(tamanhoObj: inteiro);

Retorna no AX o valor do segmento do bloco alocado para o objeto ;-------------------------------------------------- --------- criarobj proc near push bp mov bp, sp ;salva registradores push bx push cx push dx push si push di ;calcula quantos parágrafos são necessários para o objeto mov ax, word ptr[bp+4] ; ax <- tamanhoObj ;divide ax por 16 mov bx, 16 cwd div bx ;ax possui a quantidade de parágrafos ;se tem resto soma mais um parágrafo cmp dx, 0 jz criarobj_label1 add ax, 1 criarobj_label1: ;aloca pelo menos um parágrafo cmp ax, 0 jnz criarobj_label2 add ax, 1 criarobj_label2: ;aloca a memória mov bx, ax ; bx <- quantidade de parágrafos mov ah, 48h ;função do DOS int 21h jc criarobj_erro jmp criarobj_fim ; ax contém o valor do segmento do bloco alocado (retorno da função)

criarobj_erro: mov ah, 9 lea dx, erro@criar@objeto int 21h ; emite a mensagem de erro mov ax, 0 criarobj_fim: ;recupera registradores pop di pop si pop dx pop cx pop bx mov sp, bp pop bp ret 2 criarobj endp

Quadro 79 - Procedimento do núcleo criarobj

Page 108: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

107

No Quadro 80 é apresentado o código completo da rotina do núcleo destruirobj .

;-------------------------------------------------- --------- ; NÚCLEO: Libera a memória alocada para o objeto - FURBOL: destruirobj(blocoMemoria: inteiro); ;-------------------------------------------------- --------- destruirobj proc near push bp mov bp, sp ;salva registradores push ax push bx push cx push dx push si push di push es mov ah, 49h ;função do DOS mov es, word ptr [bp + 4] ; es <- blocoMemoria int 21h jc destruirobj_erro jmp destruirobj_fim destruirobj_erro: mov ah, 9 lea dx, erro@destruir@objeto int 21h destruirobj_fim: ;recupera registradores pop es pop di pop si pop dx pop cx pop bx pop ax mov sp, bp pop bp ret 2 destruirobj endp

Quadro 80 - Procedimento do núcleo destruirobj

Page 109: IMPLEMENTAÇÃO DE TIPOS ABSTRATOS DE DADOS NO …campeche.inf.furb.br/tccs/2008-I/2008-1-04-vf-andrelgarlini.pdf · Quadro 73 – Trecho de código do compilador que invoca os métodos

108

APÊNDICE B – Procedimento do núcleo para imprimir inteiros

No Quadro 81 é apresentado o código do procedimento usado para imprimir inteiros.

;-------------------------------------------------- --------- ; NÚCLEO: Imprimir inteiro - FURBOL: imprime(i: int eiro); ;-------------------------------------------------- --------- tam_buffer_imprime equ 12 imprime proc near push bp mov bp, sp ;variaveis locais sub sp, tam_buffer_imprime ;buffer para guardar os caracteres ;salva registradores push ax push bx push cx push dx push si push di mov ax, word ptr[bp+4] ; ax <- i mov si, -1 ;indexador do buffer ;calcula o abs de ax cwd xor ax, dx sub ax, dx loop_divide: ;divide ax por 10 mov bx, 10 cwd div bx add dl, 48 ;transforma o resto da divisão em u m caractere ascii add si, 1 mov byte ptr[bp-tam_buffer_imprime+si], dl ;guar da o caractere no buffer de caracteres ;testa se a divisão acabou cmp ax, 0 jne loop_divide ;se o parâmetro i for negativo imprime o caracte r '-' cmp word ptr[bp+4], 0 jnl loop_imprime mov ah, 2 mov dl, 45 int 21h ;imprime os caracteres do buffer loop_imprime: ;imprime caracter na tela mov dl, byte ptr[bp-tam_buffer_imprime+si] mov ah, 2 int 21h ;testa se os caracteres acabaram sub si, 1 cmp si, 0 jnl loop_imprime ;imprime quebra de linha mov ah, 2 mov dl, 13 int 21h mov dl, 10 int 21h ;recupera registradores pop di pop si pop dx pop cx pop bx pop ax mov sp, bp pop bp ret 2 imprime endp

Quadro 81 - Procedimento do núcleo imprime