ブロックコード ジェネレータ

ブロックコード ジェネレータは、ブロックのコードを生成して文字列として返す関数です。ブロックが生成するコードは、ブロックのタイプによって異なります。

  • 値ブロックには出力接続があります。これらのブロックは、テキストベースの言語の式のように機能し、式を含む文字列を生成します。
  • ステートメント ブロックは、出力接続のないブロックです。これらのブロックは、テキストベースの言語のステートメントのように機能し、ステートメントを含む文字列を生成します。

ブロックコード生成ツールを作成する方法

作成するカスタムブロックごとに、サポートする言語ごとにブロックコード ジェネレータを記述する必要があります。ブロックコード ジェネレータは、別の言語でコードを生成する場合でも、すべて 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) { /* ... */ };

フィールド値を取得する

フィールドを使用すると、ユーザーは文字列、数値、色などの値を入力できます。フィールドの値を取得するには、getFieldValue を呼び出します。返される内容はフィールドごとに異なります。たとえば、テキスト フィールドはユーザーが入力したテキストをそのまま返しますが、プルダウン フィールドはユーザーが選択した項目に関連付けられた言語中立の文字列を返します。詳細については、組み込みフィールドのドキュメントをご覧ください。

フィールドによっては、コードで使用する前に戻り値を変換する必要があります。

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 条件の値の内部ブロックと、条件が true の場合に実行されるコードのステートメント内部ブロックがあります。

フィールド値とは異なり、インナーブロックから取得したコードはすぐに使用でき、変換する必要はありません。

内部値ブロック

値入力に接続された内部ブロックからコードを取得するには、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 に伝える必要があります。これにより、valueToCode は内部ブロックのコードをかっこで囲む必要があるかどうかを判断できます。

たとえば、custom_ifNOT チェックボックスをオンにすると、条件に論理否定演算子(!)が適用されます。この場合、not 演算子の優先順位(Order.LOGICAL_NOT)を valueToCode に渡し、valueToCode はこれを内部ブロックの最も弱い演算子の優先順位と比較します。必要に応じて、内部ブロックのコードをラップします。

  • CONDITION が変数ブロックの場合、否定演算子は変数(!myBoolean)に直接適用できるため、valueToCode はかっこを追加しません。
  • 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;
}