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

如何使用ANTLR重写具有复合根的子树?,antlr,antlr3,Antlr,Antlr3,我有一个antlr语法,其子树如下: ^(type ID) 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;}; 我想转换为: ^(ty

我有一个antlr语法,其子树如下:

 ^(type ID)
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;};
我想转换为:

 ^(type DUMMY ID)
其中类型为“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)
错误消息将继续。到目前为止,我的调试:

  • 输入通过初始语法生成两棵树。a酒吧和b酒吧
  • 第二个语法与树匹配。它正在打印td和bu
  • 重写失败了,但我不知道为什么?RewriteEmptyStreamException是什么意思
  • 这种重写的正确方式是什么?

    我的主要语法重写。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;};
    
    和模式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,但我也遇到了同样的崩溃。

    我对树模式没有太多经验,无论是否重写。但是当在其中使用重写规则时,我相信您的选项还应该包括
    rewrite=true。最终的ANTLR引用不能处理它们,所以我不能完全确定(请查看以获取更多信息)

    然而,对于这种(相对)简单的重写,实际上并不需要单独的语法。您可以将
    DUMMY
    设为一个虚构的标记,并将其注入其他解析器规则中,如下所示:

     ^(type ID)
    
    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
    
    进入以下步骤:

    请注意,如果您的lexer还打算将输入
    “dummy”
    标记为
    dummy
    标记,请将
    标记{…}
    块更改为:

    tokens {
      DUMMY='dummy';
    }
    

    您仍然可以在其他规则中插入一个

    要使重写工作正常,有两个小问题需要解决,一个是
    重写中的问题,另一个是
    模式中的问题

    重写
    语法在输出AST中生成
    ^(type ID)
    作为根元素,如输出
    (a条)(b条)
    中所示。无法转换根元素,因为转换实际上是子交换的一种形式:元素的父元素删除该元素,并将其替换为新的“转换”版本。如果没有父项,则会出现错误
    无法将单个子项设置为列表
    。添加根是创建一个虚构的令牌
    root
    或任何您喜欢的名称,并在您的入门级规则的AST生成中引用它,就像这样:
    test:id+->^(root id+

    产生错误的
    模式
    语法被
    类型
    规则弄糊涂了:
    类型:“a”|“b”作为重写的一部分。我不知道这里的底层细节,但显然树解析器在编写转换时不维护访问的根规则的状态,如
    ^(type ID)
    中的
    type
    (或者它不能或不应该,或者可能是其他一些限制)。解决此问题的最简单方法是进行以下两项更改:

    • 让文本“a”和“b”匹配lexer中的规则
      ID
      ,方法是将
      Rewrite
      中的规则
      type
      键入:“a”|“b”只需
      类型:ID
      
    • 模式中的规则
      bu
      ^(ID)
      匹配,并转换为
      ^(ID伪ID)
    现在,对
    Rewrite
    main
    进行一些小的调试更改后,输入
    “一个foo\nb条”
    将产生以下输出:

    (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))
    
    以下是我更改的文件:

    重写 模式g
    非常好的问题,顺便说一句。一个正确的问题,这样其他人可以看到你看到的确切错误!独生子女信息非常有用。非常感谢。在使用ID而不是a | b时,使用lexer/parser的目的不就失败了吗?我的实际语法比较复杂。a和b是树可以从中开始的许多ID之一。我只想要有a和b的。我遗漏了什么吗?@mmccoo你可以用最适合你的标记来描述“a”和“b”(我把“a”和“b”塞进
    ID
    ,因为
    ID
    已经描述了它们),只要你最终得到了一个特定的标记来匹配
    模式。例如,假设lexer规则
    TYPE
    TYPE:'a'|'b'和令牌解析器规则
    id
    id:TYPE-id。然后
    Pattern
    bu
    变成
    bu:^(类型ID)->^(类型伪ID)。如果有帮助的话,我可以更新我的答案。@mmccoo如果您不知道,另一件需要注意的事情是,在解析器规则中使用字符串会在幕后悄悄地创建一个新的lexer规则。因此,令牌规则
    键入:“a”|“b”被视为
    类型:SOMERULE_A | SOMERULE_B
    ,其中
    SOMERULE\u A
    SOMERULE\u B
    是新的、不相关的词法规则,分别匹配“A”和“B”(我不知道它们的实际名称,因为它们在正常意义上不可见)。在lexer中显式测试“a”和“b”,如在
    中键入:“a'|“b”,使转换树和跟踪令牌相关问题变得更容易。我最初的回答是,“是的!就是这样。”。。。。但是当我这样做的时候,其他一些寻找a | c和a | b | c的法律规则变得无法实现。所有这些让我怀疑人们是否真的使用重写。看起来非常有用,但我在google上看不到太多关于它的流量。@mmccoo“无法访问的lexer规则”警告是一个非常常见的ANTLR问题,不管是好是坏,都与重写无关。我认为重写被低估了,但我怀疑大多数ANTLR语法不够复杂,不足以证明使用它们是正确的,或者人们满足于在令牌解析器中以奇怪的方式操纵AST输出(与Bart K的示例不同,Bart K的示例是一种避免编写令牌插入重写器的有用方法)。