diff --git a/src/compiler/compat-block-utility.js b/src/compiler/compat-block-utility.js index 7ac1f009..0676b21f 100644 --- a/src/compiler/compat-block-utility.js +++ b/src/compiler/compat-block-utility.js @@ -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; } } diff --git a/src/compiler/jsexecute.js b/src/compiler/jsexecute.js index ded46dab..84498bc8 100644 --- a/src/compiler/jsexecute.js +++ b/src/compiler/jsexecute.js @@ -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. diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index 42ba07de..e5d2650e 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -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) {