下面看下常见的词法结构。
词法符号类型 举例匹配标识符 ID : [a-zA-Z]+ ; // 匹配1个或多个大小写字母
匹配数字 INT : [0-9]+ ; // 匹配1个或多个数字
匹配字符串常量 STRING : '"' .*? '"' ; // 匹配两个双引号之间的任意字符序列
匹配注释和空白字符 WS : [ \t\r\n]+ -> skip ; // 匹配一个或多个空白字符并将它们丢弃
匹配标识符 INT : '0'..'9'+ ; // 匹配1个或者多个数字 ID : ('a'..'z'|'A'..'Z')+ ; // 匹配1个或多个大小写字母
这个让我们感到新鲜的是范围运算符:'a'..'z',它的意思是从a到z的所有字符。
类似ID的规则有时候会和其他词法规则或者字符串常量值产生冲突,例如if、for、while等关键字。
grammar KeywordTest; rule : IF | FOR | WHILE | ID ; IF : 'if' ; FOR : 'for' ; WHILE : 'while' ; ID : [a-zA-Z]+ ; // 不会匹配 if, for, whileANTLR对混合了词法规则和文法规则的语法文件的处理机制:
首先,ANTLR从文法规则中筛选出所有的字符串常量,并将它们和词法规则放在一起。字符串常量被隐式定义为词法规则,然后放置在文法规则之后、显式定义的词法规则之前。ANTLR词法分析器解决歧义问题的方法是优先使用位置靠前的词法规则。这意味着,ID规则必须定义在所有的关键字规则之后。ANTLR将为字符串常量隐式生成的词法规则放在显式定义的词法规则之前,所以它们总是拥有最高的优先级。
匹配数字定义一个简化版的浮点数:
一个浮点数以一列数字为开头,后面跟着一个点,然后是可选的小数部分;浮点数的另外一种格式是,以点为开头,后面是一列数字。一个单独的点不是一个合法的浮点数定义。
FLOAT: DIGIT+ '.' DIGIT* // 匹配 10., 3.14等 | '.' DIGIT+ // 匹配 .618等 ; fragment DIGIT : [0-9] ; // 匹配单个数字这里使用了一条辅助规则DIGIT,这样就不用重复书写[0-9]了。将一条规则声明为fragment可以告诉ANTLR,该规则本身不是一个词法符号,它只会被其他的词法规则使用。这意味着我们不能在文法规则中引用DIGIT。
匹配字符串常量 STRING : '"' .*? '"' ; // 匹配两个双引号之间的任意字符序列其中,点号通配符匹配任意的单个字符。因此,.*就是一个循环,它匹配零个或多个字符组成的任意字符序列。显然,它可以一直匹配到文件结束,但这没有任何意义。
为解决这个问题,ANTLR通过标准正则表达式的标记(?后缀)提供了对非贪婪匹配子规则(nongreedy subrule)的支持。
非贪婪匹配的基本含义是:
获取一些字符,直到发现匹配后续子规则的字符为止。
更准确的描述是,在保证整个父规则完成匹配的前提下,非贪婪的子规则匹配数量最少的字符。
.*是贪婪的,因为它贪婪地消费掉一切匹配的字符。
这样的STRING规则还不够完善,因为它不允许其中出现双引号。为了解决这个问题,很多语言都定义了以\开头的转义序列。
在这些语言中,如果希望在一个被双引号包围的字符串中使用双引号,我们就需要使用\"。下面规则能够支持常见的转义字符:
STRING: '"' (ESC|.)*? '"' ; fragment ESC: '\\"' | '\\\\' ; // 双字符序号\"和\\ANTLR语法本身需要对转义字符\进行转义,因此我们需要\\来表示单个反斜杠字符。
匹配注释和空白字符当词法分析器匹配到我们刚刚定义过的那些词法符号的时候,它会将匹配到的词法符号放入词法符号流,输送给语法分析器。之后,由语法分析器来检查词法符号流的语法结构。
但是,当词法分析器匹配到注释和空白字符的时候,我们通常希望将它们丢弃。这样,语法分析器就不必处理注释和空白字符了。
定义需要被丢弃的词法符号的方法和定义正常的词法符号的方法一样。我们只需要使用skip指令通知词法分析器将它们丢弃即可。
例如,下面匹配类Java语言中的单行和多行注释:
LINE_COMMENT : '//' .*? '\r'? '\n' -> skip ; // 匹配单行注释 COMMENT : '/*' .*? '*/' -> skip ; // 匹配多行注释词法分析器可以接受许多种位于->操作符之后的指令,skip只是其中之一。例如,我们能够使用channel指令将某些词法符号放入一个隐藏的通道并输送给语法分析器。