はい、これを行うより良い方法があります。シンボルテーブルが必要です。シンボル テーブルの役割は、プログラムの各ポイントでどの識別子を使用できるかを追跡することです。通常、シンボル テーブルには、識別子が何を表しているか (変数名や関数名など) やその型など、識別子に関するその他の情報も含まれています。
シンボル テーブルを使用すると、すべてではないが多くの言語の解析中にスコープ外の変数の使用を検出できます。たとえば、C と Pascal は、使用する前に識別子を宣言する必要がある言語です (いくつかの例外があります)。しかし、他の言語 (Java など) では、使用後に識別子を宣言することができます。その場合、プログラムが解析されるまで、宣言されていない変数の使用などのエラーを検出しようとしないことが最善です。(実際、Java では、識別子が別のファイルで宣言されている可能性があるため、すべてのファイルが解析されるまで待つ必要があります。)
ここでは、変数に関する情報のみを記録する必要があり、型情報はなく、使用前に宣言する必要があるという単純なシナリオを想定します。これで始められます。関数名をシンボル テーブルに追加することは気にしませんでした。
シンボル テーブルがフレームと呼ばれるもののスタックであるとします。各フレームは変更可能な一連の文字列です。(後で、文字列から追加情報への変更可能なマップに変更したい場合があります。)
void Start(): { }
{
<FUNCTION>
<IDENTIFIER>
{symttab.pushNewFrame() ;}
<LBRACKET> Parameters() <RBRACKET>
<LBRACE> Expression() <RBRACE>
{symtab.popFrame() ; }
}
void Parameters() : {}
{
( Parameter() (<COMMA> Parameter() )* )?
}
void Parameter() : { Token x ; }
<OBJECT> x=<IDENTIFIER>
{ if( symtab.topFrame().contains(x.image) ) reportError( ... ) ; }
{ symtab.topFrame().add(x.image) ; }
}
void Expression() : { }
{
Exp1() ( <PLUS> Exp1() )*
}
void Exp1() : { Token y ; }
{
y = <IDENTIFIER>
{ if( ! symtab.topFrame().contains(y.image) ) reportError( ... ) ; }
|
<NUMBER>
}