ANTLR 4.1 Переменная ANTLR 4 множественности токенов приводит к ошибке: "замыкание хотя бы с одной альтернативой, которая может соответствовать пустой строке"
По сути, я пытаюсь создать грамматику для интернационализированных идентификаторов ресурсов в ANTLR 4.1. Самое трудное время, которое у меня было до сих пор, - попытаться заставить правильно работать производственное правило для ipv6address. Способ определения ipv6-адреса в RFC 3987 заключается в том, что в основном в формате ABNF существует 9 различных альтернатив для этого производственного правила:
IPv6address = 6( h16 ":" ) ls32
/ "::" 5( h16 ":" ) ls32
/ [ h16 ] "::" 4( h16 ":" ) ls32
/ [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
/ [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
/ [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
/ [ *4( h16 ":" ) h16 ] "::" ls32
/ [ *5( h16 ":" ) h16 ] "::" h16
/ [ *6( h16 ":" ) h16 ] "::"
Здесь, ls32 и h16 - оба подправила, определенные как:
ls32 = ( h16 ":" h16 ) / IPv4address
И как таковой для h16:
h16 = 1*4HEXDIG
Где HEXDIG - это правило лексера для действительных шестнадцатеричных цифр. Я пытался написать эту грамматику ABNF с синтаксисом ANTLR следующим образом:
grammar IRI;
iri : scheme ':' ihier_part ('?' iquery)? ('#' ifragment)? ;
ihier_part : ('//' iauthority ipath_abempty
| ipath_absolute
| ipath_rootless)?
;
iri_reference : iri
| irelative_ref
;
absolute_IRI : scheme ':' ihier_part ('?' iquery)? ;
irelative_ref : irelative_part ('?' iquery)? ('#' ifragment)? ;
irelative_part : ('//' iauthority ipath_abempty
| ipath_absolute
| ipath_noscheme)?
;
iauthority : (iuserinfo '@')? ihost (':' port)? ;
iuserinfo : (iunreserved | pct_encoded | sub_delims | ':')* ;
ihost : ip_literal
| ipv4address
| ireg_name
;
ireg_name : (iunreserved | pct_encoded | sub_delims)* ;
ipath : (ipath_abempty
| ipath_absolute
| ipath_noscheme
| ipath_rootless)?
;
ipath_abempty : ('/' isegment)* ;
ipath_absolute : '/' (isegment_nz ('/' isegment)*)? ;
ipath_noscheme : isegment_nz_nc ('/' isegment)* ;
ipath_rootless : isegment_nz ('/' isegment)* ;
isegment : (ipchar)* ;
isegment_nz : (ipchar)+ ;
isegment_nz_nc : (iunreserved | pct_encoded | sub_delims | '@')+ ;
ipchar : iunreserved
| pct_encoded
| sub_delims
| ':'
| '@'
;
iquery : (ipchar | IPRIVATE | '/' | '?')* ;
ifragment : (ipchar | '/' | '?')* ;
iunreserved : ALPHA
| DIGIT
| '-'
| '.'
| '_'
| '~'
| UCSCHAR
;
fragment
UCSCHAR : '\u00A0'..'\uD7FF' | '\uF900'..'\uFDCF' | '\uFDF0'..'\uFFEF'
| '\u40000'..'\u4FFFD' | '\u50000'..'\u5FFFD' | '\u60000'..'\u6FFFD'
| '\u70000'..'\u7FFFD' | '\u80000'..'\u8FFFD' | '\u90000'..'\u9FFFD'
| '\uA0000'..'\uAFFFD' | '\uB0000'..'\uBFFFD' | '\uC0000'..'\uCFFFD'
| '\uD0000'..'\uDFFFD' | '\uE1000'..'\uEFFFD'
;
fragment
IPRIVATE : '\uE000'..'\uF8FF' | '\uF0000'..'\uFFFFD' | '\u100000'..'\u10FFFD' ;
scheme : ALPHA (ALPHA | DIGIT | '+' | '-' | '.')* ;
port : (DIGIT)* ;
ip_literal : '[' (ipv6address | ipvFuture) ']' ;
ipvFuture : 'v' (HEXDIG)+ '.' (unreserved | sub_delims | ':')+ ;
ipv6address
locals [int i1, i2, i3, i4, i5, i6, i7, i8, i9, i10 = 0;]
: ( {$i1<=6}? h16 ':' {$i1++;} )* ls32
| '::' ( {$i2<=5}? h16 ':' {$i2++;} )* ls32
| (h16)? '::' ( {$i3<=4}? h16 ':' {$i3++;} )* ls32
| ((h16 ':')? h16)? '::' ( {$i4<=3}? h16 ':'{$i4++;} )* ls32
| (( {$i5>=0 && $i5<=2}? h16 ':' {$i5++;} )* h16)? '::' ( {$i6<=2}? h16 ':' {$i6++;} )* ls32
| (( {$i7>=0 && $i7<=3}? h16 ':' {$i7++;} )* h16)? '::' h16 ':' ls32
| (( {$i8>=0 && $i8<=4}? h16 ':' {$i8++;} )* h16)? '::' ls32
| (( {$i9>=0 && $i9<=5}? h16 ':' {$i9++;} )* h16)? '::' h16
| (( {$i10>=0 && $i10<=6}? h16 ':' {$i10++;} )* h16)* '::'
;
h16
locals [int i = 1;]
: ( {$i>=1 && $i<=4}? HEXDIG {$i++;} )* ;
ls32 : h16 ':' h16 ;
ipv4address : DEC_OCTET '.' DEC_OCTET '.' DEC_OCTET '.' DEC_OCTET ;
DEC_OCTET : '0'..'9'
| '10'..'99'
| '100'..'199'
| '200'..'249'
| '250'..'255'
;
pct_encoded : '%' HEXDIG HEXDIG ;
unreserved : ALPHA | DIGIT | '-' | '.' | '_' | '~' ;
reserved : gen_delims
| sub_delims
;
gen_delims : ':' | '/' | '?' | '#' | '[' | ']' | '@' ;
sub_delims : '!' | '$' | '&' | '\'' | '(' | ')' ;
DIGIT : [0-9] ;
HEXDIG : [0-9A-F] ;
ALPHA : [a-zA-Z] ;
WS : [' ' | '\t' | '\r' | '\n']+ -> skip ;
В моей грамматике ANTLR я пытаюсь использовать семантические предикаты, чтобы указать правила множественности, определенные в грамматике ABNF, как для ipv6address, так и для h16. Когда я выполняю класс org.antlr.v4.Tool, я получаю следующий вывод:
warning(125): IRI.g4:68:20: implicit definition of token 'IPRIVATE' in parser
warning(125): IRI.g4:78:4: implicit definition of token 'UCSCHAR' in parser
error(153): IRI.g4:100:0: rule 'ipv6address' contains a closure with at least one alternative that can match an empty string
warning(154): IRI.g4:40:0: rule 'ipath' contains an optional block with at least one alternative that can match an empty string
warning(154): IRI.g4:100:0: rule 'ipv6address' contains an optional block with at least one alternative that can match an empty string
warning(154): IRI.g4:100:0: rule 'ipv6address' contains an optional block with at least one alternative that can match an empty string
warning(154): IRI.g4:100:0: rule 'ipv6address' contains an optional block with at least one alternative that can match an empty string
warning(154): IRI.g4:100:0: rule 'ipv6address' contains an optional block with at least one alternative that can match an empty string
warning(154): IRI.g4:100:0: rule 'ipv6address' contains an optional block with at least one alternative that can match an empty string
warning(154): IRI.g4:100:0: rule 'ipv6address' contains an optional block with at least one alternative that can match an empty string
Очевидно, я хотел бы также избавиться от предупреждений, но мне нужно избавиться от ошибки, в которой говорится, что "ipv6address" содержит замыкание, по крайней мере, с одной альтернативой, которая может соответствовать пустой строке. Я видел похожие посты в Stackru о множественных ошибках ошибок. Однако ни один из них не имел дело с замыканиями, которые могли бы соответствовать пустой строке. Я также почти уверен, что мне придется определить символы Юникода в UCSCHAR прошлом \uFFFF как суррогатные пары, но об этом я позабочусь позже. Просто нужно знать, как избавиться от проблемы закрытия на данный момент.
2 ответа
Есть некоторые вещи, которые идут не так, как надо:
0
Что сказал 280Z28.
1
'250'..'255'
не соответствует строкам "250"
... "255"
: вы должны соответствовать числовым диапазонам, как описано в оригинальных спецификациях ABNF:
ABNF
dec-octet = DIGIT ; 0-9
/ %x31-39 DIGIT ; 10-99
/ "1" 2DIGIT ; 100-199
/ "2" %x30-34 DIGIT ; 200-249
/ "25" %x30-35 ; 250-255
ANTLR
dec_octet
: digit
| non_zero_digit digit
| D1 digit digit
| ...
;
2
У вас много противоречивых правил лексера. Возьмите это, например:
HEXDIG : [0-9A-F] ;
ALPHA : [a-zA-Z] ;
так как HEXDIG
определяется раньше ALPHA
Лексер всегда будет создавать HEXDIG
когда он видит 'A'
, например. Вы должны понимать, что лексер не производит токены на основе того, что парсер хотел бы получить. Лексер пойдет своим путем и никогда не произведет ALPHA
для заглавных букв A-F
,
3
fragment
правила могут использоваться только внутри других правил лексера (или других fragment
правила). Вы не можете использовать их в правилах парсера.
4
На самом деле это не проблема, но предикаты затрудняют чтение вашей грамматики: по возможности старайтесь минимизировать предикаты - это мое эмпирическое правило.
Ваше правило:
h16
locals [int i = 1;]
: ( {$i>=1 && $i<=4}? HEXDIG {$i++;} )* ;
может быть написано как:
h16
: HEXDIG HEXDIG HEXDIG HEXDIG
| HEXDIG HEXDIG HEXDIG
| HEXDIG HEXDIG
| HEXDIG
;
или даже:
h16
: HEXDIG (HEXDIG (HEXDIG HEXDIG?)?)?
;
Большинство из этих проблем легко решаются, но № 2 более сложный. Что вы могли бы (должны?) Сделать, это позволить лексеру создать токены с одним символом и позволить анализатору сопоставить эти токены с одним символом в единое целое. Пример того, как вы можете позволить парсеру соответствовать dec-octet
продукция от официального ABNF:
dec_octet
: digit // 0-9
| non_zero_digit digit // 10-99
| D1 digit digit // 100-199
| D2 (D0 | D1 | D2 | D3 | D4) digit // 200-249
| D2 D5 (D0 | D1 | D2 | D3 | D4 | D5) // 250-255
;
digit
: D0
| non_zero_digit
;
non_zero_digit
: D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9
;
// lexer rules
D0 : '0';
D1 : '1';
D2 : '2';
D3 : '3';
D4 : '4';
D5 : '5';
D6 : '6';
D7 : '7';
D8 : '8';
D9 : '9';
Однажды я написал грамматику IRI для ANTLR 3. Если хотите, я мог бы поместить ее где-нибудь в Github.
Ваш h16
Правило использует (...)*
вместо (...)+
, что позволяет ему соответствовать 0 цифр. Когда вы размещаете h16*
в вашей грамматике это означает, что вы разрешаете любое количество пустяков в вашем дереве разбора, что всегда приводит к бесконечному циклу, запускающему вашу систему из памяти (создавая узлы дерева разбора без токенов).