如何使用Visitor模式漂亮地打印AST?

时间:2019-05-25 14:45:58

标签: ruby abstract-syntax-tree interpreter visitor

我正在尝试使用Visitor模式构建一个简单的解释器。我很难理解如何使用这种模式来实现诸如漂亮地打印树之类的任务。

我试图获得的结果是使用适当的缩进打印AST:

Expr
'---Abstr
    |---Id
    '---Expr
        '---App
            '---Atom
                '---Id

我定义了许多表示AST中节点的类:

class ASTNode
    attr_reader :children, :pos

    def initialize(children, pos)
         @children = children
         @pos = pos
    end

    def accept(visitor)
        visitor.visit(self)
        @children.each { |child| child.accept(visitor) } unless @children.nil?
    end
end

class ExprNode < ASTNode
    def initialize(children, pos)
         super(children, pos)
    end
end

...

和执行双重调度的基本访问者类:

class Visitor
    def visit(subject)
        method_name = "visit_#{subject.class}".intern
        send(method_name, subject)
    end
end

最后,打印AST的访问者:

class PrintVisitor < Visitor
    def visit_ExprNode(subject)
    end

    def visit_AbstrNode(subject)
    end

    ...
end

1 个答案:

答案 0 :(得分:1)

访问者模式有两种版本:一种版本只负责双重调度,另一种版本通过自动访问节点的子代来解决迭代。后一种版本的灵活性较差,因为您可以提前确定所需的遍历类型(预定或后置),而不是将决策权留给单个访问者。它还迫使您只访问所有节点一次(在许多情况下,例如在实现AST解释器时,您将不需要访问该节点)。

在您的代码中,您实际上正在实现这两个版本:您的Visitor#visit方法实现了普通的访问者模式,而ASTNode#accept实现了带有迭代的模式。这是accept方法的怪异用法,因为通常accept方法的工作只是在访问者上调用特定的visit方法(例如visit_whatever),以使双重分发工作。由于您已经使用反射来实现双重调度,因此根本不需要accept方法。

  

我假定应该在PrintVisitor的visit_ * Node(subject)方法中实现打印

是的。

  

打印每个节点需要附加的上下文以确定正确的缩进级别。

也正确。您可以通过将缩进级别存储在实例变量中来跟踪缩进级别。然后,给定的访问者方法将以给定的缩进量打印其内容,增加缩进级别,访问其子注释,然后再次减小缩进。像这样:

def visit_SomeNode(some_node)
    puts "#{@indent * " "}---SomeNode"
    @indent += 4
    some_node.children.each {|child| visit(child)}
    @indent -= 4
end

您还可以将some_node.children.each {|child| visit(child)}放入其自己的visit_children(node)方法中,仅在要对所有子项执行相同操作的情况下调用该方法(如上所述)。

如果要避免该可变状态,还可以调整访问者类,以允许将参数传递给visit,如下所示:

class Visitor
    def visit(subject, *args)
        method_name = "visit_#{subject.class}".intern
        send(method_name, subject, *args)
    end
end

然后,您可以在方法中添加缩进级别的参数,并在访问孩子时将增加的缩进级别传递给visit