块代码生成器

块代码生成器是一种函数,用于生成块的代码并以字符串形式返回。块生成的代码取决于其类型:

  • 值块具有输出连接。这些块的作用类似于基于文本的语言中的表达式,并生成包含表达式的字符串。
  • 语句块是没有输出连接的块。这些块的作用类似于基于文本的语言中的语句,并生成包含语句的字符串。

如何编写块代码生成器

对于您创建的每个自定义块,您都需要为要支持的每种语言编写一个块代码生成器。请注意,所有块代码生成器都是用 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_comparecontrols_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 就可以确定是否需要将内部块的代码括在英文圆括号中。

例如,选中 custom_if 中的 NOT 复选框会将逻辑非运算符 (!) 应用于条件。在这种情况下,您将非运算符的优先级 (Order.LOGICAL_NOT) 传递给 valueToCode,而 valueToCode 会将其与内部块中最弱运算符的优先级进行比较。然后,它会根据需要封装内部块的代码:

  • 如果 CONDITION 是变量块,则 valueToCode 不会添加英文圆括号,因为非运算符可以直接应用于变量 (!myBoolean)。
  • 如果 CONDITION 是比较块,则 valueToCode 会添加英文圆括号,以便 非运算符应用于整个比较 (!(a < b)),而不是 左侧的值 (!a < b)。

您实际上不需要知道 valueToCode 是否添加了英文圆括号。您只需将优先级传递给 valueToCode,并将返回的代码添加到代码字符串中即可。如需了解详情,请参阅 valueToCode precedence

内部语句块

如需从附加到语句输入的内部块获取代码,请调用 statementToCode。此方法会调用内部块的代码生成工具并处理代码缩进。

custom_compare

custom_compare 块没有任何语句输入。

custom_if

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

您只需为直接连接到语句输入的内部块调用 statementToCodestatementToCode 会处理附加到第一个块的任何其他块。

构建并返回代码字符串

获取字段和内部块的代码后,请构建并返回块的代码字符串。您返回的内容取决于块类型:

  • 值块 :返回一个数组,其中包含代码字符串和代码中最弱运算符的优先级。当您的块用作内部块时,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;
}

如果您在代码字符串中多次使用内部值块的代码, 则应缓存该 块 的代码,以避免细微的 bug 和不必要的副作用。

完整的代码生成器

为方便参考,以下是每个块的完整代码生成器:

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