从一个小例子理解Antlr4

1,208 阅读2分钟

1.Antrl4 简介

Antlr4 是一款强大的语法生成器工具,可用于读取、处理、执行和翻译结构化的文本或二进制文件。基本上是当前 Java 语言中使用最为广泛的语法生成器工具。目前很多的语法分析校验工具都是基于Antrl4实现,Antrl4是采用至上而下方式构建语法树,Spark SQL、Hive SQL、Presto 等工具都是基于Antrl4实现语法解析;详细的了解可以参数考《Antrl4 权威指南》,了解前四章就可以大概知道Antrl4的工作逻辑;下面我们通过一个简单例子看一下Antrl4;

2 Antrl4 计算器

2.1 语法文法文件

简单解释一下,stat 可以由 expr 换行符,ID = expr 换行符,换行符中任一组成 expr可以是 两个expr再加上 加减乘除 的结合,也可以是数字和ID, ID是字母串

grammar LabelExpr;
/** 起始规则 语法分析器起点 */
prog:	stat+ ;
 
stat:   expr NEWLINE        # printExpr
    |   ID '=' expr NEWLINE # assign
    |   NEWLINE             # blank
    ;
 
expr:	expr op=('*'|'/') expr # MulDiv
    |	expr op=('+'|'-') expr # AddSub
    |	INT                 # int
    |   ID                  # id
    |	'(' expr ')'        # parens
    ;
 
ID      : [a-zA-Z]+ ;   // 匹配标识符
INT     : [0-9]+ ;      // 匹配整数
NEWLINE : '\r'? '\n' ;     // 新行 即语句终止标志
WS      : [ \t]+ -> skip ; // 丢弃空白字符
 
MUL     : '*' ;
DIV     : '/' ;
Add     : '+' ;
SUB     : '-' ;

2.2 解析测试文法文件

左边输入解析的语句后,解析结果如右图所示;首先整体是一个 prog包含两个stat,就是上下两条语句; 10 + 20 * 30 - 20 首先才分成 (10 + 20 * 30)SUB 20; 10 + 20 * 30 继续才分成 10 ADD (20 * 30),最后再将 20 * 30 拆分成 20 MUL 30; 注意在文法文件中 "#" 号会生成相应的visit函数允许使用vistor模式去访问对应对象; image.png

2.3 生成代码

使用idea 或者 命令行生成代码,命令行可以生成很多中语言代码

2.4 实现代码和使用

Antrl4 提供了vistor模式去访问不同的节点;查看antlr4生成的代码,会发现MulDivContext,AddSubContext,IdContext,IntContext都继承 ExprContext

public class MyEvalVisitor extends LabelExprBaseVisitor<Integer> {
    Map<String, Integer> memory = new HashMap<>();
    @Override
    public Integer visitAssign(LabelExprParser.AssignContext ctx) {
        String id = ctx.ID().getText();
        int value = visit(ctx.expr());
        memory.put(id, value);
        return value;
    }
    @Override
    public Integer visitPrintExpr(LabelExprParser.PrintExprContext ctx) {
        Integer value = visit(ctx.expr());
        System.out.println(value);
        return 0;
    }
    @Override
    public Integer visitInt(LabelExprParser.IntContext ctx) {
        return Integer.valueOf(ctx.INT().getText());
    }
    @Override
    public Integer visitId(LabelExprParser.IdContext ctx) {
        String id = ctx.ID().getText();
        if (memory.containsKey(id)) {
            return memory.get(id);
        }
        return 0;
    }
 
    @Override
    public Integer visitMulDiv(LabelExprParser.MulDivContext ctx) {
        int left = visit(ctx.expr(0));
        int right = visit(ctx.expr(1));
        if (ctx.op.getType() == LabelExprParser.MUL) {
            return left * right;
        } else {
            return left / right;
        }
    }
    @Override
    public Integer visitAddSub(LabelExprParser.AddSubContext ctx) {
        int left = visit(ctx.expr(0));
        int right = visit(ctx.expr(1));
        if (ctx.op.getType() == LabelExprParser.Add) {
            return left + right;
        } else {
            return left - right;
        }
    }
    @Override
    public Integer visitParens(LabelExprParser.ParensContext ctx) {
        return visit(ctx.expr());
    }
}

2.5 测试代码

public static void main(String[] args) throws IOException {
    InputStream is = Main.class.getClassLoader().getResourceAsStream("calculate.txt");

    LabelExprLexer lexer = new LabelExprLexer(CharStreams.fromStream(is));

    CommonTokenStream tokens = new CommonTokenStream(lexer);

    LabelExprParser parser = new LabelExprParser(tokens);

    ParseTree tree = parser.prog();

    MyEvalVisitor visitor = new MyEvalVisitor();
    visitor.visit(tree);

    is.close();
}