自制编译器篇(2):JavaCC 应用案例——解析正整数加法运算
Author: stormQ
Created: Saturday, 27. February 2021 10:20PM
Last Modified: Sunday, 28. February 2021 09:40PM
本文描述了如何基于 JavaCC 生成用于“两个正整数加法运算”的解析器,并简单地分析了 JavaCC 所生成解析器的源码。
本节描述了如何编写 JavaCC 的语法描述文件,从而生成用于“两个正整数加法运算”的解析器。
step 1: 编写语法描述文件的“options”部分
在 JavaCC 中,STATIC
选项的默认值为true
,意味着 Java CC 生成的所有成员和方法都将被定义为static
。
为了所生成的解析器能够在多线程环境下使用,我们将STATIC
选项的值设置为false
。如下所示:
options {
STATIC = false;
}
step 2: 编写语法描述文件的“PARSER_BEGIN、PARSER_END”部分
1) 编写 PARSER_BEGIN 和 PARSER_END
如果我们要定义的解析器类的名称为Adder
,那么如下编写PARSER_BEGIN
和PARSER_END
:
PARSER_BEGIN(Adder)
PARSER_END(Adder)
2) 编写解析器类
编写的解析器类必须放到PARSER_BEGIN
和PARSER_END
之间。
a) 定义解析器类
public class Adder {
}
b) 编写解析器类的 main 函数
在这个例子中,解析器并不生成抽象语法树,而是直接计算表达式的结果。因此,为了能够运行,需要编写一个main
函数。
main
函数的代码如下:
static public void main(String[] args) {
for (String arg : args) {
try {
System.out.println(evaluate(arg));
}
catch (ParseException ex) {
System.err.println(ex.getMessage());
}
}
}
上述代码的意义,遍历所有的命令行参数,每次迭代时将命令行参数以字符串的形式传递给evaluate
函数,并打印其返回结果。
c) 编写解析器类的 evaluate 函数
evaluate
函数的代码如下:
static public long evaluate(String src) throws ParseException {
Reader reader = new StringReader(src);
return new Adder(reader).expr();
}
JavaCC 5.0 生成的解析器中默认提供的构造函数之一为:Adder(java.io.Reader stream)
。因此,我们才可以在evaluate
函数中将Reader
类型的实例作为解析器类Adder
的参数。
此外,要运行 JavaCC 生成的解析器类,需要如下两个步骤:1)生成解析器类的对象实例;2)用生成的对象调用和需要解析的语句同名的方法。
上述代码中,语句new Adder(reader).expr();
完成了这两个步骤。
d) 导入依赖
由于evaluate
函数中使用了StringReader
类。因此,需要导入如下依赖:
import java.io.*;
step 3: 编写语法描述文件的“扫描器的描述”部分
1) 忽略空格、制表符和换行符(optional)
SKIP: { <[" ","\t","\r","\n"]> }
上述描述中的SKIP
是 JavaCC 中用于省略单词的命令,SKIP: { <[" ","\t","\r","\n"]> }
表示跳过空格、制表符和换行符。如果不加这部分,也是可以的。但输入的表达式中不能出现空格等字符,比如表达式"1 + 2"是无法被识别的。
2) 识别正整数
TOKEN: {
<INTERGR: (["0"-"9"])+>
}
上述描述中的TOKEN
命令是 JavaCC 中用于描述token
的,INTERGR
为自定义的token
名称,(["0"-"9"])+
表示该token
需要满足的正则表达式。
上述正则表达式的含义为:由 1 个或多个 0 到 9 数字组成的单词。
step 4: 编写语法描述文件的“解析器的描述”部分
解析器的描述如下:
long expr():
{
Token x, y;
}
{
x=<INTERGR> "+" y=<INTERGR> <EOF>
{
return Long.parseLong(x.image) + Long.parseLong(y.image);
}
}
查看 JavaCC 根据上述描述所生成的expr
函数:
final public long expr() throws ParseException {
Token x, y;
x = jj_consume_token(INTERGR);
jj_consume_token(3);
y = jj_consume_token(INTERGR);
jj_consume_token(0);
{if (true) return Long.parseLong(x.image) + Long.parseLong(y.image);}
throw new Error("Missing return statement in function");
}
从上面的代码可以看出:
long expr():
,表示要生成的函数名称为expr
,函数返回值类型为long
。
Token x, y;
,表示声明数据类型为Token
的局部变量x
和y
。注意: 这里的Token
不能是其他值,比如TokenA
,否则 JavaCC 会报错。另外,Token x, y;
的外部必须要用花括号括起来。
x=<INTERGR> "+" y=<INTERGR> <EOF>
,表示要识别的表达式是什么样的,即“整数 + 整数”。
x=<INTERGR>
,对应语句x = jj_consume_token(INTERGR);
。
"+"
,对应语句jj_consume_token(3);
。这说明两点:1)"+"
是 JavaCC 默认可以使用的token
;2)该token
对应的识别码为 3。
y=<INTERGR>
,对应语句y = jj_consume_token(INTERGR);
。
<EOF>
,对应语句jj_consume_token(0);
。这说明两点:1)EOF
是 JavaCC 默认可以使用的token
;2)该token
对应的识别码为 0。
注意: x=<INTERGR>
,"+"
,y=<INTERGR>
,<EOF>
,各自可以写在单独的一行。
注意: 如果不加
,会导致"1 + 3 +"
这样看起来不太合理的表达式也能被正常识别,并计算出结果 4。
return Long.parseLong(x.image) + Long.parseLong(y.image);
表示成功识别表达式后要做的动作。注意: 必须要用花括号括起来,否则 JavaCC 会报错。
step 5: 完整的语法描述文件
语法描述文件,sumi.jj:
options {
STATIC = false;
}
PARSER_BEGIN(Adder)
import java.io.*;
public class Adder {
static public void main(String[] args) {
for (String arg : args) {
try {
System.out.println(evaluate(arg));
}
catch (ParseException ex) {
System.err.println(ex.getMessage());
}
}
}
static public long evaluate(String src) throws ParseException {
Reader reader = new StringReader(src);
return new Adder(reader).expr();
}
}
PARSER_END(Adder)
SKIP: { <[" ","\t","\r","\n"]> }
TOKEN: {
<INTERGR: (["0"-"9"])+>
}
long expr():
{
Token x, y;
}
{
x=<INTERGR> "+" y=<INTERGR> <EOF>
{
return Long.parseLong(x.image) + Long.parseLong(y.image);
}
}
step 1: 安装 javac
安装 Java 编译器——javac 的命令如下:
$ sudo apt install default-jdk
step 2: 使用 JavaCC 生成解析器
$ javacc sumi.jj
注:JavaCC 的安装和用法,可参考笔者的另一篇文章:《解析器生成器之 JavaCC(1):零基础入门 JavaCC》。
step 3: 编译 JavaCC 所生成的解析器
$ javac Adder.java
查看编译后的文件——Adder.class:
$ file Adder.class
Adder.class: compiled Java class data, version 55.0
step 4: 运行 JavaCC 所生成的解析器
$ java Adder "1+2" "1 + 3"
运行结果如下:
3
4