【背景】
折腾:
【未解决】antlr解析字符串STRING出错:no viable alternative at input,对应的错误是NoViableAltException(0@[null])
的过程中,需要搞懂antlr中的lexer和parser。到底是什么。以及两者之间的关系。
【解决过程】
1.参考:
了解到。
- 输入:一堆字符,即我们写的语法代码,一个文件,也就是一堆字符;
- 经过lexer处理;
- 输出:一系列的token,相当于一堆的变量或者说符号;
lexer也被叫做:
- lexical analyzer
- scanner
2.后来参考:
【资料下载】ANTLR的最全的官方文档:The Definitive ANTLR Reference:v2,v3,v4版本都有下载
去找到了antlr v3的文档。
看其解释,更加深入了解到了一些内容:
(1)lexer和parser的语法是相同的,但是内部含义不同。
即,最好是自己真正搞懂内部,底层所涉及的含义如何。
即,写了个语法,要知道真正的含义。
(2)Lexer的rule,是以大写字母开头的。
比如:
ID : ('a'..'z' |'A'..'Z' |'_' ) ('a'..'z' |'A'..'Z' |'_' |'0'..'9' )* ;
所以,自己去antlrworks中,把之前的某个,小写字母开头的规则,从小写:
改为大写后,antlrworks中,果然就识别出不同效果,从parser变成lexer了:
3. 为了区别rule所生成的方法,antlr会给生成的id前加m。
比如,上面的ID所生成的方法叫做mID()
4.parser有起始符号,start symbol(类似于入口函数);
lexer没有start symbol。
5.lexer的语法,其实,就是一堆的符号定义(token definition)
每个token,都是,针对于输入的内容,全局性的,任何时刻都试用,都去匹配的。
内部机制:
antlr生成一个nextToken()的方法,其中就是个大的switch结构,将输入的内容,匹配到对应的类型的token后,即路由到某个lexer的rule,就去调用对应的rule去处理。
6.不论对于lexer还是parser,其中的大的rule,都最好拆分为小的rule。
目的:使得程序可读性增加,重用性增加。
7.antlr默认所有的token都是有效(valid)的。
如果你打算某个规则,只是起到帮助作用,即helper rule,那么就应该加上对应的fragment前缀。
目的在于,告诉antlr,此rule,只是被其他(lexer)的rule调用,而不会被传递到parser中。(也不希望被parser中调用?)
举例:
UNICODE_CHAR : '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT ; fragment HEX_DIGIT : '0'..'9' |'a'..'f' |'A'..'F' ;
中的HEX_DIGIT,就是,只在lexer中用,只被lexer中的,其他的rule,此处是UNICODE_CHAR调用。
而不会在生成的token中存在。
对应的 ,如果此处的UNICODE_CHAR,也只是希望被别的lexer中的rule调用的话,则也应该加上对应的fragment前缀。
8.由于antlr支持多个通道(channel),有了这种机制,antlr甚至是可以实现这种情况:
对于解析Java代码过程中:
对于白空格和普通的注释代码,将其输出到一个通道(channel)中;
而对于JavDoc类的注释代码,则将其输出到另外一个独立的通道中;
10.
注:
关于antrl的语法的解释,更多详细内容见:
【整理】antlr中的各种语法:集合元素(Element Sets),标签元素(Element Labels),构造树操作符(Tree construction operators)