如何在语法上实现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所需的步骤:

  1. 将您的jj语法文件重命名为jjt
  2. root标签 装饰它(斜体字是我自己的术语……)
  3. 在你的jjt语法上调用jjtree ,它将为你生成一个jj文件
  4. 在生成的jj语法上调用javacc
  5. 编译生成的java源文件
  6. 测试一下

这是一个快速的分步教程,假设您正在使用MacOS或* nix,将javacc.jar文件放在与语法文件相同的目录中,并且javajavac位于系统的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丢弃! 这也意味着idexpr规则松开了终端匹配的文本。 当然,这不是你想要的。 对于需要保持内部文本与终端匹配的规则,您需要将树的.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/