使用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(或节点或生产规则)。在最初的问题中,您有很多不同的产品,这意味着您要针对不同的生产规则创建相同类型的AST:这不是您想要的。
#``:``#void
如果您不 装饰 产品,则将产品名称用作节点的类型(因此,您可以删除
#void):
void decl() :{}{ var_decl() | const_decl()}现在,该规则仅返回该AST
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和
expr规则会松开其
<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.jjtPARSER_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 删除 ConST ID ID EXPR 功能 类型 ID 参数 参数 参数 参数
请注意,您看不到
ID匹配的文本,但请相信我,它们在那里。该方法
dump()只是不显示它。
高温超导
编辑
对于包含表达式的有效语法,您可以看一下我的以下表达式评估器:https://github.com/bkiers/Curta(语法在中
src/grammar)。您可能想看看在二进制表达式的情况下如何创建根节点。



