ANTLR 4词法规则

1 词法规则

词法由词法规则组成,分为多个词法模式。

词法解析只能返回当前模式匹配规则的记号。

词法解析定义与语法解析定义相似,不同的是不能有入参、返回值和本地变量,词法名称必须大写字母开头。

如:

1
2
/** Optional document comment */
TokenName : alternative1 | ... | alternativeN ;

可以定义用于辅助识别记号的规则,如:

1
2
fragment
HelperTokenRule : alternative1 | ... | alternativeN ;
1
2
3
# DIGIT是辅助识别数值的规则
INT : DIGIT+ ; // references the DIGIT helper rule
fragment DIGIT : [0-9] ; // not a token by itself

2 词法模式

模式用于分组规则。

词法解析器只能返回当前模式匹配规则的记号。

在没有指定模式时,规则属于默认模式。

定义如下:

1
2
3
4
5
6
7
8
rules in default mode
...
mode MODE1;
rules in MODE1
...
mode MODEN;
rules in MODEN
...

示例详见Tokenizing XML

3 词法规则元素

有两种结构,且在语法解析器中不可用。..范围操作符和[characters]字符集。

注意不要混淆字符集和语法解析器中的参数。

所有元素如下:

Syntax Description
T 记号
’literal’ 字面量
[char set] 字符。支持范围、特殊字符、转义、Unicode和长短Unicode属性(不区分大小写)
’x’..’y’ 范围
T 词法规则。支持递归,但不是左侧递归。
. 任意字符
{«action»} 词法动作。4.2版本中可以在任意位置。对所有选项执行的动作可以使用圆括号包含所有选项,随后附带动作,如`END : (‘endif’ ‘end’) {System.out.println(“found an end”);} ; `。动作代码逐字符拷贝到目标语言,不会像语法解析器一样解释如\$x.y。注意只有在最外侧规则以内的动作才会被执行。
{«p»}? 评估语义谓词predicate。false时周围的规则不可见。谓词需要在词法动作之前,因此最好放在规则最后。
~x 排除字符

和语法规则一样,词法规则允许子规则和EBNF操作符:?、*、+等,以及非贪婪模式符号?

4 递归词法规则

允许递归,如用于匹配嵌套记号:

1
2
3
4
5
lexer grammar Recur;

ACTION : '{' ( ACTION | ~[{}] )* '}' ;

WS : [ \r\t\n]+ -> skip ;

5 冗余字符串

注意不要为同一字符串定义多个记号,否则导致词法错误。

1
2
3
4
lexer grammar L;
AND : '&' ;
mode STR;
MASK : '&' ;
1
2
3
4
5
6
parser grammar P;
options { tokenVocab=L; }
a : '&' // results in a tool error: no such token
AND // no problem
MASK // no problem
;
1
2
3
$ antlr4 L.g4 # yields L.tokens file needed by tokenVocab option in P.g4
$ antlr4 P.g4
error(126): P.g4:3:4: cannot create implicit token for string literal '&' in non-combined grammar

6 词法规则动作

词法规则动作用于设置记号对象的状态。

每个对记号的请求都从Lexer.nextToken开始,一旦被识别就调用emit方法。emit收集信息以构建记号对象,如_type、_text、_channel、_tokenStartCharIndex、_tokenStartLine和_tokenStartCharPositionInLine。可以在动作中设置字段,如:

1
ENUM : 'enum' {if (!enumIsKeyword) setType(Identifier);} ;

注意版本4不会在词法解析器中翻译$x属性。

每个词法规则最多有一个动作。

7 词法命令

为了避免与特定语言绑定,词法解析器支持词法命令。

与内嵌动作不同,命令遵循特殊的语法,且限定了种类。

词法命令出现在规则后面、最外面。

与其他动作相似,一个记号规则只能有一个。

命令使用如下,可以带有参数:

1
2
TokenName : «alternative» -> command-name
TokenName : «alternative» -> command-name («identifier or integer»)

一个选项可以有多个逗号分隔的命令。

常见命令如下:

(1) skip

抛弃当前文本,继续下一个记号。

1
WS : [ \t]+ -> skip ; // toss out whitespace

(2) mode(), pushMode(), popMode, and more

mode命令改变模式栈。在栈底弹出将抛出异常。

more命令继续下一个记号,但不抛弃当前文本。多个more等效于一个,且顺序无关。

以下为模式定义:

1
2
3
4
5
6
7
8
9
10
11
12
// Default "mode": Everything OUTSIDE of a tag
COMMENT : '<!--' .*? '-->' ;
CDATA : '<![CDATA[' .*? ']]>' ;
OPEN : '<' -> pushMode(INSIDE) ;
...
XMLDeclOpen : '<?xml' S -> pushMode(INSIDE) ;
SPECIAL_OPEN: '<?' Name -> more, pushMode(PROC_INSTR) ;
// ----------------- Everything INSIDE of a tag ---------------------
mode INSIDE;
CLOSE : '>' -> popMode ;
SPECIAL_CLOSE: '?>' -> popMode ; // close <?xml...?>
SLASH_CLOSE : '/>' -> popMode ;

以下为命令使用:

1
2
3
4
5
6
lexer grammar Strings;
LQUOTE : '"' -> more, mode(STR) ;
WS : [ \r\t\n]+ -> skip ;
mode STR;
STRING : '"' -> mode(DEFAULT_MODE) ; // token we want parser to see
TEXT : . -> more ; // collect more text for string

(3) type()

多个type时,只有最右侧的生效。

1
2
3
4
5
lexer grammar SetType;
tokens { STRING }
DOUBLE : '"' .*? '"' -> type(STRING) ;
SINGLE : '\'' .*? '\'' -> type(STRING) ;
WS : [ \r\t\n]+ -> skip ;

(4) channel()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BLOCK_COMMENT
: '/*' .*? '*/' -> channel(HIDDEN)
;
LINE_COMMENT
: '//' ~[\r\n]* -> channel(HIDDEN)
;
...
// ----------
// Whitespace
//
// Characters and character constructs that are of no import
// to the parser and are used to make the grammar easier to read
// for humans.
//
WS : [ \t\r\n\f]+ -> channel(HIDDEN) ;

可以在词法规则前,像枚举值一样定义:

1
channels { WSCHANNEL, MYHIDDEN }

参考资料