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。此外,這個範例 包含等待方塊,很適合用於其他非同步行為 (例如語音或音訊、使用者輸入)。