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模式去访问对应对象;
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();
}