我已经开始使用ANTLR,并注意到它的lexer规则非常多变。一个极其令人沮丧的例子如下:
grammar output;
test: FILEPATH NEWLINE TITLE ;
FILEPATH: ('A'..'Z'|'a'..'z'|'0'..'9'|':'|'\'|'/'|' '|'-'|'_'|'.')+ ;
NEWLINE: 'r'? 'n' ;
TITLE: ('A'..'Z'|'a'..'z'|' ')+ ;
此语法与以下内容不匹配:
c: \test.txt
x
奇怪的是,如果我将TITLE
更改为TITLE: 'x' ;
,这次它仍然失败,并给出一条错误消息,说"输入'x'不匹配,应为'x'",这非常令人困惑。更奇怪的是,如果我用FILEPATH
代替test
中TITLE
的用法,整个事情都会起作用(尽管FILEPATH
的匹配程度会比我想要的要高,所以一般来说,这对我来说不是一个有效的解决方案)。
我非常困惑,为什么ANTLR会出现如此奇怪的错误,然后在混乱的时候突然莫名其妙地工作。
这似乎是对ANTLR
:的常见误解
ANTLR中的语言处理:
语言处理分为两个严格分离的阶段:
- Lexing,即将文本划分为令牌
- 解析,即根据令牌构建解析树
由于词法分析必须优先于语法分析,因此有一个后果:词法分析器独立于语法分析器,语法分析器不能影响词法分析。
乐星
ANTLR中的乐星工作如下:
- 所有第一个字符大写的规则都是lexer规则
- lexer从一开始就试图找到一个与当前输入最匹配的规则
- 最佳匹配是具有最大长度的匹配,也就是说,将下一个输入字符附加到最大长度匹配后得到的令牌不符合任何lexer规则
- 令牌是根据匹配生成的:
- 如果一个规则匹配最大长度匹配,则将相应的令牌推入令牌流
- 如果多个规则匹配最大长度匹配,则语法中第一个定义的令牌将被推送到令牌流
示例:您的语法有什么问题
你的语法有两条至关重要的规则:
FILEPATH: ('A'..'Z'|'a'..'z'|'0'..'9'|':'|'\'|'/'|' '|'-'|'_'|'.')+ ;
TITLE: ('A'..'Z'|'a'..'z'|' ')+ ;
每个由TITLE匹配的匹配也将由FILEPATH匹配。FILEPATH是在TITLE:之前定义的,所以你期望成为标题的每个令牌都是FILEPATH
有两个提示:
- 保持lexer规则的析取(任何令牌都不应该与另一个令牌的超集匹配)
- 如果您的令牌有意匹配相同的字符串,那么将它们按正确的顺序排列(在您的情况下,这就足够了)
- 如果你需要一个解析器驱动的lexer,你必须换成另一个解析器生成器:PEG解析器或GLR解析器会这样做(但这当然会产生其他问题)
这不是OP的直接问题,但对于那些有相同错误消息的人,您可以检查以下内容。
当我引入一个新关键字时,我收到了相同的Mismatched Input 'x' expecting 'x'
模糊错误消息。对我来说,原因是我将新关键字放在了VARNAME
lexer规则之后,该规则将其指定为变量名,而不是新关键字。我通过将关键字放在VARNAME
规则之前来修复它。
TITLE
的任何输入都与FILEPATH
令牌匹配。lang处理器停止对处理输入的FILEPATH
的选择,并且没有机会到达TITLE
令牌。这就引出了这个问题。
解决方法是将TITLE
置于FILEPATH
令牌之前(或将FILEPATH
置于TITLE
令牌之后)。例如:
grammar output;
test: FILEPATH NEWLINE TITLE ;
NEWLINE: 'r'? 'n' ;
TITLE: ('A'..'Z'|'a'..'z'|' ')+ ;
FILEPATH: ('A'..'Z'|'a'..'z'|'0'..'9'|':'|'\'|'/'|' '|'-'|'_'|'.')+ ;
p.S。此解决方案适用于等输入
c:test.txt
x
如果你的输入是简单的文件名,没有文件夹名的扩展名,你会遇到同样的问题。
test
x
所以我会考虑对FILEPATH
使用一些限制,使其与TITLE
不同。例如,将下一个正则表达式[A-Za-z][:][\/][A-Za-z0-9]+'.'[A-Za-z0-9]+
用于FILEPATH
(不确定,因为我不清楚您的所有情况)。因此,最终的解决方案可能是:
grammar output;
test: FILEPATH NEWLINE TITLE ;
fragment FILENAME: TITLE DOT EXTENSION;
fragment LETTER: [a-zA-Z] ;
fragment DIGIT: [0-9] ;
fragment UNDERSCORE: '_' ;
fragment SPACE: ' ' ;
fragment ESCAPE: '\' ;
fragment SLASH: '/' ;
fragment QUOTE: '"' ;
fragment PLUS: '+';
fragment MINUS: '-';
fragment COLON: ':' ;
fragment DOT: '.';
EXTENSION: DOT (LETTER | DIGIT)+;
SEPARATOR: ESCAPE | SLASH;
DISC: LETTER COLON;
TITLE: (LETTER | DIGIT | UNDERSCORE | MINUS)+ ;
FILEPATH: DISC?(SEPARATOR TITLE)+ EXTENSION ;
NEWLINE: 'r'? 'n' ;