Javaでインタプリタを構築する-実行エンジンを実装する

前へ123 Page2次へ 2/3ページ

その他の側面:文字列と配列

BASIC言語の他の2つの部分、文字列と配列がCOCOAインタープリターによって実装されます。最初に文字列の実装を見てみましょう。

文字列を変数として実装するためにExpression、「文字列」式の概念を含むようにクラスが変更されました。この変更は、2つの追加の形を取りました:isStringstringValue。これら2つの新しいメソッドのソースを以下に示します。

String stringValue(Program pgm)throws BASICRuntimeError {throw new BASICRuntimeError( "No String Representation for this。"); } boolean isString(){falseを返す; }

明らかに、基本式(常に数値式またはブール式のいずれか)の文字列値を取得することは、BASICプログラムにとってあまり有用ではありません。ユーティリティの欠如から、これらのメソッドはExpressionExpression代わりにのサブクラスに属しておらず、属していたと結論付けることができます。ただし、これら2つのメソッドを基本クラスに配置Expressionすることにより、すべてのオブジェクトをテストして、実際に文字列であるかどうかを確認できます。

別の設計アプローチは、StringBufferオブジェクトを使用して値を生成する文字列として数値を返すことです。したがって、たとえば、同じコードを次のように書き直すことができます。

String stringValue(Program pgm)throws BASICRuntimeError {StringBuffer sb = new StringBuffer(); sb.append(this.value(pgm)); sb.toString();を返します。}

また、上記のコードを使用するとisString、すべての式が文字列値を返す可能性があるため、の使用を排除できます。さらに、あなたは変更することができますvalue式はを通してそれを実行することにより、文字列に評価される場合の数を返すようにしようとする方法をvalueOfする方法java.lang.Double。 Perl、TCL、REXXなどの多くの言語では、この種のアモルファスタイピングが非常に有利に使用されます。どちらのアプローチも有効であり、通訳者の設計に基づいて選択する必要があります。 BASICでは、文字列が数値変数に割り当てられたときにインタープリターがエラーを返す必要があるため、最初のアプローチ(エラーを返す)を選択しました。

配列に関しては、配列を解釈するための言語を設計するさまざまな方法があります。 Cは、配列要素を角かっこで囲んで、配列のインデックス参照を、引数を括弧で囲んだ関数参照と区別します。ただし、BASICの言語設計者は、関数と配列の両方に括弧を使用することを選択したためNAME(V1, V2)、パーサーがテキストを表示するときは、関数呼び出しまたは配列参照のいずれかである可能性があります。

字句解析プログラムは、最初に関数であると想定してテストすることにより、括弧が後に続くトークンを区別します。次に、それらがキーワードであるか変数であるかを確認します。プログラムが「SIN」という名前の変数を定義できないのは、この決定です。名前が関数名と一致する変数は、代わりに関数トークンとして字句解析プログラムによって返されます。字句アナライザーが使用する2番目のトリックは、変数名の直後に `( 'が続くかどうかを確認することです。そうである場合、アナライザーはそれが配列参照であると見なします。字句アナライザーでこれを解析することにより、文字列`を削除します。MYARRAY ( 2 )'有効な配列として解釈されないようにします(変数名とオープン括弧の間のスペースに注意してください)。

配列を実装するための最後の秘訣はVariableクラスにあります。このクラスは変数のインスタンスに使用され、先月のコラムで説明したように、のサブクラスですToken。ただし、配列をサポートするためのいくつかの機械もあり、それを以下に示します。

class Variable extends Token {//正当な変数のサブタイプfinalstatic int NUMBER = 0; 最終的な静的intSTRING = 1; 最終的な静的intNUMBER_ARRAY = 2; 最終的な静的intSTRING_ARRAY = 4; 文字列名; int subType; / * *変数がシンボルテーブルにある場合、これらの値は*初期化されます。* / int ndx []; //配列インデックス。int mult []; //配列乗数doublenArrayValues []; 文字列sArrayValues [];

上記のコードは、ConstantExpressionクラスと同様に、変数に関連付けられたインスタンス変数を示しています。使用するクラスの数とクラスの複雑さを選択する必要があります。設計上の選択の1つVariableは、スカラー変数のみを保持するクラスを作成してArrayVariableから、配列の複雑さに対処するためのサブクラスを追加することです。私はそれらを組み合わせて、スカラー変数を本質的に長さ1の配列に変換することを選択しました。

上記のコードを読むと、配列のインデックスと乗数が表示されます。 BASICの多次元配列は、単一の線形Java配列を使用して実装されているため、これらはここにあります。 Java配列への線形インデックスは、乗数配列の要素を使用して手動で計算されます。 BASICプログラムで使用されるインデックスは、インデックスのndx配列の最大有効インデックスと比較することにより、有効性がチェックされます。

たとえば、10、10、および8の3次元を持つBASIC配列では、値10、10、および8がndxに格納されます。これにより、式エバリュエーターは、BASICプログラムで使用されている数値を、現在ndxに格納されている最大の有効な数値と比較することにより、「範囲外のインデックス」条件をテストできます。この例の乗数配列には、値1、10、および100が含まれます。これらの定数は、多次元配列インデックス仕様から線形配列インデックス仕様にマップするために使用する数値を表します。実際の方程式は次のとおりです。

Java Index = Index1 + Index2 * Index1 + Index3の最大サイズ*(Index1のMaxSize * MaxSizeIndex 2)

Variableクラス内の次のJava配列を以下に示します。

 式expns []; 

expnsのアレイは次のように書かれている配列に対処するために使用されますA(10*B, i)「」その場合、インデックスは実際には定数ではなく式であるため、参照には実行時に評価される式へのポインタが含まれている必要があります。最後に、プログラムで渡された内容に応じてインデックスを計算する、このかなり見苦しいコードがあります。このプライベートメソッドを以下に示します。

private int computeIndex(int ii [])throws BASICRuntimeError {int offset = 0; if((ndx == null)||(ii.length!= ndx.length))throw new BASICRuntimeError( "Wrong number of index。"); for(int i = 0; i <ndx.length; i ++){if((ii [i] ndx [i]))throw new BASICRuntimeError( "Index out of range。"); オフセット=オフセット+(ii [i] -1)* mult [i]; }オフセットを返します。}

上記のコードを見ると、コードは最初に配列を参照するときに正しい数のインデックスが使用されていることを確認し、次に各インデックスがそのインデックスの有効範囲内にあることを確認します。エラーが検出されると、インタープリターに例外がスローされます。メソッドnumValuestringValueは、変数からそれぞれ数値または文字列として値を返します。これら2つの方法を以下に示します。

double numValue(int ii [])throws BASICRuntimeError {return nArrayValues [computeIndex(ii)]; } String stringValue(int ii [])throws BASICRuntimeError {if(subType == NUM​​BER_ARRAY)return "" + nArrayValues [computeIndex(ii)]; sArrayValues [computeIndex(ii)]を返します。}

ここに示されていない変数の値を設定するための追加の方法があります。

各部分の実装方法の複雑さの多くを隠すことにより、最終的にBASICプログラムを実行するときが来たときに、Javaコードは非常に単純です。

コードの実行

BASICステートメントを解釈して実行するためのコードは、

run

の方法

Program

クラス。このメソッドのコードを以下に示します。興味深い部分を指摘するために、このメソッドをステップスルーします。

1 public void run(InputStream in、OutputStream out)throws BASICRuntimeError {2 PrintStream pout; 3列挙e = stmts.elements(); 4 stmtStack = new Stack(); //スタックされたステートメントがないと仮定します... 5 dataStore = new Vector(); // ...そして読み取られるデータがありません。 6 dataPtr = 0; 7ステートメントs; 8 9 vars = new RedBlackTree(); 10 11 //プログラムがまだ有効でない場合。 12 if(!e.hasMoreElements())13 return; 14 15 if(out instanceof PrintStream){16 pout =(PrintStream)out; 17} else {18 pout = new PrintStream(out); 19}

上記のコードは、runメソッドが実行中のプログラムの「コンソール」として使用InputStreamするOutputStreamためにとを使用することを示しています。 3行目では、列挙オブジェクトestmtsという名前のコレクションからのステートメントのセットに設定されています。このコレクションでは、「赤黒」ツリーと呼ばれるバイナリ検索ツリーのバリエーションを使用しました。 (二分探索木の詳細については、ジェネリックコレクションを構築する上で私の以前のコラムを参照してください。)それに続いて、二つの追加のコレクションが作成されます- 1を使用Stackして1つのAを使用してVector。スタックは他のコンピューターのスタックと同じように使用されますが、ベクトルはBASICプログラムのDATAステートメントに明示的に使用されます。最後のコレクションは、BASICプログラムによって定義された変数の参照を保持する別の赤黒木です。このツリーは、プログラムの実行中にプログラムによって使用されるシンボルテーブルです。

初期化に続いて、入力ストリームと出力ストリームが設定され、eがnullでない場合は、宣言されているデータを収集することから始めます。これは、次のコードに示すように実行されます。

/ *最初にすべてのデータステートメントをロードします* / while(e.hasMoreElements()){s =(ステートメント)e.nextElement(); if(s.keyword == Statement.DATA){s.execute(this、in、pout); }}

The above loop simply looks at all of the statements, and any DATA statements it finds are then executed. The execution of each DATA statement inserts the values declared by that statement into the dataStore vector. Next we execute the program proper, which is done using this next piece of code:

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

As you can see in the code above, the first step is to reinitialize e. The next step is to fetch the first statement into the variable s and then to enter the execution loop. There is some code to check for pending input on the input stream to allow the progress of the program to be interrupted by typing at the program, and then the loop checks to see if the statement to execute would be a DATA statement. If it is, the loop skips the statement as it was already executed. The rather convoluted technique of executing all data statements first is required because BASIC allows the DATA statements that satisfy a READ statement to appear anywhere in the source code. Finally, if tracing is enabled, a trace record is printed and the very uninpressive statement s = s.execute(this, in, pout); is invoked. The beauty is that all the effort of encapsulating the base concepts into easy-to-understand classes makes the final code trivial. If it isn't trivial then perhaps you have a clue that there might be another way to split your design.

Wrapping up and further thoughts

The interpreter was designed so that it could run as a thread, thus there can be several COCOA interpreter threads running simultaneously in your program space at the same time. Further, with the use of function expansion we can provide a means whereby those threads can interact with each other. There was a program for the Apple II and later for the PC and Unix called C-robots that was a system of interacting "robotic" entities that were programmed using a simple BASIC derivative language. The game provided me and others with many hours of entertainment but was also an excellent way to introduce the basic principles of computation to younger students (who mistakenly believed they were just playing and not learning). Java based interpreter subsystems are much more powerful than their pre-Java counterparts because they are instantly available on any Java platform. COCOA ran on Unix systems and Macintoshes the same day I got working on a Windows 95 based PC. While Java gets beaten up by incompatibilities in the thread or window toolkit implementations, what is often overlooked is this: A lot of code "just works."