export const max = (a, b) => a > b ? a : b; export const min = (a, b) => a < b ? a : b; export const isNumeric = c => c.charCodeAt(0) >= "0".charCodeAt(0) && c.charCodeAt(0) <= "9".charCodeAt(0); export const explode = s => s.split(""); export const getIn = (obj, path) => path.length === 0 ? obj : getIn(obj?.[path[0]], path.slice(1)); export const merge = (lhs, rhs) => { if (lhs === undefined || lhs === null) { return rhs; } if (rhs === undefined || rhs === null) { return lhs; } if (typeof lhs !== "object") { return rhs; } const lkeys = Object.keys(lhs); const rkeys = Object.keys(rhs); const allKeys = lkeys.concat(rkeys); return [...new Set(allKeys)].reduce( (o, key) => ({ ...o, [key]: merge(lhs[key], rhs[key]) }), {}, ); }; export const compareString = (a, b) => a.localeCompare(b, "POSIX"); export const compareNumber = (a, b) => a - b; export const compareObject = (lhs, rhs) => { const lstr = Object.entries(lhs).toSorted(); const rstr = Object.entries(rhs).toSorted(); if (lstr < rstr) { return -1; } if (lstr > rstr) { return 1; } throw new Error("unreachable"); }; const FROM = /[&<>'"]/g; const ESCAPES = { '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }; const mappingFn = c => ESCAPES[c]; export const escapeHTML = s => String.prototype.replace.call(s, FROM, mappingFn); export const union = (a, b) => [...new Set(a.concat(b))] export const eq = (a, b) => { if (a === b) { return true; } if (a === null || b === null) { return false; } if (typeof a != "object" || typeof b != "object") { return false; } if (Array.isArray(a) !== Array.isArray(b)) { return false; } if (Object.keys(a).length !== Object.keys(b).length) { return false; } for (const k in a) { if (!b.hasOwnProperty(k)) { return false; } if (!eq(a[k], b[k])) { return false; } } return true; }; export const keys = (ks, obj) => ks.reduce( (ret, k) => obj.hasOwnProperty(k) ? {...ret, [k]: obj[k]} : ret, {}, ); export const difference = (a, b) => { const diff = new Set(a); for (const el of b) { diff.delete(el); } return diff; }; export const assocIn = (obj, path, value) => path.length === 0 ? obj : path.length === 1 ? { ...obj, [path[0]]: value } : { ...obj, [path[0]]: assocIn( (obj[path[0]] || {}), path.slice(1), value ) }; export const dissoc = (obj, key) => { const ret = { ...obj }; delete ret[key] return ret; }; export const partial = (fn, ...startArgs) => (...endArgs) => fn(...startArgs, ...endArgs); export const undefinedAsNull = x => x === undefined ? null : x; export const first = a => a[0]; export const rest = a => a.slice(1); export const butlast = a => a.slice(a, a.length - 1); export const last = a => a[a.length - 1]; export const complement = fn => (...args) => !fn(...args); export const take = function*(n, gen) { let i = 0n; for (const x of gen) { if (i >= n) { break; } i++; yield x; } }; export const range = function*(x, y, step = 1n) { if (x === undefined) { let i = 0n; while (true) { yield i++; } } const [from, to] = y === undefined ? [0n, x] : [x, y]; const fromn = BigInt(from); const ton = BigInt(to); const stepn = BigInt(step); if (stepn === 0n) { while (true) { yield fromn; } } if (step < 0n) { for (let i = fromn; i > ton; i+= stepn) { yield i; } } else { for (let i = fromn; i < ton; i += stepn) { yield i; } } }; const MATRIX_A = 0x9908b0df; const CONSTANTS = { MAX_INT: 4294967296.0, N: 624, M: 397, UPPER_MASK: 0x80000000, LOWER_MASK: 0x7fffffff, MATRIX_A, MAG01: new Array(0, MATRIX_A), }; const initFn = () => ({ mt: new Array(CONSTANTS.N), mti: CONSTANTS.N + 1, }); const seedFn = (ref, seed) => { ref.mt[0] = seed; for (ref.mti = 1; ref.mti < CONSTANTS.N; ref.mti++) { const s = ref.mt[ref.mti - 1] ^ (ref.mt[ref.mti - 1] >>> 30); ref.mt[ref.mti] = ( ((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253 ) + ref.mti; ref.mt[ref.mti] >>>= 0; } }; const generateFn = (ref, newSeed) => { if (newSeed) { seedFn(ref, newSeed); return; } let y = null; if (ref.mti >= CONSTANTS.N) { if (ref.mti === CONSTANTS.N + 1) { seedFn(ref, 5489); } { let kk = null; for (kk = 0; kk < CONSTANTS.N - CONSTANTS.M; kk++) { y = (ref.mt[kk] & CONSTANTS.UPPER_MASK) | (ref.mt[kk + 1] & CONSTANTS.LOWER_MASK); ref.mt[kk] = ref.mt[kk + CONSTANTS.M] ^ (y >>> 1) ^ CONSTANTS.MAG01[y & 1]; } for (; kk < CONSTANTS.N - 1; kk++) { y = (ref.mt[kk] & CONSTANTS.UPPER_MASK) | (ref.mt[kk + 1] & CONSTANTS.LOWER_MASK); ref.mt[kk] = ref.mt[ kk + (CONSTANTS.M - CONSTANTS.N) ] ^ (y >>> 1) ^ CONSTANTS.MAG01[y & 1]; } } y = (ref.mt[CONSTANTS.N - 1] & CONSTANTS.UPPER_MASK) | (ref.mt[0] & CONSTANTS.LOWER_MASK); ref.mt[CONSTANTS.N - 1] = ref.mt[CONSTANTS.M - 1] ^ (y >>> 1) ^ CONSTANTS.MAG01[y & 1]; ref.mti = 0; } y = ref.mt[ref.mti++]; y ^= (y >>> 11); y ^= (y << 7) & 0x9d2c5680; y ^= (y << 15) & 0xefc60000; y ^= (y >>> 18); return y >>> 0; }; export const makeRandom = (seed = new Date().getTime()) => { const state = initFn(); seedFn(state, seed); return partial(generateFn, state); }; class Reduced { constructor(value) { this.value = value; } } export const isReduced = x => x instanceof Reduced; export const reduced = x => new Reduced(x); const reduceRec = (coll, fn, acc, index) => isReduced(acc) ? acc.value : index === coll.length ? acc : reduceRec( coll, fn, fn(acc, coll[index], index, coll), index + 1, ); export const reduce = (coll, fn, init) => init === undefined ? reduceRec(coll.slice(1), fn, coll[0], 0) : reduceRec(coll, fn, init, 0); const reductionsRec = (coll, fn, acc, index, steps) => { if (isReduced(acc)) { return steps.concat(acc.value); } if (index === coll.length) { return steps; } const newAcc = fn(acc, coll[index], index, coll); return reductionsRec( coll, fn, newAcc, index + 1, steps.concat(newAcc), ); }; export const reductions = (coll, fn, init) => init === undefined ? reductionsRec(coll.slice(1), fn, coll[0], 0, [coll[0]]) : reductionsRec(coll, fn, init, 0, []); export const mapValues = (obj, fn) => Object.keys(obj).reduce( (o, k) => ({ ...o, [k]: fn(obj[k], k), }), {}, ); export const repeat = (n, x) => (new Array(n)).fill(x); export const red = s => `\x1b[0;31m${s}\x1b[0m`; export const redb = s => `\x1b[1;31m${s}\x1b[0m`; export const green = s => `\x1b[0;32m${s}\x1b[0m`; export const greenb = s => `\x1b[1;32m${s}\x1b[0m`; export const yellow = s => `\x1b[0;33m${s}\x1b[0m`; export const yellowb = s => `\x1b[1;33m${s}\x1b[0m`; export const yellowi = s => `\x1b[0;93m${s}\x1b[0m`; export const bluei = s => `\x1b[0;94m${s}\x1b[0m`; export const purple = s => `\x1b[0;95m${s}\x1b[0m`; export const purpleb = s => `\x1b[1;95m${s}\x1b[0m`; export const whiteb = s => `\x1b[1;37m${s}\x1b[0m`; export const grey = s => `\x1b[0;90m${s}\x1b[0m`; export const greyb = s => `\x1b[1;90m${s}\x1b[0m`; const t = ({ colors, err, assertEq }) => ({ assertEq, tap: x => { err(`tap: ${x}\n`); return x; }, start: msg => { err(`${msg}:\n`); }, testing: async (msg, fn) => { err(`${colors.yellow("testing")}: ${msg}... `); try { if (fn.constructor.name === "AsyncFunction") { await fn(); } else { fn(); } } catch (e) { err(`${colors.red("ERR")}.\n`); throw e; } err(`${colors.green("OK")}.\n`); }, }); const getJSImpl = () => { if (typeof scriptArgs !== "undefined") { return "qjs"; } if (typeof Deno !== "undefined") { return "deno"; } if (typeof process !== "undefined") { return "node"; } if (typeof window !== "undefined") { return "browser"; } }; const mappings = { ARGV: { qjs: () => scriptArgs, node: () => process.argv.slice(1), deno: () => Deno.mainModule.substring("file://".length), browser: () => null, }, exit: { qjs: async () => { const { exit } = await import("std"); return exit; }, node: async () => process.exit, deno: async () => Deno.exit, browser: () => null, }, tconf: { qjs: async () => await mappings.testConf.unknown(), deno: async () => await mappings.testConf.node(), browser: async () => { class DOMAssertionError extends Error {} const w = s => { const $testReport = document.querySelector("#test-report"); $testReport.innerHTML += s; }; const color = c => s => `${s}`; const red = s => color("red"); const green = s => color("green"); const yellow = s => color("darkgoldenrod"); return { testStart: msg => w(`${msg}:\n`), testing: msg => w(`${yellow("testing")}: ${msg}...`), testOK: () => w(` ${green("OK")}.\n`), assertEq: (x, msg = "") => { if (x) { return; } w(` ${red("ERROR")}.\n`); w("\nSee console for details.\n"); w("If possible, do report them."); throw new DOMAssertionError(msg); }, }; }, node: async () => { const assert = await import("node:assert/strict"); const process = await import("node:process"); return { err: x => process.stderr.write(x), assertEq: assert.deepEqual, colors: { red, green, yellow, }, }; }, }, tap: { qjs: () => null, node: async () => { const util = await import("node:util"); return x => { console.log(util.inspect(x, { depth: null, colors: true, })); return x; }; }, deno: () => null, browser: () => null, }, }; export const JSImpl = getJSImpl(); export const exit = await mappings.exit[ JSImpl]?.(); export const ARGV = await mappings.ARGV[ JSImpl]?.(); export const tap = await mappings.tap[ JSImpl]?.(); const tconf = await mappings.tconf[JSImpl]?.(); export const runTests = async tests => { const tFinal = t(tconf); for (const testFn of tests) { await testFn(tFinal); } };