Update接受了 Ira Baxter 的回答,因为它为我指明了正确的方向:我首先通过开始编译阶段的实现来弄清楚我真正需要什么,很快就很明显,节点内的遍历使这个方法成为不可能的方法。并非所有节点都应该被访问,其中一些节点以相反的顺序访问(例如,首先是赋值的 rhs,以便编译器可以检查类型是否与 rhs/操作符匹配)。将遍历放在访问者中使这一切变得非常容易。
在决定对应用程序中使用的迷你语言的处理进行重大重构之前,我正在玩弄 AST 等。我已经构建了一个 Lexer/Parser 并且可以很好地获得 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 非常简单:所有抽象的访问者方法只是打印出终端的名称或值成员。对于非终端,它取决于;使用默认的访问者遍历打印作业可以正常工作,因为需要有条件的额外代码:
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 中的条件访问方法和 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?
}
};
问题:这种方法不适合我的情况,还是我忽略了一些重要的东西?是否有共同的设计来应对这些问题?如果我还需要在不同的方向遍历怎么办?