禁止使用的應用程式通常會產生 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 轉譯器:
然後將其新增至你的頁面:
<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。另外,這個範例也包含一個等待區塊,以及語音、音訊、使用者輸入內容等非同步行為的良好範例。