コンパイラは型エラーからどの程度正確に回復しますか?


10

私はいくつかの論文、記事、およびコンパイラー:原則、手法、およびツール(第2版)(別名「ドラゴン・ブック」)のセクション4.1.4、第4章を読み、すべて構文コンパイラーのエラー回復のトピックについて説明しています。ただし、いくつかの最新のコンパイラーで実験した後、構文エラーだけでなく、セマンティックエラーからも回復することがわかりました

構文的に関連するエラーから回復するコンパイラーの背後にあるアルゴリズムと手法はかなりよく理解していますが、コンパイラーがセマンティックエラーから回復する方法を正確には理解していません。

現在、ビジターパターンのわずかなバリエーションを使用して、抽象構文ツリーからコードを生成しています。コンパイラが次の式をコンパイルすることを検討してください。

1 / (2 * (3 + "4"))

コンパイラーは、次の抽象構文ツリーを生成します。

      op(/)
        |
     -------
    /       \ 
 int(1)    op(*)
             |
          -------
         /       \
       int(2)   op(+)
                  |
               -------
              /       \
           int(3)   str(4)

コード生成フェーズでは、ビジターパターンを使用して、抽象構文ツリーを再帰的に走査し、型チェックを実行します。コンパイラが式の最も内側の部分に到達するまで、抽象構文ツリーをたどります。(3 + "4")。次に、コンパイラーは式の両側をチェックし、それらが意味的に同等ではないことを確認します。コンパイラは型エラーを発生させます。ここに問題があります。今コンパイラは何をすべきですか?

コンパイラは、このエラーから回復し、式の外側の部分を型チェックを継続するためには、返却しなければならないいくつかのタイプ(intまたはstrに、表現の最も内側の部分を評価するから)を、次式の最も内側の部分。ただし、単に返すタイプがありません。型エラーが発生したため、型は推定されませんでした。

私が仮定した1つの可能な解決策は、型エラーが発生した場合、エラーが発生し、型エラーが発生したことを示す特別な値が以前の抽象構文ツリートラバーサル呼び出しに返されることです。以前のトラバーサル呼び出しでこの値が検出された場合、抽象構文ツリーのより深いところで型エラーが発生したことがわかっているため、型を推測することは避けてください。この方法は機能するように見えますが、非常に非効率的です。式の最も内側の部分が抽象構文ツリーの奥にある場合、コンパイラーは多くの再帰呼び出しを行って、実際の作業が実行できないことを認識し、それぞれから単に戻る必要があります。

上記の方法を使用していますか(疑わしい)。もしそうなら、それは効率的ではありませんか?そうでない場合、コンパイラがセマンティックエラーから回復するときに使用される方法は正確には何ですか?


3
それが使用されていることをかなり確信しています、そしてあなたはそれが十分に効率的であると思いませんか?型チェックを行うには、コンパイラはツリー全体をとにかく歩く必要があります。エラーが見つかるとコンパイラが分岐を排除できるため、セマンティックエラーはより効率的です。
Telastyn、

回答:


8

あなたの提案されたアイデアは本質的に正しいです。

重要なのは、ASTノードのタイプが1回だけ計算されてから格納されることです。タイプが再び必要になったときはいつでも、格納されているタイプを取得するだけです。解決がエラーで終了した場合、代わりにエラータイプが保存されます。


3

興味深いアプローチの1つは、エラーに対して特別なタイプを使用することです。このようなエラーが最初に発生すると、診断がログに記録され、エラータイプが式のタイプとして返​​されます。このエラータイプには、いくつかの興味深いプロパティがあります。

  • その上で実行されるすべての操作は成功します(同じ元の障害が原因で発生するエラーメッセージのカスケードを防止するため)
  • エラータイプのオブジェクトに対して実行された操作の結果にもエラータイプがあります
  • エラータイプがコード生成に到達した場合、コードジェネレーターはその使用を特定し、失敗したコードを生成します(例:例外のスロー、中止、または言語に適したもの)

この組み合わせにより、型エラーを含むコードを実際に正常にコンパイルでき、そのコードが実際に使用されない限り、ランタイムエラーは発生しません。これは、たとえば、影響を受けないコードの部分に対して単体テストを実行できるようにするのに役立ちます。


答えジュールをありがとう。面白いことに、これは私が最終的に使用した方法とまったく同じです。偉大な心は同じように考えますか?;-)
クリスチャンディーン

2

セマンティックエラーがある場合、そのことを示すコンパイルエラーメッセージがユーザーに発行されます。

それが完了したら、入力プログラムにエラーがあるため、コンパイルを中止しても問題ありません。これは、この言語では正当なプログラムではないため、単に拒否することができます。

しかし、それはかなり厳しいので、より柔らかい代替案があります。コード生成と出力ファイル生成をすべて中止しますが、さらにエラーを探すために何かを続行します。

たとえば、現在の式ツリーのタイプ分析を中止して、後続のステートメントから式の処理を続行できます。


2

言語が整数の追加を許可し、+演算子で文字列を連結できると仮定しましょう。

以来はint + string評価し、許可されていない+と報告されてエラーになります。コンパイラは可能性があり、単に返すerror型として。または、それが許可されているため、「エラー、intまたはstringである可能性があります」を返す可能性があるためint + int -> int、より賢いかもしれませんstring + string -> string

次に*オペレーターが来て、int + int許可されていると仮定します。コンパイラーは、+実際にはが返されるはずであると判断し、エラーメッセージなしでのintために返される型はになる*と判断しintます。


私は@gnasherをフォローしていると思いますが、「」演算子とは正確にはどういう意味ですか?それはタイプミスでしたか?
クリスチャンディーン

@ChristianDean引用符にアスタリスクがあり、レンダリングされずにマークダウンマークアップとして解釈されます。
JakeRobb、

回答を編集して提出しました。編集内容がピアレビューされるとすぐに問題が解決します。
JakeRobb、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.