Implement CONDITIONAL blocks in compiler

This commit is contained in:
Muffin committed 2023-08-22 16:34:47 -05:00
1 parent 2027b9288f
commit db0776711f
4 files changed
+128 -5

No files matched your search

+22 -4
View File
@@ -1145,7 +1145,7 @@ class ScriptTreeGenerator {
const blockInfo = this.getBlockInfo(block.opcode);
if (blockInfo) {
const type = blockInfo.info.blockType;
if (type === BlockType.COMMAND) {
if (type === BlockType.COMMAND || type === BlockType.CONDITIONAL) {
return this.descendCompatLayer(block);
}
}
@@ -1386,19 +1386,37 @@ class ScriptTreeGenerator {
*/
descendCompatLayer (block) {
this.script.yields = true;
const inputs = {};
const fields = {};
for (const name of Object.keys(block.inputs)) {
inputs[name] = this.descendInputOfBlock(block, name);
if (!name.startsWith('SUBSTACK')) {
inputs[name] = this.descendInputOfBlock(block, name);
}
}
const fields = {};
for (const name of Object.keys(block.fields)) {
fields[name] = block.fields[name].value;
}
const blockInfo = this.getBlockInfo(block.opcode);
const blockType = (blockInfo && blockInfo.info && blockInfo.info.blockType) || BlockType.COMMAND;
const substacks = [];
if (blockType === BlockType.LOOP || blockType === BlockType.CONDITIONAL) {
const branchCount = blockInfo.info.branchCount;
for (let i = 0; i < branchCount; i++) {
const inputName = i === 0 ? 'SUBSTACK' : `SUBSTACK${i + 1}`;
substacks.push(this.descendSubstack(block, inputName));
}
}
return {
kind: 'compat',
opcode: block.opcode,
blockType,
inputs,
fields
fields,
substacks
};
}
+16 -1
View File
@@ -1,5 +1,6 @@
const log = require('../util/log');
const Cast = require('../util/cast');
const BlockType = require('../extension-support/block-type');
const VariablePool = require('./variable-pool');
const jsexecute = require('./jsexecute');
const environment = require('./environment');
@@ -760,7 +761,21 @@ class JSGenerator {
// If the last command in a loop returns a promise, immediately continue to the next iteration.
// If you don't do this, the loop effectively yields twice per iteration and will run at half-speed.
const isLastInLoop = this.isLastBlockInLoop();
this.source += `${this.generateCompatibilityLayerCall(node, isLastInLoop)};\n`;
const blockType = node.blockType;
if (blockType === BlockType.COMMAND || blockType === BlockType.HAT) {
this.source += `${this.generateCompatibilityLayerCall(node, isLastInLoop)};\n`;
} else if (blockType === BlockType.CONDITIONAL) {
this.source += `switch (Math.round(${this.generateCompatibilityLayerCall(node, isLastInLoop)})) {\n`;
for (let i = 0; i < node.substacks.length; i++) {
this.source += `case ${i + 1}: {\n`;
this.descendStack(node.substacks[i], new Frame(false));
this.source += `break;\n`;
this.source += `}\n`;
}
this.source += `}\n`;
}
if (isLastInLoop) {
this.source += 'if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;}\n';
}
Binary file not shown.
@@ -0,0 +1,90 @@
const fs = require('fs');
const path = require('path');
const {test} = require('tap');
const VirtualMachine = require('../../src/virtual-machine');
const Scratch = require('../../src/extension-support/tw-extension-api-common');
class LoopsAndThings {
getInfo () {
return {
id: 'loopsAndThings',
name: 'Loops and things test',
blocks: [
{
opcode: 'conditional',
blockType: Scratch.BlockType.CONDITIONAL,
text: 'run branch [BRANCH] of',
arguments: {
BRANCH: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 1
}
},
branchCount: 3
},
{
opcode: 'loop',
blockType: Scratch.BlockType.LOOP,
text: 'my repeat [TIMES]',
arguments: {
TIMES: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 10
}
}
}
]
};
}
conditional ({BRANCH}, util) {
return Scratch.Cast.toNumber(BRANCH);
}
loop ({TIMES}, util) {
const times = Math.round(Scratch.Cast.toNumber(TIMES));
if (typeof util.stackFrame.loopCounter === 'undefined') {
util.stackFrame.loopCounter = times;
}
util.stackFrame.loopCounter--;
if (util.stackFrame.loopCounter >= 0) {
return true;
}
}
}
const compilerAndInterpreter = (name, callback) => {
test(`${name} - interpreted`, t => {
callback(t, {
enabled: false
});
});
test(`${name} - compiled`, t => {
callback(t, {
enabled: true
});
});
};
compilerAndInterpreter('CONDITIONAL', (t, co) => {
t.plan(1);
const vm = new VirtualMachine();
vm.setCompilerOptions(co);
vm.extensionManager.addBuiltinExtension('loopsAndThings', LoopsAndThings);
vm.loadProject(fs.readFileSync(path.join(__dirname, '../fixtures/tw-conditional.sb3'))).then(() => {
let okayCount = 0;
vm.runtime.on('SAY', (target, type, text) => {
if (text === 'OK!') {
okayCount++;
} else if (text === 'end') {
t.equal(okayCount, 5);
t.end();
}
});
vm.greenFlag();
vm.runtime._step();
});
});