Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/compiler/jsexecute.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,15 @@ runtimeFunctions.retire = `const retire = () => {
thread.target.runtime.sequencer.retireThread(thread);
}`;

/**
* Converts NaN to zero. Used to match Scratch's string-to-number.
* Unlike (x || 0), -0 stays as -0 and is not converted to 0.
* This function needs to be written such that it's very easy for browsers to inline it.
* @param {number} value A number. Might be NaN.
* @returns {number} A number. Never NaN.
*/
runtimeFunctions.toNotNaN = `const toNotNaN = value => Number.isNaN(value) ? 0 : value`;

/**
* Scratch cast to boolean.
* Similar to Cast.toBoolean()
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/jsgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,9 @@ class JSGenerator {
return `(+${this.descendInput(node.target.toType(InputType.BOOLEAN))})`;
}
if (node.target.isAlwaysType(InputType.NUMBER_OR_NAN)) {
return `(${this.descendInput(node.target)} || 0)`;
return `toNotNaN(${this.descendInput(node.target)})`;
}
return `(+${this.descendInput(node.target)} || 0)`;
return `toNotNaN(+${this.descendInput(node.target)})`;
case InputOpcode.CAST_NUMBER_OR_NAN:
return `(+${this.descendInput(node.target)})`;
case InputOpcode.CAST_NUMBER_INDEX:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ const b2 = runtime.getOpcodeFunction("looks_say");
return function* genXYZ () {
b0.value = 0;
thread.timer = timer();
var a0 = Math.max(0, 1000 * (+b1.value || 0));
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
runtime.requestRedraw();
yield;
while (thread.timer.timeElapsed() < a0) {
yield;
}
thread.timer = null;
b0.value = ((+b0.value || 0) + 1);
b0.value = (toNotNaN(+b0.value) + 1);
if ((b0.value === 1)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, false, false, "]4hbk*5ix]V00h|!x1oy", null);
} else {
Expand All @@ -32,23 +32,23 @@ const b1 = stage.variables["p]KODv+)+:l=%NT~j3/d-wait"];
return function* genXYZ () {
yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, "1Ba%a0GIK#hwJ46y=WVt", null);
thread.timer = timer();
var a0 = Math.max(0, 1000 * (+b1.value || 0));
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
runtime.requestRedraw();
yield;
while (thread.timer.timeElapsed() < a0) {
yield;
}
thread.timer = null;
thread.timer = timer();
var a1 = Math.max(0, 1000 * (+b1.value || 0));
var a1 = Math.max(0, 1000 * toNotNaN(+b1.value));
runtime.requestRedraw();
yield;
while (thread.timer.timeElapsed() < a1) {
yield;
}
thread.timer = null;
thread.timer = timer();
var a2 = Math.max(0, 1000 * (+b1.value || 0));
var a2 = Math.max(0, 1000 * toNotNaN(+b1.value));
runtime.requestRedraw();
yield;
while (thread.timer.timeElapsed() < a2) {
Expand All @@ -67,14 +67,14 @@ const b2 = runtime.getOpcodeFunction("looks_say");
return function* genXYZ () {
b0.value = 0;
thread.timer = timer();
var a0 = Math.max(0, 1000 * (+b1.value || 0));
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
runtime.requestRedraw();
yield;
while (thread.timer.timeElapsed() < a0) {
yield;
}
thread.timer = null;
b0.value = ((+b0.value || 0) + 1);
b0.value = (toNotNaN(+b0.value) + 1);
if ((b0.value === 2)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, false, false, "0i[-T:vYTt=bi47@byUE", null);
} else {
Expand Down
14 changes: 7 additions & 7 deletions test/snapshot/__snapshots__/order-library.sb3.tw-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ const b1 = stage.variables["):/PVGTvoVRvq(ikGwRE-wait"];
return function* genXYZ () {
yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, "s+@:|^WPr8]N1Y9Hk2f5", null);
thread.timer = timer();
var a0 = Math.max(0, 1000 * (+b1.value || 0));
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
runtime.requestRedraw();
yield;
while (thread.timer.timeElapsed() < a0) {
yield;
}
thread.timer = null;
thread.timer = timer();
var a1 = Math.max(0, 1000 * (+b1.value || 0));
var a1 = Math.max(0, 1000 * toNotNaN(+b1.value));
runtime.requestRedraw();
yield;
while (thread.timer.timeElapsed() < a1) {
yield;
}
thread.timer = null;
thread.timer = timer();
var a2 = Math.max(0, 1000 * (+b1.value || 0));
var a2 = Math.max(0, 1000 * toNotNaN(+b1.value));
runtime.requestRedraw();
yield;
while (thread.timer.timeElapsed() < a2) {
Expand All @@ -43,14 +43,14 @@ const b2 = runtime.getOpcodeFunction("looks_say");
return function* genXYZ () {
b0.value = 0;
thread.timer = timer();
var a0 = Math.max(0, 1000 * (+b1.value || 0));
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
runtime.requestRedraw();
yield;
while (thread.timer.timeElapsed() < a0) {
yield;
}
thread.timer = null;
b0.value = ((+b0.value || 0) + 1);
b0.value = (toNotNaN(+b0.value) + 1);
if ((b0.value === 1)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, false, false, "RSQ{nVCc)6E)(`KlnFCF", null);
} else {
Expand All @@ -67,14 +67,14 @@ const b2 = runtime.getOpcodeFunction("looks_say");
return function* genXYZ () {
b0.value = 0;
thread.timer = timer();
var a0 = Math.max(0, 1000 * (+b1.value || 0));
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
runtime.requestRedraw();
yield;
while (thread.timer.timeElapsed() < a0) {
yield;
}
thread.timer = null;
b0.value = ((+b0.value || 0) + 1);
b0.value = (toNotNaN(+b0.value) + 1);
if ((b0.value === 2)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, false, false, "KP?op(=Vg2#;@]!,C#.~", null);
} else {
Expand Down
24 changes: 12 additions & 12 deletions test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,61 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aA",
if ((("" + (0 * Infinity)).toLowerCase() === "NaN".toLowerCase())) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "/", null);
}
if (((((0 * Infinity) || 0) * 1) === 0)) {
if (((toNotNaN((0 * Infinity)) * 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, "=", null);
}
if ((((((Math.acos(1.01) * 180) / Math.PI) || 0) * 1) === 0)) {
if (((toNotNaN(((Math.acos(1.01) * 180) / Math.PI)) * 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 ((((((Math.asin(1.01) * 180) / Math.PI) || 0) * 1) === 0)) {
if (((toNotNaN(((Math.asin(1.01) * 180) / Math.PI)) * 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 (((((0 / 0) || 0) * 1) === 0)) {
if (((toNotNaN((0 / 0)) * 1) === 0)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aa", null);
}
if ((("" + Math.sqrt(-1)).toLowerCase() === "NaN".toLowerCase())) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ac", null);
}
if ((((Math.sqrt(-1) || 0) * 1) === 0)) {
if (((toNotNaN(Math.sqrt(-1)) * 1) === 0)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ae", null);
}
if ((("" + mod(0, 0)).toLowerCase() === "NaN".toLowerCase())) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ag", null);
}
if ((((mod(0, 0) || 0) * 1) === 0)) {
if (((toNotNaN(mod(0, 0)) * 1) === 0)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ai", null);
}
if ((("" + Math.log(-1)).toLowerCase() === "NaN".toLowerCase())) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ak", null);
}
if ((((Math.log(-1) || 0) * 1) === 0)) {
if (((toNotNaN(Math.log(-1)) * 1) === 0)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "am", null);
}
if ((("" + (Math.log(-1) / Math.LN10)).toLowerCase() === "NaN".toLowerCase())) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ao", null);
}
if (((((Math.log(-1) / Math.LN10) || 0) * 1) === 0)) {
if (((toNotNaN((Math.log(-1) / Math.LN10)) * 1) === 0)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aq", null);
}
if (((((Math.round(Math.sin((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) {
if (((toNotNaN((Math.round(Math.sin((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10)) * 1) === 0)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "as", null);
}
if (((((Math.round(Math.cos((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) {
if (((toNotNaN((Math.round(Math.cos((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10)) * 1) === 0)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "au", null);
}
if ((((tan((1 / 0)) || 0) * 1) === 0)) {
if (((toNotNaN(tan((1 / 0))) * 1) === 0)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aw", null);
}
if ((((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || 0) * 1) === 0)) {
if (((toNotNaN(runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0))) * 1) === 0)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ax", null);
}
yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, ":", null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ retire(); return;
const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"];
const b1 = runtime.getOpcodeFunction("looks_say");
return function* genXYZ () {
if (((+b0.value || 0) === 1)) {
if ((toNotNaN(+b0.value) === 1)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass variable 1",}, b1, false, false, "m", null);
}
retire(); return;
Expand All @@ -32,7 +32,7 @@ retire(); return;
const b0 = stage.variables[")|GMR5fz;%F_H,c0wGVM"];
const b1 = runtime.getOpcodeFunction("looks_say");
return function* genXYZ () {
if (((+b0.value || 0) === 2)) {
if ((toNotNaN(+b0.value) === 2)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass variable 2",}, b1, false, false, "q", null);
}
retire(); return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "()*
target.setSize(96);
b1.value = 0;
while (!(100 === Math.round(target.size))) {
b1.value = ((+b1.value || 0) + 1);
b1.value = (toNotNaN(+b1.value) + 1);
target.setSize(target.size + ((100 - Math.round(target.size)) / 10));
yield;
}
if (((+b1.value || 0) === 20)) {
if ((toNotNaN(+b1.value) === 20)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "FPDFR?Wwq)kLj0A$0D{@", null);
}
yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "1,vLoJ4OQBv+Q#$VoYf=", null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ thread.procedures["Wsetup values"]();
b0.value = 0;
b1.value = 0;
for (var a0 = b2.value.length; a0 >= 0.5; a0--) {
b1.value = ((+b1.value || 0) + 1);
b1.value = (toNotNaN(+b1.value) + 1);
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);
b3.value = (toNotNaN(+b3.value) + 1);
b0.value = (toNotNaN(+b0.value) + 1);
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);
b0.value = (toNotNaN(+b0.value) + 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);
b0.value = (toNotNaN(+b0.value) + 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;}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ return function* genXYZ () {
yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "c", null);
b1.value = (0 + 0);
yield* executeInCompatibilityLayer({"MESSAGE":"Hello!","SECS":0.1,}, b2, false, false, "d", null);
if ((((+b1.value || 0) + 2) === 2)) {
if (((toNotNaN(+b1.value) + 2) === 2)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "i", null);
}
yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "m", null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"];
return function* genXYZ () {
yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "e", null);
b1.value = 0;
for (var a0 = (+thread.procedures["Zblock name"]() || 0); a0 >= 0.5; a0--) {
b1.value = ((+b1.value || 0) + 1);
for (var a0 = toNotNaN(+thread.procedures["Zblock name"]()); a0 >= 0.5; a0--) {
b1.value = (toNotNaN(+b1.value) + 1);
yield;
}
if (((+b1.value || 0) === 40)) {
if ((toNotNaN(+b1.value) === 40)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "n", null);
}
yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "l", null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ target.setDirection((1 / 0));
if ((target.direction === 95)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass 2",}, b0, false, false, "r", null);
}
target.setDirection(((0 / 0) || 0));
target.setDirection(toNotNaN((0 / 0)));
if ((target.direction === 0)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass 3",}, b0, false, false, "u", null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass variable",}, b0, false, fals
b2.value = [];
b2.value.push(3);
b2._monitorUpToDate = false;
if (((+(b2.value[1 - 1] ?? "") || 0) === 3)) {
if ((toNotNaN(+(b2.value[1 - 1] ?? "")) === 3)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass list",}, b0, false, false, "m", null);
}
yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "l", null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ return "";
const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"];
return function* genXYZ () {
while (true) {
b0.value = ((+b0.value || 0) + 1);
b0.value = (toNotNaN(+b0.value) + 1);
yield;
}
retire(); return;
Expand Down
24 changes: 12 additions & 12 deletions test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ if (!compareEqual((b2.value[(b1.value | 0) - 1] ?? ""), (b3.value[(b1.value | 0)
b0.value = 0;
yield* executeInCompatibilityLayer({"MESSAGE":("fail mismatch at index " + ("" + b1.value)),}, b4, true, false, ",", null);
}
b1.value = ((+b1.value || 0) + 1);
b1.value = (toNotNaN(+b1.value) + 1);
}
if (((+b0.value || 0) === 1)) {
if ((toNotNaN(+b0.value) === 1)) {
yield* executeInCompatibilityLayer({"MESSAGE":"pass sorted",}, b4, true, false, "aE", null);
}
return "";
Expand All @@ -62,16 +62,16 @@ const b7 = target.variables["JIHtr29*ug5G;5*`f2.K-min-"];
const b8 = target.variables["JIHtr29*ug5G;5*`f2.K-j-"];
return function funXYZ_qsort__ (p0,p1) {
if (compareLessThan(p0, p1)) {
if (!(((+p1 || 0) - (+p0 || 0)) <= 7)) {
b0.value = (b1.value[(((((+p1 || 0) + (+p0 || 0)) || 0) / 2) | 0) - 1] ?? "");
if (!((toNotNaN(+p1) - toNotNaN(+p0)) <= 7)) {
b0.value = (b1.value[((toNotNaN((toNotNaN(+p1) + toNotNaN(+p0))) / 2) | 0) - 1] ?? "");
b2.value = p0;
b3.value = p1;
while (true) {
while (compareLessThan(listGet(b1.value, b2.value), b0.value)) {
b2.value = ((+b2.value || 0) + 1);
b2.value = (toNotNaN(+b2.value) + 1);
}
while (compareGreaterThan(listGet(b1.value, b3.value), b0.value)) {
b3.value = ((+b3.value || 0) + -1);
b3.value = (toNotNaN(+b3.value) + -1);
}
if (compareGreaterThan(b2.value, b3.value)) {
thread.procedures["Wqsort %s %s"](p0,b3.value);
Expand All @@ -81,17 +81,17 @@ return "";
b4.value = listGet(b1.value, b2.value);
listReplace(b1, b2.value, listGet(b1.value, b3.value));
listReplace(b1, b3.value, b4.value);
b2.value = ((+b2.value || 0) + 1);
b3.value = ((+b3.value || 0) + -1);
b2.value = (toNotNaN(+b2.value) + 1);
b3.value = (toNotNaN(+b3.value) + -1);
}
}
} else {
b5.value = p0;
for (var a0 = (((+p1 || 0) - (+p0 || 0)) || 0); a0 >= 0.5; a0--) {
for (var a0 = toNotNaN((toNotNaN(+p1) - toNotNaN(+p0))); a0 >= 0.5; a0--) {
b6.value = b5.value;
b7.value = listGet(b1.value, b5.value);
b8.value = ((+b5.value || 0) + 1);
for (var a1 = (((+p1 || 0) - (+b5.value || 0)) || 0); a1 >= 0.5; a1--) {
b8.value = (toNotNaN(+b5.value) + 1);
for (var a1 = toNotNaN((toNotNaN(+p1) - toNotNaN(+b5.value))); a1 >= 0.5; a1--) {
if (compareLessThan((b1.value[(b8.value | 0) - 1] ?? ""), b7.value)) {
b7.value = (b1.value[(b8.value | 0) - 1] ?? "");
b6.value = b8.value;
Expand All @@ -104,7 +104,7 @@ b4.value = listGet(b1.value, b5.value);
listReplace(b1, b5.value, b7.value);
listReplace(b1, b6.value, b4.value);
}
b5.value = ((+b5.value || 0) + 1);
b5.value = (toNotNaN(+b5.value) + 1);
}
}
}
Expand Down
Loading
Loading