在学习使用Antlr4的Visitor模式实现一个简单的整数计算器时,笔者使用语法规则stat对输入字符流进行语法分析
输入的字符流,实际上对应多个stat的rule element,而stat一次只能匹配一个,剩余的语句将被忽略
String input = "1 + 2 * 3\n" // PrintEXpr,只有该语句被识别
+ "b = (5 - 2) / 2\n" // Assgin
+ "c = a + b\n" // Assgin
+ "c\n"; // PrintEXpr
笔者想打印lexer识别出来的token,看截断发生在词法分析,还是语法分析阶段
因此,修改main()方法如下:
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
// 新增代码,打印token
for (Token token : tokenStream.getTokens()) {
System.out.println(token);
}
却发现执行结果与先前无异,根本没有token信息。
网上查阅资料后,在StackOverflow上发现一个相同的问题:ANTLR4 Lexer getTokens() returning 0 tokens
有一个回答,得到了认可:
Normally, the Parser is responsible for initiating the lexing of the input stream. To initiate lexing manually, call CommonTokenStream.fill() (which is implemented in BufferedTokenStream).
从第一句话可知:对输入字符流的词法分析,是有parser负责触发的
修改main()方法如下,对其进行验证:
ParseTree stat = parser.stat();
// 新增代码移到此处
for (Token token : tokenStream.getTokens()) {
System.out.println(token);
}
CommonTokenStream
继承BufferedTokenStream
,BufferedTokenStream的类注释上也有类似描述:
This implementation of TokenStream loads tokens from a TokenSource
on-demand
, and places the tokens in a buffer to provide access to any previous token by index.
1+2*3
对应的token,还有一个多余的token b\n
,直接被忽略;再下一个是b,到此发现不再符合stat的语法规则,因此停止对输入字符流的词法分析CommonTokenStream.fill()
是否具有截断特性?修改main()方法如下,使用fill()方法手动触发词法分析,并将tokens与语法分析后的进行对比
public static void main(String[] args) {
// 多个stat,只能识别到第一个stat
String input = "1 + 2 * 3\n"
+ "b = (5 - 2) / 2\n"
+ "c = a + b\n"
+ "c\n";
// 词法分析,解析出token
CharStream charStream = CharStreams.fromString(input);
CalculatorLexer lexer = new CalculatorLexer(charStream);
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
tokenStream.fill();
System.out.println("语法分析前,通过tokenStream.fill()得到的tokens:");
for (Token token : tokenStream.getTokens()) {
System.out.println(token);
}
// 基于token进行语法分析,得到语法解析树
CalculatorParser parser = new CalculatorParser(tokenStream);
ParseTree stat = parser.stat();
// 语法分析,触发了词法分析,此时打印token,token已存在
System.out.println("语法分析后的tokens:");
for (Token token : tokenStream.getTokens()) {
System.out.println(token);
}
// 打印语法解析树并对其进行visit遍历
System.out.printf("stat对应的语法解析树如下:\n%s\n", stat.toStringTree(parser));
CalculatorBaseVisitor<Integer> visitor = new CalculatorVisitorImpl();
visitor.visit(stat);
}
最终执行结果如下:
fill()
方法对输入字符流进行词法分析,直到字符流末尾(Get all tokens from lexer until EOF
)fill()
方法手动触发词法分析后,parser无需再触发词法分析,可以直接使用tokenStream中缓存的tokens。因此,打印出来的tokens没有变化语法分析前,通过tokenStream.fill()得到的tokens:
[@0,0:0='1',<8>,1:0]
[@1,2:2='+',<6>,1:2]
[@2,4:4='2',<8>,1:4]
[@3,6:6='*',<4>,1:6]
[@4,8:8='3',<8>,1:8]
[@5,10:10='b',<9>,2:0]
[@6,12:12='=',<1>,2:2]
[@7,14:14='(',<2>,2:4]
[@8,15:15='5',<8>,2:5]
[@9,17:17='-',<7>,2:7]
[@10,19:19='2',<8>,2:9]
[@11,20:20=')',<3>,2:10]
[@12,22:22='/',<5>,2:12]
[@13,24:24='2',<8>,2:14]
[@14,26:26='c',<9>,3:0]
[@15,28:28='=',<1>,3:2]
[@16,30:30='a',<9>,3:4]
[@17,32:32='+',<6>,3:6]
[@18,34:34='b',<9>,3:8]
[@19,36:36='c',<9>,4:0]
[@20,38:37='' ,<-1>,5:0]
语法分析后的tokens:
... # 结果省略,与上面的tokens一致,未进行截断
stat对应的语法解析树如下:
(stat (expr (expr 1) + (expr (expr 2) * (expr 3))))
打印计算结果: 1+2*3 = 7