Files
scriptc/packages/runtime/test/gen-tonumber-cases.mjs
Chris Tate c08ab955ae Enforce linting and consistent source formatting
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks.
- Preserve compiler import boundaries and protected fixtures while cleaning up lint findings.
- Standardize source formatting and validate import, fixture, and embedded-source protections.
2026-10-05 17:51:24 -05:00

375 lines
9.4 KiB
JavaScript

// Generates ToNumber(string) test cases with Node as the oracle — the
// scr_string_to_number counterpart of gen-number-cases.mjs (formatting).
// Each line: <UTF-8 bytes of the input string as hex, "-" when empty>\t
// <16 hex digits of Number(input)'s bit pattern>
//
// node gen-tonumber-cases.mjs cases # curated + 50k seeded cases → stdout
// node gen-tonumber-cases.mjs fuzz <count> # <count> random cases → stdout
import { argv, stdout } from "node:process";
const f64 = new Float64Array(1);
const u64 = new BigUint64Array(f64.buffer);
function line(s) {
f64[0] = Number(s);
const hex = Buffer.from(s, "utf8").toString("hex");
return `${hex === "" ? "-" : hex}\t${u64[0].toString(16).padStart(16, "0")}\n`;
}
// The complete StrWhiteSpace set: WhiteSpace (TAB VT FF SP NBSP ZWNBSP +
// the Zs category) and LineTerminator (LF CR LS PS).
const WS = [
"\t",
"\n",
"\v",
"\f",
"\r",
" ",
"\u00a0",
"\u1680",
"\u2000",
"\u2001",
"\u2002",
"\u2003",
"\u2004",
"\u2005",
"\u2006",
"\u2007",
"\u2008",
"\u2009",
"\u200a",
"\u2028",
"\u2029",
"\u202f",
"\u205f",
"\u3000",
"\ufeff",
];
function* curated() {
// Whitespace: empty, each StrWhiteSpaceChar alone, all of them joined,
// and NON-whitespace lookalikes (ZWSP, ZWNJ, MVS) that must be NaN.
yield "";
yield* WS;
yield WS.join("");
yield* ["\u200b", "\u200c", "\u200d", "\u180e", "\u0085"];
// Core decimals, signed and whitespace-decorated.
const core = [
"0",
"-0",
"+0",
"1",
"42",
"-1",
"0.5",
".5",
"5.",
"1.",
"+.5",
"-.5",
"0.1",
"0.2",
"0.3",
"123.456",
"3.141592653589793",
"1e3",
"1E3",
"1e+3",
"1e-3",
"5.e3",
".5e-3",
"2e308",
"1e309",
"-1e309",
"1e-309",
"1e-324",
"5e-324",
"2.5e-324",
"2e-324",
"7.5e-324",
"1e-400",
"1e400",
"9007199254740991",
"9007199254740992",
"9007199254740993",
"9007199254740993.0",
"9007199254740994",
"9007199254740995",
"1.7976931348623157e308",
"1.7976931348623158e308",
"1.7976931348623159e308",
"-1.7976931348623157e308",
"2.2250738585072014e-308",
"2.2250738585072011e-308",
"4.9406564584124654e-324",
"2.4703282292062327e-324",
"2.4703282292062328e-324",
"1.0000000000000002",
"1.0000000000000001",
"0.000001",
"0.0000001",
"1e21",
"1e-7",
"123456789012345678901",
"Infinity",
"+Infinity",
"-Infinity",
];
yield* core;
for (const s of core) {
yield ` ${s} `;
yield `\t\n${s}\u00a0\ufeff`;
yield `\u3000${s}\u2028`;
}
// Non-decimal integer literals: every prefix case, digit sweeps, the
// 2^53 neighborhood (rounding), giant exact-integer inputs (bignum
// rounding, half-even ties), and overflow past 2^1024 → Infinity.
const radixes = [
["0x", "0X"],
["0o", "0O"],
["0b", "0B"],
];
const digits = {
"0x": "0123456789abcdefABCDEF",
"0o": "01234567",
"0b": "01",
};
for (const [lo, up] of radixes) {
const d = digits[lo];
yield* [`${lo}0`, `${up}0`, `${lo}1`, `${up}1`, `${lo}${d}`, `${lo}00007`];
yield ` ${lo}11 `;
yield* [lo, up, `${lo} 1`, `-${lo}1`, `+${lo}1`, `${lo}1.5`, `${lo}1e2x`];
}
yield* [
"0x1fffffffffffff",
"0x20000000000000",
"0x20000000000001",
"0x20000000000002",
"0x20000000000003",
"0x20000000000004",
"0x3fffffffffffff",
"0x40000000000001",
"0x40000000000002",
"0x7fffffffffffffff",
"0x8000000000000000",
"0xffffffffffffffff",
"0x10000000000000000",
"0x100000000000000001",
"0xfffffffffffff800000000000000000000", // half-even tie territory
"0xfffffffffffffbffffffffffffffffffff",
"0xfffffffffffffc00000000000000000000",
"0x" + "f".repeat(100),
"0x" + "1" + "0".repeat(255), // < 2^1024
"0x" + "1" + "0".repeat(256), // = 2^1024 → Infinity
"0x" + "f".repeat(300), // way past → Infinity
"0b" + "1".repeat(53),
"0b" + "1".repeat(54),
"0b" + "1".repeat(55),
"0b1" + "0".repeat(52) + "1",
"0b1" + "0".repeat(53) + "1",
"0b" + "1".repeat(1024),
"0b1" + "0".repeat(1023),
"0b1" + "0".repeat(1024),
"0o" + "7".repeat(400),
"0o1" + "0".repeat(340),
"0x0000000000000000000000000000",
];
// Long decimal digit strings: exactness and overflow through strtod.
for (const k of [17, 20, 25, 50, 100, 308, 309, 310, 400, 800]) {
yield "9".repeat(k);
yield "1" + "0".repeat(k);
yield "0." + "0".repeat(k) + "1";
yield "0." + "1".repeat(k);
yield "1." + "9".repeat(k) + "e-100";
}
// Garbage shapes: every one must be NaN (asserted against Node either
// way — the oracle decides).
yield* [
"12px",
"px12",
"1.2.3",
"1..2",
"..5",
"1_000",
"1,000",
"+-1",
"-+1",
"--1",
"++1",
"1 2",
"+ 1",
"- 1",
"NaN",
"nan",
"-NaN",
"infinity",
"INFINITY",
"Infinit",
"Infinityy",
"+Infinity8",
"- Infinity",
"e5",
".e5",
"+.",
"-.",
".",
"+",
"-",
"0x",
"0xg",
"0o8",
"0b2",
"1e",
"1e+",
"1e-",
"1e1.5",
"1e+1e",
"0x1_0",
"0b1_0",
"\u0661\u0662\u0663",
"12\u0663",
"1\u200b",
"\u200b1",
"1",
"1",
"0x10 20",
"true",
"false",
"null",
"undefined",
"0 x10",
"0 b10",
"00x10",
"00b1",
"0.x1",
];
// Interior-whitespace garbage per whitespace char (trim is ends-only).
for (const w of WS) yield `1${w}2`;
}
// xorshift128 — deterministic so the committed file is reproducible.
function makeRng(seed) {
let s0 = seed ^ 0x9e3779b97f4a7c15n,
s1 = 0x2545f4914f6cdd1dn;
return () => {
let x = s0;
const y = s1;
s0 = y;
x ^= (x << 23n) & 0xffffffffffffffffn;
s1 = x ^ y ^ (x >> 17n) ^ (y >> 26n);
return (s1 + y) & 0xffffffffffffffffn;
};
}
function* random(count, seed) {
const next = makeRng(seed);
const int = (n) => Number(next() % BigInt(n));
const pick = (arr) => arr[int(arr.length)];
const randDouble = () => {
u64[0] = next();
return f64[0];
};
const GARBAGE = "0123456789abcdefoxABX+-._e E\u00a0\ufeffpz$";
const digitsOf = { 16: "0123456789abcdefABCDEF", 8: "01234567", 2: "01", 10: "0123456789" };
let emitted = 0;
while (emitted < count) {
const shape = int(10);
let s;
if (shape === 0) {
// Round-trip: Node's own shortest formatting of a random double.
const x = randDouble();
s = String(x);
} else if (shape === 1) {
// Fixed/exponential renderings of a random double.
const x = randDouble();
if (!Number.isFinite(x)) continue;
s = int(2) ? x.toFixed(int(21)) : x.toExponential(int(21));
} else if (shape === 2 || shape === 3) {
// Random digit strings in a random radix with its prefix.
const radix = pick([16, 8, 2]);
const d = digitsOf[radix];
let body = "";
const len = 1 + int(shape === 2 ? 20 : 300);
for (let i = 0; i < len; i++) body += d[int(d.length)];
s =
pick([
radix === 16 ? "0x" : radix === 8 ? "0o" : "0b",
radix === 16 ? "0X" : radix === 8 ? "0O" : "0B",
]) + body;
} else if (shape === 4) {
// Random decimal spellings: sign, int/frac digits, exponent.
const digs = (n) => {
let r = "";
for (let i = 0; i < n; i++) r += digitsOf[10][int(10)];
return r;
};
s =
pick(["", "+", "-"]) +
digs(int(25)) +
(int(2) ? "." + digs(int(25)) : "") +
(int(2) ? pick(["e", "E"]) + pick(["", "+", "-"]) + digs(int(4)) : "");
} else if (shape === 5) {
// Whitespace decoration around a valid spelling.
const deco = () => {
let r = "";
const n = int(4);
for (let i = 0; i < n; i++) r += pick(WS);
return r;
};
s = deco() + String(randDouble()) + deco();
} else if (shape === 6) {
// Mutation: a random char inserted into a valid spelling.
const base = String(randDouble());
const at = int(base.length + 1);
s = base.slice(0, at) + GARBAGE[int(GARBAGE.length)] + base.slice(at);
} else if (shape === 7) {
// Fully random short ASCII.
let r = "";
const len = int(12);
for (let i = 0; i < len; i++) r += String.fromCharCode(0x20 + int(0x5f));
s = r;
} else if (shape === 8) {
// Random characters from the numeric-adjacent alphabet.
let r = "";
const len = 1 + int(16);
for (let i = 0; i < len; i++) r += GARBAGE[int(GARBAGE.length)];
s = r;
} else {
// Random BMP code points (exercises the UTF-8 walk; surrogates
// skipped — the runtime stores well-formed UTF-8 only).
let r = "";
const len = int(8);
for (let i = 0; i < len; i++) {
const cp = int(0xffff);
if (cp >= 0xd800 && cp <= 0xdfff) continue;
r += String.fromCharCode(cp);
}
s = r;
}
yield s;
emitted++;
}
}
const mode = argv[2] ?? "cases";
let chunk = "";
function emit(s) {
chunk += line(s);
if (chunk.length > 1 << 16) {
stdout.write(chunk);
chunk = "";
}
}
if (mode === "cases") {
for (const s of curated()) emit(s);
for (const s of random(50_000, 0x70b0b0e5n)) emit(s);
} else if (mode === "fuzz") {
const count = Number(argv[3] ?? 1_000_000);
const seed = BigInt(Date.now());
for (const s of random(count, seed)) emit(s);
} else {
throw new Error(`unknown mode: ${mode}`);
}
stdout.write(chunk);