乐者为王

Do one thing, and do it well.

ANTLR 4权威参考读书笔记(22)

基于监听器的方法是极好的,因为所有树遍历和方法触发被自动完成。尽管有时候自动树遍历也是一个缺点,因为我们不能控制遍历本身。例如,我们可能想遍历一段C程序的语法分析树,通过跳过函数体子树忽略函数中的一切。监听器事件方法也不能使用方法返回值去传递数据。当我们需要控制遍历或返回带有事件方法返回值的值时,我们使用访问者模式。现在,让我们构建一个基于访问者版本的属性文件加载器去比较这两种方法。

使用访问者实现应用

使用访问者代替监听器,我们只需要让ANTLR生成访问者接口和实现接口,然后创建一段测试代码在语法分析树上调用visit(),根本不需要触及到语法。

在命令行使用-visitor参数时,ANTLR生成接口PropertyFileVisitor和类PropertyFileBaseVisitor,后者有如下的默认实现:

1
2
3
4
5
6
7
public class PropertyFileBaseVisitor<T> extends AbstractParseTreeVisitor<T>
                                        implements PropertyFileVisitor<T> {
    @Override
    public T visitFile(PropertyFileParser.FileContext ctx) { ... }
    @Override
    public T visitProp(PropertyFileParser.PropContext ctx) { ... }
}

我们可以从监听器的exitProp()中拷贝映射功能,然后把它粘贴到与规则prop相关的访问者方法中。

1
2
3
4
5
6
7
8
9
10
public class TestPropertyFileVisitor {
    public static class PropertyFileVisitor extends PropertyFileBaseVisitor<Void> {
        Map<String,String> props = new OrderedHashMap<String, String>();
        public Void visitProp(PropertyFileParser.PropContext ctx) {
            String id = ctx.ID().getText();    // prop : ID '=' STRING '\n' ;
            String value = ctx.STRING().getText();
            props.put(id, value);
            return null;    // Java says must return something even when Void
        }
    }

这里是访问者接口和类之间的继承关系:

propertyfile-visitor-hierarchy

访问者在子节点上通过显式地调用接口ParseTreeVisitor的visit()方法遍历语法分析树。那些方法在AbstractParseTreeVisitor中实现。在这里,为prop调用创建的节点没有子树,因此visitProp()不需要调用visit()。

在监听器和访问者的测试代码(例如TestPropertyFileVisitor)之间最大的不同是访问者的测试代码不需要ParseTreeWalker,它只需要让访问者去访问由语法分析器创建的树。

1
2
3
PropertyFileVisitor loader = new PropertyFileVisitor();
loader.visit(tree);
System.out.println(loader.props);    // print results

以下是构建和测试序列:

1
2
3
antlr -visitor PropertyFile.g  # create visitor as well this time
compile *.java
run TestPropertyFileVisitor t.properties

这里是输出的内容:

1
{user="parrt", machine="maniac"}

我们可以使用监听器和访问者构建几乎任何我们想要的。一旦我们处于Java空间,就不再需要学习更多的ANTLR知识。我们只要知道语法、语法分析树、监听器和访问者事件方法之间的关系。除此之外,就是代码。在对识别中的输入短语的回答中,我们可以生成输出、收集信息、以某种方式验证短语,或者执行计算。

Comments