在 antlr4.7 中如何在 "ID" 规则之前解析像 ISO 8601 间隔 "P3M2D" 这样的规则
In antlr4.7 how to parse a rule like ISO 8601 interval "P3M2D" ahead of an "ID" rule
我正在尝试使用 antlr4
像 "P3M2D" 一样解析 ISO 8601 period expressions。但我遇到了某种障碍,希望得到帮助。我对 antlr 和编译器都很陌生。
我的语法如下。我在这里将词法分析器和解析器规则结合在一起:
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 : '\r\n' ;
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='\r\n',<'
'>,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))) \r\n <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='\r\n',<'
'>,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))) \r\n <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='\r\n',<'
'>,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))) \r\n <EOF>)
我相信我不是第一个想到这样的人。这里的antlr成语是什么?
编辑——我在我语法的其他部分的其他地方需要 ID
标记,我在这里省略了,以突出我面临的问题。
你想做什么是不可能的。 ID
匹配与 iso8601_interval
相同的输入。在这种情况下,ANTLR4 会选择最长的匹配项,即 ID
,因为它可以匹配无限数量的字符。
使它在语法中起作用的唯一方法是将 P
排除在可能的 ID
介绍人之外,然后可以在整个持续时间内独占使用。
另一个选项是 post 处理步骤。像任何其他标识符一样解析持续时间,并在语义阶段检查所有看起来像持续时间的 ID。这可能是最好的解决方案。
就像其他人一样,问题出在 ID 令牌中。事实上,iso-8601 的持续时间语法是一个有效的 ID。除了@Mike 想出的解决方案。如果在解析 iso 日期时调用 island grammar is suitable for your needs you can use ANTLR's lexical modes 到 排除 ID 词法分析器规则。
亲爱的,有一个关于它如何工作的例子
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 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 和句点混合。
希望这有帮助
我正在尝试使用 antlr4
像 "P3M2D" 一样解析 ISO 8601 period expressions。但我遇到了某种障碍,希望得到帮助。我对 antlr 和编译器都很陌生。
我的语法如下。我在这里将词法分析器和解析器规则结合在一起:
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 : '\r\n' ;
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='\r\n',<'
'>,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))) \r\n <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='\r\n',<'
'>,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))) \r\n <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='\r\n',<'
'>,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))) \r\n <EOF>)
我相信我不是第一个想到这样的人。这里的antlr成语是什么?
编辑——我在我语法的其他部分的其他地方需要 ID
标记,我在这里省略了,以突出我面临的问题。
你想做什么是不可能的。 ID
匹配与 iso8601_interval
相同的输入。在这种情况下,ANTLR4 会选择最长的匹配项,即 ID
,因为它可以匹配无限数量的字符。
使它在语法中起作用的唯一方法是将 P
排除在可能的 ID
介绍人之外,然后可以在整个持续时间内独占使用。
另一个选项是 post 处理步骤。像任何其他标识符一样解析持续时间,并在语义阶段检查所有看起来像持续时间的 ID。这可能是最好的解决方案。
就像其他人一样,问题出在 ID 令牌中。事实上,iso-8601 的持续时间语法是一个有效的 ID。除了@Mike 想出的解决方案。如果在解析 iso 日期时调用 island grammar is suitable for your needs you can use ANTLR's lexical modes 到 排除 ID 词法分析器规则。 亲爱的,有一个关于它如何工作的例子
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 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 和句点混合。
希望这有帮助