当算法需要派生类的知识时,将算法与数据解耦

Decoupling algorithm from data, when the algorithm needs knowledge of derived classes

本文关键字:算法 数据 解耦 派生 知识      更新时间:2023-10-16

很抱歉标题很复杂,但用一句话很难解释。

所以我正在写一个简单的解释语言来帮助我经常做的一些事情。我设置了一个词法分析器,输入到一个抽象的语法树生成器中。

抽象语法树吐出表达式。(我正在使用unique_ptrs传递(。从此基类派生的表达式有几种类型,其中包括:

  • 数字
  • 变量
  • 函数调用/原型
  • 二进制操作

等。每个派生类都包含该表达式所需的信息,即变量包含其标识符的 std::string,二进制操作包含左侧和右侧的 unique_ptrs 以及运算符的字符。

现在,这工作得很好,表达式被解析得很正常。

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 会返回下一个表达式,无论它是派生类型如何,这让我可以轻松地递归地计算二进制操作等。为了让解释器获取有关这些表达式的信息(例如数字值或标识符(,我必须基本上动态转换每个表达式并检查它是否有效,并且我必须重复执行此操作。另一种方法是做一些类似于 Visitor 模式的事情 – Expr 调用解释器并将其传递给它,这允许解释器为每个派生类型有多个定义。但同样,解释器必须返回一个值

这就是为什么我不能使用访问者模式的原因——我必须返回值,这会将 AST 完全耦合到解释器。

我也不能使用策略模式,因为每个策略返回的东西都大不相同。例如,解释器策略与LLVM策略差异太大。

我完全不知道该怎么做。一个非常容易解决的解决方案是将每个表达式类型的枚举作为 expr 基类的成员,解释器可以检查类型,然后进行适当的类型转换。但这很丑陋。真的很丑。

我在这里有什么选择?谢谢!

通常的答案(与大多数解析器生成器一样(是同时具有令牌类型值和关联数据(在讨论此类内容时称为属性(。类型值通常是一个简单的整数,并表示"数字","字符串","二进制操作"等。在决定使用哪种生产时,您只检查令牌类型,当您获得与生产规则匹配时,您就会知道哪种令牌馈送到该规则中。

如果你想自己实现这一点,请查找解析算法(LALR 和 GLR 是几个例子(,或者你可以切换到使用解析器生成器,只需要担心语法正确,然后正确实现产品,而不必关心自己实现解析引擎。

为什么不能使用访客模式?任何返回结果都只是成为本地状态:

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);