Implement CONDITIONAL blocks in compiler
This commit is contained in:
1 parent
2027b9288f
commit
db0776711f
4 files changed
+128
-5
No files matched your search
+22
-4
@@ -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
@@ -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';
|
||||
}
|
||||
|
||||
Vendored
BIN
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();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user