Construir un intérprete en Java Implement Implementar el motor de ejecución

Para los que acaban de unirse a nosotros, en mi columna «Java En Profundidad» durante los últimos meses, he estado discutiendo cómo se podría construir un intérprete en Java. En la primera columna del intérprete, cubrimos algunos de los atributos deseables de un intérprete; en la segunda columna discutimos tanto el análisis como el diseño de un paquete de clases para implementar el intérprete. En esta columna veremos cómo ejecutar el intérprete y las clases de soporte necesarias para lograrlo. Finalmente, terminaré la serie aquí con una discusión de cómo un intérprete puede conectarse a otras clases de Java, mejorando así sus habilidades.

Revisando los bits relevantes

Permítanme comenzar esbozando lo que hemos cubierto hasta ahora y señalar aquellas partes del diseño que se volverán más importantes a medida que discutamos el modo de ejecución. Para obtener una descripción más detallada de estas clases, consulte mis columnas anteriores o los enlaces de código fuente que se encuentran en la sección

Recursos

a continuación.

Hay tres clases básicas en la implementación del intérprete, Program, Statement y Expression. A continuación se muestra cómo se relacionan los tres:

Programa

Esta clase Program une los componentes de análisis y ejecución del analizador. Esta clase define dos métodos principales, load y run. El método load lee sentencias de un flujo de entrada y las analiza en una colección de sentencias, el método run itera sobre la colección y ejecuta cada una de las sentencias. La clase Program también proporciona una colección de variables para que el programa las use, así como una pila para almacenar datos.

Instrucción

La clase Statement contiene una única instrucción analizada. Esta clase se subclase en un tipo específico de instrucción (PRINT, GOTO, IF, etc.), pero todas las instrucciones contienen el método execute que se llama para ejecutar la instrucción en el contexto de una instancia de clase Program.

Expresión

La clase Expression contiene el árbol de análisis de una expresión. Durante la ejecución, se utiliza el método value para evaluar la expresión y devolver su valor. Al igual que Statement, la clase Expression está diseñada principalmente para ser subclasificada por tipos específicos de expresiones.

Todas estas clases trabajan juntas para formar la base de un intérprete. La clase Program encapsula simultáneamente la operación de análisis y la operación de ejecución, mientras que las clases Statement y Expression encapsula los conceptos computacionales reales del lenguaje que hemos implementado. Para estos tres artículos sobre la construcción de intérpretes, el lenguaje de ejemplo ha sido BÁSICO.

Instalaciones para el cálculo

Hay dos ejecutables clases en el intérprete,

Statement

y

Expression

. Primero echemos un vistazo a

Expression

Las instancias de Expression se crean mediante el método expression en la clase ParseExpression. La clase ParseExpression implementa el analizador de expresiones en este intérprete. Esta clase es un par de la clase ParseStatement, que utiliza el método statement para analizar instrucciones BÁSICAS. Las instancias de Expression tienen un type interno que identifica el operador que representa la instancia, y dos métodos, value y stringValue, que devuelven el valor calculado de la expresión. Además, cuando se crea una instancia de expresión, se le dan nominalmente dos parámetros que representan el lado izquierdo y derecho de la operación de la expresión. Mostrada en forma de fuente, la primera parte de la expresión es la siguiente:

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 puede ver en el código anterior, están las variables de instancia, un tipo de operador llamado oper y dos mitades de la operación en arg1 y arg2, y luego algunas constantes para definir los diversos tipos. Además, hay dos constructores que son utilizados por el analizador de expresiones; estos crean una nueva expresión a partir de dos expresiones. Su fuente se muestra a continuación:

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); }

El primer constructor construye un objeto de expresión arbitrario, y el segundo construye un objeto de expresión «unario», como unario menos. Una cosa a tener en cuenta es que si solo hay un argumento, arg2 se usa para almacenar su valor.

El método que se utiliza con mayor frecuencia en la clase Expression es value, que se define de la siguiente manera:

 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. ...

Puede ver que cada objeto de expresión representa una tupla que consta de un operador y uno o dos argumentos. La parte divertida de diseñar el motor de ejecución de expresiones de esta manera es que cuando construye este conjunto de tuplas de expresiones basadas en el objeto Expression, puede calcular el valor de la expresión simplemente invocando el método value. El método value invoca recursivamente el método value de los dos argumentos que componen esta expresión, les aplica la operación y devuelve el resultado. Este diseño se utilizó para que las expresiones fueran fáciles de entender.

Para mantener limpia la estructura de clases, todas las unidades computacionales, desde constantes hasta funciones trigonométricas, son subclases de Expression. Esta idea, robada descaradamente de Lisp, encapsula completamente la noción de» causar «que ocurra una evaluación, a partir de la implementación real de» cómo » se produce esa evaluación. Para demostrar cómo se aplica este principio, solo necesitamos mirar algunas de las subclases especializadas de Expression.

Las constantes de mi versión de BASIC, a las que llamé COCOA, están representadas por la clase ConstantExpression, que subclases Expression y simplemente almacena el valor numérico en un valor miembro. El código fuente ConstantExpression se muestra conceptualmente a continuación. Digo «conceptualmente» porque elegí agrupar lo que habría sido StringConstantExpression y NumericConstantExpression en una sola clase. Así que la clase real incluye un constructor para crear una constante con un argumento de cadena y para devolver su valor como una cadena. El siguiente código muestra cómo la clase ConstantExpression maneja las constantes numéricas.

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

El código mostrado anteriormente reemplaza a los constructores más complicados de Expression con un simple almacenamiento de una variable de instancia; el método value simplemente se reemplaza con un retorno del valor almacenado.

Es cierto que podrías codificar la clase Expression para aceptar en sus constructores una constante que te salvaría una clase. Sin embargo, una ventaja de diseñar Expression de la forma que tengo es que el código de Expression sigue siendo lo máximo genérico. Encuentro que este estilo de codificación me ayuda a eliminar la complejidad de los casos especiales y, por lo tanto, cuando he «terminado» con el código Expression, puedo pasar a otros aspectos de las expresiones sin volver a visitar la clase base una y otra vez. La ventaja se hace más clara cuando profundizamos en otra subclase de Expression llamada FunctionExpression.

En la clase FunctionExpression, había dos requisitos de diseño que sentí que debían cumplirse para mantener la flexibilidad del intérprete. La primera fue implementar las funciones básicas estándar; la otra era encapsular el análisis de los argumentos de las funciones en la misma clase que implementaba estas funciones. El segundo requisito, el análisis, fue motivado por el deseo de hacer extensible esta BASE mediante la creación de bibliotecas de funciones adicionales que pudieran pasarse al analizador como subclases de FunctionExpression. Además, esas clases pasadas podrían ser utilizadas por el analizador para aumentar el número de funciones disponibles para el programa del usuario.

La clase FunctionExpression es solo moderadamente más complicada que una ConstantExpression y se muestra en forma condensada a continuación:

 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 }

La fuente anterior muestra cómo se implementa el método value. La variable oper se reutiliza para contener la identidad de la función, y los objetos de expresión referenciados por arg1 y arg2 se utilizan como argumentos para las propias funciones. Por último, hay una declaración de cambio grande que envía la solicitud. Un aspecto interesante es que el método value captura las posibles excepciones aritméticas y las convierte en instancias de BASICRuntimeError. El código de análisis en FunctionExpression se muestra a continuación, nuevamente condensado para ahorrar espacio. (Recuerde que todo el código fuente está disponible mediante enlaces en la sección 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 }

Tenga en cuenta que este código aprovecha el hecho de que el analizador de expresiones de ParseStatement ya ha descubierto que está mirando una expresión y ha pasado la identidad de la expresión como parámetro ty. Este analizador solo necesita localizar el paréntesis de apertura y el paréntesis de cierre que contienen los argumentos. Pero fíjate bien: En las líneas # 9 a #18, el analizador permite que algunas funciones no tengan argumentos (en este caso RND y FRE). Esto demuestra la flexibilidad que ofrece tener la subparser de función incorporada en esta clase, en lugar de forzar a todas las funciones a ajustarse a alguna plantilla predefinida. Dado un tipo de función en el parámetro ty, la instrucción switch selecciona una rama que puede analizar los argumentos necesarios para esa función, ya sean cadenas, números, otras expresiones, etc.

Otros aspectos: Cadenas y arrays

El intérprete de COCOA implementa otras dos partes del lenguaje BÁSICO: cadenas y arrays. Veamos primero la implementación de cadenas.

Para implementar cadenas como variables, se modificó la clase Expression para incluir la noción de expresiones «cadena». Esta modificación tomó la forma de dos adiciones: isString y stringValue. La fuente de estos dos nuevos métodos se muestra a continuación.

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

Claramente, no es demasiado útil para un programa BÁSICO obtener el valor de cadena de una expresión base (que siempre es una expresión numérica o booleana). Podría concluir por la falta de utilidad que estos métodos entonces no pertenecían a Expression y pertenecían a una subclase de Expression en su lugar. Sin embargo, al colocar estos dos métodos en la clase base, se pueden probar todos los objetos Expression para ver si, de hecho, son cadenas.

Otro enfoque de diseño es devolver los valores numéricos como cadenas utilizando un objeto StringBuffer para generar un valor. Así, por ejemplo, el mismo código podría reescribirse como:

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

Y si se utiliza el código anterior, puede eliminar el uso de isString porque cada expresión puede devolver un valor de cadena. Además, puede modificar el método value para intentar devolver un número si la expresión se evalúa como una cadena ejecutándola a través del método valueOf de java.lang.Double. En muchos lenguajes como Perl, TCL y REXX, este tipo de escritura amorfa se usa con gran ventaja. Ambos enfoques son válidos, y usted debe hacer su elección en función del diseño de su intérprete. En BASIC, el intérprete necesita devolver un error cuando se asigna una cadena a una variable numérica, por lo que elegí el primer enfoque (devolver un error).

En cuanto a los arrays, hay diferentes formas en las que puede diseñar su lenguaje para interpretarlos. C utiliza los corchetes alrededor de los elementos de la matriz para distinguir las referencias de índice de la matriz de las referencias de función que tienen paréntesis alrededor de sus argumentos. Sin embargo, los diseñadores de lenguaje de BASIC optaron por usar paréntesis tanto para funciones como para matrices, de modo que cuando el analizador sintáctico vea el texto NAME(V1, V2), podría ser una llamada a función o una referencia de matriz.

El analizador léxico discrimina entre tokens que van seguidos de paréntesis asumiendo primero que son funciones y probando para eso. Luego sigue para ver si son palabras clave o variables. Es esta decisión la que impide que su programa defina una variable llamada «SIN».»Cualquier variable cuyo nombre coincida con un nombre de función sería devuelto por el analizador léxico como un token de función en su lugar. El segundo truco que utiliza el analizador léxico es comprobar si el nombre de la variable va seguido inmediatamente de `(‘. Si lo es, el analizador asume que es una referencia de matriz. Al analizar esto en el analizador léxico, eliminamos la cadena ‘MYARRAY ( 2 ) ‘ de ser interpretada como una matriz válida (tenga en cuenta el espacio entre el nombre de la variable y el paréntesis abierto).

El truco final para implementar arrays está en la clase Variable. Esta clase se usa para una instancia de una variable, y como comenté en la columna del mes pasado, es una subclase de Token. Sin embargo, también tiene algo de maquinaria para soportar matrices y eso es lo que mostraré a continuación:

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;

El código anterior muestra las variables de instancia asociadas a una variable, como en la clase ConstantExpression. Uno tiene que hacer una elección sobre el número de clases que se utilizarán versus la complejidad de una clase. Una opción de diseño podría ser construir una clase Variable que solo contenga variables escalares y luego agregar una subclase ArrayVariable para lidiar con las complejidades de los arrays. Elegí combinarlos, convirtiendo las variables escalares esencialmente en matrices de longitud 1.

Si lee el código anterior, verá índices y multiplicadores de matrices. Estos están aquí porque las matrices multidimensionales en BASIC se implementan utilizando una única matriz Java lineal. El índice lineal en el array Java se calcula manualmente utilizando los elementos del array multiplicador. Los índices utilizados en el programa BÁSICO se comprueban para verificar su validez comparándolos con el índice legal máximo en la matriz ndx de los índices.

Por ejemplo, una matriz BÁSICA con tres dimensiones de 10, 10 y 8, tendría los valores 10, 10 y 8 almacenados en ndx. Esto permite al evaluador de expresiones probar la condición de» índice fuera de límites » comparando el número utilizado en el programa BÁSICO con el número legal máximo que ahora está almacenado en ndx. La matriz de multiplicadores en nuestro ejemplo contendría los valores 1, 10 y 100. Estas constantes representan los números que se utilizan para asignar de una especificación de índice de matriz multidimensional a una especificación de índice de matriz lineal. La ecuación real es:

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

La siguiente matriz Java de la clase Variable se muestra a continuación.

 Expression expns;

La matriz expns se utiliza para tratar con matrices que se escriben como » A(10*B, i).»En ese caso, los índices son en realidad expresiones en lugar de constantes, por lo que la referencia debe contener punteros a aquellas expresiones que se evalúan en tiempo de ejecución. Por último, está esta pieza de código de aspecto bastante feo que calcula el índice en función de lo que se pasó en el programa. Este método privado se muestra a continuación.

 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; }

Mirando el código anterior, notará que el código primero comprueba que se usó el número correcto de índices al hacer referencia a la matriz, y luego que cada índice estaba dentro del rango legal para ese índice. Si se detecta un error, se lanza una excepción al intérprete. Los métodos numValue y stringValue devuelven un valor de la variable como un número o una cadena respectivamente. Estos dos métodos se muestran a continuación.

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

Hay métodos adicionales para establecer el valor de una variable que no se muestran aquí.

Al ocultar gran parte de la complejidad de cómo se implementa cada pieza, cuando finalmente llega el momento de ejecutar el programa BÁSICO, el código Java es bastante sencillo.

Ejecutar el código

El código para interpretar las instrucciones BÁSICAS y ejecutarlas está contenido en el

run

método de la

Program

clase. El código para este método se muestra a continuación, y lo revisaré para señalar las partes interesantes.

 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 }

El código anterior muestra que el método run toma un InputStream y un OutputStream para su uso como» consola » para el programa de ejecución. En la línea 3, el objeto de enumeración e se establece en el conjunto de instrucciones de la colección denominada stmts. Para esta colección utilicé una variación de un árbol de búsqueda binario llamado árbol «rojo-negro». (Para obtener más información sobre los árboles de búsqueda binarios, consulte mi columna anterior sobre creación de colecciones genéricas.) A continuación, se crean dos colecciones adicionales, una con Stack y otra con Vector. La pila se usa como la pila en cualquier computadora, pero el vector se usa expresamente para las declaraciones de DATOS en el programa BÁSICO. La colección final es otro árbol rojo-negro que contiene las referencias para las variables definidas por el programa BÁSICO. Este árbol es la tabla de símbolos que utiliza el programa mientras se ejecuta.

Después de la inicialización, se configuran los flujos de entrada y salida, y luego, si e no es null, comenzamos recopilando cualquier dato que se haya declarado. Esto se hace como se muestra en el siguiente código.

 /* 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); } }

El bucle anterior simplemente mira todas las instrucciones, y luego se ejecutan todas las instrucciones de DATOS que encuentre. La ejecución de cada instrucción de datos inserta los valores declarados por esa instrucción en el vector del almacén de datos. A continuación ejecutamos el programa adecuado, que se realiza utilizando este siguiente fragmento 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 puede ver en el código anterior, el primer paso es reiniciar e. El siguiente paso es obtener la primera instrucción en la variable s y luego ingresar al bucle de ejecución. Hay un código para verificar la entrada pendiente en el flujo de entrada para permitir que el progreso del programa se interrumpa escribiendo en el programa, y luego el bucle comprueba si la instrucción a ejecutar sería una instrucción de DATOS. Si lo es, el bucle omite la instrucción como ya se ha ejecutado. Se requiere la técnica bastante complicada de ejecutar todas las sentencias de datos primero porque BASIC permite que las sentencias de datos que satisfacen una sentencia de LECTURA aparezcan en cualquier lugar del código fuente. Por último, si se habilita el rastreo, se imprime un registro de rastreo y se invoca la instrucción s = s.execute(this, in, pout); muy poco expresiva. La belleza es que todo el esfuerzo de encapsular los conceptos básicos en clases fáciles de entender hace que el código final sea trivial. Si no es trivial, entonces tal vez tenga una pista de que podría haber otra forma de dividir su diseño.

Wrapping up and further thoughts

El intérprete fue diseñado para que pudiera ejecutarse como un hilo, por lo que puede haber varios hilos de intérprete COCOA que se ejecutan simultáneamente en el espacio del programa al mismo tiempo. Además, con el uso de la expansión de funciones podemos proporcionar un medio por el cual esos hilos pueden interactuar entre sí. Había un programa para el Apple II y más tarde para el PC y Unix llamado C-robots que era un sistema de entidades «robóticas» interactivas que se programaban utilizando un lenguaje derivado básico simple. El juego me proporcionó a mí y a otros muchas horas de entretenimiento, pero también fue una excelente manera de presentar los principios básicos de la computación a los estudiantes más jóvenes (que creían erróneamente que solo estaban jugando y no aprendiendo). Los subsistemas de intérpretes basados en Java son mucho más potentes que sus contrapartes pre-Java porque están disponibles instantáneamente en cualquier plataforma Java. COCOA se ejecutó en sistemas Unix y Macintosh el mismo día que empecé a trabajar en un PC con Windows 95. Mientras Java es golpeado por incompatibilidades en las implementaciones de herramientas de subprocesos o ventanas, lo que a menudo se pasa por alto es esto: Mucho código «simplemente funciona».»

Chuck McManis es actualmente el director de software de sistema en FreeGate Corp. , una start-up financiada por empresas que está explorando oportunidades en el mercado de Internet. Antes de unirse a FreeGate, Chuck era miembro del Grupo Java. Se unió al Grupo Java justo después de la formación de FirstPerson Inc. y fue un miembro del grupo portable OS (el grupo responsable de la Osporación de Java). Más tarde, cuando FirstPerson se disolvió, se quedó con el grupo a través del desarrollo de las versiones alfa y beta de la plataforma Java. Creó la primera página de inicio «all Java» en Internet cuando hizo la programación para la página de inicio de Javaversion of the Sun en mayo de 1995. También desarrolló una biblioteca acriptográfica para Java y versiones de Java classloader que podían filtrar clases basadas en firmas digitales.Antes de unirse a FirstPerson, Chuck trabajó en el área de sistemas operativos de SunSoft, desarrollando aplicaciones de red, donde realizó el diseño inicial de NIS+. Además, echa un vistazo a su página de inicio. : END_BIO

Más información sobre este tema

  • Aquí hay enlaces a los archivos de origen a los que se hace referencia anteriormente:
    • ConstantExpression.java
    • Expresión de funciones.Programa java
    • .Instrucción java
    • .java
    • StringExpression.Variable java
    • .java
    • Expresión variable.java
  • Y aquí hay una .Archivo ZIP de los archivos fuente:
    en profundidad.zip
  • «Uncommon Lisp» a un intérprete de Lisp escrito en Java
    http://user03.blue.aol.com/thingtone/workshop/lisp.htm
  • Intérprete NetRexx de Mike Cowlishaw escrito en Java
    http://www2.hursley.ibm.com/netrexx/
  • Una copia antigua de la FAQ BÁSICA de USENET (todavía tiene información útil en ella.)
    http://whitworth.me.ic.ac.uk/people/students/djbur/qbasic.htm
  • COCOA, un intérprete BÁSICO escrito en Java
    http://www.mcmanis.com/~cmcmanis/java/javaworld/examples/BASIC.html
  • Página de recursos Java de Chuck
    http://www.mcmanis.com/~cmcmanis/java/javaworld/
  • Intérprete TCL escrito en Java
    http://www.cs.cornell.edu/home/ioi/Jacl/
  • :
  • «Cómo construir un intérprete en Java, Parte 2La estructura»
    El truco para ensamblar las clases de base para un intérprete simple.
  • «How to build an interpreter in Java, Part 1The BASICs»
    Para aplicaciones complejas que requieren un lenguaje de scripting, Java se puede usar para implementar el intérprete, agregando capacidades de scripting a cualquier aplicación Java.
  • «Análisis léxico, Parte 2Build an application»
    Cómo usar el objeto StreamTokenizer para implementar una calculadora interactiva.
  • «Análisis léxico y JavaPart 1»
    Aprenda a convertir texto legible por humanos en datos legibles por máquinas utilizando las clases StringTokenizer y StreamTokenizer.
  • «Reutilización de código y sistemas orientados a objetos»
    Utilice una clase auxiliar para imponer el comportamiento dinámico.
  • «Soporte de contenedores para objetos en Java 1.0.2»
    Organizar objetos es fácil cuando los coloca en contenedores. Este artículo lo guía a través del diseño y la implementación de un contenedor.
  • «Los fundamentos de los cargadores de clases Java»
    Los fundamentos de este componente clave de la arquitectura Java.
  • «No usar recolección de elementos no utilizados»
    Minimice la manipulación de montones en sus programas Java.
  • «Hilos y applets y controles visuales»
    Esta parte final de la serie explora la lectura de múltiples canales de datos.
  • «Uso de canales de comunicación en applets, Parte 3»
    Desarrollar técnicas de estilo Visual Basic para diseñar applets — y convertir temperaturas en el proceso.
  • «Sincronización de subprocesos en Java, Parte II»
    Aprenda a escribir una clase de canal de datos y, a continuación, cree una aplicación de ejemplo simple que ilustre una implementación real de la clase.
  • » Sincronización de subprocesos en Java»
    Chuck McManis, antiguo desarrollador del equipo de Java, le explica un ejemplo sencillo que ilustra cómo sincronizar subprocesos para garantizar un comportamiento fiable y predecible de los applets.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.