生成和运行 JavaScript

Blockly 应用通常会生成 JavaScript 作为其输出语言,一般在网页(可能是同一网页或嵌入式 WebView)中运行。与任何生成器一样,第一步是添加 JavaScript 生成器。

import {javascriptGenerator} from 'blockly/javascript';

如需从工作区生成 JavaScript,请调用:

javascriptGenerator.addReservedWords('code');
var code = javascriptGenerator.workspaceToCode(workspace);

生成的代码可以直接在目标网页中执行:

try {
  eval(code);
} catch (e) {
  alert(e);
}

基本上,上述代码段只是生成代码并对其进行评估。不过,我们还进行了一些改进。一个改进之处在于,eval 封装在 try/catch 中,以便显示任何运行时错误,而不是静默失败。另一项改进是将 code 添加到保留字列表中,这样一来,如果用户的代码包含该名称的变量,系统会自动重命名该变量,而不是发生冲突。所有局部变量都应以这种方式预留。

突出显示块

在代码运行时突出显示当前正在执行的代码块,有助于用户了解程序的行为。您可以在生成 JavaScript 代码之前设置 STATEMENT_PREFIX,从而逐条语句地进行突出显示:

javascriptGenerator.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
javascriptGenerator.addReservedWords('highlightBlock');

定义 highlightBlock 以标记工作区中的块。

function highlightBlock(id) {
  workspace.highlightBlock(id);
}

这会导致在每个语句之前添加 highlightBlock('123'); 语句,其中 123 是要突出显示的块的序列号。

无限循环

虽然生成的代码始终保证语法正确,但可能包含无限循环。由于解决停机问题超出了 Blockly 的范围 (!),因此处理这些情况的最佳方法是维护一个计数器,并在每次执行迭代时递减该计数器。为此,只需将 javascriptGenerator.INFINITE_LOOP_TRAP 设置为将插入到每个循环和每个函数中的代码段。示例如下:

window.LoopTrap = 1000;
javascriptGenerator.INFINITE_LOOP_TRAP = 'if(--window.LoopTrap == 0) throw "Infinite loop.";\n';
var code = javascriptGenerator.workspaceToCode(workspace);

示例

以下是生成和执行 JavaScript 的实时演示

JS-Interpreter

如果您想认真处理用户屏蔽问题,那么 JS-Interpreter 项目是您的理想选择。此项目与 Blockly 分开,但专门为 Blockly 编写。

  • 以任意速度执行代码。
  • 暂停/恢复/单步执行。
  • 在执行时突出显示块。
  • 与浏览器的 JavaScript 完全隔离。

运行解释器

首先,从 GitHub 下载 JS-Interpreter:

然后将其添加到您的网页中:

<script src="acorn_interpreter.js"></script>

调用它的最简单方法是生成 JavaScript、创建解释器并运行代码:

var code = javascriptGenerator.workspaceToCode(workspace);
var myInterpreter = new Interpreter(code);
myInterpreter.run();

逐步执行解释器

为了以更慢或更受控的方式执行代码,请将对 run 的调用替换为一个循环,该循环以步进方式运行(在本例中,每 10 毫秒步进一次):

function nextStep() {
  if (myInterpreter.step()) {
    setTimeout(nextStep, 10);
  }
}
nextStep();

请注意,每个步骤不是一行或一个块,而是 JavaScript 中的一个语义单元,可能非常细粒度。

添加 API

JS-Interpreter 是一个完全与浏览器隔离的沙盒。 任何与外部世界互动的代码块都需要向解释器添加 API。如需查看完整说明,请参阅 JS-Interpreter 文档。不过,首先需要以下 API 来支持提醒和提示块:

function initApi(interpreter, globalObject) {
  // Add an API function for the alert() block.
  var wrapper = function(text) {
    return alert(arguments.length ? text : '');
  };
  interpreter.setProperty(globalObject, 'alert',
      interpreter.createNativeFunction(wrapper));

  // Add an API function for the prompt() block.
  wrapper = function(text) {
    return prompt(text);
  };
  interpreter.setProperty(globalObject, 'prompt',
      interpreter.createNativeFunction(wrapper));
}

然后,修改解释器初始化,以传入 initApi 函数:

var myInterpreter = new Interpreter(code, initApi);

在默认的模块集中,提醒模块和提示模块是唯一需要为解释器提供自定义 API 的模块。

正在关联 highlightBlock()

在 JS-Interpreter 中运行时,highlightBlock() 应在用户逐步执行程序时立即在沙盒外执行。为此,请创建一个封装函数 highlightBlock() 来捕获函数实参,并将其注册为原生函数。

function initApi(interpreter, globalObject) {
  // Add an API function for highlighting blocks.
  var wrapper = function(id) {
    return workspace.highlightBlock(id);
  };
  interpreter.setProperty(globalObject, 'highlightBlock',
      interpreter.createNativeFunction(wrapper));
}

更复杂的应用可能希望在到达突出显示命令之前不间断地重复执行步骤,然后在到达突出显示命令时暂停。此策略会模拟逐行执行。以下示例使用了此方法。

JS-Interpreter 示例

以下是逐步解读 JavaScript 的实时演示。而此演示包含一个等待块,可作为其他异步行为(例如语音或音频、用户输入)的示例。