乐者为王

Do one thing, and do it well.

使用ANTLR构建PowerScript语法分析器(1)

首先,让我们从最简单的做起,先实现一套能正确分析PowerScript注释的词法规则。PowerScript支持两种注释形式:单行注释(//)和多行注释(/* ... */)。

单行注释以双斜杠(//)起头,直到遇到行结束符为止,所以在这里要先明确一下行结束符用什么表示。在DOS系统中行结束符用“\r\n”表示,在Unix系统中行结束符用“\n”表示,现在的Mac系统也是以“\n”表示行结束符,但早期的Mac系统中行结束符则是以“\r”表示。所以兼容这几种操作系统的行结束符词法规则为:

1
2
3
4
5
6
fragment
EndOfLine
    : '\r' '\n'    // DOS
    | '\r'         // Mac
    | '\n'         // Unix
    ;

现在,有了行结束符词法规则,就可以定义单行注释的词法规则了:

1
2
3
LINE_COMMENT
    : '//' ~('\n'|'\r')* EndOfLine
    ;

通常的多行注释词法规则是这样的:

1
2
3
BLOCK_COMMENT
    : '/*' ( options {greedy=false;} : . )* '*/'
    ;

但由于PowerScript支持嵌套注释,所以上面的规则无效,必须重新定义。当词法分析器遇到“/*”后就进入了多行注释。这时假如遇到了“/”符号,词法分析器必须预取一个字符,看这个字符是不是“*”。如果是的话,就进入了嵌套的多行注释,否则,则还是处在多行注释中。

1
2
3
4
5
6
7
8
9
BLOCK_COMMENT
    : '/*'
      ( options {greedy=false;}
      : ('/' '*')=> BLOCK_COMMENT    // 在多行注释中遇到“/*”,用语法断言确定是嵌套多行注释
      | '/' ~('*')                   // 在多行注释中只遇到“/”,而没有紧跟着的“*”
      | ~('/')                       // 在多行注释中没有遇到“/”
      )*
      '*/'
    ;

当然,我们还希望忽略空格、制表符、回车符、换行符等无意义字符。

1
2
3
WS
    : (' '|'\t'|'\r'|'\n') {$channel=HIDDEN;}
    ;

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* This is a comment.   */
/* /This is a comment. */
/* *This is a comment. */
/* This is a comment. /* */*/
/* This is a comment. /* This is a nested comment. */ */
/*  The comment starts here.
 /*  The nested comment starts here.
 The nested comment ends here.  */
 The comment ends here.  */
/* The comment starts here,
 continues to this line,&
 and finally ends here. */
// This entire line is a comment.
// This entire line is a comment. //
// /This entire line is a comment.
// This entire line is a comment. */
// This entire line is a comment. /*

完整的PowerScript.g的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
grammar PowerScript;

program
    : LINE_COMMENT+
    | BLOCK_COMMENT+
    ;

LINE_COMMENT
    : '//' ~('\n'|'\r')* EndOfLine
      {
          System.out.println("lc>" + getText());
      }
    ;

BLOCK_COMMENT
    : '/*'
      ( options {greedy=false;}
      : ('/' '*')=> BLOCK_COMMENT
      | '/' ~('*')
      | ~('/')
      )*
      '*/'
      {
          System.out.println("bc>" + getText());
      }
    ;

WS
    : (' '|'\t'|'\r'|'\n') {$channel=HIDDEN;}
    ;

fragment
EndOfLine
    : '\r' '\n'    // DOS
    | '\r'         // Mac
    | '\n'         // Unix
    ;

Comments