Implement startBranch() in compiler

This commit is contained in:
Muffin committed 2023-08-23 15:50:57 -05:00
1 parent 1e88fad562
commit 2adf5ce38e
3 files changed
+44 -32

No files matched your search

+5 -3
View File
@@ -4,16 +4,17 @@ class CompatibilityLayerBlockUtility extends BlockUtility {
constructor () {
super();
this._stackFrame = {};
this._startedBranch = null;
}
get stackFrame () {
return this._stackFrame;
}
// Branching operations are not supported.
startBranch () {
throw new Error('startBranch is not supported by this BlockUtility');
startBranch (branchNumber, isLoop) {
this._startedBranch = [branchNumber, isLoop];
}
startProcedure () {
throw new Error('startProcedure is not supported by this BlockUtility');
}
@@ -33,6 +34,7 @@ class CompatibilityLayerBlockUtility extends BlockUtility {
this.thread = thread;
this.sequencer = thread.target.runtime.sequencer;
this._stackFrame = stackFrame;
this._startedBranch = null;
thread.stack[0] = fakeBlockId;
}
}
+27 -19
View File
@@ -101,7 +101,7 @@ runtimeFunctions.waitThreads = `const waitThreads = function*(threads) {
* @param {function} blockFunction The primitive's function.
* @param {boolean} useFlags Whether to set flags (hasResumedFromPromise)
* @param {string} blockId Block ID to set on the emulated block utility.
* @param {*|null} stackFrame Object to use as stack frame.
* @param {*|null} branchInfo Extra information object for CONDITIONAL and LOOP blocks. See createBranchInfo().
* @returns {*} the value returned by the block, if any.
*/
runtimeFunctions.executeInCompatibilityLayer = `let hasResumedFromPromise = false;
@@ -132,10 +132,22 @@ const isPromise = value => (
typeof value === 'object' &&
typeof value.then === 'function'
);
const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, useFlags, blockId, stackFrame) {
const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, useFlags, blockId, branchInfo) {
const thread = globalState.thread;
const blockUtility = globalState.blockUtility;
if (!stackFrame) stackFrame = {};
const stackFrame = branchInfo ? branchInfo.stackFrame : {};
const finish = (returnValue) => {
if (branchInfo) {
if (typeof returnValue === 'undefined' && blockUtility._startedBranch) {
branchInfo.isLoop = blockUtility._startedBranch[1];
return blockUtility._startedBranch[0];
}
branchInfo.isLoop = branchInfo.defaultIsLoop;
return returnValue;
}
return returnValue;
};
const executeBlock = () => {
blockUtility.init(thread, blockId, stackFrame);
@@ -143,13 +155,8 @@ const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, use
};
let returnValue = executeBlock();
if (isPromise(returnValue)) {
returnValue = yield* waitPromise(returnValue);
if (useFlags) {
hasResumedFromPromise = true;
}
return returnValue;
return finish(yield* waitPromise(returnValue));
}
if (thread.status === 1 /* STATUS_PROMISE_WAIT */) {
@@ -173,30 +180,31 @@ const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, use
}
returnValue = executeBlock();
if (isPromise(returnValue)) {
returnValue = yield* waitPromise(returnValue);
if (useFlags) {
hasResumedFromPromise = true;
}
return returnValue;
return finish(yield* waitPromise(returnValue));
}
if (thread.status === 1 /* STATUS_PROMISE_WAIT */) {
yield;
return '';
return finish('');
}
}
// todo: do we have to do anything extra if status is STATUS_DONE?
return returnValue;
return finish(returnValue);
}`;
/**
* @returns {unknown} An object to use as a stack frame.
* @param {boolean} isLoop True if the block is a LOOP by default (can be overridden by startBranch() call)
* @returns {unknown} Branch info object for compatibility layer.
*/
runtimeFunctions.persistentStackFrame = `const persistentStackFrame = () => ({});`;
runtimeFunctions.createBranchInfo = `const createBranchInfo = (isLoop) => ({
defaultIsLoop: isLoop,
isLoop: false,
branch: 0,
stackFrame: {}
});`;
/**
* End the current script.
+12 -10
View File
@@ -765,21 +765,23 @@ class JSGenerator {
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 (+(${this.generateCompatibilityLayerCall(node, isLastInLoop)})) {\n`;
} else if (blockType === BlockType.CONDITIONAL || blockType === BlockType.LOOP) {
const branchVariable = this.localVariables.next();
this.source += `const ${branchVariable} = createBranchInfo(${blockType === BlockType.LOOP});\n`;
this.source += `while (${branchVariable}.branch = +(${this.generateCompatibilityLayerCall(node, false, branchVariable)})) {\n`;
this.source += `switch (${branchVariable}.branch) {\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`; // close case
}
this.source += `}\n`;
} else if (node.blockType === BlockType.LOOP) {
const stackFrameName = this.localVariables.next();
this.source += `const ${stackFrameName} = persistentStackFrame();\n`;
this.source += `while (toBoolean(${this.generateCompatibilityLayerCall(node, isLastInLoop, stackFrameName)})) {\n`;
this.descendStack(node.substacks[0], new Frame(true));
this.source += '}\n';
this.source += '}\n'; // close switch
this.source += `if (!${branchVariable}.isLoop) break;\n`;
this.yieldLoop();
this.source += '}\n'; // close while
} else {
throw new Error(`Unknown block type: ${blockType}`);
}
if (isLastInLoop) {