本节书摘来自华章计算机《ANTLR 4权威指南》一书中的第3章,第3.1节,作者[美] 特恩斯·帕尔(Terence Parr),张博 译,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
第3章
入门的ANTLR项目
作为我们的第一个ANTLR项目,我们会构造一个语法,它是C语言或其继承者Java语法的一个很小的子集。具体来说,我们将识别包裹在花括号或者嵌套的花括号中的一些整数,像是{1, 2, 3}和{1, {2, 3}, 4}这样。这样的结构可以作为int数组或者C语言中的结构体的初始化语句。在很多情况下,针对这种语法的语法分析器都非常有用。例如,我们可以用它来构建一个对C语言的源代码进行重构的工具,这个工具能够完成这样的工作:如果初始化语句中所有的整数值都能用一个字节表示,那么将该整数数组转换为字节数组。我们也可以用这个语法分析器将Java的short数组转换为字符串。例如,我们可以将short值当作Unicode字符,从而将
转换为等价的字符串形式:
其中像u0001这样的Unicode字符标记使用四个十六进制数字来表示一个16位的字符。实际上,这样的字符就是一个short值。
我们这样做的原因是为了不受Java的.class文件格式的限制。Java的class文件将数组的初始化语句存储为一系列显式的数组元素赋值语句,上面的初始化语句等价为data[0] = 1; data[1] = 2; data[2] = 3;。这限制了我们能够使用这种方法来初始化的数组的大小。相比之下,Java的class文件将字符串存储为连续的short序列,从而不受上述约束限制。将数组的初始化语句转换为字符串可以得到更紧凑的class文件,避免了Java的对初始化方法的长度限制。
通过这个入门的项目示例,你将会学到如下内容:一些ANTLR语法的语义元素定义、ANTLR根据语法自动生成代码的机制、如何将自动生成的语法分析器和Java程序集成,以及如何使用语法分析树监听器编写一个代码翻译工具。
3.1 ANTLR工具、运行库以及自动生成的代码
在开始前,我们先浏览一下ANTLR的jar包中的内容。在ANTLR的jar包中存在两个关键部分:ANTLR工具和ANTLR运行库(运行时语法分析)API。通常,当说到“对一个语法运行ANTLR”时,我们指的是运行ANTLR工具,即org.antlr.v4.Tool类来生成一些代码(语法分析器和词法分析器),它们能够识别使用这份语法代表的语言所写成的语句。词法分析器将输入的字符流分解为词法符号序列,然后将它们传递给能够进行语法检查的语法分析器。运行库是一个由若干类和方法组成的库,这些类和方法是自动生成的代码(如Parser,Lexer和Token)运行所必须的。因此,我们完成工作的一般步骤是:首先我们对一个语法运行ANTLR,然后将生成的代码与jar包中的运行库一起编译,最后将编译好的代码和运行库放在一起运行。
构建一个语言类应用程序的第一步是创建一个能够描述这种语言的语法(即合法语句结构的集合)的语法。我们将在第5章中介绍如何编写语法,现在我们先来看下面这个能够满足我们需求的语法。
请将语法文件ArrayInit.g4放入一个单独的文件夹,例如/tmp/array(通过复制-粘贴或者从本书网站下载)。然后我们对它运行ANTLR工具。
根据语法ArrayInit.g4,ANTLR自动生成了很多文件,如图3-1所示,正常情况下这些文件都是需要我们手工编写的。
目前,我们仅仅需要大致了解这个过程,下面简单介绍一下生成的文件:
1)ArrayInitParser.java:该文件包含一个语法分析器类的定义,这个语法分析器专门用来识别我们的“数组语言”的语法ArrayInit。
在该类中,每条规则都有对应的方法,除此之外,还有一些其他的辅助代码。
2)ArrayInitLexer.java:ANTLR能够自动识别出我们的语法中的文法规则和词法规则。这个文件包含的是词法分析器的类定义,它是由ANTLR通过分析词法规则INT和WS,以及语法中的字面值'{'、',',和'}'生成的。回想一下上一章的内容,词法分析器的作用是将输入字符序列分解成词汇符号。它形如:
3)ArrayInit.tokens:ANTLR会给每个我们定义的词法符号指定一个数字形式的类型,然后将它们的对应关系存储于该文件中。有时,我们需要将一个大型语法切分为多个更小的语法,在这种情况下,这个文件就非常有用了。通过它,ANTLR可以在多个小型语法间同步全部的词法符号类型。更多内容请参阅4.1节中的“语法导入”部分。
4)ArrayInitListener.java, ArrayInitBaseListener.java:默认情况下,ANTLR生成的语法分析器能将输入文本转换为一棵语法分析树。在遍历语法分析树时,遍历器能够触发一系列“事件”(回调),并通知我们提供的监听器对象。ArrayInitListener接口给出了这些回调方法的定义,我们可以实现它来完成自定义的功能。ArrayInitBaseListener是该接口的默认实现类,为其中的每个方法提供了一个空实现。ArrayInitBaseListener类使得我们只需要覆盖那些我们感兴趣的回调方法(详见7.2节)。通过指定-visitor命令行参数,ANTLR也可以为我们生成语法分析树的访问器(参阅7.5节“使用访问器遍历语法分析树”部分)。
接下来,我们将使用监听器来将short数组初始化语句转换为字符串对象,不过在这之前,我们首先使用一些样例输入来验证我们的语法分析器是否能正常进行匹配工作。