如何使用JJTree构建抽象语法树?

时间:2018-11-16 20:58:20

标签: java parsing compiler-construction abstract-syntax-tree javacc

构建AST并将子代添加到树中时,两者之间有什么区别

void NonTerminal #Nonterminal: { Token t;}
{
    t = <MULTIPLY> OtherNonTerminal() {jjtThis.value = t.image;} #Multiply
}

和:

void NonTerminal : { Token t;}
{
    t = <MULTIPLY> OtherNonTerminal() {jjtThis.value = t.image;} #Multiply(2)
}

注意:

<MULTIPLY : "*">

有什么主要区别,并且两者都将以相同的方式工作吗?

也将为该生产规则构建树的另一种方法:

void NonTerminal() : { Token t; }
{
    t = <MULTIPLY> OtherNonTerminal() { jjtThis.value = t.image; } #Mult(2)
|   t = <DIVIDE> OtherNonTerminal() { jjtThis.value = t.image; } #Div(2)
|   {}
}

像这样:

void NonTerminal() #Nonterminal(2) : { Token t; }
{
    (t = <MULTIPLY> OtherNonTerminal() | t = <DIVIDE> OtherNonTerminal() | {}) {jjtThis.value = t.image;}
}

2 个答案:

答案 0 :(得分:1)

这个问题的答案是肯定的。

JAVACC或JJTREE语法按照不同的步骤进行编译。

  1. 词法分析,其中收集单个字符并尝试使用TOKENSPECIAL_TOKENMORESKIP部分中提供的正则表达式来构造标记。 在每次成功的词法分析之后,都会生成一个令牌。
  2. 语法分析,其中这些标记将排列在名为Syntax tree的树中,该树具有提供的production rules的终端节点和非终端节点。 语法分析收集从词法分析生成的每个令牌,然后尝试从中验证语法。

      

    非终端节点:表示其他生产规则。

         

    TERMINAL节点:表示令牌或数据节点。

这是区别,

  1. 成功验证语法后,我们需要一个有用的表格来使用它。 更有用的表示法是“树表示法”,我们已经将“语法树”作为“语法分析”的一部分进行了生成,可以对其进行修改以从中提取出有用的树,这是JJTree进入图片以重命名并创建有用的树结构的地方生产规则中的#NODE_NAME语法。

按如下所示编辑评论

Multiply(2)仅表示两个子级,如果您的运算是A * B,则这是有意义的, 如果您正在执行A * B * C并使用#Multiply(2),则树将像

          Multiply
        /          \
  Multiply           C
    /  \
  A     B

如果您执行A * B * C并使用#Multiply,则树将像

   Multiply    Multiply      Multiply
      |            |             | 
      A            B             C

基本上#Multiply和#Multiply(2)之间的区别是Multiply(2)将等待两个令牌生成,如果发现只有一个抛出异常,并且#Multiply会在生产规则发生时生成节点被匹配。

答案 1 :(得分:1)

在第一种情况下

void NonTerminal #Nonterminal: { Token t;}
{
    t = <MULTIPLY>
    OtherNonTerminal() {jjtThis.value = t.image;}
    #Multiply
}

Multiply节点将在其节点作用域内推送到堆栈上的所有节点作为子节点,但不包括在作用域末尾弹出的所有节点。在这种情况下,这意味着在解析OtherNonTerminal期间所有节点被推送,而没有弹出。

在第二个示例中

void NonTerminal #void : { Token t;}
{
    t = <MULTIPLY>
    OtherNonTerminal() {jjtThis.value = t.image;} 
    #Multiply(2)
}

Multiply节点将从堆栈中获得两个顶部节点作为其子节点。

所以可能有所不同。

另一个区别是第二个示例未指定与Nonterminal关联的节点。

在第一种情况下,该树将被推送

        Nonterminal
             |
          Multiply
              |
All nodes pushed (but not popped) during the parsing of OtherNonterminal

在第二种情况下,OtherNonterminal的解析将完成它的工作(弹出和推送节点),然后弹出两个节点,然后将这棵树推送

     Multiply
      |     |
  A child  Another child

第二个问题。之间的区别

void NonTerminal() #void : { Token t; }
{
    t = <MULTIPLY>
    OtherNonTerminal()
    { jjtThis.value = t.image; }
    #Mult(2)
|
    t = <DIVIDE>
    OtherNonTerminal()
    { jjtThis.value = t.image; }
    #Div(2)
|
    {}
}

void NonTerminal() #Nonterminal(2) : {
    Token t; }
{
    ( t = <MULTIPLY> OtherNonTerminal()
    | t = <DIVIDE> OtherNonTerminal()
    | {}
    )
    {jjtThis.value = t.image;}
}

是当匹配空序列时,第一个不构建节点。

在下一个令牌不是*/的情况下,请考虑第二种方法。您会得到

      Nonterminal
      /        \
  Some node    Some other node
  don't want   you don't want

第二个甚至超过Java编译器,实际上使我感到惊讶,因为对t的引用是一个潜在的未初始化变量。