如何用Blockly生成C语言代码?

99ANYc3cd6
预计阅读时长 39 分钟
位置: 首页 C语言 正文

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

blockly c语言
(图片来源网络,侵删)

下面我将从 核心概念、实现步骤、完整示例高级技巧 四个方面进行详细说明。


核心概念:代码生成器

Blockly 本身不直接生成代码,它只生成一个代表程序逻辑的 XMLJSON 数据结构,你需要编写一个“代码生成器”来遍历这个数据结构,并根据每个积木块的类型,生成对应的 C 语言代码。

这个过程可以想象成: Blockly 积木块 -> 程序逻辑 (JSON) -> 代码生成器 -> C 语言源代码

实现步骤:从零开始搭建一个简单的 C 语言代码生成器

假设我们要创建一个简单的 Blockly 工作区,它可以实现“....”和打印“Hello, World!”的功能。

blockly c语言
(图片来源网络,侵删)

定义积木块

我们需要在 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>

这里我们定义了三个积木块:

  1. controls_if: 标准 if 语句积木。
  2. text_print: 打印文本的积木。
  3. 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;
}

这显然更符合我们的预期。


高级技巧与注意事项

  1. 包含头文件: 如果你的程序需要特定的 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;
    }
  2. 变量处理: Blockly 的变量积木需要特殊处理,你需要确保生成的 C 代码中有对应的变量声明。

    • 声明: 在代码生成器中,可以维护一个变量列表,在 main 函数开始处统一声明 int var;
    • 作用域: C 语言的作用域规则比 Blockly 严格,需要小心处理。
  3. 函数定义: 要支持函数定义,你需要:

    • 定义一个 function_def 类型的积木块。
    • 为它编写代码生成器,生成 return_type function_name(params) { ... } 的结构。
    • 还需要一个 function_call 类型的积木块来调用已定义的函数。
  4. 错误处理: 在代码生成器中,要处理可能缺失的输入(valueToCode 返回 undefined),并提供默认值(如 0 或 ),以防止生成的 C 代码语法错误。

  5. 关于运行环境 (Emscripten): 在浏览器中运行 C 代码的完整流程是: a. 生成代码: 如上所述,得到一个 .c 文件的字符串。 b. 保存到虚拟文件系统: 使用 Emscripten 的 FS.writeFile 将代码保存到一个虚拟文件中,program.c。 c. 编译: 调用 Module.ccall('compile_and_run', ...),这个内部会执行 emcc program.c -o program.js。 d. 加载和执行: 编译完成后,会生成 program.jsprogram.wasm,浏览器会自动加载它们,然后你就可以调用 C 函数了。 这个过程比较复杂,通常需要一个后端服务来处理编译,或者使用一个配置好的 Emscripten 环境。

使用 Blockly 生成 C 语言代码是一个将可视化逻辑转化为高性能文本代码的强大方法,其核心在于 为每个积木块类型编写对应的代码生成函数,虽然实现起来需要一些 JavaScript 编程基础,但 Blockly 提供了清晰的框架,使得这个过程变得系统化和可扩展,对于教育、快速原型设计和可视化编程工具的开发来说,这是一个非常有价值的方案。

-- 展开阅读全文 --
头像
织梦排版编辑器,如何轻松排版出好文章?
« 上一篇 04-18
voidloop是什么?C语言中void的loop用法解析
下一篇 » 04-18

相关文章

取消
微信二维码
支付宝二维码

目录[+]