ANTLR 4 - 如何从自定义侦听器访问隐藏的评论频道?

时间:2016-01-11 20:08:30

标签: java antlr4

用旧语言为遗留代码编写漂亮的打印机。在编写转换器输出C ++之前,我的计划是学习解析和解析。 6月份,我和Java和ANTLR一起陷入了深渊,所以我肯定有一些知识空白。

我已经达到了为自定义侦听器编写方法的程度,我希望能够对这些注释进行漂亮打印。我的评论是在一个单独的隐藏频道上。以下是隐藏令牌的语法规则:

/* Comments and whitespace -- Nested comments are allowed, each is redirected to a specific channel */
COMMENT_1   :  '(*' (COMMENT_1|COMMENT_2|.)*? '*)'  -> channel(1)  ;
COMMENT_2   :  '{' (COMMENT_1|COMMENT_2|.)*? '}'    -> channel(1)  ;

NEWLINES    :  [\r\n]+                              -> channel(2)  ; 
WHITESPACE  :  [ \t]+                               -> skip        ; 

我一直在玩p上的Cymbol CommentShifter示例。最终的ANTLR 4参考文献207和我试图找出如何使其适应我的听众方法。

public void exitVarDecl(ParserRuleContext ctx) {
    Token semi = ctx.getStop();
    int i = semi.getTokenIndex();
    List<Token> cmtChannel = tokens.getHiddenTokensToRight(i, CymbolLexer.COMMENTS);
    if (cmtChannel != null) {
        Token cmt = cmtChannel.get(0);
        if (cmt != null) {
            String txt = cmt.getText().substring(2);
            String newCmt = "// " + txt.trim(); // printing comments in original format
            rewriter.insertAfter(ctx.stop, newCmt); // at end of line
            rewriter.replace(cmt, "\n");
        }
    }
}

我使用exitEveryRule而不是exitVarDecl来修改此示例,并且它适用于Cymbol示例但是当我将其调整到我自己的侦听器时,无论是否使用{{1},我都会得到空指针异常}或exitEveryRule

我正在关注this answer这看起来很有希望,但我认为我真正需要的是解释隐藏频道数据的存储方式以及如何访问它。我花了几个月的时间才真正在解析树中获得监听器方法和上下文。

exitSpecificThingCommonTokenStream.LT()CommonTokenStream.LA()似乎是我想要使用的,但为什么SO的答案使用与ANTLR书籍示例完全不同的方法?我应该了解令牌索引或令牌类型?

我想更好地理解这背后的逻辑。

1 个答案:

答案 0 :(得分:3)

好的,我无法回答AnTLR如何在内部存储数据,但我可以告诉您如何访问隐藏的令牌。我已经在我的计算机上使用AnTLR v4.1对C#.NET v4.5.2进行了测试。

我的规则如下:

LineComment
    :   '//' ~[\r\n]*
        -> channel(1)
    ;

在我的代码中,我得到了这样的整个原始令牌流:

IList<IToken> lTokenList = cmnTokenStream.Get( 0, cmnTokenStream.Size );

为了测试,我使用以下循环打印了令牌列表:

foreach ( IToken iToken in lTokenList )
{
    Console.WriteLine( "{0}[{1}] : {2}",
        iToken.Channel,
        iToken.TokenIndex,
        iToken.Text );
}

运行此代码:

void Foo()
{
    // comment
    i = 5;
}

产生以下输出(为了简洁起见,请假设我有一个完整的语法,也忽略了空格):

0[0] : void
0[1] : Foo
0[2] : (
0[3] : )
0[4] : {
1[5] : // comment
0[6] : i
0[7] : =
0[8] : 6
0[9] : ;
0[10] : }

您只能看到单个评论标记的频道索引1。因此,您可以使用此循环访问评论标记:

int lCommentCount = 0;
foreach ( IToken iToken in lTokenList )
{
    if ( iToken.Channel == 1 )
    {
        Console.WriteLine( "{0} : {1}",
            lCommentCount++,
            iToken.Text );
    }
}

然后你可以用这些令牌做任何事情。如果您有多个流也可以工作,但我会提醒您不要使用超过65,536个流。当我尝试使用令牌规则重定向到流索引65536编译语法时,AnTLR出现以下错误:

Serialized ATN data element out of range.

所以我猜他们只使用16位无符号整数来索引流。奇怪的。