9

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

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的解决方案 - 任何人都可以告诉我预期的设计吗?

4

3 回答 3

11

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));
    }
于 2009-12-14T10:11:11.277 回答
1

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

于 2009-12-14T05:18:06.550 回答
0

警告:我没看过那本书。

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

因此,例如,您的打印访问者将附加到作为成员变量(或作为创建访问者对象的方法中的最终局部变量)保存的 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 返回类型。

于 2009-12-14T05:02:54.867 回答