Генераторы блочного кода

Генератор блочного кода — это функция, которая генерирует код для блока и возвращает его в виде строки. Какой код генерирует блок, зависит от его типа:

  • Блоки значений имеют выходное соединение. Эти блоки действуют подобно выражениям в текстовом языке и генерируют строки, содержащие выражения.
  • Блоки операторов — это блоки без выходного соединения. Эти блоки действуют как операторы в текстовом языке и генерируют строки, содержащие операторы.

Как написать генератор блочного кода

Для каждого создаваемого пользовательского блока необходимо написать генератор кода для каждого поддерживаемого языка. Обратите внимание, что все генераторы кода написаны на JavaScript, даже если они генерируют код на другом языке.

Все генераторы блочного кода выполняют следующие шаги:

  1. Импортируйте генератор языковых кодов.
  2. Получите значение каждого поля и преобразуйте его в кодовую строку.
  3. Получите кодовые строки, сгенерированные внутренними блоками, которые представляют собой блоки, прикрепленные к входным данным в виде значений и операторов.
  4. Сформировать и вернуть строку кода блока.

Примеры блоков

В качестве примеров мы напишем генераторы кода JavaScript для следующих блоков.

  • custom_compare — это блок значений, содержащий поле ввода значения с именем LEFT , выпадающее поле с именем OPERATOR и числовое поле с именем RIGHT . Он генерирует строковое выражение вида '0 = 0' .

    Блок пользовательских значений для сравнений.

  • custom_if — это блок операторов, содержащий поле флажка с именем NOT , поле ввода значения с именем CONDITION и поле ввода оператора THEN . Он генерирует строку оператора вида 'if (...) {\n...\n};\n' .

    Пользовательский блок операторов для условного оператора if. Пользователи могут использовать флажок для отмены условия if.

В этом документе пошагово описана сборка генераторов. Готовые генераторы вы найдете в конце документа .

Обратите внимание, что эти блоки предназначены только для иллюстрации генерации кода. В реальном приложении используйте встроенные блоки logic_compare и controls_if .

Импорт генератора языковых кодов

Вы можете импортировать генератор кодов языка одним из следующих способов. Используйте импортированный генератор для хранения генераторов блочных кодов в объекте forBlock .

Модули

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

Генератор необходимо добавить после 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) { /* ... */ };

Локальные скрипты

Генератор необходимо добавить после 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) { /* ... */ };

Получение значений полей

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 .

В зависимости от поля, перед использованием возвращаемого значения в коде может потребоваться его преобразование.

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' ? '!' : '';
  ...
}

Для получения более подробной информации см. раздел «Преобразование значений полей» .

Получение кода из внутренних блоков

Внутренние блоки — это блоки, прикрепленные к входным блокам значения и оператора. Например, блок custom_if имеет внутренний блок значения для условия if и внутренние блоки оператора для кода, который выполняется, если условие истинно.

В отличие от значений полей, код, получаемый из внутренних блоков, готов к использованию и не требует преобразования.

Внутренние блоки значений

Чтобы получить код из внутреннего блока, подключенного к входному значению, вызовите метод valueToCode . Этот метод вызывает генератор кода внутреннего блока.

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

При вызове функции valueToCode необходимо указать самый строгий оператор в вашем коде, который будет применяться к коду внутреннего блока. Это позволит valueToCode определить, нужно ли заключать код внутреннего блока в скобки.

Например, установка флажка NOT в custom_if применяет логический оператор отрицания ( ! ) к условию. В этом случае вы передаете приоритет оператора отрицания ( Order.LOGICAL_NOT ) в valueToCode , и valueToCode сравнивает его с приоритетом самого слабого оператора во внутреннем блоке. Затем он соответствующим образом оборачивает код внутреннего блока:

  • Если CONDITION — это блок переменных, valueToCode не добавляет скобки, поскольку оператор not можно применить непосредственно к переменной ( !myBoolean ).
  • Если CONDITION представляет собой блок сравнения, valueToCode добавляет скобки, так что оператор not применяется ко всему сравнению ( !(a < b) ) вместо значения слева ( !a < b ).

На самом деле вам не нужно знать, добавила ли valueToCode скобки. Все, что вам нужно сделать, это передать приоритет функции valueToCode и добавить возвращенный код в вашу строку кода. Для получения дополнительной информации см. раздел «Приоритет функции valueToCode» .

Внутренние блоки операторов

Чтобы получить код из внутреннего блока, прикрепленного к входному оператору, вызовите statementToCode . Этот метод вызывает генератор кода внутреннего блока и обрабатывает отступы в коде.

custom_compare

Блок custom_compare не содержит входных данных для операторов.

custom_if

javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
  ...
  const statements = generator.statementToCode(block, 'THEN');
  ...
}

Вызов функции statementToCode необходим только для внутреннего блока, напрямую подключенного к входному сигналу оператора. statementToCode обрабатывает любые дополнительные блоки, подключенные к первому блоку.

Сформируйте и верните строку с кодом.

После получения кода для полей и внутренних блоков, сформируйте и верните строку кода для вашего блока. То, что именно вы вернете, зависит от типа вашего блока:

  • Блоки значений: Возвращает массив, содержащий строку кода и приоритет самого слабого оператора в вашем коде. valueToCode использует это для определения необходимости заключения кода в скобки при использовании блока в качестве внутреннего блока. Для получения дополнительной информации см. раздел «Возвращаемый приоритет» .

  • Блоки операторов: Возвращают строку кода.

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

Если вы используете код внутреннего блока значений несколько раз в своей строке кода, вам следует кэшировать код из этого блока, чтобы избежать скрытых ошибок и нежелательных побочных эффектов.

Полные генераторы кода

Для справки, ниже приведены полные генераторы кода для каждого блока:

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