Blockly 是一个由 Google 开发的可视化编程库,它通过拖拽积木块的方式让用户(尤其是初学者)创建程序,而将 Blockly 的逻辑转换成像 C 语言这样的文本代码,通常需要一个“代码生成器”(Code Generator)。

下面我将从 核心概念、实现步骤、完整示例 和 高级技巧 四个方面进行详细说明。
核心概念:代码生成器
Blockly 本身不直接生成代码,它只生成一个代表程序逻辑的 XML 或 JSON 数据结构,你需要编写一个“代码生成器”来遍历这个数据结构,并根据每个积木块的类型,生成对应的 C 语言代码。
这个过程可以想象成: Blockly 积木块 -> 程序逻辑 (JSON) -> 代码生成器 -> C 语言源代码
实现步骤:从零开始搭建一个简单的 C 语言代码生成器
假设我们要创建一个简单的 Blockly 工作区,它可以实现“....”和打印“Hello, World!”的功能。

定义积木块
我们需要在 Blockly 的定义文件(通常是 .xml 或使用 JavaScript 动态创建)中定义我们需要的积木块。
blocks.xml
<xml id="toolbox" style="display: none">
<category name="逻辑" colour="210">
<block type="controls_if"></block>
</category>
<category name="文本" colour="160">
<block type="text_print"></block>
<block type="text"></block>
</category>
<category name="变量" colour="330" custom="VARIABLE"></category>
</xml>
这里我们定义了三个积木块:
controls_if: 标准if语句积木。text_print: 打印文本的积木。text: 一个包含文本输入的积木。
编写代码生成器
这是最关键的一步,我们需要为每个自定义的积木块类型编写一个 JavaScript 函数,这个函数会告诉 Blockly 如何将该积木块转换为 C 代码。
在 HTML 文件中,我们需要初始化 Blockly 并注册这些代码生成器。
index.html (关键部分)
<script>
// 为 'text_print' 积木注册代码生成器
Blockly.Blocks['text_print'] = {
init: function() {
this.appendDummyInput()
.appendField("打印")
.appendField(new Blockly.FieldTextInput("hello"), "TEXT");
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(160);
this.setTooltip("打印文本到控制台");
this.setHelpUrl("");
}
};
// 这是代码生成器的核心部分
Blockly.JavaScript['text_print'] = function(block) {
// 从积木块中获取 TEXT 字段的值
const text = block.getFieldValue('TEXT');
// 生成 C 语言的 printf 语句
// 注意:C语言需要转义双引号,所以用 \"
const code = 'printf("' + text + '\\n");\n';
// 返回生成的代码
return code;
};
// 为 'controls_if' 积木注册代码生成器
// Blockly 已经内置了 JavaScript 的生成器,但我们需要为 C 语言重写
Blockly.JavaScript['controls_if'] = function(block) {
let n = 0;
let code = '';
// 遍历 if 积木的所有分支
do {
const conditionCode = Blockly.JavaScript.valueToCode(block, 'IF' + n, Blockly.JavaScript.ORDER_NONE) || 'false';
const branchCode = Blockly.JavaScript.statementToCode(block, 'DO' + n) || '';
if (n === 0) {
code += 'if (' + conditionCode + ') {\n' + branchCode + '}';
} else {
code += ' else if (' + conditionCode + ') {\n' + branchCode + '}';
}
n++;
} while (block.getInput('IF' + n));
// 处理 else 分支
const elseCount = block.elseCount_;
if (elseCount > 0) {
const elseCode = Blockly.JavaScript.statementToCode(block, 'ELSE');
code += ' else {\n' + elseCode + '}';
}
code += '\n';
return code;
};
// 初始化 Blockly 工作区
function initWorkspace() {
const workspace = Blockly.inject('blocklyDiv', {
toolbox: document.getElementById('toolbox')
});
}
</script>
生成和执行代码
我们需要一个按钮来触发代码生成,并将生成的代码显示在 <textarea> 中,为了运行 C 代码,我们需要一个编译和执行的环境,在浏览器中直接运行 C 代码是不安全的,因此我们通常使用 Emscripten 这样的工具,它可以将 C 编译成 WebAssembly (WASM),然后在浏览器中运行。
index.html (完整结构)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">Blockly C 生成器</title>
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
<script src="https://unpkg.com/blockly/blocks_compressed.js"></script>
<script src="https://unpkg.com/blockly/javascript_compressed.js"></script>
<script src="https://unpkg.com/blockly/msg/zh-hans.js"></script>
<style>
#blocklyDiv { height: 480px; width: 60%; float: left; }
#codeDiv { height: 480px; width: 40%; float: right; }
</style>
</head>
<body>
<h1>Blockly C 语言代码生成器</h1>
<div id="blocklyDiv"></div>
<div id="codeDiv">
<textarea id="codeOutput" style="width: 100%; height: 400px;"></textarea>
<button onclick="generateCode()">生成 C 代码</button>
<button onclick="runCode()">运行代码</button>
</div>
<!-- 步骤一和步骤二的 JavaScript 代码放在这里 -->
<script>
// ... (将上面步骤二中的所有 JavaScript 代码放在这里) ...
// 生成代码的函数
function generateCode() {
const code = Blockly.JavaScript.workspaceToCode(workspace);
document.getElementById('codeOutput').value = code;
}
// 运行代码的函数 (伪代码,需要Emscripten)
function runCode() {
const code = Blockly.JavaScript.workspaceToCode(workspace);
// 这里需要调用 Emscripten 的编译和运行逻辑
// 将代码保存到 .c 文件,然后调用 emcc 编译,最后加载 WASM 模块并执行
// 这是一个复杂的步骤,下面会详细说明
alert('运行代码需要配置 Emscripten 环境,这里仅为示例。');
}
// 初始化工作区
let workspace;
window.onload = function() {
workspace = Blockly.inject('blocklyDiv', {
toolbox: document.getElementById('toolbox')
});
};
</script>
</body>
</html>
完整示例:一个简单的计算器
让我们创建一个可以计算两个数之和的积木块。
定义积木块 (blocks.xml)
<xml id="toolbox" style="display: none">
<category name="数学" colour="230">
<block type="math_arithmetic"></block>
</category>
<category name="输出" colour="160">
<block type="text_print"></block>
</category>
</xml>
注册代码生成器 (在 HTML 的 <script> 标签中)
// 定义 'math_arithmetic' 积木块
Blockly.Blocks['math_arithmetic'] = {
init: function() {
this.appendValueInput("A")
.setCheck("Number");
this.appendValueInput("B")
.setCheck("Number")
.appendField(new Blockly.FieldDropdown([["加", "ADD"], ["减", "SUBTRACT"], ["乘", "MULTIPLY"], ["除", "DIVIDE"]]), "OP");
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setOutput(true, "Number"); // 设置为输出值,可以被其他积木使用
this.setColour(230);
this.setTooltip("基本数学运算");
}
};
// 为 'math_arithmetic' 注册代码生成器
Blockly.JavaScript['math_arithmetic'] = function(block) {
const operator = block.getFieldValue('OP'); // 获取运算符
const num1 = Blockly.JavaScript.valueToCode(block, 'A', Blockly.JavaScript.ORDER_MULTIPLICATION) || '0';
const num2 = Blockly.JavaScript.valueToCode(block, 'B', Blockly.JavaScript.ORDER_MULTIPLICATION) || '0';
let code = '';
switch (operator) {
case 'ADD':
code = '(' + num1 + ' + ' + num2 + ')';
break;
case 'SUBTRACT':
code = '(' + num1 + ' - ' + num2 + ')';
break;
case 'MULTIPLY':
code = '(' + num1 + ' * ' + num2 + ')';
break;
case 'DIVIDE':
code = '(' + num1 + ' / ' + num2 + ')';
break;
default:
code = '0';
}
// 因为这个积木有输出,所以直接返回代码,不需要换行
return [code, Blockly.JavaScript.ORDER_MULTIPLICATION];
};
在工作区中组合
你可以拖拽一个 math_arithmetic 积木,设置运算为 "加",然后拖拽一个 text_print 积木,并将 math_arithmetic 的输出连接到 text_print 的输入中。
生成的 C 代码
点击“生成代码”按钮,你可能会得到类似这样的代码:
#include <stdio.h>
int main() {
printf("(5 + 3)\n");
return 0;
}
为了让它真正计算,我们需要稍微修改一下生成器,让它直接计算并打印结果。
修改后的 math_arithmetic 生成器:
Blockly.JavaScript['math_arithmetic'] = function(block) {
const operator = block.getFieldValue('OP');
const num1 = Blockly.JavaScript.valueToCode(block, 'A', Blockly.JavaScript.ORDER_MULTIPLICATION) || '0';
const num2 = Blockly.JavaScript.valueToCode(block, 'B', Blockly.JavaScript.ORDER_MULTIPLICATION) || '0';
let code = '';
switch (operator) {
case 'ADD':
code = num1 + ' + ' + num2;
break;
// ... 其他 case ...
}
// 返回一个表达式,可以被 printf 使用
return [code, Blockly.JavaScript.ORDER_MULTIPLICATION];
};
修改后的 text_print 生成器:
Blockly.JavaScript['text_print'] = function(block) {
// 假设这个积木只有一个输入,用于接收要打印的值
const argument = Blockly.JavaScript.valueToCode(block, 'TEXT', Blockly.JavaScript.ORDER_NONE) || '""';
const code = 'printf("%d\\n", ' + argument + ');\n';
return code;
};
生成的代码会是:
#include <stdio.h>
int main() {
printf("%d\n", 5 + 3);
return 0;
}
这显然更符合我们的预期。
高级技巧与注意事项
-
包含头文件: 如果你的程序需要特定的 C 库(如
math.h),你可以在生成器中自动添加,在workspaceToCode函数的开头添加:function generateCode() { let code = '#include <stdio.h>\n'; // 添加标准输入输出头文件 // 如果检测到使用了数学函数,再添加 math.h // ... (检测逻辑) ... // code += '#include <math.h>\n'; code += '\nint main() {\n'; code += Blockly.JavaScript.workspaceToCode(workspace); code += '\nreturn 0;\n}\n'; document.getElementById('codeOutput').value = code; } -
变量处理: Blockly 的变量积木需要特殊处理,你需要确保生成的 C 代码中有对应的变量声明。
- 声明: 在代码生成器中,可以维护一个变量列表,在
main函数开始处统一声明int var;。 - 作用域: C 语言的作用域规则比 Blockly 严格,需要小心处理。
- 声明: 在代码生成器中,可以维护一个变量列表,在
-
函数定义: 要支持函数定义,你需要:
- 定义一个
function_def类型的积木块。 - 为它编写代码生成器,生成
return_type function_name(params) { ... }的结构。 - 还需要一个
function_call类型的积木块来调用已定义的函数。
- 定义一个
-
错误处理: 在代码生成器中,要处理可能缺失的输入(
valueToCode返回undefined),并提供默认值(如0或 ),以防止生成的 C 代码语法错误。 -
关于运行环境 (Emscripten): 在浏览器中运行 C 代码的完整流程是: a. 生成代码: 如上所述,得到一个
.c文件的字符串。 b. 保存到虚拟文件系统: 使用 Emscripten 的FS.writeFile将代码保存到一个虚拟文件中,program.c。 c. 编译: 调用Module.ccall('compile_and_run', ...),这个内部会执行emcc program.c -o program.js。 d. 加载和执行: 编译完成后,会生成program.js和program.wasm,浏览器会自动加载它们,然后你就可以调用 C 函数了。 这个过程比较复杂,通常需要一个后端服务来处理编译,或者使用一个配置好的 Emscripten 环境。
使用 Blockly 生成 C 语言代码是一个将可视化逻辑转化为高性能文本代码的强大方法,其核心在于 为每个积木块类型编写对应的代码生成函数,虽然实现起来需要一些 JavaScript 编程基础,但 Blockly 提供了清晰的框架,使得这个过程变得系统化和可扩展,对于教育、快速原型设计和可视化编程工具的开发来说,这是一个非常有价值的方案。
