Construa um interpretador em Java — implemente o mecanismo de execução

para aqueles que acabaram de se juntar a nós, na minha coluna “Java em profundidade” nos últimos dois meses, tenho discutido como alguém pode construir um interpretador em Java. Na primeira coluna do interpretador, abordamos alguns dos atributos desejáveis de um interpretador; na segunda coluna, discutimos a análise e o layout de um pacote de classes para implementar o interpretador. Nesta coluna, analisaremos a execução do interpretador e as classes de suporte necessárias para realizar isso. Finalmente, vou encerrar a série aqui com uma discussão sobre como um interpretador pode ser conectado a outras classes Java, aumentando assim suas habilidades.

revendo os bits relevantes

deixe-me começar esboçando o que cobrimos até agora e apontar as partes do design que se tornarão mais importantes à medida que discutimos o modo de execução. Para uma descrição mais detalhada dessas classes, consulte minhas colunas anteriores ou os links de código-fonte que estão na seção

recursos

abaixo.

Existem três classes de fundação na implementação do intérprete, Program, Statement, e Expression. O seguinte mostra como os três estão relacionados:

programa

esta classe Program cola os componentes de análise e execução do analisador. Esta classe define dois métodos principais, load e run. O método load lê instruções de um fluxo de entrada e as analisa em uma coleção de instruções, o método run itera sobre a coleção e executa cada uma das instruções. A classe Program também fornece uma coleção de variáveis para o programa usar, bem como uma pilha para armazenar dados.

instrução

a classe Statement contém uma única instrução analisada. Esta classe é realmente subclassificada em um tipo específico de instrução (PRINT, GOTO, IF e assim por diante), mas todas as instruções contêm o método execute que é chamado para executar a instrução no contexto de uma instância de classe Program.

expressão

a classe Expression contém a árvore de análise de uma expressão. Durante a execução, o método value é usado para avaliar a expressão e retornar seu valor. Como Statement, a classe Expression é projetada principalmente para ser subclassificada por tipos específicos de expressões.

todas essas classes trabalham juntas para formar a base de um intérprete. A classe Program encapsula simultaneamente a operação de análise e operação de execução, enquanto as classes Statement e Expression encapsulam os conceitos computacionais reais da linguagem que implementamos. Para esses três artigos sobre a construção de intérpretes, a linguagem de exemplo tem sido básica.

Instalações para computação

Há duas executável classes do interpretador,

Statement

e

Expression

. Primeiro vamos dar uma olhada

Expression

as instâncias de Expression são criadas pelo método expression na classe ParseExpression. A classe ParseExpression implementa o analisador de expressão neste interpretador. Esta classe é um par da classe ParseStatement, que usa o método statement para analisar instruções básicas. Instâncias de Expression têm um type interno que identifica qual operador a instância representa, e dois métodos, value e stringValue, que retornam o valor calculado da expressão. Além disso, quando uma instância de expressão é criada, é nominalmente dado dois parâmetros que representam o lado esquerdo e direito da operação da expressão. Mostrado na forma de origem, a primeira parte da expressão é a seguinte:

class Expression { Expression arg1, arg2; int oper; final static int OP_ADD = 1; // Addition '+' final static int OP_SUB = 2; // Subtraction '-' final static int OP_MUL = 3; // Multiplication '*' final static int OP_DIV = 4; // Division '/' final static int OP_BNOT = 19; // Boolean negation '.NOT.' final static int OP_NEG = 20; // Unary minus

Como você pode ver no código acima, existem as variáveis de instância, um tipo de operador de chamada de oper, e duas metades da operação de arg1 e arg2, e, em seguida, algumas constantes para definir os vários tipos. Além disso, existem dois construtores que são usados pelo analisador de expressão; estes criam uma nova expressão a partir de duas expressões. Sua fonte é mostrada abaixo:

protected Expression(int op, Expression a, Expression b) throws BASICSyntaxError { arg1 = a; arg2 = b; oper = op; /* * If the operator is a boolean, both arguments must be boolean. */ if (op > OP_GE) { if ( (! (arg1 instanceof BooleanExpression)) || (! (arg2 instanceof BooleanExpression)) ) throw new BASICSyntaxError(typeError); } else { if ((arg1 instanceof BooleanExpression) || (arg2 instanceof BooleanExpression)) throw new BASICSyntaxError(typeError); } } protected Expression(int op, Expression a) throws BASICSyntaxError { arg2 = a; oper = op; if ((oper == OP_BNOT) && (! (arg2 instanceof BooleanExpression))) throw new BASICSyntaxError(typeError); }

o primeiro construtor constrói um objeto de expressão arbitrária, e o segundo constrói um objeto de expressão “unário” – como unário menos. Uma coisa a notar é que, se houver apenas um argumento, arg2 é usado para armazenar seu valor.

O método que é usado com mais freqüência em Expression classe value, que é definido da seguinte forma:

 double value(Program pgm) throws BASICRuntimeError { switch (oper) { case OP_ADD : return arg1.value(pgm) + arg2.value(pgm); case OP_SUB : return arg1.value(pgm) - arg2.value(pgm); ... etc for all of the other operator types. ...

Você pode ver que cada objecto expressão representa uma tupla consiste em um operador e um ou dois argumentos. A parte divertida de projetar o mecanismo de execução de expressão dessa maneira é que, quando você constrói esse conjunto de tuplas de expressão com base no objeto Expression, pode calcular o valor da expressão simplesmente invocando o método value. O método value invoca recursivamente o método value dos dois argumentos que compõem essa expressão, aplica a operação a eles e retorna o resultado. Esse design foi usado para que as expressões fossem fáceis de entender.

para manter a estrutura da classe limpa, todas as unidades computacionais-de constantes a funções trigonométricas-são subclasses de Expression. Essa ideia, roubada descaradamente do Lisp, encapsula completamente a noção de “causar” a ocorrência de uma avaliação, A partir da implementação real de “como” essa avaliação ocorre. Para demonstrar como esse princípio é aplicado, precisamos apenas olhar para algumas das subclasses especializadas de Expression.

constantes em minha versão do BASIC, que nomeei COCOA são representadas pela classe ConstantExpression, que subclasses Expression e simplesmente armazena o valor numérico em um valor de membro. O código-fonte para ConstantExpression é mostrado conceitualmente abaixo. Eu digo “conceitualmente” porque optei por agrupar o que teria sido StringConstantExpression e NumericConstantExpression em uma única classe. Portanto, a classe real inclui um construtor para criar uma constante com um argumento de string e para retornar seu valor como uma string. O código a seguir mostra como a classe ConstantExpression lida com constantes numéricas.

class ConstantExpression extends Expression { private double v; ConstantExpression(double a) { super(); v = a; } double value(Program pgm) throws BASICRuntimeError { return v; }}

o código mostrado acima substitui os construtores mais complicados de Expression por um simples armazenamento de uma variável de instância; o método value é simplesmente substituído por um retorno do valor armazenado.

é verdade que você poderia codificar a classe Expression para aceitar em seus construtores uma constante que salvaria uma classe. No entanto, uma vantagem de projetar Expression da maneira que tenho é que o código em Expression permanece maximamente Genérico. Acho que esse estilo de codificação me ajuda a eliminar a complexidade de casos especiais e, portanto, quando estou “pronto” com o código Expression, posso passar para outros aspectos das expressões sem revisitar a classe base repetidamente. A vantagem fica mais clara quando nos aprofundamos em outra subclasse de Expression chamada FunctionExpression.

na classe FunctionExpression, havia dois requisitos de design que eu achava que deveriam ser atendidos para manter o intérprete flexível. O primeiro foi implementar as funções básicas padrão; o outro era encapsular a análise dos argumentos de funções na mesma classe que implementou essas funções. O segundo requisito, a análise, foi motivado pelo desejo de tornar esse básico extensível criando bibliotecas de funções adicionais que poderiam ser passadas para o analisador como subclasses de FunctionExpression. Além disso, essas classes passadas podem ser usadas pelo analisador para aumentar o número de funções disponíveis para o programa do Usuário.

a classe FunctionExpression é apenas moderadamente mais complicada do que a ConstantExpression e é mostrada na forma condensada abaixo:

 1 class FunctionExpression extends Expression { 2 3 double value(Program p) throws BASICRuntimeError { 4 try { 5 switch (oper) { 6 case RND : 7 if (r == null) 8 r = p.getRandom(); 9 return (r.nextDouble() * arg2.value(p));10 case INT :11 return Math.floor(arg2.value(p));12 case SIN :13 return Math.sin(arg2.value(p));14 15 default :16 throw new BASICRuntimeError("Unknown or non-numeric function.");17 }18 } catch (Exception e) {19 if (e instanceof BASICRuntimeError)20 throw (BASICRuntimeError) e;21 else22 throw new BASICRuntimeError("Arithmetic Exception.");23 }24 }

a fonte acima mostra como o método value é implementado. A variável oper é reutilizada para manter a identidade da função, e os objetos de expressão referenciados por arg1 e arg2 são usados como argumentos para as próprias funções. Finalmente, há uma grande instrução switch que despacha a solicitação. Um aspecto interessante é que o método value captura as possíveis exceções aritméticas e as converte em instâncias de BASICRuntimeError. O código de análise em FunctionExpression é mostrado abaixo, novamente condensado para economizar espaço. (Lembre-se, todo o código-fonte está disponível usando links na seção Recursos.)

 1 static FunctionExpression parse(int ty, LexicalTokenizer lt) throws BASICSyntaxError { 2 FunctionExpression result; 3 Expression a; 4 Expression b; 5 Expression se; 6 Token t; 7 8 t = lt.nextToken(); 9 if (! t.isSymbol('(')) {10 if (ty == RND) {11 lt.unGetToken();12 return new FunctionExpression(ty, new ConstantExpression(1));13 } else if (ty == FRE) {14 lt.unGetToken();15 return new FunctionExpression(ty, new ConstantExpression(0));16 }17 throw new BASICSyntaxError("Missing argument for function.");18 }19 switch (ty) {20 case RND:21 case INT:22 case SIN:23 case COS:24 case TAN:25 case ATN:26 case SQR:27 case ABS:28 case CHR:29 case VAL:30 case STR:31 case SPC:32 case TAB:33 case LOG:34 a = ParseExpression.expression(lt);35 if (a instanceof BooleanExpression) {36 throw new BASICSyntaxError(functions.toUpperCase()+" function cannot accept boolean expression.");37 }38 if ((ty == VAL) && (! a.isString()))39 throw new BASICSyntaxError(functions.toUpperCase()+" requires a string valued argument.");40 result = new FunctionExpression(ty, a);41 break; 42 default:43 throw new BASICSyntaxError("Unknown function on input.");4445 }46 t = lt.nextToken();47 if (! t.isSymbol(')')) {48 throw new BASICSyntaxError("Missing closing parenthesis for function.");49 }50 return result;51 }

observe que este código aproveita o fato de que o analisador de expressão em ParseStatement já descobriu que está olhando para uma expressão e passou a identidade da expressão em como parâmetro ty. Este analisador então precisa apenas localizar o parêntese de abertura e o parêntese de fechamento que contêm o(s) argumento (s). Mas olhe atentamente: nas linhas #9 a # 18, o analisador permite que algumas funções não tenham argumentos (neste caso RND e FRE). Isso demonstra a flexibilidade proporcionada por ter o subconjunto de funções embutido nesta classe, em vez de forçar todas as funções a se conformarem a algum modelo predefinido. Dado um tipo de função no parâmetro ty, a instrução switch seleciona um branch que pode analisar os argumentos necessários para essa função, sejam eles strings, números, outras expressões e assim por diante.

outros aspectos: Strings e arrays

duas outras partes da linguagem básica são implementadas pelo interpretador COCOA: strings e arrays. Vamos olhar para a implementação de strings primeiro.

para implementar strings como variáveis, a classe Expression foi modificada para incluir a noção de expressões “string”. Esta modificação assumiu a forma de duas adições: isString e stringValue. A fonte para esses dois novos métodos é mostrada abaixo.

 String stringValue(Program pgm) throws BASICRuntimeError { throw new BASICRuntimeError("No String representation for this."); } boolean isString() { return false; }

claramente, não é muito útil para um programa básico obter o valor da string de uma expressão base (que é sempre uma expressão numérica ou booleana). Você pode concluir pela falta de utilidade que esses métodos não pertenciam a Expression e pertenciam a uma subclasse de Expression. No entanto, colocando esses dois métodos na classe base, todos os objetos Expression podem ser testados para ver se, de fato, são strings.

outra abordagem de design é retornar os valores numéricos como strings usando um objeto StringBuffer para gerar um valor. Assim, por exemplo, o mesmo código pode ser reescrito como:

 String stringValue(Program pgm) throws BASICRuntimeError { StringBuffer sb = new StringBuffer(); sb.append(this.value(pgm)); return sb.toString(); }

e se o código acima for usado, você pode eliminar o uso de isString porque cada expressão pode retornar um valor de string. Além disso, você pode modificar o método value para tentar retornar um número se a expressão for avaliada como uma string executando-a através do método valueOf de java.lang.Double. Em muitos idiomas, como Perl, TCL e REXX, esse tipo de Digitação amorfa é usado para grande vantagem. Ambas as abordagens são válidas e você deve fazer sua escolha com base no design do seu intérprete. Em BASIC, o interpretador precisa retornar um erro quando uma string é atribuída a uma variável numérica, então escolhi a primeira abordagem (retornando um erro).

quanto às matrizes, existem diferentes maneiras pelas quais você pode projetar seu idioma para interpretá-las. C usa os colchetes em torno dos elementos da matriz para distinguir as referências de índice da matriz das referências de função que têm parênteses em torno de seus argumentos. No entanto, os designers de linguagem para BASIC optaram por usar parênteses para funções e matrizes, portanto, quando o texto NAME(V1, V2) é visto pelo analisador, pode ser uma chamada de função ou uma referência de matriz.

o analisador lexical discrimina entre tokens que são seguidos por parênteses, assumindo primeiro que são funções e testes para isso. Em seguida, ele passa a ver se eles são palavras-chave ou variáveis. É essa decisão que impede que seu programa defina uma variável chamada ” SIN.”Qualquer variável cujo nome correspondesse a um nome de função seria retornada pelo analisador lexical como um token de função. O segundo truque que o analisador lexical USA é verificar se o nome da variável é imediatamente seguido por `(‘. Se for, o analisador assume que é uma referência de matriz. Ao analisar isso no analisador lexical, eliminamos a string ‘MYARRAY ( 2 ) ‘ de ser interpretada como uma matriz válida (observe o espaço entre o nome da variável e o parêntese aberto).

o truque final para implementar matrizes está na classe Variable. Esta classe é usada para uma instância de uma variável e, como discuti na coluna do mês passado, é uma subclasse de Token. No entanto, ele também tem algumas máquinas para suportar matrizes e é isso que mostrarei abaixo:

class Variable extends Token { // Legal variable sub types final static int NUMBER = 0; final static int STRING = 1; final static int NUMBER_ARRAY = 2; final static int STRING_ARRAY = 4; String name; int subType; /* * If the variable is in the symbol table these values are * initialized. */ int ndx; // array indices. int mult; // array multipliers double nArrayValues; String sArrayValues;

o código acima mostra as variáveis de instância associadas a uma variável, como na classe ConstantExpression. É preciso fazer uma escolha sobre o número de classes a serem usadas versus a complexidade de uma classe. Uma escolha de design pode ser construir uma classe Variable que contenha apenas variáveis escalares e, em seguida, adicionar uma subclasse ArrayVariable para lidar com os meandros das matrizes. Eu escolhi combiná-los, transformando variáveis escalares essencialmente em matrizes de comprimento 1.

se você ler o código acima, verá índices e multiplicadores de matriz. Estes estão aqui porque matrizes multidimensionais no BASIC são implementadas usando uma única matriz Java linear. O índice linear na matriz Java é calculado manualmente usando os elementos da matriz multiplicadora. Os índices usados no programa básico são verificados quanto à validade, comparando-os ao índice legal máximo na matriz NDX dos índices.

Por exemplo, uma matriz BÁSICA com três dimensões de 10, 10 e 8, teria os valores 10, 10 e 8 armazenados em ndx. Isso permite que o avaliador de expressão teste uma condição “índice fora dos limites” comparando o número usado no programa básico com o número legal máximo que agora está armazenado no ndx. A matriz multiplicadora em nosso exemplo conteria os valores 1, 10 e 100. Essas constantes representam os números que se usa para mapear de uma especificação de índice de matriz multidimensional em uma especificação de índice de matriz linear. A equação real é:

Java Index = Index1 + Index2 * tamanho máximo de Index1 + Index3 * (tamanho máximo de Index1 * MaxSizeIndex 2)

a próxima matriz Java na classe Variable é mostrada abaixo.

 Expression expns;

a matriz expns é usada para lidar com matrizes que são escritas como “A(10*B, i).”Nesse caso, os índices são na verdade expressões em vez de constantes, então a referência deve conter ponteiros para aquelas expressões que são avaliadas em tempo de execução. Finalmente, há esse pedaço de código de aparência bastante feia que calcula o índice dependendo do que foi passado no programa. Este método privado é mostrado abaixo.

 private int computeIndex(int ii) throws BASICRuntimeError { int offset = 0; if ((ndx == null) || (ii.length != ndx.length)) throw new BASICRuntimeError("Wrong number of indices."); for (int i = 0; i < ndx.length; i++) { if ((ii < 1) || (ii > ndx)) throw new BASICRuntimeError("Index out of range."); offset = offset + (ii-1) * mult; } return offset; }

olhando para o código acima, você notará que o código primeiro verifica se o número correto de índices foi usado ao fazer referência à matriz e, em seguida, que cada índice estava dentro do intervalo legal para esse índice. Se um erro for detectado, uma exceção será lançada ao interpretador. Os métodos numValue e stringValue retornam um valor da variável como um número ou uma string, respectivamente. Esses dois métodos são mostrados abaixo.

 double numValue(int ii) throws BASICRuntimeError { return nArrayValues; } String stringValue(int ii) throws BASICRuntimeError { if (subType == NUMBER_ARRAY) return ""+nArrayValues; return sArrayValues; }

Existem métodos adicionais para definir o valor de uma variável que não são mostrados aqui.

ao ocultar grande parte da complexidade de como cada peça é implementada, quando finalmente chega a hora de executar o programa básico, o código Java é bastante simples.

Executando o código

O código para interpretar as instruções BÁSICAS e executá-los contido no

run

método de

Program

de classe. O código para este método é mostrado abaixo, e eu passo por ele para apontar as partes interessantes.

 1 public void run(InputStream in, OutputStream out) throws BASICRuntimeError { 2 PrintStream pout; 3 Enumeration e = stmts.elements(); 4 stmtStack = new Stack(); // assume no stacked statements ... 5 dataStore = new Vector(); // ... and no data to be read. 6 dataPtr = 0; 7 Statement s; 8 9 vars = new RedBlackTree();1011 // if the program isn't yet valid.12 if (! e.hasMoreElements())13 return;1415 if (out instanceof PrintStream) {16 pout = (PrintStream) out;17 } else {18 pout = new PrintStream(out);19 }

o código acima mostra que o método run usa um InputStream e um OutputStream para uso como o “console” para o programa em execução. Na linha 3, o objeto de enumeração e é definido como o conjunto de instruções da coleção chamada stmts. Para esta coleção, usei uma variação em uma árvore de pesquisa binária chamada de árvore “vermelho-preta”. (Para obter mais informações sobre árvores de pesquisa binária, consulte minha coluna anterior sobre como criar coleções genéricas.) Depois disso, duas coleções adicionais são criadas – uma usando um Stack e outra usando um Vector. A pilha é usada como a pilha em qualquer computador, mas o vetor é usado expressamente para as instruções de dados no programa básico. A coleção final é outra árvore vermelho-preta que contém as referências para as variáveis definidas pelo programa básico. Esta árvore é a tabela de símbolos usada pelo programa durante a execução.

após a inicialização, os fluxos de entrada e saída são configurados e, se e não for nulo, começamos coletando todos os dados declarados. Isso é feito conforme mostrado no código a seguir.

 /* First we load all of the data statements */ while (e.hasMoreElements()) { s = (Statement) e.nextElement(); if (s.keyword == Statement.DATA) { s.execute(this, in, pout); } }

o loop acima simplesmente olha para todas as instruções, e quaisquer declarações de dados que encontrar São então executadas. A execução de cada declaração de dados insere os valores declarados por essa instrução no vetor dataStore. Em seguida, execute o programa adequado, que é feito usando este próximo pedaço de código:

 e = stmts.elements(); s = (Statement) e.nextElement(); do { int yyy; /* While running we skip Data statements. */ try { yyy = in.available(); } catch (IOException ez) { yyy = 0; } if (yyy != 0) { pout.println("Stopped at :"+s); push(s); break; } if (s.keyword != Statement.DATA) { if (traceState) { s.trace(this, (traceFile != null) ? traceFile : pout); } s = s.execute(this, in, pout); } else s = nextStatement(s); } while (s != null); }

Como você pode ver no código acima, o primeiro passo é reinicializar e. O próximo passo é buscar a primeira instrução na variável s e, em seguida, para entrar no loop de execução. Há algum código para verificar se há entrada pendente no fluxo de entrada para permitir que o progresso do programa seja interrompido digitando no programa e, em seguida, o loop verifica se a instrução a ser executada seria uma instrução de dados. Se for, o loop ignora a instrução como ela já foi executada. A técnica bastante complicada de executar todas as instruções de dados primeiro é necessária porque o BASIC permite que as instruções de dados que satisfazem uma instrução de leitura apareçam em qualquer lugar do código-fonte. Finalmente, se o rastreamento estiver ativado, um registro de rastreamento será impresso e a instrução muito uninpressive s = s.execute(this, in, pout); será invocada. A beleza é que todo o esforço de encapsular os conceitos básicos em classes fáceis de entender torna o código final trivial. Se não for trivial, então talvez você tenha uma pista de que pode haver outra maneira de dividir seu design.

encerrar e outros pensamentos

o interpretador foi projetado para que pudesse ser executado como um thread, portanto, pode haver vários threads Interpretadores COCOA rodando simultaneamente no espaço do seu programa ao mesmo tempo. Além disso, com o uso da expansão da função, podemos fornecer um meio pelo qual esses threads podem interagir uns com os outros. Havia um programa para o Apple II e mais tarde para o PC e Unix chamado C-robots que era um sistema de entidades “robóticas” interagentes que eram programadas usando uma linguagem derivada básica simples. O jogo proporcionou a mim e aos outros muitas horas de entretenimento, mas também foi uma excelente maneira de introduzir os princípios básicos da Computação aos alunos mais jovens (que erroneamente acreditavam que estavam apenas jogando e não aprendendo). Os subsistemas Interpretadores baseados em Java são muito mais poderosos do que seus equivalentes pré-Java porque estão instantaneamente disponíveis em qualquer plataforma Java. COCOA rodou em sistemas Unix e Macintoshes no mesmo dia em que comecei a trabalhar em um PC baseado no Windows 95. Embora o Java seja espancado por incompatibilidades nas implementações do thread ou window toolkit, o que muitas vezes é esquecido é o seguinte: muito código “simplesmente funciona. Chuck McManis atualmente é o diretor de software do sistema na FreeGate Corp., uma start – up financiada por empreendimentos que está explorando oportunidades no mercado da Internet. Antes de se juntar ao FreeGate, Chuck era membro do Grupo Java. Ele se juntouo Grupo Java logo após a formação da FirstPerson Inc. e foium membro do grupo portable OS (o grupo responsável pela OSportion de Java). Mais tarde, quando o Primeiropessoa foi dissolvida, ele permaneceucom o grupo através do desenvolvimento do alpha e betaversions da plataforma Java. Ele criou a primeira página inicial” all Java ” na Internet quando fez a programação para a página inicial Javaversion of The Sun em maio de 1995. Ele também desenvolveu a biblioteca acryptográfica para Java e versões do Java classloader que podiam exibir classes com base em assinaturas digitais.Antes de ingressar na FirstPerson, Chuck trabalhou na área de sistemas operacionais da SunSoft, desenvolvendo aplicativos de rede, onde fez o design inicial do NIS+. Além disso, confira sua página inicial. : END_BIO

Saiba mais sobre este tópico

  • Aqui estão links para os arquivos de origem mencionados acima:
    • Constantexpressão.java
    • FunctionExpression.java
    • programa.java
    • instrução.java
    • StringExpression.java
    • variável.java
    • Variávelexpressão.java
  • e aqui está um .Arquivo ZIP dos arquivos de origem:
    profundidade.zip
  • “Incomum Lisp” — um interpretador Lisp escrito em Java
    http://user03.blue.aol.com/thingtone/workshop/lisp.htm
  • Mike. Cowlishaw do NetREXX interpretador escrito em Java
    http://www2.hursley.ibm.com/netrexx/
  • Uma cópia antiga da USENET FAQ BÁSICA (ele ainda tem informações úteis a ele.)
    http://whitworth.me.ic.ac.uk/people/students/djbur/qbasic.htm
  • CACAU, um interpretador BASIC escrito em Java
    http://www.mcmanis.com/~cmcmanis/java/javaworld/examples/BASIC.html
  • Chuck Java página de recursos
    http://www.mcmanis.com/~cmcmanis/java/javaworld/
  • TCL Interpretador escrito em Java
    http://www.cs.cornell.edu/home/ioi/Jacl/
  • :
  • “como construir um intérprete em Java, parte 2a estrutura”
    o truque para montar as classes de Fundação para um intérprete simples.
  • “como construir um interpretador em Java, parte 1o básico”
    para aplicativos complexos que exigem uma linguagem de script, o Java pode ser usado para implementar o interpretador, adicionando habilidades de script a qualquer aplicativo Java.
  • “análise Lexical, parte 2construir um aplicativo”
    como usar o objeto StreamTokenizer para implementar uma calculadora interativa.
  • ” análise Lexical e JavaPart 1″
    Aprenda como converter texto legível por humanos em dados legíveis por máquina usando as classes StringTokenizer e StreamTokenizer.
  • “reutilização de código e sistemas orientados a objetos”
    Use uma classe auxiliar para impor o comportamento dinâmico.
  • ” o Suporte de Contêiner Para objetos em Java 1.0.2″
    organizar objetos é fácil quando você os coloca em contêineres. Este artigo mostra o design e a implementação de um contêiner.
  • ” os fundamentos dos carregadores de classe Java”
    os fundamentos deste componente-chave da arquitetura Java.
  • ” não usar coleta de lixo”
    Minimize a surra de heap em seus programas Java.
  • “Threads e applets e controles visuais”
    Esta parte final da série explora a leitura de vários canais de dados.
  • “usando canais de comunicação em applets, Parte 3”
    desenvolver técnicas de estilo Visual Basic para applet design – e converter temperaturas no processo.
  • ” Sincronização de threads em Java, Parte II”
    Aprenda a escrever uma classe de canal de dados e, em seguida, crie um aplicativo de exemplo simples que ilustra uma implementação do mundo real da classe.
  • ” Synchronizing threads in Java”
    o ex-desenvolvedor da equipe Java, Chuck McManis, apresenta um exemplo simples que ilustra como sincronizar threads para garantir um comportamento confiável e previsível do applet.

Deixe uma resposta

O seu endereço de email não será publicado.