跳到主要内容

编译器补丁

Bilup 允许非沙盒化扩展修补编译器,以支持高级功能,如内联积木执行或自定义控制流。

⚠️ 高级主题: 这需要深入了解 Scratch VM 和编译器架构。内部 API 可能会发生破坏性更改。

概述

编译器补丁涉及拦截对 IRGenerator(中间表示)和 JSGenerator(JavaScript 代码生成)的调用,以修改积木的编译方式。

辅助工具:补丁函数

使用辅助工具安全地修补和取消修补方法。

const PATCHES_ID = "__patches_" + extensionId;

const 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);
}
}
}
};

示例:内联积木

此示例演示如何创建一个"内联"积木,该积木执行子程序并返回值,本质上允许带有逻辑的自定义返回值积木。

1. 访问编译器

const vm = Scratch.vm;
const runtime = vm.runtime;

// 检查编译器是否可用
if (vm.exports.IRGenerator && vm.exports.JSGenerator) {
const IRGenerator = vm.exports.IRGenerator;
const JSGenerator = vm.exports.JSGenerator;
const ScriptTreeGenerator = IRGenerator.exports.ScriptTreeGenerator;
const {Frame, TypedInput, TYPE_UNKNOWN} = JSGenerator.exports;

// 应用补丁...
}

2. 修补 ScriptTreeGenerator (IR)

修改为您的积木生成中间表示的方式。

patch(ScriptTreeGenerator.prototype, {
descendInput(original, block) {
if (block.opcode === "myExtension_inline") {
return {
kind: "myExtension.inline",
stack: this.descendSubstack(block, "SUBSTACK")
};
}
return original(block);
}
});

3. 修补 JSGenerator (代码生成)

为自定义 IR 节点生成实际的 JavaScript 代码。

patch(JSGenerator.prototype, {
descendInput(original, node) {
if (node.kind === "myExtension.inline") {
const oldSrc = this.source;

// 为子程序生成代码
this.descendStack(node.stack, new Frame(false));
const stackSrc = this.source.substring(oldSrc.length);

// 重置源
this.source = oldSrc;

// 返回编译后的内联函数
return new TypedInput(
`(yield* (function*() {
try {
${stackSrc};
return "";
} catch (e) {
if (!e.inlineReturn) throw e;
return e.value;
}
})())`,
TYPE_UNKNOWN
);
}
return original(node);
}
});

自定义积木定义

您还需要定义使用此自定义编译逻辑的积木。

{
opcode: "inline",
blockType: Scratch.BlockType.REPORTER,
text: ["inline"],
branchCount: 1, // Substack
output: "Boolean",
outputShape: 3
}

处理解释器模式

由于编译器可能在所有平台上被禁用或不支持,您还应该为解释器实现运行时逻辑。

inline(args, util) {
const thread = util.thread;
const realBlockId = util.thread.peekStackFrame().op.id;
const branchBlockId = thread.target.blocks.getBranch(realBlockId, 1);

if (!branchBlockId) return "";

// 执行分支并处理返回值的逻辑
// ...
}

另见