PROJAVA: Um Tradutor de Prolog para Java

Propaganda
UNIVERSIDADE CATÓLICA DE PELOTAS
ESCOLA DE INFORMÁTICA
CURSO DE CIÊNCIA DA COMPUTAÇÃO
PROJAVA: Um Tradutor de
Prolog para Java
por
AUGUSTO MECKING CARINGI
Projeto de Graduação submetido como requisito parcial à obtenção do grau de Bacharel em
Ciência da Computação.
Orientador: Prof. Jorge L. V. Barbosa
Co-Orientadores: Prof. Adenauer C. Yamin
Prof. Luiz A. M. Palazzo
Pelotas, Novembro de 2002
2
Dedico, in memoriam, à minha avó,
Noemi de Assumpção Osorio Caringi.
3
Quem, de três milênios,
Não é capaz de se dar conta
Vive na ignorância, na sombra,
À mercê dos dias, do tempo.
Johann Wolfgang von Goethe
4
SUMÁRIO
LISTA DE FIGURAS
6
LISTA DE TABELAS
7
RESUMO
8
ABSTRACT
9
1 INTRODUÇÃO
10
1.1 O Contexto do Trabalho . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2 Principais Contribuições do Autor . . . . . . . . . . . . . . . . . . . . 13
1.3 Estrutura do Texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2 HOLOPARADIGMA
15
2.1 Contexto e Motivação . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2 Hololinguagem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.3 Holojava . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3 PROLOG
3.1 Origens . . . . . . . . . . . . . .
3.2 Implementação . . . . . . . . . .
3.3 O Modelo de Execução Prolog . .
3.4 WAM: Warren Abstract Machine
3.5 Técnica de Binarização . . . . . .
3.6 Sistemas Prolog em Java . . . . .
3.6.1 jProlog . . . . . . . . . . .
3.6.2 Prolog Café . . . . . . . .
.
.
.
.
.
.
.
.
19
19
19
19
20
21
22
22
23
4 PROJAVA
4.1 Visão Geral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 Estrutura de um Tradutor . . . . . . . . . . . . . . . . . . . . . . . .
4.3 JavaCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
24
24
26
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
4.4
4.5
4.6
JJTree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Definição da Gramática Prolog . . . . . . . . . . . . . . . . . . . . . 29
Árvore de Sintaxe Abstrata . . . . . . . . . . . . . . . . . . . . . . . 31
5 IMPLEMENTAÇÃO
5.1 Ambiente de Execução do Prolog Café
5.2 Indexação de Cláusulas . . . . . . . . .
5.3 Funcionamento . . . . . . . . . . . . .
5.4 Estrutura do Código Traduzido . . . .
5.5 Benchmarks . . . . . . . . . . . . . . .
5.6 Limitações . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
33
34
34
35
38
39
6 CONCLUSÕES
40
7 REFERÊNCIAS BIBLIOGRÁFICAS
42
A Gramática Prolog para o JavaCC
45
B Programas Prolog usados nos
B.1 Árvore Genealógica . . . . .
B.2 Série de Fibonacci . . . . . .
B.3 Manipulação de Listas . . .
B.4 Torres de Hanói . . . . . . .
49
49
49
49
50
benchmarks
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
C Exemplo de Código Traduzido: Fibonacci
51
D Exemplo de Holoprograma
55
6
LISTA DE FIGURAS
FIGURA 1
FIGURA 2
Polı́tica de Conversão de Holo para Java . . . . . . . . . . . 18
Gerenciamento de Arquivos na Conversão de Holo para Java 18
FIGURA 3
FIGURA 4
Módulos do Prolog Café . . . . . . . . . . . . . . . . . . . . 23
Diagrama de Execução . . . . . . . . . . . . . . . . . . . . 23
FIGURA 5
FIGURA 6
FIGURA 7
Diagrama de Geração do Parser . . . . . . . . . . . . . . . 29
Gramática Prolog . . . . . . . . . . . . . . . . . . . . . . . 31
Exemplo de Árvore de Sintaxe Abstrata . . . . . . . . . . . 32
FIGURA 8
FIGURA 9
Funcionamento Interno do PROJAVA . . . . . . . . . . . . 35
Diagrama de Tradução de Prolog para Java . . . . . . . . . 35
7
LISTA DE TABELAS
TABELA 1
TABELA 2
Principais Opções do JavaCC . . . . . . . . . . . . . . . . . 27
Tipo de Produções do JavaCC . . . . . . . . . . . . . . . . 28
TABELA 3
TABELA 4
Classes Java que modelam os termos Prolog . . . . . . . . . 33
Benchmarks . . . . . . . . . . . . . . . . . . . . . . . . . . 38
8
RESUMO
O Holoparadigma foi desenvolvido com as seguintes motivações: exploração
do paralelismo implı́cito, atuar sobre arquiteturas distribuı́das, trabalhar com múltiplos paradigmas e explorar a execução dos múltiplos paradigmas de forma paralela e distribuı́da. Com o objetivo de validar o modelo no menor prazo de tempo
possı́vel, foi implementada uma ferramenta para tradução de código Holo para código
Java. Algumas abstrações da Hololinguagem podem ser traduzidas diretamente para
Java; outras, entretanto, necessitam de programas adicionais. Este trabalho se enquadra nesse contexto e contribuirá para integração da programação em lógica ao
Holoparadigma. Seu objetivo é a implementação de um módulo para tradução de
Prolog para Java. Este módulo será responsável pela tradução das ações modulares
lógicas da Hololinguagem em código Java, substituindo o módulo utilizado anteriormente, o qual apresenta sérios problemas de desempenho.
Palavras-chave: Holoparadigma, Holojava, Prolog, Técnicas de tradução Prolog,
Programação Multiparadigma
9
ABSTRACT
Holoparadigm was developed with the following motivations: to exploit the
implicit parallelism, to be run in distributed architectures, to work with multiple
paradigms and to explore the execution of multiple paradigms in a parallel and distributed manner. With the goal of validating the model in the shortest time possible,
was implemented a tool for the translation of Holo code to Java. Some abstractions
of Hololanguage could be translated direct to Java, others however need additional
programs. This work is fit in this context and will contribute to the integration of
logic programming to Holoparadigm. Its goal is the implementation of a module
to translate Prolog to Java. This module will be responsible for the translation of
Hololanguage’s modular logic actions to Java code, replacing the previously used
module which have serious performance problems.
Keywords: Holoparadigm, Holojava, Prolog, Prolog translation techniques,
Multiparadigm Programming
10
1
INTRODUÇÃO
Maior produtividade na programação pode ser obtida através da escolha do
paradigma de programação mais apropriado ao tratamento do problema, entretanto,
no desenvolvimento de sistemas de software complexos, muitas vezes, um único
paradigma não é suficiente. Nesses casos, a melhor abordagem é a programação
multiparadigma (HORSPOOL 1996).
Linguagens de programação tradicionais (baseadas no paradigma imperativo)
são criticadas por não possuı́rem embasamento matemático, pela dificuldade que elas
trazem ao desenvolvimento de software, por não possuirem clareza e por forçarem
o programador a pensar de maneira seqüencial. Por outro lado, quase a totalidade de sistemas implementados para uso industrial e comercial são desenvolvidos
em linguagens imperativas, ficando os paradigmas “alternativos” restritos ao meio
acadêmico. Provavelmente isso se atribua ao fato de que a transição-de-estados,
conceito fundamental da programação imperativa, seja, na verdade, um paradigma
muito útil para resolver problemas práticos.
Contudo, torna-se claro que certos tipos de problemas são resolvidos de muito
melhor forma através de paradigmas não-imperativos. “Resolvidos de muito melhor” forma significa que a solução é mais clara, mais legı́vel, mais concisa e que ele
modela de uma maneira muito melhor o problema tratado (HORSPOOL 1996).
Um dos principais representantes dos “paradigmas não-convencionais” é o
paradigma da programação em lógica, paradigma este que tem como modelo computacional a lógica do cálculo de predicados; conforme COLMERAUER (1992), o
termo “programação em lógica” é devido a Robert Kowalski, que vislumbrou a possibilidade de utilização da lógica como linguagem de programação.
Prolog, por sua vez, é a linguagem pioneira e principal representante do
paradigma da programação em lógica. De acordo com COLMERAUER (1992),
Prolog, que é a abreviação de “PROgrammation en LOGique”, foi desenvolvida no
inı́cio da década de 70 por ele e seus colegas, na Universidade de Marselha. Desde
11
então, a linguagem foi alvo de intensas pesquisas e, entre as suas principais áreas de
aplicação, destacam-se:
• Sistemas baseados em conhecimento;
• Sistemas de bases de dados;
• Sistemas especialistas;
• Processamento da linguagem natural;
• Engenharia de software;
• Prova automática de teoremas;
• Construção de compiladores;
• Computação simbólica.
Segundo STERLING (1994), um programa lógico é um conjunto de axiomas,
ou regras, que definem relações entre objetos. A computação de um programa lógico
é uma dedução de conseqüências do programa. Um programa define um conjunto
de conseqüências, que são seu significado. A arte da programação em lógica consiste
em construir programas concisos e elegantes que apresentem o significado desejado.
Umas das principais idéias da programação em lógica é de que um algoritmo é
constituı́do por dois elementos disjuntos: a lógica e o controle. O componente lógico
corresponde à definição do que deve ser solucionado, enquanto o componente de
controle estabelece como a solução pode ser obtida. O programador precisa somente
descrever o componente lógico de um algoritmo, deixando o controle da execução a
cargo do sistema computacional. Isto possibilita que o programador simplesmente
especifique o problema, ao invés de ter que descrever o procedimento para solucioná-lo (PALAZZO, 1997). Este alto poder de expressão, aliado à capacidade de
exploração do paralelismo implı́cito da linguagem Prolog, a torna umas das peçaschave do Holoparadigma.
HORSPOOL (1996) e BARBOSA (2002) analisam as diferentes técnicas de
programação multiparadigma. A Hololinguagem é classificada como uma linguagem
12
multiparadigma de unificação total, porém, com o objetivo de testar os holoprogramas no menor espaço de tempo possı́vel, foi desenvolvida uma ferramenta que
traduz código Holo para código Java, neste caso, a abordagem utilizada para fins
de execução consiste na programação multiparadigma baseada em tradução, posto
que os diferentes paradigmas que compõem o Holo são traduzidos para o paradigma
imperativo/orientado a objetos da linguagem Java.
A ferramenta Holojava, a qual tem seu funcionamento detalhado no capı́tulo
2, realiza a tradução dos holoprogramas para Java através de um mapeamento das
construções Holo para construções Java. Entretanto, alguns aspectos não possuem
equivalentes em Java; nesses casos, são utilizadas ferramentas adicionais, Tratandose especificamente do código Prolog, a ferramenta utilizada até então para tradução
era o Prolog Café, contudo, o baixo desempenho deste software, bem como a necessidade do domı́nio desta tecnologia de tradução, foram os fatores motivadores do
presente trabalho.
1.1
O Contexto do Trabalho
O Holoparadigma (BARBOSA, 2001) foi desenvolvido com as seguintes motivações: exploração do paralelismo implı́cito, atuar sobre arquiteturas distribuı́das,
trabalhar com múltiplos paradigmas e explorar a execução destes de forma paralela
e distribuı́da.
Este trabalho se enquadra neste contexto e tem por objetivo central a implementação de um novo tradutor de código fonte Prolog para código fonte Java, com
o intuito de substituir o utilizado anteriormente, o qual possui sérios problemas de
desempenho.
O foco do trabalho é a integração do paradigma da programação em lógica
ao Holoparadigma, através do novo tradutor.
13
1.2
Principais Contribuições do Autor
Além de participar de discussões sobre os aspectos relativos à integração do
paradigma da programação em lógica ao Holoparadigma, destaca-se a atuação do
autor nos seguintes aspectos do trabalho:
• Identificação da origem do problema de desempenho do tradutor utilizado
posteriormente;
• Definição da melhor abordagem para sanar o gargalo de desempenho;
• Identificação da necessidade de aproveitamento do ambiente de execução
do Prolog Café;
• Implementação do novo tradutor.
1.3
Estrutura do Texto
O presente trabalho está dividido em seis capı́tulos, de forma a introduzir o
leitor gradativamente no tema proposto.
O primeiro capı́tulo é a introdução, onde foram apresentados os objetivos, o
contexto do do trabalho e também as contribuições do autor.
No segundo capı́tulo, são introduzidos conceitos referentes ao Holoparadigma
e tecnologias relacionadas, incluindo Holojava, a ferramenta de tradução de Holo
para Java.
O terceiro capı́tulo trata da linguagem Prolog, sua história, suas principais
caracterı́sticas, fundamentos e técnicas de implementação, além do tradutor atualmente utilizado na Holojava: Prolog Café.
Nos capı́tulo quatro e cinco, são descritos respectivamente, o modelo proposto para o novo tradutor e sua implementação e, no sexto, estão as conclusões
deste trabalho.
14
A gramática Prolog no formato da ferramenta JavaCC utilizada no desenvolvimento do tradutor PROJAVA está no anexo A. No anexo B, por sua vez, estão
os programas em Prolog utilizados nos testes de tradução e benchmarks. A listagem
completa do programa que calcula Fibonacci traduzido para Java pode ser encontrada no anexo C e, finalmente, o anexo D contém um exemplo de programa na
Hololinguagem.
15
2
2.1
HOLOPARADIGMA
Contexto e Motivação
Conforme BARBOSA (2002), o Holoparadigma (de forma abreviada, Holo) é
um modelo multiparadigma que possui uma semântica simples e distribuı́da. Através
dessa semântica, o modelo estimula a exploração automática da distribuição (distribuição implı́cita). O estı́mulo à distribuição implı́cita é seu principal objetivo.
O paralelismo implı́cito propõe a detecção automática do paralelismo e sua
exploração através de mecanismos inseridos no software básico dos sistemas computacionais. Por sua vez, nos últimos anos, os sistemas distribuı́dos têm recebido
cada vez mais atenção, tanto dos centros de pesquisa quanto das empresas. Nesse
contexto, torna-se interessante a integração dos temas paralelismo implı́cito e sistemas distribuı́dos em um tópico de pesquisa denominado distribuição implı́cita
(BARBOSA, 2002).
O Holoparadigma possui como unidade de modelagem os entes e, como
unidade de informação, os sı́mbolos. Um ente elementar é organizado em três
partes: interface, comportamento e história. Um ente composto possui a mesma
organização de um ente elementar, no entanto, suporta a existência de outros entes
na sua composição (entes componentes). Cada ente possui uma história. A história
fica encapsulada no ente e, no caso dos entes compostos, é compartilhada pelos entes
componentes.
De acordo com BARBOSA (2002), um ente elementar assemelha-se a um
objeto do paradigma orientado a objetos. Do ponto de vista estrutural, a principal
diferença consiste na história, a qual atua como uma forma alternativa de comunicação e sincronização. Um ente composto assemelha-se a um grupo. Neste caso,
a história atua como um espaço compartilhado vinculado ao grupo.
16
2.2
Hololinguagem
Conforme BARBOSA (2002), a Hololinguagem (de forma abreviada Holo),
implementa os conceitos propostos pelo Holoparadigma. Suas principais caracterı́sticas são:
• multiparadigma: Holo integra múltiplos paradigmas básicos. Atualmente,
estão integrados os paradigmas imperativo, em lógica e orientado a objetos;
• sem tipos e orientada ao processamento simbólico: o sı́mbolo é a unidade
de informação do Holoparadigma. A Hololinguagem é baseada no processamento de sı́mbolos e não possui tipos de dados;
• integração da atribuição e unificação: as variáveis lógicas são manipuladas
através da unificação (paradigma em lógica), mas também existe suporte
para atribuição;
• programação de alta ordem: Holo suporta programação de alta ordem,
isto significa que sı́mbolos representandos entes e suas partes podem ser
manipulados através de variáveis;
• blackboard hierárquico: exite suporte a vários nı́veis de blackboard;
• suporte à Holoclonagem: Holo implementa a clonagem múltipla e seletiva
como proposto no Holoparadigma;
• mobilidade: a mobilidade lógica é suportada explicitamente, já a mobilidade fı́sica fica a cargo do ambiente de execução;
• suporte à concorrência: existe suporte à concorrência entre ações e entre
entes;
• sintaxe lógica compatı́vel com Prolog: os aspectos lógicos da sintaxe da
linguagem são compatı́veis com a linguagem Prolog;
• sintaxe imperativa baseada no Pascal: os aspectos imperativos da sintaxe são baseados no Pascal, pela sua simplicidade e algumas semelhanças
sintáticas com Prolog.
17
Um holoprograma é formado por descrições de entes (entes estáticos), as
quais são formadas por Cabeça e Corpo. A cabeça contém o nome de um ente,
seus argumentos e uma descrição de sua clonagem. O corpo é formado por duas
partes: Comportamento e História. A história é um blackboard que pode ser acessado
pelas ações, pelos entes componentes ou pela própria história. O comportamento é
constituı́do por um conjunto de ações que implementam a funcionalidade, existindo
cinco tipos diferentes:
• Ação Lógica (Logic Action - LA): ação formada por um único predicado
lógico (uma ou mais cláusulas com mesmo nome e aridade);
• Ação Imperativa (Imperativa Action - IA): ação formada por uma função
imperativa;
• Ação Modular Lógica (Modular Logic Action - MLA): ação formada por
um módulo que encapsula ações lógicas;
• Ação Modular Imperativa (Modular Imperative Action - MIA): ação formada por um módulo que encapsula ações imperativas;
• Ação Multiparadigma (Multiparadigm Action - MA): ação que suporta o
encapsulamento de LAs e IAs.
2.3
Holojava
De acordo com BARBOSA (2001), visando à realização de testes de Holoprogramas (programas em Holo) no menor espaço de tempo possı́vel, foi criada uma
ferramenta de conversão de programas, denominada HoloJava. Esta ferramenta converte programas em Holo (linguagem origem) para programas em Java (linguagem
destino). Esta técnica é bastante usada na avaliação de novas linguagens, pois a
utilização de uma linguagem destino que possua uma plataforma (compilação e execução) já disponı́vel antecipa a realização de testes e a difusão de resultados da
pesquisa.
Diversos estudos indicam que Java pode ser utilizada como linguagem intermediária na construção de compiladores. Java suporta diretamente várias abstrações
18
do Holoparadigma (ações imperativas, concorrência, etc). No entanto, algumas caracterı́sticas de Holo não são suportadas (ações lógicas, blackboard e mobilidade).
Por outro lado, existem bibliotecas que complementam a plataforma Java e podem
ser utilizadas para implementação dos aspectos não suportados. Por exemplo, ações
lógicas em Prolog Café (BANBARA, 1999), história em Jada ou JavaSpaces e mobilidade fı́sica em Voyager ou Horb. A figura 1 mostra detalhadamente esta polı́tica
de conversão.
Holo
Java
Ente estático
Classe
Clonagem de Transição
suportando concorrência
inter−ente (ação clone)
Criação de um objeto (comando new)
encapsulado em uma thread e
atualização da HoloTree
Mobilidade lógica
(ação move)
Reorganização da HoloTree e
controle de membership nos entes
envolvidos
Ação Imperativa (IA)
Método
Ação Modular Lógica (MLA)
Classes em Java (Prolog Café)
Invocação explícita
Mensagem
História
Espaço de objetos em Jada
Afirmação para a história
(símbolo "!")
Inserção de um fato no espaço Jada
(método "out")
Pergunta não destrutiva bloqueante
para a história (símbolo ".")
Obtenção de um fato no espaço Jada
(método "read")
Pergunta destrutiva bloqueante
para a história (símbolo "#")
Obtenção de um fato no espaço Jada
(método "in")
Pergunta não−bloqueante e não−
destrutiva para a história (símbolo "?")
Obtenção de um fato no espaço Jada
(método "read_nb")
Pergunta não−bloqueante destrutiva
para a história (símbolo "?#")
Obtenção de um fato no espaço Jada
(método "in_nb")
FIGURA 1: Polı́tica de Conversão de Holo para Java
A figura 2 ilustra como é feito o gerenciamento dos arquivos na conversão
de Holo para Java. É utilizado no exemplo o programa datamining.holo, o qual
é composto de 3 entes e uma ação modular lógica (MLA); a listagem completa do
19
programa está no anexo D.
datamining.holo
HoloJava
1
Ente
holo
holo.java
Ente
mine
mine.java
Ente
miner
MLA
fib
miner.java
fib.pl
2
Prolog Café
Predicado fib/2
PRED_fib_2.java
3
Compilador Java
holo.class + *.class
4
JVM
FIGURA 2: Gerenciamento de Arquivos na Conversão de Holo para Java
Através de benchmarks realizados em BARBOSA (2001a), foi possı́vel constatar o baixo desempenho do Prolog Café. Em um programa Holo de 73 linhas, das
quais apenas 12 compondo uma MLA, 98,93% do tempo total de conversão foi gasto
pelo Prolog Café na tradução da MLA, e os resultados se repetiram em diferentes
programas, a conversão de um programa sem MLAs chegou a ser 105 vezes mais
rápida se comparada a um programa com MLAs. Baseado em tal fato, a utilização
das ações modulares lógicas foi considerada um ponto crı́tico no desempenho da
conversão.
20
3
3.1
PROLOG
Origens
O nome “Prolog”, abreviação de “PROgrammation en LOGique”, foi inventado em Marselha no ano de 1972, ele foi escolhido por Philippe Roussel para designar um software projetado para implementar um sistema de comunicação homemmáquina em linguagem natural (COLMERAUER, 1992). A idéia chave por trás da
programação em lógica é que, computação pode ser expressa como dedução controlada.
3.2
Implementação
A implementação de Prolog tem uma longa história. Os primeiros sistemas
foram implementados pelo grupo em torno de Colmerauer, em Marselha, sendo que
o primeiro interpretador foi escrito em 1972, em Algol. Se, por um lado, foi demonstrado que era possı́vel a implementação de uma linguagem baseada na lógica do
cálculo de predicados, por outro, foi constatado que o principal problema era o baixo
desempenho.
O primeiro compilador Prolog foi escrito por David Warren em conjunto com
Fernando e Luı́s Pereira em 1977. Esse compilador provou que era possı́vel executar
Prolog com desempenho semelhante ao das linguagens imperativas tradicionais e
contribuiu muito para o sucesso da linguagem Prolog.
3.3
O Modelo de Execução Prolog
As duas partes básicas de um interpretador Prolog são a parte de unificação
e a de resolução. A resolução é bastante simples, ela consiste em um mecanismo de
resolução-SLD simplificado, que procura as cláusulas de cima para baixo e avalia os
objetivos da esquerda para a direita. Esta estratégia leva ao mecanismo de retrocesso
(backtracking) e ao layout usual das áreas de dados e pilha. A resolução manipula a
21
pilha de alocação, chamada de procedimentos, e o backtracking.
Unificação em Prolog é definida como a seguir:
• duas constantes unificam se elas são iguais;
• duas estruturas unificam se seus functores (nome e aridade) são iguais e
todos os argumentos unificarem;
• duas variáveis livres unificam e são amarradas juntas;
• uma variável livre e uma constante ou estrutura unificam e a constante ou
estrutura é amarrada à variável.
3.4
WAM: Warren Abstract Machine
Seis anos após o desenvolvimento de seu compilador, David Warren apresentou um novo conjunto de instruções abstratas Prolog. Este novo Prolog Engine veio
a se tornar muito popular com o nome Warren Abstract Machine (WAM). Desde
então, ele tem sido a base para praticamente todas as implementações Prolog a partir do ano de 1983. O Objetivo da WAM é de servir como um modelo simples e
eficiente de implementação para interpretadores de byte code, bem como geradores
de código nativo.
GRUNE et al. (2001) analisam o fato de que enquanto poucos códigos intermediários para linguagens imperativas alcançaram mais que fama local, o código
WAM se tornou um padrão de fato, e isso pode refletir a complexidade da tarefa de
compilar Prolog.
O modelo de execução da WAM é mais semelhante ao das linguagens imperativas do que qualquer outro modelo. A principal idéia é a divisão da unificação
em duas partes, a cópia dos argumentos do objetivo chamador para registradores de
argumentos e então a unificação destes registradores de argumentos com a cabeça da
cláusula chamada. Isto é muito semelhante à passagem de parâmetros para funções
em linguagens imperativas como C.
22
De acordo com GRUNE et al. (2001), as instruções da WAM lidam com itens
como a seleção da cláusula adequada a usar na dedução de um fato, unificação sob
várias circunstâncias e backtracking. Além disso, a WAM faz toda a alocação de
memória, empregando, para isso, cinco pilhas.
3.5
Técnica de Binarização
Conforme LINDGREN (1994), um problema fundamental em executar Prolog, consiste em como mapear eficientemente o controle (incluindo recursão e backtracking) para um hardware convencional. A solução tradicional para este problema,
é traduzir código Prolog em instruções para uma máquina abstrata que gerencie as
estruturas de controle da linguagem Prolog. Tipicamente isso é realizado através da
WAM ou uma variante dela, outra abordagem, entretanto, é a técnica de binarização.
A idéia chave do “Prolog Binário” é a transformação das cláusulas convencionais em cláusulas binárias utilizando Continuation Passing Style (CPS). A
essência da técnica de CPS consiste em inserir um argumento adicional em cada
predicado, que representa o próximo predicado a ser executado em caso de sucesso.
Pesquisas na área de linguagens funcionais demonstraram que a técnica de CPS pode
expressar o controle de uma computação de uma maneira muito semelhante ao que
os gráficos de controle de fluxo fazem para as linguagens imperativas (LINDGREN,
1994).
BinProlog, um sistema Prolog muito eficiente desenvolvido por Paul Tarau (TARAU, 1990) demonstrou as vantagens e desvantagens desta abordagem
de implementação. Em “Prolog Binário”, uma cláusula tem no máximo um subobjetivo, dessa forma, a regra de controle da resolução-SLD que era implı́cita, tornase explı́cita. O código a seguir ilustra como uma cláusula normal pode ser transformada em uma cláusula binária.
nrev([],[]).
nrev([H|T],R) :nrev(T,L), append(L,[H],R).
23
nrev([],[],Cont) :- call(Cont).
nrev([H|T],R,Cont) :nrev(T,L, append(L,[H],R,Cont)).
KRALL (2002) também analisa os prós e contras deste método de implementação, o qual é motivado pelas simplificações que se tornam possı́veis no sistema
de execução. BinProlog, por exemplo, utiliza uma versão muito simplificada da
WAM, entretanto, algumas desvantagens, como maior consumo de memória, se tornam presentes.
3.6
Sistemas Prolog em Java
A implementação de sistemas eficientes Prolog em Java é atualmente alvo de
pesquisas (BANBARA, 1999). O número de sistemas deste tipo cresce a cada dia:
BirdLand’s Prolog in Java, CKI Prolog, DGKS Prolog, JavaLog, Jinni, JP, jProlog,
LL, MINERVA, W-Prolog e Prolog Café. Destes, apenas jProlog e Prolog Café são
tradutores, os outros são interpretadores.
3.6.1
jProlog
jProlog foi o primeiro conversor de Prolog para Java, desenvolvido em 1996
por Bart Demoen e Paul Tarau, ele se baseia na técnica binarização (TARAU, 1990).
O sistema consiste em um conjunto de classes Java que dão suporte à execução, incluindo uma série de predicados built-in e um tradutor escrito em Prolog.
O tradutor não suporta o bootstraping de forma que é necessário um sistema Prolog
(SICSTus por exemplo) para realizar a conversão. Para executar o código, uma vez
que ele tenha sido traduzido, basta uma máquina virtual Java.
Conforme informações obtidas no site do projeto, JPROLOG (2002), o tradutor foi escrito em Prolog por dois motivos: a falta de um gerador de parsers em Java
eficiente na época do projeto (1996), e as facilidades oferecidas pela linguagem Prolog para tal tipo de tarefa, incluı́ndo a extensão DCG (Gramática de Cláusulas
Definidas).
24
3.6.2
Prolog Café
Conforme BANBARA (1999), Prolog Café é uma ferramenta que traduz
código fonte de um superconjunto de Prolog, chamado LLP (Linear Logic Programming Language), para código fonte Java. O programa foi desenvolvido por
Mutsunori Banbara e Naoyuki Tamura, tendo por base o jProlog. Benchmarks
também realizados por BANBARA (1999) demonstram que o código gerado é 2,2
vezes mais rápido se comparado ao código do jProlog.
Além do tradutor, o programa consiste de um ambiente de execução para o
código traduzido, o qual é fortemente inspirado na WAM e um conjunto de predicados built-in alguns escritos diretamente em Java e outros traduzidos a partir do
Prolog. A figura 3 ilustra os módulos da ferramenta, e a figura 4, por sua vez, mostra
como é feita a execução.
O sistema possui duas versões do mesmo tradutor, ambos com a mesma funcionalidade, um implementado em Prolog e outro em Java, este último sendo obtido
através da técnica de bootstraping, isto é, o tradutor em Prolog é usado como entrada
para ele próprio, obtendo como saı́da um tradutor em Java. De acordo com AHO
(1995), bootstraping consiste em utilizar as facilidades oferecidas por uma linguagem
para compilar a si mesma.
Prolog Café
Ambiente de Execução
para o código traduzido
Tradutor de Prolog
para Java
escrito em Prolog
(acompanha versão
bootstrapped)
Classes que dão suporte a
execução (termos, algoritmo
de unificação, etc.)
Predicados Built−in
escritos em Java
FIGURA 3: Módulos do Prolog Café
25
Ambiente de Execução
Código Traduzido
Conjunto de classes Java que
de Prolog para Java
dão suporte a execução do código
traduzido
Máquina Virtual Java (JVM)
Sistema Operacional
FIGURA 4: Diagrama de Execução
26
4
4.1
PROJAVA
Visão Geral
Uma vez identificado que o baixo desempenho da ferramenta Prolog Café
deve-se ao fato de o tradutor ser bootstrapped, e reconhecendo que o ambiente de
execução da mesma é fruto de muitos anos de pesquisa relacionada à tradução de
Prolog para linguagens imperativas, chegou-se à conclusão que a melhor abordagem
seria reescrever o tradutor, a partir do zero, totalmente em Java, porém aproveitando as classes que dão suporte à execução do código traduzido.
Para que isto seja possı́vel, basta que o novo tradutor seja capaz de gerar um
código compatı́vel com o run-time environment já existente. Além disso, tal abordagem minimiza o impacto de adaptação à Holojava de um módulo de tradução de
Prolog para Java totalmente diferente do que já vinha sendo utilizado.
4.2
Estrutura de um Tradutor
O trabalho de um tradutor pode ser dividido basicamente em três partes:
1. Análise léxica
2. Análise Sintática
3. Geração do Código
As primeiras duas fases compõem o núcleo do sistema e envolvem o entendimento do programa fonte e a verificação da correção léxica e sintática. A esse
processo damos o nome de parsing.
A função do analisador léxico é obter e dividir os tokens no programa fonte.
Um token é uma peça significativa do fonte, como uma palavra chave, pontuação ou
literais como números ou strings, trechos que são descartados incluem espaços em
27
branco e comentários.
A análise sintática consiste em obter uma cadeia de tokens provenientes
do analisador léxico e verificar se a mesma atende às especificações sintáticas da
gramática da linguagem. Conforme AHO (1995) existem três tipos gerais de analisadores sintáticos: “método universal”, “top-down” e “bottom-up”. Através do
método universal é possı́vel trabalhar com qualquer gramática, analisadores de tal
classe são muito ineficientes. Os do tipo “top-down” e “bottom-up”, bem mais eficientes, trabalham somente com determinadas subclasses das gramáticas, mas que
são, contudo, suficientemente expressivas para descrever as construções sintáticas
das linguagens de programação.
Para implementação, duas abordagens podem ser utilizadas, escrever o analisador léxico e o analisador sintático manualmente ou fazer uso de ferramentas que
realizam a geração automática de tais tipos de software a partir de determinadas
regras. Na década de 50, com o advento dos primeiros compiladores, a construção de
analisadores, especialmente os sintáticos, era considerada uma tarefa árdua. Desde
então, muita teoria foi desenvolvida na área, com grande destaque para a Teoria das
Linguagens Formais, de modo que, atualmente, a construção manual é desconsiderada na maior parte dos casos, deixando-se tal tarefa para ferramentas automatizadas.
Existe uma série de ferramentas deste tipo, sendo as mais conhecidas, os
tradicionais lex (gerador de analisadores léxicos) e yacc (gerador de analisadores
sintáticos) que advêm do mundo Unix; tais programas, porém, não geram código
em Java. Dentro da gama de ferramentas que geram código Java, uma destacou-se
como padrão: JavaCC (Java Compiler Compiler). É interessante observar que tal
classe de software também é conhecida como Compilador de Compiladores. Outro
fator que determinou a adoção do JavaCC foi a prévia utilização do mesmo no projeto Holoparadigma, posto que o parser da Hololinguagem é gerado através dele.
Tendo o processo de parsing sido executado com sucesso e não gerado nenhum erro, o sistema terá uma representação interna do programa a qual pode ser
facilmente manipulada pelo último estágio do tradutor: o gerador de código, que,
ao contrário do parser, precisa ser escrito manualmente a fim de se obter o compor-
28
tamento desejado.
4.3
JavaCC
JavaCC é um gerador de parsers “top-down” em Java. Essa classe de parsers
também é conhecida como “descendente recursivo” e constrói a árvore de derivação
a partir da raiz em direção às folhas, permitindo o uso de gramáticas mais gerais
do que os parsers “bottom-up” que operam de forma inversa, sendo a sua única
limitação a impossibilidade da utilização de produções com recursão à esquerda, o
que poderia ocasionar recursão infinita (PANKAJ, 2002). A estrutura do tipo de
analisador gerado é idêntica à especificação da gramática, o que pode ser visto como
um fator a mais de facilidade.
A ferramenta agrega as funcionalidades de gerador de analisador léxico e gerador de analisador sintático; dessa forma, o arquivo de especificação da gramática
contém tanto as especificações léxicas quanto as sintáticas, tornando fácil a sua
manutenção.
Os recursos de análise léxica são semelhantes aos da ferramenta “lex”, sendo
o “Token Manager” o componente responsável pelo reconhecimento dos tokens da
gramática, seu funcionamento consiste em reconhecer e retornar os mesmos para o
parser. Sua implementação é basicamente um autômato finito não-determinı́stico.
Estrutura de um Arquivo de Gramática JavaCC:
Opç~
oes JavaCC
Parser_Begin(Identificador)
Código
Parser_End(Identificador)
Produç~
oes
A seção de opções é uma seção opcional onde são especificados vários parâmetros para customização do parser gerado, ela inicia pela palavra reservada options,
29
seguida por uma lista de uma ou mais opções. A tabela 1 lista as opções mais utilizadas.
Nome da Opção
LOOKAHED
Descrição
O valor padrão desta opção é 1, ela especifica
o número de tokens que devem ser “olhados
adiante” antes de ser tomada uma decisão em
um ponto de escolha.
STATIC
Esta é uma opção booleana que tem como
valor padrão verdadeiro. Ela implica que
todos os métodos e variáveis de classe são
definidos como estáticos no Token Manager
e no parser
DEBUG PARSER
O valor padrão desta opção é falso, quando
ativada ela faz o parser prover informações
adicionais em tempo de execução.
JAVA UNICODE ESCAPE O valor padrão é falso. Quando ativada, esta
opção permite lidar com fluxos de entrada no
formato Unicode.
TABELA 1: Principais Opções do JavaCC
A seção de Código, que fica entre Parser Begin( Identificador ) e Parser end(
Identificador ), também é chamada de Java Compilation Unit e contém código
Java opcional para instanciação do parser, caso ele não seja chamado através de uma
classe externa.
A última seção é a mais importante; uma gramática usada para especificar a
sintaxe de uma linguagem de programação consiste em um conjunto de produções.
Uma produção é uma regra pela qual uma seqüência de sı́mbolos terminais e nãoterminais são reduzidos a um não terminal, JavaCC permite a definição de quatro
tipos de produções, conforme a tabela 2:
4.4
JJTree
JJTree é um pré-processador para o JavaCC, que insere ações para criação de
uma árvore de sintaxe abstrata (AST - Abstract Syntax Tree) em vários lugares, em
30
Tipo da Produção
“Javacode”
Descrição
Em certos casos é difı́cil especificar construções livres de contexto para determinadas gramáticas, este tipo de produção permite inserir código Java ao invés de uma
produção EBNF para lidar com estes casos
especiais.
“Expressão Regular” Estas produções são utilizadas para definir
as entidades léxicas da gramática, também
conhecidas como tokens, que serão posteriormente trabalhados pelo “Token Manager”.
“EBNF”
Esta é a maneira padrão de especificar
gramáticas JavaCC. Uma produção EBNF
possui o formato: NT → α, onde NT é
um único sı́mbolo não-terminal e α é uma
seqüência de zero ou mais terminais e/ou
não-terminais.
“Token Manager”
As declarações nesta seção são embutidas no
“Token Manager” gerado e são acessı́veis a
partir das ações léxicas.
TABELA 2: Tipo de Produções do JavaCC
31
uma gramática JavaCC. A saı́da do JJTree é então processada pelo JavaCC para
criação do parser (JJTREE, 2002). A figura 5 ilustra esse processo.
Com a simples especificação de uma gramática para o JavaCC, obtemos um
parser, entretanto, tal software somente servirá para determinar se um dado programa atende ou não às regras da gramática. Para que o processo de parsing gere
algum resultado (interpretação do programa, tradução para uma linguagem destino,
etc.) é necessário programação adicional.
O JavaCC permite a inserção de trechos de código Java associados a cada
produção da gramática, tais códigos podem ter como objetivo a geração direta de
um novo programa na linguagem destino. Outra abordagem, que traz maior flexibilidade, é a inserção de código para geração de uma estrutura de dados representando
o programa fruto do parsing, a qual é conhecida como Árvore de Sintaxe Abstrata.
O JJTree faz isto automaticamente, inserindo ações semânticas para criação da AST
e gerando algumas classes que modelam a estrutura da mesma.
Por padrão, para cada sı́mbolo não-terminal da gramática, o JJTree gera
código para construção de um nodo na árvore. Por exemplo, um não-terminal
chamado Term dará origem a uma classe chamada ASTTerm. Posteriormente, a
árvore de sintaxe abstrata pode ser manipulada através de uma API do JJTree.
4.5
Definição da Gramática Prolog
Foi utilizado um subconjunto da gramática Prolog definida pelo padrão ISO,
a sintaxe completa da linguagem Prolog pode ser encontrada em SCOWEN (1993).
A gramática Prolog é relativamente simples, pois não contém palavras reservadas.
Um programa em lógica consiste basicamente de uma seqüência de cláusulas, por
sua vez, cada cláusula pode ser um fato ou uma regra. Um fato possui apenas uma
cabeça, enquanto uma regra possui uma cabeça e um corpo. O tipo de dado básico
da linguagem Prolog é o “termo”, um termo pode ser uma constante, uma variável,
uma lista ou um termo composto, uma constante, por sua vez, pode ser um átomo
ou um número.
32
Gramática Prolog
para o JAVACC
JJTREE
Gramática Prolog Anotada
para o JAVACC
JAVACC
Programa Java que faz a
análise léxica e sintática de
acordo com as regras da
gramática e gera uma AST
FIGURA 5: Diagrama de Geração do Parser
Uma das caracterı́sticas não suportadas na versão atual é a definição dinâmica
de operadores. O padrão ISO Prolog especifica que novos operadores podem ser
definidos em tempo de execução, linguagens como C++ permitem a redefinição
de novos comportamentos semânticos para operadores já existentes, recurso conhecido como sobrecarga de operadores; entretanto, Prolog permite além disso, a
utilização de qualquer sı́mbolo como novo operador. Uma vez que estes operadores
não estão presentes nas regras gramaticais da linguagem, torna-se uma tarefa um
pouco complexa seu tratamento. PANKAJ (2002) propõe um método para lidar
com operadores dinâmicos com o JavaCC.
Conforme GRUNE et al. (2001), as gramáticas livres de contexto constituem
o formalismo essencial para descrever a estrutura de programas em uma linguagem
de programação. De acordo com AHO (1995), tais gramáticas foram introduzidas
por Chomsky como parte de um estudo das linguagens naturais e seu uso na especi-
33
ficação de linguagens de programação emergiu independentemente.
O conceito de gramática livre de contexto, consistindo de sı́mbolos terminais, não-terminais, produções e sı́mbolo não-terminal inicial, independe da notação
utilizada para escrever a gramática, contudo, uma notação se destacou, conhecida
como Backus-Naur Form, ou simplesmente BNF, a qual foi utilizada pela primeira
vez nos anos 60 para descrever a sintaxe da linguagem Algol (SETHI 1996). Posteriormente, foi desenvolvida uma extensão para a notação BNF, chamada EBNF
(Extended Backus-Naur Form), permitindo a especificação de listas de elementos
e elementos opcionais. O objetivo da EBNF não é adicionar novos recursos e sim
conveniência, de forma que tudo que pode ser especificado em EBNF também pode
ser especificado em BNF.
A figura 6 utiliza a notação EBNF para representar as produções da gramática.
O seguinte padrão é utilizado:
• [...] ou (...)? implica 0 ou 1 ocorrência de qualquer construção dentro
dos parênteses/chaves.
• (...)+ implica 1 ou mais ocorrências de qualquer construção dentro dos
parênteses.
• (...)* implica 0 ou mais ocorrências de qualquer construção dentro dos
parênteses.
As strings entre os sı́mbolos “h” e “i” denotam os sı́mbolos terminais da
gramática, já os trechos entre aspas compõem tokens da linguagem. A figura 6
contém a especificação da gramática no formato EBNF, a versão para o JavaCC
está no anexo A.
4.6
Árvore de Sintaxe Abstrata
A gramática livre de contexto previamente listada é utilizada para verificar
se um programa atende às regras da sintaxe Prolog. Além disso, o parser deve
construir uma representação interna do programa; a essa representação interna em
forma de árvore damos o nome de Árvore de Sintaxe Abstrata (Abstract Syntax Tree
34
Program
Clause
Head
Body
Goal
Terms
Term
List
Expression
RelExpression
MatExpression
Atom
Variable
Number
::=
::=
::=
::=
::=
::=
::=
::=
::=
::=
::=
(Clause)+ <EOF>
Head (":-" Body)? "."
Atom ("(" Terms ")")?
Goal ("," Goal)*
((Atom ( "(" Terms ")" )? ) | Expression | <CUTOPERATOR>)
Term ("," Term )*
(Atom ("(" Terms ")" )* | List | Variable | Number)
"[" (Terms ("|" Term)?)? "]"
Variable (RelExpression | MatExpression)
<RELOPERATOR> (Variable | Number)
<ISOPERATOR> (Variable | Number)
(<ADDOPERATOR> (Variable | Number))?
::= <ATOM>
::= <VARIABLE>
::= <NUMBER>
FIGURA 6: Gramática Prolog
- AST). A fase seguinte do tradutor, o gerador de código, irá trabalhar com esta
estrutura para a construção do programa Java.
O estilo orientado a objetos de construir a árvore se dá com a criação uma
classe para cada sı́mbolo não-terminal da gramática, isto é feito automaticamente
pela ferramenta JJTree, a figura 7 ilustra graficamente a árvore gerada para a
cláusula familia(X,Y) :- pai(X,Z),pai(Z,Y).
35
Program
Clause
Head
Atom (familia)
Body
Terms
Goal
Term
Term
Variable (X)
Variable (Y)
Atom (pai)
Goal
Terms
Atom (pai)
Terms
Term
Term
Term
Term
Variable (X)
Variable (Z)
Variable (Z)
Variable (Y)
FIGURA 7: Exemplo de Árvore de Sintaxe Abstrata
36
5
5.1
IMPLEMENTAÇÃO
Ambiente de Execução do Prolog Café
O run-time environment do Prolog Café consiste em um conjunto de classes
Java que dão suporte à execução do código traduzido. A classe principal, Prolog.java,
implementa entre outras coisas os registradores, a área de dados e a rotina de
unificação. Existem algumas outras classes que trabalham em conjunto, provendo,
por exemplo, estruturas para o gerenciamento do backtracking. É interessante observar que o mecanismo de funcionamento de tais classes é fortemente inspirado na
WAM, provando, mais uma vez, a influência da mesma em todas as implementações
Prolog subseqüentes a ela.
Para cada tipo de termo Prolog, existe uma classe Java equivalente, a qual é
uma sub-classe da classe abstrata Term:
Classe
VariableTerm
Descrição
Cria um objeto representando uma variável
lógica.
IntegerTerm
Representa os valores do tipo inteiro.
SymbolTerm
Esta classe cria objetos representando
átomos.
ListTerm
Modela as listas Prolog.
StructureTerm Cria um objeto representando um termo
composto, um termo composto consiste em
um functor e uma seqüência de um ou mais
termos chamados argumentos
TABELA 3: Classes Java que modelam os termos Prolog
De acordo com BANBARA (1997), a definição da classe abstrata Term torna
fácil a manipulação em Java de vetores de termos, uma vez que, em um vetor do
tipo Term, pode ser colocado qualquer objeto que seja uma sub-classe dele.
37
5.2
Indexação de Cláusulas
Uma das técnicas de otimização utilizadas pelo Prolog Café, e que teve seu
comportamento mantido no PROJAVA é conhecida como indexação de cláusulas
(clause indexing); seu objetivo é reduzir o número de cláusulas para serem testadas
e evitar a criação de pontos de escolha, sempre que possı́vel. Os resultados são
execução mais eficiente e menor consumo de memória.
A otimização mais trivial realizada por qualquer sistema Prolog é tentar apenas as cláusulas de um procedimento especı́fico ao invés de testar todas as cláusulas
do programa, durante a busca por alguma cláusula unificante. A indexação de
cláusulas é um pouco mais complexa: apenas as cláusulas em que o primeiro argumento unifica com o objetivo são selecionadas.
Todas as cláusulas do predicado append/3 a seguir tem como primeiro argumento uma lista Prolog, através da técnica de indexação de cláusulas, em uma
consulta do tipo append(teste, teste, teste). Como o primeiro argumento da
consulta é um átomo, o resultado seria falha, sem mesmo precisar testar sequer uma
única cláusula do predicado.
append([],L,L).
append([H|L],L1,[H|R]) :append(L,L1,R).
5.3
Funcionamento
O nome do programa Prolog a ser traduzido é passado via linha de comando
ao tradutor, entra então em ação o parser. Caso o programa atenda às regras léxicas
e sintáticas da gramática Prolog e nenhum erro ocorra, o processo criará uma representação interna em forma de árvore: a figura 8 ilustra graficamente os módulos
internos da ferramenta. A tarefa agora cabe ao gerador de código, o primeiro passo
é percorrer a árvore em busca de informações úteis como o nome e aridade dos predicados e o número de cláusulas de cada predicado. Para cada predicado, também
é analisado o tipo do primeiro argumento de cada cláusula, informações estas rele-
38
vantes para o recurso de clause indexing.
Colhidos estes primeiros dados sobre o programa, começa a gravação das
classes Java em disco, um predicado por vez, é gerada a classe base, que representa
o ponto de entrada e então as classes adicionais para clada cláusula. A operação
consiste em percorrer os ramos da árvore de sintaxe abstrata correspondentes a cada
cláusula e gerar o código Java equivalente. A figura 9 mostra o processo completo,
desde a leitura do arquivo Prolog até obtenção do bytecode Java. A execução do
código traduzido segue o mesmo modelo demonstrado na figura 4 no capı́tulo 3.
Código fonte Prolog
Tradutor PROJAVA
Análise Léxica
Análise Sintática
Geração da Árvore
de Sintaxe Abstrata
Geração do
Código Java
Código fonte Java
FIGURA 8: Funcionamento Interno do PROJAVA
39
Código fonte Prolog
Tradutor PROJAVA
Código fonte Java
Compilador javac
Java bytecode
FIGURA 9: Diagrama de Tradução de Prolog para Java
5.4
Estrutura do Código Traduzido
A estrutura do código traduzido segue o mesmo modelo do Prolog Café, para
cada predicado é criado um arquivo .java contendo uma classe pública, que consiste
no ponto de entrada do predicado, e uma classe adicional privada, para cada cláusula
do mesmo. Tomando como exemplo o predicado pai/2 a seguir, é criado um arquivo
com o nome PRED pai 2.java.
pai(leonardo, augusto).
pai(leonardo, alberto).
A classe pública, representando o entry point, é uma sub-classe de Predicate,
o seu método construtor recebe os argumentos passados ao predicado e os coloca
40
em variáveis de instância (arg1, arg2); o método exec() é responsável pela execução, ele coloca os argumentos nos registradores da engine, seta o argumento de
continuação e então faz uma chamada ao método call(). Este, por sua vez, cuida
da chamada as cláusulas do predicado através de uma construção herdada da WAM
(switch on term()) que implementa a indexação de cláusulas.
public class PRED_pai_2 extends Predicate
{
static Predicate fail_0 = new PRED_fail_0();
static Predicate pai_2_1 = new PRED_pai_2_1();
static Predicate pai_2_2 = new PRED_pai_2_2();
static Predicate pai_2_var = new PRED_pai_2_var();
public Term arg1, arg2;
public PRED_pai_2(Term a1, Term a2, Predicate cont)
{
arg1 = a1;
arg2 = a2;
this.cont = cont;
}
public PRED_pai_2(){}
public void setArgument(Term[] args, Predicate cont)
{
arg1 = args[0];
arg2 = args[1];
this.cont = cont;
}
public Predicate exec()
{
engine.aregs[1] = arg1;
engine.aregs[2] = arg2;
41
engine.cont = cont;
return call();
}
public Predicate call()
{
engine.setB0();
return engine.switch_on_term(
pai_2_var,
fail_0,
pai_2_var,
fail_0,
fail_0
);
}
public int arity()
{
return 2;
}
public String toString()
{
return "pai(" + arg1 + ", " + arg2 + ")";
}
}
Para cada cláusula é criada uma sub-classe da classe “base” do predicado,
sendo o método exec() o responsável pelo código da cláusula. O primeiro passo
então é a obtenção dos argumentos que estão nos registradores do ambiente de execução através da desreferência. A desreferência é necessária, porque caso o conteúdo
do registrador seja uma variável, esta pode estar ligada a outra variável, e assim sucessivamente, de forma que é preciso seguir a trilha de ponteiros de uma variável
lógica até encontrar o “último da lista”. Também é setado o parâmetro de continuação, que indica o próximo predicado a ser executado em caso de sucesso.
42
O código seguinte dentro do método exec() tenta fazer a unificação dos
argumentos com o termos da cláusula, a engine provê o método unify() que implementa o algoritmo de unificação. Caso algum termo não unifique, é chamado o
método engine.fail(), que irá se encarregar de tomar ação certa no caso de falha.
final class PRED_pai_2_1 extends PRED_pai_2
{
static SymbolTerm s1 = SymbolTerm.makeSymbol("leonardo");
static SymbolTerm s2 = SymbolTerm.makeSymbol("augusto");
public Predicate exec()
{
Term a1, a2;
a1 = engine.aregs[1].dereference();
a2 = engine.aregs[2].dereference();
this.cont = engine.cont;
if ( !s1.unify(a1, engine.trail) ) return engine.fail();
if ( !s2.unify(a2, engine.trail) ) return engine.fail();
return cont;
}
}
5.5
Benchmarks
O teste de desempenho compara o tempo que os tradutores levam para converter o código Prolog em código Java. O desempenho de execução dos programas
traduzidos não foi alvo de benchmarks, posto que o código gerado pelos dois tradutores é muito semelhante.
Foi possı́vel constatar que o tradutor PROJAVA é em média dez vezes mais
rápido no processo de tradução, contudo, vale observar que, na realidade, é o Prolog
Café que apresenta um desempenho muito baixo, por ser um programa relativamente
43
Programa
familia.pl
fibo.pl
listas.pl
hanoi.pl
No de linhas
15
9
23
11
Prolog Café
5.406s
4.753s
5.446s
5.443s
PROJAVA Speedup
0.515s
10.50
0.499s
9.53
0.530s
10.28
0.523s
10.41
TABELA 4: Benchmarks
grande em Prolog que foi traduzido para Java e possui todo overhead de emulação
Prolog em Java.
5.6
Limitações
Algumas caracterı́sticas do tradutor que faz parte do software Prolog Café
ainda não foram completamente implementadas no PROJAVA, entre elas: o suporte
completo a termos compostos aninhados e expressões, também aninhadas. O tratamento de tais recursos é de natureza altamente recursiva e, o suporte completo a
eles será incorporado a versões futuras do PROJAVA.
44
6
CONCLUSÕES
Enquanto o Holoparadigma não contar com um compilador e uma máquina
virtual multiparadigma capazes de tratar os diferentes paradigmas suportados da
forma mais otimizada possı́vel, a abordagem de tradução para Java é a alternativa
mais viável. Entretanto, a ferramenta responsável pela conversão das ações modulares lógicas da Hololinguagem para Java sofria de sérios problemas de desempenho
relativos ao tempo de tradução.
Através de constações empı́ricas foi possı́vel verificar que:
• o baixo desempenho devia-se ao fato de o tradutor ter sido escrito em
Prolog, e então ser auto-traduzido para obtenção de uma versão do mesmo
em Java;
• o modelo de código gerado é fruto de muitos anos de pesquisa primeiramente na tradução de Prolog para C, e então de Prolog para Java, de forma
que este modelo poderia permanecer sem modificações.
Dessa forma, o trabalho consistiu em implementar um novo tradutor capaz
de gerar código compatı́vel com o que é gerado pelo Prolog Café, afim de que se
pudesse fazer o aproveitamento das classes Java que dão suporte à execução do
código traduzido. Para isso, foi necessário o estudo de toda a teoria que envolve a
construção de um software de tal natureza. A ferramenta JavaCC foi utilizada para
geração do front-end do tradutor, enquanto o gerador de código foi implementado
manualmente. Através de benchmarks, foi possı́vel verificar que este novo tradutor
é cerca de 10 vezes mais rápido que o conversor do Prolog Café.
Trabalhos futuros:
• Aperfeiçoamento da ferramenta PROJAVA para suportar uma gama maior
de recursos da linguagem Prolog, como por exemplo, suporte completo a
termos compostos aninhados;
• Uma das principais motivações do Holoparadigma é a exploração do paralismo implı́cito, fato que não está sendo realizado para o paradigma lógico.
45
Futuras versões do PROJAVA poderiam gerar código Java, que se utilizaria
de múltiplas linhas de execução (threads) para exploração do paralelismo;
• Conhecimento na realização do trabalho pode servir de base para o suporte
do paradigma da programação em lógica, em uma futura máquina virtual
multiparadigma.
46
7
REFERÊNCIAS BIBLIOGRÁFICAS
AHO, Alfred V.; SETHI, Ravi; ULLMAN, Jefrrey D. Compiladores: Princı́pios, Técnicas e Ferramentas. Rio de Janeiro:
LTC Editora, 1995. 344p.
AÏT-KACI, Hassan. Warren’s Abstract Machine: A Tutorial Reconstruction. The MIT Press (out of print), 1991. Disponı́vel
em http://isg.sfu.ca/~hak
APPEL, A. W. Compiling with Continuations. Cambridge Univeristy Press, 1992.
BANBARA, M.; TAMURA N. Java implementation of a linear
logic programming language. In proceedings of Post-JICSLP’98
Workshop on Parallelism and Implementation Technology for
Logic Programming Languages. 1997.
BANBARA, M.; TAMURA, N. Translating a Linear Logic Programming Language into Java. Workshop on Parallelism and
Implementation Technology (Constraint) Logic Programming
Languages, Las Cruces, p. 19-39, December. 1999.
BARBOSA, Jorge L. V.; DU BOIS, André R.; FRANCO, Laerte
K.; GEYER, Cláudio F. R. Traduzindo Holo para Java. 2001.
BARBOSA, Jorge L. V.; Geyer, Cláudio F.R. Integrating Logic
Blackboards and Multiple Paradigm for Distributed Software
Development. Proceedings of Intr. Conference on Parallel
and Dist. Processing Techniques and Applications (PDPTA).
June. 2001.
BARBOSA, Jorge L. V.; “Holoparadigma: um Modelo Multiparadigma Orientado ao Desenvolvimento de Software Distribuı́do”. Porto Alegre: CPGCC da UFRGS, 2002. 221p.
(Teste, Doutorado em Ciência da Computação).
47
COLMERAUER A.; ROUSSEL, P. The birth of Prolog. Novembro. 1992.
GRUNE, Dick; BAL, Henri E.; JACOBS, Ceriel J. H.; LANGENDOEN, Koen G. Projeto Moderno de Compiladores: Implementação e Aplicações. Rio de Janeiro: Campus, 2001. 671p.
HORSPOOL, R. N.; LEVY, M. R. Translator-Based Multiparadigm
Programming.
JavaCC - The Java Parser Generator. http://www.webgain.
com/products/metamata/java_doc.html, Dezembro. 2001.
JJTree. http://www.webgain.com/products/java_cc/jjtree.
html, Novembro, 2002.
jProlog. http://www.cs.kuleuven.ac.be/~bmd/PrologInJava/,
Novembro, 2002.
KRALL, A. Implementation Techniques for Prolog.
LINDGREN, Thomas. A Continuation-Passing Style for Prolog.
Technical Report 86. Uppsala University. 1994.
PALAZZO, L. A. M. Introdução à Programação PROLOG. Pelotas:
EDUCAT, 1997. 353p.
PANKAJ, G. The Design and Implementation of Prolog Parser
Using JavaCC. University of North Texas. Maio de 2002.
(Dissertação, Mestrado em Ciência da Computação)
SETHI, Ravhi. Programming Languages: Concepts & Consructs.
Murray Hill, New Jersey: Addison-Wesley, 1997. 640p. 2.ed.

SCOWEN, R. S. Draft
prolog standard, Technical Report ISO/IEC
JTC1 SC22 WG17 N110, International Organization for Standardization, 1993.
48
STERLING, L; SHAPIRO, E. The Art of Prolog 2.ed. Cambridge: MIT Press, 1994. 509 p.
TARAU, P.; BOYER, M. Elementary Logic Programs. Proceedings of Programming Language Implementation and Logic
Programming, number 456 in Lecture Notes in Computer Science, páginas 159-173. Springer: Agosto. 1990.
TARAU, P. Jinni: Intelligent Mobile Agent Programming at the
Intersection of Java and Prolog. PAAM’9, The Practical Applications Company. 1999.
WERNER, Otilia. Uma Máquina Abstrata Estendida para o
Paralelismo E na Programação em Lógica. Porto Alegre:
CPGCC da UFRGS, 1994. 150p. (Dissertação, Mestrado
em Ciência da Computação).
49
A
Gramática Prolog para o JavaCC
options
{
MULTI = true;
}
PARSER_BEGIN(PrologParser)
public class PrologParser
{
public static void main(String arguments[]) throws ParseException
{
}
}
PARSER_END(PrologParser)
SPECIAL_TOKEN : /* Comments */
{
<SINGLE_LINE_COMMENT1: "%" (~["\n","\r"])* ("\n"|"\r"|"\r\n")>
| <SINGLE_LINE_COMMENT2: "//" (~["\n","\r"])* ("\n"|"\r"|"\r\n")>
| <MULTI_LINE_COMMENT: "/*" (~["*"])* "*" (~["/"] (~["*"])* "*")* "/">
}
SKIP:
{
" " | "\t" | "\r" | "\n"
}
TOKEN: /* Operators
{
< CUTOPERATOR :
| < ADDOPERATOR :
| < MULOPERATOR :
| < RELOPERATOR :
| < ISOPERATOR :
| < MATOPERATOR :
}
*/
"!" >
"+" | "-" >
"*" | "/" | "mod" >
"=" | "==" | "!=" | ">" | "<" | ">=" | "<=" >
"is" >
<ADDOPERATOR> | <MULOPERATOR> >
50
TOKEN: /* Numeric constants */
{
< NUMBER: (<DIGIT>)+ >
| < #DIGIT: ["0" - "9"] >
| < CHAR_LITERAL: "’" (~["’","\\","\n","\r"])*
}
"’" >
TOKEN: /* Function names */
{
< VARIABLE: ( (<HICASE> | "_" ) ( <ANYCHAR> )* ) >
| < ATOM:
( <LOCASE> ) ( <ANYCHAR> )* >
| < #ANYCHAR: ( <LOCASE> | <HICASE> | <DIGIT> | "_" ) >
| < #LOCASE: ["a"-"z"] >
| < #HICASE: ["A"-"Z"] >
}
ASTProgram Program() :
{}
{
(Clause())+ <EOF>
{ return jjtThis; }
}
void Clause() :
{}
{
Head() [ ":-" Body() ] "."
}
void Head() :
{}
{
Atom() [ "(" Terms() ")" ]
}
void Body() :
{}
{
Goal() ("," Goal())*
}
51
void Goal() :
{}
{
((Atom() [ "(" Terms() ")" ]) | Expression() | <CUTOPERATOR>)
}
void Terms() :
{}
{
Term() ("," Term())*
}
void Term() :
{}
{
Atom() ("(" Terms() ")")* | List() | Variable() | Number()
}
void List() :
{}
{
"[" [ Terms() ["|" Term()]] "]"
}
void Expression() :
{}
{
Variable() (RelExpression() | MatExpression())
}
void RelExpression() :
{ Token t; }
{
t = <RELOPERATOR> (Variable() | Number())
{
jjtThis.setOp(t.image);
}
}
void MatExpression() :
52
{ Token t = null; }
{
<ISOPERATOR> (Variable() | Number()) [ t = <ADDOPERATOR> (Variable() | Number()) ]
{
jjtThis.setOp(t.image);
}
}
void Atom() :
{ Token t; }
{
t = <ATOM>
{
jjtThis.setName(t.image);
}
}
void Variable() :
{ Token t; }
{
t = <VARIABLE>
{
jjtThis.setName(t.image);
}
}
void Number() :
{ Token t; }
{
t = <NUMBER>
{
jjtThis.setName(t.image);
}
}
53
B Programas Prolog usados nos benchmarks
B.1
Árvore Genealógica
familia(X,Y)
familia(X,Y)
familia(X,Y)
familia(X,Y)
::::-
pai(X,Z),pai(Z,Y).
pai(X,Z),mae(Z,Y).
mae(X,Z),pai(Z,Y).
mae(X,Z),mae(Z,Y).
pai(nadir,jorge).
pai(nadir,bia).
pai(nadir,sergio).
pai(sergio,antonio).
pai(sergio,helena).
mae(zita,jorge).
mae(zita,bia).
mae(zita,sergio).
mae(bia,victoria).
mae(bia,catarina).
B.2
Série de Fibonacci
fib(1,1).
fib(2,1).
fib(M,N) :M > 2,
M1 is M-1,
M2 is M-2,
fib(M1,N1),
fib(M2,N2),
N is N1+N2.
B.3
Manipulação de Listas
listas(T,L,Z,K) :gera_lista(T,L),
54
nrev(L,Z),
size(Z,K).
nrev([],[]).
nrev([H|L],R) :nrev(L,R1),
append(R1,[H],R).
append([],L,L).
append([H|L],L1,[H|R]) :append(L,L1,R).
gera_lista(0,[]).
gera_lista(N,[Ca|Co]) :Ca is N - 1,
gera_lista(Ca,Co).
size([],0).
size([X|Y],T) :size(Y,Z),
T is Z + 1.
B.4
Torres de Hanói
hanoi(1,A,B,C,[mv(A,C)]).
hanoi(N,A,B,C,M) :N > 1, N1 is N - 1,
hanoi(N1,A,C,B,M1),
hanoi(N1,B,A,C,M2),
append(M1,[mv(A,C)],T),
append(T,M2,M).
append([],L,L).
append([H|L],L1,[H|R]) :append(L,L1,R).
55
C Exemplo de Código Traduzido: Fibonacci
//
//
//
//
Generated Java file by ProJava
A Prolog to Java source-to-source translation system
September 2002
Author: Augusto Mecking Caringi
import jp.ac.kobe_u.cs.prolog.lang.*;
public class PRED_fib_2 extends Predicate
{
static Predicate fail_0 = new PRED_fail_0();
static Predicate fib_2_1 = new PRED_fib_2_1();
static Predicate fib_2_2 = new PRED_fib_2_2();
static Predicate fib_2_3 = new PRED_fib_2_3();
static Predicate fib_2_var = new PRED_fib_2_var();
public Term arg1, arg2;
public PRED_fib_2(Term a1, Term a2, Predicate cont)
{
arg1 = a1;
arg2 = a2;
this.cont = cont;
}
public PRED_fib_2(){}
public void setArgument(Term[] args, Predicate cont)
{
arg1 = args[0];
arg2 = args[1];
this.cont = cont;
}
public Predicate exec()
{
engine.aregs[1] = arg1;
engine.aregs[2] = arg2;
engine.cont = cont;
56
return call();
}
public Predicate call()
{
engine.setB0();
return engine.switch_on_term(
fib_2_var,
fib_2_var,
fail_0,
fail_0,
fail_0
);
}
public int arity()
{
return 2;
}
public String toString()
{
return "fib(" + arg1 + ", " + arg2 + ")";
}
}
final class PRED_fib_2_var extends PRED_fib_2
{
static Predicate fib_2_var_1 = new PRED_fib_2_var_1();
public Predicate exec()
{
return engine.jtry(fib_2_1, fib_2_var_1);
}
}
final class PRED_fib_2_var_1 extends PRED_fib_2
{
static Predicate fib_2_var_2 = new PRED_fib_2_var_2();
public Predicate exec()
{
57
return engine.retry(fib_2_2, fib_2_var_2);
}
}
final class PRED_fib_2_var_2 extends PRED_fib_2
{
public Predicate exec()
{
return engine.trust(fib_2_3);
}
}
final class PRED_fib_2_1 extends PRED_fib_2
{
static IntegerTerm s1 = new IntegerTerm(1);
static IntegerTerm s2 = new IntegerTerm(1);
public Predicate exec()
{
Term a1, a2;
a1 = engine.aregs[1].dereference();
a2 = engine.aregs[2].dereference();
this.cont = engine.cont;
if ( !s1.unify(a1, engine.trail) ) return engine.fail();
if ( !s2.unify(a2, engine.trail) ) return engine.fail();
return cont;
}
}
final class PRED_fib_2_2 extends PRED_fib_2
{
static IntegerTerm s1 = new IntegerTerm(2);
static IntegerTerm s2 = new IntegerTerm(1);
public Predicate exec()
{
Term a1, a2;
a1 = engine.aregs[1].dereference();
a2 = engine.aregs[2].dereference();
this.cont = engine.cont;
58
if ( !s1.unify(a1, engine.trail) ) return engine.fail();
if ( !s2.unify(a2, engine.trail) ) return engine.fail();
return cont;
}
}
final class PRED_fib_2_3 extends PRED_fib_2
{
public Predicate exec()
{
Term a1, a2, a3, a4, a5, a6;
a1 = engine.aregs[1].dereference();
a2 = engine.aregs[2].dereference();
this.cont = engine.cont;
a3 = engine.makeVariable();
a4 = engine.makeVariable();
a5 = engine.makeVariable();
a6 = engine.makeVariable();
Predicate p1 = new PRED_$plus_3(a5, a6, a2, cont);
Predicate p2 = new PRED_fib_2(a4, a6, p1);
Predicate p3 = new PRED_fib_2(a3, a5, p2);
Predicate p4 = new PRED_$minus_3(a1, new IntegerTerm(2), a4, p3);
Predicate p5 = new PRED_$minus_3(a1, new IntegerTerm(1), a3, p4);
return new PRED_$greater_than_2(a1, new IntegerTerm(2), p5);
}
}
59
D
Exemplo de Holoprograma
// datamining.holo
//********************
ENTE PRINCIPAL ********************
holo()
// Ente principal.
{
holo()
// Acao guia.
{
writeln(’HOLO: Vou criar tres minas e um mineiro’),
clone(mine(1),mine_d1), // Cria a primeira mina.
clone(mine(2),mine_d2), // Cria a segunda mina.
clone(mine(3),mine_d3), // Cria a terceira mina.
clone(miner,miner_d),
// Cria o mineiro.
for X := 1 to 3 do
// Aguarda pelos resultados da mineracao.
{
history#list(#Ident,#Num,#Fibo),
// Obtem o resultado de uma mineracao.
writeln(’HOLO: Terminou a mineracao da mina:’,Ident),
writeln(’HOLO: Fibonacci de ’,Num,’ e ’,Fibo)
}
writeln(’HOLO: Terminou a mineracao’)
}
}
//********************
mine()
{
mine(Ident)
{
writeln(’MINA
}
history
{
list(1,2).
list(2,4).
list(3,6).
}
}
ENTE MINA *******************
’,Ident,’: Fui criada’)
//
//
//
//
A historia da mina de cada mina possui
o mesmo conteudo. O primeiro numero
identifica a mina. O segundo e o numero
usado para o calculo do Fibonacci.
60
//******************** ENTE MINEIRO *******************
miner()
{
miner()
{
writeln(’MINEIRO: Inicio da mineracao.’),
move(self,mine_d1),
// Passo 1 - Entra na mina 1
mining(1,Num1,Res1),
// Passo 2 - Minera a mina 1
move(self,out),
// Passo 3 - Sai da mina 1
out(history)!list(1,Num1,Res1),
// Passo 4 - Salva o resultado 1
move(self,mine_d2),
// Passo 5 - Entra na mina 2
mining(2,Num2,Res2),
// Passo 6 - Minera a mina 2
move(self,out),
// Passo 7 - Sai da mina 2
out(history)!list(2,Num2,Res2),
// Passo 8 - Salva o resultado 2
move(self,mine_d3),
// Passo 9 - Entra na mina 3
mining(3,Num3,Res3),
// Passo 10 - Minera a mina 3
move(self,out),
// Passo 11 - Sai da mina 3
out(history)!list(3,Num3,Res3),
// Passo 12 - Salva o resultado 3
writeln(’MINEIRO: Fim da mineracao.’)
}
mining(Ident,Num,Result)
// IA que realiza a mineracao.
{
out(history)#list(Ident,#Num), // Minera a historia externa para
// obtencao de um dado.
fib(Num,#Result)
// Chama a MLA para determinar Fibonacci.
}
fib/2()
// Acao Modular Logica (MLA) que calcula Fibonacci.
{
fib(1,1).
fib(2,1).
fib(M,N) :M > 2,
M1 is M-1,
M2 is M-2,
fib(M1,N1),
fib(M2,N2),
N is N1+N2.
}
}
Download