# 运算符优先级

块天然带有分组含义。
例如看到下面的块,你会理解为 -(5 + 2),而不是 -5 + 2:因为 52 属于同一个块,- 属于外层另一个块。

块结构对应

但如果给每个块都加括号,代码可读性会明显下降。
比如 (((5) * (2)) + (3))5 * 2 + 3 结果都为 13,后者更易读。

Blockly 的运算符优先级规则,就是在保证语义正确的前提下,尽量少加括号。

# 生成“正确”输出

如果你不要求生成代码给人阅读,那么不必刻意最小化括号。
给每个块都包上括号是可行方案,而且能保证执行语义正确。

想要优先保证正确性时,可用这条固定策略:

  • 调用 valueToCode 时统一传 Order.ATOMIC
  • 值块代码生成器统一返回 Order.NONE

# 生成最优括号

只有在“不加括号会改变语义”时,才需要插入括号。
典型场景是:外层块运算符优先级强于内层块运算符优先级。

说明

优先级就是运算符的计算顺序。比如基础算术常用的 PEMDAS (opens new window)

下面示例里同时存在一元负号与加法,其中一元负号优先级更强:

一元负号与加法

如果不加括号会得到 -5 + 2- 会先于 + 执行,语义与块结构不一致。

你可以通过“告知生成器各运算符强度”来控制何时加括号:
当生成器发现“外层运算符强于内层运算符”时,会自动给内层代码加括号。

valueToCode 接收外层运算符优先级,返回元组里的优先级用于表示当前块内部运算符优先级。

下面是一个包含两个运算符的示例块:

包含子块的示例

import {javascriptGenerator, Order} from 'blockly/javascript';

javascriptGenerator.forBlock['negate_plus_two'] = function(block, generator) {
  // valueToCode 传入外层运算符优先级。
  const innerCode = generator.valueToCode(block, 'INNER', Order.UNARY_NEGATION);
  const code = `-${innerCode} + 2`;
  // 返回元组中的优先级表示当前块内部运算符优先级。
  return [code, Order.ADDITION];
};

# valueToCode 优先级

调用 valueToCode 生成内部块代码时,应传入“作用在该内部代码上的最强运算符优先级”。
也就是内部代码需要被保护不被拆开的那个运算符。

例如下面这个块里,一元负号和加法都作用在内部块代码上,而一元负号更强,所以应传 Order.UNARY_NEGATION

包含子块的示例

// - 是作用在内部代码上的最强运算符。
const innerCode = generator.valueToCode(block, 'INNER', Order.UNARY_NEGATION);
const code = `-${innerCode} + 2`;

# 返回优先级

从块代码生成器返回优先级时,应返回“当前块代码里最弱运算符”的优先级。
这是最需要被外层保护的运算符。

例如下面这个块包含一元负号和加法,其中加法更弱,所以应返回 Order.ADDITION

不含子块的示例

const code = `-${innerCode} + 2`;
// + 是当前块里的最弱运算符。
return [code, Order.ADDITION];

# Order 枚举

每个语言生成器模块都定义了自己的 Order 枚举,包含该语言全部运算符优先级。

强优先级对应更小的底层值,弱优先级对应更大的底层值。
可以理解为:越“强”的优先级,排序越靠前。

内置语言的 Order 定义:

# 特殊优先级

大多数 Order 项与目标文本语言保持一致。
但有两个特殊值:Order.ATOMICOrder.NONE

Order.ATOMIC 是最强优先级,常用于:

  • 你希望始终加括号,因此把它传给 valueToCode
  • 块本身不含运算符,因此从块代码生成器返回它

Order.NONE 是最弱优先级,常用于:

  • 你希望始终加括号,因此从块代码生成器返回它
  • 作用在内部块上的运算符为空,因此把它传给 valueToCode