在 antlr4.7 中,如何在 "ID" 规则之前解析像 ISO 8601 间隔"P3M2D"这样的规则



我正在尝试使用antlr4来解析ISO 8601时期表达式,例如" P3M2D"。但是我遇到了某种障碍,并感谢您的帮助。我对Antlr和编译器都是新手。

我的语法如下。我已经将Lexer和解析器规则结合在一起,请转到这里:

grammar test_iso ;
// import testLexerRules ;
iso : ( date_expr NEWLINE)* EOF;
date_expr
    :   date_expr op=( '+' | '-' ) iso8601_interval #dateexpr_Interval
    |   date_expr op='-' date_expr                  #dateexpr_Diff
    |   DATETIME_NAME                               #dateexpr_Named
    |   '(' inner=date_expr ')'                     #dateexpr_Paren
    ;
///////////////////////////////////////////
iso8601_interval
    :   iso8601_interval_d
        { System.out.println("ISO8601_INTERVAL DATE seen " + $text);}
    ;
iso8601_interval_d
    :   'P' ( y=NUMBER_INT 'Y' )? ( m=NUMBER_INT 'M' )? ( w=NUMBER_INT 'W' )? ( d=NUMBER_INT 'D' )?
    ;
///////////////////////////////////////////
// in separate file : test_lexer.g4
// lexer grammar testLexerRules ;
///////////////////////////////////////////
fragment
TODAY 
    :   'today' | 'TODAY' 
    ;
fragment
NOW 
    :   'now' | 'NOW' 
    ;
DATETIME_NAME
    :   TODAY
    |   NOW
    ;
///////////////////////////////////////////
NUMBER_INT
    :   '-'? INT                    // -3, 45
    ;
fragment
DIGIT :     [0-9] ;
fragment
INT :       '0' | [1-9] DIGIT* ;
//////////////////////////////////////////////
//
// identifiers
//
ID 
    :   ALPHA ALPH_NUM* 
    { System.out.println("ID seen " + getText()); }
    ;
ID_SQLFUNC
    :   'h$' ALPHA_UPPER ALPHA_UPPER_NUM*
    { System.out.println("SQL FUNC seen " + getText()); }
    ;
fragment
ALPHA :    [a-zA-Z] ;
fragment
ALPH_NUM : [a-zA-Z_0-9] ;
fragment
ALPHA_UPPER :    [A-Z] ;
fragment
ALPHA_UPPER_NUM : [A-Z_0-9] ;
//////////////////////////////////////////////
NEWLINE : 'rn' ;
WS  :  [ t]+ -> skip  ;

在测试运行中,它永远不会达到iso8601_interval_d规则,它总是进入ID规则。

C:lab>java org.antlr.v4.gui.TestRig test_iso iso -tokens -tree
now + P3M2D
^Z
ID seen P3M2D
[@0,0:2='now',<DATETIME_NAME>,1:0]
[@1,4:4='+',<'+'>,1:4]
[@2,6:10='P3M2D',<ID>,1:6]
[@3,11:12='rn',<'
'>,1:11]
[@4,13:12='<EOF>',<EOF>,2:0]
line 1:6 mismatched input 'P3M2D' expecting 'P'
ISO8601_INTERVAL DATE seen P3M2D
(iso (date_expr (date_expr now) + (iso8601_interval (iso8601_interval_d P3M2D))) rn <EOF>)

如果我删除" ID"规则并再次运行,则根据需要解析:

now + P3M2D
^Z
[@0,0:2='now',<DATETIME_NAME>,1:0]
[@1,4:4='+',<'+'>,1:4]
[@2,6:6='P',<'P'>,1:6]
[@3,7:7='3',<NUMBER_INT>,1:7]
[@4,8:8='M',<'M'>,1:8]
[@5,9:9='2',<NUMBER_INT>,1:9]
[@6,10:10='D',<'D'>,1:10]
[@7,11:12='rn',<'
'>,1:11]
[@8,13:12='<EOF>',<EOF>,2:0]
ISO8601_INTERVAL DATE seen P3M2D
(iso (date_expr (date_expr now) + (iso8601_interval (iso8601_interval_d P 3 M 2 D))) rn <EOF>)

我还尝试在解析器规则中的特殊字符前缀"@"

iso8601_interval_d
    :   '@P' ( y=NUMBER_INT 'Y' )? ( m=NUMBER_INT 'M' )? ( w=NUMBER_INT 'W' )? ( d=NUMBER_INT 'D' )?
    ;

但是现在是另一种失败

now + @P3M2D
^Z
ID seen M2D
[@0,0:2='now',<DATETIME_NAME>,1:0]
[@1,4:4='+',<'+'>,1:4]
[@2,6:7='@P',<'@P'>,1:6]
[@3,8:8='3',<NUMBER_INT>,1:8]
[@4,9:11='M2D',<ID>,1:9]
[@5,12:13='rn',<'
'>,1:12]
[@6,14:13='<EOF>',<EOF>,2:0]
line 1:9 no viable alternative at input '3M2D'
ISO8601_INTERVAL DATE seen @P3M2D
(iso (date_expr (date_expr now) + (iso8601_interval (iso8601_interval_d @P 3 M2D))) rn <EOF>)

我确定我不是第一个击中这样的事情的人。这里的antlr成语是什么?

编辑 - 我需要在这里省略的语法其他部分的其他地方的ID令牌,以突出我面临的问题。

也喜欢发现其他问题,问题在ID令牌中。事实是,ISO-8601的持续时间语法是有效的ID。除了@mike弄清楚的解决方案。如果所谓的岛语法适合您的需求,则可以在解析ISO日期时使用Antlr的词汇模式排除ID LEXER规则。Belove有一个示例有关它如何工作的示例

parser grammar iso;
options { tokenVocab=iso_lexer; }
iso : ISO_BEGIN ( date_expr NEWLINE)* ISO_END;
date_expr
    :   date_expr op=( '+' | '-' ) iso8601_interval #dateexpr_Interval
    |   date_expr op='-' date_expr                  #dateexpr_Diff
    |   DATETIME_NAME                               #dateexpr_Named
    |   '(' inner=date_expr ')'                     #dateexpr_Paren
    ;
///////////////////////////////////////////
iso8601_interval
    :   iso8601_interval_d
        { System.out.println("ISO8601_INTERVAL DATE seen " + $text);}
    ;
iso8601_interval_d
    :   'P' ( y=NUMBER_INT 'Y' )? ( m=NUMBER_INT 'M' )? ( w=NUMBER_INT 'W' )? ( d=NUMBER_INT 'D' )?
    ;

然后在lexer

lexer grammar iso_lexer;
//
// identifiers (in DEFAULT_MODE)
//
ISO_BEGIN
    : '<@' -> mode(ISO)
    ;
ID
    :   ALPHA ALPH_NUM*
    { System.out.println("ID seen " + getText()); }
    ;
ID_SQLFUNC
    :   'h$' ALPHA_UPPER ALPHA_UPPER_NUM*
    { System.out.println("SQL FUNC seen " + getText()); }
    ;
WS0  :  [ t]+ -> skip  ;
// all the following token are scanned only when iso mode is active
mode ISO;
ISO_END
    : '@>' -> mode(DEFAULT_MODE)
    ;
WS0  :  [ t]+ -> skip  ;
NEWLINE : 'r'? 'n' ;

ADD : '+' ;
SUB : '-' ;
LPAREN : '(' ;
RPAREN : ')' ;
P : 'P' ;
Y : 'Y' ;
M : 'M' ;
W : 'W' ;
D : 'D' ;
DATETIME_NAME
    :   TODAY
    |   NOW
    ;
fragment TODAY:   'today' | 'TODAY'    ;
fragment NOW :   'now' | 'NOW' ;

///////////////////////////////////////////
NUMBER_INT
    :   '-'? INT                    // -3, 45
    ;
fragment DIGIT :     [0-9] ;
fragment INT :       '0' | [1-9] DIGIT* ;
//////////////////////////////////////////////
fragment ALPHA :    [a-zA-Z] ;
fragment ALPH_NUM : [a-zA-Z_0-9] ;
fragment ALPHA_UPPER :    [A-Z] ;
fragment ALPHA_UPPER_NUM : [A-Z_0-9] ;

这样的语法可以解析

的表达方式
Pluton Planet <% now + P10Y
%>

我更改了解析器规则iso以演示ID和周期混合。希望这有帮助

不可能做什么。ID匹配与iso8601_interval相同的输入。在这种情况下,Antlr4选择最长的匹配,即ID,因为它可以匹配无限数量的字符。

唯一可能使其在语法中起作用的方法是将P排除为可能的ID介绍器,然后可以在持续时间内专门使用。

另一个选项是后处理步骤。像其他任何标识符一样解析持续时间,在您的语义阶段,请检查所有看起来像持续时间的ID。这可能是最好的解决方案。

相关内容

  • 没有找到相关文章

最新更新