web-dev-qa-db-ja.com

Antlr4リスナーとビジター-実装するものは?

私は「The Definitive Antlr 4 Reference」を読んでいて、リスナーとビジターがどのように機能するかに関するアイデアを得ています。この本は、リスナーがSAXパーサーとどのように関係しているかを特によく説明しており、それぞれの実装中にメソッドがいつ呼び出されるのかを明らかにしています。リスナーは入力を出力に変換するのに非常に適していることもわかりますが、リスナーを使用するタイミングとビジターを使用するタイミングに関する短い説明/例を教えてください(または、特定のケースで両方を使用する必要がありますか?)。

私の特別な意図は、構文エラーをチェックし、回復せずにカスタム関数からのエラーで実行を停止するインタープリター(Cucumberスタイル/いくつかのカスタム呼び出しを含むTinyBasicインタープリター)を作成することです-そのようなものの完全な実装を見てみたいと思いますantlr-誰かがたまたま知っている場合。

アドバイスを事前にありがとう。

45
KevinY

解釈のためにパーサーの出力を直接使用する場合は、訪問者が適しています。トラバーサルを完全に制御できるため、条件付きでは1つのブランチのみがアクセスされ、ループはn回アクセスされます。

入力をより低いレベルに変換する場合、たとえば、仮想マシン命令では、両方のパターンが役立つ場合があります。

基本的なインタープリター実装をカバーする「言語実装パターン」をご覧になるかもしれません。

より柔軟なので、私はほとんどビジターパターンを使用します。

34
FreeJack

これは私が関連すると思う本からの引用です:

リスナーメカニズムとビジターメカニズムの最大の違いは、リスナーメソッドがANTLRが提供するwalkerオブジェクトによって呼び出されるのに対し、ビジターメソッドは明示的な訪問呼び出しで子をウォークする必要があることです。ノードの子でvisit()を呼び出すのを忘れると、それらのサブツリーは訪問されません。

ビジターパターンでは、ツリーウォーキングを指示することができますが、リスナーでは、ツリーウォーカーにのみ反応します。

52
Evgeni Petrov

これら2つのパターンには別の重要な違いがあります。ビジターはcall stackを使用してツリーのトラバーサルを管理しますが、リスナーはexplicit stackを使用して、ヒープ上に割り当てられ、ウォーカーによって管理されます。 。つまり、ビジターへの大量の入力はスタックを吹き飛ばすことができ、リスナーは問題ありません。

入力が無制限である可能性がある場合、または呼び出しツリーで非常に深いビジターを呼び出す可能性がある場合は、ビジターではなくリスナーを使用するか、少なくとも解析ツリーが深すぎないことを検証する必要があります。このため、一部の企業のコーディング慣行では、末尾以外の再帰を阻止または完全に禁止しています。

12
Alex Reinking

本の120ページから。

組み込みのJava戻り値メカニズムを使用できるため、アプリケーション固有の戻り値が必要な場合、ビジターは非常にうまく機能します。子を訪問するためにビジターメソッドを明示的に呼び出す必要がない場合は、リスナーメカニズムに切り替えることができますが、残念ながら、これはJavaメソッドの戻り値を使用することのクリーンさをあきらめることを意味します。

これがビジターを利用する理由です。

0
p_champ