如何解析 ANTLR 中的部分日期?

How to parse a partial date in ANTLR?

我正在迈出使用 antlr4 的第一步,并尝试解析欧洲格式的部分日期 DD.MM.YYYY

我想识别像 15.05.20207.5.20 这样的正常日期,但也想识别仅包含月份和年份的日期,例如 05.20205.20,除此之外只包含一年以外的日期,例如 202020。在我的应用程序中,我想访问某个日期(日、月和年)的所有部分,其中某些部分可能是 empty/null.

这是我到目前为止的语法。

grammar LogicalDateExpressions;

stmt    :   date EOF
        ;

date    :   (YEAR)
        |   (MONTH DOT YEAR)
        |   (DAY DOT MONTH DOT YEAR)
        ;

YEAR    :   ([12] [0-9] [0-9] [0-9])
        |   ([0-9] [0-9])
        ;

MONTH   :   ('0'? [1-9])
        |   ('1' [012])
        ;

DAY     :   ('0'? [1-9])
        |   ([12][0-9])
        |   ('3'[01])
        ;

DOT     :   '.';
WS      :  [ \t\r\n\u000C]+ -> skip;

此语法适用于单个年份 (2020),但无法识别月-年组合 (05.2020)。 grun -tokens 告诉我以下内容。

[@0,0:1='05',<YEAR>,1:0]
[@1,2:2='.',<'.'>,1:2]
[@2,3:6='2020',<YEAR>,1:3]
[@3,9:8='<EOF>',<EOF>,2:0]
line 1:2 mismatched input '.' expecting <EOF>

因此,根据我的一知半解,我认为解析器规则 date 是问题所在,我将其重写为

date : (
          (DAY DOT)?      
          MONTH DOT     
       )?
       YEAR               
     ;

但我仍然遇到同样的错误。然后我想也许我需要重新排序词法分析器规则。所以我写的不是 YEAR -> MONTH -> DAY,而是 DAY -> MONTH -> YEAR。但是 grun 告诉我。

[@0,0:1='05',<DAY>,1:0]
[@1,2:2='.',<'.'>,1:2]
[@2,3:6='2020',<YEAR>,1:3]
[@3,9:8='<EOF>',<EOF>,2:0]
line 1:3 mismatched input '2020' expecting MONTH

我还尝试更改解析器规则 date 中 or'ed 替代项的顺序,但这也没有成功。然后我尝试更改 DAY、MONTH、YEAR 的词法分析器规则,使它们成为解析器规则(日、月、年)。在收到一些错误后,因为在解析器规则中显然不允许使用 [0-9] 符号,我将语法更改为此。

date    :   (year)
        |   (month DOT year)
        |   (day DOT month DOT year)
        ;

[...]
  
year    :   (('1'|'2') DIGIT DIGIT DIGIT)
        |   (DIGIT DIGIT)
        ;

month   :   ('0'? DIGIT_NO_ZERO)
        |   ('1' ('0'|'1'|'2'))
        ;

day     :   ('0'? DIGIT_NO_ZERO)
        |   (('1'|'2') DIGIT)
        |   ('3' ('0'|'1'))
        ;

[...]

DIGIT         :   [0-9];
DIGIT_NO_ZERO :   [1-9];

那也太可惜了。 grun 告诉我的。

[@0,0:0='0',<'0'>,1:0]
[@1,1:1='5',<DIGIT>,1:1]
[@2,2:2='.',<'.'>,1:2]
[@3,3:3='2',<'2'>,1:3]
[@4,4:4='0',<'0'>,1:4]
[@5,5:5='2',<'2'>,1:5]
[@6,6:6='0',<'0'>,1:6]
[@7,9:8='<EOF>',<EOF>,2:0]
line 1:1 no viable alternative at input '05'

据我所知,我正在寻找的语言是一种普通语言。每个输入都是明确的。所以我试图将整个“逻辑”放入词法分析器中,我成功地使用了以下语法。

grammar LogicalDateExpressions;

stmt :   date EOF
     ;

date :   DT
     ;

DT   :  (
            ((('0'? [1-9])|([12][0-9])|('3'[01])) DOT)? // Day
            (('0'? [1-9])|('1' [012])) DOT              // Month
        )?
        ((DIGIT DIGIT DIGIT DIGIT)|(DIGIT DIGIT))       // Year
    ;

DIGIT   :   [0-9];
DOT     :   '.';
WS      :  [ \t\r\n\u000C]+ -> skip;

它解析我给它的每一个输入。但问题是每个输入都只是一个 DT.

[@0,0:6='05.2020',<DT>,1:0]
[@1,9:8='<EOF>',<EOF>,2:0]

我无法区分 visitor/listener 中的日、月和年,因为词法分析器规则中不允许使用标签。


所以我的问题是第一个给定语法的问题在哪里,我需要更改什么才能使其正常工作?

从 grun 的令牌输出来看,我想我可能会抓住一天、一个月的每个输入的问题 and/or 年可能是模棱两可的,但作为一个整体输入连同点它不应该是。我怎么能告诉 antlr?

So my question is where is the problem with the first given grammar and what do I need to change to make it work?

问题是词法分析器不是由解析器驱动的。这意味着当解析器尝试匹配标记 DAY DOT MONTH 并且输入是 01.01 时,词法分析器不会为这两个 DAYMONTH 创建 01's,而是两个 MONTH 标记。这就是 ANTLR 的词法分析器的工作方式:尝试为一个标记抓取尽可能多的字符,并且当有 2 个或更多标记匹配相同字符时(比如 01 可以被 DAYMONTH),让令牌首先定义为“win”(即 MONTH 令牌)。没有办法解决这个问题。

你可以做的是这样的(未经测试):

date
 : year
 | month DOT year
 | day DOT month DOT year
 ;

day
 : N_01_12
 | N_13_31
 ;

month
 : N_01_12
 ;

year
 : N_01_12
 | N_13_31
 | N_32_99
 | N_1000_2999
 ;

N_01_12
 : '0'? D    // 01-09
 | '1' [0-2] // 10-12
 ;

N_13_31
 : '1' [3-9] // 13-19
 | '2' D     // 20-29
 | '3' [01]  // 30-31
 ;

N_32_99
 : '3' [2-9] // 32-39
 | [4-9] D   // 40-99
 ;

N_1000_2999
 : [12] D D D // 1000-2999
 ;

fragment D : [0-9];