Block-code generators

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:

  1. Import a language code generator.
  2. Get the value of each field and transform it into a code string.
  3. Get the code strings generated by inner blocks, which are blocks attached to value and statement inputs.
  4. 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 named LEFT, a dropdown field named OPERATOR, and a numeric field named RIGHT. It generates an expression string of the form '0 = 0'.

    Custom value block for
comparisons.

  • custom_if is a statement block that has a checkbox field named NOT, a value input named CONDITION, and a statement input named THEN. It generates a statement string of the form 'if (...) {\n...\n};\n'.

    Custom statement block for an if statement. Users can use a checkbox to
negate the if condition.

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;
}