【发布时间】:2020-09-18 13:08:22
【问题描述】:
尽管我对编译/解析的知识有限,但我还是敢于为 OData $filter 表达式构建一个小型递归下降解析器。解析器只需要检查表达式的正确性并在 SQL 中输出相应的条件。由于输入和输出具有几乎相同的标记和结构,这相当简单,我的实现完成了我想要的 90%。
但现在我被括号困住了,它们出现在逻辑和算术表达式的单独规则中。 ABNF 中的完整 OData 语法是 here,所涉及的规则的浓缩版本是这样的:
boolCommonExpr = ( boolMethodCallExpr
/ notExpr
/ commonExpr [ eqExpr / neExpr / ltExpr / ... ]
/ boolParenExpr
) [ andExpr / orExpr ]
commonExpr = ( primitiveLiteral
/ firstMemberExpr ; = identifier
/ methodCallExpr
/ parenExpr
) [ addExpr / subExpr / mulExpr / divExpr / modExpr ]
boolParenExpr = "(" boolCommonExpr ")"
parenExpr = "(" commonExpr ")"
这个语法如何匹配像(1 eq 2) 这样的简单表达式?据我所知,所有( 都被commonExpr 内的parenExpr 规则所消耗,即它们还必须在commonExpr 之后关闭,以免导致错误并且boolParenExpr 永远不会受到影响。我想我阅读这种语法的经验/直觉不足以理解它。 ABNF 中的一条评论说:“注意 boolCommonExpr 也是一个 commonExpr”。也许这就是谜团的一部分?
显然,单独的 ( 开头不会告诉我它将在哪里关闭:在当前 commonExpr 表达式之后或在 boolCommonExpr 之后更远的地方。我的词法分析器前面有一个 all 标记列表(URL 是非常短的输入)。我想用它来找出我有什么类型的(。好主意?
我宁愿限制输入或进行一些小技巧,也不愿切换到通常更强大的解析器模型。对于像这样的简单表达式翻译,我还想避免使用编译器工具。
编辑 1:rici 回答后的扩展 - 语法重写是否正确?
实际上我是从example for recursive-descent parsers given on Wikipedia 开始的。然后我想更好地适应 OData 标准给出的官方语法更“符合”。但是根据 rici 的建议(以及“内部服务器错误”的评论)重写语法,我倾向于回到维基百科上提供的更易于理解的结构。
适应 OData $filter 的布尔表达式,可能如下所示:
boolSequence= boolExpr {("and"|"or") boolExpr} .
boolExpr = ["not"] expression ("eq"|"ne"|"lt"|"gt"|"lt"|"le") expression .
expression = term {("add"|"sum") term} .
term = factor {("mul"|"div"|"mod") factor} .
factor = IDENT | methodCall | LITERAL | "(" boolSequence")" .
methodCall = METHODNAME "(" [ expression {"," expression} ] ")" .
以上对于布尔表达式是否有意义,它是否与上面的原始结构基本等价并且对于递归下降解析器来说是可消化的?
@rici:感谢您对类型检查的详细评论。新语法应该可以解决您对算术表达式优先级的担忧。
对于所有三个终端(上面语法中的大写),我的词法分析器提供了一个类型(字符串、数字、日期时间或布尔值)。非终结符返回它们产生的类型。有了这个,我在当前的实现中很好地进行了类型检查,包括体面的错误消息。希望这也适用于新语法。
编辑 2:返回原始 OData 语法
“逻辑”和“算术”( 之间的区别并非微不足道。为了解决这个问题,甚至 N.Wirth 也使用了一种狡猾的解决方法来保持 Pascal 的语法简单。因此,在 Pascal 中,and 和 or 表达式周围的额外一对 () 是强制。既不直观也不符合 OData :-(。关于“() 难度”的最佳读物是在 Let's Build a Compiler (Part VI) 中。其他语言似乎在语法上花了很长时间来解决问题。因为我没有经验语法结构我不再自己做。
我最终实现了原始 OData 语法。在运行解析器之前,我会向后检查所有标记以找出哪个 ( 属于逻辑/算术表达式。 URL 的潜在长度不是问题。
【问题讨论】:
-
您可能需要重新编写语法,以便实质上将
"(" this ... ")" | "(" that ... ")" | other重新编写为"(" (this ... | that ... ) ")" | other。 -
感谢“HTTP 500”。根据 rici 和您的建议,我将我的问题扩展到了这个方向。
标签: parsing grammar parentheses recursive-descent ambiguous-grammar