昨日のエントリーのどこが変かというと:
加数 ::= 因数 ('*' 因数)* | '-' 加数
これだと、積('*')と符号反転('-')の優先順位が相変わらず曖昧で、「一意的にパーズ(解析)できます。」なんてセリフが嘘になります。例えば、2+-3*5 が、2+(-(3*5)) か 2+((-3)*5) かハッキリしません。この例では計算結果は変わりませんが、今は構文解析ツリーを作るのが目的なので、2+(-(3*5)) と 2+((-3)*5) は別物とみなすのです。
昨日エントリーの後半では、なぜか積('*')が符号反転('-')より優先される前提で書かれています。そういう約束があってもいいですけど、常識的には符号反転のほうが積より強い(優先順位が高い)演算子ですよね。つまり、'+' < '*' < '-' という順序が自然でしょう。
ということで、構文定義のBNFは次のようにすべきでした。
式 ::= 加数 ('+' 加数)*
加数 ::= 因数 ('*' 因数)*
因数 ::= 数 | '-' 因数 | '(' 式 ')'
あるいは:
式 ::= 加数 | 加数 '+' 式
加数 ::= 因数 | 因数 '*' 加数
因数 ::= 数 | '-' 因数 | '(' 式 ')'
さらにあるいは:
式 ::= 加数 ('+' 式)?
加数 ::= 因数 ('*' 加数)?
因数 ::= 数 | '-' 因数 | '(' 式 ')'
実際に再帰下降法のパーザーを生成(手書き、または自動生成)するときは、1番目(whileループ使用)か3番目(再帰呼び出し使用)に基づいてコーディングします。念のため手書きしてみました。注意事項:
- 面倒だったので1ファイルに全部突っこんである。
- 構文エラーはまともにハンドリングしてない(nullを返すだけ)。
- その他、いたるところ手抜き、dirtyコーディング、行き当たりバッタリ。
- 何か変な挙動があれば、デバッグレベル3で実行のこと。
構文定義規則(BNF)に対応するメソッドを眺めると、機械的な手続きで「構文定義→プログラム」と変換できることが分かると思いますよ。