From fffb72b09d549bb37bbbc64ef7508a4a2eafa13d Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 16 Jun 2024 13:10:14 +1000 Subject: [PATCH 01/56] Upgraded Type Analysis --- src/compiler/compat-block-utility.js | 7 +- src/compiler/compat-blocks.js | 2 + src/compiler/compile.js | 12 +- src/compiler/enums.js | 295 ++++ src/compiler/environment.js | 1 + src/compiler/intermediate.js | 253 ++- src/compiler/irgen.js | 1465 +++++++---------- src/compiler/iroptimizer.js | 703 ++++++++ src/compiler/jsexecute.js | 13 +- src/compiler/jsgen.js | 1312 ++++++--------- src/compiler/variable-pool.js | 2 + .../order-library-reverse.sb3.tw-snapshot | 4 +- .../order-library.sb3.tw-snapshot | 4 +- .../__snapshots__/tw-NaN.sb3.tw-snapshot | 24 +- ...-does-not-use-rounded-size.sb3.tw-snapshot | 2 +- ...tw-color-input-returns-hex.sb3.tw-snapshot | 2 +- ...w-comparison-matrix-inline.sb3.tw-snapshot | 186 +-- ...-comparison-matrix-runtime.sb3.tw-snapshot | 14 +- .../tw-custom-report-repeat.sb3.tw-snapshot | 2 +- ...-boolean-number-comparison.sb3.tw-snapshot | 4 +- ...-name-desync-name-fallback.sb3.tw-snapshot | 4 +- ...-zero-seconds-in-warp-mode.sb3.tw-snapshot | 6 +- ...s-not-reevaluate-arguments.sb3.tw-snapshot | 4 +- .../__snapshots__/tw-list-any.sb3.tw-snapshot | 2 +- ...w-one-divide-negative-zero.sb3.tw-snapshot | 2 +- ...nce-of-procedure-387608267.sb3.tw-snapshot | 2 +- ...e-arguments-with-same-name.sb3.tw-snapshot | 4 +- ...able-input-types-430811055.sb3.tw-snapshot | 4 +- ...cedure-return-non-existant.sb3.tw-snapshot | 4 +- ...cedure-return-non-existent.sb3.tw-snapshot | 2 +- ...procedure-return-recursion.sb3.tw-snapshot | 32 +- ...tw-procedure-return-simple.sb3.tw-snapshot | 20 +- ...edure-return-stops-scripts.sb3.tw-snapshot | 8 +- .../tw-procedure-return-warp.sb3.tw-snapshot | 6 +- ...procedure-argument-casting.sb3.tw-snapshot | 2 +- .../tw-sensing-of.sb3.tw-snapshot | 4 +- .../tw-tab-equals-zero.sb3.tw-snapshot | 2 +- .../__snapshots__/tw-tangent.sb3.tw-snapshot | 14 +- .../tw-unsafe-equals.sb3.tw-snapshot | 26 +- ...t-until-timer-greater-than.sb3.tw-snapshot | 6 +- ...mbie-cube-escape-284516654.sb3.tw-snapshot | 4 +- .../order-library-reverse.sb3.tw-snapshot | 4 +- .../warp-timer/order-library.sb3.tw-snapshot | 4 +- .../warp-timer/tw-NaN.sb3.tw-snapshot | 24 +- ...-does-not-use-rounded-size.sb3.tw-snapshot | 2 +- ...tw-color-input-returns-hex.sb3.tw-snapshot | 2 +- ...w-comparison-matrix-inline.sb3.tw-snapshot | 186 +-- ...-comparison-matrix-runtime.sb3.tw-snapshot | 14 +- .../tw-custom-report-repeat.sb3.tw-snapshot | 2 +- ...-boolean-number-comparison.sb3.tw-snapshot | 4 +- ...-name-desync-name-fallback.sb3.tw-snapshot | 4 +- ...-zero-seconds-in-warp-mode.sb3.tw-snapshot | 6 +- ...s-not-reevaluate-arguments.sb3.tw-snapshot | 4 +- .../warp-timer/tw-list-any.sb3.tw-snapshot | 2 +- ...w-one-divide-negative-zero.sb3.tw-snapshot | 2 +- ...nce-of-procedure-387608267.sb3.tw-snapshot | 2 +- ...e-arguments-with-same-name.sb3.tw-snapshot | 4 +- ...able-input-types-430811055.sb3.tw-snapshot | 4 +- ...cedure-return-non-existant.sb3.tw-snapshot | 4 +- ...cedure-return-non-existent.sb3.tw-snapshot | 2 +- ...procedure-return-recursion.sb3.tw-snapshot | 32 +- ...tw-procedure-return-simple.sb3.tw-snapshot | 20 +- ...edure-return-stops-scripts.sb3.tw-snapshot | 6 +- .../tw-procedure-return-warp.sb3.tw-snapshot | 6 +- ...procedure-argument-casting.sb3.tw-snapshot | 2 +- .../warp-timer/tw-sensing-of.sb3.tw-snapshot | 4 +- .../tw-tab-equals-zero.sb3.tw-snapshot | 2 +- .../warp-timer/tw-tangent.sb3.tw-snapshot | 14 +- .../tw-unsafe-equals.sb3.tw-snapshot | 26 +- ...t-until-timer-greater-than.sb3.tw-snapshot | 6 +- ...mbie-cube-escape-284516654.sb3.tw-snapshot | 4 +- 71 files changed, 2800 insertions(+), 2063 deletions(-) create mode 100644 src/compiler/enums.js create mode 100644 src/compiler/iroptimizer.js diff --git a/src/compiler/compat-block-utility.js b/src/compiler/compat-block-utility.js index 6fedba1d9d2..b662867dc07 100644 --- a/src/compiler/compat-block-utility.js +++ b/src/compiler/compat-block-utility.js @@ -1,13 +1,16 @@ +// @ts-check + const BlockUtility = require('../engine/block-utility'); class CompatibilityLayerBlockUtility extends BlockUtility { constructor () { super(); + this._stackFrame = {}; this._startedBranch = null; } get stackFrame () { - return this.thread.compatibilityStackFrame; + return this._stackFrame; } startBranch (branchNumber, isLoop) { @@ -32,9 +35,9 @@ class CompatibilityLayerBlockUtility extends BlockUtility { init (thread, fakeBlockId, stackFrame) { this.thread = thread; this.sequencer = thread.target.runtime.sequencer; + this._stackFrame = stackFrame; this._startedBranch = null; thread.stack[0] = fakeBlockId; - thread.compatibilityStackFrame = stackFrame; } } diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js index 1c9d8f5ae9a..ba9d5b88bb8 100644 --- a/src/compiler/compat-blocks.js +++ b/src/compiler/compat-blocks.js @@ -1,3 +1,5 @@ +// @ts-check + /** * @fileoverview List of blocks to be supported in the compiler compatibility layer. * This is only for native blocks. Extensions should not be listed here. diff --git a/src/compiler/compile.js b/src/compiler/compile.js index b5edb81352b..4a5c5029636 100644 --- a/src/compiler/compile.js +++ b/src/compiler/compile.js @@ -1,14 +1,20 @@ -const {IRGenerator} = require('./irgen'); +// @ts-check + +const IRGenerator = require('./irgen'); +const {IROptimizer} = require('./iroptimizer'); const JSGenerator = require('./jsgen'); -const compile = thread => { +const compile = (/** @type {import("../engine/thread")} */ thread) => { const irGenerator = new IRGenerator(thread); const ir = irGenerator.generate(); + const irOptimizer = new IROptimizer(ir); + irOptimizer.optimize(); + const procedures = {}; const target = thread.target; - const compileScript = script => { + const compileScript = (/** @type {import("./intermediate").IntermediateScript} */ script) => { if (script.cachedCompileResult) { return script.cachedCompileResult; } diff --git a/src/compiler/enums.js b/src/compiler/enums.js new file mode 100644 index 00000000000..c1a4e90fc24 --- /dev/null +++ b/src/compiler/enums.js @@ -0,0 +1,295 @@ +// @ts-check + +/** + * @fileoverview Common enums shared amongst parts of the compiler. + */ + + +/** + * Enum for the type of the value that is returned by reporter blocks and stored in constants. + * + * At compile time, often we don't know exactly type a value will be but we can tell it must be one of a + * set of types. For this reason, the number value of each type represents a possibility space, where set + * bits indicate that their corropoding type *could* be encountered at runtime. + * For example, a type of InputType.NUMBER | InputType.STRING means the value will be either a number or + * a string at runtime, the compiler can't tell which, but we do know that it's not a boolean or NaN as + * those bits are not set. + * + * @readonly + * @enum {number} + */ +const InputType = { + /** The value Infinity */ + NUMBER_POS_INF: 0x001, + /** Any natural number */ + NUMBER_POS_INT: 0x002, + /** Any positive fractional number, excluding integers. */ + NUMBER_POS_FRACT: 0x004, + /** Any positive number excluding 0 and Infinity. Equal to NUMBER_POS_INT | NUMBER_POS_FRACT */ + NUMBER_POS_REAL: 0x006, + /** The value 0 */ + NUMBER_ZERO: 0x008, + /** The value -0 */ + NUMBER_NEG_ZERO: 0x010, + /** Any negitive integer excluding -0 */ + NUMBER_NEG_INT: 0x020, + /** Any negitive fractional number, excluding integers. */ + NUMBER_NEG_FRACT: 0x040, + /** Any negitive number excluding -0 and -Infinity. Equal to NUMBER_NEG_INT | NUMBER_NEG_FRACT */ + NUMBER_NEG_REAL: 0x060, + /** The value -Infinity */ + NUMBER_NEG_INF: 0x080, + + /** The value NaN */ + NUMBER_NAN: 0x100, + + /** Either 0 or -0. Equal to NUMBER_ZERO | NUMBER_NEG_ZERO */ + NUMBER_ANY_ZERO: 0x018, + /** Either Infinity or -Infinity. Equal to NUMBER_POS_INF | NUMBER_NEG_INF */ + NUMBER_INF: 0x081, + /** Any positive number, excluding 0. Equal to NUMBER_POS_REAL | NUMBER_POS_INF */ + NUMBER_POS: 0x007, + /** Any negitive number, excluding -0. Equal to NUMBER_NEG_REAL | NUMBER_NEG_INF */ + NUMBER_NEG: 0x0E0, + /** Any whole number. Equal to NUMBER_POS_INT | NUMBER_ZERO */ + NUMBER_WHOLE: 0x00A, + /** Any integer. Equal to NUMBER_POS_INT | NUMBER_ANY_ZERO | NUMBER_NEG_INT */ + NUMBER_INT: 0x03A, + /** Any number that works as an array index. Equal to NUMBER_INT | NUMBER_INF | NUMBER_NAN */ + NUMBER_INDEX: 0x1BB, + /** Any fractional non-integer numbers. Equal to NUMBER_POS_FRACT | NUMBER_NEG_FRACT */ + NUMBER_FRACT: 0x44, + /** Any real number. Equal to NUMBER_POS_REAL | NUMBER_ANY_ZERO | NUMBER_NEG_REAL */ + NUMBER_REAL: 0x07E, + + /** Any number, excluding NaN. Equal to NUMBER_REAL | NUMBER_INF */ + NUMBER: 0x0FF, + /** Any number, including NaN. Equal to NUMBER | NUMBER_NAN */ + NUMBER_OR_NAN: 0x1FF, + /** Anything that can be interperated as a number. Equal to NUMBER | STRING_NUM | BOOLEAN */ + NUMBER_INTERPRETABLE: 0x12FF, + + /** Any string which as a non-NaN neumeric interpretation, excluding ''. */ + STRING_NUM: 0x200, + /** Any string which has no non-NaN neumeric interpretation, including ''. */ + STRING_NAN: 0x400, + /** Either of the strings 'true' or 'false'. */ + STRING_BOOLEAN: 0x800, + + /** Any string. Equal to STRING_NUM | STRING_NAN | STRING_BOOLEAN */ + STRING: 0xE00, + + /** Any boolean. */ + BOOLEAN: 0x1000, + /** Any input that can be interperated as a boolean. Equal to BOOLEAN | STRING_BOOLEAN */ + BOOLEAN_INTERPRETABLE: 0x1800, + + /** Any type. Equal to NUMBER_OR_NAN | STRING | BOOLEAN */ + ANY: 0x1FFF +}; + +/** + * Enum for the opcodes of the stackable blocks used in the IR AST. + * @readonly + * @enum {string} + */ +const StackOpcode = { + NOP: 'noop', + + ADDON_CALL: 'addons.call', + DEBUGGER: 'tw.debugger', + VISUAL_REPORT: 'visualReport', + COMPATIBILITY_LAYER: 'compat', + + HAT_EDGE: 'hat.edge', + HAT_PREDICATE: 'hat.predicate', + + CONTROL_IF_ELSE: 'control.if', + CONTROL_CLONE_CREATE: 'control.createClone', + CONTROL_CLONE_DELETE: 'control.deleteClone', + CONTROL_WHILE: 'control.while', + CONTROL_FOR: 'control.for', + CONTROL_REPEAT: 'control.repeat', + CONTROL_STOP_ALL: 'control.stopAll', + CONTROL_STOP_OTHERS: 'control.stopOthers', + CONTROL_STOP_SCRIPT: 'control.stopScript', + CONTROL_WAIT: 'control.wait', + CONTROL_WAIT_UNTIL: 'control.waitUntil', + CONTROL_CLEAR_COUNTER: 'control.counterClear', + CONTORL_INCR_COUNTER: 'control.counterIncr', + + LIST_ADD: 'list.add', + LIST_INSERT: 'list.instert', + LIST_REPLACE: 'list.replace', + LIST_DELETE_ALL: 'list.deleteAll', + LIST_DELETE: 'list.delete', + LIST_SHOW: 'list.show', + LIST_HIDE: 'list.hide', + + VAR_SET: 'var.set', + VAR_SHOW: 'var.show', + VAR_HIDE: 'var.hide', + + EVENT_BROADCAST: 'event.broadcast', + EVENT_BROADCAST_AND_WAIT: 'event.broadcastAndWait', + + LOOKS_EFFECT_SET: 'looks.setEffect', + LOOKS_EFFECT_CHANGE: 'looks.changeEffect', + LOOKS_EFFECT_CLEAR: 'looks.clearEffects', + LOOKS_SIZE_CHANGE: 'looks.changeSize', + LOOKS_SIZE_SET: 'looks.setSize', + LOOKS_LAYER_FORWARD: 'looks.forwardLayers', + LOOKS_LAYER_BACKWARD: 'looks.backwardLayers', + LOOKS_LAYER_FRONT: 'looks.goToFront', + LOOKS_LAYER_BACK: 'looks.goToBack', + LOOKS_HIDE: 'looks.hide', + LOOKS_SHOW: 'looks.show', + LOOKS_BACKDROP_NEXT: 'looks.nextBackdrop', + LOOKS_BACKDROP_SET: 'looks.switchBackdrop', + LOOKS_COSTUME_NEXT: 'looks.nextCostume', + LOOKS_COSTUME_SET: 'looks.switchCostume', + + MOTION_X_SET: 'motion.setX', + MOTION_X_CHANGE: 'motion.changeX', + MOTION_Y_SET: 'motion.setY', + MOTION_Y_CHANGE: 'motion.changeY', + MOTION_XY_SET: 'motion.setXY', + MOTION_IF_ON_EDGE_BOUNCE: 'motion.ifOnEdgeBounce', + MOTION_STEP: 'motion.step', + MOTION_ROTATION_STYLE_SET: 'motion.setRotationStyle', + MOTION_DIRECTION_SET: 'motion.setDirection', + + PEN_UP: 'pen.up', + PEN_DOWN: 'pen.down', + PEN_CLEAR: 'pen.clear', + PEN_COLOR_PARAM_SET: 'pen.setParam', + PEN_COLOR_PARAM_CHANGE: 'pen.changeParam', + PEN_COLOR_HUE_CHANGE_LEGACY: 'pen.legacyChangeHue', + PEN_COLOR_HUE_SET_LEGACY: 'pen_setPenHueToNumber', + PEN_COLOR_SHADE_CHANGE_LEGACY: 'pen.legacyChangeShade', + PEN_COLOR_SHADE_SET_LEGACY: 'pen.legacySetShade', + PEN_COLOR_SET: 'pen.setColor', + PEN_SIZE_SET: 'pen.setSize', + PEN_SIZE_CHANGE: 'pen.changeSize', + PEN_STAMP: 'pen.stamp', + + SENSING_TIMER_RESET: 'timer.reset', + + PROCEDURE_RETURN: 'procedures.return', + PROCEDURE_CALL: 'procedures.call' +}; + +/** + * Enum for the opcodes of the reporter blocks used in the IR AST. + * @readonly + * @enum {string} + */ +const InputOpcode = { + NOP: 'noop', + + ADDON_CALL: 'addons.call', + CONSTANT: 'constant', + + CAST_NUMBER: 'cast.toNumber', + CAST_NUMBER_INDEX: 'cast.toInteger', + CAST_NUMBER_OR_NAN: 'cast.toNumberOrNaN', + CAST_STRING: 'cast.toString', + CAST_BOOLEAN: 'cast.toBoolean', + + COMPATIBILITY_LAYER: 'compat', + + LOOKS_BACKDROP_NUMBER: 'looks.backdropNumber', + LOOKS_BACKDROP_NAME: 'looks.backdropName', + LOOKS_COSTUME_NUMBER: 'looks.costumeNumber', + LOOKS_COSTUME_NAME: 'looks.costumeName', + LOOKS_SIZE_GET: 'looks.size', + + VAR_GET: 'var.get', + + LIST_GET: 'list.get', + LIST_LENGTH: 'list.length', + LIST_CONTAINS: 'list.contains', + LIST_INDEX_OF: 'list.indexOf', + LIST_CONTENTS: 'list.contents', + + MOTION_X_GET: 'motion.x', + MOTION_Y_GET: 'motion.y', + MOTION_DIRECTION_GET: 'motion.direction', + + OP_ADD: 'op.add', + OP_AND: 'op.and', + OP_CONTAINS: 'op.contains', + OP_DIVIDE: 'op.divide', + OP_EQUALS: 'op.equals', + OP_GREATER: 'op.greater', + OP_LESS: 'op.less', + OP_JOIN: 'op.join', + OP_LENGTH: 'op.length', + OP_LETTER_OF: 'op.letterOf', + OP_ABS: 'op.abs', + OP_FLOOR: 'op.floor', + OP_CEILING: 'op.ceiling', + OP_SQRT: 'op.sqrt', + OP_SIN: 'op.sin', + OP_COS: 'op.cos', + OP_TAN: 'op.tan', + OP_ASIN: 'op.asin', + OP_ACOS: 'op.acos', + OP_ATAN: 'op.atan', + OP_LOG_E: 'op.ln', + OP_LOG_10: 'op.log', + OP_POW_E: 'op.e^', + OP_POW_10: 'op.10^', + OP_MOD: 'op.mod', + OP_MULTIPLY: 'op.multiply', + OP_NOT: 'op.not', + OP_OR: 'op.or', + OP_RANDOM: 'op.random', + OP_ROUND: 'op.round', + OP_SUBTRACT: 'op.subtract', + + SENSING_ANSWER: 'sensing.answer', + SENSING_COLOR_TOUCHING_COLOR: 'sensing.colorTouchingColor', + SENSING_TIME_YEAR: 'sensing.year', + SENSING_TIME_MONTH: 'sensing.month', + SENSING_TIME_DATE: 'sensing.date', + SENSING_TIME_WEEKDAY: 'sensing.dayofweek', + SENSING_TIME_HOUR: 'sensing.hour', + SENSING_TIME_MINUTE: 'sensing.minute', + SENSING_TIME_SECOND: 'sensing.second', + SENSING_TIME_DAYS_SINCE_2000: 'sensing.daysSince2000', + SENSING_DISTANCE: 'sensing.distance', + SENSING_KEY_DOWN: 'keyboard.pressed', + SENSING_MOUSE_DOWN: 'mouse.down', + SENSING_MOUSE_X: 'mouse.x', + SENSING_MOUSE_Y: 'mouse.y', + SENSING_OF: 'sensing.of', + SENSING_OF_BACKDROP_NAME: 'sensing.of.backdropName', + SENSING_OF_BACKDROP_NUMBER: 'sensing.of.backdropNumber', + SENSING_OF_COSTUME_NAME: 'sensing.of.costumeName', + SENSING_OF_COSTUME_NUMBER: 'sensing.of.costumeNumber', + SENSING_OF_VOLUME: 'sensing.of.volume', + SENSING_OF_POS_X: 'sensing.of.x', + SENSING_OF_POS_Y: 'sensing.of.y', + SENSING_OF_DIRECTION: 'sensing.of.direction', + SENSING_OF_SIZE: 'sensing.of.size', + SENSING_OF_VAR: 'sensing.of.var', + SENSING_TIMER_GET: 'timer.get', + SENSING_TOUCHING_COLOR: 'sensing.touchingColor', + SENSING_TOUCHING_OBJECT: 'sensing.touching', + SENSING_USERNAME: 'sensing.username', + + PROCEDURE_CALL: 'procedures.call', + PROCEDURE_ARG_STRING_NUMBER: 'args.stringNumber', + PROCEDURE_ARG_BOOLEAN: 'args.boolean', + + CONTROL_COUNTER: 'control.counter', + + TW_KEY_LAST_PRESSED: 'tw.lastKeyPressed' +}; + +module.exports = { + StackOpcode, + InputOpcode, + InputType +}; diff --git a/src/compiler/environment.js b/src/compiler/environment.js index 75b300a64fe..a7b49c3155b 100644 --- a/src/compiler/environment.js +++ b/src/compiler/environment.js @@ -1,3 +1,4 @@ +// @ts-check /* eslint-disable no-eval */ /** diff --git a/src/compiler/intermediate.js b/src/compiler/intermediate.js index 9882cefc62d..dc5f4ea36d4 100644 --- a/src/compiler/intermediate.js +++ b/src/compiler/intermediate.js @@ -1,7 +1,220 @@ +// @ts-check + +const Cast = require('../util/cast'); +const {InputOpcode, InputType} = require('./enums.js'); +const log = require('../util/log'); + /** * @fileoverview Common intermediates shared amongst parts of the compiler. */ +/** + * Describes a 'stackable' block (eg. show) + */ +class IntermediateStackBlock { + /** + * @param {import("./enums").StackOpcode} opcode + * @param {Object} inputs + * @param {boolean} yields + */ + constructor (opcode, inputs = {}, yields = false) { + /** + * The type of the stackable block. + * @type {import("./enums").StackOpcode} + */ + this.opcode = opcode; + + /** + * The inputs of this block. + * @type {Object} + */ + this.inputs = inputs; + + /** + * Does this block cause a yield + * @type {boolean} + */ + this.yields = yields; + + /** + * Should state changes made by this stack block be ignored? Used for testing. + * @type {boolean} + */ + this.ignoreState = false; + + /** + * @type {import("./iroptimizer").TypeState?} + */ + this.entryState = null; + + /** + * @type {import("./iroptimizer").TypeState?} + */ + this.exitState = null; + } +} + +/** + * Describes an input to a block. + * This could be a constant, variable or math operation. + */ +class IntermediateInput { + + static getNumberInputType (number) { + if (typeof number !== 'number') throw new Error('Expected a number.'); + if (number === Infinity) return InputType.NUMBER_POS_INF; + if (number === -Infinity) return InputType.NUMBER_NEG_INF; + if (number < 0) return Number.isInteger(number) ? InputType.NUMBER_NEG_INT : InputType.NUMBER_NEG_FRACT; + if (number > 0) return Number.isInteger(number) ? InputType.NUMBER_POS_INT : InputType.NUMBER_POS_FRACT; + if (Number.isNaN(number)) return InputType.NUMBER_NAN; + if (Object.is(number, -0)) return InputType.NUMBER_NEG_ZERO; + return InputType.NUMBER_ZERO; + } + + /** + * @param {InputOpcode} opcode + * @param {InputType} type + * @param {Object} inputs + * @param {boolean} yields + */ + constructor (opcode, type, inputs = {}, yields = false) { + /** + * @type {InputOpcode} + */ + this.opcode = opcode; + + /** + * @type {InputType} + */ + this.type = type; + + /** + * @type {Object} + */ + this.inputs = inputs; + + /** + * @type {boolean} + */ + this.yields = yields; + } + + /** + * Is this input a constant whos value equals value. + * @param {*} value The value + * @returns {boolean} + */ + isConstant (value) { + if (this.opcode !== InputOpcode.CONSTANT) return false; + let equal = this.inputs.value === value; + if (!equal && typeof value === 'number') equal = (+this.inputs.value) === value; + return equal; + } + + /** + * Is the type of this input guaranteed to always be the type at runtime. + * @param {InputType} type + * @returns {boolean} + */ + isAlwaysType (type) { + return (this.type & type) === this.type; + } + + /** + * Is it possible for this input to be the type at runtime. + * @param {InputType} type + * @returns + */ + isSometimesType (type) { + return (this.type & type) !== 0; + } + + /** + * Converts this input to a target type. + * If this input is a constant the conversion is performed now, at compile time. + * If the input changes, the conversion is performed at runtime. + * @param {InputType} targetType + * @returns {IntermediateInput} An input with the new type. + */ + toType (targetType) { + let castOpcode; + switch (targetType) { + case InputType.BOOLEAN: + castOpcode = InputOpcode.CAST_BOOLEAN; + break; + case InputType.NUMBER: + castOpcode = InputOpcode.CAST_NUMBER; + break; + case InputType.NUMBER_INDEX: + castOpcode = InputOpcode.CAST_NUMBER_INDEX; + break; + case InputType.NUMBER_OR_NAN: + castOpcode = InputOpcode.CAST_NUMBER_OR_NAN; + break; + case InputType.STRING: + castOpcode = InputOpcode.CAST_STRING; + break; + default: + log.warn(`Cannot cast to type: ${targetType}`, this); + throw new Error(`Cannot cast to type: ${targetType}`); + } + + if (this.isAlwaysType(targetType)) return this; + + if (this.opcode === InputOpcode.CONSTANT) { + // If we are a constant, we can do the cast here at compile time + switch (castOpcode) { + case InputOpcode.CAST_BOOLEAN: + this.inputs.value = Cast.toBoolean(this.inputs.value); + this.type = InputType.BOOLEAN; + break; + case InputOpcode.CAST_NUMBER: + case InputOpcode.CAST_NUMBER_INDEX: + case InputOpcode.CAST_NUMBER_OR_NAN: + if (this.isAlwaysType(InputType.BOOLEAN_INTERPRETABLE)) { + this.type = InputType.NUMBER; + this.inputs.value = +Cast.toBoolean(this.inputs.value); + } + var numberValue = +this.inputs.value; + if (numberValue) { + this.inputs.value = numberValue; + } else { + // numberValue is one of 0, -0, or NaN + if (Object.is(numberValue, -0)) this.inputs.value = -0; + else this.inputs.value = 0; // Convert NaN to 0 + } + if (castOpcode === InputOpcode.CAST_NUMBER_INDEX) { + // Round numberValue to an integer + numberValue |= 0; + } + this.type = IntermediateInput.getNumberInputType(this.inputs.value); + break; + case InputOpcode.CAST_STRING: + this.inputs.value += ''; + this.type = InputType.STRING; + break; + } + return this; + } + + return new IntermediateInput(castOpcode, targetType, {target: this}); + } +} + +/** + * A 'stack' of blocks, like the contents of a script or the inside + * of a C block. + */ +class IntermediateStack { + /** + * @param {IntermediateStackBlock[]} [blocks] + */ + constructor (blocks) { + /** @type {IntermediateStackBlock[]} */ + this.blocks = blocks ?? []; + } +} + /** * An IntermediateScript describes a single script. * Scripts do not necessarily have hats. @@ -10,13 +223,13 @@ class IntermediateScript { constructor () { /** * The ID of the top block of this script. - * @type {string} + * @type {string?} */ this.topBlockId = null; /** * List of nodes that make up this script. - * @type {Array|null} + * @type {IntermediateStack?} */ this.stack = null; @@ -26,12 +239,6 @@ class IntermediateScript { */ this.isProcedure = false; - /** - * This procedure's variant, if any. - * @type {string} - */ - this.procedureVariant = ''; - /** * This procedure's code, if any. * @type {string} @@ -76,11 +283,18 @@ class IntermediateScript { */ this.cachedCompileResult = null; + /** + * Cached result of analysing this script. + * @type {import("./iroptimizer").TypeState|null} + */ + this.cachedAnalysisEndState = null; + /** * Whether the top block of this script is an executable hat. * @type {boolean} */ this.executableHat = false; + } } @@ -88,22 +302,39 @@ class IntermediateScript { * An IntermediateRepresentation contains scripts. */ class IntermediateRepresentation { - constructor () { + /** + * + * @param {IntermediateScript} entry + * @param {Object.} procedures + */ + constructor (entry, procedures) { /** * The entry point of this IR. * @type {IntermediateScript} */ - this.entry = null; + this.entry = entry; /** * Maps procedure variants to their intermediate script. * @type {Object.} */ - this.procedures = {}; + this.procedures = procedures; + } + + /** + * Gets the first procedure with the given proccode. + * @param {string} proccode + * @returns {IntermediateScript | undefined} + */ + getProcedure (proccode) { + return Object.values(this.procedures).find(procedure => procedure.procedureCode === proccode); } } module.exports = { + IntermediateStackBlock, + IntermediateInput, + IntermediateStack, IntermediateScript, IntermediateRepresentation }; diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 39707ef5c6c..91a576db301 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -1,10 +1,13 @@ +// @ts-check + const Cast = require('../util/cast'); const StringUtil = require('../util/string-util'); const BlockType = require('../extension-support/block-type'); const Variable = require('../engine/variable'); const log = require('../util/log'); -const {IntermediateScript, IntermediateRepresentation} = require('./intermediate'); +const {IntermediateStackBlock, IntermediateInput, IntermediateStack, IntermediateScript, IntermediateRepresentation} = require('./intermediate'); const compatBlocks = require('./compat-blocks'); +const {StackOpcode, InputOpcode, InputType} = require('./enums.js'); /** * @fileoverview Generate intermediate representations from Scratch blocks. @@ -13,11 +16,6 @@ const compatBlocks = require('./compat-blocks'); const SCALAR_TYPE = ''; const LIST_TYPE = 'list'; -/** - * @typedef {Object.} Node - * @property {string} kind - */ - /** * Create a variable codegen object. * @param {'target'|'stage'} scope The scope of this variable -- which object owns it. @@ -82,12 +80,24 @@ class ScriptTreeGenerator { this.variableCache = {}; this.usesTimer = false; + + this.namesOfCostumesAndSounds = new Set(); + for (const target of this.runtime.targets) { + if (target.isOriginal) { + const sprite = target.sprite; + for (const costume of sprite.costumes) { + this.namesOfCostumesAndSounds.add(costume.name); + } + for (const sound of sprite.sounds) { + this.namesOfCostumesAndSounds.add(sound.name); + } + } + } } setProcedureVariant (procedureVariant) { const procedureCode = parseProcedureCode(procedureVariant); - this.script.procedureVariant = procedureVariant; this.script.procedureCode = procedureCode; this.script.isProcedure = true; this.script.yields = false; @@ -126,63 +136,76 @@ class ScriptTreeGenerator { return blockInfo; } + createConstantInput (constant, preserveStrings = false) { + if (constant == null) throw new Error('IR: Constant cannot have a null value.'); + + constant += ''; + const numConstant = +constant; + const preserve = preserveStrings && this.namesOfCostumesAndSounds.has(constant); + + if (!Number.isNaN(numConstant) && (constant.trim() !== '' || constant.includes('\t'))) { + if (!preserve && numConstant.toString() === constant) { + return new IntermediateInput(InputOpcode.CONSTANT, IntermediateInput.getNumberInputType(numConstant), {value: numConstant}); + } + return new IntermediateInput(InputOpcode.CONSTANT, InputType.STRING_NUM, {value: constant}); + } + + if (!preserve) { + if (constant === 'true') { + return new IntermediateInput(InputOpcode.CONSTANT, InputType.STRING_BOOLEAN, {value: constant}); + } else if (constant === 'false') { + return new IntermediateInput(InputOpcode.CONSTANT, InputType.STRING_BOOLEAN, {value: constant}); + } + } + + return new IntermediateInput(InputOpcode.CONSTANT, InputType.STRING_NAN, {value: constant}); + } + /** * Descend into a child input of a block. (eg. the input STRING of "length of ( )") * @param {*} parentBlock The parent Scratch block that contains the input. * @param {string} inputName The name of the input to descend into. + * @param {boolean} preserveStrings Should this input keep the names of costumes and sounds at strings. * @private - * @returns {Node} Compiled input node for this input. + * @returns {IntermediateInput} Compiled input node for this input. */ - descendInputOfBlock (parentBlock, inputName) { + descendInputOfBlock (parentBlock, inputName, preserveStrings = false) { const input = parentBlock.inputs[inputName]; if (!input) { log.warn(`IR: ${parentBlock.opcode}: missing input ${inputName}`, parentBlock); - return { - kind: 'constant', - value: 0 - }; + return this.createConstantInput(0); } const inputId = input.block; const block = this.getBlockById(inputId); if (!block) { log.warn(`IR: ${parentBlock.opcode}: could not find input ${inputName} with ID ${inputId}`); - return { - kind: 'constant', - value: 0 - }; + return this.createConstantInput(0); } - return this.descendInput(block); + const intermediate = this.descendInput(block, preserveStrings); + this.script.yields = this.script.yields || intermediate.yields; + return intermediate; } /** * Descend into an input. (eg. "length of ( )") * @param {*} block The parent Scratch block input. + * @param {boolean} preserveStrings Should this input keep the names of costumes and sounds at strings. * @private - * @returns {Node} Compiled input node for this input. + * @returns {IntermediateInput} Compiled input node for this input. */ - descendInput (block) { + descendInput (block, preserveStrings = false) { switch (block.opcode) { case 'colour_picker': - return { - kind: 'constant', - value: block.fields.COLOUR.value - }; + return this.createConstantInput(block.fields.COLOUR.value, true); case 'math_angle': case 'math_integer': case 'math_number': case 'math_positive_number': case 'math_whole_number': - return { - kind: 'constant', - value: block.fields.NUM.value - }; + return this.createConstantInput(block.fields.NUM.value, preserveStrings); case 'text': - return { - kind: 'constant', - value: block.fields.TEXT.value - }; - + return this.createConstantInput(block.fields.TEXT.value, preserveStrings); case 'argument_reporter_string_number': { const name = block.fields.VALUE.value; // lastIndexOf because multiple parameters with the same name will use the value of the last definition @@ -190,21 +213,13 @@ class ScriptTreeGenerator { if (index === -1) { // Legacy support if (name.toLowerCase() === 'last key pressed') { - return { - kind: 'tw.lastKeyPressed' - }; + return new IntermediateInput(InputOpcode.TW_KEY_LAST_PRESSED, InputType.STRING); } } if (index === -1) { - return { - kind: 'constant', - value: 0 - }; + return this.createConstantInput(0); } - return { - kind: 'args.stringNumber', - index: index - }; + return new IntermediateInput(InputOpcode.PROCEDURE_ARG_STRING_NUMBER, InputType.ANY, {index}); } case 'argument_reporter_boolean': { // see argument_reporter_string_number above @@ -212,460 +227,343 @@ class ScriptTreeGenerator { const index = this.script.arguments.lastIndexOf(name); if (index === -1) { if (name.toLowerCase() === 'is compiled?' || name.toLowerCase() === 'is turbowarp?') { - return { - kind: 'constant', - value: true - }; + return this.createConstantInput(true).toType(InputType.BOOLEAN); } - return { - kind: 'constant', - value: 0 - }; + return this.createConstantInput(0); } - return { - kind: 'args.boolean', - index: index - }; + return new IntermediateInput(InputOpcode.PROCEDURE_ARG_BOOLEAN, InputType.BOOLEAN, {index}); } - case 'control_get_counter': - return { - kind: 'counter.get' - }; - case 'data_variable': - return { - kind: 'var.get', + return new IntermediateInput(InputOpcode.VAR_GET, InputType.ANY, { variable: this.descendVariable(block, 'VARIABLE', SCALAR_TYPE) - }; + }); case 'data_itemoflist': - return { - kind: 'list.get', + return new IntermediateInput(InputOpcode.LIST_GET, InputType.ANY, { list: this.descendVariable(block, 'LIST', LIST_TYPE), index: this.descendInputOfBlock(block, 'INDEX') - }; + }); case 'data_lengthoflist': - return { - kind: 'list.length', + return new IntermediateInput(InputOpcode.LIST_LENGTH, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO, { list: this.descendVariable(block, 'LIST', LIST_TYPE) - }; + }); case 'data_listcontainsitem': - return { - kind: 'list.contains', + return new IntermediateInput(InputOpcode.LIST_CONTAINS, InputType.BOOLEAN, { list: this.descendVariable(block, 'LIST', LIST_TYPE), item: this.descendInputOfBlock(block, 'ITEM') - }; + }); case 'data_itemnumoflist': - return { - kind: 'list.indexOf', + return new IntermediateInput(InputOpcode.LIST_INDEX_OF, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO, { list: this.descendVariable(block, 'LIST', LIST_TYPE), item: this.descendInputOfBlock(block, 'ITEM') - }; + }); case 'data_listcontents': - return { - kind: 'list.contents', + return new IntermediateInput(InputOpcode.LIST_CONTENTS, InputType.STRING, { list: this.descendVariable(block, 'LIST', LIST_TYPE) - }; + }); case 'event_broadcast_menu': { const broadcastOption = block.fields.BROADCAST_OPTION; const broadcastVariable = this.target.lookupBroadcastMsg(broadcastOption.id, broadcastOption.value); // TODO: empty string probably isn't the correct fallback const broadcastName = broadcastVariable ? broadcastVariable.name : ''; - return { - kind: 'constant', - value: broadcastName - }; + return this.createConstantInput(broadcastName); } case 'looks_backdropnumbername': if (block.fields.NUMBER_NAME.value === 'number') { - return { - kind: 'looks.backdropNumber' - }; + return new IntermediateInput(InputOpcode.LOOKS_BACKDROP_NUMBER, InputType.NUMBER_POS_REAL); } - return { - kind: 'looks.backdropName' - }; + return new IntermediateInput(InputOpcode.LOOKS_BACKDROP_NAME, InputType.STRING); case 'looks_costumenumbername': if (block.fields.NUMBER_NAME.value === 'number') { - return { - kind: 'looks.costumeNumber' - }; + return new IntermediateInput(InputOpcode.LOOKS_COSTUME_NUMBER, InputType.NUMBER_POS_REAL); } - return { - kind: 'looks.costumeName' - }; + return new IntermediateInput(InputOpcode.LOOKS_COSTUME_NAME, InputType.STRING); case 'looks_size': - return { - kind: 'looks.size' - }; + return new IntermediateInput(InputOpcode.LOOKS_SIZE_GET, InputType.NUMBER_POS_REAL); case 'motion_direction': - return { - kind: 'motion.direction' - }; + return new IntermediateInput(InputOpcode.MOTION_DIRECTION_GET, InputType.NUMBER_REAL); case 'motion_xposition': - return { - kind: 'motion.x' - }; + return new IntermediateInput(InputOpcode.MOTION_X_GET, InputType.NUMBER_REAL); case 'motion_yposition': - return { - kind: 'motion.y' - }; + return new IntermediateInput(InputOpcode.MOTION_Y_GET, InputType.NUMBER_REAL); case 'operator_add': - return { - kind: 'op.add', - left: this.descendInputOfBlock(block, 'NUM1'), - right: this.descendInputOfBlock(block, 'NUM2') - }; + return new IntermediateInput(InputOpcode.OP_ADD, InputType.NUMBER_OR_NAN, { + left: this.descendInputOfBlock(block, 'NUM1').toType(InputType.NUMBER), + right: this.descendInputOfBlock(block, 'NUM2').toType(InputType.NUMBER) + }); case 'operator_and': - return { - kind: 'op.and', - left: this.descendInputOfBlock(block, 'OPERAND1'), - right: this.descendInputOfBlock(block, 'OPERAND2') - }; + return new IntermediateInput(InputOpcode.OP_AND, InputType.BOOLEAN, { + left: this.descendInputOfBlock(block, 'OPERAND1').toType(InputType.BOOLEAN), + right: this.descendInputOfBlock(block, 'OPERAND2').toType(InputType.BOOLEAN) + }); case 'operator_contains': - return { - kind: 'op.contains', - string: this.descendInputOfBlock(block, 'STRING1'), - contains: this.descendInputOfBlock(block, 'STRING2') - }; + return new IntermediateInput(InputOpcode.OP_CONTAINS, InputType.BOOLEAN, { + string: this.descendInputOfBlock(block, 'STRING1').toType(InputType.STRING), + contains: this.descendInputOfBlock(block, 'STRING2').toType(InputType.STRING) + }); case 'operator_divide': - return { - kind: 'op.divide', - left: this.descendInputOfBlock(block, 'NUM1'), - right: this.descendInputOfBlock(block, 'NUM2') - }; + return new IntermediateInput(InputOpcode.OP_DIVIDE, InputType.NUMBER_OR_NAN, { + left: this.descendInputOfBlock(block, 'NUM1').toType(InputType.NUMBER), + right: this.descendInputOfBlock(block, 'NUM2').toType(InputType.NUMBER) + }); case 'operator_equals': - return { - kind: 'op.equals', + return new IntermediateInput(InputOpcode.OP_EQUALS, InputType.BOOLEAN, { left: this.descendInputOfBlock(block, 'OPERAND1'), right: this.descendInputOfBlock(block, 'OPERAND2') - }; + }); case 'operator_gt': - return { - kind: 'op.greater', + return new IntermediateInput(InputOpcode.OP_GREATER, InputType.BOOLEAN, { left: this.descendInputOfBlock(block, 'OPERAND1'), right: this.descendInputOfBlock(block, 'OPERAND2') - }; + }); case 'operator_join': - return { - kind: 'op.join', - left: this.descendInputOfBlock(block, 'STRING1'), - right: this.descendInputOfBlock(block, 'STRING2') - }; + return new IntermediateInput(InputOpcode.OP_JOIN, InputType.STRING, { + left: this.descendInputOfBlock(block, 'STRING1').toType(InputType.STRING), + right: this.descendInputOfBlock(block, 'STRING2').toType(InputType.STRING) + }); case 'operator_length': - return { - kind: 'op.length', - string: this.descendInputOfBlock(block, 'STRING') - }; + return new IntermediateInput(InputOpcode.OP_LENGTH, InputType.NUMBER_REAL, { + string: this.descendInputOfBlock(block, 'STRING').toType(InputType.STRING) + }); case 'operator_letter_of': - return { - kind: 'op.letterOf', - letter: this.descendInputOfBlock(block, 'LETTER'), - string: this.descendInputOfBlock(block, 'STRING') - }; + return new IntermediateInput(InputOpcode.OP_LETTER_OF, InputType.STRING, { + letter: this.descendInputOfBlock(block, 'LETTER').toType(InputType.NUMBER_INDEX), + string: this.descendInputOfBlock(block, 'STRING').toType(InputType.STRING) + }); case 'operator_lt': - return { - kind: 'op.less', + return new IntermediateInput(InputOpcode.OP_LESS, InputType.BOOLEAN, { left: this.descendInputOfBlock(block, 'OPERAND1'), right: this.descendInputOfBlock(block, 'OPERAND2') - }; + }); case 'operator_mathop': { - const value = this.descendInputOfBlock(block, 'NUM'); + const value = this.descendInputOfBlock(block, 'NUM').toType(InputType.NUMBER); const operator = block.fields.OPERATOR.value.toLowerCase(); switch (operator) { - case 'abs': return { - kind: 'op.abs', - value - }; - case 'floor': return { - kind: 'op.floor', - value - }; - case 'ceiling': return { - kind: 'op.ceiling', - value - }; - case 'sqrt': return { - kind: 'op.sqrt', - value - }; - case 'sin': return { - kind: 'op.sin', - value - }; - case 'cos': return { - kind: 'op.cos', - value - }; - case 'tan': return { - kind: 'op.tan', - value - }; - case 'asin': return { - kind: 'op.asin', - value - }; - case 'acos': return { - kind: 'op.acos', - value - }; - case 'atan': return { - kind: 'op.atan', - value - }; - case 'ln': return { - kind: 'op.ln', - value - }; - case 'log': return { - kind: 'op.log', - value - }; - case 'e ^': return { - kind: 'op.e^', - value - }; - case '10 ^': return { - kind: 'op.10^', - value - }; - default: return { - kind: 'constant', - value: 0 - }; + case 'abs': return new IntermediateInput(InputOpcode.OP_ABS, InputType.NUMBER_POS | InputType.NUMBER_ZERO, {value}); + case 'floor': return new IntermediateInput(InputOpcode.OP_FLOOR, InputType.NUMBER, {value}); + case 'ceiling': return new IntermediateInput(InputOpcode.OP_CEILING, InputType.NUMBER, {value}); + case 'sqrt': return new IntermediateInput(InputOpcode.OP_SQRT, InputType.NUMBER_OR_NAN, {value}); + case 'sin': return new IntermediateInput(InputOpcode.OP_SIN, InputType.NUMBER_OR_NAN, {value}); + case 'cos': return new IntermediateInput(InputOpcode.OP_COS, InputType.NUMBER_OR_NAN, {value}); + case 'tan': return new IntermediateInput(InputOpcode.OP_TAN, InputType.NUMBER_OR_NAN, {value}); + case 'asin': return new IntermediateInput(InputOpcode.OP_ASIN, InputType.NUMBER_OR_NAN, {value}); + case 'acos': return new IntermediateInput(InputOpcode.OP_ACOS, InputType.NUMBER_OR_NAN, {value}); + case 'atan': return new IntermediateInput(InputOpcode.OP_ATAN, InputType.NUMBER, {value}); + case 'ln': return new IntermediateInput(InputOpcode.OP_LOG_E, InputType.NUMBER_OR_NAN, {value}); + case 'log': return new IntermediateInput(InputOpcode.OP_LOG_10, InputType.NUMBER_OR_NAN, {value}); + case 'e ^': return new IntermediateInput(InputOpcode.OP_POW_E, InputType.NUMBER, {value}); + case '10 ^': return new IntermediateInput(InputOpcode.OP_POW_10, InputType.NUMBER, {value}); + default: this.createConstantInput(0); } } case 'operator_mod': - return { - kind: 'op.mod', - left: this.descendInputOfBlock(block, 'NUM1'), - right: this.descendInputOfBlock(block, 'NUM2') - }; + return new IntermediateInput(InputOpcode.OP_MOD, InputType.NUMBER_OR_NAN, { + left: this.descendInputOfBlock(block, 'NUM1').toType(InputType.NUMBER), + right: this.descendInputOfBlock(block, 'NUM2').toType(InputType.NUMBER) + }); case 'operator_multiply': - return { - kind: 'op.multiply', - left: this.descendInputOfBlock(block, 'NUM1'), - right: this.descendInputOfBlock(block, 'NUM2') - }; + return new IntermediateInput(InputOpcode.OP_MULTIPLY, InputType.NUMBER_OR_NAN, { + left: this.descendInputOfBlock(block, 'NUM1').toType(InputType.NUMBER), + right: this.descendInputOfBlock(block, 'NUM2').toType(InputType.NUMBER) + }); case 'operator_not': - return { - kind: 'op.not', - operand: this.descendInputOfBlock(block, 'OPERAND') - }; + return new IntermediateInput(InputOpcode.OP_NOT, InputType.BOOLEAN, { + operand: this.descendInputOfBlock(block, 'OPERAND').toType(InputType.BOOLEAN) + }); case 'operator_or': - return { - kind: 'op.or', - left: this.descendInputOfBlock(block, 'OPERAND1'), - right: this.descendInputOfBlock(block, 'OPERAND2') - }; + return new IntermediateInput(InputOpcode.OP_OR, InputType.BOOLEAN, { + left: this.descendInputOfBlock(block, 'OPERAND1').toType(InputType.BOOLEAN), + right: this.descendInputOfBlock(block, 'OPERAND2').toType(InputType.BOOLEAN) + }); case 'operator_random': { const from = this.descendInputOfBlock(block, 'FROM'); const to = this.descendInputOfBlock(block, 'TO'); // If both values are known at compile time, we can do some optimizations. // TODO: move optimizations to jsgen? - if (from.kind === 'constant' && to.kind === 'constant') { - const sFrom = from.value; - const sTo = to.value; + if (from.opcode === InputOpcode.CONSTANT && to.opcode === InputOpcode.CONSTANT) { + const sFrom = from.inputs.value; + const sTo = to.inputs.value; const nFrom = Cast.toNumber(sFrom); const nTo = Cast.toNumber(sTo); // If both numbers are the same, random is unnecessary. // todo: this probably never happens so consider removing if (nFrom === nTo) { - return { - kind: 'constant', - value: nFrom - }; + return this.createConstantInput(nFrom); } // If both are ints, hint this to the compiler if (Cast.isInt(sFrom) && Cast.isInt(sTo)) { - return { - kind: 'op.random', - low: nFrom <= nTo ? from : to, - high: nFrom <= nTo ? to : from, + // Both inputs are ints, so we know neither are NaN + return new IntermediateInput(InputOpcode.OP_RANDOM, InputType.NUMBER, { + low: (nFrom <= nTo ? from : to).toType(InputType.NUMBER), + high: (nFrom <= nTo ? to : from).toType(InputType.NUMBER), useInts: true, useFloats: false - }; + }); } // Otherwise hint that these are floats - return { - kind: 'op.random', - low: nFrom <= nTo ? from : to, - high: nFrom <= nTo ? to : from, + return new IntermediateInput(InputOpcode.OP_RANDOM, InputType.NUMBER_OR_NAN, { + low: (nFrom <= nTo ? from : to).toType(InputType.NUMBER), + high: (nFrom <= nTo ? to : from).toType(InputType.NUMBER), useInts: false, useFloats: true - }; - } else if (from.kind === 'constant') { + }); + } else if (from.opcode === InputOpcode.CONSTANT) { // If only one value is known at compile-time, we can still attempt some optimizations. - if (!Cast.isInt(Cast.toNumber(from.value))) { - return { - kind: 'op.random', - low: from, - high: to, + if (!Cast.isInt(Cast.toNumber(from.inputs.value))) { + return new IntermediateInput(InputOpcode.OP_RANDOM, InputType.NUMBER_OR_NAN, { + low: from.toType(InputType.NUMBER), + high: to.toType(InputType.NUMBER), useInts: false, useFloats: true - }; + }); } - } else if (to.kind === 'constant') { - if (!Cast.isInt(Cast.toNumber(to.value))) { - return { - kind: 'op.random', - low: from, - high: to, + } else if (to.opcode === InputOpcode.CONSTANT) { + if (!Cast.isInt(Cast.toNumber(from.inputs.value))) { + return new IntermediateInput(InputOpcode.OP_RANDOM, InputType.NUMBER_OR_NAN, { + low: from.toType(InputType.NUMBER), + high: to.toType(InputType.NUMBER), useInts: false, useFloats: true - }; + }); } } // No optimizations possible - return { - kind: 'op.random', + return new IntermediateInput(InputOpcode.OP_RANDOM, InputType.NUMBER_OR_NAN, { low: from, high: to, useInts: false, useFloats: false - }; + }); } case 'operator_round': - return { - kind: 'op.round', - value: this.descendInputOfBlock(block, 'NUM') - }; + return new IntermediateInput(InputOpcode.OP_ROUND, InputType.NUMBER, { + value: this.descendInputOfBlock(block, 'NUM').toType(InputType.NUMBER) + }); case 'operator_subtract': - return { - kind: 'op.subtract', - left: this.descendInputOfBlock(block, 'NUM1'), - right: this.descendInputOfBlock(block, 'NUM2') - }; + return new IntermediateInput(InputOpcode.OP_SUBTRACT, InputType.NUMBER_OR_NAN, { + left: this.descendInputOfBlock(block, 'NUM1').toType(InputType.NUMBER), + right: this.descendInputOfBlock(block, 'NUM2').toType(InputType.NUMBER) + }); - case 'procedures_call': - return this.descendProcedure(block); + case 'procedures_call': { + const procedureInfo = this.getProcedureInfo(block); + return new IntermediateInput(procedureInfo.opcode, InputType.ANY, procedureInfo.inputs, procedureInfo.yields); + } case 'sensing_answer': - return { - kind: 'sensing.answer' - }; + return new IntermediateInput(InputOpcode.SENSING_ANSWER, InputType.STRING); + case 'sensing_coloristouchingcolor': - return { - kind: 'sensing.colorTouchingColor', + return new IntermediateInput(InputOpcode.SENSING_COLOR_TOUCHING_COLOR, InputType.BOOLEAN, { target: this.descendInputOfBlock(block, 'COLOR2'), mask: this.descendInputOfBlock(block, 'COLOR') - }; + }); case 'sensing_current': switch (block.fields.CURRENTMENU.value.toLowerCase()) { - case 'year': - return { - kind: 'sensing.year' - }; - case 'month': - return { - kind: 'sensing.month' - }; - case 'date': - return { - kind: 'sensing.date' - }; - case 'dayofweek': - return { - kind: 'sensing.dayofweek' - }; - case 'hour': - return { - kind: 'sensing.hour' - }; - case 'minute': - return { - kind: 'sensing.minute' - }; - case 'second': - return { - kind: 'sensing.second' - }; + case 'year': return new IntermediateInput(InputOpcode.SENSING_TIME_YEAR, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO); + case 'month': return new IntermediateInput(InputOpcode.SENSING_TIME_MONTH, InputType.NUMBER_POS_REAL); + case 'date': return new IntermediateInput(InputOpcode.SENSING_TIME_DATE, InputType.NUMBER_POS_REAL); + case 'dayofweek': return new IntermediateInput(InputOpcode.SENSING_TIME_WEEKDAY, InputType.NUMBER_POS_REAL); + case 'hour': return new IntermediateInput(InputOpcode.SENSING_TIME_HOUR, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO); + case 'minute': return new IntermediateInput(InputOpcode.SENSING_TIME_MINUTE, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO); + case 'second': return new IntermediateInput(InputOpcode.SENSING_TIME_SECOND, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO); + default: return this.createConstantInput(0); } - return { - kind: 'constant', - value: 0 - }; case 'sensing_dayssince2000': - return { - kind: 'sensing.daysSince2000' - }; + return new IntermediateInput(InputOpcode.SENSING_TIME_DAYS_SINCE_2000, InputType.NUMBER); case 'sensing_distanceto': - return { - kind: 'sensing.distance', - target: this.descendInputOfBlock(block, 'DISTANCETOMENU') - }; + return new IntermediateInput(InputOpcode.SENSING_DISTANCE, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO, { + target: this.descendInputOfBlock(block, 'DISTANCETOMENU').toType(InputType.STRING) + }); case 'sensing_keypressed': - return { - kind: 'keyboard.pressed', - key: this.descendInputOfBlock(block, 'KEY_OPTION') - }; + return new IntermediateInput(InputOpcode.SENSING_KEY_DOWN, InputType.BOOLEAN, { + key: this.descendInputOfBlock(block, 'KEY_OPTION', true) + }); case 'sensing_mousedown': - return { - kind: 'mouse.down' - }; + return new IntermediateInput(InputOpcode.SENSING_MOUSE_DOWN, InputType.BOOLEAN); case 'sensing_mousex': - return { - kind: 'mouse.x' - }; + return new IntermediateInput(InputOpcode.SENSING_MOUSE_X, InputType.NUMBER); case 'sensing_mousey': - return { - kind: 'mouse.y' - }; + return new IntermediateInput(InputOpcode.SENSING_MOUSE_Y, InputType.NUMBER); case 'sensing_of': - return { - kind: 'sensing.of', - property: block.fields.PROPERTY.value, - object: this.descendInputOfBlock(block, 'OBJECT') - }; + const property = block.fields.PROPERTY.value; + const object = this.descendInputOfBlock(block, 'OBJECT').toType(InputType.STRING); + + if (object.opcode !== InputOpcode.CONSTANT) { + return new IntermediateInput(InputOpcode.SENSING_OF, InputType.ANY, {object, property}); + } + + if (property === 'volume') { + return new IntermediateInput(InputOpcode.SENSING_OF_VOLUME, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO, {object, property}); + } + + if (object.isConstant('_stage_')) { + switch (property) { + case 'background #': // fallthrough for scratch 1.0 compatibility + case 'backdrop #': + return new IntermediateInput(InputOpcode.SENSING_OF_BACKDROP_NUMBER, InputType.NUMBER_POS_REAL); + case 'backdrop name': + return new IntermediateInput(InputOpcode.SENSING_OF_BACKDROP_NAME, InputType.STRING); + } + } else { + switch (property) { + case 'x position': + return new IntermediateInput(InputOpcode.SENSING_OF_POS_X, InputType.NUMBER_REAL, {object}); + case 'y position': + return new IntermediateInput(InputOpcode.SENSING_OF_POS_Y, InputType.NUMBER_REAL, {object}); + case 'direction': + return new IntermediateInput(InputOpcode.SENSING_OF_DIRECTION, InputType.NUMBER_REAL, {object}); + case 'costume #': + return new IntermediateInput(InputOpcode.SENSING_OF_COSTUME_NUMBER, InputType.NUMBER_POS_REAL, {object}); + case 'costume name': + return new IntermediateInput(InputOpcode.SENSING_OF_COSTUME_NAME, InputType.STRING, {object}); + case 'size': + return new IntermediateInput(InputOpcode.SENSING_OF_SIZE, InputType.NUMBER_POS_REAL, {object}); + } + } + + return new IntermediateInput(InputOpcode.SENSING_OF_VAR, InputType.ANY, {object, property}); case 'sensing_timer': this.usesTimer = true; - return { - kind: 'timer.get' - }; + return new IntermediateInput(InputOpcode.SENSING_TIMER_GET, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO); case 'sensing_touchingcolor': - return { - kind: 'sensing.touchingColor', - color: this.descendInputOfBlock(block, 'COLOR') - }; + return new IntermediateInput(InputOpcode.SENSING_TOUCHING_COLOR, InputType.BOOLEAN, { + color: this.descendInputOfBlock(block, 'COLOR').toType(InputType.NUMBER) + }); case 'sensing_touchingobject': - return { - kind: 'sensing.touching', + return new IntermediateInput(InputOpcode.SENSING_TOUCHING_OBJECT, InputType.BOOLEAN, { object: this.descendInputOfBlock(block, 'TOUCHINGOBJECTMENU') - }; + }); case 'sensing_username': - return { - kind: 'sensing.username' - }; + return new IntermediateInput(InputOpcode.SENSING_USERNAME, InputType.STRING); case 'sound_sounds_menu': // This menu is special compared to other menus -- it actually has an opcode function. - return { - kind: 'constant', - value: block.fields.SOUND_MENU.value - }; + return this.createConstantInput(block.fields.SOUND_MENU.value); + + case 'control_get_counter': + return new IntermediateInput(InputOpcode.CONTROL_COUNTER, InputType.NUMBER_POS_INT | InputType.NUMBER_ZERO); case 'tw_getLastKeyPressed': - return { - kind: 'tw.lastKeyPressed' - }; + return new IntermediateInput(InputOpcode.TW_KEY_LAST_PRESSED, InputType.STRING); default: { const opcodeFunction = this.runtime.getOpcodeFunction(block.opcode); if (opcodeFunction) { // It might be a non-compiled primitive from a standard category if (compatBlocks.inputs.includes(block.opcode)) { - return this.descendCompatLayer(block); + return this.descendCompatLayerInput(block); } // It might be an extension block. const blockInfo = this.getBlockInfo(block.opcode); if (blockInfo) { const type = blockInfo.info.blockType; if (type === BlockType.REPORTER || type === BlockType.BOOLEAN) { - return this.descendCompatLayer(block); + return this.descendCompatLayerInput(block); } } } @@ -674,10 +572,7 @@ class ScriptTreeGenerator { const inputs = Object.keys(block.inputs); const fields = Object.keys(block.fields); if (inputs.length === 0 && fields.length === 1) { - return { - kind: 'constant', - value: block.fields[fields[0]].value - }; + return this.createConstantInput(block.fields[fields[0]].value); } log.warn(`IR: Unknown input: ${block.opcode}`, block); @@ -690,477 +585,356 @@ class ScriptTreeGenerator { * Descend into a stacked block. (eg. "move ( ) steps") * @param {*} block The Scratch block to parse. * @private - * @returns {Node} Compiled node for this block. + * @returns {IntermediateStackBlock} Compiled node for this block. */ descendStackedBlock (block) { switch (block.opcode) { case 'control_all_at_once': // In Scratch 3, this block behaves like "if 1 = 1" - return { - kind: 'control.if', - condition: { - kind: 'constant', - value: true - }, + return new IntermediateStackBlock(StackOpcode.CONTROL_IF_ELSE, { + condition: this.createConstantInput(true).toType(InputType.BOOLEAN), whenTrue: this.descendSubstack(block, 'SUBSTACK'), - whenFalse: [] - }; - case 'control_clear_counter': - return { - kind: 'counter.clear' - }; + whenFalse: new IntermediateStack() + }); case 'control_create_clone_of': - return { - kind: 'control.createClone', - target: this.descendInputOfBlock(block, 'CLONE_OPTION') - }; + return new IntermediateStackBlock(StackOpcode.CONTROL_CLONE_CREATE, { + target: this.descendInputOfBlock(block, 'CLONE_OPTION').toType(InputType.STRING) + }); case 'control_delete_this_clone': - this.script.yields = true; - return { - kind: 'control.deleteClone' - }; + return new IntermediateStackBlock(StackOpcode.CONTROL_CLONE_DELETE, {}, true); case 'control_forever': - this.analyzeLoop(); - return { - kind: 'control.while', - condition: { - kind: 'constant', - value: true - }, + return new IntermediateStackBlock(StackOpcode.CONTROL_WHILE, { + condition: this.createConstantInput(true).toType(InputType.BOOLEAN), do: this.descendSubstack(block, 'SUBSTACK') - }; + }, this.analyzeLoop()); case 'control_for_each': - this.analyzeLoop(); - return { - kind: 'control.for', + return new IntermediateStackBlock(StackOpcode.CONTROL_FOR, { variable: this.descendVariable(block, 'VARIABLE', SCALAR_TYPE), - count: this.descendInputOfBlock(block, 'VALUE'), + count: this.descendInputOfBlock(block, 'VALUE').toType(InputType.NUMBER), do: this.descendSubstack(block, 'SUBSTACK') - }; + }, this.analyzeLoop()); case 'control_if': - return { - kind: 'control.if', - condition: this.descendInputOfBlock(block, 'CONDITION'), + return new IntermediateStackBlock(StackOpcode.CONTROL_IF_ELSE, { + condition: this.descendInputOfBlock(block, 'CONDITION').toType(InputType.BOOLEAN), whenTrue: this.descendSubstack(block, 'SUBSTACK'), - whenFalse: [] - }; + whenFalse: new IntermediateStack() + }); case 'control_if_else': - return { - kind: 'control.if', - condition: this.descendInputOfBlock(block, 'CONDITION'), + return new IntermediateStackBlock(StackOpcode.CONTROL_IF_ELSE, { + condition: this.descendInputOfBlock(block, 'CONDITION').toType(InputType.BOOLEAN), whenTrue: this.descendSubstack(block, 'SUBSTACK'), whenFalse: this.descendSubstack(block, 'SUBSTACK2') - }; - case 'control_incr_counter': - return { - kind: 'counter.increment' - }; + }); case 'control_repeat': - this.analyzeLoop(); - return { - kind: 'control.repeat', - times: this.descendInputOfBlock(block, 'TIMES'), + return new IntermediateStackBlock(StackOpcode.CONTROL_REPEAT, { + times: this.descendInputOfBlock(block, 'TIMES').toType(InputType.NUMBER), do: this.descendSubstack(block, 'SUBSTACK') - }; + }, this.analyzeLoop()); case 'control_repeat_until': { - this.analyzeLoop(); // Dirty hack: automatically enable warp timer for this block if it uses timer // This fixes project that do things like "repeat until timer > 0.5" this.usesTimer = false; const condition = this.descendInputOfBlock(block, 'CONDITION'); const needsWarpTimer = this.usesTimer; - if (needsWarpTimer) { - this.script.yields = true; - } - return { - kind: 'control.while', - condition: { - kind: 'op.not', + return new IntermediateStackBlock(StackOpcode.CONTROL_WHILE, { + condition: new IntermediateInput(InputOpcode.OP_NOT, InputType.BOOLEAN, { operand: condition - }, + }), do: this.descendSubstack(block, 'SUBSTACK'), warpTimer: needsWarpTimer - }; + }, this.analyzeLoop() || needsWarpTimer); } case 'control_stop': { const level = block.fields.STOP_OPTION.value; if (level === 'all') { - this.script.yields = true; - return { - kind: 'control.stopAll' - }; + return new IntermediateStackBlock(StackOpcode.CONTROL_STOP_ALL, {}, true); } else if (level === 'other scripts in sprite' || level === 'other scripts in stage') { - return { - kind: 'control.stopOthers' - }; + return new IntermediateStackBlock(StackOpcode.CONTROL_STOP_OTHERS); } else if (level === 'this script') { - return { - kind: 'control.stopScript' - }; + return new IntermediateStackBlock(StackOpcode.CONTROL_STOP_SCRIPT); } - return { - kind: 'noop' - }; + return new IntermediateStackBlock(StackOpcode.NOP); } case 'control_wait': - this.script.yields = true; - return { - kind: 'control.wait', - seconds: this.descendInputOfBlock(block, 'DURATION') - }; + return new IntermediateStackBlock(StackOpcode.CONTROL_WAIT, { + seconds: this.descendInputOfBlock(block, 'DURATION').toType(InputType.NUMBER) + }, true); case 'control_wait_until': - this.script.yields = true; - return { - kind: 'control.waitUntil', - condition: this.descendInputOfBlock(block, 'CONDITION') - }; + return new IntermediateStackBlock(StackOpcode.CONTROL_WAIT_UNTIL, { + condition: this.descendInputOfBlock(block, 'CONDITION').toType(InputType.BOOLEAN) + }, true); case 'control_while': - this.analyzeLoop(); - return { - kind: 'control.while', - condition: this.descendInputOfBlock(block, 'CONDITION'), + return new IntermediateStackBlock(StackOpcode.CONTROL_WHILE, { + condition: this.descendInputOfBlock(block, 'CONDITION').toType(InputType.BOOLEAN), do: this.descendSubstack(block, 'SUBSTACK'), // We should consider analyzing this like we do for control_repeat_until warpTimer: false - }; + }, this.analyzeLoop()); + case 'control_clear_counter': + return new IntermediateStackBlock(StackOpcode.CONTROL_CLEAR_COUNTER); + case 'control_incr_counter': + return new IntermediateStackBlock(StackOpcode.CONTORL_INCR_COUNTER); case 'data_addtolist': - return { - kind: 'list.add', + return new IntermediateStackBlock(StackOpcode.LIST_ADD, { list: this.descendVariable(block, 'LIST', LIST_TYPE), - item: this.descendInputOfBlock(block, 'ITEM') - }; + item: this.descendInputOfBlock(block, 'ITEM', true) + }); case 'data_changevariableby': { const variable = this.descendVariable(block, 'VARIABLE', SCALAR_TYPE); - return { - kind: 'var.set', + return new IntermediateStackBlock(StackOpcode.VAR_SET, { variable, - value: { - kind: 'op.add', - left: { - kind: 'var.get', - variable - }, - right: this.descendInputOfBlock(block, 'VALUE') - } - }; + value: new IntermediateInput(InputOpcode.OP_ADD, InputType.NUMBER_OR_NAN, { + left: new IntermediateInput(InputOpcode.VAR_GET, InputType.ANY, {variable}).toType(InputType.NUMBER), + right: this.descendInputOfBlock(block, 'VALUE').toType(InputType.NUMBER) + }) + }); } case 'data_deletealloflist': - return { - kind: 'list.deleteAll', + return new IntermediateStackBlock(StackOpcode.LIST_DELETE_ALL, { list: this.descendVariable(block, 'LIST', LIST_TYPE) - }; + }); case 'data_deleteoflist': { const index = this.descendInputOfBlock(block, 'INDEX'); - if (index.kind === 'constant' && index.value === 'all') { - return { - kind: 'list.deleteAll', + if (index.isConstant('all')) { + return new IntermediateStackBlock(StackOpcode.LIST_DELETE_ALL, { list: this.descendVariable(block, 'LIST', LIST_TYPE) - }; + }); } - return { - kind: 'list.delete', + return new IntermediateStackBlock(StackOpcode.LIST_DELETE, { list: this.descendVariable(block, 'LIST', LIST_TYPE), index: index - }; + }); } case 'data_hidelist': - return { - kind: 'list.hide', + return new IntermediateStackBlock(StackOpcode.LIST_HIDE, { list: this.descendVariable(block, 'LIST', LIST_TYPE) - }; + }); case 'data_hidevariable': - return { - kind: 'var.hide', + return new IntermediateStackBlock(StackOpcode.VAR_HIDE, { variable: this.descendVariable(block, 'VARIABLE', SCALAR_TYPE) - }; + }); case 'data_insertatlist': - return { - kind: 'list.insert', + return new IntermediateStackBlock(StackOpcode.LIST_INSERT, { list: this.descendVariable(block, 'LIST', LIST_TYPE), index: this.descendInputOfBlock(block, 'INDEX'), - item: this.descendInputOfBlock(block, 'ITEM') - }; + item: this.descendInputOfBlock(block, 'ITEM', true) + }); case 'data_replaceitemoflist': - return { - kind: 'list.replace', + return new IntermediateStackBlock(StackOpcode.LIST_REPLACE, { list: this.descendVariable(block, 'LIST', LIST_TYPE), index: this.descendInputOfBlock(block, 'INDEX'), - item: this.descendInputOfBlock(block, 'ITEM') - }; + item: this.descendInputOfBlock(block, 'ITEM', true) + }); case 'data_setvariableto': - return { - kind: 'var.set', + return new IntermediateStackBlock(StackOpcode.VAR_SET, { variable: this.descendVariable(block, 'VARIABLE', SCALAR_TYPE), - value: this.descendInputOfBlock(block, 'VALUE') - }; + value: this.descendInputOfBlock(block, 'VALUE', true) + }); case 'data_showlist': - return { - kind: 'list.show', + return new IntermediateStackBlock(StackOpcode.LIST_SHOW, { list: this.descendVariable(block, 'LIST', LIST_TYPE) - }; + }); case 'data_showvariable': - return { - kind: 'var.show', + return new IntermediateStackBlock(StackOpcode.VAR_SHOW, { variable: this.descendVariable(block, 'VARIABLE', SCALAR_TYPE) - }; + }); case 'event_broadcast': - return { - kind: 'event.broadcast', - broadcast: this.descendInputOfBlock(block, 'BROADCAST_INPUT') - }; + return new IntermediateStackBlock(StackOpcode.EVENT_BROADCAST, { + broadcast: this.descendInputOfBlock(block, 'BROADCAST_INPUT').toType(InputType.STRING) + }); case 'event_broadcastandwait': - this.script.yields = true; - return { - kind: 'event.broadcastAndWait', - broadcast: this.descendInputOfBlock(block, 'BROADCAST_INPUT') - }; + return new IntermediateStackBlock(StackOpcode.EVENT_BROADCAST_AND_WAIT, { + broadcast: this.descendInputOfBlock(block, 'BROADCAST_INPUT').toType(InputType.STRING) + }, true); case 'looks_changeeffectby': - return { - kind: 'looks.changeEffect', + return new IntermediateStackBlock(StackOpcode.LOOKS_EFFECT_CHANGE, { effect: block.fields.EFFECT.value.toLowerCase(), - value: this.descendInputOfBlock(block, 'CHANGE') - }; + value: this.descendInputOfBlock(block, 'CHANGE').toType(InputType.NUMBER) + }); case 'looks_changesizeby': - return { - kind: 'looks.changeSize', - size: this.descendInputOfBlock(block, 'CHANGE') - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_SIZE_CHANGE, { + size: this.descendInputOfBlock(block, 'CHANGE').toType(InputType.NUMBER) + }); case 'looks_cleargraphiceffects': - return { - kind: 'looks.clearEffects' - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_EFFECT_CLEAR); case 'looks_goforwardbackwardlayers': if (block.fields.FORWARD_BACKWARD.value === 'forward') { - return { - kind: 'looks.forwardLayers', - layers: this.descendInputOfBlock(block, 'NUM') - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_LAYER_FORWARD, { + layers: this.descendInputOfBlock(block, 'NUM').toType(InputType.NUMBER) + }); } - return { - kind: 'looks.backwardLayers', - layers: this.descendInputOfBlock(block, 'NUM') - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_LAYER_BACKWARD, { + layers: this.descendInputOfBlock(block, 'NUM').toType(InputType.NUMBER) + }); case 'looks_gotofrontback': if (block.fields.FRONT_BACK.value === 'front') { - return { - kind: 'looks.goToFront' - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_LAYER_FRONT); } - return { - kind: 'looks.goToBack' - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_LAYER_BACK); case 'looks_hide': - return { - kind: 'looks.hide' - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_HIDE); case 'looks_nextbackdrop': - return { - kind: 'looks.nextBackdrop' - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_BACKDROP_NEXT); case 'looks_nextcostume': - return { - kind: 'looks.nextCostume' - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_COSTUME_NEXT); case 'looks_seteffectto': - return { - kind: 'looks.setEffect', + return new IntermediateStackBlock(StackOpcode.LOOKS_EFFECT_SET, { effect: block.fields.EFFECT.value.toLowerCase(), - value: this.descendInputOfBlock(block, 'VALUE') - }; + value: this.descendInputOfBlock(block, 'VALUE').toType(InputType.NUMBER) + }); case 'looks_setsizeto': - return { - kind: 'looks.setSize', - size: this.descendInputOfBlock(block, 'SIZE') - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_SIZE_SET, { + size: this.descendInputOfBlock(block, 'SIZE').toType(InputType.NUMBER) + }); case 'looks_show': - return { - kind: 'looks.show' - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_SHOW); case 'looks_switchbackdropto': - return { - kind: 'looks.switchBackdrop', - backdrop: this.descendInputOfBlock(block, 'BACKDROP') - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_BACKDROP_SET, { + backdrop: this.descendInputOfBlock(block, 'BACKDROP', true) + }); case 'looks_switchcostumeto': - return { - kind: 'looks.switchCostume', - costume: this.descendInputOfBlock(block, 'COSTUME') - }; + return new IntermediateStackBlock(StackOpcode.LOOKS_COSTUME_SET, { + costume: this.descendInputOfBlock(block, 'COSTUME', true) + }); case 'motion_changexby': - return { - kind: 'motion.changeX', - dx: this.descendInputOfBlock(block, 'DX') - }; + return new IntermediateStackBlock(StackOpcode.MOTION_X_CHANGE, { + dx: this.descendInputOfBlock(block, 'DX').toType(InputType.NUMBER) + }); case 'motion_changeyby': - return { - kind: 'motion.changeY', - dy: this.descendInputOfBlock(block, 'DY') - }; + return new IntermediateStackBlock(StackOpcode.MOTION_Y_CHANGE, { + dy: this.descendInputOfBlock(block, 'DY').toType(InputType.NUMBER) + }); case 'motion_gotoxy': - return { - kind: 'motion.setXY', - x: this.descendInputOfBlock(block, 'X'), - y: this.descendInputOfBlock(block, 'Y') - }; + return new IntermediateStackBlock(StackOpcode.MOTION_XY_SET, { + x: this.descendInputOfBlock(block, 'X').toType(InputType.NUMBER), + y: this.descendInputOfBlock(block, 'Y').toType(InputType.NUMBER) + }); case 'motion_ifonedgebounce': - return { - kind: 'motion.ifOnEdgeBounce' - }; + return new IntermediateStackBlock(StackOpcode.MOTION_IF_ON_EDGE_BOUNCE); case 'motion_movesteps': - return { - kind: 'motion.step', - steps: this.descendInputOfBlock(block, 'STEPS') - }; + return new IntermediateStackBlock(StackOpcode.MOTION_STEP, { + steps: this.descendInputOfBlock(block, 'STEPS').toType(InputType.NUMBER) + }); case 'motion_pointindirection': - return { - kind: 'motion.setDirection', - direction: this.descendInputOfBlock(block, 'DIRECTION') - }; + return new IntermediateStackBlock(StackOpcode.MOTION_DIRECTION_SET, { + direction: this.descendInputOfBlock(block, 'DIRECTION').toType(InputType.NUMBER) + }); case 'motion_setrotationstyle': - return { - kind: 'motion.setRotationStyle', + return new IntermediateStackBlock(StackOpcode.MOTION_ROTATION_STYLE_SET, { style: block.fields.STYLE.value - }; + }); case 'motion_setx': - return { - kind: 'motion.setX', - x: this.descendInputOfBlock(block, 'X') - }; + return new IntermediateStackBlock(StackOpcode.MOTION_X_SET, { + x: this.descendInputOfBlock(block, 'X').toType(InputType.NUMBER) + }); case 'motion_sety': - return { - kind: 'motion.setY', - y: this.descendInputOfBlock(block, 'Y') - }; + return new IntermediateStackBlock(StackOpcode.MOTION_Y_SET, { + y: this.descendInputOfBlock(block, 'Y').toType(InputType.NUMBER) + }); case 'motion_turnleft': - return { - kind: 'motion.setDirection', - direction: { - kind: 'op.subtract', - left: { - kind: 'motion.direction' - }, + return new IntermediateStackBlock(StackOpcode.MOTION_DIRECTION_SET, { + direction: new IntermediateInput(InputOpcode.OP_SUBTRACT, InputType.NUMBER, { + left: new IntermediateInput(InputOpcode.MOTION_DIRECTION_GET, InputType.NUMBER), right: this.descendInputOfBlock(block, 'DEGREES') - } - }; + }) + }); case 'motion_turnright': - return { - kind: 'motion.setDirection', - direction: { - kind: 'op.add', - left: { - kind: 'motion.direction' - }, + return new IntermediateStackBlock(StackOpcode.MOTION_DIRECTION_SET, { + direction: new IntermediateInput(InputOpcode.OP_ADD, InputType.NUMBER, { + left: new IntermediateInput(InputOpcode.MOTION_DIRECTION_GET, InputType.NUMBER), right: this.descendInputOfBlock(block, 'DEGREES') - } - }; + }) + }); case 'pen_clear': - return { - kind: 'pen.clear' - }; + return new IntermediateStackBlock(StackOpcode.PEN_CLEAR); case 'pen_changePenColorParamBy': - return { - kind: 'pen.changeParam', - param: this.descendInputOfBlock(block, 'COLOR_PARAM'), - value: this.descendInputOfBlock(block, 'VALUE') - }; + return new IntermediateStackBlock(StackOpcode.PEN_COLOR_PARAM_CHANGE, { + param: this.descendInputOfBlock(block, 'COLOR_PARAM').toType(InputType.STRING), + value: this.descendInputOfBlock(block, 'VALUE').toType(InputType.NUMBER) + }); case 'pen_changePenHueBy': - return { - kind: 'pen.legacyChangeHue', - hue: this.descendInputOfBlock(block, 'HUE') - }; + return new IntermediateStackBlock(StackOpcode.PEN_COLOR_HUE_CHANGE_LEGACY, { + hue: this.descendInputOfBlock(block, 'HUE').toType(InputType.NUMBER) + }); case 'pen_changePenShadeBy': - return { - kind: 'pen.legacyChangeShade', - shade: this.descendInputOfBlock(block, 'SHADE') - }; + return new IntermediateStackBlock(StackOpcode.PEN_COLOR_SHADE_CHANGE_LEGACY, { + shade: this.descendInputOfBlock(block, 'SHADE').toType(InputType.NUMBER) + }); case 'pen_penDown': - return { - kind: 'pen.down' - }; + return new IntermediateStackBlock(StackOpcode.PEN_DOWN); case 'pen_penUp': - return { - kind: 'pen.up' - }; + return new IntermediateStackBlock(StackOpcode.PEN_UP); case 'pen_setPenColorParamTo': - return { - kind: 'pen.setParam', - param: this.descendInputOfBlock(block, 'COLOR_PARAM'), - value: this.descendInputOfBlock(block, 'VALUE') - }; + return new IntermediateStackBlock(StackOpcode.PEN_COLOR_PARAM_SET, { + param: this.descendInputOfBlock(block, 'COLOR_PARAM').toType(InputType.STRING), + value: this.descendInputOfBlock(block, 'VALUE').toType(InputType.NUMBER) + }); case 'pen_setPenColorToColor': - return { - kind: 'pen.setColor', + return new IntermediateStackBlock(StackOpcode.PEN_COLOR_SET, { color: this.descendInputOfBlock(block, 'COLOR') - }; + }); case 'pen_setPenHueToNumber': - return { - kind: 'pen.legacySetHue', - hue: this.descendInputOfBlock(block, 'HUE') - }; + return new IntermediateStackBlock(StackOpcode.PEN_COLOR_HUE_SET_LEGACY, { + hue: this.descendInputOfBlock(block, 'HUE').toType(InputType.NUMBER) + }); case 'pen_setPenShadeToNumber': - return { - kind: 'pen.legacySetShade', - shade: this.descendInputOfBlock(block, 'SHADE') - }; + return new IntermediateStackBlock(StackOpcode.PEN_COLOR_SHADE_SET_LEGACY, { + shade: this.descendInputOfBlock(block, 'SHADE').toType(InputType.NUMBER) + }); case 'pen_setPenSizeTo': - return { - kind: 'pen.setSize', - size: this.descendInputOfBlock(block, 'SIZE') - }; + return new IntermediateStackBlock(StackOpcode.PEN_SIZE_SET, { + size: this.descendInputOfBlock(block, 'SIZE').toType(InputType.NUMBER) + }); case 'pen_changePenSizeBy': - return { - kind: 'pen.changeSize', - size: this.descendInputOfBlock(block, 'SIZE') - }; + return new IntermediateStackBlock(StackOpcode.PEN_SIZE_CHANGE, { + size: this.descendInputOfBlock(block, 'SIZE').toType(InputType.NUMBER) + }); case 'pen_stamp': - return { - kind: 'pen.stamp' - }; + return new IntermediateStackBlock(StackOpcode.PEN_STAMP); case 'procedures_call': { const procedureCode = block.mutation.proccode; + if (block.mutation.return) { const visualReport = this.descendVisualReport(block); if (visualReport) { return visualReport; } } + if (procedureCode === 'tw:debugger;') { - return { - kind: 'tw.debugger' - }; + return new IntermediateStackBlock(StackOpcode.DEBUGGER); } - return this.descendProcedure(block); + + const procedure = this.getProcedureInfo(block); + return new IntermediateStackBlock(procedure.opcode, procedure.inputs, procedure.yields); } case 'procedures_return': - return { - kind: 'procedures.return', + return new IntermediateStackBlock(StackOpcode.PROCEDURE_RETURN, { value: this.descendInputOfBlock(block, 'VALUE') - }; + }); case 'sensing_resettimer': - return { - kind: 'timer.reset' - }; + return new IntermediateStackBlock(StackOpcode.SENSING_TIMER_RESET); default: { const opcodeFunction = this.runtime.getOpcodeFunction(block.opcode); if (opcodeFunction) { // It might be a non-compiled primitive from a standard category if (compatBlocks.stacked.includes(block.opcode)) { - return this.descendCompatLayer(block); + return this.descendCompatLayerStack(block); } // It might be an extension block. const blockInfo = this.getBlockInfo(block.opcode); if (blockInfo) { const type = blockInfo.info.blockType; if (type === BlockType.COMMAND || type === BlockType.CONDITIONAL || type === BlockType.LOOP) { - return this.descendCompatLayer(block); + return this.descendCompatLayerStack(block); } } } @@ -1179,14 +953,14 @@ class ScriptTreeGenerator { /** * Descend into a stack of blocks (eg. the blocks contained within an "if" block) * @param {*} parentBlock The parent Scratch block that contains the stack to parse. - * @param {*} substackName The name of the stack to descend into. + * @param {string} substackName The name of the stack to descend into. * @private - * @returns {Node[]} List of stacked block nodes. + * @returns {IntermediateStack} Stacked blocks. */ descendSubstack (parentBlock, substackName) { const input = parentBlock.inputs[substackName]; if (!input) { - return []; + return new IntermediateStack(); } const stackId = input.block; return this.walkStack(stackId); @@ -1196,10 +970,10 @@ class ScriptTreeGenerator { * Descend into and walk the siblings of a stack. * @param {string} startingBlockId The ID of the first block of a stack. * @private - * @returns {Node[]} List of stacked block nodes. + * @returns {IntermediateStack} List of stacked block nodes. */ walkStack (startingBlockId) { - const result = []; + const result = new IntermediateStack(); let blockId = startingBlockId; while (blockId !== null) { @@ -1209,7 +983,8 @@ class ScriptTreeGenerator { } const node = this.descendStackedBlock(block); - result.push(node); + this.script.yields = this.script.yields || node.yields; + result.blocks.push(node); blockId = block.next; } @@ -1217,6 +992,112 @@ class ScriptTreeGenerator { return result; } + /** + * @param {*} block + * @returns {{ + * opcode: StackOpcode & InputOpcode, + * inputs?: *, + * yields: boolean + * }} + */ + getProcedureInfo (block) { + const procedureCode = block.mutation.proccode; + const paramNamesIdsAndDefaults = this.blocks.getProcedureParamNamesIdsAndDefaults(procedureCode); + + if (paramNamesIdsAndDefaults === null) { + return {opcode: StackOpcode.NOP, yields: false}; + } + + const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults; + + const addonBlock = this.runtime.getAddonBlock(procedureCode); + if (addonBlock) { + 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], true); + } else { + value = this.createConstantInput(paramDefaults[i], true); + } + args[paramNames[i]] = value; + } + + return { + opcode: StackOpcode.ADDON_CALL, + inputs: { + code: procedureCode, + arguments: args, + blockId: block.id + }, + yields: true + }; + } + + const definitionId = this.blocks.getProcedureDefinition(procedureCode); + const definitionBlock = this.blocks.getBlock(definitionId); + if (!definitionBlock) { + return {opcode: StackOpcode.NOP, yields: false}; + } + 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); + } + + 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], true); + } else { + value = this.createConstantInput(paramDefaults[i], true); + } + args.push(value); + } + + return { + opcode: StackOpcode.PROCEDURE_CALL, + inputs: { + code: procedureCode, + variant, + arguments: args + }, + yields: !this.script.isWarp && procedureCode === this.script.procedureCode + }; + } + + /** + * @param {*} block + * @returns {IntermediateStackBlock | null} + */ + descendVisualReport (block) { + if (!this.thread.stackClick || block.next) { + return null; + } + try { + return new IntermediateStackBlock(StackOpcode.VISUAL_REPORT, { + input: this.descendInput(block) + }); + } catch (e) { + return null; + } + } + /** * Descend into a variable. * @param {*} block The block that has the variable. @@ -1229,7 +1110,7 @@ class ScriptTreeGenerator { const variable = block.fields[fieldName]; const id = variable.id; - if (Object.prototype.hasOwnProperty.call(this.variableCache, id)) { + if (this.variableCache.hasOwnProperty(id)) { return this.variableCache[id]; } @@ -1250,20 +1131,20 @@ class ScriptTreeGenerator { const stage = this.stage; // Look for by ID in target... - if (Object.prototype.hasOwnProperty.call(target.variables, id)) { + if (target.variables.hasOwnProperty(id)) { return createVariableData('target', target.variables[id]); } // Look for by ID in stage... if (!target.isStage) { - if (stage && Object.prototype.hasOwnProperty.call(stage.variables, id)) { + if (stage && stage.variables.hasOwnProperty(id)) { return createVariableData('stage', stage.variables[id]); } } // Look for by name and type in target... for (const varId in target.variables) { - if (Object.prototype.hasOwnProperty.call(target.variables, varId)) { + if (target.variables.hasOwnProperty(varId)) { const currVar = target.variables[varId]; if (currVar.name === name && currVar.type === type) { return createVariableData('target', currVar); @@ -1274,7 +1155,7 @@ class ScriptTreeGenerator { // Look for by name and type in stage... if (!target.isStage && stage) { for (const varId in stage.variables) { - if (Object.prototype.hasOwnProperty.call(stage.variables, varId)) { + if (stage.variables.hasOwnProperty(varId)) { const currVar = stage.variables[varId]; if (currVar.name === name && currVar.type === type) { return createVariableData('stage', currVar); @@ -1292,7 +1173,7 @@ class ScriptTreeGenerator { // This is necessary because the script cache is shared between clones. // sprite.clones has all instances of this sprite including the original and all clones for (const clone of target.sprite.clones) { - if (!Object.prototype.hasOwnProperty.call(clone.variables, id)) { + if (!clone.variables.hasOwnProperty(id)) { clone.variables[id] = new Variable(id, name, type, false); } } @@ -1301,110 +1182,40 @@ 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; - } + /** + * Descend into an input block that uses the compatibility layer. + * @param {*} block The block to use the compatibility layer for. + * @private + * @returns {IntermediateInput} The parsed node. + */ + descendCompatLayerInput (block) { + const inputs = {}; + const fields = {}; + for (const name of Object.keys(block.inputs)) { + inputs[name] = this.descendInputOfBlock(block, name, 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); + for (const name of Object.keys(block.fields)) { + fields[name] = block.fields[name].value; } - - return { - kind: 'procedures.call', - code: procedureCode, - variant, - arguments: args - }; + return new IntermediateInput(InputOpcode.COMPATIBILITY_LAYER, InputType.ANY, { + opcode: block.opcode, + id: block.id, + inputs, + fields + }, true); } /** - * Descend into a block that uses the compatibility layer. + * Descend into a stack block that uses the compatibility layer. * @param {*} block The block to use the compatibility layer for. * @private - * @returns {Node} The parsed node. + * @returns {IntermediateStackBlock} The parsed node. */ - descendCompatLayer (block) { - this.script.yields = true; - + descendCompatLayerStack (block) { const inputs = {}; for (const name of Object.keys(block.inputs)) { if (!name.startsWith('SUBSTACK')) { - inputs[name] = this.descendInputOfBlock(block, name); + inputs[name] = this.descendInputOfBlock(block, name, true); } } @@ -1415,32 +1226,27 @@ class ScriptTreeGenerator { const blockInfo = this.getBlockInfo(block.opcode); const blockType = (blockInfo && blockInfo.info && blockInfo.info.blockType) || BlockType.COMMAND; - const substacks = {}; + const substacks = []; if (blockType === BlockType.CONDITIONAL || blockType === BlockType.LOOP) { - for (const inputName in block.inputs) { - if (!inputName.startsWith('SUBSTACK')) continue; - const branchNum = inputName === 'SUBSTACK' ? 1 : +inputName.substring('SUBSTACK'.length); - if (!isNaN(branchNum)) { - substacks[branchNum] = this.descendSubstack(block, inputName); - } + const branchCount = blockInfo.info.branchCount; + for (let i = 0; i < branchCount; i++) { + const inputName = i === 0 ? 'SUBSTACK' : `SUBSTACK${i + 1}`; + substacks.push(this.descendSubstack(block, inputName)); } } - return { - kind: 'compat', - id: block.id, + return new IntermediateStackBlock(StackOpcode.COMPATIBILITY_LAYER, { opcode: block.opcode, + id: block.id, blockType, inputs, fields, substacks - }; + }, true); } analyzeLoop () { - if (!this.script.isWarp || this.script.warpTimer) { - this.script.yields = true; - } + return !this.script.isWarp || this.script.warpTimer; } readTopBlockComment (commentId) { @@ -1474,22 +1280,9 @@ 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 {Block} hatBlock + * @param {*} hatBlock + * @returns {IntermediateStack} */ walkHat (hatBlock) { const nextBlock = hatBlock.next; @@ -1501,10 +1294,10 @@ class ScriptTreeGenerator { // interpreter parity, but the reuslt is ignored. const opcodeFunction = this.runtime.getOpcodeFunction(opcode); if (opcodeFunction) { - return [ - this.descendCompatLayer(hatBlock), - ...this.walkStack(nextBlock) - ]; + return new IntermediateStack([ + this.descendCompatLayerStack(hatBlock), + ...this.walkStack(nextBlock).blocks + ]); } return this.walkStack(nextBlock); } @@ -1513,14 +1306,13 @@ class ScriptTreeGenerator { // Edge-activated HAT this.script.yields = true; this.script.executableHat = true; - return [ - { - kind: 'hat.edge', + return new IntermediateStack([ + new IntermediateStackBlock(StackOpcode.HAT_EDGE, { id: hatBlock.id, - condition: this.descendCompatLayer(hatBlock) - }, - ...this.walkStack(nextBlock) - ]; + condition: this.descendCompatLayerInput(hatBlock).toType(InputType.BOOLEAN) + }), + ...this.walkStack(nextBlock).blocks + ]); } const opcodeFunction = this.runtime.getOpcodeFunction(opcode); @@ -1528,13 +1320,12 @@ class ScriptTreeGenerator { // Predicate-based HAT this.script.yields = true; this.script.executableHat = true; - return [ - { - kind: 'hat.predicate', - condition: this.descendCompatLayer(hatBlock) - }, - ...this.walkStack(nextBlock) - ]; + return new IntermediateStack([ + new IntermediateStackBlock(StackOpcode.HAT_PREDICATE, { + condition: this.descendCompatLayerInput(hatBlock).toType(InputType.BOOLEAN) + }), + ...this.walkStack(nextBlock).blocks + ]); } return this.walkStack(nextBlock); @@ -1601,7 +1392,7 @@ class IRGenerator { addProcedureDependencies (dependencies) { for (const procedureVariant of dependencies) { - if (Object.prototype.hasOwnProperty.call(this.procedures, procedureVariant)) { + if (this.procedures.hasOwnProperty(procedureVariant)) { continue; } if (this.compilingProcedures.has(procedureVariant)) { @@ -1687,14 +1478,8 @@ class IRGenerator { // Analyze scripts until no changes are made. while (this.analyzeScript(entry)); - const ir = new IntermediateRepresentation(); - ir.entry = entry; - ir.procedures = this.procedures; - return ir; + return new IntermediateRepresentation(entry, this.procedures); } } -module.exports = { - ScriptTreeGenerator, - IRGenerator -}; +module.exports = IRGenerator; diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js new file mode 100644 index 00000000000..4e4ce7a5f9d --- /dev/null +++ b/src/compiler/iroptimizer.js @@ -0,0 +1,703 @@ +// @ts-check + +const {IntermediateStack, IntermediateInput, IntermediateScript, IntermediateRepresentation, IntermediateStackBlock} = require('./intermediate'); +const {StackOpcode, InputOpcode, InputType} = require('./enums.js'); + +class TypeState { + constructor () { + /** @type {Object.}*/ + this.variables = {}; + /** @type {InputType | 0} */ + this.defaultType = 0; + } + + /** + * @returns {boolean} + */ + clear () { + let modified = false; + for (const varId in this.variables) { + if (this.variables[varId] !== InputType.ANY) { + modified = true; + break; + } + } + this.variables = {}; + this.defaultType = InputType.ANY; + return modified; + } + + + /** + * @returns {TypeState} + */ + clone () { + const clone = new TypeState(); + for (const varId in this.variables) { + clone.variables[varId] = this.variables[varId]; + } + clone.defaultType = this.defaultType; + return clone; + } + + /** + * @param {TypeState} other + * @param {(varId: string) => InputType | 0} stateMutator + * @returns {boolean} + * @private + */ + mutate (other, stateMutator) { + let modified = false; + for (const varId in other.variables) { + const newValue = stateMutator(varId); + if (newValue !== this.variables[varId]) { + this.variables[varId] = newValue; + modified = modified || true; + } + } + + for (const varId in this.variables) { + if (!other.variables[varId]) { + const newValue = stateMutator(varId); + if (newValue !== this.variables[varId]) { + this.variables[varId] = newValue; + modified = modified || true; + } + } + } + return modified; + } + + /** + * @param {TypeState} other + * @returns {boolean} + */ + or (other) { + return this.mutate(other, varId => { + const thisType = this.variables[varId] ?? this.defaultType; + const otherType = other.variables[varId] ?? other.defaultType; + return thisType | otherType; + }); + } + + /** + * @param {TypeState} other + * @returns {boolean} + */ + after (other) { + return this.mutate(other, varId => { + const otherType = other.variables[varId] ?? other.defaultType; + if (otherType !== 0) return otherType; + return this.variables[varId] ?? this.defaultType; + }); + } + + /** + * @param {*} variable A variable codegen object. + * @param {InputType} type The type to set this variable to + * @returns {boolean} + */ + setVariableType (variable, type) { + if (this.getVariableType(variable) === type) return false; + this.variables[variable.id] = type; + return true; + } + + /** + * + * @param {*} variable A variable codegen object. + * @returns {InputType} + */ + getVariableType (variable) { + return this.variables[variable.id] ?? (this.defaultType === 0 ? InputType.ANY : this.defaultType); + } +} + +class IROptimizer { + + /** + * @param {IntermediateRepresentation} ir + */ + constructor (ir) { + /** @type {IntermediateRepresentation} */ + this.ir = ir; + /** @type {boolean} Used for testing */ + this.ignoreYields = false; + } + + /** + * @param {IntermediateInput} inputBlock + * @param {TypeState} state + * @returns {InputType} + */ + getInputType (inputBlock, state) { + const inputs = inputBlock.inputs; + + switch (inputBlock.opcode) { + case InputOpcode.VAR_GET: + return state.getVariableType(inputs.variable); + + case InputOpcode.ADDON_CALL: + + + case InputOpcode.CAST_NUMBER: { + const innerType = inputs.target.type; + if (innerType & InputType.NUMBER) return innerType; + return InputType.NUMBER; + } case InputOpcode.CAST_NUMBER_OR_NAN: { + const innerType = inputs.target; + if (innerType & InputType.NUMBER_OR_NAN) return innerType; + return InputType.NUMBER_OR_NAN; + } + + case InputOpcode.OP_ADD: { + const leftType = inputs.left.type; + const rightType = inputs.right.type; + + let resultType = 0; + + function canBeNaN () { + // Infinity + (-Infinity) = NaN + if ((leftType & InputType.NUMBER_POS_INF) && (rightType & InputType.NUMBER_NEG_INF)) return true; + // (-Infinity) + Infinity = NaN + if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_POS_INF)) return true; + } + if (canBeNaN()) resultType |= InputType.NUMBER_NAN; + + function canBeFractional () { + // For the plus operation to return a non-whole number one of it's + // inputs has to be a non-whole number + if (leftType & InputType.NUMBER_FRACT) return true; + if (rightType & InputType.NUMBER_FRACT) return true; + } + const canBeFract = canBeFractional(); + + function canBePos () { + if (leftType & InputType.NUMBER_POS) return true; // POS + ANY ~= POS + if (rightType & InputType.NUMBER_POS) return true; // ANY + POS ~= POS + } + if (canBePos()) { + resultType |= InputType.NUMBER_POS_INT | InputType.NUMBER_POS_INF; + if (canBeFract) resultType |= InputType.NUMBER_POS_FRACT; + } + + function canBeNeg () { + if (leftType & InputType.NUMBER_NEG) return true; // NEG + ANY ~= NEG + if (rightType & InputType.NUMBER_NEG) return true; // ANY + NEG ~= NEG + } + if (canBeNeg()) { + resultType |= InputType.NUMBER_NEG_INT | InputType.NUMBER_NEG_INF; + if (canBeFract) resultType |= InputType.NUMBER_NEG_FRACT; + } + + function canBeZero () { + // POS_REAL + NEG_REAL ~= 0 + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_NEG_REAL)) return true; + // NEG_REAL + POS_REAL ~= 0 + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_POS_REAL)) return true; + // 0 + 0 = 0 + if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_ZERO)) return true; + // 0 + -0 = 0 + if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // -0 + 0 = 0 + if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_ZERO)) return true; + } + if (canBeZero()) resultType |= InputType.NUMBER_ZERO; + + function canBeNegZero () { + // -0 + -0 = -0 + if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + } + if (canBeNegZero()) resultType |= InputType.NUMBER_NEG_ZERO; + + return resultType; + } + + case InputOpcode.OP_SUBTRACT: { + const leftType = inputs.left.type; + const rightType = inputs.right.type; + + let resultType = 0; + + function canBeNaN () { + // Infinity - Infinity = NaN + if ((leftType & InputType.NUMBER_POS_INF) && (rightType & InputType.NUMBER_POS_INF)) return true; + // (-Infinity) - (-Infinity) = NaN + if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_NEG_INF)) return true; + } + if (canBeNaN()) resultType |= InputType.NUMBER_NAN; + + function canBeFractional () { + // For the subtract operation to return a non-whole number one of it's + // inputs has to be a non-whole number + if (leftType & InputType.NUMBER_FRACT) return true; + if (rightType & InputType.NUMBER_FRACT) return true; + } + const canBeFract = canBeFractional(); + + function canBePos () { + if (leftType & InputType.NUMBER_POS) return true; // POS - ANY ~= POS + if (rightType & InputType.NUMBER_NEG) return true; // ANY - NEG ~= POS + } + if (canBePos()) { + resultType |= InputType.NUMBER_POS_INT | InputType.NUMBER_POS_INF; + if (canBeFract) resultType |= InputType.NUMBER_POS_FRACT; + } + + function canBeNeg () { + if (leftType & InputType.NUMBER_NEG) return true; // NEG - ANY ~= NEG + if (rightType & InputType.NUMBER_POS) return true; // ANY - POS ~= NEG + } + if (canBeNeg()) { + resultType |= InputType.NUMBER_NEG_INT | InputType.NUMBER_NEG_INF; + if (canBeFract) resultType |= InputType.NUMBER_NEG_FRACT; + } + + function canBeZero () { + // POS_REAL - POS_REAL ~= 0 + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_POS_REAL)) return true; + // NEG_REAL - NEG_REAL ~= 0 + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_NEG_REAL)) return true; + // 0 - 0 = 0 + if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_ZERO)) return true; + // 0 - (-0) = 0 + if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // (-0) - (-0) = 0 + if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + } + if (canBeZero()) resultType |= InputType.NUMBER_ZERO; + + function canBeNegZero () { + // (-0) - 0 = -0 + if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_ZERO)) return true; + } + if (canBeNegZero()) resultType |= InputType.NUMBER_NEG_ZERO; + + return resultType; + } + + case InputOpcode.OP_MULTIPLY: { + const leftType = inputs.left.type; + const rightType = inputs.right.type; + + let resultType = 0; + + function canBeNaN () { + // (-)Infinity * 0 = NaN + if ((leftType & InputType.NUMBER_INF) && (rightType & InputType.NUMBER_ANY_ZERO)) return true; + // 0 * (-)Infinity = NaN + if ((leftType & InputType.NUMBER_ANY_ZERO) && (rightType & InputType.NUMBER_INF)) return true; + } + if (canBeNaN()) resultType |= InputType.NUMBER_NAN; + + function canBeFractional () { + // For the subtract operation to return a non-whole number one of it's + // inputs has to be a non-whole number + if (leftType & InputType.NUMBER_FRACT) return true; + if (rightType & InputType.NUMBER_FRACT) return true; + } + const canBeFract = canBeFractional(); + + function canBePos () { + // POS * POS = POS + if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_POS)) return true; + // NEG * NEG = POS + if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_NEG)) return true; + } + if (canBePos()) { + resultType |= InputType.NUMBER_POS_INT | InputType.NUMBER_POS_INF; + if (canBeFract) resultType |= InputType.NUMBER_POS_FRACT; + } + + function canBeNeg () { + // POS * NEG = NEG + if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_NEG)) return true; + // NEG * POS = NEG + if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_POS)) return true; + } + if (canBeNeg()) { + resultType |= InputType.NUMBER_NEG_INT | InputType.NUMBER_NEG_INF; + if (canBeFract) resultType |= InputType.NUMBER_NEG_FRACT; + } + + function canBeZero () { + // 0 * 0 = 0 + if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_ZERO)) return true; + // -0 * -0 = 0 + if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // 0 * POS_REAL = 0 + if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_POS_REAL)) return true; + // -0 * NEG_REAL = 0 + if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_NEG_REAL)) return true; + // POS_REAL * 0 = 0 + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_ZERO)) return true; + // NEG_REAL * -0 = 0 + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // Rounding errors like 1e-323 * 0.1 = 0 + if ((leftType & InputType.NUMBER_FRACT) && (rightType & InputType.NUMBER_FRACT)) return true; + } + if (canBeZero()) resultType |= InputType.NUMBER_ZERO; + + function canBeNegZero () { + // 0 * -0 = 0 + if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // -0 * 0 = 0 + if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_ZERO)) return true; + // -0 * POS_REAL = -0 + if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_POS_REAL)) return true; + // 0 * NEG_REAL = -0 + if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_NEG_REAL)) return true; + // POS_REAL * -0 = -0 + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // NEG_REAL * 0 = -0 + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_ZERO)) return true; + // Rounding errors like -1e-323 / 10 = -0 + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_POS_REAL)) return true; + // Rounding errors like 1e-323 / -10 = -0 + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_NEG_REAL)) return true; + } + if (canBeNegZero()) resultType |= InputType.NUMBER_NEG_ZERO; + + return resultType; + } + + case InputOpcode.OP_DIVIDE: { + const leftType = inputs.left.type; + const rightType = inputs.right.type; + + let resultType = 0; + + function canBeNaN () { + // REAL / 0 = NaN + if ((leftType & InputType.NUMBER_REAL) && (rightType & InputType.NUMBER_ZERO)) return true; + // (-)Infinity / (-)Infinity = NaN + if ((leftType & InputType.NUMBER_INF) && (rightType & InputType.NUMBER_INF)) return true; + // (-)0 / NaN = NaN + if ((leftType & InputType.NUMBER_ANY_ZERO) && (rightType & InputType.NUMBER_NAN)) return true; + } + if (canBeNaN()) resultType |= InputType.NUMBER_NAN; + + function canBePos () { + // POS / POS = POS + if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_POS)) return true; + // NEG / NEG = POS + if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_NEG)) return true; + } + if (canBePos()) resultType |= InputType.NUMBER_POS; + + function canBeNegInfinity () { + // -Infinity / 0 = -Infinity + if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_ZERO)) return true; + // Infinity / -0 = -Infinity + if ((leftType & InputType.NUMBER_POS_INF) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // NEG_REAL / NaN = -Infinity + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_NAN)) return true; + // NEG_REAL / NUMBER_OR_NAN ~= -Infinity + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_OR_NAN)) return true; + } + if (canBeNegInfinity()) resultType |= InputType.NUMBER_NEG_INF; + + function canBeInfinity () { + // Infinity / 0 = Infinity + if ((leftType & InputType.NUMBER_POS_INF) && (rightType & InputType.NUMBER_ZERO)) return true; + // -Infinity / -0 = Infinity + if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // POS_REAL / NUMBER_OR_NAN ~= Infinity + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_OR_NAN)) return true; + } + if (canBeInfinity()) resultType |= InputType.NUMBER_POS_INF; + + function canBeNeg () { + // POS / NEG = NEG + if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_NEG)) return true; + // NEG / POS = NEG + if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_POS)) return true; + } + if (canBeNeg()) resultType |= InputType.NUMBER_NEG; + + function canBeZero () { + // 0 / POS = 0 + if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_POS)) return true; + // -0 / NEG = 0 + if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_NEG)) return true; + // Rounding errors like 1e-323 / 10 = 0 + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_POS_REAL)) return true; + // Rounding errors like -1e-323 / -10 = 0 + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_NEG_REAL)) return true; + // NUMBER_POS / Infinity = 0 + if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_POS_INF)) return true; + // NUMBER_NEG / -Infinity = 0 + if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_NEG_INF)) return true; + } + if (canBeZero()) resultType |= InputType.NUMBER_ZERO; + + function canBeNegZero () { + // -0 / POS = -0 + if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_POS)) return true; + // 0 / NEG = -0 + if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_NEG)) return true; + // Rounding errors like -1e-323 / 10 = -0 + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_POS_REAL)) return true; + // Rounding errors like 1e-323 / -10 = -0 + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_NEG_REAL)) return true; + // NUMBER_POS / -Infinity = -0 + if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_NEG_INF)) return true; + // NUMBER_NEG / Infinity = -0 + if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_POS_INF)) return true; + } + if (canBeNegZero()) resultType |= InputType.NUMBER_NEG_ZERO; + + return resultType; + } + } + return inputBlock.type; + } + + /** + * @param {IntermediateInput} inputBlock + * @param {TypeState} state + * @returns {boolean} + * @private + */ + analyzeInputBlock (inputBlock, state) { + const inputs = inputBlock.inputs; + + let modified = this.analyzeInputs(inputs, state); + const newType = this.getInputType(inputBlock, state); + + modified = modified || newType !== inputBlock.type; + inputBlock.type = newType; + + switch (inputBlock.opcode) { + case InputOpcode.ADDON_CALL: + modified = state.clear() || modified; + break; + case InputOpcode.PROCEDURE_CALL: + modified = this.analyzeInputs(inputs.inputs, state) || modified; + const script = this.ir.procedures[inputs.variant]; + + if (!script || !script.cachedAnalysisEndState) { + modified = state.clear() || modified; + } else { + modified = state.after(script.cachedAnalysisEndState) || modified; + } + break; + } + + return modified; + } + + /** + * @param {Object} inputs + * @param {TypeState} state + * @returns {boolean} modified + */ + analyzeInputs (inputs, state) { + let modified = false; + for (const inputName in inputs) { + const input = inputs[inputName]; + if (input instanceof IntermediateInput) { + modified = this.analyzeInputBlock(input, state) || modified; + } + } + return modified; + } + + /** + * @param {IntermediateStackBlock} stackBlock + * @param {TypeState} state + * @returns {boolean} + * @private + */ + analyzeStackBlock (stackBlock, state) { + const inputs = stackBlock.inputs; + let modified = false; + + if (stackBlock.ignoreState) { + state = state.clone(); + } + + modified = modified || this.analyzeInputs(inputs, state); + + switch (stackBlock.opcode) { + case StackOpcode.VAR_SET: + modified = state.setVariableType(inputs.variable, inputs.value.type) || modified; + break; + case StackOpcode.CONTROL_WHILE: + case StackOpcode.CONTROL_FOR: + case StackOpcode.CONTROL_REPEAT: + modified = this.analyzeLoopedStack(inputs.do, state, stackBlock) || modified; + break; + case StackOpcode.CONTROL_IF_ELSE: { + const trueState = state.clone(); + modified = this.analyzeStack(inputs.whenTrue, trueState) || modified; + modified = this.analyzeStack(inputs.whenFalse, state) || modified; + modified = state.or(trueState) || modified; + break; + } + case StackOpcode.PROCEDURE_CALL: { + modified = this.analyzeInputs(inputs.inputs, state) || modified; + const script = this.ir.procedures[inputs.variant]; + + if (!script || !script.cachedAnalysisEndState) { + modified = state.clear() || modified; + } else { + modified = state.after(script.cachedAnalysisEndState) || modified; + } + break; + } + case StackOpcode.COMPATIBILITY_LAYER: { + this.analyzeInputs(inputs.inputs, state); + for (const substack of inputs.substacks) { + const newState = state.clone(); + modified = this.analyzeStack(substack, newState) || modified; + modified = state.or(newState) || modified; + } + break; + } + } + + // if (stackBlock.ignoreState) return false; + + return modified; + } + + /** + * @param {IntermediateStack?} stack + * @param {TypeState} state + * @returns {boolean} + * @private + */ + analyzeStack (stack, state) { + if (!stack) return false; + let modified = false; + for (const stackBlock of stack.blocks) { + let stateChanged = this.analyzeStackBlock(stackBlock, state); + + if (!stackBlock.ignoreState) { + if (stackBlock.yields && !this.ignoreYields) stateChanged = state.clear() || stateChanged; + + if (stateChanged) { + if (stackBlock.exitState) stackBlock.exitState.or(state); + else stackBlock.exitState = state.clone(); + modified = true; + } + } + } + return modified; + } + + /** + * @param {IntermediateStack} stack + * @param {TypeState} state + * @param {IntermediateStackBlock} block + * @returns {boolean} + * @private + */ + analyzeLoopedStack (stack, state, block) { + if (block.yields && !this.ignoreYields) { + const modified = state.clear(); + block.entryState = state.clone(); + block.exitState = state.clone(); + return this.analyzeStack(stack, state) || modified; + } + let modified = false; + let keepLooping; + do { + const newState = state.clone(); + this.analyzeStack(stack, newState); + modified = keepLooping = state.or(newState); + } while (keepLooping); + block.entryState = state.clone(); + return modified; + } + + /** + * @param {IntermediateInput} input + * @param {TypeState} state + * @returns {IntermediateInput} + * @private + */ + optimizeInput (input, state) { + for (const inputKey in input.inputs) { + const inputInput = input.inputs[inputKey]; + if (inputInput instanceof IntermediateInput) { + input.inputs[inputKey] = this.optimizeInput(inputInput, state); + } + } + + switch (input.opcode) { + case InputOpcode.CAST_NUMBER: { + const targetType = input.inputs.target.type; + if ((targetType & InputType.NUMBER) === targetType) { + return input.inputs.target; + } + return input; + } case InputOpcode.CAST_NUMBER_OR_NAN: { + const targetType = input.inputs.target.type; + if ((targetType & InputType.NUMBER_OR_NAN) === targetType) { + return input.inputs.target; + } + return input; + } + } + + return input; + } + + /** + * @param {IntermediateStack?} stack + * @param {TypeState} state The state of the project before this stack is run. + * @private + */ + optimizeStack (stack, state) { + if (!stack) return; + for (const stackBlock of stack.blocks) { + if (stackBlock.entryState) state = stackBlock.entryState; + for (const inputKey in stackBlock.inputs) { + const input = stackBlock.inputs[inputKey]; + if (input instanceof IntermediateInput) { + stackBlock.inputs[inputKey] = this.optimizeInput(input, state); + } else if (input instanceof IntermediateStack) { + this.optimizeStack(input, state); + } + } + if (stackBlock.exitState) { + state = stackBlock.exitState; + } + } + } + + /** + * @param {IntermediateScript} script + * @param {Set} alreadyOptimized + * @private + */ + optimizeScript (script, alreadyOptimized) { + if (script.isProcedure) { + if (alreadyOptimized.has(script.procedureCode)) { + return; + } + alreadyOptimized.add(script.procedureCode); + } + + for (const procVariant of script.dependedProcedures) { + this.optimizeScript(this.ir.procedures[procVariant], alreadyOptimized); + } + + script.cachedAnalysisEndState = new TypeState(); + this.analyzeStack(script.stack, script.cachedAnalysisEndState); + + this.optimizeStack(script.stack, new TypeState()); + } + + optimize () { + this.optimizeScript(this.ir.entry, new Set()); + } +} + + +module.exports = { + IROptimizer, + TypeState +}; diff --git a/src/compiler/jsexecute.js b/src/compiler/jsexecute.js index b7bff8956ad..a682f880913 100644 --- a/src/compiler/jsexecute.js +++ b/src/compiler/jsexecute.js @@ -1,3 +1,4 @@ +// @ts-check /** * @fileoverview Runtime for scripts generated by jsgen */ @@ -12,6 +13,7 @@ const globalState = { Cast: require('../util/cast'), log: require('../util/log'), blockUtility: require('./compat-block-utility'), + /** @type{import("../engine/thread")?} */ thread: null }; @@ -119,9 +121,8 @@ const waitPromise = function*(promise) { returnValue = value; thread.status = 0; // STATUS_RUNNING }, error => { - globalState.log.warn('Promise rejected in compiled script:', error); - returnValue = '' + error; thread.status = 0; // STATUS_RUNNING + globalState.log.warn('Promise rejected in compiled script:', error); }); yield; @@ -163,7 +164,7 @@ const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, use return returnValue; } - if (thread.status === 1 /* STATUS_PROMISE_WAIT */ || thread.status === 4 /* STATUS_DONE */) { + if (thread.status === 1 /* STATUS_PROMISE_WAIT */) { // Something external is forcing us to stop yield; // Make up a return value because whatever is forcing us to stop can't specify one @@ -190,12 +191,14 @@ const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, use return returnValue; } - if (thread.status === 1 /* STATUS_PROMISE_WAIT */ || thread.status === 4 /* STATUS_DONE */) { + if (thread.status === 1 /* STATUS_PROMISE_WAIT */) { yield; return finish(''); } } + // todo: do we have to do anything extra if status is STATUS_DONE? + return finish(returnValue); }`; @@ -586,7 +589,7 @@ runtimeFunctions.yieldThenCallGenerator = `const yieldThenCallGenerator = functi /** * Step a compiled thread. - * @param {Thread} thread The thread to step. + * @param {import("../engine/thread")} thread The thread to step. */ const execute = thread => { globalState.thread = thread; diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index d3e7784d81f..9f060c59cca 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -1,13 +1,12 @@ +// @ts-check + const log = require('../util/log'); -const Cast = require('../util/cast'); const BlockType = require('../extension-support/block-type'); const VariablePool = require('./variable-pool'); const jsexecute = require('./jsexecute'); const environment = require('./environment'); - -// Imported for JSDoc types, not to actually use -// eslint-disable-next-line no-unused-vars -const {IntermediateScript, IntermediateRepresentation} = require('./intermediate'); +const {StackOpcode, InputOpcode, InputType} = require('./enums.js'); +const {IntermediateStackBlock, IntermediateInput, IntermediateStack, IntermediateScript, IntermediateRepresentation} = require('./intermediate'); /** * @fileoverview Convert intermediate representations to JavaScript functions. @@ -24,12 +23,6 @@ const sanitize = string => { return JSON.stringify(string).slice(1, -1); }; -const TYPE_NUMBER = 1; -const TYPE_STRING = 2; -const TYPE_BOOLEAN = 3; -const TYPE_UNKNOWN = 4; -const TYPE_NUMBER_NAN = 5; - // Pen-related constants const PEN_EXT = 'runtime.ext_pen'; const PEN_STATE = `${PEN_EXT}._getPenState(target)`; @@ -49,281 +42,20 @@ const functionNameVariablePool = new VariablePool('fun'); */ const generatorNameVariablePool = new VariablePool('gen'); -/** - * @typedef Input - * @property {() => string} asNumber - * @property {() => string} asNumberOrNaN - * @property {() => string} asString - * @property {() => string} asBoolean - * @property {() => string} asColor - * @property {() => string} asUnknown - * @property {() => string} asSafe - * @property {() => boolean} isAlwaysNumber - * @property {() => boolean} isAlwaysNumberOrNaN - * @property {() => boolean} isNeverNumber - */ - -/** - * @implements {Input} - */ -class TypedInput { - constructor (source, type) { - // for debugging - if (typeof type !== 'number') throw new Error('type is invalid'); - this.source = source; - this.type = type; - } - - asNumber () { - if (this.type === TYPE_NUMBER) return this.source; - if (this.type === TYPE_NUMBER_NAN) return `(${this.source} || 0)`; - return `(+${this.source} || 0)`; - } - - asNumberOrNaN () { - if (this.type === TYPE_NUMBER || this.type === TYPE_NUMBER_NAN) return this.source; - return `(+${this.source})`; - } - - asString () { - if (this.type === TYPE_STRING) return this.source; - return `("" + ${this.source})`; - } - - asBoolean () { - if (this.type === TYPE_BOOLEAN) return this.source; - return `toBoolean(${this.source})`; - } - - asColor () { - return this.asUnknown(); - } - - asUnknown () { - return this.source; - } - - asSafe () { - return this.asUnknown(); - } - - isAlwaysNumber () { - return this.type === TYPE_NUMBER; - } - - isAlwaysNumberOrNaN () { - return this.type === TYPE_NUMBER || this.type === TYPE_NUMBER_NAN; - } - - isNeverNumber () { - return false; - } -} - -/** - * @implements {Input} - */ -class ConstantInput { - constructor (constantValue, safe) { - this.constantValue = constantValue; - this.safe = safe; - } - - asNumber () { - // Compute at compilation time - const numberValue = +this.constantValue; - if (numberValue) { - // It's important that we use the number's stringified value and not the constant value - // Using the constant value allows numbers such as "010" to be interpreted as 8 (or SyntaxError in strict mode) instead of 10. - return numberValue.toString(); - } - // numberValue is one of 0, -0, or NaN - if (Object.is(numberValue, -0)) { - return '-0'; - } - return '0'; - } - - asNumberOrNaN () { - return this.asNumber(); - } - - asString () { - return `"${sanitize('' + this.constantValue)}"`; - } - - asBoolean () { - // Compute at compilation time - return Cast.toBoolean(this.constantValue).toString(); - } - - asColor () { - // Attempt to parse hex code at compilation time - if (/^#[0-9a-f]{6,8}$/i.test(this.constantValue)) { - const hex = this.constantValue.substr(1); - return Number.parseInt(hex, 16).toString(); - } - return this.asUnknown(); - } - - asUnknown () { - // Attempt to convert strings to numbers if it is unlikely to break things - if (typeof this.constantValue === 'number') { - // todo: handle NaN? - return this.constantValue; - } - const numberValue = +this.constantValue; - if (numberValue.toString() === this.constantValue) { - return this.constantValue; - } - return this.asString(); - } - - asSafe () { - if (this.safe) { - return this.asUnknown(); - } - return this.asString(); - } - - isAlwaysNumber () { - const value = +this.constantValue; - if (Number.isNaN(value)) { - return false; - } - // Empty strings evaluate to 0 but should not be considered a number. - if (value === 0) { - return this.constantValue.toString().trim() !== ''; +const isSafeInputForEqualsOptimization = (input, other) => { + // Only optimize constants + if (input.opcode !== InputOpcode.CONSTANT) return false; + // Only optimize when the constant can always be thought of as a number + if (input.isAlwaysType(InputType.NUMBER) || input.isAlwaysType(InputType.STRING_NUM)) { + if (other.isSometimesType(InputType.STRING_NAN) || other.isSometimesType(InputType.BOOLEAN_INTERPRETABLE)) { + // Never optimize 0 if the other input can be '' or a boolean. + // eg. if '< 0 = "" >' was optimized it would turn into `0 === +""`, + // which would be true even though Scratch would return false. + return (+input.inputs.value) !== 0; } return true; } - - isAlwaysNumberOrNaN () { - return this.isAlwaysNumber(); - } - - isNeverNumber () { - return Number.isNaN(+this.constantValue); - } -} - -/** - * @implements {Input} - */ -class VariableInput { - constructor (source) { - this.source = source; - this.type = TYPE_UNKNOWN; - /** - * The value this variable was most recently set to, if any. - * @type {Input} - * @private - */ - this._value = null; - } - - /** - * @param {Input} input The input this variable was most recently set to. - */ - setInput (input) { - if (input instanceof VariableInput) { - // When being set to another variable, extract the value it was set to. - // Otherwise, you may end up with infinite recursion in analysis methods when a variable is set to itself. - if (input._value) { - input = input._value; - } else { - this.type = TYPE_UNKNOWN; - this._value = null; - return; - } - } - this._value = input; - if (input instanceof TypedInput) { - this.type = input.type; - } else { - this.type = TYPE_UNKNOWN; - } - } - - asNumber () { - if (this.type === TYPE_NUMBER) return this.source; - if (this.type === TYPE_NUMBER_NAN) return `(${this.source} || 0)`; - return `(+${this.source} || 0)`; - } - - asNumberOrNaN () { - if (this.type === TYPE_NUMBER || this.type === TYPE_NUMBER_NAN) return this.source; - return `(+${this.source})`; - } - - asString () { - if (this.type === TYPE_STRING) return this.source; - return `("" + ${this.source})`; - } - - asBoolean () { - if (this.type === TYPE_BOOLEAN) return this.source; - return `toBoolean(${this.source})`; - } - - asColor () { - return this.asUnknown(); - } - - asUnknown () { - return this.source; - } - - asSafe () { - return this.asUnknown(); - } - - isAlwaysNumber () { - if (this._value) { - return this._value.isAlwaysNumber(); - } - return false; - } - - isAlwaysNumberOrNaN () { - if (this._value) { - return this._value.isAlwaysNumberOrNaN(); - } - return false; - } - - isNeverNumber () { - if (this._value) { - return this._value.isNeverNumber(); - } - return false; - } -} - -const getNamesOfCostumesAndSounds = runtime => { - const result = new Set(); - for (const target of runtime.targets) { - if (target.isOriginal) { - const sprite = target.sprite; - for (const costume of sprite.costumes) { - result.add(costume.name); - } - for (const sound of sprite.sounds) { - result.add(sound.name); - } - } - } - return result; -}; - -const isSafeConstantForEqualsOptimization = input => { - const numberValue = +input.constantValue; - // Do not optimize 0 - if (!numberValue) { - return false; - } - // Do not optimize numbers when the original form does not match - return numberValue.toString() === input.constantValue.toString(); + return false; }; /** @@ -350,7 +82,7 @@ class JSGenerator { /** * @param {IntermediateScript} script * @param {IntermediateRepresentation} ir - * @param {Target} target + * @param {import("../sprites/rendered-target")} target */ constructor (script, ir, target) { this.script = script; @@ -358,11 +90,6 @@ class JSGenerator { this.target = target; this.source = ''; - /** - * @type {Object.} - */ - this.variableInputs = {}; - this.isWarp = script.isWarp; this.isProcedure = script.isProcedure; this.warpTimer = script.warpTimer; @@ -375,12 +102,10 @@ class JSGenerator { /** * The current Frame. - * @type {Frame} + * @type {Frame?} */ this.currentFrame = null; - this.namesOfCostumesAndSounds = getNamesOfCostumesAndSounds(target.runtime); - this.localVariables = new VariablePool('a'); this._setupVariablesPool = new VariablePool('b'); this._setupVariables = {}; @@ -425,222 +150,230 @@ class JSGenerator { } /** - * @param {object} node Input node to compile. - * @returns {Input} Compiled input. + * @param {IntermediateInput} block Input node to compile. + * @returns {string} Compiled input. */ - 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': - return new TypedInput(`p${node.index}`, TYPE_UNKNOWN); - - case 'compat': + descendInput (block) { + const node = block.inputs; + switch (block.opcode) { + case InputOpcode.NOP: + return `""`; + + case InputOpcode.PROCEDURE_ARG_BOOLEAN: + return `toBoolean(p${node.index})`; + case InputOpcode.PROCEDURE_ARG_STRING_NUMBER: + return `p${node.index}`; + + case InputOpcode.ADDON_CALL: + return `(${this.descendAddonCall(node)})`; + + case InputOpcode.CAST_BOOLEAN: + return `toBoolean(${this.descendInput(node.target)})`; + case InputOpcode.CAST_NUMBER: + if (node.target.isAlwaysType(InputType.BOOLEAN_INTERPRETABLE)) { + return `(+${this.descendInput(node.target.toType(InputType.BOOLEAN))})`; + } + if (node.target.isAlwaysType(InputType.NUMBER_OR_NAN)) { + return `(${this.descendInput(node.target)} || 0)`; + } + return `(+${this.descendInput(node.target)} || 0)`; + case InputOpcode.CAST_NUMBER_OR_NAN: + return `(+${this.descendInput(node.target)})`; + case InputOpcode.CAST_NUMBER_INDEX: + return `(${this.descendInput(node.target.toType(InputType.NUMBER_OR_NAN))} | 0)`; + case InputOpcode.CAST_STRING: + return `("" + ${this.descendInput(node.target)})`; + + case InputOpcode.COMPATIBILITY_LAYER: // Compatibility layer inputs never use flags. - return new TypedInput(`(${this.generateCompatibilityLayerCall(node, false)})`, TYPE_UNKNOWN); - - case 'constant': - return this.safeConstantInput(node.value); - - case 'counter.get': - return new TypedInput('runtime.ext_scratch3_control._counter', TYPE_NUMBER); - - case 'keyboard.pressed': - return new TypedInput(`runtime.ioDevices.keyboard.getKeyIsDown(${this.descendInput(node.key).asSafe()})`, TYPE_BOOLEAN); - - case 'list.contains': - return new TypedInput(`listContains(${this.referenceVariable(node.list)}, ${this.descendInput(node.item).asUnknown()})`, TYPE_BOOLEAN); - case 'list.contents': - return new TypedInput(`listContents(${this.referenceVariable(node.list)})`, TYPE_STRING); - case 'list.get': { - const index = this.descendInput(node.index); + return `(${this.generateCompatibilityLayerCall(node, false)})`; + + case InputOpcode.CONSTANT: + if (block.isAlwaysType(InputType.NUMBER)) { + if (typeof node.value !== 'number') throw new Error(`JS: '${block.type}' type constant had ${typeof node.value} type value. Expected number.`); + if (Object.is(node.value, -0)) return '-0'; + return node.value.toString(); + } else if (block.isAlwaysType(InputType.BOOLEAN)) { + if (typeof node.value !== 'boolean') throw new Error(`JS: '${block.type}' type constant had ${typeof node.value} type value. Expected boolean.`); + return node.value.toString(); + } else if (block.isSometimesType(InputType.STRING)) { + return `"${sanitize(node.value.toString())}"`; + } throw new Error(`JS: Unknown constant input type '${block.type}'.`); + + case InputOpcode.SENSING_KEY_DOWN: + return `runtime.ioDevices.keyboard.getKeyIsDown(${this.descendInput(node.key)})`; + + case InputOpcode.LIST_CONTAINS: + return `listContains(${this.referenceVariable(node.list)}, ${this.descendInput(node.item)})`; + case InputOpcode.LIST_CONTENTS: + return `listContents(${this.referenceVariable(node.list)})`; + case InputOpcode.LIST_GET: { if (environment.supportsNullishCoalescing) { - if (index.isAlwaysNumberOrNaN()) { - return new TypedInput(`(${this.referenceVariable(node.list)}.value[(${index.asNumber()} | 0) - 1] ?? "")`, TYPE_UNKNOWN); + if (node.index.isAlwaysType(InputType.NUMBER_INTERPRETABLE | InputType.NUMBER_NAN)) { + return `(${this.referenceVariable(node.list)}.value[${this.descendInput(node.index.toType(InputType.NUMBER_INDEX))} - 1] ?? "")`; } - if (index instanceof ConstantInput && index.constantValue === 'last') { - return new TypedInput(`(${this.referenceVariable(node.list)}.value[${this.referenceVariable(node.list)}.value.length - 1] ?? "")`, TYPE_UNKNOWN); + if (node.index.isConstant('last')) { + return `(${this.referenceVariable(node.list)}.value[${this.referenceVariable(node.list)}.value.length - 1] ?? "")`; } } - return new TypedInput(`listGet(${this.referenceVariable(node.list)}.value, ${index.asUnknown()})`, TYPE_UNKNOWN); + return `listGet(${this.referenceVariable(node.list)}.value, ${this.descendInput(node.index)})`; } - case 'list.indexOf': - return new TypedInput(`listIndexOf(${this.referenceVariable(node.list)}, ${this.descendInput(node.item).asUnknown()})`, TYPE_NUMBER); - case 'list.length': - return new TypedInput(`${this.referenceVariable(node.list)}.value.length`, TYPE_NUMBER); - - case 'looks.size': - return new TypedInput('Math.round(target.size)', TYPE_NUMBER); - case 'looks.backdropName': - return new TypedInput('stage.getCostumes()[stage.currentCostume].name', TYPE_STRING); - case 'looks.backdropNumber': - return new TypedInput('(stage.currentCostume + 1)', TYPE_NUMBER); - case 'looks.costumeName': - return new TypedInput('target.getCostumes()[target.currentCostume].name', TYPE_STRING); - case 'looks.costumeNumber': - return new TypedInput('(target.currentCostume + 1)', TYPE_NUMBER); - - case 'motion.direction': - return new TypedInput('target.direction', TYPE_NUMBER); - case 'motion.x': - return new TypedInput('limitPrecision(target.x)', TYPE_NUMBER); - case 'motion.y': - return new TypedInput('limitPrecision(target.y)', TYPE_NUMBER); - - case 'mouse.down': - return new TypedInput('runtime.ioDevices.mouse.getIsDown()', TYPE_BOOLEAN); - case 'mouse.x': - return new TypedInput('runtime.ioDevices.mouse.getScratchX()', TYPE_NUMBER); - case 'mouse.y': - return new TypedInput('runtime.ioDevices.mouse.getScratchY()', TYPE_NUMBER); - - case 'noop': - return new TypedInput('""', TYPE_STRING); - - case 'op.abs': - return new TypedInput(`Math.abs(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER); - case 'op.acos': - // Needs to be marked as NaN because Math.acos(1.0001) === NaN - return new TypedInput(`((Math.acos(${this.descendInput(node.value).asNumber()}) * 180) / Math.PI)`, TYPE_NUMBER_NAN); - case 'op.add': - // Needs to be marked as NaN because Infinity + -Infinity === NaN - return new TypedInput(`(${this.descendInput(node.left).asNumber()} + ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER_NAN); - case 'op.and': - return new TypedInput(`(${this.descendInput(node.left).asBoolean()} && ${this.descendInput(node.right).asBoolean()})`, TYPE_BOOLEAN); - case 'op.asin': - // Needs to be marked as NaN because Math.asin(1.0001) === NaN - return new TypedInput(`((Math.asin(${this.descendInput(node.value).asNumber()}) * 180) / Math.PI)`, TYPE_NUMBER_NAN); - case 'op.atan': - return new TypedInput(`((Math.atan(${this.descendInput(node.value).asNumber()}) * 180) / Math.PI)`, TYPE_NUMBER); - case 'op.ceiling': - return new TypedInput(`Math.ceil(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER); - case 'op.contains': - return new TypedInput(`(${this.descendInput(node.string).asString()}.toLowerCase().indexOf(${this.descendInput(node.contains).asString()}.toLowerCase()) !== -1)`, TYPE_BOOLEAN); - case 'op.cos': - return new TypedInput(`(Math.round(Math.cos((Math.PI * ${this.descendInput(node.value).asNumber()}) / 180) * 1e10) / 1e10)`, TYPE_NUMBER_NAN); - case 'op.divide': - // Needs to be marked as NaN because 0 / 0 === NaN - return new TypedInput(`(${this.descendInput(node.left).asNumber()} / ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER_NAN); - case 'op.equals': { - const left = this.descendInput(node.left); - const right = this.descendInput(node.right); - // When both operands are known to never be numbers, only use string comparison to avoid all number parsing. - if (left.isNeverNumber() || right.isNeverNumber()) { - return new TypedInput(`(${left.asString()}.toLowerCase() === ${right.asString()}.toLowerCase())`, TYPE_BOOLEAN); - } - const leftAlwaysNumber = left.isAlwaysNumber(); - const rightAlwaysNumber = right.isAlwaysNumber(); + case InputOpcode.LIST_INDEX_OF: + return `listIndexOf(${this.referenceVariable(node.list)}, ${this.descendInput(node.item)})`; + case InputOpcode.LIST_LENGTH: + return `${this.referenceVariable(node.list)}.value.length`; + + case InputOpcode.LOOKS_SIZE_GET: + return 'Math.round(target.size)'; + case InputOpcode.LOOKS_BACKDROP_NAME: + return 'stage.getCostumes()[stage.currentCostume].name'; + case InputOpcode.LOOKS_BACKDROP_NUMBER: + return '(stage.currentCostume + 1)'; + case InputOpcode.LOOKS_COSTUME_NAME: + return 'target.getCostumes()[target.currentCostume].name'; + case InputOpcode.LOOKS_COSTUME_NUMBER: + return '(target.currentCostume + 1)'; + + case InputOpcode.MOTION_DIRECTION_GET: + return 'target.direction'; + case InputOpcode.MOTION_X_GET: + return 'limitPrecision(target.x)'; + case InputOpcode.MOTION_Y_GET: + return 'limitPrecision(target.y)'; + + case InputOpcode.SENSING_MOUSE_DOWN: + return 'runtime.ioDevices.mouse.getIsDown()'; + case InputOpcode.SENSING_MOUSE_X: + return 'runtime.ioDevices.mouse.getScratchX()'; + case InputOpcode.SENSING_MOUSE_Y: + return 'runtime.ioDevices.mouse.getScratchY()'; + + case InputOpcode.OP_ABS: + return `Math.abs(${this.descendInput(node.value)})`; + case InputOpcode.OP_ACOS: + return `((Math.acos(${this.descendInput(node.value)}) * 180) / Math.PI)`; + case InputOpcode.OP_ADD: + return `(${this.descendInput(node.left)} + ${this.descendInput(node.right)})`; + case InputOpcode.OP_AND: + return `(${this.descendInput(node.left)} && ${this.descendInput(node.right)})`; + case InputOpcode.OP_ASIN: + return `((Math.asin(${this.descendInput(node.value)}) * 180) / Math.PI)`; + case InputOpcode.OP_ATAN: + return `((Math.atan(${this.descendInput(node.value)}) * 180) / Math.PI)`; + case InputOpcode.OP_CEILING: + return `Math.ceil(${this.descendInput(node.value)})`; + case InputOpcode.OP_CONTAINS: + return `(${this.descendInput(node.string)}.toLowerCase().indexOf(${this.descendInput(node.contains)}.toLowerCase()) !== -1)`; + case InputOpcode.OP_COS: + return `(Math.round(Math.cos((Math.PI * ${this.descendInput(node.value)}) / 180) * 1e10) / 1e10)`; + case InputOpcode.OP_DIVIDE: + return `(${this.descendInput(node.left)} / ${this.descendInput(node.right)})`; + case InputOpcode.OP_EQUALS: { + const left = node.left; + const right = node.right; + // When both operands are known to be numbers, we can use === - if (leftAlwaysNumber && rightAlwaysNumber) { - return new TypedInput(`(${left.asNumber()} === ${right.asNumber()})`, TYPE_BOOLEAN); + if (left.isAlwaysType(InputType.NUMBER_INTERPRETABLE) && right.isAlwaysType(InputType.NUMBER_INTERPRETABLE)) { + return `(${this.descendInput(left.toType(InputType.NUMBER))} === ${this.descendInput(right.toType(InputType.NUMBER))})`; } // In certain conditions, we can use === when one of the operands is known to be a safe number. - if (leftAlwaysNumber && left instanceof ConstantInput && isSafeConstantForEqualsOptimization(left)) { - return new TypedInput(`(${left.asNumber()} === ${right.asNumber()})`, TYPE_BOOLEAN); + if (isSafeInputForEqualsOptimization(left, right) || isSafeInputForEqualsOptimization(right, left)) { + return `(${this.descendInput(left.toType(InputType.NUMBER))} === ${this.descendInput(right.toType(InputType.NUMBER))})`; } - if (rightAlwaysNumber && right instanceof ConstantInput && isSafeConstantForEqualsOptimization(right)) { - return new TypedInput(`(${left.asNumber()} === ${right.asNumber()})`, TYPE_BOOLEAN); + // When either operand is known to never be a number, only use string comparison to avoid all number parsing. + if (!left.isSometimesType(InputType.NUMBER_INTERPRETABLE) || !right.isSometimesType(InputType.NUMBER_INTERPRETABLE)) { + return `(${this.descendInput(left.toType(InputType.STRING))}.toLowerCase() === ${this.descendInput(right.toType(InputType.STRING))}.toLowerCase())`; } // No compile-time optimizations possible - use fallback method. - return new TypedInput(`compareEqual(${left.asUnknown()}, ${right.asUnknown()})`, TYPE_BOOLEAN); + return `compareEqual(${this.descendInput(left)}, ${this.descendInput(right)})`; } - case 'op.e^': - return new TypedInput(`Math.exp(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER); - case 'op.floor': - return new TypedInput(`Math.floor(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER); - case 'op.greater': { - const left = this.descendInput(node.left); - const right = this.descendInput(node.right); + case InputOpcode.OP_POW_E: + return `Math.exp(${this.descendInput(node.value)})`; + case InputOpcode.OP_FLOOR: + return `Math.floor(${this.descendInput(node.value)})`; + case InputOpcode.OP_GREATER: { + const left = node.left; + const right = node.right; // When the left operand is a number and the right operand is a number or NaN, we can use > - if (left.isAlwaysNumber() && right.isAlwaysNumberOrNaN()) { - return new TypedInput(`(${left.asNumber()} > ${right.asNumberOrNaN()})`, TYPE_BOOLEAN); + if (left.isAlwaysType(InputType.NUMBER_INTERPRETABLE) && right.isAlwaysType(InputType.NUMBER_INTERPRETABLE | InputType.NUMBER_NAN)) { + return `(${this.descendInput(left.toType(InputType.NUMBER))} > ${this.descendInput(right.toType(InputType.NUMBER_OR_NAN))})`; } // When the left operand is a number or NaN and the right operand is a number, we can negate <= - if (left.isAlwaysNumberOrNaN() && right.isAlwaysNumber()) { - return new TypedInput(`!(${left.asNumberOrNaN()} <= ${right.asNumber()})`, TYPE_BOOLEAN); + if (left.isAlwaysType(InputType.NUMBER_INTERPRETABLE | InputType.NUMBER_NAN) && right.isAlwaysType(InputType.NUMBER_INTERPRETABLE)) { + return `!(${this.descendInput(left.toType(InputType.NUMBER_OR_NAN))} <= ${this.descendInput(right.toType(InputType.NUMBER))})`; } // When either operand is known to never be a number, avoid all number parsing. - if (left.isNeverNumber() || right.isNeverNumber()) { - return new TypedInput(`(${left.asString()}.toLowerCase() > ${right.asString()}.toLowerCase())`, TYPE_BOOLEAN); + if (!left.isSometimesType(InputType.NUMBER_INTERPRETABLE) || !right.isSometimesType(InputType.NUMBER_INTERPRETABLE)) { + return `(${this.descendInput(left.toType(InputType.STRING))}.toLowerCase() > ${this.descendInput(right.toType(InputType.STRING))}.toLowerCase())`; } // No compile-time optimizations possible - use fallback method. - return new TypedInput(`compareGreaterThan(${left.asUnknown()}, ${right.asUnknown()})`, TYPE_BOOLEAN); + return `compareGreaterThan(${this.descendInput(left)}, ${this.descendInput(right)})`; } - case 'op.join': - return new TypedInput(`(${this.descendInput(node.left).asString()} + ${this.descendInput(node.right).asString()})`, TYPE_STRING); - case 'op.length': - return new TypedInput(`${this.descendInput(node.string).asString()}.length`, TYPE_NUMBER); - case 'op.less': { - const left = this.descendInput(node.left); - const right = this.descendInput(node.right); + case InputOpcode.OP_JOIN: + return `(${this.descendInput(node.left)} + ${this.descendInput(node.right)})`; + case InputOpcode.OP_LENGTH: + return `${this.descendInput(node.string)}.length`; + case InputOpcode.OP_LESS: { + const left = node.left; + const right = node.right; // When the left operand is a number or NaN and the right operand is a number, we can use < - if (left.isAlwaysNumberOrNaN() && right.isAlwaysNumber()) { - return new TypedInput(`(${left.asNumberOrNaN()} < ${right.asNumber()})`, TYPE_BOOLEAN); + if (left.isAlwaysType(InputType.NUMBER_INTERPRETABLE | InputType.NUMBER_NAN) && right.isAlwaysType(InputType.NUMBER_INTERPRETABLE)) { + return `(${this.descendInput(left.toType(InputType.NUMBER_OR_NAN))} < ${this.descendInput(right.toType(InputType.NUMBER))})`; } // When the left operand is a number and the right operand is a number or NaN, we can negate >= - if (left.isAlwaysNumber() && right.isAlwaysNumberOrNaN()) { - return new TypedInput(`!(${left.asNumber()} >= ${right.asNumberOrNaN()})`, TYPE_BOOLEAN); + if (left.isAlwaysType(InputType.NUMBER_INTERPRETABLE) && right.isAlwaysType(InputType.NUMBER_INTERPRETABLE | InputType.NUMBER_NAN)) { + return `!(${this.descendInput(left.toType(InputType.NUMBER))} >= ${this.descendInput(right.toType(InputType.NUMBER_OR_NAN))})`; } // When either operand is known to never be a number, avoid all number parsing. - if (left.isNeverNumber() || right.isNeverNumber()) { - return new TypedInput(`(${left.asString()}.toLowerCase() < ${right.asString()}.toLowerCase())`, TYPE_BOOLEAN); + if (!left.isSometimesType(InputType.NUMBER_INTERPRETABLE) || !right.isSometimesType(InputType.NUMBER_INTERPRETABLE)) { + return `(${this.descendInput(left.toType(InputType.STRING))}.toLowerCase() < ${this.descendInput(right.toType(InputType.STRING))}.toLowerCase())`; } // No compile-time optimizations possible - use fallback method. - return new TypedInput(`compareLessThan(${left.asUnknown()}, ${right.asUnknown()})`, TYPE_BOOLEAN); + return `compareLessThan(${this.descendInput(left)}, ${this.descendInput(right)})`; } - case 'op.letterOf': - return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING); - case 'op.ln': - // Needs to be marked as NaN because Math.log(-1) == NaN - return new TypedInput(`Math.log(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER_NAN); - case 'op.log': - // Needs to be marked as NaN because Math.log(-1) == NaN - return new TypedInput(`(Math.log(${this.descendInput(node.value).asNumber()}) / Math.LN10)`, TYPE_NUMBER_NAN); - case 'op.mod': + case InputOpcode.OP_LETTER_OF: + return `((${this.descendInput(node.string)})[${this.descendInput(node.letter)} - 1] || "")`; + case InputOpcode.OP_LOG_E: + return `Math.log(${this.descendInput(node.value)})`; + case InputOpcode.OP_LOG_10: + return `(Math.log(${this.descendInput(node.value)}) / Math.LN10)`; + case InputOpcode.OP_MOD: this.descendedIntoModulo = true; - // Needs to be marked as NaN because mod(0, 0) (and others) == NaN - return new TypedInput(`mod(${this.descendInput(node.left).asNumber()}, ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER_NAN); - case 'op.multiply': - // Needs to be marked as NaN because Infinity * 0 === NaN - return new TypedInput(`(${this.descendInput(node.left).asNumber()} * ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER_NAN); - case 'op.not': - return new TypedInput(`!${this.descendInput(node.operand).asBoolean()}`, TYPE_BOOLEAN); - case 'op.or': - return new TypedInput(`(${this.descendInput(node.left).asBoolean()} || ${this.descendInput(node.right).asBoolean()})`, TYPE_BOOLEAN); - case 'op.random': + return `mod(${this.descendInput(node.left)}, ${this.descendInput(node.right)})`; + case InputOpcode.OP_MULTIPLY: + return `(${this.descendInput(node.left)} * ${this.descendInput(node.right)})`; + case InputOpcode.OP_NOT: + return `!${this.descendInput(node.operand)}`; + case InputOpcode.OP_OR: + return `(${this.descendInput(node.left)} || ${this.descendInput(node.right)})`; + case InputOpcode.OP_RANDOM: if (node.useInts) { - // Both inputs are ints, so we know neither are NaN - return new TypedInput(`randomInt(${this.descendInput(node.low).asNumber()}, ${this.descendInput(node.high).asNumber()})`, TYPE_NUMBER); + return `randomInt(${this.descendInput(node.low)}, ${this.descendInput(node.high)})`; } if (node.useFloats) { - return new TypedInput(`randomFloat(${this.descendInput(node.low).asNumber()}, ${this.descendInput(node.high).asNumber()})`, TYPE_NUMBER_NAN); + return `randomFloat(${this.descendInput(node.low)}, ${this.descendInput(node.high)})`; } - return new TypedInput(`runtime.ext_scratch3_operators._random(${this.descendInput(node.low).asUnknown()}, ${this.descendInput(node.high).asUnknown()})`, TYPE_NUMBER_NAN); - case 'op.round': - return new TypedInput(`Math.round(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER); - case 'op.sin': - return new TypedInput(`(Math.round(Math.sin((Math.PI * ${this.descendInput(node.value).asNumber()}) / 180) * 1e10) / 1e10)`, TYPE_NUMBER_NAN); - case 'op.sqrt': - // Needs to be marked as NaN because Math.sqrt(-1) === NaN - return new TypedInput(`Math.sqrt(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER_NAN); - case 'op.subtract': - // Needs to be marked as NaN because Infinity - Infinity === NaN - return new TypedInput(`(${this.descendInput(node.left).asNumber()} - ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER_NAN); - case 'op.tan': - return new TypedInput(`tan(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER_NAN); - case 'op.10^': - return new TypedInput(`(10 ** ${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER); - - case 'procedures.call': { + return `runtime.ext_scratch3_operators._random(${this.descendInput(node.low)}, ${this.descendInput(node.high)})`; + case InputOpcode.OP_ROUND: + return `Math.round(${this.descendInput(node.value)})`; + case InputOpcode.OP_SIN: + return `(Math.round(Math.sin((Math.PI * ${this.descendInput(node.value)}) / 180) * 1e10) / 1e10)`; + case InputOpcode.OP_SQRT: + return `Math.sqrt(${this.descendInput(node.value)})`; + case InputOpcode.OP_SUBTRACT: + return `(${this.descendInput(node.left)} - ${this.descendInput(node.right)})`; + case InputOpcode.OP_TAN: + return `tan(${this.descendInput(node.value)})`; + case InputOpcode.OP_POW_10: + return `(10 ** ${this.descendInput(node.value)})`; + + case InputOpcode.PROCEDURE_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); + return '""'; } // Recursion makes this complicated because: @@ -650,7 +383,7 @@ class JSGenerator { const procedureReference = `thread.procedures["${sanitize(procedureVariant)}"]`; const args = []; for (const input of node.arguments) { - args.push(this.descendInput(input).asSafe()); + args.push(this.descendInput(input)); } const joinedArgs = args.join(','); @@ -658,109 +391,105 @@ class JSGenerator { const yieldForHat = this.isInHat; if (yieldForRecursion || yieldForHat) { const runtimeFunction = procedureData.yields ? 'yieldThenCallGenerator' : 'yieldThenCall'; - return new TypedInput(`(yield* ${runtimeFunction}(${procedureReference}, ${joinedArgs}))`, TYPE_UNKNOWN); + return `(yield* ${runtimeFunction}(${procedureReference}, ${joinedArgs}))`; } if (procedureData.yields) { - return new TypedInput(`(yield* ${procedureReference}(${joinedArgs}))`, TYPE_UNKNOWN); + return `(yield* ${procedureReference}(${joinedArgs}))`; } - return new TypedInput(`${procedureReference}(${joinedArgs})`, TYPE_UNKNOWN); + return `${procedureReference}(${joinedArgs})`; } - case 'sensing.answer': - return new TypedInput(`runtime.ext_scratch3_sensing._answer`, TYPE_STRING); - case 'sensing.colorTouchingColor': - return new TypedInput(`target.colorIsTouchingColor(colorToList(${this.descendInput(node.target).asColor()}), colorToList(${this.descendInput(node.mask).asColor()}))`, TYPE_BOOLEAN); - case 'sensing.date': - return new TypedInput(`(new Date().getDate())`, TYPE_NUMBER); - case 'sensing.dayofweek': - return new TypedInput(`(new Date().getDay() + 1)`, TYPE_NUMBER); - case 'sensing.daysSince2000': - return new TypedInput('daysSince2000()', TYPE_NUMBER); - case 'sensing.distance': + case InputOpcode.SENSING_ANSWER: + return `runtime.ext_scratch3_sensing._answer`; + case InputOpcode.SENSING_COLOR_TOUCHING_COLOR: + return `target.colorIsTouchingColor(colorToList(${this.descendInput(node.target)}), colorToList(${this.descendInput(node.mask)}))`; + case InputOpcode.SENSING_TIME_DATE: + return `(new Date().getDate())`; + case InputOpcode.SENSING_TIME_WEEKDAY: + return `(new Date().getDay() + 1)`; + case InputOpcode.SENSING_TIME_DAYS_SINCE_2000: + return 'daysSince2000()'; + case InputOpcode.SENSING_DISTANCE: // TODO: on stages, this can be computed at compile time - return new TypedInput(`distance(${this.descendInput(node.target).asString()})`, TYPE_NUMBER); - case 'sensing.hour': - return new TypedInput(`(new Date().getHours())`, TYPE_NUMBER); - case 'sensing.minute': - return new TypedInput(`(new Date().getMinutes())`, TYPE_NUMBER); - case 'sensing.month': - return new TypedInput(`(new Date().getMonth() + 1)`, TYPE_NUMBER); - case 'sensing.of': { - const object = this.descendInput(node.object).asString(); - const property = node.property; - if (node.object.kind === 'constant') { - const isStage = node.object.value === '_stage_'; - // Note that if target isn't a stage, we can't assume it exists - const objectReference = isStage ? 'stage' : this.evaluateOnce(`runtime.getSpriteTargetByName(${object})`); - if (property === 'volume') { - return new TypedInput(`(${objectReference} ? ${objectReference}.volume : 0)`, TYPE_NUMBER); - } - if (isStage) { - switch (property) { - case 'background #': - // fallthrough for scratch 1.0 compatibility - case 'backdrop #': - return new TypedInput(`(${objectReference}.currentCostume + 1)`, TYPE_NUMBER); - case 'backdrop name': - return new TypedInput(`${objectReference}.getCostumes()[${objectReference}.currentCostume].name`, TYPE_STRING); - } - } else { - switch (property) { - case 'x position': - return new TypedInput(`(${objectReference} ? ${objectReference}.x : 0)`, TYPE_NUMBER); - case 'y position': - return new TypedInput(`(${objectReference} ? ${objectReference}.y : 0)`, TYPE_NUMBER); - case 'direction': - return new TypedInput(`(${objectReference} ? ${objectReference}.direction : 0)`, TYPE_NUMBER); - case 'costume #': - return new TypedInput(`(${objectReference} ? ${objectReference}.currentCostume + 1 : 0)`, TYPE_NUMBER); - case 'costume name': - return new TypedInput(`(${objectReference} ? ${objectReference}.getCostumes()[${objectReference}.currentCostume].name : 0)`, TYPE_UNKNOWN); - case 'size': - return new TypedInput(`(${objectReference} ? ${objectReference}.size : 0)`, TYPE_NUMBER); - } - } - const variableReference = this.evaluateOnce(`${objectReference} && ${objectReference}.lookupVariableByNameAndType("${sanitize(property)}", "", true)`); - return new TypedInput(`(${variableReference} ? ${variableReference}.value : 0)`, TYPE_UNKNOWN); - } - return new TypedInput(`runtime.ext_scratch3_sensing.getAttributeOf({OBJECT: ${object}, PROPERTY: "${sanitize(property)}" })`, TYPE_UNKNOWN); - } - case 'sensing.second': - return new TypedInput(`(new Date().getSeconds())`, TYPE_NUMBER); - case 'sensing.touching': - return new TypedInput(`target.isTouchingObject(${this.descendInput(node.object).asUnknown()})`, TYPE_BOOLEAN); - case 'sensing.touchingColor': - return new TypedInput(`target.isTouchingColor(colorToList(${this.descendInput(node.color).asColor()}))`, TYPE_BOOLEAN); - case 'sensing.username': - return new TypedInput('runtime.ioDevices.userData.getUsername()', TYPE_STRING); - case 'sensing.year': - return new TypedInput(`(new Date().getFullYear())`, TYPE_NUMBER); - - case 'timer.get': - return new TypedInput('runtime.ioDevices.clock.projectTimer()', TYPE_NUMBER); - - case 'tw.lastKeyPressed': - return new TypedInput('runtime.ioDevices.keyboard.getLastKeyPressed()', TYPE_STRING); - - case 'var.get': - return this.descendVariable(node.variable); + return `distance(${this.descendInput(node.target)})`; + case InputOpcode.SENSING_TIME_HOUR: + return `(new Date().getHours())`; + case InputOpcode.SENSING_TIME_MINUTE: + return `(new Date().getMinutes())`; + case InputOpcode.SENSING_TIME_MONTH: + return `(new Date().getMonth() + 1)`; + case InputOpcode.SENSING_OF: + return `runtime.ext_scratch3_sensing.getAttributeOf({OBJECT: ${this.descendInput(node.object)}, PROPERTY: "${sanitize(node.property)}" })`; + case InputOpcode.SENSING_OF_VOLUME: { + const targetRef = this.descendTargetReference(node.object); + return `(${targetRef} ? ${targetRef}.volume : 0)`; + } case InputOpcode.SENSING_OF_BACKDROP_NUMBER: + return `(stage.currentCostume + 1)`; + case InputOpcode.SENSING_OF_BACKDROP_NAME: + return `stage.getCostumes()[stage.currentCostume].name`; + case InputOpcode.SENSING_OF_POS_X: { + const targetRef = this.descendTargetReference(node.object); + return `(${targetRef} ? ${targetRef}.x : 0)`; + } case InputOpcode.SENSING_OF_POS_Y: { + const targetRef = this.descendTargetReference(node.object); + return `(${targetRef} ? ${targetRef}.y : 0)`; + } case InputOpcode.SENSING_OF_DIRECTION: { + const targetRef = this.descendTargetReference(node.object); + return `(${targetRef} ? ${targetRef}.direction : 0)`; + } case InputOpcode.SENSING_OF_COSTUME_NUMBER: { + const targetRef = this.descendTargetReference(node.object); + return `(${targetRef} ? ${targetRef}.currentCostume + 1 : 0)`; + } case InputOpcode.SENSING_OF_COSTUME_NAME: { + const targetRef = this.descendTargetReference(node.object); + return `(${targetRef} ? ${targetRef}.getCostumes()[${targetRef}.currentCostume].name : 0)`; + } case InputOpcode.SENSING_OF_SIZE: { + const targetRef = this.descendTargetReference(node.object); + return `(${targetRef} ? ${targetRef}.size : 0)`; + } case InputOpcode.SENSING_OF_VAR: { + const targetRef = this.descendTargetReference(node.object); + const varRef = this.evaluateOnce(`${targetRef} && ${targetRef}.lookupVariableByNameAndType("${sanitize(node.property)}", "", true)`); + return `(${varRef} ? ${varRef}.value : 0)`; + } case InputOpcode.SENSING_TIME_SECOND: + return `(new Date().getSeconds())`; + case InputOpcode.SENSING_TOUCHING_OBJECT: + return `target.isTouchingObject(${this.descendInput(node.object)})`; + case InputOpcode.SENSING_TOUCHING_COLOR: + return `target.isTouchingColor(colorToList(${this.descendInput(node.color)}))`; + case InputOpcode.SENSING_USERNAME: + return 'runtime.ioDevices.userData.getUsername()'; + case InputOpcode.SENSING_TIME_YEAR: + return `(new Date().getFullYear())`; + + case InputOpcode.SENSING_TIMER_GET: + return 'runtime.ioDevices.clock.projectTimer()'; + + case InputOpcode.CONTROL_COUNTER: + return 'runtime.ext_scratch3_control._counter'; + + case InputOpcode.TW_KEY_LAST_PRESSED: + return 'runtime.ioDevices.keyboard.getLastKeyPressed()'; + + case InputOpcode.VAR_GET: + return `${this.referenceVariable(node.variable)}.value`; default: - log.warn(`JS: Unknown input: ${node.kind}`, node); - throw new Error(`JS: Unknown input: ${node.kind}`); + log.warn(`JS: Unknown input: ${block.opcode}`, node); + throw new Error(`JS: Unknown input: ${block.opcode}`); } } /** - * @param {*} node Stacked node to compile. + * @param {IntermediateStackBlock} block Stacked block to compile. */ - descendStackedBlock (node) { - switch (node.kind) { - case 'addons.call': + descendStackedBlock (block) { + const node = block.inputs; + switch (block.opcode) { + case StackOpcode.ADDON_CALL: { this.source += `${this.descendAddonCall(node)};\n`; break; + } - case 'compat': { + case StackOpcode.COMPATIBILITY_LAYER: { // If the last command in a loop returns a promise, immediately continue to the next iteration. // If you don't do this, the loop effectively yields twice per iteration and will run at half-speed. const isLastInLoop = this.isLastBlockInLoop(); @@ -773,9 +502,9 @@ class JSGenerator { this.source += `const ${branchVariable} = createBranchInfo(${blockType === BlockType.LOOP});\n`; this.source += `while (${branchVariable}.branch = +(${this.generateCompatibilityLayerCall(node, false, branchVariable)})) {\n`; this.source += `switch (${branchVariable}.branch) {\n`; - for (const index in node.substacks) { - this.source += `case ${+index}: {\n`; - this.descendStack(node.substacks[index], new Frame(false)); + for (let i = 0; i < node.substacks.length; i++) { + this.source += `case ${i + 1}: {\n`; + this.descendStack(node.substacks[i], new Frame(false)); this.source += `break;\n`; this.source += `}\n`; // close case } @@ -793,61 +522,86 @@ class JSGenerator { break; } - case 'control.createClone': - this.source += `runtime.ext_scratch3_control._createClone(${this.descendInput(node.target).asString()}, target);\n`; + case StackOpcode.HAT_EDGE: + this.isInHat = true; + this.source += '{\n'; + // For exact Scratch parity, evaluate the input before checking old edge state. + // Can matter if the input is not instantly evaluated. + this.source += `const resolvedValue = ${this.descendInput(node.condition)};\n`; + this.source += `const id = "${sanitize(node.id)}";\n`; + this.source += 'const hasOldEdgeValue = target.hasEdgeActivatedValue(id);\n'; + this.source += `const oldEdgeValue = target.updateEdgeActivatedValue(id, resolvedValue);\n`; + this.source += `const edgeWasActivated = hasOldEdgeValue ? (!oldEdgeValue && resolvedValue) : resolvedValue;\n`; + this.source += `if (!edgeWasActivated) {\n`; + this.retire(); + this.source += '}\n'; + this.source += 'yield;\n'; + this.source += '}\n'; + this.isInHat = false; break; - case 'control.deleteClone': + + case StackOpcode.HAT_PREDICATE: + this.isInHat = true; + this.source += `if (!${this.descendInput(node.condition)}) {\n`; + this.retire(); + this.source += '}\n'; + this.source += 'yield;\n'; + this.isInHat = false; + break; + + case StackOpcode.CONTROL_CLONE_CREATE: + this.source += `runtime.ext_scratch3_control._createClone(${this.descendInput(node.target)}, target);\n`; + break; + case StackOpcode.CONTROL_CLONE_DELETE: this.source += 'if (!target.isOriginal) {\n'; this.source += ' runtime.disposeTarget(target);\n'; this.source += ' runtime.stopForTarget(target);\n'; this.retire(); this.source += '}\n'; break; - case 'control.for': { - this.resetVariableInputs(); + case StackOpcode.CONTROL_FOR: const index = this.localVariables.next(); this.source += `var ${index} = 0; `; - this.source += `while (${index} < ${this.descendInput(node.count).asNumber()}) { `; + this.source += `while (${index} < ${this.descendInput(node.count)}) { `; this.source += `${index}++; `; this.source += `${this.referenceVariable(node.variable)}.value = ${index};\n`; this.descendStack(node.do, new Frame(true)); this.yieldLoop(); this.source += '}\n'; break; - } - case 'control.if': - this.source += `if (${this.descendInput(node.condition).asBoolean()}) {\n`; + case StackOpcode.CONTROL_IF_ELSE: + this.source += `if (${this.descendInput(node.condition)}) {\n`; this.descendStack(node.whenTrue, new Frame(false)); // only add the else branch if it won't be empty // this makes scripts have a bit less useless noise in them - if (node.whenFalse.length) { + if (node.whenFalse.blocks.length) { this.source += `} else {\n`; this.descendStack(node.whenFalse, new Frame(false)); } this.source += `}\n`; break; - case 'control.repeat': { + case StackOpcode.CONTROL_REPEAT: { const i = this.localVariables.next(); - this.source += `for (var ${i} = ${this.descendInput(node.times).asNumber()}; ${i} >= 0.5; ${i}--) {\n`; + this.source += `for (var ${i} = ${this.descendInput(node.times)}; ${i} >= 0.5; ${i}--) {\n`; this.descendStack(node.do, new Frame(true)); this.yieldLoop(); this.source += `}\n`; break; } - case 'control.stopAll': + case StackOpcode.CONTROL_STOP_ALL: this.source += 'runtime.stopAll();\n'; this.retire(); break; - case 'control.stopOthers': + case StackOpcode.CONTROL_STOP_OTHERS: this.source += 'runtime.stopForTarget(target, thread);\n'; break; - case 'control.stopScript': + case StackOpcode.CONTROL_STOP_SCRIPT: this.stopScript(); break; - case 'control.wait': { + case StackOpcode.CONTROL_WAIT: { const duration = this.localVariables.next(); this.source += `thread.timer = timer();\n`; - this.source += `var ${duration} = Math.max(0, 1000 * ${this.descendInput(node.seconds).asNumber()});\n`; + this.source += `var ${duration} = Math.max(0, 1000 * ${this.descendInput(node.seconds)});\n`; this.requestRedraw(); // always yield at least once, even on 0 second durations this.yieldNotWarp(); @@ -857,16 +611,14 @@ class JSGenerator { this.source += 'thread.timer = null;\n'; break; } - case 'control.waitUntil': { - this.resetVariableInputs(); - this.source += `while (!${this.descendInput(node.condition).asBoolean()}) {\n`; + case StackOpcode.CONTROL_WAIT_UNTIL: { + this.source += `while (!${this.descendInput(node.condition)}) {\n`; this.yieldStuckOrNotWarp(); this.source += `}\n`; break; } - case 'control.while': - this.resetVariableInputs(); - this.source += `while (${this.descendInput(node.condition).asBoolean()}) {\n`; + case StackOpcode.CONTROL_WHILE: + this.source += `while (${this.descendInput(node.condition)}) {\n`; this.descendStack(node.do, new Frame(true)); if (node.warpTimer) { this.yieldStuckOrNotWarp(); @@ -875,234 +627,202 @@ class JSGenerator { } this.source += `}\n`; break; - - case 'counter.clear': + case StackOpcode.CONTROL_CLEAR_COUNTER: this.source += 'runtime.ext_scratch3_control._counter = 0;\n'; break; - case 'counter.increment': + case StackOpcode.CONTORL_INCR_COUNTER: this.source += 'runtime.ext_scratch3_control._counter++;\n'; break; - case 'hat.edge': - this.isInHat = true; - this.source += '{\n'; - // For exact Scratch parity, evaluate the input before checking old edge state. - // Can matter if the input is not instantly evaluated. - this.source += `const resolvedValue = ${this.descendInput(node.condition).asBoolean()};\n`; - this.source += `const id = "${sanitize(node.id)}";\n`; - this.source += 'const hasOldEdgeValue = target.hasEdgeActivatedValue(id);\n'; - this.source += `const oldEdgeValue = target.updateEdgeActivatedValue(id, resolvedValue);\n`; - this.source += `const edgeWasActivated = hasOldEdgeValue ? (!oldEdgeValue && resolvedValue) : resolvedValue;\n`; - this.source += `if (!edgeWasActivated) {\n`; - this.retire(); - this.source += '}\n'; - this.source += 'yield;\n'; - this.source += '}\n'; - this.isInHat = false; - break; - case 'hat.predicate': - this.isInHat = true; - this.source += `if (!${this.descendInput(node.condition).asBoolean()}) {\n`; - this.retire(); - this.source += '}\n'; - this.source += 'yield;\n'; - this.isInHat = false; - break; - - case 'event.broadcast': - this.source += `startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: ${this.descendInput(node.broadcast).asString()} });\n`; - this.resetVariableInputs(); + case StackOpcode.EVENT_BROADCAST: + this.source += `startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: ${this.descendInput(node.broadcast)} });\n`; break; - case 'event.broadcastAndWait': - this.source += `yield* waitThreads(startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: ${this.descendInput(node.broadcast).asString()} }));\n`; + case StackOpcode.EVENT_BROADCAST_AND_WAIT: + this.source += `yield* waitThreads(startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: ${this.descendInput(node.broadcast)} }));\n`; this.yielded(); break; - case 'list.add': { + case StackOpcode.LIST_ADD: { const list = this.referenceVariable(node.list); - this.source += `${list}.value.push(${this.descendInput(node.item).asSafe()});\n`; + this.source += `${list}.value.push(${this.descendInput(node.item)});\n`; this.source += `${list}._monitorUpToDate = false;\n`; break; } - case 'list.delete': { + case StackOpcode.LIST_DELETE: { const list = this.referenceVariable(node.list); - const index = this.descendInput(node.index); - if (index instanceof ConstantInput) { - if (index.constantValue === 'last') { - this.source += `${list}.value.pop();\n`; - this.source += `${list}._monitorUpToDate = false;\n`; - break; - } - if (+index.constantValue === 1) { - this.source += `${list}.value.shift();\n`; - this.source += `${list}._monitorUpToDate = false;\n`; - break; - } - // do not need a special case for all as that is handled in IR generation (list.deleteAll) + if (node.index.isConstant('last')) { + this.source += `${list}.value.pop();\n`; + this.source += `${list}._monitorUpToDate = false;\n`; + break; + } + if (node.index.isConstant(1)) { + this.source += `${list}.value.shift();\n`; + this.source += `${list}._monitorUpToDate = false;\n`; + break; } - this.source += `listDelete(${list}, ${index.asUnknown()});\n`; + // do not need a special case for all as that is handled in IR generation (list.deleteAll) + this.source += `listDelete(${list}, ${this.descendInput(node.index)});\n`; break; } - case 'list.deleteAll': + case StackOpcode.LIST_DELETE_ALL: this.source += `${this.referenceVariable(node.list)}.value = [];\n`; break; - case 'list.hide': + case StackOpcode.LIST_HIDE: this.source += `runtime.monitorBlocks.changeBlock({ id: "${sanitize(node.list.id)}", element: "checkbox", value: false }, runtime);\n`; break; - case 'list.insert': { + case StackOpcode.LIST_INSERT: { const list = this.referenceVariable(node.list); - const index = this.descendInput(node.index); const item = this.descendInput(node.item); - if (index instanceof ConstantInput && +index.constantValue === 1) { - this.source += `${list}.value.unshift(${item.asSafe()});\n`; + if (node.index.isConstant(1)) { + this.source += `${list}.value.unshift(${item});\n`; this.source += `${list}._monitorUpToDate = false;\n`; break; } - this.source += `listInsert(${list}, ${index.asUnknown()}, ${item.asSafe()});\n`; + this.source += `listInsert(${list}, ${this.descendInput(node.index)}, ${item});\n`; break; } - case 'list.replace': - this.source += `listReplace(${this.referenceVariable(node.list)}, ${this.descendInput(node.index).asUnknown()}, ${this.descendInput(node.item).asSafe()});\n`; + case StackOpcode.LIST_REPLACE: + this.source += `listReplace(${this.referenceVariable(node.list)}, ${this.descendInput(node.index)}, ${this.descendInput(node.item)});\n`; break; - case 'list.show': + case StackOpcode.LIST_SHOW: this.source += `runtime.monitorBlocks.changeBlock({ id: "${sanitize(node.list.id)}", element: "checkbox", value: true }, runtime);\n`; break; - case 'looks.backwardLayers': + case StackOpcode.LOOKS_LAYER_BACKWARD: if (!this.target.isStage) { - this.source += `target.goBackwardLayers(${this.descendInput(node.layers).asNumber()});\n`; + this.source += `target.goBackwardLayers(${this.descendInput(node.layers)});\n`; } break; - case 'looks.clearEffects': + case StackOpcode.LOOKS_EFFECT_CLEAR: this.source += 'target.clearEffects();\n'; break; - case 'looks.changeEffect': - if (Object.prototype.hasOwnProperty.call(this.target.effects, node.effect)) { - this.source += `target.setEffect("${sanitize(node.effect)}", runtime.ext_scratch3_looks.clampEffect("${sanitize(node.effect)}", ${this.descendInput(node.value).asNumber()} + target.effects["${sanitize(node.effect)}"]));\n`; + case StackOpcode.LOOKS_EFFECT_CHANGE: + if (this.target.effects.hasOwnProperty(node.effect)) { + this.source += `target.setEffect("${sanitize(node.effect)}", runtime.ext_scratch3_looks.clampEffect("${sanitize(node.effect)}", ${this.descendInput(node.value)} + target.effects["${sanitize(node.effect)}"]));\n`; } break; - case 'looks.changeSize': - this.source += `target.setSize(target.size + ${this.descendInput(node.size).asNumber()});\n`; + case StackOpcode.LOOKS_SIZE_CHANGE: + this.source += `target.setSize(target.size + ${this.descendInput(node.size)});\n`; break; - case 'looks.forwardLayers': + case StackOpcode.LOOKS_LAYER_FORWARD: if (!this.target.isStage) { - this.source += `target.goForwardLayers(${this.descendInput(node.layers).asNumber()});\n`; + this.source += `target.goForwardLayers(${this.descendInput(node.layers)});\n`; } break; - case 'looks.goToBack': + case StackOpcode.LOOKS_LAYER_BACK: if (!this.target.isStage) { this.source += 'target.goToBack();\n'; } break; - case 'looks.goToFront': + case StackOpcode.LOOKS_LAYER_FRONT: if (!this.target.isStage) { this.source += 'target.goToFront();\n'; } break; - case 'looks.hide': + case StackOpcode.LOOKS_HIDE: this.source += 'target.setVisible(false);\n'; this.source += 'runtime.ext_scratch3_looks._renderBubble(target);\n'; break; - case 'looks.nextBackdrop': + case StackOpcode.LOOKS_BACKDROP_NEXT: this.source += 'runtime.ext_scratch3_looks._setBackdrop(stage, stage.currentCostume + 1, true);\n'; break; - case 'looks.nextCostume': + case StackOpcode.LOOKS_COSTUME_NEXT: this.source += 'target.setCostume(target.currentCostume + 1);\n'; break; - case 'looks.setEffect': - if (Object.prototype.hasOwnProperty.call(this.target.effects, node.effect)) { - this.source += `target.setEffect("${sanitize(node.effect)}", runtime.ext_scratch3_looks.clampEffect("${sanitize(node.effect)}", ${this.descendInput(node.value).asNumber()}));\n`; + case StackOpcode.LOOKS_EFFECT_SET: + if (this.target.effects.hasOwnProperty(node.effect)) { + this.source += `target.setEffect("${sanitize(node.effect)}", runtime.ext_scratch3_looks.clampEffect("${sanitize(node.effect)}", ${this.descendInput(node.value)}));\n`; } break; - case 'looks.setSize': - this.source += `target.setSize(${this.descendInput(node.size).asNumber()});\n`; + case StackOpcode.LOOKS_SIZE_SET: + this.source += `target.setSize(${this.descendInput(node.size)});\n`; break; - case 'looks.show': + case StackOpcode.LOOKS_SHOW: this.source += 'target.setVisible(true);\n'; this.source += 'runtime.ext_scratch3_looks._renderBubble(target);\n'; break; - case 'looks.switchBackdrop': - this.source += `runtime.ext_scratch3_looks._setBackdrop(stage, ${this.descendInput(node.backdrop).asSafe()});\n`; + case StackOpcode.LOOKS_BACKDROP_SET: + this.source += `runtime.ext_scratch3_looks._setBackdrop(stage, ${this.descendInput(node.backdrop)});\n`; break; - case 'looks.switchCostume': - this.source += `runtime.ext_scratch3_looks._setCostume(target, ${this.descendInput(node.costume).asSafe()});\n`; + case StackOpcode.LOOKS_COSTUME_SET: + this.source += `runtime.ext_scratch3_looks._setCostume(target, ${this.descendInput(node.costume)});\n`; break; - case 'motion.changeX': - this.source += `target.setXY(target.x + ${this.descendInput(node.dx).asNumber()}, target.y);\n`; + case StackOpcode.MOTION_X_CHANGE: + this.source += `target.setXY(target.x + ${this.descendInput(node.dx)}, target.y);\n`; break; - case 'motion.changeY': - this.source += `target.setXY(target.x, target.y + ${this.descendInput(node.dy).asNumber()});\n`; + case StackOpcode.MOTION_Y_CHANGE: + this.source += `target.setXY(target.x, target.y + ${this.descendInput(node.dy)});\n`; break; - case 'motion.ifOnEdgeBounce': + case StackOpcode.MOTION_IF_ON_EDGE_BOUNCE: this.source += `runtime.ext_scratch3_motion._ifOnEdgeBounce(target);\n`; break; - case 'motion.setDirection': - this.source += `target.setDirection(${this.descendInput(node.direction).asNumber()});\n`; + case StackOpcode.MOTION_DIRECTION_SET: + this.source += `target.setDirection(${this.descendInput(node.direction)});\n`; break; - case 'motion.setRotationStyle': + case StackOpcode.MOTION_ROTATION_STYLE_SET: this.source += `target.setRotationStyle("${sanitize(node.style)}");\n`; break; - case 'motion.setX': // fallthrough - case 'motion.setY': // fallthrough - case 'motion.setXY': { + case StackOpcode.MOTION_X_SET: // fallthrough + case StackOpcode.MOTION_Y_SET: // fallthrough + case StackOpcode.MOTION_XY_SET: { this.descendedIntoModulo = false; - const x = 'x' in node ? this.descendInput(node.x).asNumber() : 'target.x'; - const y = 'y' in node ? this.descendInput(node.y).asNumber() : 'target.y'; + const x = 'x' in node ? this.descendInput(node.x) : 'target.x'; + const y = 'y' in node ? this.descendInput(node.y) : 'target.y'; this.source += `target.setXY(${x}, ${y});\n`; if (this.descendedIntoModulo) { this.source += `if (target.interpolationData) target.interpolationData = null;\n`; } break; } - case 'motion.step': - this.source += `runtime.ext_scratch3_motion._moveSteps(${this.descendInput(node.steps).asNumber()}, target);\n`; + case StackOpcode.MOTION_STEP: + this.source += `runtime.ext_scratch3_motion._moveSteps(${this.descendInput(node.steps)}, target);\n`; break; - case 'noop': + case StackOpcode.NOP: break; - case 'pen.clear': + case StackOpcode.PEN_CLEAR: this.source += `${PEN_EXT}.clear();\n`; break; - case 'pen.down': + case StackOpcode.PEN_DOWN: this.source += `${PEN_EXT}._penDown(target);\n`; break; - case 'pen.changeParam': - this.source += `${PEN_EXT}._setOrChangeColorParam(${this.descendInput(node.param).asString()}, ${this.descendInput(node.value).asNumber()}, ${PEN_STATE}, true);\n`; + case StackOpcode.PEN_COLOR_PARAM_CHANGE: + this.source += `${PEN_EXT}._setOrChangeColorParam(${this.descendInput(node.param)}, ${this.descendInput(node.value)}, ${PEN_STATE}, true);\n`; break; - case 'pen.changeSize': - this.source += `${PEN_EXT}._changePenSizeBy(${this.descendInput(node.size).asNumber()}, target);\n`; + case StackOpcode.PEN_SIZE_CHANGE: + this.source += `${PEN_EXT}._changePenSizeBy(${this.descendInput(node.size)}, target);\n`; break; - case 'pen.legacyChangeHue': - this.source += `${PEN_EXT}._changePenHueBy(${this.descendInput(node.hue).asNumber()}, target);\n`; + case StackOpcode.PEN_COLOR_HUE_CHANGE_LEGACY: + this.source += `${PEN_EXT}._changePenHueBy(${this.descendInput(node.hue)}, target);\n`; break; - case 'pen.legacyChangeShade': - this.source += `${PEN_EXT}._changePenShadeBy(${this.descendInput(node.shade).asNumber()}, target);\n`; + case StackOpcode.PEN_COLOR_SHADE_CHANGE_LEGACY: + this.source += `${PEN_EXT}._changePenShadeBy(${this.descendInput(node.shade)}, target);\n`; break; - case 'pen.legacySetHue': - this.source += `${PEN_EXT}._setPenHueToNumber(${this.descendInput(node.hue).asNumber()}, target);\n`; + case StackOpcode.PEN_COLOR_HUE_SET_LEGACY: + this.source += `${PEN_EXT}._setPenHueToNumber(${this.descendInput(node.hue)}, target);\n`; break; - case 'pen.legacySetShade': - this.source += `${PEN_EXT}._setPenShadeToNumber(${this.descendInput(node.shade).asNumber()}, target);\n`; + case StackOpcode.PEN_COLOR_SHADE_SET_LEGACY: + this.source += `${PEN_EXT}._setPenShadeToNumber(${this.descendInput(node.shade)}, target);\n`; break; - case 'pen.setColor': - this.source += `${PEN_EXT}._setPenColorToColor(${this.descendInput(node.color).asColor()}, target);\n`; + case StackOpcode.PEN_COLOR_SET: + this.source += `${PEN_EXT}._setPenColorToColor(${this.descendInput(node.color)}, target);\n`; break; - case 'pen.setParam': - this.source += `${PEN_EXT}._setOrChangeColorParam(${this.descendInput(node.param).asString()}, ${this.descendInput(node.value).asNumber()}, ${PEN_STATE}, false);\n`; + case StackOpcode.PEN_COLOR_PARAM_SET: + this.source += `${PEN_EXT}._setOrChangeColorParam(${this.descendInput(node.param)}, ${this.descendInput(node.value)}, ${PEN_STATE}, false);\n`; break; - case 'pen.setSize': - this.source += `${PEN_EXT}._setPenSizeTo(${this.descendInput(node.size).asNumber()}, target);\n`; + case StackOpcode.PEN_SIZE_SET: + this.source += `${PEN_EXT}._setPenSizeTo(${this.descendInput(node.size)}, target);\n`; break; - case 'pen.stamp': + case StackOpcode.PEN_STAMP: this.source += `${PEN_EXT}._stamp(target);\n`; break; - case 'pen.up': + case StackOpcode.PEN_UP: this.source += `${PEN_EXT}._penUp(target);\n`; break; - case 'procedures.call': { + case StackOpcode.PROCEDURE_CALL: { const procedureCode = node.code; const procedureVariant = node.variant; const procedureData = this.ir.procedures[procedureVariant]; @@ -1110,114 +830,129 @@ class JSGenerator { // TODO still need to evaluate arguments break; } - const yieldForRecursion = !this.isWarp && procedureCode === this.script.procedureCode; if (yieldForRecursion) { + // Direct yields. 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)}"](`; const args = []; for (const input of node.arguments) { - args.push(this.descendInput(input).asSafe()); + args.push(this.descendInput(input)); } this.source += args.join(','); - this.source += ');\n'; - - this.resetVariableInputs(); + this.source += `);\n`; break; } - case 'procedures.return': - this.stopScriptAndReturn(this.descendInput(node.value).asSafe()); + case StackOpcode.PROCEDURE_RETURN: + this.stopScriptAndReturn(this.descendInput(node.value)); break; - case 'timer.reset': + case StackOpcode.SENSING_TIMER_RESET: this.source += 'runtime.ioDevices.clock.resetProjectTimer();\n'; break; - case 'tw.debugger': + case StackOpcode.DEBUGGER: this.source += 'debugger;\n'; break; - case 'var.hide': + case StackOpcode.VAR_HIDE: this.source += `runtime.monitorBlocks.changeBlock({ id: "${sanitize(node.variable.id)}", element: "checkbox", value: false }, runtime);\n`; break; - case 'var.set': { - const variable = this.descendVariable(node.variable); - const value = this.descendInput(node.value); - variable.setInput(value); - this.source += `${variable.source} = ${value.asSafe()};\n`; + case StackOpcode.VAR_SET: { + const varReference = this.referenceVariable(node.variable); + this.source += `${varReference}.value = ${this.descendInput(node.value)};\n`; if (node.variable.isCloud) { - this.source += `runtime.ioDevices.cloud.requestUpdateVariable("${sanitize(node.variable.name)}", ${variable.source});\n`; + this.source += `runtime.ioDevices.cloud.requestUpdateVariable("${sanitize(node.variable.name)}", ${varReference}.value);\n`; } break; } - case 'var.show': + case StackOpcode.VAR_SHOW: this.source += `runtime.monitorBlocks.changeBlock({ id: "${sanitize(node.variable.id)}", element: "checkbox", value: true }, runtime);\n`; break; - case 'visualReport': { + case StackOpcode.VISUAL_REPORT: { const value = this.localVariables.next(); - this.source += `const ${value} = ${this.descendInput(node.input).asUnknown()};`; + this.source += `const ${value} = ${this.descendInput(node.input)};`; // blocks like legacy no-ops can return a literal `undefined` this.source += `if (${value} !== undefined) runtime.visualReport("${sanitize(this.script.topBlockId)}", ${value});\n`; break; } default: - log.warn(`JS: Unknown stacked block: ${node.kind}`, node); - throw new Error(`JS: Unknown stacked block: ${node.kind}`); + log.warn(`JS: Unknown stacked block: ${block.opcode}`, node); + throw new Error(`JS: Unknown stacked block: ${block.opcode}`); } } + /** + * Compiles a reference to a target. + * @param {IntermediateInput} input The target reference. Must be a string. + * @returns {string} The compiled target reference + */ + descendTargetReference (input) { + if (!input.isAlwaysType(InputType.STRING)) { + throw new Error(`JS: Object references must be strings!`); + } + if (input.isConstant('_stage_')) return 'stage'; + return this.evaluateOnce(`runtime.getSpriteTargetByName(${this.descendInput(input)})`); + } + /** * Compile a Record of input objects into a safe JS string. - * @param {Record} inputs + * @param {Record} inputs * @returns {string} */ descendInputRecord (inputs) { let result = '{'; for (const name of Object.keys(inputs)) { const node = inputs[name]; - result += `"${sanitize(name)}":${this.descendInput(node).asSafe()},`; + result += `"${sanitize(name)}":${this.descendInput(node)},`; } result += '}'; return result; } - resetVariableInputs () { - this.variableInputs = {}; - } - - descendStack (nodes, frame) { + /** + * @param {IntermediateStack} stack + * @param {Frame} frame + */ + descendStack (stack, frame) { // Entering a stack -- all bets are off. // TODO: allow if/else to inherit values - this.resetVariableInputs(); this.pushFrame(frame); - for (let i = 0; i < nodes.length; i++) { - frame.isLastBlock = i === nodes.length - 1; - this.descendStackedBlock(nodes[i]); + for (let i = 0; i < stack.blocks.length; i++) { + frame.isLastBlock = i === stack.blocks.length - 1; + this.descendStackedBlock(stack.blocks[i]); } // Leaving a stack -- any assumptions made in the current stack do not apply outside of it // TODO: in if/else this might create an extra unused object - this.resetVariableInputs(); this.popFrame(); } - descendVariable (variable) { - if (Object.prototype.hasOwnProperty.call(this.variableInputs, variable.id)) { - return this.variableInputs[variable.id]; - } - const input = new VariableInput(`${this.referenceVariable(variable)}.value`); - this.variableInputs[variable.id] = input; - return input; + /** + * @param {*} node + * @returns {string} + */ + 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})`; } + /** + * @param {*} variable + * @returns {string} + */ referenceVariable (variable) { if (variable.scope === 'target') { return this.evaluateOnce(`target.variables["${sanitize(variable.id)}"]`); @@ -1225,15 +960,12 @@ 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})`; - } - + /** + * @param {string} source + * @returns {string} + */ evaluateOnce (source) { - if (Object.prototype.hasOwnProperty.call(this._setupVariables, source)) { + if (this._setupVariables.hasOwnProperty(source)) { return this._setupVariables[source]; } const variable = this._setupVariablesPool.next(); @@ -1252,25 +984,6 @@ 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(); @@ -1306,7 +1019,6 @@ class JSGenerator { throw new Error('Script yielded but is not marked as yielding.'); } // Control may have been yielded to another script -- all bets are off. - this.resetVariableInputs(); } /** @@ -1316,14 +1028,9 @@ class JSGenerator { this.source += 'runtime.requestRedraw();\n'; } - safeConstantInput (value) { - const unsafe = typeof value === 'string' && this.namesOfCostumesAndSounds.has(value); - return new ConstantInput(value, !unsafe); - } - /** * Generate a call into the compatibility layer. - * @param {*} node The "compat" kind node to generate from. + * @param {*} node The node of the block to generate from. * @param {boolean} setFlags Whether flags should be set describing how this function was processed. * @param {string|null} [frameName] Name of the stack frame variable, if any * @returns {string} The JS of the call. @@ -1335,7 +1042,7 @@ class JSGenerator { for (const inputName of Object.keys(node.inputs)) { const input = node.inputs[inputName]; - const compiledInput = this.descendInput(input).asSafe(); + const compiledInput = this.descendInput(input); result += `"${sanitize(inputName)}":${compiledInput},`; } for (const fieldName of Object.keys(node.fields)) { @@ -1364,6 +1071,25 @@ class JSGenerator { return name; } + 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(); + } + } + /** * Generate the JS to pass into eval() based on the current state of the compiler. * @returns {string} JS to pass into eval() @@ -1431,26 +1157,6 @@ class JSGenerator { } } -// For extensions. -JSGenerator.unstable_exports = { - TYPE_NUMBER, - TYPE_STRING, - TYPE_BOOLEAN, - TYPE_UNKNOWN, - TYPE_NUMBER_NAN, - factoryNameVariablePool, - functionNameVariablePool, - generatorNameVariablePool, - VariablePool, - PEN_EXT, - PEN_STATE, - TypedInput, - ConstantInput, - VariableInput, - Frame, - sanitize -}; - // Test hook used by automated snapshot testing. JSGenerator.testingApparatus = null; diff --git a/src/compiler/variable-pool.js b/src/compiler/variable-pool.js index 11f50594021..47f463e97b3 100644 --- a/src/compiler/variable-pool.js +++ b/src/compiler/variable-pool.js @@ -1,3 +1,5 @@ +// @ts-check + class VariablePool { /** * @param {string} prefix The prefix at the start of the variable name. diff --git a/test/snapshot/__snapshots__/order-library-reverse.sb3.tw-snapshot b/test/snapshot/__snapshots__/order-library-reverse.sb3.tw-snapshot index 91457816d33..96c7ced79c1 100644 --- a/test/snapshot/__snapshots__/order-library-reverse.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/order-library-reverse.sb3.tw-snapshot @@ -17,7 +17,7 @@ yield; } thread.timer = null; b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 1)) { +if ((b0.value === 1)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, false, false, "]4hbk*5ix]V00h|!x1oy", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 1 != " + ("" + b0.value)),}, b2, false, false, "H=x@7SpNJeX|!}8x5y4,", null); @@ -75,7 +75,7 @@ yield; } thread.timer = null; b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 2)) { +if ((b0.value === 2)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, false, false, "0i[-T:vYTt=bi47@byUE", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 2 != " + ("" + b0.value)),}, b2, false, false, "Coc1aZ;L9M-RyEt`syps", null); diff --git a/test/snapshot/__snapshots__/order-library.sb3.tw-snapshot b/test/snapshot/__snapshots__/order-library.sb3.tw-snapshot index 2d10b51e1ce..5145078f7aa 100644 --- a/test/snapshot/__snapshots__/order-library.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/order-library.sb3.tw-snapshot @@ -51,7 +51,7 @@ yield; } thread.timer = null; b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 1)) { +if ((b0.value === 1)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, false, false, "RSQ{nVCc)6E)(`KlnFCF", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 1 != " + ("" + b0.value)),}, b2, false, false, "8k^j~`c^|YO@hkFd?~2d", null); @@ -75,7 +75,7 @@ yield; } thread.timer = null; b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 2)) { +if ((b0.value === 2)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, false, false, "KP?op(=Vg2#;@]!,C#.~", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 2 != " + ("" + b0.value)),}, b2, false, false, "=]|}L~4uQXTNtwJKw_;R", null); diff --git a/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot index 106f6edd6d0..417094fef11 100644 --- a/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot @@ -9,61 +9,61 @@ yield* executeInCompatibilityLayer({"MESSAGE":"plan 20",}, b0, false, false, "v" if ((("" + (0 * Infinity)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "6", null); } -if (compareEqual((((0 * Infinity) || 0) * 1), 0)) { +if (((((0 * Infinity) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "#", null); } if ((("" + ((Math.acos(1.01) * 180) / Math.PI)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "9", null); } -if (compareEqual(((((Math.acos(1.01) * 180) / Math.PI) || 0) * 1), 0)) { +if ((((((Math.acos(1.01) * 180) / Math.PI) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "(", null); } if ((("" + ((Math.asin(1.01) * 180) / Math.PI)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "*", null); } -if (compareEqual(((((Math.asin(1.01) * 180) / Math.PI) || 0) * 1), 0)) { +if ((((((Math.asin(1.01) * 180) / Math.PI) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, ",", null); } if ((("" + (0 / 0)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, ".", null); } -if (compareEqual((((0 / 0) || 0) * 1), 0)) { +if (((((0 / 0) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, ":", null); } if ((("" + Math.sqrt(-1)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "?", null); } -if (compareEqual(((Math.sqrt(-1) || 0) * 1), 0)) { +if ((((Math.sqrt(-1) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "@", null); } if ((("" + mod(0, 0)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "]", null); } -if (compareEqual(((mod(0, 0) || 0) * 1), 0)) { +if ((((mod(0, 0) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "_", null); } if ((("" + Math.log(-1)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "{", null); } -if (compareEqual(((Math.log(-1) || 0) * 1), 0)) { +if ((((Math.log(-1) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "}", null); } if ((("" + (Math.log(-1) / Math.LN10)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aa", null); } -if (compareEqual((((Math.log(-1) / Math.LN10) || 0) * 1), 0)) { +if (((((Math.log(-1) / Math.LN10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ac", null); } -if (compareEqual((((Math.round(Math.sin((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1), 0)) { +if (((((Math.round(Math.sin((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ae", null); } -if (compareEqual((((Math.round(Math.cos((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1), 0)) { +if (((((Math.round(Math.cos((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ag", null); } -if (compareEqual(((tan(((1 / 0) || 0)) || 0) * 1), 0)) { +if ((((tan(((1 / 0) || 0)) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ai", null); } -if (compareEqual(((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || 0) * 1), 0)) { +if ((((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ak", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "7", null); 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 c133a6cb76f..b7f47584e4f 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 @@ -11,7 +11,7 @@ target.setSize(96); b1.value = 0; while (!(100 === Math.round(target.size))) { b1.value = ((+b1.value || 0) + 1); -target.setSize(target.size + ((((100 - Math.round(target.size)) || 0) / 10) || 0)); +target.setSize(target.size + ((100 - Math.round(target.size)) / 10)); yield; } if (((+b1.value || 0) === 20)) { 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 bccad8ff318..070b355da4a 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 @@ -8,7 +8,7 @@ const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "c", null); b1.value = "#22388a"; -if ((("" + b1.value).toLowerCase() === "#22388a".toLowerCase())) { +if ((b1.value.toLowerCase() === "#22388a".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "f", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "e", null); 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 66d03cf758d..741ccca8ae7 100644 --- a/test/snapshot/__snapshots__/tw-comparison-matrix-inline.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-comparison-matrix-inline.sb3.tw-snapshot @@ -123,13 +123,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 38: 0 should be = 🎉",}, b if (!(("" + ("0".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 39: 0 should be > 🎉",}, b0, false, false, "uq", null); } -if (!(("" + compareLessThan(0, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("0".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 40: 0 should be < ",}, b0, false, false, "us", null); } -if (!(("" + compareEqual(0, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("0".toLowerCase() === "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 41: 0 should be = ",}, b0, false, false, "uu", null); } -if (!(("" + compareGreaterThan(0, "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("0".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 42: 0 should be > ",}, b0, false, false, "uw", null); } if (!(("" + (0 < 0)).toLowerCase() === "false".toLowerCase())) { @@ -249,13 +249,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 80: 0.0 should be = 🎉",}, if (!(("" + ("0.0".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 81: 0.0 should be > 🎉",}, b0, false, false, "vn", null); } -if (!(("" + compareLessThan("0.0", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("0.0".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 82: 0.0 should be < ",}, b0, false, false, "vp", null); } -if (!(("" + compareEqual("0.0", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("0.0".toLowerCase() === "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 83: 0.0 should be = ",}, b0, false, false, "vr", null); } -if (!(("" + compareGreaterThan("0.0", "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("0.0".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 84: 0.0 should be > ",}, b0, false, false, "vt", null); } if (!(("" + (1.23 < 0)).toLowerCase() === "false".toLowerCase())) { @@ -324,7 +324,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 105: 1.23 should be > -1",}, if (!(("" + ("1.23".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 106: 1.23 should be < true",}, b0, false, false, "v#", null); } -if (!(("" + ("1.23".toLowerCase() === "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1.23 === 1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 107: 1.23 should be = true",}, b0, false, false, "v(", null); } if (!(("" + ("1.23".toLowerCase() > "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -333,7 +333,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 108: 1.23 should be > true", if (!(("" + ("1.23".toLowerCase() < "false".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 109: 1.23 should be < false",}, b0, false, false, "v,", null); } -if (!(("" + ("1.23".toLowerCase() === "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 110: 1.23 should be = false",}, b0, false, false, "v.", null); } if (!(("" + ("1.23".toLowerCase() > "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -342,7 +342,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 111: 1.23 should be > false" if (!(("" + ("1.23".toLowerCase() < "NaN".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 112: 1.23 should be < NaN",}, b0, false, false, "v=", null); } -if (!(("" + ("1.23".toLowerCase() === "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 113: 1.23 should be = NaN",}, b0, false, false, "v@", null); } if (!(("" + ("1.23".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -360,7 +360,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 117: 1.23 should be > Infini if (!(("" + ("1.23".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 118: 1.23 should be < banana",}, b0, false, false, "wa", null); } -if (!(("" + ("1.23".toLowerCase() === "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 119: 1.23 should be = banana",}, b0, false, false, "wc", null); } if (!(("" + ("1.23".toLowerCase() > "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -369,19 +369,19 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 120: 1.23 should be > banana if (!(("" + ("1.23".toLowerCase() < "🎉".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 121: 1.23 should be < 🎉",}, b0, false, false, "wg", null); } -if (!(("" + ("1.23".toLowerCase() === "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 122: 1.23 should be = 🎉",}, b0, false, false, "wi", null); } if (!(("" + ("1.23".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 123: 1.23 should be > 🎉",}, b0, false, false, "wk", null); } -if (!(("" + compareLessThan(1.23, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("1.23".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 124: 1.23 should be < ",}, b0, false, false, "wm", null); } if (!(("" + (1.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 125: 1.23 should be = ",}, b0, false, false, "wo", null); } -if (!(("" + compareGreaterThan(1.23, "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("1.23".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 126: 1.23 should be > ",}, b0, false, false, "wq", null); } if (!(("" + (0.23 < 0)).toLowerCase() === "false".toLowerCase())) { @@ -450,7 +450,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 147: .23 should be > -1",}, if (!(("" + (".23".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 148: .23 should be < true",}, b0, false, false, "w8", null); } -if (!(("" + (".23".toLowerCase() === "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 149: .23 should be = true",}, b0, false, false, "w!", null); } if (!(("" + (".23".toLowerCase() > "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -459,7 +459,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 150: .23 should be > true",} if (!(("" + (".23".toLowerCase() < "false".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 151: .23 should be < false",}, b0, false, false, "w)", null); } -if (!(("" + (".23".toLowerCase() === "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 152: .23 should be = false",}, b0, false, false, "w+", null); } if (!(("" + (".23".toLowerCase() > "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -468,7 +468,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 153: .23 should be > false", if (!(("" + (".23".toLowerCase() < "NaN".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 154: .23 should be < NaN",}, b0, false, false, "w/", null); } -if (!(("" + (".23".toLowerCase() === "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 155: .23 should be = NaN",}, b0, false, false, "w;", null); } if (!(("" + (".23".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -486,7 +486,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 159: .23 should be > Infinit if (!(("" + (".23".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 160: .23 should be < banana",}, b0, false, false, "w|", null); } -if (!(("" + (".23".toLowerCase() === "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 161: .23 should be = banana",}, b0, false, false, "w~", null); } if (!(("" + (".23".toLowerCase() > "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -495,19 +495,19 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 162: .23 should be > banana" if (!(("" + (".23".toLowerCase() < "🎉".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 163: .23 should be < 🎉",}, b0, false, false, "xd", null); } -if (!(("" + (".23".toLowerCase() === "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 164: .23 should be = 🎉",}, b0, false, false, "xf", null); } if (!(("" + (".23".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 165: .23 should be > 🎉",}, b0, false, false, "xh", null); } -if (!(("" + compareLessThan(".23", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (".23".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 166: .23 should be < ",}, b0, false, false, "xj", null); } -if (!(("" + compareEqual(".23", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 167: .23 should be = ",}, b0, false, false, "xl", null); } -if (!(("" + compareGreaterThan(".23", "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + (".23".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 168: .23 should be > ",}, b0, false, false, "xn", null); } if (!(("" + (0.123 < 0)).toLowerCase() === "false".toLowerCase())) { @@ -576,7 +576,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 189: 0.123 should be > -1",} if (!(("" + ("0.123".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 190: 0.123 should be < true",}, b0, false, false, "x5", null); } -if (!(("" + ("0.123".toLowerCase() === "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.123 === 1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 191: 0.123 should be = true",}, b0, false, false, "x7", null); } if (!(("" + ("0.123".toLowerCase() > "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -585,7 +585,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 192: 0.123 should be > true" if (!(("" + ("0.123".toLowerCase() < "false".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 193: 0.123 should be < false",}, b0, false, false, "x#", null); } -if (!(("" + ("0.123".toLowerCase() === "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.123 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 194: 0.123 should be = false",}, b0, false, false, "x(", null); } if (!(("" + ("0.123".toLowerCase() > "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -594,7 +594,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 195: 0.123 should be > false if (!(("" + ("0.123".toLowerCase() < "NaN".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 196: 0.123 should be < NaN",}, b0, false, false, "x,", null); } -if (!(("" + ("0.123".toLowerCase() === "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.123 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 197: 0.123 should be = NaN",}, b0, false, false, "x.", null); } if (!(("" + ("0.123".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -612,7 +612,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 201: 0.123 should be > Infin if (!(("" + ("0.123".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 202: 0.123 should be < banana",}, b0, false, false, "x_", null); } -if (!(("" + ("0.123".toLowerCase() === "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.123 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 203: 0.123 should be = banana",}, b0, false, false, "x{", null); } if (!(("" + ("0.123".toLowerCase() > "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -621,19 +621,19 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 204: 0.123 should be > banan if (!(("" + ("0.123".toLowerCase() < "🎉".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 205: 0.123 should be < 🎉",}, b0, false, false, "ya", null); } -if (!(("" + ("0.123".toLowerCase() === "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.123 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 206: 0.123 should be = 🎉",}, b0, false, false, "yc", null); } if (!(("" + ("0.123".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 207: 0.123 should be > 🎉",}, b0, false, false, "ye", null); } -if (!(("" + compareLessThan(0.123, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("0.123".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 208: 0.123 should be < ",}, b0, false, false, "yg", null); } if (!(("" + (0.123 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 209: 0.123 should be = ",}, b0, false, false, "yi", null); } -if (!(("" + compareGreaterThan(0.123, "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("0.123".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 210: 0.123 should be > ",}, b0, false, false, "yk", null); } if (!(("" + (-0 < 0)).toLowerCase() === "false".toLowerCase())) { @@ -753,13 +753,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 248: -0 should be = 🎉",}, if (!(("" + ("-0".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 249: -0 should be > 🎉",}, b0, false, false, "zb", null); } -if (!(("" + compareLessThan("-0", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("-0".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 250: -0 should be < ",}, b0, false, false, "zd", null); } -if (!(("" + compareEqual("-0", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("-0".toLowerCase() === "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 251: -0 should be = ",}, b0, false, false, "zf", null); } -if (!(("" + compareGreaterThan("-0", "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("-0".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 252: -0 should be > ",}, b0, false, false, "zh", null); } if (!(("" + (-1 < 0)).toLowerCase() === "true".toLowerCase())) { @@ -828,7 +828,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 273: -1 should be > -1",}, b if (!(("" + ("-1".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 274: -1 should be < true",}, b0, false, false, "zZ", null); } -if (!(("" + ("-1".toLowerCase() === "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (-1 === 1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 275: -1 should be = true",}, b0, false, false, "z1", null); } if (!(("" + ("-1".toLowerCase() > "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -837,7 +837,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 276: -1 should be > true",}, if (!(("" + ("-1".toLowerCase() < "false".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 277: -1 should be < false",}, b0, false, false, "z5", null); } -if (!(("" + ("-1".toLowerCase() === "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (-1 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 278: -1 should be = false",}, b0, false, false, "z7", null); } if (!(("" + ("-1".toLowerCase() > "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -846,7 +846,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 279: -1 should be > false",} if (!(("" + ("-1".toLowerCase() < "NaN".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 280: -1 should be < NaN",}, b0, false, false, "z#", null); } -if (!(("" + ("-1".toLowerCase() === "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (-1 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 281: -1 should be = NaN",}, b0, false, false, "z(", null); } if (!(("" + ("-1".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -864,7 +864,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 285: -1 should be > Infinity if (!(("" + ("-1".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 286: -1 should be < banana",}, b0, false, false, "z=", null); } -if (!(("" + ("-1".toLowerCase() === "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (-1 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 287: -1 should be = banana",}, b0, false, false, "z@", null); } if (!(("" + ("-1".toLowerCase() > "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -873,19 +873,19 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 288: -1 should be > banana", if (!(("" + ("-1".toLowerCase() < "🎉".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 289: -1 should be < 🎉",}, b0, false, false, "z_", null); } -if (!(("" + ("-1".toLowerCase() === "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (-1 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 290: -1 should be = 🎉",}, b0, false, false, "z{", null); } if (!(("" + ("-1".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 291: -1 should be > 🎉",}, b0, false, false, "z}", null); } -if (!(("" + compareLessThan(-1, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("-1".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 292: -1 should be < ",}, b0, false, false, "Aa", null); } if (!(("" + (-1 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 293: -1 should be = ",}, b0, false, false, "Ac", null); } -if (!(("" + compareGreaterThan(-1, "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("-1".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 294: -1 should be > ",}, b0, false, false, "Ae", null); } if (!(("" + ("true".toLowerCase() < "0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -909,7 +909,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 300: true should be > 0.0",} if (!(("" + ("true".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 301: true should be < 1.23",}, b0, false, false, "As", null); } -if (!(("" + ("true".toLowerCase() === "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 302: true should be = 1.23",}, b0, false, false, "Au", null); } if (!(("" + ("true".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -918,7 +918,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 303: true should be > 1.23", if (!(("" + ("true".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 304: true should be < .23",}, b0, false, false, "Ay", null); } -if (!(("" + ("true".toLowerCase() === ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 305: true should be = .23",}, b0, false, false, "AA", null); } if (!(("" + ("true".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -927,7 +927,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 306: true should be > .23",} if (!(("" + ("true".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 307: true should be < 0.123",}, b0, false, false, "AE", null); } -if (!(("" + ("true".toLowerCase() === "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 308: true should be = 0.123",}, b0, false, false, "AG", null); } if (!(("" + ("true".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -945,7 +945,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 312: true should be > -0",}, if (!(("" + ("true".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 313: true should be < -1",}, b0, false, false, "AQ", null); } -if (!(("" + ("true".toLowerCase() === "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 314: true should be = -1",}, b0, false, false, "AS", null); } if (!(("" + ("true".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -981,7 +981,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 324: true should be > NaN",} if (!(("" + ("true".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 325: true should be < Infinity",}, b0, false, false, "A)", null); } -if (!(("" + ("true".toLowerCase() === "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 326: true should be = Infinity",}, b0, false, false, "A+", null); } if (!(("" + ("true".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1035,7 +1035,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 342: false should be > 0.0", if (!(("" + ("false".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 343: false should be < 1.23",}, b0, false, false, "Bp", null); } -if (!(("" + ("false".toLowerCase() === "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 344: false should be = 1.23",}, b0, false, false, "Br", null); } if (!(("" + ("false".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1044,7 +1044,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 345: false should be > 1.23" if (!(("" + ("false".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 346: false should be < .23",}, b0, false, false, "Bv", null); } -if (!(("" + ("false".toLowerCase() === ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 347: false should be = .23",}, b0, false, false, "Bx", null); } if (!(("" + ("false".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1053,7 +1053,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 348: false should be > .23", if (!(("" + ("false".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 349: false should be < 0.123",}, b0, false, false, "BB", null); } -if (!(("" + ("false".toLowerCase() === "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 350: false should be = 0.123",}, b0, false, false, "BD", null); } if (!(("" + ("false".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1071,7 +1071,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 354: false should be > -0",} if (!(("" + ("false".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 355: false should be < -1",}, b0, false, false, "BN", null); } -if (!(("" + ("false".toLowerCase() === "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 356: false should be = -1",}, b0, false, false, "BP", null); } if (!(("" + ("false".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1107,7 +1107,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 366: false should be > NaN", if (!(("" + ("false".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 367: false should be < Infinity",}, b0, false, false, "B#", null); } -if (!(("" + ("false".toLowerCase() === "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 368: false should be = Infinity",}, b0, false, false, "B(", null); } if (!(("" + ("false".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -1161,7 +1161,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 384: NaN should be > 0.0",}, if (!(("" + ("NaN".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 385: NaN should be < 1.23",}, b0, false, false, "Cm", null); } -if (!(("" + ("NaN".toLowerCase() === "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 386: NaN should be = 1.23",}, b0, false, false, "Co", null); } if (!(("" + ("NaN".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1170,7 +1170,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 387: NaN should be > 1.23",} if (!(("" + ("NaN".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 388: NaN should be < .23",}, b0, false, false, "Cs", null); } -if (!(("" + ("NaN".toLowerCase() === ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 389: NaN should be = .23",}, b0, false, false, "Cu", null); } if (!(("" + ("NaN".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1179,7 +1179,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 390: NaN should be > .23",}, if (!(("" + ("NaN".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 391: NaN should be < 0.123",}, b0, false, false, "Cy", null); } -if (!(("" + ("NaN".toLowerCase() === "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 392: NaN should be = 0.123",}, b0, false, false, "CA", null); } if (!(("" + ("NaN".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1197,7 +1197,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 396: NaN should be > -0",}, if (!(("" + ("NaN".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 397: NaN should be < -1",}, b0, false, false, "CK", null); } -if (!(("" + ("NaN".toLowerCase() === "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 398: NaN should be = -1",}, b0, false, false, "CM", null); } if (!(("" + ("NaN".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1233,7 +1233,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 408: NaN should be > NaN",}, if (!(("" + ("NaN".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 409: NaN should be < Infinity",}, b0, false, false, "C8", null); } -if (!(("" + ("NaN".toLowerCase() === "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 410: NaN should be = Infinity",}, b0, false, false, "C!", null); } if (!(("" + ("NaN".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1332,7 +1332,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 441: Infinity should be > -1 if (!(("" + ("Infinity".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 442: Infinity should be < true",}, b0, false, false, "DN", null); } -if (!(("" + ("Infinity".toLowerCase() === "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (Infinity === 1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 443: Infinity should be = true",}, b0, false, false, "DP", null); } if (!(("" + ("Infinity".toLowerCase() > "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -1341,7 +1341,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 444: Infinity should be > tr if (!(("" + ("Infinity".toLowerCase() < "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 445: Infinity should be < false",}, b0, false, false, "DT", null); } -if (!(("" + ("Infinity".toLowerCase() === "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (Infinity === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 446: Infinity should be = false",}, b0, false, false, "DV", null); } if (!(("" + ("Infinity".toLowerCase() > "false".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1350,7 +1350,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 447: Infinity should be > fa if (!(("" + ("Infinity".toLowerCase() < "NaN".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 448: Infinity should be < NaN",}, b0, false, false, "DZ", null); } -if (!(("" + ("Infinity".toLowerCase() === "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (Infinity === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 449: Infinity should be = NaN",}, b0, false, false, "D1", null); } if (!(("" + ("Infinity".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -1368,7 +1368,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 453: Infinity should be > In if (!(("" + ("Infinity".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 454: Infinity should be < banana",}, b0, false, false, "D#", null); } -if (!(("" + ("Infinity".toLowerCase() === "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (Infinity === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 455: Infinity should be = banana",}, b0, false, false, "D(", null); } if (!(("" + ("Infinity".toLowerCase() > "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1377,19 +1377,19 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 456: Infinity should be > ba if (!(("" + ("Infinity".toLowerCase() < "🎉".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 457: Infinity should be < 🎉",}, b0, false, false, "D,", null); } -if (!(("" + ("Infinity".toLowerCase() === "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (Infinity === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 458: Infinity should be = 🎉",}, b0, false, false, "D.", null); } if (!(("" + ("Infinity".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 459: Infinity should be > 🎉",}, b0, false, false, "D:", null); } -if (!(("" + compareLessThan(Infinity, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("Infinity".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 460: Infinity should be < ",}, b0, false, false, "D=", null); } if (!(("" + (Infinity === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 461: Infinity should be = ",}, b0, false, false, "D@", null); } -if (!(("" + compareGreaterThan(Infinity, "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("Infinity".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 462: Infinity should be > ",}, b0, false, false, "D]", null); } if (!(("" + ("banana".toLowerCase() < "0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -1413,7 +1413,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 468: banana should be > 0.0" if (!(("" + ("banana".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 469: banana should be < 1.23",}, b0, false, false, "Eg", null); } -if (!(("" + ("banana".toLowerCase() === "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 470: banana should be = 1.23",}, b0, false, false, "Ei", null); } if (!(("" + ("banana".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1422,7 +1422,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 471: banana should be > 1.23 if (!(("" + ("banana".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 472: banana should be < .23",}, b0, false, false, "Em", null); } -if (!(("" + ("banana".toLowerCase() === ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 473: banana should be = .23",}, b0, false, false, "Eo", null); } if (!(("" + ("banana".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1431,7 +1431,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 474: banana should be > .23" if (!(("" + ("banana".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 475: banana should be < 0.123",}, b0, false, false, "Es", null); } -if (!(("" + ("banana".toLowerCase() === "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 476: banana should be = 0.123",}, b0, false, false, "Eu", null); } if (!(("" + ("banana".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1449,7 +1449,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 480: banana should be > -0", if (!(("" + ("banana".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 481: banana should be < -1",}, b0, false, false, "EE", null); } -if (!(("" + ("banana".toLowerCase() === "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 482: banana should be = -1",}, b0, false, false, "EG", null); } if (!(("" + ("banana".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1485,7 +1485,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 492: banana should be > NaN" if (!(("" + ("banana".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 493: banana should be < Infinity",}, b0, false, false, "E2", null); } -if (!(("" + ("banana".toLowerCase() === "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 494: banana should be = Infinity",}, b0, false, false, "E4", null); } if (!(("" + ("banana".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -1539,7 +1539,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 510: 🎉 should be > 0.0",} if (!(("" + ("🎉".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 511: 🎉 should be < 1.23",}, b0, false, false, "Fd", null); } -if (!(("" + ("🎉".toLowerCase() === "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 512: 🎉 should be = 1.23",}, b0, false, false, "Ff", null); } if (!(("" + ("🎉".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1548,7 +1548,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 513: 🎉 should be > 1.23", if (!(("" + ("🎉".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 514: 🎉 should be < .23",}, b0, false, false, "Fj", null); } -if (!(("" + ("🎉".toLowerCase() === ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 515: 🎉 should be = .23",}, b0, false, false, "Fl", null); } if (!(("" + ("🎉".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1557,7 +1557,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 516: 🎉 should be > .23",} if (!(("" + ("🎉".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 517: 🎉 should be < 0.123",}, b0, false, false, "Fp", null); } -if (!(("" + ("🎉".toLowerCase() === "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 518: 🎉 should be = 0.123",}, b0, false, false, "Fr", null); } if (!(("" + ("🎉".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1575,7 +1575,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 522: 🎉 should be > -0",}, if (!(("" + ("🎉".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 523: 🎉 should be < -1",}, b0, false, false, "FB", null); } -if (!(("" + ("🎉".toLowerCase() === "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 524: 🎉 should be = -1",}, b0, false, false, "FD", null); } if (!(("" + ("🎉".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1611,7 +1611,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 534: 🎉 should be > NaN",} if (!(("" + ("🎉".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 535: 🎉 should be < Infinity",}, b0, false, false, "FZ", null); } -if (!(("" + ("🎉".toLowerCase() === "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 536: 🎉 should be = Infinity",}, b0, false, false, "F1", null); } if (!(("" + ("🎉".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1644,67 +1644,67 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 545: 🎉 should be = ",}, b if (!(("" + ("🎉".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 546: 🎉 should be > ",}, b0, false, false, "F:", null); } -if (!(("" + compareLessThan("", 0)).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "0".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 547: should be < 0",}, b0, false, false, "F=", null); } -if (!(("" + compareEqual("", 0)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() === "0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 548: should be = 0",}, b0, false, false, "F@", null); } -if (!(("" + compareGreaterThan("", 0)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 549: should be > 0",}, b0, false, false, "F]", null); } -if (!(("" + compareLessThan("", "0.0")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "0.0".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 550: should be < 0.0",}, b0, false, false, "F_", null); } -if (!(("" + compareEqual("", "0.0")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() === "0.0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 551: should be = 0.0",}, b0, false, false, "F{", null); } -if (!(("" + compareGreaterThan("", "0.0")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "0.0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 552: should be > 0.0",}, b0, false, false, "F}", null); } -if (!(("" + compareLessThan("", 1.23)).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 553: should be < 1.23",}, b0, false, false, "Ga", null); } if (!(("" + (0 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 554: should be = 1.23",}, b0, false, false, "Gc", null); } -if (!(("" + compareGreaterThan("", 1.23)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 555: should be > 1.23",}, b0, false, false, "Ge", null); } -if (!(("" + compareLessThan("", ".23")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 556: should be < .23",}, b0, false, false, "Gg", null); } -if (!(("" + compareEqual("", ".23")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 557: should be = .23",}, b0, false, false, "Gi", null); } -if (!(("" + compareGreaterThan("", ".23")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 558: should be > .23",}, b0, false, false, "Gk", null); } -if (!(("" + compareLessThan("", 0.123)).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 559: should be < 0.123",}, b0, false, false, "Gm", null); } if (!(("" + (0 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 560: should be = 0.123",}, b0, false, false, "Go", null); } -if (!(("" + compareGreaterThan("", 0.123)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 561: should be > 0.123",}, b0, false, false, "Gq", null); } -if (!(("" + compareLessThan("", "-0")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "-0".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 562: should be < -0",}, b0, false, false, "Gs", null); } -if (!(("" + compareEqual("", "-0")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() === "-0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 563: should be = -0",}, b0, false, false, "Gu", null); } -if (!(("" + compareGreaterThan("", "-0")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "-0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 564: should be > -0",}, b0, false, false, "Gw", null); } -if (!(("" + compareLessThan("", -1)).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 565: should be < -1",}, b0, false, false, "Gy", null); } if (!(("" + (0 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 566: should be = -1",}, b0, false, false, "GA", null); } -if (!(("" + compareGreaterThan("", -1)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 567: should be > -1",}, b0, false, false, "GC", null); } if (!(("" + ("".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1734,13 +1734,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 575: should be = NaN",}, b0 if (!(("" + ("".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 576: should be > NaN",}, b0, false, false, "GU", null); } -if (!(("" + compareLessThan("", Infinity)).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 577: should be < Infinity",}, b0, false, false, "GW", null); } if (!(("" + (0 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 578: should be = Infinity",}, b0, false, false, "GY", null); } -if (!(("" + compareGreaterThan("", Infinity)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 579: should be > Infinity",}, b0, false, false, "G0", null); } if (!(("" + ("".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1761,13 +1761,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 584: should be = 🎉",}, b if (!(("" + ("".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 585: should be > 🎉",}, b0, false, false, "G%", null); } -if (!(("" + compareLessThan("", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 586: should be < ",}, b0, false, false, "G)", null); } -if (!(("" + compareEqual("", "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() === "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 587: should be = ",}, b0, false, false, "G+", null); } -if (!(("" + compareGreaterThan("", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 588: should be > ",}, b0, false, false, "G.", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "G-", null); 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 f97edfc1438..69c59c95f94 100644 --- a/test/snapshot/__snapshots__/tw-comparison-matrix-runtime.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-comparison-matrix-runtime.sb3.tw-snapshot @@ -11,7 +11,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "aZ", n retire(); return; }; }) -// Sprite1 Wrun test +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["mfV;yS}9e:%h5UZ)QyiY"]; const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; @@ -29,15 +29,15 @@ b3.value = 0; for (var a1 = b2.value.length; a1 >= 0.5; a1--) { b3.value = ((+b3.value || 0) + 1); b0.value = ((+b0.value || 0) + 1); -if (!compareEqual(compareGreaterThan(listGet(b2.value, b1.value), (b2.value[((b3.value || 0) | 0) - 1] ?? "")), (b4.value[((b0.value || 0) | 0) - 1] ?? ""))) { -yield* executeInCompatibilityLayer({"MESSAGE":("fail " + (("" + listGet(b2.value, b1.value)) + (" should be > " + ("" + listGet(b2.value, b3.value))))),}, b5, true, false, "]", null); +if (!compareEqual(compareGreaterThan(listGet(b2.value, b1.value), (b2.value[(b3.value | 0) - 1] ?? "")), (b4.value[(b0.value | 0) - 1] ?? ""))) { +yield* executeInCompatibilityLayer({"MESSAGE":("fail " + (("" + listGet(b2.value, b1.value)) + (" should be > " + ("" + (b2.value[(b3.value | 0) - 1] ?? ""))))),}, b5, true, false, "]", null); } b0.value = ((+b0.value || 0) + 1); -if (!compareEqual(compareEqual(listGet(b2.value, b1.value), listGet(b2.value, b3.value)), (b4.value[((b0.value || 0) | 0) - 1] ?? ""))) { +if (!compareEqual(compareEqual(listGet(b2.value, b1.value), listGet(b2.value, b3.value)), (b4.value[(b0.value | 0) - 1] ?? ""))) { yield* executeInCompatibilityLayer({"MESSAGE":("fail " + (("" + listGet(b2.value, b1.value)) + (" should be = " + ("" + listGet(b2.value, b3.value))))),}, b5, true, false, "|", null); } b0.value = ((+b0.value || 0) + 1); -if (!compareEqual(compareLessThan(listGet(b2.value, b1.value), listGet(b2.value, b3.value)), (b4.value[((b0.value || 0) | 0) - 1] ?? ""))) { +if (!compareEqual(compareLessThan(listGet(b2.value, b1.value), listGet(b2.value, b3.value)), (b4.value[(b0.value | 0) - 1] ?? ""))) { yield* executeInCompatibilityLayer({"MESSAGE":("fail " + (("" + listGet(b2.value, b1.value)) + (" should be < " + ("" + listGet(b2.value, b3.value))))),}, b5, true, true, "ab", null); if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;} } @@ -46,7 +46,7 @@ if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;} return ""; }; }) -// Sprite1 Wsetup values +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["n^wm8jw#b24sggt.S^tD"]; return function funXYZ_setup_values () { @@ -105,7 +105,7 @@ b0.value.push((-1 / 0)); b0._monitorUpToDate = false; b0.value.push((0 / 0)); b0._monitorUpToDate = false; -b0.value.push(NaN); +b0.value.push("NaN"); b0._monitorUpToDate = false; b0.value.push("nan"); b0._monitorUpToDate = false; diff --git a/test/snapshot/__snapshots__/tw-custom-report-repeat.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-custom-report-repeat.sb3.tw-snapshot index 060537d67aa..1442da79f1a 100644 --- a/test/snapshot/__snapshots__/tw-custom-report-repeat.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-custom-report-repeat.sb3.tw-snapshot @@ -19,7 +19,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "l", nu retire(); return; }; }) -// Sprite1 Zblock name +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_block_name () { return 40; 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 a63ba8842c5..5f84b8b41da 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 @@ -12,10 +12,10 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "k", n if (compareLessThan(("something".toLowerCase() === "else".toLowerCase()), ("1" + ""))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "m", null); } -if (compareGreaterThan(("something".toLowerCase() === "something".toLowerCase()), (0 + 0))) { +if (((+("something".toLowerCase() === "something".toLowerCase())) > (0 + 0))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "n", null); } -if (compareLessThan(("something".toLowerCase() === "else".toLowerCase()), (1 + 0))) { +if (((+("something".toLowerCase() === "else".toLowerCase())) < (1 + 0))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "o", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "l", null); 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 7c736d4333e..d52a998ab3e 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 @@ -9,13 +9,13 @@ const b2 = target.variables["zShM`!CD?d_|Z,]5X}N6"]; return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, "d", null); b1.value = 2; -if (((+b1.value || 0) === 2)) { +if ((b1.value === 2)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass variable",}, b0, false, false, "k", null); } b2.value = []; b2.value.push(3); b2._monitorUpToDate = false; -if (((+(b2.value[(1 | 0) - 1] ?? "") || 0) === 3)) { +if (((+(b2.value[1 - 1] ?? "") || 0) === 3)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass list",}, b0, false, false, "m", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "l", null); 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 704116bfee2..27d2ced7847 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 @@ -21,7 +21,7 @@ retire(); return; retire(); return; }; }) -// Sprite1 Wno refresh +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); @@ -41,14 +41,14 @@ runtime.ext_scratch3_motion._moveSteps(0, target); return ""; }; }) -// Sprite1 Wruns below with no refresh +// Sprite1 script (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 Whas refresh +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); diff --git a/test/snapshot/__snapshots__/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot index 07442f3cde8..1fc921d2408 100644 --- a/test/snapshot/__snapshots__/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot @@ -11,14 +11,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "g", nu retire(); return; }; }) -// Sprite1 Zfoo %s +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_foo_ (p0) { return ""; return ""; }; }) -// Sprite1 Zno op +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_no_op () { return ""; diff --git a/test/snapshot/__snapshots__/tw-list-any.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-list-any.sb3.tw-snapshot index cb39a249b24..d6988304d85 100644 --- a/test/snapshot/__snapshots__/tw-list-any.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-list-any.sb3.tw-snapshot @@ -30,7 +30,7 @@ listDelete(b1, "any"); if ((b1.value.length === 2)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "C", null); } -if (compareEqual(listGet(b1.value, "*"), "")) { +if ((("" + listGet(b1.value, "*")).toLowerCase() === "".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "F", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "E", null); 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 2557ac32e0b..d87b4b227fe 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 @@ -6,7 +6,7 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "@|B*yJ0zKh!acN`7L-N5", null); -if ((((1 / -0) || 0) === -Infinity)) { +if (((1 / -0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "=enYDFG11Nj/0BL:y56w", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, ",Cpv8W0RH0RgNky[1xb:", null); 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 58b908a70ce..d6e18a37046 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 @@ -11,7 +11,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "Q^(MKg retire(); return; }; }) -// Player ZSet Costume +// Player 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_Set_Costume () { 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 f2456f2e254..2555707ccc2 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 @@ -12,7 +12,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "$R-1lb retire(); return; }; }) -// Sprite1 Znumber or text %s %s +// 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_number_or_text__ (p0,p1) { @@ -22,7 +22,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "HH`yR return ""; }; }) -// Sprite1 Zboolean %b %b +// 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_boolean__ (p0,p1) { 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 548eb4b32c7..4c4667116e7 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 @@ -9,14 +9,14 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "qf{MD}-f+l?U+)KA#Vnm", null); b1.value = ""; thread.procedures["Zdo something"](); -if (!compareEqual(b1.value, "")) { +if (!(b1.value.toLowerCase() === "".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "Sgf_#7|GOpx!R]?Q3]$s", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, ",vD-ZG7f{]FoJ`,))JWh", null); retire(); return; }; }) -// Sprite1 Zdo something +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function funXYZ_do_something () { 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 index 2aa9b207d24..4e435ae0fdb 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-non-existant.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-non-existant.sb3.tw-snapshot @@ -39,14 +39,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "P", nu retire(); return; }; }) -// Sprite1 Zinvalid params - reporter +// Sprite1 script (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 Zinvalid params - boolean +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_invalid_params___boo () { return 0; diff --git a/test/snapshot/__snapshots__/tw-procedure-return-non-existent.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-non-existent.sb3.tw-snapshot index 8a2837c67b0..cea7ecccc17 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-non-existent.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-non-existent.sb3.tw-snapshot @@ -9,7 +9,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "d", null); b1.value = "discard me"; b1.value = ""; -if (compareEqual(b1.value, "")) { +if ((("" + b1.value).toLowerCase() === "".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non existent procedure returned empty string",}, b0, false, false, "h", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "g", null); diff --git a/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot index 6251bdac2ea..e1176792d95 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot @@ -10,17 +10,17 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 18",}, b0, false, false, "G", null); b1.value = 0; b2.value = (yield* thread.procedures["Znon warp recursion should yield %s"](8)); -if (((+b1.value || 0) === 4)) { +if ((b1.value === 4)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp recursion yields",}, b0, false, false, "ao", null); } b1.value = 0; b2.value = thread.procedures["Wwarp recursion should not yield %s"](8); -if (((+b1.value || 0) === 0)) { +if ((b1.value === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass warp recursion does not yield",}, b0, false, false, "ar", null); } b1.value = 0; b2.value = (yield* thread.procedures["Zfib %s"](7)); -if (((+b1.value || 0) === 20)) { +if ((b1.value === 20)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp fib yielded",}, b0, false, false, "au", null); } yield* thread.procedures["Zrecursing yields between each %s"]("initial"); @@ -30,7 +30,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "av", n retire(); return; }; }) -// Sprite1 Znon warp recursion should yield %s +// Sprite1 script (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)) { @@ -39,7 +39,7 @@ return (yield* yieldThenCallGenerator(thread.procedures["Znon warp recursion sho return ""; }; }) -// Sprite1 Wwarp recursion should not yield %s +// Sprite1 script (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)) { @@ -48,7 +48,7 @@ return thread.procedures["Wwarp recursion should not yield %s"](((+p0 || 0) - 1) return ""; }; }) -// Sprite1 Zfib %s +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function* genXYZ_fib_ (p0) { if (compareLessThan(p0, 2)) { @@ -59,7 +59,7 @@ return ((+(yield* yieldThenCallGenerator(thread.procedures["Zfib %s"], ((+p0 || return ""; }; }) -// Sprite1 Zrecursing yields between each %s +// 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"]; const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; @@ -68,7 +68,7 @@ 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)) { +if ((b0.value === 3)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recursing between calls yields final",}, b2, false, false, "aK", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":"fail recursing between calls yields final",}, b2, false, false, "aL", null); @@ -83,7 +83,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":("fail recursing between calls yie return ""; }; }) -// Sprite1 Zrecursing arguments eval order %s %s %s %s +// Sprite1 script (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"]; @@ -94,25 +94,25 @@ 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())) { +if ((("" + (b0.value[1 - 1] ?? "")).toLowerCase() === "1/child 4".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 1",}, b3, false, false, "aZ", null); } -if ((("" + (b0.value[(2 | 0) - 1] ?? "")).toLowerCase() === "1/child 5".toLowerCase())) { +if ((("" + (b0.value[2 - 1] ?? "")).toLowerCase() === "1/child 5".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 2",}, b3, false, false, "a#", null); } -if ((("" + (b0.value[(3 | 0) - 1] ?? "")).toLowerCase() === "2/child 6".toLowerCase())) { +if ((("" + (b0.value[3 - 1] ?? "")).toLowerCase() === "2/child 6".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 3",}, b3, false, false, "a(", null); } -if ((("" + (b0.value[(4 | 0) - 1] ?? "")).toLowerCase() === "2/child 3".toLowerCase())) { +if ((("" + (b0.value[4 - 1] ?? "")).toLowerCase() === "2/child 3".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 4",}, b3, false, false, "a*", null); } -if ((("" + (b0.value[(5 | 0) - 1] ?? "")).toLowerCase() === "3/child 2".toLowerCase())) { +if ((("" + (b0.value[5 - 1] ?? "")).toLowerCase() === "3/child 2".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 5",}, b3, false, false, "a,", null); } -if ((("" + (b0.value[(6 | 0) - 1] ?? "")).toLowerCase() === "3/child 7".toLowerCase())) { +if ((("" + (b0.value[6 - 1] ?? "")).toLowerCase() === "3/child 7".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 6",}, b3, false, false, "a.", null); } -if ((("" + (b0.value[(7 | 0) - 1] ?? "")).toLowerCase() === "4/child 1".toLowerCase())) { +if ((("" + (b0.value[7 - 1] ?? "")).toLowerCase() === "4/child 1".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 7",}, b3, false, false, "a:", null); } if ((b0.value.length === 7)) { diff --git a/test/snapshot/__snapshots__/tw-procedure-return-simple.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-simple.sb3.tw-snapshot index b0ae54eb5bc..c37d030d128 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-simple.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-simple.sb3.tw-snapshot @@ -20,21 +20,21 @@ 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, "")) { +if ((("" + b1.value).toLowerCase() === "".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass default return value",}, b0, false, false, "|", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "`", null); retire(); return; }; }) -// Sprite1 Zsimplest +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_simplest () { return "It works!"; return ""; }; }) -// Sprite1 Znesting 1 +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_nesting_1 () { thread.procedures["Znesting 2"](); @@ -42,14 +42,14 @@ return (("" + thread.procedures["Znesting 3 %s %s"](6,7)) + ("" + thread.procedu return ""; }; }) -// Sprite1 Wwarp fib %s +// Sprite1 script (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 Wfactorial %s +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_factorial_ (p0) { if (compareGreaterThan(p0, 1)) { @@ -59,7 +59,7 @@ return 1; return ""; }; }) -// Sprite1 Zno shadowing 1 %s %s +// 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"]; @@ -78,21 +78,21 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass shadow check 3",}, b0, false return ""; }; }) -// Sprite1 Znesting 2 +// Sprite1 script (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 Znesting 3 %s %s +// Sprite1 script (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 Wfib %s +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_fib_ (p0) { if (compareLessThan(p0, 2)) { @@ -103,7 +103,7 @@ return ((+thread.procedures["Wfib %s"](((+p0 || 0) - 1)) || 0) + (+thread.proced return ""; }; }) -// Sprite1 Zno shadowing 2 %s %s +// Sprite1 script (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"; 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 index 29d85a58139..4980c3d3459 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-stops-scripts.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-stops-scripts.sb3.tw-snapshot @@ -19,15 +19,15 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "v", nu retire(); return; }; }) -// Sprite1 Wreturn stops the script immediately +// 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"]; 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)) { +b0.value = (b0.value + 1); +if ((b0.value === 25)) { return "stopped!"; } } @@ -42,7 +42,7 @@ return function* genXYZ () { b0.value = 0; while (true) { b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 18)) { +if ((b0.value === 18)) { retire(); return; } yield; diff --git a/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot index fde5f016583..cc9735dfe4b 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot @@ -32,7 +32,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "N", nu retire(); return; }; }) -// Sprite1 Znon warp +// 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"]; const b1 = runtime.getOpcodeFunction("looks_say"); @@ -73,7 +73,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp 2",}, b1, false, fa return ""; }; }) -// Sprite1 Wverify runs warp %s +// 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"]; const b1 = runtime.getOpcodeFunction("looks_say"); @@ -95,7 +95,7 @@ return ("warp: " + ("" + p0)); return ""; }; }) -// Sprite1 Zverify runs non warp %s +// 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"]; const b1 = runtime.getOpcodeFunction("looks_say"); 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 88f7ab0b015..033cebf8ce1 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 @@ -18,7 +18,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "$R-1lb retire(); return; }; }) -// Sprite1 Zswitch %s +// Sprite1 script (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); diff --git a/test/snapshot/__snapshots__/tw-sensing-of.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-sensing-of.sb3.tw-snapshot index ee91780681f..a370af3e46d 100644 --- a/test/snapshot/__snapshots__/tw-sensing-of.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-sensing-of.sb3.tw-snapshot @@ -36,7 +36,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass direction",}, b0, false, fal if (((b2 ? b2.currentCostume + 1 : 0) === 3)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass costume #",}, b0, false, false, "+zO[+f?c7F`ZGTbD.oqI", null); } -if ((("" + (b2 ? b2.getCostumes()[b2.currentCostume].name : 0)).toLowerCase() === "Costume name test".toLowerCase())) { +if (((b2 ? b2.getCostumes()[b2.currentCostume].name : 0).toLowerCase() === "Costume name test".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass costume name",}, b0, false, false, "Y6L|T0Pvwsct2gq+HGvv", null); } if (((b2 ? b2.size : 0) === 76.01)) { @@ -52,7 +52,7 @@ if (compareEqual((b4 ? b4.value : 0), 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non existent variable",}, b0, false, false, ")nnN?*l+E)dC(fT5(_@q", null); } b5.value = (("" + randomInt(1, 9)) + ("" + randomInt(1, 9))); -if (compareEqual(runtime.ext_scratch3_sensing.getAttributeOf({OBJECT: b5.value, PROPERTY: "backdrop #" }), 0)) { +if (compareEqual(runtime.ext_scratch3_sensing.getAttributeOf({OBJECT: ("" + b5.value), PROPERTY: "backdrop #" }), 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass NE backdrop #",}, b0, false, false, "UFr{fbR3@a.u_paq:r]F", null); } if (compareEqual(runtime.ext_scratch3_sensing.getAttributeOf({OBJECT: ("" + b5.value), PROPERTY: "backdrop name" }), 0)) { 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 0169ba16db3..119556a43f1 100644 --- a/test/snapshot/__snapshots__/tw-tab-equals-zero.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-tab-equals-zero.sb3.tw-snapshot @@ -29,7 +29,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass \\t and other spaces = strin if (compareEqual(b0.value, (0 + 0))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass \\t in a variable = number 0",}, b1, false, false, "z", null); } -if (compareEqual("\t", (0 + 0))) { +if ((0 === (0 + 0))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass literal \\t = number 0",}, b1, false, false, "B", null); } if (compareEqual((" " + ("" + b0.value)), (0 + 0))) { diff --git a/test/snapshot/__snapshots__/tw-tangent.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-tangent.sb3.tw-snapshot index b2ccf9ce3a3..17992397285 100644 --- a/test/snapshot/__snapshots__/tw-tangent.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-tangent.sb3.tw-snapshot @@ -6,31 +6,31 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 15",}, b0, false, false, "p", null); -if (compareEqual(tan(0), 0)) { +if (((tan(0) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 0",}, b0, false, false, "O", null); } if (((tan(90) || 0) === Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 90",}, b0, false, false, "G", null); } -if (compareEqual(tan(180), 0)) { +if (((tan(180) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 180",}, b0, false, false, "I", null); } if (((tan(270) || 0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 270",}, b0, false, false, "K", null); } -if (compareEqual(tan(360), 0)) { +if (((tan(360) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 360",}, b0, false, false, "M", null); } if (((tan(450) || 0) === Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 450",}, b0, false, false, "Q", null); } -if (compareEqual(tan(540), 0)) { +if (((tan(540) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 540",}, b0, false, false, "S", null); } if (((tan(630) || 0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 630",}, b0, false, false, "U", null); } -if (compareEqual(tan(720), 0)) { +if (((tan(720) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 720",}, b0, false, false, "W", null); } if (((tan(810) || 0) === Infinity)) { @@ -39,13 +39,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass 810",}, b0, false, false, "Y if (((tan(-90) || 0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass -90",}, b0, false, false, "0", null); } -if (compareEqual(tan(-180), 0)) { +if (((tan(-180) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass -180",}, b0, false, false, "2", null); } if (((tan(-270) || 0) === Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass -270",}, b0, false, false, "4", null); } -if (compareEqual(tan(-360), 0)) { +if (((tan(-360) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass -360",}, b0, false, false, "6", null); } if (((tan(-450) || 0) === -Infinity)) { diff --git a/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot index 082b46ce62d..90a01cba05d 100644 --- a/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot @@ -15,23 +15,23 @@ if ((10 === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 2",}, b0, false, false, "@A5}x{mm-Gk?CVz3o0Yn", null); } b1.value = 10; -if (((+b1.value || 0) === 10)) { +if ((b1.value === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 3",}, b0, false, false, "Ul:BCck-}Fvdux~x#$${", null); } -if (compareEqual(b1.value, "010")) { +if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 4",}, b0, false, false, "8]2$7P)o#+#Lo]mFSBbx", null); } -if (compareEqual(b1.value, "0000000010")) { +if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 5",}, b0, false, false, "ZU^{OfKTg|+Au$$q0[]u", null); } for (var a0 = 1; a0 >= 0.5; a0--) { if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 6",}, b0, false, false, "HB+_IN}6=K[*ksxKXH0`", null); } -if (compareEqual(b1.value, "010")) { +if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 7",}, b0, false, false, ";73ODiwcp8IthYURTX;S", null); } -if (compareEqual(b1.value, "0000000010")) { +if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 8",}, b0, false, true, "${[MFmBL-D*1rbas9Q89", null); if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;} } @@ -41,20 +41,20 @@ b2.value = "010"; if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 9",}, b0, false, false, "#.`@SBj!g-c0:_q/tMZo", null); } -if (compareEqual(b2.value, "010")) { +if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 10",}, b0, false, false, "B`o?V6/q6g),/2w};a#y", null); } -if (compareEqual(b2.value, "0000000010")) { +if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 11",}, b0, false, false, "TJ:#TkYBys*!RYiKLXb)", null); } for (var a1 = 1; a1 >= 0.5; a1--) { if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 12",}, b0, false, false, ",Z,~10Qo~j;(+VL+I3q:", null); } -if (compareEqual(b2.value, "010")) { +if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 13",}, b0, false, false, "|Mqx([(26M%#ggW9)U0s", null); } -if (compareEqual(b2.value, "0000000010")) { +if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 14",}, b0, false, true, "YvtiKF231lU8p5Qd97RP", null); if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;} } @@ -69,16 +69,16 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "XzQt! if ((0 === 1)) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "rxZqw7cv8g;PDM4B%{`?", null); } -if (compareEqual(" ", 0)) { +if ((" ".toLowerCase() === "0".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "3G|)eVw1mQm;O~cRy`}0", null); } -if (compareEqual(0, " ")) { +if (("0".toLowerCase() === " ".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "sd5xXX*tsW/~A_Q!0;^w", null); } -if (compareEqual("", 0)) { +if (("".toLowerCase() === "0".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "7**baE=].WD9OoY1+IEu", null); } -if (compareEqual(0, "")) { +if (("0".toLowerCase() === "".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "7!IB!=o/2H.Jqj-8Vwhz", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "df{tf!WhfRwXgQ?SN_dj", null); 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 bd4bdca8439..d986e3e81a9 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 @@ -11,14 +11,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "s", nu retire(); return; }; }) -// Sprite1 Wrun without screen refresh +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["F?*}X,`9XBpN_[piGRrz"]; const b1 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_run_without_screen_r () { -b0.value = (((daysSince2000() * 86400) || 0) + 3); +b0.value = ((daysSince2000() * 86400) + 3); runtime.ioDevices.clock.resetProjectTimer(); -while (!((runtime.ioDevices.clock.projectTimer() > 0.1) || compareGreaterThan((daysSince2000() * 86400), b0.value))) { +while (!((runtime.ioDevices.clock.projectTimer() > 0.1) || ((daysSince2000() * 86400) > b0.value))) { if (isStuck()) yield; } if (compareLessThan((daysSince2000() * 86400), b0.value)) { 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 2faaed414da..826f1e0058f 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 @@ -10,7 +10,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, ",a.euo_AgQTxR+D^x0M0", null); b1.value = 0; b2.value = ""; -while (!compareGreaterThan(b2.value, "")) { +while (!(b2.value.toLowerCase() > "".toLowerCase())) { startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "step" }); yield; } @@ -25,7 +25,7 @@ const b0 = stage.variables["7qur6!bGgvC9I(Nd5.HP"]; const b1 = stage.variables["sUOp@-6J4y0PqwiXit4!"]; return function* genXYZ () { b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 5)) { +if ((b0.value === 5)) { b1.value = "end"; } 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 91457816d33..96c7ced79c1 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 @@ -17,7 +17,7 @@ yield; } thread.timer = null; b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 1)) { +if ((b0.value === 1)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, false, false, "]4hbk*5ix]V00h|!x1oy", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 1 != " + ("" + b0.value)),}, b2, false, false, "H=x@7SpNJeX|!}8x5y4,", null); @@ -75,7 +75,7 @@ yield; } thread.timer = null; b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 2)) { +if ((b0.value === 2)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, false, false, "0i[-T:vYTt=bi47@byUE", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 2 != " + ("" + b0.value)),}, b2, false, false, "Coc1aZ;L9M-RyEt`syps", null); 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 2d10b51e1ce..5145078f7aa 100644 --- a/test/snapshot/__snapshots__/warp-timer/order-library.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/order-library.sb3.tw-snapshot @@ -51,7 +51,7 @@ yield; } thread.timer = null; b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 1)) { +if ((b0.value === 1)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, false, false, "RSQ{nVCc)6E)(`KlnFCF", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 1 != " + ("" + b0.value)),}, b2, false, false, "8k^j~`c^|YO@hkFd?~2d", null); @@ -75,7 +75,7 @@ yield; } thread.timer = null; b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 2)) { +if ((b0.value === 2)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, false, false, "KP?op(=Vg2#;@]!,C#.~", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":("fail order is incorrect 2 != " + ("" + b0.value)),}, b2, false, false, "=]|}L~4uQXTNtwJKw_;R", null); 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 106f6edd6d0..417094fef11 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot @@ -9,61 +9,61 @@ yield* executeInCompatibilityLayer({"MESSAGE":"plan 20",}, b0, false, false, "v" if ((("" + (0 * Infinity)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "6", null); } -if (compareEqual((((0 * Infinity) || 0) * 1), 0)) { +if (((((0 * Infinity) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "#", null); } if ((("" + ((Math.acos(1.01) * 180) / Math.PI)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "9", null); } -if (compareEqual(((((Math.acos(1.01) * 180) / Math.PI) || 0) * 1), 0)) { +if ((((((Math.acos(1.01) * 180) / Math.PI) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "(", null); } if ((("" + ((Math.asin(1.01) * 180) / Math.PI)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "*", null); } -if (compareEqual(((((Math.asin(1.01) * 180) / Math.PI) || 0) * 1), 0)) { +if ((((((Math.asin(1.01) * 180) / Math.PI) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, ",", null); } if ((("" + (0 / 0)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, ".", null); } -if (compareEqual((((0 / 0) || 0) * 1), 0)) { +if (((((0 / 0) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, ":", null); } if ((("" + Math.sqrt(-1)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "?", null); } -if (compareEqual(((Math.sqrt(-1) || 0) * 1), 0)) { +if ((((Math.sqrt(-1) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "@", null); } if ((("" + mod(0, 0)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "]", null); } -if (compareEqual(((mod(0, 0) || 0) * 1), 0)) { +if ((((mod(0, 0) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "_", null); } if ((("" + Math.log(-1)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "{", null); } -if (compareEqual(((Math.log(-1) || 0) * 1), 0)) { +if ((((Math.log(-1) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "}", null); } if ((("" + (Math.log(-1) / Math.LN10)).toLowerCase() === "NaN".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aa", null); } -if (compareEqual((((Math.log(-1) / Math.LN10) || 0) * 1), 0)) { +if (((((Math.log(-1) / Math.LN10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ac", null); } -if (compareEqual((((Math.round(Math.sin((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1), 0)) { +if (((((Math.round(Math.sin((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ae", null); } -if (compareEqual((((Math.round(Math.cos((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1), 0)) { +if (((((Math.round(Math.cos((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ag", null); } -if (compareEqual(((tan(((1 / 0) || 0)) || 0) * 1), 0)) { +if ((((tan(((1 / 0) || 0)) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ai", null); } -if (compareEqual(((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || 0) * 1), 0)) { +if ((((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ak", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "7", null); 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 c133a6cb76f..b7f47584e4f 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 @@ -11,7 +11,7 @@ target.setSize(96); b1.value = 0; while (!(100 === Math.round(target.size))) { b1.value = ((+b1.value || 0) + 1); -target.setSize(target.size + ((((100 - Math.round(target.size)) || 0) / 10) || 0)); +target.setSize(target.size + ((100 - Math.round(target.size)) / 10)); yield; } if (((+b1.value || 0) === 20)) { 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 bccad8ff318..070b355da4a 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 @@ -8,7 +8,7 @@ const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "c", null); b1.value = "#22388a"; -if ((("" + b1.value).toLowerCase() === "#22388a".toLowerCase())) { +if ((b1.value.toLowerCase() === "#22388a".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "f", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "e", null); 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 66d03cf758d..741ccca8ae7 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 @@ -123,13 +123,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 38: 0 should be = 🎉",}, b if (!(("" + ("0".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 39: 0 should be > 🎉",}, b0, false, false, "uq", null); } -if (!(("" + compareLessThan(0, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("0".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 40: 0 should be < ",}, b0, false, false, "us", null); } -if (!(("" + compareEqual(0, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("0".toLowerCase() === "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 41: 0 should be = ",}, b0, false, false, "uu", null); } -if (!(("" + compareGreaterThan(0, "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("0".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 42: 0 should be > ",}, b0, false, false, "uw", null); } if (!(("" + (0 < 0)).toLowerCase() === "false".toLowerCase())) { @@ -249,13 +249,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 80: 0.0 should be = 🎉",}, if (!(("" + ("0.0".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 81: 0.0 should be > 🎉",}, b0, false, false, "vn", null); } -if (!(("" + compareLessThan("0.0", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("0.0".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 82: 0.0 should be < ",}, b0, false, false, "vp", null); } -if (!(("" + compareEqual("0.0", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("0.0".toLowerCase() === "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 83: 0.0 should be = ",}, b0, false, false, "vr", null); } -if (!(("" + compareGreaterThan("0.0", "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("0.0".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 84: 0.0 should be > ",}, b0, false, false, "vt", null); } if (!(("" + (1.23 < 0)).toLowerCase() === "false".toLowerCase())) { @@ -324,7 +324,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 105: 1.23 should be > -1",}, if (!(("" + ("1.23".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 106: 1.23 should be < true",}, b0, false, false, "v#", null); } -if (!(("" + ("1.23".toLowerCase() === "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1.23 === 1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 107: 1.23 should be = true",}, b0, false, false, "v(", null); } if (!(("" + ("1.23".toLowerCase() > "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -333,7 +333,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 108: 1.23 should be > true", if (!(("" + ("1.23".toLowerCase() < "false".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 109: 1.23 should be < false",}, b0, false, false, "v,", null); } -if (!(("" + ("1.23".toLowerCase() === "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 110: 1.23 should be = false",}, b0, false, false, "v.", null); } if (!(("" + ("1.23".toLowerCase() > "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -342,7 +342,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 111: 1.23 should be > false" if (!(("" + ("1.23".toLowerCase() < "NaN".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 112: 1.23 should be < NaN",}, b0, false, false, "v=", null); } -if (!(("" + ("1.23".toLowerCase() === "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 113: 1.23 should be = NaN",}, b0, false, false, "v@", null); } if (!(("" + ("1.23".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -360,7 +360,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 117: 1.23 should be > Infini if (!(("" + ("1.23".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 118: 1.23 should be < banana",}, b0, false, false, "wa", null); } -if (!(("" + ("1.23".toLowerCase() === "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 119: 1.23 should be = banana",}, b0, false, false, "wc", null); } if (!(("" + ("1.23".toLowerCase() > "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -369,19 +369,19 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 120: 1.23 should be > banana if (!(("" + ("1.23".toLowerCase() < "🎉".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 121: 1.23 should be < 🎉",}, b0, false, false, "wg", null); } -if (!(("" + ("1.23".toLowerCase() === "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 122: 1.23 should be = 🎉",}, b0, false, false, "wi", null); } if (!(("" + ("1.23".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 123: 1.23 should be > 🎉",}, b0, false, false, "wk", null); } -if (!(("" + compareLessThan(1.23, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("1.23".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 124: 1.23 should be < ",}, b0, false, false, "wm", null); } if (!(("" + (1.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 125: 1.23 should be = ",}, b0, false, false, "wo", null); } -if (!(("" + compareGreaterThan(1.23, "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("1.23".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 126: 1.23 should be > ",}, b0, false, false, "wq", null); } if (!(("" + (0.23 < 0)).toLowerCase() === "false".toLowerCase())) { @@ -450,7 +450,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 147: .23 should be > -1",}, if (!(("" + (".23".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 148: .23 should be < true",}, b0, false, false, "w8", null); } -if (!(("" + (".23".toLowerCase() === "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 149: .23 should be = true",}, b0, false, false, "w!", null); } if (!(("" + (".23".toLowerCase() > "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -459,7 +459,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 150: .23 should be > true",} if (!(("" + (".23".toLowerCase() < "false".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 151: .23 should be < false",}, b0, false, false, "w)", null); } -if (!(("" + (".23".toLowerCase() === "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 152: .23 should be = false",}, b0, false, false, "w+", null); } if (!(("" + (".23".toLowerCase() > "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -468,7 +468,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 153: .23 should be > false", if (!(("" + (".23".toLowerCase() < "NaN".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 154: .23 should be < NaN",}, b0, false, false, "w/", null); } -if (!(("" + (".23".toLowerCase() === "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 155: .23 should be = NaN",}, b0, false, false, "w;", null); } if (!(("" + (".23".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -486,7 +486,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 159: .23 should be > Infinit if (!(("" + (".23".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 160: .23 should be < banana",}, b0, false, false, "w|", null); } -if (!(("" + (".23".toLowerCase() === "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 161: .23 should be = banana",}, b0, false, false, "w~", null); } if (!(("" + (".23".toLowerCase() > "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -495,19 +495,19 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 162: .23 should be > banana" if (!(("" + (".23".toLowerCase() < "🎉".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 163: .23 should be < 🎉",}, b0, false, false, "xd", null); } -if (!(("" + (".23".toLowerCase() === "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 164: .23 should be = 🎉",}, b0, false, false, "xf", null); } if (!(("" + (".23".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 165: .23 should be > 🎉",}, b0, false, false, "xh", null); } -if (!(("" + compareLessThan(".23", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (".23".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 166: .23 should be < ",}, b0, false, false, "xj", null); } -if (!(("" + compareEqual(".23", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.23 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 167: .23 should be = ",}, b0, false, false, "xl", null); } -if (!(("" + compareGreaterThan(".23", "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + (".23".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 168: .23 should be > ",}, b0, false, false, "xn", null); } if (!(("" + (0.123 < 0)).toLowerCase() === "false".toLowerCase())) { @@ -576,7 +576,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 189: 0.123 should be > -1",} if (!(("" + ("0.123".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 190: 0.123 should be < true",}, b0, false, false, "x5", null); } -if (!(("" + ("0.123".toLowerCase() === "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.123 === 1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 191: 0.123 should be = true",}, b0, false, false, "x7", null); } if (!(("" + ("0.123".toLowerCase() > "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -585,7 +585,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 192: 0.123 should be > true" if (!(("" + ("0.123".toLowerCase() < "false".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 193: 0.123 should be < false",}, b0, false, false, "x#", null); } -if (!(("" + ("0.123".toLowerCase() === "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.123 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 194: 0.123 should be = false",}, b0, false, false, "x(", null); } if (!(("" + ("0.123".toLowerCase() > "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -594,7 +594,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 195: 0.123 should be > false if (!(("" + ("0.123".toLowerCase() < "NaN".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 196: 0.123 should be < NaN",}, b0, false, false, "x,", null); } -if (!(("" + ("0.123".toLowerCase() === "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.123 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 197: 0.123 should be = NaN",}, b0, false, false, "x.", null); } if (!(("" + ("0.123".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -612,7 +612,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 201: 0.123 should be > Infin if (!(("" + ("0.123".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 202: 0.123 should be < banana",}, b0, false, false, "x_", null); } -if (!(("" + ("0.123".toLowerCase() === "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.123 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 203: 0.123 should be = banana",}, b0, false, false, "x{", null); } if (!(("" + ("0.123".toLowerCase() > "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -621,19 +621,19 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 204: 0.123 should be > banan if (!(("" + ("0.123".toLowerCase() < "🎉".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 205: 0.123 should be < 🎉",}, b0, false, false, "ya", null); } -if (!(("" + ("0.123".toLowerCase() === "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0.123 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 206: 0.123 should be = 🎉",}, b0, false, false, "yc", null); } if (!(("" + ("0.123".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 207: 0.123 should be > 🎉",}, b0, false, false, "ye", null); } -if (!(("" + compareLessThan(0.123, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("0.123".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 208: 0.123 should be < ",}, b0, false, false, "yg", null); } if (!(("" + (0.123 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 209: 0.123 should be = ",}, b0, false, false, "yi", null); } -if (!(("" + compareGreaterThan(0.123, "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("0.123".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 210: 0.123 should be > ",}, b0, false, false, "yk", null); } if (!(("" + (-0 < 0)).toLowerCase() === "false".toLowerCase())) { @@ -753,13 +753,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 248: -0 should be = 🎉",}, if (!(("" + ("-0".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 249: -0 should be > 🎉",}, b0, false, false, "zb", null); } -if (!(("" + compareLessThan("-0", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("-0".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 250: -0 should be < ",}, b0, false, false, "zd", null); } -if (!(("" + compareEqual("-0", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("-0".toLowerCase() === "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 251: -0 should be = ",}, b0, false, false, "zf", null); } -if (!(("" + compareGreaterThan("-0", "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("-0".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 252: -0 should be > ",}, b0, false, false, "zh", null); } if (!(("" + (-1 < 0)).toLowerCase() === "true".toLowerCase())) { @@ -828,7 +828,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 273: -1 should be > -1",}, b if (!(("" + ("-1".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 274: -1 should be < true",}, b0, false, false, "zZ", null); } -if (!(("" + ("-1".toLowerCase() === "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (-1 === 1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 275: -1 should be = true",}, b0, false, false, "z1", null); } if (!(("" + ("-1".toLowerCase() > "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -837,7 +837,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 276: -1 should be > true",}, if (!(("" + ("-1".toLowerCase() < "false".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 277: -1 should be < false",}, b0, false, false, "z5", null); } -if (!(("" + ("-1".toLowerCase() === "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (-1 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 278: -1 should be = false",}, b0, false, false, "z7", null); } if (!(("" + ("-1".toLowerCase() > "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -846,7 +846,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 279: -1 should be > false",} if (!(("" + ("-1".toLowerCase() < "NaN".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 280: -1 should be < NaN",}, b0, false, false, "z#", null); } -if (!(("" + ("-1".toLowerCase() === "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (-1 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 281: -1 should be = NaN",}, b0, false, false, "z(", null); } if (!(("" + ("-1".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -864,7 +864,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 285: -1 should be > Infinity if (!(("" + ("-1".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 286: -1 should be < banana",}, b0, false, false, "z=", null); } -if (!(("" + ("-1".toLowerCase() === "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (-1 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 287: -1 should be = banana",}, b0, false, false, "z@", null); } if (!(("" + ("-1".toLowerCase() > "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -873,19 +873,19 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 288: -1 should be > banana", if (!(("" + ("-1".toLowerCase() < "🎉".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 289: -1 should be < 🎉",}, b0, false, false, "z_", null); } -if (!(("" + ("-1".toLowerCase() === "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (-1 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 290: -1 should be = 🎉",}, b0, false, false, "z{", null); } if (!(("" + ("-1".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 291: -1 should be > 🎉",}, b0, false, false, "z}", null); } -if (!(("" + compareLessThan(-1, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("-1".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 292: -1 should be < ",}, b0, false, false, "Aa", null); } if (!(("" + (-1 === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 293: -1 should be = ",}, b0, false, false, "Ac", null); } -if (!(("" + compareGreaterThan(-1, "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("-1".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 294: -1 should be > ",}, b0, false, false, "Ae", null); } if (!(("" + ("true".toLowerCase() < "0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -909,7 +909,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 300: true should be > 0.0",} if (!(("" + ("true".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 301: true should be < 1.23",}, b0, false, false, "As", null); } -if (!(("" + ("true".toLowerCase() === "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 302: true should be = 1.23",}, b0, false, false, "Au", null); } if (!(("" + ("true".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -918,7 +918,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 303: true should be > 1.23", if (!(("" + ("true".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 304: true should be < .23",}, b0, false, false, "Ay", null); } -if (!(("" + ("true".toLowerCase() === ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 305: true should be = .23",}, b0, false, false, "AA", null); } if (!(("" + ("true".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -927,7 +927,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 306: true should be > .23",} if (!(("" + ("true".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 307: true should be < 0.123",}, b0, false, false, "AE", null); } -if (!(("" + ("true".toLowerCase() === "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 308: true should be = 0.123",}, b0, false, false, "AG", null); } if (!(("" + ("true".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -945,7 +945,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 312: true should be > -0",}, if (!(("" + ("true".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 313: true should be < -1",}, b0, false, false, "AQ", null); } -if (!(("" + ("true".toLowerCase() === "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 314: true should be = -1",}, b0, false, false, "AS", null); } if (!(("" + ("true".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -981,7 +981,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 324: true should be > NaN",} if (!(("" + ("true".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 325: true should be < Infinity",}, b0, false, false, "A)", null); } -if (!(("" + ("true".toLowerCase() === "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (1 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 326: true should be = Infinity",}, b0, false, false, "A+", null); } if (!(("" + ("true".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1035,7 +1035,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 342: false should be > 0.0", if (!(("" + ("false".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 343: false should be < 1.23",}, b0, false, false, "Bp", null); } -if (!(("" + ("false".toLowerCase() === "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 344: false should be = 1.23",}, b0, false, false, "Br", null); } if (!(("" + ("false".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1044,7 +1044,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 345: false should be > 1.23" if (!(("" + ("false".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 346: false should be < .23",}, b0, false, false, "Bv", null); } -if (!(("" + ("false".toLowerCase() === ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 347: false should be = .23",}, b0, false, false, "Bx", null); } if (!(("" + ("false".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1053,7 +1053,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 348: false should be > .23", if (!(("" + ("false".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 349: false should be < 0.123",}, b0, false, false, "BB", null); } -if (!(("" + ("false".toLowerCase() === "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 350: false should be = 0.123",}, b0, false, false, "BD", null); } if (!(("" + ("false".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1071,7 +1071,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 354: false should be > -0",} if (!(("" + ("false".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 355: false should be < -1",}, b0, false, false, "BN", null); } -if (!(("" + ("false".toLowerCase() === "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 356: false should be = -1",}, b0, false, false, "BP", null); } if (!(("" + ("false".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1107,7 +1107,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 366: false should be > NaN", if (!(("" + ("false".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 367: false should be < Infinity",}, b0, false, false, "B#", null); } -if (!(("" + ("false".toLowerCase() === "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 368: false should be = Infinity",}, b0, false, false, "B(", null); } if (!(("" + ("false".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -1161,7 +1161,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 384: NaN should be > 0.0",}, if (!(("" + ("NaN".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 385: NaN should be < 1.23",}, b0, false, false, "Cm", null); } -if (!(("" + ("NaN".toLowerCase() === "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 386: NaN should be = 1.23",}, b0, false, false, "Co", null); } if (!(("" + ("NaN".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1170,7 +1170,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 387: NaN should be > 1.23",} if (!(("" + ("NaN".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 388: NaN should be < .23",}, b0, false, false, "Cs", null); } -if (!(("" + ("NaN".toLowerCase() === ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 389: NaN should be = .23",}, b0, false, false, "Cu", null); } if (!(("" + ("NaN".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1179,7 +1179,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 390: NaN should be > .23",}, if (!(("" + ("NaN".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 391: NaN should be < 0.123",}, b0, false, false, "Cy", null); } -if (!(("" + ("NaN".toLowerCase() === "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 392: NaN should be = 0.123",}, b0, false, false, "CA", null); } if (!(("" + ("NaN".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1197,7 +1197,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 396: NaN should be > -0",}, if (!(("" + ("NaN".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 397: NaN should be < -1",}, b0, false, false, "CK", null); } -if (!(("" + ("NaN".toLowerCase() === "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 398: NaN should be = -1",}, b0, false, false, "CM", null); } if (!(("" + ("NaN".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1233,7 +1233,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 408: NaN should be > NaN",}, if (!(("" + ("NaN".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 409: NaN should be < Infinity",}, b0, false, false, "C8", null); } -if (!(("" + ("NaN".toLowerCase() === "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 410: NaN should be = Infinity",}, b0, false, false, "C!", null); } if (!(("" + ("NaN".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1332,7 +1332,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 441: Infinity should be > -1 if (!(("" + ("Infinity".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 442: Infinity should be < true",}, b0, false, false, "DN", null); } -if (!(("" + ("Infinity".toLowerCase() === "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (Infinity === 1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 443: Infinity should be = true",}, b0, false, false, "DP", null); } if (!(("" + ("Infinity".toLowerCase() > "true".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -1341,7 +1341,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 444: Infinity should be > tr if (!(("" + ("Infinity".toLowerCase() < "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 445: Infinity should be < false",}, b0, false, false, "DT", null); } -if (!(("" + ("Infinity".toLowerCase() === "false".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (Infinity === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 446: Infinity should be = false",}, b0, false, false, "DV", null); } if (!(("" + ("Infinity".toLowerCase() > "false".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1350,7 +1350,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 447: Infinity should be > fa if (!(("" + ("Infinity".toLowerCase() < "NaN".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 448: Infinity should be < NaN",}, b0, false, false, "DZ", null); } -if (!(("" + ("Infinity".toLowerCase() === "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (Infinity === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 449: Infinity should be = NaN",}, b0, false, false, "D1", null); } if (!(("" + ("Infinity".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -1368,7 +1368,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 453: Infinity should be > In if (!(("" + ("Infinity".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 454: Infinity should be < banana",}, b0, false, false, "D#", null); } -if (!(("" + ("Infinity".toLowerCase() === "banana".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (Infinity === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 455: Infinity should be = banana",}, b0, false, false, "D(", null); } if (!(("" + ("Infinity".toLowerCase() > "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1377,19 +1377,19 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 456: Infinity should be > ba if (!(("" + ("Infinity".toLowerCase() < "🎉".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 457: Infinity should be < 🎉",}, b0, false, false, "D,", null); } -if (!(("" + ("Infinity".toLowerCase() === "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (Infinity === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 458: Infinity should be = 🎉",}, b0, false, false, "D.", null); } if (!(("" + ("Infinity".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 459: Infinity should be > 🎉",}, b0, false, false, "D:", null); } -if (!(("" + compareLessThan(Infinity, "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("Infinity".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 460: Infinity should be < ",}, b0, false, false, "D=", null); } if (!(("" + (Infinity === 0)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 461: Infinity should be = ",}, b0, false, false, "D@", null); } -if (!(("" + compareGreaterThan(Infinity, "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("Infinity".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 462: Infinity should be > ",}, b0, false, false, "D]", null); } if (!(("" + ("banana".toLowerCase() < "0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -1413,7 +1413,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 468: banana should be > 0.0" if (!(("" + ("banana".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 469: banana should be < 1.23",}, b0, false, false, "Eg", null); } -if (!(("" + ("banana".toLowerCase() === "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 470: banana should be = 1.23",}, b0, false, false, "Ei", null); } if (!(("" + ("banana".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1422,7 +1422,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 471: banana should be > 1.23 if (!(("" + ("banana".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 472: banana should be < .23",}, b0, false, false, "Em", null); } -if (!(("" + ("banana".toLowerCase() === ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 473: banana should be = .23",}, b0, false, false, "Eo", null); } if (!(("" + ("banana".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1431,7 +1431,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 474: banana should be > .23" if (!(("" + ("banana".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 475: banana should be < 0.123",}, b0, false, false, "Es", null); } -if (!(("" + ("banana".toLowerCase() === "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 476: banana should be = 0.123",}, b0, false, false, "Eu", null); } if (!(("" + ("banana".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1449,7 +1449,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 480: banana should be > -0", if (!(("" + ("banana".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 481: banana should be < -1",}, b0, false, false, "EE", null); } -if (!(("" + ("banana".toLowerCase() === "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 482: banana should be = -1",}, b0, false, false, "EG", null); } if (!(("" + ("banana".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1485,7 +1485,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 492: banana should be > NaN" if (!(("" + ("banana".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 493: banana should be < Infinity",}, b0, false, false, "E2", null); } -if (!(("" + ("banana".toLowerCase() === "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 494: banana should be = Infinity",}, b0, false, false, "E4", null); } if (!(("" + ("banana".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { @@ -1539,7 +1539,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 510: 🎉 should be > 0.0",} if (!(("" + ("🎉".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 511: 🎉 should be < 1.23",}, b0, false, false, "Fd", null); } -if (!(("" + ("🎉".toLowerCase() === "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 512: 🎉 should be = 1.23",}, b0, false, false, "Ff", null); } if (!(("" + ("🎉".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1548,7 +1548,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 513: 🎉 should be > 1.23", if (!(("" + ("🎉".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 514: 🎉 should be < .23",}, b0, false, false, "Fj", null); } -if (!(("" + ("🎉".toLowerCase() === ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 515: 🎉 should be = .23",}, b0, false, false, "Fl", null); } if (!(("" + ("🎉".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1557,7 +1557,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 516: 🎉 should be > .23",} if (!(("" + ("🎉".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 517: 🎉 should be < 0.123",}, b0, false, false, "Fp", null); } -if (!(("" + ("🎉".toLowerCase() === "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 518: 🎉 should be = 0.123",}, b0, false, false, "Fr", null); } if (!(("" + ("🎉".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1575,7 +1575,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 522: 🎉 should be > -0",}, if (!(("" + ("🎉".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 523: 🎉 should be < -1",}, b0, false, false, "FB", null); } -if (!(("" + ("🎉".toLowerCase() === "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 524: 🎉 should be = -1",}, b0, false, false, "FD", null); } if (!(("" + ("🎉".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1611,7 +1611,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 534: 🎉 should be > NaN",} if (!(("" + ("🎉".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 535: 🎉 should be < Infinity",}, b0, false, false, "FZ", null); } -if (!(("" + ("🎉".toLowerCase() === "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 536: 🎉 should be = Infinity",}, b0, false, false, "F1", null); } if (!(("" + ("🎉".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1644,67 +1644,67 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 545: 🎉 should be = ",}, b if (!(("" + ("🎉".toLowerCase() > "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 546: 🎉 should be > ",}, b0, false, false, "F:", null); } -if (!(("" + compareLessThan("", 0)).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "0".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 547: should be < 0",}, b0, false, false, "F=", null); } -if (!(("" + compareEqual("", 0)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() === "0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 548: should be = 0",}, b0, false, false, "F@", null); } -if (!(("" + compareGreaterThan("", 0)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 549: should be > 0",}, b0, false, false, "F]", null); } -if (!(("" + compareLessThan("", "0.0")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "0.0".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 550: should be < 0.0",}, b0, false, false, "F_", null); } -if (!(("" + compareEqual("", "0.0")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() === "0.0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 551: should be = 0.0",}, b0, false, false, "F{", null); } -if (!(("" + compareGreaterThan("", "0.0")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "0.0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 552: should be > 0.0",}, b0, false, false, "F}", null); } -if (!(("" + compareLessThan("", 1.23)).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "1.23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 553: should be < 1.23",}, b0, false, false, "Ga", null); } if (!(("" + (0 === 1.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 554: should be = 1.23",}, b0, false, false, "Gc", null); } -if (!(("" + compareGreaterThan("", 1.23)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "1.23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 555: should be > 1.23",}, b0, false, false, "Ge", null); } -if (!(("" + compareLessThan("", ".23")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < ".23".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 556: should be < .23",}, b0, false, false, "Gg", null); } -if (!(("" + compareEqual("", ".23")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + (0 === 0.23)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 557: should be = .23",}, b0, false, false, "Gi", null); } -if (!(("" + compareGreaterThan("", ".23")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > ".23".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 558: should be > .23",}, b0, false, false, "Gk", null); } -if (!(("" + compareLessThan("", 0.123)).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "0.123".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 559: should be < 0.123",}, b0, false, false, "Gm", null); } if (!(("" + (0 === 0.123)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 560: should be = 0.123",}, b0, false, false, "Go", null); } -if (!(("" + compareGreaterThan("", 0.123)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "0.123".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 561: should be > 0.123",}, b0, false, false, "Gq", null); } -if (!(("" + compareLessThan("", "-0")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "-0".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 562: should be < -0",}, b0, false, false, "Gs", null); } -if (!(("" + compareEqual("", "-0")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() === "-0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 563: should be = -0",}, b0, false, false, "Gu", null); } -if (!(("" + compareGreaterThan("", "-0")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "-0".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 564: should be > -0",}, b0, false, false, "Gw", null); } -if (!(("" + compareLessThan("", -1)).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "-1".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 565: should be < -1",}, b0, false, false, "Gy", null); } if (!(("" + (0 === -1)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 566: should be = -1",}, b0, false, false, "GA", null); } -if (!(("" + compareGreaterThan("", -1)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "-1".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 567: should be > -1",}, b0, false, false, "GC", null); } if (!(("" + ("".toLowerCase() < "true".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1734,13 +1734,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 575: should be = NaN",}, b0 if (!(("" + ("".toLowerCase() > "NaN".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 576: should be > NaN",}, b0, false, false, "GU", null); } -if (!(("" + compareLessThan("", Infinity)).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "Infinity".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 577: should be < Infinity",}, b0, false, false, "GW", null); } if (!(("" + (0 === Infinity)).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 578: should be = Infinity",}, b0, false, false, "GY", null); } -if (!(("" + compareGreaterThan("", Infinity)).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "Infinity".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 579: should be > Infinity",}, b0, false, false, "G0", null); } if (!(("" + ("".toLowerCase() < "banana".toLowerCase())).toLowerCase() === "true".toLowerCase())) { @@ -1761,13 +1761,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail 584: should be = 🎉",}, b if (!(("" + ("".toLowerCase() > "🎉".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 585: should be > 🎉",}, b0, false, false, "G%", null); } -if (!(("" + compareLessThan("", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() < "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 586: should be < ",}, b0, false, false, "G)", null); } -if (!(("" + compareEqual("", "")).toLowerCase() === "true".toLowerCase())) { +if (!(("" + ("".toLowerCase() === "".toLowerCase())).toLowerCase() === "true".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 587: should be = ",}, b0, false, false, "G+", null); } -if (!(("" + compareGreaterThan("", "")).toLowerCase() === "false".toLowerCase())) { +if (!(("" + ("".toLowerCase() > "".toLowerCase())).toLowerCase() === "false".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail 588: should be > ",}, b0, false, false, "G.", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "G-", null); 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 0808a3a2fab..40c61fa7448 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 @@ -11,7 +11,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "aZ", n retire(); return; }; }) -// Sprite1 Wrun test +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["mfV;yS}9e:%h5UZ)QyiY"]; const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; @@ -29,15 +29,15 @@ b3.value = 0; for (var a1 = b2.value.length; a1 >= 0.5; a1--) { b3.value = ((+b3.value || 0) + 1); b0.value = ((+b0.value || 0) + 1); -if (!compareEqual(compareGreaterThan(listGet(b2.value, b1.value), (b2.value[((b3.value || 0) | 0) - 1] ?? "")), (b4.value[((b0.value || 0) | 0) - 1] ?? ""))) { -yield* executeInCompatibilityLayer({"MESSAGE":("fail " + (("" + listGet(b2.value, b1.value)) + (" should be > " + ("" + listGet(b2.value, b3.value))))),}, b5, true, false, "]", null); +if (!compareEqual(compareGreaterThan(listGet(b2.value, b1.value), (b2.value[(b3.value | 0) - 1] ?? "")), (b4.value[(b0.value | 0) - 1] ?? ""))) { +yield* executeInCompatibilityLayer({"MESSAGE":("fail " + (("" + listGet(b2.value, b1.value)) + (" should be > " + ("" + (b2.value[(b3.value | 0) - 1] ?? ""))))),}, b5, true, false, "]", null); } b0.value = ((+b0.value || 0) + 1); -if (!compareEqual(compareEqual(listGet(b2.value, b1.value), listGet(b2.value, b3.value)), (b4.value[((b0.value || 0) | 0) - 1] ?? ""))) { +if (!compareEqual(compareEqual(listGet(b2.value, b1.value), listGet(b2.value, b3.value)), (b4.value[(b0.value | 0) - 1] ?? ""))) { yield* executeInCompatibilityLayer({"MESSAGE":("fail " + (("" + listGet(b2.value, b1.value)) + (" should be = " + ("" + listGet(b2.value, b3.value))))),}, b5, true, false, "|", null); } b0.value = ((+b0.value || 0) + 1); -if (!compareEqual(compareLessThan(listGet(b2.value, b1.value), listGet(b2.value, b3.value)), (b4.value[((b0.value || 0) | 0) - 1] ?? ""))) { +if (!compareEqual(compareLessThan(listGet(b2.value, b1.value), listGet(b2.value, b3.value)), (b4.value[(b0.value | 0) - 1] ?? ""))) { yield* executeInCompatibilityLayer({"MESSAGE":("fail " + (("" + listGet(b2.value, b1.value)) + (" should be < " + ("" + listGet(b2.value, b3.value))))),}, b5, true, true, "ab", null); if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;} } @@ -48,7 +48,7 @@ if (isStuck()) yield; return ""; }; }) -// Sprite1 Wsetup values +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["n^wm8jw#b24sggt.S^tD"]; return function funXYZ_setup_values () { @@ -107,7 +107,7 @@ b0.value.push((-1 / 0)); b0._monitorUpToDate = false; b0.value.push((0 / 0)); b0._monitorUpToDate = false; -b0.value.push(NaN); +b0.value.push("NaN"); b0._monitorUpToDate = false; b0.value.push("nan"); b0._monitorUpToDate = false; diff --git a/test/snapshot/__snapshots__/warp-timer/tw-custom-report-repeat.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-custom-report-repeat.sb3.tw-snapshot index 060537d67aa..1442da79f1a 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-custom-report-repeat.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-custom-report-repeat.sb3.tw-snapshot @@ -19,7 +19,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "l", nu retire(); return; }; }) -// Sprite1 Zblock name +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_block_name () { return 40; 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 a63ba8842c5..5f84b8b41da 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 @@ -12,10 +12,10 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "k", n if (compareLessThan(("something".toLowerCase() === "else".toLowerCase()), ("1" + ""))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "m", null); } -if (compareGreaterThan(("something".toLowerCase() === "something".toLowerCase()), (0 + 0))) { +if (((+("something".toLowerCase() === "something".toLowerCase())) > (0 + 0))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "n", null); } -if (compareLessThan(("something".toLowerCase() === "else".toLowerCase()), (1 + 0))) { +if (((+("something".toLowerCase() === "else".toLowerCase())) < (1 + 0))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "o", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "l", null); 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 7c736d4333e..d52a998ab3e 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 @@ -9,13 +9,13 @@ const b2 = target.variables["zShM`!CD?d_|Z,]5X}N6"]; return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, "d", null); b1.value = 2; -if (((+b1.value || 0) === 2)) { +if ((b1.value === 2)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass variable",}, b0, false, false, "k", null); } b2.value = []; b2.value.push(3); b2._monitorUpToDate = false; -if (((+(b2.value[(1 | 0) - 1] ?? "") || 0) === 3)) { +if (((+(b2.value[1 - 1] ?? "") || 0) === 3)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass list",}, b0, false, false, "m", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "l", null); 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 a3d93933160..7a3bed7c7af 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 @@ -21,7 +21,7 @@ retire(); return; retire(); return; }; }) -// Sprite1 Wno refresh +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); @@ -42,14 +42,14 @@ if (isStuck()) yield; return ""; }; }) -// Sprite1 Wruns below with no refresh +// Sprite1 script (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 Whas refresh +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); diff --git a/test/snapshot/__snapshots__/warp-timer/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot index 07442f3cde8..1fc921d2408 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot @@ -11,14 +11,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "g", nu retire(); return; }; }) -// Sprite1 Zfoo %s +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_foo_ (p0) { return ""; return ""; }; }) -// Sprite1 Zno op +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_no_op () { 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 cb39a249b24..d6988304d85 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 @@ -30,7 +30,7 @@ listDelete(b1, "any"); if ((b1.value.length === 2)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "C", null); } -if (compareEqual(listGet(b1.value, "*"), "")) { +if ((("" + listGet(b1.value, "*")).toLowerCase() === "".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "F", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "E", null); 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 2557ac32e0b..d87b4b227fe 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 @@ -6,7 +6,7 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "@|B*yJ0zKh!acN`7L-N5", null); -if ((((1 / -0) || 0) === -Infinity)) { +if (((1 / -0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "=enYDFG11Nj/0BL:y56w", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, ",Cpv8W0RH0RgNky[1xb:", null); 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 58b908a70ce..d6e18a37046 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 @@ -11,7 +11,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "Q^(MKg retire(); return; }; }) -// Player ZSet Costume +// Player 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_Set_Costume () { 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 f2456f2e254..2555707ccc2 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 @@ -12,7 +12,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "$R-1lb retire(); return; }; }) -// Sprite1 Znumber or text %s %s +// 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_number_or_text__ (p0,p1) { @@ -22,7 +22,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "HH`yR return ""; }; }) -// Sprite1 Zboolean %b %b +// 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_boolean__ (p0,p1) { 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 548eb4b32c7..4c4667116e7 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 @@ -9,14 +9,14 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "qf{MD}-f+l?U+)KA#Vnm", null); b1.value = ""; thread.procedures["Zdo something"](); -if (!compareEqual(b1.value, "")) { +if (!(b1.value.toLowerCase() === "".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "Sgf_#7|GOpx!R]?Q3]$s", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, ",vD-ZG7f{]FoJ`,))JWh", null); retire(); return; }; }) -// Sprite1 Zdo something +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function funXYZ_do_something () { 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 index 2aa9b207d24..4e435ae0fdb 100644 --- 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 @@ -39,14 +39,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "P", nu retire(); return; }; }) -// Sprite1 Zinvalid params - reporter +// Sprite1 script (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 Zinvalid params - boolean +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_invalid_params___boo () { return 0; diff --git a/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-non-existent.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-non-existent.sb3.tw-snapshot index 8a2837c67b0..cea7ecccc17 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-non-existent.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-procedure-return-non-existent.sb3.tw-snapshot @@ -9,7 +9,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "d", null); b1.value = "discard me"; b1.value = ""; -if (compareEqual(b1.value, "")) { +if ((("" + b1.value).toLowerCase() === "".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non existent procedure returned empty string",}, b0, false, false, "h", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "g", null); 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 index 6251bdac2ea..e1176792d95 100644 --- 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 @@ -10,17 +10,17 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 18",}, b0, false, false, "G", null); b1.value = 0; b2.value = (yield* thread.procedures["Znon warp recursion should yield %s"](8)); -if (((+b1.value || 0) === 4)) { +if ((b1.value === 4)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp recursion yields",}, b0, false, false, "ao", null); } b1.value = 0; b2.value = thread.procedures["Wwarp recursion should not yield %s"](8); -if (((+b1.value || 0) === 0)) { +if ((b1.value === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass warp recursion does not yield",}, b0, false, false, "ar", null); } b1.value = 0; b2.value = (yield* thread.procedures["Zfib %s"](7)); -if (((+b1.value || 0) === 20)) { +if ((b1.value === 20)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp fib yielded",}, b0, false, false, "au", null); } yield* thread.procedures["Zrecursing yields between each %s"]("initial"); @@ -30,7 +30,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "av", n retire(); return; }; }) -// Sprite1 Znon warp recursion should yield %s +// Sprite1 script (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)) { @@ -39,7 +39,7 @@ return (yield* yieldThenCallGenerator(thread.procedures["Znon warp recursion sho return ""; }; }) -// Sprite1 Wwarp recursion should not yield %s +// Sprite1 script (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)) { @@ -48,7 +48,7 @@ return thread.procedures["Wwarp recursion should not yield %s"](((+p0 || 0) - 1) return ""; }; }) -// Sprite1 Zfib %s +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function* genXYZ_fib_ (p0) { if (compareLessThan(p0, 2)) { @@ -59,7 +59,7 @@ return ((+(yield* yieldThenCallGenerator(thread.procedures["Zfib %s"], ((+p0 || return ""; }; }) -// Sprite1 Zrecursing yields between each %s +// 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"]; const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; @@ -68,7 +68,7 @@ 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)) { +if ((b0.value === 3)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recursing between calls yields final",}, b2, false, false, "aK", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":"fail recursing between calls yields final",}, b2, false, false, "aL", null); @@ -83,7 +83,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":("fail recursing between calls yie return ""; }; }) -// Sprite1 Zrecursing arguments eval order %s %s %s %s +// Sprite1 script (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"]; @@ -94,25 +94,25 @@ 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())) { +if ((("" + (b0.value[1 - 1] ?? "")).toLowerCase() === "1/child 4".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 1",}, b3, false, false, "aZ", null); } -if ((("" + (b0.value[(2 | 0) - 1] ?? "")).toLowerCase() === "1/child 5".toLowerCase())) { +if ((("" + (b0.value[2 - 1] ?? "")).toLowerCase() === "1/child 5".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 2",}, b3, false, false, "a#", null); } -if ((("" + (b0.value[(3 | 0) - 1] ?? "")).toLowerCase() === "2/child 6".toLowerCase())) { +if ((("" + (b0.value[3 - 1] ?? "")).toLowerCase() === "2/child 6".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 3",}, b3, false, false, "a(", null); } -if ((("" + (b0.value[(4 | 0) - 1] ?? "")).toLowerCase() === "2/child 3".toLowerCase())) { +if ((("" + (b0.value[4 - 1] ?? "")).toLowerCase() === "2/child 3".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 4",}, b3, false, false, "a*", null); } -if ((("" + (b0.value[(5 | 0) - 1] ?? "")).toLowerCase() === "3/child 2".toLowerCase())) { +if ((("" + (b0.value[5 - 1] ?? "")).toLowerCase() === "3/child 2".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 5",}, b3, false, false, "a,", null); } -if ((("" + (b0.value[(6 | 0) - 1] ?? "")).toLowerCase() === "3/child 7".toLowerCase())) { +if ((("" + (b0.value[6 - 1] ?? "")).toLowerCase() === "3/child 7".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 6",}, b3, false, false, "a.", null); } -if ((("" + (b0.value[(7 | 0) - 1] ?? "")).toLowerCase() === "4/child 1".toLowerCase())) { +if ((("" + (b0.value[7 - 1] ?? "")).toLowerCase() === "4/child 1".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recurse arg order - 7",}, b3, false, false, "a:", null); } if ((b0.value.length === 7)) { 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 index b0ae54eb5bc..c37d030d128 100644 --- 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 @@ -20,21 +20,21 @@ 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, "")) { +if ((("" + b1.value).toLowerCase() === "".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass default return value",}, b0, false, false, "|", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "`", null); retire(); return; }; }) -// Sprite1 Zsimplest +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_simplest () { return "It works!"; return ""; }; }) -// Sprite1 Znesting 1 +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_nesting_1 () { thread.procedures["Znesting 2"](); @@ -42,14 +42,14 @@ return (("" + thread.procedures["Znesting 3 %s %s"](6,7)) + ("" + thread.procedu return ""; }; }) -// Sprite1 Wwarp fib %s +// Sprite1 script (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 Wfactorial %s +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_factorial_ (p0) { if (compareGreaterThan(p0, 1)) { @@ -59,7 +59,7 @@ return 1; return ""; }; }) -// Sprite1 Zno shadowing 1 %s %s +// 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"]; @@ -78,21 +78,21 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass shadow check 3",}, b0, false return ""; }; }) -// Sprite1 Znesting 2 +// Sprite1 script (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 Znesting 3 %s %s +// Sprite1 script (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 Wfib %s +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_fib_ (p0) { if (compareLessThan(p0, 2)) { @@ -103,7 +103,7 @@ return ((+thread.procedures["Wfib %s"](((+p0 || 0) - 1)) || 0) + (+thread.proced return ""; }; }) -// Sprite1 Zno shadowing 2 %s %s +// Sprite1 script (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"; 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 index c0355f7aa47..7ea58c0cd93 100644 --- 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 @@ -19,7 +19,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "v", nu retire(); return; }; }) -// Sprite1 Wreturn stops the script immediately +// 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"]; const b1 = runtime.getOpcodeFunction("looks_say"); @@ -27,7 +27,7 @@ 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)) { +if ((b0.value === 25)) { return "stopped!"; } if (isStuck()) yield; @@ -43,7 +43,7 @@ return function* genXYZ () { b0.value = 0; while (true) { b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 18)) { +if ((b0.value === 18)) { retire(); return; } yield; 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 index d7afc882d25..edf65b39d91 100644 --- 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 @@ -32,7 +32,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "N", nu retire(); return; }; }) -// Sprite1 Znon warp +// 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"]; const b1 = runtime.getOpcodeFunction("looks_say"); @@ -73,7 +73,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp 2",}, b1, false, fa return ""; }; }) -// Sprite1 Wverify runs warp %s +// 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"]; const b1 = runtime.getOpcodeFunction("looks_say"); @@ -96,7 +96,7 @@ return ("warp: " + ("" + p0)); return ""; }; }) -// Sprite1 Zverify runs non warp %s +// 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"]; const b1 = runtime.getOpcodeFunction("looks_say"); 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 88f7ab0b015..033cebf8ce1 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 @@ -18,7 +18,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "$R-1lb retire(); return; }; }) -// Sprite1 Zswitch %s +// Sprite1 script (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); 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 ee91780681f..a370af3e46d 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 @@ -36,7 +36,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass direction",}, b0, false, fal if (((b2 ? b2.currentCostume + 1 : 0) === 3)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass costume #",}, b0, false, false, "+zO[+f?c7F`ZGTbD.oqI", null); } -if ((("" + (b2 ? b2.getCostumes()[b2.currentCostume].name : 0)).toLowerCase() === "Costume name test".toLowerCase())) { +if (((b2 ? b2.getCostumes()[b2.currentCostume].name : 0).toLowerCase() === "Costume name test".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"pass costume name",}, b0, false, false, "Y6L|T0Pvwsct2gq+HGvv", null); } if (((b2 ? b2.size : 0) === 76.01)) { @@ -52,7 +52,7 @@ if (compareEqual((b4 ? b4.value : 0), 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non existent variable",}, b0, false, false, ")nnN?*l+E)dC(fT5(_@q", null); } b5.value = (("" + randomInt(1, 9)) + ("" + randomInt(1, 9))); -if (compareEqual(runtime.ext_scratch3_sensing.getAttributeOf({OBJECT: b5.value, PROPERTY: "backdrop #" }), 0)) { +if (compareEqual(runtime.ext_scratch3_sensing.getAttributeOf({OBJECT: ("" + b5.value), PROPERTY: "backdrop #" }), 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass NE backdrop #",}, b0, false, false, "UFr{fbR3@a.u_paq:r]F", null); } if (compareEqual(runtime.ext_scratch3_sensing.getAttributeOf({OBJECT: ("" + b5.value), PROPERTY: "backdrop name" }), 0)) { 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 0169ba16db3..119556a43f1 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 @@ -29,7 +29,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass \\t and other spaces = strin if (compareEqual(b0.value, (0 + 0))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass \\t in a variable = number 0",}, b1, false, false, "z", null); } -if (compareEqual("\t", (0 + 0))) { +if ((0 === (0 + 0))) { yield* executeInCompatibilityLayer({"MESSAGE":"pass literal \\t = number 0",}, b1, false, false, "B", null); } if (compareEqual((" " + ("" + b0.value)), (0 + 0))) { 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 b2ccf9ce3a3..17992397285 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-tangent.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-tangent.sb3.tw-snapshot @@ -6,31 +6,31 @@ const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 15",}, b0, false, false, "p", null); -if (compareEqual(tan(0), 0)) { +if (((tan(0) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 0",}, b0, false, false, "O", null); } if (((tan(90) || 0) === Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 90",}, b0, false, false, "G", null); } -if (compareEqual(tan(180), 0)) { +if (((tan(180) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 180",}, b0, false, false, "I", null); } if (((tan(270) || 0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 270",}, b0, false, false, "K", null); } -if (compareEqual(tan(360), 0)) { +if (((tan(360) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 360",}, b0, false, false, "M", null); } if (((tan(450) || 0) === Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 450",}, b0, false, false, "Q", null); } -if (compareEqual(tan(540), 0)) { +if (((tan(540) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 540",}, b0, false, false, "S", null); } if (((tan(630) || 0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 630",}, b0, false, false, "U", null); } -if (compareEqual(tan(720), 0)) { +if (((tan(720) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 720",}, b0, false, false, "W", null); } if (((tan(810) || 0) === Infinity)) { @@ -39,13 +39,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass 810",}, b0, false, false, "Y if (((tan(-90) || 0) === -Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass -90",}, b0, false, false, "0", null); } -if (compareEqual(tan(-180), 0)) { +if (((tan(-180) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass -180",}, b0, false, false, "2", null); } if (((tan(-270) || 0) === Infinity)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass -270",}, b0, false, false, "4", null); } -if (compareEqual(tan(-360), 0)) { +if (((tan(-360) || 0) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass -360",}, b0, false, false, "6", null); } if (((tan(-450) || 0) === -Infinity)) { 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 082b46ce62d..90a01cba05d 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 @@ -15,23 +15,23 @@ if ((10 === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 2",}, b0, false, false, "@A5}x{mm-Gk?CVz3o0Yn", null); } b1.value = 10; -if (((+b1.value || 0) === 10)) { +if ((b1.value === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 3",}, b0, false, false, "Ul:BCck-}Fvdux~x#$${", null); } -if (compareEqual(b1.value, "010")) { +if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 4",}, b0, false, false, "8]2$7P)o#+#Lo]mFSBbx", null); } -if (compareEqual(b1.value, "0000000010")) { +if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 5",}, b0, false, false, "ZU^{OfKTg|+Au$$q0[]u", null); } for (var a0 = 1; a0 >= 0.5; a0--) { if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 6",}, b0, false, false, "HB+_IN}6=K[*ksxKXH0`", null); } -if (compareEqual(b1.value, "010")) { +if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 7",}, b0, false, false, ";73ODiwcp8IthYURTX;S", null); } -if (compareEqual(b1.value, "0000000010")) { +if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 8",}, b0, false, true, "${[MFmBL-D*1rbas9Q89", null); if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;} } @@ -41,20 +41,20 @@ b2.value = "010"; if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 9",}, b0, false, false, "#.`@SBj!g-c0:_q/tMZo", null); } -if (compareEqual(b2.value, "010")) { +if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 10",}, b0, false, false, "B`o?V6/q6g),/2w};a#y", null); } -if (compareEqual(b2.value, "0000000010")) { +if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 11",}, b0, false, false, "TJ:#TkYBys*!RYiKLXb)", null); } for (var a1 = 1; a1 >= 0.5; a1--) { if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 12",}, b0, false, false, ",Z,~10Qo~j;(+VL+I3q:", null); } -if (compareEqual(b2.value, "010")) { +if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 13",}, b0, false, false, "|Mqx([(26M%#ggW9)U0s", null); } -if (compareEqual(b2.value, "0000000010")) { +if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 14",}, b0, false, true, "YvtiKF231lU8p5Qd97RP", null); if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;} } @@ -69,16 +69,16 @@ yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "XzQt! if ((0 === 1)) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "rxZqw7cv8g;PDM4B%{`?", null); } -if (compareEqual(" ", 0)) { +if ((" ".toLowerCase() === "0".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "3G|)eVw1mQm;O~cRy`}0", null); } -if (compareEqual(0, " ")) { +if (("0".toLowerCase() === " ".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "sd5xXX*tsW/~A_Q!0;^w", null); } -if (compareEqual("", 0)) { +if (("".toLowerCase() === "0".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "7**baE=].WD9OoY1+IEu", null); } -if (compareEqual(0, "")) { +if (("0".toLowerCase() === "".toLowerCase())) { yield* executeInCompatibilityLayer({"MESSAGE":"fail",}, b0, false, false, "7!IB!=o/2H.Jqj-8Vwhz", null); } yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "df{tf!WhfRwXgQ?SN_dj", null); 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 bd4bdca8439..d986e3e81a9 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 @@ -11,14 +11,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "s", nu retire(); return; }; }) -// Sprite1 Wrun without screen refresh +// Sprite1 script (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["F?*}X,`9XBpN_[piGRrz"]; const b1 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_run_without_screen_r () { -b0.value = (((daysSince2000() * 86400) || 0) + 3); +b0.value = ((daysSince2000() * 86400) + 3); runtime.ioDevices.clock.resetProjectTimer(); -while (!((runtime.ioDevices.clock.projectTimer() > 0.1) || compareGreaterThan((daysSince2000() * 86400), b0.value))) { +while (!((runtime.ioDevices.clock.projectTimer() > 0.1) || ((daysSince2000() * 86400) > b0.value))) { if (isStuck()) yield; } if (compareLessThan((daysSince2000() * 86400), b0.value)) { 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 2faaed414da..826f1e0058f 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 @@ -10,7 +10,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, ",a.euo_AgQTxR+D^x0M0", null); b1.value = 0; b2.value = ""; -while (!compareGreaterThan(b2.value, "")) { +while (!(b2.value.toLowerCase() > "".toLowerCase())) { startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "step" }); yield; } @@ -25,7 +25,7 @@ const b0 = stage.variables["7qur6!bGgvC9I(Nd5.HP"]; const b1 = stage.variables["sUOp@-6J4y0PqwiXit4!"]; return function* genXYZ () { b0.value = ((+b0.value || 0) + 1); -if (((b0.value || 0) === 5)) { +if ((b0.value === 5)) { b1.value = "end"; } retire(); return; From d3ed93cdfc5bdd1cf0ce85abe1d78a79a0b37f73 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 16 Jun 2024 13:49:53 +1000 Subject: [PATCH 02/56] Make tests happy :3 --- src/compiler/compat-block-utility.js | 4 ++-- src/compiler/irgen.js | 12 +++++++----- src/compiler/iroptimizer.js | 8 ++++---- src/compiler/jsexecute.js | 6 ++++-- src/compiler/jsgen.js | 6 +++--- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/compiler/compat-block-utility.js b/src/compiler/compat-block-utility.js index b662867dc07..1afade0e27e 100644 --- a/src/compiler/compat-block-utility.js +++ b/src/compiler/compat-block-utility.js @@ -10,7 +10,7 @@ class CompatibilityLayerBlockUtility extends BlockUtility { } get stackFrame () { - return this._stackFrame; + return this.thread?.compatibilityStackFrame; } startBranch (branchNumber, isLoop) { @@ -35,9 +35,9 @@ class CompatibilityLayerBlockUtility extends BlockUtility { init (thread, fakeBlockId, stackFrame) { this.thread = thread; this.sequencer = thread.target.runtime.sequencer; - this._stackFrame = stackFrame; this._startedBranch = null; thread.stack[0] = fakeBlockId; + thread.compatibilityStackFrame = stackFrame; } } diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 91a576db301..c0e81344b27 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -1226,12 +1226,14 @@ class ScriptTreeGenerator { const blockInfo = this.getBlockInfo(block.opcode); const blockType = (blockInfo && blockInfo.info && blockInfo.info.blockType) || BlockType.COMMAND; - const substacks = []; + const substacks = {}; if (blockType === BlockType.CONDITIONAL || blockType === BlockType.LOOP) { - const branchCount = blockInfo.info.branchCount; - for (let i = 0; i < branchCount; i++) { - const inputName = i === 0 ? 'SUBSTACK' : `SUBSTACK${i + 1}`; - substacks.push(this.descendSubstack(block, inputName)); + for (const inputName in block.inputs) { + if (!inputName.startsWith('SUBSTACK')) continue; + const branchNum = inputName === 'SUBSTACK' ? 1 : +inputName.substring('SUBSTACK'.length); + if (!isNaN(branchNum)) { + substacks[branchNum] = this.descendSubstack(block, inputName); + } } } diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index 4e4ce7a5f9d..de43312e1b5 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -138,14 +138,14 @@ class IROptimizer { return state.getVariableType(inputs.variable); case InputOpcode.ADDON_CALL: - + break; case InputOpcode.CAST_NUMBER: { const innerType = inputs.target.type; if (innerType & InputType.NUMBER) return innerType; return InputType.NUMBER; } case InputOpcode.CAST_NUMBER_OR_NAN: { - const innerType = inputs.target; + const innerType = inputs.target.type; if (innerType & InputType.NUMBER_OR_NAN) return innerType; return InputType.NUMBER_OR_NAN; } @@ -548,9 +548,9 @@ class IROptimizer { } case StackOpcode.COMPATIBILITY_LAYER: { this.analyzeInputs(inputs.inputs, state); - for (const substack of inputs.substacks) { + for (const substackName in inputs.substacks) { const newState = state.clone(); - modified = this.analyzeStack(substack, newState) || modified; + modified = this.analyzeStack(inputs.substacks[substackName], newState) || modified; modified = state.or(newState) || modified; } break; diff --git a/src/compiler/jsexecute.js b/src/compiler/jsexecute.js index a682f880913..e8139245512 100644 --- a/src/compiler/jsexecute.js +++ b/src/compiler/jsexecute.js @@ -123,6 +123,8 @@ const waitPromise = function*(promise) { }, error => { thread.status = 0; // STATUS_RUNNING globalState.log.warn('Promise rejected in compiled script:', error); + returnValue = '' + error; + thread.status = 0; // STATUS_RUNNING }); yield; @@ -164,7 +166,7 @@ const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, use return returnValue; } - if (thread.status === 1 /* STATUS_PROMISE_WAIT */) { + if (thread.status === 1 /* STATUS_PROMISE_WAIT */ || thread.status === 4 /* STATUS_DONE */) { // Something external is forcing us to stop yield; // Make up a return value because whatever is forcing us to stop can't specify one @@ -191,7 +193,7 @@ const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, use return returnValue; } - if (thread.status === 1 /* STATUS_PROMISE_WAIT */) { + if (thread.status === 1 /* STATUS_PROMISE_WAIT */ || thread.status === 4 /* STATUS_DONE */) { yield; return finish(''); } diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index 9f060c59cca..31da1f0880d 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -502,9 +502,9 @@ class JSGenerator { this.source += `const ${branchVariable} = createBranchInfo(${blockType === BlockType.LOOP});\n`; this.source += `while (${branchVariable}.branch = +(${this.generateCompatibilityLayerCall(node, false, branchVariable)})) {\n`; this.source += `switch (${branchVariable}.branch) {\n`; - for (let i = 0; i < node.substacks.length; i++) { - this.source += `case ${i + 1}: {\n`; - this.descendStack(node.substacks[i], new Frame(false)); + for (const index in node.substacks) { + this.source += `case ${+index}: {\n`; + this.descendStack(node.substacks[index], new Frame(false)); this.source += `break;\n`; this.source += `}\n`; // close case } From 9d28fccb9955ebe272df0b71dffe6ebb0cbd26a5 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 16 Jun 2024 13:50:30 +1000 Subject: [PATCH 03/56] Add operator type testing --- test/integration/tw_operator_type_matrix.js | 201 ++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 test/integration/tw_operator_type_matrix.js diff --git a/test/integration/tw_operator_type_matrix.js b/test/integration/tw_operator_type_matrix.js new file mode 100644 index 00000000000..4bb8499764a --- /dev/null +++ b/test/integration/tw_operator_type_matrix.js @@ -0,0 +1,201 @@ +const {test} = require('tap'); +const VM = require('../../src/virtual-machine'); +const {BlockType, ArgumentType} = require('../../src/extension-support/tw-extension-api-common'); +const IRGenerator = require('../../src/compiler/irgen'); +const {IROptimizer} = require('../../src/compiler/iroptimizer'); +const {IntermediateInput} = require('../../src/compiler/intermediate'); +const nanolog = require('@turbowarp/nanolog'); + +const VALUES = [ + NaN, + + -Infinity, + -1e+308, + -1, + -0.5, + -1e-324, + -0, + 0, + 1e-324, + 0.5, + 1, + 1e+308, + Infinity +]; + +const createBinaryOperator = opcode => ({ + opcode, + inputNames: ['NUM1', 'NUM2'], + fields: {} +}); + +const createMathopOperator = name => ({ + opcode: 'operator_mathop', + inputNames: ['NUM'], + fields: { + OPERATOR: [ + name, + null + ] + } +}); + +const OPERATORS = [ + createBinaryOperator('operator_add'), + createBinaryOperator('operator_subtract'), + createBinaryOperator('operator_divide'), + createBinaryOperator('operator_multiply'), + createBinaryOperator('operator_mod'), + + createMathopOperator('abs'), + createMathopOperator('floor'), + createMathopOperator('ceiling'), + createMathopOperator('sqrt'), + createMathopOperator('sin'), + createMathopOperator('cos'), + createMathopOperator('tan'), + createMathopOperator('asin'), + createMathopOperator('acos'), + createMathopOperator('atan'), + createMathopOperator('ln'), + createMathopOperator('log'), + createMathopOperator('e ^'), + createMathopOperator('10 ^') +]; + +const str = number => (Object.is(number, -0) ? '-0' : number.toString()); + +test('operator type matrix', async t => { + + const vm = new VM(); + nanolog.disable(); + + let reportedValue; + + class TestExtension { + getInfo () { + return { + id: 'test', + name: 'Test', + blocks: [ + { + opcode: 'report', + blockType: BlockType.COMMAND, + text: 'report [INPUT]', + isEdgeActivated: false, + arguments: { + INPUT: { + type: ArgumentType.NUMBER, + defaultValue: 0 + } + } + } + ] + }; + } + report (args) { + reportedValue = args.INPUT; + } + } + + vm.extensionManager.addBuiltinExtension('test', TestExtension); + vm.setCompilerOptions({enabled: true}); + + vm.on('COMPILE_ERROR', () => { + t.fail('Compile error'); + }); + + const testOperator = async (operator, inputs) => { + + const inputsSB3 = {}; + for (let i = 0; i < inputs.length; i++) { + inputsSB3[operator.inputNames[i]] = [ + 1, + [ + 4, + `${inputs[i]}` + ] + ]; + } + + await vm.loadProject({ + targets: [ + { + isStage: true, + name: 'Stage', + variables: {}, + lists: {}, + costumes: [ + { + name: 'dummy', + dataFormat: 'svg', + assetId: 'cd21514d0531fdffb22204e0ec5ed84a', + md5ext: 'cd21514d0531fdffb22204e0ec5ed84a.svg' + } + ], + sounds: [], + + blocks: { + report: { + opcode: 'test_report', + inputs: { + INPUT: [ + 3, + 'operator' + ] + } + }, + operator: { + opcode: operator.opcode, + inputs: inputsSB3, + fields: operator.fields + } + } + } + ], + meta: { + semver: '3.0.0', + vm: '0.2.0', + agent: '' + } + }); + + const thread = vm.runtime._pushThread('report', vm.runtime.targets[0]); + + const irGenerator = new IRGenerator(thread); + const ir = irGenerator.generate(); + const irOptimizer = new IROptimizer(ir); + irOptimizer.optimize(); + + + while (vm.runtime.threads.length !== 0) { + vm.runtime._step(); + } + + // The ir input representing our operator + const irOperator = ir.entry.stack.blocks[0].inputs.inputs.INPUT; + + const expectedType = IntermediateInput.getNumberInputType(reportedValue); + + t.ok( + irOperator.isSometimesType(expectedType), + `${operator.opcode}${JSON.stringify(operator.fields)}[${inputs.map(str)}] outputted value ${str(reportedValue)} is of the expected type ${irOperator.type}.` + ); + }; + + for (const operator of OPERATORS) { + if (operator.inputNames.length == 2) { + for (const left of VALUES) { + for (const right of VALUES) { + await testOperator(operator, [left, right]); + } + } + } else { + for (const value of VALUES) { + await testOperator(operator, [value]); + } + } + } + + t.end(); +}); From 71b75aad51fe9a2e6810cb5b3b96f3f9a529bab9 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 16 Jun 2024 13:50:39 +1000 Subject: [PATCH 04/56] Add type assertion checking --- test/fixtures/tw-type-assertions.sb3 | Bin 0 -> 4947 bytes test/integration/tw_type_assertions.js | 184 +++++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 test/fixtures/tw-type-assertions.sb3 create mode 100644 test/integration/tw_type_assertions.js diff --git a/test/fixtures/tw-type-assertions.sb3 b/test/fixtures/tw-type-assertions.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..406d204a94e856ea9271bda0bad10ec6eda0c911 GIT binary patch literal 4947 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 UiH-9w6z1Qz_P4$!|JUz-0IX9sk^lez literal 0 HcmV?d00001 diff --git a/test/integration/tw_type_assertions.js b/test/integration/tw_type_assertions.js new file mode 100644 index 00000000000..4f8cc9e6f40 --- /dev/null +++ b/test/integration/tw_type_assertions.js @@ -0,0 +1,184 @@ +const fs = require('fs'); +const path = require('path'); +const {test} = require('tap'); +const VM = require('../../src/virtual-machine'); +const BlockType = require('../../src/extension-support/block-type'); +const ArgumentType = require('../../src/extension-support/argument-type'); +const IRGenerator = require('../../src/compiler/irgen'); +const {IROptimizer} = require('../../src/compiler/iroptimizer'); +const {StackOpcode, InputType, InputOpcode} = require('../../src/compiler/enums'); +const {IntermediateStack} = require('../../src/compiler/intermediate'); + +const fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'tw-type-assertions.sb3')); + +test('type assertions', async t => { + const vm = new VM(); + vm.setCompilerOptions({enabled: true, warpTimer: false}); + + class TestExtension { + getInfo () { + return { + id: 'typeassert', + name: 'Type Assertions', + blocks: [ + { + opcode: 'assert', + blockType: BlockType.COMMAND, + text: 'assert [VALUE] is [ADVERB] [NOUN]', + arguments: { + VALUE: { + type: ArgumentType.STRING + }, + ADVERB: { + type: ArgumentType.STRING, + menu: 'ADVERB_MENU' + }, + NOUN: { + type: ArgumentType.STRING, + menu: 'NOUN_MENU' + } + } + }, + { + opcode: 'region', + blockType: BlockType.CONDITIONAL, + text: 'region [NAME]', + arguments: { + NAME: { + type: ArgumentType.STRING + } + } + } + ], + menus: { + ADVERB_MENU: { + acceptReporters: false, + items: ['never', 'always', 'sometimes', 'exactly'] + }, + NOUN_MENU: { + acceptReporters: false, + items: ['zero', 'infinity', 'NaN', 'a number', 'a string', 'number interpretable', 'anything'] + } + } + }; + } + assert () { } + region () { + return true; + } + } + + vm.extensionManager.addBuiltinExtension('typeassert', TestExtension); + + vm.on('COMPILE_ERROR', () => { + t.fail('Compile error'); + }); + + await vm.loadProject(fixture); + + const thread = vm.runtime.startHats('event_whenflagclicked')[0]; + + function* enumerateAssertions (blocks, region) { + for (const block of blocks) { + if (block.opcode === StackOpcode.COMPATIBILITY_LAYER) { + switch (block.inputs.opcode) { + case 'typeassert_assert': + yield {block, region}; + break; + case 'typeassert_region': + const newRegionNameInput = block.inputs.inputs.NAME; + if (newRegionNameInput.opcode !== InputOpcode.CONSTANT) { + throw new Error('Region block inputs must be a constant.'); + } + yield* enumerateAssertions(block.inputs.substacks["1"].blocks, (region ? `${region}, ` : '') + newRegionNameInput.inputs.value); + break; + } + } else { + for (const inputName in block.inputs) { + const input = block.inputs[inputName]; + if (input instanceof IntermediateStack) { + yield* enumerateAssertions(input.blocks, region); + } + } + } + } + } + + const irGenerator = new IRGenerator(thread); + const ir = irGenerator.generate(); + + runTests('run tests with yields', false); + runTests('run tests without yields', true); + + function runTests (proccode, ignoreYields) { + + const assertions = [...enumerateAssertions(ir.getProcedure(proccode).stack.blocks)]; + + for (const {block} of assertions) { + block.ignoreState = true; + } + + const irOptimizer = new IROptimizer(ir); + irOptimizer.ignoreYields = ignoreYields; + irOptimizer.optimize(); + + for (const {block, region} of assertions) { + const valueInput = block.inputs.inputs.VALUE; + const adverb = block.inputs.fields.ADVERB; + const noun = block.inputs.fields.NOUN; + + let nounType; + + switch (noun) { + case 'zero': + nounType = InputType.NUMBER_ZERO; + break; + case 'infinity': + nounType = InputType.NUMBER_POS_INF; + break; + case 'NaN': + nounType = InputType.NUMBER_NAN; + break; + case 'a number': + nounType = InputType.NUMBER; + break; + case 'a string': + nounType = InputType.STRING; + break; + case 'number interpretable': + nounType = InputType.NUMBER_INTERPRETABLE; + break; + case 'anything': + nounType = InputType.ANY; + break; + default: throw new Error(`$Invalid noun menu option ${noun}`); + } + + let message; + + if (valueInput.opcode == InputOpcode.VAR_GET) { + message = `(${region}) assert variable '${valueInput.inputs.variable.name}' (type ${valueInput.type}) is ${adverb} ${noun}`; + } else { + message = `(${region}) assert ${valueInput.opcode} (type ${valueInput.type}) is ${adverb} ${noun}`; + } + + switch (adverb) { + case 'never': + t.ok(!valueInput.isSometimesType(nounType), message); + break; + case 'always': + t.ok(valueInput.isAlwaysType(nounType), message); + break; + case 'sometimes': + t.ok(valueInput.isSometimesType(nounType), message); + break; + case 'exactly': + t.equal(valueInput.type, nounType, message); + break; + default: throw new Error(`$Invalid adverb menu option ${adverb}`); + } + } + } + + t.end(); +}); From 41e816757305c2d3546f3a37d2244a99445c8542 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Mon, 17 Jun 2024 10:06:33 +1000 Subject: [PATCH 05/56] Fix snapshot tests falling back to "script" instead of the procedure variant --- src/compiler/intermediate.js | 6 ++++++ src/compiler/irgen.js | 1 + ...w-comparison-matrix-runtime.sb3.tw-snapshot | 4 ++-- .../tw-custom-report-repeat.sb3.tw-snapshot | 2 +- ...t-zero-seconds-in-warp-mode.sb3.tw-snapshot | 6 +++--- ...es-not-reevaluate-arguments.sb3.tw-snapshot | 4 ++-- ...ence-of-procedure-387608267.sb3.tw-snapshot | 2 +- ...re-arguments-with-same-name.sb3.tw-snapshot | 4 ++-- ...iable-input-types-430811055.sb3.tw-snapshot | 2 +- ...ocedure-return-non-existant.sb3.tw-snapshot | 4 ++-- ...-procedure-return-recursion.sb3.tw-snapshot | 10 +++++----- .../tw-procedure-return-simple.sb3.tw-snapshot | 18 +++++++++--------- ...cedure-return-stops-scripts.sb3.tw-snapshot | 2 +- .../tw-procedure-return-warp.sb3.tw-snapshot | 6 +++--- ...-procedure-argument-casting.sb3.tw-snapshot | 2 +- ...at-until-timer-greater-than.sb3.tw-snapshot | 2 +- ...w-comparison-matrix-runtime.sb3.tw-snapshot | 4 ++-- .../tw-custom-report-repeat.sb3.tw-snapshot | 2 +- ...t-zero-seconds-in-warp-mode.sb3.tw-snapshot | 6 +++--- ...es-not-reevaluate-arguments.sb3.tw-snapshot | 4 ++-- ...ence-of-procedure-387608267.sb3.tw-snapshot | 2 +- ...re-arguments-with-same-name.sb3.tw-snapshot | 4 ++-- ...iable-input-types-430811055.sb3.tw-snapshot | 2 +- ...ocedure-return-non-existant.sb3.tw-snapshot | 4 ++-- ...-procedure-return-recursion.sb3.tw-snapshot | 10 +++++----- .../tw-procedure-return-simple.sb3.tw-snapshot | 18 +++++++++--------- ...cedure-return-stops-scripts.sb3.tw-snapshot | 2 +- .../tw-procedure-return-warp.sb3.tw-snapshot | 6 +++--- ...-procedure-argument-casting.sb3.tw-snapshot | 2 +- ...at-until-timer-greater-than.sb3.tw-snapshot | 2 +- test/snapshot/lib.js | 1 + 31 files changed, 76 insertions(+), 68 deletions(-) diff --git a/src/compiler/intermediate.js b/src/compiler/intermediate.js index dc5f4ea36d4..6e47d379ccb 100644 --- a/src/compiler/intermediate.js +++ b/src/compiler/intermediate.js @@ -239,6 +239,12 @@ class IntermediateScript { */ this.isProcedure = false; + /** + * This procedure's variant, if any. + * @type {string} + */ + this.procedureVariant = ''; + /** * This procedure's code, if any. * @type {string} diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index c0e81344b27..c959c5cc3a5 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -98,6 +98,7 @@ class ScriptTreeGenerator { setProcedureVariant (procedureVariant) { const procedureCode = parseProcedureCode(procedureVariant); + this.script.procedureVariant = procedureVariant; this.script.procedureCode = procedureCode; this.script.isProcedure = true; this.script.yields = false; 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 69c59c95f94..686c36b9e13 100644 --- a/test/snapshot/__snapshots__/tw-comparison-matrix-runtime.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-comparison-matrix-runtime.sb3.tw-snapshot @@ -11,7 +11,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "aZ", n retire(); return; }; }) -// Sprite1 script +// Sprite1 Wrun test (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["mfV;yS}9e:%h5UZ)QyiY"]; const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; @@ -46,7 +46,7 @@ if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;} return ""; }; }) -// Sprite1 script +// Sprite1 Wsetup values (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["n^wm8jw#b24sggt.S^tD"]; return function funXYZ_setup_values () { diff --git a/test/snapshot/__snapshots__/tw-custom-report-repeat.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-custom-report-repeat.sb3.tw-snapshot index 1442da79f1a..060537d67aa 100644 --- a/test/snapshot/__snapshots__/tw-custom-report-repeat.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-custom-report-repeat.sb3.tw-snapshot @@ -19,7 +19,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "l", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Zblock name (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_block_name () { return 40; 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 27d2ced7847..704116bfee2 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 @@ -21,7 +21,7 @@ retire(); return; retire(); return; }; }) -// Sprite1 script +// Sprite1 Wno refresh (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); @@ -41,14 +41,14 @@ runtime.ext_scratch3_motion._moveSteps(0, target); return ""; }; }) -// Sprite1 script +// Sprite1 Wruns 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 script +// Sprite1 Whas refresh (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); diff --git a/test/snapshot/__snapshots__/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot index 1fc921d2408..07442f3cde8 100644 --- a/test/snapshot/__snapshots__/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot @@ -11,14 +11,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "g", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Zfoo %s (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_foo_ (p0) { return ""; return ""; }; }) -// Sprite1 script +// Sprite1 Zno op (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_no_op () { 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 d6e18a37046..58b908a70ce 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 @@ -11,7 +11,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "Q^(MKg retire(); return; }; }) -// Player script +// Player ZSet Costume (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_Set_Costume () { 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 2555707ccc2..f2456f2e254 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 @@ -12,7 +12,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "$R-1lb retire(); return; }; }) -// Sprite1 script +// Sprite1 Znumber or text %s %s (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_number_or_text__ (p0,p1) { @@ -22,7 +22,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "HH`yR return ""; }; }) -// Sprite1 script +// Sprite1 Zboolean %b %b (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_boolean__ (p0,p1) { 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 4c4667116e7..f80c51477bd 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 @@ -16,7 +16,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, ",vD-ZG retire(); return; }; }) -// Sprite1 script +// Sprite1 Zdo something (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function funXYZ_do_something () { 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 index 4e435ae0fdb..2aa9b207d24 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-non-existant.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-non-existant.sb3.tw-snapshot @@ -39,14 +39,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "P", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Zinvalid 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 script +// Sprite1 Zinvalid 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; diff --git a/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot index e1176792d95..ee666ba30fc 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot @@ -30,7 +30,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "av", n retire(); return; }; }) -// Sprite1 script +// Sprite1 Znon 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)) { @@ -39,7 +39,7 @@ return (yield* yieldThenCallGenerator(thread.procedures["Znon warp recursion sho return ""; }; }) -// Sprite1 script +// Sprite1 Wwarp 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)) { @@ -48,7 +48,7 @@ return thread.procedures["Wwarp recursion should not yield %s"](((+p0 || 0) - 1) return ""; }; }) -// Sprite1 script +// Sprite1 Zfib %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)) { @@ -59,7 +59,7 @@ return ((+(yield* yieldThenCallGenerator(thread.procedures["Zfib %s"], ((+p0 || return ""; }; }) -// Sprite1 script +// Sprite1 Zrecursing 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"]; @@ -83,7 +83,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":("fail recursing between calls yie return ""; }; }) -// Sprite1 script +// Sprite1 Zrecursing 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"]; diff --git a/test/snapshot/__snapshots__/tw-procedure-return-simple.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-simple.sb3.tw-snapshot index c37d030d128..3b4a34ac8e8 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-simple.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-simple.sb3.tw-snapshot @@ -27,14 +27,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "`", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Zsimplest (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_simplest () { return "It works!"; return ""; }; }) -// Sprite1 script +// Sprite1 Znesting 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"](); @@ -42,14 +42,14 @@ return (("" + thread.procedures["Znesting 3 %s %s"](6,7)) + ("" + thread.procedu return ""; }; }) -// Sprite1 script +// Sprite1 Wwarp 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 script +// Sprite1 Wfactorial %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)) { @@ -59,7 +59,7 @@ return 1; return ""; }; }) -// Sprite1 script +// Sprite1 Zno 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"]; @@ -78,21 +78,21 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass shadow check 3",}, b0, false return ""; }; }) -// Sprite1 script +// Sprite1 Znesting 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 script +// Sprite1 Znesting 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 script +// Sprite1 Wfib %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)) { @@ -103,7 +103,7 @@ return ((+thread.procedures["Wfib %s"](((+p0 || 0) - 1)) || 0) + (+thread.proced return ""; }; }) -// Sprite1 script +// Sprite1 Zno 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"; 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 index 4980c3d3459..21e08e6c87b 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-stops-scripts.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-stops-scripts.sb3.tw-snapshot @@ -19,7 +19,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "v", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Wreturn 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"); diff --git a/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot index cc9735dfe4b..fde5f016583 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot @@ -32,7 +32,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "N", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Znon 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"); @@ -73,7 +73,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp 2",}, b1, false, fa return ""; }; }) -// Sprite1 script +// Sprite1 Wverify 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"); @@ -95,7 +95,7 @@ return ("warp: " + ("" + p0)); return ""; }; }) -// Sprite1 script +// Sprite1 Zverify 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"); 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 033cebf8ce1..88f7ab0b015 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 @@ -18,7 +18,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "$R-1lb retire(); return; }; }) -// Sprite1 script +// Sprite1 Zswitch %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); 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 d986e3e81a9..e5108d03f52 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 @@ -11,7 +11,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "s", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Wrun without screen refresh (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["F?*}X,`9XBpN_[piGRrz"]; const b1 = runtime.getOpcodeFunction("looks_say"); 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 40c61fa7448..f274502d07a 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 @@ -11,7 +11,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "aZ", n retire(); return; }; }) -// Sprite1 script +// Sprite1 Wrun test (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["mfV;yS}9e:%h5UZ)QyiY"]; const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; @@ -48,7 +48,7 @@ if (isStuck()) yield; return ""; }; }) -// Sprite1 script +// Sprite1 Wsetup values (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["n^wm8jw#b24sggt.S^tD"]; return function funXYZ_setup_values () { diff --git a/test/snapshot/__snapshots__/warp-timer/tw-custom-report-repeat.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-custom-report-repeat.sb3.tw-snapshot index 1442da79f1a..060537d67aa 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-custom-report-repeat.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-custom-report-repeat.sb3.tw-snapshot @@ -19,7 +19,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "l", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Zblock name (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_block_name () { return 40; 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 7a3bed7c7af..a3d93933160 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 @@ -21,7 +21,7 @@ retire(); return; retire(); return; }; }) -// Sprite1 script +// Sprite1 Wno refresh (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); @@ -42,14 +42,14 @@ if (isStuck()) yield; return ""; }; }) -// Sprite1 script +// Sprite1 Wruns 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 script +// Sprite1 Whas refresh (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); diff --git a/test/snapshot/__snapshots__/warp-timer/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot index 1fc921d2408..07442f3cde8 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-gh-201-stop-script-does-not-reevaluate-arguments.sb3.tw-snapshot @@ -11,14 +11,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "g", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Zfoo %s (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_foo_ (p0) { return ""; return ""; }; }) -// Sprite1 script +// Sprite1 Zno op (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_no_op () { 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 d6e18a37046..58b908a70ce 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 @@ -11,7 +11,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "Q^(MKg retire(); return; }; }) -// Player script +// Player ZSet Costume (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_Set_Costume () { 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 2555707ccc2..f2456f2e254 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 @@ -12,7 +12,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "$R-1lb retire(); return; }; }) -// Sprite1 script +// Sprite1 Znumber or text %s %s (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_number_or_text__ (p0,p1) { @@ -22,7 +22,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "HH`yR return ""; }; }) -// Sprite1 script +// Sprite1 Zboolean %b %b (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_boolean__ (p0,p1) { 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 4c4667116e7..f80c51477bd 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 @@ -16,7 +16,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, ",vD-ZG retire(); return; }; }) -// Sprite1 script +// Sprite1 Zdo something (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function funXYZ_do_something () { 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 index 4e435ae0fdb..2aa9b207d24 100644 --- 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 @@ -39,14 +39,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "P", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Zinvalid 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 script +// Sprite1 Zinvalid 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; 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 index e1176792d95..ee666ba30fc 100644 --- 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 @@ -30,7 +30,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "av", n retire(); return; }; }) -// Sprite1 script +// Sprite1 Znon 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)) { @@ -39,7 +39,7 @@ return (yield* yieldThenCallGenerator(thread.procedures["Znon warp recursion sho return ""; }; }) -// Sprite1 script +// Sprite1 Wwarp 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)) { @@ -48,7 +48,7 @@ return thread.procedures["Wwarp recursion should not yield %s"](((+p0 || 0) - 1) return ""; }; }) -// Sprite1 script +// Sprite1 Zfib %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)) { @@ -59,7 +59,7 @@ return ((+(yield* yieldThenCallGenerator(thread.procedures["Zfib %s"], ((+p0 || return ""; }; }) -// Sprite1 script +// Sprite1 Zrecursing 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"]; @@ -83,7 +83,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":("fail recursing between calls yie return ""; }; }) -// Sprite1 script +// Sprite1 Zrecursing 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"]; 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 index c37d030d128..3b4a34ac8e8 100644 --- 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 @@ -27,14 +27,14 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "`", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Zsimplest (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function funXYZ_simplest () { return "It works!"; return ""; }; }) -// Sprite1 script +// Sprite1 Znesting 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"](); @@ -42,14 +42,14 @@ return (("" + thread.procedures["Znesting 3 %s %s"](6,7)) + ("" + thread.procedu return ""; }; }) -// Sprite1 script +// Sprite1 Wwarp 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 script +// Sprite1 Wfactorial %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)) { @@ -59,7 +59,7 @@ return 1; return ""; }; }) -// Sprite1 script +// Sprite1 Zno 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"]; @@ -78,21 +78,21 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass shadow check 3",}, b0, false return ""; }; }) -// Sprite1 script +// Sprite1 Znesting 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 script +// Sprite1 Znesting 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 script +// Sprite1 Wfib %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)) { @@ -103,7 +103,7 @@ return ((+thread.procedures["Wfib %s"](((+p0 || 0) - 1)) || 0) + (+thread.proced return ""; }; }) -// Sprite1 script +// Sprite1 Zno 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"; 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 index 7ea58c0cd93..6f96f52693d 100644 --- 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 @@ -19,7 +19,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "v", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Wreturn 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"); 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 index edf65b39d91..d7afc882d25 100644 --- 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 @@ -32,7 +32,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "N", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Znon 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"); @@ -73,7 +73,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp 2",}, b1, false, fa return ""; }; }) -// Sprite1 script +// Sprite1 Wverify 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"); @@ -96,7 +96,7 @@ return ("warp: " + ("" + p0)); return ""; }; }) -// Sprite1 script +// Sprite1 Zverify 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"); 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 033cebf8ce1..88f7ab0b015 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 @@ -18,7 +18,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "$R-1lb retire(); return; }; }) -// Sprite1 script +// Sprite1 Zswitch %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); 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 d986e3e81a9..e5108d03f52 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 @@ -11,7 +11,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "s", nu retire(); return; }; }) -// Sprite1 script +// Sprite1 Wrun without screen refresh (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["F?*}X,`9XBpN_[piGRrz"]; const b1 = runtime.getOpcodeFunction("looks_say"); diff --git a/test/snapshot/lib.js b/test/snapshot/lib.js index b49033ffa02..7c817a85828 100644 --- a/test/snapshot/lib.js +++ b/test/snapshot/lib.js @@ -95,6 +95,7 @@ const generateActualSnapshot = async testCase => { const generatedJS = []; JSGenerator.testingApparatus = { report: (jsgen, factorySource) => { + console.log(jsgen.script); const targetName = jsgen.target.getName(); const scriptName = jsgen.script.procedureVariant || 'script'; const js = normalizeJS(factorySource); From 02806b654ac2a7ad39f22cdf42b7ecfc40578217 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Mon, 17 Jun 2024 10:37:37 +1000 Subject: [PATCH 06/56] Linty linty code --- src/compiler/intermediate.js | 11 +- src/compiler/irgen.js | 31 ++++-- src/compiler/iroptimizer.js | 117 +++++++++++--------- src/compiler/jsgen.js | 21 +++- test/integration/tw_operator_type_matrix.js | 5 +- test/integration/tw_type_assertions.js | 27 +++-- test/snapshot/lib.js | 1 - 7 files changed, 124 insertions(+), 89 deletions(-) diff --git a/src/compiler/intermediate.js b/src/compiler/intermediate.js index 6e47d379ccb..581ddb7e817 100644 --- a/src/compiler/intermediate.js +++ b/src/compiler/intermediate.js @@ -170,18 +170,18 @@ class IntermediateInput { break; case InputOpcode.CAST_NUMBER: case InputOpcode.CAST_NUMBER_INDEX: - case InputOpcode.CAST_NUMBER_OR_NAN: + case InputOpcode.CAST_NUMBER_OR_NAN: { if (this.isAlwaysType(InputType.BOOLEAN_INTERPRETABLE)) { this.type = InputType.NUMBER; this.inputs.value = +Cast.toBoolean(this.inputs.value); } - var numberValue = +this.inputs.value; + let numberValue = +this.inputs.value; if (numberValue) { this.inputs.value = numberValue; + } else /* numberValue is one of 0, -0, or NaN */ if (Object.is(numberValue, -0)) { + this.inputs.value = -0; } else { - // numberValue is one of 0, -0, or NaN - if (Object.is(numberValue, -0)) this.inputs.value = -0; - else this.inputs.value = 0; // Convert NaN to 0 + this.inputs.value = 0; // Convert NaN to 0 } if (castOpcode === InputOpcode.CAST_NUMBER_INDEX) { // Round numberValue to an integer @@ -189,6 +189,7 @@ class IntermediateInput { } this.type = IntermediateInput.getNumberInputType(this.inputs.value); break; + } case InputOpcode.CAST_STRING: this.inputs.value += ''; this.type = InputType.STRING; diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index c959c5cc3a5..7c75b3a32a4 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -5,14 +5,22 @@ const StringUtil = require('../util/string-util'); const BlockType = require('../extension-support/block-type'); const Variable = require('../engine/variable'); const log = require('../util/log'); -const {IntermediateStackBlock, IntermediateInput, IntermediateStack, IntermediateScript, IntermediateRepresentation} = require('./intermediate'); const compatBlocks = require('./compat-blocks'); const {StackOpcode, InputOpcode, InputType} = require('./enums.js'); +const { + IntermediateStackBlock, + IntermediateInput, + IntermediateStack, + IntermediateScript, + IntermediateRepresentation +} = require('./intermediate'); /** * @fileoverview Generate intermediate representations from Scratch blocks. */ +/* eslint-disable max-len */ + const SCALAR_TYPE = ''; const LIST_TYPE = 'list'; @@ -138,7 +146,7 @@ class ScriptTreeGenerator { } createConstantInput (constant, preserveStrings = false) { - if (constant == null) throw new Error('IR: Constant cannot have a null value.'); + if (constant === null) throw new Error('IR: Constant cannot have a null value.'); constant += ''; const numConstant = +constant; @@ -358,7 +366,7 @@ class ScriptTreeGenerator { case 'log': return new IntermediateInput(InputOpcode.OP_LOG_10, InputType.NUMBER_OR_NAN, {value}); case 'e ^': return new IntermediateInput(InputOpcode.OP_POW_E, InputType.NUMBER, {value}); case '10 ^': return new IntermediateInput(InputOpcode.OP_POW_10, InputType.NUMBER, {value}); - default: this.createConstantInput(0); + default: return this.createConstantInput(0); } } case 'operator_mod': @@ -490,7 +498,7 @@ class ScriptTreeGenerator { return new IntermediateInput(InputOpcode.SENSING_MOUSE_X, InputType.NUMBER); case 'sensing_mousey': return new IntermediateInput(InputOpcode.SENSING_MOUSE_Y, InputType.NUMBER); - case 'sensing_of': + case 'sensing_of': { const property = block.fields.PROPERTY.value; const object = this.descendInputOfBlock(block, 'OBJECT').toType(InputType.STRING); @@ -528,6 +536,7 @@ class ScriptTreeGenerator { } return new IntermediateInput(InputOpcode.SENSING_OF_VAR, InputType.ANY, {object, property}); + } case 'sensing_timer': this.usesTimer = true; return new IntermediateInput(InputOpcode.SENSING_TIMER_GET, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO); @@ -1111,7 +1120,7 @@ class ScriptTreeGenerator { const variable = block.fields[fieldName]; const id = variable.id; - if (this.variableCache.hasOwnProperty(id)) { + if (Object.prototype.hasOwnProperty.call(this.variableCache, id)) { return this.variableCache[id]; } @@ -1132,20 +1141,20 @@ class ScriptTreeGenerator { const stage = this.stage; // Look for by ID in target... - if (target.variables.hasOwnProperty(id)) { + if (Object.prototype.hasOwnProperty.call(target.variables, id)) { return createVariableData('target', target.variables[id]); } // Look for by ID in stage... if (!target.isStage) { - if (stage && stage.variables.hasOwnProperty(id)) { + if (stage && Object.prototype.hasOwnProperty.call(stage.variables, id)) { return createVariableData('stage', stage.variables[id]); } } // Look for by name and type in target... for (const varId in target.variables) { - if (target.variables.hasOwnProperty(varId)) { + if (Object.prototype.hasOwnProperty.call(target.variables, varId)) { const currVar = target.variables[varId]; if (currVar.name === name && currVar.type === type) { return createVariableData('target', currVar); @@ -1156,7 +1165,7 @@ class ScriptTreeGenerator { // Look for by name and type in stage... if (!target.isStage && stage) { for (const varId in stage.variables) { - if (stage.variables.hasOwnProperty(varId)) { + if (Object.prototype.hasOwnProperty.call(stage.variables, varId)) { const currVar = stage.variables[varId]; if (currVar.name === name && currVar.type === type) { return createVariableData('stage', currVar); @@ -1174,7 +1183,7 @@ class ScriptTreeGenerator { // This is necessary because the script cache is shared between clones. // sprite.clones has all instances of this sprite including the original and all clones for (const clone of target.sprite.clones) { - if (!clone.variables.hasOwnProperty(id)) { + if (!Object.prototype.hasOwnProperty.call(clone.variables, id)) { clone.variables[id] = new Variable(id, name, type, false); } } @@ -1395,7 +1404,7 @@ class IRGenerator { addProcedureDependencies (dependencies) { for (const procedureVariant of dependencies) { - if (this.procedures.hasOwnProperty(procedureVariant)) { + if (Object.prototype.hasOwnProperty.call(this.procedures, procedureVariant)) { continue; } if (this.compilingProcedures.has(procedureVariant)) { diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index de43312e1b5..c161f3c8a4f 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -1,8 +1,18 @@ // @ts-check -const {IntermediateStack, IntermediateInput, IntermediateScript, IntermediateRepresentation, IntermediateStackBlock} = require('./intermediate'); const {StackOpcode, InputOpcode, InputType} = require('./enums.js'); +// These imports are used by jsdoc comments but eslint doesn't know that +/* eslint-disable no-unused-vars */ +const { + IntermediateStack, + IntermediateInput, + IntermediateScript, + IntermediateRepresentation, + IntermediateStackBlock +} = require('./intermediate'); +/* eslint-enable no-unused-vars */ + class TypeState { constructor () { /** @type {Object.}*/ @@ -156,41 +166,41 @@ class IROptimizer { let resultType = 0; - function canBeNaN () { + const canBeNaN = function () { // Infinity + (-Infinity) = NaN if ((leftType & InputType.NUMBER_POS_INF) && (rightType & InputType.NUMBER_NEG_INF)) return true; // (-Infinity) + Infinity = NaN if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_POS_INF)) return true; - } + }; if (canBeNaN()) resultType |= InputType.NUMBER_NAN; - function canBeFractional () { + const canBeFractional = function () { // For the plus operation to return a non-whole number one of it's // inputs has to be a non-whole number if (leftType & InputType.NUMBER_FRACT) return true; if (rightType & InputType.NUMBER_FRACT) return true; - } + }; const canBeFract = canBeFractional(); - function canBePos () { + const canBePos = function () { if (leftType & InputType.NUMBER_POS) return true; // POS + ANY ~= POS if (rightType & InputType.NUMBER_POS) return true; // ANY + POS ~= POS - } + }; if (canBePos()) { resultType |= InputType.NUMBER_POS_INT | InputType.NUMBER_POS_INF; if (canBeFract) resultType |= InputType.NUMBER_POS_FRACT; } - function canBeNeg () { + const canBeNeg = function () { if (leftType & InputType.NUMBER_NEG) return true; // NEG + ANY ~= NEG if (rightType & InputType.NUMBER_NEG) return true; // ANY + NEG ~= NEG - } + }; if (canBeNeg()) { resultType |= InputType.NUMBER_NEG_INT | InputType.NUMBER_NEG_INF; if (canBeFract) resultType |= InputType.NUMBER_NEG_FRACT; } - function canBeZero () { + const canBeZero = function () { // POS_REAL + NEG_REAL ~= 0 if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_NEG_REAL)) return true; // NEG_REAL + POS_REAL ~= 0 @@ -201,13 +211,13 @@ class IROptimizer { if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; // -0 + 0 = 0 if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_ZERO)) return true; - } + }; if (canBeZero()) resultType |= InputType.NUMBER_ZERO; - function canBeNegZero () { + const canBeNegZero = function () { // -0 + -0 = -0 if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; - } + }; if (canBeNegZero()) resultType |= InputType.NUMBER_NEG_ZERO; return resultType; @@ -219,41 +229,41 @@ class IROptimizer { let resultType = 0; - function canBeNaN () { + const canBeNaN = function () { // Infinity - Infinity = NaN if ((leftType & InputType.NUMBER_POS_INF) && (rightType & InputType.NUMBER_POS_INF)) return true; // (-Infinity) - (-Infinity) = NaN if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_NEG_INF)) return true; - } + }; if (canBeNaN()) resultType |= InputType.NUMBER_NAN; - function canBeFractional () { + const canBeFractional = function () { // For the subtract operation to return a non-whole number one of it's // inputs has to be a non-whole number if (leftType & InputType.NUMBER_FRACT) return true; if (rightType & InputType.NUMBER_FRACT) return true; - } + }; const canBeFract = canBeFractional(); - function canBePos () { + const canBePos = function () { if (leftType & InputType.NUMBER_POS) return true; // POS - ANY ~= POS if (rightType & InputType.NUMBER_NEG) return true; // ANY - NEG ~= POS - } + }; if (canBePos()) { resultType |= InputType.NUMBER_POS_INT | InputType.NUMBER_POS_INF; if (canBeFract) resultType |= InputType.NUMBER_POS_FRACT; } - function canBeNeg () { + const canBeNeg = function () { if (leftType & InputType.NUMBER_NEG) return true; // NEG - ANY ~= NEG if (rightType & InputType.NUMBER_POS) return true; // ANY - POS ~= NEG - } + }; if (canBeNeg()) { resultType |= InputType.NUMBER_NEG_INT | InputType.NUMBER_NEG_INF; if (canBeFract) resultType |= InputType.NUMBER_NEG_FRACT; } - function canBeZero () { + const canBeZero = function () { // POS_REAL - POS_REAL ~= 0 if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_POS_REAL)) return true; // NEG_REAL - NEG_REAL ~= 0 @@ -264,13 +274,13 @@ class IROptimizer { if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; // (-0) - (-0) = 0 if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; - } + }; if (canBeZero()) resultType |= InputType.NUMBER_ZERO; - function canBeNegZero () { + const canBeNegZero = function () { // (-0) - 0 = -0 if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_ZERO)) return true; - } + }; if (canBeNegZero()) resultType |= InputType.NUMBER_NEG_ZERO; return resultType; @@ -282,45 +292,45 @@ class IROptimizer { let resultType = 0; - function canBeNaN () { + const canBeNaN = function () { // (-)Infinity * 0 = NaN if ((leftType & InputType.NUMBER_INF) && (rightType & InputType.NUMBER_ANY_ZERO)) return true; // 0 * (-)Infinity = NaN if ((leftType & InputType.NUMBER_ANY_ZERO) && (rightType & InputType.NUMBER_INF)) return true; - } + }; if (canBeNaN()) resultType |= InputType.NUMBER_NAN; - function canBeFractional () { + const canBeFractional = function () { // For the subtract operation to return a non-whole number one of it's // inputs has to be a non-whole number if (leftType & InputType.NUMBER_FRACT) return true; if (rightType & InputType.NUMBER_FRACT) return true; - } + }; const canBeFract = canBeFractional(); - function canBePos () { + const canBePos = function () { // POS * POS = POS if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_POS)) return true; // NEG * NEG = POS if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_NEG)) return true; - } + }; if (canBePos()) { resultType |= InputType.NUMBER_POS_INT | InputType.NUMBER_POS_INF; if (canBeFract) resultType |= InputType.NUMBER_POS_FRACT; } - function canBeNeg () { + const canBeNeg = function () { // POS * NEG = NEG if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_NEG)) return true; // NEG * POS = NEG if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_POS)) return true; - } + }; if (canBeNeg()) { resultType |= InputType.NUMBER_NEG_INT | InputType.NUMBER_NEG_INF; if (canBeFract) resultType |= InputType.NUMBER_NEG_FRACT; } - function canBeZero () { + const canBeZero = function () { // 0 * 0 = 0 if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_ZERO)) return true; // -0 * -0 = 0 @@ -335,10 +345,10 @@ class IROptimizer { if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; // Rounding errors like 1e-323 * 0.1 = 0 if ((leftType & InputType.NUMBER_FRACT) && (rightType & InputType.NUMBER_FRACT)) return true; - } + }; if (canBeZero()) resultType |= InputType.NUMBER_ZERO; - function canBeNegZero () { + const canBeNegZero = function () { // 0 * -0 = 0 if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; // -0 * 0 = 0 @@ -355,7 +365,7 @@ class IROptimizer { if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_POS_REAL)) return true; // Rounding errors like 1e-323 / -10 = -0 if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_NEG_REAL)) return true; - } + }; if (canBeNegZero()) resultType |= InputType.NUMBER_NEG_ZERO; return resultType; @@ -367,25 +377,25 @@ class IROptimizer { let resultType = 0; - function canBeNaN () { + const canBeNaN = function () { // REAL / 0 = NaN if ((leftType & InputType.NUMBER_REAL) && (rightType & InputType.NUMBER_ZERO)) return true; // (-)Infinity / (-)Infinity = NaN if ((leftType & InputType.NUMBER_INF) && (rightType & InputType.NUMBER_INF)) return true; // (-)0 / NaN = NaN if ((leftType & InputType.NUMBER_ANY_ZERO) && (rightType & InputType.NUMBER_NAN)) return true; - } + }; if (canBeNaN()) resultType |= InputType.NUMBER_NAN; - function canBePos () { + const canBePos = function () { // POS / POS = POS if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_POS)) return true; // NEG / NEG = POS if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_NEG)) return true; - } + }; if (canBePos()) resultType |= InputType.NUMBER_POS; - function canBeNegInfinity () { + const canBeNegInfinity = function () { // -Infinity / 0 = -Infinity if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_ZERO)) return true; // Infinity / -0 = -Infinity @@ -394,28 +404,28 @@ class IROptimizer { if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_NAN)) return true; // NEG_REAL / NUMBER_OR_NAN ~= -Infinity if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_OR_NAN)) return true; - } + }; if (canBeNegInfinity()) resultType |= InputType.NUMBER_NEG_INF; - function canBeInfinity () { + const canBeInfinity = function () { // Infinity / 0 = Infinity if ((leftType & InputType.NUMBER_POS_INF) && (rightType & InputType.NUMBER_ZERO)) return true; // -Infinity / -0 = Infinity if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; // POS_REAL / NUMBER_OR_NAN ~= Infinity if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_OR_NAN)) return true; - } + }; if (canBeInfinity()) resultType |= InputType.NUMBER_POS_INF; - function canBeNeg () { + const canBeNeg = function () { // POS / NEG = NEG if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_NEG)) return true; // NEG / POS = NEG if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_POS)) return true; - } + }; if (canBeNeg()) resultType |= InputType.NUMBER_NEG; - function canBeZero () { + const canBeZero = function () { // 0 / POS = 0 if ((leftType & InputType.NUMBER_ZERO) && (rightType & InputType.NUMBER_POS)) return true; // -0 / NEG = 0 @@ -428,10 +438,10 @@ class IROptimizer { if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_POS_INF)) return true; // NUMBER_NEG / -Infinity = 0 if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_NEG_INF)) return true; - } + }; if (canBeZero()) resultType |= InputType.NUMBER_ZERO; - function canBeNegZero () { + const canBeNegZero = function () { // -0 / POS = -0 if ((leftType & InputType.NUMBER_NEG_ZERO) && (rightType & InputType.NUMBER_POS)) return true; // 0 / NEG = -0 @@ -444,7 +454,7 @@ class IROptimizer { if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_NEG_INF)) return true; // NUMBER_NEG / Infinity = -0 if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_POS_INF)) return true; - } + }; if (canBeNegZero()) resultType |= InputType.NUMBER_NEG_ZERO; return resultType; @@ -472,7 +482,7 @@ class IROptimizer { case InputOpcode.ADDON_CALL: modified = state.clear() || modified; break; - case InputOpcode.PROCEDURE_CALL: + case InputOpcode.PROCEDURE_CALL: { modified = this.analyzeInputs(inputs.inputs, state) || modified; const script = this.ir.procedures[inputs.variant]; @@ -483,6 +493,7 @@ class IROptimizer { } break; } + } return modified; } @@ -557,8 +568,6 @@ class IROptimizer { } } - // if (stackBlock.ignoreState) return false; - return modified; } diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index 31da1f0880d..152d0dafa9b 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -6,7 +6,17 @@ const VariablePool = require('./variable-pool'); const jsexecute = require('./jsexecute'); const environment = require('./environment'); const {StackOpcode, InputOpcode, InputType} = require('./enums.js'); -const {IntermediateStackBlock, IntermediateInput, IntermediateStack, IntermediateScript, IntermediateRepresentation} = require('./intermediate'); + +// These imports are used by jsdoc comments but eslint doesn't know that +/* eslint-disable no-unused-vars */ +const { + IntermediateStackBlock, + IntermediateInput, + IntermediateStack, + IntermediateScript, + IntermediateRepresentation +} = require('./intermediate'); +/* eslint-enable no-unused-vars */ /** * @fileoverview Convert intermediate representations to JavaScript functions. @@ -559,7 +569,7 @@ class JSGenerator { this.retire(); this.source += '}\n'; break; - case StackOpcode.CONTROL_FOR: + case StackOpcode.CONTROL_FOR: { const index = this.localVariables.next(); this.source += `var ${index} = 0; `; this.source += `while (${index} < ${this.descendInput(node.count)}) { `; @@ -569,6 +579,7 @@ class JSGenerator { this.yieldLoop(); this.source += '}\n'; break; + } case StackOpcode.CONTROL_IF_ELSE: this.source += `if (${this.descendInput(node.condition)}) {\n`; this.descendStack(node.whenTrue, new Frame(false)); @@ -697,7 +708,7 @@ class JSGenerator { this.source += 'target.clearEffects();\n'; break; case StackOpcode.LOOKS_EFFECT_CHANGE: - if (this.target.effects.hasOwnProperty(node.effect)) { + if (Object.prototype.hasOwnProperty.call(this.target.effects, node.effect)) { this.source += `target.setEffect("${sanitize(node.effect)}", runtime.ext_scratch3_looks.clampEffect("${sanitize(node.effect)}", ${this.descendInput(node.value)} + target.effects["${sanitize(node.effect)}"]));\n`; } break; @@ -730,7 +741,7 @@ class JSGenerator { this.source += 'target.setCostume(target.currentCostume + 1);\n'; break; case StackOpcode.LOOKS_EFFECT_SET: - if (this.target.effects.hasOwnProperty(node.effect)) { + if (Object.prototype.hasOwnProperty.call(this.target.effects, node.effect)) { this.source += `target.setEffect("${sanitize(node.effect)}", runtime.ext_scratch3_looks.clampEffect("${sanitize(node.effect)}", ${this.descendInput(node.value)}));\n`; } break; @@ -965,7 +976,7 @@ class JSGenerator { * @returns {string} */ evaluateOnce (source) { - if (this._setupVariables.hasOwnProperty(source)) { + if (Object.prototype.hasOwnProperty.call(this._setupVariables, source)) { return this._setupVariables[source]; } const variable = this._setupVariablesPool.next(); diff --git a/test/integration/tw_operator_type_matrix.js b/test/integration/tw_operator_type_matrix.js index 4bb8499764a..3cba07213c0 100644 --- a/test/integration/tw_operator_type_matrix.js +++ b/test/integration/tw_operator_type_matrix.js @@ -179,12 +179,13 @@ test('operator type matrix', async t => { t.ok( irOperator.isSometimesType(expectedType), - `${operator.opcode}${JSON.stringify(operator.fields)}[${inputs.map(str)}] outputted value ${str(reportedValue)} is of the expected type ${irOperator.type}.` + `${operator.opcode}${JSON.stringify(operator.fields)}[${inputs.map(str)}] ` + + `outputted value ${str(reportedValue)} is of the expected type ${irOperator.type}.` ); }; for (const operator of OPERATORS) { - if (operator.inputNames.length == 2) { + if (operator.inputNames.length === 2) { for (const left of VALUES) { for (const right of VALUES) { await testOperator(operator, [left, right]); diff --git a/test/integration/tw_type_assertions.js b/test/integration/tw_type_assertions.js index 4f8cc9e6f40..c2958ccf1b4 100644 --- a/test/integration/tw_type_assertions.js +++ b/test/integration/tw_type_assertions.js @@ -78,21 +78,25 @@ test('type assertions', async t => { const thread = vm.runtime.startHats('event_whenflagclicked')[0]; - function* enumerateAssertions (blocks, region) { + const enumerateAssertions = function* (blocks, region) { for (const block of blocks) { if (block.opcode === StackOpcode.COMPATIBILITY_LAYER) { switch (block.inputs.opcode) { case 'typeassert_assert': yield {block, region}; break; - case 'typeassert_region': + case 'typeassert_region': { const newRegionNameInput = block.inputs.inputs.NAME; if (newRegionNameInput.opcode !== InputOpcode.CONSTANT) { throw new Error('Region block inputs must be a constant.'); } - yield* enumerateAssertions(block.inputs.substacks["1"].blocks, (region ? `${region}, ` : '') + newRegionNameInput.inputs.value); + yield* enumerateAssertions( + block.inputs.substacks['1'].blocks, + (region ? `${region}, ` : '') + newRegionNameInput.inputs.value + ); break; } + } } else { for (const inputName in block.inputs) { const input = block.inputs[inputName]; @@ -102,15 +106,12 @@ test('type assertions', async t => { } } } - } + }; const irGenerator = new IRGenerator(thread); const ir = irGenerator.generate(); - runTests('run tests with yields', false); - runTests('run tests without yields', true); - - function runTests (proccode, ignoreYields) { + const runTests = function (proccode, ignoreYields) { const assertions = [...enumerateAssertions(ir.getProcedure(proccode).stack.blocks)]; @@ -156,8 +157,9 @@ test('type assertions', async t => { let message; - if (valueInput.opcode == InputOpcode.VAR_GET) { - message = `(${region}) assert variable '${valueInput.inputs.variable.name}' (type ${valueInput.type}) is ${adverb} ${noun}`; + if (valueInput.opcode === InputOpcode.VAR_GET) { + message = `(${region}) assert variable '${valueInput.inputs.variable.name}' ` + + `(type ${valueInput.type}) is ${adverb} ${noun}`; } else { message = `(${region}) assert ${valueInput.opcode} (type ${valueInput.type}) is ${adverb} ${noun}`; } @@ -178,7 +180,10 @@ test('type assertions', async t => { default: throw new Error(`$Invalid adverb menu option ${adverb}`); } } - } + }; + + runTests('run tests with yields', false); + runTests('run tests without yields', true); t.end(); }); diff --git a/test/snapshot/lib.js b/test/snapshot/lib.js index 7c817a85828..b49033ffa02 100644 --- a/test/snapshot/lib.js +++ b/test/snapshot/lib.js @@ -95,7 +95,6 @@ const generateActualSnapshot = async testCase => { const generatedJS = []; JSGenerator.testingApparatus = { report: (jsgen, factorySource) => { - console.log(jsgen.script); const targetName = jsgen.target.getName(); const scriptName = jsgen.script.procedureVariant || 'script'; const js = normalizeJS(factorySource); From 1dc9aadf3fab9d0970576dfe4ff46d7017fa19ab Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Tue, 25 Jun 2024 18:25:47 +1000 Subject: [PATCH 07/56] Fix issue when setting variables to InputType.ANY and add test --- src/compiler/iroptimizer.js | 18 +++++++----------- test/fixtures/tw-type-assertions.sb3 | Bin 4947 -> 5400 bytes ...procedure-return-recursion.sb3.tw-snapshot | 8 ++++---- ...procedure-return-recursion.sb3.tw-snapshot | 8 ++++---- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index c161f3c8a4f..15424a7263e 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -17,8 +17,6 @@ class TypeState { constructor () { /** @type {Object.}*/ this.variables = {}; - /** @type {InputType | 0} */ - this.defaultType = 0; } /** @@ -33,7 +31,6 @@ class TypeState { } } this.variables = {}; - this.defaultType = InputType.ANY; return modified; } @@ -46,7 +43,6 @@ class TypeState { for (const varId in this.variables) { clone.variables[varId] = this.variables[varId]; } - clone.defaultType = this.defaultType; return clone; } @@ -84,8 +80,8 @@ class TypeState { */ or (other) { return this.mutate(other, varId => { - const thisType = this.variables[varId] ?? this.defaultType; - const otherType = other.variables[varId] ?? other.defaultType; + const thisType = this.variables[varId] ?? InputType.ANY; + const otherType = other.variables[varId] ?? InputType.ANY; return thisType | otherType; }); } @@ -96,9 +92,9 @@ class TypeState { */ after (other) { return this.mutate(other, varId => { - const otherType = other.variables[varId] ?? other.defaultType; + const otherType = other.variables[varId]; if (otherType !== 0) return otherType; - return this.variables[varId] ?? this.defaultType; + return this.variables[varId] ?? InputType.ANY; }); } @@ -108,7 +104,7 @@ class TypeState { * @returns {boolean} */ setVariableType (variable, type) { - if (this.getVariableType(variable) === type) return false; + if (this.variables[variable.id] === type) return false; this.variables[variable.id] = type; return true; } @@ -119,7 +115,7 @@ class TypeState { * @returns {InputType} */ getVariableType (variable) { - return this.variables[variable.id] ?? (this.defaultType === 0 ? InputType.ANY : this.defaultType); + return this.variables[variable.id] ?? InputType.ANY; } } @@ -528,7 +524,7 @@ class IROptimizer { state = state.clone(); } - modified = modified || this.analyzeInputs(inputs, state); + modified = this.analyzeInputs(inputs, state) || modified; switch (stackBlock.opcode) { case StackOpcode.VAR_SET: diff --git a/test/fixtures/tw-type-assertions.sb3 b/test/fixtures/tw-type-assertions.sb3 index 406d204a94e856ea9271bda0bad10ec6eda0c911..29392ebe18ebd7d02e3b51aef8f8101cb342b93e 100644 GIT binary patch delta 4206 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 F004rQF>?R_ diff --git a/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot index ee666ba30fc..b7824ee767a 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot @@ -10,17 +10,17 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 18",}, b0, false, false, "G", null); b1.value = 0; b2.value = (yield* thread.procedures["Znon warp recursion should yield %s"](8)); -if ((b1.value === 4)) { +if (((+b1.value || 0) === 4)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp recursion yields",}, b0, false, false, "ao", null); } b1.value = 0; b2.value = thread.procedures["Wwarp recursion should not yield %s"](8); -if ((b1.value === 0)) { +if (compareEqual(b1.value, 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass warp recursion does not yield",}, b0, false, false, "ar", null); } b1.value = 0; b2.value = (yield* thread.procedures["Zfib %s"](7)); -if ((b1.value === 20)) { +if (((+b1.value || 0) === 20)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp fib yielded",}, b0, false, false, "au", null); } yield* thread.procedures["Zrecursing yields between each %s"]("initial"); @@ -68,7 +68,7 @@ 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 === 3)) { +if (((+b0.value || 0) === 3)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recursing between calls yields final",}, b2, false, false, "aK", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":"fail recursing between calls yields final",}, b2, false, false, "aL", null); 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 index ee666ba30fc..b7824ee767a 100644 --- 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 @@ -10,17 +10,17 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 18",}, b0, false, false, "G", null); b1.value = 0; b2.value = (yield* thread.procedures["Znon warp recursion should yield %s"](8)); -if ((b1.value === 4)) { +if (((+b1.value || 0) === 4)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp recursion yields",}, b0, false, false, "ao", null); } b1.value = 0; b2.value = thread.procedures["Wwarp recursion should not yield %s"](8); -if ((b1.value === 0)) { +if (compareEqual(b1.value, 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass warp recursion does not yield",}, b0, false, false, "ar", null); } b1.value = 0; b2.value = (yield* thread.procedures["Zfib %s"](7)); -if ((b1.value === 20)) { +if (((+b1.value || 0) === 20)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp fib yielded",}, b0, false, false, "au", null); } yield* thread.procedures["Zrecursing yields between each %s"]("initial"); @@ -68,7 +68,7 @@ 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 === 3)) { +if (((+b0.value || 0) === 3)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass recursing between calls yields final",}, b2, false, false, "aK", null); } else { yield* executeInCompatibilityLayer({"MESSAGE":"fail recursing between calls yields final",}, b2, false, false, "aL", null); From 1dd7744525b4be05674b766dcdc93253abefeddd Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:45:49 +1000 Subject: [PATCH 08/56] Handle colors correctly + more smartly --- src/compiler/enums.js | 8 ++++++-- src/compiler/intermediate.js | 11 +++++++++-- src/compiler/irgen.js | 6 +++--- src/compiler/jsgen.js | 15 +++++++++++++-- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/compiler/enums.js b/src/compiler/enums.js index c1a4e90fc24..9a68a186f78 100644 --- a/src/compiler/enums.js +++ b/src/compiler/enums.js @@ -84,8 +84,11 @@ const InputType = { /** Any input that can be interperated as a boolean. Equal to BOOLEAN | STRING_BOOLEAN */ BOOLEAN_INTERPRETABLE: 0x1800, - /** Any type. Equal to NUMBER_OR_NAN | STRING | BOOLEAN */ - ANY: 0x1FFF + /** Any value type (a type a scratch variable can hold). Equal to NUMBER_OR_NAN | STRING | BOOLEAN */ + ANY: 0x1FFF, + + /** An array of values in the form [R, G, B] */ + COLOR: 0x2000 }; /** @@ -195,6 +198,7 @@ const InputOpcode = { CAST_NUMBER_OR_NAN: 'cast.toNumberOrNaN', CAST_STRING: 'cast.toString', CAST_BOOLEAN: 'cast.toBoolean', + CAST_COLOR: 'cast.toColor', COMPATIBILITY_LAYER: 'compat', diff --git a/src/compiler/intermediate.js b/src/compiler/intermediate.js index 581ddb7e817..23c82cc8fe8 100644 --- a/src/compiler/intermediate.js +++ b/src/compiler/intermediate.js @@ -154,6 +154,9 @@ class IntermediateInput { case InputType.STRING: castOpcode = InputOpcode.CAST_STRING; break; + case InputType.COLOR: + castOpcode = InputOpcode.CAST_COLOR; + break; default: log.warn(`Cannot cast to type: ${targetType}`, this); throw new Error(`Cannot cast to type: ${targetType}`); @@ -175,7 +178,7 @@ class IntermediateInput { this.type = InputType.NUMBER; this.inputs.value = +Cast.toBoolean(this.inputs.value); } - let numberValue = +this.inputs.value; + const numberValue = +this.inputs.value; if (numberValue) { this.inputs.value = numberValue; } else /* numberValue is one of 0, -0, or NaN */ if (Object.is(numberValue, -0)) { @@ -185,7 +188,7 @@ class IntermediateInput { } if (castOpcode === InputOpcode.CAST_NUMBER_INDEX) { // Round numberValue to an integer - numberValue |= 0; + this.inputs.value |= 0; } this.type = IntermediateInput.getNumberInputType(this.inputs.value); break; @@ -194,6 +197,10 @@ class IntermediateInput { this.inputs.value += ''; this.type = InputType.STRING; break; + case InputOpcode.CAST_COLOR: + this.inputs.value = Cast.toRgbColorList(this.inputs.value); + this.type = InputType.COLOR; + break; } return this; } diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 7c75b3a32a4..47a0e070d4e 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -468,8 +468,8 @@ class ScriptTreeGenerator { case 'sensing_coloristouchingcolor': return new IntermediateInput(InputOpcode.SENSING_COLOR_TOUCHING_COLOR, InputType.BOOLEAN, { - target: this.descendInputOfBlock(block, 'COLOR2'), - mask: this.descendInputOfBlock(block, 'COLOR') + target: this.descendInputOfBlock(block, 'COLOR2').toType(InputType.COLOR), + mask: this.descendInputOfBlock(block, 'COLOR').toType(InputType.COLOR) }); case 'sensing_current': switch (block.fields.CURRENTMENU.value.toLowerCase()) { @@ -542,7 +542,7 @@ class ScriptTreeGenerator { return new IntermediateInput(InputOpcode.SENSING_TIMER_GET, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO); case 'sensing_touchingcolor': return new IntermediateInput(InputOpcode.SENSING_TOUCHING_COLOR, InputType.BOOLEAN, { - color: this.descendInputOfBlock(block, 'COLOR').toType(InputType.NUMBER) + color: this.descendInputOfBlock(block, 'COLOR').toType(InputType.COLOR) }); case 'sensing_touchingobject': return new IntermediateInput(InputOpcode.SENSING_TOUCHING_OBJECT, InputType.BOOLEAN, { diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index 152d0dafa9b..2f3c88e7327 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -193,6 +193,8 @@ class JSGenerator { return `(${this.descendInput(node.target.toType(InputType.NUMBER_OR_NAN))} | 0)`; case InputOpcode.CAST_STRING: return `("" + ${this.descendInput(node.target)})`; + case InputOpcode.CAST_COLOR: + return `colorToList(${this.descendInput(node.target)})`; case InputOpcode.COMPATIBILITY_LAYER: // Compatibility layer inputs never use flags. @@ -206,6 +208,15 @@ class JSGenerator { } else if (block.isAlwaysType(InputType.BOOLEAN)) { if (typeof node.value !== 'boolean') throw new Error(`JS: '${block.type}' type constant had ${typeof node.value} type value. Expected boolean.`); return node.value.toString(); + } else if (block.isAlwaysType(InputType.COLOR)) { + if (!Array.isArray(node.value)) throw new Error(`JS: '${block.type}' type constant was not an array.`); + if (node.value.length !== 3) throw new Error(`JS: '${block.type}' type constant had an array of length '${node.value.length}'. Expected 3.`); + for (let i = 0; i < 3; i++) { + if (typeof node.value[i] !== 'number') { + throw new Error(`JS: '${block.type}' type constant element ${i} had a value of type '${node.value[i]}'. Expected number.`); + } + } + return `[${node.value[0]},${node.value[1]},${node.value[2]}]`; } else if (block.isSometimesType(InputType.STRING)) { return `"${sanitize(node.value.toString())}"`; } throw new Error(`JS: Unknown constant input type '${block.type}'.`); @@ -412,7 +423,7 @@ class JSGenerator { case InputOpcode.SENSING_ANSWER: return `runtime.ext_scratch3_sensing._answer`; case InputOpcode.SENSING_COLOR_TOUCHING_COLOR: - return `target.colorIsTouchingColor(colorToList(${this.descendInput(node.target)}), colorToList(${this.descendInput(node.mask)}))`; + return `target.colorIsTouchingColor(${this.descendInput(node.target)}, ${this.descendInput(node.mask)})`; case InputOpcode.SENSING_TIME_DATE: return `(new Date().getDate())`; case InputOpcode.SENSING_TIME_WEEKDAY: @@ -464,7 +475,7 @@ class JSGenerator { case InputOpcode.SENSING_TOUCHING_OBJECT: return `target.isTouchingObject(${this.descendInput(node.object)})`; case InputOpcode.SENSING_TOUCHING_COLOR: - return `target.isTouchingColor(colorToList(${this.descendInput(node.color)}))`; + return `target.isTouchingColor(${this.descendInput(node.color)})`; case InputOpcode.SENSING_USERNAME: return 'runtime.ioDevices.userData.getUsername()'; case InputOpcode.SENSING_TIME_YEAR: From 177b8991389726a51cd5c12fa1d3775cbcdfad60 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:58:07 +1000 Subject: [PATCH 09/56] Fix issue when switching to costume with a number name --- src/compiler/irgen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 47a0e070d4e..bfbcb613f26 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -582,7 +582,7 @@ class ScriptTreeGenerator { const inputs = Object.keys(block.inputs); const fields = Object.keys(block.fields); if (inputs.length === 0 && fields.length === 1) { - return this.createConstantInput(block.fields[fields[0]].value); + return this.createConstantInput(block.fields[fields[0]].value, preserveStrings); } log.warn(`IR: Unknown input: ${block.opcode}`, block); From a589e57d9edc6008bd9b140034c74d53fafb177c Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 28 Jul 2024 20:14:05 +1000 Subject: [PATCH 10/56] Fix old and add new unsupported APIs. --- src/compiler/irgen.js | 5 ++++- src/virtual-machine.js | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index bfbcb613f26..63c22d018ab 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -1494,4 +1494,7 @@ class IRGenerator { } } -module.exports = IRGenerator; +module.exports = { + ScriptTreeGenerator, + IRGenerator +}; diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 09e9593353d..2dcc9b6612c 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -228,6 +228,11 @@ class VirtualMachine extends EventEmitter { JSGenerator: require('./compiler/jsgen.js'), IRGenerator: require('./compiler/irgen.js').IRGenerator, ScriptTreeGenerator: require('./compiler/irgen.js').ScriptTreeGenerator, + IntermediateStackBlock: require("./compiler/intermediate.js").IntermediateStackBlock, + IntermediateInput: require("./compiler/intermediate.js").IntermediateInput, + IntermediateStack: require("./compiler/intermediate.js").IntermediateStack, + IntermediateScript: require("./compiler/intermediate.js").IntermediateScript, + IntermediateRepresentation: require("./compiler/intermediate.js").IntermediateRepresentation, Thread: require('./engine/thread.js'), execute: require('./engine/execute.js') }); From 34491603f88bfc5c29aa31a9e9c47c28254e9d30 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 28 Jul 2024 20:22:30 +1000 Subject: [PATCH 11/56] Linty linty code --- src/virtual-machine.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 2dcc9b6612c..4ed5ebf09fc 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -228,11 +228,11 @@ class VirtualMachine extends EventEmitter { JSGenerator: require('./compiler/jsgen.js'), IRGenerator: require('./compiler/irgen.js').IRGenerator, ScriptTreeGenerator: require('./compiler/irgen.js').ScriptTreeGenerator, - IntermediateStackBlock: require("./compiler/intermediate.js").IntermediateStackBlock, - IntermediateInput: require("./compiler/intermediate.js").IntermediateInput, - IntermediateStack: require("./compiler/intermediate.js").IntermediateStack, - IntermediateScript: require("./compiler/intermediate.js").IntermediateScript, - IntermediateRepresentation: require("./compiler/intermediate.js").IntermediateRepresentation, + IntermediateStackBlock: require('./compiler/intermediate.js').IntermediateStackBlock, + IntermediateInput: require('./compiler/intermediate.js').IntermediateInput, + IntermediateStack: require('./compiler/intermediate.js').IntermediateStack, + IntermediateScript: require('./compiler/intermediate.js').IntermediateScript, + IntermediateRepresentation: require('./compiler/intermediate.js').IntermediateRepresentation, Thread: require('./engine/thread.js'), execute: require('./engine/execute.js') }); From 59cc74c10b932352e04224ac5f6d7048ed89483c Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 28 Jul 2024 20:31:07 +1000 Subject: [PATCH 12/56] Fix imports :c --- src/compiler/compile.js | 2 +- test/integration/tw_operator_type_matrix.js | 2 +- test/integration/tw_type_assertions.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/compile.js b/src/compiler/compile.js index 4a5c5029636..3b5125f5efe 100644 --- a/src/compiler/compile.js +++ b/src/compiler/compile.js @@ -1,6 +1,6 @@ // @ts-check -const IRGenerator = require('./irgen'); +const {IRGenerator} = require('./irgen'); const {IROptimizer} = require('./iroptimizer'); const JSGenerator = require('./jsgen'); diff --git a/test/integration/tw_operator_type_matrix.js b/test/integration/tw_operator_type_matrix.js index 3cba07213c0..2402067302c 100644 --- a/test/integration/tw_operator_type_matrix.js +++ b/test/integration/tw_operator_type_matrix.js @@ -1,7 +1,7 @@ const {test} = require('tap'); const VM = require('../../src/virtual-machine'); const {BlockType, ArgumentType} = require('../../src/extension-support/tw-extension-api-common'); -const IRGenerator = require('../../src/compiler/irgen'); +const {IRGenerator} = require('../../src/compiler/irgen'); const {IROptimizer} = require('../../src/compiler/iroptimizer'); const {IntermediateInput} = require('../../src/compiler/intermediate'); const nanolog = require('@turbowarp/nanolog'); diff --git a/test/integration/tw_type_assertions.js b/test/integration/tw_type_assertions.js index c2958ccf1b4..745ffac7014 100644 --- a/test/integration/tw_type_assertions.js +++ b/test/integration/tw_type_assertions.js @@ -4,7 +4,7 @@ const {test} = require('tap'); const VM = require('../../src/virtual-machine'); const BlockType = require('../../src/extension-support/block-type'); const ArgumentType = require('../../src/extension-support/argument-type'); -const IRGenerator = require('../../src/compiler/irgen'); +const {IRGenerator} = require('../../src/compiler/irgen'); const {IROptimizer} = require('../../src/compiler/iroptimizer'); const {StackOpcode, InputType, InputOpcode} = require('../../src/compiler/enums'); const {IntermediateStack} = require('../../src/compiler/intermediate'); From 430353b1e36c62a867415d46e3470c54b3920e20 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 28 Jul 2024 20:36:24 +1000 Subject: [PATCH 13/56] Re-add JSGenerator.unstable_exports and expose enums as unsupported APIs --- src/compiler/jsgen.js | 12 ++++++++++++ src/virtual-machine.js | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index 2f3c88e7327..03cd375bdf1 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -1179,6 +1179,18 @@ class JSGenerator { } } +// For extensions. +JSGenerator.unstable_exports = { + factoryNameVariablePool, + functionNameVariablePool, + generatorNameVariablePool, + VariablePool, + PEN_EXT, + PEN_STATE, + Frame, + sanitize +}; + // Test hook used by automated snapshot testing. JSGenerator.testingApparatus = null; diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 4ed5ebf09fc..419bf3bd855 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -233,6 +233,9 @@ class VirtualMachine extends EventEmitter { IntermediateStack: require('./compiler/intermediate.js').IntermediateStack, IntermediateScript: require('./compiler/intermediate.js').IntermediateScript, IntermediateRepresentation: require('./compiler/intermediate.js').IntermediateRepresentation, + StackOpcode: require('./compiler/enums.js').StackOpcode, + InputOpcode: require('./compiler/enums.js').InputOpcode, + InputType: require('./compiler/enums.js').InputType, Thread: require('./engine/thread.js'), execute: require('./engine/execute.js') }); From fbca1bf49b3ec1c4b2c1b4defb5c533a00a6db0e Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Mon, 8 Sep 2025 11:48:55 +1000 Subject: [PATCH 14/56] Fixed `NaN = 0` wrongly reporting true, and add a test --- src/compiler/jsgen.js | 4 +- test/fixtures/execute/tw-NaN.sb3 | Bin 2773 -> 2869 bytes .../__snapshots__/tw-NaN.sb3.tw-snapshot | 49 ++++++++++-------- .../__snapshots__/tw-tangent.sb3.tw-snapshot | 14 ++--- .../warp-timer/tw-NaN.sb3.tw-snapshot | 49 ++++++++++-------- .../warp-timer/tw-tangent.sb3.tw-snapshot | 14 ++--- 6 files changed, 68 insertions(+), 62 deletions(-) diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index 10e41eaa9d4..82f6caf839d 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -57,8 +57,8 @@ const isSafeInputForEqualsOptimization = (input, other) => { if (input.opcode !== InputOpcode.CONSTANT) return false; // Only optimize when the constant can always be thought of as a number if (input.isAlwaysType(InputType.NUMBER) || input.isAlwaysType(InputType.STRING_NUM)) { - if (other.isSometimesType(InputType.STRING_NAN) || other.isSometimesType(InputType.BOOLEAN_INTERPRETABLE)) { - // Never optimize 0 if the other input can be '' or a boolean. + if (other.isSometimesType(InputType.NUMBER_NAN | InputType.STRING_NAN | InputType.BOOLEAN_INTERPRETABLE)) { + // Never optimize 0 if the other input can be NaN, '' or a boolean. // eg. if '< 0 = "" >' was optimized it would turn into `0 === +""`, // which would be true even though Scratch would return false. return (+input.inputs.value) !== 0; diff --git a/test/fixtures/execute/tw-NaN.sb3 b/test/fixtures/execute/tw-NaN.sb3 index 17ddc26bdb14c47bb00600accae4b19495d903eb..411b0c6670dc97141a3da3f88a42f25a25e06e48 100644 GIT binary patch delta 2305 zcmV+c3I6ug6}1+BP)h>@3IG5A2mouh$WTllbhT~>002ls000aC003}uZ)#;@bS`Rh zZ*IMud2^dM7{3YIZO~l z8puB$5JEh>@9%v8+u|Bf7+8pgpnrS~a5$pSLZDBB2sMCz2VJ%dlC}nz!T>|fL9Flf zO#?VMr0Jp-5NPP|e%&GZBwf~t>mu)Viyj35TiYZdjpD&uafkVGq98q_2K9*%5MO17 zH6VpMByb^R8^Wo@nqU|rx@)j?-Ds;lwQIC`9n~~UQ){=|tuAUIy@!m|E@YiuqlYf& z6Z$rj!2?2nA;rYoWI9ps6ZG5NR<`vQ=&P%(o0A3zN#q%^cD6hv&itg*7Tdvrmnawp z1`6h9Ns;Rl&|hYYQ*0nI4xktMkOf1(1pVG^GmABi13)|w?{h)NWaJ~H+k4QnqIqt- z$Z>8O50YgU%Jv4gNNy?H2O2@Nn;9|ty$ zGWPsvJ{%Z++dfzyY^f|Jod&o7jboLa9XMeDN;kFHCN*hpv34fF#K;MSDf@tt4wcmK^xoV z(bJiYJkx=e?qGd_j5~c`1Ki6?#NO$9>TwevX2AEMRhwEk)PK1V)tQ;AUY#YAYuB9LcDX!sz1hstIiDhU*=$DHhXCiXDBl+gE4EJj8x ziP|U;0nh_Z8R5km9Ox-+nbOf0Waz}B0dY>Td9TBKr~@tN*c=YFcE`KJ0aMa$umrLs zjSe?P0USug%`AaenZ zWYF4`M=Qi4kJ6`NXxX)(g-g*o16&aqw7xt>>slPGFEVK10<^wX@26ko{PYXQPyemb zPydyH{_Ar=e=P<*t_AwDQs^guDrCEwKBg zu%832hz#t9Ij~Q}VLz0C-7oS_rxyQoI7k&Q(Bao(%Ly&jP(G2K{+0&v#2r`diLWfAI`8sMJsc8R~yO5B29_)Q7cDPfMwf09QnY`WHFWhvL+~kfELyQ2(-e z>R-xH|AIsPt16*?CByv79OmP4q)++JzSwWBkmd_4{u$#H|AA3|t>|ZrP^=(nEd?2s z79<2*5t)K~ol}sJctO6FDafdxAm3D9kZkGnm4u-c5kk0Q*k~SWutygKQRN^?Z{EC5^@_~9^6;Yh| zebU0J(sdz5ddXxlNzzd%>H0&`H;PDqmqYrxIOz?UTZMN;--c{df3E=7#j=NL$=8Z9biUJDwh&h>53KgJ%|tSg6gXpfyFYSr0{2I0D3~fU>^MqDK#n zn7YtEKp}CW_{{>Kua>;*pwsEB!S-6O13Q|z+}5;z)fVb?O|!SsGc~l`gIEQK^|-{tto~9(?4*$wvfk;si|G7DG(_$+76v-aRT<1PTBE z00;nUx5!YFMhMXaYq!WylN$-r4r{l_P)r|mwQdLi07yfV`w1)rYq!WylPd}u0hp6U b3Nr+2x5!YFeF_=@ZIhS^HwHlp00000jP_Al delta 2208 zcmV;R2w(TL7S$DhP)h>@3IG5A2mpKxh*jg$Dqju=001>W000aC003}uZ)#;@bS`Rh zZ*IL?3v-(`0RAgXz1{8MdVuZP@ou-Jd9-O0yN#Vbmt_J3CkYv1h(ZT}K8>cR0RrfMv)3RUYk(PyFf=^Gj=i~U z01wABduc=jS|(g=d&Hb%uTA3nD7f2WPNRrz?U9&HlgZWOfaU8HL3T+4niDG`p~fz2 zK?)B^W?Qyx=(?`9k&4U?vU+XE4)(1Mx~7jf zwwX>I5eg}PCcz%ViK0)Suea6w=x5N^dg|?Y1H@z+SP6FadPcnEO=&7S!GjAF4I>Lh z%e$n=4+-eMU@vFbLS!7lAPylT!(4!V=WdvBO%no00FraA>6nZ{giQAs2F`S;O)&Mm z+r|U2Z0N-g)Cwa^ktU}XP?KLIG>NrD5uylE5?!Ewt0}t(n=@EanLucOCHjO|%68x& zoGzyW!*>U#yMuj=anfo42IQ;8?hc$Yg0fvY+oh$$DjR12Y>YfB0~5Q@BJ=y(q9i;< zGvs9hx5<=SG5S-F;?P_0P^xPv2WKZ4LY*OOX8_q~2Cki4M;x2Z!3L>ILLM?Lk1PcS zFH>ZH&+|Z19>>E|b{xwuvr01I`D5_4Wb`!)^lewKZ(B~^7DwM{mHKwpu(5psy_mZw zus!IQ9yTY)y2l4*U~mV zXtew4V5L#W>QsV?t{4cj7z7F|2@E|stIW55StT`vy6Kanz{UZl3kiHXVjNku2sJAK zvGm71hE53v4+e@Z6Kl8z!z2NXhzF^7U1fd<886Y+cR}GCxkml=RE_#ftWk|x8ugnTNej*s%8yk( zq5L581bV&Y2{ePyR0|(JA^Y=9;Q14khSCPy(#VB!cQcefiHCAmCX}cklrO6f<w`uxc#7(~^V|u&5^uN~e4*rXn zvFuum<&=u&0xpQ0c>9~;HN?f+ml4k?5bvP+#yXIT<31;j&#R1ML(EoAEw=hqrqcK1 zD?P>{9}_*7L3i*}=-kpsV!#zJ7s{fOJ|#otiGR}mFx_am7&UKYghk2<>avb4q~fNT0(jU8>)*q`Dx z_NQ13FKdb6q?8s9a6#l~d9z7>%U|NOypgffq`*#Zt8e$Wa_#h06?l~-zcA8-lES^jv_^5^1~KbEn)S77;n>S)k&F=KhP7|SmeF92K+Iq^<5 z#k&v}??gsCzd*b}^?fyv>#Gw^U;SHUUtNgV%CE&%|8NK*77xcU|Cw)plNSj@{|X}- z)Ql)7jUEA9p>ok1Zbt7$JbFW!=miDQ`(8(H-iR4IsKwxRtaq&h=*;*MF?i^dDtB|6z;glQ`Os`M-RzuQVZl5;J~Oi}9hoou=Zi zD?al7P}HjVx*{$OBn4b@a)JD`C6JDIAV0|jGA;<@=jsFbSuT*DID!09Wgx%E1oAT{ zknUpwDfUlo(3Oe8m)fHM9kHT_Ybgp^8b}jxLF59dZV4n74`PDD5>Uho{54BJ#ik>x_rlsGp5W z*~gc$^|XFldlln)UJ0<mE^kei3#VwVLBHMYH9J~w^^-2H>vWrHtGoJP z5a!sT?5&{H)tcgTI5Zivq=1D0lDGIB?j}$WO+!hB8Nr z2?K@Ym`&%W^RV4)zz?y9B4rYgd3-+}r-vITnZBuP76Pt8CEgeq{(L|t7uy@AzL8VaV$9DAM;)8uz{ZLn16af`sSv1E0ufvFF} zQxp?#nykWRT9qseZ?#%GP~YjaV9T&y=!Vf#QKxO&oo>f6%;hT0`-S0U$MP12Z(A1r z_(kDbvgA9)hKC+3^K89XI#KVjNtl7M(<~ezGrjLton$kDjsuq`X7;*+F`|oPl%>;C zV3eQ$2_ByWn1wS##BLe|F*`cj_7Pov*c2mwmH-2-rrJzaOV3#M0JW(vH=vV7m?`)_ zP)h>@3IG5A2mpKxh*guO2+;(542V^edkNAGd<=+HSC42V^e iiwYV6IFqIdGX#7Lh*gu_3K{_ollTfZ2H^<+0000tjU3 Date: Mon, 8 Sep 2025 15:11:03 +1000 Subject: [PATCH 15/56] Re-analyze loop block inputs after each loop --- src/compiler/iroptimizer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index 15424a7263e..4e2941018d6 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -610,8 +610,9 @@ class IROptimizer { let keepLooping; do { const newState = state.clone(); - this.analyzeStack(stack, newState); - modified = keepLooping = state.or(newState); + modified = this.analyzeStack(stack, newState) || modified; + modified = (keepLooping = state.or(newState)) || modified; + modified = this.analyzeInputs(block.inputs, state) || modified; } while (keepLooping); block.entryState = state.clone(); return modified; From d8c73c71d255d1a9a6526b1cd7e3da8c077fac89 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Mon, 8 Sep 2025 15:28:11 +1000 Subject: [PATCH 16/56] Account for `stop this script` in type analysis and add test --- src/compiler/iroptimizer.js | 27 +++++++++++++++++++++++++-- test/fixtures/tw-type-assertions.sb3 | Bin 5400 -> 5697 bytes 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index 4e2941018d6..c17379102e8 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -129,6 +129,9 @@ class IROptimizer { this.ir = ir; /** @type {boolean} Used for testing */ this.ignoreYields = false; + + /** @private @type {TypeState | null} The state the analyzed script could exit in */ + this.exitState = null; } /** @@ -510,6 +513,18 @@ class IROptimizer { return modified; } + /** + * @param {TypeState} state + */ + addPossibleExitState (state) { + if (this.exitState === null) { + this.exitState = state.clone(); + return; + } + + this.exitState.or(state); + } + /** * @param {IntermediateStackBlock} stackBlock * @param {TypeState} state @@ -542,6 +557,10 @@ class IROptimizer { modified = state.or(trueState) || modified; break; } + case StackOpcode.CONTROL_STOP_SCRIPT: { + this.addPossibleExitState(state); + break; + } case StackOpcode.PROCEDURE_CALL: { modified = this.analyzeInputs(inputs.inputs, state) || modified; const script = this.ir.procedures[inputs.variant]; @@ -691,8 +710,12 @@ class IROptimizer { this.optimizeScript(this.ir.procedures[procVariant], alreadyOptimized); } - script.cachedAnalysisEndState = new TypeState(); - this.analyzeStack(script.stack, script.cachedAnalysisEndState); + this.exitState = null; + const exitState = new TypeState(); + this.analyzeStack(script.stack, exitState); + + this.addPossibleExitState(exitState); + script.cachedAnalysisEndState = this.exitState; this.optimizeStack(script.stack, new TypeState()); } diff --git a/test/fixtures/tw-type-assertions.sb3 b/test/fixtures/tw-type-assertions.sb3 index 29392ebe18ebd7d02e3b51aef8f8101cb342b93e..1c91c399a197b9e6f845024d0d46980b9baef1b6 100644 GIT binary patch delta 4499 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 From 7cc8f580b3e15b805ef5b1b441b190392cfcd49b Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Mon, 8 Sep 2025 17:08:23 +1000 Subject: [PATCH 17/56] Fix yielding loop arguments not re-analyzing --- src/compiler/iroptimizer.js | 4 +++- .../tw-warp-repeat-until-timer-greater-than.sb3.tw-snapshot | 2 +- .../tw-zombie-cube-escape-284516654.sb3.tw-snapshot | 2 +- .../tw-warp-repeat-until-timer-greater-than.sb3.tw-snapshot | 2 +- .../tw-zombie-cube-escape-284516654.sb3.tw-snapshot | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index c17379102e8..100f38900fa 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -620,11 +620,13 @@ class IROptimizer { */ analyzeLoopedStack (stack, state, block) { if (block.yields && !this.ignoreYields) { - const modified = state.clear(); + let modified = state.clear(); block.entryState = state.clone(); block.exitState = state.clone(); + modified = this.analyzeInputs(block.inputs, state) || modified; return this.analyzeStack(stack, state) || modified; } + let modified = false; let keepLooping; do { 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 e5108d03f52..8f918a81081 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 @@ -18,7 +18,7 @@ const b1 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_run_without_screen_r () { b0.value = ((daysSince2000() * 86400) + 3); runtime.ioDevices.clock.resetProjectTimer(); -while (!((runtime.ioDevices.clock.projectTimer() > 0.1) || ((daysSince2000() * 86400) > b0.value))) { +while (!((runtime.ioDevices.clock.projectTimer() > 0.1) || compareGreaterThan((daysSince2000() * 86400), b0.value))) { if (isStuck()) yield; } if (compareLessThan((daysSince2000() * 86400), b0.value)) { 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 826f1e0058f..5a401185577 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 @@ -10,7 +10,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, ",a.euo_AgQTxR+D^x0M0", null); b1.value = 0; b2.value = ""; -while (!(b2.value.toLowerCase() > "".toLowerCase())) { +while (!(("" + b2.value).toLowerCase() > "".toLowerCase())) { startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "step" }); yield; } 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 e5108d03f52..8f918a81081 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 @@ -18,7 +18,7 @@ const b1 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_run_without_screen_r () { b0.value = ((daysSince2000() * 86400) + 3); runtime.ioDevices.clock.resetProjectTimer(); -while (!((runtime.ioDevices.clock.projectTimer() > 0.1) || ((daysSince2000() * 86400) > b0.value))) { +while (!((runtime.ioDevices.clock.projectTimer() > 0.1) || compareGreaterThan((daysSince2000() * 86400), b0.value))) { if (isStuck()) yield; } if (compareLessThan((daysSince2000() * 86400), b0.value)) { 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 826f1e0058f..5a401185577 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 @@ -10,7 +10,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, ",a.euo_AgQTxR+D^x0M0", null); b1.value = 0; b2.value = ""; -while (!(b2.value.toLowerCase() > "".toLowerCase())) { +while (!(("" + b2.value).toLowerCase() > "".toLowerCase())) { startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "step" }); yield; } From 7ed29f5801c4ad4399d852bf5b4f19244b13cc5b Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Mon, 8 Sep 2025 17:11:50 +1000 Subject: [PATCH 18/56] Fix sound dropdown playing wrong sound when sounds have a number name --- src/compiler/irgen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 5ff94513b1b..89a72949a73 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -548,7 +548,7 @@ class ScriptTreeGenerator { case 'sound_sounds_menu': // This menu is special compared to other menus -- it actually has an opcode function. - return this.createConstantInput(block.fields.SOUND_MENU.value); + return this.createConstantInput(block.fields.SOUND_MENU.value, true); case 'control_get_counter': return new IntermediateInput(InputOpcode.CONTROL_COUNTER, InputType.NUMBER_POS_INT | InputType.NUMBER_ZERO); From e1ad53e7f52debaa67649205c00eb9fea100284d Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Sun, 14 Sep 2025 11:14:18 -0500 Subject: [PATCH 19/56] Add compatibility layer for extensions relying on old compiler (#264) The new compiler broke all the extensions doing compiler patches. We promised not to help them when those break but it seems enough people use those extensions that we sort of got forced into finding some solution, so this makes these changes: - The exports for the new compiler are now in `these_broke_before_and_will_break_again` - `i_will_not_ask_for_help_when_these_break` now returns some stubs that pretend to be the old compiler. It also emits an event so -gui can show a warning. - Focus is on compatibility & safety, not performance. There will be unnecessary casts and scripts marked as yielding. - Extensions that meet these assumptions should work without changes: - Extensions do not try to combine this compatibility layer with any APIs provided by the new compiler. - Extensions treat IR nodes received from descendSubstack and similar as opaque objects because they will be the new intermediate objects instead of { kind: "..." } - Extensions need to implement the JS generators for all AST node kinds they use. Can not rely on the default JS generator because those generators are expecting a different format than { kind: "..." } Lack of tests is intentional since this will be removed at some point when we have a proper API we can tell people to use --- src/compiler/enums.js | 2 + src/compiler/irgen.js | 21 ++ src/compiler/jsgen.js | 9 + src/compiler/old-compiler-compatibility.js | 349 +++++++++++++++++++++ src/virtual-machine.js | 21 +- 5 files changed, 399 insertions(+), 3 deletions(-) create mode 100644 src/compiler/old-compiler-compatibility.js diff --git a/src/compiler/enums.js b/src/compiler/enums.js index 5432aa4b78c..53b311886b2 100644 --- a/src/compiler/enums.js +++ b/src/compiler/enums.js @@ -103,6 +103,7 @@ const StackOpcode = { DEBUGGER: 'tw.debugger', VISUAL_REPORT: 'visualReport', COMPATIBILITY_LAYER: 'compat', + OLD_COMPILER_COMPATIBILITY_LAYER: 'oldCompiler', HAT_EDGE: 'hat.edge', HAT_PREDICATE: 'hat.predicate', @@ -201,6 +202,7 @@ const InputOpcode = { CAST_COLOR: 'cast.toColor', COMPATIBILITY_LAYER: 'compat', + OLD_COMPILER_COMPATIBILITY_LAYER: 'oldCompiler', LOOKS_BACKDROP_NUMBER: 'looks.backdropNumber', LOOKS_BACKDROP_NAME: 'looks.backdropName', diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 89a72949a73..e84cbc21e5a 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -14,6 +14,7 @@ const { IntermediateScript, IntermediateRepresentation } = require('./intermediate'); +const oldCompilerCompatiblity = require('./old-compiler-compatibility.js'); /** * @fileoverview Generate intermediate representations from Scratch blocks. @@ -96,6 +97,12 @@ class ScriptTreeGenerator { } } } + + this.oldCompilerStub = ( + oldCompilerCompatiblity.enabled ? + new oldCompilerCompatiblity.ScriptTreeGeneratorStub(this) : + null + ); } setProcedureVariant (procedureVariant) { @@ -199,6 +206,13 @@ class ScriptTreeGenerator { * @returns {IntermediateInput} Compiled input node for this input. */ descendInput (block, preserveStrings = false) { + if (this.oldCompilerStub) { + const oldCompilerResult = this.oldCompilerStub.descendInputFromNewCompiler(block); + if (oldCompilerResult) { + return oldCompilerResult; + } + } + switch (block.opcode) { case 'colour_picker': return this.createConstantInput(block.fields.COLOUR.value, true); @@ -593,6 +607,13 @@ class ScriptTreeGenerator { * @returns {IntermediateStackBlock} Compiled node for this block. */ descendStackedBlock (block) { + if (this.oldCompilerStub) { + const oldCompilerResult = this.oldCompilerStub.descendStackedBlockFromNewCompiler(block); + if (oldCompilerResult) { + return oldCompilerResult; + } + } + switch (block.opcode) { case 'control_all_at_once': // In Scratch 3, this block behaves like "if 1 = 1" diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index 82f6caf839d..5008e6edbfa 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -6,6 +6,7 @@ const VariablePool = require('./variable-pool'); const jsexecute = require('./jsexecute'); const environment = require('./environment'); const {StackOpcode, InputOpcode, InputType} = require('./enums.js'); +const oldCompilerCompatibility = require('./old-compiler-compatibility.js'); // These imports are used by jsdoc comments but eslint doesn't know that /* eslint-disable no-unused-vars */ @@ -124,6 +125,8 @@ class JSGenerator { this.isInHat = false; this.debug = this.target.runtime.debug; + + this.oldCompilerStub = new oldCompilerCompatibility.JSGeneratorStub(this); } /** @@ -198,6 +201,9 @@ class JSGenerator { // Compatibility layer inputs never use flags. return `(${this.generateCompatibilityLayerCall(node, false)})`; + case InputOpcode.OLD_COMPILER_COMPATIBILITY_LAYER: + return this.oldCompilerStub.descendInputFromNewCompiler(block); + case InputOpcode.CONSTANT: if (block.isAlwaysType(InputType.NUMBER)) { if (typeof node.value !== 'number') throw new Error(`JS: '${block.type}' type constant had ${typeof node.value} type value. Expected number.`); @@ -540,6 +546,9 @@ class JSGenerator { break; } + case InputOpcode.OLD_COMPILER_COMPATIBILITY_LAYER: + return this.oldCompilerStub.descendStackedBlockFromNewCompiler(block); + case StackOpcode.HAT_EDGE: this.isInHat = true; this.source += '{\n'; diff --git a/src/compiler/old-compiler-compatibility.js b/src/compiler/old-compiler-compatibility.js new file mode 100644 index 00000000000..07a4086c557 --- /dev/null +++ b/src/compiler/old-compiler-compatibility.js @@ -0,0 +1,349 @@ +/** + * @fileoverview + * Reimplements a subset of the old (pre-September 2025) compiler + * to maintain compatibility with extensions patching the old compiler. + * + * Safety and compatibility is more important than performance. There may be + * unnecessary type casts and scripts may be marked as yielding even when they + * don't actually yield. Additionally, anything running in this compatibility layer + * is not expected to receive the performance benefits of the new compiler. + * + * These assumptions are made about extensions using this compatibility layer: + * - Extensions do not try to combine this compatibility layer with any APIs + * provided by the new compiler. + * - Extensions treat IR nodes received from descendSubstack and similar as + * opaque objects. + * - Extensions need to implement the JS generators for all AST node kinds + * they use. Can not rely on the default JS generator. + */ + +const {InputOpcode, InputType} = require('./enums'); +// eslint-disable-next-line no-unused-vars +const {IntermediateInput, IntermediateStackBlock, IntermediateStack} = require('./intermediate'); + +class IRGeneratorStub { + // Doesn't seem like extensions override anything, though the class may + // still need to exist to avoid type errors. +} + +class ScriptTreeGeneratorStub { + /** + * @param {import("./irgen").ScriptTreeGenerator} real + */ + constructor (real) { + /** + * @type {import("./irgen").ScriptTreeGenerator} + */ + this.real = real; + + this.fakeThis = { + thread: real.thread, + target: real.target, + blocks: real.blocks, + runtime: real.runtime, + stage: real.stage, + script: real.script, + + /** + * @param parentBlock Parent VM block. + * @param {string} inputName Name of input. + * @returns opaque object + */ + descendInputOfBlock (parentBlock, inputName) { + const node = real.descendInputOfBlock(parentBlock, inputName, true); + return node; + }, + + /** + * @param {*} parentBlock Parent VM block. + * @param {*} substackName Name of substack. + * @returns opaque object + */ + descendSubstack (parentBlock, substackName) { + const substack = real.descendSubstack(parentBlock, substackName); + return substack; + }, + + analyzeLoop () { + // TODO: not always necessary + real.script.yields = true; + } + }; + } + + /** + * Intended for extensions to override. + * Always call from `fakeThis` context. + * @param {{opcode: string}} block VM block + * @returns {{kind: string}} Node object from old compiler. + */ + descendInput (block) { // eslint-disable-line no-unused-vars + return null; + } + + /** + * Intended for extensions to override. + * Always call from `fakeThis` context. + * @param {{opcode: string}} block VM block + * @returns {{kind: string}} Node object from old compiler. + */ + descendStackedBlock (block) { // eslint-disable-line no-unused-vars + return null; + } + + /** + * @param block VM block + * @returns {IntermediateInput|null} + */ + descendInputFromNewCompiler (block) { + const node = this.descendInput.call(this.fakeThis, block); + if (node) { + return new IntermediateInput(InputOpcode.OLD_COMPILER_COMPATIBILITY_LAYER, InputType.ANY, { + oldNode: node + }, true); + } + return null; + } + + /** + * @param block VM block + * @returns {IntermediateStackBlock|null} + */ + descendStackedBlockFromNewCompiler (block) { + const node = this.descendStackedBlock.call(this.fakeThis, block); + if (node) { + return new IntermediateStackBlock(InputOpcode.OLD_COMPILER_COMPATIBILITY_LAYER, { + oldNode: node + }, true); + } + return null; + } +} + +// These are part of the old compiler's API. +const TYPE_NUMBER = 1; +const TYPE_STRING = 2; +const TYPE_BOOLEAN = 3; +const TYPE_UNKNOWN = 4; +const TYPE_NUMBER_NAN = 5; + +/** + * Part of the old compiler's API. + */ +class TypedInput { + /** + * @param {string} source JavaScript + * @param {number|IntermediateInput} typeOrIntermediate + */ + constructor (source, typeOrIntermediate) { + /** + * JavaScript. + * @type {string} + */ + this.source = source; + + if (typeOrIntermediate instanceof IntermediateInput) { + // Path used by the compatibility layer itself + + /** + * @type {IntermediateInput} + */ + this.intermediate = typeOrIntermediate; + + /** + * @type {number} See TYPE_* constants above + */ + this.type = TYPE_UNKNOWN; + } else { + // Path used by extensions + this.intermediate = null; + this.type = typeOrIntermediate; + } + } + + asNumber () { + return `(+${this.source} || 0)`; + } + + asNumberOrNaN () { + return `(+${this.source})`; + } + + asString () { + return `("" + ${this.source})`; + } + + asBoolean () { + return `toBoolean(${this.source})`; + } + + asColor () { + return this.asUnknown(); + } + + asUnknown () { + return this.source; + } + + asSafe () { + return this.asUnknown(); + } + + isAlwaysNumber () { + // TODO + return false; + } + + isAlwaysNumberOrNaN () { + // TODO + return false; + } + + isNeverNumber () { + // TODO + return false; + } +} + +/** + * Part of the old compiler's API. + */ +class VariablePool { + constructor (prefix) { + this.prefix = prefix; + this.count = 0; + } + + next () { + return `${this.prefix}${this.count++}`; + } +} + +/** + * Part of the old compiler's API. + */ +class Frame { + constructor (isLoop) { + this.isLoop = isLoop; + this.isLastBlock = false; + } +} + +class JSGeneratorStub { + /** + * @param {import("./jsgen")} real + */ + constructor (real) { + /** + * @type {import("./jsgen")} + */ + this.real = real; + + this.fakeThis = { + script: real.script, + ir: real.ir, + target: real.target, + + get frames () { + return real.frames; + }, + get currentFrame () { + return real.currentFrame; + }, + + get source () { + return real.source; + }, + set source (newSource) { + real.source = newSource; + }, + + localVariables: new VariablePool('oldCompilerLocal'), + + /** + * @param {IntermediateInput} intermediate + * @returns {void} output is concatenated in this.source + */ + descendInput (intermediate) { + const js = real.descendInput(intermediate); + return new TypedInput(js, intermediate); + }, + + /** + * @param {IntermediateStack} stack Stack of blocks. + * @param {Frame} frame New frame + */ + descendStack (stack, frame) { + real.descendStack(stack, frame); + }, + + yieldLoop: () => real.yieldLoop(), + yieldNotWarp: () => real.yieldNotWarp(), + yieldStuckOrNotWarp: () => real.yieldStuckOrNotWarp(), + yielded: () => real.yielded(), + requestRedraw: () => real.requestRedraw() + }; + } + + /** + * Intended for extensions to override. + * Always call from `fakeThis` context. + * @param {{kind: string}} node Old compiler AST node. + * @returns {TypedInput} Old compiler TypedInput. + */ + descendInput (node) { + throw new Error(`Unknown input: ${node.kind}`); + } + + /** + * Intended for extensions to override. + * Always call from `fakeThis` context. + * @param {{kind: string}} node Old compiler AST node. + */ + descendStackedBlock (node) { + throw new Error(`Unknown stacked block: ${node.kind}`); + } + + /** + * @param {IntermediateInput} intermediate + * @returns {string} JavaScript + */ + descendInputFromNewCompiler (intermediate) { + const oldNode = intermediate.inputs.oldNode; + const typedInput = this.descendInput.call(this.fakeThis, oldNode); + return typedInput.asSafe(); + } + + /** + * @param {IntermediateStackBlock} intermediate + * @returns {void} source property on real JSGenerator is modified directly + */ + descendStackedBlockFromNewCompiler (intermediate) { + const oldNode = intermediate.inputs.oldNode; + this.descendStackedBlock.call(this.fakeThis, oldNode); + } +} + +/** + * Part of old compiler's API. + */ +JSGeneratorStub.unstable_exports = { + TYPE_NUMBER, + TYPE_STRING, + TYPE_BOOLEAN, + TYPE_UNKNOWN, + TYPE_NUMBER_NAN, + VariablePool, + TypedInput, + Frame +}; + +const oldCompilerCompatibility = { + enabled: false, + IRGeneratorStub, + ScriptTreeGeneratorStub, + TypedInput, + JSGeneratorStub +}; + +module.exports = oldCompilerCompatibility; diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 9feb91a7c00..4a8f55cf613 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -223,9 +223,9 @@ class VirtualMachine extends EventEmitter { JSZip, Variable, - i_will_not_ask_for_help_when_these_break: () => { + these_broke_before_and_will_break_again: () => { console.warn('You are using unsupported APIs. WHEN your code breaks, do not expect help.'); - return ({ + return { JSGenerator: require('./compiler/jsgen.js'), IRGenerator: require('./compiler/irgen.js').IRGenerator, ScriptTreeGenerator: require('./compiler/irgen.js').ScriptTreeGenerator, @@ -239,7 +239,22 @@ class VirtualMachine extends EventEmitter { InputType: require('./compiler/enums.js').InputType, Thread: require('./engine/thread.js'), execute: require('./engine/execute.js') - }); + }; + }, + + i_will_not_ask_for_help_when_these_break: () => { + this.emit('LEGACY_EXTENSION_API', 'i_will_not_ask_for_help_when_these_break'); + + const oldCompilerCompatibility = require('./compiler/old-compiler-compatibility.js'); + oldCompilerCompatibility.enabled = true; + + return { + IRGenerator: oldCompilerCompatibility.IRGeneratorStub, + ScriptTreeGenerator: oldCompilerCompatibility.ScriptTreeGeneratorStub, + JSGenerator: oldCompilerCompatibility.JSGeneratorStub, + Thread: require('./engine/thread.js'), + execute: require('./engine/execute.js') + }; } }; } From 5b027b995e9a797a34f14ad444c3ce769f2912ee Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Mon, 15 Sep 2025 17:56:52 -0500 Subject: [PATCH 20/56] Fix motion_turnleft and motion_turnright missing type casts --- src/compiler/irgen.js | 4 ++-- ...-rotate-by-decimal-without-leading-zero.sb3 | Bin 0 -> 2784 bytes ...ecimal-without-leading-zero.sb3.tw-snapshot | 17 +++++++++++++++++ ...ecimal-without-leading-zero.sb3.tw-snapshot | 17 +++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/execute/tw-rotate-by-decimal-without-leading-zero.sb3 create mode 100644 test/snapshot/__snapshots__/tw-rotate-by-decimal-without-leading-zero.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-rotate-by-decimal-without-leading-zero.sb3.tw-snapshot diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index e84cbc21e5a..2a22749b345 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -865,14 +865,14 @@ class ScriptTreeGenerator { return new IntermediateStackBlock(StackOpcode.MOTION_DIRECTION_SET, { direction: new IntermediateInput(InputOpcode.OP_SUBTRACT, InputType.NUMBER, { left: new IntermediateInput(InputOpcode.MOTION_DIRECTION_GET, InputType.NUMBER), - right: this.descendInputOfBlock(block, 'DEGREES') + right: this.descendInputOfBlock(block, 'DEGREES').toType(InputType.NUMBER) }) }); case 'motion_turnright': return new IntermediateStackBlock(StackOpcode.MOTION_DIRECTION_SET, { direction: new IntermediateInput(InputOpcode.OP_ADD, InputType.NUMBER, { left: new IntermediateInput(InputOpcode.MOTION_DIRECTION_GET, InputType.NUMBER), - right: this.descendInputOfBlock(block, 'DEGREES') + right: this.descendInputOfBlock(block, 'DEGREES').toType(InputType.NUMBER) }) }); diff --git a/test/fixtures/execute/tw-rotate-by-decimal-without-leading-zero.sb3 b/test/fixtures/execute/tw-rotate-by-decimal-without-leading-zero.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..4b8c5e145296e691992db55f6540c239b6e163f8 GIT binary patch literal 2784 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 literal 0 HcmV?d00001 diff --git a/test/snapshot/__snapshots__/tw-rotate-by-decimal-without-leading-zero.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-rotate-by-decimal-without-leading-zero.sb3.tw-snapshot new file mode 100644 index 00000000000..d92f0cf7405 --- /dev/null +++ b/test/snapshot/__snapshots__/tw-rotate-by-decimal-without-leading-zero.sb3.tw-snapshot @@ -0,0 +1,17 @@ +// TW Snapshot +// Input SHA-256: 0a7a3ee1aac2ba5b063f53b742ef992e395ee668efa9205279f93f1949e8a755 + +// 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 1",}, b0, false, false, "e", null); +target.setDirection(90); +target.setDirection((target.direction + 0.25)); +target.setDirection((target.direction + 0.25)); +if ((target.direction === 90.5)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "i", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "h", null); +retire(); return; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-rotate-by-decimal-without-leading-zero.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-rotate-by-decimal-without-leading-zero.sb3.tw-snapshot new file mode 100644 index 00000000000..d92f0cf7405 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-rotate-by-decimal-without-leading-zero.sb3.tw-snapshot @@ -0,0 +1,17 @@ +// TW Snapshot +// Input SHA-256: 0a7a3ee1aac2ba5b063f53b742ef992e395ee668efa9205279f93f1949e8a755 + +// 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 1",}, b0, false, false, "e", null); +target.setDirection(90); +target.setDirection((target.direction + 0.25)); +target.setDirection((target.direction + 0.25)); +if ((target.direction === 90.5)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "i", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "h", null); +retire(); return; +}; }) From fa035541dae86a69cfc9f7ada69c9b79ce23dd2f Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Sat, 20 Sep 2025 13:41:23 -0500 Subject: [PATCH 21/56] Give up analyzing looped stack after 10000 iterations Temporary fix for an infinite loop, to be properly fixed later --- src/compiler/iroptimizer.js | 12 +++++++ ...cedure-reporter-infinite-analyzer-loop.sb3 | Bin 0 -> 35213 bytes ...ter-infinite-analyzer-loop.sb3.tw-snapshot | 29 +++++++++++++++++ ...ter-infinite-analyzer-loop.sb3.tw-snapshot | 30 ++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 test/fixtures/execute/tw-repeat-procedure-reporter-infinite-analyzer-loop.sb3 create mode 100644 test/snapshot/__snapshots__/tw-repeat-procedure-reporter-infinite-analyzer-loop.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-repeat-procedure-reporter-infinite-analyzer-loop.sb3.tw-snapshot diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index 100f38900fa..f44dd730ce8 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -627,9 +627,21 @@ class IROptimizer { return this.analyzeStack(stack, state) || modified; } + let iterations = 0; let modified = false; let keepLooping; do { + // If we are stuck in an apparent infinite loop, give up and assume the worst. + if (iterations > 10000) { + console.error('analyzeLoopedStack stuck in likely infinite loop; quitting', block, state); + modified = state.clear(); + block.entryState = state.clone(); + block.exitState = state.clone(); + modified = this.analyzeInputs(block.inputs, state) || modified; + return this.analyzeStack(stack, state) || modified; + } + iterations++; + const newState = state.clone(); modified = this.analyzeStack(stack, newState) || modified; modified = (keepLooping = state.or(newState)) || modified; diff --git a/test/fixtures/execute/tw-repeat-procedure-reporter-infinite-analyzer-loop.sb3 b/test/fixtures/execute/tw-repeat-procedure-reporter-infinite-analyzer-loop.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..1d91e7bc4db94f2ec92bdf34eb6ae9660b1fc418 GIT binary patch literal 35213 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 literal 0 HcmV?d00001 diff --git a/test/snapshot/__snapshots__/tw-repeat-procedure-reporter-infinite-analyzer-loop.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-repeat-procedure-reporter-infinite-analyzer-loop.sb3.tw-snapshot new file mode 100644 index 00000000000..2244a1bfbde --- /dev/null +++ b/test/snapshot/__snapshots__/tw-repeat-procedure-reporter-infinite-analyzer-loop.sb3.tw-snapshot @@ -0,0 +1,29 @@ +// TW Snapshot +// Input SHA-256: a12680688ca945364abe1e094b35cf53326f70189cb09b58d5f52b4de0c3b2a9 + +// Text 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 0",}, b0, false, false, "a~", null); +thread.procedures["Wtest 1"](); +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "dh", null); +retire(); return; +}; }) + +// Text Wtest 1 +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = target.variables["ts-.PQ{/]mN_9@MbG:m/"]; +return function funXYZ_test_1 () { +for (var a0 = (+thread.procedures["Wany procedure reporter"]() || 0); a0 >= 0.5; a0--) { +b0.value = ((+b0.value || 0) + 1); +} +return ""; +}; }) + +// Text Wany procedure reporter +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_any_procedure_report () { +return 0; +return ""; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-repeat-procedure-reporter-infinite-analyzer-loop.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-repeat-procedure-reporter-infinite-analyzer-loop.sb3.tw-snapshot new file mode 100644 index 00000000000..f3b5937c508 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-repeat-procedure-reporter-infinite-analyzer-loop.sb3.tw-snapshot @@ -0,0 +1,30 @@ +// TW Snapshot +// Input SHA-256: a12680688ca945364abe1e094b35cf53326f70189cb09b58d5f52b4de0c3b2a9 + +// Text 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 0",}, b0, false, false, "a~", null); +yield* thread.procedures["Wtest 1"](); +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "dh", null); +retire(); return; +}; }) + +// Text Wtest 1 +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = target.variables["ts-.PQ{/]mN_9@MbG:m/"]; +return function* genXYZ_test_1 () { +for (var a0 = (+thread.procedures["Wany procedure reporter"]() || 0); a0 >= 0.5; a0--) { +b0.value = ((+b0.value || 0) + 1); +if (isStuck()) yield; +} +return ""; +}; }) + +// Text Wany procedure reporter +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function funXYZ_any_procedure_report () { +return 0; +return ""; +}; }) From cc96ee8c70ac24b165dfe536d8b2f1b327011cfe Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 21 Sep 2025 22:59:06 +1000 Subject: [PATCH 22/56] Fix `-0` being turned into `0` in the type matrix test --- src/compiler/intermediate.js | 43 ++++++++++++++++++++- test/integration/tw_operator_type_matrix.js | 6 +-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/compiler/intermediate.js b/src/compiler/intermediate.js index 23c82cc8fe8..2a663145651 100644 --- a/src/compiler/intermediate.js +++ b/src/compiler/intermediate.js @@ -209,6 +209,46 @@ class IntermediateInput { } } + +/** + * @param {InputType} type + * @returns {string} + */ +const stringifyType = type => { + let formatFlags = []; + + for (const enumValue in InputType) { + const testFormat = InputType[enumValue]; + + if ((testFormat & type) === testFormat) { + for (const existingFormat of formatFlags) { + if ((testFormat & InputType[existingFormat]) === testFormat) { + continue; + } + } + + formatFlags = formatFlags.filter(value => (InputType[value] & testFormat) !== InputType[value]); + formatFlags.push(enumValue); + } + } + + let str = null; + + for (const formatFlag of formatFlags) { + if (str === null) { + str = formatFlag; + } else { + str = `${str} | ${formatFlag}`; + } + } + + if (str === null) { + return 'INVALID'; + } + + return str; +}; + /** * A 'stack' of blocks, like the contents of a script or the inside * of a C block. @@ -350,5 +390,6 @@ module.exports = { IntermediateInput, IntermediateStack, IntermediateScript, - IntermediateRepresentation + IntermediateRepresentation, + stringifyType }; diff --git a/test/integration/tw_operator_type_matrix.js b/test/integration/tw_operator_type_matrix.js index 2402067302c..035102fe037 100644 --- a/test/integration/tw_operator_type_matrix.js +++ b/test/integration/tw_operator_type_matrix.js @@ -3,7 +3,7 @@ const VM = require('../../src/virtual-machine'); const {BlockType, ArgumentType} = require('../../src/extension-support/tw-extension-api-common'); const {IRGenerator} = require('../../src/compiler/irgen'); const {IROptimizer} = require('../../src/compiler/iroptimizer'); -const {IntermediateInput} = require('../../src/compiler/intermediate'); +const {IntermediateInput, stringifyType} = require('../../src/compiler/intermediate'); const nanolog = require('@turbowarp/nanolog'); const VALUES = [ @@ -113,7 +113,7 @@ test('operator type matrix', async t => { 1, [ 4, - `${inputs[i]}` + `${Object.is(inputs[i], -0) ? '-0' : inputs[i]}` ] ]; } @@ -180,7 +180,7 @@ test('operator type matrix', async t => { t.ok( irOperator.isSometimesType(expectedType), `${operator.opcode}${JSON.stringify(operator.fields)}[${inputs.map(str)}] ` + - `outputted value ${str(reportedValue)} is of the expected type ${irOperator.type}.` + `outputted value ${str(reportedValue)} is of the expected type ${stringifyType(irOperator.type)}.` ); }; From c16be2517896a0f825f494fd3b171b6de33eb7e8 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 21 Sep 2025 22:59:56 +1000 Subject: [PATCH 23/56] Fix #273 --- src/compiler/iroptimizer.js | 2 +- .../__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index f44dd730ce8..fe162349062 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -644,8 +644,8 @@ class IROptimizer { const newState = state.clone(); modified = this.analyzeStack(stack, newState) || modified; + modified = this.analyzeInputs(block.inputs, newState) || modified; modified = (keepLooping = state.or(newState)) || modified; - modified = this.analyzeInputs(block.inputs, state) || modified; } while (keepLooping); block.entryState = state.clone(); return modified; diff --git a/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot index 224f800f45e..d2efe972b71 100644 --- a/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot @@ -67,10 +67,10 @@ b0.value = (b1.value[(((((+p1 || 0) + (+p0 || 0)) || 0) / 2) | 0) - 1] ?? ""); b2.value = p0; b3.value = p1; while (true) { -while (compareLessThan(listGet(b1.value, b2.value), b0.value)) { +while (compareLessThan((b1.value[(b2.value | 0) - 1] ?? ""), b0.value)) { b2.value = ((+b2.value || 0) + 1); } -while (compareGreaterThan(listGet(b1.value, b3.value), b0.value)) { +while (compareGreaterThan((b1.value[(b3.value | 0) - 1] ?? ""), b0.value)) { b3.value = ((+b3.value || 0) + -1); } if (compareGreaterThan(b2.value, b3.value)) { From 749e9233787baf51d6e0ce48e1766973cb0ee774 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 21 Sep 2025 23:10:50 +1000 Subject: [PATCH 24/56] Fix #272 (thanks @Geotale) --- src/compiler/iroptimizer.js | 34 ++++++++++--------- .../__snapshots__/tw-NaN.sb3.tw-snapshot | 6 ++-- ...s-515-non-finite-direction.sb3.tw-snapshot | 2 +- .../warp-timer/tw-NaN.sb3.tw-snapshot | 6 ++-- ...s-515-non-finite-direction.sb3.tw-snapshot | 2 +- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index fe162349062..f20a47ea4f1 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -377,8 +377,8 @@ class IROptimizer { let resultType = 0; const canBeNaN = function () { - // REAL / 0 = NaN - if ((leftType & InputType.NUMBER_REAL) && (rightType & InputType.NUMBER_ZERO)) return true; + // (-)0 / (-)0 = NaN + if ((leftType & InputType.NUMBER_ANY_ZERO) && (rightType & InputType.NUMBER_ANY_ZERO)) return true; // (-)Infinity / (-)Infinity = NaN if ((leftType & InputType.NUMBER_INF) && (rightType & InputType.NUMBER_INF)) return true; // (-)0 / NaN = NaN @@ -395,24 +395,26 @@ class IROptimizer { if (canBePos()) resultType |= InputType.NUMBER_POS; const canBeNegInfinity = function () { - // -Infinity / 0 = -Infinity - if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_ZERO)) return true; - // Infinity / -0 = -Infinity - if ((leftType & InputType.NUMBER_POS_INF) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; - // NEG_REAL / NaN = -Infinity - if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_NAN)) return true; - // NEG_REAL / NUMBER_OR_NAN ~= -Infinity - if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_OR_NAN)) return true; + // NEG / 0 = -Infinity + if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_ZERO)) return true; + // POS / -0 = -Infinity + if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // NEG_REAL / POS_REAL ~= -Infinity + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_POS_REAL)) return true; + // POS_REAL / NEG_REAL ~= -Infinity + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_NEG_REAL)) return true; }; if (canBeNegInfinity()) resultType |= InputType.NUMBER_NEG_INF; const canBeInfinity = function () { - // Infinity / 0 = Infinity - if ((leftType & InputType.NUMBER_POS_INF) && (rightType & InputType.NUMBER_ZERO)) return true; - // -Infinity / -0 = Infinity - if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; - // POS_REAL / NUMBER_OR_NAN ~= Infinity - if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_OR_NAN)) return true; + // POS / 0 = Infinity + if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_ZERO)) return true; + // NEG / -0 = Infinity + if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // POS_REAL / POS_REAL ~= Infinity + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_POS_REAL)) return true; + // NEG_REAL / NEG_REAL ~= Infinity + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_NEG_REAL)) return true; }; if (canBeInfinity()) resultType |= InputType.NUMBER_POS_INF; diff --git a/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot index 537fd7102ad..655a04a4e77 100644 --- a/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot @@ -57,13 +57,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ao", if (((((Math.log(-1) / Math.LN10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aq", null); } -if (((((Math.round(Math.sin((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { +if (((((Math.round(Math.sin((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "as", null); } -if (((((Math.round(Math.cos((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { +if (((((Math.round(Math.cos((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "au", null); } -if ((((tan(((1 / 0) || 0)) || 0) * 1) === 0)) { +if ((((tan((1 / 0)) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aw", null); } if ((((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || 0) * 1) === 0)) { 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 59991893cea..00bb8c4d3e3 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 @@ -11,7 +11,7 @@ target.setDirection(95); if ((target.direction === 95)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 1",}, b0, false, false, "o", null); } -target.setDirection(((1 / 0) || 0)); +target.setDirection((1 / 0)); if ((target.direction === 95)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 2",}, b0, false, false, "r", null); } 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 537fd7102ad..655a04a4e77 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot @@ -57,13 +57,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ao", if (((((Math.log(-1) / Math.LN10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aq", null); } -if (((((Math.round(Math.sin((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { +if (((((Math.round(Math.sin((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "as", null); } -if (((((Math.round(Math.cos((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { +if (((((Math.round(Math.cos((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "au", null); } -if ((((tan(((1 / 0) || 0)) || 0) * 1) === 0)) { +if ((((tan((1 / 0)) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aw", null); } if ((((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || 0) * 1) === 0)) { 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 59991893cea..00bb8c4d3e3 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 @@ -11,7 +11,7 @@ target.setDirection(95); if ((target.direction === 95)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 1",}, b0, false, false, "o", null); } -target.setDirection(((1 / 0) || 0)); +target.setDirection((1 / 0)); if ((target.direction === 95)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 2",}, b0, false, false, "r", null); } From d17ceccd830188887ac2746b2c1fc720ffc91a52 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 21 Sep 2025 23:13:11 +1000 Subject: [PATCH 25/56] Fix #269 --- src/compiler/irgen.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 2a22749b345..8bc9897b97e 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -299,14 +299,14 @@ class ScriptTreeGenerator { } return new IntermediateInput(InputOpcode.LOOKS_COSTUME_NAME, InputType.STRING); case 'looks_size': - return new IntermediateInput(InputOpcode.LOOKS_SIZE_GET, InputType.NUMBER_POS_REAL); + return new IntermediateInput(InputOpcode.LOOKS_SIZE_GET, InputType.NUMBER_POS); case 'motion_direction': return new IntermediateInput(InputOpcode.MOTION_DIRECTION_GET, InputType.NUMBER_REAL); case 'motion_xposition': - return new IntermediateInput(InputOpcode.MOTION_X_GET, InputType.NUMBER_REAL); + return new IntermediateInput(InputOpcode.MOTION_X_GET, InputType.NUMBER); case 'motion_yposition': - return new IntermediateInput(InputOpcode.MOTION_Y_GET, InputType.NUMBER_REAL); + return new IntermediateInput(InputOpcode.MOTION_Y_GET, InputType.NUMBER); case 'operator_add': return new IntermediateInput(InputOpcode.OP_ADD, InputType.NUMBER_OR_NAN, { @@ -494,7 +494,7 @@ class ScriptTreeGenerator { case 'sensing_dayssince2000': return new IntermediateInput(InputOpcode.SENSING_TIME_DAYS_SINCE_2000, InputType.NUMBER); case 'sensing_distanceto': - return new IntermediateInput(InputOpcode.SENSING_DISTANCE, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO, { + return new IntermediateInput(InputOpcode.SENSING_DISTANCE, InputType.NUMBER_POS | InputType.NUMBER_ZERO, { target: this.descendInputOfBlock(block, 'DISTANCETOMENU').toType(InputType.STRING) }); case 'sensing_keypressed': From 41af457060a0814dfae161c61c6fa1507dfe6c27 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Mon, 22 Sep 2025 17:05:30 -0500 Subject: [PATCH 26/56] Move analyzeStackBlock analyzeInputs call into the switch statement Makes later commits easier. --- src/compiler/iroptimizer.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index f20a47ea4f1..d3bbbeaf000 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -541,18 +541,19 @@ class IROptimizer { state = state.clone(); } - modified = this.analyzeInputs(inputs, state) || modified; - switch (stackBlock.opcode) { case StackOpcode.VAR_SET: + modified = this.analyzeInputs(inputs, state) || modified; modified = state.setVariableType(inputs.variable, inputs.value.type) || modified; break; case StackOpcode.CONTROL_WHILE: case StackOpcode.CONTROL_FOR: case StackOpcode.CONTROL_REPEAT: + modified = this.analyzeInputs(inputs, state) || modified; modified = this.analyzeLoopedStack(inputs.do, state, stackBlock) || modified; break; case StackOpcode.CONTROL_IF_ELSE: { + modified = this.analyzeInputs(inputs, state) || modified; const trueState = state.clone(); modified = this.analyzeStack(inputs.whenTrue, trueState) || modified; modified = this.analyzeStack(inputs.whenFalse, state) || modified; @@ -560,10 +561,12 @@ class IROptimizer { break; } case StackOpcode.CONTROL_STOP_SCRIPT: { + modified = this.analyzeInputs(inputs, state) || modified; this.addPossibleExitState(state); break; } case StackOpcode.PROCEDURE_CALL: { + modified = this.analyzeInputs(inputs, state) || modified; modified = this.analyzeInputs(inputs.inputs, state) || modified; const script = this.ir.procedures[inputs.variant]; @@ -575,6 +578,7 @@ class IROptimizer { break; } case StackOpcode.COMPATIBILITY_LAYER: { + modified = this.analyzeInputs(inputs, state) || modified; this.analyzeInputs(inputs.inputs, state); for (const substackName in inputs.substacks) { const newState = state.clone(); @@ -583,6 +587,9 @@ class IROptimizer { } break; } + default: + modified = this.analyzeInputs(inputs, state) || modified; + break; } return modified; From 44e2589e65fe2c4ed97bc6aa2f4ead75b8a72c50 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Mon, 22 Sep 2025 17:06:21 -0500 Subject: [PATCH 27/56] Fix https://github.com/TurboWarp/scratch-vm/issues/277 Need to clear the state before analyzing the condition as the condition may be re-evaluated after yielding. --- src/compiler/iroptimizer.js | 5 +++ .../tw-wait-until-condition-gh-277.sb3 | Bin 0 -> 2928 bytes ...ait-until-condition-gh-277.sb3.tw-snapshot | 34 ++++++++++++++++++ ...ait-until-condition-gh-277.sb3.tw-snapshot | 34 ++++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 test/fixtures/execute/tw-wait-until-condition-gh-277.sb3 create mode 100644 test/snapshot/__snapshots__/tw-wait-until-condition-gh-277.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-wait-until-condition-gh-277.sb3.tw-snapshot diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index d3bbbeaf000..91ebd5cf46b 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -565,6 +565,11 @@ class IROptimizer { this.addPossibleExitState(state); break; } + case StackOpcode.CONTROL_WAIT_UNTIL: { + modified = state.clear() || modified; + modified = this.analyzeInputs(inputs, state) || modified; + break; + } case StackOpcode.PROCEDURE_CALL: { modified = this.analyzeInputs(inputs, state) || modified; modified = this.analyzeInputs(inputs.inputs, state) || modified; diff --git a/test/fixtures/execute/tw-wait-until-condition-gh-277.sb3 b/test/fixtures/execute/tw-wait-until-condition-gh-277.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..a170d42f7370e5d81c336d3298b08a1c52dee520 GIT binary patch literal 2928 zcma);c{CL28^>qtJDIU>W8bokWlX}@%?y&zD50^HEEBTVFpOkNwu&T-U6w{9!jK}_ zuB=0mtdlKUR9yG`{SU$*MUW4ndaqMfP3$CoJ%}~`tkvx zXycZG%8%j4yw-ICVx9&;YT5^HivdXrgL3FuCtm!F1@&4zT zuyr3kMaFqTbmb&f{kErj?vE@ToC%-K-(A1;N^DQK4;W=wU`I#CzRa0?d&?)NJ0q@y zR@C9lQft#>-xTk+5{`v^qQWal|iwc)MGTQx&;wxSS=Nfl?>x$gLdXQQnK z>ZEwhV*IEjXlvn!%x(lMN zJiy3ASZxJE=&XH`G5aJkWKt|eKDHNC?w6#w3!|J@4(e=I6ACNwLhP!KKlxP1MQbAI zHBHg2x;ne0RML@?8;4r;mMN4z$%?sGRH`U~2v@2h?Bhdk?;oy3vxb*zGw6w`R<~X# z9;}Y3`b>!J{rKW5cLF+8U88?7P--))-+x4G z`n-)|iY@sI)_sALt>N^qn2-^O&^}{0%gEyTdGinP6*1f~J7R2l;SK}-dH-i@pI0}_|?R+~e zNIgZl`A39j)z|8p%h;oLUkf^KeWrgXE*|jW#;i>qBa;Q}kih`{xdhMZW+kw?hsFc` zZE95}KTVqv07zv2pD8#L=Ai?F!lBxpFf1JI1=H2gglTHKYw2pa>%gJVzf3)Kr213M zd4BN`7!&y+V|sdIa~ zT6$)zuXHnPQhc{ETl9I~+^RqR!gJVgaA^4T&5ey2@o}-#F#$>>9(dF>|LuC?H2$Z& zg;KHehf=?-+G#6TrDG4W2T5JOY+G30SGxLrV{7*M$U%Ro_rTi3?5}2; z*Qx5g(#p=wkde{e=?G!`QmmS)X-xr5@kvB#48H0xcc*@BeKd5guCdqQ$KoD-N}?pc zq4d)z^;iA0>(R5r)8gK9^T90=M?SfgtNvpW>!EWM(-OO`Z{`a8Y=$>CPQgbYE{nlG4Fqs_?K@)5LTPD}m{ zXR}>Gne=^i<*Rghs;|qFDEsLTn57o|RxT;n@SZj4lHFkw2thfyo2+2B6L`C>4yMP8 z3&2NTOe`vJ&fqu#03ZUKIJk|5t{#;7>SO1PS#PC_Dat=G+gFMq9Tbb|YX3?H-C88M zPU4xv+k_+puI%kKPt&O&rZS-m8(K7CUhAtcxTX0mPe#U|kd>kV6IW5BW%(9bQEF21j_Tcl|xFDP0Ha0?a0nC}EtVp38#@ zfaDPKc9wXcIBCa<$FV_3Q%eUkcaJf{LE!XlkK4A#rkbo$zR77M)c@BYLFu{~Q zuJ(^Y83^pP(wfk z_Q3{eBDt=L0c^K^b9XXr1jM_27u4BwKS=pXzFCqarp4z<|IH_yw$v>udXLAR#7gHpXS&CYxev^#rene}5M>`R+`uxi zrkMiLLzrrC2sVC}YBIlq@S*rU&|~+zbk4pfxbip+GPoKX>dFxg$R)uqX}-<*$>rmW zkTN$eK83-arJ5VwMT=$T&KI|nv-k_x%`Nmj?cbyiWK$A)4Sc2}>Yx@+#lFf11R@(8 zqAm%^O7-QoyVBx=4tG)gJtoGPgBZ4k^d5eWik%X?&ahm-v kRQkQ+?-2OABjYg|{tFUp(Tq%gspyaI$KyZhFQb4z0Uli<>i_@% literal 0 HcmV?d00001 diff --git a/test/snapshot/__snapshots__/tw-wait-until-condition-gh-277.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-wait-until-condition-gh-277.sb3.tw-snapshot new file mode 100644 index 00000000000..4312367087b --- /dev/null +++ b/test/snapshot/__snapshots__/tw-wait-until-condition-gh-277.sb3.tw-snapshot @@ -0,0 +1,34 @@ +// TW Snapshot +// Input SHA-256: 2422e407892596fbf410e032196131f9bae718563fd80b1ff32cf972859c034b + +// 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 0",}, b0, false, false, "e", null); +startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "message1" }); +b1.value = "bwah"; +while (!(("" + b1.value).toLowerCase() === "bleh".toLowerCase())) { +yield; +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "h", null); +retire(); return; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +b0.value = (1 + 2); +thread.timer = timer(); +var a0 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a0) { +yield; +} +thread.timer = null; +b0.value = "bleh"; +retire(); return; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-wait-until-condition-gh-277.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-wait-until-condition-gh-277.sb3.tw-snapshot new file mode 100644 index 00000000000..4312367087b --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-wait-until-condition-gh-277.sb3.tw-snapshot @@ -0,0 +1,34 @@ +// TW Snapshot +// Input SHA-256: 2422e407892596fbf410e032196131f9bae718563fd80b1ff32cf972859c034b + +// 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 0",}, b0, false, false, "e", null); +startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "message1" }); +b1.value = "bwah"; +while (!(("" + b1.value).toLowerCase() === "bleh".toLowerCase())) { +yield; +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "h", null); +retire(); return; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +b0.value = (1 + 2); +thread.timer = timer(); +var a0 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a0) { +yield; +} +thread.timer = null; +b0.value = "bleh"; +retire(); return; +}; }) From 5d917cca0580b9e5dcff5fdeeb95d8994859421f Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Mon, 22 Sep 2025 17:36:38 -0500 Subject: [PATCH 28/56] Use log.error instead of console.error --- src/compiler/iroptimizer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index 91ebd5cf46b..73e348a6720 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -1,6 +1,7 @@ // @ts-check const {StackOpcode, InputOpcode, InputType} = require('./enums.js'); +const log = require('../util/log'); // These imports are used by jsdoc comments but eslint doesn't know that /* eslint-disable no-unused-vars */ @@ -647,7 +648,7 @@ class IROptimizer { do { // If we are stuck in an apparent infinite loop, give up and assume the worst. if (iterations > 10000) { - console.error('analyzeLoopedStack stuck in likely infinite loop; quitting', block, state); + log.error('analyzeLoopedStack stuck in likely infinite loop; quitting', block, state); modified = state.clear(); block.entryState = state.clone(); block.exitState = state.clone(); From 2639653647652199f46995af0a7e1ad1d13dc1ee Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Mon, 22 Sep 2025 18:32:13 -0500 Subject: [PATCH 29/56] Revert "Fix #273" This reverts commit c16be2517896a0f825f494fd3b171b6de33eb7e8. Caused https://github.com/TurboWarp/scratch-vm/issues/276 --- src/compiler/iroptimizer.js | 2 +- .../__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index 73e348a6720..97135787272 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -659,8 +659,8 @@ class IROptimizer { const newState = state.clone(); modified = this.analyzeStack(stack, newState) || modified; - modified = this.analyzeInputs(block.inputs, newState) || modified; modified = (keepLooping = state.or(newState)) || modified; + modified = this.analyzeInputs(block.inputs, state) || modified; } while (keepLooping); block.entryState = state.clone(); return modified; diff --git a/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot index d2efe972b71..224f800f45e 100644 --- a/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot @@ -67,10 +67,10 @@ b0.value = (b1.value[(((((+p1 || 0) + (+p0 || 0)) || 0) / 2) | 0) - 1] ?? ""); b2.value = p0; b3.value = p1; while (true) { -while (compareLessThan((b1.value[(b2.value | 0) - 1] ?? ""), b0.value)) { +while (compareLessThan(listGet(b1.value, b2.value), b0.value)) { b2.value = ((+b2.value || 0) + 1); } -while (compareGreaterThan((b1.value[(b3.value | 0) - 1] ?? ""), b0.value)) { +while (compareGreaterThan(listGet(b1.value, b3.value), b0.value)) { b3.value = ((+b3.value || 0) + -1); } if (compareGreaterThan(b2.value, b3.value)) { From 40715dc259e24d811514b6ca49597c03685d1c53 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Mon, 22 Sep 2025 18:32:21 -0500 Subject: [PATCH 30/56] Add test case for https://github.com/TurboWarp/scratch-vm/issues/276 --- .../tw-loop-condition-optimization-gh-276.sb3 | Bin 0 -> 3277 bytes ...dition-optimization-gh-276.sb3.tw-snapshot | 28 +++++++++++++++++ ...dition-optimization-gh-276.sb3.tw-snapshot | 29 ++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 test/fixtures/execute/tw-loop-condition-optimization-gh-276.sb3 create mode 100644 test/snapshot/__snapshots__/tw-loop-condition-optimization-gh-276.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-loop-condition-optimization-gh-276.sb3.tw-snapshot diff --git a/test/fixtures/execute/tw-loop-condition-optimization-gh-276.sb3 b/test/fixtures/execute/tw-loop-condition-optimization-gh-276.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..48099303ce15a5d8950c76836b2560cd7b0e4bf3 GIT binary patch literal 3277 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 literal 0 HcmV?d00001 diff --git a/test/snapshot/__snapshots__/tw-loop-condition-optimization-gh-276.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-loop-condition-optimization-gh-276.sb3.tw-snapshot new file mode 100644 index 00000000000..20e23cc6056 --- /dev/null +++ b/test/snapshot/__snapshots__/tw-loop-condition-optimization-gh-276.sb3.tw-snapshot @@ -0,0 +1,28 @@ +// TW Snapshot +// Input SHA-256: 3c81a01417b9a927457132a5f89b63e54b2499714376246739535b51dbce2d45 + +// 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 1",}, b0, false, false, "g", null); +thread.procedures["Wtest %s"]("random"); +if ((("" + b1.value).toLowerCase() === "random".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "p", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "n", null); +retire(); return; +}; }) + +// Sprite1 Wtest %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +const b1 = stage.variables["t)]?yi[*8XU73qhMqOa8"]; +return function funXYZ_test_ (p0) { +b0.value = p0; +while (!(("" + listGet(b1.value, b0.value)).toLowerCase() === "something".toLowerCase())) { +b0.value = ((+b0.value || 0) + 1); +} +return ""; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-loop-condition-optimization-gh-276.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-loop-condition-optimization-gh-276.sb3.tw-snapshot new file mode 100644 index 00000000000..5cef84247bc --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-loop-condition-optimization-gh-276.sb3.tw-snapshot @@ -0,0 +1,29 @@ +// TW Snapshot +// Input SHA-256: 3c81a01417b9a927457132a5f89b63e54b2499714376246739535b51dbce2d45 + +// 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 1",}, b0, false, false, "g", null); +yield* thread.procedures["Wtest %s"]("random"); +if ((("" + b1.value).toLowerCase() === "random".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "p", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "n", null); +retire(); return; +}; }) + +// Sprite1 Wtest %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +const b1 = stage.variables["t)]?yi[*8XU73qhMqOa8"]; +return function* genXYZ_test_ (p0) { +b0.value = p0; +while (!(("" + listGet(b1.value, b0.value)).toLowerCase() === "something".toLowerCase())) { +b0.value = ((+b0.value || 0) + 1); +if (isStuck()) yield; +} +return ""; +}; }) From 5a90e13981cb62201724fd16497bb64728405055 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Mon, 22 Sep 2025 18:37:42 -0500 Subject: [PATCH 31/56] Fix TypeState#after setting variable types to undefined If a variable existed in `this` but not in `other`, its resulting type would be set to undefined. Changing it to use the type from `this` seems to fix https://github.com/TurboWarp/scratch-vm/issues/273. --- src/compiler/iroptimizer.js | 2 +- .../tw-procedure-return-recursion.sb3.tw-snapshot | 6 +++--- .../tw-procedure-return-recursion.sb3.tw-snapshot | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index 97135787272..6cadd77168d 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -94,7 +94,7 @@ class TypeState { after (other) { return this.mutate(other, varId => { const otherType = other.variables[varId]; - if (otherType !== 0) return otherType; + if (otherType) return otherType; return this.variables[varId] ?? InputType.ANY; }); } diff --git a/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot index b7824ee767a..6b2bc2a9f60 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot @@ -10,17 +10,17 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 18",}, b0, false, false, "G", null); b1.value = 0; b2.value = (yield* thread.procedures["Znon warp recursion should yield %s"](8)); -if (((+b1.value || 0) === 4)) { +if ((b1.value === 4)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp recursion yields",}, b0, false, false, "ao", null); } b1.value = 0; b2.value = thread.procedures["Wwarp recursion should not yield %s"](8); -if (compareEqual(b1.value, 0)) { +if ((b1.value === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass warp recursion does not yield",}, b0, false, false, "ar", null); } b1.value = 0; b2.value = (yield* thread.procedures["Zfib %s"](7)); -if (((+b1.value || 0) === 20)) { +if ((b1.value === 20)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp fib yielded",}, b0, false, false, "au", null); } yield* thread.procedures["Zrecursing yields between each %s"]("initial"); 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 index b7824ee767a..6b2bc2a9f60 100644 --- 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 @@ -10,17 +10,17 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 18",}, b0, false, false, "G", null); b1.value = 0; b2.value = (yield* thread.procedures["Znon warp recursion should yield %s"](8)); -if (((+b1.value || 0) === 4)) { +if ((b1.value === 4)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp recursion yields",}, b0, false, false, "ao", null); } b1.value = 0; b2.value = thread.procedures["Wwarp recursion should not yield %s"](8); -if (compareEqual(b1.value, 0)) { +if ((b1.value === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass warp recursion does not yield",}, b0, false, false, "ar", null); } b1.value = 0; b2.value = (yield* thread.procedures["Zfib %s"](7)); -if (((+b1.value || 0) === 20)) { +if ((b1.value === 20)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp fib yielded",}, b0, false, false, "au", null); } yield* thread.procedures["Zrecursing yields between each %s"]("initial"); From c372e925a52c20cc5410e7128d0c88e9ca8b6c60 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Mon, 22 Sep 2025 19:27:08 -0500 Subject: [PATCH 32/56] Add test case for 6a37fee3866cb96fbc054009b6b1f31bb9244cf9 --- .../tw-warp-loop-condition-analysis.sb3 | Bin 0 -> 3363 bytes ...rp-loop-condition-analysis.sb3.tw-snapshot | 39 +++++++++++++++++ ...rp-loop-condition-analysis.sb3.tw-snapshot | 40 ++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 test/fixtures/execute/tw-warp-loop-condition-analysis.sb3 create mode 100644 test/snapshot/__snapshots__/tw-warp-loop-condition-analysis.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-warp-loop-condition-analysis.sb3.tw-snapshot diff --git a/test/fixtures/execute/tw-warp-loop-condition-analysis.sb3 b/test/fixtures/execute/tw-warp-loop-condition-analysis.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..126857522d82ea44de2d6b67d6097a33716bdeb4 GIT binary patch literal 3363 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 Date: Mon, 22 Sep 2025 19:28:17 -0500 Subject: [PATCH 33/56] Add test case for 7cc8f580b3e15b805ef5b1b441b190392cfcd49b --- .../tw-non-warp-loop-condition-analysis.sb3 | Bin 0 -> 3185 bytes ...rp-loop-condition-analysis.sb3.tw-snapshot | 29 ++++++++++++++++++ ...rp-loop-condition-analysis.sb3.tw-snapshot | 29 ++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 test/fixtures/execute/tw-non-warp-loop-condition-analysis.sb3 create mode 100644 test/snapshot/__snapshots__/tw-non-warp-loop-condition-analysis.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-non-warp-loop-condition-analysis.sb3.tw-snapshot diff --git a/test/fixtures/execute/tw-non-warp-loop-condition-analysis.sb3 b/test/fixtures/execute/tw-non-warp-loop-condition-analysis.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..59c1cfcf66cee6cdfcb5254567d83f32845187be GIT binary patch literal 3185 zcma);c{CJ!7stmklWpv4vS#063}ftBVh}^oC|iS(eJvD?y{yT;reS1Fqz0+UBqW7s zY-2=Y%M!9h;q{*PkLP(h=RN27-E+_HoO^!vp7Z_V_x)U?B?BW1003YD__n-5UM(l~ zo&W*>iCh2x+i^80B+$pxJxs+XG%(=#CI7&$TAYW9&Slmwa_x!|0Ce8Zdk8Bc6L}9< zr~ z1ap1jDUDjy5{OZx&9pd+TIS3L+lr@7NkNvT0FKNF^=!QAGzd@aGQWZk&2P5#T) zXH<5|OWcg;Bp{kn*{LhI2wd|146ZX-JJp&*A<+sz4R_2kdPKUml;St4Q09P++Fl_S2<**^J{BwTijFcj z*q7Ll0)~Z0>RHJ739I5_a?k^`1`Dc&hb%|oLp{ekDio?qgWHqSxl6*_ z{8czmhVp8JjLRfSI4sE3({vpOtcp&7`V+2NQ|yheb-;96#8XNkl(tt8y8vad5Q3kS z)pi-;dSAc7pSx7e$uO+&Z0BB4->qjY`m+!@hTUQA#lx|*FXExkcvMXc49)Nd1Fh#>u<`pyXd`5WJ)YWuU*iOu1xE#xu-nU&gUH8x? z8C^AGNgrSUJ?$^@>zeBKdBk>suhNr%iY1p@j7xn;E^jQW%W=eA^g&EKvF4^-jJ~ka z1(l}V=Ym`R7QUc)vd@Dl;1YL>qHvnj`3G;>)e9XWn6C$~_#5{-Tluyw_bDPi8^>%f ztQRRzVQ9g~!LAfVe!Q)fS43W7mZ^YJ2xG1#M=TGgJXn&+RYtJvJ>EB^+Q44>>|B=1 zki8D;8EJg}2M_x6z^qSlZM=BaFh?<5P)I(K6T@B-b!Jr`!h$_@m)ze+W(?bh- zRVDO>m;ZT7_IPpL$y+I-EhpYKK;M)jYzBFrStMNS>Zlu}j9$(^k5MZO6dt8>;3?m1 zHtLl&3ej^+F24~rug_Ml&}Zk@X@>8u zC@wpSd)naJ)LzXRv-4KaY4a;Xp{QtJ>-8DyQXt3-cF3p?|N02a>}Dmfx@(CA{8QAb zR9U9RF$Gds|0fC#fq7`cAaIC=CkzdTV_;fp>M(T;cc>QBT@wz0{8!Xt2TB0t9OnUF z9597nd{jrLxw$Ku9zy$H%YX42)0)jQMPN@V%7k5#Kb*EeT^$@~4`UIX99XAatzG1?8g zRdt;kH^?J>)SD-cMzBUq0YijAU|Fy7-ofLaiMyUES zLOFOwbv=4|s4MD4n+tm>cH~oBw;C`iwiZsSrHbvicGGbF*25d?TuEzD2RgzxpTXFd z+wFhN+%9r)#2Wai^-)%#qGc=F0WDn|+F16dj%5KOJ+%GJ+GtN5C+E_0tqubN4g;$@ zO-GHwyTyk)jY4ON_0Ba-;mGaVVZ(k7t50I%vIQ-^`|)zCy*}$?UIKYUgUTj@+wJaF zpUziR^rqKQd0Uf8+RG|9A-Ukc zz90vl4q^)g@WjCr2e&LCtNT@cdgwVL=G$2!^0FnSdkV4m{mN5X8V6ZCw-@lP6W9}x zuZ6?}uI=u&Q|XlrCvzb4>(J%IQsS!!xcRx;oWUr= zAoKJLnNwQU7^WCWBW{X17Bh``vXgi%jHl)VQ}rD|Z_Q;?IAlylBu5gw`YUZ0NX$72 za&WJ+5z4%l0-RDcC@9^F%F`Fiv|cv~q7m5-mse6Ex~;N!B!R;D$uA3r#!*UC&&B~UMYuJuPh1S2V%6!BLRIlq<7g*L(R z;(WyzG(En9Rc@@j4<2s8^SO8M^b{Uz%JJ2f?i|sO6b!?&q4P&Q2BI44fLQO#zg57#_MPhbEXL( z>jvmakRb~{Q$sVq+{r`H`#cZa@6kK?aj}`ZlF$*ue#HHG6eeE!@TT^gh|-|$P2K?yW zIMK*RTtkUy7n)mL*&maAC&MtE^Ud!_2~@ErU;Ty92jKYovOUPN&0t>AbS*~|ssJ{n z0P+2hzobn6B77kg6M4}!4(KXBlh_qT;C)#{ca6lS6;CsT~{nzoF8f i6aR$3UlTzf0N~#s0ci<5ehC0D9LLo09es=w0N_8jvX`I$ literal 0 HcmV?d00001 diff --git a/test/snapshot/__snapshots__/tw-non-warp-loop-condition-analysis.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-non-warp-loop-condition-analysis.sb3.tw-snapshot new file mode 100644 index 00000000000..018d12fbdc0 --- /dev/null +++ b/test/snapshot/__snapshots__/tw-non-warp-loop-condition-analysis.sb3.tw-snapshot @@ -0,0 +1,29 @@ +// TW Snapshot +// Input SHA-256: be149d21cc69628647000cc698ebd60949252730a0ea7b19f71cc85fe927b294 + +// 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["%ehs:~!Y0l-5=!mnd-B?"]; +const b2 = stage.variables["=3aHfv[mKa)v,Wfpy:y?"]; +const b3 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "d", null); +b1.value = []; +b1.value.push((1 + 2)); +b1._monitorUpToDate = false; +b1.value.push("eof"); +b1._monitorUpToDate = false; +b2.value = 0; +b3.value = "bwah"; +while (!(("" + b3.value).toLowerCase() === "eof".toLowerCase())) { +b2.value = ((+b2.value || 0) + 1); +b3.value = (b1.value[(b2.value | 0) - 1] ?? ""); +yield; +} +if (((+b2.value || 0) === 2)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "q", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "o", null); +retire(); return; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-non-warp-loop-condition-analysis.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-non-warp-loop-condition-analysis.sb3.tw-snapshot new file mode 100644 index 00000000000..018d12fbdc0 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-non-warp-loop-condition-analysis.sb3.tw-snapshot @@ -0,0 +1,29 @@ +// TW Snapshot +// Input SHA-256: be149d21cc69628647000cc698ebd60949252730a0ea7b19f71cc85fe927b294 + +// 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["%ehs:~!Y0l-5=!mnd-B?"]; +const b2 = stage.variables["=3aHfv[mKa)v,Wfpy:y?"]; +const b3 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "d", null); +b1.value = []; +b1.value.push((1 + 2)); +b1._monitorUpToDate = false; +b1.value.push("eof"); +b1._monitorUpToDate = false; +b2.value = 0; +b3.value = "bwah"; +while (!(("" + b3.value).toLowerCase() === "eof".toLowerCase())) { +b2.value = ((+b2.value || 0) + 1); +b3.value = (b1.value[(b2.value | 0) - 1] ?? ""); +yield; +} +if (((+b2.value || 0) === 2)) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "q", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "o", null); +retire(); return; +}; }) From 214b86a063f72c4a126bb47d77a56049cb398e48 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Mon, 22 Sep 2025 19:29:06 -0500 Subject: [PATCH 34/56] Add test case for d8c73c71d255d1a9a6526b1cd7e3da8c077fac89 --- ...-analysis-understands-stop-this-script.sb3 | Bin 0 -> 3262 bytes ...derstands-stop-this-script.sb3.tw-snapshot | 30 ++++++++++++++++++ ...derstands-stop-this-script.sb3.tw-snapshot | 30 ++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 test/fixtures/execute/tw-state-analysis-understands-stop-this-script.sb3 create mode 100644 test/snapshot/__snapshots__/tw-state-analysis-understands-stop-this-script.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-state-analysis-understands-stop-this-script.sb3.tw-snapshot diff --git a/test/fixtures/execute/tw-state-analysis-understands-stop-this-script.sb3 b/test/fixtures/execute/tw-state-analysis-understands-stop-this-script.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..735f45d77caa48a86c503a91a37b36a1dee968fb GIT binary patch literal 3262 zcma);c{CJ!7srQ;eT%_J_K1`;&0w;ReT%GPXBcE(Bij@a%D%-|B0FI$BTFJXg_5xh zN|Pm;5JSoGde8gE^E{pNp7Z?fx#ym9&+p!IzW;nb*I54y4I=;mpa*!i4jJoyu}7Ez z0RYrl0D$?l>lfhbfp!j(_6YR#d1+}A@>!PsSgPvay;86h@yUih)^MgUKh=eA$UaZK zs=en3dx zbbS2y4Z`$|sA8gIZ6pGNAsa;P94CFpM5z3tRIHG&lPOsa>9fPAv-uB4Oqu$TI?#rR zZ^TYY8ULM*y`rKUD1ldJRZfa6NI$8@`x4sES!}%OIgi&wtn*#1mliD8k`;_y@FlP< z^f(-aD76?}Q6(s)lO$N)sgim9;}TNdYQa)+RU`+L^*QpSyMDq~WfYtdD|%KoS0
ROiMLqst~xX&NKW&c~nw* z`9mjvgvahJfd)K^b=3gHi`!-PGdbpMu^V5^j8gNixINy}9=$AiYsN_tjk6fRd83bc zm*w(}>sm6Q!(fkTC;23LbY!w=nv+bNiaL1?!Q7QI0=R6hOf?@#7?q$5+U3W!t?1&0KPh^;P20u0C0*IN9 z)AV?bbDf9=3%3hqYK%CIKX7!*9}JMhbSs)xRIS44Iz!(wD(j26ZObrP*bBSotgn4r zIXEoA_y@AM+c8C&FBOOE*G;P^bwEYJUSu>o*4>m8Me!~pAHKQxDxM2_0i>2@RcRWz>0adBw)Xlw`)~Pk#U4y9m!Z#aEfx zR~fWtv2Bu1U!={uh2V$GFs->CC*dTJPxn0sQejzUP=3U&Ee+i;Yv8-V$?taBzv=pi zp3E#ZT&-<;DIYmaRvgz1Z<>y>siemj+9-h?b&&d$uVf${4m0TEi~UWtn)-Eq6HwKUr1YR(`Sd)=m&;iCyW0z3(g z_g@=KQz@?LE*B&wqc(mvR-;F%Jk^ZQ6YFMv#8*C2TVy*VACdgBgtgJmo{K0vImaQRbbEC0LP-6E_n!s2pNhm`M?St)-U<(5AT=NHnx(Mei z!MUEJ8eFze%MhDkq6o!;tb+K^@6(qSL4$A@J~Tf5yeDM<$~b7krnZqsXvaITFVsrD zuo;~Zn=ko+iF*OsuynUv?zXNvyJi$a}D&irczc1at(G3 z>LLTXe$Caz)fEMSKxCk38MHGD?II6#k`BD%=546Y8Y2Xn4^NzKrR%GMzAn)1S+diF7#2eWC|SjFMZ>TXYiLSm%lBsARgg*9W%U`Ftio{lE^+7>lV4mppd)%+gn?o z1ZMb?r+Iq9F;pl0%Rl^E7chqxwJ(*~eyH%?X;?6jtFai?9o8kTxX#;uG#JtxE;BA^ zep^BQxwW(8KXot`;x_($cIl{N!}Uhpenn01_MIuh=t9^z%v#Jp{hNIKZ5jzlZi+7)QMuoxe=7$@)HP8#_3vB!H2yjQVGTDZmHsSZleq?ZM`{(f`@ zSns1I6L z_?8$tV35u!0_4j{=zQ{ic2}IfK8_9gLr#cBx3sj+Zi`_OMK#qODKm7STS8NK-!HmQ z!T(KcTe>XDo+`Vvv?r8>fc`om0eE~ztD8QKN}!Ztz;1!(y$+Q{lG1<~<~)iIT^<_W zGXgNu2>C{^CaZ?OuF8w1nTXdo^Pu5<$ps-06w7EZo&Vyjs46vrxiV8E`GPsUz~i9= z#mujaS+&GTVhf2*ECQnFMB4mW|!Zc$lo3#Qys5t}qfS z%T;R{SjZBSc5%>Ht%(KH^QoFG0{KL4@e;)IP0soi>h_S;N3M6x9HM~^l1s7uK_t$% zIx1XWgiRrjxE%Q)pyfjsF!yv~iz?C!$@yzHNads}Y5s)aQ$OWLO3L~^=8UmE6*UO( z|8MzIp#1Iq{j&da@=p%^H5PSh Date: Mon, 22 Sep 2025 19:30:23 -0500 Subject: [PATCH 35/56] Add test case for 7ed29f5801c4ad4399d852bf5b4f19244b13cc5b --- ...avoids-number-literal-if-name-conflict.sb3 | Bin 0 -> 2035 bytes ...r-literal-if-name-conflict.sb3.tw-snapshot | 21 ++++++++++++++++++ ...r-literal-if-name-conflict.sb3.tw-snapshot | 21 ++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 test/fixtures/execute/tw-sound-menu-avoids-number-literal-if-name-conflict.sb3 create mode 100644 test/snapshot/__snapshots__/tw-sound-menu-avoids-number-literal-if-name-conflict.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-sound-menu-avoids-number-literal-if-name-conflict.sb3.tw-snapshot diff --git a/test/fixtures/execute/tw-sound-menu-avoids-number-literal-if-name-conflict.sb3 b/test/fixtures/execute/tw-sound-menu-avoids-number-literal-if-name-conflict.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..ce978f1954e0192d3a7d2bc9941df72e9e62949e GIT binary patch literal 2035 zcma)-cTm&W7RP_|P#4zhQk0@}1YSrXVNpPE3DUcZfFy)OAQTq}eU~B$y^KLXq%1{N zjDi%Y@}w70L}Hevp+tBrgoh-oNHZ_9vor28^Jctv&L8*AJ>NT@bIzROY$qfv0RR99 zAhwS>+fDe6;DiByB?kaf{M+jh;lX%ZlwojWco?I^H*(n+OyNn?Fk4}U+t(~Rf-dz( zLme>Mz5k7FR@C!u-ag}b{wtcJys*jTq~Z|)T1^U-p-m(O3-5`OI%rw1d~(gfl<7Y#4? zvR`B_W(su6>2He}p3$#T_Z6N@qZdAl6eQ)BP;fIBWAq2K|GiaOS3mFmYlYRNkzokx zj7(2)LQbfBwxnT`R9vD&nS0^|U;1yAZJ$!QtkHhVK%EsYd$4JteA$Y%;&=9#k11JI zYVPkmZIv`nk-nWlftZ|h~y|^mmufm>rk{5o`S=Aa% zwY9>Q1<<}fDuB8c(G|50!*ccFA_Ryn91=|=f?9!u zL4~?y>3 zQ)#Pn$&#SAz|~p&s+e|o+b=!3IG?Q!RJM;EXZ$_|agVfwiHMteS}GGyKAC;D;-LB4 zY`BTE0z>(Qtxn%c|DEmvWInOk$)}j8aFC8B*e3@A+i6Q<^yy{dG`zLg-AxPjkUbE5xlJtDYh{gcs?5l=_-0Ry&6}BqB1?Qsc?Q zJP(gWHJwj%oyNgiuIhWgT#+L@Ad|n=)6PI8@;Y=Dc9Wf0VC!Pis66*j_wFl8>}lG= zcG4dlO1{d}JmG2zGTvuhXhEWW$CYX;p95Rsy!=69vRu55Bcw}hO}MV+@ltuGwG$*W z8PRzt=KPOllrbSIK;<`~XMEG(8!iB9gfM~!KoBq^LI8o_4~0S@a6AN$L*N5YaI9fu z^tDh2JDD45;5P|b)9qq|4e@; zA7l4wP*5xZPC&vDus@;lUP#=4$fo&evO7x$E5NKi zq+Ds`P)T73q|Ir;{R#M)N;G9pfl&Ll*;l(?0X*s{Xlkr#lQO<%a{a1p(0EVo7{?LR zcQ)ztz>?7ep2h6$d2=_6i0Z7aY;T$M21hsMH9il`Xy;;-3}9*)_`~1enKD*`e$KRJ!+Hu9>iT$kT nF9HBxlIG)z$3yv9(NYuuz9g{Db|U Date: Mon, 22 Sep 2025 19:35:06 -0500 Subject: [PATCH 36/56] Use Object.create(null) instead of {} Avoids any weird issues if variables have strange IDs like "toString" --- src/compiler/iroptimizer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index 6cadd77168d..416f1c3e060 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -17,7 +17,7 @@ const { class TypeState { constructor () { /** @type {Object.}*/ - this.variables = {}; + this.variables = Object.create(null); } /** @@ -31,7 +31,7 @@ class TypeState { break; } } - this.variables = {}; + this.variables = Object.create(null); return modified; } From 2244b26773ca958613873b33dc23681775d9f963 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Wed, 24 Sep 2025 16:48:01 -0500 Subject: [PATCH 37/56] Discard old state after calling yielding procedure --- src/compiler/iroptimizer.js | 12 ++++++ ...analyze-yields-due-to-direct-recursion.sb3 | Bin 0 -> 3330 bytes ...tate-accounts-for-yields-in-procedures.sb3 | Bin 0 -> 3243 bytes ...ds-due-to-direct-recursion.sb3.tw-snapshot | 39 ++++++++++++++++++ ...s-for-yields-in-procedures.sb3.tw-snapshot | 38 +++++++++++++++++ ...procedure-return-recursion.sb3.tw-snapshot | 4 +- ...ds-due-to-direct-recursion.sb3.tw-snapshot | 39 ++++++++++++++++++ ...s-for-yields-in-procedures.sb3.tw-snapshot | 38 +++++++++++++++++ ...procedure-return-recursion.sb3.tw-snapshot | 4 +- 9 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/execute/tw-analyze-yields-due-to-direct-recursion.sb3 create mode 100644 test/fixtures/execute/tw-exit-state-accounts-for-yields-in-procedures.sb3 create mode 100644 test/snapshot/__snapshots__/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index 416f1c3e060..ae7d98e495f 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -99,6 +99,14 @@ class TypeState { }); } + /** + * @param {TypeState} other + * @returns {boolean} + */ + overwrite (other) { + return this.mutate(other, varId => other.variables[varId] ?? InputType.ANY); + } + /** * @param {*} variable A variable codegen object. * @param {InputType} type The type to set this variable to @@ -490,6 +498,8 @@ class IROptimizer { if (!script || !script.cachedAnalysisEndState) { modified = state.clear() || modified; + } else if (script.yields) { + modified = state.overwrite(script.cachedAnalysisEndState) || modified; } else { modified = state.after(script.cachedAnalysisEndState) || modified; } @@ -578,6 +588,8 @@ class IROptimizer { if (!script || !script.cachedAnalysisEndState) { modified = state.clear() || modified; + } else if (script.yields) { + modified = state.overwrite(script.cachedAnalysisEndState) || modified; } else { modified = state.after(script.cachedAnalysisEndState) || modified; } diff --git a/test/fixtures/execute/tw-analyze-yields-due-to-direct-recursion.sb3 b/test/fixtures/execute/tw-analyze-yields-due-to-direct-recursion.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..136864b417e341ade429b75c618475aca7379c65 GIT binary patch literal 3330 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 XAi>lCc<~bept-p77oFvw5eM)O$|&Lp literal 0 HcmV?d00001 diff --git a/test/fixtures/execute/tw-exit-state-accounts-for-yields-in-procedures.sb3 b/test/fixtures/execute/tw-exit-state-accounts-for-yields-in-procedures.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..6d194449637014725c30803b67f292eeb3ef212b GIT binary patch literal 3243 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 literal 0 HcmV?d00001 diff --git a/test/snapshot/__snapshots__/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot new file mode 100644 index 00000000000..c19e45f7049 --- /dev/null +++ b/test/snapshot/__snapshots__/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot @@ -0,0 +1,39 @@ +// TW Snapshot +// Input SHA-256: 848a4efc16b174b53f0a4b581e6b6d1091ae9eaa916e040c963e360ce3883509 + +// 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"]; +const b2 = stage.variables["FpLI$ida6)qR,q~y`1|*"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "j", null); +b1.value = (1 + 2); +yield* thread.procedures["Znon-warp recursion %s"](2); +if ((("" + listGet(b2.value, b1.value)).toLowerCase() === "the only thing".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "t", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "s", null); +retire(); return; +}; }) + +// Sprite1 Znon-warp recursion %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function* genXYZ_non_warp_recursion_ (p0) { +if (compareGreaterThan(p0, 0)) { +yield; +yield* thread.procedures["Znon-warp recursion %s"](((+p0 || 0) - 1)); +} +return ""; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +for (var a0 = 1; a0 >= 0.5; a0--) { +yield; +} +b0.value = "random"; +retire(); return; +}; }) diff --git a/test/snapshot/__snapshots__/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot new file mode 100644 index 00000000000..c8bf63116b7 --- /dev/null +++ b/test/snapshot/__snapshots__/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot @@ -0,0 +1,38 @@ +// TW Snapshot +// Input SHA-256: 4715c75fe8effcdcdea3ad810949e33a7b6beb3fbd253c1edc2ed4f2bd093df3 + +// 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"]; +const b2 = stage.variables["FpLI$ida6)qR,q~y`1|*"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "e", null); +b1.value = (1 + 2); +yield* thread.procedures["Zsomething that yields"](); +if ((("" + listGet(b2.value, b1.value)).toLowerCase() === "the only thing".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "o", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "n", null); +retire(); return; +}; }) + +// Sprite1 Zsomething that yields +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function* genXYZ_something_that_yield () { +for (var a0 = 2; a0 >= 0.5; a0--) { +yield; +} +return ""; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +for (var a0 = 1; a0 >= 0.5; a0--) { +yield; +} +b0.value = "random"; +retire(); 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 index 6b2bc2a9f60..c242a148a32 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-recursion.sb3.tw-snapshot @@ -10,7 +10,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 18",}, b0, false, false, "G", null); b1.value = 0; b2.value = (yield* thread.procedures["Znon warp recursion should yield %s"](8)); -if ((b1.value === 4)) { +if (((+b1.value || 0) === 4)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp recursion yields",}, b0, false, false, "ao", null); } b1.value = 0; @@ -20,7 +20,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass warp recursion does not yiel } b1.value = 0; b2.value = (yield* thread.procedures["Zfib %s"](7)); -if ((b1.value === 20)) { +if (((+b1.value || 0) === 20)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp fib yielded",}, b0, false, false, "au", null); } yield* thread.procedures["Zrecursing yields between each %s"]("initial"); diff --git a/test/snapshot/__snapshots__/warp-timer/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot new file mode 100644 index 00000000000..c19e45f7049 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot @@ -0,0 +1,39 @@ +// TW Snapshot +// Input SHA-256: 848a4efc16b174b53f0a4b581e6b6d1091ae9eaa916e040c963e360ce3883509 + +// 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"]; +const b2 = stage.variables["FpLI$ida6)qR,q~y`1|*"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "j", null); +b1.value = (1 + 2); +yield* thread.procedures["Znon-warp recursion %s"](2); +if ((("" + listGet(b2.value, b1.value)).toLowerCase() === "the only thing".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "t", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "s", null); +retire(); return; +}; }) + +// Sprite1 Znon-warp recursion %s +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function* genXYZ_non_warp_recursion_ (p0) { +if (compareGreaterThan(p0, 0)) { +yield; +yield* thread.procedures["Znon-warp recursion %s"](((+p0 || 0) - 1)); +} +return ""; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +for (var a0 = 1; a0 >= 0.5; a0--) { +yield; +} +b0.value = "random"; +retire(); return; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot new file mode 100644 index 00000000000..c8bf63116b7 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot @@ -0,0 +1,38 @@ +// TW Snapshot +// Input SHA-256: 4715c75fe8effcdcdea3ad810949e33a7b6beb3fbd253c1edc2ed4f2bd093df3 + +// 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"]; +const b2 = stage.variables["FpLI$ida6)qR,q~y`1|*"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "e", null); +b1.value = (1 + 2); +yield* thread.procedures["Zsomething that yields"](); +if ((("" + listGet(b2.value, b1.value)).toLowerCase() === "the only thing".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "o", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "n", null); +retire(); return; +}; }) + +// Sprite1 Zsomething that yields +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +return function* genXYZ_something_that_yield () { +for (var a0 = 2; a0 >= 0.5; a0--) { +yield; +} +return ""; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +for (var a0 = 1; a0 >= 0.5; a0--) { +yield; +} +b0.value = "random"; +retire(); 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 index 6b2bc2a9f60..c242a148a32 100644 --- 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 @@ -10,7 +10,7 @@ return function* genXYZ () { yield* executeInCompatibilityLayer({"MESSAGE":"plan 18",}, b0, false, false, "G", null); b1.value = 0; b2.value = (yield* thread.procedures["Znon warp recursion should yield %s"](8)); -if ((b1.value === 4)) { +if (((+b1.value || 0) === 4)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp recursion yields",}, b0, false, false, "ao", null); } b1.value = 0; @@ -20,7 +20,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass warp recursion does not yiel } b1.value = 0; b2.value = (yield* thread.procedures["Zfib %s"](7)); -if ((b1.value === 20)) { +if (((+b1.value || 0) === 20)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp fib yielded",}, b0, false, false, "au", null); } yield* thread.procedures["Zrecursing yields between each %s"]("initial"); From bbfa559972cc30e9f6cdd995cc4d5c642d1e538b Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 28 Sep 2025 12:22:03 +1000 Subject: [PATCH 38/56] Fix sensing size of type --- src/compiler/irgen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 8bc9897b97e..09ece34548f 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -540,7 +540,7 @@ class ScriptTreeGenerator { case 'costume name': return new IntermediateInput(InputOpcode.SENSING_OF_COSTUME_NAME, InputType.STRING, {object}); case 'size': - return new IntermediateInput(InputOpcode.SENSING_OF_SIZE, InputType.NUMBER_POS_REAL, {object}); + return new IntermediateInput(InputOpcode.SENSING_OF_SIZE, InputType.NUMBER_POS, {object}); } } From bc9a3d03b1b85305801a7ce8741023938dd2e8a7 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 28 Sep 2025 12:29:50 +1000 Subject: [PATCH 39/56] Fix sensing x/y of types --- src/compiler/irgen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 09ece34548f..66997be19b0 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -530,9 +530,9 @@ class ScriptTreeGenerator { } else { switch (property) { case 'x position': - return new IntermediateInput(InputOpcode.SENSING_OF_POS_X, InputType.NUMBER_REAL, {object}); + return new IntermediateInput(InputOpcode.SENSING_OF_POS_X, InputType.NUMBER, {object}); case 'y position': - return new IntermediateInput(InputOpcode.SENSING_OF_POS_Y, InputType.NUMBER_REAL, {object}); + return new IntermediateInput(InputOpcode.SENSING_OF_POS_Y, InputType.NUMBER, {object}); case 'direction': return new IntermediateInput(InputOpcode.SENSING_OF_DIRECTION, InputType.NUMBER_REAL, {object}); case 'costume #': From 8fe321c357b75dda23ab1c10dca57991606d44de Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 28 Sep 2025 12:30:32 +1000 Subject: [PATCH 40/56] Make types for blocks which returns ints more spercific --- src/compiler/irgen.js | 26 +++++++++---------- .../tw-gh-249-quicksort.sb3.tw-snapshot | 2 +- .../tw-gh-249-quicksort.sb3.tw-snapshot | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 8bc9897b97e..f6b8f85c67a 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -262,7 +262,7 @@ class ScriptTreeGenerator { index: this.descendInputOfBlock(block, 'INDEX') }); case 'data_lengthoflist': - return new IntermediateInput(InputOpcode.LIST_LENGTH, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO, { + return new IntermediateInput(InputOpcode.LIST_LENGTH, InputType.NUMBER_POS_INT | InputType.NUMBER_ZERO, { list: this.descendVariable(block, 'LIST', LIST_TYPE) }); case 'data_listcontainsitem': @@ -271,7 +271,7 @@ class ScriptTreeGenerator { item: this.descendInputOfBlock(block, 'ITEM') }); case 'data_itemnumoflist': - return new IntermediateInput(InputOpcode.LIST_INDEX_OF, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO, { + return new IntermediateInput(InputOpcode.LIST_INDEX_OF, InputType.NUMBER_POS_INT | InputType.NUMBER_ZERO, { list: this.descendVariable(block, 'LIST', LIST_TYPE), item: this.descendInputOfBlock(block, 'ITEM') }); @@ -290,12 +290,12 @@ class ScriptTreeGenerator { case 'looks_backdropnumbername': if (block.fields.NUMBER_NAME.value === 'number') { - return new IntermediateInput(InputOpcode.LOOKS_BACKDROP_NUMBER, InputType.NUMBER_POS_REAL); + return new IntermediateInput(InputOpcode.LOOKS_BACKDROP_NUMBER, InputType.NUMBER_POS_INT); } return new IntermediateInput(InputOpcode.LOOKS_BACKDROP_NAME, InputType.STRING); case 'looks_costumenumbername': if (block.fields.NUMBER_NAME.value === 'number') { - return new IntermediateInput(InputOpcode.LOOKS_COSTUME_NUMBER, InputType.NUMBER_POS_REAL); + return new IntermediateInput(InputOpcode.LOOKS_COSTUME_NUMBER, InputType.NUMBER_POS_INT); } return new IntermediateInput(InputOpcode.LOOKS_COSTUME_NAME, InputType.STRING); case 'looks_size': @@ -482,13 +482,13 @@ class ScriptTreeGenerator { }); case 'sensing_current': switch (block.fields.CURRENTMENU.value.toLowerCase()) { - case 'year': return new IntermediateInput(InputOpcode.SENSING_TIME_YEAR, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO); - case 'month': return new IntermediateInput(InputOpcode.SENSING_TIME_MONTH, InputType.NUMBER_POS_REAL); - case 'date': return new IntermediateInput(InputOpcode.SENSING_TIME_DATE, InputType.NUMBER_POS_REAL); - case 'dayofweek': return new IntermediateInput(InputOpcode.SENSING_TIME_WEEKDAY, InputType.NUMBER_POS_REAL); - case 'hour': return new IntermediateInput(InputOpcode.SENSING_TIME_HOUR, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO); - case 'minute': return new IntermediateInput(InputOpcode.SENSING_TIME_MINUTE, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO); - case 'second': return new IntermediateInput(InputOpcode.SENSING_TIME_SECOND, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO); + case 'year': return new IntermediateInput(InputOpcode.SENSING_TIME_YEAR, InputType.NUMBER_POS_INT | InputType.NUMBER_ZERO); + case 'month': return new IntermediateInput(InputOpcode.SENSING_TIME_MONTH, InputType.NUMBER_POS_INT); + case 'date': return new IntermediateInput(InputOpcode.SENSING_TIME_DATE, InputType.NUMBER_POS_INT); + case 'dayofweek': return new IntermediateInput(InputOpcode.SENSING_TIME_WEEKDAY, InputType.NUMBER_POS_INT); + case 'hour': return new IntermediateInput(InputOpcode.SENSING_TIME_HOUR, InputType.NUMBER_POS_INT | InputType.NUMBER_ZERO); + case 'minute': return new IntermediateInput(InputOpcode.SENSING_TIME_MINUTE, InputType.NUMBER_POS_INT | InputType.NUMBER_ZERO); + case 'second': return new IntermediateInput(InputOpcode.SENSING_TIME_SECOND, InputType.NUMBER_POS_INT | InputType.NUMBER_ZERO); default: return this.createConstantInput(0); } case 'sensing_dayssince2000': @@ -523,7 +523,7 @@ class ScriptTreeGenerator { switch (property) { case 'background #': // fallthrough for scratch 1.0 compatibility case 'backdrop #': - return new IntermediateInput(InputOpcode.SENSING_OF_BACKDROP_NUMBER, InputType.NUMBER_POS_REAL); + return new IntermediateInput(InputOpcode.SENSING_OF_BACKDROP_NUMBER, InputType.NUMBER_POS_INT); case 'backdrop name': return new IntermediateInput(InputOpcode.SENSING_OF_BACKDROP_NAME, InputType.STRING); } @@ -536,7 +536,7 @@ class ScriptTreeGenerator { case 'direction': return new IntermediateInput(InputOpcode.SENSING_OF_DIRECTION, InputType.NUMBER_REAL, {object}); case 'costume #': - return new IntermediateInput(InputOpcode.SENSING_OF_COSTUME_NUMBER, InputType.NUMBER_POS_REAL, {object}); + return new IntermediateInput(InputOpcode.SENSING_OF_COSTUME_NUMBER, InputType.NUMBER_POS_INT, {object}); case 'costume name': return new IntermediateInput(InputOpcode.SENSING_OF_COSTUME_NAME, InputType.STRING, {object}); case 'size': diff --git a/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot index 224f800f45e..edbe98dd2e9 100644 --- a/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot @@ -19,7 +19,7 @@ const b1 = stage.variables["rQq!wAuU2T9DV9,S[/s."]; return function funXYZ_run () { b0.value = []; for (var a0 = 100; a0 >= 0.5; a0--) { -b0.value.push((b1.value[((b0.value.length + 1) | 0) - 1] ?? "")); +b0.value.push((b1.value[(b0.value.length + 1) - 1] ?? "")); b0._monitorUpToDate = false; } thread.procedures["Wqsort %s %s"](1,b0.value.length); diff --git a/test/snapshot/__snapshots__/warp-timer/tw-gh-249-quicksort.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-gh-249-quicksort.sb3.tw-snapshot index 283e439826b..5f407d57fb6 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-gh-249-quicksort.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-gh-249-quicksort.sb3.tw-snapshot @@ -19,7 +19,7 @@ const b1 = stage.variables["rQq!wAuU2T9DV9,S[/s."]; return function* genXYZ_run () { b0.value = []; for (var a0 = 100; a0 >= 0.5; a0--) { -b0.value.push((b1.value[((b0.value.length + 1) | 0) - 1] ?? "")); +b0.value.push((b1.value[(b0.value.length + 1) - 1] ?? "")); b0._monitorUpToDate = false; if (isStuck()) yield; } From 2ac7095947a0f0e0b0b2d510f778d0aa717bbd88 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Sat, 4 Oct 2025 20:05:31 -0500 Subject: [PATCH 41/56] Optimize loops with provably-integer iteration counts --- src/compiler/jsgen.js | 6 +++++- ...analyze-yields-due-to-direct-recursion.sb3.tw-snapshot | 2 +- .../tw-comparison-matrix-runtime.sb3.tw-snapshot | 4 ++-- test/snapshot/__snapshots__/tw-counter.sb3.tw-snapshot | 2 +- ...tate-accounts-for-yields-in-procedures.sb3.tw-snapshot | 4 ++-- ...rus-515-wait-zero-seconds-in-warp-mode.sb3.tw-snapshot | 4 ++-- .../__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot | 4 ++-- ...tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot | 2 +- .../tw-procedure-return-stops-scripts.sb3.tw-snapshot | 2 +- .../tw-procedure-return-warp.sb3.tw-snapshot | 8 ++++---- .../tw-promise-loop-double-yield-kouzeru.sb3.tw-snapshot | 2 +- ...rting-script-keeps-running-until-yield.sb3.tw-snapshot | 2 +- .../__snapshots__/tw-unsafe-equals.sb3.tw-snapshot | 4 ++-- ...analyze-yields-due-to-direct-recursion.sb3.tw-snapshot | 2 +- .../tw-comparison-matrix-runtime.sb3.tw-snapshot | 4 ++-- .../__snapshots__/warp-timer/tw-counter.sb3.tw-snapshot | 2 +- ...tate-accounts-for-yields-in-procedures.sb3.tw-snapshot | 4 ++-- ...rus-515-wait-zero-seconds-in-warp-mode.sb3.tw-snapshot | 4 ++-- .../warp-timer/tw-gh-249-quicksort.sb3.tw-snapshot | 4 ++-- ...tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot | 2 +- .../tw-procedure-return-stops-scripts.sb3.tw-snapshot | 2 +- .../warp-timer/tw-procedure-return-warp.sb3.tw-snapshot | 8 ++++---- .../tw-promise-loop-double-yield-kouzeru.sb3.tw-snapshot | 2 +- ...rting-script-keeps-running-until-yield.sb3.tw-snapshot | 2 +- .../warp-timer/tw-unsafe-equals.sb3.tw-snapshot | 4 ++-- 25 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index 5008e6edbfa..a74c633ec33 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -610,7 +610,11 @@ class JSGenerator { break; case StackOpcode.CONTROL_REPEAT: { const i = this.localVariables.next(); - this.source += `for (var ${i} = ${this.descendInput(node.times)}; ${i} >= 0.5; ${i}--) {\n`; + if (node.times.isAlwaysType(InputType.NUMBER_INT)) { + this.source += `for (var ${i} = ${this.descendInput(node.times)}; ${i} > 0; ${i}--) {\n`; + } else { + this.source += `for (var ${i} = ${this.descendInput(node.times)}; ${i} >= 0.5; ${i}--) {\n`; + } this.descendStack(node.do, new Frame(true)); this.yieldLoop(); this.source += `}\n`; diff --git a/test/snapshot/__snapshots__/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot index c19e45f7049..8536858fee5 100644 --- a/test/snapshot/__snapshots__/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot @@ -31,7 +31,7 @@ return ""; (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function* genXYZ () { -for (var a0 = 1; a0 >= 0.5; a0--) { +for (var a0 = 1; a0 > 0; a0--) { yield; } b0.value = "random"; 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 686c36b9e13..6c6b55de7f5 100644 --- a/test/snapshot/__snapshots__/tw-comparison-matrix-runtime.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-comparison-matrix-runtime.sb3.tw-snapshot @@ -23,10 +23,10 @@ return function* genXYZ_run_test () { thread.procedures["Wsetup values"](); b0.value = 0; b1.value = 0; -for (var a0 = b2.value.length; a0 >= 0.5; a0--) { +for (var a0 = b2.value.length; a0 > 0; a0--) { b1.value = ((+b1.value || 0) + 1); b3.value = 0; -for (var a1 = b2.value.length; a1 >= 0.5; a1--) { +for (var a1 = b2.value.length; a1 > 0; a1--) { b3.value = ((+b3.value || 0) + 1); b0.value = ((+b0.value || 0) + 1); if (!compareEqual(compareGreaterThan(listGet(b2.value, b1.value), (b2.value[(b3.value | 0) - 1] ?? "")), (b4.value[(b0.value | 0) - 1] ?? ""))) { diff --git a/test/snapshot/__snapshots__/tw-counter.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-counter.sb3.tw-snapshot index a4d1176a8e7..abbfa97a51e 100644 --- a/test/snapshot/__snapshots__/tw-counter.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-counter.sb3.tw-snapshot @@ -32,7 +32,7 @@ runtime.ext_scratch3_control._counter = 0; if ((runtime.ext_scratch3_control._counter === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass clear = 0",}, b0, false, false, "w", null); } -for (var a0 = 10; a0 >= 0.5; a0--) { +for (var a0 = 10; a0 > 0; a0--) { runtime.ext_scratch3_control._counter++; yield; } diff --git a/test/snapshot/__snapshots__/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot index c8bf63116b7..7676e37b15c 100644 --- a/test/snapshot/__snapshots__/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot @@ -20,7 +20,7 @@ retire(); return; // Sprite1 Zsomething that yields (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function* genXYZ_something_that_yield () { -for (var a0 = 2; a0 >= 0.5; a0--) { +for (var a0 = 2; a0 > 0; a0--) { yield; } return ""; @@ -30,7 +30,7 @@ return ""; (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function* genXYZ () { -for (var a0 = 1; a0 >= 0.5; a0--) { +for (var a0 = 1; a0 > 0; a0--) { yield; } b0.value = "random"; 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 704116bfee2..af096a9106d 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 @@ -26,7 +26,7 @@ retire(); return; const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); return function* genXYZ_no_refresh () { -for (var a0 = 30; a0 >= 0.5; a0--) { +for (var a0 = 30; a0 > 0; a0--) { thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); runtime.requestRedraw(); @@ -53,7 +53,7 @@ return ""; const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); return function* genXYZ_has_refresh () { -for (var a0 = 30; a0 >= 0.5; a0--) { +for (var a0 = 30; a0 > 0; a0--) { thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); runtime.requestRedraw(); diff --git a/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot index edbe98dd2e9..1dc2a0741df 100644 --- a/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot @@ -18,7 +18,7 @@ const b0 = stage.variables["EF^k=u-t@S)w60;RP?dZ-list-list"]; const b1 = stage.variables["rQq!wAuU2T9DV9,S[/s."]; return function funXYZ_run () { b0.value = []; -for (var a0 = 100; a0 >= 0.5; a0--) { +for (var a0 = 100; a0 > 0; a0--) { b0.value.push((b1.value[(b0.value.length + 1) - 1] ?? "")); b0._monitorUpToDate = false; } @@ -36,7 +36,7 @@ const b4 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_validate () { b0.value = 1; b1.value = 1; -for (var a0 = b2.value.length; a0 >= 0.5; a0--) { +for (var a0 = b2.value.length; a0 > 0; a0--) { if (!compareEqual((b2.value[(b1.value | 0) - 1] ?? ""), (b3.value[(b1.value | 0) - 1] ?? ""))) { b0.value = 0; yield* executeInCompatibilityLayer({"MESSAGE":("fail mismatch at index " + ("" + b1.value)),}, b4, true, false, ",", null); 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 e3cf8b7c36e..0f6a49f62b7 100644 --- a/test/snapshot/__snapshots__/tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-preciseProjectTimer-drift-453118719.sb3.tw-snapshot @@ -9,7 +9,7 @@ const b2 = stage.variables["l^q!%fq]Bv;72dlGf}^Z"]; return function* genXYZ () { b0.value = 0; yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b1, false, false, "}fY]VN(X|v/[G`P0?@s2", null); -for (var a0 = 30; a0 >= 0.5; a0--) { +for (var a0 = 30; a0 > 0; a0--) { b2.value = runtime.ioDevices.clock.projectTimer(); thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); 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 index 21e08e6c87b..9159b5c7232 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-stops-scripts.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-stops-scripts.sb3.tw-snapshot @@ -25,7 +25,7 @@ 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--) { +for (var a0 = 100; a0 > 0; a0--) { b0.value = (b0.value + 1); if ((b0.value === 25)) { return "stopped!"; diff --git a/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot index fde5f016583..d6aad6c51fb 100644 --- a/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-procedure-return-warp.sb3.tw-snapshot @@ -38,7 +38,7 @@ 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--) { +for (var a0 = 5; a0 > 0; a0--) { thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); runtime.requestRedraw(); @@ -56,7 +56,7 @@ if ((("" + (yield* thread.procedures["Wverify runs warp %s"]((yield* thread.proc yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp and warp mix",}, b1, false, false, "S", null); } b0.value = 0; -for (var a2 = 5; a2 >= 0.5; a2--) { +for (var a2 = 5; a2 > 0; a2--) { thread.timer = timer(); var a3 = Math.max(0, 1000 * 0); runtime.requestRedraw(); @@ -79,7 +79,7 @@ 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--) { +for (var a0 = 5; a0 > 0; a0--) { thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); runtime.requestRedraw(); @@ -101,7 +101,7 @@ 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--) { +for (var a0 = 5; a0 > 0; a0--) { thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); runtime.requestRedraw(); 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 63559f0dbfb..646fdfd02e4 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 @@ -29,7 +29,7 @@ const b2 = stage.variables["JbF5exWEi*m?-UEmkASS"]; return function* genXYZ () { b0.value = 0; yield* executeInCompatibilityLayer({"MESSAGE":"plan 15",}, b1, false, false, "0/2-)Gp^^=haQ1OMD.xb", null); -for (var a0 = 15; a0 >= 0.5; a0--) { +for (var a0 = 15; a0 > 0; a0--) { if (((+b2.value || 0) === 200)) { b2.value = 50; } else { diff --git a/test/snapshot/__snapshots__/tw-self-restarting-script-keeps-running-until-yield.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-self-restarting-script-keeps-running-until-yield.sb3.tw-snapshot index 2783ca0147a..62e48ec1cae 100644 --- a/test/snapshot/__snapshots__/tw-self-restarting-script-keeps-running-until-yield.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-self-restarting-script-keeps-running-until-yield.sb3.tw-snapshot @@ -24,7 +24,7 @@ if (compareEqual(b0.value, 0)) { b0.value = 1; startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "message1" }); b1.value = 1; -for (var a0 = 1; a0 >= 0.5; a0--) { +for (var a0 = 1; a0 > 0; a0--) { yield; } b1.value = 2; diff --git a/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot index 90a01cba05d..3804c87f6b3 100644 --- a/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-unsafe-equals.sb3.tw-snapshot @@ -24,7 +24,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass 4",}, b0, false, false, "8]2 if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 5",}, b0, false, false, "ZU^{OfKTg|+Au$$q0[]u", null); } -for (var a0 = 1; a0 >= 0.5; a0--) { +for (var a0 = 1; a0 > 0; a0--) { if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 6",}, b0, false, false, "HB+_IN}6=K[*ksxKXH0`", null); } @@ -47,7 +47,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass 10",}, b0, false, false, "B` if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 11",}, b0, false, false, "TJ:#TkYBys*!RYiKLXb)", null); } -for (var a1 = 1; a1 >= 0.5; a1--) { +for (var a1 = 1; a1 > 0; a1--) { if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 12",}, b0, false, false, ",Z,~10Qo~j;(+VL+I3q:", null); } diff --git a/test/snapshot/__snapshots__/warp-timer/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot index c19e45f7049..8536858fee5 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-analyze-yields-due-to-direct-recursion.sb3.tw-snapshot @@ -31,7 +31,7 @@ return ""; (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function* genXYZ () { -for (var a0 = 1; a0 >= 0.5; a0--) { +for (var a0 = 1; a0 > 0; a0--) { yield; } b0.value = "random"; 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 f274502d07a..656fc83d0bc 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 @@ -23,10 +23,10 @@ return function* genXYZ_run_test () { thread.procedures["Wsetup values"](); b0.value = 0; b1.value = 0; -for (var a0 = b2.value.length; a0 >= 0.5; a0--) { +for (var a0 = b2.value.length; a0 > 0; a0--) { b1.value = ((+b1.value || 0) + 1); b3.value = 0; -for (var a1 = b2.value.length; a1 >= 0.5; a1--) { +for (var a1 = b2.value.length; a1 > 0; a1--) { b3.value = ((+b3.value || 0) + 1); b0.value = ((+b0.value || 0) + 1); if (!compareEqual(compareGreaterThan(listGet(b2.value, b1.value), (b2.value[(b3.value | 0) - 1] ?? "")), (b4.value[(b0.value | 0) - 1] ?? ""))) { diff --git a/test/snapshot/__snapshots__/warp-timer/tw-counter.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-counter.sb3.tw-snapshot index a4d1176a8e7..abbfa97a51e 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-counter.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-counter.sb3.tw-snapshot @@ -32,7 +32,7 @@ runtime.ext_scratch3_control._counter = 0; if ((runtime.ext_scratch3_control._counter === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass clear = 0",}, b0, false, false, "w", null); } -for (var a0 = 10; a0 >= 0.5; a0--) { +for (var a0 = 10; a0 > 0; a0--) { runtime.ext_scratch3_control._counter++; yield; } diff --git a/test/snapshot/__snapshots__/warp-timer/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot index c8bf63116b7..7676e37b15c 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-exit-state-accounts-for-yields-in-procedures.sb3.tw-snapshot @@ -20,7 +20,7 @@ retire(); return; // Sprite1 Zsomething that yields (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); return function* genXYZ_something_that_yield () { -for (var a0 = 2; a0 >= 0.5; a0--) { +for (var a0 = 2; a0 > 0; a0--) { yield; } return ""; @@ -30,7 +30,7 @@ return ""; (function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; return function* genXYZ () { -for (var a0 = 1; a0 >= 0.5; a0--) { +for (var a0 = 1; a0 > 0; a0--) { yield; } b0.value = "random"; 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 a3d93933160..7fe8cccf8ce 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 @@ -26,7 +26,7 @@ retire(); return; const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); return function* genXYZ_no_refresh () { -for (var a0 = 30; a0 >= 0.5; a0--) { +for (var a0 = 30; a0 > 0; a0--) { thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); runtime.requestRedraw(); @@ -54,7 +54,7 @@ return ""; const b0 = runtime.getOpcodeFunction("music_restForBeats"); const b1 = runtime.getOpcodeFunction("music_playDrumForBeats"); return function* genXYZ_has_refresh () { -for (var a0 = 30; a0 >= 0.5; a0--) { +for (var a0 = 30; a0 > 0; a0--) { thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); runtime.requestRedraw(); diff --git a/test/snapshot/__snapshots__/warp-timer/tw-gh-249-quicksort.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-gh-249-quicksort.sb3.tw-snapshot index 5f407d57fb6..8b5d4a41ffa 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-gh-249-quicksort.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-gh-249-quicksort.sb3.tw-snapshot @@ -18,7 +18,7 @@ const b0 = stage.variables["EF^k=u-t@S)w60;RP?dZ-list-list"]; const b1 = stage.variables["rQq!wAuU2T9DV9,S[/s."]; return function* genXYZ_run () { b0.value = []; -for (var a0 = 100; a0 >= 0.5; a0--) { +for (var a0 = 100; a0 > 0; a0--) { b0.value.push((b1.value[(b0.value.length + 1) - 1] ?? "")); b0._monitorUpToDate = false; if (isStuck()) yield; @@ -37,7 +37,7 @@ const b4 = runtime.getOpcodeFunction("looks_say"); return function* genXYZ_validate () { b0.value = 1; b1.value = 1; -for (var a0 = b2.value.length; a0 >= 0.5; a0--) { +for (var a0 = b2.value.length; a0 > 0; a0--) { if (!compareEqual(listGet(b2.value, b1.value), listGet(b3.value, b1.value))) { b0.value = 0; yield* executeInCompatibilityLayer({"MESSAGE":("fail mismatch at index " + ("" + b1.value)),}, b4, true, false, ",", null); 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 e3cf8b7c36e..0f6a49f62b7 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 @@ -9,7 +9,7 @@ const b2 = stage.variables["l^q!%fq]Bv;72dlGf}^Z"]; return function* genXYZ () { b0.value = 0; yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b1, false, false, "}fY]VN(X|v/[G`P0?@s2", null); -for (var a0 = 30; a0 >= 0.5; a0--) { +for (var a0 = 30; a0 > 0; a0--) { b2.value = runtime.ioDevices.clock.projectTimer(); thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); 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 index 6f96f52693d..6d536299287 100644 --- 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 @@ -25,7 +25,7 @@ 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--) { +for (var a0 = 100; a0 > 0; a0--) { b0.value = ((+b0.value || 0) + 1); if ((b0.value === 25)) { return "stopped!"; 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 index d7afc882d25..db6051903b0 100644 --- 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 @@ -38,7 +38,7 @@ 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--) { +for (var a0 = 5; a0 > 0; a0--) { thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); runtime.requestRedraw(); @@ -56,7 +56,7 @@ if ((("" + (yield* thread.procedures["Wverify runs warp %s"]((yield* thread.proc yield* executeInCompatibilityLayer({"MESSAGE":"pass non warp and warp mix",}, b1, false, false, "S", null); } b0.value = 0; -for (var a2 = 5; a2 >= 0.5; a2--) { +for (var a2 = 5; a2 > 0; a2--) { thread.timer = timer(); var a3 = Math.max(0, 1000 * 0); runtime.requestRedraw(); @@ -79,7 +79,7 @@ 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--) { +for (var a0 = 5; a0 > 0; a0--) { thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); runtime.requestRedraw(); @@ -102,7 +102,7 @@ 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--) { +for (var a0 = 5; a0 > 0; a0--) { thread.timer = timer(); var a1 = Math.max(0, 1000 * 0); runtime.requestRedraw(); 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 63559f0dbfb..646fdfd02e4 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 @@ -29,7 +29,7 @@ const b2 = stage.variables["JbF5exWEi*m?-UEmkASS"]; return function* genXYZ () { b0.value = 0; yield* executeInCompatibilityLayer({"MESSAGE":"plan 15",}, b1, false, false, "0/2-)Gp^^=haQ1OMD.xb", null); -for (var a0 = 15; a0 >= 0.5; a0--) { +for (var a0 = 15; a0 > 0; a0--) { if (((+b2.value || 0) === 200)) { b2.value = 50; } else { diff --git a/test/snapshot/__snapshots__/warp-timer/tw-self-restarting-script-keeps-running-until-yield.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-self-restarting-script-keeps-running-until-yield.sb3.tw-snapshot index 2783ca0147a..62e48ec1cae 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-self-restarting-script-keeps-running-until-yield.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-self-restarting-script-keeps-running-until-yield.sb3.tw-snapshot @@ -24,7 +24,7 @@ if (compareEqual(b0.value, 0)) { b0.value = 1; startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "message1" }); b1.value = 1; -for (var a0 = 1; a0 >= 0.5; a0--) { +for (var a0 = 1; a0 > 0; a0--) { yield; } b1.value = 2; 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 90a01cba05d..3804c87f6b3 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 @@ -24,7 +24,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass 4",}, b0, false, false, "8]2 if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 5",}, b0, false, false, "ZU^{OfKTg|+Au$$q0[]u", null); } -for (var a0 = 1; a0 >= 0.5; a0--) { +for (var a0 = 1; a0 > 0; a0--) { if (((+b1.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 6",}, b0, false, false, "HB+_IN}6=K[*ksxKXH0`", null); } @@ -47,7 +47,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass 10",}, b0, false, false, "B` if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 11",}, b0, false, false, "TJ:#TkYBys*!RYiKLXb)", null); } -for (var a1 = 1; a1 >= 0.5; a1--) { +for (var a1 = 1; a1 > 0; a1--) { if (((+b2.value || 0) === 10)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 12",}, b0, false, false, ",Z,~10Qo~j;(+VL+I3q:", null); } From a2701efdf5d227f499a3d04505a738adea927eb6 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Sat, 4 Oct 2025 20:36:46 -0500 Subject: [PATCH 42/56] Use more specific type for round, floor, ceiling --- src/compiler/irgen.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 295fe9df998..73bb2f85aca 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -362,8 +362,8 @@ class ScriptTreeGenerator { const operator = block.fields.OPERATOR.value.toLowerCase(); switch (operator) { case 'abs': return new IntermediateInput(InputOpcode.OP_ABS, InputType.NUMBER_POS | InputType.NUMBER_ZERO, {value}); - case 'floor': return new IntermediateInput(InputOpcode.OP_FLOOR, InputType.NUMBER, {value}); - case 'ceiling': return new IntermediateInput(InputOpcode.OP_CEILING, InputType.NUMBER, {value}); + case 'floor': return new IntermediateInput(InputOpcode.OP_FLOOR, InputType.NUMBER_INT | InputType.NUMBER_INF, {value}); + case 'ceiling': return new IntermediateInput(InputOpcode.OP_CEILING, InputType.NUMBER_INT | InputType.NUMBER_INF, {value}); case 'sqrt': return new IntermediateInput(InputOpcode.OP_SQRT, InputType.NUMBER_OR_NAN, {value}); case 'sin': return new IntermediateInput(InputOpcode.OP_SIN, InputType.NUMBER_OR_NAN, {value}); case 'cos': return new IntermediateInput(InputOpcode.OP_COS, InputType.NUMBER_OR_NAN, {value}); @@ -458,7 +458,7 @@ class ScriptTreeGenerator { }); } case 'operator_round': - return new IntermediateInput(InputOpcode.OP_ROUND, InputType.NUMBER, { + return new IntermediateInput(InputOpcode.OP_ROUND, InputType.NUMBER_INT | InputType.NUMBER_INF, { value: this.descendInputOfBlock(block, 'NUM').toType(InputType.NUMBER) }); case 'operator_subtract': From 2a2d15282b5a74a37cd0676ead4290f4f45f47d1 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Sat, 4 Oct 2025 20:37:54 -0500 Subject: [PATCH 43/56] Infinite iteration count can also use simpler condition --- src/compiler/jsgen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index a74c633ec33..65ffe9c42d2 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -610,7 +610,7 @@ class JSGenerator { break; case StackOpcode.CONTROL_REPEAT: { const i = this.localVariables.next(); - if (node.times.isAlwaysType(InputType.NUMBER_INT)) { + if (node.times.isAlwaysType(InputType.NUMBER_INT | InputType.NUMBER_INF)) { this.source += `for (var ${i} = ${this.descendInput(node.times)}; ${i} > 0; ${i}--) {\n`; } else { this.source += `for (var ${i} = ${this.descendInput(node.times)}; ${i} >= 0.5; ${i}--) {\n`; From d848fd0e6ed836efbc1ab4e19746f22f90bec9b2 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Tue, 7 Oct 2025 18:05:38 -0500 Subject: [PATCH 44/56] Convert default extension URLs from dict to object --- .../tw-default-extension-urls.js | 16 ++++++++-------- src/virtual-machine.js | 5 ++++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/extension-support/tw-default-extension-urls.js b/src/extension-support/tw-default-extension-urls.js index 7b05eb80ccd..53ed768c65a 100644 --- a/src/extension-support/tw-default-extension-urls.js +++ b/src/extension-support/tw-default-extension-urls.js @@ -1,15 +1,15 @@ // If a project uses an extension but does not specify a URL, it will default to // the URLs given here, if it exists. This is useful for compatibility with other mods. -const defaults = new Map(); +const defaults = { + // Box2D (`griffpatch`) is not listed here because our extension is not actually + // compatible with the original version due to fields vs inputs. -// Box2D (`griffpatch`) is not listed here because our extension is not actually -// compatible with the original version due to fields vs inputs. + // Scratch Lab Animated Text - https://lab.scratch.mit.edu/text/ + text: 'https://extensions.turbowarp.org/lab/text.js', -// Scratch Lab Animated Text - https://lab.scratch.mit.edu/text/ -defaults.set('text', 'https://extensions.turbowarp.org/lab/text.js'); - -// Turboloader's AudioStream -defaults.set('audiostr', 'https://extensions.turbowarp.org/turboloader/audiostream.js'); + // Turboloader's AudioStream + audiostr: 'https://extensions.turbowarp.org/turboloader/audiostream.js' +}; module.exports = defaults; diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 4a8f55cf613..2cc5d260c73 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -779,7 +779,10 @@ class VirtualMachine extends EventEmitter { this.extensionManager.loadExtensionIdSync(extensionID); } else { // Custom extension - const url = extensionURLs.get(extensionID) || defaultExtensionURLs.get(extensionID); + let url = extensionURLs.get(extensionID); + if (!url && Object.prototype.hasOwnProperty.call(defaultExtensionURLs, extensionID)) { + url = defaultExtensionURLs[extensionID]; + } if (!url) { throw new Error(`Unknown extension: ${extensionID}`); } From 4ab24673c9033c4273e8fadbae5bf79ceee64939 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Tue, 7 Oct 2025 18:06:10 -0500 Subject: [PATCH 45/56] Add face sensing to default extension URLs --- src/extension-support/tw-default-extension-urls.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/extension-support/tw-default-extension-urls.js b/src/extension-support/tw-default-extension-urls.js index 53ed768c65a..de3c3ec77d0 100644 --- a/src/extension-support/tw-default-extension-urls.js +++ b/src/extension-support/tw-default-extension-urls.js @@ -9,7 +9,10 @@ const defaults = { text: 'https://extensions.turbowarp.org/lab/text.js', // Turboloader's AudioStream - audiostr: 'https://extensions.turbowarp.org/turboloader/audiostream.js' + audiostr: 'https://extensions.turbowarp.org/turboloader/audiostream.js', + + // https://scratch.mit.edu/discuss/topic/842592/ + faceSensing: 'https://extensions.turbowarp.org/lab/face-sensing.js' }; module.exports = defaults; From d09f82a2fe16f411e93a85a67db6a1c8dcaea7bb Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Wed, 8 Oct 2025 17:15:24 -0500 Subject: [PATCH 46/56] Add new test case that does some simple string operations --- .../execute/tw-simple-string-operations.sb3 | Bin 0 -> 3341 bytes ...w-simple-string-operations.sb3.tw-snapshot | 36 +++++++++++++++++ ...w-simple-string-operations.sb3.tw-snapshot | 37 ++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 test/fixtures/execute/tw-simple-string-operations.sb3 create mode 100644 test/snapshot/__snapshots__/tw-simple-string-operations.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-simple-string-operations.sb3.tw-snapshot diff --git a/test/fixtures/execute/tw-simple-string-operations.sb3 b/test/fixtures/execute/tw-simple-string-operations.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..0687b08ace3b0b0dcdcbc3819d7a45456d14d858 GIT binary patch literal 3341 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 0.5; a0--) { +if ((((("" + b0.value))[(b2.value | 0) - 1] || "").toLowerCase() === "a".toLowerCase())) { +b1.value = (("" + b1.value) + "b"); +} else { +b1.value = (("" + b1.value) + "a"); +} +b2.value = (b2.value + 1); +} +return ""; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-simple-string-operations.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-simple-string-operations.sb3.tw-snapshot new file mode 100644 index 00000000000..c48c90242a5 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-simple-string-operations.sb3.tw-snapshot @@ -0,0 +1,37 @@ +// TW Snapshot +// Input SHA-256: e6a6f5ea252886949eab2f4b5608526dc3686160d65f6e07c2d864c82db24326 + +// 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["PJl9-2GypE.OstQk*B_*"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "d", null); +yield* thread.procedures["Wa"](); +if ((("" + b1.value).toLowerCase() === "babab".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "s", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "q", null); +retire(); return; +}; }) + +// Sprite1 Wa +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["uEt-Ix{%|r3`}-W%+kdm"]; +const b1 = stage.variables["PJl9-2GypE.OstQk*B_*"]; +const b2 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ_a () { +b0.value = "ababa"; +b1.value = ""; +b2.value = 1; +for (var a0 = ("" + b0.value).length; a0 >= 0.5; a0--) { +if ((((("" + b0.value))[((+b2.value) | 0) - 1] || "").toLowerCase() === "a".toLowerCase())) { +b1.value = (("" + b1.value) + "b"); +} else { +b1.value = (("" + b1.value) + "a"); +} +b2.value = ((+b2.value || 0) + 1); +if (isStuck()) yield; +} +return ""; +}; }) From 07309b6ebfd8c2bc21d52139cb859391d1921f20 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Wed, 8 Oct 2025 17:15:44 -0500 Subject: [PATCH 47/56] Use more specific type for string length --- src/compiler/irgen.js | 2 +- .../__snapshots__/tw-simple-string-operations.sb3.tw-snapshot | 2 +- .../warp-timer/tw-simple-string-operations.sb3.tw-snapshot | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 73bb2f85aca..4175840ed98 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -344,7 +344,7 @@ class ScriptTreeGenerator { right: this.descendInputOfBlock(block, 'STRING2').toType(InputType.STRING) }); case 'operator_length': - return new IntermediateInput(InputOpcode.OP_LENGTH, InputType.NUMBER_REAL, { + return new IntermediateInput(InputOpcode.OP_LENGTH, InputType.NUMBER_POS_INT | InputType.NUMBER_ZERO, { string: this.descendInputOfBlock(block, 'STRING').toType(InputType.STRING) }); case 'operator_letter_of': diff --git a/test/snapshot/__snapshots__/tw-simple-string-operations.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-simple-string-operations.sb3.tw-snapshot index b58442515ef..78cdbd86e7f 100644 --- a/test/snapshot/__snapshots__/tw-simple-string-operations.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-simple-string-operations.sb3.tw-snapshot @@ -24,7 +24,7 @@ return function funXYZ_a () { b0.value = "ababa"; b1.value = ""; b2.value = 1; -for (var a0 = ("" + b0.value).length; a0 >= 0.5; a0--) { +for (var a0 = ("" + b0.value).length; a0 > 0; a0--) { if ((((("" + b0.value))[(b2.value | 0) - 1] || "").toLowerCase() === "a".toLowerCase())) { b1.value = (("" + b1.value) + "b"); } else { diff --git a/test/snapshot/__snapshots__/warp-timer/tw-simple-string-operations.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-simple-string-operations.sb3.tw-snapshot index c48c90242a5..ec187c5fd8e 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-simple-string-operations.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-simple-string-operations.sb3.tw-snapshot @@ -24,7 +24,7 @@ return function* genXYZ_a () { b0.value = "ababa"; b1.value = ""; b2.value = 1; -for (var a0 = ("" + b0.value).length; a0 >= 0.5; a0--) { +for (var a0 = ("" + b0.value).length; a0 > 0; a0--) { if ((((("" + b0.value))[((+b2.value) | 0) - 1] || "").toLowerCase() === "a".toLowerCase())) { b1.value = (("" + b1.value) + "b"); } else { From 55674f82eef8d901affdb2e6061d5127020be489 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Sat, 18 Oct 2025 23:43:22 -0500 Subject: [PATCH 48/56] Implement Scratch.external - VM part (#308) --- .../tw-extension-api-common.js | 4 +- src/extension-support/tw-external.js | 87 +++++++++++++++++++ test/unit/tw_extension_api_common.js | 10 +++ test/unit/tw_external.js | 58 +++++++++++++ 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 src/extension-support/tw-external.js create mode 100644 test/unit/tw_external.js diff --git a/src/extension-support/tw-extension-api-common.js b/src/extension-support/tw-extension-api-common.js index 1f5928fbc0f..82e706de98f 100644 --- a/src/extension-support/tw-extension-api-common.js +++ b/src/extension-support/tw-extension-api-common.js @@ -3,13 +3,15 @@ const BlockType = require('./block-type'); const BlockShape = require('./tw-block-shape'); const TargetType = require('./target-type'); const Cast = require('../util/cast'); +const external = require('./tw-external'); const Scratch = { ArgumentType, BlockType, BlockShape, TargetType, - Cast + Cast, + external }; module.exports = Scratch; diff --git a/src/extension-support/tw-external.js b/src/extension-support/tw-external.js new file mode 100644 index 00000000000..de7b3a9ad47 --- /dev/null +++ b/src/extension-support/tw-external.js @@ -0,0 +1,87 @@ +/** + * @param {string} url + * @returns {void} if URL is supported + * @throws if URL is unsupported + */ +const checkURL = url => { + // URL might be a very long data: URL, so try to avoid fully parsing it if we can. + // The notable requirement here is that the URL must be an absolute URL, not something + // relative to where the extension is loaded from or where the extension is running. + // This ensures that the same extension file will always load resources from the same + // place, regardless of how it is running or packaged or whatever else. + if ( + !url.startsWith('http:') && + !url.startsWith('https:') && + !url.startsWith('data:') && + !url.startsWith('blob:') + ) { + throw new Error(`Unsupported URL: ${url}`); + } +}; + +const external = {}; + +/** + * @param {string} url + * @template T + * @returns {Promise} + */ +external.importModule = url => { + checkURL(url); + // Need to specify webpackIgnore so that webpack compiles this directly to a call to import() + // instead of trying making it try to use the webpack import system. + return import(/* webpackIgnore: true */ url); +}; + +/** + * @param {string} url + * @returns {Promise} + */ +external.fetch = async url => { + checkURL(url); + const res = await fetch(url); + if (!res.ok) { + throw new Error(`HTTP ${res.status} fetching ${url}`); + } + return res; +}; + +/** + * @param {string} url + * @returns {Promise} + */ +external.dataURL = async url => { + const res = await external.fetch(url); + const blob = await res.blob(); + return new Promise((resolve, reject) => { + const fr = new FileReader(); + fr.onload = () => resolve(fr.result); + fr.onerror = () => reject(fr.error); + fr.readAsDataURL(blob); + }); +}; + +/** + * @param {string} url + * @returns {Promise} + */ +external.blob = async url => { + const res = await external.fetch(url); + return res.blob(); +}; + +/** + * @param {string} url + * @param {string} returnExpression + * @template T + * @returns {Promise} + */ +external.evalAndReturn = async (url, returnExpression) => { + const res = await external.fetch(url); + const text = await res.text(); + const js = `${text};return ${returnExpression}`; + const fn = new Function(js); + return fn(); +}; + +module.exports = external; diff --git a/test/unit/tw_extension_api_common.js b/test/unit/tw_extension_api_common.js index 051fcf134fc..cbb1d6bbfa5 100644 --- a/test/unit/tw_extension_api_common.js +++ b/test/unit/tw_extension_api_common.js @@ -32,3 +32,13 @@ test('Cast', t => { t.equal(ScratchCommon.Cast.toListIndex('1.5', 10, false), 1); t.end(); }); + +test('external', t => { + // has more tests in separate file, mostly just making sure that external exists at all + ScratchCommon.external.fetch('data:text/plain;,test').then(r => { + r.text().then(text => { + t.equal(text, 'test'); + t.end(); + }); + }); +}); diff --git a/test/unit/tw_external.js b/test/unit/tw_external.js new file mode 100644 index 00000000000..745c694bcdc --- /dev/null +++ b/test/unit/tw_external.js @@ -0,0 +1,58 @@ +const external = require('../../src/extension-support/tw-external'); +const {test} = require('tap'); + +test('importModule', t => { + external.importModule('data:text/javascript;,export%20default%201').then(mod => { + t.equal(mod.default, 1); + t.end(); + }); +}); + +test('fetch', t => { + external.fetch('data:text/plain;,test').then(res => { + res.text().then(text => { + t.equal(text, 'test'); + t.end(); + }); + }); +}); + +test('dataURL', t => { + global.FileReader = class { + readAsDataURL (blob) { + blob.arrayBuffer().then(arrayBuffer => { + const base64 = Buffer.from(arrayBuffer).toString('base64'); + this.result = `data:${blob.type};base64,${base64}`; + this.onload(); + }); + } + }; + + external.dataURL('data:text/plain;,doesthiswork').then(dataURL => { + t.equal(dataURL, `data:text/plain;base64,${btoa('doesthiswork')}`); + t.end(); + }); +}); + +test('blob', t => { + external.blob('data:text/plain;,test').then(blob => { + blob.text().then(blobText => { + t.equal(blobText, 'test'); + t.end(); + }); + }); +}); + +test('evalAndReturn', t => { + external.evalAndReturn('data:text/plain;,var%20x=20', 'x').then(result => { + t.equal(result, 20); + t.end(); + }); +}); + +test('relative URL throws', t => { + external.fetch('./test.js').catch(err => { + t.equal(err.message, `Unsupported URL: ./test.js`); + t.end(); + }); +}); From 475ffd0cb4f2f614172caec02405d5356c0f678e Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Sat, 1 Nov 2025 19:13:23 -0500 Subject: [PATCH 49/56] Update scratch-translate-extension-languages to 1.0.7 --- package-lock.json | 9 +++++---- package.json | 2 +- test/unit/extension_text_to_speech.js | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1bdcb228867..c64871a087e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "immutable": "3.8.2", "scratch-parser": "github:TurboWarp/scratch-parser#master", "scratch-sb1-converter": "0.2.7", - "scratch-translate-extension-languages": "0.0.20191118205314", + "scratch-translate-extension-languages": "^1.0.7", "text-encoding": "0.7.0", "uuid": "8.3.2", "worker-loader": "^1.1.1" @@ -13782,9 +13782,10 @@ } }, "node_modules/scratch-translate-extension-languages": { - "version": "0.0.20191118205314", - "resolved": "https://registry.npmjs.org/scratch-translate-extension-languages/-/scratch-translate-extension-languages-0.0.20191118205314.tgz", - "integrity": "sha512-r0lvpgQjPMjbhI2wROrgoXzBhCvWJdkbAqJMfl2CdNqrwBpUXqLvNNtI3VtNLPJAcp9VfxEylkU9lVZ0gvU46Q==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/scratch-translate-extension-languages/-/scratch-translate-extension-languages-1.0.7.tgz", + "integrity": "sha512-6+bQU9iVYv23T8J0SjpV6MTugm0y8myh/4DPgu1BGfccysdkaWzu3MkNGQyQRUlbqAiW9wM7ctfv3USPEkzTgg==", + "license": "BSD-3-Clause" }, "node_modules/script-loader": { "version": "0.7.2", diff --git a/package.json b/package.json index 2c841260adb..c126de34260 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "immutable": "3.8.2", "scratch-parser": "github:TurboWarp/scratch-parser#master", "scratch-sb1-converter": "0.2.7", - "scratch-translate-extension-languages": "0.0.20191118205314", + "scratch-translate-extension-languages": "^1.0.7", "text-encoding": "0.7.0", "uuid": "8.3.2", "worker-loader": "^1.1.1" diff --git a/test/unit/extension_text_to_speech.js b/test/unit/extension_text_to_speech.js index a6fd9dbfa61..83c065493c1 100644 --- a/test/unit/extension_text_to_speech.js +++ b/test/unit/extension_text_to_speech.js @@ -39,6 +39,6 @@ test('use localized spoken language name in place of localized written language ext.getEditorLanguage = () => 'es'; const languageMenu = ext.getLanguageMenu(); const localizedNameForChineseInSpanish = languageMenu.find(el => el.value === 'zh-cn').text; - t.strictEqual(localizedNameForChineseInSpanish, 'Chino (Mandarín)'); // i.e. should not be 'Chino (simplificado)' + t.strictEqual(localizedNameForChineseInSpanish, 'Chino (mandarín)'); // i.e. should not be 'Chino (simplificado)' t.end(); }); From 4187f550a086c31436dcad94f705671df9d5cb12 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Sat, 15 Nov 2025 19:00:09 -0600 Subject: [PATCH 50/56] Record which keys the project has used Related to https://github.com/TurboWarp/scratch-gui/issues/1106 --- src/io/keyboard.js | 13 +++++++++++++ test/unit/io_keyboard_tw.js | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/io/keyboard.js b/src/io/keyboard.js index ed6605feaf8..e2d2f0e93e3 100644 --- a/src/io/keyboard.js +++ b/src/io/keyboard.js @@ -53,6 +53,10 @@ class Keyboard { // tw: track last pressed key this.lastKeyPressed = ''; this._numeralKeyCodesToStringKey = new Map(); + /** + * Set of Scratch keys used by the project. + */ + this._usedKeys = new Set(); } /** @@ -198,6 +202,7 @@ class Keyboard { return this._keysPressed.length > 0; } const scratchKey = this._keyArgToScratchKey(keyArg); + this._usedKeys.add(scratchKey); return this._keysPressed.indexOf(scratchKey) > -1; } @@ -205,6 +210,14 @@ class Keyboard { getLastKeyPressed () { return this.lastKeyPressed; } + + /** + * @param {string} scratchKey Scratch key + * @returns {boolean} true if the project has used this key + */ + hasUsedKey (scratchKey) { + return this._usedKeys.has(scratchKey); + } } module.exports = Keyboard; diff --git a/test/unit/io_keyboard_tw.js b/test/unit/io_keyboard_tw.js index a365d11c5dc..4e76e3e9206 100644 --- a/test/unit/io_keyboard_tw.js +++ b/test/unit/io_keyboard_tw.js @@ -110,3 +110,21 @@ test('holding shift and key, releasing shift, waiting, then releasing key', t => t.end(); }); + +test('hasUsedKey', t => { + const rt = new Runtime(); + const k = new Keyboard(rt); + + t.equal(k.hasUsedKey('backspace'), false); + t.equal(k.hasUsedKey('delete'), false); + + k.getKeyIsDown('backspace'); + t.equal(k.hasUsedKey('backspace'), true); + t.equal(k.hasUsedKey('delete'), false); + + k.getKeyIsDown('delete'); + t.equal(k.hasUsedKey('backspace'), true); + t.equal(k.hasUsedKey('delete'), true); + + t.end(); +}); From 30aaa6fbef43e2323fa241be33cb59be5fe93510 Mon Sep 17 00:00:00 2001 From: CST1229 <68464103+CST1229@users.noreply.github.com> Date: Fri, 21 Nov 2025 20:16:08 +0100 Subject: [PATCH 51/56] fix comments_reporter --- src/compiler/irgen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index de94ada5d1e..e75ee93b1e0 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -682,7 +682,7 @@ class ScriptTreeGenerator { case 'comments_reporter': return new IntermediateInput(InputOpcode.COMMENTS_REPORTER, InputType.ANY, { - value: this.descendInputOfBlock(block, 'VALUE').toType(InputType.ANY), + value: this.descendInputOfBlock(block, 'VALUE'), comment: this.descendInputOfBlock(block, 'COMMENT').toType(InputType.STRING), }); case 'comments_boolean': From 6da1c1764fae2d852388cec36e515417b2354ddc Mon Sep 17 00:00:00 2001 From: CST1229 <68464103+CST1229@users.noreply.github.com> Date: Fri, 21 Nov 2025 20:28:36 +0100 Subject: [PATCH 52/56] fix json_index_of_value (and a double evaluation in it) --- src/compiler/irgen.js | 4 ++-- src/compiler/jsexecute.js | 12 ++++++++++++ src/compiler/jsgen.js | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index e75ee93b1e0..78f0a8e61d3 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -357,8 +357,8 @@ class ScriptTreeGenerator { array: this.descendInputOfBlock(block, 'ARR').toType(InputType.ARRAY) }); case 'json_index_of_value': - return new IntermediateInput(InputOpcode.JSON_INDEX_OF_VALUE, InputType.NUMBER, { - value: this.descendInputOfBlock(block, 'VALUE').toType(InputType.ANY), + return new IntermediateInput(InputOpcode.JSON_INDEX_OF_VALUE, InputType.NUMBER_WHOLE | InputType.STRING_NAN, { + value: this.descendInputOfBlock(block, 'VALUE'), array: this.descendInputOfBlock(block, 'ARR').toType(InputType.ARRAY) }); case 'json_add_item': diff --git a/src/compiler/jsexecute.js b/src/compiler/jsexecute.js index 3253e5f73f7..6dee9b2548c 100644 --- a/src/compiler/jsexecute.js +++ b/src/compiler/jsexecute.js @@ -40,6 +40,18 @@ runtimeFunctions.mergeObjects = `const mergeObjects = (a, b) => { return Object.fromEntries(Object.entries(a).concat(Object.entries(b))); };`; +/** + * Find the 0-indexed index of an item in an array. + * @param {Array<*>} array The array. + * @param {*} item The item to search for + * @returns {number | string} The 0-indexed index of the item in the list, otherwise empty string + */ +runtimeFunctions.arrayIndexOf = `const arrayIndexOf = (array, item) => { + const index = array.indexOf(item); + if (index === -1) return ""; + return index; +}`; + /** * Determine whether the current tick is likely stuck. * This implements similar functionality to the warp timer found in Scratch. diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index 6f15620fe56..f89c73f712e 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -287,7 +287,7 @@ class JSGenerator { case InputOpcode.JSON_VALUE_OF_INDEX: return `(${this.descendInput(node.array)}[${this.descendInput(node.index)}] ?? "")`; case InputOpcode.JSON_INDEX_OF_VALUE: - return `(${this.descendInput(node.array)}.indexOf(${this.descendInput(node.value)}) !== -1 ? ${this.descendInput(node.array)}.indexOf(${this.descendInput(node.value)}) : "")`; + return `arrayIndexOf(${this.descendInput(node.array)}, ${this.descendInput(node.value)})`; case InputOpcode.JSON_ADD_ITEM: return `(array = ${this.descendInput(node.array)}.slice(0), array.push(${this.descendInput(node.item)}), array)`; case InputOpcode.JSON_REPLACE_INDEX: From c65a3854e0b98691ed057e6ddab05c67c3b0e4fc Mon Sep 17 00:00:00 2001 From: Cubester Date: Fri, 21 Nov 2025 17:31:00 -0500 Subject: [PATCH 53/56] More double descent fixes and lint fixes --- src/compiler/irgen.js | 8 ++++---- src/compiler/jsexecute.js | 31 +++++++++++++++++++++++++++++++ src/compiler/jsgen.js | 6 +++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 78f0a8e61d3..4b9bc5a8c5b 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -683,22 +683,22 @@ class ScriptTreeGenerator { case 'comments_reporter': return new IntermediateInput(InputOpcode.COMMENTS_REPORTER, InputType.ANY, { value: this.descendInputOfBlock(block, 'VALUE'), - comment: this.descendInputOfBlock(block, 'COMMENT').toType(InputType.STRING), + comment: this.descendInputOfBlock(block, 'COMMENT').toType(InputType.STRING) }); case 'comments_boolean': return new IntermediateInput(InputOpcode.COMMENTS_BOOLEAN, InputType.BOOLEAN, { value: this.descendInputOfBlock(block, 'VALUE').toType(InputType.BOOLEAN), - comment: this.descendInputOfBlock(block, 'COMMENT').toType(InputType.STRING), + comment: this.descendInputOfBlock(block, 'COMMENT').toType(InputType.STRING) }); case 'comments_object': return new IntermediateInput(InputOpcode.COMMENTS_OBJECT, InputType.OBJECT, { value: this.descendInputOfBlock(block, 'VALUE').toType(InputType.OBJECT), - comment: this.descendInputOfBlock(block, 'COMMENT').toType(InputType.STRING), + comment: this.descendInputOfBlock(block, 'COMMENT').toType(InputType.STRING) }); case 'comments_array': return new IntermediateInput(InputOpcode.COMMENTS_ARRAY, InputType.ARRAY, { value: this.descendInputOfBlock(block, 'VALUE').toType(InputType.ARRAY), - comment: this.descendInputOfBlock(block, 'COMMENT').toType(InputType.STRING), + comment: this.descendInputOfBlock(block, 'COMMENT').toType(InputType.STRING) }); case 'tw_getLastKeyPressed': diff --git a/src/compiler/jsexecute.js b/src/compiler/jsexecute.js index 6dee9b2548c..90284bff92c 100644 --- a/src/compiler/jsexecute.js +++ b/src/compiler/jsexecute.js @@ -52,6 +52,37 @@ runtimeFunctions.arrayIndexOf = `const arrayIndexOf = (array, item) => { return index; }`; +/** + * Replaces an item at the defined index in an array + * @param {Array<*>} array The array + * @param {number} index The index of the item to replace + * @param {*} item The replacement item + * @returns {Array<*>} Updated array with the replaced item + */ +runtimeFunctions.arrayReplaceAtIndex = `const arrayReplaceAtIndex = (array, index, item) => { + if (index >= 0 && index < array.length) { + const newArray = [...array]; + newArray[index] = item; + return newArray; + } + return new Array(); +}`; + +/** + * Deletes an item at the defined index in an array + * @param {Array<*>} array The array + * @param {number} index The index of the item to delete + * @returns {Array<*>} Updated array after deleting the item + */ +runtimeFunctions.arrayDeleteAtIndex = `const arrayDeleteAtIndex = (array, index) => { + if (index >= 0 && index < array.length) { + const newArray = [...array]; + newArray.splice(index, 1); + return newArray; + } + return new Array(); +}`; + /** * Determine whether the current tick is likely stuck. * This implements similar functionality to the warp timer found in Scratch. diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index f89c73f712e..d3014ffd587 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -18,7 +18,7 @@ const { IntermediateScript, IntermediateRepresentation } = require('./intermediate'); -const { Stack } = require('immutable'); +const {Stack} = require('immutable'); /* eslint-enable no-unused-vars */ /** @@ -291,9 +291,9 @@ class JSGenerator { case InputOpcode.JSON_ADD_ITEM: return `(array = ${this.descendInput(node.array)}.slice(0), array.push(${this.descendInput(node.item)}), array)`; case InputOpcode.JSON_REPLACE_INDEX: - return `(${this.descendInput(node.index)} >= 0 && ${this.descendInput(node.index)} < ${this.descendInput(node.array)}.length ? (array = [...${this.descendInput(node.array)}], array[${this.descendInput(node.index)}] = ${this.descendInput(node.item)}, array) : new Array())`; + return `arrayReplaceAtIndex(${this.descendInput(node.array)}, ${this.descendInput(node.index)}, ${this.descendInput(node.item)})`; case InputOpcode.JSON_DELETE_INDEX: - return `(${this.descendInput(node.index)} >= 0 && ${this.descendInput(node.index)} < ${this.descendInput(node.array)}.length ? (array = [...${this.descendInput(node.array)}], array.splice(${this.descendInput(node.index)}, 1), array) : new Array())`; + return `arrayDeleteAtIndex(${this.descendInput(node.array)}, ${this.descendInput(node.index)})`; case InputOpcode.JSON_DELETE_ALL_OCCURRENCES: return `${this.descendInput(node.array)}.filter((item) => item !== ${this.descendInput(node.item)})`; case InputOpcode.JSON_MERGE_ARRAY: From 5fdf9f262107e81a7ae583e762cc1e78bfb0924d Mon Sep 17 00:00:00 2001 From: Cubester Date: Tue, 2 Dec 2025 10:32:18 -0500 Subject: [PATCH 54/56] Jwklong made me do it --- src/compiler/enums.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/enums.js b/src/compiler/enums.js index 148c3c2dac6..61683693920 100644 --- a/src/compiler/enums.js +++ b/src/compiler/enums.js @@ -84,17 +84,17 @@ const InputType = { /** Any input that can be interperated as a boolean. Equal to BOOLEAN | STRING_BOOLEAN */ BOOLEAN_INTERPRETABLE: 0x1800, - /** Any value type (a type a scratch variable can hold). Equal to NUMBER_OR_NAN | STRING | BOOLEAN */ - ANY: 0x1FFF, - - /** An array of values in the form [R, G, B] */ - COLOR: 0x2000, - /** Any object. */ - OBJECT: 0x4000, + OBJECT: 0x2000, /** Any array. */ - ARRAY: 0x8000 + ARRAY: 0x4000, + + /** Any value type (a type a scratch variable can hold). Equal to NUMBER_OR_NAN | STRING | BOOLEAN | OBJECT | ARRAY */ + ANY: 0x7FFF, + + /** An array of values in the form [R, G, B] */ + COLOR: 0x8000 }; /** From bfc39740755600a593703122d59b082e15c2c2d1 Mon Sep 17 00:00:00 2001 From: LordCat Date: Tue, 2 Dec 2025 22:43:49 -0500 Subject: [PATCH 55/56] Fix object and array inputs on custom blocks --- src/compiler/irgen.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 4b9bc5a8c5b..f4f188af8ca 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -150,6 +150,14 @@ class ScriptTreeGenerator { createConstantInput (constant, preserveStrings = false) { if (constant === null) throw new Error('IR: Constant cannot have a null value.'); + if (typeof constant === 'object') { + if (Array.isArray(constant)) { + return new IntermediateInput(InputOpcode.CONSTANT, InputType.ARRAY, {value: constant}); + } else { + return new IntermediateInput(InputOpcode.CONSTANT, InputType.OBJECT, {value: constant}); + } + } + constant += ''; const numConstant = +constant; const preserve = preserveStrings && this.namesOfCostumesAndSounds.has(constant); From c7929d3ec26da35ec64aaa49226384c7d057d819 Mon Sep 17 00:00:00 2001 From: Cubester Date: Tue, 2 Dec 2025 22:58:25 -0500 Subject: [PATCH 56/56] Make lint happy --- src/compiler/enums.js | 5 ++++- src/compiler/irgen.js | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/compiler/enums.js b/src/compiler/enums.js index 61683693920..cec778adb4e 100644 --- a/src/compiler/enums.js +++ b/src/compiler/enums.js @@ -90,7 +90,10 @@ const InputType = { /** Any array. */ ARRAY: 0x4000, - /** Any value type (a type a scratch variable can hold). Equal to NUMBER_OR_NAN | STRING | BOOLEAN | OBJECT | ARRAY */ + /** + * Any value type (a type a scratch variable can hold). + * Equal to NUMBER_OR_NAN | STRING | BOOLEAN | OBJECT | ARRAY + */ ANY: 0x7FFF, /** An array of values in the form [R, G, B] */ diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index f4f188af8ca..5570099c242 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -153,9 +153,8 @@ class ScriptTreeGenerator { if (typeof constant === 'object') { if (Array.isArray(constant)) { return new IntermediateInput(InputOpcode.CONSTANT, InputType.ARRAY, {value: constant}); - } else { - return new IntermediateInput(InputOpcode.CONSTANT, InputType.OBJECT, {value: constant}); } + return new IntermediateInput(InputOpcode.CONSTANT, InputType.OBJECT, {value: constant}); } constant += '';