Parsing 解析器/语法:嵌套规则中有2个括号

Parsing 解析器/语法:嵌套规则中有2个括号,parsing,grammar,parentheses,recursive-descent,ambiguous-grammar,Parsing,Grammar,Parentheses,Recursive Descent,Ambiguous Grammar,尽管我在编译/解析方面的知识有限,但我还是敢于为OData$filter表达式构建一个小型递归下降解析器。解析器只需要检查表达式的正确性,并在SQL中输出相应的条件。由于输入和输出具有几乎相同的标记和结构,这相当简单,我的实现实现了我想要的90% 但现在我被括号卡住了,它出现在逻辑表达式和算术表达式的不同规则中。ABNF中完整的OData语法是,所涉及规则的浓缩版本如下: boolCommonExpr = ( boolMethodCallExpr / notE

尽管我在编译/解析方面的知识有限,但我还是敢于为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等式2)
这样的简单表达式?从我所能看到的一切
parenExpr
内部的
commonExpr
规则所消耗,也就是说,它们必须在
commonExpr
之后关闭,以避免引起错误,并且
boolParenExpr
永远不会被击中。我想我阅读这种语法的经验/直觉不足以得到它。ABNF中的一条评论说:“注意boolCommonExpr也是一个commonExpr”,也许这就是谜团的一部分

显然,一个开始的
本身并不能告诉我它将在哪里结束:在当前的
commonExpr
表达式之后,或者在
boolCommonExpr
之后更远的地方。我的lexer前面有一个所有标记的列表(URL是非常短的输入)。我想用它来找出
的类型(
我有。好主意

我宁愿在输入上有一些限制或者稍微修改一下,也不愿切换到一个通常更强大的解析器模型。对于这样一个简单的表达式转换,我还希望避免使用编译器工具


编辑1:rici回答后的扩展-语法重写正确吗?

事实上,我是从开始的。然后我想更好地适应OData标准给出的官方语法,使其更加“一致”。但根据rici的建议(以及“内部服务器错误”的评论),重写语法,我倾向于回到维基百科上提供的更易于理解的结构

适用于OData$过滤器的布尔表达式,可能如下所示:

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} ] ")" .
对于布尔表达式来说,上述内容一般有意义吗?它是否基本上等同于上面的原始结构,并且对于递归下降解析器来说是可消化的

@里希:谢谢你对类型检查的详细评论。新语法应该能解决你对算术表达式优先级的担忧

对于所有三个终端(上面语法中的大写),我的lexer都提供一个类型(字符串、数字、日期时间或布尔值)。非终端返回它们生成的类型。通过这一点,我在当前实现中很好地进行了动态类型检查,包括适当的错误消息。希望这也适用于新语法


编辑2:返回原始OData语法

“逻辑”和“算术”
之间的区别并不是一个微不足道的问题。为了解决这个问题,即使是N.沃思也使用了一种狡猾的变通方法来保持Pascal语法的简单。因此,在Pascal中有一对额外的
()
表达式中是强制性的。既不直观也不符合OData:-(。我发现的“()困难”的最佳读物是在。其他语言似乎在语法中花费了大量时间来解决这个问题。由于我没有语法构造的经验,我停止了自己的语法构造


我最终实现了原始的OData语法。在运行解析器之前,我回顾了所有标记,以找出哪个
属于逻辑/算术表达式。URL的潜在长度不是问题。

就我个人而言,我只是修改语法,使它只有一种表达式类型,因此只有一种括号类型。我不相信它实际上是正确的;它在LL(1)(或递归下降)中肯定不可用这正是您提到的原因

具体来说,如果目标是
boolCommonExpr
,则有两个产品可以匹配
lookahead令牌:

boolCommonExpr = ( … 
                 / commonExpr [ eqExpr / neExpr / … ]
                 / boolParenExpr
                 / …
                 ) …
commonExpr     = ( …
                 / parenExpr
                 / …
                 ) …
在大多数情况下,这是一种误导性的尝试,试图让语法检测到类型冲突(如果实际上是类型冲突)这是错误的,因为如果存在布尔变量,那么它注定会失败,而这种环境中显然存在布尔变量。由于没有关于变量类型的语法线索,解析器无法确定特定表达式是否格式正确,因此有一个很好的理由不尝试,特别是更好的解决方案是首先将表达式解析为某种形式的AST,然后再次传递AST以检查每个运算符是否具有正确类型的操作数(如果需要,还可能插入显式强制转换运算符)

除了任何其他优点外,在单独的过程中执行类型检查可以生成更好的错误消息。如果出现(某些)类型冲突语法错误,则可能会让用户对其表达式被拒绝的原因感到困惑;相反,如果您注意到一个比较操作正被用作乘法操作数,则可能会让用户感到困惑(如果您的语言语义不允许从True/False自动转换为1/0),则可以生成目标明确的错误消息(“例如,比较不能用作算术运算符的操作数”)

将不同运算符(而不是括号)放入不同语法变量的一个可能原因是为了表示语法优先级。这种考虑可能会鼓励您以明确的优先级重写语法。(如前所述,语法假定所有算术运算符具有相同的优先级,这可能会导致
2+3*a
被解析为a