UNIVERSIDADE DO ESTADO DO AMAZONAS - UEA
ESCOLA SUPERIOR DE TECNOLOGIA
ENGENHARIA DE COMPUTAÇÃO
RODRIGO BARROS BERNARDINO
IMPLEMENTAÇÃO DO SUPORTE A
ORIENTAÇÃO A OBJETOS NO COMPILADOR
JARAKI
Manaus
2012
RODRIGO BARROS BERNARDINO
IMPLEMENTAÇÃO DO SUPORTE A ORIENTAÇÃO A OBJETOS NO
COMPILADOR JARAKI
Trabalho de Conclusão de Curso apresentado
à banca avaliadora do Curso de Engenharia
de Computação, da Escola Superior de
Tecnologia, da Universidade do Estado do
Amazonas, como pré-requisito para obtenção
do tı́tulo de Engenheiro de Computação.
Orientador: Prof. M. Sc. Jucimar Maia da Silva Júnior
Manaus
2012
ii
Universidade do Estado do Amazonas - UEA
Escola Superior de Tecnologia - EST
Reitor:
José Aldemir de Oliveira
Vice-Reitor:
Marly Guimarães Fernandes Costa
Diretor da Escola Superior de Tecnologia:
Mário Augusto Bessa de Figueiredo
Coordenador do Curso de Engenharia de Computação:
Raimundo Corrêa de Oliveira
Coordenador da Disciplina Projeto Final:
Mário Augusto Bessa de Figueiredo
Banca Avaliadora composta por:
Data da Defesa: 23/11/2012.
Prof. M.Sc. Jucimar Maia da Silva Júnior (Orientador)
Prof. M.Sc. Raimundo Corrêa de Oliveira
Prof. M.Sc. Rodrigo Choji de Freitas
CIP - Catalogação na Publicação
B523i
BERNARDINO, Rodrigo
Implementação do Suporte a Orientação a Objetos no Compilador Jaraki /
Rodrigo Bernardino; [orientado por] Prof. MSc. Jucimar Maia da Silva Júnior
- Manaus: UEA, 2012.
84 p.: il.; 30cm
Inclui Bibliografia
Trabalho de Conclusão de Curso (Graduação em Engenharia de Computação). Universidade do Estado do Amazonas, 2012.
CDU: 004.4’4
iii
RODRIGO BARROS BERNARDINO
IMPLEMENTAÇÃO DO SUPORTE A ORIENTAÇÃO A OBJETOS NO
COMPILADOR JARAKI
Trabalho de Conclusão de Curso apresentado
à banca avaliadora do Curso de Engenharia
de Computação, da Escola Superior de
Tecnologia, da Universidade do Estado do
Amazonas, como pré-requisito para obtenção
do tı́tulo de Engenheiro de Computação.
Aprovado em: 23/11/2012
BANCA EXAMINADORA
Prof. Jucimar Maia da Silva Júnior, Mestre
UNIVERSIDADE DO ESTADO DO AMAZONAS
Prof. Raimundo Corrêa de Oliveira, M.Sc.
UNIVERSIDADE DO ESTADO DO AMAZONAS
Prof. Rodrigo Choji de Freitas, M.Sc.
UNIVERSIDADE DO ESTADO DO AMAZONAS
iv
Agradecimentos
Serei eternamente grato aos meus pais e meu
grande irmão, que me acompanharam desde
o inı́cio da minha vida. Porém, nada adiantaria o apoio e ensinamentos deles sem a
sincera dedicação que meus caros professores tiveram. Acredito ainda, que muito desse
saber teria sido desperdiçado e bem menos
desenvolvido se não fossem os vários conselhos, ensinamentos e a grande confiança que
o professor MSc. Jucimar Júnior tanto dedicou a mim, fazendo jus ao posto de orientador e, ainda mais, o de um verdadeiro educador! Obrigado! Seja como for, sem as várias
oportunidades e a proteção divina que tive,
absolutamente nada disso teria sido possı́vel
em minha vida. Portanto, agradeço a Deus
por tudo que veio e está por vir.
v
Resumo
Este trabalho apresenta a implementação do suporte a diversos recursos e funcionalidades relacionados à orientação a objetos (OO) da linguagem Java no compilador Jaraki.
Este compilador recebe como entrada um código na linguagem de programação Java e gera
um outro com mesmo comportamento na linguagem Erlang. A grande diferença é que o
programa escrito pelo usuário em Java poderá utilizar as mesmas vantagens da Máquina
Virtual do Erlang.
Palavras Chave: compiladores, erlang, java, tradutor, linguagens de programação
vi
Abstract
This work provides the implementation of the support to several resources and functionalities related to object orientation (OO) of the Java language on the Jaraki compiler.
This compiler receives as input a code written in the Java programming language and generates another one with the same behaviour in the Erlang language. The great difference is
that the program written by the user in Java would be able to explore the same advantages
of the Erlang Virtual Machine.
Key-words: compilers, erlang, java, translator, programming languages
vii
Sumário
Lista de Tabelas
x
Lista de Figuras
xi
Lista de Códigos
xii
1 Introdução
1.1 Objetivos . . . . . . . .
1.2 Trabalhos Relacionados .
1.3 Justificativa . . . . . . .
1.4 Metodologia . . . . . . .
1.5 Estrutura da Monografia
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2 Referencial Teórico
2.1 Compilador . . . . . . . . . . . . . . . . . . . . . . .
2.1.1 Estrutura de um Compilador . . . . . . . . .
2.1.2 Análise Léxica . . . . . . . . . . . . . . . . . .
2.1.3 Análise Sintática . . . . . . . . . . . . . . . .
2.1.4 Análise Semântica . . . . . . . . . . . . . . .
2.1.5 Tabela de Sı́mbolos . . . . . . . . . . . . . . .
2.1.6 Ferramentas para construção de compiladores
2.2 Orientação a Objetos . . . . . . . . . . . . . . . . . .
2.2.1 Objetos . . . . . . . . . . . . . . . . . . . . .
2.2.2 Classes . . . . . . . . . . . . . . . . . . . . . .
2.2.3 Representação de um objeto . . . . . . . . . .
2.2.4 Diagrama de Classes . . . . . . . . . . . . . .
2.2.5 Herança . . . . . . . . . . . . . . . . . . . . .
2.2.6 Polimorfismo . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
2
3
3
4
5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
6
8
8
9
12
12
13
15
16
16
18
20
21
23
viii
2.3
2.4
2.5
2.2.7 Sobrescrita de método . . . . . . . . . . . . . . .
2.2.8 Vinculação Dinâmica de Métodos . . . . . . . . .
Erlang . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3.1 Funções . . . . . . . . . . . . . . . . . . . . . . .
2.3.2 Compilação e Execução . . . . . . . . . . . . . . .
2.3.3 Variáveis . . . . . . . . . . . . . . . . . . . . . . .
2.3.4 Tipos de Dados . . . . . . . . . . . . . . . . . . .
Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.4.1 Compilação e Execução . . . . . . . . . . . . . . .
2.4.2 Modificadores de Visibilidade . . . . . . . . . . .
2.4.3 Construtores . . . . . . . . . . . . . . . . . . . .
2.4.4 Campos static . . . . . . . . . . . . . . . . . . .
2.4.5 Herança de Métodos . . . . . . . . . . . . . . . .
2.4.6 Assinaturas e Subassinaturas de Métodos . . . . .
2.4.7 Sobrescrita de Métodos . . . . . . . . . . . . . . .
2.4.8 As variáveis this e super . . . . . . . . . . . . .
2.4.9 Ocultamento de Métodos . . . . . . . . . . . . . .
2.4.10 Sobrecarga de Métodos . . . . . . . . . . . . . . .
2.4.11 Exemplo: Sobrecarga, Sobrescrita e Ocultamento
Jaraki . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.5.1 Análise Léxica e Sintática . . . . . . . . . . . . .
2.5.2 Arquitetura Geral . . . . . . . . . . . . . . . . . .
3 Desenvolvimento
3.1 O Compilador Jaraki . . . . . .
3.1.1 Estrutura do Compilador
3.1.2 Tabela de Sı́mbolos . . .
3.2 Tabela de Sı́mbolos para Classes
3.3 Compilando Classes . . . . . . .
3.3.1 Corpo da Classe . . . . .
3.3.2 Objetos . . . . . . . . .
3.3.3 Métodos . . . . . . . . .
3.3.4 Campos . . . . . . . . .
3.3.5 Construtores . . . . . .
3.4 Herança . . . . . . . . . . . . .
3.4.1 Herança de Campos . .
3.4.2 Herança de Métodos . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
24
25
26
27
28
28
29
30
31
32
32
33
34
35
35
36
36
37
37
38
38
39
.
.
.
.
.
.
.
.
.
.
.
.
.
41
41
41
42
43
43
44
44
47
50
53
55
56
57
ix
4 Testes
4.1 Metodologia de Teste . . . . . . . . . . . . . . . . .
4.1.1 Ambiente de Testes . . . . . . . . . . . . . .
4.1.2 Medição de Tempo . . . . . . . . . . . . . .
4.2 Processo de Compilação . . . . . . . . . . . . . . .
4.3 Instanciação de Objetos . . . . . . . . . . . . . . .
4.3.1 Construtor Padrão . . . . . . . . . . . . . .
4.3.2 Construtor Definido pelo Usuário . . . . . .
4.4 Manipulação de Campos de Objeto . . . . . . . . .
4.4.1 Manipulação Externa . . . . . . . . . . . . .
4.4.2 Manipulação Interna . . . . . . . . . . . . .
4.5 Herança . . . . . . . . . . . . . . . . . . . . . . . .
4.5.1 Herança de Campos, Métodos e Sobrescrita
4.5.2 Polimorfismo . . . . . . . . . . . . . . . . .
4.6 Resultado dos Testes . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
60
60
60
61
61
63
63
64
64
64
66
68
68
71
73
5 Conclusão
5.1 Trabalhos Futuros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
75
Referências Bibliográficas
76
A Conteúdo do CD: Códigos Fonte, PDFs e Vı́deos
A.1 Estrutura Principal e Simbologia . . . . . . . . . .
A.2 Pasta jaraki . . . . . . . . . . . . . . . . . . . . .
A.2.1 Pasta src . . . . . . . . . . . . . . . . . . . .
A.3 Pasta testes . . . . . . . . . . . . . . . . . . . . .
A.3.1 Testes de Instanciação . . . . . . . . . . . .
A.3.2 Testes de Campos . . . . . . . . . . . . . . .
A.3.3 Testes de Herança . . . . . . . . . . . . . . .
A.4 Pasta PDF . . . . . . . . . . . . . . . . . . . . . . .
A.5 Vı́deos . . . . . . . . . . . . . . . . . . . . . . . . .
78
78
79
80
81
82
83
84
85
85
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
x
Lista de Tabelas
3.1
3.2
3.3
Estrutura da Tabela de Sı́mbolos para dados das classes . . . . . . . . . . .
Dados dos Campos das Classes . . . . . . . . . . . . . . . . . . . . . . . .
Dados dos Campos das Classes . . . . . . . . . . . . . . . . . . . . . . . .
43
56
56
4.1
4.2
4.3
4.4
4.5
4.6
4.7
Teste 1: Instanciação . . . .
Teste 2: Instanciação . . . .
Teste 3: Campos Externo .
Teste 4: Campos Interno . .
Teste 5: Herança 1 . . . . .
Teste 5: Herança 2 . . . . .
Tempos de Todos os Testes .
63
64
65
67
70
71
73
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
xi
Lista de Figuras
2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
2.9
2.10
2.11
2.12
2.13
2.14
2.15
2.16
2.17
Um compilador de Linguagem de Programação . . . . . . . . .
Um interpretador de Linguagem de Programação . . . . . . .
Combinação de compilação e interpretação . . . . . . . . . . .
Processamento de Linguagem com Carregador . . . . . . . . .
Exemplo de sistema de processamento de linguagem . . . . . .
Exemplo de Gramática . . . . . . . . . . . . . . . . . . . . . .
Análise Ascendente . . . . . . . . . . . . . . . . . . . . . . . .
Exemplo de sistema de processamento de linguagem detalhado
Como armazenar um objeto . . . . . . . . . . . . . . . . . . .
Diagrama de Classes . . . . . . . . . . . . . . . . . . . . . . .
Diagrama dos Animais . . . . . . . . . . . . . . . . . . . . . .
Objeto Passaro . . . . . . . . . . . . . . . . . . . . . . . . . .
Diagrama dos Animais com Peregrino . . . . . . . . . . . . . .
Compilador Jaraki . . . . . . . . . . . . . . . . . . . . . . . .
Geração dos analisadores léxico e sintático no Jaraki . . . . .
Compilador Jaraki: Arquitetura Geral - Parte 1 . . . . . . . .
Compilador Jaraki: Arquitetura Geral - Parte 2 . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
7
7
8
9
11
11
13
19
21
21
22
23
38
39
39
40
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
Guardando um Objeto do Tipo Animal . . . . . . . . . .
Execução da Instanciação de um Objeto . . . . . . . . .
Execução de Chamada de Método de Objeto . . . . . . .
Acesso a Campos em Método de Objeto . . . . . . . . .
Acesso a Campos de Método de Objeto por outra Classe
Execução de um Construtor Definido pelo Usuário . . . .
Diagrama dos Animais . . . . . . . . . . . . . . . . . . .
Instanciando com Campos da Superclasse . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
44
46
49
52
53
54
55
57
4.1
Compilação e Execução do Cadastro de Bola . . . . . . . . . . . . . . . . .
62
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
xii
4.2
Diagrama dos Funcionários . . . . . . . . . . . . . . . . . . . . . . . . . . .
68
A.1
A.2
A.3
A.4
A.5
A.6
A.7
A.8
Simbologia para Representar Pastas e Arquivos
Pasta do Jaraki . . . . . . . . . . . . . . . . . .
Pasta dos Códigos fonte do Jaraki . . . . . . . .
Pasta de Testes . . . . . . . . . . . . . . . . . .
Arquivos dos Testes de Instanciação . . . . . . .
Arquivos dos Testes de Campos . . . . . . . . .
Arquivos dos Testes de Herança . . . . . . . . .
Documentos da Monografia e Apresentação . . .
78
79
80
81
82
83
84
85
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
xiii
Lista de Códigos
2.1.1 Exemplo de descrição de analisador léxico . . . . . . . . . . . . . . . . . .
15
2.1.2 Exemplo de descrição de analisador sintático . . . . . . . . . . . . . . . . .
16
2.2.1 Código de exemplo: Classe Bicicleta . . . . . . . . . . . . . . . . . . . . . .
18
2.2.2 Código de exemplo: Classe Principal1 . . . . . . . . . . . . . . . . . . . . .
19
2.2.3 Código de exemplo: Classes dos Animais . . . . . . . . . . . . . . . . . . .
22
2.2.4 Código de exemplo: Classe Principal2 . . . . . . . . . . . . . . . . . . . . .
22
2.2.5 Código de exemplo: Classe Principal3 . . . . . . . . . . . . . . . . . . . . .
23
2.2.6 Código de exemplo: Sobrescrita de métodos . . . . . . . . . . . . . . . . . .
24
2.2.7 Código de exemplo: Principal4 . . . . . . . . . . . . . . . . . . . . . . . . .
24
2.2.8 Código de exemplo: Vários tipos de Passaro . . . . . . . . . . . . . . . . .
25
2.3.1 Código Exemplo de Erlang . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
2.3.2 Exemplo de Compilação de Código em Erlang . . . . . . . . . . . . . . . .
28
2.3.3 Atribuindo novos valores a mesma variável . . . . . . . . . . . . . . . . . .
29
2.4.1 Código de exemplo: Pessoa e PrincipalPessoa . . . . . . . . . . . . . . . .
31
2.4.2 Exemplo de Construtor na Classe Ponto . . . . . . . . . . . . . . . . . . .
33
2.4.3 Exemplo de Principal para Construtor na Classe Ponto . . . . . . . . . . .
33
2.4.4 Exemplo de Principal para Campos Static . . . . . . . . . . . . . . . . . . .
34
2.4.5 Exemplo de Classe para Assinatura de Métodos . . . . . . . . . . . . . . .
35
2.4.6 Exemplo de Sobrescrita . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
2.4.7 Exemplo de Sobrecarga, Sobrescrita e ocultamento . . . . . . . . . . . . . .
38
3.3.1 Exemplo de Instanciação de um Objeto . . . . . . . . . . . . . . . . . . . .
45
3.3.2 Tradução de Chamada de Método Estático . . . . . . . . . . . . . . . . . .
48
3.3.3 Tradução de Métodos de Objeto . . . . . . . . . . . . . . . . . . . . . . . .
48
xiv
3.3.4 Acesso a Campos de Objeto . . . . . . . . . . . . . . . . . . . . . . . . . .
51
3.3.5 Construtor Definido pelo Usuário . . . . . . . . . . . . . . . . . . . . . . .
54
3.3.6 Construtor Padrão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
3.4.1 Instanciando com Campos da Superclasse . . . . . . . . . . . . . . . . . . .
57
3.4.2 Herdando Métodos das Superclasses . . . . . . . . . . . . . . . . . . . . . .
58
3.4.3 Exemplo do Uso do Super . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
4.2.1 Códigos de Exemplo para Processo de Compilação: Cadastro de Bola . . .
62
4.3.1 Teste 1: Instanciação - Java . . . . . . . . . . . . . . . . . . . . . . . . . .
63
4.3.2 Teste 2: Instanciação - Java . . . . . . . . . . . . . . . . . . . . . . . . . .
64
4.4.1 Teste 3: Manipulação de Campos - Java . . . . . . . . . . . . . . . . . . .
65
4.4.2 Execução do Teste 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
66
4.4.3 Teste 4: Manipulação de Campos - Java . . . . . . . . . . . . . . . . . . .
66
4.4.4 Execução do Teste 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
4.5.1 Teste de Herança 1: Classe Principal . . . . . . . . . . . . . . . . . . . . .
69
4.5.2 Teste 5: Herança 1 - Classes Funcionario e Tecnico . . . . . . . . . . . . .
69
4.5.3 Execução do Teste 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70
4.5.4 Teste de Herança 2: Classe Principal . . . . . . . . . . . . . . . . . . . . .
71
4.5.5 Execução do Teste 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
72
Capı́tulo 1
Introdução
Como exposto por [Stroustrup1991], programação orientada a objeto surgiu da necessidade
de dar um melhor controle e hierarquia ao código. A princı́pio, a programação imperativa
focava nos procedimentos: “decida quais procedimentos você quer; use os melhores algoritmos que você encontrar”. Com a evolução das linguagens, uma importância maior foi
dada aos dados, dando inı́cio aos tipos abstratos de dados e ao paradigma “decida os tipos
desejados; forneça um conjunto completo de operações para cada tipo” [Stroustrup1991].
Porém, não havia nenhuma estrutura hierárquica com dados e foi então que a programação orientada a objetos surgiu. As partes comuns de uma coleção de tipos abstratos de
dados similares são fatoradas e colocadas em um novo tipo. Os membros da coleção herdam
essas partes comuns do novo tipo. Esse recurso é a herança, que, segundo Sebesta, está no
centro da programação orientada a objetos e das linguagens que a suportam. [Sebesta2011].
Java é a linguagem orientada a objetos (OO) mais utilizada segundo levantamento
do [TIOBE2012]. Gosling, um dos criadores da linguagem, declarou que ela foi projetada
para não exigir muito treinamento para usá-la, que códigos escritos em Java pudessem ser
facilmente reutilizados, os dados, em tempo de execução, pudessem ser facilmente distribuı́dos em uma rede de computadores, fornecendo um ambiente seguro para essas ocasiões
e independente de arquitetura, podendo estar presentes em pequenos computadores e grandes sistemas de vários tipos. [Gosling1995]
Erlang, como descrita por Armstrong, criador da linguagem, é uma linguagem funcional
desenvolvida para ser usada em sistemas distribuı́dos que exigem uma alta performance,
Objetivos
2
processando grandes quantidades de dados paralela e concorrentemente. Também de acordo
com Armstrong, programas escritos em Erlang podem ser facilmente escaláveis, tolerantes
a falhas e exigem uma quantidade menor de linhas de código para serem implementados,
desde que o problema seja adequado ao paradigma. [Armstrong2007]
Cada linguagem e paradigma de programação foram projetados com propósitos diferentes e visando atender situações diferentes. Sendo assim, desenvolver uma aplicação em
uma linguagem pode ser mais difı́cil e menos eficiente que a mesma aplicação em outra
linguagem. Porém, os programadores podem estar mais habituados a desenvolver em determinada linguagem e, logo, mantêm seus projetos na mesma linguagem. Para que não
seja necessário trocar a linguagem de programação, diversos projetos como [Jython2012]
ou [Efene2012] procuram desenvolver compiladores alternativos para que uma linguagem
se beneficie das vantagens de outra linguagem.
1.1
Objetivos
Este trabalho apresenta a implementação do suporte a Orientação a Objetos (OO)
no compilador Jaraki. O compilador Jaraki tem o propósito de permitir que softwares
escritos em Java sejam interpretados na Máquina Virtual do Erlang (EVM). Para tanto, o
compilador deve interpretar os aspectos imperativo e orientado a objetos do Java e gerar
códigos em Erlang.
Para suportar OO é necessário implementar diversos recursos relacionados, são eles:
• Programação modular através de classes com funções estáticas;
• Definição de tipos abstratos de dados (classes e instanciação de objetos);
• Toda classe deve herdar os métodos e atributos de sua classe pai;
• Vinculação dinâmica em chamadas de métodos (polimorfismo);
• Diferentes nı́veis de visibilidade de atributos e métodos.
Trabalhos Relacionados
1.2
3
Trabalhos Relacionados
Entre as que utiliza a Máquina Virtual do Java (JVM) está o Jython [Jython2012], uma
implementação da linguagem de programação Python escrita em Java 100% puro. Outro
projeto semelhante é o JRuby [JRuby2012], que é uma implementação da linguagem Ruby
na JVM. Seguindo a mesma linha, foi desenvolvido Clojure [Clojure2012], uma implementação de um dialeto da linguagem Lisp que roda sobre a JVM. Todas elas permitem uma
integração totalmente transparente entre programas escritos na linguagem sendo compilada e programas em Java, além de usufruı́rem de todas as caracterı́sticas do Java, como,
por exemplo, a portabilidade.
Existem linguagens que rodam também sobre a EVM. Reia [Reia2012] é uma linguagem
script semelhante ao Ruby escrito para a EVM. Já a linguagem Efene [Efene2012] possui
sintaxe semelhante a python e javascript. Joxa [Joxa2012] possui sintaxe semelhante ao
Lisp, mas é uma linguagem com diversas caracterı́sticas únicas. Essas linguagens procuram
fornecer uma sintaxe mais intuitiva para gerar código executável na EVM. Especificamente
o Reia procura também facilitar o uso de OO em um ambiente próprio para concorrência:
a EVM.
1.3
Justificativa
Programação orientada a objetos está no centro da linguagem Java, logo, para tornar
o compilador Jaraki totalmente funcional, é necessário que este suporte OO. Além disso,
interpretar OO com uma linguagem funcional implica em diversos desafios por se tratarem
de paradigmas diferentes. Este trabalho apresenta soluções para se representar e gerenciar
certas estruturas exclusivas de linguagens OO em uma linguagem funcional.
Algumas das linguagens citadas na seção anterior possuem caracterı́sticas OO, porém
de forma diferente de como é visto no Java. No caso do Jython, foi implementado OO
de acordo com a sintaxe e lógica do Python. Já as linguagens Reia e Efene utilizam
uma abordagem compatı́vel com a EVM, pois, devido à filosofia do Erlang, não existem
variáveis globais e variáveis na função só podem ser atribuı́das uma única vez, dentre outras
limitações. Este projeto procura, de fato, implementar OO sem oferecer nenhuma limitação
Metodologia
4
devido à natureza do Erlang.
1.4
Metodologia
O desenvolvimento desse trabalho foi estruturado sobre as seguintes atividades:
1. Estudo da linguagem de programação Java. Há detalhes que passam desapercebidos pelos programadores, erros que raramente são cometidos, mas que o compilador deve verificar. Nesta etapa foram estudadas algumas possibilidades sintáticas
e semânticas da linguagem.
2. Delimitação das funcionalidades suportadas pelo Jaraki. Java possui diferentes pacotes, módulos e recursos sintáticos e semânticos. Foi preciso selecionar quais
seriam implementados por este trabalho.
3. Desenvolvimento da arquitetura e protótipo inicial. O compilador foi dividido em diversos módulos e nesta etapa implementou-se um conjunto bem reduzido
de recursos, focando mais na arquitetura e interface com o usuário do que na funcionalidade.
4. Desenvolvimento iterativo e incremental. Foram desenvolvidos vários pequenos
programas que utilizam diferentes recursos da linguagem, sendo que, para cada novo
programa, todos os módulos do compilador eram incrementados (analisador léxico,
sintático e semântico).
5. Testes de validação e desempenho. Comparou-se as entradas e saı́das de programas compilados com o compilador da OracleTM e o Jaraki, além do desempenho
dos mesmos.
Estrutura da Monograa
1.5
5
Estrutura da Monografia
Esta seção descreve brevemente o conteúdo dos próximos capı́tulos. no capı́tulo “Referencial Teórico” são apresentados os principais conceitos utilizados de compiladores, OO
e as linguagem Java e Erlang. No capı́tulo “Desenvolvimento” são descritos os módulos e
bibliotecas desenvolvidos para suportar OO tanto na compilação quanto na execução. O
capı́tulo “Testes” apresenta a eficácia e eficiência das bibliotecas e módulos desenvolvidos
comparando com o compilador oficial da OracleTM . Por fim, em “Conclusões” são descritas
as lições aprendidas, habilidades desenvolvidas e propostas de trabalhos futuros.
Capı́tulo 2
Referencial Teórico
Neste capı́tulo são apresentados os principais conceitos utilizados no desenvolvimento do
trabalho. Será descrito a estrutura de um compilador, detalhando seus principais componentes; os recursos de orientação a objetos (OO) implementados neste trabalho; as
principais caracterı́sticas da linguagem Erlang; os recursos especı́ficos da linguagem Java
relacionados a OO implementados neste trabalho; o que é o compilador Jaraki e como este
trabalho afetou a sua estrutura.
2.1
Compilador
Um compilador (Figura 2.1) é um programa que recebe como entrada um programa em
uma linguagem de programação (linguagem fonte) e traduz para um programa equivalente
em outra linguagem de programação (linguagem objeto) [Aho2007]. Geralmente a tradução
é feita de uma linguagem de alto nı́vel para outra de baixo nı́vel, sendo que a linguagem
objeto pode estar em nı́vel de máquina ou não.
programa
fonte
Compilador
programa
objeto
Figura 2.1: Um compilador de Linguagem de Programação
Compilador
7
Um outro tipo de processador de linguagens é o interpretador (Figura 2.2). Ao invés
de gerar novos programas para que posteriormente sejam executados, ele lê o programa
e suas entradas e executa diretamente. Geralmente programas compilados (que foram
processados por um compilador) são mais eficientes que programas interpretados, porém
os últimos geralmente oferecem uma melhor forma de diagnosticar erros [Aho2007].
programa fonte
entrada
Interpretador
saída
Figura 2.2: Um interpretador de Linguagem de Programação
Os processadores de linguagens como Java e Erlang combinam compilação e interpretação. Para tanto, eles fazem uso do recurso conhecido como “máquina virtual”. Primeiramente o programa fonte é compilado para uma forma intermediária, chamada bytecodes, e
então são interpretados em uma máquina virtual (Figura 2.3) [Aho2007].
programa fonte
Tradutor
código intermediário
entrada
Interpretador
saída
Figura 2.3: Combinação de compilação e interpretação
Em alguns casos, um programa pode ser divido em vários arquivos (módulos) e, para
compilá-lo, é preciso unir a informações de todos eles. Esse trabalho tanto pode ser feito por
um outro programa, chamado pré-processador, quanto pelo próprio compilador [Aho2007].
Além disso, certas linguagens de programação, como o Java, possuem programas précompilados (bibliotecas) que estão disponı́veis em forma de rotinas ou funções para o
programador utilizar na composição de seu programa. Quando determinada biblioteca for
utilizada, é preciso acrescentar seu código ao programa objeto do código do programador.
Esse trabalho também pode ser feito pelo compilador ou por outro programa, chamado
“editor de ligação” ou “carregador” (Figura 2.4) [Aho2007].
Compilador
8
programa fonte
Compilador
código de máquina relocável
Editor de ligação / Carregador
código de máquina alvo
Figura 2.4: Processamento de Linguagem com Carregador
2.1.1
Estrutura de um Compilador
Até aqui o compilador foi tratado como uma “caixa preta”, que mapeia um programa
fonte para um semanticamente equivalente na linguagem objeto.
No conteúdo dessa
“caixa” existem duas partes: análise e sı́ntese, cada uma com suas respectivas subdivisões [Aho2007].
A parte da análise separa um código fonte em diversas pequenas partes e verifica se estão
organizadas de acordo com uma estrutura gramatical. Essa estrutura é usada para criar
uma representação intermediária do programa fonte. Se forem detectados erros, devem ser
apresentadas mensagens esclarecedoras ao usuário para que este possa tomar uma ação
corretiva. Além disso, a análise também é responsável por reunir informações sobre o
programa de entrada, que são guardadas na chamada “tabela de sı́mbolos”, passada junto
com a representação intermediária para a parte de sı́ntese. A parte de sı́ntese constrói o
programa objeto a partir dessas informações [Aho2007].
A figura 2.5 representa um compilador como uma sequência de fases, onde o programa
é transformado de uma representação para outra, até a geração do código objeto. A tabela
de sı́mbolos, que armazena informações sobre todo o programa fonte, é usada em todas as
fases [Aho2007].
2.1.2
Análise Léxica
A primeira fase de um compilador é a análise léxica. O analisador léxico lê o fluxo
de caracteres que compõem o programa fonte e os agrupa em sequências significativas,
Compilador
9
fluxo de caracteres
Analisador Léxico
fluxo de tokens
Analisador Sintático
árvore sintática
Tabela
de
Símbolos
Analisador Semântico
árvore sintática
Gerador de código intermediário
representação de código intermediário
Gerador de código
código de máquina alvo
Figura 2.5: Exemplo de sistema de processamento de linguagem
chamadas lexemas [Aho2007]. Ou seja, é nessa fase que as palavras ganham significado.
A sequência de caracteres “1”, “.” e “0”, por exemplo, formará um lexema e, mais adiante, ele será identificado como o número de ponto flutuante “1.0”. Para representar essa
informação, o analisador léxico produz como saı́da um token no formato [nome-token,
valor-atributo] [Aho2007]. O componente nome-token é um sı́mbolo abstrato usado
durante a análise sintática e o segundo componente valor-atributo contém uma especificação desse token ou aponta para uma entrada na tabela de sı́mbolos referente a esse token.
No exemplo dado anteriormente o token dele pode ser construı́do assim: [float, 1.0].
O primeiro bloco da Figura 2.8 demonstra essa fase da compilação.
2.1.3
Análise Sintática
A segunda fase do compilador é a análise sintática. O analisador sintático, também
chamado de parser, utiliza o primeiro componente dos tokens (nome-token), produzido
pelo analisador léxico, para criar uma representação intermediária tipo árvore, que mostra
a estrutura gramatical da sequência de tokens, chamada “árvore sintática” [Aho2007]. As
Compilador
10
instruções na linguagem fonte são quebradas em partes de nı́vel mais baixo e colocadas na
ordem de execução adequada (segundo bloco da Figura 2.8). Nesse momento também são
verificados se existem erros sintáticos que, caso presentes, são notificados ao usuário.
Gramáticas Livres de Contexto
A estrutura sintática das linguagens de programação são descritas por “gramáticas
livre de contexto”, ou “gramáticas” para abreviar. Isso significa que é definidos um ou mais
“agrupamentos sintáticos” de forma hierárquica. Por exemplo, uma express~
ao pode ser
uma soma de outra express~
ao com um termo; um termo, por sua vez, pode ser um número.
Assim, juntando as várias partes se constrói a sintaxe da linguagem. Formalmente, como
exposto por [Aho2007], essas gramáticas são compostas por:
• Terminais: são os sı́mbolos básicos a partir dos quais as cadeias são formadas. São
os nomes dos tokens provenientes da análise léxica;
• Não-terminais: são variáveis sintáticas que representam conjuntos de cadeias. Eles
impõem uma estrutura hierárquica sobre a linguagem que é a chave para a análise
sintática e tradução;
• Sı́mbolo inicial: um não-terminal que representa a linguagem gerada pela cadeia;
• Produções: especificam a forma como os terminais e não-terminais podem ser combinados para formar cadeias. Cada produção consiste em:
– Um não-terminal chamado de cabeça ou lado esquerdo da produção, que define
algumas das cadeias representadas pela cabeça;
– O sı́mbolo de “seta”;
– Um corpo ou lado direito da produção, que consiste de zero ou mais terminais
e não-terminais. Eles descrevem uma forma como as cadeias do lado esquerdo
da produção podem ser construı́das.
A gramática da Figura 2.6 define expressões de soma e subtração, que podem, ou não,
vir entre parênteses. Os sı́mbolos terminais estão em negrito e os não-terminais não. O
sı́mbolo inicial é expressao.
Compilador
11
expressao
expressao
expressao
termo
termo
→
→
→
→
→
expressao + termo
expressao - termo
termo
( expressao )
id
Figura 2.6: Exemplo de Gramática
Análise Ascendente com LALR(1)
O método para construção da árvore sintática mais frequentemente usado é a análise
ascendente LALR(1) [Aho2007]. A análise sintática ascendente corresponde à construção
da árvore sintática a partir das folhas. A Figura 2.7 representa a construção de uma árvore
de derivação com esse tipo de análise para uma soma. Esta árvore é uma representação
gráfica de como as produções são aplicadas para substituir não-terminais. As letras E e T
representam as regras da gramática da Figura 2.6 expressao e termo, respectivamente.
id + id
T + id
id
E + id
E + T
T
T
id
id
id
E
E + T
T
id
id
Figura 2.7: Análise Ascendente
Analisadores LALR(1), ou LR(1) com Look Ahead (LA) são uma otimização dos LR(k).
O L representa que a entrada é lida da esquerda para a direita; o R, que a construção das
derivações será feita à direita ao reverso; o k, quantos sı́mbolos (tokens) à frente no fluxo
de entrada auxiliam na decisão de qual regra adotar. Seu princı́pio de funcionamento é a
construção de tabelas que indicam o que fazer ao encontrar determinado token. As ações
são ler o próximo token ou substituir por uma regra o que já foi lido. Mais detalhes podem
ser encontrados em [Aho2007].
Compilador
2.1.4
12
Análise Semântica
O analisador semântico utiliza a árvore sintática e as informações na tabela de sı́mbolos
para verificar a consistência semântica do programa fonte com a definição da linguagem.
Nesta fase o significado dos ramos da árvore sintática e a relação entre eles são analisados.
A “verificação de tipos” é uma parte importante nessa análise [Aho2007]. Para tanto,
compilador verifica se cada operador possui operandos compatı́veis. Por exemplo, se determinada linguagem não permite que uma variável do tipo string receba valores inteiros,
isso deve ser checado nessa fase.
Outra importante atividade relacionada é a realização de conversões de tipos, chamadas
“coerções” [Aho2007]. Considere, por exemplo, que em uma operação de atribuição uma
variável de um tipo ponto flutuante receba um número inteiro. Em certas linguagens, como
o Java, o compilador deve realizar a conversão do valor do número para ponto flutuante.
O terceiro bloco da Figura 2.8 ilustra essa fase da compilação.
2.1.5
Tabela de Sı́mbolos
A tabela de sı́mbolos é uma estrutura de dados usada pelos compiladores para conter
informações sobre as construções do programa fonte. Ela é fundamental durante o processo
de compilação, pois registra os nomes de variáveis usados no programa fonte e guarda
informações sobre os diversos atributos de cada nome. Esses atributos podem determinar
qual o escopo de um nome (onde ele pode ser usado), seu tipo, a quantidade de tipos dos
parâmetros para funções, o valor e espaço de memória alocados para variáveis, onde se
encontra a implementação de determinada função, entre outros. [Aho2007]
Ela deve ser projetada de tal forma que rapidamente o compilador possa consultar
informações especı́ficas de cada nome. Além disso, em certos casos, ela pode ser composta
de outros tipos de tabelas, como no caso de classes, em que é preciso armazenar uma
entrada para cada campo e método [Aho2007]. Ela deve estar acessı́vel durante todas as
fases da compilação, como mostrado na Figura 2.8.
Compilador
13
position = initial + rate * 60
Analisador Léxico
<id,1><=><id,2><+><id,3><*><60>
Analisador Sintático
=
+
<id,1>
*
<id,2>
<id,3>
Tabela
de
Símbolos
60
Analisador Semântico
=
+
<id,1>
<id,2>
*
<id,3>
inttofloat
60
Gerador de código intermediário
t1 = inttofloat(60)
t2 = id3 * t1
t3 = id2 + t2
id1 = t3
Figura 2.8: Exemplo de sistema de processamento de linguagem detalhado
2.1.6
Ferramentas para construção de compiladores
Muitos desenvolvedores de software tiram proveito de diversos “ambientes de desenvolvimento”, como editores de texto integrados a compiladores, depuradores, gerenciadores de
versão, entre outros. O projetista de compiladores também possui suas ferramentas que
auxiliam o desenvolvimento desse tipo de software.
Essas ferramentas utilizam linguagens próprias para especificar e implementar diversas
partes de um compilador e frequentemente usam algoritmos bastante sofisticados. Algumas
das mais utilizadas são os geradores de analisadores sintáticos e geradores de analisadores
léxicos [Aho2007].
Compilador
14
Flex
Flex, ou Fast Lexical Analyser, [Flex2012] é um gerador de analisador léxico rápido.
Ele é uma implementação gratuita do programa lex. Este programa recebe como entrada
um arquivo de texto contendo a descrição do analisador a ser gerado. Esta descrição é
composta por uma série de pares de expressões regulares (regras que definem como formar
os lexemas) e código em C (definem os tokens). Após processar as regras, o Flex gera um
código-fonte em C, chamado lex.yy.c, que define a função yylex(). Este programa gerado
analisa sua entrada (código-fonte da linguagem desenvolvida) em busca de ocorrências de
texto que correspondam a algum dos padrões definidos nas regras. Ao encontrar um padrão
ele executa o código em C correspondente.
Leex
Leex é o gerador de analisador léxico do Erlang e é similar ao flex [Leex2012]. Recebe
como entrada um arquivo de extensão .xrl contendo a descrição do analisador. A descrição
é dividida em definições, com expressões regulares associadas a nomes; regras, que utilizam
as definições através dos nomes para formar os lexemas e tokens correspondentes; e código
Erlang, que permite definir funções auxiliares para formar os tokens. Ao processar o .xrl,
o Leex gera um código em Erlang com o mesmo nome do arquivo de entrada. Este arquivo
define a função string(), que recebe uma cadeia de caracteres contendo o código-fonte e
gera uma lista de tokens para ser usado posteriormente. O Código 2.1.1 exemplifica essa
estrutura.
1
2
3
Definitions.
ComparatorOp
= (<|<=|==|>=|>|!=)
4
5
6
Rules.
{ComparatorOp}
: {token, {comparation_op, TokenLine, op(TokenChars)}}.
7
8
Erlang code.
op(OpChar) ->
case OpChar of
"<=" ->
"!=" ->
"&&" ->
"||" ->
"!" ->
_
end.
9
10
11
12
13
14
15
16
’=<’;
’=/=’;
’and’;
’or’;
’not’;
->
list_to_atom(OpChar)
Código 2.1.1: Exemplo de descrição de analisador léxico
Compilador
15
Bison
Bison é um gerador de analisador sintático de propósito geral que converte uma gramática livre de contexto em um analisador LALR(1) [Bison2012]. Para tanto, o arquivo
de entrada deve definir um ou mais sı́mbolos terminais e não-terminais, a precedência de
operadores e a gramática. Além disso, pode conter código em C para auxiliar na tomada
de decisão durante a análise. Após analisar a definição da linguagem, ele gera um código
em C que define a função yyparse(). Para criação da árvore sintática e uso posterior pelo
analisador semântico, o usuário deve importar tipos auxiliares definidos no bison, como o
tree, que está na biblioteca ptypes.h.
Yecc
Yecc é o gerador de analisador sintático LALR(1) para o Erlang [Yecc2012] e é semelhante ao bison. Ele recebe uma gramática como entrada e produz código Erlang de um
analisador sintático. O arquivo de entrada deve ter extensão .yrl, contendo uma lista
de não-terminais; terminais; a definição do sı́mbolo inicial ; opcionalmente uma ordem de
prioridade ao analisar terminais; um conjunto de regras (a gramática); e código Erlang
para auxiliar na construção da árvore. O código em Erlang gerado pelo analisador define
uma função parse() que recebe como entrada uma lista de tokens e aplica as definições
para criar a árvore sintática, que em Erlang são tuplas dentro de tuplas, sem a necessidade
de definir novos tipos. O Código 2.1.2 exemplifica essa estrutura.
1
2
3
4
5
6
7
8
9
Nonterminals
start_parser
Rootsymbol start_parser.
start_parser -> add_expr : ’$1’.
add_expr -> mult_expr add_op add_expr :
{op, line(’$2’), unwrap(’$2’), ’$1’, ’$3’}.
10
11
12
mult_expr -> literal mult_op mult_expr :
{op, line(’$2’), unwrap(’$2’), ’$1’, ’$3’}.
mult_expr -> literal
: ’$1’.
13
14
15
literal -> integer
16
17
18
Erlang code.
unwrap({_, _, Value})
line({_, Line, _})
: ’$1’.
-> Value.
-> Line;
Código 2.1.2: Exemplo de descrição de analisador sintático
Orientação a Objetos
2.2
16
Orientação a Objetos
Programação Orientada a Objetos (POO) surgiu da necessidade de fornecer uma estrutura hierárquica ao código e dispor de funções e estruturas de dados como uma única
unidade [Sebesta2011]. Um objeto é um tipo abstrato que contém caracterı́sticas (atributos) e comportamentos (métodos). Uma linguagem que suporta POO deve oferecer meios
de descrever um objeto qualquer, criar objetos que são especializações deste primeiro e
permitir exprimir a distinção entre eles. Por exemplo, uma forma possui area e cor e a
função desenhar. Já um circulo, que também é uma forma, possui area, cor e raio
e a função desenha-circulo. Se a linguagem permite definir tipos abstratos de dados,
expressar a distinção entre os tipos que são generalizações de outros (como entre forma e
circulo) e permite o polimorfismo, então ela suporta orientação a objetos [Sebesta2011].
2.2.1
Objetos
Objetos no mundo real possuem todos um estado e um comportamento. Cachorros tem
um estado (nome, cor, fome, humor) e comportamento (latir, andar, comer). Bicicletas
também tem um estado (marcha, velocidade atual) e comportamentos (mudar a marcha,
frear, pedalar). Objetos de software são conceitualmente semelhantes aos objetos do mundo
real. Objetos guardam o estado em campos (variáveis das linguagens de programação) e
apresentam comportamentos através de métodos (funções em linguagens de programação).
É possı́vel ocultar o estado interno e exigir que toda interação com o objeto seja feita através
de métodos, isso é chamado encapsulamento de dados, que é um conceito fundamental em
POO.
2.2.2
Classes
No mundo real, frequentemente encontra-se muitos objetos do mesmo tipo. Devem
existir várias bicicletas, todas da mesma marca e modelo. Cada uma foi construı́da do
mesmo conjunto de diagramas e, então, contêm os mesmos componentes. Em termos de
orientação a objetos, a bicicleta de um indivı́duo é dita como uma instância da classe de
objetos conhecida como bicicletas. Classes em software, de forma semelhante, agrupam
objetos semelhantes em um novo tipo.
Orientação a Objetos
17
Considere o Código 2.2.1. Ele contém uma possı́vel implementação de Bicicleta na
linguagem de programação Java.
1
2
3
class Bicicleta {
int velocidade = 0;
int marcha = 1;
4
5
void mudarMarcha(int novoValor) {
marcha = novoValor;
}
6
7
8
void acelerar(int incremento) {
velocidade = velocidade + incremento;
}
9
10
11
12
13
void frear(int decremento) {
velocidade = velocidade - decremento;
}
14
15
16
void imprimirEstado() {
System.out.println(
"velocidade: " + velocidade + "\n" +
"marcha: " + marcha);
}
17
18
19
20
21
22
}
Código 2.2.1: Código de exemplo: Classe Bicicleta
O código apresenta uma descrição de como todo objeto dessa classe deve ser. Os
campos velocidade e marcha representam o estado do objeto e os métodos mudarMarcha,
acelerar, frear e imprimirEstado definem a interação dele com outros objetos. Porém,
este código não pode ser executado, ele é de fato apenas um “protótipo”. É necessário um
método principal (em Java chamado main) que instancie um objeto dessa classe para que
ele possa ser usado.
2.2.3
Representação de um objeto
Sempre que um objeto é criado, é reservado um espaço na memória para guardar o
valor das variáveis dele e a qual classe ele pertence (Figura 2.9). Uma referência a esse
espaço de memória é criada para que seja possı́vel alterar o estado do objeto no futuro. A
referência à classe do objeto deve ser mantida para dar suporte à vinculação dinâmica de
métodos, como mostrado na Seção 2.2.8, Página 25.
Orientação a Objetos
18
memória heap
área dos métodos
referência para dados da classe
referência para objeto
dado de instância
dado de instância
......
dados da
classe
Figura 2.9: Como armazenar um objeto
Em Java, para criar um objeto, deve ser usado o operador new juntamente com um
construtor da classe que se deseja instanciar. As variáveis que armazenam referências a
objetos são chamadas “variáveis de referência”. O Código 2.2.2 contém um exemplo de
instanciação.
1
2
3
4
5
6
7
class Principal1 {
public static void main(String[] args) {
Bicicleta b = new Bicicleta();
b.mudarMarcha(2);
b.acelerar(2);
}
}
Código 2.2.2: Código de exemplo: Classe Principal1
Orientação a Objetos
2.2.4
19
Diagrama de Classes
Diagrama de classe é um tipo de representação da UML [Booch2006] para expressar a
estrutura e as relações entre as classes. Eles podem expressar conceitos de POO como associação, agregação, multiplicidades, etc., mas para entendimento deste trabalho é exposto
apenas a representação da generalização (herança), campos e métodos.
UML
A UML (Unified Modeling Language) é uma linguagem de modelagem visual de propósito geral usada para especificar, visualizar, construir e documentar artefatos de um sistema
de software. Como exposto em [Booch2006], ela expressa, dentre outros detalhes, as decisões e o entendimento de um sistema a ser construı́do. Ela inclui conceitos semânticos,
notações e diretrizes. Além disso, pode representar a estrutura estática e o comportamento
dinâmico de um sistema.
Um sistema é modelado como uma coleção de objetos discretos que interagem entre si
para beneficiar um usuário externo. A estrutura estática define os tipos de objetos importantes para um sistema, suas implementações e o relacionamento entre eles. O comportamento dinâmico define a história dos objetos ao longo do tempo e a comunicação entre eles.
Modelar um sistema através de diversos, porém correlatos, pontos de vista [Booch2006].
Representação de Classes
A notação para uma classe em UML é um retângulo com “compartimentos” para o
nome da classe, atributos (campos) e operações (métodos), como mostrado na Figura 2.10.
A visibilidade dos métodos é expressa pelo sinal de “+” ou “-” antes do nome do campo ou
método, sendo public ou private, respectivamente. Após o nome do campo há um sinal
“:” seguido do tipo dele. Já para o método, o que vem após esse sinal é o tipo de retorno.
Para expressar a generalização (ou herança, explicado na Seção 2.2.5), são usadas setas
com a extremidade branca. A seta aponta para a superclasse.
Sendo assim, o diagrama da Figura 2.10 é interpretado da seguinte maneira: define a
classe Animal, que tem os campos public peso, do tipo int e cor do tipo int; os métodos
public comer(), com tipo de retorno void e andar(), com tipo de retorno void.
Orientação a Objetos
20
tipo
Animal
visibilidade
+ peso : int
+ cor : int
campos
+ comer() : void
+ andar() : void
métodos
retorno
classe
generalização
Figura 2.10: Diagrama de Classes
2.2.5
Herança
POO permite que classes herdem variáveis e comportamentos comuns. Na prática, é
a funcionalidade de definir uma nova classe que é uma versão modificada de uma classe
existente sem a necessidade de redefinir as partes comuns à classe herdada. A classe original
é chamada superclasse e a nova classe é chamada subclasse. Uma vantagem disso é que a
subclasse pode criar novos campos e métodos, além de redefinir métodos existentes.
Para exemplificar, considere o diagrama de classes da Figura 2.11 e as implementações
de suas classes no Código 2.2.3. Um Animal pode ser um Cachorro ou um Passaro. Ambos
podem comer e andar, possuem peso e cor. Mas o Cachorro late e tem pelos, o Passaro
voa e tem penas.
Figura 2.11: Diagrama dos Animais
Orientação a Objetos
1
2
3
4
5
6
7
8
21
class Animal {
float peso;
String cor;
void comer(){}
void andar(){}
}
9
10
class Cachorro extends Animal {
int pelos;
11
12
13
}
14
15
16
class Passaro extends Animal {
int penas;
void latir(){}
17
18
19
void voar(){}
}
Código 2.2.3: Código de exemplo: Classes dos Animais
Para esse conjunto de classes, um objeto da classe Passaro deve ser armazenado como
mostrado na Figura 2.12.
memória heap
área dos métodos
referência para métodos de Passaro
peso
cor
penas
Passaro
comer() {...}
andar(){...}
voar(){...}
Figura 2.12: Objeto Passaro
Isso porque o estado de um Passaro é definido pelos campos definidos na superclasse
e na própria classe. Sendo assim, o Código 2.2.4 é válido:
1
2
3
4
5
6
class Principal2 {
public static void main(String[] args) {
Passaro p = new Passaro();
p.peso = 15;
p.penas = 100;
p.comer();
p.voar();
7
8
9
10
}
}
Código 2.2.4: Código de exemplo: Classe Principal2
Orientação a Objetos
2.2.6
22
Polimorfismo
O polimorfismo é uma propriedade de linguagens OO que permite um objeto em particular ser usado como se ele fosse de vários tipos. Esses tipos correspondem às suas
superclasses. Para exemplificar, considere que no exemplo dos animais, da Figura 2.2.3,
fosse criado uma nova classe de pássaros, que podem mergulhar, chamada Peregrino (Figura 2.13).
Figura 2.13: Diagrama dos Animais com Peregrino
Nesse caso, um objeto da classe Peregrino possui todos os campos e métodos de
Passaro e Animal, logo pode interagir com outros objetos como se ele fosse dessas classes.
A linguagem Java permite, com o polimorfismo, o que é mostrado no Código 2.2.5.
1
2
3
4
5
class Principal2 {
public static void main(String[] args) {
Passaro p = new Passaro();
p.peso = 15;
p.penas = 100;
6
7
8
9
10
p.comer();
p.voar();
}
}
Código 2.2.5: Código de exemplo: Classe Principal3
Orientação a Objetos
2.2.7
23
Sobrescrita de método
Algumas linguagens OO, como o Java, permitem que um método definido em uma
superclasse seja reimplementado pela subclasse. No exemplo dos animais, uma espécie de
pássaro pode andar dando saltos, enquanto outra pode andar dando passos de fato. São
formas diferentes de ter o mesmo comportamento de andar. Para tanto, o método original
e o sobrescrito devem ser definidos com o mesmo nome, tipo de retorno, quantidade e tipos
de parâmetros na mesma ordem. Por exemplo, considere as classes do Código 2.2.6.
1
2
3
4
5
6
7
8
9
10
11
class Passaro {
void andar(int distancia) {
System.out.println("Andei como um Passaro");
}
}
class Pombo extends Passaro {
void andar(int distancia) {
System.out.println("Andei como um Pombo " + distancia + " metros");
}
void andar(float distancia) {
System.out.println("Andei como um Pombo " + distancia + " metros com ponto flutuante!");
}
12
13
14
15
}
Código 2.2.6: Código de exemplo: Sobrescrita de métodos
Na linha 2, o método andar, com parâmetro int foi definido. Na linha 8, a classe Pombo
também define um método andar com parâmetro int. Como Pombo estende Passaro, caso
um objeto da classe Pombo seja criado e o método andar seja chamado com parâmetro int,
a implementação da subclasse é que será usada. Na linha 12, o método andar foi definido
com um parâmetro float, logo ele é identificado como outro método.
Por exemplo, a execução do Código 2.2.7, imprimiria as seguintes mensagens na tela:
Andei como um Pombo 14 metros
Andei como um Pombo 5.0 metros com ponto flutuante!
1
2
3
4
5
6
7
class Principal4 {
public static void main(String[] args) {
Pombo p = new Pombo();
p.andar(14);
p.andar(5.0f);
}
}
Código 2.2.7: Código de exemplo: Principal4
Orientação a Objetos
2.2.8
24
Vinculação Dinâmica de Métodos
Outra funcionalidade fundamental que toda linguagem OO deve implementar é a vinculação dinâmica de métodos. É um tipo de polimorfismo onde, ao realizar uma chamada
de método, a determinação de qual implementação do método será usada é feita em tempo
de execução [Sebesta2011].
Considere, por exemplo, que foram definidos vários tipos diferentes de pássaros, todos
estendendo a classe Passaro. Na superclasse existe o método cantar e cada pássaro
sobrescreve esse método. Pelo polimorfismo, é possı́vel instanciar um pássaro de cada tipo
e guardá-los todos em um mesmo vetor do tipo Passaro. Se esse vetor fosse percorrido,
executando o método cantar de cada objeto, não seria a implementação genérica desse
método em Passaro que seria chamada, mas sim cada uma das implementações especı́ficas
definidas nas várias subclasses. Ou seja, a vinculação de qual implementação de método
chamar não é feita em tempo de compilação, ao determinar o tipo da variável que conterá
o objeto, mas sim em tempo de execução.
O Código 2.2.8 exemplifica essa situação.
1
2
class Passaro {
void cantar() { System.out.println("Cantando como um Passaro! piu piu piu..."); }
3
4
5
public static void main(String[] args) {
Passaro[] listaPassaros = new Passaro[2];
6
7
Pombo pb = new Pombo();
Aguia ag = new Aguia();
8
9
10
listaPassaros[0] = pb;
listaPassaros[1] = ag;
11
12
13
for(int i = 0; i<2; i++)
listaPassaros[i].cantar();
14
15
16
17
18
19
20
21
22
23
24
}
}
class Pombo extends Passaro {
void cantar() { System.out.println("Cantando como um Pombo! grururuuuu gururuuu..."); }
}
class Aguia extends Passaro {
void cantar() { System.out.println("Cantando como uma Aguia! piaaaaaa piaaaaaaa..."); }
}
Código 2.2.8: Código de exemplo: Vários tipos de Passaro
Erlang
25
A execução do método main presente na classe Passaro resultaria na seguinte saı́da:
Cantando como um Pombo!
grururuuuu gururuuu...
Cantando como uma Aguia!
piaaaaaa piaaaaaaa...
Os dois objetos, o do tipo Pombo e o outro do tipo Aguia, foram armazenados no mesmo
vetor do tipo Passaro. Em seguida, o método cantar() foi chamado para cada um dos
objetos a partir do vetor preenchido. A implementação particular de cada objeto para esse
método foi então executada. Isso porque Passaro é superclasse de Pombo e Aguia, herdam
o método cantar(), mas reimplementam eles, cada uma com suas próprias necessidades.
2.3
Erlang
Em telecomunicações existem sistemas distribuı́dos, de alta performance, larga escala
que precisam executar de forma confiável e serem bastante escaláveis. Como descrito
em [Armstrong2007] e [Cesarini2009], Erlang procura atender a esse tipo de problema e foi
desenvolvido para que:
• Os programas rodem mais rápido em computadores de vários núcleos (multicore);
• Softwares distribuı́dos pudessem ser desenvolvidos com facilidade e eficiência ;
• Sistemas pudessem ser tolerantes a erros de software e falhas de hardware;
• O software pudesse ser atualizado sem a necessidade de parar a execução.
Erlang é uma linguagem funcional com tipagem dinâmica, os programas são divididos
em módulos e frequentemente executam em vários processos. Ela é essencialmente concorrente e, como dito por [Armstrong2007], criou o paradigma de programação orientado
a concorrência (POC). Dentre outras coisas, isso significa que ela proı́be o uso de variáveis globais e processos não compartilham estado nem variáveis. Além disso, processos
comunicam-se por troca de mensagens e a detecção e correção de falhas é bastante facilitada, como discutido em [Armstrong2003]. Estes processos (chamados “processos Erlang”)
são leves, ou seja, a criação e envio de mensagens é relativamente barata em comparação
com os processos ou threads usuais do sistema operacional.
Essas caracterı́sticas fazem essa linguagem ideal para o desenvolvimento de aplicações
distribuı́das que exijam alta escalabilidade e comunicação rápida. Nesta seção será descrito
Erlang
26
uma visão geral dos conceitos da linguagem Erlang necessários para a implementação desse
trabalho.
2.3.1
Funções
Um programa em Erlang é divido em módulos. Cada módulo tem seu próprio arquivo
com extensão .erl. Por exemplo, o código calcula.erl:
1
2
3
4
5
6
7
8
9
10
11
12
%% programa de demonstracao 1
-module(calcula).
-export([soma/2, dist_pontos/4]).
soma(X, Y) ->
X + Y.
dist_pontos(X1, Y1, X2, Y2) ->
A = math:pow(X1 - X2, 2),
B = math:pow(Y1 - Y2, 2),
C = soma(A, B),
math:sqrt(C).
Código 2.3.1: Código Exemplo de Erlang
1. A linha 1 é um comentário.
2. A linha 2 indica o nome do módulo, deve ter o mesmo nome do arquivo .erl.
3. A linha 3 lista as funções exportadas (visı́veis a outros módulos). É indicado também
o número de parâmetros.
4. A linha 5 é o cabeçalho da função soma/2.
5. A linha 6 é uma única expressão que representa o corpo de soma/2.
6. As linhas 8-12 contêm a definição de uma função para calcular a distância entre dois
pontos.
7. A linha 9 contém a chamada de uma função de outro módulo.
8. A linha 11 contém a chamada de uma função no mesmo módulo.
Erlang
27
Funções pré-definidas
Funções pré-definidas, ou built-in functions (BIF) são funções construı́das internamente
no Erlang. Geralmente realizam tarefas impossı́veis de se implementar em Erlang, como
conversões de tipos.
2.3.2
Compilação e Execução
Para compilar e executar programas Erlang utiliza-se o shell :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
usuario@linux:~/erlang$ erl
Erlang R15B01 (erts-5.9.1) [source] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.1 (abort with ^G)
1> c(calcula).
c(calcula).
{ok,calcula}
2> calcula:soma(1, 2).
3
3> A = 1.
1
4> B = 2.
2
5> C = calcula:soma(A, B).
3
6> C.
3
7>
Código 2.3.2: Exemplo de Compilação de Código em Erlang
1. A linha 1 inicia o shell do Erlang.
2. A linha 5 compila o módulo de exemplo.
3. Linhas 10 a 13 mostram como atribuir variáveis no shell.
4. A linha 14 chama a função soma e na próxima linha é exibido o seu retorno.
2.3.3
Variáveis
Variáveis em Erlang começam, por definição, com letras maiúsculas. O escopo delas é a
unidade léxica em que ela aparece. Além disso, elas são imutáveis, ou seja, após receberem
um valor ele não pode ser mudado. Para simular re-atribuições das linguagens imperativas
é preciso criar novas variáveis, como mostrado no Código 2.3.3.
Erlang
1
2
3
4
5
6
28
Em Java
int a =
int b =
a = a +
b = b +
b = b *
1;
1;
1;
1;
a;
Em Erlang
A = 1,
B = 1,
A2 = A + 1,
B2 = B + 1,
B3 = A2 * B2,
Código 2.3.3: Atribuindo novos valores a mesma variável
2.3.4
Tipos de Dados
Existem três tipos de dados em Erlang: tipos de dados primitivos, tipos de dados
compostos e tipos de dados de sintaxe amigável. Mais informações sobre estes e outros
aspectos do Erlang podem ser encontrados em [Armstrong2003].
Tipos de dados primitivos
Existem 8 tipos de dados primitivos em Erlang, mas aqui são descritos apenas os mais
relevantes para este trabalho:
• References ou refer^
encias são sı́mbolos globais únicos. Sempre que a BIF (função
pré-definida do Erlang) make_ref/0 é chamada, é gerada uma nova refer^
encia e
esse sı́mbolo é único para todo programa executado após a inicialização do ambiente
de execução do Erlang.
• Atoms ou átomos são uma sequência de caracteres (string) única, não numérica e
constante. O primeiro caractere deve ser uma letra minúscula e pode ser seguida
de caracteres alfanuméricos, sublinhados (_) ou at (@). Opcionalmente, se a string
for colocada entre aspas simples, ela poderá conter qualquer caractere. Exemplos de
átomos: segunda, terca, ’Janeiro’, ’ tipos’, etc.
Tipos de dados compostos
Esses tipos de dados são estruturas que contêm elementos. Os elementos podem conter
qualquer tipo válido m Erlang.
• Tuplas são recipientes para um número fixo de elementos. Os elementos são separados por vı́rgulas e colocados entre chaves. Para criar uma tupla com os elementos
E1, E2, ..., En, por exemplo, deve-se escrever: {E1, E2, ..., En}. Essa é uma
Java
29
expressão que pode ser atribuı́da a uma variável: Var = {E1, E2}. A ordem dos
elementos é fixa, ou seja, um elemento pode ser acessado pela sua posição. Esse
acesso é realizado a tempo constante, ou seja, uma tupla se comporta de forma semelhante aos vetores (arrays) nas linguagens imperativas. Porém, tuplas são imutáveis,
como todos os outros valores em Erlang. Exemplos concretos de tuplas: {1, 2, 3},
{atomo1, 2.0, 3}.
• Listas podem conter um número variável de elementos. Listas também são imutáveis,
o que pode parecer um paradoxo. Na realidade, quando se diz isso, significa que
existe uma operação eficiente para adicionar novos elementos à lista: ela recebe o
elemento e a lista e resulta em uma nova lista com o novo elemento no topo da lista
seguido dos elementos da lista original. Diferente das tuplas, o tempo de acesso a
um determinado elemento de uma lista é proporcional à posição dele. Cria-se uma
lista colocando os elementos separados por vı́rgulas em volta de colchetes. Exemplo:
Lista = [elemento, 1, 2].
Tipos de dados de sintaxe amigável
Em Erlang é possı́vel trabalhar com tuplas com uma sintaxe que se assemelha a algumas
estruturas das linguagens imperativas, que é feito com os registros, ou records. Além disso,
internamente strings em Erlang são apenas listas com números correspondentes ao código
ASCII dos caracteres exibidas de forma mais amigável.
2.4
Java
Java é uma linguagem de programação Orientada a Objetos (OO) que se baseou na
linguagem C++ procurando melhorá-la. A Programação OO (POO) foi simplificada e
vários recursos confusos do C++ foram removidos. Sendo assim, ela foi projetada para
ser fácil de aprender, distribuı́da, robusta, segura, independente de arquitetura, portável,
de alta performance, dinâmica e que seus códigos sejam reutilizáveis, como exposto por
[Gosling1995], o criador da linguagem.
Em Java, um programa pode ser dividido em pacotes, classes e interfaces. Os pacotes
são conjuntos de classes relacionadas. As classes encapsulam dados e funcionalidades como
uma única unidade. Interfaces são protótipos de classes e definem regras para implementar
Java
30
classes. Com esses recursos pode-se dar uma complexa hierarquia ao código, onde cada
parte tem uma tarefa bem definida. Assim, cada parte pode ser definida em paralelo por
diferentes pessoas e equipes e o código pode ser facilmente reusado [Deitel2010].
Nesta seção serão apresentados brevemente os principais conceitos da linguagem Java
dando especial atenção aos conceitos de POO.
2.4.1
Compilação e Execução
Um programa em Java é dividido em classes. Cada classes tem seu próprio arquivo com
extensão .java. A compilação de cada classe resulta em um arquivo com extensão .class
escrito em código intermediário (bytecodes). Esse arquivo pode, então, ser interpretado em
uma implementação da Máquina Virtual do Java (JVM).
Considere o Código 2.4.1. Em Java, para executar um programa é necessário que a
classe tenha um método main e algumas vezes uma classe é criada só para conter esse
método, como mostrado nas linhas 20 a 27. Para simplificação ele foi mostrado como um
código só, porém deve ser criado um arquivo para cada classe.
1
2
3
4
package cidade;
public class Pessoa {
private int idade;
public int getIdade() {
return idade;
}
public int setIdade(int idade) {
this.idade = idade;
}
public void envelhecer(int anos) {
idade = somar(idade, anos);
}
private void somar(int a, int b) {
return a + b;
}
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
}
package cidade;
public class PrincipalPessoa {
public static void main(String[] args) {
Pessoa p = new Pessoa();
p.setIdade(20);
System.out.println("Idade 1: " + p.getIdade());
p.envelhecer(1);
System.out.println("Idade 2: " + p.getIdade());
}
}
Código 2.4.1: Código de exemplo: Pessoa e PrincipalPessoa
Java
31
• A linha 1 indica o nome do pacote onde foi definido a classe Pessoa;
• A linha 2 indica o nome da classe;
• A linha 3 define o campo idade;
• As linhas 5 a 16 definem os métodos da classe;
• Na linha 21 é definido o método que torna possı́vel executar o programa;
• Na linha 22 instancia-se um objeto da classe Pessoa, em seguida é realizada uma
chamada de método e então é mostrado o novo valor do campo idade.
2.4.2
Modificadores de Visibilidade
Classes, campos e métodos podem conter modificadores de visibilidade em sua declaração. Quando aplicados em classes, podem limitar a visibilidade de dentro e fora do pacote
onde foram definidas. Da mesma forma, métodos e campos podem ser isolados de outras
classes e até mesmo de classes estendidas.
No Código 2.4.1, o campo idade e o método somar, por serem private, não podem ser
acessados por outras classes. Isso significa que se na linha 21, método main, tivesse sido
feito p.idade seria gerado um erro de compilação. Da mesma forma, se fosse chamado
p.somar(20, 1) no método main seria gerado um erro. Já com relação às classes, na definição de Pessoa, se fosse trocado o modificador public por private, uma classe definida
em outro pacote (diferente de cidade) não poderia manipular objetos dessa classe.
2.4.3
Construtores
Construtores permitem instanciar objetos com parâmetros, permitindo definir valores
iniciais para seus campos. São parecidos com métodos, com exceção que seus nomes correspondem ao nome da classe e não possuem tipo de retorno. Considere, por exemplo, o
construtor da classe Ponto, no Código 2.4.2.
Java
1
2
32
class Ponto {
public int x, y, contUso;
3
4
5
public static int origemX, origemY;
final public static Ponto origem = new Ponto(0, 0);
6
7
8
public Ponto(int x, int y) {
this.x = x;
this.y = y;
}
9
10
11
12
}
Código 2.4.2: Exemplo de Construtor na Classe Ponto
Caso seja feito new Ponto(1, 2) para instanciar um objeto da classe Ponto, primeiramente a estrutura para guardar os campos é criada (instanciação comum). Em seguida,
o corpo do construtor é executado (linhas 7 e 8). A execução desse corpo é realizada da
mesma forma que um método de objeto, ou seja, os campos da classe correspondente podem ser acessados. Sendo assim, no Código 2.4.3, caso não houvesse erro, a linha 4 exibiria
os valores “1” para x e “2” para y.
Quando não se define um construtor, é implicitamente declarado o chamado construtor
padrão (new Ponto()), que não recebe parâmetros e apenas instancia o objeto. Porém,
após o usuário definir seu construtor, o padrão deixa de existir, logo, no Código 2.4.3, a
linha 3 está correta, mas a linha 6 gerará um erro de compilação.
1
2
3
4
5
6
7
8
class PrincipalPonto {
public static void main(String[] args) {
Ponto p1 = new Ponto(1, 2);
System.out.println("x: " + p1.x + " y: " + p1.y);
Ponto p2 = new Ponto(); // erro
}
}
Código 2.4.3: Exemplo de Principal para Construtor na Classe Ponto
2.4.4
Campos static
Um campo pode ser definido também como static (estático). Nesse caso, existe exatamente uma instância desse campo, não importando quantas instâncias (mesmo que nenhuma) da classe que o contém existam. Nesse caso, são chamados de variáveis de
classe, já que são únicos para a classe. Quando ele não é estático, é chamado variável
Java
33
de inst^
ancia, pois sempre que uma nova instância da classe é criada (objeto), uma nova
variável é declarada para cada campo declarado na classe ou em qualquer das superclasses.
Ainda considerando a classe Ponto, o Código 2.4.4 ilustra o uso dos campos static:
1
2
3
4
5
6
class PrincipalPonto {
public static void main(String[] args) {
Ponto p1 = new Ponto(1, 2);
Ponto p2 = new Ponto(3, 4);
Ponto.origemX = 0;
System.out.printl("origem x: " + p1.origemX + "; ");
7
8
System.out.print(Ponto.origem.contUso +
System.out.print(p1.origem.contUso + ",
p1.origem.contUso = 1;
System.out.print(p2.origem.contUso + ",
System.out.print(Ponto.origem.contUso +
9
10
11
12
13
14
", ");
");
");
" ");
}
}
Código 2.4.4: Exemplo de Principal para Campos Static
A saı́da seria:
origem x: 0; 0, 0, 1, 1
Nas linhas 4 e 5, a mesma variável é acessada de duas formas diferentes, afinal só existe
uma única instância do campo origemX. Nas linhas 8 a 12 é exemplificado como, quando
o campo static for um tipo de referência, ele pode ser acessado de diversas formas. Os
objetos referenciados por p1.origem, Ponto.origem e p2.origem são os mesmos, como
evidenciados pelas saı́das.
2.4.5
Herança de Métodos
Em Java, uma classe CSub herda de sua superclasse CSuper todos os métodos que
satisfazem às seguintes regras: [Gosling2005]
• Os métodos não podem ser privados;
• CSuper, onde o método é declarado, deve ter os modificadores public ou protected;
• Se a regra acima não for satisfeita, CSuper deve estar no mesmo pacote que CSub e
ser declarada com o acesso padrão (sem nenhum modificador de visibilidade).
Java
34
2.4.6
Assinaturas e Subassinaturas de Métodos
A assinatura de um método é utilizada para identificar um método como único em
determinada classe. A assinatura é definida pelo nome e sequência de tipos de parâmetros.
No Código 2.4.5, o método mover() das linhas 4 e 5 têm a mesma assinatura, o que é
um erro. Já os das linhas 5, 6 e 7 são todos de assinaturas diferentes, pois os tipos de
parâmetro e ordem são diferentes, então não há erro, pois o Java permite a sobrecarga de
métodos e, nesse caso, é dito que a assinatura de um é subassinatura do outro.
1
2
3
4
class PontoAssinatura {
public int x, y;
void
void
void
void
5
6
7
8
mover(int dx, int dy) {
mover(int dx, int dy) {
mover(int dx, float dy)
mover(float dx, int dy)
x = x + dx *2; y = y + dy/2;}
x += dx; y += dy; } // erro, redeclarando mover()
{x += dx; y += dy;}
{x += dx; y += dy;}
}
Código 2.4.5: Exemplo de Classe para Assinatura de Métodos
2.4.7
Sobrescrita de Métodos
Em Java é possı́vel que uma subclasse “re-implemente” um método de uma de suas
superclasses. Pode-se então alterar a visibilidade, os parâmetros que recebe, o tipo de
retorno, seu corpo, enfim, seu comportamento como um todo. Mas isso só pode ocorrer se
determinadas regras forem satisfeitas.
Considere um método de instância mSub() declarado na classe CSub e outro mSuper()
declarado na classe CSuper. Diz-se que mSub() sobrescreve mSuper() se: [Gosling2005]
• CSub é uma subclasse de CSuper;
• a assinatura de mSub() é uma subassinatura de mSuper();
• os métodos se encontram em uma das situações:
– mSuper() é public ou protected, ou
– mSuper() é declarado com acesso padrão e CSuper() está no mesmo pacote de
CSub
• mSuper() não é static, pois métodos com esse modificador são imutáveis.
Java
35
Para ilustrar o funcionamento da sobrescrita, considere o Código 2.4.6. A classe PontoLento sobrescreve a declaração do método mover da classe Ponto com seu próprio método,
o que limita a distância que aquele ponto pode mover a cada chamada do método. Quando
o método mover é chamado a partir de um objeto da classe PontoLento, a definição sobrescrita (linha 8) será sempre chamada, mesmo que a referência ao PontoLento seja obtida a
partir de uma variável do tipo Ponto.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Ponto {
int x, y;
void mover(int dx, int dy) { x += dx; y += dy; }
}
classe PontoLento extends Ponto {
int xLimite, yLimite;
void mover(int dx, int dy) {
super.mover(limit(dx, xLimit), limit(dy, yLimit));
}
static int limitar(int d, int limit) {
return d > limit ? limit : d < -limit ? -limit : d;
}
}
Código 2.4.6: Exemplo de Sobrescrita
2.4.8
As variáveis this e super
Em todo método de objeto a variável this pode ser usada. Ela aponta para o próprio
objeto e é geralmente usada em construtores para referenciar explicitamente o campo do
objeto ao invés do nome de uma variável declarada no método com o mesmo nome do
campo. No código 2.4.2, Seção 2.4.3, ele é usado na classe Ponto.
Outra variável semelhante é a super, que também aponta para o próprio objeto, porém
com a visão da superclasse. Isso significa que apenas as variáveis e métodos presentes
na superclasse estarão visı́veis. Além disso, caso um método tenha sido sobrescrito e seja
feita a chamada super.metodoSobrescrito(), a implementação do método original, da
superclasse, é que será usada.
2.4.9
Ocultamento de Métodos
Considere o exemplo anterior. Caso o método mover da classe PontoLento fosse declarado como static, então o método original, da classe Ponto, seria ocultado. Isso geraria
um erro de compilação.
Java
36
Alguns Requisitos para Sobrescrita e Ocultamento
O modificador de acesso de um método sobrescrito ou oculto deve fornecer ao menos o
acesso que o método sobrescrito ou ocultado tem, ou seja, considerando um método mSub
(da subclasse) que sobrescreve mSuper (da superclasse), então:
• Se mSuper é public, mSub deve também ser public;
• Se mSuper é protected, mSub deve ser public ou protected;
• Se mSuper possui o acesso padrão (sem modificadores), então mSub não pode ser
private.
Note que não foi considerado o caso em que mSuper é private. Isso porque nessa situação não haveria sobrescrita. Um método em uma subclasse pode ter a mesma assinatura
que um método private em sua superclasse e nesse caso não há nenhuma restrição, pois o
método da superclasse não teria nenhuma relação com o de mesma assinatura na subclasse.
2.4.10
Sobrecarga de Métodos
Se dois métodos da mesma classes têm o mesmo nome, porém suas assinaturas não são
equivalentes de forma a se sobrescreverem, então o nome do método é dito como sendo
“sobrecarregado”. Isso pode ocorrer caso os métodos sejam declarados na mesma classe, ou
ambos sejam herdados ou até se um for declarado e o outro herdado. [Gosling2005]
Métodos são sobrescritos baseados em suas assinaturas. Isso significa que se uma classe
declara dois métodos public com o mesmo nome e uma subclasse desta sobrescreve um
desses métodos, a subclasse continua a herdar o outro método.
2.4.11
Exemplo: Sobrecarga, Sobrescrita e Ocultamento
No exemplo do Código 2.4.7, a classe PontoReal oculta as declarações das variáveis de
instância do tipo int x e y (linha 2) e sobrescreve o método mover() (linha 6) da classe
Ponto com seu próprio método mover() (linha 12). Ele também sobrecarrega o método
mover() com outro método com uma assinatura diferente (parâmetros float, linha 13).
Além disso, o campo cor é herdado para a classe PontoReal, porém o campo contUso não,
pois é privado.
Jaraki
1
2
3
4
5
6
7
8
9
10
class Ponto {
int x = 0, y = 0;
int cor;
private int contUso;
void mover(int dx, int dy) { x += dx; y += dy; }
}
class PontoReal extends Ponto {
float x = 0.0f, y = 0.0f;
11
12
13
14
37
void mover(int dx, int dy)
{ mover((float) dx, (float) dy); }
void mover(float dx, float dy) { x += dx; y += dy; }
}
Código 2.4.7: Exemplo de Sobrecarga, Sobrescrita e ocultamento
2.5
Jaraki
O Jaraki é o projeto de um compilador que recebe como entrada um código em Java e
gera outro com o mesmo comportamento em Erlang. O código gerado é então processado
pelo compilador padrão do Erlang e ligado às bibliotecas do Jaraki para que possam ser
executados na máquina virtual do Erlang (Figura 2.14).
Java
Jaraki
Erlang
bibliotecas
erros
random
vector
...
Figura 2.14: Compilador Jaraki
2.5.1
Análise Léxica e Sintática
A análise léxica é realizada pelo analisador gerado pelo LEEX. A análise sintática é
realizada pelo analisador gerado pelo YECC. Os arquivos .xrl e .yrl especificam como
gerar os analisadores que, uma vez processados, criam os módulos Erlang lexer (análise
léxica) e parser (análise sintática) (Figura 2.15).
Jaraki
38
lexer
.xrl
lexer
.erl
leex
parser
.yrl
parser
.erl
yecc
Figura 2.15: Geração dos analisadores léxico e sintático no Jaraki
2.5.2
Arquitetura Geral
fonte
.java
st
4
erros oo e pckg
1
AST + info
ast
jaraki (IU)
5
string
tokens tokens
AST
lexer
parser
10110
2
core
3
Figura 2.16: Compilador Jaraki: Arquitetura Geral - Parte 1
A análise semântica e geração de código foram inteiramente implementados sem geradores de analisador, com os módulos apresentados na Figura 2.16 e, mais adiante, na
Figura 2.17. A seguir, são descritos os passos que o compilador executa:
• Primeiramente, em 1, o código fonte em Java é lido do arquivo pelo módulo de
“interface com o usuário” jaraki e convertido em uma cadeia de caracteres (string);
• Em seguida, em 2, a string é passada para o lexer, que realiza a análise léxica e
retorna as palavras (tokens) correspondentes ao fonte;
• Em 3, o módulo ast repassa os tokens para o parser, que analisa a estrutura do
código e retorna a árvore sintática (AST);
• Após isso, em 4, o mesmo módulo ast percorre a AST em busca de erros relativos
ao nome do arquivo, classe, atributos e métodos, como no tratamento de pacotes ou
orientação a objetos;
Jaraki
39
• Além disso, o módulo ast extrai as informações da classe e de seus respectivos atributos e métodos. Em 5, essas informações, bem como a AST são repassadas para o
módulo core.
8
core
6
jaraki (IU)
java
expr
info
objeto
.erl
.beam
erlang
expr
gen_erl_code
st
variáveis
erros
7
Figura 2.17: Compilador Jaraki: Arquitetura Geral - Parte 2
• Em 6, o módulo core gerencia a tradução de expressões Erlang contidas nos métodos,
bem como a montagem das funções Erlang a partir dos métodos. Para tanto, antes
de iniciar as traduções, as informações das classes, extraı́das da AST, são inseridas
na Tabela de Sı́mbolos (módulo st).
• Em 7, o módulo gen_erl_code é responsável por identificar, dada expressão em
Java, como ela se comporta em Erlang. Nesse momento a AST do código Java lido
é convertida em uma AST para o código Erlang a ser gerado.
• Finalmente, em 8, o core monta o módulo Erlang gerado, e o jaraki gera o código
Erlang, que posteriormente é compilado para o arquivo .beam, executável na máquina
virtual do Erlang.
Capı́tulo 3
Desenvolvimento
Este capı́tulo trata dos módulos e bibliotecas desenvolvidos para suportar OO no compilador Jaraki e uma breve introdução do funcionamento deste.
3.1
O Compilador Jaraki
O compilador Jaraki recebe como entrada um ou mais arquivos .java e gera um programa de mesmo comportamento em Erlang. Para tanto, ele gera um analisador léxico e
sintático e aplica-os no código de entrada para gerar a árvore sintática no formato do Java
(jAST ). Em seguida é preciso percorrer todas as jAST geradas para reunir informações
de todas as classes. Então, ele compila cada expressão de cada método, percorrendo as
subárvores que formam o seus corpos. O resultado dessa etapa é a criação de uma árvore
sintática no formato do Erlang, a eAST. Ela é usada para criar um módulo Erlang para
cada arquivo Java da entrada. Diversos módulos estão envolvidos nesse processo e nesta
seção é descrito como está estruturado o compilador Jaraki, bem como o funcionamento
dos seus principais módulos.
3.1.1
Estrutura do Compilador
Os principais módulos do compilador são:
• lexer.xrl - descrição do analisador léxico a ser gerado pelo leex ;
• parser.yrl - descrição do analisador sintático a ser gerado pelo yecc;
• jaraki.erl - fornece a interface com o usuário final, chama o analisador léxico e
sintático e repassa a árvore sintática para o módulo core.erl;
O Compilador Jaraki
41
• ast.erl - monta a árvore sintática com o analisador sintático gerado, monta a estrutura das informações das classes;
• st.erl - módulo para criar, destruir e gerenciar a tabela de sı́mbolos, usada na
compilação, e o dicionário de variáveis, usado na execução;
• gen_erl_code.erl - módulo que contém funções para percorrer, recursivamente, uma
subárvore com expressões provenientes do analisador sintático e gerar a subárvore no
formato do Erlang (eAST);
• core.erl - insere na tabela de sı́mbolos as informações das classes, provenientes do
módulo ast.erl e usando funções do módulo st.erl, compila cada método chamando o gen_erl_code.erl para traduzir as expressões e une os resultados para
formar o módulo Erlang.
3.1.2
Tabela de Sı́mbolos
A Tabela de Sı́mbolos (do inglês, Symbol Table - ST) do Jaraki guarda informações
do escopo, classes e variáveis. O escopo define qual classe e método está sendo analisado
em dado instante da compilação. Para isso, ao compilar um método, antes de chamar o
gen_erl_code, o core atualiza a entrada do escopo com o método que será analisado. As
informações das classes são descritas na Seção 3.2. Para as variáveis são guardados seus
tipos e a chave é formada pelo escopo e o nome da variável.
As principais funções do módulo st.erl são:
• put_scope(Escopo) e get_scope() - insere e busca valor do escopo. O escopo tem
o formato {NomeClasse, {NomeMetodo, ListaTiposParametros}};
• get_value(Linha, Escopo, NomeVariavel) - busca o valor da variável especificada;
• put_value({Escopo, NomeVariavel}, Valor - insere ou atualiza valor da variável
especificada.
Tabela de Símbolos para Classes
3.2
42
Tabela de Sı́mbolos para Classes
Em determinadas fases da análise semântica de uma classe é necessário utilizar as
informações de outras classes. Por exemplo, quando um método de um objeto de uma
classe C1 utiliza um método de um objeto de outra classe C2, é preciso checar se esse
método de fato existe e, caso não exista, gerar um erro de compilação. Sendo assim,
antes da análise semântica de uma classe é realizada a análise sintática de todas as outras
envolvidas.
As informações das classes são extraı́das das árvores sintáticas e então postas na ST.
Para cada classe é criada uma entrada na ST. A chave dessa entrada é o próprio nome da
classe e o valor é uma tupla com o nome da superclasse, se houver, lista com dados dos
campos, lista com dados dos métodos e lista com dados dos construtores. A estrutura de
cada elemento dessas listas é mostrada na Tabela 3.1.
Lista
Estrutura dos elementos
ChaveClasse
{ oo_classes, NomeDaClasse }
ValorClasse
{ NomeSuperClasse, Campos, Metodos, Construtores }
Campos
{Nome, ValorCampo}
ValorCampo
{Tipo, Modificadores}
Metodos
{ChaveMetodo, ValorMetodo}
ChaveMetodo {Nome, Parametros}
ValorMetodo
{TipoDeRetorno, Modificadores}
Construtores
{Parametros, Visibilidade}
Tabela 3.1: Estrutura da Tabela de Sı́mbolos para dados das classes
3.3
Compilando Classes
A declaração de classes em Java é feita de acordo com a seguinte sintaxe:
<class declaration> ::= “public” “class” “identifier” “{” <class body> “}”
<class declaration> ::= “public” “class” “identifier” “extends” “identifier”
“{” <class body> “}”
O nome da classe é obtido com o primeiro token identifier e é usado para definir
o nome do módulo em Erlang. As informações de que a classe é public e o nome da
Compilando Classes
43
sua superclasse (segundo identifier), quando existir, são usadas para consulta futura na
análise semântica.
3.3.1
Corpo da Classe
O corpo da classe é definido pela regra class_body e é composto de declaração de
campos, métodos ou construtores. A sintaxe é como segue:
<class body> ::= <method declaration> [<class body>]
<class body> ::= <constructor declaration> [<class body>]
<class body> ::= <f ield declaration> [<class body>]
3.3.2
Objetos
As variáveis de referência, no compilador Jaraki, contêm instâncias do tipo reference
do Erlang. A reference é usada para formar chaves únicas correspondentes a entradas
no dicionário de variáveis. Para cada objeto existe uma chave para buscar pela classe do
objeto e uma chave para cada campo. Cada chave pode ser entendida, analogamente, como
um endereço de memória e o conteúdo desse endereço como estruturas de dados (tuplas do
Erlang) com diversas informações. A Figura 3.1 ilustra essa estrutura para um objeto do
tipo Animal, que possui o atributo peso e método printPeso().
referência a
um objeto
ref#123
Módulos Erlang
Dicionário
chave
animal.beam
valor
{objt, ref#123}
``Animal´´
{objt_var, ref#123, "peso"}
{integer, 0}
mov, goto,
...
Figura 3.1: Guardando um Objeto do Tipo Animal
Os átomos objt e objt_var são utilizados para indicar, dentre as outras chaves, que
essas são, respectivamente, chaves para buscar a classe de um objeto e chaves para buscar
os campos de um objeto. Os arquivos .beam são os módulos Erlang compilados e contêm
a implementação das funções, logo, métodos das classes. Para acessá-lo basta ter o nome
do módulo. Então, para realizar a vinculação dinâmica de métodos basta descobrir o nome
da classe.
Compilando Classes
44
Manipular os Campos
Para manipular os campo é preciso da referência do objeto ao qual ela pertence e
do nome do campo. Mais detalhes se encontram na Seção 3.3.4. Para cada campo, é
guardado o seu tipo e valor. As funções da biblioteca de OO desenvolvida que realizam
essas operações são:
• get_attribute (ObjectID, VarName): Recebe a referência ao objeto (ObjectID) e
o nome da variável (VarName), busca o campo no dicionário e retorna seu valor;
• update_attribute (ObjectID, {VarName, VarType, NewVarValue}): Recebe a
referência ao objeto, o nome da variável, seu tipo e seu novo valor. Com essas
informações, o campo é atualizado no dicionário.
Instanciar um Objeto
Para instanciar um objeto utiliza-se o operador new e uma chamada a um construtor
da classe, como descrito na sintaxe a seguir:
<new stmt> ::= “new” “identifier” “(” [<argument list>] “)”
Ao encontrar uma expressão desse tipo, o compilador busca todas as variáveis que esse
objeto deve ter e gera uma chamada à função new(ClassName, VarsList), da biblioteca
OO desenvolvida. Ela recebe o nome da classe do objeto e a lista de variáveis com seus
respectivos nomes, tipos e valores iniciais. Para isso, o compilador busca informações da
classe do objeto e de todas as suas superclasses (herança de campos, Seção 3.4.1). O
Código 3.3.1 demonstra a instanciação de um objeto e, logo em seguida, a Figura 3.2
demonstra os passos que o código gerado em Erlang executa.
2
3
public class Animal {
int peso;
}
1
Animal a = new Animal();
1
Código 3.3.1: Exemplo de Instanciação de um Objeto
Compilando Classes
45
3
2
1
declara variável "a"
chama construtor
tipo: "Animal"
gera reference
1.1
{a, Escopo}
2.1
{animal, null}
insere peso no
dicionário
atualiza "a" com
a reference
3.1
2.2
ref#123
{ref#123, peso}
animal
{integer, 0}
{a, Escopo}
{animal, ref#123}
dicionário de variáveis
Figura 3.2: Execução da Instanciação de um Objeto
• Em 1, a variável a é declarada, sendo seu valor inicial nulo (null, em 1.1);
• Em 2, o construtor é chamado (que por sua vez chama a função new) para criar a
referência única ao novo objeto (em 2.1) e criar a entrada do dicionário para seu
campo (peso, em 2.2);
• Por fim, em 3 a atribuição proveniente da declaração de a é concluı́da. O construtor
retorna a referência do objeto e ela é atribuı́da a a.
É importante notar que, devido ao polimorfismo, o tipo da variável pode ser diferente do tipo do objeto, que é definido na instanciação pelo primeiro parâmetro da função
oo_lib:new(). Sendo assim, em 2.1 a classe poderia ser Animal, como está, ou uma das
subclasses de Animal.
Polimorfismo
Em uma atribuição onde a variável que está recebendo um novo valor for um “tipo de
referência”, o compilador se comporta um pouco diferente dos outros tipos. Ele deve checar
se o tipo da variável é igual ao tipo do objeto ou de uma de suas superclasses. Para isso,
durante a análise semântica da expressão ele consulta a ST buscando pelas informações
das classes envolvidas (superclasses do objeto).
Compilando Classes
3.3.3
46
Métodos
No que se refere a métodos, neste trabalho é descrito a compilação da chamada de
métodos estáticos (static) entre duas classes diferentes e chamada de métodos de objeto.
A chamada de métodos de objeto pode ser realizada usando uma variável de referência ou
a super (discutida na Seção 3.4.2, página 57). A sintaxe nesses casos é:
<method invocation> ::= “identifier” “.” “identifier” “(”
[<argument list>] “)”
<method invocation> ::= “super” “.” “identifier” “(” [<argument list>] “)”
O primeiro identifier pode tanto ser o nome de uma classe quanto uma variável
que faz referência a um objeto. Essa variável pode ter o escopo do método que realizou a
chamada ou ser um atributo da classe. A associação do identifier com algum desses casos
é feita durante a análise semântica. O valor desse identifier (um nome) é comparado
com as entradas da ST e o compilador executa os seguintes passos:
1. Se existir uma variável no escopo do método com o mesmo nome do valor desse
identifier, então ele é associado a essa variável;
2. Se a checagem anterior falhar, procura-se um atributo da classe com esse nome;
3. Se a checagem anterior falhar, procura-se uma classe com esse nome;
4. Se não for encontrada uma classe com esse nome, gera-se um erro.
Métodos de Classe
Caso o primeiro identificador seja um nome de classe válido, este método foi chamado
como sendo um “método de classe”, ou seja, static. Busca-se então na ST, dentre as
informações das classes, se existe um método com o nome correspondente ao segundo
identificador. Se for encontrado, a chamada é finalmente realizada como mostrado no
Código 3.3.2. Como o Erlang já suporta programação modular, a tradução é direta: o
nome da classe se torna o nome do módulo e o do método o da função Erlang.
Compilando Classes
1
2
3
Java:
4
5
Erlang:
47
Animal.propriedades();
animal:propriedades(),
Código 3.3.2: Tradução de Chamada de Método Estático
Métodos de Objeto
Caso o primeiro identificador seja uma variável, busca-se na ST seu tipo e valor para
checar se de fato é um objeto ou não, gerando erro caso não seja. Sendo um objeto, o
tipo corresponde a uma classe. Verifica-se então na ST se existe o método sendo chamado
naquela classe. Caso não exista, é gerado um erro. Caso exista, checa-se se a visibilidade
é suficiente para ser chamado. Se for, finalmente o método é chamado.
A chamada de método, porém, é diferente de um método de classe, pois acrescentase um parâmetro extra na chamada: a referência ao objeto. Essa referência é sempre
recebida no primeiro parâmetro da função gerada. Com a referência, durante a execução
do método de objeto, é possı́vel manipular os campos do objeto sem precisar consultá-la
no dicionário. O Código 3.3.3 contém um exemplo de chamada de método de objeto e a
Figura 3.3 demonstra os passos que o código gerado executa.
1
2
3
4
5
1
2
3
4
5
6
public class Passaro{
public void voar() {
System.out.println("voando...");
}
}
public class Principal {
public static void main(String[] args) {
Passaro p = new Passaro();
p.voar();
}
}
2
Código 3.3.3: Tradução de Métodos de Objeto
Compilando Classes
48
3
2
1
cria objeto "p"
busca classe de "p"
busca reference de "p"
1.2
1.1
{p, Escopo}
2.1
{ref#312, peso}
{p, Escopo}
{passaro, ref#123}
2.2
passaro
3.1
3.2
{p, Escopo}
ref#123
{integer, 0}
dicionário de variáveis
4
realiza chamada de
função dinâmica
apply(
passaro,
voar,
[ref#123])
Figura 3.3: Execução de Chamada de Método de Objeto
• Primeiramente, cria-se o objeto p, em 1;
• Então, em 2 busca-se a classe deste objeto. A chave (2.1) e o nome da classe obtida
(2.2) foram definidos em 1.1, na instanciação;
• Em seguida, em 3, semelhante ao passo 2, a referência ao objeto é buscada;
• Finalmente, em 4, com as informações do nome da função (voar), nome do módulo
Erlang (passaro) e referência ao objeto (ref#123), é realizada uma chamada de
função dinâmica.
É importante ressaltar que a função apply(Modulo, Funcao, Parametros) é usada
(chamada de função dinâmica), porque o módulo que contém a função a ser chamada só
é descoberto em tempo de execução. Isso é feito para fornecer o suporte à vinculação
dinâmica de métodos (Seção 2.2.8). Além disso, o nome do módulo Erlang é conhecido
porque ele corresponde ao nome da classe, obtida no passo 2.
Compilando Classes
3.3.4
49
Campos
Os campos são declarados da mesma forma que as variáveis comuns no corpo de um
método, porém é possı́vel acrescentar modificadores aos campos:
<f ield declaration> ::= { <modif iers> } <local variable declaration statement>
<modif iers> ::= ( static | <visibility modif iers> )
<visibility modif iers> ::= (“public” | “protected” | “private”)
A declaração de campos não gera nenhum código diretamente. Durante a análise semântica, ao ler essas informações da árvore, guarda-se elas na ST juntamente com as outras
informações das classe. Elas indicarão quais variáveis devem ser criadas na ST para cada
objeto da classe no momento da instanciação. Os modificadores são usados apenas para
consulta na análise semântica e geração de erros quando necessário.
Acesso aos Campos de Objeto
O acesso aos campos de objeto pode ser de três formas: a partir de um método do
próprio objeto com o operador this, sem o operador e a partir de um método externo
ao objeto. A diferença entre eles está na forma como, durante a execução, a referência
ao objeto é obtida para manipular seus campos. A sintaxe para uma atribuição que pode
alterar um campo é como segue:
<element value pair> ::= “identifier” “.” “identifier” “=”
<element value> “;”
<element value pair> ::= “this” “.” “identifier” “=” <element value> “;”
<element value pair> ::= “identifier” “=” <element value> “;”
Compilando Classes
1
2
public class Animal {
int peso;
3
4
5
6
7
8
1
2
3
4
5
6
50
public void printPeso() {
System.out.println("Peso1: " + peso);
System.out.println("Peso2: " + this.peso);
}
}
public class Principal {
public static void main(String[] args) {
Animal a = new Animal();
System.out.println("Peso de fora: " + a.peso);
}
}
Código 3.3.4: Acesso a Campos de Objeto
As classes no Código 3.3.4 ilustram os três casos. Na linha 5 da classe Animal, para determinar a qual variável o nome peso deve ser associado o compilador executa os seguintes
passos:
1. Procura uma variável declarada no método printPeso() com nome peso
2. Se houver, a variável do método é associada a ela
3. Se não houver, verifica se, na classe Animal, existe um campo com esse nome
4. Se não houver gera um erro e, caso contrário, verifica se a visibilidade é suficiente
5. Se for visı́vel, verifica então se o campo é estático
6. Se for estático, gera erro, se não for, o nome é associado ao campo peso
Caso o nome seja associado a um campo do objeto e o método de objeto seja chamado,
o código gerado executará os passos como descritos na Figura 3.4. Quando é utilizado
o operador this, o procedimento de execução é o mesmo, porém, ao gerar o código, o
compilador realiza as checagens a partir do item 3 (verificar se existe o campo na classe).
Compilando Classes
51
2
1
cria objeto "a"
ref#234
3
realiza chamada
de método
4
busca valor do
campo peso
imprime valor
printPeso(ref#234)
3.1
{ref#234, peso}
3.2
Valor
dicionário de variáveis
Figura 3.4: Acesso a Campos em Método de Objeto
• Em 1 é realizada a instanciação;
• Em 2 é chamado o método de objeto;
• Em 3, já dentro do método, o nome “peso” foi associado ao campo e a referência
recebida por parâmetro (em 2, ref#234) é usada para buscar o valor do campo (3.2);
• Em 4 é impresso o valor do campo.
Outra forma de realizar o acesso a campos é a partir de um método externo, como na
linha 4 da classe Principal, Código 3.3.4. Nesse caso, na análise semântica, o compilador
deve checar a variável que referencia o objeto e gerar código para obter sua referência:
1. Verificar se a variável é um objeto. Se não for, gerar erro;
2. Se for, checa se, na classe Animal, existe um campo com esse nome
3. Se não houver gera um erro e, caso contrário, verifica se a visibilidade é suficiente
4. Se for visı́vel, verifica então se o campo é estático
5. Se for estático, gera erro, se não for, cria o acesso ao campo.
Nesse caso, o código traduzido deverá buscar a referência ao objeto no dicionário de
variáveis antes de recuperar o valor do campo, como mostrado na Figura 3.5.
Compilando Classes
52
3
2
1
cria objeto "a"
4
busca valor do
campo peso
busca valor de "a"
imprime valor
ref#152
2.1
{a, Escopo}
2.2
ref#152
3.1
{ref#152, peso}
3.2
Valor
dicionário de variáveis
Figura 3.5: Acesso a Campos de Método de Objeto por outra Classe
• Em 1 cria-se o objeto, que é armazenado na variável a;
• Em 2, a partir de a, busca-se a referência a esse objeto (ref#152, em 2.2);
• Em 3, com a referência, busca-se o valor do campo;
• Em 4, o valor é impresso.
3.3.5
Construtores
Para cada construtor, o compilador gera uma função no módulo resultante com o nome
especial de ’__constructor__’. A função, primeiramente, cria uma instância da classe
a que pertence e guarda a referência ao objeto criado na variável de nome ObjectID. A
partir daı́, as expressões definidas no corpo do construtor são compiladas como um método
de objeto comum. Ao final do corpo da função também é acrescido o nome da variável
ObjectID, pois assim a função sempre retornará a referência ao objeto criado.
A declaração de um construtor é feita seguindo a seguinte sintaxe:
<constructor declaration> ::= “public” “identifier”
“(” [<parameters list>] “)” <block>
O Código 3.3.5 ilustra um exemplo de construtor definido pelo usuário em Java. É
importante notar o uso da variável this. Ela indica que a variável peso que receberá o
valor é um campo do objeto. Isso é necessário porque quando se tem uma variável em um
método com o mesmo nome de um campo o nome é associado à variável. Na Figura 3.6 é
detalhada a execução do código gerado.
Compilando Classes
53
public class Animal {
int peso;
1
2
3
4
5
public Animal(int peso) {
this.peso = peso;
}
6
7
}
Código 3.3.5: Construtor Definido pelo Usuário
3
2
1
recebe parâmetros
peso:integer
declara objeto e campos
2.1
1.1
ref#312
{peso, Escopo}
animal
{integer, Valor}
busca valor da
variável peso
2.2
3.2
{ref#312, peso} 3.1
{peso, Escopo} Valor
{integer, 0}
dicionário de variáveis
4.1
4
{integer, Valor}
{ref#312, peso}
atualiza campo peso
5
retorna reference
Figura 3.6: Execução de um Construtor Definido pelo Usuário
• Em 1.1 o parâmetro recebido (peso) é declarado como variável;
• Em 2 cria-se a referência ao novo objeto (ref#312, em 2.1) e declara-se o campo do
objeto, no caso apenas peso (2.2);
• Em seguida a expressão da atribuição deve ser resolvida e, para isso, em 3 o valor da
variável peso é buscado;
• Em 4, o valor obtido em 3.1 é atribuı́do ao campo do objeto recém-criado;
• Por fim, em 5 o construtor retorna a referência ao objeto criado.
Herança
54
Construtor Padrão
Se, e somente se, nenhum construtor for definido pelo usuário, é gerado o “construtor
padrão”, que não recebe nenhum parâmetro e apenas cria o objeto, inicializando-o como
descrito na classe a que ele pertence. O Código 3.3.6 ilustra esse comportamento.
1
2
3
4
-module(animal).
’__constructor__’() ->
oo_lib:new(animal, [], [{peso, int, 0}]).
Código 3.3.6: Construtor Padrão
3.4
Herança
Para suportar a herança, as informações de métodos e campos (membros) da classe,
presentes na ST, são acrescidas dos membros visı́veis nas superclasses. Sendo assim, primeiramente ele insere na ST as informações definidas na própria classe. Em seguida, ele
percorre as informações recém inseridas para mesclar com a superclasse de cada classe.
Desse modo, ficará transparente para o resto do processo de compilação quando for feita
uma chamada de método ou acesso a campo, seja ele da própria classe ou de uma superclasse. Para as explicações dadas nessa seção, considere o diagrama da Figura 3.7.
Figura 3.7: Diagrama dos Animais
Herança
55
Inicialmente, apenas as informações das classes Animal e Passaro são inseridas na ST
(Tabela 3.2).
Chave
Valor
{oo_classes, animal}
{null, CamposAnimal, MetodosAnimal}
CamposAnimal
[ {peso, {integer, public}},
{cor, {integer, public}}} ]
MetodosAnimal
[ {{comer, []}, {void, [public]}},
{{andar, []}, {void, [private]}} ]
{oo_classes, passaro} {animal, CamposPassaro, MetodosPassaro}
CamposPassaro
[ {penas, {integer, public}} ]
MetodosPassaro
[ {{voa, []}, {void, [public]}} ]
Tabela 3.2: Dados dos Campos das Classes
Em seguida, o compilador percorre essas informações inseridas e verifica que a classe
Passaro tem a superclasse Animal. Sabendo disso, ele busca os dados dos campos e
métodos de Animal e adiciona aos dados da classe Passaro todos aqueles que não são
private, ficando como mostrado na Tabela 3.3.
Chave
Valor
{oo_classes, passaro} {animal, CamposPassaro, MetodosPassaro}
CamposPassaro
[ {penas, {integer, public}},
{peso, {integer, public}},
{cor, {integer, public}} ]
MetodosPassaro
[ {{voa, []}, {void, [public]}},
{{comer, []}, {void, [public]}} ]
Tabela 3.3: Dados dos Campos das Classes
3.4.1
Herança de Campos
A principal consequência para a geração de código devido à herança de campos está na
geração dos construtores. Os construtores de Passaro devem inserir os campos peso, cor
e penas no dicionário de variáveis. Como as informações das classes são mescladas com
as da sua superclasse antes da geração dos construtores, a instanciação segue os mesmos
passos como mostrado na Seção 3.3.2. O Código 3.4.1 apresenta uma implementação da
classe Passaro e uma instanciação de um objeto dessa classe.
Herança
56
3
4
5
public class Passaro extends Animal {
int penas;
public void voar(){}
public void comer(){}
}
1
Passaro p = new Passaro();
1
2
Código 3.4.1: Instanciando com Campos da Superclasse
Mais adiante, a Figura 3.8 demonstra como é feita a execução da instanciação em
Erlang. É semelhante à instanciação da Figura 3.2 (página 46), porém, em 2.2, além
do campo definido na própria classe, é inserido também os que foram definidos na sua
superclasse.
3
2
1
declara variável "p"
cria objeto com construtor
tipo: "Passaro"
insere peso, pena e cor
2.2
2.1
1.1
atualiza "p" com
a reference
{p, Escopo}
ref#312
{passaro, null}
passaro
{ref#312, peso} => {integer, 0}
{ref#312, pena} => {integer, 0}
{ref#312, cor} => {integer, 0}
3.1
{p, Escopo}
{passaro, ref#312}
dicionário de variáveis
Figura 3.8: Instanciando com Campos da Superclasse
3.4.2
Herança de Métodos
Considere a classe Peregrino. Devido à herança, o compilador acrescenta na entrada
da ST dessa classe as informações do método comer(), de Animal e voar(), de Passaro.
Porém, a implementação destes métodos estão nas suas classes de origem e não na classe
Peregrino. Logo, uma chamada ao método peregrino:comer() no código gerado em
Erlang geraria um erro, pois ele não existe no módulo peregrino, mas sim em animal.
Para resolver isso, o compilador insere no corpo do módulo peregrino uma nova função,
que pode ser entendida como “proxy”, pois ela apenas recebe os parâmetros e repassa-os
para a função na superclasse correspondente. Para exemplificar, o Código 3.4.2 demonstra
essa situação. É importante notar que a função proxy deve chamar a função correspondente
na superclasse imediatamente acima. Ou seja, a função peregrino:comer() chama a
Herança
57
função passaro:comer(), que, por sua vez, pode ou não (no caso de sobrescrita) chamar
animal:comer().
Sobrescrita de Métodos
Para suportar a sobrescrita, o compilador precisa detectar, durante a mesclagem de
métodos, que determinado método foi sobrescrito. Para tanto, antes de mesclar cada
método, é checado se ele já existe na ST original (antes da mesclagem), nas informações da
subclasse. Caso exista, a função proxy não é gerada e compila-se o método normalmente.
O método comer(), de Animal é sobrescrito em Passaro e o resultado da compilação é
apresentado no Código 3.4.2.
1
2
3
4
1
2
3
4
5
6
7
1
2
3
1
2
3
4
5
1
2
3
4
5
6
Java, Animal - método comer()
public void comer() {
System.out.println("comendo...");
}
Java, Passaro - métodos comer() e voar()
public void comer() {
System.out.println("comendo...");
}
public void voar() {
System.out.println("comendo...");
}
Erlang, animal - funç~
ao comer()
comer(ObjectID) ->
io:format("~s~n", ["comendo..."]).
Erlang, passaro - funç~
oes comer() e voar()
comer(ObjectID) ->
’%% sobrescrito
io:format("~s~n", ["comendo como um passaro..."]).
voar(ObjectID) ->
io:format("~s~n", ["voando..."]).
Erlang, peregrino - funç~
oes comer(), voar() e mergulhar()
comer(ObjectID) -> passaro:comer(ObjectID).
%% herdado
voar(ObjectID) -> passaro:voar(ObjectID).
%% herdado
mergulhar(ObjectID) ->
io:format("~s~n", ["mergulhando..."]).
Código 3.4.2: Herdando Métodos das Superclasses
A Variável super
A variável super pode ser utilizada para realizar uma chamada ao método da superclasse. Nesse caso, ao encontrar uma expressão desse tipo, o compilador deve seguir os
seguinte passos:
Herança
58
1. Buscar na ST o método onde foi encontrada a expressão e verificar se ele possui o
modificador (static), gerando erro caso seja.
2. Buscar qual a classe onde foi encontrada a expressão;
3. Verificar se essa classe tem uma superclasse, gerando erro caso contrário;
4. Verificar se a superclasse possui o método e ele é visı́vel, gerando erro caso contrário;
5. Verificar se o método chamado é de classe (static), gerando erro caso seja;
6. Gerar uma chamada de função para a implementação do método na superclasse
passando os parâmetros da função e acrescentando a referência do objeto, obtida
pela variável ObjectID, como mostrado na Seção 3.3.3.
Para exemplificar o uso do super, considere que o método comer(), sobrescrito em
Passaro, realizasse uma chamada super.comer(). O código gerado seria como mostrado
no Código 3.4.3.
1
2
3
public class Passaro extends Animal {
int penas;
public void comer() {
super.comer();
}
4
5
6
7
8
9
}
1
-module(passaro).
2
3
4
comer(ObjectID) ->
animal:comer().
5
6
7
public void voar(){...}
voar(ObjectID) ->
...
Código 3.4.3: Exemplo do Uso do Super
Capı́tulo 4
Testes
Este capı́tulo tem por finalidade apresentar a eficácia e eficiência do compilador Jaraki no
que se refere ao aspecto orientado a objetos da linguagem. Para cada teste é apresentado
o código original em Java (.java), e os tempos de execução e compilação utilizando o
programa da OracleTM e o Jaraki. Os códigos Erlang gerados e as árvores sintáticas para
cada teste encontram-se no apêndice. Cada teste foi realizado 10 vezes e o tempo médio
é apresentado. Além dos testes apresentados, outros 9 foram realizados para cada caso e
seus respectivos códigos estão no apêndice. Ao final do capı́tulo é apresentado um resumo
dos tempos de todos os testes executados.
4.1
Metodologia de Teste
Aqui são apresentadas as condições nas quais os testes foram realizados, bem como as
ferramentas utilizadas para medir os tempos de compilação e execução.
4.1.1
Ambiente de Testes
Os testes foram realizados nas seguintes condições:
• Sistema Operacional: Linux Mint 12 (lisa), Unix-like, baseado no Ubuntu e Debian;
• Kernel Linux 3.0.0-12-generic;
• Interface: GNOME 3.2.1
• Memória RAM: 1,6 GB;
• Processador dual-core 32bits AMD E-350;
Processo de Compilação
60
• Versão do Java: OracleTM 1.7.0_03;
• Versão do Erlang: Erlang R15B01.
4.1.2
Medição de Tempo
Para a medição dos tempos de compilação e execução foi utilizado a função time,
presente na biblioteca padrão fornecida pelo kernel do Linux. A saı́da desta função que
indica o tempo total de execução de determinado programa é o “tempo real” (real time),
sendo assim apenas este foi considerado nos testes. Para exemplificar, caso desejássemos
medir o tempo de compilação do código Principal.java, a linha a seguir seria executada,
onde o tempo de execução (ou compilação) foi de 1,095s:
rodrigo@rodrigo-PC: $ time javac Principal.java
real 0m1.095s
user 0m1.196s
sys 0m0.124s
4.2
Processo de Compilação
Nesta seção é demonstrado o processo de compilação de um .java (Código 4.2.1) utilizando o Jaraki. O compilador primeiramente gera a árvore sintática do código Java,
chamada jAST e, após processá-la, cria a árvore sintática do código em Erlang a ser gerado, chamada eAST. Por fim, a partir da eAST, o .erl é gerado.
Processo de Compilação
1
2
3
4
5
public class Principal {
public static void main(String args[]){
Bola b = new Bola();
b.cor = "azul";
System.out.println(b.cor);
b.mostraCor();
6
7
8
9
61
}
}
(a) Classe Principal
1
2
3
4
5
6
7
8
9
public class Bola {
String cor;
float circunferencia;
String material;
public void mostraCor() {
System.out.println("Cor: " + cor);
}
}
(b) Classe Bola
Código 4.2.1: Códigos de Exemplo para Processo de Compilação: Cadastro de Bola
Para executar o compilador, foi desenvolvido um script (jkc), que inicia o ambiente de
execução do Erlang, recebe o nome e localização do código Java e executa o compilador.
Sendo assim, os comandos para compilação e execução, bem como suas respectivas saı́das
são apresentados na Figura 4.1
rodrigo@myPC:~/jaraki/ex0$ javac Principal.java Bola.java
rodrigo@myPC:~/jaraki/ex0$ java Principal
azul
Cor: azul
(a) Java
rodrigo@myPC:~/jaraki$ ./jkc ex0/Principal.java ex0/Bola.java
rodrigo@myPC:~/git/jaraki$ ./jk mono/ex0/principal.beam
azul
Cor: azul
(b) Erlang
Figura 4.1: Compilação e Execução do Cadastro de Bola
Instanciação de Objetos
4.3
62
Instanciação de Objetos
Nesta seção são apresentados os resultados para a instanciação de objetos. Para cada
teste, variou-se a quantidade e tipos dos campos da classe, os nı́veis de hierarquia e os campos das superclasses. Além disso, a instanciação divide-se em duas categorias principais:
com construtor padrão e com construtor definido pelo usuário.
4.3.1
Construtor Padrão
Instanciação com construtor padrão Código 4.3.1.
1
2
3
4
5
public class Principal {
public static void main(String[] args) {
Produto p = new Produto();
}
}
(a) Classe Principal
1
2
3
4
5
public class Produto {
String nome;
float preco;
int codigo;
}
(b) Classe Produto
Código 4.3.1: Teste 1: Instanciação - Java
Os tempos para esse teste foram:
Compilação
Execução
Java
1.165ms
2ms
Erlang
367ms
10ms
Tabela 4.1: Teste 1: Instanciação
Manipulação de Campos de Objeto
4.3.2
63
Construtor Definido pelo Usuário
Instanciação com construtor definido pelo usuário. Código 4.3.2.
1
2
3
4
5
public class Principal {
public static void main(String[] args) {
Produto p = new Produto("Sabonete Avon", 1.50f, 1);
}
}
(a) Classe Principal
1
2
3
4
5
public class Produto {
String nome;
float preco;
int codigo;
public Produto(String nome, float preco, int codigo) {
this.nome = nome;
this.preco = preco;
this.codigo = codigo;
}
6
7
8
9
10
11
}
(b) Classe Produto
Código 4.3.2: Teste 2: Instanciação - Java
Os tempos medidos para esse teste foram:
Compilação
Execução
Java
1.093ms
1ms
Erlang
385ms
17ms
Tabela 4.2: Teste 2: Instanciação
4.4
Manipulação de Campos de Objeto
Para o testes de acesso a campos de objeto, foram consideradas duas situações: manipulação do objeto externamente a ele e internamente (métodos de objeto).
4.4.1
Manipulação Externa
O Código 4.4.1 ilustra esse caso.
Manipulação de Campos de Objeto
1
2
3
4
5
public class Principal {
public static void main(String[] args) {
Produto produto1 = new Produto();
Produto produto2 = new Produto();
Produto produto3 = new Produto();
6
7
8
produto1.nome = "Sabonete Avon";
produto1.preco = 2.5f;
produto1.codigo = 1;
System.out.println(produto1.codigo + ". " + produto1.nome +
" - R$" + produto1.preco);
9
10
11
12
13
produto2.nome = "Detergente LimpaTudo";
produto2.preco = 9.90f;
produto2.codigo = 2;
System.out.println(produto2.codigo + ". " + produto2.nome +
" - R$" + produto2.preco);
14
15
16
17
18
produto3.nome = "Escova de Dentes OralB";
produto3.preco = 6.10f;
produto3.codigo = 3;
System.out.println(produto3.codigo + ". " + produto3.nome +
" - R$" + produto3.preco);
19
20
21
22
23
24
25
64
}
}
(a) Classe Principal
1
2
3
4
5
public class Produto {
String nome;
float preco;
int codigo;
}
(b) Classe Produto
Código 4.4.1: Teste 3: Manipulação de Campos - Java
Os tempos para esse teste foram:
Compilação
Execução
Java
1.186ms
4ms
Erlang
417ms
21ms
Tabela 4.3: Teste 3: Campos Externo
A execução desse teste para o Java e Erlang foram como mostrado no Código 4.4.2.
Manipulação de Campos de Objeto
1
2
3
4
65
rodrigo@rodrigo-PC:~\$ java Principal
1. Sabonete Avon - R$2.5
2. Detergente LimpaTudo - R$9.9
3. Escova de Dentes OralB - R$6.1
(a) Java
1
2
3
4
rodrigo@rodrigo-PC:~\$ ./jk principal.beam
1. Sabonete Avon - R$2.500000
2. Detergente LimpaTudo - R$9.900000
3. Escova de Dentes OralB - R$6.100000
(a) Erlang
Código 4.4.2: Execução do Teste 3
4.4.2
Manipulação Interna
O Código 4.4.3 ilustra esse caso.
1
2
3
4
5
public class Principal {
public static void main(String[] args) {
Produto produto1 = new Produto("Sabonete Avon", 2.5f, 1);
Produto produto2 = new Produto("Detergente LimpaTudo", 9.9f, 2);
Produto produto3 = new Produto("Escova de Dentes OralB", 6.1f,3);
6
7
8
System.out.println("-------------Produtos-------------\n");
9
10
11
produto1.printInfo();
System.out.println("------------\n");
12
13
produto2.printInfo();
System.out.println("------------\n");
14
15
16
produto3.printInfo();
System.out.println("------------\n");
17
18
}
}
(a) Classe Principal
1
2
3
4
5
6
public class Produto {
String nome;
float preco;
int codigo;
public Produto(String nome, float preco, int codigo) {
this.nome = nome;
this.preco = preco;
this.codigo = codigo;
}
7
8
9
10
11
public void printInfo() {
System.out.println("Codigo: " + codigo);
System.out.println("Nome: " + nome);
System.out.println("Preco: " + preco);
}
12
13
14
15
16
17
}
(a) Classe Produto
Código 4.4.3: Teste 4: Manipulação de Campos - Java
Manipulação de Campos de Objeto
66
Os tempos para esse teste foram:
Compilação
Execução
Java
1.185ms
3ms
Erlang
411ms
22ms
Tabela 4.4: Teste 4: Campos Interno
A execução desse teste para o Java e Erlang foram como mostrado no Código 4.4.4.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
rodrigo@rodrigo-PC:~\$ java Principal
-------------Produtos------------Codigo: 1
Nome: Sabonete Avon
Preco: 2.5
-----------Codigo: 2
Nome: Detergente LimpaTudo
Preco: 9.9
-----------Codigo: 3
Nome: Escova de Dentes OralB
Preco: 6.1
------------
(a) Java
1
2
3
rodrigo@rodrigo-PC:~\$ ./jk principal.beam
-------------Produtos-------------
4
5
Codigo: 1
Nome: Sabonete Avon
Preco: 2.500000
------------
6
7
8
9
10
11
12
13
14
15
16
17
18
Codigo: 2
Nome: Detergente LimpaTudo
Preco: 9.900000
-----------Codigo: 3
Nome: Escova de Dentes OralB
Preco: 6.100000
------------
(a) Erlang
Código 4.4.4: Execução do Teste 4
Herança
4.5
67
Herança
Para os testes de herança, considere o diagrama da Figura 4.2
Figura 4.2: Diagrama dos Funcionários
4.5.1
Herança de Campos, Métodos e Sobrescrita
O Código 4.5.1 exemplifica o uso da herança de campos e métodos. O Código 4.5.2
apresenta a implementação das classes Funcionário e Técnico.
Herança
1
2
3
4
5
6
7
8
68
public class Principal{
public static void main(String[] args) {
Gerente ger = new Gerente("Jucimar Jr", 500000.0f, 100.0f);
Administrativo asstAdm =
new Administrativo("Pedro Nascimento", 1000.0f, 1, 1, 100.0f);
Tecnico asstTecnico = new Tecnico("Jorge Barroso", 100.0f, 2, "eletricista");
ger.fazerHoraExtra(3.4f);
ger.exibeDados();
asstAdm.exibeDados();
asstTecnico.exibeDados();
System.out.println("Gerente: " + ger.nome);
System.out.println("Assistente Adiministrativo: " +
asstAdm.nome + " - mat " + asstAdm.matricula);
System.out.println("Assistente Tecnico: " +
asstTecnico.nome + " - mat " + asstTecnico.matricula);
9
10
11
12
13
14
15
16
17
18
}
}
Código 4.5.1: Teste de Herança 1: Classe Principal
1
2
3
4
public class Funcionario {
String nome;
float salario;
public void exibeDados() {
System.out.println("======== DADOS ========");
System.out.println("Nome: " + nome);
System.out.println("Salario: " + salario);
}
5
6
7
8
9
10
}
(a) Classe Funcionario
1
2
3
4
// bonus salarial
public class Tecnico extends Assistente {
String especialidade;
public Tecnico(String nome, float salario, int matricula, String especialidade) {
this.nome = nome;
this.salario = salario;
this.matricula = matricula;
this.especialidade = especialidade;
}
5
6
7
8
9
10
11
12
public void exibeDados() {
super.exibeDados();
13
14
15
16
17
18
System.out.println("Especialidade: " + especialidade);
System.out.println("--------------------------------");
}
}
(b) Classe Assistente
Código 4.5.2: Teste 5: Herança 1 - Classes Funcionario e Tecnico
Herança
69
Os tempos medidos para esse teste foram:
Compilação
Execução
Java
1.599ms
225ms
Erlang
966ms
384ms
Tabela 4.5: Teste 5: Herança 1
A execução desse teste para o Java e Erlang foram como mostrado no Código 4.5.3.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
rodrigo@rodrigo-PC:~\$ java Principal
======== DADOS ========
Nome: Jucimar Jr
Salario: 500000.0
Salario + bonus: 500340.0
-------------------------------======== DADOS ========
Nome: Pedro Nascimento
Salario: 1000.0
Matricula: 1
Turno: diurno
-------------------------------======== DADOS ========
Nome: Jorge Barroso
Salario: 100.0
Matricula: 2
Especialidade: eletricista
-------------------------------Gerente: Jucimar Jr
Assistente Adiministrativo: Pedro Nascimento - mat 1
Assistente Tecnico: Jorge Barroso - mat 2
(a) Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
rodrigo@rodrigo-PC:~\$ ./jk principal.beam
======== DADOS ========
Nome: Jucimar Jr
Salario: 500000.000000
Salario + bonus: 500340.000000
-------------------------------======== DADOS ========
Nome: Pedro Nascimento
Salario: 1000.000000
Matricula: 1
Turno: diurno
-------------------------------======== DADOS ========
Nome: Jorge Barroso
Salario: 100.000000
Matricula: 2
Especialidade: eletricista
-------------------------------Gerente: Jucimar Jr
Assistente Adiministrativo: Pedro Nascimento - mat 1
Assistente Tecnico: Jorge Barroso - mat 2
(a) Erlang
Código 4.5.3: Execução do Teste 5
Herança
4.5.2
70
Polimorfismo
O Código 4.5.4 exemplifica o uso do polimorfismo utilizando as classes já apresentadas
nesta seção.
1
2
3
4
5
public class Principal{
public static void main(String[] args) {
Assistente assistente1 = new Tecnico("Jorge Barroso", 100.0f, 2, "eletricista");
Administrativo asstAdm = new Administrativo("Pedro Nascimento", 1000.0f, 1, 1, 100.0f);
Assistente assistente2 = asstAdm;
6
7
8
Funcionario funcionario1 = new Gerente("Jucimar Jr", 500000.0f, 100.0f);
Funcionario funcionario2 = assistente1;
Funcionario funcionario3 = assistente2;
9
10
11
12
13
System.out.println("-------------------------------------------------------");
System.out.println("Tipos dos funcionarios");
System.out.print("Funcionario 1: "); funcionario1.printTipo();
System.out.print("Funcionario 2: "); funcionario2.printTipo();
System.out.print("Funcionario 3: "); funcionario3.printTipo();
14
15
16
17
18
System.out.println("-------------------------------------------------------");
System.out.println("Dados de funcionario3 X dados de assistente2 X dados de asstAdm");
System.out.println("Funcionario 3:");
funcionario3.exibeDados();
System.out.println("Assistente 2:");
assistente2.exibeDados();
System.out.println("Assistente Administrativo:");
asstAdm.exibeDados();
19
20
21
22
23
24
25
26
27
28
}
}
Código 4.5.4: Teste de Herança 2: Classe Principal
Os tempos medidos para esse teste foram:
Compilação
Execução
Java
1.370ms
180ms
Erlang
979ms
389ms
Tabela 4.6: Teste 5: Herança 2
Herança
71
A execução desse teste para o Java e Erlang foram como mostrado no Código 4.5.5.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
rodrigo@rodrigo-PC:~\$ java Principal
------------------------------------------------------Tipos dos funcionarios
Funcionario 1: Sou um Funcionario Gerente
Funcionario 2: Sou um Funcionario Assistente Tecnico
Funcionario 3: Sou um Funcionario Assistente Administrativo
------------------------------------------------------Dados de funcionario3 X dados de assistente2 X dados de asstAdm
Funcionario 3:
======== DADOS ========
Nome: Pedro Nascimento
Salario: 1000.0
Matricula: 1
Turno: diurno
Assistente 2:
======== DADOS ========
Nome: Pedro Nascimento
Salario: 1000.0
Matricula: 1
Turno: diurno
Assistente Administrativo:
======== DADOS ========
Nome: Pedro Nascimento
Salario: 1000.0
Matricula: 1
Turno: diurno
(a) Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
rodrigo@rodrigo-PC:~\$ ./jk principal.beam
------------------------------------------------------Tipos dos funcionarios
Funcionario 1: Sou um Funcionario Gerente
Funcionario 2: Sou um Funcionario Assistente Tecnico
Funcionario 3: Sou um Funcionario Assistente Administrativo
------------------------------------------------------Dados de funcionario3 X dados de assistente2 X dados de asstAdm
Funcionario 3:
======== DADOS ========
Nome: Pedro Nascimento
Salario: 1000.000000
Matricula: 1
Turno: diurno
Assistente 2:
======== DADOS ========
Nome: Pedro Nascimento
Salario: 1000.000000
Matricula: 1
Turno: diurno
Assistente Administrativo:
======== DADOS ========
Nome: Pedro Nascimento
Salario: 1000.000000
Matricula: 1
Turno: diurno
(a) Erlang
Código 4.5.5: Execução do Teste 6
Resultado dos Testes
4.6
72
Resultado dos Testes
Nesta seção é apresentado o resultado geral dos testes.
Java
Erlang
Diferença
Dif. em %
Teste 1 Comp. 1.165ms
Exec.
2ms
367ms
10ms
798ms
8ms
68%
80%
Teste 2 Comp. 1.093ms
Exec.
1ms
385ms
17ms
708ms
16ms
65%
94%
Teste 3 Comp. 1.186ms
Exec.
4ms
417ms
21ms
769ms
17ms
65%
81%
Teste 4 Comp. 1.185ms
Exec.
3ms
411ms
22ms
774ms
19ms
65%
86%
Teste 5 Comp. 1.599ms
Exec.
225ms
966ms
384ms
633ms
159ms
40%
41%
Teste 6 Comp. 1.370ms
Exec.
180ms
979ms
389ms
391ms
209ms
29%
54%
Tabela 4.7: Tempos de Todos os Testes
Houve uma considerável diferença entre os tempos de compilação e execução entre o
compilador da OracleTM e o Jaraki. A diferença entre os tempos em porcentagem indica
quanto o menor tempo foi mais rápido que o maior. Sendo assim, o tempo de compilação
com o Erlang para os exemplos de herança foi, em média, 35% mais rápido que com o Java
e para os outros testes, 65% mais rápido. Já para o tempo de execução, usando a herança o
Java foi em média 48% mais rápido e, para os outros testes, 85%. Porém, desconsiderando
os zeros a direita dos pontos flutuantes, o Jaraki teve uma eficácia de 100% da saı́da gerada,
fornecendo saı́das exatamente iguais quando compilados com o compilador da OracleTM .
A diferença no tempo de execução deve-se às diferenças no tratamento de variáveis.
Considere, por exemplo, que uma variável seja declarada, um valor é atribuı́do a ela e
depois recuperado. Em Java, é alocado memória, ela é preenchida com o valor do usuário e
depois acessada. Já em Erlang, para ter o mesmo comportamento, primeiramente é alocado
memória, ela é preenchida com um valor padrão e então é copiado o valor do usuário. Em
seguida, a variável é acessada, seu valor é copiado para onde ela foi requerida e só então é
usada. Sendo assim, a memória é sempre acessada indiretamente, com cópias.
Capı́tulo 5
Conclusão
O resultado deste trabalho permite que o Jaraki, um compilador da linguagem Java implementado em Erlang, suporte a programação orientada a objetos do Java, logo o objetivo
proposto foi atingido. Para tanto, alguns módulos do Jaraki tiveram suas bases modificadas
e foi criada uma nova biblioteca que dá suporte à POO no ambiente de execução. Códigos
de teste foram criados para verificar a eficácia e eficiência dos recursos implementados.
Além disso, o Jaraki foi desenvolvido em conjunto, o que exigiu um controle extra para
garantir o sucesso.
Para cada programa de teste, o desempenho do Jaraki foi comparado com o do compilador Java da OracleTM . Para tanto, as saı́das dos programas e tempos de compilação e
execução foram registrados. Observou-se que o programa original em Java teve o mesmo
comportamento que o seu correspondente em Erlang, porém os tempos medidos apresentaram uma considerável divergência. Quando se fez uso de herança, o tempo de compilação
do Erlang foi, em média, 35% mais rápido que do Java e para os outros testes, 65% mais
rápido. Já para o tempo de execução, usando a herança, o Java foi em média 48% mais
rápido e para os outros testes, 85%.
A diferença no tempo de compilação deve-se ao fato de o Jaraki não ser um compilador
completo da linguagem Java, apenas os principais aspectos foram implementados. Já com
relação à execução, o maior gargalo está no tratamento das variáveis, pois o princı́pio
fundamental do Erlang são as variáveis imutáveis e a impossibilidade de acesso direto à
memória.
Trabalhos Futuros
74
No inı́cio do desenvolvimento, todos participavam da programação e, posteriormente,
cada um ficou responsável por uma parte. A fase em grupo foi essencial para definir o
estilo de programação e permitir que todos conhecessem as bases do compilador. Após
a divisão, outro ponto importante foi a comunicação: todos eram mantidos informados
do que e como cada um estava implementando a sua parte. Sempre que algo que fosse
afetar todas as partes do compilador precisasse ser feito, uma reunião era realizada para
compatibilizar as mudanças. Desse modo, os poucos conflitos que ocorreram puderam ser
resolvidos sem grandes problemas.
5.1
Trabalhos Futuros
Ao definir o escopo deste trabalho, considerou-se as funcionalidades mais usadas e que
definem a POO na linguagem Java, porém há outros aspectos relacionados que não foram
abordados e em trabalhos futuros podem ser implementados. Dentre esses, há recursos
nativos da linguagem (como interface) e bibliotecas padrão (como as de interface gráfica
ou criação e gerenciamento de Threads).
A interface é um tipo especial de classe abstrata usada para criar um “contrato” de
como classes devem ser implementadas. Para utilizar recursos gráficos, uma biblioteca
padrão é o SWING. Para manipular Threads (processos leves), existem as classes Thread
e Runnable, o pacote concurrent com classes como BlockingQueue e Callable, dentre
outros.
Além disso, é possı́vel também realizar diversas melhorias na eficiência do código da
compilação e no código gerado, porém afetariam diversas partes do compilador e por isso
não foram realizadas durante esse trabalho.
Referências Bibliográficas
[Aho2007] Aho, A. V. (2007). Compiladores: Princı́pios, técnicas e ferramentas. AddisonWesley, segunda edição.
[Armstrong2003] Armstrong, J. (2003). Making reliable distributed systems in the presence
of software errors. PhD in Computer Science, Royal Institute of Technology, Swedish
Institute of Computer Science (SICS), Stockholm.
[Armstrong2007] Armstrong, J. (2007). Pragmatic Programming in Erlang. Pragmatic
Bookshelf.
[Bison2012] Bison (2012). Disponı́vel em: http://www.gnu.org/software/bison/. Acessado em Março de 2012.
[Booch2006] Booch (2006). UML Guia do Usuário. Elsevier Editora, segunda edição.
[Cesarini2009] Cesarini, F. (2009). Erlang Programming. O’Reilly.
[Clojure2012] Clojure (2012). Disponı́vel em: http://clojure.org/. Acessado em Março
de 2012.
[Deitel2010] Deitel, P. (2010). Java - Como Programar. Prentice Hall, oitava edição.
[Efene2012] Efene (2012). Disponı́vel em: http://www.marianoguerra.com.ar/efene/.
Acessado em Março de 2012.
[Flex2012] Flex (2012). Disponı́vel em: http://flex.sourceforge.net/. Acessado em
Março de 2012.
[Gosling1995] Gosling, J. (1995). Java: an overview.
[Gosling2005] Gosling, J. (2005). The Java Language Specification. Addison-Wesley.
REFERÊNCIAS BIBLIOGRÁFICAS
76
[Joxa2012] Joxa (2012). Disponı́vel em: http://joxa.org/. Acessado em Março de 2012.
[JRuby2012] JRuby (2012). Disponı́vel em: http://jruby.org/. Acessado em Março de
2012.
[Jython2012] Jython (2012). Disponı́vel em: http://www.jython.org/. Acessado em
Março de 2012.
[Leex2012] Leex (2012). Disponı́vel em: http://www.erlang.org/doc/man/leex.html.
Acessado em Março de 2012.
[Reia2012] Reia (2012). Disponı́vel em: http://reia-lang.org/. Acessado em Março de
2012.
[Sebesta2011] Sebesta, R. W. (2011). Conceitos de Linguagens de Programao. Bookman,
nona edição.
[Stroustrup1991] Stroustrup, B. (1991). What is “Object-Oriented Programming”?
[TIOBE2012] TIOBE (2012). Tiobe programming community index for august 2012”. Disponı́vel em: http://www.tiobe.com/index.php/content/paperinfo/tpci/. Acessado
em Agosto de 2012.
[Yecc2012] Yecc (2012). Disponı́vel em: http://www.erlang.org/doc/man/leex.html.
Acessado em Março de 2012.
Apêndice A
Conteúdo do CD: Códigos Fonte,
PDFs e Vı́deos
Aqui é apresentado a estrutura do CD que contém os códigos-fonte do compilador, dos
testes realizados, os documentos em PDF da monografia, da apresentação e os vı́deos de
demonstração. Para cada teste foi guardado o código original em Java (arquivo .java),
a árvore sintática gerada pelo parser (jAST, arquivo .jast), a árvore sintática do código
em Erlang gerado (eAST, arquivo .east) e o código gerado (arquivo .erl). A numeração
de cada teste é única e foram divididos em classes de teste, a saber: instanciação, campos
e herança.
A.1
Estrutura Principal e Simbologia
O CD contém três pastas na raiz: jaraki, testes e pdf. A primeira contém o código
fonte do Jaraki, bem como scripts que funcionam como uma interface com o usuário. Há
scripts para compilar o Jaraki, utilizar o Jaraki para compilar códigos em Java e executar os
códigos gerados em Erlang. Nas seções deste capı́tulo, a simbologia usada para representar
as pastas e arquivos é como exemplificado na Figura A.1.
jaraki
pastas
jk
arquivos
Figura A.1: Simbologia para Representar Pastas e Arquivos
Pasta jaraki
A.2
78
Pasta jaraki
A pasta jaraki possui a estrutura apresentada na Figura A.2.
jaraki
ebin
include
bytecodes dos .erl
definição de constantes
jaraki_define.hrl
src
java_src
jk
jkc
Makefile
código-fonte do compilador
testes utilizados durante o desenvolvimento
script para executar .erl gerado
script para compilar .java
Makefile para compilar compilador
Figura A.2: Pasta do Jaraki
Pasta jaraki
A.2.1
79
Pasta src
A pasta src contém os códigos-fonte do compilador Jaraki, ou seja, são módulos Erlang
que contêm as funções para a compilação. A Figura A.3 apresenta os arquivos nela
contidos.
src
ast.erl
interface com o analisador sintático
core.erl
interface para converter jAST em eAST
file_lib.erl
biblioteca para suportar manipulação de arquivos
gen_ast.erl
funções auxiliares para montar nós da eAST
gen_erl_code.erl
principal módulo do analisador semântico
helpers.erl
funções auxiliares diversas
jaraki.erl
interface com usuário para compilar .java
jaraki_exception.erl
módulo que trata erros de compilação
jaraki_lexer.erl
analisador léxico gerado
jaraki_lexer.xrl
código fonte para o leex gerar o analisador léxico
jaraki_parser.erl
analisador sintático gerado
jaraki_parser.yrl
código fonte para o yecc gerar o analisador sintático
jaraki_utils.erl
funções para auxiliar o desenvolvimento do Jaraki
loop.erl
biblioteca para suportar laços
matrix.erl
oo_lib.erl
biblioteca para suportar matrizes
random_lib.erl
biblioteca para suportar o uso da classe Random
st.erl
funções para gerenciar a tabela de símbolos
vector.erl
biblioteca para suportar vetores
biblioteca para suportar orientação a objetos
Figura A.3: Pasta dos Códigos fonte do Jaraki
Pasta testes
A.3
80
Pasta testes
A pasta testes contém os testes realizados, separados por pastas no formato "número - tipo_de_teste", onde número é a numeração única do teste e o tipo_de_teste
é instanciação, campos ou herança. A Figura A.4 apresenta esta estrutura.
testes
1 - instanciacao
2 - instanciacao
3 - campos
4 - campos
5 - heranca
6 - heranca
Figura A.4: Pasta de Testes
Pasta testes
A.3.1
81
Testes de Instanciação
Os arquivos dos testes de instanciação são apresentados na Figura A.5.
1 - instanciacao
principal.east
principal.erl
Principal.jast
Principal.java
árvore sintática do código gerado
código gerado
árvore sintática extraída pelo parser
código em Java original
nome da classe
produto.east
produto.erl
Produto.jast
Produto.java
2 - instanciacao
principal.east
principal.erl
Principal.jast
Principal.java
produto.east
produto.erl
Produto.jast
Produto.java
Figura A.5: Arquivos dos Testes de Instanciação
Pasta testes
A.3.2
82
Testes de Campos
Os arquivos dos testes de acesso a campos são apresentados na Figura A.6.
3 - campos
principal.east
principal.erl
Principal.jast
Principal.java
produto.east
produto.erl
Produto.jast
Produto.java
4 - campos
principal.east
principal.erl
Principal.jast
Principal.java
produto.east
produto.erl
Produto.jast
Produto.java
Figura A.6: Arquivos dos Testes de Campos
Pasta testes
A.3.3
83
Testes de Herança
Os arquivos dos testes de herança são apresentados na Figura A.7.
5 - heranca
administrativo.east
administrativo.erl
Administrativo.jast
Administrativo.java
assistente.east
assistente.erl
Assistente.jast
Assistente.java
funcionario.east
funcionario.erl
Funcionario.jast
Funcionario.java
gerente.east
gerente.erl
Gerente.jast
Gerente.java
principal.east
principal.erl
Principal.jast
Principal.java
tecnico.east
tecnico.erl
Tecnico.jast
Tecnico.java
6 - heranca
administrativo.east
administrativo.erl
Administrativo.jast
Administrativo.java
assistente.east
assistente.erl
Assistente.jast
Assistente.java
funcionario.east
funcionario.erl
Funcionario.jast
Funcionario.java
gerente.east
gerente.erl
Gerente.jast
Gerente.java
principal.east
principal.erl
Principal.jast
Principal.java
tecnico.east
tecnico.erl
Tecnico.jast
Tecnico.java
Figura A.7: Arquivos dos Testes de Herança
Pasta PDF
A.4
84
Pasta PDF
Na pasta PDF estão os documentos da monografia e dos slides da defesa. A Figura
A.8 ilustra essa estrutura.
pdf
mono-rodrigo_bernardino.pdf
slides-rodrigo_bernardino.pdf
monografia
apresentação
Figura A.8: Documentos da Monografia e Apresentação
A.5
Vı́deos
Na pasta videos encontram-se os vı́deos usados para a demonstração do uso do compilador.
Download

implementa¸c˜ao do suporte a orienta¸c˜ao a objetos no compilador