运算符的优先级&同优先级内的结合性&序列点

运算符的优先级&同优先级内的结合性&序列点

本文以C语言为基础来进行探讨。

参考资料:

运算符的结合性:
https://en.wikipedia.org/wiki/Operator_associativity
c的优先级与结合性:
https://msdn.microsoft.com/en-us/library/2bxt6kc4.aspx
https://msdn.microsoft.com/en-us/library/126fe14k.aspx
http://en.cppreference.com/w/c/language/operator_precedence
http://en.cppreference.com/w/cpp/language/operator_precedence
边效应:
https://msdn.microsoft.com/en-us/library/8a425116.aspx
https://gcc.gnu.org/onlinedocs/gccint/Side-Effects.html
https://gcc.gnu.org/onlinedocs/cpp/Duplication-of-Side-Effects.html
序列点:
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf 的附录C
https://msdn.microsoft.com/en-us/library/azk8zbxd.aspx
http://en.cppreference.com/w/cpp/language/eval_order
https://en.wikipedia.org/wiki/Sequence_point
最大匹配:
c语言n1256.pdf 的6.4 Lexical elements第4段落
https://en.wikipedia.org/wiki/Maximal_munch

优先级Precedence

基础知识略。参考Microsoft的C语言文档。

例子:

  • *p++ : 后缀++的优先级高于*,所以解析成 *(p++) 而不是 (*p)++
  • a & b || c : 位与运算符&的优先级高于||,所以解析成 (a & b) || c
  • a = b || c : 解析成 a = (b || c)
  • 1 || 0 && 0 解析成 1 || (0 && 0) 结果是1
  • q && r || s-- : 解析成 (q && r) || (s––)
  • a.b++ : 解析成 (a.b)++

结合性Associativity

结合性就是指,当同等优先级的操作符一起出现时,怎么将操作数和操作符分组(或说绑定)(以确定运算顺序)。

In programming languages, the associativity (or fixity) of an operator is a property that determines how operators of the same precedence are grouped in the absence of parentheses.

If an operand is both preceded and followed by operators (for example, “^ 4 ^“), and those operators have equal precedence, then the operand may be used as input to two different operations (i.e. the two operations indicated by the two operators). The choice of which operations to apply the operand to, is determined by the “associativity” of the operators.

注意:结合性只有在出现同等优先级操作符的时候才需要考虑(Associativity is only needed when the operators in an expression have the same precedence)。

一般来讲,操作符的结合性有4种:

  • associative (meaning the operations can be grouped arbitrarily)
  • left-associative (meaning the operations are grouped from the left)
  • right-associative (meaning the operations are grouped from the right)
  • non-associative (meaning operations can not be chained, often because the output type is incompatible with the input types)

比如,抽象表达式 a ~ b ~ c 中,~代表某操作符,其结合性如果:

  • left associativity,即从左到右:解析为(a ~ b) ~ c
  • right associativity,即从右到左:解析为a ~ (b ~ c)
  • 其他结合性定义:则其他特殊意义。

代入一个具体的例子,例如 7 − 4 + 2 中,假设+-操作符具有同等优先级,如果:

  • +-都是左结合性: (7 − 4) + 2 = 5
  • +-都是右结合性: 7 − (4 + 2) = 1

下面是一个更详细的例子,来说明结合性
表达式 5^4^3^2 ,这里 ^ 代表幂运算。语法分析器(parser)从左向右扫描读入该表达式。

  • 这里如果幂运算符^右结合性,即从右向左结合顺序,则:

    1. 读入5
    2. 读入^,现在节点是5^
    3. 读入4,现在节点是5^4
    4. 读入^,现在节点是5^(4^
    5. 读入3,现在节点是5^(4^3
    6. 读入^,现在节点是5^(4^(3^
    7. 读入2,现在节点是5^(4^(3^2
    8. 没有token可读,结束,现在节点是5^(4^(3^2))

    接下来,进行求值(depth-first),从树最顶端的^开始走:

    1. 求值程序(evaluator)沿着树,从第一个、经过第二个、到达第三个^表达式
    2. 计算3^2=9,这个结果替换到第二个^的表达式中
    3. 继续在parse tree的向上一层,计算4^9=262144,同样地这个结果替换到第一个^表达式中作为其操作数
    4. 继续向上一层,计算5^262144=...得到结果,最后这棵树就可以销毁了并返回总结果
    5. 结束
  • 这里如果幂运算符^左结合性,则计算式被解析成: ((5^4)^3)^2 ,当然运算结果和上面就不一样了。

序列点Sequence point

边效应Side Effects

最大匹配规则Maximal munch

词法分析器读入能构成有意义标记结构的最多的连续输入(the longest sequence of characters that could constitute a preprocessing token)。

例如:

  • x+++y : 因为从左向右能构成的最大有意义标记是++运算符,所以解析成 (x++)+y ,而不是x+(++y)
  • x+++++y : 同理,被解析成(x++)(++)(+y),所以编译报错。

这里需要注意的是“连续输入”。
例如在x+++y中,三个+字符是连续的,所以被解析成了(x++)+y;但如果是x+ ++y在第一个+符后面跟了空白,那么一般的词法分析器就会把该语句解析成x+(++y)
同理,如果写成x+++ ++y的表达式,就可以编译通过了。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为你介绍一下如何设计一个简单的语法分析器。 1. 定义语法规则 首先,我们需要定义语法规则,即规定单词序列如何组成语句。例如,我们可以定义一个简单的四则运算语言,语法规则如下: ``` <expression> ::= <term> | <expression> <addop> <term> <term> ::= <factor> | <term> <mulop> <factor> <factor> ::= <number> | "(" <expression> ")" <addop> ::= "+" | "-" <mulop> ::= "*" | "/" <number> ::= <digit> | <number> <digit> <digit> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ``` 2. 构建语法树 接下来,我们可以使用递归下降分析法来构建语法树。具体地,我们可以为每个非终结符定义一个函数,该函数负责将该非终结符所对应的语法规则转换成语法树。例如,对于上述四则运算语言,我们可以定义如下函数: ```python def expression(): # <expression> ::= <term> | <expression> <addop> <term> node = term() while token in {"+", "-"}: op = token match(token) right = term() node = BinaryOperatorNode(op, node, right) return node def term(): # <term> ::= <factor> | <term> <mulop> <factor> node = factor() while token in {"*", "/"}: op = token match(token) right = factor() node = BinaryOperatorNode(op, node, right) return node def factor(): # <factor> ::= <number> | "(" <expression> ")" if token == "(": match("(") node = expression() match(")") return node else: return number() def number(): # <number> ::= <digit> | <number> <digit> node = NumberNode(int(token)) match(token) return node ``` 在上述函数中,`match` 函数用于匹配当前单词,如果匹配成功,则将指针指向下一个单词。`BinaryOperatorNode` 表示二元运算符,`NumberNode` 表示数字节。 3. 处理优先级 如果语言中存在运算符优先级结合性等特性,我们也需要在语法分析器中进行处理。例如,对于四则运算语言,乘法和除法的优先级高于加法和减法,因此我们需要在函数中添加相应的处理逻辑: ```python def expression(): # <expression> ::= <term> | <expression> <addop> <term> node = term() while token in {"+", "-"}: op = token match(token) right = term() if op == "+": node = BinaryOperatorNode(op, node, right) else: node = BinaryOperatorNode(op, node, expression()) return node def term(): # <term> ::= <factor> | <term> <mulop> <factor> node = factor() while token in {"*", "/"}: op = token match(token) right = factor() if op == "*": node = BinaryOperatorNode(op, node, right) else: node = BinaryOperatorNode(op, node, term()) return node ``` 在上述代码中,当遇到乘法或除法运算符时,我们将右侧的表达式解析成一个 `factor`,然后递归调用 `term` 函数,直到遇到加法或减法运算符。 4. 错误处理 最后,我们还需要在语法分析器中添加错误处理逻辑,以便在遇到语法错误时能够及时提示用户。例如,在上述四则运算语言中,如果用户输入了无法识别的字符,我们可以在 `match` 函数中添加相应的错误处理逻辑: ```python def match(expected): if token == expected: next_token() else: raise SyntaxError(f"Unexpected token: {token}") ``` 在上述代码中,当遇到无法识别的字符时,我们会抛出一个 `SyntaxError` 异常,并提示用户当前无法识别的字符是什么。 以上就是一个简单的语法分析器的设计流程。当然,实际的语法分析器可能会更加复杂,例如需要支持更多的语法规则、处理更多的特殊情况等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值