解析器生成器之 JavaCC(4):如何基于 JavaCC 生成抽象语法树
Author: stormQ
Created: Sunday, 14. March 2021 04:19PM
Last Modified: Tuesday, 23. March 2021 09:55PM
本文介绍了基于 JavaCC 生成抽象语法树时所使用的action
功能,以及用于半自动化生成action
和节点类的工具JJTree
的使用方法。
JavaCC 中的action
功能是指当token
序列和语法规则匹配时能够执行任意的 Java 代码。
action
可以写在任何地方,即在语法规则的前面、中间和后面都可以。当解析进行到写有action
的地方时,action
就会被执行。
1) 设置终端符号的语义值
终端符号的语义值就是Token
类的实例。Token
类是由 JavaCC 自动生成的。
a) 设置终端符号<IDENTIFIER>
的语义值
void name():
{
Token x;
}
{
x=<IDENTIFIER>
}
上述语法描述中,通过书写x=<IDENTIFIER>
(即Token 类的变量名=<终端符号名>
的形式),从而将该终端符号的语义值设置到临时变量x
中。
2) 获取终端符号的语义值
Token
类中,image
字段的值就是终端符号的语义值。
void name():
{
Token x;
}
{
x=<IDENTIFIER>
{
System.out.println(x.image);
}
}
上述语法描述中,通过访问Token
类实例x
中的image
字段来获取终端符号的语义值。
1) 设置非终端符号的语义值
如果要设置非终端符号的语义值,可以使用return
语句从action
返回语义值。
a) 设置非终端符号name()
的语义值
String name():
{
Token x;
}
{
x=<IDENTIFIER>
{
return x.image;
}
}
上述语法描述中,通过在action
中返回临时变量x
中的image
字段的值,从而作为非终端符号name()
的语义值。这里返回的语义值的数据类型为String
。
2) 获取非终端符号的语义值
a) 获取非终端符号name()
的语义值
void import_stmt():
{
String x;
}
{
<IMPORT> x=name() ("." name())* ";"
}
上述语法描述中,通过x=name()
将非终端符号name()
的语义值赋值给临时变量x
。
注意: 由于非终端符号name()
的语义值的数据类型为String
。因此。临时变量x
的数据类型也必须是String
。
1) 选择规则中设置 action
a) 选择规则中,为各选项分别设置 action
String expr():
{
Token x, y;
}
{
x=<IDENTIFIER>
{
return x.image;
}
| y=<STRING>
{
return y.image;
}
}
上述语法描述表示,如果是标识符,那么返回标识符的字面值;否则,如果是字符串,那么返回字符串的字面值;否则,为语法错误。
注意: 上述两个action
只有一个可能被执行。
b) 选择规则中,为所有选项设置共同的 action
String expr():
{
Token x;
}
{
(x=<IDENTIFIER> | x=<STRING>)
{
return x.image;
}
}
上述语法描述表示,如果是标识符或字符串,那么返回其字面值;否则,为语法错误。
2) 重复规则中设置 action
a) 重复规则中,为每次重复设置 action
void import_stmt():
{
String x;
}
{
<IMPORT> name()
(
"." x=name() { System.out.println(x); }
)*
";"
}
上述语法描述表示,通过将action
写在重复规则的里面,从而每次匹配. name()
后都会执行action
——System.out.println(x);
。
b) 重复规则中,仅为最后一次重复设置 action
void import_stmt():
{
String x = "";
}
{
<IMPORT> name()
(
"." x=name()
)*
{
System.out.println(x);
}
";"
}
上述语法描述表示,通过将action
写在重复规则的外面,从而仅在最后一次匹配. name()
后才执行action
——System.out.println(x);
。
step 1: 编写语法描述文件
1) 修改编译单元的语法规则
将编译单元符号的语义值设置为返回节点对象。修改后的内容如下所示。
SimpleNode compilation_unit():
{}
{
import_stmts() <EOF>
{
return jjtThis;
}
}
注:SimpleNode
类表示节点,是由 JJTree 自动生成的。
2) 生成SimpleNode
类,并打印抽象语法树
在解析器的主函数中添加如下内容:
SimpleNode node = parser.compilation_unit();
node.dump("");
3) 语法描述文件示例
options {
STATIC = false;
}
PARSER_BEGIN(Parser)
import java.io.*;
public class Parser {
static public void main(String[] args) {
for (String arg : args) {
try {
parser(arg);
}
catch (ParseException ex) {
System.err.println(ex.getMessage());
}
}
}
static public void parser(String file) throws ParseException {
try {
InputStream input = new FileInputStream(file);
Parser parser = new Parser(input);
SimpleNode node = parser.compilation_unit();
node.dump("");
}
catch (FileNotFoundException ex) {
System.err.println(ex.getMessage());
}
}
}
PARSER_END(Parser)
SKIP:
{
<[" ","\t","\n"]>
}
TOKEN: {
<IMPORT: "import">
}
TOKEN [IGNORE_CASE] :
{
< IDENTIFIER: ["a"-"z", "_"] (["a"-"z","0"-"9","_"])* >
}
SimpleNode compilation_unit():
{}
{
import_stmts() <EOF>
{
return jjtThis;
}
}
void import_stmts():
{}
{
(import_stmt())*
}
void import_stmt():
{}
{
<IMPORT> name() ("." name())* ";"
}
void name():
{}
{
<IDENTIFIER>
}
step 2: 运行 JJTree
运行 JJTree:
$ jjtree ./CbParser_part.jj
注:JJTree 的输入文件的后缀可以不是.jjt
。
JJTree 生成的文件如下所示:
CbParser_part.jj.jj Node.java SimpleNode.java
JJTParserState.java ParserTreeConstants.java
其中,CbParser_part.jj.jj
作为后续要使用的语法描述文件。
step 3: 运行解析器
1) 生成解析器
$ javacc ./CbParser_part.jj.jj
Java Compiler Compiler Version 5.0 (Parser Generator)
(type "javacc" with no arguments for help)
Reading from file ./CbParser_part.jj.jj . . .
File "TokenMgrError.java" does not exist. Will create one.
File "ParseException.java" does not exist. Will create one.
File "Token.java" does not exist. Will create one.
File "SimpleCharStream.java" does not exist. Will create one.
Parser generated successfully.
2) 编译解析器
$ javac Parser.java
3) 运行解析器
$ java Parser ./hello_part.cb
compilation_unit
import_stmts
import_stmt
name
import_stmt
name
name
从上面的结果中可以看出,生成了一棵语法树。其形状如下:
compilation_unit
|
import_stmts
/ \
import_stmt import_stmt
/ / \
name name name
hello_part.cb 的内容如下:
import stdio;
import sys.time;
下一篇:上一级目录