块代码生成器是一种函数,用于生成块的代码并以字符串形式返回。块生成的代码取决于其类型:
- 值块具有输出连接。这些块的作用类似于基于文本的语言中的表达式,并生成包含表达式的字符串。
- 语句块是没有输出连接的块。这些块的作用类似于基于文本的语言中的语句,并生成包含语句的字符串。
如何编写块代码生成器
对于您创建的每个自定义块,您都需要为要支持的每种语言编写一个块代码生成器。请注意,所有块代码生成器都是用 JavaScript 编写的,即使它们生成的是其他语言的代码也是如此。
所有块代码生成器都执行以下步骤:
- 导入语言代码生成工具。
- 获取每个字段的值,并将其转换为代码字符串。
- 获取由内部块生成的代码字符串,这些内部块是附加到值和语句输入的块。
- 构建并返回块的代码字符串。
示例块
作为示例,我们将为以下块编写 JavaScript 代码生成器。
custom_compare是一个值块,它具有一个名为LEFT的值输入、一个名为OPERATOR的下拉字段和一个名为RIGHT的数字字段。它 会生成'0 = 0'形式的表达式字符串。
custom_if是一个语句块,它具有一个名为NOT的复选框字段、一个名为CONDITION的值输入和一个名为THEN的语句输入。它会生成'if (...) {\n...\n};\n'形式的语句字符串。
本文档将逐步构建生成器。您可以在本文档末尾找到完整的 生成器。
请注意,这些块仅用于说明代码生成。在实际应用中,请使用内置的 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 就可以确定是否需要将内部块的代码括在英文圆括号中。
例如,选中 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');
...
}
您只需为直接连接到语句输入的内部块调用 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;
}
如果您在代码字符串中多次使用内部值块的代码, 则应缓存该 块 的代码,以避免细微的 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;
}