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 をダウンロードします。

ZIP ファイルをダウンロードする TAR ボールをダウンロードする GitHub で表示

次に、ページに追加します。

<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 インタープリタのドキュメントをご覧ください。まず、アラート ブロックとプロンプト ブロックをサポートするために必要な 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 インタープリタで実行する場合、ユーザーがプログラムをステップ実行すると、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 インタープリタの例

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