1// SPDX-License-Identifier: MIT
2//
3// Writes testdata/fromjson_cases.json: for a corpus of JSON-ish inputs (hand
4// picked and seeded random mutations of them), whether the runner's
5// fromJSON accepts the text and what toJSON of the result is. The Go tests
6// replay it (TestFromJSONMatchesRunner) and use the inputs as fuzz seeds.
7//
8// node expr/gen/runner/fromjson_cases.mjs > expr/testdata/fromjson_cases.json
9//
10// Needs the built driver, see README.md.
11
12import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
13import { tmpdir } from "node:os";
14import { join } from "node:path";
15import { runDriver, here, checkEnvironment } from "./env.mjs";
16
17const seeds = [
18 "null", "true", "false", "NaN", "Infinity", "-Infinity", "undefined", "0", "-0", "010", "08", "0x1F", "1.5e3", ".5", "5.", "12345678901234567890",
19 '"a"', "'a'", '"\\u00e9\\ud83d\\ude00"', '"\\ud800"', "[]", "{}", "[1,2,3]", "[1,]", "[,1]", "[1,,2]", '{"a":1}', '{"a":1,"A":2}', "{a:1}", "{'a':'b'}",
20 '{"a":[1,{"b":null}]}', "[[[1]]]", "[null,true,false]", "1 2", "[] x", ",", ",1", "/*c*/1", "1/*c*/", "[1/*c*/,2]", "null/x", "new Date(1)",
21 "[010,08,-010]", '{"a":010}', "1e5", "1E+5", "-1.5e-3", "0.1e1", "0e0", "00", "-00", "0.0", "00.5",
22];
23const alphabet = [...'"\'{}[],:/*-+.0123456789xXeE tfnuNaIy\\\n\tabc_$'];
24let state = 20260101;
25const rnd = n => { state = (state * 1103515245 + 12345) & 0x7fffffff; return state % n; };
26const set = new Set(seeds);
27for (let i = 0; i < 4000; i++) {
28 const base = seeds[rnd(seeds.length)];
29 let s = base;
30 for (let k = 0, n = 1 + rnd(3); k < n; k++) {
31 const pos = rnd(s.length + 1);
32 switch (rnd(3)) {
33 case 0: s = s.slice(0, pos) + alphabet[rnd(alphabet.length)] + s.slice(pos); break;
34 case 1: s = s.slice(0, pos) + s.slice(pos + 1); break;
35 default: s = s.slice(0, pos) + alphabet[rnd(alphabet.length)] + s.slice(pos + 1);
36 }
37 }
38 set.add(s);
39}
40const inputs = [...set];
41const lit = s => "'" + s.replaceAll("'", "''") + "'";
42const cases = inputs.map(s => ({ e: `toJSON(fromJSON(${lit(s)}))` }));
43const tmp = mkdtempSync(join(tmpdir(), "fj-"));
44writeFileSync(join(tmp, "in.json"), JSON.stringify({ contexts: {}, cases }));
45const out = runDriver([join(tmp, "in.json")]).split("\n").filter(Boolean);
46rmSync(tmp, { recursive: true });
47if (out.length !== inputs.length) throw new Error("answer count");
48const res = inputs.map((s, i) => {
49 const r = JSON.parse(out[i]);
50 if (r.err) {
51 // An error while parsing the expression (e.g. the text broke the literal) is not about fromJSON.
52 return r.err.t === "eval" ? { in: s, ok: false } : null;
53 }
54 return { in: s, ok: true, out: r.s };
55}).filter(Boolean);
56process.stdout.write(JSON.stringify(res));