summaryrefslogtreecommitdiff
path: root/src/sjs.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'src/sjs.mjs')
-rw-r--r--src/sjs.mjs449
1 files changed, 449 insertions, 0 deletions
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 = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ "'": '&#39;',
+ '"': '&quot;'
+};
+
+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);
+ }
+};