Skip to content

Commit da989fd

Browse files
ANTLR
1 parent 8c018c4 commit da989fd

File tree

1 file changed

+146
-0
lines changed

1 file changed

+146
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
---
2+
3+
---
4+
5+
# ANTLR:从语法规则到代码生成
6+
7+
> 今天周五,最近在忙其他事情,有一段时间没写博客了,打开编辑器,想记录一下关于antlr这个功能强大的 语法分析器生成器。
8+
9+
## ANTLR是什么
10+
11+
首先介绍一下ANTLR,其全称 Another Tool for Language Recognition,是一个功能强大的 语法分析器生成器。它的作用是根据你定义的一套 语法规则(Grammar),自动生成用于 词法分析(Lexer) 和 语法分析(Parser) 的 Java、Python、C++ 等语言代码。简单来说就是,你告诉ANTLR要识别什么规则的结构
12+
,其便会帮你构建出对应的Token(Lexer),语法树(Parse),并通过监听者模式(Listener) or 访问者模式(Visitor)无侵入的对语法树局部进行定制化操作,而不用关心整个词法、语法树的构建与访问等情况。
13+
14+
**关于词法和语法、语义等知识概念,可以去学习一下编译原理相关知识进行补充了解,笔者虽然大学的时候编译原理这一门课学得还行,也写过词法分析、语法分析、语义分析到最终解释器的代码,但是时间一久,相关内容也忘了不少,而且这门课程确实也很难,值得且需要花费时间与精力去学校**
15+
16+
## 适用场景
17+
18+
- 🌐 自定义语言或 DSL 的解析器(如 SQL 方言、脚本语言等)
19+
20+
- 🛠️ 静态代码分析工具(比如代码质量检查器)
21+
22+
- 🧠 编译器前端(语法分析器)
23+
24+
- 📚 学习语言构造和编译原理
25+
26+
- 📝 配置文件解析(如自定义规则语言)
27+
28+
## 背景
29+
30+
在我们项目中,有许多用到领域特定语言(DSL)以及用户自定义表达式解析计算等需求。但在老项目随着功能需求的不断迭代,业务复杂度不断提高。原有的方案已经不满足现有的场景。例如,需要访问深层嵌套的语法树的局部节点做特定处理,需要在整个分析过程中构建语法树进行处理上下文等等场景。如果在原有的方案上开发成本和心智负担过于沉重。因此,选择了通过ANTLR自定义语法规则进行方案替换优化。
31+
32+
## ANTLR 简单使用
33+
34+
### ANTLR 的核心组件
35+
36+
在使用ANTLR前,先介绍一下ANTLR相关的几个核心概念来组织语法和生成代码:
37+
38+
| 组件 | 说明 |
39+
| ---------------- | ----------------------- |
40+
| `.g4` 文件 | ANTLR 的语法规则文件,定义语法结构 |
41+
| Lexer(词法分析器) | 将字符流拆解成 **Token**|
42+
| Parser(语法分析器) | 根据语法规则组织 Token,构造语法树 |
43+
| Visitor/Listener | 对语法树进行遍历,做语义分析、执行、代码生成等 |
44+
45+
### 一个简单的表达式语法例子
46+
47+
因此,首先我们需要创建一个*.g4的语法规则文件,定义词法和语法规则(实际生产中根据场景,可以将词法与语法.g4文件拆分来进行维护,避免冗余等情况,便于维护)。
48+
49+
下面便创建一个支持 + 和 * 运算的简单计算器。
50+
51+
```bash
52+
grammar Expr;
53+
54+
expr: expr op=('*'|'/') expr # MulDiv
55+
| expr op=('+'|'-') expr # AddSub
56+
| INT # Int
57+
| '(' expr ')' # Parens
58+
;
59+
60+
INT: [0-9]+;
61+
WS: [ \t\r\n]+ -> skip;
62+
```
63+
64+
**解释:expr 是递归定义,支持括号嵌套和操作符优先级。每个语法规则后加的 # Name 是“标签名”,用于 Visitor 模式中区分子节点。**
65+
66+
> 具体以及其他用法可以参考官方文档:https://github.com/antlr/antlr4/tree/master
67+
68+
### 从语法到代码:使用流程
69+
70+
1. 安装 ANTLR 工具
71+
72+
```bash
73+
brew install antlr # macOS
74+
# or 下载 jar:https://www.antlr.org/download.html
75+
```
76+
77+
如果习惯使用idea的朋友可以在idae上安装对应插件也是一样的。
78+
79+
2. 生成代码
80+
81+
```bash
82+
antlr4 Expr.g4 -visitor -package com.example.expr
83+
```
84+
85+
输出包括但不局限于下面的文件:
86+
- ExprLexer.java
87+
- ExprParser.java
88+
- ExprBaseVisitor.java
89+
- ExprVisitor.java
90+
91+
3.编写 Visitor 解析表达式
92+
93+
```java
94+
public class EvalVisitor extends ExprBaseVisitor<Integer> {
95+
@Override
96+
public Integer visitAddSub(ExprParser.AddSubContext ctx) {
97+
int left = visit(ctx.expr(0));
98+
int right = visit(ctx.expr(1));
99+
return ctx.op.getType() == ExprParser.PLUS ? left + right : left - right;
100+
}
101+
102+
@Override
103+
public Integer visitMulDiv(ExprParser.MulDivContext ctx) {
104+
int left = visit(ctx.expr(0));
105+
int right = visit(ctx.expr(1));
106+
return ctx.op.getType() == ExprParser.MUL ? left * right : left / right;
107+
}
108+
109+
@Override
110+
public Integer visitInt(ExprParser.IntContext ctx) {
111+
return Integer.parseInt(ctx.getText());
112+
}
113+
114+
@Override
115+
public Integer visitParens(ExprParser.ParensContext ctx) {
116+
return visit(ctx.expr());
117+
}
118+
}
119+
```
120+
121+
4. 执行并测试
122+
123+
```java
124+
ANTLRInputStream input = new ANTLRInputStream("2 * (3 + 4)");
125+
ExprLexer lexer = new ExprLexer(input);
126+
CommonTokenStream tokens = new CommonTokenStream(lexer);
127+
ExprParser parser = new ExprParser(tokens);
128+
ParseTree tree = parser.expr();
129+
130+
EvalVisitor visitor = new EvalVisitor();
131+
System.out.println(visitor.visit(tree)); // 输出 14
132+
```
133+
134+
至此,一个简单的计算表达式便开发完毕了,ANTLR给我们封装和屏蔽了整个词法,语法规则的构建过程。因此我们只需要专注于编写规则文件以及根据需求使用Listener或Visitor去访问语法树实现我们的业务诉求即可。
135+
136+
## 小结
137+
138+
上面只是一个简单的介绍和例子,实际的开发场景中会遇到奇奇怪怪、各式各样的诉求:可能需要支持上下文敏感语法,可能需要处理优雅的错误提示,可能希望和已有项目无缝集成,甚至还有人拿它写代码转换、重写器、生成器……但不管是哪一种,用 ANTLR 的过程总会逼着你直面语言本质的混乱与秩序。
139+
140+
越往深处走,就越发现语法这东西——远不只是“规则”的堆砌。它是抽象、是妥协、是试图给混乱世界套上的结构化“框”。当站在一棵语法树面前凝视它的结构时,可能会突然意识到:语言本身,就是我们人类思维的一种折射。
141+
142+
而这时,再联想到当下火热的人工智能与自然语言处理,很多问题似乎也不再只是技术性的。是的,我们试图让机器理解人类语言,而 ANTLR 则是在更底层,帮我们定义“什么才算是一门语言”。
143+
144+
当然,最后我想写的是:ANTLR 给了我们一种可能,但并没有告诉我们哪种方式一定是“对”的,也没告诉我们什么时候该停下,什么时候该继续拆分与组合。它只提供了一个工具,而不是答案。因地制宜,适配自己的诉求,或许才是最佳实践。可以写一个优雅的 DSL,也可以做一个粗糙但可用的语法识别器;可以深入语言结构,也可以浅尝辄止。这没有标准答案,只有当下最适合的方案。
145+
146+

0 commit comments

Comments
 (0)