antlr4-共享上下文(六)

当我们收集信息或者计算值的时候,最好的方式是传递参数和返回值,而不是使用全局变量或者类变量。
由于antlr自动对监听器生成了不带参数和返回值的方法。antlr对访问器也生成了没有应用指定参数的方法。

这一小节中,我们将探索基于事件的处理方式。我们将构建3个不同的实现计算器的例子来说明问题。

Visitor方式

通常的访问器并没有指定返回值,在这个例子中,我们只需要返回Integer对象即可实现功能。

public static class EvalVisitor extends LExprBaseVisitor<Integer> {
    public Integer visitMult(LExprParser.MultContext ctx) {
        return visit(ctx.e(0)) * visit(ctx.e(1));
    }

    public Integer visitAdd(LExprParser.AddContext ctx) {
        return visit(ctx.e(0)) + visit(ctx.e(1));
    }

    public Integer visitInt(LExprParser.IntContext ctx) {
        return Integer.valueOf(ctx.INT().getText());
    }
}

注意在这个例子中我们并没有对规则s进行定义,默认的实现在LExprBaseVisitor如下:

@Override public T visitS(@NotNull LExprParser.SContext ctx) { return visitChildren(ctx); }

visitChildren()方法返回最后一个子节点返回的值。

Listener方式

在listener方式中,我们使用Stack来保存返回的值。

public static class Evaluator extends LExprBaseListener {
    Stack<Integer> stack = new Stack<Integer>();

    public void exitMult(LExprParser.MultContext ctx) {
        int right = stack.pop();
        int left = stack.pop();
        stack.push( left * right );
    }

    public void exitAdd(LExprParser.AddContext ctx) {
        int right = stack.pop();
        int left = stack.pop();
        stack.push(left + right);
    }

    public void exitInt(LExprParser.IntContext ctx) {
        stack.push( Integer.valueOf(ctx.INT().getText()) );
    }
}

annotating方式

为了代替在事件的方式中需要临时存储空间,我们可以存储这些值在解析树上。我们可以用树注解的方式在visitor或者listener中。下面将使用listener解释如何使用annotating。

每个子表达式都有一个相应的子根(对一个e规则的调用)。从e节点向右的箭头指向的是一个局部的结果,我们可以认为是返回值。

所以,我们可以按照下面这种方式来赋值。但是不幸的是,我们不能在java中通过继承ExprContext来动态的添加字段value的值(Ruby和Python可以)。

public void exitAdd(LExprParser.AddContext ctx) {
// e(0).value is the subexpression value of the first e in the alternative
    ctx.value = ctx.e(0).value + ctx.e(1).value; // e '+' e # Add
}

所以,我们用一个map来保存所有的值。

public static class EvaluatorWithProps extends LExprBaseListener {
    /** maps nodes to integers with Map<ParseTree,Integer> */
    ParseTreeProperty<Integer> values = new ParseTreeProperty<Integer>();

    /** Need to pass e's value out of rule s : e ; */
    public void exitS(LExprParser.SContext ctx) {
        setValue(ctx, getValue(ctx.e())); // like: int s() { return e(); }
    }

    public void exitMult(LExprParser.MultContext ctx) {
        int left = getValue(ctx.e(0));  // e '*' e   # Mult
        int right = getValue(ctx.e(1));
        setValue(ctx, left * right);
    }

    public void exitAdd(LExprParser.AddContext ctx) {
        int left = getValue(ctx.e(0)); // e '+' e   # Add
        int right = getValue(ctx.e(1));
        setValue(ctx, left + right);
    }

    public void exitInt(LExprParser.IntContext ctx) {
        String intText = ctx.INT().getText(); // INT   # Int
        setValue(ctx, Integer.valueOf(intText));
    }

    public void setValue(ParseTree node, int value) { values.put(node, value); }
    public int getValue(ParseTree node) { return values.get(node); }
}

//保存树的节点和值的map
public class ParseTreeProperty <V> {
    protected java.util.Map<org.antlr.v4.runtime.tree.ParseTree,V> annotations;

    public ParseTreeProperty() { /* compiled code */ }

    public V get(org.antlr.v4.runtime.tree.ParseTree node) { /* compiled code */ }

    public void put(org.antlr.v4.runtime.tree.ParseTree node, V value) { /* compiled code */ }

    public V removeFrom(org.antlr.v4.runtime.tree.ParseTree node) { /* compiled code */ }
}

对比

  • 本地java调用栈:访问者方法返回了用户定义的类型的值。如果访问者需要传递参数,也需要用到下面2中技术。
  • 基于栈: 栈字段模拟了java的参数和返回值。
  • 注解:map字段映射了节点和值的关系。