字句解析とJava:パート1

字句解析と構文解析

Javaアプリケーションを作成する場合、作成する必要がある最も一般的なものの1つはパーサーです。パーサーは単純なものから複雑なものまであり、コマンドラインオプションの確認からJavaソースコードの解釈まですべてに使用されます。でJavaWorldの12月号、私はあなたにジャック、自動パーサジェネレータを示したパーサを実装するJavaクラスに変換高レベルの文法仕様は、これらの仕様で記載されています。今月は、Javaがターゲットの字句アナライザーとパーサーを作成するために提供するリソースを紹介します。これらのやや単純なパーサーは、単純な文字列比較とJackがコンパイルする複雑な文法との間のギャップを埋めます。

字句アナライザーの目的は、入力文字のストリームを取得し、パーサーが理解できる高レベルのトークンにデコードすることです。パーサーは字句アナライザーの出力を消費し、返されたトークンのシーケンスを分析することによって動作します。パーサーは、これらのシーケンスを終了状態に一致させます。終了状態は、おそらく多くの終了状態の1つである可能性があります。最終状態は目標を定義しますパーサーの。終了状態に達すると、パーサーを使用するプログラムは、データ構造を設定するか、アクション固有のコードを実行するなど、何らかのアクションを実行します。さらに、パーサーは、処理されたトークンのシーケンスから、正当な終了状態に到達できない場合を検出できます。その時点で、パーサーは現在の状態をエラー状態として識別します。パーサーが終了状態またはエラー状態のいずれかを識別したときに実行するアクションを決定するのは、アプリケーションの責任です。

標準のJavaクラスベースには、いくつかの字句解析クラスが含まれていますが、汎用パーサークラスは定義されていません。このコラムでは、Javaに付属している字句解析プログラムについて詳しく見ていきます。

Javaの字句解析プログラム

Java言語仕様バージョン1.0.2は、2つの字句アナライザークラスとStringTokenizerを定義していStreamTokenizerます。その名前からは、と推論できるStringTokenizer用途がStringその入力などのオブジェクト、およびStreamTokenizer用途のInputStreamオブジェクト。

StringTokenizerクラス

利用可能な2つの字句アナライザークラスのうち、最も理解しやすいのはStringTokenizerです。新しいStringTokenizerオブジェクトを作成する場合、コンストラクターメソッドは名目上2つの値(入力文字列と区切り文字列)を取ります。次に、クラスは、区切り文字の間の文字を表すトークンのシーケンスを作成します。

字句解析プログラムとして、StringTokenizer以下に示すように正式に定義できます。

[〜delim1、delim2、...、delim N ] ::トークン

この定義は、区切り文字を除くすべての文字に一致する正規表現で構成されています。隣接するすべての一致する文字が1つのトークンに収集され、トークンとして返されます。

このStringTokenizerクラスの最も一般的な使用法は、コンマで区切られた数値のリストなど、パラメーターのセットを分離することです。StringTokenizerセパレータを削除してデータを返すため、この役割では理想的です。このStringTokenizerクラスは、「null」トークンが存在するリストを識別するためのメカニズムも提供します。一部のパラメーターにデフォルト値があるか、すべての場合に存在する必要がないアプリケーションでは、nullトークンを使用します。

以下のアプレットは単純なStringTokenizerエクササイズです。StringTokenizerアプレットのソースはこちらです。アプレットを使用するには、分析するテキストを入力文字列領域に入力してから、セパレータ文字列領域に区切り文字で構成される文字列を入力します。最後に、Tokenizeをクリックしてください!ボタン。結果は入力文字列の下のトークンリストに表示され、1行に1つのトークンとして整理されます。

このアプレットを表示するには、Java対応のブラウザが必要です。

StringTokenizerとして、区切り文字としてコンマ(、)を使用して作成されたオブジェクトに渡される文字列「a、b、d」について考えてみます。これらの値を上のエクササイズアプレットに入れると、Tokenizerオブジェクトが文字列「a」、「b」、および「d」を返すことがわかります。 1つのパラメーターが欠落していることに注意することを意図していた場合は、トークンシーケンスにその兆候が見られないことに驚いた可能性があります。欠落しているトークンを検出する機能は、Tokenizerオブジェクトの作成時に設定できるReturnSeparatorブール値によって有効になります。Tokenizerが構築されるときにこのパラメーターを設定すると、各セパレーターも返されます。上のアプレットの[セパレータを返す]チェックボックスをクリックし、文字列とセパレータはそのままにしておきます。今、Tokenizer「a、コンマ、b、コンマ、コンマ、およびd」を返します。2つの区切り文字を順番に取得することに注意することで、「null」トークンが入力文字列に含まれていることを確認できます。

StringTokenizerパーサーで正常に使用するための秘訣は、区切り文字がデータに表示されないように入力を定義することです。明らかに、アプリケーションで設計することにより、この制限を回避できます。以下のメソッド定義は、パラメータストリームで赤、緑、青の値の形式の色を受け入れるアプレットの一部として使用できます。

/ ***「10,20,30」の形式のパラメーターを*カラー値のRGBタプルとして解析します。 * / 1 Color getColor(String name){2 String data; 3 StringTokenizer st; 4 int赤、緑、青; 56データ= getParameter(name); 7 if(data == null)8 return null; 9 10 st = new StringTokenizer(data、 "、"); 11 try {12 red = Integer.parseInt(st.nextToken()); 13緑= Integer.parseInt(st.nextToken()); 14青= Integer.parseInt(st.nextToken()); 15} catch(Exception e){16 return null; //(ERROR STATE)は解析できませんでした17} 18 return new Color(red、green、blue); //(END STATE)完了しました。 19}

上記のコードは、文字列「number、number、number」を読み取り、新しいColorオブジェクトを返す非常に単純なパーサーを実装しています。 10行目で、コードはStringTokenizerパラメーターデータ(このメソッドがアプレットの一部であると想定)とコンマで構成される区切り文字リストを含む新しいオブジェクトを作成します。次に、12、13、および14行目で、各トークンが文字列から抽出され、IntegerparseIntメソッドを使用して数値に変換されます。これらの変換はtry/catch、数値文字列が有効な数値ではなかった場合、またはTokenizerトークンが不足したために例外をスローした場合に備えて、ブロックで囲まれています。すべての数値が変換されると、終了状態に到達し、Colorオブジェクトが返されます。それ以外の場合はエラー状態になり、nullが返されます。

このStringTokenizerクラスの特徴の1つは、簡単に積み重ねられることです。getColor上記のメソッドの10行目から18行目である、以下の名前のメソッドを見てください。

/ ** *カラータプル「r、g、b」をAWTColorオブジェクトに解析します。* / 1 Color getColor(String data){2 int赤、緑、青; 3 StringTokenizer st = new StringTokenizer(data、 "、"); 4 try {5 red = Integer.parseInt(st.nextToken()); 6緑= Integer.parseInt(st.nextToken()); 7青= Integer.parseInt(st.nextToken()); 8} catch(Exception e){9 return null; //(ERROR STATE)は解析できませんでした10} 11 return new Color(red、green、blue); //(END STATE)完了しました。12}

以下のコードに、もう少し複雑なパーサーを示します。このパーサーはgetColorsColorオブジェクトの配列を返すように定義されているメソッドに実装されています。

/ ** *色のセット "r1、g1、b1:r2、g2、b2:...:rn、gn、bn"を* AWTColorオブジェクトの配列に解析します。 * / 1 Color [] getColors(String data){2 Vector accum = new Vector(); 3色cl、結果[]; 4 StringTokenizer st = new StringTokenizer(data、 ":"); 5 while(st.hasMoreTokens()){6 cl = getColor(st.nextToken()); 7 if(cl!= null){8 accum.addElement(cl); 9} else {10 System.out.println( "エラー-色が悪い。"); 11} 12} 13 if(accum.size()== 0)14nullを返す; 15結果=新しい色[accum.size()]; 16 for(int i = 0; i <accum.size(); i ++){17 result [i] =(Color)accum.elementAt(i); 18} 19結果を返します。 20}

上記のgetColor方法では、方法とわずかに異なりますが、4行目から12行目のコードはTokenizer、コロン(:)文字で囲まれたトークンを抽出するための新しいものを作成します。このメソッドのドキュメントのコメントで読むことができるように、このメソッドはカラータプルがコロンで区切られていることを想定しています。クラスnextToken内の各呼び出しStringTokenizerは、文字列が使い果たされるまで新しいトークンを返します。返されるトークンは、コンマで区切られた数字の文字列になります。これらのトークン文字列はに供給されgetColor、次に3つの数値から色が抽出されます。StringTokenizer別のStringTokenizerオブジェクトから返されたトークンを使用して新しいオブジェクトを作成すると、記述したパーサーコードを、文字列入力の解釈方法についてもう少し洗練されたものにすることができます。

それは便利ですが、最終的にはStringTokenizerクラスの能力を使い果たし、その兄貴に移らなければなりませんStreamTokenizer

StreamTokenizerクラス

クラスの名前が示すように、StreamTokenizerオブジェクトはその入力がクラスからのものであることを期待していますInputStream。同様にStringTokenizer上記のように、このクラスでは、あなたの解析コードを解釈できることをチャンクに入力ストリームを変換し、それはどこ類似の終了です。

StreamTokenizerあるテーブル駆動字句解析が。これは、すべての可能な入力文字に重要度が割り当てられ、スキャナーが現在の文字の重要度を使用して何をするかを決定することを意味します。このクラスの実装では、文字には3つのカテゴリのいずれかが割り当てられます。これらは:

  • 空白文字-その字句の重要性は単語の区切りに限定されます

  • 単語文字-別の単語文字に隣接している場合は集約する必要があります

  • 通常の文字-すぐにパーサーに返される必要があります

このクラスの実装を、アイドル累積の2つの状態を持つ単純なステートマシンとして想像してみてください。各状態では、入力は上記のカテゴリのいずれかからの文字です。クラスは文字を読み取り、そのカテゴリをチェックして何らかのアクションを実行し、次の状態に進みます。次の表に、このステートマシンを示します。

状態 入力 アクション 新しい状態
アイドル 単語文字 キャラクターを押し戻す 蓄積する
普通のキャラクター 文字を返す アイドル
空白文字 キャラクターを消費する アイドル
蓄積する 単語文字 現在の単語に追加 蓄積する
普通のキャラクター

現在の単語を返す

キャラクターを押し戻す

アイドル
空白文字

現在の単語を返す

キャラクターを消費する

アイドル

この単純なメカニズムにStreamTokenizer加えて、クラスはいくつかのヒューリスティックを追加します。これらには、数値処理、引用符付き文字列処理、コメント処理、および行末処理が含まれます。

The first example is number processing. Certain character sequences can be interpreted as representing a numerical value. For example, the sequence of characters 1, 0, 0, ., and 0 adjacent to each other in the input stream represent the numerical value 100.0. When all of the digit characters (0 through 9), the dot character (.), and the minus (-) character are specified as being part of the word set, the StreamTokenizer class can be told to interpret the word it is about to return as a possible number. Setting this mode is achieved by calling the parseNumbers method on the tokenizer object that you instantiated (this is the default). If the analyzer is in the accumulate state, and the next character would not be part of a number, the currently accumulated word is checked to see if it is a valid number. If it is valid, it is returned, and the scanner moves to the next appropriate state.

次の例は、引用符で囲まれた文字列の処理です。多くの場合、引用符で囲まれた文字列(通常は二重( ")または単一( ')の引用符)を単一のトークンとして渡すことが望ましいです。このStreamTokenizerクラスでは、任意の文字を引用符として指定できます。デフォルトでは、一重引用符( ')と二重引用符( ")の文字です。ステートマシンは、別の引用文字または行末文字が処理されるまで、累積状態の文字を消費するように変更されます。引用符を引用できるようにするために、アナライザーは、入力ストリーム内および引用符内のバックスラッシュ(\)が前に付いた引用符文字を単語文字として扱います。