【问题标题】:How should a unary operator be applied in an expression grammar?一元运算符应该如何应用于表达式语法?
【发布时间】:2021-05-11 07:53:18
【问题描述】:

我有一个用 C# 实现的语法。我的问题与评估表达式有关。仅与表达式有关的语法的简化版本是。请注意,语法还支持逻辑运算(使用 AND 和 OR,而不是 && 和 ||):

Expression => [ NegOp ] <Term> { <AddOp> <Term> }
Term =>       <Power> { <MulOp> <Power> }
Power =>      <Factor> [ ^ <Factor> ]
Factor =>     <Numeric> | ( OPENPAREN <Expression> CLOSEPAREN )
Numeric =>    <Float> | <Integer>
NegOp =>      + | - | NOT
AddOp =>      + | - | OR
MulOp =>      * | / | % | AND

我已经实现了表达式的从左到右求值,例如:

5 / 6 / 7

被解释为:

(5 / 6) / 7

但是,我不清楚如何将 NegOp 应用于表达式。如果我有表达式:

5 - 4 * 3    => -7

我在它前面加上一个减号,因此:

-5 - 4 * 3

这应该被解释为:

-(5 - 4 * 3)      => 7

或:

(-5) - 4 * 3      => -17

即NegOp 应该与表达式中的第一个 Term 紧密绑定,还是在评估后应用于整个表达式?

【问题讨论】:

  • "NegOp 应该紧密绑定到表达式中的第一个 Term,还是在评估后应用于整个表达式?"那么,想要哪种行为?
  • 嗨@Sweeper,我想要被普遍接受为计算机语言规范的行为。我不知道是否有规范,或者这因语言而异。我希望以正确的方式从“知道的人”那里获得启迪。
  • 根据您的语法,-5 - 4 * 3 会将一元减号应用于整个表达式,5 * -4 在不添加括号的情况下根本不合法。但这绝对不是常态。
  • 啊,我明白你的意思了。您是否会将 NegOp 转移到 Factor 语句中,例如:Factor =&gt; ( [ &lt;NegOp&gt; ] &lt;Numeric&gt; ) | ( [ &lt;NegOp&gt; ] OPENPAREN &lt;Expression&gt; CLOSEPAREN ),然后将其从 Expression 语句中删除?
  • 数学上正确的形式是最直观的。如果你将该术语分配给像int i=-5-4*3; 这样的变量,你会在 c#、java 和 c++ 中得到 -17 for i ...

标签: c# expression grammar ambiguous-grammar


【解决方案1】:

通常,一元前缀运算符几乎位于运算符优先级链的顶部,就在后缀运算符的下方。一种方法是将它们添加到您所谓的Factor

Factor =>     <Numeric> | OPENPAREN <Expression> CLOSEPAREN | NegOp Factor

这可以写成{ NegOp } ( &lt;Numeric&gt; | OPENPAREN &lt;Expression&gt; CLOSEPAREN ),但我觉得使用“重复NegOp”会不恰当地将运算符与操作数分开。此外,有些人可能倾向于使用单个可选的[ NegOp ] 而不是任意重复,但这似乎很难证明。是否有充分的理由禁止在同一操作数上使用两个不同的一元运算符?但这些都是你必须做出的决定。

但是,在一元减号与取幂结合的特定情况下,赋予取幂优先级是比较常见但并不普遍,因为您实际上永远不会希望 -2^20 表示 (-2)^20,这在数学上与2^20。我倾向于同意大多数人的观点,但这两个决定都是合法的。

与其他数学运算符不同,指数本身通常是右结合的,这也是因为其他分组不会添加任何有用的东西。 (a^b)^ca^(b*c) 完全相同,通常最好计算为第二个;你通常想要的表达式是a^(b^c)。我不知道任何常用的幂语言,其中幂在左侧分组。在我看来,您使求幂不分组的决定也很古怪。但这是你的语言。


也许还值得补充的是,并非所有前缀运算符都具有天生的高优先级。但这取决于您所说的“操作员”是什么意思。

例如,大多数语言都有类似的结构

return x + y
assert i < n

这些可能看起来更像“语句”而不是“前缀运算符”,但在语法上差异很小。确实它们不返回值,但也有一些变体可以,例如 Python 的 yield 运算符。

另外,一些语言有类似的结构

lambda t: 42 * t         # There are many ways to spell lambda
let t = f(a) in t + 6

这些都是带括号的前缀运算符(就像下标索引运算符v[i]是带括号的后缀运算符或者C族的“三元运算符”实际上是带括号的低优先级运算符一样。括号部分确实包含一个操作数,但它实际上是用括号括起来的,所以在解析过程中你不需要担心它。所以它们在语法上与前缀运算符相同。

所有这些运算符都位于(或接近)优先级层次结构的底部;他们基本上将“子表达式的其余部分”作为他们的论点。 (换句话说,参数一直持续到以右括号或语言中的语句分隔符传递的任何内容终止。因此,如果您有这些东西,您的优先级层次结构将在优先级列表的两端附近有前缀运算符。但那是很好。解析仍然有效。

【讨论】:

    猜你喜欢
    • 2017-04-15
    • 2015-02-04
    • 1970-01-01
    • 2014-11-27
    • 1970-01-01
    • 2020-07-28
    • 2017-07-23
    • 2012-08-25
    • 1970-01-01
    相关资源
    最近更新 更多