Compilers:Principles、Techniques、and Tools(2nd Edition)のいくつかの論文、記事、およびセクション4.1.4、第4章を読みました。 -) (別名 "The Dragon Book")はすべて、構文コンパイラのエラー回復のトピックについて説明しています。ただし、いくつかの最新のコンパイラーを実験した後、それらはsemanticエラーおよび構文エラーからも回復することを確認しました。
構文的に関連するエラーから回復するコンパイラの背後にあるアルゴリズムとテクニックはかなりよく理解していますが、コンパイラがセマンティックエラーから回復する方法を正確には理解していません。
現在、ビジターパターンのわずかなバリエーションを使用して、抽象構文ツリーからコードを生成しています。コンパイラが次の式をコンパイルすることを検討してください。
1 / (2 * (3 + "4"))
コンパイラーは、次の抽象構文ツリーを生成します。
op(/)
|
-------
/ \
int(1) op(*)
|
-------
/ \
int(2) op(+)
|
-------
/ \
int(3) str(4)
コード生成フェーズでは、ビジターパターンを使用して、抽象構文ツリーを再帰的に走査し、型チェックを実行します。コンパイラが式の最も内側の部分に到達するまで、抽象構文ツリーをたどります。 (3 + "4")
。次に、コンパイラーは式の両側をチェックし、それらが意味的に同等ではないことを確認します。コンパイラは型エラーを発生させます。ここに問題があります。 今コンパイラがすべきこと?
コンパイラがこのエラーから回復し、式の外側の部分の型チェックを続行するには、someタイプ(int
またはstr
)を返す必要があります式の最も内側の部分をnext式の最も内側の部分に評価します。 しかし、それは単に返すタイプを持ちません。型エラーが発生したため、型は推定されませんでした。
私が仮定した1つの考えられる解決策は、型エラーが発生した場合、エラーが発生し、型エラーが発生したことを示す特別な値が、以前の抽象構文ツリートラバーサル呼び出しに返されることです。以前のトラバーサル呼び出しでこの値が検出された場合、抽象構文ツリーのより深いところで型エラーが発生したことがわかっているため、型の推定を回避する必要があります。この方法は機能するようですが、非常に非効率的です。式の最も内側の部分が抽象構文ツリーの奥にある場合、コンパイラーは多くの再帰呼び出しを行って、実際の作業が実行できないことを認識し、それぞれから単に戻る必要があります。
上で説明した方法が使用されていますか(疑わしい)。もしそうなら、それは効率的ではありませんか?そうでない場合、コンパイラがセマンティックエラーから回復するときに使用されるメソッドは正確には何ですか?
あなたの提案されたアイデアは本質的に正しいです。
重要なのは、ASTノードのタイプが1回だけ計算されてから格納されることです。タイプが再度必要になると、ノードは格納されたタイプを単に取得します。解決がエラーで終了した場合、代わりにエラータイプが保存されます。
興味深いアプローチの1つは、エラーに対して特別なタイプを使用することです。このようなエラーが最初に発生すると、診断がログに記録され、エラータイプが式のタイプとして返されます。このエラータイプには、いくつかの興味深いプロパティがあります。
この組み合わせにより、実際に型エラーを含む正常にコンパイルコードを使用でき、そのコードが実際に使用されない限り、ランタイムエラーは発生しません。これは、たとえば、影響を受けないコードの部分に対して単体テストを実行できるようにする場合に役立ちます。
言語が整数の追加を許可し、+
演算子で文字列を連結できると仮定しましょう。
int + string
は許可されていないため、+
を評価するとエラーが報告されます。コンパイラcouldは、型としてerror
を返すだけです。または、int + int -> int
とstring + string -> string
が許可されているため、「エラー、intまたはstringである可能性があります」を返す可能性があるため、より賢いかもしれません。
次に*
演算子が来て、int + int
のみが許可されていると想定します。次に、コンパイラは+
が実際にint
を返すはずであると判断し、*
に対して返される型はエラーメッセージなしでint
になります。
セマンティックエラーがある場合、そのことを示すコンパイルエラーメッセージがユーザーに発行されます。
それが完了したら、入力プログラムにエラーがあるため、コンパイルを中止しても問題ありません。これは、この言語では正当なプログラムではないため、単に拒否することができます。
しかし、それはかなり厳しいので、より柔らかい代替案があります。コード生成と出力ファイル生成をすべて中止しますが、さらにエラーを探すために何かを続行します。
たとえば、現在の式ツリーの以降の型分析を中止して、後続のステートメントから式の処理を続行できます。