在前面的章節中,我們已經學習了如何使用 Blockly 的基本功能以及如何創建自定義積木。本章將深入探討 Blockly 的另一個核心功能:程式碼生成。我們將學習如何將視覺化的積木轉換為 JavaScript 和 Python 程式碼,以及如何自定義和擴展這個過程。
Blockly 的程式碼生成系統是將視覺化的積木結構轉換為文字形式的程式碼。這個過程是通過一系列的生成器函數完成的,每種積木類型都有對應的生成器函數,定義了如何將該積木轉換為特定語言的程式碼。
程式碼生成的基本流程如下:
1. 從工作區中獲取所有頂層積木(沒有連接到其他積木頂部的積木)。
2. 對每個頂層積木,遞迴地處理其所有子積木。
3. 每個積木類型都有對應的生成器函數,定義了如何將該積木轉換為特定語言的程式碼。
4. 將所有生成的程式碼片段組合成完整的程式碼。
Blockly 內建了對 JavaScript 的支援,可以將積木直接轉換為 JavaScript 程式碼。
以下是生成 JavaScript 程式碼的基本方法:
// 引入 JavaScript 生成器
import 'blockly/javascript';
// 生成 JavaScript 程式碼
const code = Blockly.JavaScript.workspaceToCode(workspace);
console.log(code);
生成的 JavaScript 程式碼可以通過多種方式運行:
使用 eval 函數(不推薦用於生產環境,因為有安全風險):
try {
eval(code);
} catch (e) {
console.error('運行程式碼時發生錯誤:', e);
}
使用 Function 構造函數(較安全,但仍有風險):
try {
const runCode = new Function(code);
runCode();
} catch (e) {
console.error('運行程式碼時發生錯誤:', e);
}
使用沙箱環境(最安全,但較複雜):
// 使用 iframe 作為沙箱
const sandbox = document.createElement('iframe');
sandbox.style.display = 'none';
document.body.appendChild(sandbox);
// 在沙箱中運行程式碼
try {
const sandboxWindow = sandbox.contentWindow;
sandboxWindow.eval(code);
} catch (e) {
console.error('運行程式碼時發生錯誤:', e);
} finally {
document.body.removeChild(sandbox);
}
您可以自定義 JavaScript 生成器,以支援自定義積木或修改現有積木的程式碼生成:
// 為自定義積木定義 JavaScript 生成器
Blockly.JavaScript['custom_block'] = function(block) {
// 獲取積木的輸入值
const value = Blockly.JavaScript.valueToCode(block, 'VALUE', Blockly.JavaScript.ORDER_ATOMIC);
// 生成 JavaScript 程式碼
const code = `console.log(${value});\n`;
return code;
};
Blockly 也內建了對 Python 的支援,可以將積木轉換為 Python 程式碼。
以下是生成 Python 程式碼的基本方法:
// 引入 Python 生成器
import 'blockly/python';
// 生成 Python 程式碼
const code = Blockly.Python.workspaceToCode(workspace);
console.log(code);
由於 Python 是一種伺服器端語言,您通常需要將生成的程式碼發送到伺服器進行運行。以下是一個簡單的例子:
// 將生成的 Python 程式碼發送到伺服器
fetch('/run-python', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ code: code }),
})
.then(response => response.json())
.then(data => {
console.log('運行結果:', data.result);
})
.catch(error => {
console.error('運行程式碼時發生錯誤:', error);
});
您可以自定義 Python 生成器,以支援自定義積木或修改現有積木的程式碼生成:
// 為自定義積木定義 Python 生成器
Blockly.Python['custom_block'] = function(block) {
// 獲取積木的輸入值
const value = Blockly.Python.valueToCode(block, 'VALUE', Blockly.Python.ORDER_ATOMIC);
// 生成 Python 程式碼
const code = `print(${value})\n`;
return code;
};
生成器函數是程式碼生成的核心,它定義了如何將特定類型的積木轉換為程式碼。以下是生成器函數的詳細說明:
Blockly.JavaScript['block_type'] = function(block) {
// 獲取積木的輸入值
// 生成程式碼
// 返回程式碼
};
valueToCode:獲取連接到值輸入槽的積木生成的程式碼。
const value = Blockly.JavaScript.valueToCode(block, 'INPUT_NAME', Blockly.JavaScript.ORDER_ATOMIC);
statementToCode:獲取連接到語句輸入槽的積木生成的程式碼。
const statements = Blockly.JavaScript.statementToCode(block, 'STATEMENTS');
getFieldValue:獲取積木上字段的值。
const fieldValue = block.getFieldValue('FIELD_NAME');
在生成程式碼時,需要考慮運算符優先級,以確保生成的程式碼在組合時保持正確的優先級。Blockly 提供了一系列的常量來表示不同的優先級:
Blockly.JavaScript.ORDER_ATOMIC = 0; // 0 ""
Blockly.JavaScript.ORDER_MEMBER = 1; // . []
Blockly.JavaScript.ORDER_NEW = 1; // new
Blockly.JavaScript.ORDER_FUNCTION_CALL = 2; // ()
Blockly.JavaScript.ORDER_INCREMENT = 3; // ++
// ... 更多優先級常量
在返回值積木的生成器函數中,需要返回一個包含程式碼和優先級的數組:
return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
在實際應用中,我們經常需要處理更複雜的程式結構,如變數、函數和類別等。
Blockly 內建了對變數的支援,可以自動處理變數的聲明和使用:
// 獲取工作區中的所有變數
const variables = Blockly.Variables.allUsedVarModels(workspace);
// 生成變數聲明
let declarations = '';
for (const variable of variables) {
declarations += `let ${variable.name};\n`;
}
// 生成主程式碼
const code = Blockly.JavaScript.workspaceToCode(workspace);
// 組合完整的程式碼
const fullCode = declarations + code;
Blockly 也支援函數的定義和調用:
// 在 JavaScript 生成器中提供一個輔助函數
Blockly.JavaScript.provideFunction_ = function(name, code) {
if (!this.definitions_[name]) {
this.definitions_[name] = code.join('\n');
}
return name;
};
// 在生成器函數中使用輔助函數
const functionName = Blockly.JavaScript.provideFunction_(
'myFunction',
[
'function myFunction(param) {',
' console.log(param);',
'}'
]
);
// 生成調用函數的程式碼
const code = `${functionName}(${value});\n`;
讓我們通過一個實際案例來綜合應用上述知識,創建一個簡單的計算器應用,它可以執行基本的數學運算。
Blockly 計算器
JavaScript 程式碼
Python 程式碼
運行結果
// script.js
document.addEventListener('DOMContentLoaded', function() {
// 定義工具箱
const toolbox = {
kind: 'categoryToolbox',
contents: [
{
kind: 'category',
name: '數學',
colour: '%{BKY_MATH_HUE}',
contents: [
{
kind: 'block',
type: 'math_number'
},
{
kind: 'block',
type: 'math_arithmetic'
},
{
kind: 'block',
type: 'math_single'
},
{
kind: 'block',
type: 'math_trig'
},
{
kind: 'block',
type: 'math_constant'
},
{
kind: 'block',
type: 'math_round'
}
]
},
{
kind: 'category',
name: '變數',
colour: '%{BKY_VARIABLES_HUE}',
custom: 'VARIABLE'
},
{
kind: 'category',
name: '函數',
colour: '%{BKY_PROCEDURES_HUE}',
custom: 'PROCEDURE'
}
]
};
// 初始化 Blockly 工作區
const workspace = Blockly.inject('blocklyDiv', {
toolbox: toolbox,
grid: {
spacing: 20,
length: 3,
colour: '#ccc',
snap: true
},
zoom: {
controls: true,
wheel: true,
startScale: 1.0,
maxScale: 3,
minScale: 0.3,
scaleSpeed: 1.2
},
trashcan: true
});
// 監聽工作區變化事件
workspace.addChangeListener(function(event) {
if (event.type === Blockly.Events.BLOCK_CHANGE ||
event.type === Blockly.Events.BLOCK_CREATE ||
event.type === Blockly.Events.BLOCK_DELETE ||
event.type === Blockly.Events.BLOCK_MOVE) {
// 生成 JavaScript 程式碼
const jsCode = Blockly.JavaScript.workspaceToCode(workspace);
document.getElementById('jsCode').textContent = jsCode || '// 沒有積木';
// 生成 Python 程式碼
const pythonCode = Blockly.Python.workspaceToCode(workspace);
document.getElementById('pythonCode').textContent = pythonCode || '# 沒有積木';
}
});
// 運行按鈕點擊事件
document.getElementById('runButton').addEventListener('click', function() {
// 生成 JavaScript 程式碼
const jsCode = Blockly.JavaScript.workspaceToCode(workspace);
if (!jsCode) {
document.getElementById('result').textContent = '請先添加一些積木';
return;
}
// 運行 JavaScript 程式碼
try {
// 使用 Function 構造函數創建一個函數
const runCode = new Function('return ' + jsCode);
const result = runCode();
// 顯示結果
document.getElementById('result').textContent = result !== undefined ? result : '運行完成,沒有返回值';
} catch (e) {
document.getElementById('result').textContent = '運行錯誤: ' + e.message;
}
});
});
1. 從工具箱中拖出一些數學積木到工作區。
2. 觀察生成的 JavaScript 和 Python 程式碼。
3. 點擊「運行」按鈕,查看運行結果。
在使用 Blockly 生成程式碼時,以下是一些最佳實踐和注意事項:
安全性考慮:在運行生成的程式碼時,要注意安全性問題,特別是在處理用戶生成的程式碼時。考慮使用沙箱環境或其他安全機制。
錯誤處理:添加適當的錯誤處理機制,以便在程式碼生成或運行過程中出現問題時能夠提供有用的反饋。
程式碼格式化:考慮使用程式碼格式化工具,使生成的程式碼更易讀。
性能優化:對於複雜的工作區,程式碼生成可能會變得較慢。考慮使用防抖動或節流技術來優化性能。
多語言支援:如果您的應用需要支援多種程式語言,確保為每種語言提供適當的生成器。
在本章中,我們探討了 Blockly 的程式碼生成功能,包括如何生成 JavaScript 和 Python 程式碼,如何自定義生成器函數,以及如何處理複雜的程式結構。通過這些知識,您可以將 Blockly 積木轉換為實際可運行的程式碼,實現從視覺化程式設計到實際程式執行的完整流程。
在下一章中,我們將探討如何將 Blockly 整合到現有的網站中,包括如何處理事件、儲存和讀取積木配置,以及響應式設計考量等方面。