当我们收集信息或者计算值的时候,最好的方式是传递参数和返回值,而不是使用全局变量或者类变量。
由于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字段映射了节点和值的关系。