尽管我对编译/解析的知识有限,但我还是敢于为 OData $filter 表达式构建一个小型递归下降解析器。解析器只需要检查表达式的正确性并在 SQL 中输出相应的条件。由于输入和输出具有几乎相同的标记和结构,这相当简单,我的实现完成了我想要的 90%。
但现在我被括号困住了,它们出现在逻辑和算术表达式的单独规则中。ABNF 中的完整 OData 语法在这里,所涉及的规则的浓缩版本是这样的:
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)
?据我所知,所有这些都被里面(
的规则所消耗,即它们也必须在之后关闭才能不会导致错误并且永远不会被击中。我想我阅读这种语法的经验/直觉不足以理解它。ABNF 中的一条评论说:“注意 boolCommonExpr 也是一个 commonExpr”。也许这就是谜团的一部分?parenExpr
commonExpr
commonExpr
boolParenExpr
显然,(
单独的开头不会告诉我它将在哪里关闭:在当前commonExpr
表达式之后或更远之后boolCommonExpr
。我的词法分析器前面有一个所有标记的列表(URL 是非常短的输入)。我想用它来找出(
我有什么类型。好主意?
我宁愿限制输入或进行一些小技巧,也不愿切换到通常更强大的解析器模型。对于像这样的简单表达式翻译,我也想避免使用编译器工具。
编辑 1:rici 回答后的扩展 - 语法重写是否正确?
实际上,我从Wikipedia 上给出的递归下降解析器示例开始。然后我想更好地适应 OData 标准给出的官方语法更“符合”。但是根据 rici 的建议(以及“内部服务器错误”的评论)来重写语法,我倾向于回到 Wikipedia 上提供的更易于理解的结构。
适应 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 表达式周围必须有()
一个额外的一对。既不直观也不符合 OData :-(。关于“() 困难”的最佳读物是在Let's Build a Compiler (Part VI)中。其他语言似乎在语法上花费了很大的篇幅来解决问题。正如我没有语法结构的经验我停止自己做。and
or
我最终实现了原始的 OData 语法。在我运行解析器之前,我会向后检查所有标记以找出哪些(
属于逻辑/算术表达式。URL 的潜在长度不是问题。