ANTLR:树解析会产生意外的结果

时间:2019-06-11 15:47:22

标签: c# antlr antlr4

我正在尝试创建自己的ANTLR4语法,以便我可以解析一些表达式,例如:

  

STARTS_WITH(this.sequence,“ startSeq”)

     

ENDS_WITH(“ sequenceToTest”,“ endSeq”)

(我混合使用常量和变量作为函数参数)。

已经创建了Lexer和Parser规则,但是当我尝试显示函数 ToStringTree 时,似乎树被错误地解析了。

我正在将Antlr 4与ASP.Net Core 2.1结合使用。

语法文件:

grammar expression;

expression : bool_function (OPERATOR bool_function)* ;

bool_function : (FUNCTION_NAME PAR_OPEN parameter (COMMA parameter)* PAR_CLOSE) ;

parameter : constant | current_object_field ;

constant : DOUBLE_QUOTE ANY_BUT_DOUBLE_QUOTE DOUBLE_QUOTE ;

current_object_field : THIS ALPHANUM ;

WHITESPACE : (' ' | '\t' | '\n' |'\r' )+ -> skip ;

COMPARATOR : ('==' | '<=' | '>=' | '<>') ;

OPERATOR : ('&&' | '||') ;

PAR_OPEN : '(' ;

PAR_CLOSE : ')' ;

COMMA : ',' ;

DOLLAR : '$' ;

DOUBLE_QUOTE : '"' ;

THIS : 'this.' ;

NUMBER : [0-9]+ ;

ALPHANUM : [a-zA-Z_0-9]+ ;

ANY_BUT_DOUBLE_QUOTE    : ~('"')+ ;

FUNCTION_NAME : ('STARTS_WITH' | 'ENDS_WITH' | 'CONTAINS' | 'EQUALS') ;

单元测试,试图解析一个基本表达式:

        try
        {
            expressionParser expressionParser = Setup("STARTS_WITH(this.sequence, \"startSeq\")");
            expressionParser.ExpressionContext expressionContext = expressionParser.expression();

            var a = expressionContext.ToStringTree(expressionParser);
        }
        catch (Exception e)
        {
            var a = e.Message;
            throw;
        }

通过 ToStringTree 解析表达式时收到的输出如下:

(expression (bool_function STARTS_WITH(this.sequence,  " startSeq " )))

但是我希望结果会更深入,例如:

(expression (bool_function STARTS_WITH((parameter(current_objet_field this.sequence)),(parameter(constant "startSeq")))))

我定义Lexer / Parser的方式是否存在明显的错误?

1 个答案:

答案 0 :(得分:1)

您没有得到想要的树,因为您的输入根本无法正确解析。解析失败,并出现以下语法错误:

  

第1:0行的输入'STARTS_WITH(this.sequence,'期待FUNCTION_NAME

因此,您应该检查的第一件事就是为什么没有看到错误消息。默认错误侦听器会将语法错误输出到stderr。如果您在看不见stderr的环境中运行,则应安装自己的错误侦听器,该侦听器将以明显的方式通知用户输入中的语法错误。

现在为什么不解析输入?嗯,该错误消息似乎令人怀疑,因为它一开始就抱怨缺少FUNCTION_NAME,而实际上正是STARTS_WITH的含义(或至少应该如此)。似乎也将STARTS_WITH(this.sequence,视为单个令牌,这显然不是我们想要的。因此,您的词法分析器规则似乎有些不对。

当您认为词法分析器可能生成错误的标记时,您应该做的第一件事是实际打印出词法分析器生成的标记。您可以通过将grun-tokens选项一起使用来实现此目的(这要求您使用Java,但这并不是什么大问题,因为您的语法不包含任何操作),也可以通过遍历您的令牌流C#代码(请注意,您必须在对其进行迭代之后重置流,否则解析器将仅看到空流)。

这样做,我们将看到词法分析器生成了以下标记:

[@0,0:26='STARTS_WITH(this.sequence, ',<ANY_BUT_DOUBLE_QUOTE>,1:0]
[@1,27:27='"',<'"'>,1:27]
[@2,28:35='startSeq',<ALPHANUM>,1:28]
[@3,36:36='"',<'"'>,1:36]
[@4,37:37=')',<')'>,1:37]
[@5,38:37='<EOF>',<EOF>,1:38]

现在我们可以在这里看到的第一个问题是开头的ANY_BUT_DOUBLE_QUOTE令牌。显然,我们希望它是多个令牌,但都不是ANY_BUT_DOBULE_QUOTE。发生这种情况是因为ANY_BUT_DOUBLE_QUOTE可以匹配整个字符串STARTS_WITH(this.sequence,,而FUNCTION_NAME仅可以匹配STARTS_WITH。 ANTLR生成的词法分析器遵循最大的munch规则,该规则规定始终使用产生最长匹配项的规则(如果使用平局,则使用语法中最先出现的规则)。

另一个问题是startSeqALPHANUM,因为语法允许两个双引号之间的唯一内容是ANY_BUT_DOUBLE_QUOTE。在这里,词法分析器生成的是ALPHANUM而不是ANY_BUT_DOUBLE_QUOTE,因为两个规则都将生成相同长度的匹配项,但是ALPHANUM在语法中排在第一位。请注意,如果仅切换ALPHANUMANY_BUT_DOUBLE_QUOTE的顺序,则词法分析器将永远不会产生ALPHANUM令牌,这也不是您想要的。

这两个问题均源于ANY_BUT_DOUBLE_QUOTE基本上可以匹配任何内容,因此与大多数其他规则重叠的事实。这是一件坏事。

您应该做的是为字符串文字使用单个lexer规则(因此将constant变成lexer规则,然后将DOUBLE_QUOTEANY_BUT_DOUBLE_QUOTE变成片段或直接内联它们放入CONSTANT中)。这样,就不再存在与所有内容冲突的ANY_BUT_DOUBLE_QUOTE规则,并且CONSTANT不会与任何内容冲突,因为它是唯一以双引号开头的规则。这样还可以防止在双引号内丢弃空格。

完成此操作后,您会收到关于STARTS_WITHALPHANUM而不是FUNCTION_NAME的错误,但是可以通过移动FUNCTION_NAME来解决此问题。在语法中ALPHANUM之前。请注意,这意味着您的函数名称永远不能用作成员名称。如果您不希望这样做,则不应该使函数名称成为关键字(即,您应该只允许使用任意标识符作为函数名称,然后在以后检查您是否知道具有该名称的函数)或使它们成为上下文关键字(具有解析器规则,可以匹配ALPHANUM或任何函数名称。