自定义语法分析过程
语法中嵌入动作
除了使用监听器和访问器,我们还可以手动实现对语法分析树的访问
例如要对如下三列文本进行识别,打印指定的列,例如第一列为parrt tombu bke,列之间以Tab分割
parrt Terence Parr 101
tombu Tom Burns 020
bke Kevin Edgar 008
在语法文件Rows.g4中添加一些自定义的动作,通过members将RowParser注入到生成的语法分析器类中
grammar Rows;
@parser::members {
int col;
public RowsParser(TokenStream input, int col) { // custom constructor
this(input);
this.col = col;
}
}
file: (row NL)+ ;
row
locals [int i=0] //使用locals定义局部变量$i
: ( STUFF
{
$i++;
if ( $i == col ) System.out.println($STUFF.text); //$STUFF.text获取词法符号匹配的文本
}
)+
;
TAB : '\t' -> skip ; // match but don't pass to the parser
NL : '\r'? '\n' ; // match and pass to the parser
STUFF: ~[\t\r\n]+ ; // match any chars except tab, newline
接下来在主程序中调用语法分析器,这里传入为RowsParser
传入词法符号tokens和列号参数col,并且设置不自动生成语法分析树
public class Col {
public static void main(String[] args) throws Exception {
ANTLRInputStream input = new ANTLRInputStream(System.in);
RowsLexer lexer = new RowsLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
int col = Integer.valueOf(args[0]);
RowsParser parser = new RowsParser(tokens, col); // 传递列好作为参数
parser.setBuildParseTree(false); // 不需要自动生成语法树
parser.file(); // parse
}
}
构建项目并进行测试如下,可以看到输出了第一列的内容
D:\Code\antlr\demo\chapter4>antlr4 -no-listener Rows.g4
D:\Code\antlr\demo\chapter4>javac Rows*.java Col.java
D:\Code\antlr\demo\chapter4>java Col 1 < t.rows
parrt
tombu
bke
语义判定
通过表达式{$i<=$n}?
可以对匹配条件进行判定,从而执行不同的语法分支
例如要对如下的数字序列进行匹配,第一个数字为2,则往后匹配两个数9、10,接下来为3,向后匹配三个数1、2、3
2 9 10 3 1 2 3
使用如下语法文件对数字序列进行匹配,通过判定表达式实现向后匹配n个整数
grammar Data;
file : group+ ;
group: INT sequence[$INT.int] ;
sequence[int n]
locals [int i = 1;]
: ( {$i<=$n}? INT {$i++;} )* // 匹配n个整数
;
INT : [0-9]+ ; // match integers
WS : [ \t\n\r]+ -> skip ; // toss out all whitespace
词法分析器特性
孤岛语法:输入文件中包含多种语言,需要将模板表达式之外的文本按照不同方式处理。
ANTLR的词法分析模式(lexical model)可以对不同格式数据的文件进行处理,例如对XML文件进行处理时,当看到<
时,词法分析器就会进入“标签内部”模式,看到>
或者/>
时就切回默认模式
重写输入流:TokenStreamRewriter
可以对词法输入流进行修改之后再输出,它只是修改词法符号流的“视图”而非其本身的内容。如下所示为一个Java类的输入流中添加指定代码public static final long serialVersionUID = 1L;
,重写监听器进入类的方法,在其中使用rewriter追加内容
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.TokenStreamRewriter;
public class InsertSerialIDListener extends JavaBaseListener {
TokenStreamRewriter rewriter;
public InsertSerialIDListener(TokenStream tokens) {
rewriter = new TokenStreamRewriter(tokens);
}
@Override
public void enterClassBody(JavaParser.ClassBodyContext ctx) {
String field = "\n\tpublic static final long serialVersionUID = 1L;"; //对输入流添加指定内容
rewriter.insertAfter(ctx.start, field);
}
}