写在之前
我们曾经介绍过这样一种情况:
忽略空白和注释,但在必要的时候也能调用他们。我们的处理方法是:
那么这次,我们尝试将空白和注释放在多个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是介于Parser和Lexer之间的这么一个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());
}
