Updateは、正しい方向を示したため、Ira Baxterの回答を受け入れました。コンパイル段階の実装を開始することで、実際に必要なものを最初に理解しました。ノード内をトラバースすると、不可能なアプローチになることがすぐに明らかになりました。すべてのノードにアクセスする必要はなく、一部のノードには逆の順序でアクセスする必要があります(たとえば、最初に割り当てのrhsを実行して、コンパイラーがタイプがrhs / operatorと一致するかどうかを確認できるようにします)。訪問者にトラバーサルを配置すると、これはすべて非常に簡単になります。
アプリケーションで使用されるミニ言語の処理の主要なリファクトリーを決定する前に、ASTなどで遊んでいます。レクサー/パーサーを作成しましたが、ASTを問題なく取得できます。ビジターもあり、具体的な実装として、元のソースファイルを再作成するだけのASTToOriginalを作成しました。最終的には、Vsisitorも実装し、実行時に実際のC ++コードを作成するある種のコンパイラーが登場するので、最初からすべてが正しいことを確認したいと思います。現在はすべて正常に機能していますが、トラバーサル順序はビジター自体に実装されているため、類似した/重複したコードがいくつかあります。
より多くの情報を調べるとき、いくつかの実装は、各具体的な訪問者でこれを繰り返さないために、代わりに訪問されたオブジェクト自体でトラバーサル順序を維持することを好むようです。同じように、GoFでさえこれについて簡単に話します。だから私もこのアプローチを試してみたかったのですが、すぐに行き詰まりました。説明させてください。
サンプルソースラインと対応するASTノード:
if(t>100?x=1;sety(20,true):x=2)
Conditional
BinaryOp
left=Variable [name=t], operator=[>], right=Integer [value=100]
IfTrue
Assignment
left=Variable [name=x], operator=[=], right=Integer [value=1]
Method
MethodName [name=sety], Arguments( Integer [value=20], Boolean [value=true] )
IfFalse
Assignment
left=Variable [name=x], operator=[=], right=Integer [value=1]
いくつかのコード:
class BinaryOp {
void Accept( Visitor* v ){ v->Visit( this ); }
Expr* left;
Op* op;
Expr* right;
};
class Variable {
void Accept( Visitor* v ){ v->Visit( this ); }
Name* name;
};
class Visitor { //provide basic traversal, terminal visitors are abstract
void Visit( Variable* ) = 0;
void Visit( BinaryOp* p ) {
p->left->Accept( this );
p->op->Accept( this );
p->right->Accept( this );
}
void Visit( Conditional* p ) {
p->cond->Accept( this );
VisitList( p->ifTrue ); //VisitList just iterates over the array, calling Accept on each element
VisitList( p->ifFalse );
}
};
ASTToOriginalの実装は非常に簡単です。すべての抽象Visitorメソッドは、ターミナルの名前または値のメンバーを出力するだけです。非終端記号の場合、それは依存します。条件付きの追加コードが必要なため、割り当ての印刷はデフォルトのビジタートラバーサルで問題なく機能します。
class ASTToOriginal {
void Visit( Conditional* p ) {
str << "if(";
p->cond->Accept( this );
str << "?";
//VisitListWithPostOp is like VisitList but calls op for each *except the last* iteration
VisitListWithPostOp( p->ifTrue, AppendText( str, ";" ) );
VisitListWithPostOp( p->ifFalse, AppendText( str, ";" ) );
str << ")";
}
};
したがって、VisitorのConditionalのVisitメソッドとASTToOriginalの両方が実際に非常に似ていることがわかります。ただし、ノードにトラバーサルを配置してこれを解決しようとすると、事態が悪化するだけでなく、完全に混乱します。PreVisitメソッドとPostVisitメソッドを使用していくつかの問題を解決するアプローチを試しましたが、ノードにコードを追加するだけでした。また、閉じ括弧などをいつ追加するかを知るために、訪問者内のいくつかの状態を追跡する必要があるように見え始めました。
class BinaryOp {
void Accept( Conditional* v ) {
v->Visit( this );
op->Accept( v )
VisitList( ifTrue, v );
VisitList( ifFalse, v );
};
class Vistor {
//now all methods are pure virtual
};
class ASTToOriginal {
void Visit( Conditional* p ) {
str << "if(";
//now what??? after returning here, BinaryOp will visit the op automatically so I can't insert the "?"
//If I make a PostVisit( BinaryOp* ), and call it it BinaryOp::Accept, I get the chance to insert the "?",
//but now I have to keep a state: my PostVisit method needs to know it's currently being called as part of a Conditional
//Things are even worse for the ifTrue/ifFalse statement arrays: each element needs a ";" appended, but not the last one,
//how am I ever going to do that in a clean way?
}
};
質問:このアプローチは私の場合には適していないのですか、それとも私は何か重要なものを見落としていますか?これらの問題に対処するための共通の設計はありますか?別の方向へのトラバーサルも必要な場合はどうなりますか?