【问题标题】:ANTLR : Tree parsing giving unexpected resultsANTLR:树解析给出了意想不到的结果
【发布时间】:2019-06-11 15:47:22
【问题描述】:

我正在尝试创建自己的 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 行不匹配的输入 'STARTS_WITH(this.sequence, ' Expecting FUNCTION_NAME”。所以我相信您确实收到了错误,但你没有看到它。你运行你的应用程序的方式是否可以看到它的 stderr 输出?如果没有,你应该添加你自己的错误处理程序,它会以你会注意到的方式通知你错误。
  • 感谢您的帮助,我已经添加了我在测试期间使用的 try catch(没有抛出异常)我在我的 ANTLR 单元测试中遗漏了什么吗?
  • 默认错误监听器不会抛出异常 - 它只是将错误打印到 stderr 并继续解析(返回一棵树,其中某些字段可能是 null,当它们无法正确解析时)。您可以使用parser.NumberOfSyntaxErrors 检查是否有错误(以及有多少错误)。对于测试,我建议使用一个错误侦听器,它收集一组中的所有错误消息,然后检查您是否收到了正确数量的错误以及正确的消息(对于应该产生错误的输入)。

标签: c# antlr antlr4


【解决方案1】:

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

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

因此,您首先应该检查的是为什么您没有看到错误消息。默认错误侦听器会将语法错误打印到标准错误。如果您在看不到 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 基本上可以匹配任何内容,因此与您的大多数其他规则重叠。这是一件坏事。

您应该做的是为字符串文字设置一个词法分析器规则(因此将constant 转换为词法分析器规则并将DOUBLE_QUOTEANY_BUT_DOUBLE_QUOTE 转换为片段或将它们直接内联到CONSTANT)。这样一来,ANY_BUT_DOUBLE_QUOTE 规则不再与所有内容冲突,CONSTANT 不会与任何内容冲突,因为它是唯一以双引号开头的规则。这也将防止空格在双引号内被丢弃。

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

【讨论】:

  • 您对错误输出问题(错误侦听器)和帮助我了解 ANTLR 的工作方式都提供了巨大帮助;我认为 ANTLR 有一个自上而下的方法,首先尝试匹配一些解析器规则,然后深入定义的词法分析器规则。但是正如您所解释的,这是相反的,首先尝试以某种贪婪的方式匹配词法分析器规则,然后使用解析器规则重建树。 \n有没有办法选择自上而下而不是自下而上的方法?
  • @zurk 词法分析器完全独立于解析器工作。解析器无法告诉词法分析器“这次只尝试匹配以下规则之一”。事实上,您可以在不调用解析器的情况下自行运行词法分析器,并且您会知道它提供给您的序列标记将与它提供给解析器的序列标记相同。有词法分析器模式,允许您在看到某些标记后应用一组不同的规则,但这些仍然完全由词法分析器控制(这意味着您只能在给定标记之后切换模式,而不是给定的非终端)。
  • 所以不,没有办法从解析器控制词法分析器。请注意,解析器本身确实以自上而下的方式工作,只是没有扩展到解析器和词法分析器之间的交互。
  • 好的,我现在知道,由于一些“优先”规则等,词法分析器标记是独立构造的,当这组标记提交给它自己的解析器规则时,解析器会发出潜在的错误。再次感谢您的宝贵时间:)
猜你喜欢
  • 2020-11-12
  • 1970-01-01
  • 2019-05-19
  • 2017-01-05
  • 2021-10-14
  • 1970-01-01
  • 1970-01-01
  • 2021-12-17
  • 2021-04-22
相关资源
最近更新 更多