1. parser rule中的label
1.1 简介
- Antrl4语法文件
Calculator.g4
,stat和expr两个parser rule含有多个rule element,我们这两个parse rule的每个rule element添加了Alternative labels(简称label)
- 按照Antlr4的语法规则:
- label一般位于每个rule element的行尾,以
#
开头。 - 同时,要么为parser rule的每个rule element添加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 ')' ;
- label一般位于每个rule element的行尾,以
1.2 label对parse tree的影响
- 使用IDEA的Antlr4插件,测试语法规则prog
- 为stat的rule element添加label后,解析出的stat将使用label进行标识
- 去除rule element的label后,只能使用序号进行标识
- 为stat的rule element添加label后,解析出的stat将使用label进行标识
- 可以说,使用label标识rule element,可以为语法解析带来意想不到的便利
1.3 label的作用
- Antlr4官网是这样介绍label的:
- 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- 由于没有给stat的rule element添加label,只能通过
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); } ... // 其他代码省略 }
- 添加了label,将基于StatContext创建更加具体的Context,这有利于访问parse tree中的各节点
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); ... // 其他代码省略 }
- 由于没有给stat的rule element添加label,只能通过
-
同时,
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);
2. 小建议
-
建议: 为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; }