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/13] 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/13] 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/13] 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/13] 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 zcma)Abxa)Yvc-!##buG=vaon@Til_vK#NPeEbi{UWhw3srC4F1xD^V;i+d?<#fvTM z>&^S)-up{l^5)BA@=bD1a{ic;%;;%iV&S8qq2Z#@P)xqK@jTK0O@xLf3PnRB{M&VP zhdSHYcnUasKwVZX2uzp2^pCur{?BLmS@AYl1O7C{E-m4EcQ!41SxB>EDto*Yu5?^n ze;l*L&=Lw$SMGt5e0!Fv?z?s$HGG8GGkMH%6n1cnIoU};D2;2>u&iYEMj@2}Q!E`@ zd~()mMBer{=8YS|=fU!}te|_XJGjNNb-6NV43hmY>Pqg%)Cph<`xKahopfivd6gdM z^(f?P$7qr)ZHEBnvZgwOC`Vgi3ZJbW@B4e977R7Lt?E2NTphZDL~~!DEg}b42nbh8 zLYarGMi52ieP^v!)$_d#&Nr$vd zcnz74%rrlBk+Q}0XX<-GCuu5b#-kCRF8H*xdCsN9)@+=Y3pT_0g9o2IRl6qkcYV(a zL%^4}HnCpIA1QK&9te!=IOHVn7>&v_g5@Q)7iBa{7KYD~AUDeDe(n+I_d_!7qFZ-H z*0xe&mFYtpNwt9cnW*$#`94Af$;W{Wp%meA!g_xBlh*!F2_s;#e1>!a%bJI3Gf2j3hP^*VF(K;PQ$k++wtrP-7^1{e%w% zg2|h|aV;_6%k$e}VFcDN<{y(Sf)_pFTqQ^Z1bp9Y-JK~wc3oj2sf*9NfJsatp<42Q zn=dCU9~%X_JoVMCqv#7yPWPaSdpQW4Cy&hUQBQ0q8I2mICRW%oXn3YtQycSAr(ZsZ zWN{hRTEw~s$2PIWH_=b*s+ZE@&wh4OMbyxr^Of4w=Qa4-C%ovlnQ0l;U&pIfD~Jh_ z)|L?_n1KZ|%rDB>+>oB%oUooz;|5FSpaDvJ;7d!t=Q}qUHL6v5+E_Fp*|^nyo^z!o zUE>W3WaN16K)S3$nyH1?gh`WMqJmDyQnb`Xq^{1B6Y5d*SjsIw$b!0QiZQRxjHbL* zf}+QHp$_pAr=HV5Jyoc~b2@aVLDCH_=V3+_?FIcN-%qPf$*kbir`x=q`Yx{r3`zTs zwUxW9c}9HTje%;J+ko}*I`AHGJ3o1E{agAB|9kkkb7JSX%{rb{9kZwpi4I9}c4Acf zPcqY@@37(C!t~tvc0he|gn2CFn*8A?1UW)Q4kz9Ssa-kV-i^&3^tg z5(K~nBo{k=V8@<&CqlgKg1h~ArXNwHM-$5@Awx$r@SrTL=n%!Z(xYnEqv3v zRwIy&cN~^Q`N^KOi8J>wN+ave8hrVV!YXfWJ=M0)YA~PW!Kp$s%;y!_->Ld~|_G_%qv3CZ{E%;=YdW95%FGt7z zDMkhpHV7>`oK)qB!om5#Y090q@r`P#?N9M2|~|L~lue2pLm)g;tQUhi<;LAfWnNq#c7dSyG@+Wy~I#Waau(Nnl@IzPGsh?AB(K%>t;>=@P*?m3i#N zX7tc-4|(R(t=7lgIL6j&3BQg?e!r%jS%=W-q_}zqz#a|n0T`<*fDsMcM}msK`^<~h zGC8#R$aED&ROPA!aNA_s=-Ay6v6k3R_xSyw z>>V(-RxePNs=*TGfEq^Dh&M+G(ZaNthOR#NQWo{VN`9EdS4K{&l+|gDwHRJjbmOP{ zBQZ4kChbaBipLL9P)@iXS0nkf$vP=MULS4YI}+TFLOX2f8g4zIz}rjl4XLZ>Z;)BM!MrtbKv&BS?JRBv8ozW?>=| zK3YL?BV|$iOH18o$$zA|@Z$k3$-@yqGwHkT9DJAau-Gj)q6JoCH^GH9jZ#`&E)+0?I<tAstlzt1| z$A}>3L*vkWKM+(4=P_KQe5yJ`hcj>pTgm(Cpw$528rs znAqxvHsM`^g9ASP;bRTD)BZALym{Mo7Fe}mzJ_+I^x>1wbK}{J^!aWIjtd#x9cDp5 zU5MbPOzG#46BBdbhK`iO`6Gu!>iRU*<3=c@0Mct@8c}JDa&T9(%#(Yblc>6R&nDS* z!T#XPY1ZAAU;LV%Dgl2Ppj>rBaDC{8<{eG( zkhd9aR@w8@f|NNvXgW0Aqhq;H~5sp+dgYOxO9-pljb*ZH9dr0A&# z_pj1FEaT55cNq6|-qlv)fw;&A6NXHyE*?X;m#F%9_j@>E%MzfD!ITik@iU)U*pxLe zPIH!#PN^hnQ${yNnWQF4(4itGp)y!q95EUAMIt9JkaPy1P0@@FO{_zAR)%9f>$totoGu3^)y3osygs z#XTO@%E8~NTgN)}F)F`$R&|h2=3WaoZPrYKRO!)rui?YthTpfpdl%(qG^Yty)p-*6 z?Bfh>n_KFaes4D51G@tW+Q28Cc;Ytyj~_L8#(&!_$58WeX{q5}cVI4S0DrTZ^i& z+2S9Gj1>vX?gYgK?LV9$)l@YG`sW7KmGZ{i;A8h{tS^ie4r58nZZGv&#d1w3Hy+A4 z+H#sI?&(8ecObvlk-?q*avkc^7Nx>%E&Ejj(u~beUzSWjJjF^y3A-~L?VbT)XfY6? z$>}7uS*x>1;Z*C%%B%AtHIgv$FfFC~yaqR$VpjDhVWrD``>RG1^u{)N$IHx0Oyg0Q zm*9laB4@|Y89U3k2)K4r%rl`ODsPG^Ul!^KHN7^X-Uy9*%wPWS6+Dth5Eo?(aI-hh zB4{;u>0TDE3oSag4UZ$HFjQebMDTwc$W48ML^N(H6=3`pW^2M{{5EO44sNdfNyHAD zXUsJw5{77x7qF6?5EUqn&wrPGdrK$t{(|wB!KdcVE+B9`WWjolo8ksmTSK+?LEI)2 zI=6KWJa@qphoUNa>fRXnBcG(Uy!J5EDOU9uzwu+8> zKv;L4a?OLH%BgqreUNK_n^=YtGkVq6pv+#ZKwXxaD$kqq3ueFKHqz?Vm2p=IQ#ogl z^Vn)d;1i-5z}G~F;N(UVcG&nK6TP)00X%j&wY#`ygJuVYt^5NXa#YrV)8 zH-jM+Sd^Q~#0-)f-F-Ep3+G4SmA2THkH*nFDs>f$pHn7&9~rc~NW6=uN)3Ywqjt#) zLF0L-s)E$ZHOkNVrX6qcl6I4+dj*9M{gMNO(b>eQv3hThY8;hMiyjKFB30=6M{iXC z5k*X_Wy*fAM_Y77V-;dq;x$DA(p#@VL(g<0=^csXz)c^|K(<23wDk{6`AL6L7>Th6 zG}iQFWgAQAq<1oURhKa6KdZMOql6wd{3yiK2%m+#h$vvT68uP_w%~O~g+yW5gXDZK zq|-?4$6+~2g9N!)U!3@em}lQ_B#f@RQ>$&W*97mgsa-sDE5(3n<2)|Om^S*`qFm;m z9V}iY5#b~}Jy$k;KzT*SXf2g|3Re^;7)iZD4YG6I<{lOF5Fe)e=n6%5TlRjiz>FAQ zahiZEuJ6H<`1V?HX!os;d}nL;wdy7Z7$ldE--Y$QIhE7TO zV=D|41%hmaL`8u1w)Xbc!otErAUh#D8&Nx336Pb5hqnVnTa!3~g>oeze!k_&*IH0t zvAXUgMUzIP(Lhi23~hU07L@dN#}R%nXoP zUlguAGA*GNmG0;i3FbcY+>s?L<{2?~*RNJOT#{3O3jbXDwxPX}AoR~y8uN=^m|09r z&@WyaI(gVQYT}PrvXZ~bL-5{cQ6FDxhM@hosGe(Y5Fgak+kXe|n|vH|@SDQ+$HFHtqd+OTCY(#~tsdLF$dH1#u0@>YxO_vVI$G3Nds zIF{^LHEX|GoRsf{oajB>_nqGda0gEAosD&5ZTNc1$^D)z$+H(H_8R1#n4B(R-jP}k z2)SnVNeVfBf+J;IYy|g;9-&%5$KV1Pe9+NIG>--T4_T(xBDlt7G zbsmQOpqxv|F$`qz;tro0M*D!6wN5rDN;&@X){;*1+3%_F5i5qBvh+u-f{QT^;uT8g z7~~A$g?BEoS9502=CNR84*9vgqD`M2nKRkWd|C8B)!RU&pjNpZP=OElO&{ zHc$AO3Z=EXO-Qbo>ATe|@*V_0CC0;=QBitDuc@GTB`+QAgp?wuKXVaXzSkcffwQqz z3Td5&(ir2dQQ#IC7g5Q+I(^%#`$1T~I!qvzFeM%zs71*SGVBN%u#}{JV`n|biEv~; z8A>{#E~>Q7Ara2ua%{L)L98YpX{x(V#Mkh-rD8FWtB7Jtd*7mITjRhrFf z)O)R-^>S?BSqT-xJ&1nC_c9 zuQk)VY>#C}6?NDO(X0r)q$%>MEUt|I+*1_Cu`9&eQHaf}3{Ez*_+Z?_PNm_74%+LY zoUBcQ*^Ko!W|ydXSqb*C zFZdaqFL$r>c^<5T_=Zg9zEMfLmZr4KsTpWmuQ$so+gTowaP9bqpq(Nf+zL+8VWz$- zF}k-oA?SLV=opk}|M!9E@AUmg{O7sppU8h&(7$0c#Q#4-`X}Q*=l|b~$A20BKW?C> 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/13] 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/13] 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/13] 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 zcmV-!5RvcGCYUOJP)h>@3IG5A2mq-%*;qB-3Z%0S001U{000aC003}uZ)#;@bS`Rh zZ*JXP33r-G8~!Vno}4DV#t33G-g8fDwl-}vty#LHxpst6X%H-oS?m4n-z=zu0uID2 zUymo1VHltHop)y#9wh{v7DTR8x_p!j_Y{E^luF3y!L($5fe!vIv9`2y0~`Z1Oo;nF zN(uivZ}?>5R8na$39YS0OfM=nUoQd+r6OO?IN%F8X~*X3jXg>_u4 zwF+8g&_%&xzlWx2OSt2k+q3o3Va?UH?c71@;poo(>&2jBPD*F5`f15DTmtwJe{DE6 z&^0hRHf`;H+WW5Aoes1HXSAN<;G;d;MLj}RmUN)s8SNgi4WM1?j@`w$B^@AeXgeK% z@Co;(g`0ruLUg3#V@+4&ysYZkd`@oa&1OSUl&lJ~P|HKTr~=%%qvzor66l*@gF7|? z$gr(Fj1xLvrIMm%$6LQiCApaGV@|nt&(aBY{Qbs%HYrhZ7GGe3d+3}vI&>(N2zI)* zRNBU0ZwwvUXAZF3F2Kyt?xj+G(2Qpp5eC?nM6S6*XZC3qLaluatX7Xoh1E07ewq`L zCNN!2PP&ePpgblig7YN-i~O~_nyr(;;0-3Xe%ppt(*!NeG_-4|H&4;2qGAd7pZK4%SpGbf@RLr)TB8 zkGP{kxOOGV`2o_FDAIN~pU07=2vo~PDb2QS@K{h!Av`J$1V^1 zAxb<_3zU!DFs`%zM(}~n<1Yu|&td#ZItD^U?>M#P{fmRzE}_-JU>zw)~)ndunPxVsn{fq4t=-3cO@kfsb`=DQ9=bg&picasslw-};5^YPTF zZHw9rgBRXbgw1e`PS=DfqnUDv3nU>!fT7rrZz%SK4Ml^;+Kw_5UYxWjTVaTD`&T#x zA>7`C_$MM+eh{DK2f{1|JYZu*TsQE4!N(p9Iks6hnsvgf5%xnK`+*Pp-br=;=x1d-}z7;)=nx6g~~vrrL&V^gOUW*Z&5joWZ#&7pvsGs zRA(L(EZDjoibg{3rKuFGW+3!lIMzcviHD0J(X~4e86*K8Cld@J_88$0N*4aSUp}oa zatx~#gVsifpBT;O5kH;#Q7+;`fQ#tG_XZ!5c!NfiH@KODH@HdS4L;1<8}t&|(#EQI zgCk)x!`E#>@UOP&VG0tNnWN=@(;C~PJ_twCsRX;=pG zB{1MFyrL*+Jr~)XIxSXGQY3IMZX!p8Mp@aWg+^J)7yERO5QO+EvWaYkiPJ}Edc~9d z?igRvQm4o2$>g^YXjNJ9cG#Mhw6FQeZmvr>T487mMC@CqA9b<>&&m|Df5W!ld1A?e1{Dl zk{PnfQfp)}+HU+z3%yA&< zIaU~FPNFQq_D+Hb?L@qPtmLctS69R)Rl*pl0n_BbKk(p>1q3Yk7w7L|CS;`}YIQB1 zM02{;4G{H7^ZO~j`TZ;$GVk$-?dN2D!M>2(jj6A_Ot}-aY(4=@OP)b0;e?P#<0oNz z;k(7wS)tmPElk2^o`g>!@=0Vt(Cm@rAc4z8#AI$SSy=OVnqq~2!%C=3z`956c**B4 zQ7d;Hes?3#%H0##0fw!XAg(+}HUX#0y$zXQHfq8uavC1$FE1!K4J;zw0)uxx2R!nc z5BdIit?ZQ%G5CD4iriV80R22(fPR=DK;KphEI!Y%`1~*o-_-d0Y#H%6)nhEh^9{_; zg$4gY3ggH!@3Ovs6U9F^r57vYpD#qT_Z`pv^Vw^!De8|pJdo=Me-t3VlV0gd0P%!+`pfj+JFzq?8CSDr42KTRKLfGvR=f0i?Ti@in0_IW$nl`$_l+x zGd0TkzSJmdF?G-v!nWh36vmn*hFdQMB9%2B_>0*GDr@2e++!Zf%N2_FFtSAD@U=PG zVjj1vJe_x$t)nT3S$mvJpQ(c`fSb z|Kjz-U9z8lkG-VfFHthnJejW-UmT22+Oe>WtAuA`#u|b^Ak_o<%$a?_>|0;Ts;S4@+Mj0?{`T4#NV)7Lk~*! z-S~$5pV`B+aceNXxAF1)8>e3F#H&|-PEfCw^Ldr+5TX|8!u`)-0H+qL|6NAG+UZ#- z1YP8&ZVlANQ^gkixU;KP6Uc1QZY_*8QPsYG+oe?d;<06%(x#|g_ATJNTa0I&#cm73 z9h8V~IhFcU1H|#TdpVmge#XQ{LFUxIpt z@T1GpqZCRPU(vh}t!U2h#_C1zv~;jdHM+!!22W0JJ|)iN50d{(X6E$>`w|}3|A{aE ze}psHFTAk)d~U@E@I{c&v5EMz7(!=%lDMf#Mc3Yr@31v>gR0~e(wPtzx=-b}J_Fyrh*pHg3{%F`APq}@(QH2; zJ=GG#?cWp5L|B-+%A39b7^lbs_$`nHsywYEn!>Ul;O!D=}Bo4@|;H3Rk~jpW8e z)!hu(nzWO^Q1~BH7F-;76JC0kz{VW*Cfx`|8ND|t@UMTL!{($N(I_C2e|)6%dI=@| z*TKwndky3OE!=#LJv2Oj+Zul&G5Hl{d&c4!-}?_hTfpqhK30igcP7@6v_<8PIrI^! zk-Oq9HvH`SUP46{QQ*JS8PQir5xa=JOMbhMDQ4B3?c$D{D-Ko~6htEzHwN6qJC1*gVsXtdgx_F^HUZs*k8&WVR$yy0+P4;v`Rb#a4btX#lw0EH)ZDT2U_+)vVmet3Xq8aiY-+Q zc*B;e3cp}WCa=$bat->%vthiDrBg_6&p!+<(hUb{jP4zB=zvy>y(rC_m;s7YbZ^pZ z&@^#$PcTRXO~=N8kL@t-8iYN8wMo*EX$PmN z@dlVZHr1XeW=FD@Wui<8&vQKSh;vN?v#VlQj4kYl?Da~2d1JWyhO0Rcc}%1)86zYH zRw-vfVs2GPoN8xgLav;YT)9Z;%@v{e2+0(k#x}cqeGr* zIGohlncYEiX78j@!8yTGeuFq_A7aKc8qloe@z7a+!(~k@4QFlmkYwKY;7J4yBDBF= zJ%RW9hk>8zkUo}8%t+azgHFonS?#F$ZZ^MKc+%YRA8+=S%#;}s!+-Ek^oEv#QCY7X zRL_a6Sg|>vD)WCn1BAwz^FQ%8aj#7<*R%2$to%K1f8mwB%EvSE84NXP#vY+uIapDT zK%xDApE!g?D#O(Nc_D=%79!z{@)rHc69_a7c2Ui{@980;dB#&@7H2!1QMCDru3n)^ zKw>Jo25)dQyniuh@E)LfMff;pLp3tY&x4I*xzB)2hDC44+z*bcpgLqQ{=@Eb@hz+X zH)vt!$xrYvy&d-VcDhS62~+!r^}+2qZU(r2ZY&HR=8*oA%-(V*hv_8Ae#wx-{`Bv_ zAdPb33>m`3d|0>O?b=vAyS}l(V39-~%PL*;=E4qHk5!U#nQSJD^Z%O;KFDSidH`Ci zB1-B4CO}Qwp|yOkGI!Q<8uk}bbxlh>$HXmdgt~5NYYUMxq#m2Goz|Ar@Av-)P)h>} z1PTBE00;o7I@yyY4$=duI@z=O4sinxsXEzMHQx%Pvkw3OCV-Q95-cF8I@wr=&JY2U z0002W0000a0000000000000000NoFh#S$_DsXEz{`Vt!gvJjIA4@3IG5A2mlxp&{+6%?@{gz001>_000aC003}uZ)#;@bS`Rh zZ*JXP3s>4m8~!VFdOX!FA_?Ild-k-5+S&?sy|mkPT{|QLM00C0sA$>W{>@|pOfZ2- zM8Izk$0n0Wc;5Sk$?OBb(Cr}Xf$I4K(7h83I|x*<+e2A@U_p}{12&cgH_+8#%RprA z1Nd>Zcl}=ZrJujvzkat~x!b6H&25_f%y1HU9=#zWfvkLy1&#V<LVXN1F!Le$aCbEWC)Sl~-v%2l!9wjSJC-}#V_JG(HRIfGHb_i~uK@9h8*Myi{ z@NPO}2znmE2O2q7HCZZ3ik2@Hq_);>w`5t)D=3fDBGM`fB$G|8i0-h!+#GwjYh#FY z+o}_s$o&gcWhFm3`W;lIN`62%<=H(;qu9yujcxdUL`ivag8}=9I%gOJ)igjT2LYlYP_j6s%DlQuLw zPEQw%OG+^d2rD*=`2xA47Xm_c;F8FEND{Wk zDB2tfKrLhh0(`9bWun5g&Uyn@aS(s=$o|$2mT+B{|97$&>d8<(a+o?ml!J1xs$V@NRt-jD|AtAU&T`p!|hE( zzG5oNcapPwN0{Xy-=?of=nlIr62Y*4Ya5q(`;zi%jJ?id-w9!_A2oLmP7jWnjD@@4 z5m%wgkIBKHmYmnSA+WZCT!?M=68-Fv331K_hR_~;**mT^cO~E707yt2I+5(~nmiVS zG$9Bfj-qIkBn8%NGY;i$2<86qQG=mS7>F!5JsJ(jX;7)6dODST6N)M=Qd6~m=b&K0 z-mNa03GIQI6s%?y{b{F5fA*G7tA}0P>Lj4G z6%!}M^4#aeS(_FjK176wPV#8*Ax$)BO^XKIXNU&fG|}M0b4P}En) zc{KjjHa!@d5>IzC`^%>_u}i&w70#yb;)|{2Wz!!+@EzAyk=ApOcd4PaZs|Bwz(Br& zndm42wdY~my!3mOJ}CfRi@V5iqfwGee*Z<1i^u5N!%S z8YHZTJp+%%Z=vhZMk<8{29zgPDV^cR716ruIR7wNG8??jT+J?-$83LGDjy^d*w9Gl z$UZH#N0u=E{hY)F947MsW-1R*C@>FD+AaF6lr(!J@zM`D4r@T`@(yZi6S$ znC*2VRQ<90Z6@Py@ z!d#)=m?ccY5l=!hMn9=82)aGG8YFVHh?vgV>C&2`S%wu4E2S}iL936u$%@Y_Q7?BL z|LaDim+Mp55svLkioEh?IsyE-+@B#+NaKsJiToL#>Mt)T_%pJJcnftt`26INe+ePq zJv*)iZA1dTn64vtoFqX%NtU3yDH8OOTxR)sf#v6J>iqn88Tq+y$3%;tH!(jER{T>C z$B||J%Ic+TF`TY{P<%cWv0jg7|H=HVH@1Z^8IMdJ$n}(X6rsS6qtaQ5sD#r+rLRlP zw^Q_|zY3Om`~phC&}F3DqR#4(|7i%S-GnSn5xoFsqNtX3JS(cSx1ud6l$t=r%+Lrt*>oyMG%q;7#rDj=+ z>4Sb3b{&6%IMysN-TGS~Q+dS$|33dj<&}5?H}q5H-z${y!Mivsncfb6^^C4q2zM_I z_p~mcEnwKsn3Bw=5BNG=qT)C+nhdeuKAoTZ>S1Ec7-8tig;N7`=UkQ({RJ^J7c4DlTaJK zk~tB>+;9v#Ye?w=gAyzNB~OsWeXf!!Q`r<2PY%R?lA0nqEYQw#!X$3-ByP?kFie9S--lTqF*f)iwfHzM5*`-_v>*0XEv1x< zyeq-<^7&t$fQt|TleKchJJMc?@;T-m=}v5}s|hqH62^(C5WYq zZ)koLZD`K%ch!&4VljzJ)g2l`qlM!=Plq%8d*uJ7^YYph{}P|o|46R?KZFa}172DF zeQCW2cpymVgdlz_hS0fG@0aOx&6}csYq16HLvmGn5LUI&Z=shjPSrl8Q?-W|q-wG6 zlT2n!E-%X8r)XLWwe84to$JXpZ7ZE2(_fal%T3(}5(u%&yh+ntq%yj<>8T4l9ky?H zAebSF6WphxBx)KN=u!-TqA_htCuut4u1?t#X|8l4D^6IGgbal1m65-H@~sG(6V*oG z45)7VyIZqX0kgUrX!Mw<169nAbuZ6U*(bcn@gyM5Gf1xEHgt=zg`LqAo<47MZ&&wJ z7vX@3{+`>E#7N7X3yFnQA#rA0nG3m6T5_d|Pj8{}WP*tf6H`Sb>$NSa#hjA|{I#>= zL!ScfG9$DK9mmjB-$=H9uHX$MkJlZXRShz2P0pVp5AO}1LeAI9J8StGJ&^Mq;!>=( zy}mIq&VSfq)+rPCkFKp1=#)2Om~YnP`po;NUkvU%C3lj4YeRqQ2|Yly4cnF2Bi3Yj zO)gY+>IJ3d9}C_t^S77lI|W5fa(jWlokXquLPhX)w)=f>dvP{@mqQ-`slsh^Yd-W0N)SG-`IjpvRR!7nhi!#Twa zhMEqOh)`?nt!PB>q5Yo(ghd*|%=twjgP|TGr9^p;{$v3{RYeXy4&Fiw5cze+Q*@S8 zJLWjseB*a?d?TPamEV*eo{jdA9S#HwO5RXDKC`15JLZ>v!A9Bw8?s5a{Dm<6;dv7_ zM+_!^Sc9J*VK1r=N7!}x75x7MW;8!|^E;IgX3meBL&gi71h{D|4IkBz;fu`Qau-MC zB&~kwBS-W7ze9&Ku8DK>5gy^gx`l4nChFPsjSU8i7V<<_{r`1_hfLa|tb#%=pUac_ z|HdRI`JC)QpTG|5k$|P)h>@3IG5A2mlxp(39#9(gPS1(6he~aRUw*6VO=rbnj8_4FCW&Z<9V0 zECU!5(35Qx8v+;(lZq5F0~iy~lfM)j0__fy&JZRRP)h{{000000{{a6-T(jqUlITS 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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') });