对于复杂的标题感到抱歉,但只用一句话就难以解释。
所以我正在编写一种简单的解释语言来帮助我经常做的一些事情。我有一个词法分析器,用于抽象语法树生成器。
抽象语法树吐出表达式。 (我正在使用unique_ptrs传递)。有几种类型的表达式派生自这个基类,包括:
等。每个派生类都包含该表达式所需的信息,即变量包含其标识符的std :: string,二进制操作包含左侧和右侧的unique_ptrs以及运算符的char。
现在这完全正常,表达式的解析就像它们应该的那样。
This is what an AST would look like for 'x=y*6^(z-4)+5'
+--Assignment (=)--+
| |
Var (x) +--------BinOp (+)----+
| |
5 +------------BinOp (*)---+
| |
+---------BinOp (^)-------+ Var (y)
| |
Num (6) +------BinOp (-)-----+
| |
Var (z) Num (4)
当尝试将AST与解释器分离时出现问题。我想保持它解耦,以防我想在将来为编译提供支持,或者其他什么。此外,AST已经变得相当复杂,我不想添加它。我只希望AST获得有关如何获取令牌的信息,并按正确的顺序将它们转换为表达式树。
现在,解释器应该能够遍历这个自上而下的表达式列表,并递归地评估每个子表达式,向内存添加定义,评估常量,为其函数分配定义等等。但是,每个评估必须返回一个值,以便我可以递归遍历表达式树。
例如,二元运算表达式必须递归地计算左侧和右侧,然后执行两侧的相加并返回。
现在,问题是,AST返回指向基类Expr 的指针 - 而不是派生类型。调用getExpression返回下一个表达式,无论它的派生类型如何,这使我能够轻松地递归计算二进制操作等。为了让解释器获取有关这些表达式的信息(例如数字值或标识符),我会有基本上动态地转换每个表达式并检查它是否有效,我必须重复这样做。另一种方法是执行类似访问者模式的操作--Expr调用解释器并将 this 传递给它,这允许解释器为每个派生类型提供多个定义。但同样,解释器必须返回一个值!
这就是为什么我不能使用访问者模式 - 我必须返回值,这会将AST完全耦合到解释器。
我也不能使用策略模式,因为每个策略都会返回截然不同的东西。例如,解释器策略与LLVM策略有太大的不同。
我完全不知道该怎么做。一个非常好的解决方案是将每个表达式类型的枚举作为expr基类的成员,并且解释器可以检查类型然后进行适当的类型转换。但那很难看。真的很难看。
我有什么选择?谢谢!
答案 0 :(得分:1)
通常的答案(与大多数解析器生成器一样)是同时具有令牌类型值和关联数据(在讨论此类事物时称为属性)。类型值通常是一个简单的整数,并表示" number"," string" "二元运算"在决定使用哪种产品时,只检查令牌类型,当你获得与生产规则的匹配时,就可以知道哪种令牌符合该规则。
如果你想实现这个,你自己查找解析算法(LALR和GLR是几个例子),或者你可以切换到使用解析器生成器,只需要担心让你的语法正确,然后正确实现生产并且不必担心自己实现解析引擎。
答案 1 :(得分:0)
为什么不能使用访客模式?任何返回结果都会变成本地状态:
class EvalVisitor
{
void visit(X x)
{
visit(x.y);
int res1 = res();
visit(x.z);
int res2 = res();
res(res1 + res2);
}
....
};
以上内容可以抽象出来,以便逻辑处于正确的评估范围内 功能:
class Visitor
{
public:
virtual void visit(X) = 0;
virtual void visit(Y) = 0;
virtual void visit(Z) = 0;
};
class EvalVisitor : public Visitor
{
public:
int eval(X);
int eval(Y);
int eval(Z);
int result;
virtual void visit(X x) { result = eval(x); }
virtual void visit(Y y) { result = eval(y); }
virtual void visit(Z z) { result = eval(z); }
};
int evalExpr(Expr& x)
{
EvalVisitor v;
x.accept(v);
return x.result;
}
然后你可以这样做:
Expr& expr = ...;
int result = evalExpr(expr);