A block-code generator is a function that generates the code for a block and returns it as a string. What code a block generates depend on its type:
- Value blocks have an output connection. These blocks act like expressions in a text-based language and generate strings that contain expressions.
- Statement blocks are blocks without an output connection. These blocks act like statements in a text-based language and generate strings that contain statements.
How to write a block-code generator
For each custom block you create, you need to write a block-code generator for each language you want to support. Note that all block-code generators are written in JavaScript, even if they generate code in another language.
All block-code generators perform the following steps:
- Import a language code generator.
- Get the value of each field and transform it into a code string.
- Get the code strings generated by inner blocks, which are blocks attached to value and statement inputs.
- Build and return the block's code string.
Example blocks
As examples, we will write JavaScript code generators for the following blocks.
custom_compare
is a value block that has a value input namedLEFT
, a dropdown field namedOPERATOR
, and a numeric field namedRIGHT
. It generates an expression string of the form'0 = 0'
.custom_if
is a statement block that has a checkbox field namedNOT
, a value input namedCONDITION
, and a statement input namedTHEN
. It generates a statement string of the form'if (...) {\n...\n};\n'
.
This document builds the generators step by step. You can find the completed generators at the end of this document.
Note that these blocks are intended only to illustrate code generation. In a
real application, use the built-in logic_compare
and controls_if
blocks.
Import a language code generator
You can import a language code generator using one of the following methods. Use
the imported generator to store block-code generators in the forBlock
object.
Modules
import {javascriptGenerator} from 'blockly/javascript';
import {pythonGenerator} from 'blockly/python';
import {phpGenerator} from 'blockly/php';
import {luaGenerator} from 'blockly/lua';
import {dartGenerator} from 'blockly/dart';
// Add block-code generators for the custom_if block.
javascriptGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
pythonGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
phpGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
luaGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
dartGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
Unpkg
You must include the generator after you include Blockly.
<script src="https://unpkg.com/blockly"></script>
<script src="https://unpkg.com/blockly/javascript_compressed"></script>
<script src="https://unpkg.com/blockly/python_compressed"></script>
<script src="https://unpkg.com/blockly/php_compressed"></script>
<script src="https://unpkg.com/blockly/lua_compressed"></script>
<script src="https://unpkg.com/blockly/dart_compressed"></script>
// Add block-code generators for the custom_if block.
javascript.javascriptGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
python.pythonGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
php.phpGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
lua.luaGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
dart.dartGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
Local scripts
You must include the generator after you include Blockly.
<script src="blockly_compressed.js"></script>
<script src="javascript_compressed.js"></script>
<script src="python_compressed.js"></script>
<script src="php_compressed.js"></script>
<script src="lua_compressed.js"></script>
<script src="dart_compressed.js"></script>
// Add block-code generators for the custom_if block.
javascript.javascriptGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
python.pythonGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
php.phpGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
lua.luaGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
dart.dartGenerator.forBlock['custom_if'] = function (block, generator) { /* ... */ };
Get field values
Fields allow users to enter values like strings, numbers, and colours. To get a
field's value, call getFieldValue
. What is returned is different from field to
field. For example, text fields return the exact text entered by the user, but
dropdown fields return a language-neutral string associated with the item the
user selected. For more information, see the documentation for built-in
fields.
Depending on the field, you may need to transform the returned value before using it in code.
custom_compare
javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
// Use the value of the OPERATOR dropdown to look up the actual operator.
const OPERATORS = {
EQUALS: '==',
LESS: '<',
GREATER: '>',
};
const operator = OPERATORS[block.getFieldValue('OPERATOR')];
// The value of the RIGHT field is a number and can be used directly when
// building the block's code string.
const right = block.getFieldValue('RIGHT');
...
}
custom_if
javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
// Use the value of the NOT field to get the negation operator (if any).
const checkbox = block.getFieldValue('NOT');
const negate = checkbox === 'TRUE' ? '!' : '';
...
}
For more information, see Transform field values.
Get code from inner blocks
Inner blocks are the blocks attached to a block's value and statement inputs.
For example, the custom_if
block has a value inner block for the if condition,
and statement inner blocks for the code that is executed if the condition is
true.
Unlike field values, the code you get from inner blocks is ready to go and does not need to be transformed.
Inner value blocks
To get code from an inner block attached to a value input, call valueToCode
.
This method calls the inner block's code generator.
custom_compare
import {javascriptGenerator, Order} from 'blockly/javascript';
javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
...
const order = operator === '==' ? Order.EQUALITY : Order.RELATIONAL;
const left = generator.valueToCode(block, 'LEFT', order);
...
}
custom_if
import {javascriptGenerator, Order} from 'blockly/javascript';
javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
...
const order = checkbox === 'TRUE' ? Order.LOGICAL_NOT : Order.NONE;
const condition = generator.valueToCode(block, 'CONDITION', order) || 'false';
...
}
When you call valueToCode
, you need to tell it about the strongest operator in
your code that will apply to the inner block's code. This allows valueToCode
to determine whether it needs to wrap the inner block's code in parentheses.
For example, checking the NOT
box in custom_if
applies a logical not
operator (!
) to the condition. In this case, you pass the not operator's
precedence (Order.LOGICAL_NOT
) to valueToCode
and valueToCode
compares
this to the precedence of the weakest operator in the inner block. It then wraps
the inner block's code as needed:
- If
CONDITION
is a variable block,valueToCode
doesn't add parentheses because the not operator can be applied directly to a variable (!myBoolean
). - If
CONDITION
is a comparison block,valueToCode
adds parentheses so the not operator applies to the entire comparison (!(a < b)
) instead of the left-hand value (!a < b
).
You don't actually need to know whether valueToCode
added parentheses. All you
need to do is pass the precedence to valueToCode
and add the returned code to
your code string. For more information, see valueToCode
precedence.
Inner statement blocks
To get code from an inner block attached to a statement input, call
statementToCode
. This method calls the inner block's code generator and
handles indenting code.
custom_compare
The custom_compare
block does not have any statement inputs.
custom_if
javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
...
const statements = generator.statementToCode(block, 'THEN');
...
}
You only need to call statementToCode
for the inner block directly connected
to a statement input. statementToCode
handles any additional blocks attached
to the first block.
Build and return your code string
After you have gotten the code for the fields and inner blocks, build and return the code string for your block. Exactly what you return depends on your block type:
Value blocks: Return an array containing the code string and the precedence of the weakest operator in your code.
valueToCode
uses this to decide if your code needs to be wrapped in parentheses when your block is used as an inner block. For more information, see Return precedence.Statement blocks: Return the code string.
custom_compare
import {javascriptGenerator, Order} from 'blockly/javascript';
javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
...
const order = operator === '==' ? Order.EQUALITY : Order.RELATIONAL;
...
const code = left + ' ' + operator + ' ' + right;
return [code, order];
}
custom_if
javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
...
const code = 'if (' + negate + condition + ') {\n' + statements + '}\n';
return code;
}
If you use the code of an inner value block multiple times in your code string, you should cache the code from that block to avoid subtle bugs and unwanted side-effects.
Complete code generators
For reference, here are the complete code generators for each block:
custom_compare
import {javascriptGenerator, Order} from 'blockly/javascript';
javascriptGenerator.forBlock['custom_compare'] = function (block, generator) {
const OPERATORS = {
EQUALS: '==',
LESS: '<',
GREATER: '>',
};
const operator = OPERATORS[block.getFieldValue('OPERATOR')];
const order = operator === '==' ? Order.EQUALITY : Order.RELATIONAL;
const left = generator.valueToCode(block, 'LEFT', order);
const right = block.getFieldValue('RIGHT');
const code = left + ' ' + operator + ' ' + right;
return [code, order];
}
custom_if
import {javascriptGenerator, Order} from 'blockly/javascript';
javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
const checkbox = block.getFieldValue('NOT');
const negate = checkbox === 'TRUE' ? '!' : '';
const order = checkbox === 'TRUE' ? Order.LOGICAL_NOT : Order.NONE;
const condition = generator.valueToCode(block, 'CONDITION', order) || 'false';
const statements = generator.statementToCode(block, 'THEN');
const code = 'if (' + negate + condition + ') {\n' + statements + '}\n';
return code;
}