Antlr4: 为parser rule添加label

ANTLR4的语法文件中,为ruleelement添加label可以方便地标识和访问解析树节点。不添加label时,只能通过序号访问,而添加label后,能生成更具体的ParserRuleContext,使访问和监听parsetree节点更加精确。此外,label还影响监听器和visitor接口,提供针对性的方法,简化代码逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 ')' 
          ;
      

1.2 label对parse tree的影响

  • 使用IDEA的Antlr4插件,测试语法规则prog
    • 为stat的rule element添加label后,解析出的stat将使用label进行标识
    • 去除rule element的label后,只能使用序号进行标识
  • 可以说,使用label标识rule element,可以为语法解析带来意想不到的便利

1.3 label的作用

  • Antlr4官网是这样介绍label的:
    1. 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.
    2. All alternatives within a rule must be labeled, or none of them. Here are two rules with labeled alternatives.
    3. 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.

总结起来如下:

  1. 添加label,可以为每个rule element生成对应的ParserRuleContext,从而快速访问各rule element

    • 由于没有给stat的rule element添加label,只能通过CalculatorParser.StatContext的getter方法获取rule element
      public 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);
      	
      	... // 其他代码省略
      }
      
  2. 同时,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);
    
  3. 自己的补充: 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;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值