1. 概述

本文将快速介绍 ANTLR(ANother Tool for Language Recognition)这一强大的语法解析器生成工具,并结合实际场景展示其应用价值。

ANTLR 能够帮助我们处理结构化文本,广泛应用于构建 DSL、代码分析工具、查询语言解析器等。比如 Hibernate 使用 ANTLR 解析 HQL,Elasticsearch 用它处理 Painless 脚本语言。

本文重点在于:
✅ 如何集成 ANTLR 到 Maven 项目
✅ 自动生成解析器代码的流程
✅ 基于语法文件构建监听器实现自定义逻辑
✅ 动手实现一个简单的代码规范检查器

面向读者是有一定编译原理基础或对代码自动化处理感兴趣的开发者,因此不展开讲解词法分析、语法树等基础概念。


2. ANTLR 简介

ANTLR 是一个用于语言识别的工具,全称是 ANother Tool for Language Recognition。它的核心能力是根据用户定义的语法规则(grammar),自动生成词法分析器(Lexer)和语法分析器(Parser)。

它支持多种目标语言,包括 Java、C#、Python、JavaScript、Go、C++ 和 Swift。本文聚焦 Java 环境下的使用。

ANTLR 的典型应用场景包括:

  • 自定义 DSL 设计与解析
  • 静态代码分析 / Lint 工具开发
  • SQL 或表达式引擎构建
  • 模板语言解析(如类 Velocity 语法)

⚠️ 注意:ANTLR 本身不是运行时框架,而是“生成器”——它产出 Java 代码,这些代码才是真正的解析引擎。


3. 项目配置

要使用 ANTLR,首先需要引入两个关键依赖。

3.1 运行时依赖

<dependency>
    <groupId>org.antlr</groupId>
    <artifactId>antlr4-runtime</artifactId>
    <version>4.7.1</version>
</dependency>

这是生成的解析器在运行时所需的库。

3.2 Maven 插件

<plugin>
    <groupId>org.antlr</groupId>
    <artifactId>antlr4-maven-plugin</artifactId>
    <version>4.7.1</version>
    <executions>
        <execution>
            <goals>
                <goal>antlr4</goal>
            </goals>
        </execution>
    </executions>
</plugin>

该插件负责在编译阶段自动读取 .g4 语法文件并生成对应的 Java 解析器类。

📌 默认情况下,插件会扫描 src/main/antlr4 目录下的所有 .g4 文件。


4. 工作原理

使用 ANTLR 构建解析器主要分为三步:

  1. ✅ 编写 .g4 语法文件(grammar)
  2. ✅ 执行 Maven 构建生成 Lexer 和 Parser 类
  3. ✅ 实现 Listener 或 Visitor 处理语法树节点

整个流程简单粗暴,但非常高效。下面我们通过一个实际例子来演示。


5. 实战:检测方法名是否大写开头

我们来做一个简单的代码规范检查工具:扫描 Java 源码,找出所有以大写字母开头的方法名(违反 Java 命名规范)。

5.1 准备语法文件

从官方仓库获取 Java8.g4,放入 src/main/antlr4/Java8.g4

这个文件定义了完整的 Java 8 语法结构,我们无需自己造轮子。

5.2 生成源码

执行以下命令:

mvn package

Maven 插件会自动生成一系列文件到 target/generated-sources/antlr4

Java8Lexer.java
Java8Parser.java
Java8Listener.java
Java8BaseListener.java
Java8.tokens
Java8.interp
...

⚠️ 文件名由 .g4 文件中的 grammar 名称决定(即 grammar Java8;)。

其中关键类说明:

文件 作用
Java8Lexer 词法分析器,将源码切分为 token
Java8Parser 语法分析器,构建抽象语法树(AST)
Java8BaseListener 默认空实现的监听器基类,供我们继承
Java8Listener 接口定义,列出所有可监听的语法节点

我们要做的就是继承 Java8BaseListener,重写感兴趣的方法。

5.3 创建 MethodUppercaseListener

目标:当遇到方法声明时,检查方法名首字母是否大写。

根据语法文件中定义的方法结构:

methodDeclarator
    :   Identifier '(' formalParameterList? ')' dims?
    ;

ANTLR 自动生成了对应的 enterMethodDeclarator 回调方法。我们只需覆盖它即可。

public class UppercaseMethodListener extends Java8BaseListener {

    private List<String> errors = new ArrayList<>();

    public List<String> getErrors() {
        return errors;
    }
 
    @Override
    public void enterMethodDeclarator(Java8Parser.MethodDeclaratorContext ctx) {
        TerminalNode node = ctx.Identifier();
        String methodName = node.getText();

        if (Character.isUpperCase(methodName.charAt(0))) {
            String error = String.format("Method %s is uppercased!", methodName);
            errors.add(error);
        }
    }
}

📌 ctx.Identifier() 获取方法名标识符,getText() 返回原始字符串。

✅ 这里利用了 ANTLR 的上下文对象(Context Object)机制,自动绑定语法规则中的命名元素。

5.4 测试验证

编写测试代码,验证能否正确捕获违规方法名:

String javaClassContent = "public class SampleClass { void DoSomething(){} }";
Java8Lexer lexer = new Java8Lexer(CharStreams.fromString(javaClassContent));
CommonTokenStream tokens = new CommonTokenStream(lexer);
Java8Parser parser = new Java8Parser(tokens);

ParseTree tree = parser.compilationUnit(); // 从编译单元开始解析

ParseTreeWalker walker = new ParseTreeWalker();
UppercaseMethodListener listener = new UppercaseMethodListener();

walker.walk(listener, tree); // 开始遍历语法树

// 断言结果
assertThat(listener.getErrors().size(), is(1));
assertThat(listener.getErrors().get(0),
  is("Method DoSomething is uppercased!"));

✅ 成功捕获 DoSomething 方法命名不规范问题。

💡 小贴士:compilationUnit 是 Java 语法的根规则,表示整个类文件结构。


6. 自定义语法:解析日志文件

除了复用现有语法,我们也可以自己定义简单的 DSL。例如,解析如下格式的日志:

2025-03-01 12:00:00 INFO User login successful
2025-03-01 12:00:01 ERROR Failed to connect database

6.1 编写 Log.g4

创建 src/main/antlr4/Log.g4

grammar Log;

log : entry+;

entry : timestamp ' ' level ' ' message CRLF;

timestamp : DATE ' ' TIME;

level : 'INFO' | 'WARN' | 'ERROR';

message : .+?; // 非贪婪匹配任意字符直到换行

CRLF : '\r'? '\n';

DATE : [0-9][0-9][0-9][0-9]'-'[0-9][0-9]'-'[0-9][0-9];
TIME : [0-9][0-9]':'[0-9][0-9]':'[0-9][0-9];

WS : [ \t]+ -> skip;

执行 mvn compile 后,ANTLR 自动生成 LogLexer, LogParser, LogBaseListener 等类。

6.2 实现 LogListener

public class LogListener extends LogBaseListener {

    private List<LogEntry> entries = new ArrayList<>();
    private LogEntry current;

    @Override
    public void enterEntry(LogParser.EntryContext ctx) {
        current = new LogEntry();
        current.setLevel(ctx.level().getText());
        current.setMessage(ctx.message().getText().trim());
        // timestamp 可进一步解析
        entries.add(current);
    }

    public List<LogEntry> getEntries() {
        return entries;
    }
}

配合 ParseTreeWalker 使用即可完成结构化解析。


7. 总结

本文通过两个示例展示了 ANTLR 在 Java 中的实际应用:

  • ✅ 使用现成的 Java8.g4 实现代码静态检查
  • ✅ 自定义 Log.g4 解析特定格式文本

ANTLR 的优势在于:

  • 语法清晰易维护
  • 生成代码性能高
  • 支持多语言绑定
  • 社区有大量现成语法文件可用(grammars-v4

踩坑提醒:

  • ❌ 忘记添加 antlr4-maven-plugin 导致无法生成代码
  • ❌ 语法文件未放在 src/main/antlr4 导致插件找不到
  • ❌ 忽略大小写处理,在语法规则中应显式控制(如使用 ignoreCase=true 或手动处理)

所有示例代码已托管至 GitHub:
👉 https://github.com/johnchen902/antlr-demo

如果你想做代码生成、DSL 设计或高级 Lint 工具,ANTLR 是绕不开的利器。


原始标题:Java with ANTLR