编译器补丁
编译扩展的核心机制是为 Bilup 的编译器打补丁以注入自定义代码生成逻辑。此过程涉及修改三个关键编译器阶段来处理扩展积木。
理解编译流程
Bilup 的编译器通过多个阶段转换 Scratch 积木:
- 积木解析:处理原始积木数据
- 脚本树生成:将积木组织成树结构
- IR 生成:将树转换为中间表示
- JavaScript 生成:生成最终的 JavaScript 代码
编译扩展在阶段 2 和 4 注入钩子,通过跳过 IR 阶段实现直接优化。
补丁实现策略
安全补丁模式
补丁系统确保多个扩展可以共存而不冲突:
const PATCHES_ID = 'myextension_patches';
const cst_patch = (obj, functions) => {
// 防止重复打补丁
if (obj[PATCHES_ID]) return;
obj[PATCHES_ID] = {};
for (const name in functions) {
const original = obj[name];
obj[PATCHES_ID][name] = obj[name];
if (original) {
// 包装现有函数
obj[name] = function(...args) {
const callOriginal = (...args) => original.call(this, ...args);
return functions[name].call(this, callOriginal, ...args);
};
} else {
// 创建新函数
obj[name] = function(...args) {
return functions[name].call(this, () => {}, ...args);
};
}
}
};
此模式在添加扩展特定行为的同时保留原始功能。
脚本树生成补丁
脚本树生成器识别积木并将其转换为结构化格式以供编译。
处理不同的积木类型
cst_patch(ScriptTreeGenerator.prototype, {
descendStackedBlock(fn, block, ...args) {
switch (block.opcode) {
// 命令积木(帽子/堆叠积木)
case 'myextension_command':
return {
block,
kind: 'myextension.command',
INPUT1: this.descendInputOfBlock(block, 'INPUT1'),
INPUT2: this.descendInputOfBlock(block, 'INPUT2'),
};
// 布尔积木
case 'myextension_comparison':
return {
block,
kind: 'myextension.comparison',
LEFT: this.descendInputOfBlock(block, 'LEFT'),
RIGHT: this.descendInputOfBlock(block, 'RIGHT'),
};
default:
return fn(block, ...args);
}
},
descendInput(fn, block, ...args) {
switch (block.opcode) {
// 返回值积木
case 'myextension_reporter':
return {
block,
kind: 'myextension.reporter',
VALUE: this.descendInputOfBlock(block, 'VALUE'),
};
// 布尔返回值
case 'myextension_predicate':
return {
block,
kind: 'myextension.predicate',
TEST: this.descendInputOfBlock(block, 'TEST'),
};
default:
return fn(block, ...args);
}
}
});