如何使用ANTLR重写具有复合根的子树?

时间:2012-12-03 19:49:41

标签: antlr antlr3

我有一个带有这样子树的antlr语法:

 ^(type ID)

我要转换为:

 ^(type DUMMY ID)

其中type为'a'|'b'。

注意:我真正想做的是通过生成虚拟名称将匿名实例化转换为显式。

我已将其缩小到下面的语法,但我得到了这个:

(a bar) (b bar)
got td
got bu
Exception in thread "main" org.antlr.runtime.tree.RewriteEmptyStreamException: rule type
        at org.antlr.runtime.tree.RewriteRuleElementStream._next(RewriteRuleElementStream.java:157)
        at org.antlr.runtime.tree.RewriteRuleSubtreeStream.nextNode(RewriteRuleSubtreeStream.java:77)
        at Pattern.bu(Pattern.java:382)

错误消息继续。到目前为止我的调试:

  1. 输入通过初始语法生成两棵树。一个酒吧和一个酒吧。
  2. 第二个语法与树匹配。它打印td和bu。
  3. 重写崩溃,但我不明白为什么? RewriteEmptyStreamException是什么意思。
  4. 进行此类重写的正确方法是什么?

    我的主要语法Rewrite.g:

    grammar Rewrite;
    
    options {
      output=AST;
    }
    
    @members{
      public static void main(String[] args) throws Exception {
          RewriteLexer lexer = new RewriteLexer(new ANTLRStringStream("a foo\nb bar"));
          RewriteParser parser = new RewriteParser(new CommonTokenStream(lexer));
          CommonTree tree = (CommonTree)parser.test().getTree();
          System.out.println(tree.toStringTree());
    
          CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree);        
          Pattern p = new Pattern(nodes);
    
          CommonTree newtree = (CommonTree) p.downup(tree);
      }
    }
    
    type 
        : 'a'
        | 'b'
        ;
    
    test : id+;
    id   : type ID  -> ^(type ID["bar"]);
    
    DUMMY : 'dummy';
    ID   : ('a'..'z')+;
    WS : (' '|'\n'|'r')+ {$channel=HIDDEN;};
    

    和Pattern.g

    tree grammar Pattern;
    
    options {
        tokenVocab = Rewrite;
        ASTLabelType=CommonTree;
        output=AST;
        filter=true;             // tree pattern matching mode
    }
    
    topdown
        : td
        ;
    
    bottomup
        : bu
        ;
    
    type 
        : 'a'
        | 'b'
        ;
    
    td
        : ^(type ID) { System.out.println("got td"); }
        ;
    
    bu
        : ^(type ID) { System.out.println("got bu"); }
            -> ^(type DUMMY ID)
        ;
    

    做编译:

    java -cp ../jar/antlr-3.4-complete-no-antlrv2.jar org.antlr.Tool Rewrite.g 
    java -cp ../jar/antlr-3.4-complete-no-antlrv2.jar org.antlr.Tool Pattern.g 
    javac -cp ../jar/antlr-3.4-complete-no-antlrv2.jar *.java 
    java -classpath .:../jar/antlr-3.4-complete-no-antlrv2.jar RewriteParser
    

    编辑1:我也尝试过使用antlr4,我也遇到了同样的崩溃。

2 个答案:

答案 0 :(得分:2)

要解密才能解决两个小问题,Rewrite中的一个问题和Pattern中的另一个问题。

Rewrite语法生成^(type ID)作为输出AST中的根元素,如输出(a bar) (b bar)中所示。无法转换根元素,因为转换实际上是一种子交换形式:元素的父元素删除元素并用新的“转换”版本替换它。没有父母,您将收到错误Can't set single child to a list。添加根是创建一个虚构的令牌ROOT或任何你喜欢的名称,并在你的入门级规则的AST生成中引用它,如下所示:test : id+ -> ^(ROOT id+);

Pattern语法,即产生错误的语法,被type规则混淆:type : 'a' | 'b' ;作为重写的一部分。我不知道这里的低级细节,但显然树编译器在编写转换时不会在type中保持被访问根规则的状态,如^(type ID)(或者它可以'或者不应该,或者可能是其他一些限制)。解决此问题的最简单方法是进行以下两项更改:

  • 通过将ID中的type中的规则Rewritetype: 'a' | 'b';更改为type: ID;,让文字“a”和“b”与词法分析器中的bu匹配。
  • Pattern中的规则^(ID ID)^(ID DUMMY ID)匹配,然后转换为Rewrite

现在对main的{​​{1}}进行一些小的调试更改,输入"a foo\nb bar"会产生以下输出:

(ROOT (a foo) (b bar))
got td
got bu
(a foo) -> (a DUMMY foo)
got td
got bu
(b bar) -> (b DUMMY bar)
(ROOT (a DUMMY foo) (b DUMMY bar))

以下是我更改过的文件:

Rewrite.g

grammar Rewrite;

options {
  output=AST;
}

tokens { 
 ROOT;
}

@members{
  public static void main(String[] args) throws Exception {
      RewriteLexer lexer = new RewriteLexer(new ANTLRStringStream("a foo\nb bar"));
      RewriteParser parser = new RewriteParser(new CommonTokenStream(lexer));
      CommonTree tree = (CommonTree)parser.test().getTree();
      System.out.println(tree.toStringTree());

      CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree);        
      Pattern p = new Pattern(nodes);

      CommonTree newtree = (CommonTree) p.downup(tree, true); //print the transitions to help debugging
      System.out.println(newtree.toStringTree()); //print the final result
  }
}

type : ID;
test : id+ -> ^(ROOT id+);
id   : type ID  -> ^(type ID);

DUMMY : 'dummy';
ID   : ('a'..'z')+;
WS : (' '|'\n'|'r')+ {$channel=HIDDEN;};

Pattern.g

tree grammar Pattern;

options {
    tokenVocab = Rewrite;
    ASTLabelType=CommonTree;
    output=AST;
    filter=true;             // tree pattern matching mode
}

topdown
    : td
    ;

bottomup
    : bu
    ;

td
    : ^(ID ID) { System.out.println("got td"); }
    ;

bu
    : ^(ID ID) { System.out.println("got bu"); }
        -> ^(ID DUMMY ID)
    ;

答案 1 :(得分:1)

我对树模式没有太多经验,有或没有重写。但是当在其中使用重写规则时,我相信您的选项还应包括rewrite=true;。最终的ANTLR参考文献没有处理它们,所以我不完全确定(有关更多信息,请查看ANTLR wiki)。

然而,对于这种(相对)简单的重写,你并不需要单独的语法。您可以在其他解析器规则中使DUMMY成为虚构的令牌并注入,如下所示:

grammar T;

options {
  output=AST;
}

tokens {
  DUMMY;
}

test : id+;
id   : type ID  -> ^(type DUMMY["dummy"] ID);

type 
 : 'a'
 | 'b'
 ;

ID : ('a'..'z')+;
WS : (' '|'\n'|'r')+ {$channel=HIDDEN;};

将解析输入:

a bar 
b foo

进入以下AST:

enter image description here

请注意,如果您的词法分析器还要将输入"dummy"标记为DUMMY令牌,请将tokens { ... }块更改为:

tokens {
  DUMMY='dummy';
}

你仍然可以在其他规则中注入 DUMMY