Costruisci un interprete in Java-Implementa il motore di esecuzione

Per quelli di voi che si sono appena uniti a noi, nella mia colonna “Java In Depth” negli ultimi due mesi, ho discusso di come si potrebbe creare un interprete in Java. Nella prima colonna interpreter, abbiamo trattato alcuni degli attributi desiderabili di un interprete; nella seconda colonna abbiamo discusso sia l’analisi che il layout di un pacchetto di classi per l’implementazione dell’interprete. In questa colonna vedremo l’esecuzione dell’interprete e le classi di supporto necessarie per farlo. Infine, concluderò la serie qui con una discussione su come un interprete può essere collegato ad altre classi Java, migliorando così le proprie capacità.

Rivedere i bit rilevanti

Vorrei iniziare abbozzando ciò che abbiamo trattato finora e sottolineare quelle parti del design che diventeranno più importanti man mano che discutiamo della modalità di esecuzione. Per una descrizione più dettagliata di queste classi, fare riferimento alle mie colonne precedenti o ai collegamenti al codice sorgente che si trovano nella sezione

Risorse

di seguito.

Ci sono tre classi di base nell’implementazione dell’interprete, Program, Statemente Expression. Quanto segue mostra come i tre sono correlati:

Programma

Questa classe Program incolla insieme i componenti di analisi ed esecuzione del parser. Questa classe definisce due metodi principali, loade run. Il metodo load legge le istruzioni da un flusso di input e le analizza in una raccolta di istruzioni, il metodo run itera la raccolta ed esegue ciascuna delle istruzioni. La classe Program fornisce anche una raccolta di variabili da utilizzare per il programma e uno stack per la memorizzazione dei dati.

Istruzione

La classe Statement contiene una singola istruzione analizzata. Questa classe è in realtà sottoclasse in un tipo specifico di istruzione (PRINT, GOTO, IF e così via) ma tutte le istruzioni contengono il metodo execute che viene chiamato per eseguire l’istruzione nel contesto di un’istanza di classe Program.

Espressione

La classe Expression contiene l’albero di analisi di un’espressione. Durante l’esecuzione, il metodo value viene utilizzato per valutare l’espressione e restituirne il valore. Come Statement, la classe Expression è progettata principalmente per essere sottoclassata da specifici tipi di espressioni.

Tutte queste classi lavorano insieme per formare la base di un interprete. La classe Program incapsula simultaneamente l’operazione di analisi e l’operazione di esecuzione, mentre le classi Statement e Expression incapsulano i concetti computazionali effettivi del linguaggio che abbiamo implementato. Per questi tre articoli sulla costruzione di interpreti, il linguaggio di esempio è stato di base.

Strutture per il calcolo

Ci sono due classi eseguibili nell’interprete,

Statement

e

Expression

. Per prima cosa diamo un’occhiata a

Expression

Le istanze di Expression vengono create con il metodo expressionnella classe ParseExpression. La classe ParseExpression implementa il parser di espressioni in questo interprete. Questa classe è un peer della classe ParseStatement, che utilizza il metodo statement per analizzare le istruzioni di BASE. Le istanze di Expression hanno un type interno che identifica quale operatore rappresenta l’istanza e due metodi, value e stringValue, che restituiscono il valore calcolato dell’espressione. Inoltre, quando viene creata un’istanza di espressione, vengono dati nominalmente due parametri che rappresentano il lato sinistro e destro dell’operazione dell’espressione. Mostrato in forma di origine, la prima parte dell’espressione è la seguente:

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

Come puoi vedere nel codice sopra, ci sono le variabili di istanza, un tipo di operatore chiamato oper e due metà dell’operazione in arg1 e arg2, e poi alcune costanti per definire i vari tipi. Inoltre, ci sono due costruttori che vengono utilizzati dal parser di espressioni; questi creano una nuova espressione da due espressioni. La loro fonte è mostrata di seguito:

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

Il primo costruttore costruisce un oggetto espressione arbitraria, e il secondo costruisce un oggetto espressione “unario” – come meno unario. Una cosa da notare è che se c’è un solo argomento, arg2 viene utilizzato per memorizzare il suo valore.

Il metodo utilizzato più frequentemente nella classe Expression è value, definito come segue:

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

Puoi vedere che ogni oggetto expression rappresenta una tupla composta da un operatore e uno o due argomenti. La parte divertente della progettazione del motore di esecuzione delle espressioni in questo modo è che quando si costruisce questo insieme di tuple di espressioni basate sull’oggetto Expression, è possibile calcolare il valore dell’espressione semplicemente invocando il metodo value. Il metodo value richiama ricorsivamente il metodo value dei due argomenti che compongono questa espressione, applica l’operazione a loro e restituisce il risultato. Questo disegno è stato utilizzato in modo che le espressioni sarebbero facili da capire.

Per mantenere pulita la struttura della classe, tutte le unità computazionali-dalle costanti alle funzioni trigonometriche-sono sottoclassi di Expression. Questa idea, rubata spudoratamente dal Lisp, incapsula completamente la nozione di “causare” una valutazione, dall’effettiva implementazione di “come” si verifica tale valutazione. Per dimostrare come viene applicato questo principio, dobbiamo solo guardare alcune delle sottoclassi specializzate di Expression.

Le costanti nella mia versione di BASIC, che ho chiamato COCOA, sono rappresentate dalla classe ConstantExpression, che sottoclasse Expression e memorizza semplicemente il valore numerico in un valore membro. Il codice sorgente a ConstantExpression è mostrato concettualmente di seguito. Dico “concettualmente” perché ho scelto di raggruppare ciò che sarebbe stato StringConstantExpression e NumericConstantExpression in una singola classe. Quindi la classe reale include un costruttore per creare una costante con un argomento stringa e per restituire il suo valore come stringa. Il codice seguente mostra come la classe ConstantExpression gestisce le costanti numeriche.

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

Il codice mostrato sopra sostituisce i costruttori più complicati di Expression con un semplice archivio di una variabile di istanza; il metodo value viene semplicemente sostituito con un ritorno del valore memorizzato.

È vero che potresti codificare la classe Expression per accettare nei suoi costruttori una costante che ti salverebbe una classe. Tuttavia, un vantaggio di progettare Expression nel modo in cui ho è che il codice in Expression rimane al massimo generico. Trovo che questo stile di codifica mi aiuti ad eliminare la complessità dei casi speciali e quindi quando ho “finito” con il codice Expression, posso passare ad altri aspetti delle espressioni senza rivisitare la classe base ancora e ancora. Il vantaggio diventa più chiaro quando approfondiamo un’altra sottoclasse di Expression denominata FunctionExpression.

Nella classe FunctionExpression, c’erano due requisiti di progettazione che ritenevo dovessero essere soddisfatti per mantenere l’interprete flessibile. Il primo è stato quello di implementare le funzioni di base standard; l’altro era incapsulare l’analisi degli argomenti delle funzioni nella stessa classe che implementava queste funzioni. Il secondo requisito, l’analisi, era motivato dal desiderio di rendere questa base estensibile creando librerie di funzioni aggiuntive che potevano essere passate al parser come sottoclassi di FunctionExpression. Inoltre, quelle classi passate potrebbero essere utilizzate dal parser per aumentare il numero di funzioni disponibili per il programma dell’utente.

La classe FunctionExpression è solo moderatamente più complicata di una ConstantExpression ed è mostrata in forma condensata di seguito:

 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 fonte sopra mostra come viene implementato il metodo value. La variabile oper viene riutilizzata per mantenere l’identità della funzione e gli oggetti Expression a cui fa riferimento arg1 e arg2 vengono utilizzati come argomenti per le funzioni stesse. Infine, c’è una grande istruzione switch che invia la richiesta. Un aspetto interessante è che il metodo valuecattura le potenziali eccezioni aritmetiche e le converte in istanze di BASICRuntimeError. Il codice di analisi in FunctionExpression è mostrato di seguito, nuovamente condensato per risparmiare spazio. (Ricorda, tutto il codice sorgente è disponibile utilizzando i collegamenti nella sezione Risorse.)

 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 }

Si noti che questo codice sfrutta il fatto che il parser di espressioni in ParseStatement ha già capito che sta guardando un’espressione e ha passato l’identità dell’espressione come parametro ty. Questo parser quindi deve solo individuare la parentesi di apertura e la parentesi di chiusura che contengono gli argomenti. Ma guarda attentamente: nelle righe da 9 a 18, il parser consente ad alcune funzioni di non avere argomenti (in questo caso RND e FRE). Ciò dimostra la flessibilità offerta dall’avere il subparser di funzioni integrato in questa classe, piuttosto che forzare tutte le funzioni a conformarsi a un modello predefinito. Dato un tipo di funzione nel parametro ty, l’istruzione switch seleziona un ramo che può analizzare gli argomenti richiesti per quella funzione, siano essi stringhe, numeri, altre espressioni e così via.

Altri aspetti: Stringhe e array

Altre due parti del linguaggio BASIC sono implementate dall’interprete COCOA: stringhe e array. Diamo un’occhiata prima all’implementazione delle stringhe.

Per implementare le stringhe come variabili, la classe Expression è stata modificata per includere la nozione di espressioni “string”. Questa modifica ha assunto la forma di due aggiunte: isString e stringValue. La fonte per questi due nuovi metodi è mostrata di seguito.

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

Chiaramente, non è troppo utile per un programma di BASE per ottenere il valore stringa di un’espressione di base (che è sempre un’espressione numerica o booleana). Si potrebbe concludere dalla mancanza di utilità che questi metodi non appartenevano a Expression e appartenevano invece a una sottoclasse di Expression. Tuttavia, inserendo questi due metodi nella classe base, tutti gli oggetti Expression possono essere testati per vedere se, in realtà, sono stringhe.

Un altro approccio di progettazione consiste nel restituire i valori numerici come stringhe utilizzando un oggetto StringBuffer per generare un valore. Quindi, ad esempio, lo stesso codice potrebbe essere riscritto come:

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

E se viene utilizzato il codice sopra, è possibile eliminare l’uso di isString perché ogni espressione può restituire un valore stringa. Inoltre, è possibile modificare il metodo value per provare a restituire un numero se l’espressione restituisce una stringa eseguendola attraverso il metodo valueOfdi java.lang.Double. In molte lingue come Perl, TCL e REXX, questo tipo di digitazione amorfa viene utilizzato con grande vantaggio. Entrambi gli approcci sono validi e dovresti fare la tua scelta in base al design del tuo interprete. In BASIC, l’interprete deve restituire un errore quando una stringa viene assegnata a una variabile numerica, quindi ho scelto il primo approccio (restituendo un errore).

Per quanto riguarda gli array, ci sono diversi modi in cui puoi progettare il tuo linguaggio per interpretarli. C utilizza le parentesi quadre attorno agli elementi dell’array per distinguere i riferimenti all’indice dell’array dai riferimenti alle funzioni che hanno parentesi attorno ai loro argomenti. Tuttavia, i progettisti del linguaggio per BASIC hanno scelto di utilizzare le parentesi sia per le funzioni che per gli array, quindi quando il testo NAME(V1, V2) viene visualizzato dal parser, potrebbe essere una chiamata di funzione o un riferimento all’array.

L’analizzatore lessicale discrimina tra token seguiti da parentesi supponendo che siano funzioni e test per questo. Poi continua a vedere se sono parole chiave o variabili. È questa decisione che impedisce al tuo programma di definire una variabile denominata ” SIN.”Qualsiasi variabile il cui nome corrispondesse a un nome di funzione sarebbe invece restituita dall’analizzatore lessicale come token di funzione. Il secondo trucco utilizzato dall’analizzatore lessicale è quello di verificare se il nome della variabile è immediatamente seguito da `(‘. Se lo è, l’analizzatore presuppone che sia un riferimento di matrice. Analizzando questo nell’analizzatore lessicale, eliminiamo la stringa ‘MYARRAY ( 2 ) ‘ dall’essere interpretata come una matrice valida (notare lo spazio tra il nome della variabile e la parentesi aperta).

Il trucco finale per implementare gli array è nella classe Variable. Questa classe viene utilizzata per un’istanza di una variabile e, come ho discusso nella colonna del mese scorso, è una sottoclasse di Token. Tuttavia, ha anche alcuni macchinari per supportare gli array e questo è ciò che mostrerò di seguito:

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;

Il codice precedente mostra le variabili di istanza associate a una variabile, come nella classe ConstantExpression. Bisogna fare una scelta sul numero di classi da utilizzare rispetto alla complessità di una classe. Una scelta di progettazione potrebbe essere quella di creare una classe Variable che contenga solo variabili scalari e quindi aggiungere una sottoclasse ArrayVariable per gestire le complessità degli array. Ho scelto di combinarli, trasformando le variabili scalari essenzialmente in array di lunghezza 1.

Se leggi il codice sopra, vedrai indici e moltiplicatori di array. Questi sono qui perché gli array multidimensionali in BASIC sono implementati utilizzando un singolo array Java lineare. L’indice lineare nell’array Java viene calcolato manualmente utilizzando gli elementi dell’array moltiplicatore. Gli indici utilizzati nel programma BASIC vengono controllati per la validità confrontandoli con l’indice legale massimo nell’array ndx degli indici.

Ad esempio, un array di BASE con tre dimensioni di 10, 10 e 8, avrebbe i valori 10, 10 e 8 memorizzati in ndx. Ciò consente al valutatore di espressioni di verificare una condizione di “indice fuori limite” confrontando il numero utilizzato nel programma BASIC con il numero massimo legale ora memorizzato in ndx. L’array moltiplicatore nel nostro esempio conterrebbe i valori 1, 10 e 100. Queste costanti rappresentano i numeri che si utilizzano per mappare da una specifica di indice di array multidimensionale in una specifica di indice di array lineare. L’equazione reale è:

Java Index = Index1 + Index2 * Dimensione massima di Index1 + Index3 *(MaxSize of Index1 * MaxSizeIndex 2)

Il prossimo array Java nella classe Variable è mostrato di seguito.

 Expression expns;

L’array expns viene utilizzato per gestire array scritti come “A(10*B, i).”In tal caso, gli indici sono in realtà espressioni piuttosto che costanti, quindi il riferimento deve contenere puntatori a quelle espressioni che vengono valutate in fase di esecuzione. Infine c’è questo pezzo di codice abbastanza brutto che calcola l’indice in base a ciò che è stato passato nel programma. Questo metodo privato è mostrato di seguito.

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

Guardando il codice sopra, noterai che il codice prima controlla che sia stato utilizzato il numero corretto di indici quando si fa riferimento all’array e quindi che ogni indice era all’interno dell’intervallo legale per quell’indice. Se viene rilevato un errore, viene generata un’eccezione all’interprete. I metodi numValuee stringValue restituiscono un valore dalla variabile rispettivamente come numero o stringa. Questi due metodi sono mostrati di seguito.

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

Esistono metodi aggiuntivi per impostare il valore di una variabile che non sono mostrati qui.

Nascondendo gran parte della complessità di come ogni pezzo è implementato, quando finalmente arriva il momento di eseguire il programma di base, il codice Java è abbastanza semplice.

Esecuzione del codice

Il codice per interpretare le istruzioni di BASE ed eseguirle è contenuto nel

run

metodo dell’

Program

classe. Il codice per questo metodo è mostrato di seguito, e io passo attraverso di esso per sottolineare le parti interessanti.

 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 }

Il codice precedente mostra che il metodo run richiede un InputStream e un OutputStream da utilizzare come “console” per il programma in esecuzione. Nella riga 3, l’oggetto enumerazione e viene impostato sull’insieme di istruzioni della raccolta denominata stmts. Per questa raccolta ho usato una variazione su un albero di ricerca binario chiamato albero “rosso-nero”. (Per ulteriori informazioni sugli alberi di ricerca binari, vedere la mia colonna precedente sulla creazione di raccolte generiche.) A seguito di ciò, vengono create due raccolte aggiuntive: una con Stacke una con Vector. Lo stack viene utilizzato come lo stack in qualsiasi computer, ma il vettore viene utilizzato espressamente per le istruzioni di dati nel programma di base. La raccolta finale è un altro albero rosso-nero che contiene i riferimenti per le variabili definite dal programma di BASE. Questo albero è la tabella dei simboli utilizzata dal programma durante l’esecuzione.

Dopo l’inizializzazione, vengono impostati i flussi di input e output, quindi se e non è null, iniziamo raccogliendo tutti i dati che sono stati dichiarati. Questo viene fatto come mostrato nel seguente codice.

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

Il ciclo precedente esamina semplicemente tutte le istruzioni e tutte le istruzioni di DATI che trova vengono quindi eseguite. L’esecuzione di ogni istruzione DATI inserisce i valori dichiarati da tale istruzione nel vettore dataStore. Successivamente eseguiamo il programma corretto, che viene fatto usando questo successivo pezzo di codice:

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

Come puoi vedere nel codice sopra, il primo passo è reinizializzare e. Il prossimo passo è recuperare la prima istruzione nella variabile s e quindi entrare nel ciclo di esecuzione. C’è del codice per controllare l’input in sospeso sul flusso di input per consentire l’interruzione del progresso del programma digitando il programma, quindi il ciclo controlla se l’istruzione da eseguire sarebbe un’istruzione di DATI. Se lo è, il ciclo salta l’istruzione poiché è già stata eseguita. La tecnica piuttosto complicata di eseguire prima tutte le istruzioni di dati è necessaria perché BASIC consente alle istruzioni di DATI che soddisfano un’istruzione di LETTURA di apparire in qualsiasi punto del codice sorgente. Infine, se la traccia è abilitata, viene stampato un record di traccia e viene invocata l’istruzione molto poco significativa s = s.execute(this, in, pout);. Il bello è che tutto lo sforzo di incapsulare i concetti di base in classi facili da capire rende banale il codice finale. Se non è banale, forse hai la minima idea che potrebbe esserci un altro modo per dividere il tuo design.

Conclusione e ulteriori pensieri

L’interprete è stato progettato in modo che potesse essere eseguito come thread, quindi possono esserci diversi thread dell’interprete COCOA in esecuzione contemporaneamente nello spazio del programma allo stesso tempo. Inoltre, con l’uso dell’espansione della funzione possiamo fornire un mezzo per cui quei thread possono interagire tra loro. C’era un programma per Apple II e successivamente per PC e Unix chiamato C-robots che era un sistema di entità “robotiche” interagenti che venivano programmate usando un semplice linguaggio derivato di BASE. Il gioco ha fornito a me e agli altri molte ore di intrattenimento, ma è stato anche un ottimo modo per introdurre i principi di base del calcolo agli studenti più giovani (che credevano erroneamente che stessero solo giocando e non imparando). I sottosistemi di interpreti basati su Java sono molto più potenti delle loro controparti pre-Java perché sono immediatamente disponibili su qualsiasi piattaforma Java. COCOA è stato eseguito su sistemi Unix e Macintosh lo stesso giorno in cui ho lavorato su un PC basato su Windows 95. Mentre Java viene picchiato da incompatibilità nelle implementazioni di thread o window toolkit, ciò che viene spesso trascurato è questo: un sacco di codice “funziona solo.”

Chuck Mcmanè attualmente il direttore del software di sistema presso FreeGate Corp., una start-up finanziata da venture capital che sta esplorando opportunità nel mercato di Internet. Prima di unirsi a FreeGate, Chuck era un membro del gruppo Java. Si è unito al gruppo Java subito dopo la formazione di FirstPerson Inc. e wasun membro del portable OS group (il gruppo responsabile dell’OSportion di Java). Più tardi, quando FirstPerson fu sciolto, rimase con il gruppo attraverso lo sviluppo di alpha e betaversions della piattaforma Java. Ha creato la prima homepage” all Java ” su Internet quando ha fatto la programmazione per la Javaversion della home page di Sun nel maggio 1995. Ha anche sviluppato una libreria acriptografica per Java e versioni del classloader Java che potrebbero schermare le classi basate su firme digitali.Prima di entrare in FirstPerson, Chuck ha lavorato nell’area dei sistemi operativi di SunSoft, sviluppando applicazioni di rete, dove ha realizzato la progettazione iniziale di NIS+. Inoltre, controlla la sua home page. : END_BIO

Ulteriori informazioni su questo argomento

  • Ecco i link ai file sorgente di cui sopra:
    • ConstantExpression.java
    • FunctionExpression.java
    • Programma.java
    • Istruzione.java
    • StringExpression.java
    • Variabile.java
    • VariableExpression.java
  • Ed ecco un .File ZIP dei file sorgente:
    in profondità.zip
  • “Uncommon Lisp” a un interprete Lisp scritto in Java
    http://user03.blue.aol.com/thingtone/workshop/lisp.htm
  • L’interprete NetREXX di Mike Cowlishaw scritto in Java
    http://www2.hursley.ibm.com/netrexx/
  • Una vecchia copia delle FAQ di USENET BASIC (contiene ancora informazioni utili.)
    http://whitworth.me.ic.ac.uk/people/students/djbur/qbasic.htm
  • COCOA, un interprete BASIC scritto in Java
    http://www.mcmanis.com/~cmcmanis/java/javaworld/examples/BASIC.html
  • Pagina risorse Java di Chuck
    http://www.mcmanis.com/~cmcmanis/java/javaworld/
  • Interprete TCL scritto in Java
    http://www.cs.cornell.edu/home/ioi/Jacl/
  • :
  • “Come costruire un interprete in Java, Parte 2La struttura”
    Il trucco per assemblare le classi foundation per un semplice interprete.
  • “Come costruire un interprete in Java, Parte 1le basi”
    Per applicazioni complesse che richiedono un linguaggio di scripting, Java può essere utilizzato per implementare l’interprete, aggiungendo capacità di scripting a qualsiasi app Java.
  • “Analisi lessicale, Parte 2costruire un’applicazione”
    Come utilizzare l’oggetto StreamTokenizer per implementare una calcolatrice interattiva.
  • ” Lexical analysis and JavaPart 1″
    Scopri come convertire il testo leggibile dall’uomo in dati leggibili dalla macchina utilizzando le classi StringTokenizer e StreamTokenizer.
  • “Riutilizzo del codice e sistemi orientati agli oggetti”
    Utilizzare una classe helper per applicare il comportamento dinamico.
  • ” Supporto contenitore per oggetti in Java 1.0.2″
    Organizzare gli oggetti è facile quando li metti in contenitori. Questo articolo ti guida attraverso la progettazione e l’implementazione di un contenitore.
  • ” Le basi dei caricatori di classe Java”
    I fondamenti di questo componente chiave dell’architettura Java.
  • ” Non utilizzare la garbage collection”
    Ridurre al minimo il thrashing dell’heap nei programmi Java.
  • “Thread e applet e controlli visivi”
    Questa parte finale della serie esplora la lettura di più canali di dati.
  • “Utilizzo dei canali di comunicazione nelle applet, Parte 3”
    Sviluppa tecniche in stile Visual Basic per progettare le applet — e convertire le temperature nel processo.
  • ” Sincronizzazione dei thread in Java, Parte II”
    Scopri come scrivere una classe di canale dati e quindi creare una semplice applicazione di esempio che illustra un’implementazione reale della classe.
  • ” Sincronizzazione dei thread in Java”
    L’ex sviluppatore del team Java Chuck McManis ti guida attraverso un semplice esempio che illustra come sincronizzare i thread per assicurare un comportamento affidabile e prevedibile delle applet.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.