diff --git a/src/blocks/scratch3_procedures.js b/src/blocks/scratch3_procedures.js index 498116de65..b981a0389e 100644 --- a/src/blocks/scratch3_procedures.js +++ b/src/blocks/scratch3_procedures.js @@ -15,6 +15,7 @@ class Scratch3ProcedureBlocks { return { procedures_definition: this.definition, procedures_call: this.call, + procedures_return: this.return, argument_reporter_string_number: this.argumentReporterStringNumber, argument_reporter_boolean: this.argumentReporterBoolean }; @@ -25,45 +26,76 @@ class Scratch3ProcedureBlocks { } call (args, util) { - if (!util.stackFrame.executed) { - const procedureCode = args.mutation.proccode; - const paramNamesIdsAndDefaults = util.getProcedureParamNamesIdsAndDefaults(procedureCode); - - // If null, procedure could not be found, which can happen if custom - // block is dragged between sprites without the definition. - // Match Scratch 2.0 behavior and noop. - if (paramNamesIdsAndDefaults === null) { - return; + const stackFrame = util.stackFrame; + const isReporter = !!args.mutation.return; + + if (stackFrame.executed) { + if (isReporter) { + const returnValue = stackFrame.returnValue; + // This stackframe will be reused for other reporters in this block, so clean it up for them. + // Can't use reset() because that will reset too much. + const threadStackFrame = util.thread.peekStackFrame(); + threadStackFrame.params = null; + threadStackFrame.executionContext = null; + return returnValue; } + return; + } - const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults; - - // Initialize params for the current stackFrame to {}, even if the procedure does - // not take any arguments. This is so that `getParam` down the line does not look - // at earlier stack frames for the values of a given parameter (#1729) - util.initParams(); - for (let i = 0; i < paramIds.length; i++) { - if (args.hasOwnProperty(paramIds[i])) { - util.pushParam(paramNames[i], args[paramIds[i]]); - } else { - util.pushParam(paramNames[i], paramDefaults[i]); - } + const procedureCode = args.mutation.proccode; + const paramNamesIdsAndDefaults = util.getProcedureParamNamesIdsAndDefaults(procedureCode); + + // If null, procedure could not be found, which can happen if custom + // block is dragged between sprites without the definition. + // Match Scratch 2.0 behavior and noop. + if (paramNamesIdsAndDefaults === null) { + if (isReporter) { + return ''; } + return; + } - const addonBlock = util.runtime.getAddonBlock(procedureCode); - if (addonBlock) { - const result = addonBlock.callback(util.thread.getAllparams(), util); - if (util.thread.status === 1 /* STATUS_PROMISE_WAIT */) { - // If the addon block is using STATUS_PROMISE_WAIT to force us to sleep, - // make sure to not re-run this block when we resume. - util.stackFrame.executed = true; - } - return result; + const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults; + + // Initialize params for the current stackFrame to {}, even if the procedure does + // not take any arguments. This is so that `getParam` down the line does not look + // at earlier stack frames for the values of a given parameter (#1729) + util.initParams(); + for (let i = 0; i < paramIds.length; i++) { + if (args.hasOwnProperty(paramIds[i])) { + util.pushParam(paramNames[i], args[paramIds[i]]); + } else { + util.pushParam(paramNames[i], paramDefaults[i]); } + } - util.stackFrame.executed = true; + const addonBlock = util.runtime.getAddonBlock(procedureCode); + if (addonBlock) { + const result = addonBlock.callback(util.thread.getAllparams(), util); + if (util.thread.status === 1 /* STATUS_PROMISE_WAIT */) { + // If the addon block is using STATUS_PROMISE_WAIT to force us to sleep, + // make sure to not re-run this block when we resume. + stackFrame.executed = true; + } + return result; + } + + stackFrame.executed = true; + + if (isReporter) { + util.thread.peekStackFrame().waitingReporter = true; + // Default return value + stackFrame.returnValue = ''; + } + + util.startProcedure(procedureCode); + } - util.startProcedure(procedureCode); + return (args, util) { + util.stopThisScript(); + // If used outside of a custom block, there may be no stackframe. + if (util.thread.peekStackFrame()) { + util.stackFrame.returnValue = args.VALUE; } } diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 4a00a626f2..45f3735111 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -535,6 +535,9 @@ class ScriptTreeGenerator { right: this.descendInputOfBlock(block, 'NUM2') }; + case 'procedures_call': + return this.descendProcedure(block); + case 'sensing_answer': return { kind: 'sensing.answer' @@ -1106,102 +1109,25 @@ class ScriptTreeGenerator { }; case 'procedures_call': { - // setting of yields will be handled later in the analysis phase - const procedureCode = block.mutation.proccode; - if (procedureCode === 'tw:debugger;') { - return { - kind: 'tw.debugger' - }; - } - const paramNamesIdsAndDefaults = this.blocks.getProcedureParamNamesIdsAndDefaults(procedureCode); - if (paramNamesIdsAndDefaults === null) { - return { - kind: 'noop' - }; - } - - const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults; - - const addonBlock = this.runtime.getAddonBlock(procedureCode); - if (addonBlock) { - this.script.yields = true; - const args = {}; - for (let i = 0; i < paramIds.length; i++) { - let value; - if (block.inputs[paramIds[i]] && block.inputs[paramIds[i]].block) { - value = this.descendInputOfBlock(block, paramIds[i]); - } else { - value = { - kind: 'constant', - value: paramDefaults[i] - }; - } - args[paramNames[i]] = value; + if (block.mutation.return) { + const visualReport = this.descendVisualReport(block); + if (visualReport) { + return visualReport; } - return { - kind: 'addons.call', - code: procedureCode, - arguments: args, - blockId: block.id - }; } - - const definitionId = this.blocks.getProcedureDefinition(procedureCode); - const definitionBlock = this.blocks.getBlock(definitionId); - if (!definitionBlock) { + if (procedureCode === 'tw:debugger;') { return { - kind: 'noop' + kind: 'tw.debugger' }; } - const innerDefinition = this.blocks.getBlock(definitionBlock.inputs.custom_block.block); - - let isWarp = this.script.isWarp; - if (!isWarp) { - if (innerDefinition && innerDefinition.mutation) { - const warp = innerDefinition.mutation.warp; - if (typeof warp === 'boolean') { - isWarp = warp; - } else if (typeof warp === 'string') { - isWarp = JSON.parse(warp); - } - } - } - - const variant = generateProcedureVariant(procedureCode, isWarp); - - if (!this.script.dependedProcedures.includes(variant)) { - this.script.dependedProcedures.push(variant); - } - - // Non-warp direct recursion yields. - if (!this.script.isWarp) { - if (procedureCode === this.script.procedureCode) { - this.script.yields = true; - } - } - - const args = []; - for (let i = 0; i < paramIds.length; i++) { - let value; - if (block.inputs[paramIds[i]] && block.inputs[paramIds[i]].block) { - value = this.descendInputOfBlock(block, paramIds[i]); - } else { - value = { - kind: 'constant', - value: paramDefaults[i] - }; - } - args.push(value); - } - + return this.descendProcedure(block); + } + case 'procedures_return': return { - kind: 'procedures.call', - code: procedureCode, - variant, - arguments: args + kind: 'procedures.return', + value: this.descendInputOfBlock(block, 'VALUE') }; - } case 'sensing_resettimer': return { @@ -1225,18 +1151,9 @@ class ScriptTreeGenerator { } } - // When this thread was triggered by a stack click, attempt to compile as an input. - // TODO: perhaps this should be moved to generate()? - if (this.thread.stackClick) { - try { - const inputNode = this.descendInput(block); - return { - kind: 'visualReport', - input: inputNode - }; - } catch (e) { - // Ignore - } + const asVisualReport = this.descendVisualReport(block); + if (asVisualReport) { + return asVisualReport; } log.warn(`IR: Unknown stacked block: ${block.opcode}`, block); @@ -1370,6 +1287,97 @@ class ScriptTreeGenerator { return createVariableData('target', newVariable); } + descendProcedure (block) { + const procedureCode = block.mutation.proccode; + const paramNamesIdsAndDefaults = this.blocks.getProcedureParamNamesIdsAndDefaults(procedureCode); + if (paramNamesIdsAndDefaults === null) { + return { + kind: 'noop' + }; + } + + const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults; + + const addonBlock = this.runtime.getAddonBlock(procedureCode); + if (addonBlock) { + this.script.yields = true; + const args = {}; + for (let i = 0; i < paramIds.length; i++) { + let value; + if (block.inputs[paramIds[i]] && block.inputs[paramIds[i]].block) { + value = this.descendInputOfBlock(block, paramIds[i]); + } else { + value = { + kind: 'constant', + value: paramDefaults[i] + }; + } + args[paramNames[i]] = value; + } + return { + kind: 'addons.call', + code: procedureCode, + arguments: args, + blockId: block.id + }; + } + + const definitionId = this.blocks.getProcedureDefinition(procedureCode); + const definitionBlock = this.blocks.getBlock(definitionId); + if (!definitionBlock) { + return { + kind: 'noop' + }; + } + const innerDefinition = this.blocks.getBlock(definitionBlock.inputs.custom_block.block); + + let isWarp = this.script.isWarp; + if (!isWarp) { + if (innerDefinition && innerDefinition.mutation) { + const warp = innerDefinition.mutation.warp; + if (typeof warp === 'boolean') { + isWarp = warp; + } else if (typeof warp === 'string') { + isWarp = JSON.parse(warp); + } + } + } + + const variant = generateProcedureVariant(procedureCode, isWarp); + + if (!this.script.dependedProcedures.includes(variant)) { + this.script.dependedProcedures.push(variant); + } + + // Non-warp direct recursion yields. + if (!this.script.isWarp) { + if (procedureCode === this.script.procedureCode) { + this.script.yields = true; + } + } + + const args = []; + for (let i = 0; i < paramIds.length; i++) { + let value; + if (block.inputs[paramIds[i]] && block.inputs[paramIds[i]].block) { + value = this.descendInputOfBlock(block, paramIds[i]); + } else { + value = { + kind: 'constant', + value: paramDefaults[i] + }; + } + args.push(value); + } + + return { + kind: 'procedures.call', + code: procedureCode, + variant, + arguments: args + }; + } + /** * Descend into a block that uses the compatibility layer. * @param {*} block The block to use the compatibility layer for. @@ -1431,6 +1439,20 @@ class ScriptTreeGenerator { } } + descendVisualReport (block) { + if (!this.thread.stackClick || block.next) { + return null; + } + try { + return { + kind: 'visualReport', + input: this.descendInput(block) + }; + } catch (e) { + return null; + } + } + /** * @param {string} topBlockId The ID of the top block of the script. * @returns {IntermediateScript} diff --git a/src/compiler/jsexecute.js b/src/compiler/jsexecute.js index e9e4a3ac68..40109b339f 100644 --- a/src/compiler/jsexecute.js +++ b/src/compiler/jsexecute.js @@ -549,6 +549,26 @@ runtimeFunctions.tan = `const tan = (angle) => { return Math.round(Math.tan((Math.PI * angle) / 180) * 1e10) / 1e10; }`; +/** + * @param {function} callback The function to run + * @param {...unknown} args The arguments to pass to the function + * @returns {unknown} A generator that will yield once then call the function and return its value. + */ +runtimeFunctions.yieldThenCall = `const yieldThenCall = function* (callback, ...args) { + yield; + return callback(...args); +}`; + +/** + * @param {function} callback The generator function to run + * @param {...unknown} args The arguments to pass to the generator function + * @returns {unknown} A generator that will yield once then delegate to the generator function and return its value. + */ +runtimeFunctions.yieldThenCallGenerator = `const yieldThenCallGenerator = function* (callback, ...args) { + yield; + return yield* callback(...args); +}`; + /** * Step a compiled thread. * @param {Thread} thread The thread to step. diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index 6dea28b15f..ac3d3e2f20 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -428,6 +428,9 @@ class JSGenerator { */ descendInput (node) { switch (node.kind) { + case 'addons.call': + return new TypedInput(`(${this.descendAddonCall(node)})`, TYPE_UNKNOWN); + case 'args.boolean': return new TypedInput(`toBoolean(p${node.index})`, TYPE_BOOLEAN); case 'args.stringNumber': @@ -623,6 +626,37 @@ class JSGenerator { case 'op.10^': return new TypedInput(`(10 ** ${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER); + case 'procedures.call': { + const procedureCode = node.code; + const procedureVariant = node.variant; + const procedureData = this.ir.procedures[procedureVariant]; + if (procedureData.stack === null) { + // TODO still need to evaluate arguments for side effects + return new TypedInput('""', TYPE_STRING); + } + + // Recursion makes this complicated because: + // - We need to yield *between* each call in the same command block + // - We need to evaluate arguments *before* that yield happens + + const procedureReference = `thread.procedures["${sanitize(procedureVariant)}"]`; + const args = []; + for (const input of node.arguments) { + args.push(this.descendInput(input).asSafe()); + } + const joinedArgs = args.join(','); + + const yieldForRecursion = !this.isWarp && procedureCode === this.script.procedureCode; + if (yieldForRecursion) { + const runtimeFunction = procedureData.yields ? 'yieldThenCallGenerator' : 'yieldThenCall'; + return new TypedInput(`(yield* ${runtimeFunction}(${procedureReference}, ${joinedArgs}))`, TYPE_UNKNOWN); + } + if (procedureData.yields) { + return new TypedInput(`(yield* ${procedureReference}(${joinedArgs}))`, TYPE_UNKNOWN); + } + return new TypedInput(`${procedureReference}(${joinedArgs})`, TYPE_UNKNOWN); + } + case 'sensing.answer': return new TypedInput(`runtime.ext_scratch3_sensing._answer`, TYPE_STRING); case 'sensing.colorTouchingColor': @@ -713,13 +747,9 @@ class JSGenerator { */ descendStackedBlock (node) { switch (node.kind) { - case 'addons.call': { - const inputs = this.descendInputRecord(node.arguments); - const blockFunction = `runtime.getAddonBlock("${sanitize(node.code)}").callback`; - const blockId = `"${sanitize(node.blockId)}"`; - this.source += `yield* executeInCompatibilityLayer(${inputs}, ${blockFunction}, ${this.isWarp}, false, ${blockId});\n`; + case 'addons.call': + this.source += `${this.descendAddonCall(node)};\n`; break; - } case 'compat': { // If the last command in a loop returns a promise, immediately continue to the next iteration. @@ -781,11 +811,7 @@ class JSGenerator { this.source += 'runtime.stopForTarget(target, thread);\n'; break; case 'control.stopScript': - if (this.isProcedure) { - this.source += 'return;\n'; - } else { - this.retire(); - } + this.stopScript(); break; case 'control.wait': { const duration = this.localVariables.next(); @@ -1016,35 +1042,34 @@ class JSGenerator { case 'procedures.call': { const procedureCode = node.code; const procedureVariant = node.variant; - // Do not generate any code for empty procedures. const procedureData = this.ir.procedures[procedureVariant]; if (procedureData.stack === null) { + // TODO still need to evaluate arguments break; } - if (!this.isWarp && procedureCode === this.script.procedureCode) { - // Direct recursion yields. + + const yieldForRecursion = !this.isWarp && procedureCode === this.script.procedureCode; + if (yieldForRecursion) { this.yieldNotWarp(); } + if (procedureData.yields) { this.source += 'yield* '; - if (!this.script.yields) { - throw new Error('Script uses yielding procedure but is not marked as yielding.'); - } } this.source += `thread.procedures["${sanitize(procedureVariant)}"](`; - // Only include arguments if the procedure accepts any. - if (procedureData.arguments.length) { - const args = []; - for (const input of node.arguments) { - args.push(this.descendInput(input).asSafe()); - } - this.source += args.join(','); + const args = []; + for (const input of node.arguments) { + args.push(this.descendInput(input).asSafe()); } - this.source += `);\n`; - // Variable input types may have changes after a procedure call. + this.source += args.join(','); + this.source += ');\n'; + this.resetVariableInputs(); break; } + case 'procedures.return': + this.stopScriptAndReturn(this.descendInput(node.value).asSafe()); + break; case 'timer.reset': this.source += 'runtime.ioDevices.clock.resetProjectTimer();\n'; @@ -1137,6 +1162,13 @@ class JSGenerator { return this.evaluateOnce(`stage.variables["${sanitize(variable.id)}"]`); } + descendAddonCall (node) { + const inputs = this.descendInputRecord(node.arguments); + const blockFunction = `runtime.getAddonBlock("${sanitize(node.code)}").callback`; + const blockId = `"${sanitize(node.blockId)}"`; + return `yield* executeInCompatibilityLayer(${inputs}, ${blockFunction}, ${this.isWarp}, false, ${blockId})`; + } + evaluateOnce (source) { if (this._setupVariables.hasOwnProperty(source)) { return this._setupVariables[source]; @@ -1157,6 +1189,25 @@ class JSGenerator { } } + stopScript () { + if (this.isProcedure) { + this.source += 'return "";\n'; + } else { + this.retire(); + } + } + + /** + * @param {string} valueJS JS code of value to return. + */ + stopScriptAndReturn (valueJS) { + if (this.isProcedure) { + this.source += `return ${valueJS};\n`; + } else { + this.retire(); + } + } + yieldLoop () { if (this.warpTimer) { this.yieldStuckOrNotWarp(); @@ -1286,10 +1337,6 @@ class JSGenerator { script += this.source; - if (!this.isProcedure) { - script += 'retire();\n'; - } - script += '}; })'; return script; @@ -1303,6 +1350,7 @@ class JSGenerator { if (this.script.stack) { this.descendStack(this.script.stack, new Frame(false)); } + this.stopScript(); const factory = this.createScriptFactory(); const fn = jsexecute.scopedEval(factory); diff --git a/src/engine/execute.js b/src/engine/execute.js index 1dfe7fa19b..6324798b86 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -490,6 +490,7 @@ const execute = function (sequencer, thread) { currentStackFrame.reporting = null; currentStackFrame.reported = null; + currentStackFrame.waitingReporter = false; } const start = i; @@ -497,6 +498,7 @@ const execute = function (sequencer, thread) { for (; i < length; i++) { const lastOperation = i === length - 1; const opCached = ops[i]; + currentStackFrame.op = opCached; const blockFunction = opCached._blockFunction; @@ -517,9 +519,11 @@ const execute = function (sequencer, thread) { const primitiveReportedValue = blockFunction(argValues, blockUtility); - // If it's a promise, wait until promise resolves. - if (isPromise(primitiveReportedValue)) { - handlePromise(primitiveReportedValue, sequencer, thread, opCached, lastOperation); + const primitiveIsPromise = isPromise(primitiveReportedValue); + if (primitiveIsPromise || currentStackFrame.waitingReporter) { + if (primitiveIsPromise) { + handlePromise(primitiveReportedValue, sequencer, thread, opCached, lastOperation); + } // Store the already reported values. They will be thawed into the // future versions of the same operations by block id. The reporting @@ -543,7 +547,7 @@ const execute = function (sequencer, thread) { }; }); - // We are waiting for a promise. Stop running this set of operations + // We are waiting to be resumed later. Stop running this set of operations // and continue them later after thawing the reported values. break; } else if (thread.status === Thread.STATUS_RUNNING) { diff --git a/src/engine/runtime.js b/src/engine/runtime.js index eae8b773fd..0fddccbb12 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -2587,14 +2587,17 @@ class Runtime extends EventEmitter { * @param {object} options Options object * @param {string} options.procedureCode The ID of the block * @param {function} options.callback The callback, called with (args, BlockUtility). May return a promise. - * @param {string[]} options.arguments Names of the arguments accepted - * @param {boolean} [hidden] True to not include this block in the block palette + * @param {string[]} [options.arguments] Names of the arguments accepted. Optional if no arguments. + * @param {boolean} [options.hidden] True to not include this block in the block palette + * @param {1|2} [options.return] 1 for round reporter, 2 for boolean reported, leave empty for statement. */ addAddonBlock (options) { const procedureCode = options.procedureCode; - const names = options.arguments; - const ids = options.arguments.map((_, i) => `arg${i}`); - const defaults = options.arguments.map(() => ''); + + const argumentNames = options.arguments || []; + const names = argumentNames; + const ids = argumentNames.map((_, i) => `arg${i}`); + const defaults = argumentNames.map(() => ''); this.addonBlocks[procedureCode] = { namesIdsDefaults: [names, ids, defaults], ...options @@ -2631,6 +2634,7 @@ class Runtime extends EventEmitter { ` argumentnames="${xmlEscape(JSON.stringify(names))}"` + ` argumentids="${xmlEscape(JSON.stringify(ids))}"` + ` argumentdefaults="${xmlEscape(JSON.stringify(defaults))}"` + + `${options.return ? ` return="${xmlEscape(options.return.toString())}"` : ''}` + '>' }); } diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index 74f6474052..bcc4a3c281 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -239,7 +239,7 @@ class Sequencer { return; } // If no control flow has happened, switch to next block. - if (thread.peekStack() === currentBlockId) { + if (thread.peekStack() === currentBlockId && !thread.peekStackFrame().waitingReporter) { thread.goToNextBlock(); } // If no next block has been found at this point, look on the stack. @@ -274,7 +274,7 @@ class Sequencer { // This level of the stack was waiting for a value. // This means a reporter has just returned - so don't go // to the next block for this level of the stack. - return; + continue; } // Get next block of existing block on the stack. thread.goToNextBlock(); diff --git a/src/engine/thread.js b/src/engine/thread.js index 018a16e2f6..a48b356dcb 100644 --- a/src/engine/thread.js +++ b/src/engine/thread.js @@ -63,6 +63,13 @@ class _StackFrame { * @type {Object} */ this.executionContext = null; + + /** + * Internal block object being executed. This is *not* the same as the object found + * in target.blocks. + * @type {object} + */ + this.op = null; } /** @@ -79,6 +86,7 @@ class _StackFrame { this.waitingReporter = null; this.params = null; this.executionContext = null; + this.op = null; return this; } @@ -307,7 +315,10 @@ class Thread { let blockID = this.peekStack(); while (blockID !== null) { const block = this.target.blocks.getBlock(blockID); - if (typeof block !== 'undefined' && block.opcode === 'procedures_call') { + if ( + (typeof block !== 'undefined' && block.opcode === 'procedures_call') || + this.peekStackFrame().waitingReporter + ) { break; } this.popStack(); @@ -426,9 +437,9 @@ class Thread { */ isRecursiveCall (procedureCode) { let callCount = 5; // Max number of enclosing procedure calls to examine. - const sp = this.stack.length - 1; + const sp = this.stackFrames.length - 1; for (let i = sp - 1; i >= 0; i--) { - const block = this.target.blocks.getBlock(this.stack[i]); + const block = this.target.blocks.getBlock(this.stackFrames[i].op.id); if (block.opcode === 'procedures_call' && block.mutation.proccode === procedureCode) { return true; diff --git a/test/fixtures/execute/tw-procedure-return-non-existant.sb3 b/test/fixtures/execute/tw-procedure-return-non-existant.sb3 new file mode 100644 index 0000000000..0804d7a10c Binary files /dev/null and b/test/fixtures/execute/tw-procedure-return-non-existant.sb3 differ diff --git a/test/fixtures/execute/tw-procedure-return-recursion.sb3 b/test/fixtures/execute/tw-procedure-return-recursion.sb3 new file mode 100644 index 0000000000..9f46c91b64 Binary files /dev/null and b/test/fixtures/execute/tw-procedure-return-recursion.sb3 differ diff --git a/test/fixtures/execute/tw-procedure-return-simple.sb3 b/test/fixtures/execute/tw-procedure-return-simple.sb3 new file mode 100644 index 0000000000..1714cc0e42 Binary files /dev/null and b/test/fixtures/execute/tw-procedure-return-simple.sb3 differ diff --git a/test/fixtures/execute/tw-procedure-return-stops-scripts.sb3 b/test/fixtures/execute/tw-procedure-return-stops-scripts.sb3 new file mode 100644 index 0000000000..0acdda88f1 Binary files /dev/null and b/test/fixtures/execute/tw-procedure-return-stops-scripts.sb3 differ diff --git a/test/fixtures/execute/tw-procedure-return-warp.sb3 b/test/fixtures/execute/tw-procedure-return-warp.sb3 new file mode 100644 index 0000000000..9043730428 Binary files /dev/null and b/test/fixtures/execute/tw-procedure-return-warp.sb3 differ diff --git a/test/fixtures/tw-addon-blocks.sb3 b/test/fixtures/tw-addon-blocks.sb3 index 245a7a30ef..dd2b66b995 100644 Binary files a/test/fixtures/tw-addon-blocks.sb3 and b/test/fixtures/tw-addon-blocks.sb3 differ diff --git a/test/fixtures/tw-stackframe-op.sb3 b/test/fixtures/tw-stackframe-op.sb3 new file mode 100644 index 0000000000..238216c945 Binary files /dev/null and b/test/fixtures/tw-stackframe-op.sb3 differ diff --git a/test/integration/tw_addon_blocks.js b/test/integration/tw_addon_blocks.js index a8a1dc240a..e48ddfe816 100644 --- a/test/integration/tw_addon_blocks.js +++ b/test/integration/tw_addon_blocks.js @@ -2,145 +2,269 @@ const tap = require('tap'); const fs = require('fs'); const path = require('path'); const VirtualMachine = require('../../src/virtual-machine'); +const Thread = require('../../src/engine/thread'); const fixtureData = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'tw-addon-blocks.sb3')); -const runExecutionTests = compilerEnabled => async test => { +const runExecutionTests = compilerEnabled => test => { const load = async () => { const vm = new VirtualMachine(); vm.setCompilerOptions({ enabled: compilerEnabled }); + vm.on('COMPILE_ERROR', (target, error) => { + test.fail(`Compile error in ${target.getName()}: ${error}`); + }); await vm.loadProject(fixtureData); - vm.on('COMPILE_ERROR', (target, error) => test.fail(`Compile error ${target.getName()} ${error}`)); return vm; }; - const getOutput = vm => vm.runtime.getTargetForStage().lookupVariableByNameAndType('output').value; - - await test.test('simple use', async t => { - t.plan(7); - - const vm = await load(); - - let calledBlock1 = false; - let calledBlock2 = false; - - vm.addAddonBlock({ - procedureCode: 'block 2 %s', - callback: (args, util) => { - calledBlock1 = true; - t.type(util.thread, 'object'); - // may have to update this ID when the project changes to match whatever the ID is for the - // procedures_call block to block 2 %s - t.equal(util.thread.peekStack(), 'c'); - t.same(args, { - 'number or text': 'banana' - }); - }, - arguments: ['number or text'] - }); - - vm.addAddonBlock({ - procedureCode: 'block 3', - // eslint-disable-next-line no-unused-vars - callback: (args, util) => { - calledBlock2 = true; - t.same(args, {}); - }, - arguments: [] + const getVar = (vm, variableName) => { + const variable = vm.runtime.getTargetForStage().lookupVariableByNameAndType(variableName, ''); + return variable.value; + }; + + test.test('baseline - no addon blocks', t => { + load().then(vm => { + t.equal(getVar(vm, 'block 1'), 'initial'); + t.equal(getVar(vm, 'block 2'), 'initial'); + t.equal(getVar(vm, 'block 3'), 'initial'); + t.equal(getVar(vm, 'block 4'), 'initial'); + t.equal(getVar(vm, 'block 4 output'), 'initial'); + + vm.greenFlag(); + vm.runtime._step(); + + t.equal(getVar(vm, 'block 1'), 'block 1 ran'); + t.equal(getVar(vm, 'block 2'), 'block 2: banana'); + t.equal(getVar(vm, 'block 3'), 'block 3 ran'); + t.equal(getVar(vm, 'block 4'), 'block 4 ran'); + t.equal(getVar(vm, 'block 4 output'), 'block 4: apple'); + + t.end(); }); - - vm.greenFlag(); - vm.runtime._step(); - - t.equal(getOutput(vm), 'block 1 value'); - t.ok(calledBlock1); - t.ok(calledBlock2); - t.end(); }); - await test.test('yield by thread.status = STATUS_PROMISE_WAIT', async t => { - const vm = await load(); + test.test('simple statement blocks', t => { + load().then(vm => { + t.plan(9); + + let calledBlock1 = false; + let calledBlock2 = false; + + vm.addAddonBlock({ + procedureCode: 'block 1', + callback: (args, util) => { + calledBlock1 = true; + t.same(args, {}); + t.ok(util.thread instanceof Thread); + // may have to update when project changes + t.equal(util.thread.peekStack(), 'd'); + } + }); + + vm.addAddonBlock({ + procedureCode: 'block 2 %s', + arguments: ['number or text'], + callback: (args, util) => { + calledBlock2 = true; + t.same(args, { + 'number or text': 'banana' + }); + // may have to update when project changes + t.equal(util.thread.peekStack(), 'c'); + } + }); + + vm.greenFlag(); + vm.runtime._step(); + + t.ok(calledBlock1); + t.ok(calledBlock2); - let threadToResume; + // Overridden blocks should not run + t.equal(getVar(vm, 'block 1'), 'false'); + t.equal(getVar(vm, 'block 2'), 'false'); - vm.addAddonBlock({ - procedureCode: 'block 1', - callback: (args, util) => { - util.thread.status = 1; // STATUS_PROMISE_WAIT - threadToResume = util.thread; - }, - arguments: [] + t.end(); }); + }); - vm.greenFlag(); - vm.runtime._step(); - if (!threadToResume) { - t.fail('did not run addon block'); - } + test.test('yield with thread.status = STATUS_PROMISE_WAIT', t => { + load().then(vm => { + t.plan(7); + + let threadToResume; + let ranBlock3 = false; + vm.addAddonBlock({ + procedureCode: 'block 3', + callback: (args, util) => { + ranBlock3 = true; + util.thread.status = Thread.STATUS_PROMISE_WAIT; + threadToResume = util.thread; + }, + arguments: [] + }); + + vm.greenFlag(); + vm.runtime._step(); - t.equal(getOutput(vm), 'initial value'); - threadToResume.status = 0; // STATUS_RUNNING - vm.runtime._step(); - t.equal(getOutput(vm), 'block 3 value'); + // Make sure we paused it + t.ok(ranBlock3); + t.equal(threadToResume.status, Thread.STATUS_PROMISE_WAIT); - t.end(); - }); + // Should've stopped after block 2 + t.equal(getVar(vm, 'block 2'), 'block 2: banana'); + t.equal(getVar(vm, 'block 3'), 'false'); + t.equal(getVar(vm, 'block 4'), 'false'); - await test.test('yield by block utility methods', async t => { - const vm = await load(); + threadToResume.status = Thread.STATUS_RUNNING; + vm.runtime._step(); - let shouldYield = true; + // Should've finished running + t.equal(getVar(vm, 'block 3'), 'false'); // overridden, should not run + t.equal(getVar(vm, 'block 4'), 'block 4 ran'); - vm.addAddonBlock({ - procedureCode: 'block 1', - callback: (args, util) => { - if (shouldYield) { - util.runtime.requestRedraw(); - util.yield(); - } - }, - arguments: [] + t.end(); }); + }); - vm.greenFlag(); - for (let i = 0; i < 10; i++) { + test.test('yield with util.yield()', t => { + load().then(vm => { + t.plan(10); + + let shouldYield = true; + let calledBlock1 = 0; + + vm.addAddonBlock({ + procedureCode: 'block 1', + callback: (args, util) => { + calledBlock1++; + + if (shouldYield) { + util.runtime.requestRedraw(); + util.yield(); + } + }, + arguments: [] + }); + + vm.greenFlag(); + for (let i = 0; i < 10; i++) { + vm.runtime._step(); + } + + t.equal(calledBlock1, 10); + t.equal(getVar(vm, 'block 1'), 'false'); + t.equal(getVar(vm, 'block 2'), 'false'); + t.equal(getVar(vm, 'block 3'), 'false'); + t.equal(getVar(vm, 'block 4'), 'false'); + + shouldYield = false; vm.runtime._step(); - } - t.equal(getOutput(vm), 'initial value'); - shouldYield = false; - vm.runtime._step(); - t.equal(getOutput(vm), 'block 3 value'); + t.equal(calledBlock1, 11); + t.equal(getVar(vm, 'block 1'), 'false'); // overrridden, should not run + t.equal(getVar(vm, 'block 2'), 'block 2: banana'); + t.equal(getVar(vm, 'block 3'), 'block 3 ran'); + t.equal(getVar(vm, 'block 4'), 'block 4 ran'); - t.end(); + t.end(); + }); }); - await test.test('yield by returning Promise', async t => { - const vm = await load(); - - let resolveCallback; - vm.addAddonBlock({ - procedureCode: 'block 1', - callback: () => new Promise(resolve => { - resolveCallback = resolve; - }), - arguments: [] + test.test('yield with resolved Promise', t => { + load().then(vm => { + let resolveCallback; + vm.addAddonBlock({ + procedureCode: 'block 2 %s', + arguments: ['number or text'], + callback: () => new Promise(resolve => { + resolveCallback = resolve; + }) + }); + + vm.greenFlag(); + for (let i = 0; i < 5; i++) { + vm.runtime._step(); + } + + t.type(resolveCallback, 'function'); + t.equal(getVar(vm, 'block 1'), 'block 1 ran'); + t.equal(getVar(vm, 'block 2'), 'false'); + t.equal(getVar(vm, 'block 3'), 'false'); + t.equal(getVar(vm, 'block 4'), 'false'); + + resolveCallback(); + Promise.resolve().then(() => { + vm.runtime._step(); + t.equal(getVar(vm, 'block 2'), 'false'); // overridden, should not run + t.equal(getVar(vm, 'block 3'), 'block 3 ran'); + t.equal(getVar(vm, 'block 4'), 'block 4 ran'); + + t.end(); + }); }); + }); - vm.greenFlag(); - vm.runtime._step(); - t.equal(getOutput(vm), 'initial value'); + test.skip('yield with resolved Promise', t => { + load().then(vm => { + let rejectCallback; + vm.addAddonBlock({ + procedureCode: 'block 2 %s', + arguments: ['number or text'], + callback: () => new Promise((resolve, reject) => { + rejectCallback = reject; + }) + }); + + vm.greenFlag(); + for (let i = 0; i < 5; i++) { + vm.runtime._step(); + } + + t.type(rejectCallback, 'function'); + t.equal(getVar(vm, 'block 1'), 'block 1 ran'); + t.equal(getVar(vm, 'block 2'), 'false'); + t.equal(getVar(vm, 'block 3'), 'false'); + t.equal(getVar(vm, 'block 4'), 'false'); + + rejectCallback(new Error('Intentional error for testing')); + Promise.resolve().then(() => { + vm.runtime._step(); + t.equal(getVar(vm, 'block 2'), 'false'); // overridden, should not run + t.equal(getVar(vm, 'block 3'), 'block 3 ran'); + t.equal(getVar(vm, 'block 4'), 'block 4 ran'); + + t.end(); + }); + }); + }); - resolveCallback(); - // Allow the promise callback to run - await Promise.resolve(); + test.test('returning values', t => { + load().then(vm => { + vm.addAddonBlock({ + procedureCode: 'block 4 %s', + callback: args => { + t.same(args, { + 'number or text': 'apple' + }); + return `value from addon block: ${args['number or text']}`; + }, + arguments: ['number or text'] + }); + + vm.greenFlag(); + vm.runtime._step(); - vm.runtime._step(); - t.equal(getOutput(vm), 'block 3 value'); + t.equal(getVar(vm, 'block 1'), 'block 1 ran'); + t.equal(getVar(vm, 'block 2'), 'block 2: banana'); + t.equal(getVar(vm, 'block 3'), 'block 3 ran'); + t.equal(getVar(vm, 'block 4'), 'false'); // block 4 itself should not have run, we overrode it + t.equal(getVar(vm, 'block 4 output'), 'value from addon block: apple'); - t.end(); + t.end(); + }); }); test.end(); @@ -165,10 +289,16 @@ tap.test('block info', t => { t.equal(blockInfo, undefined); vm.addAddonBlock({ - procedureCode: 'something %s', + procedureCode: 'statement %s', arguments: ['number or text'], callback: () => {} }); + vm.addAddonBlock({ + procedureCode: 'input %s', + arguments: ['an argument'], + callback: () => {}, + return: 1 + }); blockInfo = vm.runtime._blockInfo.find(i => i.id === BLOCK_INFO_ID); t.type(blockInfo.id, 'string'); @@ -180,7 +310,12 @@ tap.test('block info', t => { { info: {}, // eslint-disable-next-line max-len - xml: '' + xml: '' + }, + { + info: {}, + // eslint-disable-next-line max-len + xml: '' } ]); diff --git a/test/integration/tw_stackframe_op.js b/test/integration/tw_stackframe_op.js new file mode 100644 index 0000000000..2d632588ca --- /dev/null +++ b/test/integration/tw_stackframe_op.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const path = require('path'); +const {test} = require('tap'); +const VirtualMachine = require('../../src/virtual-machine'); + +const projectPath = path.join(__dirname, '..', 'fixtures', 'tw-stackframe-op.sb3'); + +test('util.thread.peekStackFrame().op', t => { + const vm = new VirtualMachine(); + + vm.runtime.setCompilerOptions({ + enabled: false + }); + + // Easiest way to test this is to overwrite an existing block + vm.runtime._primitives.operator_add = function (args, util) { + const op = util.thread.peekStackFrame().op; + t.equal(op.id, 'c'); + t.end(); + + // return value not used + return 4; + }; + + vm.loadProject(fs.readFileSync(projectPath)).then(() => { + vm.greenFlag(); + vm.runtime._step(); + }); +}); diff --git a/test/snapshot/__snapshots__/order-library-reverse.sb3.tw-snapshot b/test/snapshot/__snapshots__/order-library-reverse.sb3.tw-snapshot index 83a524246e..9fb6e5dcc3 100644 --- a/test/snapshot/__snapshots__/order-library-reverse.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/order-library-reverse.sb3.tw-snapshot @@ -22,7 +22,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 1 != " + ("" + b0.value)),}, b2, false, false, null); } -retire(); +retire(); return; }; }) // Sprite3 script @@ -56,7 +56,7 @@ yield; } thread.timer = null; yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -80,5 +80,5 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 2 != " + ("" + b0.value)),}, b2, false, false, null); } -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/order-library.sb3.tw-snapshot b/test/snapshot/__snapshots__/order-library.sb3.tw-snapshot index 8d5b86d442..b3d290734d 100644 --- a/test/snapshot/__snapshots__/order-library.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/order-library.sb3.tw-snapshot @@ -32,7 +32,7 @@ yield; } thread.timer = null; yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -56,7 +56,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 1 != " + ("" + b0.value)),}, b2, false, false, null); } -retire(); +retire(); return; }; }) // Sprite2 script @@ -80,5 +80,5 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 2 != " + ("" + b0.value)),}, b2, false, false, null); } -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot index 3da0d357d4..9ef871b3d1 100644 --- a/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot @@ -67,5 +67,5 @@ if (compareEqual(((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-add-can-return-nan.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-add-can-return-nan.sb3.tw-snapshot index c0559ddbba..8d5ef26746 100644 --- a/test/snapshot/__snapshots__/tw-add-can-return-nan.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-add-can-return-nan.sb3.tw-snapshot @@ -10,5 +10,5 @@ if (!((Infinity + -Infinity) <= 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-all-at-once.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-all-at-once.sb3.tw-snapshot index 6654884071..a411f91426 100644 --- a/test/snapshot/__snapshots__/tw-all-at-once.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-all-at-once.sb3.tw-snapshot @@ -10,5 +10,5 @@ if (true) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-broadcast-id-and-name-desync.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-broadcast-id-and-name-desync.sb3.tw-snapshot index 9fcae5532e..0001992170 100644 --- a/test/snapshot/__snapshots__/tw-broadcast-id-and-name-desync.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-broadcast-id-and-name-desync.sb3.tw-snapshot @@ -6,7 +6,7 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -16,5 +16,5 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, null); yield* waitThreads(startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "message1" })); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-change-size-does-not-use-rounded-size.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-change-size-does-not-use-rounded-size.sb3.tw-snapshot index d44534337e..47a89a5940 100644 --- a/test/snapshot/__snapshots__/tw-change-size-does-not-use-rounded-size.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-change-size-does-not-use-rounded-size.sb3.tw-snapshot @@ -18,5 +18,5 @@ if (((+b1.value || 0) === 20)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-color-input-returns-hex.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-color-input-returns-hex.sb3.tw-snapshot index f5cbbe5960..d0d058ca2f 100644 --- a/test/snapshot/__snapshots__/tw-color-input-returns-hex.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-color-input-returns-hex.sb3.tw-snapshot @@ -12,5 +12,5 @@ if ((("" + b1.value).toLowerCase() === "#22388a".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-comparison-matrix-inline.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-comparison-matrix-inline.sb3.tw-snapshot index 5c76fc1cad..6983ba45b8 100644 --- a/test/snapshot/__snapshots__/tw-comparison-matrix-inline.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-comparison-matrix-inline.sb3.tw-snapshot @@ -1771,5 +1771,5 @@ if (!(("" + compareGreaterThan("", "")).toLowerCase() === "false".toLowerCase()) yield* executeInCompatibilityLayer({"MESSAGE":"fail 588: should be > ",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-comparison-matrix-runtime.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-comparison-matrix-runtime.sb3.tw-snapshot index 9981e8e130..e0b33e4cd6 100644 --- a/test/snapshot/__snapshots__/tw-comparison-matrix-runtime.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-comparison-matrix-runtime.sb3.tw-snapshot @@ -8,7 +8,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b0, false, false, null); yield* thread.procedures["Wrun test"](); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 run test @@ -43,6 +43,7 @@ if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;} } } } +return ""; }; }) // Sprite1 setup values @@ -126,4 +127,5 @@ b0.value.push(" "); b0._monitorUpToDate = false; b0.value.push("🎉"); b0._monitorUpToDate = false; +return ""; }; }) diff --git a/test/snapshot/__snapshots__/tw-coordinate-precision.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-coordinate-precision.sb3.tw-snapshot index 06c67b745a..8b09c54033 100644 --- a/test/snapshot/__snapshots__/tw-coordinate-precision.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-coordinate-precision.sb3.tw-snapshot @@ -32,5 +32,5 @@ if ((limitPrecision(target.x) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass x slightly below 0 rounds",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-forkphorus-515-boolean-number-comparison.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-forkphorus-515-boolean-number-comparison.sb3.tw-snapshot index c984a1e75d..f1c7c9c441 100644 --- a/test/snapshot/__snapshots__/tw-forkphorus-515-boolean-number-comparison.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-forkphorus-515-boolean-number-comparison.sb3.tw-snapshot @@ -19,5 +19,5 @@ if (compareLessThan(("something".toLowerCase() === "else".toLowerCase()), (1 + 0 yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot index 13f910ed0e..19981ca8de 100644 --- a/test/snapshot/__snapshots__/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot @@ -24,5 +24,5 @@ if ((target.direction === 90)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 4",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-forkphorus-515-random-with-invalid-number-with-period.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-forkphorus-515-random-with-invalid-number-with-period.sb3.tw-snapshot index c3c297df67..44ea509f38 100644 --- a/test/snapshot/__snapshots__/tw-forkphorus-515-random-with-invalid-number-with-period.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-forkphorus-515-random-with-invalid-number-with-period.sb3.tw-snapshot @@ -28,5 +28,5 @@ if ((("" + b1.value).toLowerCase().indexOf(".".toLowerCase()) !== -1)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-forkphorus-515-variable-id-name-desync-name-fallback.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-forkphorus-515-variable-id-name-desync-name-fallback.sb3.tw-snapshot index 9b47965efd..d394fffa07 100644 --- a/test/snapshot/__snapshots__/tw-forkphorus-515-variable-id-name-desync-name-fallback.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-forkphorus-515-variable-id-name-desync-name-fallback.sb3.tw-snapshot @@ -19,5 +19,5 @@ if (((+(b2.value[(1 | 0) - 1] ?? "") || 0) === 3)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass list",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-forkphorus-515-wait-zero-seconds-in-warp-mode.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-forkphorus-515-wait-zero-seconds-in-warp-mode.sb3.tw-snapshot index 901446f175..c474c52c88 100644 --- a/test/snapshot/__snapshots__/tw-forkphorus-515-wait-zero-seconds-in-warp-mode.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-forkphorus-515-wait-zero-seconds-in-warp-mode.sb3.tw-snapshot @@ -18,7 +18,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b2, false, false, null); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b2, false, false, null); runtime.stopAll(); retire(); return; -retire(); +retire(); return; }; }) // Sprite1 no refresh @@ -38,12 +38,14 @@ yield* executeInCompatibilityLayer({"BEATS":0,}, b0, true, false, null); yield* executeInCompatibilityLayer({"DRUM":1,"BEATS":0,}, b1, true, false, null); runtime.ext_scratch3_motion._moveSteps(0, target); } +return ""; }; }) // Sprite1 runs below with no refresh (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function* genXYZ_runs_below_with_no_r () { yield* thread.procedures["Whas refresh"](); +return ""; }; }) // Sprite1 has refresh @@ -63,6 +65,7 @@ yield* executeInCompatibilityLayer({"BEATS":0,}, b0, true, false, null); yield* executeInCompatibilityLayer({"DRUM":1,"BEATS":0,}, b1, true, false, null); runtime.ext_scratch3_motion._moveSteps(0, target); } +return ""; }; }) // Sprite1 script @@ -73,5 +76,5 @@ while (true) { b0.value = ((+b0.value || 0) + 1); yield; } -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-list-any.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-list-any.sb3.tw-snapshot index 9c1558e0ec..47fb9c0042 100644 --- a/test/snapshot/__snapshots__/tw-list-any.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-list-any.sb3.tw-snapshot @@ -34,5 +34,5 @@ if (compareEqual(listGet(b1.value, "*"), "")) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-obsolete-blocks.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-obsolete-blocks.sb3.tw-snapshot index 9abad94588..4a2f8c0f8b 100644 --- a/test/snapshot/__snapshots__/tw-obsolete-blocks.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-obsolete-blocks.sb3.tw-snapshot @@ -29,5 +29,5 @@ yield* executeInCompatibilityLayer({"CHANGE":10,}, b9, false, false, null); yield* executeInCompatibilityLayer({}, b10, false, false, null); yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-one-divide-negative-zero.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-one-divide-negative-zero.sb3.tw-snapshot index 5b219446e5..ef51b2fa84 100644 --- a/test/snapshot/__snapshots__/tw-one-divide-negative-zero.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-one-divide-negative-zero.sb3.tw-snapshot @@ -10,5 +10,5 @@ if ((((1 / -0) || 0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot index 026192b774..0624028f3a 100644 --- a/test/snapshot/__snapshots__/tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot @@ -23,5 +23,5 @@ yield; } b0.value = 1; yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b1, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-prefers-first-occurence-of-procedure-387608267.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-prefers-first-occurence-of-procedure-387608267.sb3.tw-snapshot index 44587a8de6..b7521033ca 100644 --- a/test/snapshot/__snapshots__/tw-prefers-first-occurence-of-procedure-387608267.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-prefers-first-occurence-of-procedure-387608267.sb3.tw-snapshot @@ -8,7 +8,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, null); yield* thread.procedures["ZSet Costume"](); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Player Set Costume @@ -16,4 +16,5 @@ retire(); const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_Set_Costume () { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); +return ""; }; }) diff --git a/test/snapshot/__snapshots__/tw-procedure-arguments-with-same-name.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-arguments-with-same-name.sb3.tw-snapshot index bcf0381a94..81002ba0a4 100644 --- a/test/snapshot/__snapshots__/tw-procedure-arguments-with-same-name.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-arguments-with-same-name.sb3.tw-snapshot @@ -9,7 +9,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, null yield* thread.procedures["Znumber or text %s %s"]("bad","ok"); yield* thread.procedures["Zboolean %b %b"]("false",!false); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 number or text %s %s @@ -19,6 +19,7 @@ return function* genXYZ_number_or_text__ (p0,p1) { if ((("" + p1).toLowerCase() === "ok".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } +return ""; }; }) // Sprite1 boolean %b %b @@ -28,4 +29,5 @@ return function* genXYZ_boolean__ (p0,p1) { if (toBoolean(p1)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } +return ""; }; }) diff --git a/test/snapshot/__snapshots__/tw-procedure-call-resets-variable-input-types-430811055.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-call-resets-variable-input-types-430811055.sb3.tw-snapshot index 3c7f4981bb..e01bbe8d5b 100644 --- a/test/snapshot/__snapshots__/tw-procedure-call-resets-variable-input-types-430811055.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-call-resets-variable-input-types-430811055.sb3.tw-snapshot @@ -13,7 +13,7 @@ if (!compareEqual(b1.value, "")) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 do something @@ -21,4 +21,5 @@ retire(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function funXYZ_do_something () { b0.value = "help"; +return ""; }; }) diff --git a/test/snapshot/__snapshots__/tw-procedure-prototype-exists-but-not-definition-549160843.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-prototype-exists-but-not-definition-549160843.sb3.tw-snapshot index d45d0aee37..7f69ac3fad 100644 --- a/test/snapshot/__snapshots__/tw-procedure-prototype-exists-but-not-definition-549160843.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-prototype-exists-but-not-definition-549160843.sb3.tw-snapshot @@ -7,5 +7,5 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b0, false, false, null); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-procedure-return-non-existant.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-non-existant.sb3.tw-snapshot new file mode 100644 index 0000000000..87f5825985 --- /dev/null +++ b/test/snapshot/__snapshots__/tw-procedure-return-non-existant.sb3.tw-snapshot @@ -0,0 +1,54 @@ +// TW Snapshot +// Input SHA-256: b9110339d39947f68168cbb129a20b23b7d4e607ffd5624bd67e096451f6a329 + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +return function* genXYZ () { +b0.value = 0; +while (true) { +b0.value = ((+b0.value || 0) + 1); +thread.timer = timer(); +var a0 = Math.max(0, 1000 * -1); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a0) { +yield; +} +thread.timer = null; +yield; +} +retire(); return; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, null); +b1.value = 0; +if (compareEqual(thread.procedures["Zinvalid params - reporter"](), 0)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass invalid params reporter",}, b0, false, false, null); +} +if (compareEqual(thread.procedures["Zinvalid params - boolean"](), 0)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass invalid params boolean",}, b0, false, false, null); +} +runtime.stopForTarget(target, thread); +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); +retire(); return; +}; }) + +// Sprite1 invalid params - reporter +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_invalid_params___rep () { +return 0; +return ""; +}; }) + +// Sprite1 invalid params - boolean +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_invalid_params___boo () { +return 0; +return ""; +}; }) diff --git a/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot new file mode 100644 index 0000000000..7afc9c764a --- /dev/null +++ b/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot @@ -0,0 +1,146 @@ +// TW Snapshot +// Input SHA-256: b1802636bbbd479dcf8634a525798c5b4dd9b0201cf6af6f5b4879455368c328 + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b2 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 18",}, b0, false, false, null); +b1.value = 0; +b2.value = (yield* thread.procedures["Znon warp recursion should yield %s"](8)); +if (((+b1.value || 0) === 4)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp recursion yields",}, b0, false, false, null); +} +b1.value = 0; +b2.value = thread.procedures["Wwarp recursion should not yield %s"](8); +if (((+b1.value || 0) === 0)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass warp recursion does not yield",}, b0, false, false, null); +} +b1.value = 0; +b2.value = (yield* thread.procedures["Zfib %s"](7)); +if (((+b1.value || 0) === 20)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp fib yielded",}, b0, false, false, null); +} +yield* thread.procedures["Zrecursing yields between each %s"]("initial"); +yield* thread.procedures["Zrecursing arguments eval order %s %s %s %s"]("initial","","",""); +runtime.stopForTarget(target, thread); +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); +retire(); return; +}; }) + +// Sprite1 non warp recursion should yield %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function* genXYZ_non_warp_recursion_s (p0) { +if (compareGreaterThan(p0, 0)) { +return (yield* yieldThenCallGenerator(thread.procedures["Znon warp recursion should yield %s"], ((+p0 || 0) - 1))); +} +return ""; +}; }) + +// Sprite1 warp recursion should not yield %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_warp_recursion_shoul (p0) { +if (compareGreaterThan(p0, 0)) { +return thread.procedures["Wwarp recursion should not yield %s"](((+p0 || 0) - 1)); +} +return ""; +}; }) + +// Sprite1 fib %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function* genXYZ_fib_ (p0) { +if (compareLessThan(p0, 2)) { +return p0; +} else { +return ((+(yield* yieldThenCallGenerator(thread.procedures["Zfib %s"], ((+p0 || 0) - 1))) || 0) + (+(yield* yieldThenCallGenerator(thread.procedures["Zfib %s"], ((+p0 || 0) - 2))) || 0)); +} +return ""; +}; }) + +// Sprite1 recursing yields between each %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +const b2 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_recursing_yields_bet (p0) { +if ((("" + p0).toLowerCase() === "initial".toLowerCase())) { +b0.value = 0; +b1.value = ((+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 1)) || 0) + (((+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 1)) || 0) + (((+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 2)) || 0) + (((+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 2)) || 0) + (((+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 3)) || 0) + (+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 3)) || 0)) || 0)) || 0)) || 0)) || 0)); +if (((+b0.value || 0) === 3)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recursing between calls yields final",}, b2, false, false, null); +} else { +yield* executeInCompatibilityLayer({"MESSAGE":"fail recursing between calls yields final",}, b2, false, false, null); +} +} else { +if (compareEqual(b0.value, p0)) { +yield* executeInCompatibilityLayer({"MESSAGE":("pass recursing between calls yields " + ("" + p0)),}, b2, false, false, null); +} else { +yield* executeInCompatibilityLayer({"MESSAGE":("fail recursing between calls yields " + ("" + p0)),}, b2, false, false, null); +} +} +return ""; +}; }) + +// Sprite1 recursing arguments eval order %s %s %s %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["4HH82mPlVMOONdl(Ot*7"]; +const b1 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b2 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +const b3 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_recursing_arguments_ (p0,p1,p2,p3) { +if ((("" + p0).toLowerCase() === "initial".toLowerCase())) { +b0.value = []; +b1.value = 0; +b2.value = (yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 1",(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 2",(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 3",(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 4","","","")),(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 5","","","")),(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 6","","","")))),"","")),"",(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 7","","","")))); +if ((("" + (b0.value[(1 | 0) - 1] ?? "")).toLowerCase() === "1/child 4".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 1",}, b3, false, false, null); +} +if ((("" + (b0.value[(2 | 0) - 1] ?? "")).toLowerCase() === "1/child 5".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 2",}, b3, false, false, null); +} +if ((("" + (b0.value[(3 | 0) - 1] ?? "")).toLowerCase() === "2/child 6".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 3",}, b3, false, false, null); +} +if ((("" + (b0.value[(4 | 0) - 1] ?? "")).toLowerCase() === "2/child 3".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 4",}, b3, false, false, null); +} +if ((("" + (b0.value[(5 | 0) - 1] ?? "")).toLowerCase() === "3/child 2".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 5",}, b3, false, false, null); +} +if ((("" + (b0.value[(6 | 0) - 1] ?? "")).toLowerCase() === "3/child 7".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 6",}, b3, false, false, null); +} +if ((("" + (b0.value[(7 | 0) - 1] ?? "")).toLowerCase() === "4/child 1".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 7",}, b3, false, false, null); +} +if ((b0.value.length === 7)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - length is correct",}, b3, false, false, null); +} +} else { +b0.value.push((("" + b1.value) + ("/" + ("" + p0)))); +b0._monitorUpToDate = false; +} +return ""; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +return function* genXYZ () { +b0.value = 0; +while (true) { +b0.value = ((+b0.value || 0) + 1); +thread.timer = timer(); +var a0 = Math.max(0, 1000 * -1); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a0) { +yield; +} +thread.timer = null; +yield; +} +retire(); return; +}; }) diff --git a/test/snapshot/__snapshots__/tw-procedure-return-simple.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-simple.sb3.tw-snapshot new file mode 100644 index 0000000000..0c8bd71d47 --- /dev/null +++ b/test/snapshot/__snapshots__/tw-procedure-return-simple.sb3.tw-snapshot @@ -0,0 +1,111 @@ +// TW Snapshot +// Input SHA-256: cd516553f3cbf4a41ae9a8bc1f5d06a3392d365226bccdfa294578314f18facb + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 8",}, b0, false, false, null); +if ((("" + thread.procedures["Zsimplest"]()).toLowerCase() === "It works!".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass simplest",}, b0, false, false, null); +} +if ((("" + thread.procedures["Znesting 1"]()).toLowerCase() === "42-54".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass nesting1",}, b0, false, false, null); +} +if (((+thread.procedures["Wwarp fib %s"](12) || 0) === 144)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass fib 12",}, b0, false, false, null); +} +if (((+thread.procedures["Wfactorial %s"](12) || 0) === 479001600)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass factorial 12",}, b0, false, false, null); +} +b1.value = (yield* thread.procedures["Zno shadowing 1 %s %s"]("f","g")); +if (compareEqual(b1.value, "")) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass default return value",}, b0, false, false, null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); +retire(); return; +}; }) + +// Sprite1 simplest +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_simplest () { +return "It works!"; +return ""; +}; }) + +// Sprite1 nesting 1 +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_nesting_1 () { +thread.procedures["Znesting 2"](); +return (("" + thread.procedures["Znesting 3 %s %s"](6,7)) + ("" + thread.procedures["Znesting 3 %s %s"](-1,54))); +return ""; +}; }) + +// Sprite1 warp fib %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_warp_fib_ (p0) { +return thread.procedures["Wfib %s"](p0); +return ""; +}; }) + +// Sprite1 factorial %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_factorial_ (p0) { +if (compareGreaterThan(p0, 1)) { +return ((+p0 || 0) * (+thread.procedures["Wfactorial %s"](((+p0 || 0) - 1)) || 0)); +} +return 1; +return ""; +}; }) + +// Sprite1 no shadowing 1 %s %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ_no_shadowing_1__ (p0,p1) { +if (((("" + p0).toLowerCase() === "f".toLowerCase()) && (("" + p1).toLowerCase() === "g".toLowerCase()))) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass shadow check 1",}, b0, false, false, null); +} +b1.value = thread.procedures["Zno shadowing 2 %s %s"](1,2); +if (((("" + p0).toLowerCase() === "f".toLowerCase()) && (("" + p1).toLowerCase() === "g".toLowerCase()))) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass shadow check 2",}, b0, false, false, null); +} +b1.value = thread.procedures["Zno shadowing 2 %s %s"](3,4); +if (((("" + p0).toLowerCase() === "f".toLowerCase()) && (("" + p1).toLowerCase() === "g".toLowerCase()))) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass shadow check 3",}, b0, false, false, null); +} +return ""; +}; }) + +// Sprite1 nesting 2 +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_nesting_2 () { +return "discard nesting 2"; +return ""; +}; }) + +// Sprite1 nesting 3 %s %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_nesting_3__ (p0,p1) { +return ((+p0 || 0) * (+p1 || 0)); +return ""; +}; }) + +// Sprite1 fib %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_fib_ (p0) { +if (compareLessThan(p0, 2)) { +return p0; +} else { +return ((+thread.procedures["Wfib %s"](((+p0 || 0) - 1)) || 0) + (+thread.procedures["Wfib %s"](((+p0 || 0) - 2)) || 0)); +} +return ""; +}; }) + +// Sprite1 no shadowing 2 %s %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_no_shadowing_2__ (p0,p1) { +return "discard shadow 2"; +return ""; +}; }) diff --git a/test/snapshot/__snapshots__/tw-procedure-return-stops-scripts.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-stops-scripts.sb3.tw-snapshot new file mode 100644 index 0000000000..0a31092a84 --- /dev/null +++ b/test/snapshot/__snapshots__/tw-procedure-return-stops-scripts.sb3.tw-snapshot @@ -0,0 +1,51 @@ +// TW Snapshot +// Input SHA-256: 27368530b97e0ab5053bf4faf8fd116aa30cea34adcbd95fc6ddcf0a9d6d4976 + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["PsAI*C{QHI3*4?O8p#TM"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, null); +yield* thread.procedures["Wreturn stops the script immediately"](); +if (((+b1.value || 0) === 25)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass return stopped the script immediately",}, b0, false, false, null); +} +yield* waitThreads(startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "Test return outside of custom block" })); +if (((+b1.value || 0) === 18)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass return worked to stop outside of custom block",}, b0, false, false, null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); +retire(); return; +}; }) + +// Sprite1 return stops the script immediately +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["PsAI*C{QHI3*4?O8p#TM"]; +const b1 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_return_stops_the_scr () { +b0.value = 0; +for (var a0 = 100; a0 >= 0.5; a0--) { +b0.value = ((+b0.value || 0) + 1); +if (((b0.value || 0) === 25)) { +return "stopped!"; +} +} +yield* executeInCompatibilityLayer({"MESSAGE":"fail return did not stop the script immediately",}, b1, true, false, null); +return ""; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["PsAI*C{QHI3*4?O8p#TM"]; +return function* genXYZ () { +b0.value = 0; +while (true) { +b0.value = ((+b0.value || 0) + 1); +if (((b0.value || 0) === 18)) { +retire(); return; +} +yield; +} +retire(); return; +}; }) diff --git a/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot new file mode 100644 index 0000000000..7ea1f2b53c --- /dev/null +++ b/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot @@ -0,0 +1,120 @@ +// TW Snapshot +// Input SHA-256: e652f448fbf09a37afacb03afa9f3448cce43aef06439b58b44aeec73edb0438 + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +return function* genXYZ () { +b0.value = 0; +while (true) { +b0.value = ((+b0.value || 0) + 1); +thread.timer = timer(); +var a0 = Math.max(0, 1000 * -1); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a0) { +yield; +} +thread.timer = null; +yield; +} +retire(); return; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 3",}, b0, false, false, null); +yield* thread.procedures["Znon warp"](); +runtime.stopForTarget(target, thread); +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); +retire(); return; +}; }) + +// Sprite1 non warp +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b1 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_non_warp () { +b0.value = 0; +for (var a0 = 5; a0 >= 0.5; a0--) { +thread.timer = timer(); +var a1 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a1) { +yield; +} +thread.timer = null; +yield; +} +if (((+b0.value || 0) === 5)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp 1",}, b1, false, false, null); +} +if ((("" + (yield* thread.procedures["Wverify runs warp %s"]((yield* thread.procedures["Zverify runs non warp %s"]((yield* thread.procedures["Wverify runs warp %s"]("abc"))))))).toLowerCase() === "warp: non warp: warp: abc".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp and warp mix",}, b1, false, false, null); +} +b0.value = 0; +for (var a2 = 5; a2 >= 0.5; a2--) { +thread.timer = timer(); +var a3 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a3) { +yield; +} +thread.timer = null; +yield; +} +if (((+b0.value || 0) === 5)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp 2",}, b1, false, false, null); +} +return ""; +}; }) + +// Sprite1 verify runs warp %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b1 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_verify_runs_warp_ (p0) { +b0.value = 0; +for (var a0 = 5; a0 >= 0.5; a0--) { +thread.timer = timer(); +var a1 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +while (thread.timer.timeElapsed() < a1) { +if (isStuck()) yield; +} +thread.timer = null; +} +if (!compareEqual(b0.value, 0)) { +yield* executeInCompatibilityLayer({"MESSAGE":"fail did not run warp",}, b1, true, false, null); +} +return ("warp: " + ("" + p0)); +return ""; +}; }) + +// Sprite1 verify runs non warp %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b1 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_verify_runs_non_warp (p0) { +b0.value = 0; +for (var a0 = 5; a0 >= 0.5; a0--) { +thread.timer = timer(); +var a1 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a1) { +yield; +} +thread.timer = null; +yield; +} +if (compareEqual(b0.value, 0)) { +yield* executeInCompatibilityLayer({"MESSAGE":"fail ran warp",}, b1, false, false, null); +} +return ("non warp: " + ("" + p0)); +return ""; +}; }) diff --git a/test/snapshot/__snapshots__/tw-promise-loop-double-yield-kouzeru.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-promise-loop-double-yield-kouzeru.sb3.tw-snapshot index e519a948af..25f742e3c5 100644 --- a/test/snapshot/__snapshots__/tw-promise-loop-double-yield-kouzeru.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-promise-loop-double-yield-kouzeru.sb3.tw-snapshot @@ -18,7 +18,7 @@ if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;} } yield; } -retire(); +retire(); return; }; }) // Sprite1 script @@ -48,5 +48,5 @@ yield; yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b1, false, false, null); runtime.stopAll(); retire(); return; -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-restart-broadcast-threads.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-restart-broadcast-threads.sb3.tw-snapshot index 4459d5baf0..ccec1e74d7 100644 --- a/test/snapshot/__snapshots__/tw-restart-broadcast-threads.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-restart-broadcast-threads.sb3.tw-snapshot @@ -22,7 +22,7 @@ if (((+b1.value || 0) === 1)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite2 script @@ -30,5 +30,5 @@ retire(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function* genXYZ () { b0.value = ((+b0.value || 0) + 1); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-safe-procedure-argument-casting.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-safe-procedure-argument-casting.sb3.tw-snapshot index 4802e14767..c7dbdb0cde 100644 --- a/test/snapshot/__snapshots__/tw-safe-procedure-argument-casting.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-safe-procedure-argument-casting.sb3.tw-snapshot @@ -15,11 +15,12 @@ if ((((target.currentCostume + 1) === 1) && ((+target.getCostumes()[target.curre yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 switch %s (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_switch_ (p0) { runtime.ext_scratch3_looks._setCostume(target, p0); +return ""; }; }) diff --git a/test/snapshot/__snapshots__/tw-sensing-of.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-sensing-of.sb3.tw-snapshot index 239d1f55f9..96b07d55d8 100644 --- a/test/snapshot/__snapshots__/tw-sensing-of.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-sensing-of.sb3.tw-snapshot @@ -110,5 +110,5 @@ if (((b6 ? b6.volume : 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass NE direction",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-stage-cannot-move-layers.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-stage-cannot-move-layers.sb3.tw-snapshot index 974d8cbe06..923e8f1ece 100644 --- a/test/snapshot/__snapshots__/tw-stage-cannot-move-layers.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-stage-cannot-move-layers.sb3.tw-snapshot @@ -18,7 +18,7 @@ yield; thread.timer = null; yield* waitThreads(startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "Test 1" })); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -29,13 +29,13 @@ return function* genXYZ () { if ((("" + b0.value).toLowerCase() === "Initial".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b1, false, false, null); } -retire(); +retire(); return; }; }) // Stage script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function* genXYZ () { -retire(); +retire(); return; }; }) // Stage script @@ -43,5 +43,5 @@ retire(); const b0 = target.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function* genXYZ () { b0.value = "Stage callback"; -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-subtract-can-return-nan.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-subtract-can-return-nan.sb3.tw-snapshot index 7ce71511ea..e9573d3a27 100644 --- a/test/snapshot/__snapshots__/tw-subtract-can-return-nan.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-subtract-can-return-nan.sb3.tw-snapshot @@ -10,5 +10,5 @@ if (!((Infinity - Infinity) <= 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-tab-equals-zero.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-tab-equals-zero.sb3.tw-snapshot index 4b5c21bd64..7ba4410d7f 100644 --- a/test/snapshot/__snapshots__/tw-tab-equals-zero.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-tab-equals-zero.sb3.tw-snapshot @@ -9,7 +9,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 6",}, b0, false, false, null); b1.value = "\t"; startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "message1" }); -retire(); +retire(); return; }; }) // Sprite1 script @@ -36,5 +36,5 @@ if (compareEqual((" " + ("" + b0.value)), (0 + 0))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass \\t and other spaces = number 0",}, b1, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b1, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-tangent.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-tangent.sb3.tw-snapshot index a1349a1f5f..ea9202ade3 100644 --- a/test/snapshot/__snapshots__/tw-tangent.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-tangent.sb3.tw-snapshot @@ -52,5 +52,5 @@ if (((tan(-450) || 0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass -450",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot index 0d508339fc..34ae2a649e 100644 --- a/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot @@ -82,5 +82,5 @@ if (compareEqual(0, "")) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-warp-repeat-until-timer-greater-than.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-warp-repeat-until-timer-greater-than.sb3.tw-snapshot index 10d08edb04..7037c0256f 100644 --- a/test/snapshot/__snapshots__/tw-warp-repeat-until-timer-greater-than.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-warp-repeat-until-timer-greater-than.sb3.tw-snapshot @@ -8,7 +8,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, null); yield* thread.procedures["Wrun without screen refresh"](); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 run without screen refresh @@ -24,4 +24,5 @@ if (isStuck()) yield; if (compareLessThan((daysSince2000() * 86400), b0.value)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b1, true, false, null); } +return ""; }; }) diff --git a/test/snapshot/__snapshots__/tw-when-backdrop-switches-to-next-backdrop.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-when-backdrop-switches-to-next-backdrop.sb3.tw-snapshot index 58f2fcf03f..646f9d73a8 100644 --- a/test/snapshot/__snapshots__/tw-when-backdrop-switches-to-next-backdrop.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-when-backdrop-switches-to-next-backdrop.sb3.tw-snapshot @@ -6,7 +6,7 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -15,5 +15,5 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b0, false, false, null); runtime.ext_scratch3_looks._setBackdrop(stage, stage.currentCostume + 1, true); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-when-backdrop-switches-to-switch-backdrop-to.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-when-backdrop-switches-to-switch-backdrop-to.sb3.tw-snapshot index c1528683ad..30302e62b2 100644 --- a/test/snapshot/__snapshots__/tw-when-backdrop-switches-to-switch-backdrop-to.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-when-backdrop-switches-to-switch-backdrop-to.sb3.tw-snapshot @@ -6,7 +6,7 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -15,5 +15,5 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b0, false, false, null); runtime.ext_scratch3_looks._setBackdrop(stage, "backdrop2"); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/tw-zombie-cube-escape-284516654.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-zombie-cube-escape-284516654.sb3.tw-snapshot index d6e4f2e995..fc4a516916 100644 --- a/test/snapshot/__snapshots__/tw-zombie-cube-escape-284516654.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-zombie-cube-escape-284516654.sb3.tw-snapshot @@ -16,7 +16,7 @@ yield; } yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite2 script @@ -28,5 +28,5 @@ b0.value = ((+b0.value || 0) + 1); if (((b0.value || 0) === 5)) { b1.value = "end"; } -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/order-library-reverse.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/order-library-reverse.sb3.tw-snapshot index 83a524246e..9fb6e5dcc3 100644 --- a/test/snapshot/__snapshots__/warp-timer/order-library-reverse.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/order-library-reverse.sb3.tw-snapshot @@ -22,7 +22,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 1 != " + ("" + b0.value)),}, b2, false, false, null); } -retire(); +retire(); return; }; }) // Sprite3 script @@ -56,7 +56,7 @@ yield; } thread.timer = null; yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -80,5 +80,5 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 2 != " + ("" + b0.value)),}, b2, false, false, null); } -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/order-library.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/order-library.sb3.tw-snapshot index 8d5b86d442..b3d290734d 100644 --- a/test/snapshot/__snapshots__/warp-timer/order-library.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/order-library.sb3.tw-snapshot @@ -32,7 +32,7 @@ yield; } thread.timer = null; yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -56,7 +56,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 1 != " + ("" + b0.value)),}, b2, false, false, null); } -retire(); +retire(); return; }; }) // Sprite2 script @@ -80,5 +80,5 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 2 != " + ("" + b0.value)),}, b2, false, false, null); } -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot index 3da0d357d4..9ef871b3d1 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot @@ -67,5 +67,5 @@ if (compareEqual(((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-add-can-return-nan.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-add-can-return-nan.sb3.tw-snapshot index c0559ddbba..8d5ef26746 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-add-can-return-nan.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-add-can-return-nan.sb3.tw-snapshot @@ -10,5 +10,5 @@ if (!((Infinity + -Infinity) <= 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-all-at-once.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-all-at-once.sb3.tw-snapshot index 6654884071..a411f91426 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-all-at-once.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-all-at-once.sb3.tw-snapshot @@ -10,5 +10,5 @@ if (true) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-broadcast-id-and-name-desync.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-broadcast-id-and-name-desync.sb3.tw-snapshot index 9fcae5532e..0001992170 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-broadcast-id-and-name-desync.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-broadcast-id-and-name-desync.sb3.tw-snapshot @@ -6,7 +6,7 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -16,5 +16,5 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, null); yield* waitThreads(startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "message1" })); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-change-size-does-not-use-rounded-size.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-change-size-does-not-use-rounded-size.sb3.tw-snapshot index d44534337e..47a89a5940 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-change-size-does-not-use-rounded-size.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-change-size-does-not-use-rounded-size.sb3.tw-snapshot @@ -18,5 +18,5 @@ if (((+b1.value || 0) === 20)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-color-input-returns-hex.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-color-input-returns-hex.sb3.tw-snapshot index f5cbbe5960..d0d058ca2f 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-color-input-returns-hex.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-color-input-returns-hex.sb3.tw-snapshot @@ -12,5 +12,5 @@ if ((("" + b1.value).toLowerCase() === "#22388a".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-comparison-matrix-inline.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-comparison-matrix-inline.sb3.tw-snapshot index 5c76fc1cad..6983ba45b8 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-comparison-matrix-inline.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-comparison-matrix-inline.sb3.tw-snapshot @@ -1771,5 +1771,5 @@ if (!(("" + compareGreaterThan("", "")).toLowerCase() === "false".toLowerCase()) yield* executeInCompatibilityLayer({"MESSAGE":"fail 588: should be > ",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-comparison-matrix-runtime.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-comparison-matrix-runtime.sb3.tw-snapshot index e796d21e79..8e1981ba0b 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-comparison-matrix-runtime.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-comparison-matrix-runtime.sb3.tw-snapshot @@ -8,7 +8,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b0, false, false, null); yield* thread.procedures["Wrun test"](); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 run test @@ -45,6 +45,7 @@ if (isStuck()) yield; } if (isStuck()) yield; } +return ""; }; }) // Sprite1 setup values @@ -128,4 +129,5 @@ b0.value.push(" "); b0._monitorUpToDate = false; b0.value.push("🎉"); b0._monitorUpToDate = false; +return ""; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-coordinate-precision.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-coordinate-precision.sb3.tw-snapshot index 06c67b745a..8b09c54033 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-coordinate-precision.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-coordinate-precision.sb3.tw-snapshot @@ -32,5 +32,5 @@ if ((limitPrecision(target.x) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass x slightly below 0 rounds",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-boolean-number-comparison.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-boolean-number-comparison.sb3.tw-snapshot index c984a1e75d..f1c7c9c441 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-boolean-number-comparison.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-boolean-number-comparison.sb3.tw-snapshot @@ -19,5 +19,5 @@ if (compareLessThan(("something".toLowerCase() === "else".toLowerCase()), (1 + 0 yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot index 13f910ed0e..19981ca8de 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot @@ -24,5 +24,5 @@ if ((target.direction === 90)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 4",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-random-with-invalid-number-with-period.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-random-with-invalid-number-with-period.sb3.tw-snapshot index c3c297df67..44ea509f38 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-random-with-invalid-number-with-period.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-random-with-invalid-number-with-period.sb3.tw-snapshot @@ -28,5 +28,5 @@ if ((("" + b1.value).toLowerCase().indexOf(".".toLowerCase()) !== -1)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-variable-id-name-desync-name-fallback.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-variable-id-name-desync-name-fallback.sb3.tw-snapshot index 9b47965efd..d394fffa07 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-variable-id-name-desync-name-fallback.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-variable-id-name-desync-name-fallback.sb3.tw-snapshot @@ -19,5 +19,5 @@ if (((+(b2.value[(1 | 0) - 1] ?? "") || 0) === 3)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass list",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-wait-zero-seconds-in-warp-mode.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-wait-zero-seconds-in-warp-mode.sb3.tw-snapshot index 592940dd7e..e8b77424eb 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-wait-zero-seconds-in-warp-mode.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-wait-zero-seconds-in-warp-mode.sb3.tw-snapshot @@ -18,7 +18,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b2, false, false, null); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b2, false, false, null); runtime.stopAll(); retire(); return; -retire(); +retire(); return; }; }) // Sprite1 no refresh @@ -39,12 +39,14 @@ yield* executeInCompatibilityLayer({"DRUM":1,"BEATS":0,}, b1, true, false, null) runtime.ext_scratch3_motion._moveSteps(0, target); if (isStuck()) yield; } +return ""; }; }) // Sprite1 runs below with no refresh (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function* genXYZ_runs_below_with_no_r () { yield* thread.procedures["Whas refresh"](); +return ""; }; }) // Sprite1 has refresh @@ -65,6 +67,7 @@ yield* executeInCompatibilityLayer({"DRUM":1,"BEATS":0,}, b1, true, false, null) runtime.ext_scratch3_motion._moveSteps(0, target); if (isStuck()) yield; } +return ""; }; }) // Sprite1 script @@ -75,5 +78,5 @@ while (true) { b0.value = ((+b0.value || 0) + 1); yield; } -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-list-any.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-list-any.sb3.tw-snapshot index 9c1558e0ec..47fb9c0042 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-list-any.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-list-any.sb3.tw-snapshot @@ -34,5 +34,5 @@ if (compareEqual(listGet(b1.value, "*"), "")) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-obsolete-blocks.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-obsolete-blocks.sb3.tw-snapshot index 9abad94588..4a2f8c0f8b 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-obsolete-blocks.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-obsolete-blocks.sb3.tw-snapshot @@ -29,5 +29,5 @@ yield* executeInCompatibilityLayer({"CHANGE":10,}, b9, false, false, null); yield* executeInCompatibilityLayer({}, b10, false, false, null); yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-one-divide-negative-zero.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-one-divide-negative-zero.sb3.tw-snapshot index 5b219446e5..ef51b2fa84 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-one-divide-negative-zero.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-one-divide-negative-zero.sb3.tw-snapshot @@ -10,5 +10,5 @@ if ((((1 / -0) || 0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot index 026192b774..0624028f3a 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot @@ -23,5 +23,5 @@ yield; } b0.value = 1; yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b1, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-prefers-first-occurence-of-procedure-387608267.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-prefers-first-occurence-of-procedure-387608267.sb3.tw-snapshot index 44587a8de6..b7521033ca 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-prefers-first-occurence-of-procedure-387608267.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-prefers-first-occurence-of-procedure-387608267.sb3.tw-snapshot @@ -8,7 +8,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, null); yield* thread.procedures["ZSet Costume"](); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Player Set Costume @@ -16,4 +16,5 @@ retire(); const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_Set_Costume () { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); +return ""; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-procedure-arguments-with-same-name.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-procedure-arguments-with-same-name.sb3.tw-snapshot index bcf0381a94..81002ba0a4 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-procedure-arguments-with-same-name.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-procedure-arguments-with-same-name.sb3.tw-snapshot @@ -9,7 +9,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, null yield* thread.procedures["Znumber or text %s %s"]("bad","ok"); yield* thread.procedures["Zboolean %b %b"]("false",!false); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 number or text %s %s @@ -19,6 +19,7 @@ return function* genXYZ_number_or_text__ (p0,p1) { if ((("" + p1).toLowerCase() === "ok".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } +return ""; }; }) // Sprite1 boolean %b %b @@ -28,4 +29,5 @@ return function* genXYZ_boolean__ (p0,p1) { if (toBoolean(p1)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } +return ""; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-procedure-call-resets-variable-input-types-430811055.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-procedure-call-resets-variable-input-types-430811055.sb3.tw-snapshot index 3c7f4981bb..e01bbe8d5b 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-procedure-call-resets-variable-input-types-430811055.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-procedure-call-resets-variable-input-types-430811055.sb3.tw-snapshot @@ -13,7 +13,7 @@ if (!compareEqual(b1.value, "")) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 do something @@ -21,4 +21,5 @@ retire(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function funXYZ_do_something () { b0.value = "help"; +return ""; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-procedure-prototype-exists-but-not-definition-549160843.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-procedure-prototype-exists-but-not-definition-549160843.sb3.tw-snapshot index d45d0aee37..7f69ac3fad 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-procedure-prototype-exists-but-not-definition-549160843.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-procedure-prototype-exists-but-not-definition-549160843.sb3.tw-snapshot @@ -7,5 +7,5 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b0, false, false, null); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-non-existant.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-non-existant.sb3.tw-snapshot new file mode 100644 index 0000000000..87f5825985 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-non-existant.sb3.tw-snapshot @@ -0,0 +1,54 @@ +// TW Snapshot +// Input SHA-256: b9110339d39947f68168cbb129a20b23b7d4e607ffd5624bd67e096451f6a329 + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +return function* genXYZ () { +b0.value = 0; +while (true) { +b0.value = ((+b0.value || 0) + 1); +thread.timer = timer(); +var a0 = Math.max(0, 1000 * -1); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a0) { +yield; +} +thread.timer = null; +yield; +} +retire(); return; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, null); +b1.value = 0; +if (compareEqual(thread.procedures["Zinvalid params - reporter"](), 0)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass invalid params reporter",}, b0, false, false, null); +} +if (compareEqual(thread.procedures["Zinvalid params - boolean"](), 0)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass invalid params boolean",}, b0, false, false, null); +} +runtime.stopForTarget(target, thread); +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); +retire(); return; +}; }) + +// Sprite1 invalid params - reporter +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_invalid_params___rep () { +return 0; +return ""; +}; }) + +// Sprite1 invalid params - boolean +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_invalid_params___boo () { +return 0; +return ""; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-recursion.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-recursion.sb3.tw-snapshot new file mode 100644 index 0000000000..7afc9c764a --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-recursion.sb3.tw-snapshot @@ -0,0 +1,146 @@ +// TW Snapshot +// Input SHA-256: b1802636bbbd479dcf8634a525798c5b4dd9b0201cf6af6f5b4879455368c328 + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b2 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 18",}, b0, false, false, null); +b1.value = 0; +b2.value = (yield* thread.procedures["Znon warp recursion should yield %s"](8)); +if (((+b1.value || 0) === 4)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp recursion yields",}, b0, false, false, null); +} +b1.value = 0; +b2.value = thread.procedures["Wwarp recursion should not yield %s"](8); +if (((+b1.value || 0) === 0)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass warp recursion does not yield",}, b0, false, false, null); +} +b1.value = 0; +b2.value = (yield* thread.procedures["Zfib %s"](7)); +if (((+b1.value || 0) === 20)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp fib yielded",}, b0, false, false, null); +} +yield* thread.procedures["Zrecursing yields between each %s"]("initial"); +yield* thread.procedures["Zrecursing arguments eval order %s %s %s %s"]("initial","","",""); +runtime.stopForTarget(target, thread); +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); +retire(); return; +}; }) + +// Sprite1 non warp recursion should yield %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function* genXYZ_non_warp_recursion_s (p0) { +if (compareGreaterThan(p0, 0)) { +return (yield* yieldThenCallGenerator(thread.procedures["Znon warp recursion should yield %s"], ((+p0 || 0) - 1))); +} +return ""; +}; }) + +// Sprite1 warp recursion should not yield %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_warp_recursion_shoul (p0) { +if (compareGreaterThan(p0, 0)) { +return thread.procedures["Wwarp recursion should not yield %s"](((+p0 || 0) - 1)); +} +return ""; +}; }) + +// Sprite1 fib %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function* genXYZ_fib_ (p0) { +if (compareLessThan(p0, 2)) { +return p0; +} else { +return ((+(yield* yieldThenCallGenerator(thread.procedures["Zfib %s"], ((+p0 || 0) - 1))) || 0) + (+(yield* yieldThenCallGenerator(thread.procedures["Zfib %s"], ((+p0 || 0) - 2))) || 0)); +} +return ""; +}; }) + +// Sprite1 recursing yields between each %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +const b2 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_recursing_yields_bet (p0) { +if ((("" + p0).toLowerCase() === "initial".toLowerCase())) { +b0.value = 0; +b1.value = ((+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 1)) || 0) + (((+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 1)) || 0) + (((+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 2)) || 0) + (((+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 2)) || 0) + (((+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 3)) || 0) + (+(yield* yieldThenCallGenerator(thread.procedures["Zrecursing yields between each %s"], 3)) || 0)) || 0)) || 0)) || 0)) || 0)); +if (((+b0.value || 0) === 3)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recursing between calls yields final",}, b2, false, false, null); +} else { +yield* executeInCompatibilityLayer({"MESSAGE":"fail recursing between calls yields final",}, b2, false, false, null); +} +} else { +if (compareEqual(b0.value, p0)) { +yield* executeInCompatibilityLayer({"MESSAGE":("pass recursing between calls yields " + ("" + p0)),}, b2, false, false, null); +} else { +yield* executeInCompatibilityLayer({"MESSAGE":("fail recursing between calls yields " + ("" + p0)),}, b2, false, false, null); +} +} +return ""; +}; }) + +// Sprite1 recursing arguments eval order %s %s %s %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["4HH82mPlVMOONdl(Ot*7"]; +const b1 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b2 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +const b3 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_recursing_arguments_ (p0,p1,p2,p3) { +if ((("" + p0).toLowerCase() === "initial".toLowerCase())) { +b0.value = []; +b1.value = 0; +b2.value = (yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 1",(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 2",(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 3",(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 4","","","")),(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 5","","","")),(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 6","","","")))),"","")),"",(yield* yieldThenCallGenerator(thread.procedures["Zrecursing arguments eval order %s %s %s %s"], "child 7","","","")))); +if ((("" + (b0.value[(1 | 0) - 1] ?? "")).toLowerCase() === "1/child 4".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 1",}, b3, false, false, null); +} +if ((("" + (b0.value[(2 | 0) - 1] ?? "")).toLowerCase() === "1/child 5".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 2",}, b3, false, false, null); +} +if ((("" + (b0.value[(3 | 0) - 1] ?? "")).toLowerCase() === "2/child 6".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 3",}, b3, false, false, null); +} +if ((("" + (b0.value[(4 | 0) - 1] ?? "")).toLowerCase() === "2/child 3".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 4",}, b3, false, false, null); +} +if ((("" + (b0.value[(5 | 0) - 1] ?? "")).toLowerCase() === "3/child 2".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 5",}, b3, false, false, null); +} +if ((("" + (b0.value[(6 | 0) - 1] ?? "")).toLowerCase() === "3/child 7".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 6",}, b3, false, false, null); +} +if ((("" + (b0.value[(7 | 0) - 1] ?? "")).toLowerCase() === "4/child 1".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 7",}, b3, false, false, null); +} +if ((b0.value.length === 7)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - length is correct",}, b3, false, false, null); +} +} else { +b0.value.push((("" + b1.value) + ("/" + ("" + p0)))); +b0._monitorUpToDate = false; +} +return ""; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +return function* genXYZ () { +b0.value = 0; +while (true) { +b0.value = ((+b0.value || 0) + 1); +thread.timer = timer(); +var a0 = Math.max(0, 1000 * -1); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a0) { +yield; +} +thread.timer = null; +yield; +} +retire(); return; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-simple.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-simple.sb3.tw-snapshot new file mode 100644 index 0000000000..0c8bd71d47 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-simple.sb3.tw-snapshot @@ -0,0 +1,111 @@ +// TW Snapshot +// Input SHA-256: cd516553f3cbf4a41ae9a8bc1f5d06a3392d365226bccdfa294578314f18facb + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 8",}, b0, false, false, null); +if ((("" + thread.procedures["Zsimplest"]()).toLowerCase() === "It works!".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass simplest",}, b0, false, false, null); +} +if ((("" + thread.procedures["Znesting 1"]()).toLowerCase() === "42-54".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass nesting1",}, b0, false, false, null); +} +if (((+thread.procedures["Wwarp fib %s"](12) || 0) === 144)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass fib 12",}, b0, false, false, null); +} +if (((+thread.procedures["Wfactorial %s"](12) || 0) === 479001600)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass factorial 12",}, b0, false, false, null); +} +b1.value = (yield* thread.procedures["Zno shadowing 1 %s %s"]("f","g")); +if (compareEqual(b1.value, "")) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass default return value",}, b0, false, false, null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); +retire(); return; +}; }) + +// Sprite1 simplest +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_simplest () { +return "It works!"; +return ""; +}; }) + +// Sprite1 nesting 1 +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_nesting_1 () { +thread.procedures["Znesting 2"](); +return (("" + thread.procedures["Znesting 3 %s %s"](6,7)) + ("" + thread.procedures["Znesting 3 %s %s"](-1,54))); +return ""; +}; }) + +// Sprite1 warp fib %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_warp_fib_ (p0) { +return thread.procedures["Wfib %s"](p0); +return ""; +}; }) + +// Sprite1 factorial %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_factorial_ (p0) { +if (compareGreaterThan(p0, 1)) { +return ((+p0 || 0) * (+thread.procedures["Wfactorial %s"](((+p0 || 0) - 1)) || 0)); +} +return 1; +return ""; +}; }) + +// Sprite1 no shadowing 1 %s %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ_no_shadowing_1__ (p0,p1) { +if (((("" + p0).toLowerCase() === "f".toLowerCase()) && (("" + p1).toLowerCase() === "g".toLowerCase()))) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass shadow check 1",}, b0, false, false, null); +} +b1.value = thread.procedures["Zno shadowing 2 %s %s"](1,2); +if (((("" + p0).toLowerCase() === "f".toLowerCase()) && (("" + p1).toLowerCase() === "g".toLowerCase()))) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass shadow check 2",}, b0, false, false, null); +} +b1.value = thread.procedures["Zno shadowing 2 %s %s"](3,4); +if (((("" + p0).toLowerCase() === "f".toLowerCase()) && (("" + p1).toLowerCase() === "g".toLowerCase()))) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass shadow check 3",}, b0, false, false, null); +} +return ""; +}; }) + +// Sprite1 nesting 2 +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_nesting_2 () { +return "discard nesting 2"; +return ""; +}; }) + +// Sprite1 nesting 3 %s %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_nesting_3__ (p0,p1) { +return ((+p0 || 0) * (+p1 || 0)); +return ""; +}; }) + +// Sprite1 fib %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_fib_ (p0) { +if (compareLessThan(p0, 2)) { +return p0; +} else { +return ((+thread.procedures["Wfib %s"](((+p0 || 0) - 1)) || 0) + (+thread.procedures["Wfib %s"](((+p0 || 0) - 2)) || 0)); +} +return ""; +}; }) + +// Sprite1 no shadowing 2 %s %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_no_shadowing_2__ (p0,p1) { +return "discard shadow 2"; +return ""; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-stops-scripts.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-stops-scripts.sb3.tw-snapshot new file mode 100644 index 0000000000..fa4325d2f3 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-stops-scripts.sb3.tw-snapshot @@ -0,0 +1,52 @@ +// TW Snapshot +// Input SHA-256: 27368530b97e0ab5053bf4faf8fd116aa30cea34adcbd95fc6ddcf0a9d6d4976 + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["PsAI*C{QHI3*4?O8p#TM"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, null); +yield* thread.procedures["Wreturn stops the script immediately"](); +if (((+b1.value || 0) === 25)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass return stopped the script immediately",}, b0, false, false, null); +} +yield* waitThreads(startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "Test return outside of custom block" })); +if (((+b1.value || 0) === 18)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass return worked to stop outside of custom block",}, b0, false, false, null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); +retire(); return; +}; }) + +// Sprite1 return stops the script immediately +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["PsAI*C{QHI3*4?O8p#TM"]; +const b1 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_return_stops_the_scr () { +b0.value = 0; +for (var a0 = 100; a0 >= 0.5; a0--) { +b0.value = ((+b0.value || 0) + 1); +if (((b0.value || 0) === 25)) { +return "stopped!"; +} +if (isStuck()) yield; +} +yield* executeInCompatibilityLayer({"MESSAGE":"fail return did not stop the script immediately",}, b1, true, false, null); +return ""; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["PsAI*C{QHI3*4?O8p#TM"]; +return function* genXYZ () { +b0.value = 0; +while (true) { +b0.value = ((+b0.value || 0) + 1); +if (((b0.value || 0) === 18)) { +retire(); return; +} +yield; +} +retire(); return; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-warp.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-warp.sb3.tw-snapshot new file mode 100644 index 0000000000..6b2ec81d26 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-warp.sb3.tw-snapshot @@ -0,0 +1,121 @@ +// TW Snapshot +// Input SHA-256: e652f448fbf09a37afacb03afa9f3448cce43aef06439b58b44aeec73edb0438 + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +return function* genXYZ () { +b0.value = 0; +while (true) { +b0.value = ((+b0.value || 0) + 1); +thread.timer = timer(); +var a0 = Math.max(0, 1000 * -1); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a0) { +yield; +} +thread.timer = null; +yield; +} +retire(); return; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 3",}, b0, false, false, null); +yield* thread.procedures["Znon warp"](); +runtime.stopForTarget(target, thread); +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); +retire(); return; +}; }) + +// Sprite1 non warp +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b1 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_non_warp () { +b0.value = 0; +for (var a0 = 5; a0 >= 0.5; a0--) { +thread.timer = timer(); +var a1 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a1) { +yield; +} +thread.timer = null; +yield; +} +if (((+b0.value || 0) === 5)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp 1",}, b1, false, false, null); +} +if ((("" + (yield* thread.procedures["Wverify runs warp %s"]((yield* thread.procedures["Zverify runs non warp %s"]((yield* thread.procedures["Wverify runs warp %s"]("abc"))))))).toLowerCase() === "warp: non warp: warp: abc".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp and warp mix",}, b1, false, false, null); +} +b0.value = 0; +for (var a2 = 5; a2 >= 0.5; a2--) { +thread.timer = timer(); +var a3 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a3) { +yield; +} +thread.timer = null; +yield; +} +if (((+b0.value || 0) === 5)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp 2",}, b1, false, false, null); +} +return ""; +}; }) + +// Sprite1 verify runs warp %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b1 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_verify_runs_warp_ (p0) { +b0.value = 0; +for (var a0 = 5; a0 >= 0.5; a0--) { +thread.timer = timer(); +var a1 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +while (thread.timer.timeElapsed() < a1) { +if (isStuck()) yield; +} +thread.timer = null; +if (isStuck()) yield; +} +if (!compareEqual(b0.value, 0)) { +yield* executeInCompatibilityLayer({"MESSAGE":"fail did not run warp",}, b1, true, false, null); +} +return ("warp: " + ("" + p0)); +return ""; +}; }) + +// Sprite1 verify runs non warp %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["Go=PJS7BFXYo_qi2S:kQ"]; +const b1 = runtime.getOpcodeFunction("looks_say"); +return function* genXYZ_verify_runs_non_warp (p0) { +b0.value = 0; +for (var a0 = 5; a0 >= 0.5; a0--) { +thread.timer = timer(); +var a1 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a1) { +yield; +} +thread.timer = null; +yield; +} +if (compareEqual(b0.value, 0)) { +yield* executeInCompatibilityLayer({"MESSAGE":"fail ran warp",}, b1, false, false, null); +} +return ("non warp: " + ("" + p0)); +return ""; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-promise-loop-double-yield-kouzeru.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-promise-loop-double-yield-kouzeru.sb3.tw-snapshot index e519a948af..25f742e3c5 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-promise-loop-double-yield-kouzeru.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-promise-loop-double-yield-kouzeru.sb3.tw-snapshot @@ -18,7 +18,7 @@ if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;} } yield; } -retire(); +retire(); return; }; }) // Sprite1 script @@ -48,5 +48,5 @@ yield; yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b1, false, false, null); runtime.stopAll(); retire(); return; -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-restart-broadcast-threads.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-restart-broadcast-threads.sb3.tw-snapshot index 4459d5baf0..ccec1e74d7 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-restart-broadcast-threads.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-restart-broadcast-threads.sb3.tw-snapshot @@ -22,7 +22,7 @@ if (((+b1.value || 0) === 1)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite2 script @@ -30,5 +30,5 @@ retire(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function* genXYZ () { b0.value = ((+b0.value || 0) + 1); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-safe-procedure-argument-casting.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-safe-procedure-argument-casting.sb3.tw-snapshot index 4802e14767..c7dbdb0cde 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-safe-procedure-argument-casting.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-safe-procedure-argument-casting.sb3.tw-snapshot @@ -15,11 +15,12 @@ if ((((target.currentCostume + 1) === 1) && ((+target.getCostumes()[target.curre yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 switch %s (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_switch_ (p0) { runtime.ext_scratch3_looks._setCostume(target, p0); +return ""; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-sensing-of.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-sensing-of.sb3.tw-snapshot index 239d1f55f9..96b07d55d8 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-sensing-of.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-sensing-of.sb3.tw-snapshot @@ -110,5 +110,5 @@ if (((b6 ? b6.volume : 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass NE direction",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-stage-cannot-move-layers.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-stage-cannot-move-layers.sb3.tw-snapshot index 974d8cbe06..923e8f1ece 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-stage-cannot-move-layers.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-stage-cannot-move-layers.sb3.tw-snapshot @@ -18,7 +18,7 @@ yield; thread.timer = null; yield* waitThreads(startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "Test 1" })); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -29,13 +29,13 @@ return function* genXYZ () { if ((("" + b0.value).toLowerCase() === "Initial".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b1, false, false, null); } -retire(); +retire(); return; }; }) // Stage script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function* genXYZ () { -retire(); +retire(); return; }; }) // Stage script @@ -43,5 +43,5 @@ retire(); const b0 = target.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function* genXYZ () { b0.value = "Stage callback"; -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-subtract-can-return-nan.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-subtract-can-return-nan.sb3.tw-snapshot index 7ce71511ea..e9573d3a27 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-subtract-can-return-nan.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-subtract-can-return-nan.sb3.tw-snapshot @@ -10,5 +10,5 @@ if (!((Infinity - Infinity) <= 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-tab-equals-zero.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-tab-equals-zero.sb3.tw-snapshot index 4b5c21bd64..7ba4410d7f 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-tab-equals-zero.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-tab-equals-zero.sb3.tw-snapshot @@ -9,7 +9,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 6",}, b0, false, false, null); b1.value = "\t"; startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "message1" }); -retire(); +retire(); return; }; }) // Sprite1 script @@ -36,5 +36,5 @@ if (compareEqual((" " + ("" + b0.value)), (0 + 0))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass \\t and other spaces = number 0",}, b1, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b1, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-tangent.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-tangent.sb3.tw-snapshot index a1349a1f5f..ea9202ade3 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-tangent.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-tangent.sb3.tw-snapshot @@ -52,5 +52,5 @@ if (((tan(-450) || 0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass -450",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-unsafe-equals.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-unsafe-equals.sb3.tw-snapshot index 0d508339fc..34ae2a649e 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-unsafe-equals.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-unsafe-equals.sb3.tw-snapshot @@ -82,5 +82,5 @@ if (compareEqual(0, "")) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-warp-repeat-until-timer-greater-than.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-warp-repeat-until-timer-greater-than.sb3.tw-snapshot index 10d08edb04..7037c0256f 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-warp-repeat-until-timer-greater-than.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-warp-repeat-until-timer-greater-than.sb3.tw-snapshot @@ -8,7 +8,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, null); yield* thread.procedures["Wrun without screen refresh"](); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 run without screen refresh @@ -24,4 +24,5 @@ if (isStuck()) yield; if (compareLessThan((daysSince2000() * 86400), b0.value)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b1, true, false, null); } +return ""; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-when-backdrop-switches-to-next-backdrop.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-when-backdrop-switches-to-next-backdrop.sb3.tw-snapshot index 58f2fcf03f..646f9d73a8 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-when-backdrop-switches-to-next-backdrop.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-when-backdrop-switches-to-next-backdrop.sb3.tw-snapshot @@ -6,7 +6,7 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -15,5 +15,5 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b0, false, false, null); runtime.ext_scratch3_looks._setBackdrop(stage, stage.currentCostume + 1, true); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-when-backdrop-switches-to-switch-backdrop-to.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-when-backdrop-switches-to-switch-backdrop-to.sb3.tw-snapshot index c1528683ad..30302e62b2 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-when-backdrop-switches-to-switch-backdrop-to.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-when-backdrop-switches-to-switch-backdrop-to.sb3.tw-snapshot @@ -6,7 +6,7 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite1 script @@ -15,5 +15,5 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b0, false, false, null); runtime.ext_scratch3_looks._setBackdrop(stage, "backdrop2"); -retire(); +retire(); return; }; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-zombie-cube-escape-284516654.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-zombie-cube-escape-284516654.sb3.tw-snapshot index d6e4f2e995..fc4a516916 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-zombie-cube-escape-284516654.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-zombie-cube-escape-284516654.sb3.tw-snapshot @@ -16,7 +16,7 @@ yield; } yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, null); yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, null); -retire(); +retire(); return; }; }) // Sprite2 script @@ -28,5 +28,5 @@ b0.value = ((+b0.value || 0) + 1); if (((b0.value || 0) === 5)) { b1.value = "end"; } -retire(); +retire(); return; }; }) diff --git a/test/unit/engine_sequencer.js b/test/unit/engine_sequencer.js index a383001fee..284c61e0d4 100644 --- a/test/unit/engine_sequencer.js +++ b/test/unit/engine_sequencer.js @@ -74,9 +74,14 @@ const generateThread = function (runtime) { let next = randomString(); let inp = randomString(); let name = th.topBlock; + + const pushStack = id => { + th.pushStack(id); + th.peekStackFrame().op = {id}; + }; rt.blocks.createBlock(generateBlockInput(name, next, inp)); - th.pushStack(name); + pushStack(name); rt.blocks.createBlock(generateBlock(inp)); for (let i = 0; i < 10; i++) { @@ -85,11 +90,11 @@ const generateThread = function (runtime) { inp = randomString(); rt.blocks.createBlock(generateBlockInput(name, next, inp)); - th.pushStack(name); + pushStack(name); rt.blocks.createBlock(generateBlock(inp)); } rt.blocks.createBlock(generateBlock(next)); - th.pushStack(next); + pushStack(next); th.target = rt; th.blockContainer = rt.blocks; diff --git a/test/unit/engine_thread.js b/test/unit/engine_thread.js index 36206eb99b..0a7c3f3e42 100644 --- a/test/unit/engine_thread.js +++ b/test/unit/engine_thread.js @@ -267,12 +267,17 @@ test('isRecursiveCall', t => { rt.blocks.createBlock(block2); th.target = rt; + const pushStack = id => { + th.pushStack(id); + th.peekStackFrame().op = {id}; + }; + t.strictEquals(th.isRecursiveCall('fakeCode'), false); - th.pushStack('secondString'); + pushStack('secondString'); t.strictEquals(th.isRecursiveCall('fakeCode'), false); - th.pushStack('arbitraryString'); + pushStack('arbitraryString'); t.strictEquals(th.isRecursiveCall('fakeCode'), true); - th.pushStack('arbitraryString'); + pushStack('arbitraryString'); t.strictEquals(th.isRecursiveCall('fakeCode'), true); th.popStack(); t.strictEquals(th.isRecursiveCall('fakeCode'), true);