【问题标题】:Expression tree and implementation of visitor pattern表达树及访问者模式的实现
【发布时间】:2014-01-04 06:52:02
【问题描述】:

我有一个对象的层次结构来表示算术表达式。类似于以下内容:

      TreeNode
     /        \
    /          \
   /            \ 
NumericNode    BinaryOpNode
              /  |    \    \
             /   |     \    \
            /    |      \    \   
      AddNode MulNode DivNode ...etc  

我的词法分析器和解析器正在工作。现在,我正在尝试找出以实用且易于适应的方式对生成的 AST 执行操作的最佳方法。 现在我已经用一个访问者和访问接口实现了访问者模式,但是我不确定我是否真的需要所有的具体类来实现访问者接口。

由于我想控制访问者的遍历顺序,我发现自己重复了很多代码。例如,对于 PrintInOrder 访问者,我有如下方法:

    public void visit(AddNode node) { 
       node.getLeft().accept(this);
       System.out.print(node + " ");
       node.getRight().accept(this);
    }

代表某个算术运算的每个具体节点。

但我可以在超类中实现相同的实现 Visitable。例如,相同的 PrintInOrder 访问者看起来像:

    public void visit(BinaryOpNode node) { 
       node.getLeft().accept(this);
       System.out.print(node + " ");
       node.getRight().accept(this);
    }

但是,我不知道这项任务最常用的方法是什么。

问题 1: 如果我希望我的设计让我几乎可以通过 AST 做任何我想做的事情,我真的需要访问所有具体节点并编写所有重复的代码吗?

问题 2:假设访问者可以返回某种类型,例如:

public interface Visitor<T> {
    public T visit(AddNode node);
    public T visit(MulNode node);
        ...
}

这看起来比普通的 void 版本更通用。但它有有用的好处吗?并且可以和void版本一样使用吗?

【问题讨论】:

    标签: java parsing abstract-syntax-tree visitor-pattern


    【解决方案1】:

    答案1:您可以使用抽象超类访问方法和任何通用功能,这在每个具体类中都是相同的,因此每个具体类都可以从这个抽象类派生并使用该方法而无需实现这是一种多余的方式。

    如图所示,您可以在单独的类中实现访问者逻辑。这样,您可以将遍历顺序以及访问节点的实现与 Node 实现解耦。如果您根据算术运算优先级构建了抽象语法树,则不必更改不同类型节点的实现。您可以为它们中的每一个使用相同的 ConcreteVisitor。

    答案 2:是的,使用泛型类有很多好处。这样你就可以在编译时进行类型检查。如果你没有这个机会,你可能隐藏了 bug,很难找到,但是如果你可以检查类型有效性编译时间,你可以确保你的系统中没有这种类型的 bug。

    【讨论】:

    • 拜托,你能给出第一个答案的一些伪代码示例吗?
    【解决方案2】:

    到目前为止,我用来解析和解释简单算术表达式的最佳方法是构建 Pratt 解析器。下面的链接很好地解释了该过程,示例甚至是用 java 编写的。

    基本上,您最终将拥有单独的迷你解析器,作为您的应用支持的每个运算符的函数。您的所有运算符都将分类为前缀、中缀或后缀。此外,您将在评估每个子表达式时为您将引用的每个运算符分配一个优先级值。

    我用 C# 编写了一个 ECMAScript 解释器,它使用递归后代解析器来处理语言结构和评估控制流语句。而且,它使用 Pratt 解析器来评估表达式。 Pratt 解析器为您提供了一种处理运算符关联性和优先级的方法。

    我只使用访问者模式从 AST 本身生成漂亮打印和无空格格式的代码。其他一切都是使用继承和接口完成的。如果您真的想使用访问者的想法,那么基于堆栈的表达式解析器就是您想要制作的。 Pratt Parser 只是使用您应用程序的调用堆栈。

    作为一名 java 程序员,我想你会发现这篇文章特别有帮助。无论哪种方式。保重,祝你好运。

    http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/

    【讨论】:

    • 不错的信息。我从龙书开始,但我还没有在那里找到任何关于“普拉特解析器”的信息。
    • 别忘了抓紧学习作者的bantam语言的源代码。那里的信息比文章提到的要多,它基本上应该澄清一切。它具有所有表达式类和 parselets。
    • 我对我的经典静态方法很满意,(我可以使用显式堆栈来实现,但我的是递归的)。从我读到的内容来看,pratt 解析器是解析器生成器的一个很好的解决方案,它很容易根据用户需求进行扩展,我将在接下来的解析步骤中考虑到这一点。谢谢。
    • 没问题,很高兴能帮上忙。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    相关资源
    最近更新 更多