写点什么

ANTLR 入门(一)

用户头像
zane
关注
发布于: 2020 年 04 月 30 日
ANTLR入门(一)

学习背景

最近做项目需要开发一个类似 Graphql 的简单版的自定义查询功能。功能主要是通过前端自定义的复查询条件来控制后端的查询字段以及最终返回的 JSON 格式。

最初准备直接使用 Graphql 实现但是研究后发现 Graphql 还是比较重,需要重新定义对象关系来配置 Graphql 的描述文件。最终决定参照 Graphql 的查询条件结构来自定义符合需求的查询。研究了 Graphql 的底层后发现是用 ANTLR 来做的语法解析。

开始了解 ANTLR 这个技术,结果一接触感觉发现了新大陆。觉得很有比较写个笔记记录一下。

ANTLR 简介

ANTLR(全名:ANother Tool for Language Recognition)是基于 LL(\*)算法实现的语法解析器生成器(parser generator),用 Java 语言编写,使用自上而下(top-down)的递归下降 LL 剖析器方法。由旧金山大学的 Terence Parr 博士等人于 1989 年开始发展。

如同一般的词法分析器(lexer)和语法分析器(parser),ANTLR 可以用来产生树状分析器(tree parsers)。ANTLR 文法定义使用类似 EBNF(Extended Backus-Naur Form)的定义方式,形象十分简洁直观。例如: ANTLR 用 A : a;来表示规则,旧式的方法则是以 A=>a 表示,所以 ANTLR 是以“:”代替了“=>”。ANTLR 的规则要以分号“;”结束。又如其他 ANTLR 符号“|”代表“或”的关系,又如“\*,+”表示可以出现 0 次或多次。

目前 Hibernate 与 WebLogic 都是使用 ANTLR 做为来解析 HQL。在 NetBeans IDE 中更以 ANTLR 解析 C++。Twitter 搜索使用 ANTLR 解析,一天超过 200 亿次查询。

是什么

简单来说 ANTLR 是一个可以开发自动工具的工具,使用 ANTLR 可以定义自己的“程序语言”。

不管你使用的是什么开发语言, 大家都知道所谓的代码其实就是定义好的一些有具体含义的语句,但在最终运行在计算机上其实都机器语言。

把程序员写的代码解释为机器语言,这里必不可少的都有一个语法解释器。ANTLR 就是一个可以帮助你编写一个语法解释器的技术。 还有的时候,我们需要将一个语法翻译为另外一个语言,这时候需要一个语法翻译器, ANTLR 也可以帮你实现。

在接触到 ANTLR 后,发现可以填补上之前知识点欠缺的一块。理解了语法解释器和语法翻译器后,是不是很容易理解一些代码生成器,代码翻译器的实现原理。 之前接触过的很多技术底层可能都能看到 ANTLR 的影子。

能做什么

已目前对 ANTLR 的了解,它能做的事情真的很多,稍微梳理一些我的思路。

  1. 语言翻译器

开发一个把 JAVA 代码翻译成 Python 的工具

  1. 自定义一种数据交换格式

开发一个类似 json 的格式,这正是我上面的需求中需要的

  1. 做代码分析的工具

写一个工具来进行代码规范的检查,快速检查项目不符合编码规范的代码

  1. 快速重构项目代码

ANTLR 可以重写输入流,在项目中需要做一些重构时,写一个快速自动重构的工具

我相信 ANTLR 能做的事情还很多,发挥一下脑洞, 是不是可以基于 JAVA 开发一种自己的程序语言,把自己定义的语法翻译成 JAVA 最终在 JVM 中执行。

安装 ANTLR

作者的电脑是 MAC 的操作系统 macOS Catalina 10.15.2。

安装步骤后 linux 操作的系统的一样, Windows 系统大致步骤一样,但是环境变量等配置有差别,作者很久没使用过 win 系统,只能基于 MAC 的系统介绍了。

环境准备

ANLTR 是用 JAVA 编写的,需要先安装好 JAVA,需要的 JAVA 版本是 1.6 以上。相信看这篇文章的各位同学电脑上应该都有安装 JAVA。😊

下载 ANTLR

使用 ANTLR 的功能其实很简单, 下载一个 ANTLR 的 jar 包即可。

https://www.antlr.org/download.html

配置

最后一步配置环境变量,方便后面在命令行操作 ANTLR

打开环境变量文件,作者安装的是 zsh,所以编辑~/.zshrc 文件,各位同学应该也清楚环境变量的配置,在文件加入几行:

export CLASSPATH=".:/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH"  #设置antlr的jar包到环境变量alias antlr4='java -Xmx500M -cp "/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool'  #快速运行ANTLR的解释器alias grun='java -Xmx500M -cp "/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH" org.antlr.v4.gui.TestRig' #快速运行ANTLR测试工具
复制代码

完成上面的准备工作就可以开始体验 ANTLR 的强大功能了

第一个语法解释器

下面用一个简单的例子展示 ANTLR 的使用,编译一个最简单的赋值语句的语法。

编写 g4 文件

新建一个 Hello.g4 的文件,这个文件是针对语法的描述,相当于告诉 ANTLR 我的语法规范

Hello.g4 内容如下:

grammar Hello; //定义一个名为 Hello 的语法statement: ID '=' NUM; //匹配类似 a=1 age=100 这样的语句ID: [a-z]+; // 定义了一个词法 ID,由小写字母组成NUM:[0-9]+; // 定义了一个词法 NUM,由数字组成WS: [ \t\r\n]+ -> skip; //在进行解析的过程中,忽略掉空格,换行
复制代码


这样其实就定义好了一个简单语法。

对于不同的程序语言来说,语法结构越是复杂,想对应的 g4 文件也会越复杂

https://github.com/antlr/grammars-v4

各位同学可以找找接触过哪些,看看你用的语言复杂不复杂。反正 JAVA 文件是很复杂💔

生成解释器

生成 ANTLR 的解释器很简单,一条命令搞定。

antlr4 Hello.g4
复制代码


运行完这条命令会生成如下几个文件:

Hello.interp

Hello.tokens

HelloBaseListener.java

HelloLexer.interp

HelloLexer.java

HelloLexer.tokens

HelloListener.java

HelloParser.java


  • HelloParser.java

该文件包含一个语法解释器类的定义,负责识别我们定义的语法

public class HelloParser extends Parser { ... }
复制代码


  • HelloLexer.java

该文件包含一个词法解释器类的定义,负责自动识别我们定义的语法中的文法规则和词法规则。

public class HelloLexer extends Lexer { ... }
复制代码


  • HelloListener.java 和 HelloBaseListener.java

这两个类都是事件监听类,是留给开发者自己来定义相应的事件。因为 ANTLR 在进行遍历解析时,遍历器会触发一系列的事件。 比如进入某某标签,读到一个数字等。ANTLR 开放了这些接口,开发者通过实现这些事件可以做到除了解释语法以外更复杂的功能。这里就不详细解释,后面会再介绍。先挖个坑😊

public interface HelloListener extends ParseTreeListener {  /**  * Enter a parse tree produced by {@link HelloParser#statement}.  * @param ctx the parse tree  */  void enterStatement(HelloParser.StatementContext ctx);
/** * Exit a parse tree produced by {@link HelloParser#statement}. * @param ctx the parse tree */ void exitStatement(HelloParser.StatementContext ctx);}
复制代码


  • 其他非 java 文件

ANTLR 会给每个定义的词法符号指定一个数字形式的类型,然后将对应关系存储在这些文件中。当不同的语法有相同的词时这个文件就有大作用了。

测试解释器

最后到了验收结果的时候,测试一下 ANTLR 的语法解释器做了什么。下面会展示两种测试方式。

JAVA 代码

1. 将前面生成的 4 个 JAVA 文件添加到一个测试工程内,该工程需要引入 ANTLR 依赖。

<dependency>  <groupId>org.antlr</groupId>  <artifactId>antlr4</artifactId>  <version>4.7.1</version></dependency>
复制代码


2. 编写相关测试代码

public static void main(String[] args) {  HelloLexer lexer = new HelloLexer(CharStreams.fromString("a = 1"));  CommonTokenStream tokenStream = new CommonTokenStream(lexer);  HelloParser parser = new HelloParser(tokenStream);  System.out.println(parser.statement().toStringTree(parser));}
复制代码


3. 运行后会有如下输入:

(statement a = 1)

这个代码 ANTLR 成功识别到了我们的赋值语句。

命令行

ANTLR 也提供了自动的测试工具,可以直接在命令行测试。详细用法如下

1. 编译 java 代码,就跟一般的 java 代码一样我们需要同 javac 把 java 文件编译成 class 文件。

javac *.java
复制代码


2. 使用 ANTLR 测试工具,输入如下命令

grun Hello statement -gui
复制代码

Hello 对应我们定义的语法 grammar Hello

statement 对应我们定义的词法 statement: ID '=' NUM;

-gui 表示输出图形界面

3. 进入测试工具后,输入 a = 1。 MAC 电脑结束输入符号 control+D。

$ grun Hello statement -guia = 1^D
复制代码

ru m

ANTLR 会输出图形界面


可以看到 ANTLR 最终解释出来的语法树。


发布于: 2020 年 04 月 30 日阅读数: 430
用户头像

zane

关注

还未添加个人签名 2019.12.09 加入

还未添加个人简介

评论

发布
暂无评论
ANTLR入门(一)