antlr是一款适合拿来开发领域特定语言(DSL)的工具。它可以根据开发人员定义的词法和文法生成词法分析器(lexer)程序,语法分析器(parser)程序。如果没有antlr这样的工具,就要自己写底层的词法分析,语法分析,语义分析,中间代码生成等复杂的工作。antlr降低了dsl开发的复杂度。
ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files. It’s widely used to build languages, tools, and frameworks. From a grammar, ANTLR generates a parser that can build and walk parse trees.
antlr的github地址
antlr官网传送门
antlr有别于编译器,它只做了compiler的前三个步骤
<dependency>
<groupId>org.antlrgroupId>
<artifactId>antlr4artifactId>
<version>4.5.3version>
dependency>
这里需要注意的是,antlr的maven版本和插件支持的版本需要保持一致,否则代码运行时会报错。用的时候注意比对下版本。
插件自动生成的代码会校验jar包版本,校验代码如下:
static {
RuntimeMetaData.checkVersion("4.5.3", RuntimeMetaData.VERSION);
}
g4文件里定义的是dsl语言的词法和文法规则,antlr插件会根据g4文件里的词法文法规则生成解析代码。标准的g4文件格式如下
/** Optional javadoc style comment */
grammar Name; ①
options {...}
import ... ;
tokens {...}
channels {...} // lexer only
@actionName {...}
rule1 // parser and lexer rules, possibly intermingled
...
ruleN
语法名字和文件名字必须一致。options,import, tokens, channels, @actionName都是可选的。rule是词法或者文法规则,必须定义。
简单表达式语言的语法定义
grammar Math;
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
;
// 词法定义
MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
ID : [a-zA-Z]+ ;
INT : [0-9]+ ;
NEWLINE:'\r'? '\n' ;
WS : [ \t]+ -> skip;
输入一个满足文法定义的句子,antlr能生成语法树的可视图,十分有用。
语法分析的作用
根据给定文法,将一个句子构造出语法树(parse tree)
语法树的构造方法
antlr采用的是自顶向下的左递归推导分析。左递归容易遇到歧义情况,对此antlr会对输入的左递归文法改造非左递归的文法,同时引入优先级进行语法制导的翻译。这里不赘述,有兴趣的可以看antlr的文档 https://github.com/antlr/antlr4/blob/master/doc/left-recursion.md
antlr提供两种方式遍历语法树(syntax tree)
定义g4文件,见上文
选择visitor遍历模式,实现MathBaseVisitor类
生成好的MathBaseVisitor是一个MathVisitor的空实现
package antlr;
import antlr.gen.math.MathBaseVisitor;
import antlr.gen.math.MathParser;
import com.google.common.collect.Maps;
import java.util.Map;
/**
* 后根遍历序
*
* @author lvsheng
* @date 2018/10/5
**/
public class MathVisitorImpl extends MathBaseVisitor<Integer> {
/**
* 存储变量值
*/
private Map<String, Integer> memory = Maps.newHashMap();
@Override
public Integer visitAssign(MathParser.AssignContext ctx) {
String id = ctx.ID().getText();
Integer value = visit(ctx.expr());
this.memory.put(id, value);
return value;
}
@Override
public Integer visitPrintExpr(MathParser.PrintExprContext ctx) {
Integer value = visit(ctx.expr());
System.out.println(value);
return value;
}
@Override
public Integer visitInt(MathParser.IntContext ctx) {
return Integer.valueOf(ctx.INT().getText());
}
@Override
public Integer visitId(MathParser.IdContext ctx) {
String id = ctx.ID().getText();
if (memory.containsKey(id)) {
return memory.get(id);
}
return 0;
}
@Override
public Integer visitMulDiv(MathParser.MulDivContext ctx) {
Integer left = visit(ctx.expr(0));
Integer right = visit(ctx.expr(1));
if (ctx.op.getType() == MathParser.MUL) {
return left * right;
} else {
return left / right;
}
}
@Override
public Integer visitAddSub(MathParser.AddSubContext ctx) {
Integer left = visit(ctx.expr(0));
Integer right = visit(ctx.expr(1));
if (ctx.op.getType() == MathParser.ADD) {
return left + right;
} else {
return left - right;
}
}
@Override
public Integer visitParens(MathParser.ParensContext ctx) {
return visit(ctx.expr());
}
}
package antlr;
/**
* @author lvsheng
* @date 2018/10/5
**/
import antlr.gen.math.MathLexer;
import antlr.gen.math.MathParser;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
public class MathTest {
public static void main(String[] args) {
//String simpleInput = " 3 * 4+ 6 / 2\r\n";
String sentence = "a = 5\r\n b = 6\r\n a * b + 3 - ( 5 + 10 )\r\n";
ANTLRInputStream inputStream = new ANTLRInputStream(sentence);
MathLexer lexer = new MathLexer(inputStream);
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
MathParser parser = new MathParser(tokenStream);
// 生成解析树
ParseTree parseTree = parser.prog();
MathVisitorImpl visitor = new MathVisitorImpl();
// 遍历解析树
Integer rtn = visitor.visit(parseTree);
System.out.println("result : " + rtn.toString());
}
}