# 块代码生成器
块代码生成器是一个函数,用来为某个块生成代码字符串并返回。
一个块生成什么代码,取决于它的块类型:
- 值块有输出连接。它在文本语言里更像表达式,生成的是表达式代码字符串。
- 语句块没有输出连接。它在文本语言里更像语句,生成的是语句代码字符串。
# 如何编写块代码生成器
你创建的每个自定义块,都需要为每个目标语言各写一个块代码生成器。
即使最终生成的是其他语言代码,这些块代码生成器本身也都用 JavaScript 编写。
所有块代码生成器都遵循同一流程:
- 导入语言代码生成器。
- 读取每个字段值,并转换为代码字符串。
- 读取内部块生成的代码字符串(值输入和语句输入上连接的块)。
- 拼接并返回当前块的代码字符串。
# 示例块
下面用两个示例块演示 JavaScript 代码生成器的写法。
custom_compare是一个值块,包含:- 名为
LEFT的值输入 - 名为
OPERATOR的下拉字段 - 名为
RIGHT的数字字段
它生成形如'0 = 0'的表达式字符串。
- 名为

custom_if是一个语句块,包含:- 名为
NOT的复选框字段 - 名为
CONDITION的值输入 - 名为
THEN的语句输入
它生成形如'if (...) {\n...\n};\n'的语句字符串。
- 名为

本文按步骤搭建这两个生成器,完整版本见文末“完整代码生成器”。
这两个块仅用于演示代码生成思路。实际项目中可优先使用内置的 logic_compare 和 controls_if。
# 导入语言代码生成器
你可以用以下任一种方式导入语言代码生成器。
导入后,把自定义块的块代码生成器挂到对应生成器的 forBlock 对象上。
# 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';
// 为 custom_if 块添加块代码生成器。
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>
// 为 custom_if 块添加块代码生成器。
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>
// 为 custom_if 块添加块代码生成器。
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) {
// 用 OPERATOR 字段值查表得到真正运算符。
const OPERATORS = {
EQUALS: '==',
LESS: '<',
GREATER: '>',
};
const operator = OPERATORS[block.getFieldValue('OPERATOR')];
// RIGHT 是数字字段,可直接用于拼接代码。
const right = block.getFieldValue('RIGHT');
// ...
};
# custom_if
javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
// 用 NOT 字段值决定是否拼接取反运算符。
const checkbox = block.getFieldValue('NOT');
const negate = checkbox === 'TRUE' ? '!' : '';
// ...
};
更多见转换字段值。
# 获取内部块代码
内部块是连接在当前块值输入和语句输入上的块。
例如 custom_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 会用它判断是否要给内部块代码补括号。
例如 custom_if 勾选 NOT 后,会对条件应用逻辑非 !。
这时传入 Order.LOGICAL_NOT,valueToCode 会和内部块的最低优先级比较,并按需加括号:
- 如果
CONDITION是变量块,会得到!myBoolean,不需要额外括号。 - 如果
CONDITION是比较块,会得到!(a < b),避免错误的!a < b。
你不需要手工判断是否加括号,只需正确传入优先级并拼接返回值。
更多见运算符优先级。
# 内部语句块
从语句输入连接的内部块读取代码,使用 statementToCode。
它会调用内部块生成器,并自动处理缩进。
# custom_compare
custom_compare 没有语句输入。
# custom_if
javascriptGenerator.forBlock['custom_if'] = function (block, generator) {
// ...
const statements = generator.statementToCode(block, 'THEN');
// ...
};
你只需要对语句输入直接连接的第一个块调用 statementToCode,后续连接的语句块会由它一并处理。
# 构建并返回代码字符串
拿到字段值和内部块代码后,就可以拼接并返回当前块代码。返回值取决于块类型:
- 值块:返回
[code, order],其中order是当前代码最低优先级,供外层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;
};