如何在语法上实现JJTree
我有一个任务是使用JavaCC为讲师提供的语言制作一个带有语义分析的自上而下的解析器。 我已经写出了生产规则,没有错误。 我完全坚持如何将JJTree用于我的代码,而我在互联网上搜索教程的时间并没有让我任何地方。 只是想知道是否有人可以抽出时间来解释如何在代码中实现JJTree? 或者,如果有一个隐藏的分步教程,那将是一个很好的帮助!
以下是我的一些生产规则,以防他们提供帮助。 提前致谢!
void program() : {} { (decl())* (function())* main_prog() } void decl() #void : {} { ( var_decl() | const_decl() ) } void var_decl() #void : {} { ident_list() type() ( ident_list() type())* } void const_decl() #void : {} { identifier() type() expression() ( identifier() type() expression())* } void function() #void : {} { type() identifier() param_list() (decl())* (statement() )* returnRule() (expression() | {} ) }
使用JavaCC创建AST看起来很像创建一个“普通”解析器(在jj
文件中定义)。 如果你已经有一个工作语法,它(相对)容易:)
以下是创建AST所需的步骤:
- 将您的
jj
语法文件重命名为jjt
- 用root标签 装饰它(斜体字是我自己的术语……)
- 在你的
jjt
语法上调用jjtree
,它将为你生成一个jj
文件 - 在生成的
jj
语法上调用javacc
- 编译生成的
java
源文件 - 测试一下
这是一个快速的分步教程,假设您正在使用MacOS或* nix,将javacc.jar
文件放在与语法文件相同的目录中,并且java
和javac
位于系统的PATH上:
1
假设您的jj
语法文件名为TestParser.jj
,请将其重命名为:
mv TestParser.jj TestParser.jjt
2
现在是棘手的部分: 装饰你的语法,以便创建适当的AST结构。 您通过在其之后(以及之前)添加#
后跟一个标识符来装饰 AST(或节点或生产规则(所有相同))。 在您的原始问题中,您在不同的制作中有很多#void
,这意味着您为不同的制作规则创建了相同类型的AST:这不是您想要的。
如果您不装饰您的作品,则将作品的名称用作节点的类型(因此,您可以删除#void
):
void decl() : {} { var_decl() | const_decl() }
现在规则只返回规则var_decl()
或const_decl()
返回的AST。
现在让我们看一下(简化的) var_decl
规则:
void var_decl() #VAR : {} { id() id() expr() } void id() #ID : {} { } void expr() #EXPR : {} { }
我用#VAR
类型装饰。 现在这意味着此规则将返回以下树结构:
VAR / | \ / | \ ID ID EXPR
如您所见,终端被AST丢弃! 这也意味着id
和expr
规则松开了
终端匹配的文本。 当然,这不是你想要的。 对于需要保持内部文本与终端匹配的规则,您需要将树的.value
显式设置为匹配终端的.image
:
void id() #ID : {Token t;} { t= {jjtThis.value = t.image;} } void expr() #EXPR : {Token t;} { t= {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 : ("_" | ) ("_" | )* > } TOKEN : { < #DIGIT : ["0"-"9"] > | < #LETTER : ["a"-"z","A"-"Z"] > | < #ALPHANUM : | > } SKIP : { " " | "\t" | "\r" | "\n" } SimpleNode program() #PROGRAM : {} { (decl())* (function())* {return jjtThis;} } void decl() : {} { var_decl() | const_decl() } void var_decl() #VAR : {} { id() id() expr() } void const_decl() #CONST : {} { id() id() expr() } void function() #FUNCTION : {} { type() id() params() /* ... */ } void type() #TYPE : {Token t;} { t= {jjtThis.value = t.image;} } void id() #ID : {Token t;} { t= {jjtThis.value = t.image;} } void params() #PARAMS : {} { (param() ( param())*)? } void param() #PARAM : {Token t;} { t= {jjtThis.value = t.image;} } void expr() #EXPR : {Token t;} { t= {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
五
要编译所有源文件,请执行:
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 function 类型 ID PARAMS PARAM PARAM PARAM
请注意,您没有看到ID
匹配的文本,但请相信我,他们就在那里。 方法dump()
根本不显示它。
HTH
编辑
对于包含表达式的工作语法,您可以查看我的以下表达式求值程序: https : //github.com/bkiers/Curta (语法在src/grammar
)。 您可能想要了解如何在二进制表达式的情况下创建根节点。
这是一个使用JJTree的例子http://anandsekar.github.io/writing-an-interpretter-using-javacc/