3. 强制运算顺序¶
3.1. 表达式的求值¶
本节我们将学习:
运算符优先级
运算符结合性
EBNF 扩展及其优点
在上一节中,我们看到应当避免二义性文法,因为它们所允许的语法分析树在我们为语法分析树结构赋予语义(即意义)时会导致混乱。无法依靠这棵树来指定运算的顺序。
尽管 第一个示例文法 不存在二义性,但它还有另一个问题。特别是,如果你回顾 第一个示例文法 附带的语法分析树幻灯片,就会注意到在分析 \(A+B*C\) 时,\(+\) 运算位于树中比 \(*\) 更深的一层,这表明 \(A+B\) 会先求值,然后再与 \(C\) 相乘。然而,这一运算顺序与几乎所有程序设计语言中的运算符优先级规则都不一致。因此,第一个示例文法 虽然无二义性,却并不是我们需要的代数表达式文法。取而代之,请考虑下面的文法。
3.1.1. 第三个示例文法¶
请注意,下面幻灯片中使用第三个示例文法生成的语法分析树与 第一个示例文法 生成的语法分析树有何不同。
特别是,在第三个示例文法中,对应乘法运算符的子树在语法分析树中出现在比加法子树更深的一层,从而与程序设计语言中通常的运算符优先级一致。
下面的幻灯片是第三个示例文法的一个“生成器”,你可以输入想要生成语法分析树的表达式来控制它。你应当尝试生成各种各样的幻灯片,直到你有信心能够手动构造任何可能表达式所对应的语法分析树为止。
一旦你对语法分析树的操作有了信心,在开始本节复习题之前,这里有两个问题值得思考。
问题 1: 如果你正在设计一个对应表达式的文法,你会采用什么策略来引入一个与其他运算符不同(更高或更低)的运算符优先级层次?如果你想要添加一个对应幂运算的运算符,这个策略在第三个示例文法上会如何体现?
问题 2: 在第三个示例文法中,同一优先级层次上的运算符按从左到右的顺序结合,也就是说,\(A+B-C\) 会作为加括号的表达式 \(((A+B)-C)\) 求值。文法中的什么决定了这种从左到右的结合性?你会如何修改产生式来实现从右到左的结合性,也就是 \((A+(B-C))\) ?
本节的题集包含五道复习题,其中前四道涉及文法如何决定运算符优先级和结合性。在思考出上述两个问题的答案之前,不要开始做这些题。
3.2. 表达式求值¶
第一道题说明了语法结构如何影响算术表达式的求值,从而影响程序的语义。请注意, 要获得第一道题的分数, 你必须连续三次正确解答它,因为题目是随机生成的。在你一次答对之后, 检查答案 按钮将允许你继续到该题的下一个实例。
3.3. 结合性¶
这道题演示了语法结构如何影响算术运算符的结合性。
3.4. 优先级与结合性¶
这道题说明了语法结构如何影响算术运算符的结合性和优先级顺序。
3.5. 给定 BNF 文法刻画一个语言¶
这道题要求你用英语描述一个 BNF 文法所生成的语言。完成之后,还有一道关于扩展巴科斯范式(EBNF)的题,题目之前给出了相关说明。
3.6. 扩展 BNF¶
回想一下,我们在表示文法时使用的那些符号共同构成了所谓的 巴科斯范式 (Backus-Naur Form,BNF)。在 扩展巴科斯范式 (Extended Backus-Naur Form,EBNF)中,我们在 BNF 记法已使用的符号之外又添加了五个元符号:
克林闭包运算符 \(*\) ,其含义是“零个或多个”。因此,如果 \(<fn\_name>\) 是一个表示合法函数名的非终结符,\(<argument>\) 是一个表示合法实参的非终结符,那么带零个或多个实参(实参之间没有逗号)的函数调用的 EBNF 记法就是
\[<fn\_name>\ "("\ <argument>*\ ")"\]正闭包运算符 \(+\) 。对于必须至少有一个实参的函数调用,其 EBNF 记法是
\[<fn\_name>\ "("\ <argument>+\ ")"\]一对括号符号 \(( \; )\) ,用于分组。例如,如果 \(<positive\_number>\) 是表示合法正数的非终结符,那么下面的 EBNF 规定数字前面 必须 有一个正号或负号
\[(+ | -) <positive\_number>\]
“可选运算符” \(?\) ,它规定运算符前面的语法结构可以出现零次或一次。例如,如果我们的语言允许数字前面有一个可选的加号或减号,就会使用这样的 EBNF
\[(+ | -)? <positive\_number>\]
EBNF 用于减少一个文法指定某个语言所需的产生式数量。然而,它并不会增强文法的表达能力,也就是说,任何可以用 EBNF 表达的语法结构,只要愿意使用更多产生式,也都可以用 BNF 表达。
最后这道题是关于给定 BNF 文法(与上一题相同)与一个更小的 EBNF 文法之间的等价性。

