Code generators are used to convert Blockly's programs into JavaScript, Python, PHP, Lua, Dart, etc. The most challenging issue when writing a code generator for a new block is handling the order of operations so that the resulting code executes as intended. For more information on operator precedence you can also watch our 2021 Operator Precedence talk.
Bad Parentheses
Consider the following assembly of blocks.
If the generators were not aware of operator precedence, the resulting JavaScript code would be:
alert(2 * 3 + 4);
This is obviously incorrect, since the multiplication operator rips apart the addition, grabbing the '3' for itself. One solution is to wrap the result of every value block in parentheses:
alert(((2) * ((3) + (4)));
This solution works perfectly, but it results in extremely messy code with large numbers of redundant parentheses. For some use cases this is not an issue. If human eyes are never going to see the generated code, then this is acceptable. However, Blockly is often used as an educational tool to introduce programming, a use case which relies on generating human-readable code.
Good Parentheses
To generate correct code without an unreasonable number of parentheses, each language generator is provided with an ordered list of precedences. Here is the list for JavaScript, ordered from highest to lowest precedence:
ORDER_ATOMIC = 0; // 0 "" ... ORDER_NEW = 1.1; // new ORDER_MEMBER = 1.2; // . [] ORDER_FUNCTION_CALL = 2; // () ORDER_INCREMENT = 3; // ++ ORDER_DECREMENT = 3; // -- ORDER_BITWISE_NOT = 4.1; // ~ ORDER_UNARY_PLUS = 4.2; // + ORDER_UNARY_NEGATION = 4.3; // - ORDER_LOGICAL_NOT = 4.4; // ! ORDER_TYPEOF = 4.5; // typeof ORDER_VOID = 4.6; // void ORDER_DELETE = 4.7; // delete ORDER_AWAIT = 4.8; // await ORDER_EXPONENTIATION = 5.0; // ** ORDER_MULTIPLICATION = 5.1; // * ORDER_DIVISION = 5.2; // / ORDER_MODULUS = 5.3; // % ORDER_SUBTRACTION = 6.1; // - ORDER_ADDITION = 6.2; // + ORDER_BITWISE_SHIFT = 7; // << >> >>> ORDER_RELATIONAL = 8; // < <= > >= ORDER_IN = 8; // in ORDER_INSTANCEOF = 8; // instanceof ORDER_EQUALITY = 9; // == != === !== ORDER_BITWISE_AND = 10; // & ORDER_BITWISE_XOR = 11; // ^ ORDER_BITWISE_OR = 12; // | ORDER_LOGICAL_AND = 13; // && ORDER_LOGICAL_OR = 14; // || ORDER_CONDITIONAL = 15; // ?: ORDER_ASSIGNMENT = 16; // = += -= **= *= /= %= <<= >>= ... ORDER_YIELD = 16.5; // yield ORDER_COMMA = 17; // , ORDER_NONE = 99; // (...)
The bulk of this list is taken directly from JavaScript's
language spec,
with ORDER_ATOMIC
added to the start and ORDER_NONE
added to the end.
Applying these orders occurs in two places within each block's generator. The first place is when fetching generated code from a connected value block. In this case we pass the constant which represents the maximum binding strength of any operators adjacent to the sub-block's generated code. For example:
var arg0 = javascriptGenerator.valueToCode(this, 'NUM1', javascriptGenerator.ORDER_DIVISION);
The second place is when returning generated code from a value block. In this case we pass the constant which represents the minimum binding strength of any operators in the block's generated code. For example:
return [arg0 + ' / ' + arg1, javascriptGenerator.ORDER_DIVISION];
If the order value returned by the sub-block is weaker than or equal to the
order value for the order argument of the parent block, then the valueToCode
function will automatically wrap the contents of the sub-block's code in
parentheses to prevent it from being ripped apart by the parent block's code.
Below are some more examples. In each case the block has one connected sub-block which is represented as 'X' (the contents of 'X' is unknown and doesn't matter). The second column lists the strongest operator which might split 'X'. The third column lists the weakest operator in the final code for the block.
Generated Code | Max strength against X | Min strength of block |
---|---|---|
X + 1 |
ORDER_ADDITION |
ORDER_ADDITION |
Math.sqrt(X) |
ORDER_NONE |
ORDER_FUNCTION_CALL |
!X && false |
ORDER_LOGICAL_NOT |
ORDER_LOGICAL_AND |
foo[X % 60] |
ORDER_MODULUS |
ORDER_MEMBER |
Math is hard
Still don't understand? No problem. Just use ORDER_ATOMIC
as the order on
every call to valueToCode
, and use ORDER_NONE
as the order for the final
return statement on every value block. The resulting code will be infested with
needless parentheses, but is guaranteed to be correct.