ANTLR4(十四) 调用在隐藏Channel里的信息

写在之前

我们曾经介绍过这样一种情况:

忽略空白和注释,但在必要的时候也能调用他们。我们的处理方法是:

将不需要的注释和空白放到 channel HIDDEN中。

那么这次,我们尝试将空白和注释放在多个Channel中。

之后我们会用一个例子演示如何读取在隐藏Channel中的信息。

将空白和注释放到不同的Channel中

注意在最新的版本中,以下这种方法已经过时了。

@lexer::members {
    public static final int WHITESPACE = 1;
    public static final int COMMENTS = 2;
}
...
WS  :   [ \t\n\r]+ -> channel(WHITESPACE) ;  // channel(1)

SL_COMMENT
    :   '//' .*? '\n' -> channel(COMMENTS)   // channel(2)
    ;

方便起见,我们会直接用数字指定隐藏Channel,而不是标识符

语法文件

我们只列举了需要的部分,即语法文件入口file,语法规则varDecl(变量声明语句),以及空白词法和注释词法。

可以观察到,我们将空白词法移到了Channel1,将注释词法移到了Channel2。(注意默认的Channel是Channel0,其它的Channel都是HIDDEN类型)

//Cymbol.g4
grammar Cymbol;

file:   (functionDecl | varDecl)+ ;

varDecl
    :   type ID ('=' expr)? ';'
    ;
...
WS  :   [ \t\n\r]+ -> channel(1) ;  // channel(1)

SL_COMMENT
    :   '//' .*? '\n' -> channel(2);   // channel(2)

运行效果

antlr4 Cymbol.g4

grun Cymbol -tokens -tree

输入:

 int i=3; //testing

输出:

可以看到空白符和注释已经分别对应到Channel1和Channel2上了。
在这里插入图片描述

将隐藏通道里的注释放到声明前

我们的计划如下。

输入:

//t.cym
int n = 0; // define a counter
int i = 9;

输出:

//t.cym
/* define a counter */
int n = 0; 
int i = 9;

思路

我们假设只有一条注释语句。思路很简单,我们需要重构tokens流。

那么在我们自定义的监听器中,需要一个BufferedTokenStream存放已经在CommonTokenStream处理过的tokens流。还需要一个TokenStreamReWriter去重构这个tokens流。

可以看出,这个ReWriter是介于ParserLexer之间的这么一个tokens流处理器。

//CymbolShifter extends CymbolBaseListener
        BufferedTokenStream tokens;
        TokenStreamRewriter rewriter;

        public CymbolShifter(BufferedTokenStream tokens){
            this.tokens=tokens;
            rewriter=new TokenStreamRewriter(tokens);
        }

接下来我们定位到varDecl规则,这意味着我们需要进入exitVarDecl函数中处理。

public void exitVarDecl(CymbolParser.VarDeclContext ctx) {...}

首先我们需要定位到;这个Token,通过;来获取注释在整个tokens流的index

            Token semi=ctx.getStop();
            int i=semi.getTokenIndex();

有了index,就可以通过getHiddenTokensToRight(index,channel)来获取该tokens在index后对应特定channel的tokens了,一直获取到写一个默认Channel可以识别的token为止。
在这里插入图片描述

List<Token> cmtChannel=tokens.getHiddenTokensToRight(i,2);

接下来我们读取cmtChannel的第一个token,由于我们假设只有一条注释,实际上也只有一个token。并从这条注释的第3个字符开始读(过滤了//),然后在注释前后加上“/”和 “/”。最后通过rewriter,将重构的注释放在当前ctx的前面,并且将旧注释替换成\n。

List<Token> cmtChannel=tokens.getHiddenTokensToRight(i,2);
            if(cmtChannel!=null){
                Token cmt=cmtChannel.get(0);
                if(cmt!=null){
                    String txt=cmt.getText().substring(2);
                    String newCmt="/*"+txt.trim()+"*/\n";
                    rewriter.insertBefore(ctx.start,newCmt);
                    rewriter.replace(cmt,"\n");
                }
            }

自定义监听器

    public static class CymbolShifter extends CymbolBaseListener{

        BufferedTokenStream tokens;
        TokenStreamRewriter rewriter;

        public CymbolShifter(BufferedTokenStream tokens){
            this.tokens=tokens;
            rewriter=new TokenStreamRewriter(tokens);
        }

        @Override 
        public void exitVarDecl(CymbolParser.VarDeclContext ctx) {
            Token semi=ctx.getStop();
            int i=semi.getTokenIndex();
            List<Token> cmtChannel=tokens.getHiddenTokensToRight(i,2);
            if(cmtChannel!=null){
                Token cmt=cmtChannel.get(0);
                if(cmt!=null){
                    String txt=cmt.getText().substring(2);
                    String newCmt="/*"+txt.trim()+"*/\n";
                    rewriter.insertBefore(ctx.start,newCmt);
                    rewriter.replace(cmt,"\n");
                }
            }
        }

    }

运行结果

主程序和普通的自定义监听器的调用并无二样:

public static void main(String[] args)throws Exception{

        String inputFile = null;
        if ( args.length>0 ) inputFile = args[0];
        InputStream is = System.in;
        if ( inputFile!=null ) {
            is = new FileInputStream(inputFile);
        }

        ANTLRInputStream input=new ANTLRInputStream(is);
        CymbolLexer lexer=new CymbolLexer(input);
        CommonTokenStream tokens=new CommonTokenStream(lexer);
        CymbolParser parser=new CymbolParser(tokens);
        RuleContext tree=parser.file();

        ParseTreeWalker walker=new ParseTreeWalker();
        CymbolShifter shifter=new CymbolShifter(tokens);
        walker.walk(shifter,tree);
        System.out.println(shifter.rewriter.getText());
    }

在这里插入图片描述


版权声明:本文为pourtheworld原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。