【问题标题】:Tree Transformations Using Visitor Pattern使用访问者模式的树转换
【发布时间】:2009-12-14 04:41:00
【问题描述】:

(免责声明:这些示例是在构建编译器的上下文中给出的,但是这个问题都是关于访问者模式的,不需要任何编译器理论知识。)我正在阅读 Andrew Appel 的 Java 中的现代编译器实现尝试自学编译器理论(所以不,这不是家庭作业),我无法理解他想如何使用访问者模式将 AST 转换为 IR 树。 (注意:我在 Python 中执行此操作,因此我也可以学习 Python,这就是为什么即将出现的示例不在 Java 中的原因。)据我了解,访问者模式中的访问和接受方法是设计为无效类型的,所以如果我有类似的东西

class PlusExp(Exp):
    def __init__(self, exp_left, exp_right):
        self.exp_left = exp_left
        self.exp_right = exp_right

    def accept(self, v):
        v.visit_plus_exp(self)

那么我希望能够编写一个访问者方法,例如

def visit_plus_exp(self, plus_exp):
    return BINOP(BinOp.PLUS, 
                 plus_exp.exp_left.accept(self), 
                 plus_exp.exp_right.accept(self))

这会将两个子表达式转换为 IR,然后将它们与表示加号表达式的 BINOP 链接起来。当然,除非我修改所有接受函数以返回额外信息,否则这是不可能的,这也很麻烦,因为有时您只需要一个不返回任何内容的打印访问者。然而,本文坚持访问者是正确的方式,在 Java 中,这意味着它可以在没有 Python 的灵活性的情况下完成。我想不出任何不是非常 hacky 的解决方案 - 任何人都可以告诉我预期的设计吗?

【问题讨论】:

  • 嘿,我也想学编译器……那本书是学习编译器的好书吗???
  • 我觉得作为介绍还是挺不错的。它会引导您完成(无需牵手)编译器的实现,该编译器将 Java 的一个子集作为其源并发出本机代码。它还包含一系列编译器理论的高级主题。
  • 是否涵盖代码生成部分?
  • 是的 - 请参阅我之前的评论,我说过它会发出本机代码。
  • 是的,你做到了!!!谢谢哥们:-)

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


【解决方案1】:

SAX 解析器是一种访问者。为避免向方法添加返回值,可以使用堆栈:

class Visitor {
    Stack<Node> stack = new Stack<Node>();

//    . . .

    void visitPlus(PlusExp pe) {
        pe.left.accept(this);
        pe.right.accept(this);
        Node b = stack.pop();
        Node a = stack.pop();
        stack.push(new BinOp(BinOp.PLUS, a, b));
    }

【讨论】:

  • 是的,这就是我想要的。非常感谢。
【解决方案2】:

查看THIS编译器的源代码。我认为这家伙使用了访问者模式。

【讨论】:

  • 是的,他的代码只是实现了访问/接受函数以在需要时返回值。我可以这样做,它在 Python 中会比在 Java 中更干净,但我想知道是否没有按规定使用 Visitor 的预期方法。
  • 对不起:(我不知道我能不能做得更好。
【解决方案3】:

警告:我没有读过那本书。

该方法可能是 void 类型的,但在 Java 中(本书是为它编写的)它也是对象的一部分。因此,visitor 方法可以在本地成员变量中构建结构,从而在调用之间维护必要的上下文。

因此,例如,您的打印访问者将附加到作为成员变量(或作为创建访问者对象的方法中的最终局部变量)保存的 StringBuilder 中——这在 Java 中相当常见,其中创建小的匿名内部类对象是一种常见的习惯)。

在 python 中,您可以类似地让访问者方法访问非方法局部变量以维护上下文并构建结构。例如,闭包或小对象。

更新——从下面的评论中添加了一小段代码作为示例

result = new Node();
result.left.add(n1.accept(this)); 
result.right.add(n2.accept(this)); 
return result;

result = new Node(); 
this.nextLoc.add(result); 
this.nextLoc = result.left; 
n1.accept(this); 
this.nextLoc = result.right; 
n2.accept(this); 

第一个更漂亮(尽管仍然是糟糕的注释示例代码),但第二个可以让你保留 void 返回类型,如果你真的需要的话。

【讨论】:

  • 对不起,我想你误解了——打印访问者很容易构建,因为每个节点的字符串表示可以在访问节点时写入输出流。但是这里的问题是,如果我们想要构建一棵树,那么当我们访问完一个子节点后,我们必须知道要将它附加到哪个父节点,以及该父节点的哪个属性。
  • 我可能误解了您,但在我看来,无论您是更改返回类型还是更改“下一个位置”目标引用,访问者代码总是设置结果的位置下一个(更深层次的)调用。 (请参阅添加到答案的低质量代码。)
  • 对 - 你最初没有提到位置参考,但这更接近我想要的。我也想过,但我认为这是有问题的(尽管我可能会弄错),因为 nextLoc 只会指向我要设置的位置,因此直接分配给它实际上不会影响它指向的位置到。相反,我认为(至少在 Python 中)它可以通过构造和传递一个设置下一个位置的函数来完成,但在 Java 中这是不可能的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多