UNPKG

squint-cljs

Version:

<img src="./logo/logo.svg" width="100%">

1,699 lines (1,531 loc) 126 kB
/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_", "destructuredArrayIgnorePattern": "^_"}]*/ // __toFn is not public API - the leading underscores mark it as an // implementation helper shared with other squint runtime modules // (e.g. multi.js). Signature and semantics may change without notice. export function __toFn(x) { if (x == null || typeof x === 'function') return x; if (typeof x === 'string') return (coll, d) => get(coll, x, d); // a value is callable as a lookup only if it is a collection or a custom // type implementing ILookup; a seq, list or opaque object throws when // called, like a non-IFn in CLJS switch (typeConst(x)) { case MAP_TYPE: case ARRAY_TYPE: case OBJECT_TYPE: case SET_TYPE: return (k, d) => get(x, k, d); case INSTANCE_TYPE: if (x[ILookup__lookup] !== undefined) return (k, d) => get(x, k, d); } return x; } // inlined and modified version of https://github.com/lukeed/dequal var has = Object.prototype.hasOwnProperty; function findKey(iter, tar, key) { for (key of iter.keys()) { if (dequal(key, tar)) return key; } } function isSortedMap(m) { return m != null && m[SORTED_TAG] === true && m[TYPE_TAG] === MAP_TYPE; } function isSetLike(s) { return s != null && (s instanceof Set || s[TYPE_TAG] === SET_TYPE); } function isMapLike(m) { return ( m != null && typeof m === 'object' && (m.constructor === Object || m instanceof Map || m[TYPE_TAG] === MAP_TYPE) ); } function mapHas(m, k) { return m instanceof Map || m[TYPE_TAG] === MAP_TYPE ? m.has(k) : has.call(m, k); } function mapGet(m, k) { return m instanceof Map || m[TYPE_TAG] === MAP_TYPE ? m.get(k) : m[k]; } function mapCount(m) { return m instanceof Map || m[TYPE_TAG] === MAP_TYPE ? m.size : Object.keys(m).length; } // shared by dequal's fast path and fall-through. element-wise for arrays, own // enumerable keys otherwise; callers guarantee a shared ctor, no brand, no -equiv function dequalSameCtor(foo, bar, ctor) { var len; if (ctor === Array) { if ((len = foo.length) === bar.length) { while (len-- && dequal(foo[len], bar[len])); } return len === -1; } len = 0; for (const k in foo) { if (has.call(foo, k) && ++len && !has.call(bar, k)) return false; if (!(k in bar) || !dequal(foo[k], bar[k])) return false; } return Object.keys(bar).length === len; } function dequal(foo, bar) { // supports primitives, Array, Set, Map and plain objects // like CLJS: does not support NaN if (foo === bar) return true; // null and undefined are both nil in CLJS, so they compare equal if (foo == null) return bar == null; if (bar == null) return false; // a primitive is only equal by identity, checked above; bail before the // property checks below box it if (typeof foo !== 'object' || typeof bar !== 'object') return false; var ctor = foo.constructor, tmp; // same-constructor plain objects and arrays skip the protocol, sorted and // cross-type checks below; neither type can carry -equiv or a brand if (ctor === bar.constructor && (ctor === Object || ctor === Array)) { return dequalSameCtor(foo, bar, ctor); } // -equiv dispatches on the left argument, like CLJS = if (typeof foo === 'object' && foo[IEquiv__equiv] !== undefined) return !!foo[IEquiv__equiv](foo, bar); // when only the right side has -equiv (the left is e.g. a plain object), // dispatch on it so = stays symmetric if (typeof bar === 'object' && bar[IEquiv__equiv] !== undefined) return !!bar[IEquiv__equiv](bar, foo); // A sorted map compares by entries against any map type (object, Map, sorted). const fooSorted = isSortedMap(foo); if (fooSorted || isSortedMap(bar)) { const sm = fooSorted ? foo : bar; const other = sm === foo ? bar : foo; if (!isMapLike(other) || mapCount(sm) !== mapCount(other)) return false; for (const k of sm.keys()) { if (!mapHas(other, k) || !dequal(sm.get(k), mapGet(other, k))) return false; } return true; } // A plain object and a js/Map are both map reps; compare by entries. // Same-type pairs skip this and keep their fast paths below. if (isMapLike(foo) && isMapLike(bar) && foo.constructor !== bar.constructor) { if (mapCount(foo) !== mapCount(bar)) return false; for (const k of foo instanceof Map ? foo.keys() : Object.keys(foo)) { if (!mapHas(bar, k) || !dequal(mapGet(foo, k), mapGet(bar, k))) return false; } return true; } // Sets (hash or sorted) compare by elements, across concrete types. if (isSetLike(foo) || isSetLike(bar)) { if (!isSetLike(foo) || !isSetLike(bar) || foo.size !== bar.size) return false; for (let e of foo) { if (e && typeof e === 'object') { e = findKey(bar, e); if (!e) return false; } if (!bar.has(e)) return false; } return true; } if (foo && bar && ctor === bar.constructor) { if (ctor === Date) return foo.getTime() === bar.getTime(); // regexes only compare by identity, like CLJS if (ctor === RegExp) return false; // no Array branch: same-ctor arrays already returned via the fast path if (ctor === Map) { if (foo.size !== bar.size) { return false; } for (const kv of foo) { tmp = kv[0]; if (tmp && typeof tmp === 'object') { tmp = findKey(bar, tmp); if (!tmp) return false; } if (!dequal(kv[1], bar.get(tmp))) { return false; } } return true; } // LazyIterable falls through to the sequential-equality path below; it is an // object but must compare element-wise, not by enumerable properties if ((!ctor || typeof foo === 'object') && foo[TYPE_TAG] !== LAZY_ITERABLE_TYPE) { return dequalSameCtor(foo, bar, Object); } } // Cross-type sequential equality, like CLJS `(= '(1 2) [1 2])`: vectors, // lists and lazy seqs compare element-wise regardless of concrete type. Only // reached when the same-constructor paths above did not apply, so equal-typed // collections keep their fast paths. if ( foo && bar && (Array.isArray(foo) || foo[TYPE_TAG] === LAZY_ITERABLE_TYPE) && (Array.isArray(bar) || bar[TYPE_TAG] === LAZY_ITERABLE_TYPE) ) { const fi = foo[Symbol.iterator](); const bi = bar[Symbol.iterator](); for (;;) { const a = fi.next(); const b = bi.next(); if (a.done || b.done) return !!(a.done && b.done); if (!dequal(a.value, b.value)) return false; } } return false; } // end inlined version of dequals function walkArray(arr, comp) { return arr.every(function (x, i) { return i === 0 || comp(arr[i - 1], x); }); } export function _EQ_(...xs) { return walkArray(xs, (x, y) => dequal(x, y)); } export function _GT_(...xs) { return walkArray(xs, (x, y) => x > y); } export function _GT__EQ_(...xs) { return walkArray(xs, (x, y) => x >= y); } export function _LT_(...xs) { return walkArray(xs, (x, y) => x < y); } export function _LT__EQ_(...xs) { return walkArray(xs, (x, y) => x <= y); } export function _PLUS_(...xs) { return xs.reduce((x, y) => x + y, 0); } export function _STAR_(...xs) { return xs.reduce((x, y) => x * y, 1); } export function _(...xs) { if (xs.length == 1) { return 0 - xs[0]; } return xs.reduce((x, y) => x - y); } export function _SLASH_(...xs) { if (xs.length === 1) { return 1 / xs[0]; } return xs.reduce((x, y) => x / y); } export const __protocol_satisfies = {}; export function satisfies_QMARK_(protocol, x) { if (x == null) { return protocol[null]; } if (typeof protocol == 'symbol') return x[protocol]; return x[protocol.__sym]; } function mapAssocMut(m, k, v) { m.set(k, v); return m; } function objAssocMut(m, k, v) { m[k] = v; return m; } function getAssocMut(m) { switch (typeConst(m)) { case MAP_TYPE: return mapAssocMut; case ARRAY_TYPE: case OBJECT_TYPE: case INSTANCE_TYPE: return objAssocMut; } } function validateArrayKeys(o, k, kvs) { // like CLJS: a vector key is an index in [0, count], count appends let len = o.length; for (let i = 0; i < kvs.length + 2; i += 2) { const key = i === 0 ? k : kvs[i - 2]; if (!Number.isInteger(key)) { throw new Error("Vector's key for assoc must be a number."); } if (key < 0 || key > len) { throw new Error(`Index ${key} out of bounds [0,${len}]`); } if (key === len) len++; } } export function assoc_BANG_(m, k, v, ...kvs) { if (arguments.length < 3 || kvs.length % 2 !== 0) { throw new Error('Illegal argument: assoc expects an odd number of arguments.'); } switch (typeConst(m)) { case MAP_TYPE: m.set(k, v); for (let i = 0; i < kvs.length; i += 2) { m.set(kvs[i], kvs[i + 1]); } break; case ARRAY_TYPE: validateArrayKeys(m, k, kvs); m[k] = v; for (let i = 0; i < kvs.length; i += 2) { m[kvs[i]] = kvs[i + 1]; } break; case INSTANCE_TYPE: if (m[ITransientAssociative__assoc_BANG_] !== undefined) { // re-read the slot off the current value: an -assoc! impl may return a // different handle let ret = m[ITransientAssociative__assoc_BANG_](m, k, v); for (let i = 0; i < kvs.length; i += 2) { ret = ret[ITransientAssociative__assoc_BANG_](ret, kvs[i], kvs[i + 1]); } return ret; } // fall through: an instance without -assoc! keeps the object behavior case OBJECT_TYPE: m[k] = v; for (let i = 0; i < kvs.length; i += 2) { m[kvs[i]] = kvs[i + 1]; } break; default: throw new Error( `Illegal argument: assoc! expects a Map, Array, or Object as the first argument, but got ${typeof m}.` ); } return m; } // value-producing ops (copy, empty, conj, into) carry metadata by // forwarding the instance-level meta slots onto the fresh structure function copyMeta(from, to) { const f = from?.[IMeta__meta]; if (f !== undefined) { to[IMeta__meta] = f; to[IWithMeta__with_meta] = from[IWithMeta__with_meta]; } return to; } function copy(o) { switch (typeConst(o)) { case MAP_TYPE: // new o.constructor(o) preserves a SortedMap; for a plain Map it is new Map(o) return copyMeta(o, new o.constructor(o)); case SET_TYPE: return copyMeta(o, new o.constructor(o)); case ARRAY_TYPE: return copyMeta(o, [...o]); case INSTANCE_TYPE: case OBJECT_TYPE: return copyMeta(o, { ...o }); case LIST_TYPE: return copyMeta(o, new List(...o)); default: throw new Error(`Don't know how to copy object of type ${typeof o}.`); } } export function assoc(o, k, v, ...kvs) { if (arguments.length < 3 || kvs.length % 2 !== 0) { throw new Error('Illegal argument: assoc expects an odd number of arguments.'); } // only nil puns to an empty map; assoc on false throws, like CLJS if (o == null) { o = {}; } // plain objects and arrays never carry the slot: skip the lookup, like get if (!isObj(o) && !Array.isArray(o) && o[IAssociative__assoc] !== undefined) { let ret = o[IAssociative__assoc](o, k, v); for (let i = 0; i < kvs.length; i += 2) { ret = ret[IAssociative__assoc](ret, kvs[i], kvs[i + 1]); } return ret; } const ret = copy(o); assoc_BANG_(ret, k, v, ...kvs); return ret; } // squint has no distinct hash-map or array-map type; both build a plain object. export function hash_map(...kvs) { if (kvs.length === 0) return {}; if (kvs.length % 2 !== 0) { throw new Error('No value supplied for key: ' + kvs[kvs.length - 1]); } return assoc({}, ...kvs); } export const array_map = hash_map; // req! and some-vals are clojure.core fns added for 1.13 destructuring. // Ported from Clojure (clojure/core.clj), Copyright (c) Rich Hickey and // contributors, Eclipse Public License 1.0. const REQ_NOT_FOUND = {}; // Like arity-2 get, but throws if key not present. export function req_BANG_(m, k) { const v = get(m, k, REQ_NOT_FOUND); if (v === REQ_NOT_FOUND) throw new Error('Missing required key: ' + k); return v; } // Returns a map with only the non-nil values of m, or nil if there are none. export function some_vals(m) { if (m == null) return null; let ret = null; for (const [k, v] of iterable(m)) { if (v !== null && v !== undefined) { if (ret === null) ret = {}; ret[k] = v; } } return ret; } // https://clojure.org/reference/special_forms#keyword-arguments export function seq_to_map_for_destructuring(s) { const arr = Array.isArray(s) ? s : [...iterable(s)]; const n = arr.length; if (n < 2) return n ? arr[0] : {}; const m = {}; for (let i = 0; i < n; i += 2) { // an odd count leaves a trailing map if (i === n - 1) for (const [k, v] of iterable(arr[i])) m[k] = v; else m[arr[i]] = arr[i + 1]; } return m; } const MAP_TYPE = 1; const ARRAY_TYPE = 2; const OBJECT_TYPE = 3; const LIST_TYPE = 4; const SET_TYPE = 5; const LAZY_ITERABLE_TYPE = 6; // a class instance or null-prototype object: the extension point for the // map-facing protocols. Plain objects keep the OBJECT_TYPE fast path. const INSTANCE_TYPE = 7; // type tag set in each collection ctor, read by typeConst (DCE: no instanceof). const TYPE_TAG = /* @__PURE__ */ Symbol('squint.lang.type'); const SORTED_TAG = /* @__PURE__ */ Symbol('squint.lang.sorted'); // @__NO_SIDE_EFFECTS__ lets a bundler drop unused defclass/withApply calls; see doc/dev/dce.md // @__NO_SIDE_EFFECTS__ function defclass(c) { return c; } // @__NO_SIDE_EFFECTS__ function withApply(f, applyFn) { f.squint$lang$variadic = applyFn; return f; } function emptyOfType(type) { switch (type) { case MAP_TYPE: return new Map(); case ARRAY_TYPE: return []; case OBJECT_TYPE: return {}; // Object.create? case LIST_TYPE: return new List(); case SET_TYPE: return new Set(); case LAZY_ITERABLE_TYPE: return lazy(function* () { return; }); } return undefined; } function isObj(coll) { return coll.constructor === Object; } function isVectorArray(x) { return Array.isArray(x) && x[TYPE_TAG] !== LIST_TYPE; } export function object_QMARK_(coll) { return coll != null && isObj(coll); } function typeConst(obj) { if (obj == null) { return undefined; } // optimize for object if (isObj(obj)) { return OBJECT_TYPE; } if (obj instanceof Map) return MAP_TYPE; if (obj instanceof Set) return SET_TYPE; // brand, not instanceof, so dispatch does not reference the classes const tag = obj[TYPE_TAG]; if (tag !== undefined) return tag; if (isVectorArray(obj)) return ARRAY_TYPE; // any remaining object (class instance, null-proto) is associative if (typeof obj === 'object') return INSTANCE_TYPE; return undefined; } function assoc_in_with(f, fname, o, keys, value) { keys = vec(keys); o = o || {}; // default nil behavior is JS object const baseType = typeConst(o); if (baseType !== MAP_TYPE && baseType !== ARRAY_TYPE && baseType !== OBJECT_TYPE && baseType !== INSTANCE_TYPE) throw new Error( `Illegal argument: ${fname} expects the first argument to be a Map, Array, or Object.` ); const chain = [o]; let lastInChain = o; for (let i = 0; i < keys.length - 1; i += 1) { const k = keys[i]; let chainValue; if (lastInChain instanceof Map) chainValue = lastInChain.get(k); else if (lastInChain != null && lastInChain[ILookup__lookup] !== undefined) { chainValue = lastInChain[ILookup__lookup](lastInChain, k, undefined); } else chainValue = lastInChain[k]; if (!chainValue) { // an instance root has no empty-of-type: missing levels become plain maps chainValue = emptyOfType(baseType) ?? {}; } chain.push(chainValue); lastInChain = chainValue; } chain.push(value); for (let i = chain.length - 2; i >= 0; i -= 1) { chain[i] = f(chain[i], keys[i], chain[i + 1]); } return chain[0]; } export function assoc_in(o, keys, value) { return assoc_in_with(assoc, 'assoc-in', o, keys, value); } export function assoc_in_BANG_(o, keys, value) { keys = vec(keys); var currObj = o; const baseType = typeConst(o); for (const k of keys.splice(0, keys.length - 1)) { let v = get(currObj, k); if (v === undefined) { v = emptyOfType(baseType); assoc_BANG_(currObj, k, v); } currObj = v; } assoc_BANG_(currObj, keys[keys.length - 1], value); return o; } export function comp(...fs) { fs = fs.map(__toFn); if (fs.length === 0) { return identity; } else if (fs.length === 1) { return fs[0]; } const [f, ...more] = fs.slice().reverse(); return function (...args) { let x = f(...args); for (const g of more) { x = g(x); } return x; }; } function conj_BANG_set(o, rest) { for (const x of rest) { o.add(x); } return o; } export function conj_BANG_(...xs) { const n = xs.length; if (n === 0) { return vector(); } // single arg: return the coll unchanged, including nil, like CLJS if (n === 1) { return xs[0]; } let o = xs[0]; if (o === null || o === undefined) { o = []; } // Fast path for the common single-element conj! onto an array or set, // avoiding the rest-array allocation and spread. if (n === 2) { switch (typeConst(o)) { case ARRAY_TYPE: o.push(xs[1]); return o; case SET_TYPE: o.add(xs[1]); return o; } } const rest = xs.slice(1); switch (typeConst(o)) { case SET_TYPE: conj_BANG_set(o, rest); break; case LIST_TYPE: o.unshift(...rest.reverse()); break; case ARRAY_TYPE: o.push(...rest); break; case MAP_TYPE: for (const x of rest) { if (isVectorArray(x)) { asMapEntry(x); o.set(x[0], x[1]); } else for (const kv of mapEntriesOf(x)) o.set(kv[0], kv[1]); } break; case INSTANCE_TYPE: if (o[ITransientCollection__conj_BANG_] !== undefined) { // re-dispatch per element: a -conj! impl may return a different handle let acc = o[ITransientCollection__conj_BANG_](o, rest[0]); for (let i = 1; i < rest.length; i++) acc = conj_BANG_(acc, rest[i]); return acc; } // fall through: an instance without -conj! keeps the object behavior case OBJECT_TYPE: for (const x of rest) { if (isVectorArray(x)) { asMapEntry(x); o[x[0]] = x[1]; } else for (const kv of mapEntriesOf(x)) o[kv[0]] = kv[1]; } break; default: throw new Error( 'Illegal argument: conj! expects a Set, Array, List, Map, or Object as the first argument.' ); } return o; } // entries carried by a non-entry conj arg onto a map: a map merges, a // seqable must contain entry vectors, like CLJS function* mapEntriesOf(x) { if (isMapLike(x)) { yield* iterable(x); return; } for (const kv of iterable(x)) { if (!isVectorArray(kv)) { throw new Error('conj on a map takes map entries or seqables of map entries'); } yield kv; } } function asMapEntry(x) { if (x.length < 2) { throw new Error('Vector arg to map conj must be a pair'); } return x; } export function conj(...xs) { if (xs.length === 0) { return vector(); } const [_o, ...rest] = xs; // (conj coll) with nothing to add returns coll unchanged, including nil. if (rest.length === 0) return _o; let o = _o; if (o === null || o === undefined) { o = list(); } let m, o2; switch (typeConst(o)) { case SET_TYPE: // brand, not instanceof, so conj does not pin SortedSet if (o[SORTED_TAG]) { // prevent re-sorting of collection return copyMeta(o, conj_BANG_set(new o.constructor(o), rest)); } else { return copyMeta(o, new o.constructor([...o, ...rest])); } case LIST_TYPE: return copyMeta(o, new List(...rest.reverse(), ...o)); case ARRAY_TYPE: return copyMeta(o, [...o, ...rest]); case MAP_TYPE: m = new Map(o); for (const x of rest) { if (isVectorArray(x)) { asMapEntry(x); m.set(x[0], x[1]); } else for (const kv of mapEntriesOf(x)) m.set(kv[0], kv[1]); } return copyMeta(o, m); case LAZY_ITERABLE_TYPE: return lazy(function* () { yield* rest; yield* o; }); case INSTANCE_TYPE: if (o[ICollection__conj] !== undefined) { // re-dispatch per element: a -conj impl may return a different type o2 = o[ICollection__conj](o, rest[0]); for (let i = 1; i < rest.length; i++) o2 = conj(o2, rest[i]); return o2; } // fall through: an instance without -conj keeps the object behavior case OBJECT_TYPE: o2 = { ...o }; for (const x of rest) { if (isVectorArray(x)) { asMapEntry(x); o2[x[0]] = x[1]; } else for (const kv of mapEntriesOf(x)) o2[kv[0]] = kv[1]; } return copyMeta(o, o2); default: throw new Error( 'Illegal argument: conj expects a Set, Array, List, Map, or Object as the first argument.' ); } } export function disj_BANG_(s, ...xs) { if (s != null && s[ITransientSet__disjoin_BANG_] !== undefined) { let ret = s; for (const x of xs) { ret = ret != null && ret[ITransientSet__disjoin_BANG_] !== undefined ? ret[ITransientSet__disjoin_BANG_](ret, x) : disj_BANG_(ret, x); } return ret; } for (const x of xs) { s.delete(x); } return s; } export function disj(s, ...xs) { if (s == null) return s; if (xs.length === 0) return s; if (s[ISet__disjoin] !== undefined) { let ret = s[ISet__disjoin](s, xs[0]); for (let i = 1; i < xs.length; i++) ret = disj(ret, xs[i]); return ret; } // pass s itself (not a spread) so a SortedSet keeps its comparator const s1 = new s.constructor(s); return copyMeta(s, disj_BANG_(s1, ...xs)); } export function contains_QMARK_(coll, v) { if (typeof coll === 'string') { return int_QMARK_(v) && v >= 0 && v < coll.length; } switch (typeConst(coll)) { case SET_TYPE: case MAP_TYPE: return coll.has(v); case undefined: return false; case INSTANCE_TYPE: if (coll[IAssociative__contains_key_QMARK_] !== undefined) { return coll[IAssociative__contains_key_QMARK_](coll, v); } // fall through default: return v in coll; } } export function dissoc_BANG_(m, ...ks) { if (m != null && m[ITransientMap__dissoc_BANG_] !== undefined) { let ret = m; for (const k of ks) ret = ret != null && ret[ITransientMap__dissoc_BANG_] !== undefined ? ret[ITransientMap__dissoc_BANG_](ret, k) : dissoc_BANG_(ret, k); return ret; } for (const k of ks) { delete m[k]; } return m; } export function dissoc(m, ...ks) { if (!m) return; if (ks.length === 0) return m; const tc = typeConst(m); if (tc !== MAP_TYPE && tc !== OBJECT_TYPE && tc !== INSTANCE_TYPE) { throw new Error('dissoc expects a map, got: ' + typeof m); } if (tc === INSTANCE_TYPE && m[IMap__dissoc] !== undefined) { let ret = m; // re-dispatch per key: a -dissoc impl may return a different type for (const k of ks) { if (ret == null) return ret; ret = ret[IMap__dissoc] !== undefined ? ret[IMap__dissoc](ret, k) : dissoc(ret, k); } return ret; } if (tc === MAP_TYPE) { let present = false; for (const k of ks) if (m.has(k)) { present = true; break; } if (!present) return m; const m2 = copy(m); for (const k of ks) m2.delete(k); return m2; } let present = false; for (const k of ks) if (k in m) { present = true; break; } if (!present) return m; const m2 = copy(m); for (const k of ks) delete m2[k]; return m2; } export function inc(n) { return n + 1; } export function dec(n) { return n - 1; } export const _STAR_print_newline_STAR_ = { val: false }; export const _STAR_print_fn_STAR_ = { val: (s) => console.log(s) }; export const _STAR_print_err_fn_STAR_ = { val: (s) => console.error(s) }; export function print(...args) { _STAR_print_fn_STAR_.val(args.map((v) => toEDN(v, undefined, false)).join(' ')); } export function println(...args) { print(...args); if (_STAR_print_newline_STAR_.val) _STAR_print_fn_STAR_.val('\n'); } export function print_str(...args) { return args.map((v) => toEDN(v, undefined, false)).join(' '); } export function println_str(...args) { return print_str(...args) + '\n'; } export function pr(...xs) { _STAR_print_fn_STAR_.val(pr_str(...xs)); } export function nth(coll, idx, orElse) { if (typeof idx !== 'number') { throw new Error('Index argument to nth must be a number'); } const hasDefault = arguments.length > 2; // nil coll puns to nil, like Clojure if (coll == null) return hasDefault ? orElse : null; // "found" is decided by the index bound, not the value. An in-bounds element // that happens to be undefined is still found. if (Array.isArray(coll)) { if (idx >= 0 && idx < coll.length) { return coll[idx]; } } else if (coll[IIndexed__nth] !== undefined) { return hasDefault ? coll[IIndexed__nth](coll, idx, orElse) : coll[IIndexed__nth](coll, idx); } else if (idx >= 0) { // non-array: skip whole chunks instead of counting elements (handles // infinite seqs since it stops once idx is reached) const next = chunkCursor(coll); let base = 0; let ch; while ((ch = next()) !== null) { if (idx < base + ch.length) return ch[idx - base]; base += ch.length; } } // out of bounds. With a default return it, otherwise throw like Clojure if (hasDefault) return orElse; throw new Error('Index out of bounds: ' + idx); } export function get(coll, key, otherwise = undefined) { if (coll == null) { return otherwise; } let v; // optimize for getting values out of objects if (isObj(coll)) { v = coll[key]; if (v === undefined) { return otherwise; } else { return v; } } let g; switch (typeConst(coll)) { case SET_TYPE: if (coll.has(key)) v = key; break; case MAP_TYPE: v = coll.get(key); break; case ARRAY_TYPE: v = coll[key]; break; default: if (coll[ILookup__lookup] !== undefined) { v = coll[ILookup__lookup](coll, key, otherwise); return v === undefined ? otherwise : v; } // we choose .get as the default implementation, e.g. fetch Headers are not Maps, but do implement a .get method g = coll['get']; if (typeof g === 'function') { try { v = coll.get(key); break; } catch (e) { // ignore error } } v = coll[key]; break; } return v !== undefined ? v : otherwise; } export function seq_QMARK_(x) { return x != null && !!x[Symbol.iterator]; } export function sequential_QMARK_(x) { // vectors and lists are arrays; lazy seqs and cons carry the lazy brand. // Sets, maps and strings are iterable but not sequential. return Array.isArray(x) || x?.[TYPE_TAG] === LAZY_ITERABLE_TYPE || (x != null && x[IVector.__sym] !== undefined); } export function seqable_QMARK_(x) { return ( x === null || x === undefined || // plain objects (squint maps) are seqable via Object.entries in `iterable`, // even though they lack Symbol.iterator. object_QMARK_(x) || // we used to check instanceof Object but this returns false for TC39 Records // also we used to write `Symbol.iterator in` but this does not work for strings and some other types !!x[Symbol.iterator] || !!x[ISEQABLE_SYM] ); } // squint has no distinct MapEntry type (map entries are plain 2-element // arrays). We tag entries produced from a map with this marker symbol so // map-entry? can tell them apart from ordinary vectors. Symbol-keyed props are // invisible to =, into, iteration and JSON, so the effect is contained. const MAP_ENTRY = /* @__PURE__ */ Symbol('squint.lang.map-entry'); function tagMapEntry(e) { e[MAP_ENTRY] = true; return e; } export function map_entry_QMARK_(x) { return Array.isArray(x) && x[MAP_ENTRY] === true; } export function iterable(x) { // nil puns to empty iterable, support passing nil to first/rest/reduce, etc. if (x === null || x === undefined) { return []; } // fast path: anything with Symbol.iterator (arrays, strings, sets, maps, // lazy seqs). Inlined rather than calling seqable?, which also reports plain // objects as seqable; those are handled by the Object.entries branch below. if (x[Symbol.iterator]) { return x; } // a type extended to ISeqable seqs through its -seq method if (x[ISeqable__seq] !== undefined) return iterable(x[ISeqable__seq](x)); // only a plain object is a squint map; a class instance without a native // iterator or ISeqable is not iterable, matching seqable? and CLJS, and // never leaks its internal fields if (isObj(x)) return Object.entries(x).map(tagMapEntry); throw new TypeError(`${x} is not iterable`); } export const IIterable = /* @__PURE__ */ Symbol('Iterable'); export const IIterable__iterator = Symbol.iterator; export function _iterator(coll) { return coll[Symbol.iterator](); } export const es6_iterator = _iterator; export function seq(x) { if (x == null) return x; if (!seqable_QMARK_(x)) throw new TypeError(x + ' is not ISeqable'); // a string seqs into its characters, like CLJS. if (typeof x === 'string') return x.length ? [...x] : null; const iter = iterable(x); // return nil for terminal checking if (iter.length === 0 || iter.size === 0) { return null; } // a set or map is iterable but not a sequence; materialize its entries // into a distinct seq so the result is not itself a set or map, like CLJS if (iter instanceof Set || iter[TYPE_TAG] === SET_TYPE) { return [...iter]; } if (iter instanceof Map || iter[TYPE_TAG] === MAP_TYPE) { return [...iter].map(tagMapEntry); } // an instance with -dissoc is a map rep: same distinct entry seq if (iter[IMap__dissoc] !== undefined) { const entries = [...iter].map(tagMapEntry); return entries.length === 0 ? null : entries; } const _i = iter[Symbol.iterator](); if (_i.next().done) return null; return iter; } export function first(coll) { if (coll == null) return undefined; if (Array.isArray(coll)) return coll[0]; if (coll instanceof LazyIterable) { coll.force(); return coll.chunk === null ? undefined : coll.chunk[0]; } // destructuring uses iterable protocol const [first] = iterable(coll); return first; } export function second(coll) { if (coll instanceof LazyIterable) { coll.force(); const ch = coll.chunk; if (ch === null) return undefined; return ch.length > 1 ? ch[1] : first(coll._rest); } const [_, v] = iterable(coll); return v; } export function ffirst(coll) { return first(first(coll)); } export function fnext(coll) { return first(next(coll)); } export function nfirst(coll) { return next(first(coll)); } export function rest(coll) { // chunk-aware: drop the first element of the first chunk, keep the rest of // the chain chunked (preserves chunkedness, unlike re-iterating element-wise) const cell = chunkCells(coll); cell.force(); const ch = cell.chunk; if (ch === null) return cell; // (rest ()) is () if (ch.length > 1) { const c = new LazyIterable(null); c.realized = true; c.chunk = ch.slice(1); c._rest = cell._rest; return c; } return cell._rest; // first chunk had one element; the next cell is the rest } const REDUCED_DEREF = (self) => self.value; class Reduced { value; constructor(x) { this.value = x; this[IDeref__deref] = REDUCED_DEREF; } } export function last(coll) { coll = iterable(coll); if (Array.isArray(coll)) { return coll[coll.length - 1]; } // non-array: walk chunks, keep the last chunk's last element const next = chunkCursor(coll); let lastEl; let ch; while ((ch = next()) !== null) lastEl = ch[ch.length - 1]; return lastEl; } export function reduced(x) { return new Reduced(x); } export function reduced_QMARK_(x) { return x instanceof Reduced; } export function reduce(f, arg1, arg2) { f = __toFn(f); const hasInit = arguments.length !== 2; const coll = hasInit ? arg2 : arg1; let val = hasInit ? arg1 : undefined; // fast path: index loop over an array if (Array.isArray(coll)) { let i = 0; if (!hasInit) { if (coll.length === 0) return f(); val = coll[0]; i = 1; } if (val instanceof Reduced) return val.value; for (; i < coll.length; i++) { val = f(val, coll[i]); if (val instanceof Reduced) return val.value; } return val; } // non-array: walk chunks (chunked cell or any other seqable) const next = chunkCursor(coll); let ch = next(); let i = 0; if (!hasInit) { if (ch === null) return f(); val = ch[0]; i = 1; } if (val instanceof Reduced) return val.value; while (ch !== null) { for (; i < ch.length; i++) { val = f(val, ch[i]); if (val instanceof Reduced) return val.value; } ch = next(); i = 0; } return val; } function* _reductions2(f, s) { const vd = s.next(); if (vd.done) { yield f(); } else { yield* _reductions3(f, vd.value, s); } } function* _reductions3(f, init, coll) { let i = init; const rst = coll; while (true) { if (reduced_QMARK_(i)) { yield i.value; return; } else yield i; const vd = rst.next(); if (vd.done) { break; } i = f(i, vd.value); } } export function reductions(f, arg1, arg2) { f = __toFn(f); if (arguments.length === 2) { const it = es6_iterator(iterable(arg1)); return lazy(function* () { yield* _reductions2(f, it); }); } const it = es6_iterator(iterable(arg2)); return lazy(function* () { yield* _reductions3(f, arg1, it); }); } // Deprecated: lazy values are now cached, so reuse no longer recomputes. Kept // for API compatibility; remove in a future release. export function warn_on_lazy_reusage_BANG_() { console.warn( 'warn-on-lazy-reusage! is deprecated and does nothing: lazy values are now cached.', ); } const CHUNK_SIZE = 32; // One cell of a self-caching chunked seq. `step` is a thunk returning // [nonEmptyChunkArray, nextStep] or null at the end. See doc/dev/lazy-seqs.md. const LazyIterable = defclass( class LazyIterable { constructor(step) { this[TYPE_TAG] = LAZY_ITERABLE_TYPE; this[IIterable] = true; // Closure compatibility this.step = step; this.realized = false; this.chunk = null; // array, or null when this is the terminal (empty) cell this._rest = null; } force() { if (!this.realized) { this.realized = true; const r = this.step(); this.step = null; if (r !== null && r !== undefined) { this.chunk = r[0]; this._rest = new LazyIterable(r[1]); } } return this; } [Symbol.iterator]() { let cell = this; let i = 0; return { next() { for (;;) { cell.force(); const ch = cell.chunk; if (ch === null) return { value: undefined, done: true }; if (i < ch.length) return { value: ch[i++], done: false }; cell = cell._rest; i = 0; } }, [Symbol.iterator]() { return this; }, }; } // Mirrors Array.prototype.indexOf so lazy seqs support (.indexOf coll x): // reference equality, returns -1 when absent. Unlike cljs.core, not by value. indexOf(x, fromIndex = 0) { let i = 0; for (const v of this) { if (i >= fromIndex && v === x) return i; i++; } return -1; } } ); // One-element chunks: an unchunked seq, realized one element at a time. function unchunkedSteps(iter) { const step = () => { const r = iter.next(); return r.done ? null : [[r.value], step]; }; return step; } // f is a zero-arg generator function; its result is an unchunked lazy seq. export function lazy(f) { return new LazyIterable(unchunkedSteps(f())); } // gen(it) over a hoisted iterator: keeps the input head unpinned (streaming) function lazyIter(coll, gen) { const it = es6_iterator(iterable(coll)); return lazy(() => gen(it)); } // A chunked view of any seqable for chunk-aware ops, preserving chunkedness: // cells pass through, arrays slice into CHUNK_SIZE batches, others stay unchunked. function chunkCells(coll) { if (coll instanceof LazyIterable) return coll; if (Array.isArray(coll)) { const step = (pos) => () => { if (pos >= coll.length) return null; const end = Math.min(pos + CHUNK_SIZE, coll.length); return [coll.slice(pos, end), step(end)]; }; return new LazyIterable(step(0)); } return new LazyIterable(unchunkedSteps(es6_iterator(iterable(coll)))); } // A cursor over a seq's chunks for realizers: returns a function yielding the // next chunk array or null. Drive it with an inline loop (keeps an accumulator a // plain local, unlike a callback). Callers keep their own array shortcut. function chunkCursor(coll) { if (coll instanceof LazyIterable) { let cell = coll; return () => { if (cell === null) return null; cell.force(); const ch = cell.chunk; cell = ch === null ? null : cell._rest; return ch; }; } const it = es6_iterator(iterable(coll)); return () => { const b = []; for (let i = 0; i < CHUNK_SIZE; i++) { const r = it.next(); if (r.done) break; b.push(r.value); } return b.length === 0 ? null : b; }; } // Build a lazy seq transforming each input chunk via xf(chunk, baseIndex) -> new // chunk array, preserving chunkedness. Empty results are skipped (e.g. filter). // baseIndex is the input element count before the chunk, for indexed ops. function mapChunks(coll, xf) { const src = chunkCells(coll); const step = (cell, base) => () => { let c = cell; let b = base; for (;;) { c.force(); const ch = c.chunk; if (ch === null) return null; const out = xf(ch, b); const rest = c._rest; b += ch.length; if (out.length !== 0) return [out, step(rest, b)]; c = rest; } }; return new LazyIterable(step(src, 0)); } export const Cons = defclass( class Cons { constructor(x, coll) { this[TYPE_TAG] = LAZY_ITERABLE_TYPE; this.x = x; this.coll = coll; } [Symbol.iterator]() { const x = this.x; let coll = this.coll; let started = false; let it = null; return { next() { if (!started) { started = true; return { value: x, done: false }; } if (!it) { it = es6_iterator(iterable(coll)); coll = null; // release the tail head so a single pass streams } return it.next(); }, [Symbol.iterator]() { return this; }, }; } } ); export function cons(x, coll) { // like CLJS cons, which seqs a non-ISeq tail if (!seqable_QMARK_(coll)) throw new TypeError(coll + ' is not ISeqable'); return new Cons(x, coll); } export function map(f, ...colls) { f = __toFn(f); switch (colls.length) { case 0: return (rf) => { return (...args) => { switch (args.length) { case 0: { return rf(); } case 1: { return rf(args[0]); } case 2: { return rf(args[0], f(args[1])); } default: { return rf(args[0], f(...args.slice(1))); } } }; }; case 1: return mapChunks(colls[0], (ch) => { const out = new Array(ch.length); for (let i = 0; i < ch.length; i++) out[i] = f(ch[i]); return out; }); default: { const iters = colls.map((coll) => es6_iterator(iterable(coll))); return lazy(function* () { while (true) { const args = []; for (const i of iters) { const nextVal = i.next(); if (nextVal.done) { return; } args.push(nextVal.value); } yield f(...args); } }); } } } // 0/1 arities pass through to rf; step(rf) is the 2-arity reducer function transducer(step) { return (rf) => { const s = step(rf); return (...args) => args.length === 0 ? rf() : args.length === 1 ? rf(args[0]) : s(args[0], args[1]); }; } function filter1(pred) { return transducer((rf) => (r, x) => (truth_(pred(x)) ? rf(r, x) : r)); } export function filter(pred, coll) { if (arguments.length === 1) { return filter1(pred); } pred = __toFn(pred); return mapChunks(coll, (ch) => { const out = []; for (let i = 0; i < ch.length; i++) { const x = ch[i]; if (truth_(pred(x))) out.push(x); } return out; }); } export function filterv(pred, coll) { // filter is chunked; vec bulk-appends its chunks return pushAll([], filter(pred, coll)); } export function random_sample(prob, coll) { if (arguments.length === 1) { return filter((_) => rand() < prob); } return filter((_) => rand() < prob, coll); } export function remove(pred, coll) { if (arguments.length === 1) { return filter1(complement(pred)); } return filter(complement(pred), coll); } function map_indexed1(f) { return transducer((rf) => { let i = -1; return (r, x) => rf(r, f(++i, x)); }); } export function map_indexed(f, coll) { f = __toFn(f); if (arguments.length === 1) { return map_indexed1(f); } return mapChunks(coll, (ch, base) => { const out = new Array(ch.length); for (let i = 0; i < ch.length; i++) out[i] = f(base + i, ch[i]); return out; }); } function keep_indexed2(f, coll) { f = __toFn(f); return mapChunks(coll, (ch, base) => { const out = []; for (let i = 0; i < ch.length; i++) { const v = f(base + i, ch[i]); if (truth_(v)) out.push(v); } return out; }); } function keep_indexed1(f) { return transducer((rf) => { let ia = -1; return (r, x) => { const v = f(++ia, x); return v == null ? r : rf(r, v); }; }); } export function keep_indexed(f, coll) { if (arguments.length === 1) { return keep_indexed1(f); } else { return keep_indexed2(f, coll); } } export function str(...xs) { return xs.join(''); } export function name(x) { if (typeof x === 'string') { // keywords/symbols are strings in squint; name is the part after the "/" // ns separator (consistent with `namespace`, which returns the part before) const i = x.indexOf('/'); return i >= 1 ? x.slice(i + 1) : x; } throw new Error("Doesn't support name: " + typeof x); } export function not(expr) { return !truth_(expr); } export function nil_QMARK_(v) { return v == null; } export const PROTOCOL_SENTINEL = {}; // marker protocols so (satisfies? IAtom x) works, like CLJS. Marked in the // constructor, not on the prototype, so no top-level mutation pins Atom // into bundles that do not use it. const IATOM_SYM = /* @__PURE__ */ Symbol('squint.core.IAtom'); const IDEREF_SYM = /* @__PURE__ */ Symbol('squint.core.IDeref'); const ISEQABLE_SYM = /* @__PURE__ */ Symbol('squint.core.ISeqable'); export const IAtom = { __sym: IATOM_SYM }; export const IDeref = { __sym: IDEREF_SYM }; // method slot for (-deref x), named like the defprotocol emission so // (extend-type T IDeref (-deref [x] ...)) fills it export const IDeref__deref = /* @__PURE__ */ Symbol('IDeref_-deref'); export function _deref(o) { if (o != null && o[IDeref__deref] !== undefined) return o[IDeref__deref](o); return nilImpl(_deref, 'IDeref.-deref', o)(o); } export const ISeqable = { __sym: ISEQABLE_SYM }; export const ISeqable__seq = /* @__PURE__ */ Symbol('ISeqable_-seq'); // map-facing protocols. Each dispatches through its slot in the extension // path (INSTANCE_TYPE) of the corresponding core fn, so plain objects and // arrays never pay for them. Slot symbols are separate consts so a bundle // using only e.g. conj pulls one symbol, not the whole protocol set. export const ILookup = { __sym: /* @__PURE__ */ Symbol('squint.core.ILookup') }; export const ILookup__lookup = /* @__PURE__ */ Symbol('ILookup_-lookup'); export const IAssociative = { __sym: /* @__PURE__ */ Symbol('squint.core.IAssociative') }; export const IAssociative__assoc = /* @__PURE__ */ Symbol('IAssociative_-assoc'); export const IAssociative__contains_key_QMARK_ = /* @__PURE__ */ Symbol('IAssociative_-contains-key?'); export const IMap = { __sym: /* @__PURE__ */ Symbol('squint.core.IMap') }; export const IMap__dissoc = /* @__PURE__ */ Symbol('IMap_-dissoc'); export const ICounted = { __sym: /* @__PURE__ */ Symbol('squint.core.ICounted') }; export const ICounted__count = /* @__PURE__ */ Symbol('ICounted_-count'); export const IKVReduce = { __sym: /* @__PURE__ */ Symbol('squint.core.IKVReduce') }; export const IKVReduce__kv_reduce = /* @__PURE__ */ Symbol('IKVReduce_-kv-reduce'); export const ICollection = { __sym: /* @__PURE__ */ Symbol('squint.core.ICollection') }; export const ICollection__conj = /* @__PURE__ */ Symbol('ICollection_-conj'); export const IEmptyableCollection = { __sym: /* @__PURE__ */ Symbol('squint.core.IEmptyableCollection') }; export const IEmptyableCollection__empty = /* @__PURE__ */ Symbol('IEmptyableCollection_-empty'); export const IEquiv = { __sym: /* @__PURE__ */ Symbol('squint.core.IEquiv') }; export const IEquiv__equiv = /* @__PURE__ */ Symbol('IEquiv_-equiv'); // set and transient protocols, same extension-path dispatch as the map-facing // protocols above export const ISet = { __sym: /* @__PURE__ */ Symbol('squint.core.ISet') }; export const ISet__disjoin = /* @__PURE__ */ Symbol('ISet_-disjoin'); export const IEditableCollection = { __sym: /* @__PURE__ */ Symbol('squint.core.IEditableCollection') }; export const IEditableCollection__as_transient = /* @__PURE__ */ Symbol('IEditableCollection_-as-transient'); export const ITransientCollection = { __sym: /* @__PURE__ */ Symbol('squint.core.ITransientCollection') }; export const ITransientCollection__conj_BANG_ = /* @__PURE__ */ Symbol('ITransientCollection_-conj!'); export const ITransientCollection__persistent_BANG_ = /* @__PURE__ */ Symbol('ITransientCollection_-persistent!'); export const ITransientAssociative = { __sym: /* @__PURE__ */ Symbol('squint.core.ITransientAssociative') }; export const ITransientAssociative__assoc_BANG_ = /* @__PURE__ */ Symbol('ITransientAssociative_-assoc!'); export const ITransientMap = { __sym: /* @__PURE__ */ Symbol('squint.core.ITransientMap') }; export const ITransientMap__dissoc_BANG_ = /* @__PURE__ */ Symbol('ITransientMap_-dissoc!'); export const ITransientSet = { __sym: /* @__PURE__ */ Symbol('squint.core.ITransientSet') }; export const ITransientSet__disjoin_BANG_ = /* @__PURE__ */ Symbol('ITransientSet_-disjoin!'); // metadata protocols, like CLJS: types implement the slots, plain values // get instance-level impls installed by with-meta export const IMeta = { __sym: /* @__PURE__ */ Symbol('squint.core.IMeta') }; export const IMeta__meta = /* @__PURE__ */ Symbol('IMeta_-meta'); export const IWithMeta = { __sym: /* @__PURE__ */ Symbol('squint.core.IWithMeta') }; export const IWithMeta__with_meta = /* @__PURE__ */ Symbol('IWithMeta_-with-meta'); // hashing (Murmur3, like CLJS). The contract: (= a b) implies (hash a) === // (hash b), where = is dequal. None of this is referenced by =, so bundles // that only compare pay nothing. // Ported from ClojureScript (cljs/core.cljs), Copyright (c) Rich Hickey and // contributors, Eclipse Public License 1.0. MurmurHash3 by Austin Appleby // (public domain). // IHash: a custom type opts into value hashing export const IHash = { __sym: /* @__PURE__ */ Symbol('squint.core.IHash') }; export const IHash__hash = /* @__PURE__ */ Symbol('IHash_-hash'); // the equality hashed collections key by: identical or -equiv, never a // deep compare like = export function equiv(x, y) { if (x === y) return true; if (x == null) return y == null; if (y == null) return false; // a primitive is only equal by identity, checked above; bail before the // slot checks below box it if (typeof x !== 'object' || typeof y !== 'object') return false; if (x[IEquiv__equiv] !== undefined) return !!x[IEquiv__equiv](x, y); if (y[IEquiv__equiv] !== undefined) return !!y[IEquiv__equiv](y, x); if (x instanceof Date && y instanceof Date) return x.getTime() === y.getTime(); return false; } // identity uid for reference-keyed values, like CLJS goog/getUid const UIDS = /* @__PURE__ */ new WeakMap(); let uidCounter = 0; function uid(o) { let id = UIDS.get(o); if (id === undefined) { id = ++uidCounter; UIDS.set(o, id); } return id; } const imul = Math.imul; const M3_C1 = 0xcc9e2d51 | 0; const M3_C2 = 0x1b873593 | 0; function rotl(x, n) { return (x << n) | (x >>> (32 - n)); } function m3MixK1(k1) { return imul(rotl(imul(k1 | 0, M3_C1), 15), M3_C2); } function m3MixH1(h1, k1) { return (imul(rotl((h1 | 0) ^ (k1 | 0), 13), 5) + (0xe6546b64 | 0)) | 0; } function m3Fmix(h1, len) { h1 = (h1 ^ len) | 0; h1 = (h1 ^ (h1 >>> 16)) | 0; h1 = imul(h1, 0x85ebca6b | 0); h1 = (h1 ^ (h1 >>> 13)) | 0; h1 = imul(h1, 0xc2b2ae35 | 0); return (h1 ^ (h1 >>> 16)) | 0; } function m3HashInt(x) { return x === 0 ? 0 : m3Fmix(m3MixH1(0, m3MixK1(x)), 4); } let stringHashCache = /* @__PURE__ */ Object.create(null); let stringHashCacheCount = 0; function hashStringRaw(s) { let h = 0; for (let i = 0; i < s.length; i++) h = (imul(31, h) + s.charCodeAt(i)) | 0; return h; } function hashString(s) { if (stringHas