当算法需要知道派生类

时间:2018-05-08 22:18:21

标签: c++ inheritance design-patterns decoupling

对于复杂的标题感到抱歉,但只用一句话就难以解释。

所以我正在编写一种简单的解释语言来帮助我经常做的一些事情。我有一个词法分析器,用于抽象语法树生成器。

抽象语法树吐出表达式。 (我正在使用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基类的成员,并且解释器可以检查类型然后进行适当的类型转换。但那很难看。真的很难看。

我有什么选择?谢谢!

2 个答案:

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