Blocks imply parentheses. For example, when you see the following blocks, you
assume it means -(5 + 2)
not -5 + 2
because the 5
and the 2
are part of
one block, and the -
is part of another block.
But if you put parentheses around every block, it makes the code much less
readable. Compare (((5) * (2)) + (3))
with 5 * 2 + 3
. Both of these
expressions evaluate to the same thing (13
) but the second one is much easier
to read.
Blockly's operator precedence rules help you generate code with the minimum number of parentheses, for maximum readability.
Generate "correct" output
If you don't need your generated code to be human-readable, then there's no need to worry about minimizing parentheses. Wrapping every block is a fine approach, and it ensures that your generated code is always evaluated correctly.
To ensure correctness, always pass Order.ATOMIC
to valueToCode
calls, and
always return Order.NONE
from your block-code generator.
Generate optimal parentheses
Parentheses only need to be inserted if the generated code is incorrect without them. This happens when the precedence of an operator in the outer block is stronger than the precedence of an operator in the inner block.
For example, in the following blocks there is a unary negation operator and an addition operator. The unary negation has a stronger precedence than the addition operator.
So if you don't add parentheses you get -5 + 2
, and the -
is evaluated
before the +
, which is doesn't match the blocks.
You can tell the generator when to insert parentheses by telling it how strong your different operators are. If it sees that the outer operator is stronger than the inner operator, it inserts parentheses to protect the inner operator.
valueToCode
takes in the precedence of the outer operator, and the return
tuple specifies the precedence of the inner operator.
Here is an example for a block that includes two operators:
import {javascriptGenerator, Order} from 'blockly/javascript';
javascriptGenerator.forBlock['negate_plus_two'] = function(block, generator) {
// valueToCode takes in the precedence of the outer operator.
const innerCode = generator.valueToCode(block, 'INNER', Order.UNARY_NEGATION);
const code = `-${innerCode} + 2`;
// The return tuple specifies the precedence of the inner operator.
return [code, Order.ADDITION];
}
valueToCode precedence
When you call valueToCode
to generate the code of an inner block, you pass it
the precedence of the strongest operator acting on the code of the inner
block. This is the operator the inner block's code needs to be protected from.
For example, in the following blocks both the unary negation operator and the
addition operator are acting on the code of the inner block. The unary negation
is stronger, so that is the precedence you should pass to valueToCode
.
// The - is the strongest operator acting on the inner code.
const innerCode = generator.valueToCode(block, 'INNER', Order.UNARY_NEGATION);
const code = `-${innerCode} + 2`;
Return precedence
When you return a precedence from your block-code generator, return the precedence of the weakest operator within the code of the block. This is the operator that needs protecting.
For example, the following block contains both a unary negation operator and an addition operator. The addition is weaker, so that is the precedence you should return from the block-code generator.
const code = `-${innerCode} + 2`;
// The + is the weakest operator in the block.
return [code, Order.ADDITION];
Order enum
Every language generator module defines an Order
enum which includes all of
the precedences for that language.
Stronger precedences have lower backing values, and weaker precedences have higher backing values. You can think of strong precedences as being "ranked higher" in strength, and weaker precedences as being "ranked lower" - as if they were competitive fighters.
Here is where you can find the Order
enums for all of the built-in languages:
Special precedences
Most of the precedences in the generators' Order
enums match the precedences
defined by their respective text-based languages. But there are two special
precedences, Order.ATOMIC
and Order.NONE
.
Order.ATOMIC
is the strongest precedence. It is used when:
- You want to ensure code is always parenthesized,
so you pass it to
valueToCode
. - Your block doesn't include any operators, so you return it from your block-code generator.
Order.NONE
is the weakest precedence. It is used when:
- You want to ensure code is always parenthesized, so you return it from your block-code generator.
- There are no operators acting on an inner block, so you pass it to
valueToCode
.