用于正整数文字的SML扼制的ANTLR4语法

问题描述

我正在使用ANTLR 4.8构建用于SML的解析器,由于某种原因,生成的解析器使整数字面值感到窒息:

# CLAsspATH=bin ./scripts/grun SML expression -tree <<<'1'
line 1:0 mismatched input '1' expecting {'(','let','op','{','()','[','#','raise','if','while','case','fn',LONGID,CONSTANT}
(expression 1)

我从语法上进行了尽可能多的修剪,以显示此问题,这看起来很奇怪。此语法显示了问题(尽管LABEL甚至没有被使用):

grammar SML_Small;

Whitespace : [ \t\r\n]+ -> skip ;

expression : CONSTANT ;

LABEL : [1-9] NUM* ;

CONSTANT : INT ;
INT : '~'? NUM ;
NUM : DIGIT+ ;
DIGIT : [0-9] ;

另一方面,删除LABEL可使正数再次起作用:

grammar SML_Small;

Whitespace : [ \t\r\n]+ -> skip ;

expression : CONSTANT ;

CONSTANT : INT ;
INT : '~'? NUM ;
NUM : DIGIT+ ;
DIGIT : [0-9] ;

我尝试用NUM*和类似的变体替换DIGIT?,但这并不能解决我的问题。

我真的不确定发生了什么,因此我怀疑它比我使用的语法更深。

解决方法

正如Rici的评论中已经提到的:词法分析器尝试匹配尽可能多的字符,并且当2个或更多规则与相同字符匹配时,一个规则将首先定义“获胜”。因此,使用以下规则:

LABEL    : [1-9] NUM* ;
CONSTANT : INT ;
INT      : '~'? NUM ;
NUM      : DIGIT+ ;
DIGIT    : [0-9] ;

输入1将始终成为LABEL。并且像0这样的输入将始终是CONSTANT。仅当遇到INT后跟一些数字时,才会创建~令牌。 NUMDIGIT将永远不会产生令牌,因为之前的规则将被匹配。 NUMDIGIT永远不能自己成为令牌的事实,使它们成为fragment tokens的候选者:

fragment NUM   : DIGIT+ ;
fragment DIGIT : [0-9] ;

这样,您就不会在解析器规则中意外使用这些令牌。

此外,将~用作令牌的一部分通常也不可行。您可能还希望~(1 + 2)是一个有效的表达式。因此,通常最好在解析器规则:~中使用像expression : '~' expression | ... ;这样的一元运算符。

最后,如果您想区分非零整数值作为标签,可以这样做:

grammar SML_Small;


expression
 : '(' expression ')'
 | '~' expression
 | integer 
 ;

integer
 : INT
 | INT_NON_ZERO
 ;

label
 : INT_NON_ZERO
 ;

INT_NON_ZERO : [1-9] DIGIT* ;
INT          : DIGIT+ ;
SPACES       : [ \t\r\n]+ -> skip ;

fragment DIGIT : [0-9] ;