Calculator.g4
,stat和expr两个parser rule含有多个rule element,我们这两个parse rule的每个rule element添加了Alternative labels(简称label)#
开头。// 定义stat,不添加label
stat: expr | ID '=' expr ;// 定义expre,每个rule element都添加label
expr: expr op=(MUL|DIV) expr| expr op=(ADD|SUB) expr | INT | ID | '(' expr ')' ;
- Labeling Rule Alternatives for Precise Event Methods, we can get more precise parse-tree listener events by labeling the outermost alternatives of a rule using the # operator.
- All alternatives within a rule must be labeled, or none of them. Here are two rules with labeled alternatives.
- Alternative labels do not have to be at the end of the line and there does not have to be a space after the # symbol. ANTLR generates a rule context class definition for each label.
总结起来如下:
添加label,可以为每个rule element生成对应的ParserRuleContext
,从而快速访问各rule element
CalculatorParser.StatContext
的getter方法获取rule elementpublic static class StatContext extends ParserRuleContext {// getter方法public ExprContext expr() {return getRuleContext(ExprContext.class,0);}public TerminalNode ID() { return getToken(CalculatorNoLabelParser.ID, 0); }... // 其他代码省略
}
public static class StatContext extends ParserRuleContext {public StatContext(ParserRuleContext parent, int invokingState) {super(parent, invokingState);}@Override public int getRuleIndex() { return RULE_stat; }public StatContext() { }public void copyFrom(StatContext ctx) {super.copyFrom(ctx);}
}
public static class AssignContext extends StatContext {public TerminalNode ID() { return getToken(CalculatorParser.ID, 0); }public ExprContext expr() {return getRuleContext(ExprContext.class,0);}... // 其他代码省略
}
public static class PrintExprContext extends StatContext {public ExprContext expr() {return getRuleContext(ExprContext.class,0);... // 其他代码省略
}
同时,CalculatorListener
接口中的监听器方法也更加丰富,以便更加精确地监听parse tree中的节点访问事件
// 未添加label,只能监听stat节点。具体是赋值节点还是打印节点,需要在代码中区分
void enterStat(CalculatorNoLabelParser.StatContext ctx);
void exitStat(CalculatorNoLabelParser.StatContext ctx);// 添加label后的监听器方法更加有针对性
void enterPrintExpr(CalculatorParser.PrintExprContext ctx);
void exitPrintExpr(CalculatorParser.PrintExprContext ctx);
void enterAssign(CalculatorParser.AssignContext ctx);
void exitAssign(CalculatorParser.AssignContext ctx);
自己的补充: CalculatorVisitor
接口中的visitXxx()方法也将变得更加丰富,以便更加精确地访问rule element,从而访问parse tree中的各节点
// 未给添加label,只能访问stat节点。具体是赋值节点还是打印节点,需要在代码中区分
T visitStat(CalculatorNoLabelParser.StatContext ctx);// 添加label后,visitStat()方法被以下有针对性的方法替代
T visitPrintExpr(CalculatorParser.PrintExprContext ctx);
T visitAssign(CalculatorParser.AssignContext ctx);
建议: 为rule element都加上label,这样获取具体的节点将会更加方便
以visitor模式实现计算器为例,若不为stat的rule element添加label,在CalculatorVisitorImpl
中重写CalculatorVisitor.visitStat()
方法时,需要自主判断节点的类型
@Override
public Integer visitStat(CalculatorNoLabelParser.StatContext ctx) {if (ctx.ID() != null) { // 存在ID说明是赋值语句String variable = ctx.ID().getText();Integer value = visit(ctx.expr());variables.put(variable, value);} else { // 否则是打印语句if (ctx.expr() instanceof CalculatorNoLabelParser.ConstantContext) {System.out.printf("%d\n", visit(ctx.expr()));} else {System.out.printf("%s = %d\n", ctx.expr().getText(), visit(ctx.expr()));}}return 0; // 打印语句统一返回0
}
如果定义了label,无需自主判断节点的类型,可以直接访问具体的节点
@Override
public Integer visitPrintExpr(CalculatorParser.PrintExprContext ctx) {Integer result = visit(ctx.expr());if (ctx.expr() instanceof CalculatorParser.ConstantContext) {System.out.println("打印常量的值: " +result);} else {System.out.printf("打印计算结果: %s = %d\n", ctx.expr().getText(), result);}return result;
}@Override
public Integer visitAssign(CalculatorParser.AssignContext ctx) {String variable = ctx.ID().getText();Integer value = visit(ctx.expr());variables.put(variable, value);return value;
}