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);
}

基本的に、上記のスニペットはコードを生成して評価するだけです。ただし、いくつか改善点があります。1 つの改善点は、eval が try/catch でラップされているため、ランタイム エラーが静かに失敗するのではなく、表示されることです。もう 1 つの改善点は、予約語のリストに 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 ミリ秒ごとに 1 ステップ)に置き換えます。

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 を必要とするデフォルトのブロックセット内の 2 つのブロックです。

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 をステップごとに解釈するライブデモをご覧ください。また、このデモには待機ブロックが含まれています。これは、他の非同期動作(音声、音声、ユーザー入力など)に使用するのに適した例です。