私はCのような言語のコンパイラーを書いていて、抽象的な構文ツリーをトラバースするエレガントな方法を探しています。 Visitorパターンを実装しようとしていますが、正しく実行しているとは確信していません。
_struct Visitor {
// Expressions
virtual void visit(AsgnExpression&);
virtual void visit(ConstantExpression&);
...
virtual void visit(Statement&);
...
virtual void finished(ASTNode&);
protected:
virtual void visit(ASTNode&) = 0;
};
_
visit
はタイプごとにオーバーロードされます。デフォルトでは、各オーバーロードがvisit(ASTNode&)
を呼び出し、サブクラスは強制的に実装されます。これにより、型ごとにvisit
を定義するのは面倒ですが、すばやく簡単に行うことができます。 ASTNode
の各サブクラスは、ツリー構造をトラバースするために使用されるaccept
メソッドを実装する必要があります。
_class ASTNode {
public:
virtual ~ASTNode();
virtual void accept(Visitor& visitor) = 0;
};
_
ただし、accept
メソッドはよく似ているため、この設計はすぐに退屈になります。
誰が構造、ノード、またはビジターのトラバースを担当する必要がありますか?私はASTNode
に子にアクセスするためのイテレーターを提供させ、ビジターに構造をトラバースさせるようにしています。抽象構文木を設計した経験があれば、私にあなたの知恵を共有してください!
トラバーサルの責任者は、ビジターで実行する分析、言語構造の詳細、および個人の好みの一部に大きく依存します。
特に、親ノードのビジターが子の処理の途中でアクションを実行する必要がある場合は、トラバーサルロジックをビジターに配置する必要があります。たとえば、言語に、新しく導入された変数が、変数を導入するノードの子ノードの一部でのみ使用可能な構成がある場合。
もう1つのケースは、プレオーダートラバースとポストオーダートラバースの混合が必要な場合です。ノード内のトラバーサルでは、各ノードがビジターを2回呼び出す必要があります。その場合、訪問者にトラバーサルを行わせる方が簡単な場合があります。
そうでなければ、それは主に好みの問題です。トラバーサルは、ノード内またはビジター内のいずれかです。
異なる問題には、異なるトラバーサル戦略が必要です。したがって、私の提案は、戦略パターンを使用して別のトラバーサーを実装することです。
Visitorパターンには、多くのボイラープレートコードが必要になる場合があります。これは実際には自動コード生成に適しています(Javaの jaxb-visitor を参照)。私の提案は、基本的にすべてのノードタイプに対して何もしない基本/デフォルトのビジターを実装することです。何か面白いことをしようとしている訪問者は、ベースの定型訪問者を拡張する必要があります。新しいノードタイプがビジターインターフェースに追加された場合、基本クラスに空の実装を提供するだけで済みます。それ以外の場合は、新しいノードタイプを追加するたびに、すべての実装を探し、新しいメソッドを追加する必要があります。