私は学位を取得するためにゆっくりと取り組んでおり、この学期はCompilers 101です。DragonBookを使用しています。まもなくコースに入り、語彙分析と、決定論的有限オートマトン(以下、DFA)を介してそれを実装する方法について説明します。さまざまなレクサーの状態を設定し、それらの間の遷移を定義します。
しかし、教授と本は両方とも、巨大な2D配列(1つの次元としてのさまざまな非終端状態、および他の可能性のある入力シンボル)に相当する遷移テーブルと、すべての端子を処理するswitchステートメントを介して実装することを提案していますまた、非終端状態の場合は遷移テーブルにディスパッチします。
理論はすべて良好で優れていますが、実際にコードを何十年も書いた人として、実装は下手です。それはテスト可能ではなく、保守可能でもなく、読み取り可能でもなく、デバッグするのに苦労します。さらに悪いことに、その言語がUTFに対応していれば、どのように実用的であるかわかりません。非終端状態ごとに100万程度の遷移テーブルエントリがあると、急いで扱いにくくなります。
それで、取引は何ですか?主題に関する決定的な本が、このようにそれをするように言っているのはなぜですか?
関数呼び出しのオーバーヘッドは本当にそれほどですか?これはうまく機能するものですか、文法が事前にわからない場合に必要ですか(正規表現)?または、より具体的なソリューションがより具体的な文法でうまく機能する場合でも、すべてのケースを処理する何かでしょうか?
(注:可能性のある重複「なぜ巨大なswitchステートメントの代わりにオブジェクト指向アプローチを使用するのか?」は近いですが、オブジェクト指向については気にしません。機能的アプローチ、またはスタンドアロン関数での賢明な命令型アプローチでも問題ありません。)
また、例のために、識別子のみを持つ言語を考えてみましょう[a-zA-Z]+
。これらの識別子はです。DFA実装では、次のようなものが得られます。
private enum State
{
Error = -1,
Start = 0,
IdentifierInProgress = 1,
IdentifierDone = 2
}
private static State[][] transition = new State[][]{
///* Start */ new State[]{ State.Error, State.Error (repeat until 'A'), State.IdentifierInProgress, ...
///* IdentifierInProgress */ new State[]{ State.IdentifierDone, State.IdentifierDone (repeat until 'A'), State.IdentifierInProgress, ...
///* etc. */
};
public static string NextToken(string input, int startIndex)
{
State currentState = State.Start;
int currentIndex = startIndex;
while (currentIndex < input.Length)
{
switch (currentState)
{
case State.Error:
// Whatever, example
throw new NotImplementedException();
case State.IdentifierDone:
return input.Substring(startIndex, currentIndex - startIndex);
default:
currentState = transition[(int)currentState][input[currentIndex]];
currentIndex++;
break;
}
}
return String.Empty;
}
(ただし、ファイルの終わりを正しく処理するもの)
私が期待するものと比較して:
public static string NextToken(string input, int startIndex)
{
int currentIndex = startIndex;
while (currentIndex < startIndex && IsLetter(input[currentIndex]))
{
currentIndex++;
}
return input.Substring(startIndex, currentIndex - startIndex);
}
public static bool IsLetter(char c)
{
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
}
NextToken
DFAの開始から複数の宛先を取得したら、コードを独自の機能にリファクタリングします。