Pour ceux d’entre vous qui viennent de nous rejoindre, dans ma colonne « Java en profondeur » au cours des deux derniers mois, j’ai discuté de la façon dont on pourrait construire un interpréteur en Java. Dans la première colonne interpréteur, nous avons couvert certains des attributs souhaitables d’un interpréteur ; dans la deuxième colonne, nous avons discuté à la fois de l’analyse syntaxique et de la disposition d’un package de classe pour implémenter l’interpréteur. Dans cette colonne, nous examinerons l’exécution de l’interpréteur et les classes de support nécessaires pour y parvenir. Enfin, je terminerai la série ici avec une discussion sur la façon dont un interpréteur peut être connecté à d’autres classes Java, améliorant ainsi leurs capacités.
Examen des bits pertinents
Permettez-moi de commencer par esquisser ce que nous avons couvert jusqu’à présent et de souligner les parties de la conception qui deviendront plus importantes à mesure que nous discuterons du mode d’exécution. Pour une description plus détaillée de ces classes, reportez-vous à mes colonnes précédentes ou aux liens de code source qui se trouvent dans la section
Ressources
ci-dessous.
Il existe trois classes de base dans l’implémentation de l’interpréteur, Program
, Statement
et Expression
. Ce qui suit montre comment les trois sont liés:
Programme
Cette classe
Program
colle les composants d’analyse et d’exécution de l’analyseur. Cette classe définit deux méthodes principales,load
etrun
. La méthodeload
lit les instructions d’un flux d’entrée et les analyse en une collection d’instructions, la méthoderun
itère sur la collection et exécute chacune des instructions. La classeProgram
fournit également une collection de variables à utiliser par le programme ainsi qu’une pile pour stocker les données.
Instruction
La classe
Statement
contient une seule instruction analysée. Cette classe est en fait sous-classée dans un type d’instruction spécifique (PRINT, GOTO, IF, etc.) mais toutes les instructions contiennent la méthodeexecute
qui est appelée pour exécuter l’instruction dans le contexte d’une instance de classeProgram
.
Expression
La classe
Expression
contient l’arborescence d’analyse d’une expression. Lors de l’exécution, la méthodevalue
est utilisée pour évaluer l’expression et renvoyer sa valeur. CommeStatement
, la classeExpression
est principalement conçue pour être sous-classée par des types d’expressions spécifiques.
Toutes ces classes travaillent ensemble pour former la base d’un interpréteur. La classe Program
encapsule simultanément l’opération d’analyse et l’opération d’exécution, tandis que les classes Statement
et Expression
encapsulent les concepts de calcul réels du langage que nous avons implémenté. Pour ces trois articles sur la construction d’interprètes, le langage d’exemple a été BASIQUE.
Facilités de calcul
Il existe deux classes exécutables dans l’interpréteur,
Statement
et
Expression
. D’abord, jetons un coup d’œil à
Expression
Les instances de Expression
sont créées par la méthode expression
dans la classe ParseExpression
. La classe ParseExpression
implémente l’analyseur d’expression dans cet interpréteur. Cette classe est un homologue de la classe ParseStatement
, qui utilise la méthode statement
pour analyser les instructions DE BASE. Les instances de Expression
ont un type
interne qui identifie l’opérateur que représente l’instance, et deux méthodes, value
et stringValue
, qui renvoient la valeur calculée de l’expression. De plus, lorsqu’une instance d’expression est créée, elle reçoit nominalement deux paramètres représentant les côtés gauche et droit de l’opération de l’expression. Représentée sous forme source, la première partie de l’expression est la suivante:
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
Comme vous pouvez le voir dans le code ci-dessus, il y a les variables d’instance, un type d’opérateur nommé oper et deux moitiés de l’opération dans arg1 et arg2, puis quelques constantes pour définir les différents types. En outre, deux constructeurs sont utilisés par l’analyseur d’expression ; ceux-ci créent une nouvelle expression à partir de deux expressions. Leur source est indiquée ci-dessous:
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); }
Le premier constructeur construit un objet d’expression arbitraire, et le second construit un objet d’expression « unaire » – tel que unaire moins. Une chose à noter est que s’il n’y a qu’un seul argument, arg2 est utilisé pour stocker sa valeur.
La méthode la plus utilisée dans la classe Expression
est value
, qui est définie comme suit:
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. ...
Vous pouvez voir que chaque objet expression représente un tuple composé d’un opérateur et d’un ou deux arguments. La partie amusante de la conception du moteur d’exécution d’expression de cette manière est que lorsque vous construisez cet ensemble de tuples d’expression basés sur l’objet Expression
, vous pouvez calculer la valeur de l’expression en invoquant simplement la méthode value
. La méthode value
appelle récursivement la méthode value
des deux arguments qui composent cette expression, leur applique l’opération et renvoie le résultat. Cette conception a été utilisée pour que les expressions soient faciles à comprendre.
Pour garder la structure de classe propre, toutes les unités de calcul – des constantes aux fonctions trigonométriques – sont des sous-classes de Expression
. Cette idée, volée sans vergogne à Lisp, résume complètement la notion de « provoquer » une évaluation, de la mise en œuvre réelle du « comment » cette évaluation se produit. Pour démontrer comment ce principe est appliqué, il suffit d’examiner certaines des sous-classes spécialisées de Expression
.
Les constantes de ma version de BASIC, que j’ai nommée COCOA, sont représentées par la classe ConstantExpression
, qui sous-classe Expression
et stocke simplement la valeur numérique dans une valeur membre. Le code source à ConstantExpression
est présenté conceptuellement ci-dessous. Je dis « conceptuellement » parce que j’ai choisi de regrouper ce qui aurait été StringConstantExpression
et NumericConstantExpression
en une seule classe. Ainsi, la classe real inclut un constructeur pour créer une constante avec un argument de chaîne et pour renvoyer sa valeur sous forme de chaîne. Le code suivant montre comment la classe ConstantExpression
gère les constantes numériques.
class ConstantExpression extends Expression { private double v; ConstantExpression(double a) { super(); v = a; } double value(Program pgm) throws BASICRuntimeError { return v; }}
Le code ci-dessus remplace les constructeurs les plus compliqués de Expression
par un simple stockage d’une variable d’instance ; la méthode value
est simplement remplacée par un retour de la valeur stockée.
Il est vrai que vous pourriez coder la classe Expression
pour accepter dans ses constructeurs une constante qui vous sauverait une classe. Cependant, un avantage de concevoir Expression
comme je l’ai fait est que le code dans Expression
reste au maximum générique. Je trouve que ce style de codage m’aide à éliminer la complexité des cas particuliers et donc quand j’ai « fini » avec le code Expression
, je peux passer à d’autres aspects des expressions sans revisiter encore et encore la classe de base. L’avantage devient plus clair lorsque nous nous plongeons dans une autre sous-classe de Expression
nommée FunctionExpression
.
Dans la classe FunctionExpression
, il y avait deux exigences de conception qui, selon moi, devraient être satisfaites pour garder l’interpréteur flexible. La première consistait à implémenter les fonctions DE BASE standard; l’autre consistait à encapsuler l’analyse des arguments de fonctions dans la même classe qui implémentait ces fonctions. La deuxième exigence, l’analyse syntaxique, était motivée par le désir de rendre cette BASE extensible en créant des bibliothèques de fonctions supplémentaires qui pourraient être transmises à l’analyseur en tant que sous-classes de FunctionExpression
. De plus, ces classes transmises pourraient être utilisées par l’analyseur pour augmenter le nombre de fonctions disponibles pour le programme de l’utilisateur.
La classe FunctionExpression
n’est que modérément plus compliquée que la classe ConstantExpression
et est présentée sous forme condensée ci-dessous:
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 source ci-dessus montre comment la méthode value
est implémentée. La variable oper est réutilisée pour contenir l’identité de la fonction, et les objets d’expression référencés par arg1 et arg2 sont utilisés comme arguments pour les fonctions elles-mêmes. Enfin, il existe une grande instruction switch qui envoie la demande. Un aspect intéressant est que la méthode value
capture les exceptions arithmétiques potentielles et les convertit en instances de BASICRuntimeError
. Le code d’analyse dans FunctionExpression
est montré ci-dessous, à nouveau condensé pour économiser de l’espace. (N’oubliez pas que tout le code source est disponible à l’aide de liens dans la section Ressources.)
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 }
Notez que ce code tire parti du fait que l’analyseur d’expression dans ParseStatement
a déjà compris qu’il regarde une expression et a transmis l’identité de l’expression en tant que paramètre ty. Cet analyseur n’a alors qu’à localiser la parenthèse d’ouverture et la parenthèse de fermeture qui contiennent le ou les arguments. Mais regardez bien: Dans les lignes #9 à #18, l’analyseur permet à certaines fonctions de n’avoir aucun argument (dans ce cas RND et FRE). Cela démontre la flexibilité offerte par le sous-analyseur de fonction intégré à cette classe, plutôt que de forcer toutes les fonctions à se conformer à un modèle prédéfini. Étant donné un type de fonction dans le paramètre ty, l’instruction switch sélectionne une branche qui peut analyser les arguments requis pour cette fonction, qu’il s’agisse de chaînes, de nombres, d’autres expressions, etc.
Autres aspects : Chaînes et tableaux
Deux autres parties du langage DE BASE sont implémentées par l’interpréteur COCOA : chaînes et tableaux. Regardons d’abord l’implémentation des chaînes.
Pour implémenter des chaînes en tant que variables, la classe Expression
a été modifiée pour inclure la notion d’expressions « chaîne ». Cette modification a pris la forme de deux ajouts : isString
et stringValue
. La source de ces deux nouvelles méthodes est présentée ci-dessous.
String stringValue(Program pgm) throws BASICRuntimeError { throw new BASICRuntimeError("No String representation for this."); } boolean isString() { return false; }
Clairement, il n’est pas trop utile pour un programme DE BASE d’obtenir la valeur de chaîne d’une expression de base (qui est toujours une expression numérique ou booléenne). Vous pouvez conclure du manque d’utilité que ces méthodes n’appartenaient alors pas à Expression
et appartenaient à une sous-classe de Expression
à la place. Cependant, en plaçant ces deux méthodes dans la classe de base, tous les objets Expression
peuvent être testés pour voir si, en fait, ce sont des chaînes.
Une autre approche de conception consiste à renvoyer les valeurs numériques sous forme de chaînes à l’aide d’un objet StringBuffer
pour générer une valeur. Ainsi, par exemple, le même code pourrait être réécrit comme:
String stringValue(Program pgm) throws BASICRuntimeError { StringBuffer sb = new StringBuffer(); sb.append(this.value(pgm)); return sb.toString(); }
Et si le code ci-dessus est utilisé, vous pouvez éliminer l’utilisation de isString
car chaque expression peut renvoyer une valeur de chaîne. De plus, vous pouvez modifier la méthode value
pour essayer de renvoyer un nombre si l’expression est évaluée en chaîne en l’exécutant via la méthode valueOf
de java.lang.Double
. Dans de nombreux langages tels que Perl, TCL et REXX, ce type de typage amorphe est très utilisé. Les deux approches sont valables et vous devez faire votre choix en fonction de la conception de votre interprète. En BASIC, l’interpréteur doit renvoyer une erreur lorsqu’une chaîne est affectée à une variable numérique, j’ai donc choisi la première approche (renvoyer une erreur).
En ce qui concerne les tableaux, vous pouvez concevoir votre langage de différentes manières pour les interpréter. C utilise les crochets autour des éléments du tableau pour distinguer les références d’index du tableau des références de fonction qui ont des parenthèses autour de leurs arguments. Cependant, les concepteurs de langage pour BASIC ont choisi d’utiliser des parenthèses pour les fonctions et les tableaux, de sorte que lorsque le texte NAME(V1, V2)
est vu par l’analyseur, il peut s’agir d’un appel de fonction ou d’une référence de tableau.
L’analyseur lexical distingue les jetons suivis de parenthèses en supposant d’abord qu’ils sont des fonctions et en les testant. Ensuite, il continue pour voir s’il s’agit de mots clés ou de variables. C’est cette décision qui empêche votre programme de définir une variable nommée « SIN. »Toute variable dont le nom correspond à un nom de fonction serait renvoyée par l’analyseur lexical en tant que jeton de fonction à la place. La deuxième astuce utilisée par l’analyseur lexical consiste à vérifier si le nom de la variable est immédiatement suivi de `(‘. Si c’est le cas, l’analyseur suppose qu’il s’agit d’une référence de tableau. En analysant cela dans l’analyseur lexical, nous éliminons l’interprétation de la chaîne `MYARRAY ( 2 )
‘ comme un tableau valide (notez l’espace entre le nom de la variable et la parenthèse ouverte).
La dernière astuce pour implémenter des tableaux se trouve dans la classe Variable
. Cette classe est utilisée pour une instance d’une variable, et comme je l’ai discuté dans la colonne du mois dernier, c’est une sous-classe de Token
. Cependant, il dispose également de machines pour prendre en charge les tableaux et c’est ce que je vais montrer ci-dessous:
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;
Le code ci-dessus affiche les variables d’instance associées à une variable, comme dans la classe ConstantExpression
. Il faut faire un choix sur le nombre de classes à utiliser par rapport à la complexité d’une classe. Un choix de conception peut consister à créer une classe Variable
qui ne contient que des variables scalaires, puis à ajouter une sous-classe ArrayVariable
pour gérer les subtilités des tableaux. J’ai choisi de les combiner, transformant les variables scalaires essentiellement en tableaux de longueur 1.
Si vous lisez le code ci-dessus, vous verrez des indices de tableau et des multiplicateurs. Ceux-ci sont ici parce que les tableaux multidimensionnels en BASIC sont implémentés en utilisant un seul tableau Java linéaire. L’indice linéaire dans le tableau Java est calculé manuellement à l’aide des éléments du tableau multiplicateur. La validité des indices utilisés dans le programme DE BASE est vérifiée en les comparant à l’indice légal maximal dans le tableau ndx des indices.
Par exemple, un tableau DE BASE avec trois dimensions de 10, 10 et 8 aurait les valeurs 10, 10 et 8 stockées dans ndx. Cela permet à l’évaluateur d’expression de tester une condition « index hors limites » en comparant le nombre utilisé dans le programme DE BASE au nombre légal maximum qui est maintenant stocké dans ndx. Le tableau multiplicateur de notre exemple contiendrait les valeurs 1, 10 et 100. Ces constantes représentent les nombres que l’on utilise pour mapper d’une spécification d’index de tableau multidimensionnel à une spécification d’index de tableau linéaire. L’équation réelle est:
Index Java = Index1 + Index2 * Taille maximale d’Index1 + Index3 * (Taille maximale d’Index1 * MaxSizeIndex 2)
Le tableau Java suivant de la classe Variable
est illustré ci-dessous.
Expression expns;
Le tableau expns est utilisé pour traiter les tableaux écrits en tant que « A(10*B, i)
. »Dans ce cas, les indices sont en fait des expressions plutôt que des constantes, donc la référence doit contenir des pointeurs vers les expressions évaluées au moment de l’exécution. Enfin, il y a ce morceau de code assez laid qui calcule l’index en fonction de ce qui a été passé dans le programme. Cette méthode privée est illustrée ci-dessous.
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; }
En regardant le code ci-dessus, vous remarquerez que le code vérifie d’abord que le nombre correct d’index a été utilisé lors du référencement du tableau, puis que chaque index se trouvait dans la plage légale pour cet index. Si une erreur est détectée, une exception est levée vers l’interpréteur. Les méthodes numValue
et stringValue
renvoient une valeur de la variable sous forme de nombre ou de chaîne respectivement. Ces deux méthodes sont présentées ci-dessous.
double numValue(int ii) throws BASICRuntimeError { return nArrayValues; } String stringValue(int ii) throws BASICRuntimeError { if (subType == NUMBER_ARRAY) return ""+nArrayValues; return sArrayValues; }
Il existe des méthodes supplémentaires pour définir la valeur d’une variable qui ne sont pas affichées ici.
En cachant une grande partie de la complexité de la façon dont chaque pièce est implémentée, quand vient enfin le temps d’exécuter le programme DE BASE, le code Java est assez simple.
Exécution du code
Le code pour interpréter les instructions DE BASE et les exécuter est contenu dans le
run
méthode de l’
Program
classe. Le code de cette méthode est montré ci-dessous, et je vais le parcourir pour souligner les parties intéressantes.
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 }
Le code ci-dessus montre que la méthode run
prend un InputStream
et un OutputStream
pour être utilisée comme « console » pour le programme en cours d’exécution. À la ligne 3, l’objet d’énumération e est défini sur l’ensemble des instructions de la collection nommée stmts. Pour cette collection, j’ai utilisé une variation sur un arbre de recherche binaire appelé arbre « rouge-noir ». (Pour plus d’informations sur les arbres de recherche binaires, voir ma colonne précédente sur la création de collections génériques.) Après cela, deux collections supplémentaires sont créées – une en utilisant un Stack
et une en utilisant un Vector
. La pile est utilisée comme la pile dans n’importe quel ordinateur, mais le vecteur est utilisé expressément pour les instructions de DONNÉES du programme DE BASE. La collection finale est un autre arbre rouge-noir qui contient les références pour les variables définies par le programme DE BASE. Cette arborescence est la table de symboles utilisée par le programme pendant son exécution.
Après l’initialisation, les flux d’entrée et de sortie sont configurés, puis si e n’est pas nul, nous commençons par collecter toutes les données déclarées. Cela se fait comme indiqué dans le code suivant.
/* 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); } }
La boucle ci-dessus examine simplement toutes les instructions, et toutes les instructions de DONNÉES qu’elle trouve sont ensuite exécutées. L’exécution de chaque instruction de DONNÉES insère les valeurs déclarées par cette instruction dans le vecteur de banque de données. Ensuite, nous exécutons le programme proprement dit, ce qui est fait en utilisant ce morceau de code suivant:
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); }
Comme vous pouvez le voir dans le code ci-dessus, la première étape consiste à réinitialiser e. L’étape suivante consiste à récupérer la première instruction dans la variable s, puis à entrer dans la boucle d’exécution. Il y a du code à vérifier pour l’entrée en attente sur le flux d’entrée pour permettre l’interruption de la progression du programme en tapant sur le programme, puis la boucle vérifie pour voir si l’instruction à exécuter serait une instruction de DONNÉES. Si c’est le cas, la boucle ignore l’instruction telle qu’elle a déjà été exécutée. La technique plutôt compliquée d’exécuter toutes les instructions de données en premier est requise car BASIC permet aux instructions de DONNÉES qui satisfont une instruction READ d’apparaître n’importe où dans le code source. Enfin, si le traçage est activé, un enregistrement de trace est imprimé et l’instruction très peu impressionnante s = s.execute(this, in, pout);
est invoquée. La beauté est que tous les efforts d’encapsulation des concepts de base dans des classes faciles à comprendre rendent le code final trivial. Si ce n’est pas trivial, vous avez peut-être une idée qu’il pourrait y avoir une autre façon de diviser votre conception.
Conclusion et réflexions supplémentaires
L’interpréteur a été conçu de sorte qu’il puisse s’exécuter en tant que thread, il peut donc y avoir plusieurs threads d’interpréteur COCOA s’exécutant simultanément dans votre espace programme en même temps. De plus, avec l’utilisation de l’expansion des fonctions, nous pouvons fournir un moyen par lequel ces threads peuvent interagir les uns avec les autres. Il y avait un programme pour l’Apple II et plus tard pour le PC et Unix appelé C-robots qui était un système d’entités « robotiques » en interaction programmées à l’aide d’un langage dérivé de BASE simple. Le jeu m’a fourni, à moi et à d’autres, de nombreuses heures de divertissement, mais était également un excellent moyen d’introduire les principes de base du calcul aux jeunes étudiants (qui croyaient à tort qu’ils ne faisaient que jouer et n’apprenaient pas). Les sous-systèmes d’interpréteurs basés sur Java sont beaucoup plus puissants que leurs homologues pré-Java car ils sont instantanément disponibles sur n’importe quelle plate-forme Java. COCOA a fonctionné sur les systèmes Unix et Macintosh le jour même où je travaillais sur un PC basé sur Windows 95. Alors que Java est battu par des incompatibilités dans les implémentations de thread ou de window toolkit, ce qui est souvent négligé est ceci: beaucoup de code « fonctionne juste. »
Chuck McManis est actuellement directeur des logiciels système chez FreeGate Corp., une start-up financée par des fonds de capital-risque qui explore les opportunités sur le marché Internet. Avant de rejoindre FreeGate, Chuck était membre du groupe Java. Il a rejoint le groupe Java juste après la création de FirstPerson Inc. et était membre du groupe portable OS (le groupe responsable de l’exploitation de Java). Plus tard, lorsque FirstPerson a été dissous, il est resté avec le groupe grâce au développement des versions alpha et bêta de la plate-forme Java. Il a créé la première page d’accueil « tout Java » sur Internet lorsqu’il a fait la programmation de la page d’accueil Javaversion of the Sun en mai 1995. Il a également développé une bibliothèque acryptographique pour Java et des versions du chargeur de classes Java qui pourraient filtrer les classes en fonction des signatures numériques.Avant de rejoindre FirstPerson, Chuck a travaillé dans le domaine des systèmes d’exploitation de SunSoft, développant des applications réseau, où il a fait la conception initiale de NIS+. Consultez également sa page d’accueil. : END_BIO
En savoir plus sur ce sujet
- Voici des liens vers les fichiers sources référencés ci-dessus:
- Expression constante.java
- Expression de fonction.programme java
- .instruction java
- .java
- Expression de chaîne.variable java
- .java
- Expression variable.java
- Et voici un.Fichier ZIP des fichiers sources :
profondeur.zip - « Uncommon Lisp » interpreter un interpréteur Lisp écrit en Java
http://user03.blue.aol.com/thingtone/workshop/lisp.htm - L’interpréteur NetREXX de Mike Cowlishaw écrit en Java
http://www2.hursley.ibm.com/netrexx/ - Une ancienne copie de la FAQ de BASE de USENET (elle contient toujours des informations utiles.)
http://whitworth.me.ic.ac.uk/people/students/djbur/qbasic.htm - COCOA, un interpréteur de BASE écrit en Java
http://www.mcmanis.com/~cmcmanis/java/javaworld/examples/BASIC.html - Page des ressources Java de Chuck
http://www.mcmanis.com/~cmcmanis/java/javaworld/ - Interpréteur TCL écrit en Java
http://www.cs.cornell.edu/home/ioi/Jacl/
- :
- » Comment construire un interpréteur en Java, Partie 2La structure «
L’astuce pour assembler les classes de base pour un interpréteur simple. - « Comment construire un interpréteur en Java, partie 1Les bases »
Pour les applications complexes nécessitant un langage de script, Java peut être utilisé pour implémenter l’interpréteur, ajoutant des capacités de script à n’importe quelle application Java. - « Analyse lexicale, partie 2construire une application »
Comment utiliser l’objet StreamTokenizer pour implémenter une calculatrice interactive. - « Analyse lexicale et JavaPart 1 »
Apprenez à convertir du texte lisible par l’homme en données lisibles par machine à l’aide des classes StringTokenizer et StreamTokenizer. - « Réutilisation de code et systèmes orientés objet »
Utilisez une classe d’assistance pour appliquer un comportement dynamique. - « Support des conteneurs pour les objets en Java 1.0.2 »
L’organisation des objets est facile lorsque vous les mettez dans des conteneurs. Cet article vous guide à travers la conception et la mise en œuvre d’un conteneur. - « Les bases des chargeurs de classe Java »
Les bases de ce composant clé de l’architecture Java. - « Ne pas utiliser le ramasse-miettes »
Minimisez l’écrasement de tas dans vos programmes Java. - « Threads et applets et contrôles visuels »
Cette dernière partie de la série explore la lecture de plusieurs canaux de données. - « Utilisation des canaux de communication dans les applets, Partie 3 »
Développer des techniques de style Visual Basic pour concevoir des applets — et convertir les températures dans le processus. - « Synchronisation des threads en Java, Partie II »
Apprenez à écrire une classe de canal de données, puis créez un exemple d’application simple qui illustre une implémentation réelle de la classe. - « Synchronisation des threads en Java »
Chuck McManis, ancien développeur de l’équipe Java, vous présente un exemple simple illustrant comment synchroniser les threads pour assurer un comportement d’applet fiable et prévisible.