ANTLR:树解析给出意想不到的结果

ANTLR : Tree parsing giving unexpected results

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

STARTS_WITH(this.sequence, "startSeq")

ENDS_WITH("sequenceToTest", "endSeq")

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

已经创建了 Lexer 和 Parser Rules,但是当我尝试显示函数 ToStringTree 时,似乎以错误的方式解析了树。

我正在使用带有 ASP.Net Core 2.1 的 Antlr 4。

语法文件:

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 的方式是否有任何明显的错误?

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

line 1:0 mismatched input 'STARTS_WITH(this.sequence, ' expecting 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 标记。显然,我们希望这是多个标记,none 其中 ANY_BUT_DOBULE_QUOTE。发生这种情况是因为 ANY_BUT_DOUBLE_QUOTE 可以匹配整个字符串 STARTS_WITH(this.sequence,FUNCTION_NAME 只能匹配 STARTS_WITH。 ANTLR-generated 词法分析器遵循最大咀嚼规则,即始终使用产生最长匹配的规则(在并列的情况下使用语法中排在第一位的规则)。

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

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

你应该做的是对字符串文字有一个词法分析器规则(所以把 constant 变成一个词法分析器规则,把 DOUBLE_QUOTEANY_BUT_DOUBLE_QUOTE 变成片段或内联他们直接进入 CONSTANT)。这样就不再有 ANY_BUT_DOUBLE_QUOTE 规则与所有内容发生冲突,而 CONSTANT 也不会与任何内容发生冲突,因为它是唯一以双引号开头的规则。这也将防止白色 space 在双引号内被丢弃。

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