Observações
Estarei em minha sala quase todo o
tempo, principalmente às tardes. Se alguém
não conseguir me encontrar, mande-me um e-mail para marcarmos um horário.
Imprimam os trabalhos em fonte courier. Assim podemos ver ser a tabulação
está correta. Para quem não sabe, letras em courier ocupam sempre o mesmo espaço
horizontal, independente do tamanho da letra. O mesmo não ocorre com outras
fontes, onde o “i”, por exemplo, ocupa menos espaço do que o “m” e outras
letras mais gordinhas.
Quanto à organização das
classes de envio de mensagem, faça assim:
abstract class MessageSend extends Expr
{ ... }
class MessageSendToVariable extends MessageSend {
... }
class MessageSendToSelf extends MessageSend { ... }
class MessageSendToSuper extends MessageSend { ... }
class MessageSendStatement extends
Statement {
MessageSend messageSend;
public void gen(
PrintWriter out ) {
pw.print(“”);
messageSend.gen(out);
out.println(“;”);
}
...
}
Note que há um envio de mensagem que
não deve ser considerado um envio de mensagem: é a criação de objetos, com new:
a = A.new();
Aqui,
devemos criar um objeto de uma classe MessageSendNew
(você escolhe o nome). Em qualquer caso, a classe deve herdar de Expr, não de MessageSend. Ou talvez você consiga fazer funcionar herdando
de MessageSend.
Na tabela de símbolos, não há
necessidade de colocar os métodos ou variáveis de instância. Coloque apenas as
variáveis locais, parâmetros (primeiro nível) e classes (segundo nível). Isto já
está feito na classe SymbolTable do compilador
fornecido neste site.
Os métodos deverão ser procurados
por um método searchMethod da classe ClassDec (que
representa uma classe de Simples). Quando houver um envio de mensagem, como em
x.m();
deve-se procurar por método “m” na classe de “x”. Assim:
classOf_x.searchMethod(“m”);
Onde classOf_x foi obtido por uma busca na tabela de símbolos
pela classe “A”, assim:
classOf_x = (ClassDec ) symbolTable.getInGlobal(“A”);
Assume-se
que x tenha sido declarado como
var x : A;
e que
método getInGlobal da tabela de símbolos procure por um símbolo apenas nas
classes da tabela (isto é verdade para a classe SymbolTable fornecida na página
da disciplina).
O mesmo raciocínio se aplica a
variáveis de instância.
A classe ClassDec
(que representa uma classe de Simples), deve ser algo como
class ClassDec
public ClassDec(
String name ) {
this.name = name;
}
private String
name;
private ClassDec
superclass;
private
InstanceVariableList instanceVariableList;
private
MethodList publicMethodList, privateMethodList;
// métodos
públicos get e set para obter e iniciar as variáveis acima,
// entre
outros métodos
}
Não deve haver
variáveis como publicPart ou privatePart. Nem deve
haver classes PublicPart e PrivatePart. Nem deve
existir classe UnPubPri.
ClassDec
deve herdar de Type para que a classe Variable e suas subclasses funcionem
corretamente:
class Variable
private String name;
private Type type;
...
}
Como type é do tipo Type, esta
variável de instância pode apontar para objetos de Type e suas subclasses, o
que inclui ClassDec. Assim, o tipo de uma variável
pode ser “integer” (objeto de Type), “boolean” (objeto de Type) ou uma classe
(objeto de ClassDec).
O construtor de ClassDec
deve ter um único parâmetro, o nome da classe. Assim, pode-se criar um objeto
de ClassDec tão logo saibamos o nome da classe. Isto é
necessário pois o objeto que representa a classe deve
logo ser inserido na Tabela de Símbolos, pois uma classe pode declarar um
objeto dela mesma:
class A
private:
var x : A;
...
end
Assim, ao encontar
o “x”, haverá uma busca na tabela de símbolos e lá será encontrado o objeto de ClassDec que representa a classe A, que foi inserido lá tão
logo o nome da classe se tornou disponível.
Para comparar strings em Java,
utilize compareTo:
if ( methodId.compareTo(“new”) == 0 )
...
Assim, ao
encontrar um comando
x = y;
o
compilador deve procurar por x na tabela de símbolos de tal forma que o objeto
de AssignmentStatement correspondente a esta atribuição tenha um ponteiro para
o objeto Variable representando x. Este objeto é o que foi criado na declaração
de x. O mesmo se aplica a y. Você deve fazer algum assim:
// lexer.getStringValue() retorna “x”
Variable left
= st.getInLocal( lexer.getStringValue() );
if ( left == null ) error.show(“...”);
return new AssignmentStatement( left, expr() );
Duas
estratégias ERRADAS são dadas abaixo.
1)
representar x como String. A classe AssignmentStatement
seria
class AssignmentStatement {
private String
leftSide;
private Expr
rightSide;
...
}
2)
representar x como uma variável, mas criar esta variável ao encontrar x :
// lexer.getStringValue() retorna “x”
Variable left
= new Variable( lexer.getStringValue() );
return new AssignmentStatement( left, expr() );
Os
únicos lugares onde deve-se criar objetos de Variable,
InstanceVariable e Parameter é na declaração das variáveis correspondentes. E
nunca se deve representar variáveis por Strings ---
utilize objetos de Variable e suas subclasses. O mesmo se aplica a ClassDec e ao tipo de variáveis.
Os compiladores sinalizam uma
exceção depois de mostrar um erro. Esta exceção deve ser (é) capturada em um
bloco try no método compile da classe Compiler. A impressão da pilha de
chamadas (com e.printStackTrace()) deve ser utilizada
apenas na fase de depuração do compilador e não deve estar presente no
compilador entregue ao professor.
Seria interessante que vocês
lessem um artigo sobre o
compilador Green. Ele fala sobre algumas de minhas experiências na
construção do compilador de Green.
Não deve haver uma classe IdList no compilador. A produção IdList
na gramática é utilizada para três coisas diferentes: análise de parâmetros,
variáveis de instância e variáveis locais. Em cada caso, o método IdList correspondente à produção IdList teria que criar
objetos de Parameter, InstanceVariable e Variable. Então, crie três métodos
iguais ao seu IdList correspondentes a cada uma das
classes Parameter, InstanceVariable e Variable. Veja no compilador Green como
foi feito.
A expressão “true > false” é
semânticamente correta em Simples. A sua tradução para Java resulta no mesmo
código, “true > false”. Contudo, Java não considera esta expressão correta,
embora devesse considerar. Não se preocupem com este problema. Resolvê-lo seria
complicado e então vamos deixar as coisas assim mesmo.
Interessante, mas ninguém até agora
notou uma terrível inconsistência no nome dos métodos dos analisadores
sintáticos que eu apresentei como exemplos. Lembram dos nomes dos métodos ? São expr, ifStat,
whileStat, varDec, etc. Absurdamente errado. Estes nomes são substantivos.
Métodos designam ações e portanto devem ter nomes
verbais. Os nomes corretos seriam analyzeExpr,
analyzeIfStat, etc, dizendo "analise uma expressão", ... Eu esqueci
de fazer esta observação durante o primeiro curso. Naturalmente, utizamos expr no
lugar de analyzeExpr por um motivo de economia de
digitação. Mas no vosso trabalho vocês não devem repetir este
erro em outras classes que não
o analisador sintático. O guia de programação recomenda que vocês coloquem
nomes significativos nos métodos e isto significa colocar nomes verbais.
Peguem
o máximo possível de código dos trabalhos exemplo que deixei na página de
Construção de Compiladores do semestre anterior. E peguem o máximo do
compilador Green que agora está disponível nesta página:
Todo o compilador
O analisador sintático
O analisador léxico
A árvore de sintaxe abstrata
Alguns
vão achar difícil de entender o compilador, pois ele possui muitas outras
coisas além do que vocês precisam. Mas ainda assim vale a pena vê-lo. Veja
também a página da linguagem Green.
Algumas dicas
para fazer o trabalho:
Utilize a classe PW para fazer a tabulação do código
gerado corretamente. Faça assim: antes de usar pw, inicialize-a uma única vez
com o objeto PrintWriter utilizado para saída
pw.set(out);
pw possui métodos print e println que automaticamente indentam o que você
imprime com pw.print ou pw.println.
Se você quiser aumentar a indentação, utilize pw.add().
Para diminuir, utilize pw.sub(). Teste o seguinte
código
PrintWriter out = new
PrintWriter( ... );
pw.set(out); // faça
isso uma única vez antes do início da geração de código
pw.add();
pw.print("if");
pw.out.println( "
a > b"); // código sem indentação, pois foi escrito com
pw.out
pw.print("then");
pw.add(); //
comandos dentro do then devem ser indentados
pw.println("a =
b;");
pw.sub(); //
diminui a indentação: acabou o then
pw.println("endif");
pw.sub();
pw.println("Texto
normal, não indentado");
Esta classe está no diretório AST do
analisador sintático fornecido na página Material
de Aula.
Por
curiosidade, mostrarei a geração de código de Green. A partir de uma classe
Green A, do arquivo A.g, o compilador
produz uma interface A.gi e arquivos em
Java _A.java e $co$A.java. Este dois últimos contém
o código compilado de A.g. São produzidos também
arquivos contendo interfaces, um arquivo para cada método declarado na classe
A. O código compilado é complexo porque ele contém suporte à biblioteca de
reflexão introspectiva de Green. Esta biblioteca permite ao programa descobrir
que classes o compõem, quais os métodos de cada classe ou objeto, os parâmetros
de dado método, o tipo de cada variável de instância de certa classe, etc.
Veja o site Java & Internet Glossary.
Há muitas coisas interessantes lá. E possivelmente muitas dicas que vocês
deverão empregar neste curso. Em particular, veja o significado das mensagens de erro
dadas por compiladores Java e JVM.