我设计了一个相当简单的语法来解析uri。它是在antlr4-maven-plugin
的帮助下编译的。编译不会产生警告或错误。我写了一个简单的测试。
Uri.g4
:
/**
* Uniform Resource Identifier (RFC 3986).
*
* @author Oliver Yasuna
* @see <a href="https://www.rfc-editor.org/rfc/rfc3986.html">RFC 3986</a>
* @since 1.0.0
*/
grammar Uri;
options {
tokenVocab = Common;
}
@header {
package com.oliveryasuna.http.antlr;
}
// Parser
//--------------------------------------------------
pctEncoded
: '%' HEXDIG HEXDIG
;
reserved
: genDelims | subDelims
;
genDelims
: ':' | '/' | '?' | '#' | '[' | ']' | '@'
;
subDelims
: '!' | '$' | '&' | ''' | '(' | ')' | '*' | '+' | ',' | ';' | '='
;
unreserved
: ALPHA | DIGIT | '-' | '.' | '_' | '~'
;
uri
: scheme ':' hierPart ('?' query)? ('#' fragment_)?
;
hierPart
: '//' authority pathAbEmpty
| pathAbsolute
| pathRootless
| pathEmpty
;
scheme
: ALPHA (ALPHA | DIGIT | '+' | '-' | '.')*
;
authority
: (userinfo '@')? host (':' port)?
;
userinfo
: (unreserved | pctEncoded | subDelims | ':')*
;
host
: ipLiteral
| ipv4Address
| regName
;
ipLiteral
: '[' (ipv6Address | ipvFuture) ']'
;
ipvFuture
: 'v' HEXDIG+ '.' (unreserved | subDelims | ':')+
;
ipv6Address
: '::' (h16 ':') (h16 ':') (h16 ':') (h16 ':') (h16 ':') (h16 ':') ls32
| '::' (h16 ':') (h16 ':') (h16 ':') (h16 ':') (h16 ':') ls32
| h16? '::' (h16 ':') (h16 ':') (h16 ':') (h16 ':') ls32
| ((h16 ':')? h16)? '::' (h16 ':') (h16 ':') (h16 ':') ls32
| ((h16 ':')? (h16 ':')? h16)? '::' (h16 ':') (h16 ':') ls32
| ((h16 ':')? (h16 ':')? (h16 ':')? h16)? '::' h16 ':' ls32
| ((h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? h16)? '::' ls32
| ((h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? h16)? '::' h16
| ((h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? h16)? '::'
;
ls32
: (h16 ':' h16)
| ipv4Address
;
h16
: HEXDIG HEXDIG? HEXDIG? HEXDIG?
;
ipv4Address
: decOctet '.' decOctet '.' decOctet '.' decOctet
;
decOctet
: DIGIT
| ('1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9') DIGIT
| '1' DIGIT DIGIT
| '2' ('0' | '1' | '2' | '3' | '4') DIGIT
| '2' '5' ('0' | '1' | '2' | '3' | '4' | '5')
;
regName
: (unreserved | pctEncoded | subDelims)*
;
port
: DIGIT*
;
path
: pathAbEmpty
| pathAbsolute
| pathNoScheme
| pathRootless
| pathEmpty
;
pathAbEmpty
: ('/' segment)*
;
pathAbsolute
: '/' (segmentNz ('/' segment)?)?
;
pathNoScheme
: segmentNzNc ('/' segment)?
;
pathRootless
: segmentNz ('/' segment)?
;
pathEmpty
: // TODO: 0<pchar>.
;
segment
: pchar*
;
segmentNz
: pchar+
;
segmentNzNc
: (unreserved | pctEncoded | subDelims | '@')+
;
pchar
: unreserved | pctEncoded | subDelims | ':' | '@'
;
query
: (pchar | '/' | '?')*
;
fragment_
: (pchar | '/' | '?')*
;
uriReference
: uri
| relativeRef
;
relativeRef
: relativePart ('?' query)? ('#' fragment_)?
;
relativePart
: '//' authority pathAbEmpty
| pathAbEmpty
| pathNoScheme
| pathEmpty
;
absoluteUri
: scheme ':' hierPart ('?' query)?
;
Common.g4
:
lexer grammar Common;
// ASCII
//--------------------------------------------------
BANG : '!' ;
//DOUBLE_QUOTE : '"' ;
HASH : '#' ;
DOLLAR : '$' ;
PERCENT : '%' ;
AND : '&' ;
SINGLE_QUOTE : ''' ;
LEFT_PARENTHESES : '(' ;
RIGHT_PARENTHESES : ')' ;
STAR : '*' ;
PLUS : '+' ;
COMMA : ',' ;
MINUS : '-' ;
DOT : '.' ;
SLASH : '/' ;
COLON : ':' ;
SEMICOLON : ';' ;
LEFT_ANGLE_BRACKET : '<' ;
EQUAL : '=' ;
RIGHT_ANGLE_BRACKET : '>' ;
QUESTION : '?' ;
AT : '@' ;
LEFT_SQUARE_BRACKET : '[' ;
BACKSLASH : '\' ;
RIGHT_SQUARE_BRACKET : ']' ;
CARROT : '^' ;
UNDERSCORE : '_' ;
BACKTICK : '`' ;
LEFT_CURLY_BRACKET : '{' ;
BAR : '|' ;
RIGHT_CURLY_BRACKET : '}' ;
TILDE : '~' ;
// Core
//--------------------------------------------------
// Taken from ABNF.
ALPHA : [a-zA-Z] ;
DIGIT : [0-9] ;
HEXDIG : [0-9a-fA-F] ;
DQUOTE : '"' ;
SP : ' ' ;
HTAB : 't' ;
WSP : SP | HTAB ;
//LWSP : (WSP | CRLF WSP)* ;
VCHAR : [u0021-u007F] ;
CHAR : [u0001-u007F] ;
OCTET : [u0000-u00FF] ;
CTL : [u0000-u001Fu007F] ;
CR : 'r' ;
LF : 'n' ;
CRLF : CR LF ;
BIT : '0' | '1' ;
// Miscellaneous
//--------------------------------------------------
DOUBLE_SLASH : '//' ;
DOUBLE_COLON : '::' ;
LOWER_V : 'v' ;
ZERO : '0' ;
ONE : '1' ;
TWO : '2' ;
THREE : '3' ;
FOUR : '4' ;
FIVE : '5' ;
SIX : '6' ;
SEVEN : '7' ;
EIGHT : '8' ;
NINE : '9' ;
测试方法:
@Test
final void google() {
final String uri = "https://www.google.com/";
final UriLexer lexer = new UriLexer(new ANTLRInputStream(uri));
final UriParser parser = new UriParser(new CommonTokenStream(lexer));
parser.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendingSymbol, final int line, final int charPositionInLine, final String msg, final RecognitionException e) {
throw new IllegalStateException("[" + line + ":" + charPositionInLine + "] Symbol [" + offendingSymbol + "] produced error: " + msg + ".", e);
}
});
Assertions.assertDoesNotThrow(parser::uri);
}
当我输入https://www.google.com/
时,我得到以下错误:
我完全不知道是什么导致了这些解析错误。有人知道吗?
输出:
line 1:0 token recognition error at: 'h'
line 1:1 token recognition error at: 't'
line 1:2 token recognition error at: 't'
line 1:3 token recognition error at: 'p'
line 1:4 token recognition error at: 's'
line 1:5 missing '6' at ':'
ANTLR的词法分析器在解析和标记化/词法分析之间有严格的分离。词法分析器也独立于解析器工作,并基于2个简单规则创建令牌:
- 尝试为单个词法分析器规则消耗尽可能多的字符
- 当两个或多个词法分析器规则匹配相同的字符时,让先定义的那个"win">
如果我们现在看看你的规则:
ALPHA : [a-zA-Z] ;
DIGIT : [0-9] ;
HEXDIG : [0-9a-fA-F] ;
很明显,词法分析器规则HEXDIG
永远不会匹配,因为ALPHA
或DIGIT
将匹配HEXDIG
匹配的内容,并且在HEXDIG
之前定义。切换顺序:
HEXDIG : [0-9a-fA-F] ;
ALPHA : [a-zA-Z] ;
DIGIT : [0-9] ;
将无法工作,因为任何数字现在都不会变成DIGIT
令牌,并且F
现在也不会变成ALPHA
。
请注意,这只是一个单独的例子:在您的lexer语法中还有更多这样的情况。
一种解决方案是将部分责任转移给解析器而不是词法分析器:
A : [aA];
B : [bB];
C : [cC];
D : [dD];
E : [eE];
F : [fF];
G : [gG];
H : [hH];
I : [iI];
J : [jJ];
K : [kK];
L : [lL];
M : [mM];
N : [nN];
O : [oO];
P : [pP];
Q : [qQ];
R : [rR];
S : [sS];
T : [tT];
U : [uU];
V : [vV];
W : [wW];
X : [xX];
Y : [yY];
Z : [zZ];
D0 : '0';
D1 : '1';
D2 : '2';
D3 : '3';
D4 : '4';
D5 : '5';
D6 : '6';
D7 : '7';
D8 : '8';
D9 : '9';
,然后在解析器中:
alpha
: A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z
;
digit
: D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9
;
hexdig
: A | B | C | D | E | F | digit
;
同时,删除所有从解析器中删除像'6'
这样的文字标记,并使用适当的词法分析器规则(在本例中是D6
)。每当解析器看到这样一个没有在词法分析器中定义的文字标记时,它就会"神奇地"为它创建一个新的令牌,导致神秘的错误/警告消息。最好从解析器中删除所有(我的意思是所有!)这样的文字标记。
除了Bart对语法的回答之外——都是正确的——这不是如何编写拆分语法!
你必须有"语法分析器"在UriParser。g4(重命名Uri。g4到UriParser.g4),以及"词法分析器语法UriLexer;"在UriLexer。g4(重命名普通。(4).
如果您尝试为原始的"split"生成解析器语法中,您将得到由Antlr工具生成的三个.token文件,它们的大小和内容都不同。这表明词法分析器和解析器之间可能没有标记类型的协调。这与"标记识别错误"没有任何关系。因为正如Bart所说,词法分析器的操作完全独立于解析器。但是,当您开始使用其他输入测试语法结果时,它将产生影响。
同样,您不应该在语法中包含@header { package ...; }
。您需要使用-package
选项。使用@header
使语法完全无法移植到其他目标,并且如果在一个目录中有多个语法,有些有@header
,有些没有,则会产生问题。
如果您修复了这些问题,代码将解析您的输入—并警告您的词法分析器规则不正确(参见Bart的回答)。
不清楚你为什么要拆分语法。
UriParser.g4:
/**
* Uniform Resource Identifier (RFC 3986).
*
* @author Oliver Yasuna
* @see <a href="https://www.rfc-editor.org/rfc/rfc3986.html">RFC 3986</a>
* @since 1.0.0
*/
parser grammar UriParser;
options {
tokenVocab = UriLexer;
}
// Parser
//--------------------------------------------------
pctEncoded
: '%' HEXDIG HEXDIG
;
reserved
: genDelims | subDelims
;
genDelims
: ':' | '/' | '?' | '#' | '[' | ']' | '@'
;
subDelims
: '!' | '$' | '&' | ''' | '(' | ')' | '*' | '+' | ',' | ';' | '='
;
unreserved
: ALPHA | DIGIT | '-' | '.' | '_' | '~'
;
uri
: scheme ':' hierPart ('?' query)? ('#' fragment_)?
;
hierPart
: '//' authority pathAbEmpty
| pathAbsolute
| pathRootless
| pathEmpty
;
scheme
: ALPHA (ALPHA | DIGIT | '+' | '-' | '.')*
;
authority
: (userinfo '@')? host (':' port)?
;
userinfo
: (unreserved | pctEncoded | subDelims | ':')*
;
host
: ipLiteral
| ipv4Address
| regName
;
ipLiteral
: '[' (ipv6Address | ipvFuture) ']'
;
ipvFuture
: 'v' HEXDIG+ '.' (unreserved | subDelims | ':')+
;
ipv6Address
: '::' (h16 ':') (h16 ':') (h16 ':') (h16 ':') (h16 ':') (h16 ':') ls32
| '::' (h16 ':') (h16 ':') (h16 ':') (h16 ':') (h16 ':') ls32
| h16? '::' (h16 ':') (h16 ':') (h16 ':') (h16 ':') ls32
| ((h16 ':')? h16)? '::' (h16 ':') (h16 ':') (h16 ':') ls32
| ((h16 ':')? (h16 ':')? h16)? '::' (h16 ':') (h16 ':') ls32
| ((h16 ':')? (h16 ':')? (h16 ':')? h16)? '::' h16 ':' ls32
| ((h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? h16)? '::' ls32
| ((h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? h16)? '::' h16
| ((h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? (h16 ':')? h16)? '::'
;
ls32
: (h16 ':' h16)
| ipv4Address
;
h16
: HEXDIG HEXDIG? HEXDIG? HEXDIG?
;
ipv4Address
: decOctet '.' decOctet '.' decOctet '.' decOctet
;
decOctet
: DIGIT
| ('1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9') DIGIT
| '1' DIGIT DIGIT
| '2' ('0' | '1' | '2' | '3' | '4') DIGIT
| '2' '5' ('0' | '1' | '2' | '3' | '4' | '5')
;
regName
: (unreserved | pctEncoded | subDelims)*
;
port
: DIGIT*
;
path
: pathAbEmpty
| pathAbsolute
| pathNoScheme
| pathRootless
| pathEmpty
;
pathAbEmpty
: ('/' segment)*
;
pathAbsolute
: '/' (segmentNz ('/' segment)?)?
;
pathNoScheme
: segmentNzNc ('/' segment)?
;
pathRootless
: segmentNz ('/' segment)?
;
pathEmpty
: // TODO: 0<pchar>.
;
segment
: pchar*
;
segmentNz
: pchar+
;
segmentNzNc
: (unreserved | pctEncoded | subDelims | '@')+
;
pchar
: unreserved | pctEncoded | subDelims | ':' | '@'
;
query
: (pchar | '/' | '?')*
;
fragment_
: (pchar | '/' | '?')*
;
uriReference
: uri
| relativeRef
;
relativeRef
: relativePart ('?' query)? ('#' fragment_)?
;
relativePart
: '//' authority pathAbEmpty
| pathAbEmpty
| pathNoScheme
| pathEmpty
;
absoluteUri
: scheme ':' hierPart ('?' query)?
;
UriLexer.g4:
lexer grammar UriLexer;
// ASCII
//--------------------------------------------------
BANG : '!' ;
//DOUBLE_QUOTE : '"' ;
HASH : '#' ;
DOLLAR : '$' ;
PERCENT : '%' ;
AND : '&' ;
SINGLE_QUOTE : ''' ;
LEFT_PARENTHESES : '(' ;
RIGHT_PARENTHESES : ')' ;
STAR : '*' ;
PLUS : '+' ;
COMMA : ',' ;
MINUS : '-' ;
DOT : '.' ;
SLASH : '/' ;
COLON : ':' ;
SEMICOLON : ';' ;
LEFT_ANGLE_BRACKET : '<' ;
EQUAL : '=' ;
RIGHT_ANGLE_BRACKET : '>' ;
QUESTION : '?' ;
AT : '@' ;
LEFT_SQUARE_BRACKET : '[' ;
BACKSLASH : '\' ;
RIGHT_SQUARE_BRACKET : ']' ;
CARROT : '^' ;
UNDERSCORE : '_' ;
BACKTICK : '`' ;
LEFT_CURLY_BRACKET : '{' ;
BAR : '|' ;
RIGHT_CURLY_BRACKET : '}' ;
TILDE : '~' ;
// Core
//--------------------------------------------------
// Taken from ABNF.
ALPHA : [a-zA-Z] ;
DIGIT : [0-9] ;
HEXDIG : [0-9a-fA-F] ;
DQUOTE : '"' ;
SP : ' ' ;
HTAB : 't' ;
WSP : SP | HTAB ;
//LWSP : (WSP | CRLF WSP)* ;
VCHAR : [u0021-u007F] ;
CHAR : [u0001-u007F] ;
OCTET : [u0000-u00FF] ;
CTL : [u0000-u001Fu007F] ;
CR : 'r' ;
LF : 'n' ;
CRLF : CR LF ;
BIT : '0' | '1' ;
// Miscellaneous
//--------------------------------------------------
DOUBLE_SLASH : '//' ;
DOUBLE_COLON : '::' ;
LOWER_V : 'v' ;
ZERO : '0' ;
ONE : '1' ;
TWO : '2' ;
THREE : '3' ;
FOUR : '4' ;
FIVE : '5' ;
SIX : '6' ;
SEVEN : '7' ;
EIGHT : '8' ;
NINE : '9' ;