Antlr 是什么

Antlr 是一个开源的工具,支持根据规则文件生成词法分析器和语法分析器。Antlr 支持很多的目标语言,包括 Java、C#、JavaScript、Python、Go、C++、Swift等。无论你用上面哪种语言,都可以用它生成词法和语法分析的功能。同时,相对于其他的前端工具, Antlr 的语法更加简单。它能把类似左递归的一些常见难点在工具中解决,对提升工作效率有很大的帮助。
安装 Antlr4
Windows上的安装流程如下所示,其他系统的安装教程见官方文档
第一步:我们需要使用下面的命令安装 antlr4-tools
shell
pip install antlr4-tools
第二步,安装完 antlr4-tools
后执行下面命令,它会配置好相应的依赖和命令。
shell
antlr4
第三步,配置 CLASSPATH 环境变量,如下所示。配置完成后,记得重启命令行。
shell
.;F:\antlr\antlr-4.13.2-complete.jar;C:\Program Files\Java\jdk-17\lib;
第四步,创建一个 Calculate.g4
规则文件,该文件在Antlr中是用来生成词法分析器和语法分析器的,内容如下所示(该文件的内容后面介绍,这里不需要看懂):
g4
grammar Calculate;
@header {
package example.calculate;
}
prog: expr EOF;
expr: expr ('*'|'/') expr # MulDiv
| expr ('+'|'-') expr # AddSub
| INT # int
| '(' expr ')' # parens
;
INT:[0-9]+;
MUL: '*';
DIV: '/';
ADD: '+';
SUB: '-';
WS : [ \t\r\n]+ -> skip; // 跳过空格、制表符、换行符
然后使用如下的命令让 Antlr 编译规则文件,并生成相应的代码
shell
antlr4 Hello.g4
Antlr4的规则文件
g4文件语法规则
前面我们创建了一个 Calculate.g4
文件,它是Antlr中的规则文件,现在我们来看看g4文件的规则。
grammar Calculate;
grammar
表示这是一个语法规则文件,Calculate
是文件名,它必须和该规则文件的文件名相同。除了 grammar
,还有 lexer
,它表示词法分析的规则文件。它们的区别是,grammar Calculate
相对于 lexer Calculate
会多生成 XXXParser
、XXXListener
等语法解析相关文件。
通配符
markdown
- `|`表示或
- `*`表示出现0次或以上
- `?`表示出现0次或者1次
- `+`表示出现1次或以上
- `~`表示取反,例如`~'"'`表示除`"`之外的字符
词法符号
词法符号:是一门语言的基本词汇符号,例如float
,代表单精度浮点型。在Antlr中词法使用大写字母开头 ,如下面的示例的INT
就是代表整数的词法符号。
vbnet
//lexer文件
INT : [0-9]+ ;//匹配1个或多个数字
//匹配null
NULL_LITERAL: 'null';
//匹配true和false,| 是或的意思
BOOL_LITERAL: 'true'
| 'false'
;
WS: [\t\r\n]+ -> skip //-> skip 表示跳过,这里是跳过空白字符
在g4文件中,关键字:内容;
表示定义一个规则。其中单引号''
引用的内容,表示完全匹配该内容。
语法
语法:是定义语言中的语义规则。例如void f()
代表方法的语法。在Antlr中语法规则使用小写字母 ,如下面示例的 init
就是语法
csharp
//grammar文件,以小写开头
//匹配 { value, value,value,...}
init: '{' value (',' value)* '}' ;
//表示一个value可以是嵌套的花括号结构,也可以是一个整数
value: init
| INT
;
注意:在g4文件中,
{}
、[]
、()
在使用上是一个意思,代表子表达式。
标签语法
ini
| expression bop=('*'|'/'|'%') expression // 乘法、除法、取模
| expression bop=('+'|'-') expression // 加法、减法
这里的bop=
是一种标签(label)语法,用于为语法规则中的某个元素指定一个名称。
Calculate.g4
了解了 g4 规则文件的语法规则后,现在来看看 Calculate.g4
的内容就简单多了,代码如下所示:
g4
// 表示这是一个语法规则文件
grammar Calculate;
// 设置生成的 java 文件所在的包名为 example.calculate
@header {
package example.calculate;
}
prog: expr EOF;
// # MulDiv 不是注释,而是给该语句设置标签
expr: expr ('*'|'/') expr # MulDiv
| expr ('+'|'-') expr # AddSub
| INT # int
| '(' expr ')' # parens
;
INT:[0-9]+; // 匹配整数
MUL: '*'; // 匹配 *
DIV: '/'; // 匹配 /
ADD: '+'; // 匹配 +
SUB: '-'; // 匹配 -
WS : [ \t\r\n]+ -> skip; // 跳过空格、制表符、换行符
如何使用 Antlr4 来实现四则运算
在 Antlr4 中执行 antlr4 -visitor Calculate.g4
就可以让 Antlr 能帮我们生成一个 Visitor 处理模式的框架。通过它我们可以很简单地执行自定义的命令。这里以Calculate.g4
规则生成的代码为例,来实现四则运算的效果。
首先,我们需要创建一个 CustomCalculateVisitor
来继承通过 antlr4 -visitor Calculate.g4
命令生成的类。代码示例如下:
kotlin
class CustomCalculateVisitor: CalculateBaseVisitor<Int>() {
/**
* 处理类似 1+2 的加法运算
*/
override fun visitAddSub(ctx: CalculateParser.AddSubContext?): Int? {
return when {
ctx == null -> {
0
}
ctx.ADD() != null -> {
// visit 方法会根据 ctx.expr() 返回的类型执行其对应的操作。
// 比如 ctx.expr(1) 返回的是 CalculateParser.MulDivContext,则最后会
// 调用到 visitMulDiv 方法
val left = visit(ctx.expr(0))
val right = visit(ctx.expr(1))
left + right
}
ctx.SUB() != null -> {
val left = visit(ctx.expr(0))
val right = visit(ctx.expr(1))
left - right
}
else -> 0
}
}
/**
* 处理类似 1*2 的乘法运算
*/
override fun visitMulDiv(ctx: CalculateParser.MulDivContext?): Int? {
return when {
ctx == null -> {
0
}
ctx.MUL() != null -> {
val left = visit(ctx.expr(0))
val right = visit(ctx.expr(1))
left * right
}
ctx.DIV() != null -> {
val left = visit(ctx.expr(0))
val right = visit(ctx.expr(1))
if (right == 0) {
throw Exception("div zero")
}
left / right
}
else -> 0
}
}
/**
* 处理类似 1、2、3 这样的整数字面量
*/
override fun visitInt(ctx: CalculateParser.IntContext?): Int? {
return ctx?.INT()?.text?.toInt() ?: 0
}
/**
* 处理类似 (1+2) 的括号运算
*/
override fun visitParens(ctx: CalculateParser.ParensContext?): Int? {
return when {
ctx == null -> {
0
}
// 假设括号为 (1+2), 那么 ctx.expr() 为 1+2
else -> visit(ctx.expr())
}
}
}
然后我们就可以使用 CustomCalculateVisitor
来实现四则运算的功能。代码示例如下:
kotlin
fun main() {
val script = "1 + ( 2 + 3 ) * 4 + 6 / 3"
//创建一个CharStream,从标准输入读取数据
val input = CharStreams.fromString(script)
// 新建一个词法分析器,处理输入的CharStreams
val lexer = CalculateLexer(input)
// 新建一个词法符号缓冲区,用于存储词法分析器将生成的词法符号
val commonTokenStream = CommonTokenStream(lexer)
// 新建一个语法分析器,处理词法符号缓冲区中的内容
val parser = CalculateParser(commonTokenStream)
val tree = parser.expr()
val calculateVisitor = CustomCalculateVisitor()
val result = calculateVisitor.visit(tree)
println("result: $result")
}