產生並執行 JavaScript

禁止使用的應用程式通常會產生 JavaScript 做為輸出語言,通常是在網頁 (可能相同或內嵌的 WebView) 中執行。就像任何產生器一樣,第一步是加入 JavaScript 產生器。

import {javascriptGenerator} from 'blockly/javascript';

如要從工作區產生 JavaScript,請呼叫:

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

產生的程式碼可以直接在到達網頁中執行:

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

基本上,上述程式碼片段只會產生程式碼並加以評估。不過,還是有幾項修正措施。其中一個修正是,評估值會納入在 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 解譯器

如果您正要正確執行使用者區塊,可以使用「JS 解譯器專案」。這個專案與 Blockly 不同,但特別專為 Blockly 編寫。

  • 任何速度皆可執行程式碼。
  • 暫停/繼續/逐步執行。
  • 在執行方塊時醒目顯示。
  • 與瀏覽器的 JavaScript 完全隔離。

執行翻譯工具

首先,從 GitHub 下載 JS 轉譯器:

下載 ZIP 檔案 下載 TAR Ball 前往 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 解譯器是一種沙箱,能夠與瀏覽器完全隔離。凡是使用外部環境執行動作的區塊,都需要在解譯器中新增 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 的區塊。

正在連結「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。另外,這個範例也包含一個等待區塊,以及語音、音訊、使用者輸入內容等非同步行為的良好範例。