diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/sjs.js | 304 | ||||
| -rw-r--r-- | src/sjs.mjs | 449 |
2 files changed, 449 insertions, 304 deletions
diff --git a/src/sjs.js b/src/sjs.js deleted file mode 100644 index 799f964..0000000 --- a/src/sjs.js +++ /dev/null @@ -1,304 +0,0 @@ -export const getIn = (obj, path) => - path.length === 0 - ? obj - : getIn(obj?.[path[0]], path.slice(1)); - -export const merge = (lhs, rhs) => { - if (lhs === undefined) { - return rhs; - } - - if (rhs === undefined) { - return lfs; - } - - 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 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 escape = s => - String.prototype.replace.call(s, FROM, mappingFn); - -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 findFirst = (arr, fn) => { - for (const x of arr) { - const ret = fn(x); - if (ret) { - return ret; - } - } - - return null; -}; - -export const partial = (fn, ...startArgs) => - (...endArgs) => - fn(...startArgs, ...endArgs); - -export const strSortFn = (a, b) => a.localeCompare(b, "POSIX"); - -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 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 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 { - await fn(); - } catch (e) { - err(`${colors.red("ERR")}.\n`); - throw e; - } - err(`${colors.green("OK")}.\n`); - }, -}); - -class UnimplementedError extends Error {} - -export const getJSImpl = () => { - if (typeof scriptArgs !== "undefined") { - return "qjs"; - } - if (typeof Deno !== "undefined") { - return "deno"; - } - if (typeof process !== "undefined") { - return "node"; - } - // FIXME: add browser and bun - return "unknown"; -}; - -export const JSImpl = getJSImpl(); - -const mappings = { - ARGV: { - qjs: () => scriptArgs, - node: () => process.argv.slice(1), - deno: () => Deno.mainModule.substring("file://".length), - unknown: () => { - throw new UnimplementedError(`ARGV["${JSImpl}"]`); - }, - }, - exit: { - qjs: async () => { - const { exit } = await import("std"); - return exit; - }, - node: async () => process.exit, - deno: async () => Deno.exit, - unknown: () => { - throw new UnimplementedError(`exit["${JSImpl}"]`); - }, - }, - tconf: { - qjs: async () => await mappings.testConf.unknown(), - deno: async () => await mappings.testConf.node(), - browser: async () => { - class DOMAssertionError extends Error {} - - const w = s => document.querySelector("#test-report").innerHTML += s; - - const red = s => `<span style="color: red; ">${s}</span>`; - const green = s => `<span style="color: green; ">${s}</span>`; - const yellow = s => `<span style="color: darkgoldenrod;">${s}</span>`; - return { - testStart: msg => w(`${msg}:\n`), - testing: msg => w(`${yellow("testing")}: ${msg}...`), - testOK: () => w(` ${green("OK")}.\n`), - assert: (x, msg = "") => { - if (!x) { - 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"); - - const err = x => process.stderr.write(x); - const red = s => `\x1b[31m${s}\x1b[0m`; - const green = s => `\x1b[32m${s}\x1b[0m`; - const yellow = s => `\x1b[33m${s}\x1b[0m`; - - return { - err, - assertEq: assert.deepEqual, - colors: { - red, - green, - yellow, - }, - }; - }, - unknown: () => { throw new UnimplementedError(`exit["${JSImpl}"]`); }, - }, -}; - -export const exit = await mappings.exit[ JSImpl](); -export const ARGV = await mappings.ARGV[ JSImpl](); -export const tconf = await mappings.tconf[JSImpl](); - -export const runTests = async tests => { - const tFinal = t(tconf); - for (const testFn of tests) { - await testFn(tFinal); - } -}; diff --git a/src/sjs.mjs b/src/sjs.mjs new file mode 100644 index 0000000..0b2c705 --- /dev/null +++ b/src/sjs.mjs @@ -0,0 +1,449 @@ +export const max = (a, b) => a > b ? a : b; +export const min = (a, b) => a < b ? a : b; + +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); + +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 => + `<span style="color: ${c}; ">${s}</span>`; + 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, + }, + }; + }, + }, +}; + +export const JSImpl = getJSImpl(); +export const exit = await mappings.exit[ JSImpl]?.(); +export const ARGV = await mappings.ARGV[ JSImpl]?.(); +const tconf = await mappings.tconf[JSImpl]?.(); + +export const runTests = async tests => { + const tFinal = t(tconf); + for (const testFn of tests) { + await testFn(tFinal); + } +}; |
