使用 JavaCC 创建 AST 看起来很像创建“普通”解析器(在jj
文件中定义)。如果你已经有一个有效的语法,它(相对)容易:)
以下是创建 AST 所需的步骤:
- 将您的
jj
语法文件重命名为jjt
- 用根标签装饰它(斜体字是我自己的术语......)
- 调用
jjtree
您的jjt
语法,这将为您生成一个jj
文件
- 调用
javacc
您生成的jj
语法
- 编译生成的
java
源文件
- 测试一下
这是一个快速的分步教程,假设您使用的是 MacOS 或 *nix,将javacc.jar
文件放在与语法文件相同的目录中,java
并且javac
位于系统的 PATH 中:
1
假设你的jj
语法文件被称为TestParser.jj
,重命名它:
mv TestParser.jj TestParser.jjt
2
现在是棘手的部分:修饰您的语法,以便创建正确的 AST 结构。您可以通过在它之后(以及在 之前)添加一个后跟一个标识符来装饰AST(或节点,或生产规则(都一样))。在您最初的问题中,您有很多不同的制作,这意味着您正在为不同的制作规则创建相同类型的 AST:这不是您想要的。#
:
#void
如果您不装饰您的产品,则使用产品的名称作为节点的类型(因此,您可以删除#void
):
void decl() :
{}
{
var_decl()
| const_decl()
}
现在规则只是返回规则var_decl()
或返回的任何 AST const_decl()
。
现在让我们看一下(简化的)var_decl
规则:
void var_decl() #VAR :
{}
{
<VAR> id() <COL> id() <EQ> expr() <SCOL>
}
void id() #ID :
{}
{
<ID>
}
void expr() #EXPR :
{}
{
<ID>
}
我用#VAR
类型装饰。现在这意味着该规则将返回以下树结构:
VAR
/ | \
/ | \
ID ID EXPR
如您所见,终端已从 AST 中丢弃!这也意味着id
andexpr
规则会丢失与<ID>
终端匹配的文本。当然,这不是你想要的。对于需要保持终端匹配的内部文本的规则,您需要将.value
树的显式设置.image
为匹配终端的 :
void id() #ID :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void expr() #EXPR :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
导致输入"var x : int = i;"
看起来像这样:
VAR
|
.---+------.
/ | \
/ | \
ID["x"] ID["int"] EXPR["i"]
这就是您为 AST 创建适当结构的方式。下面是一个小语法,它是您自己的语法的一个非常简单的版本,包括一个main
测试它的小方法:
// TestParser.jjt
PARSER_BEGIN(TestParser)
public class TestParser {
public static void main(String[] args) throws ParseException {
TestParser parser = new TestParser(new java.io.StringReader(args[0]));
SimpleNode root = parser.program();
root.dump("");
}
}
PARSER_END(TestParser)
TOKEN :
{
< OPAR : "(" >
| < CPAR : ")" >
| < OBR : "{" >
| < CBR : "}" >
| < COL : ":" >
| < SCOL : ";" >
| < COMMA : "," >
| < VAR : "var" >
| < EQ : "=" >
| < CONST : "const" >
| < ID : ("_" | <LETTER>) ("_" | <ALPHANUM>)* >
}
TOKEN :
{
< #DIGIT : ["0"-"9"] >
| < #LETTER : ["a"-"z","A"-"Z"] >
| < #ALPHANUM : <LETTER> | <DIGIT> >
}
SKIP : { " " | "\t" | "\r" | "\n" }
SimpleNode program() #PROGRAM :
{}
{
(decl())* (function())* <EOF> {return jjtThis;}
}
void decl() :
{}
{
var_decl()
| const_decl()
}
void var_decl() #VAR :
{}
{
<VAR> id() <COL> id() <EQ> expr() <SCOL>
}
void const_decl() #CONST :
{}
{
<CONST> id() <COL> id() <EQ> expr() <SCOL>
}
void function() #FUNCTION :
{}
{
type() id() <OPAR> params() <CPAR> <OBR> /* ... */ <CBR>
}
void type() #TYPE :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void id() #ID :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void params() #PARAMS :
{}
{
(param() (<COMMA> param())*)?
}
void param() #PARAM :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void expr() #EXPR :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
3
让jjtree
类(包含在 中javacc.jar
)为您创建一个jj
文件:
java -cp javacc.jar jjtree TestParser.jjt
4
上一步已创建文件TestParser.jj
(如果一切正常)。让javacc
(也存在于javacc.jar
)处理它:
java -cp javacc.jar javacc TestParser.jj
5
要编译所有源文件,请执行以下操作:
javac -cp .:javacc.jar *.java
(在 Windows 上,执行javac -cp .;javacc.jar *.java
:)
6
关键时刻已经到来:让我们看看一切是否真的有效!让解析器处理输入:
var n : int = I;
const x : bool = B;
double f(a,b,c)
{
}
执行以下操作:
java -cp . TestParser "var n : int = I; const x : bool = B; double f(a,b,c) { }"
您应该会在控制台上看到以下内容:
程序
声明
VAR
ID
ID
EXPR
声明
常数
ID
ID
EXPR
功能
类型
ID
参数
参数
参数
参数
请注意,您看不到ID
' 匹配的文本,但相信我,它们就在那里。该方法dump()
根本没有显示它。
高温高压
编辑
对于包含表达式的有效语法,您可以查看我的以下表达式评估器:https ://github.com/bkiers/Curta (语法在 中src/grammar
)。您可能想看看如何在二进制表达式的情况下创建根节点。