Antlr
是一个强大的跨语言语法解析器。
- 可以用来读取、处理、执行或翻译结构化文本或二进制文件。
Antlr
可以从语法上来生成一个可以构建和遍历解析树的解析器。我们无法仅从一个个单个的字符,去理解一句话完整的意思的。
- 因为单个的字符并没有意义,一句话之所以有含义,是因为组成它的每一个单词具有意义。
ANTLR
的语法分析过程与我们解读某句话意义的过程是相同的。
ANTLR
官方网址:http://www.antlr.org/
ANTLR
官方 Github:https://github.com/antlr/antlr4大量语法文件例子:https://github.com/antlr/grammars-v4
基本概念
抽象语法树 (
AST
) :
- 抽象语法树是源代码结构的一种抽象表示,它以树的形状表示语言的语法结构。
语法解析器 (
Parser
) 语法解析器:
- 进行语法检查,并构建由输入单词(
Token
)组成的数据结构(即抽象语法树)。词法分析器 (
Lexer
) 词法分析:
是指在计算机科学中,将 字符序列 转换为单词(
Token
)的过程。执行 词法分析 的程序便称为词法分析器。
词法分析器(
Lexer
)一般是用来供 语法解析器(Parser
) 调用的。
ANTLR语法分析过程
首先进行词法分析,它是将字符聚集为单词或者符号的过程。
通过词法分析,
ANTLR
根据事先定义的词法规则,将用户的输入字符解析为词法符号。
- 并根据各个词法符号出现的位置,匹配语法规则,生成一颗语法分析树。
通过遍历
ANTLR
生成的语法分析树,就能够根据我们自己的需求,实现自定义的语法功能。
安装插件
IDEA
作为开发工具,在Preference->Plugins
中搜索Antlr
然后安装即可。
定义语法和词法
需要创建一个
.g4
文件,用于定义词法分析器(Lexer
)和语法解析器(Parser
)。下面是一个简单的例子:
Hello.g4
。
// file Hello.g4
// Define a grammar called Hello
grammar Hello; // 1. grammer name
@header { package pers.me.expression.parser; } // 2. java package
r : 'hello' ID ; // 3. match keyword hello followed by an identifier
ID : [a-z]+ ; // match lower-case identifiers
WS : [ \t\r\n]+ -> skip ; // 4. skip spaces, tabs, newlines
定义了
grammar
的名字,名字需要与文件名对应。定义生成的
Java
类的package
。
r
定义的语法,会使用到下方定义的正则表达式词法。定义了空白字符,后面的
skip
是一个特殊的标记,标记空白字符会被忽略。
生成代码
在
IDEA
中右键点击.g4
文件,选择Generate ANTLR Recognizer
,插件会自动在gen
目录下生成一堆Java
代码。
- 我们需要移动到对应的
package
中。如果定义了
@header
,IDEA
也会自动生成package
信息。
测试
利用下面这段代码来测试一下
ParseTree
。
public class HelloTest {
public static void main(String[] args) throws Exception {
HelloLexer lexer = new HelloLexer(CharStreams.fromString("hello parrt"));
CommonTokenStream tokens = new CommonTokenStream(lexer);
HelloParser parser = new HelloParser(tokens);
ParseTree tree = parser.r();
System.out.println(tree.toStringTree(parser));
}
}
运行上面的代码可以得到如下输出,程序识别出输入的字符串符合
r
的语法。
(r hello parrt)
Listener和Visitor
ANTLR
提供了两种方法来访问ParseTree
:
- 一种是通过
Parse-Tree Listener
的方法。- 另一种是通过
Parse-Tree Visitor
的方法。
Listener
有以下特点:
访问
AST
的所有节点。重写(
Override
)进入时(enterXXX
方法)和退出时(exitXXX
方法)要执行的方法。要重写的方法没有返回值,因此需要在属性中保留所需的值。
Visitor
有以下特点:
并非所有
AST
节点都被访问。根据目的重写进入节点时要执行的过程(
visitXXX
方法)。重写方法有一个返回值,因此不必在属性中保存所需的值。
最大的区别是Listener
会自动访问 AST
的所有节点,而Visitor
如果要访问当前节点的子树,则需要手工实现。
Visitor
较为简单方便,继承HelloBaseVisitor
类即可,内部的方法与g4
文件定义相对应,对照看即可理解。实现了
visitor
之后,就可以完成一个简单的自定义解析器了。