From 66f840e3ba8ea55b6d0bec56324e2a8a8ecbdf63 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 8 May 2025 23:54:32 -0700 Subject: [PATCH] remove circular dependencies --- src/Collection.js | 27 - src/CollectionImpl.js | 796 ------------- src/Immutable.js | 95 +- src/Iterator.js | 47 +- src/List.js | 837 ++++++-------- src/Map.js | 887 ++++----------- src/MapOrdered.js | 181 +++ src/Operations.js | 1004 ----------------- src/OrderedMap.js | 162 --- src/OrderedSet.js | 61 - src/Range.js | 240 ++-- src/Record.js | 239 +--- src/Repeat.js | 177 +-- src/Seq.js | 409 ++----- src/SeqArray.js | 87 ++ src/SeqObject.js | 100 ++ src/Set.js | 420 +++---- src/SetOrdered.js | 73 ++ src/Stack.js | 411 +++---- src/TrieUtils.ts | 7 - src/Universe.js | 217 ++++ src/collection/collection.js | 770 +++++++++++++ src/collection/collectionCastIndexedSeq.js | 60 + src/collection/collectionCastKeyedSeq.js | 60 + src/collection/collectionCastSetSeq.js | 50 + src/collection/collectionConcat.js | 128 +++ src/collection/collectionIndexed.js | 44 + src/collection/collectionIndexedSeq.js | 43 + .../collectionIndexedSeqFromCollection.js | 14 + src/collection/collectionKeyed.js | 36 + src/collection/collectionKeyedSeq.js | 41 + .../collectionKeyedSeqFromEntries.js | 87 ++ src/collection/collectionRecord.js | 179 +++ src/collection/collectionSeq.js | 125 ++ src/collection/collectionSet.js | 11 + src/collection/collectionX.js | 804 +++++++++++++ src/collection/kernelIndexed.js | 256 +++++ src/collection/kernelKeyed.js | 652 +++++++++++ src/const.js | 79 ++ src/factory/factory.js | 210 ++++ src/factory/factoryConcat.js | 46 + src/factory/factoryFlatten.js | 65 ++ src/factory/factoryFlip.js | 59 + src/factory/factoryMap.js | 41 + src/factory/factoryMax.js | 38 + src/factory/factoryReverse.js | 59 + src/factory/factorySlice.js | 115 ++ src/factory/factoryZipWith.js | 76 ++ src/fromJS.js | 28 +- src/functional/get.ts | 72 -- src/functional/getIn.ts | 41 - src/functional/has.ts | 28 - src/functional/hasIn.ts | 25 - src/functional/merge.js | 99 -- src/functional/remove.ts | 81 -- src/functional/removeIn.ts | 27 - src/functional/set.ts | 76 -- src/functional/setIn.ts | 28 - src/functional/update.ts | 112 -- src/functional/updateIn.ts | 208 ---- src/is.ts | 82 -- src/methods/README.md | 5 - src/methods/asImmutable.js | 3 - src/methods/asMutable.js | 5 - src/methods/deleteIn.js | 5 - src/methods/getIn.js | 5 - src/methods/hasIn.js | 5 - src/methods/merge.js | 48 - src/methods/mergeDeep.js | 9 - src/methods/mergeDeepIn.js | 9 - src/methods/mergeIn.js | 7 - src/methods/setIn.js | 5 - src/methods/toObject.js | 10 - src/methods/update.js | 7 - src/methods/updateIn.js | 5 - src/methods/wasAltered.js | 3 - src/methods/withMutations.js | 5 - src/predicates/isAssociative.ts | 25 - src/predicates/isCollection.ts | 27 - src/predicates/isImmutable.ts | 24 - src/predicates/isIndexed.ts | 27 - src/predicates/isKeyed.ts | 26 - src/predicates/isList.ts | 14 - src/predicates/isMap.ts | 16 - src/predicates/isOrdered.ts | 31 - src/predicates/isOrderedMap.ts | 12 - src/predicates/isOrderedSet.ts | 12 - src/predicates/isRecord.ts | 14 - src/predicates/isSeq.ts | 19 - src/predicates/isSet.ts | 16 - src/predicates/isStack.ts | 14 - src/predicates/isValueObject.ts | 18 - src/probe.js | 563 +++++++++ src/toJS.js | 11 +- src/transformToMethods.js | 100 ++ src/util.js | 149 +++ src/utils/arrCopy.ts | 12 - src/utils/assertNotInfinite.ts | 8 - src/utils/coerceKeyPath.ts | 15 - src/utils/deepEqual.ts | 97 -- src/utils/hasOwnProperty.ts | 1 - src/utils/invariant.ts | 6 - src/utils/isArrayLike.ts | 25 - src/utils/isDataStructure.ts | 20 - src/utils/isPlainObj.ts | 26 - src/utils/mixin.ts | 20 - src/utils/quoteString.ts | 11 - src/utils/shallowCopy.ts | 19 - 108 files changed, 7071 insertions(+), 5975 deletions(-) delete mode 100644 src/Collection.js delete mode 100644 src/CollectionImpl.js create mode 100644 src/MapOrdered.js delete mode 100644 src/Operations.js delete mode 100644 src/OrderedMap.js delete mode 100644 src/OrderedSet.js create mode 100644 src/SeqArray.js create mode 100644 src/SeqObject.js create mode 100644 src/SetOrdered.js create mode 100644 src/Universe.js create mode 100644 src/collection/collection.js create mode 100644 src/collection/collectionCastIndexedSeq.js create mode 100644 src/collection/collectionCastKeyedSeq.js create mode 100644 src/collection/collectionCastSetSeq.js create mode 100644 src/collection/collectionConcat.js create mode 100644 src/collection/collectionIndexed.js create mode 100644 src/collection/collectionIndexedSeq.js create mode 100644 src/collection/collectionIndexedSeqFromCollection.js create mode 100644 src/collection/collectionKeyed.js create mode 100644 src/collection/collectionKeyedSeq.js create mode 100644 src/collection/collectionKeyedSeqFromEntries.js create mode 100644 src/collection/collectionRecord.js create mode 100644 src/collection/collectionSeq.js create mode 100644 src/collection/collectionSet.js create mode 100644 src/collection/collectionX.js create mode 100644 src/collection/kernelIndexed.js create mode 100644 src/collection/kernelKeyed.js create mode 100644 src/const.js create mode 100644 src/factory/factory.js create mode 100644 src/factory/factoryConcat.js create mode 100644 src/factory/factoryFlatten.js create mode 100644 src/factory/factoryFlip.js create mode 100644 src/factory/factoryMap.js create mode 100644 src/factory/factoryMax.js create mode 100644 src/factory/factoryReverse.js create mode 100644 src/factory/factorySlice.js create mode 100644 src/factory/factoryZipWith.js delete mode 100644 src/functional/get.ts delete mode 100644 src/functional/getIn.ts delete mode 100644 src/functional/has.ts delete mode 100644 src/functional/hasIn.ts delete mode 100644 src/functional/merge.js delete mode 100644 src/functional/remove.ts delete mode 100644 src/functional/removeIn.ts delete mode 100644 src/functional/set.ts delete mode 100644 src/functional/setIn.ts delete mode 100644 src/functional/update.ts delete mode 100644 src/functional/updateIn.ts delete mode 100644 src/is.ts delete mode 100644 src/methods/README.md delete mode 100644 src/methods/asImmutable.js delete mode 100644 src/methods/asMutable.js delete mode 100644 src/methods/deleteIn.js delete mode 100644 src/methods/getIn.js delete mode 100644 src/methods/hasIn.js delete mode 100644 src/methods/merge.js delete mode 100644 src/methods/mergeDeep.js delete mode 100644 src/methods/mergeDeepIn.js delete mode 100644 src/methods/mergeIn.js delete mode 100644 src/methods/setIn.js delete mode 100644 src/methods/toObject.js delete mode 100644 src/methods/update.js delete mode 100644 src/methods/updateIn.js delete mode 100644 src/methods/wasAltered.js delete mode 100644 src/methods/withMutations.js delete mode 100644 src/predicates/isAssociative.ts delete mode 100644 src/predicates/isCollection.ts delete mode 100644 src/predicates/isImmutable.ts delete mode 100644 src/predicates/isIndexed.ts delete mode 100644 src/predicates/isKeyed.ts delete mode 100644 src/predicates/isList.ts delete mode 100644 src/predicates/isMap.ts delete mode 100644 src/predicates/isOrdered.ts delete mode 100644 src/predicates/isOrderedMap.ts delete mode 100644 src/predicates/isOrderedSet.ts delete mode 100644 src/predicates/isRecord.ts delete mode 100644 src/predicates/isSeq.ts delete mode 100644 src/predicates/isSet.ts delete mode 100644 src/predicates/isStack.ts delete mode 100644 src/predicates/isValueObject.ts create mode 100644 src/probe.js create mode 100644 src/transformToMethods.js create mode 100644 src/util.js delete mode 100644 src/utils/arrCopy.ts delete mode 100644 src/utils/assertNotInfinite.ts delete mode 100644 src/utils/coerceKeyPath.ts delete mode 100644 src/utils/deepEqual.ts delete mode 100644 src/utils/hasOwnProperty.ts delete mode 100644 src/utils/invariant.ts delete mode 100644 src/utils/isArrayLike.ts delete mode 100644 src/utils/isDataStructure.ts delete mode 100644 src/utils/isPlainObj.ts delete mode 100644 src/utils/mixin.ts delete mode 100644 src/utils/quoteString.ts delete mode 100644 src/utils/shallowCopy.ts diff --git a/src/Collection.js b/src/Collection.js deleted file mode 100644 index fac8306e76..0000000000 --- a/src/Collection.js +++ /dev/null @@ -1,27 +0,0 @@ -import { Seq, KeyedSeq, IndexedSeq, SetSeq } from './Seq'; -import { isCollection } from './predicates/isCollection'; -import { isKeyed } from './predicates/isKeyed'; -import { isIndexed } from './predicates/isIndexed'; -import { isAssociative } from './predicates/isAssociative'; - -export const Collection = (value) => (isCollection(value) ? value : Seq(value)); -export class CollectionImpl {} - -export const KeyedCollection = (value) => - isKeyed(value) ? value : KeyedSeq(value); - -export class KeyedCollectionImpl extends CollectionImpl {} - -export const IndexedCollection = (value) => - isIndexed(value) ? value : IndexedSeq(value); - -export class IndexedCollectionImpl extends CollectionImpl {} - -export const SetCollection = (value) => - isCollection(value) && !isAssociative(value) ? value : SetSeq(value); - -export class SetCollectionImpl extends CollectionImpl {} - -Collection.Keyed = KeyedCollectionImpl; -Collection.Indexed = IndexedCollectionImpl; -Collection.Set = SetCollectionImpl; diff --git a/src/CollectionImpl.js b/src/CollectionImpl.js deleted file mode 100644 index 485276f918..0000000000 --- a/src/CollectionImpl.js +++ /dev/null @@ -1,796 +0,0 @@ -import { - Collection, - CollectionImpl, - IndexedCollectionImpl, - KeyedCollectionImpl, - SetCollectionImpl, -} from './Collection'; -import { hash } from './Hash'; -import { is } from './is'; -import { - ITERATE_ENTRIES, - ITERATE_KEYS, - ITERATE_VALUES, - Iterator, - ITERATOR_SYMBOL, -} from './Iterator'; -import { imul, smi } from './Math'; -import { IS_COLLECTION_SYMBOL } from './predicates/isCollection'; -import { IS_INDEXED_SYMBOL, isIndexed } from './predicates/isIndexed'; -import { IS_KEYED_SYMBOL, isKeyed } from './predicates/isKeyed'; -import { IS_ORDERED_SYMBOL, isOrdered } from './predicates/isOrdered'; -import { - ensureSize, - NOT_SET, - resolveBegin, - returnTrue, - wrapIndex, -} from './TrieUtils'; - -import arrCopy from './utils/arrCopy'; -import assertNotInfinite from './utils/assertNotInfinite'; -import deepEqual from './utils/deepEqual'; -import mixin from './utils/mixin'; -import quoteString from './utils/quoteString'; - -import { List } from './List'; -import { Map } from './Map'; -import { getIn } from './methods/getIn'; -import { hasIn } from './methods/hasIn'; -import { toObject } from './methods/toObject'; -import { - concatFactory, - countByFactory, - filterFactory, - flatMapFactory, - flattenFactory, - flipFactory, - FromEntriesSequence, - groupByFactory, - interposeFactory, - mapFactory, - maxFactory, - partitionFactory, - reify, - reverseFactory, - skipWhileFactory, - sliceFactory, - sortFactory, - takeWhileFactory, - ToIndexedSequence, - ToKeyedSequence, - ToSetSequence, - zipWithFactory, -} from './Operations'; -import { OrderedMap } from './OrderedMap'; -import { OrderedSet } from './OrderedSet'; -import { Range } from './Range'; -import { - ArraySeq, - IndexedSeq, - IndexedSeqImpl, - KeyedSeqImpl, - SetSeqImpl, -} from './Seq'; -import { Set } from './Set'; -import { Stack } from './Stack'; -import { toJS } from './toJS'; - -export { Collection, CollectionPrototype, IndexedCollectionPrototype }; - -Collection.Iterator = Iterator; - -mixin(CollectionImpl, { - // ### Conversion to other types - - toArray() { - assertNotInfinite(this.size); - const array = new Array(this.size || 0); - const useTuples = isKeyed(this); - let i = 0; - this.__iterate((v, k) => { - // Keyed collections produce an array of tuples. - array[i++] = useTuples ? [k, v] : v; - }); - return array; - }, - - toIndexedSeq() { - return new ToIndexedSequence(this); - }, - - toJS() { - return toJS(this); - }, - - toKeyedSeq() { - return new ToKeyedSequence(this, true); - }, - - toMap() { - // Use Late Binding here to solve the circular dependency. - return Map(this.toKeyedSeq()); - }, - - toObject: toObject, - - toOrderedMap() { - // Use Late Binding here to solve the circular dependency. - return OrderedMap(this.toKeyedSeq()); - }, - - toOrderedSet() { - // Use Late Binding here to solve the circular dependency. - return OrderedSet(isKeyed(this) ? this.valueSeq() : this); - }, - - toSet() { - // Use Late Binding here to solve the circular dependency. - return Set(isKeyed(this) ? this.valueSeq() : this); - }, - - toSetSeq() { - return new ToSetSequence(this); - }, - - toSeq() { - return isIndexed(this) - ? this.toIndexedSeq() - : isKeyed(this) - ? this.toKeyedSeq() - : this.toSetSeq(); - }, - - toStack() { - // Use Late Binding here to solve the circular dependency. - return Stack(isKeyed(this) ? this.valueSeq() : this); - }, - - toList() { - // Use Late Binding here to solve the circular dependency. - return List(isKeyed(this) ? this.valueSeq() : this); - }, - - // ### Common JavaScript methods and properties - - toString() { - return '[Collection]'; - }, - - __toString(head, tail) { - if (this.size === 0) { - return head + tail; - } - return ( - head + - ' ' + - this.toSeq().map(this.__toStringMapper).join(', ') + - ' ' + - tail - ); - }, - - // ### ES6 Collection methods (ES6 Array and Map) - - concat(...values) { - return reify(this, concatFactory(this, values)); - }, - - includes(searchValue) { - return this.some((value) => is(value, searchValue)); - }, - - entries() { - return this.__iterator(ITERATE_ENTRIES); - }, - - every(predicate, context) { - assertNotInfinite(this.size); - let returnValue = true; - this.__iterate((v, k, c) => { - if (!predicate.call(context, v, k, c)) { - returnValue = false; - return false; - } - }); - return returnValue; - }, - - filter(predicate, context) { - return reify(this, filterFactory(this, predicate, context, true)); - }, - - partition(predicate, context) { - return partitionFactory(this, predicate, context); - }, - - find(predicate, context, notSetValue) { - const entry = this.findEntry(predicate, context); - return entry ? entry[1] : notSetValue; - }, - - forEach(sideEffect, context) { - assertNotInfinite(this.size); - return this.__iterate(context ? sideEffect.bind(context) : sideEffect); - }, - - join(separator) { - assertNotInfinite(this.size); - separator = separator !== undefined ? '' + separator : ','; - let joined = ''; - let isFirst = true; - this.__iterate((v) => { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - isFirst ? (isFirst = false) : (joined += separator); - joined += v !== null && v !== undefined ? v.toString() : ''; - }); - return joined; - }, - - keys() { - return this.__iterator(ITERATE_KEYS); - }, - - map(mapper, context) { - return reify(this, mapFactory(this, mapper, context)); - }, - - reduce(reducer, initialReduction, context) { - return reduce( - this, - reducer, - initialReduction, - context, - arguments.length < 2, - false - ); - }, - - reduceRight(reducer, initialReduction, context) { - return reduce( - this, - reducer, - initialReduction, - context, - arguments.length < 2, - true - ); - }, - - reverse() { - return reify(this, reverseFactory(this, true)); - }, - - slice(begin, end) { - return reify(this, sliceFactory(this, begin, end, true)); - }, - - some(predicate, context) { - assertNotInfinite(this.size); - let returnValue = false; - this.__iterate((v, k, c) => { - if (predicate.call(context, v, k, c)) { - returnValue = true; - return false; - } - }); - return returnValue; - }, - - sort(comparator) { - return reify(this, sortFactory(this, comparator)); - }, - - values() { - return this.__iterator(ITERATE_VALUES); - }, - - // ### More sequential methods - - butLast() { - return this.slice(0, -1); - }, - - isEmpty() { - return this.size !== undefined ? this.size === 0 : !this.some(() => true); - }, - - count(predicate, context) { - return ensureSize( - predicate ? this.toSeq().filter(predicate, context) : this - ); - }, - - countBy(grouper, context) { - return countByFactory(this, grouper, context); - }, - - equals(other) { - return deepEqual(this, other); - }, - - entrySeq() { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const collection = this; - if (collection._cache) { - // We cache as an entries array, so we can just return the cache! - return new ArraySeq(collection._cache); - } - const entriesSequence = collection.toSeq().map(entryMapper).toIndexedSeq(); - entriesSequence.fromEntrySeq = () => collection.toSeq(); - return entriesSequence; - }, - - filterNot(predicate, context) { - return this.filter(not(predicate), context); - }, - - findEntry(predicate, context, notSetValue) { - let found = notSetValue; - this.__iterate((v, k, c) => { - if (predicate.call(context, v, k, c)) { - found = [k, v]; - return false; - } - }); - return found; - }, - - findKey(predicate, context) { - const entry = this.findEntry(predicate, context); - return entry && entry[0]; - }, - - findLast(predicate, context, notSetValue) { - return this.toKeyedSeq().reverse().find(predicate, context, notSetValue); - }, - - findLastEntry(predicate, context, notSetValue) { - return this.toKeyedSeq() - .reverse() - .findEntry(predicate, context, notSetValue); - }, - - findLastKey(predicate, context) { - return this.toKeyedSeq().reverse().findKey(predicate, context); - }, - - first(notSetValue) { - return this.find(returnTrue, null, notSetValue); - }, - - flatMap(mapper, context) { - return reify(this, flatMapFactory(this, mapper, context)); - }, - - flatten(depth) { - return reify(this, flattenFactory(this, depth, true)); - }, - - fromEntrySeq() { - return new FromEntriesSequence(this); - }, - - get(searchKey, notSetValue) { - return this.find((_, key) => is(key, searchKey), undefined, notSetValue); - }, - - getIn: getIn, - - groupBy(grouper, context) { - return groupByFactory(this, grouper, context); - }, - - has(searchKey) { - return this.get(searchKey, NOT_SET) !== NOT_SET; - }, - - hasIn: hasIn, - - isSubset(iter) { - iter = typeof iter.includes === 'function' ? iter : Collection(iter); - return this.every((value) => iter.includes(value)); - }, - - isSuperset(iter) { - iter = typeof iter.isSubset === 'function' ? iter : Collection(iter); - return iter.isSubset(this); - }, - - keyOf(searchValue) { - return this.findKey((value) => is(value, searchValue)); - }, - - keySeq() { - return this.toSeq().map(keyMapper).toIndexedSeq(); - }, - - last(notSetValue) { - return this.toSeq().reverse().first(notSetValue); - }, - - lastKeyOf(searchValue) { - return this.toKeyedSeq().reverse().keyOf(searchValue); - }, - - max(comparator) { - return maxFactory(this, comparator); - }, - - maxBy(mapper, comparator) { - return maxFactory(this, comparator, mapper); - }, - - min(comparator) { - return maxFactory( - this, - comparator ? neg(comparator) : defaultNegComparator - ); - }, - - minBy(mapper, comparator) { - return maxFactory( - this, - comparator ? neg(comparator) : defaultNegComparator, - mapper - ); - }, - - rest() { - return this.slice(1); - }, - - skip(amount) { - return amount === 0 ? this : this.slice(Math.max(0, amount)); - }, - - skipLast(amount) { - return amount === 0 ? this : this.slice(0, -Math.max(0, amount)); - }, - - skipWhile(predicate, context) { - return reify(this, skipWhileFactory(this, predicate, context, true)); - }, - - skipUntil(predicate, context) { - return this.skipWhile(not(predicate), context); - }, - - sortBy(mapper, comparator) { - return reify(this, sortFactory(this, comparator, mapper)); - }, - - take(amount) { - return this.slice(0, Math.max(0, amount)); - }, - - takeLast(amount) { - return this.slice(-Math.max(0, amount)); - }, - - takeWhile(predicate, context) { - return reify(this, takeWhileFactory(this, predicate, context)); - }, - - takeUntil(predicate, context) { - return this.takeWhile(not(predicate), context); - }, - - update(fn) { - return fn(this); - }, - - valueSeq() { - return this.toIndexedSeq(); - }, - - // ### Hashable Object - - hashCode() { - return this.__hash || (this.__hash = hashCollection(this)); - }, - - // ### Internal - - // abstract __iterate(fn, reverse) - - // abstract __iterator(type, reverse) -}); - -const CollectionPrototype = CollectionImpl.prototype; -CollectionPrototype[IS_COLLECTION_SYMBOL] = true; -CollectionPrototype[ITERATOR_SYMBOL] = CollectionPrototype.values; -CollectionPrototype.toJSON = CollectionPrototype.toArray; -CollectionPrototype.__toStringMapper = quoteString; -CollectionPrototype.inspect = CollectionPrototype.toSource = function () { - return this.toString(); -}; -CollectionPrototype.chain = CollectionPrototype.flatMap; -CollectionPrototype.contains = CollectionPrototype.includes; - -mixin(KeyedCollectionImpl, { - // ### More sequential methods - - flip() { - return reify(this, flipFactory(this)); - }, - - mapEntries(mapper, context) { - let iterations = 0; - return reify( - this, - this.toSeq() - .map((v, k) => mapper.call(context, [k, v], iterations++, this)) - .fromEntrySeq() - ); - }, - - mapKeys(mapper, context) { - return reify( - this, - this.toSeq() - .flip() - .map((k, v) => mapper.call(context, k, v, this)) - .flip() - ); - }, -}); - -const KeyedCollectionPrototype = KeyedCollectionImpl.prototype; -KeyedCollectionPrototype[IS_KEYED_SYMBOL] = true; -KeyedCollectionPrototype[ITERATOR_SYMBOL] = CollectionPrototype.entries; -KeyedCollectionPrototype.toJSON = toObject; -KeyedCollectionPrototype.__toStringMapper = (v, k) => - quoteString(k) + ': ' + quoteString(v); - -mixin(IndexedCollectionImpl, { - // ### Conversion to other types - - toKeyedSeq() { - return new ToKeyedSequence(this, false); - }, - - // ### ES6 Collection methods (ES6 Array and Map) - - filter(predicate, context) { - return reify(this, filterFactory(this, predicate, context, false)); - }, - - findIndex(predicate, context) { - const entry = this.findEntry(predicate, context); - return entry ? entry[0] : -1; - }, - - indexOf(searchValue) { - const key = this.keyOf(searchValue); - return key === undefined ? -1 : key; - }, - - lastIndexOf(searchValue) { - const key = this.lastKeyOf(searchValue); - return key === undefined ? -1 : key; - }, - - reverse() { - return reify(this, reverseFactory(this, false)); - }, - - slice(begin, end) { - return reify(this, sliceFactory(this, begin, end, false)); - }, - - splice(index, removeNum /*, ...values*/) { - const numArgs = arguments.length; - removeNum = Math.max(removeNum || 0, 0); - if (numArgs === 0 || (numArgs === 2 && !removeNum)) { - return this; - } - // If index is negative, it should resolve relative to the size of the - // collection. However size may be expensive to compute if not cached, so - // only call count() if the number is in fact negative. - index = resolveBegin(index, index < 0 ? this.count() : this.size); - const spliced = this.slice(0, index); - return reify( - this, - numArgs === 1 - ? spliced - : spliced.concat(arrCopy(arguments, 2), this.slice(index + removeNum)) - ); - }, - - // ### More collection methods - - findLastIndex(predicate, context) { - const entry = this.findLastEntry(predicate, context); - return entry ? entry[0] : -1; - }, - - first(notSetValue) { - return this.get(0, notSetValue); - }, - - flatten(depth) { - return reify(this, flattenFactory(this, depth, false)); - }, - - get(index, notSetValue) { - index = wrapIndex(this, index); - return index < 0 || - this.size === Infinity || - (this.size !== undefined && index > this.size) - ? notSetValue - : this.find((_, key) => key === index, undefined, notSetValue); - }, - - has(index) { - index = wrapIndex(this, index); - return ( - index >= 0 && - (this.size !== undefined - ? this.size === Infinity || index < this.size - : this.indexOf(index) !== -1) - ); - }, - - interpose(separator) { - return reify(this, interposeFactory(this, separator)); - }, - - interleave(/*...collections*/) { - const collections = [this].concat(arrCopy(arguments)); - const zipped = zipWithFactory(this.toSeq(), IndexedSeq.of, collections); - const interleaved = zipped.flatten(true); - if (zipped.size) { - interleaved.size = zipped.size * collections.length; - } - return reify(this, interleaved); - }, - - keySeq() { - return Range(0, this.size); - }, - - last(notSetValue) { - return this.get(-1, notSetValue); - }, - - skipWhile(predicate, context) { - return reify(this, skipWhileFactory(this, predicate, context, false)); - }, - - zip(/*, ...collections */) { - const collections = [this].concat(arrCopy(arguments)); - return reify(this, zipWithFactory(this, defaultZipper, collections)); - }, - - zipAll(/*, ...collections */) { - const collections = [this].concat(arrCopy(arguments)); - return reify(this, zipWithFactory(this, defaultZipper, collections, true)); - }, - - zipWith(zipper /*, ...collections */) { - const collections = arrCopy(arguments); - collections[0] = this; - return reify(this, zipWithFactory(this, zipper, collections)); - }, -}); - -const IndexedCollectionPrototype = IndexedCollectionImpl.prototype; -IndexedCollectionPrototype[IS_INDEXED_SYMBOL] = true; -IndexedCollectionPrototype[IS_ORDERED_SYMBOL] = true; - -mixin(SetCollectionImpl, { - // ### ES6 Collection methods (ES6 Array and Map) - - get(value, notSetValue) { - return this.has(value) ? value : notSetValue; - }, - - includes(value) { - return this.has(value); - }, - - // ### More sequential methods - - keySeq() { - return this.valueSeq(); - }, -}); - -const SetCollectionPrototype = SetCollectionImpl.prototype; -SetCollectionPrototype.has = CollectionPrototype.includes; -SetCollectionPrototype.contains = SetCollectionPrototype.includes; -SetCollectionPrototype.keys = SetCollectionPrototype.values; - -// Mixin subclasses - -mixin(KeyedSeqImpl, KeyedCollectionPrototype); -mixin(IndexedSeqImpl, IndexedCollectionPrototype); -mixin(SetSeqImpl, SetCollectionPrototype); - -// #pragma Helper functions - -function reduce(collection, reducer, reduction, context, useFirst, reverse) { - assertNotInfinite(collection.size); - collection.__iterate((v, k, c) => { - if (useFirst) { - useFirst = false; - reduction = v; - } else { - reduction = reducer.call(context, reduction, v, k, c); - } - }, reverse); - return reduction; -} - -function keyMapper(v, k) { - return k; -} - -function entryMapper(v, k) { - return [k, v]; -} - -function not(predicate) { - return function () { - return !predicate.apply(this, arguments); - }; -} - -function neg(predicate) { - return function () { - return -predicate.apply(this, arguments); - }; -} - -function defaultZipper() { - return arrCopy(arguments); -} - -function defaultNegComparator(a, b) { - return a < b ? 1 : a > b ? -1 : 0; -} - -function hashCollection(collection) { - if (collection.size === Infinity) { - return 0; - } - const ordered = isOrdered(collection); - const keyed = isKeyed(collection); - let h = ordered ? 1 : 0; - - collection.__iterate( - keyed - ? ordered - ? (v, k) => { - h = (31 * h + hashMerge(hash(v), hash(k))) | 0; - } - : (v, k) => { - h = (h + hashMerge(hash(v), hash(k))) | 0; - } - : ordered - ? (v) => { - h = (31 * h + hash(v)) | 0; - } - : (v) => { - h = (h + hash(v)) | 0; - } - ); - - return murmurHashOfSize(collection.size, h); -} - -function murmurHashOfSize(size, h) { - h = imul(h, 0xcc9e2d51); - h = imul((h << 15) | (h >>> -15), 0x1b873593); - h = imul((h << 13) | (h >>> -13), 5); - h = ((h + 0xe6546b64) | 0) ^ size; - h = imul(h ^ (h >>> 16), 0x85ebca6b); - h = imul(h ^ (h >>> 13), 0xc2b2ae35); - h = smi(h ^ (h >>> 16)); - return h; -} - -function hashMerge(a, b) { - return (a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2))) | 0; // int -} diff --git a/src/Immutable.js b/src/Immutable.js index 98945f762e..7897f243bc 100644 --- a/src/Immutable.js +++ b/src/Immutable.js @@ -1,51 +1,70 @@ -import { Seq } from './Seq'; -import { OrderedMap } from './OrderedMap'; -import { List } from './List'; -import { Map } from './Map'; import { Stack } from './Stack'; -import { OrderedSet } from './OrderedSet'; import { PairSorting } from './PairSorting'; -import { Set } from './Set'; import { Record } from './Record'; -import { Range } from './Range'; import { Repeat } from './Repeat'; -import { is } from './is'; import { fromJS } from './fromJS'; - -import isPlainObject from './utils/isPlainObj'; +import { Map } from './Map'; +import { Seq } from './Seq'; +import { SeqArray as ArraySeq } from './SeqArray'; +import { SeqObject as ObjectSeq } from './SeqObject'; +import { Set } from './Set'; +import { SetOrdered as OrderedSet } from './SetOrdered'; +import { List } from './List'; +import { Range } from './Range'; +import { MapOrdered as OrderedMap } from './MapOrdered'; +import { Collection } from './Universe'; // Functional predicates -import { isImmutable } from './predicates/isImmutable'; -import { isCollection } from './predicates/isCollection'; -import { isKeyed } from './predicates/isKeyed'; -import { isIndexed } from './predicates/isIndexed'; -import { isAssociative } from './predicates/isAssociative'; -import { isOrdered } from './predicates/isOrdered'; -import { isValueObject } from './predicates/isValueObject'; -import { isSeq } from './predicates/isSeq'; -import { isList } from './predicates/isList'; -import { isMap } from './predicates/isMap'; -import { isOrderedMap } from './predicates/isOrderedMap'; -import { isStack } from './predicates/isStack'; -import { isSet } from './predicates/isSet'; -import { isOrderedSet } from './predicates/isOrderedSet'; -import { isRecord } from './predicates/isRecord'; +import { + probeIsSame as is, + probeIsPlainObject as isPlainObject, + probeIsIndexed as isIndexed, + probeIsValueObject as isValueObject, + probeIsList as isList, + probeIsSet as isSet, + probeIsRecord as isRecord, + probeIsImmutable as isImmutable, + probeIsCollection as isCollection, + probeIsAssociative as isAssociative, + probeIsOrderedMap as isOrderedMap, + probeIsOrdered as isOrdered, + probeIsOrderedSet as isOrderedSet, + probeIsKeyed as isKeyed, + probeIsStack as isStack, + probeIsSeq as isSeq, + probeIsMap as isMap, +} from './probe'; -import { Collection } from './CollectionImpl'; import { hash } from './Hash'; // Functional read/write API -import { get } from './functional/get'; -import { getIn } from './functional/getIn'; -import { has } from './functional/has'; -import { hasIn } from './functional/hasIn'; -import { merge, mergeDeep, mergeWith, mergeDeepWith } from './functional/merge'; -import { remove } from './functional/remove'; -import { removeIn } from './functional/removeIn'; -import { set } from './functional/set'; -import { setIn } from './functional/setIn'; -import { update } from './functional/update'; -import { updateIn } from './functional/updateIn'; +import { + collectionOrAnyOpHas as has, + collectionOrAnyOpHasIn as hasIn, + collectionOrAnyOpRemove as remove, + collectionOrAnyOpGet as get, + collectionOrAnyOpGetIn as getIn, + collectionOrAnyOpSet as set, +} from './collection/collection'; + +import { + collectionXOrAnyOpUpdateIn as updateIn, + collectionXOrAnyOpUpdate as update, + collectionXOrAnyOpRemoveIn as removeIn, + collectionXOrAnyOpSetIn as setIn, + collectionXOpMergeWithSources, + collectionXOpMergeDeep, + collectionXOpMergeDeepWith, + collectionXOpMergeWith, +} from './collection/collectionX'; + +// user-friendly interface mapped to internal interface +const merge = (cx, ...sources) => collectionXOpMergeWithSources(cx, sources); +const mergeDeep = (cx, ...sources) => collectionXOpMergeDeep(cx, sources); +const mergeWith = (merger, cx, ...sources) => + collectionXOpMergeWith(merger, cx, sources); +const mergeDeepWith = (merger, cx, ...sources) => + collectionXOpMergeDeepWith(cx, merger, sources); import { version } from '../package.json'; @@ -56,6 +75,8 @@ export { version, Collection, Iterable, + ArraySeq, + ObjectSeq, Seq, Map, OrderedMap, diff --git a/src/Iterator.js b/src/Iterator.js index e68aef6eb5..6b624f3b54 100644 --- a/src/Iterator.js +++ b/src/Iterator.js @@ -2,12 +2,13 @@ export const ITERATE_KEYS = 0; export const ITERATE_VALUES = 1; export const ITERATE_ENTRIES = 2; -const REAL_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; -const FAUX_ITERATOR_SYMBOL = '@@iterator'; +import { + ITERATOR_SYMBOL_REAL, + ITERATOR_SYMBOL_FAUX, + ITERATOR_SYMBOL, +} from './const'; -export const ITERATOR_SYMBOL = REAL_ITERATOR_SYMBOL || FAUX_ITERATOR_SYMBOL; - -export class Iterator { +class Iterator { constructor(next) { if (next) { // Map extends Iterator and has a `next` method, do not erase it in that case. We could have checked `if (next && !this.next)` too. @@ -31,7 +32,7 @@ Iterator.prototype[ITERATOR_SYMBOL] = function () { return this; }; -export function iteratorValue(type, k, v, iteratorResult) { +function iteratorValue(type, k, v, iteratorResult) { const value = type === ITERATE_KEYS ? k : type === ITERATE_VALUES ? v : [k, v]; // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here @@ -44,24 +45,11 @@ export function iteratorValue(type, k, v, iteratorResult) { return iteratorResult; } -export function iteratorDone() { +function iteratorDone() { return { value: undefined, done: true }; } -export function hasIterator(maybeIterable) { - if (Array.isArray(maybeIterable)) { - // IE11 trick as it does not support `Symbol.iterator` - return true; - } - - return !!getIteratorFn(maybeIterable); -} - -export function isIterator(maybeIterator) { - return maybeIterator && typeof maybeIterator.next === 'function'; -} - -export function getIterator(iterable) { +function getIterator(iterable) { const iteratorFn = getIteratorFn(iterable); return iteratorFn && iteratorFn.call(iterable); } @@ -69,19 +57,28 @@ export function getIterator(iterable) { function getIteratorFn(iterable) { const iteratorFn = iterable && - ((REAL_ITERATOR_SYMBOL && iterable[REAL_ITERATOR_SYMBOL]) || - iterable[FAUX_ITERATOR_SYMBOL]); + ((ITERATOR_SYMBOL_REAL && iterable[ITERATOR_SYMBOL_REAL]) || + iterable[ITERATOR_SYMBOL_FAUX]); if (typeof iteratorFn === 'function') { return iteratorFn; } } -export function isEntriesIterable(maybeIterable) { +function isEntriesIterable(maybeIterable) { const iteratorFn = getIteratorFn(maybeIterable); return iteratorFn && iteratorFn === maybeIterable.entries; } -export function isKeysIterable(maybeIterable) { +function isKeysIterable(maybeIterable) { const iteratorFn = getIteratorFn(maybeIterable); return iteratorFn && iteratorFn === maybeIterable.keys; } + +export { + Iterator, + iteratorValue, + iteratorDone, + getIterator, + isEntriesIterable, + isKeysIterable, +}; diff --git a/src/List.js b/src/List.js index 1b733d56a6..abcfcf9933 100644 --- a/src/List.js +++ b/src/List.js @@ -1,440 +1,71 @@ +import transformToMethods from './transformToMethods'; + import { - DELETE, SHIFT, SIZE, MASK, OwnerID, MakeRef, - SetRef, wrapIndex, wholeSlice, resolveBegin, resolveEnd, } from './TrieUtils'; -import { IS_LIST_SYMBOL, isList } from './predicates/isList'; -import { IndexedCollectionImpl, IndexedCollection } from './Collection'; -import { hasIterator, Iterator, iteratorValue, iteratorDone } from './Iterator'; -import { setIn } from './methods/setIn'; -import { deleteIn } from './methods/deleteIn'; -import { update } from './methods/update'; -import { updateIn } from './methods/updateIn'; -import { mergeIn } from './methods/mergeIn'; -import { mergeDeepIn } from './methods/mergeDeepIn'; -import { withMutations } from './methods/withMutations'; -import { asMutable } from './methods/asMutable'; -import { asImmutable } from './methods/asImmutable'; -import { wasAltered } from './methods/wasAltered'; -import assertNotInfinite from './utils/assertNotInfinite'; - -export const List = (value) => { - const empty = emptyList(); - if (value === undefined || value === null) { - return empty; - } - if (isList(value)) { - return value; - } - const iter = IndexedCollection(value); - const size = iter.size; - if (size === 0) { - return empty; - } - assertNotInfinite(size); - if (size > 0 && size < SIZE) { - return makeList(0, size, SHIFT, null, new VNode(iter.toArray())); - } - return empty.withMutations((list) => { - list.setSize(size); - iter.forEach((v, i) => list.set(i, v)); - }); -}; - -List.of = function (/*...values*/) { - return List(arguments); -}; -export class ListImpl extends IndexedCollectionImpl { - // @pragma Construction +import { utilFlagSpread, utilAssertNotInfinite } from './util'; - create(value) { - return List(value); - } +import { DELETE, IS_LIST_SYMBOL, SHAPE_LIST, DONE } from './const'; - toString() { - return this.__toString('List [', ']'); - } - - // @pragma Access - - get(index, notSetValue) { - index = wrapIndex(this, index); - if (index >= 0 && index < this.size) { - index += this._origin; - const node = listNodeFor(this, index); - return node && node.array[index & MASK]; - } - return notSetValue; - } - - // @pragma Modification - - set(index, value) { - return updateList(this, index, value); - } - - remove(index) { - return !this.has(index) - ? this - : index === 0 - ? this.shift() - : index === this.size - 1 - ? this.pop() - : this.splice(index, 1); - } - - insert(index, value) { - return this.splice(index, 0, value); - } - - clear() { - if (this.size === 0) { - return this; - } - if (this.__ownerID) { - this.size = this._origin = this._capacity = 0; - this._level = SHIFT; - this._root = this._tail = this.__hash = undefined; - this.__altered = true; - return this; - } - return emptyList(); - } +import { probeIsList, probeHasIterator } from './probe'; - push(/*...values*/) { - const values = arguments; - const oldSize = this.size; - return this.withMutations((list) => { - setListBounds(list, 0, oldSize + values.length); - for (let ii = 0; ii < values.length; ii++) { - list.set(oldSize + ii, values[ii]); - } - }); - } - - pop() { - return setListBounds(this, 0, -1); - } - - unshift(/*...values*/) { - const values = arguments; - return this.withMutations((list) => { - setListBounds(list, -values.length); - for (let ii = 0; ii < values.length; ii++) { - list.set(ii, values[ii]); - } - }); - } - - shift() { - return setListBounds(this, 1); - } - - shuffle(random = Math.random) { - return this.withMutations((mutable) => { - // implementation of the Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle - let current = mutable.size; - let destination; - let tmp; - - while (current) { - destination = Math.floor(random() * current--); - - tmp = mutable.get(destination); - mutable.set(destination, mutable.get(current)); - mutable.set(current, tmp); - } - }); - } - - // @pragma Composition - - concat(/*...collections*/) { - const seqs = []; - for (let i = 0; i < arguments.length; i++) { - const argument = arguments[i]; - const seq = IndexedCollection( - typeof argument !== 'string' && hasIterator(argument) - ? argument - : [argument] - ); - if (seq.size !== 0) { - seqs.push(seq); - } - } - if (seqs.length === 0) { - return this; - } - if (this.size === 0 && !this.__ownerID && seqs.length === 1) { - return List(seqs[0]); - } - return this.withMutations((list) => { - seqs.forEach((seq) => seq.forEach((value) => list.push(value))); - }); - } - - setSize(size) { - return setListBounds(this, 0, size); - } - - map(mapper, context) { - return this.withMutations((list) => { - for (let i = 0; i < this.size; i++) { - list.set(i, mapper.call(context, list.get(i), i, this)); - } - }); - } +import { Iterator, iteratorValue, iteratorDone } from './Iterator'; - // @pragma Iteration - - slice(begin, end) { - const size = this.size; - if (wholeSlice(begin, end, size)) { - return this; - } - return setListBounds( - this, - resolveBegin(begin, size), - resolveEnd(end, size) - ); - } - - __iterator(type, reverse) { - let index = reverse ? this.size : 0; - const values = iterateList(this, reverse); - return new Iterator(() => { - const value = values(); - return value === DONE - ? iteratorDone() - : iteratorValue(type, reverse ? --index : index++, value); - }); - } +import { + collectionOpWithMutations, + collectionOpWasAltered, + collectionOpAsMutable, + collectionOpAsImmutable, +} from './collection/collection'; - __iterate(fn, reverse) { - let index = reverse ? this.size : 0; - const values = iterateList(this, reverse); - let value; - while ((value = values()) !== DONE) { - if (fn(value, reverse ? --index : index++, this) === false) { - break; - } - } - return index; - } +import { collectionIndexedPropertiesCreate } from './collection/collectionIndexed.js'; - __ensureOwner(ownerID) { - if (ownerID === this.__ownerID) { - return this; - } - if (!ownerID) { - if (this.size === 0) { - return emptyList(); - } - this.__ownerID = ownerID; - this.__altered = false; - return this; - } - return makeList( - this._origin, - this._capacity, - this._level, - this._root, - this._tail, - ownerID, - this.__hash - ); - } -} +import { SeqIndexed, SeqIndexedWhenNotIndexed } from './Seq'; -List.isList = isList; - -const ListPrototype = ListImpl.prototype; -ListPrototype[IS_LIST_SYMBOL] = true; -ListPrototype[DELETE] = ListPrototype.remove; -ListPrototype.merge = ListPrototype.concat; -ListPrototype.setIn = setIn; -ListPrototype.deleteIn = ListPrototype.removeIn = deleteIn; -ListPrototype.update = update; -ListPrototype.updateIn = updateIn; -ListPrototype.mergeIn = mergeIn; -ListPrototype.mergeDeepIn = mergeDeepIn; -ListPrototype.withMutations = withMutations; -ListPrototype.wasAltered = wasAltered; -ListPrototype.asImmutable = asImmutable; -ListPrototype['@@transducer/init'] = ListPrototype.asMutable = asMutable; -ListPrototype['@@transducer/step'] = function (result, arr) { - return result.push(arr); -}; -ListPrototype['@@transducer/result'] = function (obj) { - return obj.asImmutable(); +import { + kernelIndexedOpIterate, + kernelIndexedOpFindVNodeFor, + kernelIndexedVNodeCreate, + kernelIndexedVNodeOpUpdate, + kernelIndexedVNodeOpEditable, + kernelIndexedVNodeOpRemoveBefore, + kernelIndexedVNodeOpRemoveAfter, +} from './collection/kernelIndexed'; + +const listToString = (cx) => { + return cx.__toString('List [', ']'); }; -class VNode { - constructor(array, ownerID) { - this.array = array; - this.ownerID = ownerID; - } - - // TODO: seems like these methods are very similar - - removeBefore(ownerID, level, index) { - if ( - (index & ((1 << (level + SHIFT)) - 1)) === 0 || - this.array.length === 0 - ) { - return this; - } - const originIndex = (index >>> level) & MASK; - if (originIndex >= this.array.length) { - return new VNode([], ownerID); - } - const removingFirst = originIndex === 0; - let newChild; - if (level > 0) { - const oldChild = this.array[originIndex]; - newChild = - oldChild && oldChild.removeBefore(ownerID, level - SHIFT, index); - if (newChild === oldChild && removingFirst) { - return this; - } - } - if (removingFirst && !newChild) { - return this; - } - const editable = editableVNode(this, ownerID); - if (!removingFirst) { - for (let ii = 0; ii < originIndex; ii++) { - editable.array[ii] = undefined; - } - } - if (newChild) { - editable.array[originIndex] = newChild; - } - return editable; - } - - removeAfter(ownerID, level, index) { - if ( - index === (level ? 1 << (level + SHIFT) : SIZE) || - this.array.length === 0 - ) { - return this; - } - const sizeIndex = ((index - 1) >>> level) & MASK; - if (sizeIndex >= this.array.length) { - return this; - } - - let newChild; - if (level > 0) { - const oldChild = this.array[sizeIndex]; - newChild = - oldChild && oldChild.removeAfter(ownerID, level - SHIFT, index); - if (newChild === oldChild && sizeIndex === this.array.length - 1) { - return this; - } - } - - const editable = editableVNode(this, ownerID); - editable.array.splice(sizeIndex + 1); - if (newChild) { - editable.array[sizeIndex] = newChild; - } - return editable; - } -} - -const DONE = {}; - -function iterateList(list, reverse) { - const left = list._origin; - const right = list._capacity; - const tailPos = getTailOffset(right); - const tail = list._tail; - - return iterateNodeOrLeaf(list._root, list._level, 0); - - function iterateNodeOrLeaf(node, level, offset) { - return level === 0 - ? iterateLeaf(node, offset) - : iterateNode(node, level, offset); - } - - function iterateLeaf(node, offset) { - const array = offset === tailPos ? tail && tail.array : node && node.array; - let from = offset > left ? 0 : left - offset; - let to = right - offset; - if (to > SIZE) { - to = SIZE; - } - return () => { - if (from === to) { - return DONE; - } - const idx = reverse ? --to : from++; - return array && array[idx]; - }; - } - - function iterateNode(node, level, offset) { - let values; - const array = node && node.array; - let from = offset > left ? 0 : (left - offset) >> level; - let to = ((right - offset) >> level) + 1; - if (to > SIZE) { - to = SIZE; - } - return () => { - while (true) { - if (values) { - const value = values(); - if (value !== DONE) { - return value; - } - values = null; - } - if (from === to) { - return DONE; - } - const idx = reverse ? --to : from++; - values = iterateNodeOrLeaf( - array && array[idx], - level - SHIFT, - offset + (idx << level) - ); - } - }; +const listGet = (cx, index, notSetValue) => { + index = wrapIndex(cx, index); + if (index >= 0 && index < cx.size) { + index += cx._origin; + const node = kernelIndexedOpFindVNodeFor(cx, index); + return node && node.array[index & MASK]; } -} - -function makeList(origin, capacity, level, root, tail, ownerID, hash) { - const list = Object.create(ListPrototype); - list.size = capacity - origin; - list._origin = origin; - list._capacity = capacity; - list._level = level; - list._root = root; - list._tail = tail; - list.__ownerID = ownerID; - list.__hash = hash; - list.__altered = false; - return list; -} + return notSetValue; +}; -export function emptyList() { - return makeList(0, 0, SHIFT); -} +const listRemove = (cx, index) => { + return !cx.has(index) + ? cx + : index === 0 + ? cx.shift() + : index === cx.size - 1 + ? cx.pop() + : cx.splice(index, 1); +}; -function updateList(list, index, value) { +const listUpdate = (list, index, value) => { index = wrapIndex(list, index); if (index !== index) { @@ -442,11 +73,11 @@ function updateList(list, index, value) { } if (index >= list.size || index < 0) { - return list.withMutations((list) => { + return collectionOpWithMutations(list, (list) => { // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here index < 0 - ? setListBounds(list, index).set(0, value) - : setListBounds(list, 0, index + 1).set(index, value); + ? listBoundsSet(list, index).set(0, value) + : listBoundsSet(list, 0, index + 1).set(index, value); }); } @@ -456,9 +87,16 @@ function updateList(list, index, value) { let newRoot = list._root; const didAlter = MakeRef(); if (index >= getTailOffset(list._capacity)) { - newTail = updateVNode(newTail, list.__ownerID, 0, index, value, didAlter); + newTail = kernelIndexedVNodeOpUpdate( + newTail, + list.__ownerID, + 0, + index, + value, + didAlter + ); } else { - newRoot = updateVNode( + newRoot = kernelIndexedVNodeOpUpdate( newRoot, list.__ownerID, list._level, @@ -479,76 +117,16 @@ function updateList(list, index, value) { list.__altered = true; return list; } - return makeList(list._origin, list._capacity, list._level, newRoot, newTail); -} - -function updateVNode(node, ownerID, level, index, value, didAlter) { - const idx = (index >>> level) & MASK; - const nodeHas = node && idx < node.array.length; - if (!nodeHas && value === undefined) { - return node; - } - - let newNode; - - if (level > 0) { - const lowerNode = node && node.array[idx]; - const newLowerNode = updateVNode( - lowerNode, - ownerID, - level - SHIFT, - index, - value, - didAlter - ); - if (newLowerNode === lowerNode) { - return node; - } - newNode = editableVNode(node, ownerID); - newNode.array[idx] = newLowerNode; - return newNode; - } - - if (nodeHas && node.array[idx] === value) { - return node; - } - - if (didAlter) { - SetRef(didAlter); - } - - newNode = editableVNode(node, ownerID); - if (value === undefined && idx === newNode.array.length - 1) { - newNode.array.pop(); - } else { - newNode.array[idx] = value; - } - return newNode; -} - -function editableVNode(node, ownerID) { - if (ownerID && node && ownerID === node.ownerID) { - return node; - } - return new VNode(node ? node.array.slice() : [], ownerID); -} - -function listNodeFor(list, rawIndex) { - if (rawIndex >= getTailOffset(list._capacity)) { - return list._tail; - } - if (rawIndex < 1 << (list._level + SHIFT)) { - let node = list._root; - let level = list._level; - while (node && level > 0) { - node = node.array[(rawIndex >>> level) & MASK]; - level -= SHIFT; - } - return node; - } -} + return listCreate( + list._origin, + list._capacity, + list._level, + newRoot, + newTail + ); +}; -function setListBounds(list, begin, end) { +const listBoundsSet = (list, begin, end) => { // Sanitize begin & end using this shorthand for ToInt32(argument) // http://www.ecma-international.org/ecma-262/6.0/#sec-toint32 if (begin !== undefined) { @@ -573,7 +151,8 @@ function setListBounds(list, begin, end) { // If it's going to end after it starts, it's empty. if (newOrigin >= newCapacity) { - return list.clear(); + // throw new Error('unverified') + return listClear(list); } let newLevel = list._level; @@ -582,7 +161,7 @@ function setListBounds(list, begin, end) { // New origin might need creating a higher root. let offsetShift = 0; while (newOrigin + offsetShift < 0) { - newRoot = new VNode( + newRoot = kernelIndexedVNodeCreate( newRoot && newRoot.array.length ? [undefined, newRoot] : [], owner ); @@ -601,7 +180,7 @@ function setListBounds(list, begin, end) { // New size might need creating a higher root. while (newTailOffset >= 1 << (newLevel + SHIFT)) { - newRoot = new VNode( + newRoot = kernelIndexedVNodeCreate( newRoot && newRoot.array.length ? [newRoot] : [], owner ); @@ -612,9 +191,9 @@ function setListBounds(list, begin, end) { const oldTail = list._tail; let newTail = newTailOffset < oldTailOffset - ? listNodeFor(list, newCapacity - 1) + ? kernelIndexedOpFindVNodeFor(list, newCapacity - 1) : newTailOffset > oldTailOffset - ? new VNode([], owner) + ? kernelIndexedVNodeCreate([], owner) : oldTail; // Merge Tail into tree. @@ -624,18 +203,23 @@ function setListBounds(list, begin, end) { newOrigin < oldCapacity && oldTail.array.length ) { - newRoot = editableVNode(newRoot, owner); + newRoot = kernelIndexedVNodeOpEditable(newRoot, owner); let node = newRoot; for (let level = newLevel; level > SHIFT; level -= SHIFT) { const idx = (oldTailOffset >>> level) & MASK; - node = node.array[idx] = editableVNode(node.array[idx], owner); + node = node.array[idx] = kernelIndexedVNodeOpEditable( + node.array[idx], + owner + ); } node.array[(oldTailOffset >>> SHIFT) & MASK] = oldTail; } // If the size has been reduced, there's a chance the tail needs to be trimmed. if (newCapacity < oldCapacity) { - newTail = newTail && newTail.removeAfter(owner, 0, newCapacity); + newTail = + newTail && + kernelIndexedVNodeOpRemoveAfter(newTail, owner, 0, newCapacity); } // If the new origin is within the tail, then we do not need a root. @@ -644,7 +228,8 @@ function setListBounds(list, begin, end) { newCapacity -= newTailOffset; newLevel = SHIFT; newRoot = null; - newTail = newTail && newTail.removeBefore(owner, 0, newOrigin); + newTail = + newTail && kernelIndexedVNodeOpRemoveBefore(newTail, owner, 0, newOrigin); // Otherwise, if the root has been trimmed, garbage collect. } else if (newOrigin > oldOrigin || newTailOffset < oldTailOffset) { @@ -665,10 +250,16 @@ function setListBounds(list, begin, end) { // Trim the new sides of the new root. if (newRoot && newOrigin > oldOrigin) { - newRoot = newRoot.removeBefore(owner, newLevel, newOrigin - offsetShift); + newRoot = kernelIndexedVNodeOpRemoveBefore( + newRoot, + owner, + newLevel, + newOrigin - offsetShift + ); } if (newRoot && newTailOffset < oldTailOffset) { - newRoot = newRoot.removeAfter( + newRoot = kernelIndexedVNodeOpRemoveAfter( + newRoot, owner, newLevel, newTailOffset - offsetShift @@ -691,9 +282,255 @@ function setListBounds(list, begin, end) { list.__altered = true; return list; } - return makeList(newOrigin, newCapacity, newLevel, newRoot, newTail); -} + return listCreate(newOrigin, newCapacity, newLevel, newRoot, newTail); +}; function getTailOffset(size) { return size < SIZE ? 0 : ((size - 1) >>> SHIFT) << SHIFT; } + +const listInsert = (cx, index, value) => { + return cx.splice(index, 0, value); +}; + +const listClear = (cx) => { + if (cx.size === 0) { + return cx; + } + if (cx.__ownerID) { + cx.size = cx._origin = cx._capacity = 0; + cx._level = SHIFT; + cx._root = cx._tail = cx.__hash = undefined; + cx.__altered = true; + return cx; + } + return listCreateEmpty(); +}; + +const listPush = (cx, values) => { + const oldSize = cx.size; + + return collectionOpWithMutations(cx, (list) => { + listBoundsSet(list, 0, oldSize + values.length); + for (let ii = 0; ii < values.length; ii++) { + list.set(oldSize + ii, values[ii]); + } + }); +}; + +const listUnshift = (cx, values) => { + return collectionOpWithMutations(cx, (list) => { + listBoundsSet(list, -values.length); + for (let ii = 0; ii < values.length; ii++) { + list.set(ii, values[ii]); + } + }); +}; + +const listShuffle = (cx, random) => { + random = random || Math.random; + + return collectionOpWithMutations(cx, (mutable) => { + // implementation of the Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + let current = mutable.size; + let destination; + let tmp; + + while (current) { + destination = Math.floor(random() * current--); + + tmp = mutable.get(destination); + mutable.set(destination, mutable.get(current)); + mutable.set(current, tmp); + } + }); +}; + +const listConcat = (cx, collections) => { + const seqs = []; + + for (let i = 0; i < collections.length; i++) { + const argument = collections[i]; + const seq = SeqIndexedWhenNotIndexed( + typeof argument !== 'string' && probeHasIterator(argument) + ? argument + : [argument] + ); + + if (seq.size !== 0) { + seqs.push(seq); + } + } + if (seqs.length === 0) { + return cx; + } + if (cx.size === 0 && !cx.__ownerID && seqs.length === 1) { + return List(seqs[0]); + } + + return collectionOpWithMutations(cx, (list) => { + seqs.forEach((seq) => seq.forEach((value) => list.push(value))); + }); +}; + +const listMap = (cx, mapper, context) => { + return collectionOpWithMutations(cx, (list) => { + for (let i = 0; i < cx.size; i++) { + list.set(i, mapper.call(context, list.get(i), i, cx)); + } + }); +}; + +const listSlice = (cx, begin, end) => { + const size = cx.size; + if (wholeSlice(begin, end, size)) { + return cx; + } + return listBoundsSet(cx, resolveBegin(begin, size), resolveEnd(end, size)); +}; + +const list__iterator = (cx, type, reverse) => { + let index = reverse ? cx.size : 0; + const values = kernelIndexedOpIterate(cx, reverse); + return new Iterator(() => { + const value = values(); + return value === DONE + ? iteratorDone() + : iteratorValue(type, reverse ? --index : index++, value); + }); +}; + +const list__iterate = (cx, fn, reverse) => { + let index = reverse ? cx.size : 0; + const values = kernelIndexedOpIterate(cx, reverse); + let value; + while ((value = values()) !== DONE) { + if (fn(value, reverse ? --index : index++, cx) === false) { + break; + } + } + return index; +}; + +const list__ensureOwner = (cx, ownerID) => { + if (ownerID === cx.__ownerID) { + return cx; + } + if (!ownerID) { + if (cx.size === 0) { + return listCreateEmpty(); + } + cx.__ownerID = ownerID; + cx.__altered = false; + return cx; + } + return listCreate( + cx._origin, + cx._capacity, + cx._level, + cx._root, + cx._tail, + ownerID, + cx.__hash + ); +}; + +const listPropertiesCreate = ( + (cache) => () => + cache || + (cache = Object.assign( + {}, + collectionIndexedPropertiesCreate(), + { + create: (value) => List(value), + ['@@transducer/init']() { + return collectionOpAsMutable(this); + }, + ['@@transducer/step'](result, arr) { + return result.push(arr); + }, + ['@@transducer/result']: (obj) => { + return collectionOpAsImmutable(obj); + }, + }, + transformToMethods({ + [IS_LIST_SYMBOL]: true, + [DELETE]: listRemove, + toString: listToString, + get: listGet, + set: listUpdate, + remove: listRemove, + insert: listInsert, + clear: listClear, + push: utilFlagSpread(listPush), + pop: (cx) => listBoundsSet(cx, 0, -1), + unshift: utilFlagSpread(listUnshift), + shift: (cx) => listBoundsSet(cx, 1), + shuffle: (cx, random) => listShuffle(cx, random), + concat: utilFlagSpread(listConcat), + setSize: (cx, size) => listBoundsSet(cx, 0, size), + map: listMap, + slice: listSlice, + wasAltered: collectionOpWasAltered, + __iterator: list__iterator, + __iterate: list__iterate, + __ensureOwner: list__ensureOwner, + }) + )) +)(); + +const listCreate = (origin, capacity, level, root, tail, ownerID, hash) => { + const list = Object.create(listPropertiesCreate()); + list.size = capacity - origin; + list._origin = origin; + list._capacity = capacity; + list._level = level; + list._root = root; + list._tail = tail; + list.__ownerID = ownerID; + list.__hash = hash; + list.__altered = false; + list.__shape = SHAPE_LIST; + + return list; +}; + +const listCreateEmpty = () => { + return listCreate(0, 0, SHIFT); +}; + +const List = (value) => { + if (value === undefined || value === null) { + return listCreateEmpty(); + } + if (probeIsList(value)) { + return value; + } + const iter = SeqIndexed(value); + const size = iter.size; + if (size === 0) { + return listCreateEmpty(); + } + utilAssertNotInfinite(size); + if (size > 0 && size < SIZE) { + return listCreate( + 0, + size, + SHIFT, + null, + kernelIndexedVNodeCreate(iter.toArray()) + ); + } + + return collectionOpWithMutations(listCreateEmpty(), (list) => { + list.setSize(size); + iter.forEach((v, i) => list.set(i, v)); + }); +}; + +Object.assign(List, { + isList: probeIsList, + of: (...args) => List(args), +}); + +export { List, listPropertiesCreate, listCreateEmpty }; diff --git a/src/Map.js b/src/Map.js index dd9faf5e49..a06a26e7f9 100644 --- a/src/Map.js +++ b/src/Map.js @@ -1,555 +1,38 @@ -import { is } from './is'; -import { Collection, KeyedCollection, KeyedCollectionImpl } from './Collection'; -import { IS_MAP_SYMBOL, isMap } from './predicates/isMap'; -import { isOrdered } from './predicates/isOrdered'; +import transformToMethods from './transformToMethods'; + import { DELETE, - SHIFT, - SIZE, - MASK, NOT_SET, - OwnerID, - MakeRef, - SetRef, -} from './TrieUtils'; -import { hash } from './Hash'; -import { Iterator, iteratorValue, iteratorDone } from './Iterator'; -import { sortFactory } from './Operations'; -import arrCopy from './utils/arrCopy'; -import assertNotInfinite from './utils/assertNotInfinite'; -import { setIn } from './methods/setIn'; -import { deleteIn } from './methods/deleteIn'; -import { update } from './methods/update'; -import { updateIn } from './methods/updateIn'; -import { merge, mergeWith } from './methods/merge'; -import { mergeDeep, mergeDeepWith } from './methods/mergeDeep'; -import { mergeIn } from './methods/mergeIn'; -import { mergeDeepIn } from './methods/mergeDeepIn'; -import { withMutations } from './methods/withMutations'; -import { asMutable } from './methods/asMutable'; -import { asImmutable } from './methods/asImmutable'; -import { wasAltered } from './methods/wasAltered'; - -import { OrderedMap } from './OrderedMap'; - -export const Map = (value) => - value === undefined || value === null - ? emptyMap() - : isMap(value) && !isOrdered(value) - ? value - : emptyMap().withMutations((map) => { - const iter = KeyedCollection(value); - assertNotInfinite(iter.size); - iter.forEach((v, k) => map.set(k, v)); - }); - -export class MapImpl extends KeyedCollectionImpl { - create(value) { - return Map(value); - } - - toString() { - return this.__toString('Map {', '}'); - } - - // @pragma Access - - get(k, notSetValue) { - return this._root - ? this._root.get(0, undefined, k, notSetValue) - : notSetValue; - } - - // @pragma Modification - - set(k, v) { - return updateMap(this, k, v); - } - - remove(k) { - return updateMap(this, k, NOT_SET); - } - - deleteAll(keys) { - const collection = Collection(keys); - - if (collection.size === 0) { - return this; - } - - return this.withMutations((map) => { - collection.forEach((key) => map.remove(key)); - }); - } - - clear() { - if (this.size === 0) { - return this; - } - if (this.__ownerID) { - this.size = 0; - this._root = null; - this.__hash = undefined; - this.__altered = true; - return this; - } - return emptyMap(); - } - - // @pragma Composition - - sort(comparator) { - // Late binding - return OrderedMap(sortFactory(this, comparator)); - } - - sortBy(mapper, comparator) { - // Late binding - return OrderedMap(sortFactory(this, comparator, mapper)); - } - - map(mapper, context) { - return this.withMutations((map) => { - map.forEach((value, key) => { - map.set(key, mapper.call(context, value, key, this)); - }); - }); - } - - // @pragma Mutability - - __iterator(type, reverse) { - return new MapIterator(this, type, reverse); - } - - __iterate(fn, reverse) { - let iterations = 0; - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - this._root && - this._root.iterate((entry) => { - iterations++; - return fn(entry[1], entry[0], this); - }, reverse); - return iterations; - } - - __ensureOwner(ownerID) { - if (ownerID === this.__ownerID) { - return this; - } - if (!ownerID) { - if (this.size === 0) { - return emptyMap(); - } - this.__ownerID = ownerID; - this.__altered = false; - return this; - } - return makeMap(this.size, this._root, ownerID, this.__hash); - } -} - -Map.isMap = isMap; - -const MapPrototype = MapImpl.prototype; -MapPrototype[IS_MAP_SYMBOL] = true; -MapPrototype[DELETE] = MapPrototype.remove; -MapPrototype.removeAll = MapPrototype.deleteAll; -MapPrototype.setIn = setIn; -MapPrototype.removeIn = MapPrototype.deleteIn = deleteIn; -MapPrototype.update = update; -MapPrototype.updateIn = updateIn; -MapPrototype.merge = MapPrototype.concat = merge; -MapPrototype.mergeWith = mergeWith; -MapPrototype.mergeDeep = mergeDeep; -MapPrototype.mergeDeepWith = mergeDeepWith; -MapPrototype.mergeIn = mergeIn; -MapPrototype.mergeDeepIn = mergeDeepIn; -MapPrototype.withMutations = withMutations; -MapPrototype.wasAltered = wasAltered; -MapPrototype.asImmutable = asImmutable; -MapPrototype['@@transducer/init'] = MapPrototype.asMutable = asMutable; -MapPrototype['@@transducer/step'] = function (result, arr) { - return result.set(arr[0], arr[1]); -}; -MapPrototype['@@transducer/result'] = function (obj) { - return obj.asImmutable(); -}; - -// #pragma Trie Nodes - -class ArrayMapNode { - constructor(ownerID, entries) { - this.ownerID = ownerID; - this.entries = entries; - } - - get(shift, keyHash, key, notSetValue) { - const entries = this.entries; - for (let ii = 0, len = entries.length; ii < len; ii++) { - if (is(key, entries[ii][0])) { - return entries[ii][1]; - } - } - return notSetValue; - } - - update(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) { - const removed = value === NOT_SET; - - const entries = this.entries; - let idx = 0; - const len = entries.length; - for (; idx < len; idx++) { - if (is(key, entries[idx][0])) { - break; - } - } - const exists = idx < len; - - if (exists ? entries[idx][1] === value : removed) { - return this; - } - - SetRef(didAlter); - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - (removed || !exists) && SetRef(didChangeSize); - - if (removed && entries.length === 1) { - return; // undefined - } - - if (!exists && !removed && entries.length >= MAX_ARRAY_MAP_SIZE) { - return createNodes(ownerID, entries, key, value); - } - - const isEditable = ownerID && ownerID === this.ownerID; - const newEntries = isEditable ? entries : arrCopy(entries); - - if (exists) { - if (removed) { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - idx === len - 1 - ? newEntries.pop() - : (newEntries[idx] = newEntries.pop()); - } else { - newEntries[idx] = [key, value]; - } - } else { - newEntries.push([key, value]); - } - - if (isEditable) { - this.entries = newEntries; - return this; - } - - return new ArrayMapNode(ownerID, newEntries); - } -} - -class BitmapIndexedNode { - constructor(ownerID, bitmap, nodes) { - this.ownerID = ownerID; - this.bitmap = bitmap; - this.nodes = nodes; - } - - get(shift, keyHash, key, notSetValue) { - if (keyHash === undefined) { - keyHash = hash(key); - } - const bit = 1 << ((shift === 0 ? keyHash : keyHash >>> shift) & MASK); - const bitmap = this.bitmap; - return (bitmap & bit) === 0 - ? notSetValue - : this.nodes[popCount(bitmap & (bit - 1))].get( - shift + SHIFT, - keyHash, - key, - notSetValue - ); - } - - update(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) { - if (keyHash === undefined) { - keyHash = hash(key); - } - const keyHashFrag = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; - const bit = 1 << keyHashFrag; - const bitmap = this.bitmap; - const exists = (bitmap & bit) !== 0; - - if (!exists && value === NOT_SET) { - return this; - } - - const idx = popCount(bitmap & (bit - 1)); - const nodes = this.nodes; - const node = exists ? nodes[idx] : undefined; - const newNode = updateNode( - node, - ownerID, - shift + SHIFT, - keyHash, - key, - value, - didChangeSize, - didAlter - ); - - if (newNode === node) { - return this; - } - - if (!exists && newNode && nodes.length >= MAX_BITMAP_INDEXED_SIZE) { - return expandNodes(ownerID, nodes, bitmap, keyHashFrag, newNode); - } - - if ( - exists && - !newNode && - nodes.length === 2 && - isLeafNode(nodes[idx ^ 1]) - ) { - return nodes[idx ^ 1]; - } - - if (exists && newNode && nodes.length === 1 && isLeafNode(newNode)) { - return newNode; - } - - const isEditable = ownerID && ownerID === this.ownerID; - const newBitmap = exists ? (newNode ? bitmap : bitmap ^ bit) : bitmap | bit; - const newNodes = exists - ? newNode - ? setAt(nodes, idx, newNode, isEditable) - : spliceOut(nodes, idx, isEditable) - : spliceIn(nodes, idx, newNode, isEditable); - - if (isEditable) { - this.bitmap = newBitmap; - this.nodes = newNodes; - return this; - } - - return new BitmapIndexedNode(ownerID, newBitmap, newNodes); - } -} - -class HashArrayMapNode { - constructor(ownerID, count, nodes) { - this.ownerID = ownerID; - this.count = count; - this.nodes = nodes; - } - - get(shift, keyHash, key, notSetValue) { - if (keyHash === undefined) { - keyHash = hash(key); - } - const idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; - const node = this.nodes[idx]; - return node - ? node.get(shift + SHIFT, keyHash, key, notSetValue) - : notSetValue; - } - - update(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) { - if (keyHash === undefined) { - keyHash = hash(key); - } - const idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; - const removed = value === NOT_SET; - const nodes = this.nodes; - const node = nodes[idx]; - - if (removed && !node) { - return this; - } - - const newNode = updateNode( - node, - ownerID, - shift + SHIFT, - keyHash, - key, - value, - didChangeSize, - didAlter - ); - if (newNode === node) { - return this; - } - - let newCount = this.count; - if (!node) { - newCount++; - } else if (!newNode) { - newCount--; - if (newCount < MIN_HASH_ARRAY_MAP_SIZE) { - return packNodes(ownerID, nodes, newCount, idx); - } - } - - const isEditable = ownerID && ownerID === this.ownerID; - const newNodes = setAt(nodes, idx, newNode, isEditable); - - if (isEditable) { - this.count = newCount; - this.nodes = newNodes; - return this; - } - - return new HashArrayMapNode(ownerID, newCount, newNodes); - } -} - -class HashCollisionNode { - constructor(ownerID, keyHash, entries) { - this.ownerID = ownerID; - this.keyHash = keyHash; - this.entries = entries; - } - - get(shift, keyHash, key, notSetValue) { - const entries = this.entries; - for (let ii = 0, len = entries.length; ii < len; ii++) { - if (is(key, entries[ii][0])) { - return entries[ii][1]; - } - } - return notSetValue; - } - - update(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) { - if (keyHash === undefined) { - keyHash = hash(key); - } + IS_MAP_SYMBOL, + SHAPE_MAP, + ITERATOR_SYMBOL, +} from './const'; - const removed = value === NOT_SET; - - if (keyHash !== this.keyHash) { - if (removed) { - return this; - } - SetRef(didAlter); - SetRef(didChangeSize); - return mergeIntoNode(this, ownerID, shift, keyHash, [key, value]); - } - - const entries = this.entries; - let idx = 0; - const len = entries.length; - for (; idx < len; idx++) { - if (is(key, entries[idx][0])) { - break; - } - } - const exists = idx < len; - - if (exists ? entries[idx][1] === value : removed) { - return this; - } - - SetRef(didAlter); - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - (removed || !exists) && SetRef(didChangeSize); - - if (removed && len === 2) { - return new ValueNode(ownerID, this.keyHash, entries[idx ^ 1]); - } - - const isEditable = ownerID && ownerID === this.ownerID; - const newEntries = isEditable ? entries : arrCopy(entries); - - if (exists) { - if (removed) { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - idx === len - 1 - ? newEntries.pop() - : (newEntries[idx] = newEntries.pop()); - } else { - newEntries[idx] = [key, value]; - } - } else { - newEntries.push([key, value]); - } - - if (isEditable) { - this.entries = newEntries; - return this; - } - - return new HashCollisionNode(ownerID, this.keyHash, newEntries); - } -} - -class ValueNode { - constructor(ownerID, keyHash, entry) { - this.ownerID = ownerID; - this.keyHash = keyHash; - this.entry = entry; - } - - get(shift, keyHash, key, notSetValue) { - return is(key, this.entry[0]) ? this.entry[1] : notSetValue; - } - - update(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) { - const removed = value === NOT_SET; - const keyMatch = is(key, this.entry[0]); - if (keyMatch ? value === this.entry[1] : removed) { - return this; - } - - SetRef(didAlter); +import { Iterator, iteratorValue, iteratorDone } from './Iterator'; - if (removed) { - SetRef(didChangeSize); - return; // undefined - } +import { MakeRef } from './TrieUtils'; - if (keyMatch) { - if (ownerID && ownerID === this.ownerID) { - this.entry[1] = value; - return this; - } - return new ValueNode(ownerID, this.keyHash, [key, value]); - } +import { SeqKeyed, SeqWhenNotCollection } from './Seq'; - SetRef(didChangeSize); - return mergeIntoNode(this, ownerID, shift, hash(key), [key, value]); - } -} +import { probeIsMap, probeIsOrdered } from './probe'; -// #pragma Iterators +import { utilQuoteString, utilAssertNotInfinite } from './util'; -ArrayMapNode.prototype.iterate = HashCollisionNode.prototype.iterate = - function (fn, reverse) { - const entries = this.entries; - for (let ii = 0, maxIndex = entries.length - 1; ii <= maxIndex; ii++) { - if (fn(entries[reverse ? maxIndex - ii : ii]) === false) { - return false; - } - } - }; +import { + collectionOpForEach, + collectionOpWithMutations, + collectionOpWasAltered, + collectionOpAsMutable, + collectionOpToObject, +} from './collection/collection'; -BitmapIndexedNode.prototype.iterate = HashArrayMapNode.prototype.iterate = - function (fn, reverse) { - const nodes = this.nodes; - for (let ii = 0, maxIndex = nodes.length - 1; ii <= maxIndex; ii++) { - const node = nodes[reverse ? maxIndex - ii : ii]; - if (node && node.iterate(fn, reverse) === false) { - return false; - } - } - }; +import { collectionKeyedPropertiesCreate } from './collection/collectionKeyed'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -ValueNode.prototype.iterate = function (fn, reverse) { - return fn(this.entry); -}; +import { + kernelKeyedArrayMapCreate, + kernelKeyedOpUpdateOrCreate, + kernelKeyedArrayEntriesIterate, +} from './collection/kernelKeyed'; class MapIterator extends Iterator { constructor(map, type, reverse) { @@ -610,22 +93,164 @@ function mapIteratorFrame(node, prev) { }; } -function makeMap(size, root, ownerID, hash) { - const map = Object.create(MapPrototype); +const mapOpCreate = (m, value) => { + return Map(value); +}; + +const mapOpToString = (m) => { + return m.__toString('Map {', '}'); +}; + +const mapOpGet = (m, k, notSetValue) => { + return m._root ? m._root.get(0, undefined, k, notSetValue) : notSetValue; +}; + +// @pragma Modification + +const mapOpSet = (m, k, v) => { + return mapUpdate(m, k, v); +}; + +const mapOpRemove = (m, k) => { + return mapUpdate(m, k, NOT_SET); +}; + +const mapOpDeleteAll = (m, keys) => { + const collection = SeqWhenNotCollection(keys); + + if (collection.size === 0) { + return m; + } + + return collectionOpWithMutations(m, (map) => { + collection.forEach((key) => map.remove(key)); + }); +}; + +const mapOpClear = (m) => { + if (m.size === 0) { + return m; + } + if (m.__ownerID) { + m.size = 0; + m._root = null; + m.__hash = undefined; + m.__altered = true; + return m; + } + return mapCreateEmpty(); +}; + +const mapOpMap = (m, mapper, context) => { + return collectionOpWithMutations(m, (map) => { + map.forEach((value, key) => { + map.set(key, mapper.call(context, value, key, m)); + }); + }); +}; + +const mapOpIterator = (m, type, reverse) => { + return new MapIterator(m, type, reverse); +}; + +const mapOpIterate = (m, fn, reverse) => { + let iterations = 0; + + if (m._root) { + if (m._root.nodetype === 'nodeHashArrayMap') { + kernelKeyedArrayEntriesIterate( + m._root, + (entry) => { + iterations++; + return fn(entry[1], entry[0], m); + }, + reverse + ); + } else { + m._root.iterate((entry) => { + iterations++; + return fn(entry[1], entry[0], m); + }, reverse); + } + } + + return iterations; +}; + +const mapOpEnsureOwner = (m, ownerID) => { + if (ownerID === m.__ownerID) { + return m; + } + if (!ownerID) { + if (m.size === 0) { + return mapCreateEmpty(); + } + m.__ownerID = ownerID; + m.__altered = false; + return m; + } + return mapCreate(m.size, m._root, ownerID, m.__hash); +}; + +const mapOpToStringMapper = (m, v, k) => { + return utilQuoteString(k) + ': ' + utilQuoteString(v); +}; + +const mapPropertiesCreate = ((cache) => () => { + return ( + cache || + (cache = Object.assign( + {}, + collectionKeyedPropertiesCreate(), + { + ['@@transducer/init']() { + return collectionOpAsMutable(this); + }, + ['@@transducer/step']: (result, arr) => { + return result.set(arr[0], arr[1]); + }, + ['@@transducer/result']: (obj) => { + return obj.asImmutable(); + }, + }, + transformToMethods({ + [IS_MAP_SYMBOL]: true, + toJSON: collectionOpToObject, + __toStringMapper: mapOpToStringMapper, + create: mapOpCreate, + toString: mapOpToString, + get: mapOpGet, + set: mapOpSet, + remove: mapOpRemove, + removeAll: mapOpDeleteAll, + [DELETE]: mapOpRemove, + deleteAll: mapOpDeleteAll, + clear: mapOpClear, + map: mapOpMap, + wasAltered: collectionOpWasAltered, + __iterator: mapOpIterator, + __iterate: mapOpIterate, + __ensureOwner: mapOpEnsureOwner, + }) + )) + ); +})(); + +const mapCreate = (size, root, ownerID, hash) => { + const map = Object.create(mapPropertiesCreate()); + + map[ITERATOR_SYMBOL] = map.entries; map.size = size; map._root = root; map.__ownerID = ownerID; map.__hash = hash; map.__altered = false; - return map; -} + map.__shape = SHAPE_MAP; -let EMPTY_MAP; -export function emptyMap() { - return EMPTY_MAP || (EMPTY_MAP = makeMap(0)); -} + return map; +}; -function updateMap(map, k, v) { +const mapUpdate = (map, k, v) => { let newRoot; let newSize; if (!map._root) { @@ -633,11 +258,11 @@ function updateMap(map, k, v) { return map; } newSize = 1; - newRoot = new ArrayMapNode(map.__ownerID, [[k, v]]); + newRoot = kernelKeyedArrayMapCreate(map.__ownerID, [[k, v]]); } else { const didChangeSize = MakeRef(); const didAlter = MakeRef(); - newRoot = updateNode( + newRoot = kernelKeyedOpUpdateOrCreate( map._root, map.__ownerID, 0, @@ -659,149 +284,27 @@ function updateMap(map, k, v) { map.__altered = true; return map; } - return newRoot ? makeMap(newSize, newRoot) : emptyMap(); -} - -function updateNode( - node, - ownerID, - shift, - keyHash, - key, - value, - didChangeSize, - didAlter -) { - if (!node) { - if (value === NOT_SET) { - return node; - } - SetRef(didAlter); - SetRef(didChangeSize); - return new ValueNode(ownerID, keyHash, [key, value]); - } - return node.update( - ownerID, - shift, - keyHash, - key, - value, - didChangeSize, - didAlter - ); -} - -function isLeafNode(node) { - return ( - node.constructor === ValueNode || node.constructor === HashCollisionNode - ); -} - -function mergeIntoNode(node, ownerID, shift, keyHash, entry) { - if (node.keyHash === keyHash) { - return new HashCollisionNode(ownerID, keyHash, [node.entry, entry]); - } - - const idx1 = (shift === 0 ? node.keyHash : node.keyHash >>> shift) & MASK; - const idx2 = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; - - let newNode; - const nodes = - idx1 === idx2 - ? [mergeIntoNode(node, ownerID, shift + SHIFT, keyHash, entry)] - : ((newNode = new ValueNode(ownerID, keyHash, entry)), - idx1 < idx2 ? [node, newNode] : [newNode, node]); - - return new BitmapIndexedNode(ownerID, (1 << idx1) | (1 << idx2), nodes); -} - -function createNodes(ownerID, entries, key, value) { - if (!ownerID) { - ownerID = new OwnerID(); - } - let node = new ValueNode(ownerID, hash(key), [key, value]); - for (let ii = 0; ii < entries.length; ii++) { - const entry = entries[ii]; - node = node.update(ownerID, 0, undefined, entry[0], entry[1]); - } - return node; -} - -function packNodes(ownerID, nodes, count, excluding) { - let bitmap = 0; - let packedII = 0; - const packedNodes = new Array(count); - for (let ii = 0, bit = 1, len = nodes.length; ii < len; ii++, bit <<= 1) { - const node = nodes[ii]; - if (node !== undefined && ii !== excluding) { - bitmap |= bit; - packedNodes[packedII++] = node; - } - } - return new BitmapIndexedNode(ownerID, bitmap, packedNodes); -} + return newRoot ? mapCreate(newSize, newRoot) : mapCreateEmpty(); +}; -function expandNodes(ownerID, nodes, bitmap, including, node) { - let count = 0; - const expandedNodes = new Array(SIZE); - for (let ii = 0; bitmap !== 0; ii++, bitmap >>>= 1) { - expandedNodes[ii] = bitmap & 1 ? nodes[count++] : undefined; - } - expandedNodes[including] = node; - return new HashArrayMapNode(ownerID, count + 1, expandedNodes); -} +const mapCreateEmpty = ( + (mapEmptyCached) => () => + mapEmptyCached || (mapEmptyCached = mapCreate(0)) +)(); -function popCount(x) { - x -= (x >> 1) & 0x55555555; - x = (x & 0x33333333) + ((x >> 2) & 0x33333333); - x = (x + (x >> 4)) & 0x0f0f0f0f; - x += x >> 8; - x += x >> 16; - return x & 0x7f; -} +const Map = (value) => + value === undefined || value === null + ? mapCreateEmpty() + : probeIsMap(value) && !probeIsOrdered(value) + ? value + : collectionOpWithMutations(mapCreateEmpty(), (map) => { + const iter = SeqKeyed(value); -function setAt(array, idx, val, canEdit) { - const newArray = canEdit ? array : arrCopy(array); - newArray[idx] = val; - return newArray; -} + utilAssertNotInfinite(iter.size); -function spliceIn(array, idx, val, canEdit) { - const newLen = array.length + 1; - if (canEdit && idx + 1 === newLen) { - array[idx] = val; - return array; - } - const newArray = new Array(newLen); - let after = 0; - for (let ii = 0; ii < newLen; ii++) { - if (ii === idx) { - newArray[ii] = val; - after = -1; - } else { - newArray[ii] = array[ii + after]; - } - } - return newArray; -} + collectionOpForEach(iter, (v, k) => mapUpdate(map, k, v)); + }); -function spliceOut(array, idx, canEdit) { - const newLen = array.length - 1; - if (canEdit && idx === newLen) { - array.pop(); - return array; - } - const newArray = new Array(newLen); - let after = 0; - for (let ii = 0; ii < newLen; ii++) { - if (ii === idx) { - after = 1; - } - newArray[ii] = array[ii + after]; - } - return newArray; -} +Map.isMap = probeIsMap; -const MAX_ARRAY_MAP_SIZE = SIZE / 4; -const MAX_BITMAP_INDEXED_SIZE = SIZE / 2; -const MIN_HASH_ARRAY_MAP_SIZE = SIZE / 4; +export { Map, mapPropertiesCreate, mapCreate, mapCreateEmpty }; diff --git a/src/MapOrdered.js b/src/MapOrdered.js new file mode 100644 index 0000000000..f28974e653 --- /dev/null +++ b/src/MapOrdered.js @@ -0,0 +1,181 @@ +import transformToMethods from './transformToMethods'; + +import { SIZE } from './TrieUtils'; + +import { IS_ORDERED_SYMBOL, DELETE, NOT_SET } from './const'; + +import { utilAssertNotInfinite } from './util'; + +import { probeIsMapOrdered } from './probe'; + +import { SeqKeyedWhenNotKeyed } from './Seq'; + +import { mapPropertiesCreate, mapCreateEmpty } from './Map'; + +import { listCreateEmpty } from './List'; + +import { collectionOpWithMutations } from './collection/collection'; + +const mapOrderedOpUpdate = (omap, k, v) => { + const map = omap._map; + const list = omap._list; + const i = map.get(k); + const has = i !== undefined; + let newMap; + let newList; + if (v === NOT_SET) { + // removed + if (!has) { + return omap; + } + if (list.size >= SIZE && list.size >= map.size * 2) { + newList = list.filter((entry, idx) => entry !== undefined && i !== idx); + newMap = newList + .toKeyedSeq() + .map((entry) => entry[0]) + .flip() + .toMap(); + if (omap.__ownerID) { + newMap.__ownerID = newList.__ownerID = omap.__ownerID; + } + } else { + newMap = map.remove(k); + newList = i === list.size - 1 ? list.pop() : list.set(i, undefined); + } + } else if (has) { + if (v === list.get(i)[1]) { + return omap; + } + newMap = map; + newList = list.set(i, [k, v]); + } else { + newMap = map.set(k, list.size); + newList = list.set(list.size, [k, v]); + } + if (omap.__ownerID) { + omap.size = newMap.size; + omap._map = newMap; + omap._list = newList; + omap.__hash = undefined; + omap.__altered = true; + return omap; + } + return mapOrderedCreate(newMap, newList); +}; + +const mapOrderedOpRemove = (cx, k) => { + return mapOrderedOpUpdate(cx, k, NOT_SET); +}; + +const mapOrderedOpToString = (cx) => { + return cx.__toString('OrderedMap {', '}'); +}; + +const mapOrderedOpGet = (cx, k, notSetValue) => { + const index = cx._map.get(k); + return index !== undefined ? cx._list.get(index)[1] : notSetValue; +}; + +const mapOrderedOpClear = (cx) => { + if (cx.size === 0) { + return cx; + } + if (cx.__ownerID) { + cx.size = 0; + cx._map.clear(); + cx._list.clear(); + cx.__altered = true; + return cx; + } + return mapOrderedCreateEmpty(); +}; + +const mapOrderedOpIterate = (cx, fn, reverse) => { + return cx._list.__iterate((entry) => { + return entry && fn(entry[1], entry[0], cx); + }, reverse); +}; + +const mapOrderedOpIterator = (cx, type, reverse) => { + return cx._list.fromEntrySeq().__iterator(type, reverse); +}; + +const mapOrderedOpEnsureOwner = (cx, ownerID) => { + if (ownerID === cx.__ownerID) { + return cx; + } + const newMap = cx._map.__ensureOwner(ownerID); + const newList = cx._list.__ensureOwner(ownerID); + if (!ownerID) { + if (cx.size === 0) { + return mapOrderedCreateEmpty(); + } + cx.__ownerID = ownerID; + cx.__altered = false; + cx._map = newMap; + cx._list = newList; + return cx; + } + return mapOrderedCreate(newMap, newList, ownerID, cx.__hash); +}; + +const mapOrderedPropertiesCreate = ( + (cache) => () => + (cache = + cache || + (cache = Object.assign( + {}, + mapPropertiesCreate(), + { + create: MapOrdered, + }, + transformToMethods({ + [IS_ORDERED_SYMBOL]: true, + toString: mapOrderedOpToString, + get: mapOrderedOpGet, + set: mapOrderedOpUpdate, + remove: mapOrderedOpRemove, + clear: mapOrderedOpClear, + [DELETE]: mapOrderedOpRemove, + __iterate: mapOrderedOpIterate, + __iterator: mapOrderedOpIterator, + __ensureOwner: mapOrderedOpEnsureOwner, + }) + ))) +)(); + +const mapOrderedCreate = (map, list, ownerID, hash) => { + const omap = Object.create(mapOrderedPropertiesCreate()); + omap.size = map ? map.size : 0; + + omap._map = map; + omap._list = list; + omap.__ownerID = ownerID; + omap.__hash = hash; + omap.__altered = false; + omap.__shape = 'orderedmap'; + + return omap; +}; + +const mapOrderedCreateEmpty = ( + (cache) => () => + cache || (cache = mapOrderedCreate(mapCreateEmpty(), listCreateEmpty())) +)(); + +const MapOrdered = (value) => + value === undefined || value === null + ? mapOrderedCreateEmpty() + : probeIsMapOrdered(value) + ? value + : collectionOpWithMutations(mapOrderedCreateEmpty(), (map) => { + const iter = SeqKeyedWhenNotKeyed(value); + utilAssertNotInfinite(iter.size); + iter.forEach((v, k) => map.set(k, v)); + }); + +MapOrdered.of = (...args) => MapOrdered(args); +MapOrdered.isOrderedMap = probeIsMapOrdered; +MapOrdered.isMapOrdered = probeIsMapOrdered; + +export { MapOrdered, mapOrderedCreate, mapOrderedCreateEmpty }; diff --git a/src/Operations.js b/src/Operations.js deleted file mode 100644 index 9c3ba24920..0000000000 --- a/src/Operations.js +++ /dev/null @@ -1,1004 +0,0 @@ -import { - NOT_SET, - ensureSize, - wrapIndex, - wholeSlice, - resolveBegin, - resolveEnd, -} from './TrieUtils'; -import { - Collection, - KeyedCollection, - SetCollection, - IndexedCollection, -} from './Collection'; -import { isCollection } from './predicates/isCollection'; -import { IS_KEYED_SYMBOL, isKeyed } from './predicates/isKeyed'; -import { IS_INDEXED_SYMBOL, isIndexed } from './predicates/isIndexed'; -import { isOrdered, IS_ORDERED_SYMBOL } from './predicates/isOrdered'; -import { isSeq } from './predicates/isSeq'; -import { - getIterator, - Iterator, - iteratorValue, - iteratorDone, - ITERATE_KEYS, - ITERATE_VALUES, - ITERATE_ENTRIES, -} from './Iterator'; -import { - SeqImpl, - KeyedSeq, - SetSeq, - IndexedSeq, - keyedSeqFromValue, - indexedSeqFromValue, - ArraySeq, - KeyedSeqImpl, - IndexedSeqImpl, - SetSeqImpl, -} from './Seq'; - -import { Map } from './Map'; -import { OrderedMap } from './OrderedMap'; - -export class ToKeyedSequence extends KeyedSeqImpl { - constructor(indexed, useKeys) { - super(); - - this._iter = indexed; - this._useKeys = useKeys; - this.size = indexed.size; - } - - get(key, notSetValue) { - return this._iter.get(key, notSetValue); - } - - has(key) { - return this._iter.has(key); - } - - valueSeq() { - return this._iter.valueSeq(); - } - - reverse() { - const reversedSequence = reverseFactory(this, true); - if (!this._useKeys) { - reversedSequence.valueSeq = () => this._iter.toSeq().reverse(); - } - return reversedSequence; - } - - map(mapper, context) { - const mappedSequence = mapFactory(this, mapper, context); - if (!this._useKeys) { - mappedSequence.valueSeq = () => this._iter.toSeq().map(mapper, context); - } - return mappedSequence; - } - - __iterate(fn, reverse) { - return this._iter.__iterate((v, k) => fn(v, k, this), reverse); - } - - __iterator(type, reverse) { - return this._iter.__iterator(type, reverse); - } -} -ToKeyedSequence.prototype[IS_ORDERED_SYMBOL] = true; - -export class ToIndexedSequence extends IndexedSeqImpl { - constructor(iter) { - super(); - - this._iter = iter; - this.size = iter.size; - } - - includes(value) { - return this._iter.includes(value); - } - - __iterate(fn, reverse) { - let i = 0; - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - reverse && ensureSize(this); - return this._iter.__iterate( - (v) => fn(v, reverse ? this.size - ++i : i++, this), - reverse - ); - } - - __iterator(type, reverse) { - const iterator = this._iter.__iterator(ITERATE_VALUES, reverse); - let i = 0; - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - reverse && ensureSize(this); - return new Iterator(() => { - const step = iterator.next(); - return step.done - ? step - : iteratorValue( - type, - reverse ? this.size - ++i : i++, - step.value, - step - ); - }); - } -} - -export class ToSetSequence extends SetSeqImpl { - constructor(iter) { - super(); - - this._iter = iter; - this.size = iter.size; - } - - has(key) { - return this._iter.includes(key); - } - - __iterate(fn, reverse) { - return this._iter.__iterate((v) => fn(v, v, this), reverse); - } - - __iterator(type, reverse) { - const iterator = this._iter.__iterator(ITERATE_VALUES, reverse); - return new Iterator(() => { - const step = iterator.next(); - return step.done - ? step - : iteratorValue(type, step.value, step.value, step); - }); - } -} - -export class FromEntriesSequence extends KeyedSeqImpl { - constructor(entries) { - super(); - - this._iter = entries; - this.size = entries.size; - } - - entrySeq() { - return this._iter.toSeq(); - } - - __iterate(fn, reverse) { - return this._iter.__iterate((entry) => { - // Check if entry exists first so array access doesn't throw for holes - // in the parent iteration. - if (entry) { - validateEntry(entry); - const indexedCollection = isCollection(entry); - return fn( - indexedCollection ? entry.get(1) : entry[1], - indexedCollection ? entry.get(0) : entry[0], - this - ); - } - }, reverse); - } - - __iterator(type, reverse) { - const iterator = this._iter.__iterator(ITERATE_VALUES, reverse); - return new Iterator(() => { - while (true) { - const step = iterator.next(); - if (step.done) { - return step; - } - const entry = step.value; - // Check if entry exists first so array access doesn't throw for holes - // in the parent iteration. - if (entry) { - validateEntry(entry); - const indexedCollection = isCollection(entry); - return iteratorValue( - type, - indexedCollection ? entry.get(0) : entry[0], - indexedCollection ? entry.get(1) : entry[1], - step - ); - } - } - }); - } -} - -ToIndexedSequence.prototype.cacheResult = - ToKeyedSequence.prototype.cacheResult = - ToSetSequence.prototype.cacheResult = - FromEntriesSequence.prototype.cacheResult = - cacheResultThrough; - -export function flipFactory(collection) { - const flipSequence = makeSequence(collection); - flipSequence._iter = collection; - flipSequence.size = collection.size; - flipSequence.flip = () => collection; - flipSequence.reverse = function () { - const reversedSequence = collection.reverse.apply(this); // super.reverse() - reversedSequence.flip = () => collection.reverse(); - return reversedSequence; - }; - flipSequence.has = (key) => collection.includes(key); - flipSequence.includes = (key) => collection.has(key); - flipSequence.cacheResult = cacheResultThrough; - flipSequence.__iterateUncached = function (fn, reverse) { - return collection.__iterate((v, k) => fn(k, v, this) !== false, reverse); - }; - flipSequence.__iteratorUncached = function (type, reverse) { - if (type === ITERATE_ENTRIES) { - const iterator = collection.__iterator(type, reverse); - return new Iterator(() => { - const step = iterator.next(); - if (!step.done) { - const k = step.value[0]; - step.value[0] = step.value[1]; - step.value[1] = k; - } - return step; - }); - } - return collection.__iterator( - type === ITERATE_VALUES ? ITERATE_KEYS : ITERATE_VALUES, - reverse - ); - }; - return flipSequence; -} - -export function mapFactory(collection, mapper, context) { - const mappedSequence = makeSequence(collection); - mappedSequence.size = collection.size; - mappedSequence.has = (key) => collection.has(key); - mappedSequence.get = (key, notSetValue) => { - const v = collection.get(key, NOT_SET); - return v === NOT_SET - ? notSetValue - : mapper.call(context, v, key, collection); - }; - mappedSequence.__iterateUncached = function (fn, reverse) { - return collection.__iterate( - (v, k, c) => fn(mapper.call(context, v, k, c), k, this) !== false, - reverse - ); - }; - mappedSequence.__iteratorUncached = function (type, reverse) { - const iterator = collection.__iterator(ITERATE_ENTRIES, reverse); - return new Iterator(() => { - const step = iterator.next(); - if (step.done) { - return step; - } - const entry = step.value; - const key = entry[0]; - return iteratorValue( - type, - key, - mapper.call(context, entry[1], key, collection), - step - ); - }); - }; - return mappedSequence; -} - -export function reverseFactory(collection, useKeys) { - const reversedSequence = makeSequence(collection); - reversedSequence._iter = collection; - reversedSequence.size = collection.size; - reversedSequence.reverse = () => collection; - if (collection.flip) { - reversedSequence.flip = function () { - const flipSequence = flipFactory(collection); - flipSequence.reverse = () => collection.flip(); - return flipSequence; - }; - } - reversedSequence.get = (key, notSetValue) => - collection.get(useKeys ? key : -1 - key, notSetValue); - reversedSequence.has = (key) => collection.has(useKeys ? key : -1 - key); - reversedSequence.includes = (value) => collection.includes(value); - reversedSequence.cacheResult = cacheResultThrough; - reversedSequence.__iterate = function (fn, reverse) { - let i = 0; - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - reverse && ensureSize(collection); - return collection.__iterate( - (v, k) => fn(v, useKeys ? k : reverse ? this.size - ++i : i++, this), - !reverse - ); - }; - reversedSequence.__iterator = (type, reverse) => { - let i = 0; - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - reverse && ensureSize(collection); - const iterator = collection.__iterator(ITERATE_ENTRIES, !reverse); - return new Iterator(() => { - const step = iterator.next(); - if (step.done) { - return step; - } - const entry = step.value; - return iteratorValue( - type, - useKeys ? entry[0] : reverse ? this.size - ++i : i++, - entry[1], - step - ); - }); - }; - return reversedSequence; -} - -export function filterFactory(collection, predicate, context, useKeys) { - const filterSequence = makeSequence(collection); - if (useKeys) { - filterSequence.has = (key) => { - const v = collection.get(key, NOT_SET); - return v !== NOT_SET && !!predicate.call(context, v, key, collection); - }; - filterSequence.get = (key, notSetValue) => { - const v = collection.get(key, NOT_SET); - return v !== NOT_SET && predicate.call(context, v, key, collection) - ? v - : notSetValue; - }; - } - filterSequence.__iterateUncached = function (fn, reverse) { - let iterations = 0; - collection.__iterate((v, k, c) => { - if (predicate.call(context, v, k, c)) { - iterations++; - return fn(v, useKeys ? k : iterations - 1, this); - } - }, reverse); - return iterations; - }; - filterSequence.__iteratorUncached = function (type, reverse) { - const iterator = collection.__iterator(ITERATE_ENTRIES, reverse); - let iterations = 0; - return new Iterator(() => { - while (true) { - const step = iterator.next(); - if (step.done) { - return step; - } - const entry = step.value; - const key = entry[0]; - const value = entry[1]; - if (predicate.call(context, value, key, collection)) { - return iteratorValue(type, useKeys ? key : iterations++, value, step); - } - } - }); - }; - return filterSequence; -} - -export function countByFactory(collection, grouper, context) { - const groups = Map().asMutable(); - collection.__iterate((v, k) => { - groups.update(grouper.call(context, v, k, collection), 0, (a) => a + 1); - }); - return groups.asImmutable(); -} - -export function groupByFactory(collection, grouper, context) { - const isKeyedIter = isKeyed(collection); - const groups = (isOrdered(collection) ? OrderedMap() : Map()).asMutable(); - collection.__iterate((v, k) => { - groups.update( - grouper.call(context, v, k, collection), - (a) => ((a = a || []), a.push(isKeyedIter ? [k, v] : v), a) - ); - }); - const coerce = collectionClass(collection); - return groups.map((arr) => reify(collection, coerce(arr))).asImmutable(); -} - -export function partitionFactory(collection, predicate, context) { - const isKeyedIter = isKeyed(collection); - const groups = [[], []]; - collection.__iterate((v, k) => { - groups[predicate.call(context, v, k, collection) ? 1 : 0].push( - isKeyedIter ? [k, v] : v - ); - }); - const coerce = collectionClass(collection); - return groups.map((arr) => reify(collection, coerce(arr))); -} - -export function sliceFactory(collection, begin, end, useKeys) { - const originalSize = collection.size; - - if (wholeSlice(begin, end, originalSize)) { - return collection; - } - - // begin or end can not be resolved if they were provided as negative numbers and - // this collection's size is unknown. In that case, cache first so there is - // a known size and these do not resolve to NaN. - if (typeof originalSize === 'undefined' && (begin < 0 || end < 0)) { - return sliceFactory(collection.toSeq().cacheResult(), begin, end, useKeys); - } - - const resolvedBegin = resolveBegin(begin, originalSize); - const resolvedEnd = resolveEnd(end, originalSize); - - // Note: resolvedEnd is undefined when the original sequence's length is - // unknown and this slice did not supply an end and should contain all - // elements after resolvedBegin. - // In that case, resolvedSize will be NaN and sliceSize will remain undefined. - const resolvedSize = resolvedEnd - resolvedBegin; - let sliceSize; - if (resolvedSize === resolvedSize) { - sliceSize = resolvedSize < 0 ? 0 : resolvedSize; - } - - const sliceSeq = makeSequence(collection); - - // If collection.size is undefined, the size of the realized sliceSeq is - // unknown at this point unless the number of items to slice is 0 - sliceSeq.size = - sliceSize === 0 ? sliceSize : (collection.size && sliceSize) || undefined; - - if (!useKeys && isSeq(collection) && sliceSize >= 0) { - sliceSeq.get = function (index, notSetValue) { - index = wrapIndex(this, index); - return index >= 0 && index < sliceSize - ? collection.get(index + resolvedBegin, notSetValue) - : notSetValue; - }; - } - - sliceSeq.__iterateUncached = function (fn, reverse) { - if (sliceSize === 0) { - return 0; - } - if (reverse) { - return this.cacheResult().__iterate(fn, reverse); - } - let skipped = 0; - let isSkipping = true; - let iterations = 0; - collection.__iterate((v, k) => { - if (!(isSkipping && (isSkipping = skipped++ < resolvedBegin))) { - iterations++; - return ( - fn(v, useKeys ? k : iterations - 1, this) !== false && - iterations !== sliceSize - ); - } - }); - return iterations; - }; - - sliceSeq.__iteratorUncached = function (type, reverse) { - if (sliceSize !== 0 && reverse) { - return this.cacheResult().__iterator(type, reverse); - } - // Don't bother instantiating parent iterator if taking 0. - if (sliceSize === 0) { - return new Iterator(iteratorDone); - } - const iterator = collection.__iterator(type, reverse); - let skipped = 0; - let iterations = 0; - return new Iterator(() => { - while (skipped++ < resolvedBegin) { - iterator.next(); - } - if (++iterations > sliceSize) { - return iteratorDone(); - } - const step = iterator.next(); - if (useKeys || type === ITERATE_VALUES || step.done) { - return step; - } - if (type === ITERATE_KEYS) { - return iteratorValue(type, iterations - 1, undefined, step); - } - return iteratorValue(type, iterations - 1, step.value[1], step); - }); - }; - - return sliceSeq; -} - -export function takeWhileFactory(collection, predicate, context) { - const takeSequence = makeSequence(collection); - takeSequence.__iterateUncached = function (fn, reverse) { - if (reverse) { - return this.cacheResult().__iterate(fn, reverse); - } - let iterations = 0; - collection.__iterate( - (v, k, c) => - predicate.call(context, v, k, c) && ++iterations && fn(v, k, this) - ); - return iterations; - }; - takeSequence.__iteratorUncached = function (type, reverse) { - if (reverse) { - return this.cacheResult().__iterator(type, reverse); - } - const iterator = collection.__iterator(ITERATE_ENTRIES, reverse); - let iterating = true; - return new Iterator(() => { - if (!iterating) { - return iteratorDone(); - } - const step = iterator.next(); - if (step.done) { - return step; - } - const entry = step.value; - const k = entry[0]; - const v = entry[1]; - if (!predicate.call(context, v, k, this)) { - iterating = false; - return iteratorDone(); - } - return type === ITERATE_ENTRIES ? step : iteratorValue(type, k, v, step); - }); - }; - return takeSequence; -} - -export function skipWhileFactory(collection, predicate, context, useKeys) { - const skipSequence = makeSequence(collection); - skipSequence.__iterateUncached = function (fn, reverse) { - if (reverse) { - return this.cacheResult().__iterate(fn, reverse); - } - let isSkipping = true; - let iterations = 0; - collection.__iterate((v, k, c) => { - if (!(isSkipping && (isSkipping = predicate.call(context, v, k, c)))) { - iterations++; - return fn(v, useKeys ? k : iterations - 1, this); - } - }); - return iterations; - }; - skipSequence.__iteratorUncached = function (type, reverse) { - if (reverse) { - return this.cacheResult().__iterator(type, reverse); - } - const iterator = collection.__iterator(ITERATE_ENTRIES, reverse); - let skipping = true; - let iterations = 0; - return new Iterator(() => { - let step; - let k; - let v; - do { - step = iterator.next(); - if (step.done) { - if (useKeys || type === ITERATE_VALUES) { - return step; - } - if (type === ITERATE_KEYS) { - return iteratorValue(type, iterations++, undefined, step); - } - return iteratorValue(type, iterations++, step.value[1], step); - } - const entry = step.value; - k = entry[0]; - v = entry[1]; - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - skipping && (skipping = predicate.call(context, v, k, this)); - } while (skipping); - return type === ITERATE_ENTRIES ? step : iteratorValue(type, k, v, step); - }); - }; - return skipSequence; -} - -class ConcatSeq extends SeqImpl { - constructor(iterables) { - super(); - - this._wrappedIterables = iterables.flatMap((iterable) => { - if (iterable._wrappedIterables) { - return iterable._wrappedIterables; - } - return [iterable]; - }); - this.size = this._wrappedIterables.reduce((sum, iterable) => { - if (sum !== undefined) { - const size = iterable.size; - if (size !== undefined) { - return sum + size; - } - } - }, 0); - this[IS_KEYED_SYMBOL] = this._wrappedIterables[0][IS_KEYED_SYMBOL]; - this[IS_INDEXED_SYMBOL] = this._wrappedIterables[0][IS_INDEXED_SYMBOL]; - this[IS_ORDERED_SYMBOL] = this._wrappedIterables[0][IS_ORDERED_SYMBOL]; - } - - __iterateUncached(fn, reverse) { - if (this._wrappedIterables.length === 0) { - return; - } - - if (reverse) { - return this.cacheResult().__iterate(fn, reverse); - } - - let iterableIndex = 0; - const useKeys = isKeyed(this); - const iteratorType = useKeys ? ITERATE_ENTRIES : ITERATE_VALUES; - let currentIterator = this._wrappedIterables[iterableIndex].__iterator( - iteratorType, - reverse - ); - - let keepGoing = true; - let index = 0; - while (keepGoing) { - let next = currentIterator.next(); - while (next.done) { - iterableIndex++; - if (iterableIndex === this._wrappedIterables.length) { - return index; - } - currentIterator = this._wrappedIterables[iterableIndex].__iterator( - iteratorType, - reverse - ); - next = currentIterator.next(); - } - const fnResult = useKeys - ? fn(next.value[1], next.value[0], this) - : fn(next.value, index, this); - keepGoing = fnResult !== false; - index++; - } - return index; - } - - __iteratorUncached(type, reverse) { - if (this._wrappedIterables.length === 0) { - return new Iterator(iteratorDone); - } - - if (reverse) { - return this.cacheResult().__iterator(type, reverse); - } - - let iterableIndex = 0; - let currentIterator = this._wrappedIterables[iterableIndex].__iterator( - type, - reverse - ); - return new Iterator(() => { - let next = currentIterator.next(); - while (next.done) { - iterableIndex++; - if (iterableIndex === this._wrappedIterables.length) { - return next; - } - currentIterator = this._wrappedIterables[iterableIndex].__iterator( - type, - reverse - ); - next = currentIterator.next(); - } - return next; - }); - } -} - -export function concatFactory(collection, values) { - const isKeyedCollection = isKeyed(collection); - const iters = [collection] - .concat(values) - .map((v) => { - if (!isCollection(v)) { - v = isKeyedCollection - ? keyedSeqFromValue(v) - : indexedSeqFromValue(Array.isArray(v) ? v : [v]); - } else if (isKeyedCollection) { - v = KeyedCollection(v); - } - return v; - }) - .filter((v) => v.size !== 0); - - if (iters.length === 0) { - return collection; - } - - if (iters.length === 1) { - const singleton = iters[0]; - if ( - singleton === collection || - (isKeyedCollection && isKeyed(singleton)) || - (isIndexed(collection) && isIndexed(singleton)) - ) { - return singleton; - } - } - - return new ConcatSeq(iters); -} - -export function flattenFactory(collection, depth, useKeys) { - const flatSequence = makeSequence(collection); - flatSequence.__iterateUncached = function (fn, reverse) { - if (reverse) { - return this.cacheResult().__iterate(fn, reverse); - } - let iterations = 0; - let stopped = false; - function flatDeep(iter, currentDepth) { - iter.__iterate((v, k) => { - if ((!depth || currentDepth < depth) && isCollection(v)) { - flatDeep(v, currentDepth + 1); - } else { - iterations++; - if (fn(v, useKeys ? k : iterations - 1, flatSequence) === false) { - stopped = true; - } - } - return !stopped; - }, reverse); - } - flatDeep(collection, 0); - return iterations; - }; - flatSequence.__iteratorUncached = function (type, reverse) { - if (reverse) { - return this.cacheResult().__iterator(type, reverse); - } - let iterator = collection.__iterator(type, reverse); - const stack = []; - let iterations = 0; - return new Iterator(() => { - while (iterator) { - const step = iterator.next(); - if (step.done !== false) { - iterator = stack.pop(); - continue; - } - let v = step.value; - if (type === ITERATE_ENTRIES) { - v = v[1]; - } - if ((!depth || stack.length < depth) && isCollection(v)) { - stack.push(iterator); - iterator = v.__iterator(type, reverse); - } else { - return useKeys ? step : iteratorValue(type, iterations++, v, step); - } - } - return iteratorDone(); - }); - }; - return flatSequence; -} - -export function flatMapFactory(collection, mapper, context) { - const coerce = collectionClass(collection); - return collection - .toSeq() - .map((v, k) => coerce(mapper.call(context, v, k, collection))) - .flatten(true); -} - -export function interposeFactory(collection, separator) { - const interposedSequence = makeSequence(collection); - interposedSequence.size = collection.size && collection.size * 2 - 1; - interposedSequence.__iterateUncached = function (fn, reverse) { - let iterations = 0; - collection.__iterate( - (v) => - (!iterations || fn(separator, iterations++, this) !== false) && - fn(v, iterations++, this) !== false, - reverse - ); - return iterations; - }; - interposedSequence.__iteratorUncached = function (type, reverse) { - const iterator = collection.__iterator(ITERATE_VALUES, reverse); - let iterations = 0; - let step; - return new Iterator(() => { - if (!step || iterations % 2) { - step = iterator.next(); - if (step.done) { - return step; - } - } - return iterations % 2 - ? iteratorValue(type, iterations++, separator) - : iteratorValue(type, iterations++, step.value, step); - }); - }; - return interposedSequence; -} - -export function sortFactory(collection, comparator, mapper) { - if (!comparator) { - comparator = defaultComparator; - } - const isKeyedCollection = isKeyed(collection); - let index = 0; - const entries = collection - .toSeq() - .map((v, k) => [k, v, index++, mapper ? mapper(v, k, collection) : v]) - .valueSeq() - .toArray(); - entries - .sort((a, b) => comparator(a[3], b[3]) || a[2] - b[2]) - .forEach( - isKeyedCollection - ? (v, i) => { - entries[i].length = 2; - } - : (v, i) => { - entries[i] = v[1]; - } - ); - return isKeyedCollection - ? KeyedSeq(entries) - : isIndexed(collection) - ? IndexedSeq(entries) - : SetSeq(entries); -} - -export function maxFactory(collection, comparator, mapper) { - if (!comparator) { - comparator = defaultComparator; - } - if (mapper) { - const entry = collection - .toSeq() - .map((v, k) => [v, mapper(v, k, collection)]) - .reduce((a, b) => (maxCompare(comparator, a[1], b[1]) ? b : a)); - return entry && entry[0]; - } - return collection.reduce((a, b) => (maxCompare(comparator, a, b) ? b : a)); -} - -function maxCompare(comparator, a, b) { - const comp = comparator(b, a); - // b is considered the new max if the comparator declares them equal, but - // they are not equal and b is in fact a nullish value. - return ( - (comp === 0 && b !== a && (b === undefined || b === null || b !== b)) || - comp > 0 - ); -} - -export function zipWithFactory(keyIter, zipper, iters, zipAll) { - const zipSequence = makeSequence(keyIter); - const sizes = new ArraySeq(iters).map((i) => i.size); - zipSequence.size = zipAll ? sizes.max() : sizes.min(); - // Note: this a generic base implementation of __iterate in terms of - // __iterator which may be more generically useful in the future. - zipSequence.__iterate = function (fn, reverse) { - /* generic: - var iterator = this.__iterator(ITERATE_ENTRIES, reverse); - var step; - var iterations = 0; - while (!(step = iterator.next()).done) { - iterations++; - if (fn(step.value[1], step.value[0], this) === false) { - break; - } - } - return iterations; - */ - // indexed: - const iterator = this.__iterator(ITERATE_VALUES, reverse); - let step; - let iterations = 0; - while (!(step = iterator.next()).done) { - if (fn(step.value, iterations++, this) === false) { - break; - } - } - return iterations; - }; - zipSequence.__iteratorUncached = function (type, reverse) { - const iterators = iters.map( - (i) => ((i = Collection(i)), getIterator(reverse ? i.reverse() : i)) - ); - let iterations = 0; - let isDone = false; - return new Iterator(() => { - let steps; - if (!isDone) { - steps = iterators.map((i) => i.next()); - isDone = zipAll - ? steps.every((s) => s.done) - : steps.some((s) => s.done); - } - if (isDone) { - return iteratorDone(); - } - return iteratorValue( - type, - iterations++, - zipper.apply( - null, - steps.map((s) => s.value) - ) - ); - }); - }; - return zipSequence; -} - -// #pragma Helper Functions - -export function reify(iter, seq) { - return iter === seq - ? iter - : isSeq(iter) - ? seq - : iter.create - ? iter.create(seq) - : iter.constructor(seq); -} - -function validateEntry(entry) { - if (entry !== Object(entry)) { - throw new TypeError('Expected [K, V] tuple: ' + entry); - } -} - -function collectionClass(collection) { - return isKeyed(collection) - ? KeyedCollection - : isIndexed(collection) - ? IndexedCollection - : SetCollection; -} - -function makeSequence(collection) { - return Object.create( - (isKeyed(collection) - ? KeyedSeqImpl - : isIndexed(collection) - ? IndexedSeqImpl - : SetSeqImpl - ).prototype - ); -} - -function cacheResultThrough() { - if (this._iter.cacheResult) { - this._iter.cacheResult(); - this.size = this._iter.size; - return this; - } - return SeqImpl.prototype.cacheResult.call(this); -} - -function defaultComparator(a, b) { - if (a === undefined && b === undefined) { - return 0; - } - - if (a === undefined) { - return 1; - } - - if (b === undefined) { - return -1; - } - - return a > b ? 1 : a < b ? -1 : 0; -} diff --git a/src/OrderedMap.js b/src/OrderedMap.js deleted file mode 100644 index b121bdb2d1..0000000000 --- a/src/OrderedMap.js +++ /dev/null @@ -1,162 +0,0 @@ -import { KeyedCollection } from './Collection'; -import { IS_ORDERED_SYMBOL } from './predicates/isOrdered'; -import { isOrderedMap } from './predicates/isOrderedMap'; -import { MapImpl, emptyMap } from './Map'; -import { emptyList } from './List'; -import { DELETE, NOT_SET, SIZE } from './TrieUtils'; -import assertNotInfinite from './utils/assertNotInfinite'; - -export const OrderedMap = (value) => - value === undefined || value === null - ? emptyOrderedMap() - : isOrderedMap(value) - ? value - : emptyOrderedMap().withMutations((map) => { - const iter = KeyedCollection(value); - assertNotInfinite(iter.size); - iter.forEach((v, k) => map.set(k, v)); - }); -OrderedMap.of = function (/*...values*/) { - return OrderedMap(arguments); -}; -export class OrderedMapImpl extends MapImpl { - create(value) { - return OrderedMap(value); - } - - toString() { - return this.__toString('OrderedMap {', '}'); - } - - // @pragma Access - - get(k, notSetValue) { - const index = this._map.get(k); - return index !== undefined ? this._list.get(index)[1] : notSetValue; - } - - // @pragma Modification - - clear() { - if (this.size === 0) { - return this; - } - if (this.__ownerID) { - this.size = 0; - this._map.clear(); - this._list.clear(); - this.__altered = true; - return this; - } - return emptyOrderedMap(); - } - - set(k, v) { - return updateOrderedMap(this, k, v); - } - - remove(k) { - return updateOrderedMap(this, k, NOT_SET); - } - - __iterate(fn, reverse) { - return this._list.__iterate( - (entry) => entry && fn(entry[1], entry[0], this), - reverse - ); - } - - __iterator(type, reverse) { - return this._list.fromEntrySeq().__iterator(type, reverse); - } - - __ensureOwner(ownerID) { - if (ownerID === this.__ownerID) { - return this; - } - const newMap = this._map.__ensureOwner(ownerID); - const newList = this._list.__ensureOwner(ownerID); - if (!ownerID) { - if (this.size === 0) { - return emptyOrderedMap(); - } - this.__ownerID = ownerID; - this.__altered = false; - this._map = newMap; - this._list = newList; - return this; - } - return makeOrderedMap(newMap, newList, ownerID, this.__hash); - } -} - -OrderedMap.isOrderedMap = isOrderedMap; - -OrderedMapImpl.prototype[IS_ORDERED_SYMBOL] = true; -OrderedMapImpl.prototype[DELETE] = OrderedMapImpl.prototype.remove; - -function makeOrderedMap(map, list, ownerID, hash) { - const omap = Object.create(OrderedMapImpl.prototype); - omap.size = map ? map.size : 0; - omap._map = map; - omap._list = list; - omap.__ownerID = ownerID; - omap.__hash = hash; - omap.__altered = false; - return omap; -} - -let EMPTY_ORDERED_MAP; -export function emptyOrderedMap() { - return ( - EMPTY_ORDERED_MAP || - (EMPTY_ORDERED_MAP = makeOrderedMap(emptyMap(), emptyList())) - ); -} - -function updateOrderedMap(omap, k, v) { - const map = omap._map; - const list = omap._list; - const i = map.get(k); - const has = i !== undefined; - let newMap; - let newList; - if (v === NOT_SET) { - // removed - if (!has) { - return omap; - } - if (list.size >= SIZE && list.size >= map.size * 2) { - newList = list.filter((entry, idx) => entry !== undefined && i !== idx); - newMap = newList - .toKeyedSeq() - .map((entry) => entry[0]) - .flip() - .toMap(); - if (omap.__ownerID) { - newMap.__ownerID = newList.__ownerID = omap.__ownerID; - } - } else { - newMap = map.remove(k); - newList = i === list.size - 1 ? list.pop() : list.set(i, undefined); - } - } else if (has) { - if (v === list.get(i)[1]) { - return omap; - } - newMap = map; - newList = list.set(i, [k, v]); - } else { - newMap = map.set(k, list.size); - newList = list.set(list.size, [k, v]); - } - if (omap.__ownerID) { - omap.size = newMap.size; - omap._map = newMap; - omap._list = newList; - omap.__hash = undefined; - omap.__altered = true; - return omap; - } - return makeOrderedMap(newMap, newList); -} diff --git a/src/OrderedSet.js b/src/OrderedSet.js deleted file mode 100644 index ffb08b27fe..0000000000 --- a/src/OrderedSet.js +++ /dev/null @@ -1,61 +0,0 @@ -import { KeyedCollection, SetCollection } from './Collection'; -import { IndexedCollectionPrototype } from './CollectionImpl'; -import { emptyOrderedMap } from './OrderedMap'; -import { IS_ORDERED_SYMBOL } from './predicates/isOrdered'; -import { isOrderedSet } from './predicates/isOrderedSet'; -import { SetImpl } from './Set'; -import assertNotInfinite from './utils/assertNotInfinite'; - -export const OrderedSet = (value) => - value === undefined || value === null - ? emptyOrderedSet() - : isOrderedSet(value) - ? value - : emptyOrderedSet().withMutations((set) => { - const iter = SetCollection(value); - assertNotInfinite(iter.size); - iter.forEach((v) => set.add(v)); - }); - -OrderedSet.of = function (/*...values*/) { - return OrderedSet(arguments); -}; - -OrderedSet.fromKeys = function (value) { - return OrderedSet(KeyedCollection(value).keySeq()); -}; -export class OrderedSetImpl extends SetImpl { - create(value) { - return OrderedSet(value); - } - - toString() { - return this.__toString('OrderedSet {', '}'); - } -} - -OrderedSet.isOrderedSet = isOrderedSet; - -const OrderedSetPrototype = OrderedSetImpl.prototype; -OrderedSetPrototype[IS_ORDERED_SYMBOL] = true; -OrderedSetPrototype.zip = IndexedCollectionPrototype.zip; -OrderedSetPrototype.zipWith = IndexedCollectionPrototype.zipWith; -OrderedSetPrototype.zipAll = IndexedCollectionPrototype.zipAll; - -OrderedSetPrototype.__empty = emptyOrderedSet; -OrderedSetPrototype.__make = makeOrderedSet; - -function makeOrderedSet(map, ownerID) { - const set = Object.create(OrderedSetPrototype); - set.size = map ? map.size : 0; - set._map = map; - set.__ownerID = ownerID; - return set; -} - -let EMPTY_ORDERED_SET; -function emptyOrderedSet() { - return ( - EMPTY_ORDERED_SET || (EMPTY_ORDERED_SET = makeOrderedSet(emptyOrderedMap())) - ); -} diff --git a/src/Range.js b/src/Range.js index b3e9a56f1d..9cf10be385 100644 --- a/src/Range.js +++ b/src/Range.js @@ -1,22 +1,143 @@ +import transformToMethods from './transformToMethods'; + import { wrapIndex, wholeSlice, resolveBegin, resolveEnd } from './TrieUtils'; -import { IndexedSeqImpl } from './Seq'; + import { Iterator, iteratorValue, iteratorDone } from './Iterator'; -import invariant from './utils/invariant'; -import deepEqual from './utils/deepEqual'; +import { collectionIndexedSeqPropertiesCreate } from './collection/collectionIndexedSeq'; + +import { probeIsSameDeep } from './probe'; + +import { SHAPE_RANGE } from './const'; + +import { utilInvariant } from './util'; + +const rangeOpToString = (cx) => + cx.size === 0 + ? 'Range []' + : `Range [ ${cx._start}...${cx._end}${cx._step !== 1 ? ' by ' + cx._step : ''} ]`; + +const rangeOpGet = (cx, index, notSetValue) => { + return cx.has(index) + ? cx._start + wrapIndex(cx, index) * cx._step + : notSetValue; +}; + +const rangeOpIncludes = (cx, searchValue) => { + const possibleIndex = (searchValue - cx._start) / cx._step; + return ( + possibleIndex >= 0 && + possibleIndex < cx.size && + possibleIndex === Math.floor(possibleIndex) + ); +}; + +const rangeOpSlice = (cx, begin, end) => { + if (wholeSlice(begin, end, cx.size)) { + return cx; + } + begin = resolveBegin(begin, cx.size); + end = resolveEnd(end, cx.size); + if (end <= begin) { + return Range(0, 0); + } + return Range(cx.get(begin, cx._end), cx.get(end, cx._end), cx._step); +}; + +const rangeOpIndexOf = (cx, searchValue) => { + const offsetValue = searchValue - cx._start; + if (offsetValue % cx._step === 0) { + const index = offsetValue / cx._step; + if (index >= 0 && index < cx.size) { + return index; + } + } + return -1; +}; + +const rangeOpIterate = (cx, fn, reverse) => { + const size = cx.size; + const step = cx._step; + let value = reverse ? cx._start + (size - 1) * step : cx._start; + let i = 0; + while (i !== size) { + if (fn(value, reverse ? size - ++i : i++, cx) === false) { + break; + } + value += reverse ? -step : step; + } + return i; +}; + +const rangeOpIterator = (cx, type, reverse) => { + const size = cx.size; + const step = cx._step; + let value = reverse ? cx._start + (size - 1) * step : cx._start; + let i = 0; + return new Iterator(() => { + if (i === size) { + return iteratorDone(); + } + const v = value; + value += reverse ? -step : step; + return iteratorValue(type, reverse ? size - ++i : i++, v); + }); +}; + +const rangeOpEquals = (cx, other) => { + return other && other.__shape === SHAPE_RANGE + ? cx._start === other._start && + cx._end === other._end && + cx._step === other._step + : probeIsSameDeep(cx, other); +}; + +const RangeCreate = ((cache) => (start, end, step, size) => { + const range = Object.create( + cache || + (cache = Object.assign( + {}, + collectionIndexedSeqPropertiesCreate(), + transformToMethods({ + toString: rangeOpToString, + get: rangeOpGet, + includes: rangeOpIncludes, + slice: rangeOpSlice, + indexOf: rangeOpIndexOf, + lastIndexOf: (cx, searchValue) => cx.indexOf(searchValue), + __iterate: rangeOpIterate, + __iterator: rangeOpIterator, + equals: rangeOpEquals, + }) + )) + ); + + range._start = start; + range._end = end; + range._step = step; + range.size = size; + range.__shape = SHAPE_RANGE; + + return range; +})(); + +let EMPTY_RANGE; /** * Returns a lazy seq of nums from start (inclusive) to end * (exclusive), by step, where start defaults to 0, step to 1, and end to * infinity. When start is equal to end, returns empty list. */ -export const Range = (start, end, step = 1) => { - invariant(step !== 0, 'Cannot step a Range by 0'); - invariant( +const Range = (start, end, step = 1) => { + utilInvariant(step !== 0, 'Cannot step a Range by 0'); + utilInvariant( start !== undefined, 'You must define a start value when using Range' ); - invariant(end !== undefined, 'You must define an end value when using Range'); + utilInvariant( + end !== undefined, + 'You must define an end value when using Range' + ); step = Math.abs(step); if (end < start) { @@ -25,110 +146,11 @@ export const Range = (start, end, step = 1) => { const size = Math.max(0, Math.ceil((end - start) / step - 1) + 1); if (size === 0) { if (!EMPTY_RANGE) { - EMPTY_RANGE = new RangeImpl(start, end, step, 0); + EMPTY_RANGE = RangeCreate(start, end, step, 0); } return EMPTY_RANGE; } - return new RangeImpl(start, end, step, size); + return RangeCreate(start, end, step, size); }; -export class RangeImpl extends IndexedSeqImpl { - constructor(start, end, step, size) { - super(); - - this._start = start; - this._end = end; - this._step = step; - this.size = size; - } - - toString() { - return this.size === 0 - ? 'Range []' - : `Range [ ${this._start}...${this._end}${this._step !== 1 ? ' by ' + this._step : ''} ]`; - } - get(index, notSetValue) { - return this.has(index) - ? this._start + wrapIndex(this, index) * this._step - : notSetValue; - } - - includes(searchValue) { - const possibleIndex = (searchValue - this._start) / this._step; - return ( - possibleIndex >= 0 && - possibleIndex < this.size && - possibleIndex === Math.floor(possibleIndex) - ); - } - - slice(begin, end) { - if (wholeSlice(begin, end, this.size)) { - return this; - } - begin = resolveBegin(begin, this.size); - end = resolveEnd(end, this.size); - if (end <= begin) { - return Range(0, 0); - } - return Range( - this.get(begin, this._end), - this.get(end, this._end), - this._step - ); - } - - indexOf(searchValue) { - const offsetValue = searchValue - this._start; - if (offsetValue % this._step === 0) { - const index = offsetValue / this._step; - if (index >= 0 && index < this.size) { - return index; - } - } - return -1; - } - - lastIndexOf(searchValue) { - return this.indexOf(searchValue); - } - - __iterate(fn, reverse) { - const size = this.size; - const step = this._step; - let value = reverse ? this._start + (size - 1) * step : this._start; - let i = 0; - while (i !== size) { - if (fn(value, reverse ? size - ++i : i++, this) === false) { - break; - } - value += reverse ? -step : step; - } - return i; - } - - __iterator(type, reverse) { - const size = this.size; - const step = this._step; - let value = reverse ? this._start + (size - 1) * step : this._start; - let i = 0; - return new Iterator(() => { - if (i === size) { - return iteratorDone(); - } - const v = value; - value += reverse ? -step : step; - return iteratorValue(type, reverse ? size - ++i : i++, v); - }); - } - - equals(other) { - return other instanceof Range - ? this._start === other._start && - this._end === other._end && - this._step === other._step - : deepEqual(this, other); - } -} - -let EMPTY_RANGE; +export { Range }; diff --git a/src/Record.js b/src/Record.js index 8c43b45bbc..2139ae21f2 100644 --- a/src/Record.js +++ b/src/Record.js @@ -1,60 +1,35 @@ -import { toJS } from './toJS'; -import { KeyedCollection } from './Collection'; -import { keyedSeqFromValue } from './Seq'; import { List } from './List'; -import { ITERATE_ENTRIES, ITERATOR_SYMBOL } from './Iterator'; -import { isRecord, IS_RECORD_SYMBOL } from './predicates/isRecord'; -import { CollectionPrototype } from './CollectionImpl'; -import { DELETE } from './TrieUtils'; -import { getIn } from './methods/getIn'; -import { setIn } from './methods/setIn'; -import { deleteIn } from './methods/deleteIn'; -import { update } from './methods/update'; -import { updateIn } from './methods/updateIn'; -import { merge, mergeWith } from './methods/merge'; -import { mergeDeep, mergeDeepWith } from './methods/mergeDeep'; -import { mergeIn } from './methods/mergeIn'; -import { mergeDeepIn } from './methods/mergeDeepIn'; -import { withMutations } from './methods/withMutations'; -import { asMutable } from './methods/asMutable'; -import { asImmutable } from './methods/asImmutable'; -import invariant from './utils/invariant'; -import quoteString from './utils/quoteString'; -import { isImmutable } from './predicates/isImmutable'; +import { utilAssignNamedPropAccessor } from './util'; -function throwOnInvalidDefaultValues(defaultValues) { - if (isRecord(defaultValues)) { - throw new Error( - 'Can not call `Record` with an immutable Record as default values. Use a plain javascript object instead.' - ); - } +import { SeqKeyed } from './Seq'; - if (isImmutable(defaultValues)) { - throw new Error( - 'Can not call `Record` with an immutable Collection as default values. Use a plain javascript object instead.' - ); - } +import { collectionOpWithMutations } from './collection/collection'; - if (defaultValues === null || typeof defaultValues !== 'object') { - throw new Error( - 'Can not call `Record` with a non-object as default values. Use a plain javascript object instead.' - ); - } -} +import { + collectionRecordPropertiesCreate, + collectionRecordAssertValidDefaultValues, +} from './collection/collectionRecord'; + +import { probeIsRecord, probeIsKeyed } from './probe'; -export const Record = (defaultValues, name) => { +const recordOpNameGet = (record) => { + return record.constructor.displayName || record.constructor.name || 'Record'; +}; + +const Record = (defaultValues, name) => { let hasInitialized; - throwOnInvalidDefaultValues(defaultValues); + collectionRecordAssertValidDefaultValues(defaultValues); const RecordType = function Record(values) { if (values instanceof RecordType) { return values; } - if (!(this instanceof RecordType)) { + if (this instanceof RecordType === false) { return new RecordType(values); } + if (!hasInitialized) { hasInitialized = true; const keys = Object.keys(defaultValues); @@ -75,29 +50,36 @@ export const Record = (defaultValues, name) => { console.warn && console.warn( 'Cannot define ' + - recordName(this) + + recordOpNameGet(this) + ' with property "' + propName + '" since that property name is part of the Record API.' ); /* eslint-enable no-console */ } else { - setProp(RecordTypePrototype, propName); + utilAssignNamedPropAccessor(RecordTypePrototype, propName); } } } this.__ownerID = undefined; - this._values = List().withMutations((l) => { + + const collectionKeyedCreateBest = (value) => { + return probeIsKeyed(value) ? value : SeqKeyed(value); + }; + + this._values = collectionOpWithMutations(List(), (l) => { l.setSize(this._keys.length); - KeyedCollection(values).forEach((v, k) => { + + collectionKeyedCreateBest(values).forEach((v, k) => { l.set(this._indices[k], v === this._defaultValues[k] ? undefined : v); }); }); return this; }; - const RecordTypePrototype = (RecordType.prototype = - Object.create(RecordPrototype)); + const RecordTypePrototype = (RecordType.prototype = Object.create( + collectionRecordPropertiesCreate() + )); RecordTypePrototype.constructor = RecordType; RecordTypePrototype.create = RecordType; @@ -108,162 +90,7 @@ export const Record = (defaultValues, name) => { return RecordType; }; -export class RecordImpl { - toString() { - let str = recordName(this) + ' { '; - const keys = this._keys; - let k; - for (let i = 0, l = keys.length; i !== l; i++) { - k = keys[i]; - str += (i ? ', ' : '') + k + ': ' + quoteString(this.get(k)); - } - return str + ' }'; - } - - equals(other) { - return ( - this === other || - (isRecord(other) && recordSeq(this).equals(recordSeq(other))) - ); - } - - hashCode() { - return recordSeq(this).hashCode(); - } - - // @pragma Access - - has(k) { - return this._indices.hasOwnProperty(k); - } - - get(k, notSetValue) { - if (!this.has(k)) { - return notSetValue; - } - const index = this._indices[k]; - const value = this._values.get(index); - return value === undefined ? this._defaultValues[k] : value; - } - - // @pragma Modification - - set(k, v) { - if (this.has(k)) { - const newValues = this._values.set( - this._indices[k], - v === this._defaultValues[k] ? undefined : v - ); - if (newValues !== this._values && !this.__ownerID) { - return makeRecord(this, newValues); - } - } - return this; - } - - remove(k) { - return this.set(k); - } - - clear() { - const newValues = this._values.clear().setSize(this._keys.length); - - return this.__ownerID ? this : makeRecord(this, newValues); - } - - wasAltered() { - return this._values.wasAltered(); - } - - toSeq() { - return recordSeq(this); - } - - toJS() { - return toJS(this); - } - - entries() { - return this.__iterator(ITERATE_ENTRIES); - } - - __iterator(type, reverse) { - return recordSeq(this).__iterator(type, reverse); - } - - __iterate(fn, reverse) { - return recordSeq(this).__iterate(fn, reverse); - } - - __ensureOwner(ownerID) { - if (ownerID === this.__ownerID) { - return this; - } - const newValues = this._values.__ensureOwner(ownerID); - if (!ownerID) { - this.__ownerID = ownerID; - this._values = newValues; - return this; - } - return makeRecord(this, newValues, ownerID); - } -} - -Record.isRecord = isRecord; -Record.getDescriptiveName = recordName; -const RecordPrototype = RecordImpl.prototype; -RecordPrototype[IS_RECORD_SYMBOL] = true; -RecordPrototype[DELETE] = RecordPrototype.remove; -RecordPrototype.deleteIn = RecordPrototype.removeIn = deleteIn; -RecordPrototype.getIn = getIn; -RecordPrototype.hasIn = CollectionPrototype.hasIn; -RecordPrototype.merge = merge; -RecordPrototype.mergeWith = mergeWith; -RecordPrototype.mergeIn = mergeIn; -RecordPrototype.mergeDeep = mergeDeep; -RecordPrototype.mergeDeepWith = mergeDeepWith; -RecordPrototype.mergeDeepIn = mergeDeepIn; -RecordPrototype.setIn = setIn; -RecordPrototype.update = update; -RecordPrototype.updateIn = updateIn; -RecordPrototype.withMutations = withMutations; -RecordPrototype.asMutable = asMutable; -RecordPrototype.asImmutable = asImmutable; -RecordPrototype[ITERATOR_SYMBOL] = RecordPrototype.entries; -RecordPrototype.toJSON = RecordPrototype.toObject = - CollectionPrototype.toObject; -RecordPrototype.inspect = RecordPrototype.toSource = function () { - return this.toString(); -}; - -function makeRecord(likeRecord, values, ownerID) { - const record = Object.create(Object.getPrototypeOf(likeRecord)); - record._values = values; - record.__ownerID = ownerID; - return record; -} - -function recordName(record) { - return record.constructor.displayName || record.constructor.name || 'Record'; -} +Record.isRecord = probeIsRecord; +Record.getDescriptiveName = recordOpNameGet; -function recordSeq(record) { - return keyedSeqFromValue(record._keys.map((k) => [k, record.get(k)])); -} - -function setProp(prototype, name) { - try { - Object.defineProperty(prototype, name, { - get: function () { - return this.get(name); - }, - set: function (value) { - invariant(this.__ownerID, 'Cannot set on an immutable record.'); - this.set(name, value); - }, - }); - // eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO enable eslint here - } catch (error) { - // Object.defineProperty failed. Probably IE8. - } -} +export { Record }; diff --git a/src/Repeat.js b/src/Repeat.js index ddaacc2ef2..deaf38f0f9 100644 --- a/src/Repeat.js +++ b/src/Repeat.js @@ -1,102 +1,127 @@ +import transformToMethods from './transformToMethods'; + import { wholeSlice, resolveBegin, resolveEnd } from './TrieUtils'; -import { IndexedSeqImpl } from './Seq'; -import { is } from './is'; +import { probeIsSameDeep, probeIsSame, probeIsRepeat } from './probe'; import { Iterator, iteratorValue, iteratorDone } from './Iterator'; -import deepEqual from './utils/deepEqual'; +import { collectionIndexedSeqPropertiesCreate } from './collection/collectionIndexedSeq'; /** * Returns a lazy Seq of `value` repeated `times` times. When `times` is * undefined, returns an infinite sequence of `value`. */ -export const Repeat = (value, times) => { - const size = times === undefined ? Infinity : Math.max(0, times); - if (size === 0) { - if (!EMPTY_REPEAT) { - EMPTY_REPEAT = new RepeatImpl(value, 0); - } - return EMPTY_REPEAT; +const repeatOpToString = (cx) => { + if (cx.size === 0) { + return 'Repeat []'; } - return new RepeatImpl(value, size); + return 'Repeat [ ' + cx._value + ' ' + cx.size + ' times ]'; }; -export class RepeatImpl extends IndexedSeqImpl { - constructor(value, size) { - super(); - - this._value = value; - this.size = size; - } +const repeatOpGet = (cx, index, notSetValue) => { + return cx.has(index) ? cx._value : notSetValue; +}; - toString() { - if (this.size === 0) { - return 'Repeat []'; - } - return 'Repeat [ ' + this._value + ' ' + this.size + ' times ]'; - } +const repeatOpIncludes = (cx, searchValue) => { + return probeIsSame(cx._value, searchValue); +}; - get(index, notSetValue) { - return this.has(index) ? this._value : notSetValue; - } +const repeatOpSlice = (cx, begin, end) => { + const size = cx.size; + return wholeSlice(begin, end, size) + ? cx + : repeatCreate( + cx._value, + resolveEnd(end, size) - resolveBegin(begin, size) + ); +}; - includes(searchValue) { - return is(this._value, searchValue); - } +const repeatOpReverse = (cx) => { + return cx; +}; - slice(begin, end) { - const size = this.size; - return wholeSlice(begin, end, size) - ? this - : new RepeatImpl( - this._value, - resolveEnd(end, size) - resolveBegin(begin, size) - ); +const repeatOpIndexOf = (cx, searchValue) => { + if (probeIsSame(cx._value, searchValue)) { + return 0; } + return -1; +}; - reverse() { - return this; +const repeatOpLastIndexOf = (cx, searchValue) => { + if (probeIsSame(cx._value, searchValue)) { + return cx.size; } + return -1; +}; - indexOf(searchValue) { - if (is(this._value, searchValue)) { - return 0; +const repeatOpIterate = (cx, fn, reverse) => { + const size = cx.size; + let i = 0; + while (i !== size) { + if (fn(cx._value, reverse ? size - ++i : i++, cx) === false) { + break; } - return -1; } + return i; +}; - lastIndexOf(searchValue) { - if (is(this._value, searchValue)) { - return this.size; - } - return -1; - } +const repeatOpIterator = (cx, type, reverse) => { + const size = cx.size; + let i = 0; + return new Iterator(() => + i === size + ? iteratorDone() + : iteratorValue(type, reverse ? size - ++i : i++, cx._value) + ); +}; - __iterate(fn, reverse) { - const size = this.size; - let i = 0; - while (i !== size) { - if (fn(this._value, reverse ? size - ++i : i++, this) === false) { - break; - } - } - return i; - } +const repeatOpEquals = (cx, other) => { + return probeIsRepeat(other) + ? probeIsSame(cx._value, other._value) + : probeIsSameDeep(cx, other); +}; - __iterator(type, reverse) { - const size = this.size; - let i = 0; - return new Iterator(() => - i === size - ? iteratorDone() - : iteratorValue(type, reverse ? size - ++i : i++, this._value) - ); - } +const repeatPropertiesCreate = ((cache) => () => { + return ( + cache || + (cache = Object.assign( + {}, + collectionIndexedSeqPropertiesCreate(), + transformToMethods({ + toString: repeatOpToString, + get: repeatOpGet, + includes: repeatOpIncludes, + slice: repeatOpSlice, + reverse: repeatOpReverse, + indexOf: repeatOpIndexOf, + lastIndexOf: repeatOpLastIndexOf, + equals: repeatOpEquals, + __iterate: repeatOpIterate, + __iterator: repeatOpIterator, + }) + )) + ); +})(); - equals(other) { - return other instanceof Repeat - ? is(this._value, other._value) - : deepEqual(this, other); - } -} +const repeatCreate = (value, size) => { + const repeat = Object.create(repeatPropertiesCreate()); + repeat._value = value; + repeat.size = size; + + return repeat; +}; + +const repeatCreateEmpty = ((cache) => () => { + return cache || (cache = repeatCreate(undefined, 0)); +})(); + +/** + * Returns a lazy Seq of `value` repeated `times` times. When `times` is + * undefined, returns an infinite sequence of `value`. + */ +const Repeat = (value, times) => { + const size = times === undefined ? Infinity : Math.max(0, times); + + return size === 0 ? repeatCreateEmpty() : repeatCreate(value, size); +}; -let EMPTY_REPEAT; +export { Repeat, repeatCreate }; diff --git a/src/Seq.js b/src/Seq.js index a3987c34bb..915f6b9af6 100644 --- a/src/Seq.js +++ b/src/Seq.js @@ -1,304 +1,47 @@ -import { wrapIndex } from './TrieUtils'; -import { CollectionImpl } from './Collection'; -import { IS_SEQ_SYMBOL, isSeq } from './predicates/isSeq'; -import { isImmutable } from './predicates/isImmutable'; -import { isCollection } from './predicates/isCollection'; -import { isKeyed } from './predicates/isKeyed'; -import { isAssociative } from './predicates/isAssociative'; -import { isRecord } from './predicates/isRecord'; -import { IS_ORDERED_SYMBOL } from './predicates/isOrdered'; -import { - Iterator, - iteratorValue, - iteratorDone, - hasIterator, - isIterator, - getIterator, - isEntriesIterable, - isKeysIterable, -} from './Iterator'; - -import hasOwnProperty from './utils/hasOwnProperty'; -import isArrayLike from './utils/isArrayLike'; - -export const Seq = (value) => - value === undefined || value === null - ? emptySequence() - : isImmutable(value) - ? value.toSeq() - : seqFromValue(value); -export class SeqImpl extends CollectionImpl { - toSeq() { - return this; - } - - toString() { - return this.__toString('Seq {', '}'); - } - - cacheResult() { - if (!this._cache && this.__iterateUncached) { - this._cache = this.entrySeq().toArray(); - this.size = this._cache.length; - } - return this; - } - - // abstract __iterateUncached(fn, reverse) - - __iterate(fn, reverse) { - const cache = this._cache; - if (cache) { - const size = cache.length; - let i = 0; - while (i !== size) { - const entry = cache[reverse ? size - ++i : i++]; - if (fn(entry[1], entry[0], this) === false) { - break; - } - } - return i; - } - return this.__iterateUncached(fn, reverse); - } - - // abstract __iteratorUncached(type, reverse) - - __iterator(type, reverse) { - const cache = this._cache; - if (cache) { - const size = cache.length; - let i = 0; - return new Iterator(() => { - if (i === size) { - return iteratorDone(); - } - const entry = cache[reverse ? size - ++i : i++]; - return iteratorValue(type, entry[0], entry[1]); - }); - } - return this.__iteratorUncached(type, reverse); - } -} - -export const KeyedSeq = (value) => - value === undefined || value === null - ? emptySequence().toKeyedSeq() - : isCollection(value) - ? isKeyed(value) - ? value.toSeq() - : value.fromEntrySeq() - : isRecord(value) - ? value.toSeq() - : keyedSeqFromValue(value); -export class KeyedSeqImpl extends SeqImpl { - toKeyedSeq() { - return this; - } -} +import { isEntriesIterable, isKeysIterable } from './Iterator'; -export const IndexedSeq = (value) => - value === undefined || value === null - ? emptySequence() - : isCollection(value) - ? isKeyed(value) - ? value.entrySeq() - : value.toIndexedSeq() - : isRecord(value) - ? value.toSeq().entrySeq() - : indexedSeqFromValue(value); -IndexedSeq.of = function (/*...values*/) { - return IndexedSeq(arguments); -}; -export class IndexedSeqImpl extends SeqImpl { - toIndexedSeq() { - return this; - } - - toString() { - return this.__toString('Seq [', ']'); - } -} -export const SetSeq = (value) => - (isCollection(value) && !isAssociative(value) - ? value - : IndexedSeq(value) - ).toSetSeq(); - -SetSeq.of = function (/*...values*/) { - return SetSeq(arguments); +import { + probeHasIterator, + probeIsArrayLike, + probeIsAssociative, + probeIsImmutable, + probeIsCollection, + probeIsIndexed, + probeIsRecord, + probeIsKeyed, + probeIsSeq, +} from './probe'; + +import { SeqArray, seqArrayCreateEmpty } from './SeqArray.js'; + +import { SeqObject } from './SeqObject.js'; + +import { collectionIndexedSeqFromCollectionCreate } from './collection/collectionIndexedSeqFromCollection'; + +const maybeIndexedSeqFromValue = (value) => { + return probeIsArrayLike(value) + ? SeqArray(value) + : probeHasIterator(value) + ? collectionIndexedSeqFromCollectionCreate(value) + : undefined; }; -export class SetSeqImpl extends SeqImpl { - toSetSeq() { - return this; - } -} - -Seq.isSeq = isSeq; -Seq.Keyed = KeyedSeq; -Seq.Set = SetSeq; -Seq.Indexed = IndexedSeq; - -SeqImpl.prototype[IS_SEQ_SYMBOL] = true; - -// #pragma Root Sequences - -export class ArraySeq extends IndexedSeqImpl { - constructor(array) { - super(); - this._array = array; - this.size = array.length; - } - - get(index, notSetValue) { - return this.has(index) ? this._array[wrapIndex(this, index)] : notSetValue; - } - - __iterate(fn, reverse) { - const array = this._array; - const size = array.length; - let i = 0; - while (i !== size) { - const ii = reverse ? size - ++i : i++; - if (fn(array[ii], ii, this) === false) { - break; - } - } - return i; - } - - __iterator(type, reverse) { - const array = this._array; - const size = array.length; - let i = 0; - return new Iterator(() => { - if (i === size) { - return iteratorDone(); - } - const ii = reverse ? size - ++i : i++; - return iteratorValue(type, ii, array[ii]); - }); - } -} - -class ObjectSeq extends KeyedSeqImpl { - constructor(object) { - super(); - const keys = Object.keys(object).concat( - Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(object) : [] - ); - this._object = object; - this._keys = keys; - this.size = keys.length; - } - - get(key, notSetValue) { - if (notSetValue !== undefined && !this.has(key)) { - return notSetValue; - } - return this._object[key]; - } - - has(key) { - return hasOwnProperty.call(this._object, key); - } - - __iterate(fn, reverse) { - const object = this._object; - const keys = this._keys; - const size = keys.length; - let i = 0; - while (i !== size) { - const key = keys[reverse ? size - ++i : i++]; - if (fn(object[key], key, this) === false) { - break; - } - } - return i; - } - - __iterator(type, reverse) { - const object = this._object; - const keys = this._keys; - const size = keys.length; - let i = 0; - return new Iterator(() => { - if (i === size) { - return iteratorDone(); - } - const key = keys[reverse ? size - ++i : i++]; - return iteratorValue(type, key, object[key]); - }); - } -} -ObjectSeq.prototype[IS_ORDERED_SYMBOL] = true; - -class CollectionSeq extends IndexedSeqImpl { - constructor(collection) { - super(); - this._collection = collection; - this.size = collection.length || collection.size; - } - - __iterateUncached(fn, reverse) { - if (reverse) { - return this.cacheResult().__iterate(fn, reverse); - } - const collection = this._collection; - const iterator = getIterator(collection); - let iterations = 0; - if (isIterator(iterator)) { - let step; - while (!(step = iterator.next()).done) { - if (fn(step.value, iterations++, this) === false) { - break; - } - } - } - return iterations; - } - - __iteratorUncached(type, reverse) { - if (reverse) { - return this.cacheResult().__iterator(type, reverse); - } - const collection = this._collection; - const iterator = getIterator(collection); - if (!isIterator(iterator)) { - return new Iterator(iteratorDone); - } - let iterations = 0; - return new Iterator(() => { - const step = iterator.next(); - return step.done ? step : iteratorValue(type, iterations++, step.value); - }); - } -} - -// # pragma Helper functions - -let EMPTY_SEQ; - -function emptySequence() { - return EMPTY_SEQ || (EMPTY_SEQ = new ArraySeq([])); -} - -export function keyedSeqFromValue(value) { +const SeqKeyedFromValue = (value) => { const seq = maybeIndexedSeqFromValue(value); + if (seq) { return seq.fromEntrySeq(); } if (typeof value === 'object') { - return new ObjectSeq(value); + return SeqObject(value); } throw new TypeError( 'Expected Array or collection object of [k, v] entries, or keyed object: ' + value ); -} +}; -export function indexedSeqFromValue(value) { +const SeqIndexedFromValue = (value) => { const seq = maybeIndexedSeqFromValue(value); if (seq) { return seq; @@ -306,9 +49,9 @@ export function indexedSeqFromValue(value) { throw new TypeError( 'Expected Array or collection object of values: ' + value ); -} +}; -function seqFromValue(value) { +const SeqFromValue = (value) => { const seq = maybeIndexedSeqFromValue(value); if (seq) { return isEntriesIterable(value) @@ -318,17 +61,87 @@ function seqFromValue(value) { : seq; } if (typeof value === 'object') { - return new ObjectSeq(value); + return SeqObject(value); } throw new TypeError( 'Expected Array or collection object of values, or keyed object: ' + value ); -} +}; -function maybeIndexedSeqFromValue(value) { - return isArrayLike(value) - ? new ArraySeq(value) - : hasIterator(value) - ? new CollectionSeq(value) - : undefined; -} +const Seq = (value) => { + return value === undefined || value === null + ? seqArrayCreateEmpty() + : probeIsImmutable(value) + ? value.toSeq() + : SeqFromValue(value); +}; + +const SeqKeyed = (value) => + value === undefined || value === null + ? seqArrayCreateEmpty().toKeyedSeq() + : probeIsCollection(value) + ? probeIsKeyed(value) + ? value.toSeq() + : value.fromEntrySeq() + : probeIsRecord(value) + ? value.toSeq() + : SeqKeyedFromValue(value); + +const SeqIndexed = (value) => + value === undefined || value === null + ? seqArrayCreateEmpty() + : probeIsCollection(value) + ? probeIsKeyed(value) + ? value.entrySeq() + : value.toIndexedSeq() + : probeIsRecord(value) + ? value.toSeq().entrySeq() + : SeqIndexedFromValue(value); + +// Was Collection/1 +const SeqWhenNotCollection = (value) => + probeIsCollection(value) ? value : Seq(value); + +// WAS KeyedCollection/1 +const SeqKeyedWhenNotKeyed = (value) => + probeIsKeyed(value) ? value : SeqKeyed(value); + +// WAS IndexedCollection/1 +const SeqIndexedWhenNotIndexed = (value) => + probeIsIndexed(value) ? value : SeqIndexed(value); + +const SeqSet = (value) => { + return ( + probeIsCollection(value) && !probeIsAssociative(value) + ? value + : SeqIndexed(value) + ).toSetSeq(); +}; + +// WAS SetCollection/1 +const SeqSetWhenNotAssociative = (value) => + probeIsCollection(value) && !probeIsAssociative(value) + ? value + : SeqIndexed(value).toSetSeq(); + +SeqIndexed.of = (...values) => SeqIndexed(values); +SeqSet.of = (...values) => SeqSet(...values); + +Seq.isSeq = probeIsSeq; +Seq.Indexed = SeqIndexed; +Seq.Keyed = SeqKeyed; +Seq.Set = SeqSet; + +export { + Seq, + SeqWhenNotCollection, + SeqFromValue, + SeqKeyed, + SeqKeyedWhenNotKeyed, + SeqKeyedFromValue, + SeqIndexed, + SeqIndexedWhenNotIndexed, + SeqIndexedFromValue, + SeqSet, + SeqSetWhenNotAssociative, +}; diff --git a/src/SeqArray.js b/src/SeqArray.js new file mode 100644 index 0000000000..9d32b8af47 --- /dev/null +++ b/src/SeqArray.js @@ -0,0 +1,87 @@ +import transformToMethods from './transformToMethods'; + +import { wrapIndex } from './TrieUtils'; + +import { Iterator, iteratorValue, iteratorDone } from './Iterator'; + +import { collectionIndexedSeqPropertiesCreate } from './collection/collectionIndexedSeq'; + +import { probeIsImmutable } from './probe'; + +const indexedSeqArrayOpGet = (cx, index, notSetValue) => { + return cx.has(index) ? cx._array[wrapIndex(cx, index)] : notSetValue; +}; + +const indexedSeqArrayOpIterate = (cx, fn, reverse) => { + const array = cx._array; + const size = array.length; + + let i = 0; + while (i !== size) { + const ii = reverse ? size - ++i : i++; + if (fn(array[ii], ii, cx) === false) { + break; + } + } + return i; +}; + +const indexedSeqArrayOpIterator = (cx, type, reverse) => { + const array = cx._array; + const size = array.length; + let i = 0; + return new Iterator(() => { + if (i === size) { + return iteratorDone(); + } + const ii = reverse ? size - ++i : i++; + return iteratorValue(type, ii, array[ii]); + }); +}; + +const seqArrayPropertiesCreate = ((cache) => () => { + return ( + cache || + (cache = Object.assign( + {}, + collectionIndexedSeqPropertiesCreate(), + transformToMethods({ + __iterate: indexedSeqArrayOpIterate, + __iterator: indexedSeqArrayOpIterator, + get: indexedSeqArrayOpGet, + toIndexedSeq: (cx) => cx, + toString: (cx) => cx.__toString('Seq [', ']'), + _toString: (cx) => cx.__toString('Seq [', ']'), + }) + )) + ); +})(); + +const seqArrayCreate = (array) => { + const seqArray = Object.create(seqArrayPropertiesCreate()); + + seqArray._shape = 'seqarray'; + seqArray._array = array; + seqArray.size = array.length; + + return seqArray; +}; + +const seqArrayCreateEmpty = ((cache) => () => { + return cache || (cache = seqArrayCreate([])); +})(); + +const SeqArray = (value) => + value === undefined || value === null + ? seqArrayCreateEmpty([]) + : probeIsImmutable(value) + ? value.toSeq() + : seqArrayCreate(value); + +export { + SeqArray as default, + SeqArray, + seqArrayPropertiesCreate, + seqArrayCreate, + seqArrayCreateEmpty, +}; diff --git a/src/SeqObject.js b/src/SeqObject.js new file mode 100644 index 0000000000..176b6a724f --- /dev/null +++ b/src/SeqObject.js @@ -0,0 +1,100 @@ +import transformToMethods from './transformToMethods'; + +import { probeIsImmutable, probeHasOwnProperty } from './probe'; + +import { Iterator, iteratorValue, iteratorDone } from './Iterator'; + +import { + collectionKeyedSeqPropertiesCreate, +} from './collection/collectionKeyedSeq.js'; + +import { IS_ORDERED_SYMBOL } from './const'; + +const seqObjectOpHas = (cx, key) => { + return probeHasOwnProperty.call(cx._object, key); +}; + +const seqObjectOpGet = (cx, key, notSetValue) => { + if (notSetValue !== undefined && !seqObjectOpHas(key)) { + return notSetValue; + } + + return cx._object[key]; +}; + +const seqObjectOpIterate = (cx, fn, reverse) => { + const object = cx._object; + const keys = cx._keys; + const size = keys.length; + let i = 0; + while (i !== size) { + const key = keys[reverse ? size - ++i : i++]; + if (fn(object[key], key, cx) === false) { + break; + } + } + return i; +}; + +const seqObjectOpIterator = (cx, type, reverse) => { + const object = cx._object; + const keys = cx._keys; + const size = keys.length; + let i = 0; + return new Iterator(() => { + if (i === size) { + return iteratorDone(); + } + const key = keys[reverse ? size - ++i : i++]; + return iteratorValue(type, key, object[key]); + }); +}; + +const seqObjectPropertiesCreate = ((cache) => () => { + return ( + cache || + (cache = Object.assign( + {}, + collectionKeyedSeqPropertiesCreate(), + transformToMethods({ + [IS_ORDERED_SYMBOL]: true, + toKeyedSeq: (cx) => cx, + get: seqObjectOpGet, + has: seqObjectOpHas, + __iterate: seqObjectOpIterate, + __iterator: seqObjectOpIterator, + }) + )) + ); +})(); + +const seqObjectCreate = (object) => { + const seqObject = Object.create(seqObjectPropertiesCreate()); + const keys = Object.keys(object).concat( + Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(object) : [] + ); + seqObject._shape = 'seqobject'; + seqObject._object = object; + seqObject._keys = keys; + seqObject.size = keys.length; + + return seqObject; +}; + +const seqObjectCreateEmpty = ((cache) => () => { + return cache || (cache = seqObjectCreate({})); +})(); + +const SeqObject = (value) => + value === undefined || value === null + ? seqObjectCreateEmpty({}) + : probeIsImmutable(value) + ? value.toSeq() + : seqObjectCreate(value); + +export { + SeqObject as default, + seqObjectPropertiesCreate, + seqObjectCreate, + SeqObject, +}; diff --git a/src/Set.js b/src/Set.js index e205bd1905..31750d3ea0 100644 --- a/src/Set.js +++ b/src/Set.js @@ -1,238 +1,266 @@ -import { - Collection, - SetCollectionImpl, - KeyedCollection, - SetCollection, -} from './Collection'; -import { isOrdered } from './predicates/isOrdered'; -import { IS_SET_SYMBOL, isSet } from './predicates/isSet'; -import { emptyMap } from './Map'; -import { DELETE } from './TrieUtils'; -import { sortFactory } from './Operations'; -import assertNotInfinite from './utils/assertNotInfinite'; -import { asImmutable } from './methods/asImmutable'; -import { asMutable } from './methods/asMutable'; -import { withMutations } from './methods/withMutations'; - -import { OrderedSet } from './OrderedSet'; - -export const Set = (value) => - value === undefined || value === null - ? emptySet() - : isSet(value) && !isOrdered(value) - ? value - : emptySet().withMutations((set) => { - const iter = SetCollection(value); - assertNotInfinite(iter.size); - iter.forEach((v) => set.add(v)); - }); +import transformToMethods from './transformToMethods'; -Set.of = function (/*...values*/) { - return Set(arguments); -}; +import { DELETE, IS_SET_SYMBOL, SHAPE_SET } from './const'; -Set.fromKeys = (value) => Set(KeyedCollection(value).keySeq()); +import { + probeIsSet, + probeIsOrdered, + probeIsCollection, + probeIsAssociative, +} from './probe'; -Set.intersect = (sets) => { - sets = Collection(sets).toArray(); - return sets.length - ? SetPrototype.intersect.apply(Set(sets.pop()), sets) - : emptySet(); -}; +import { mapCreateEmpty } from './Map'; -Set.union = (sets) => { - const setArray = Collection(sets).toArray(); - return setArray.length - ? SetPrototype.union.apply(Set(setArray.pop()), setArray) - : emptySet(); -}; +import { + SeqIndexed, + SeqKeyedWhenNotKeyed, + SeqWhenNotCollection, + SeqSetWhenNotAssociative, +} from './Seq'; -export class SetImpl extends SetCollectionImpl { - create(value) { - return Set(value); - } +import { + collectionOpWithMutations, + collectionOpAsMutable, + collectionPropertiesCreate, +} from './collection/collection'; - toString() { - return this.__toString('Set {', '}'); - } +import { collectionCastSetSeqCreate } from './collection/collectionCastSetSeq'; - // @pragma Access +import { utilAssertNotInfinite, utilFlagSpread } from './util'; - has(value) { - return this._map.has(value); +const setOpUpdate = (set, newMap) => { + if (set.__ownerID) { + set.size = newMap.size; + set._map = newMap; + return set; } - // @pragma Modification - - add(value) { - return updateSet(this, this._map.set(value, value)); - } + return newMap === set._map + ? set + : newMap.size === 0 + ? set.__empty() + : set.__make(newMap); +}; - remove(value) { - return updateSet(this, this._map.remove(value)); - } +const setOpIterate = (cx, fn, reverse) => { + return cx._map.__iterate((k) => fn(k, k, cx), reverse); +}; - clear() { - return updateSet(this, this._map.clear()); - } +const setOpMap = (cx, mapper, context) => { + // keep track if the set is altered by the map function + let didChanges = false; - // @pragma Composition + const newMap = setOpUpdate( + cx, + cx._map.mapEntries(([, v]) => { + const mapped = mapper.call(context, v, v, cx); - map(mapper, context) { - // keep track if the set is altered by the map function - let didChanges = false; + if (mapped !== v) { + didChanges = true; + } - const newMap = updateSet( - this, - this._map.mapEntries(([, v]) => { - const mapped = mapper.call(context, v, v, this); + return [mapped, mapped]; + }, context) + ); - if (mapped !== v) { - didChanges = true; - } + return didChanges ? newMap : cx; +}; - return [mapped, mapped]; - }, context) - ); +const setOpIterator = (cx, type, reverse) => { + return cx._map.__iterator(type, reverse); +}; - return didChanges ? newMap : this; +const setOpEnsureOwner = (cx, ownerID) => { + if (ownerID === cx.__ownerID) { + return cx; } - union(...iters) { - iters = iters.filter((x) => x.size !== 0); - if (iters.length === 0) { - return this; + const newMap = cx._map.__ensureOwner(ownerID); + if (!ownerID) { + if (cx.size === 0) { + return cx.__empty(); } - if (this.size === 0 && !this.__ownerID && iters.length === 1) { - return Set(iters[0]); - } - return this.withMutations((set) => { - for (let ii = 0; ii < iters.length; ii++) { - if (typeof iters[ii] === 'string') { - set.add(iters[ii]); - } else { - SetCollection(iters[ii]).forEach((value) => set.add(value)); - } - } - }); + cx.__ownerID = ownerID; + cx._map = newMap; + return cx; } + return cx.__make(newMap, ownerID); +}; - intersect(...iters) { - if (iters.length === 0) { - return this; - } - iters = iters.map((iter) => SetCollection(iter)); - const toRemove = []; - this.forEach((value) => { - if (!iters.every((iter) => iter.includes(value))) { - toRemove.push(value); - } - }); - return this.withMutations((set) => { - toRemove.forEach((value) => { - set.remove(value); - }); - }); - } +const setOpToString = (cx) => { + return cx.__toString('Set {', '}'); +}; - subtract(...iters) { - if (iters.length === 0) { - return this; - } - iters = iters.map((iter) => SetCollection(iter)); - const toRemove = []; - this.forEach((value) => { - if (iters.some((iter) => iter.includes(value))) { - toRemove.push(value); - } - }); - return this.withMutations((set) => { - toRemove.forEach((value) => { - set.remove(value); - }); - }); - } +const setOpHas = (cx, value) => { + return cx._map.has(value); +}; - sort(comparator) { - // Late binding - return OrderedSet(sortFactory(this, comparator)); - } +const setOpAdd = (cx, value) => { + return setOpUpdate(cx, cx._map.set(value, value)); +}; - sortBy(mapper, comparator) { - // Late binding - return OrderedSet(sortFactory(this, comparator, mapper)); - } +const setOpRemove = (cx, value) => { + return setOpUpdate(cx, cx._map.remove(value)); +}; - wasAltered() { - return this._map.wasAltered(); - } +const setOpClear = (cx) => { + return setOpUpdate(cx, cx._map.clear()); +}; - __iterate(fn, reverse) { - return this._map.__iterate((k) => fn(k, k, this), reverse); - } +const setOpWasAltered = (cx) => { + return cx._map.wasAltered(); +}; - __iterator(type, reverse) { - return this._map.__iterator(type, reverse); +const setOpUnion = (cx, iters) => { + iters = iters.filter((x) => x.size !== 0); + if (iters.length === 0) { + return cx; } - - __ensureOwner(ownerID) { - if (ownerID === this.__ownerID) { - return this; - } - const newMap = this._map.__ensureOwner(ownerID); - if (!ownerID) { - if (this.size === 0) { - return this.__empty(); + if (cx.size === 0 && !cx.__ownerID && iters.length === 1) { + return Set(iters[0]); + } + return collectionOpWithMutations(cx, (set) => { + for (let ii = 0; ii < iters.length; ii++) { + if (typeof iters[ii] === 'string') { + set.add(iters[ii]); + } else { + SeqSetWhenNotAssociative(iters[ii]).forEach((value) => set.add(value)); } - this.__ownerID = ownerID; - this._map = newMap; - return this; } - return this.__make(newMap, ownerID); - } -} - -Set.isSet = isSet; - -const SetPrototype = SetImpl.prototype; -SetPrototype[IS_SET_SYMBOL] = true; -SetPrototype[DELETE] = SetPrototype.remove; -SetPrototype.merge = SetPrototype.concat = SetPrototype.union; -SetPrototype.withMutations = withMutations; -SetPrototype.asImmutable = asImmutable; -SetPrototype['@@transducer/init'] = SetPrototype.asMutable = asMutable; -SetPrototype['@@transducer/step'] = function (result, arr) { - return result.add(arr); -}; -SetPrototype['@@transducer/result'] = function (obj) { - return obj.asImmutable(); + }); }; -SetPrototype.__empty = emptySet; -SetPrototype.__make = makeSet; +const setOpIntersect = (cx, iters) => { + if (iters.length === 0) { + return cx; + } + iters = iters.map((iter) => SeqSetWhenNotAssociative(iter)); + const toRemove = []; + cx.forEach((value) => { + if (!iters.every((iter) => iter.includes(value))) { + toRemove.push(value); + } + }); + return collectionOpWithMutations(cx, (set) => { + toRemove.forEach((value) => { + set.remove(value); + }); + }); +}; -function updateSet(set, newMap) { - if (set.__ownerID) { - set.size = newMap.size; - set._map = newMap; - return set; +const setOpSubtract = (cx, iters) => { + if (iters.length === 0) { + return cx; } - return newMap === set._map - ? set - : newMap.size === 0 - ? set.__empty() - : set.__make(newMap); -} + iters = iters.map((iter) => SeqSetWhenNotAssociative(iter)); + const toRemove = []; + cx.forEach((value) => { + if (iters.some((iter) => iter.includes(value))) { + toRemove.push(value); + } + }); + return collectionOpWithMutations(cx, (set) => { + toRemove.forEach((value) => { + set.remove(value); + }); + }); +}; -function makeSet(map, ownerID) { - const set = Object.create(SetPrototype); +const setPropertiesCreate = ((cache) => () => { + cache = + cache || + (cache = Object.assign( + {}, + collectionPropertiesCreate(), + { + __make: setCreate, + __empty: setCreateEmpty, + create: Set, + ['@@transducer/init']() { + return collectionOpAsMutable(this); + }, + ['@@transducer/step']: (result, arr) => { + return result.add(arr); + }, + ['@@transducer/result']: (obj) => { + return obj.asImmutable(); + }, + }, + transformToMethods({ + [IS_SET_SYMBOL]: true, + toString: setOpToString, + wasAltered: setOpWasAltered, + remove: setOpRemove, + [DELETE]: setOpRemove, + clear: setOpClear, + has: setOpHas, + add: setOpAdd, + map: setOpMap, + withMutations: collectionOpWithMutations, + merge: utilFlagSpread(setOpUnion), + concat: utilFlagSpread(setOpUnion), + union: utilFlagSpread(setOpUnion), + intersect: utilFlagSpread(setOpIntersect), + subtract: utilFlagSpread(setOpSubtract), + contains: setOpHas, + __ensureOwner: setOpEnsureOwner, + __iterate: setOpIterate, + __iterator: setOpIterator, + }) + )); + + // existing tests expect ref-equal keys, values, iterator + cache.keys = cache.values; + + return cache; +})(); + +const setCreate = (map, ownerID) => { + const set = Object.create(setPropertiesCreate()); set.size = map ? map.size : 0; set._map = map; set.__ownerID = ownerID; + set.__shape = SHAPE_SET; return set; -} +}; + +const setCreateEmpty = ((cache) => () => { + return cache || (cache = setCreate(mapCreateEmpty())); +})(); + +const setCollection = (value) => + collectionCastSetSeqCreate( + probeIsCollection(value) && !probeIsAssociative(value) + ? value + : SeqIndexed(value) + ); + +const Set = (value) => + value === undefined || value === null + ? setCreateEmpty() + : probeIsSet(value) && !probeIsOrdered(value) + ? value + : collectionOpWithMutations(setCreateEmpty(), (set) => { + const iter = setCollection(value); + utilAssertNotInfinite(iter.size); + iter.forEach((v) => set.add(v)); + }); + +Set.isSet = probeIsSet; +Set.of = (...args) => Set(args); +Set.fromKeys = (value) => Set(SeqKeyedWhenNotKeyed(value).keySeq()); +Set.union = (sets) => { + const setArray = SeqWhenNotCollection(sets).toArray(); + return setArray.length + ? setOpUnion(Set(setArray.pop()), setArray) + : setCreateEmpty(); +}; + +Set.intersect = (sets) => { + sets = SeqWhenNotCollection(sets).toArray(); + return sets.length + ? setOpIntersect(Set(sets.pop()), sets) + : setCreateEmpty(); +}; -let EMPTY_SET; -function emptySet() { - return EMPTY_SET || (EMPTY_SET = makeSet(emptyMap())); -} +export { Set, setPropertiesCreate, setCreateEmpty }; diff --git a/src/SetOrdered.js b/src/SetOrdered.js new file mode 100644 index 0000000000..5cc5acf47e --- /dev/null +++ b/src/SetOrdered.js @@ -0,0 +1,73 @@ +import transformToMethods from './transformToMethods'; + +import { setPropertiesCreate } from './Set'; + +import { mapOrderedCreateEmpty } from './MapOrdered'; + +import { SeqSetWhenNotAssociative, SeqKeyedWhenNotKeyed } from './Seq'; + +import { utilAssertNotInfinite } from './util'; + +import { probeIsOrderedSet } from './probe'; + +import { IS_ORDERED_SYMBOL } from './const'; + +const setOrderedOpToString = (cx) => { + return cx.__toString('OrderedSet {', '}'); +}; + +const setOrderedPropertiesCreate = ( + (cache) => () => + (cache = + cache || + (cache = Object.assign( + {}, + setPropertiesCreate(), + { + create: SetOrdered, + __make: setOrderedCreate, + __empty: setOrderedCreateEmpty, + }, + transformToMethods({ + [IS_ORDERED_SYMBOL]: true, + toString: setOrderedOpToString, + }) + ))) +)(); + +const setOrderedCreate = (map, ownerID) => { + const oset = Object.create(setOrderedPropertiesCreate()); + oset.size = map ? map.size : 0; + oset._map = map; + oset.__ownerID = ownerID; + return oset; +}; + +const setOrderedCreateEmpty = ( + (cache) => () => + cache || (cache = setOrderedCreate(mapOrderedCreateEmpty())) +)(); + +const SetOrdered = (value) => { + return value === undefined || value === null + ? setOrderedCreateEmpty() + : probeIsOrderedSet(value) + ? value + : setOrderedCreateEmpty().withMutations((set) => { + const iter = SeqSetWhenNotAssociative(value); + utilAssertNotInfinite(iter.size); + iter.forEach((v) => set.add(v)); + }); +}; + +SetOrdered.isOrderedSet = probeIsOrderedSet; +SetOrdered.of = (...args) => SetOrdered(args); +SetOrdered.fromKeys = (value) => + SetOrdered(SeqKeyedWhenNotKeyed(value).keySeq()); + +export { + SetOrdered, + setOrderedPropertiesCreate, + setOrderedCreate, + setOrderedCreateEmpty, +}; diff --git a/src/Stack.js b/src/Stack.js index 64692d207e..52634acd22 100644 --- a/src/Stack.js +++ b/src/Stack.js @@ -1,227 +1,250 @@ +import transformToMethods from './transformToMethods'; + import { wholeSlice, resolveBegin, resolveEnd, wrapIndex } from './TrieUtils'; -import { IndexedCollection, IndexedCollectionImpl } from './Collection'; -import { ArraySeq } from './Seq'; + +import { + collectionOpAsMutable, + collectionOpAsImmutable, +} from './collection/collection'; + +import { collectionIndexedPropertiesCreate } from './collection/collectionIndexed'; + +import { SeqIndexedWhenNotIndexed } from './Seq'; + +import { SeqArray } from './SeqArray'; + import { Iterator, iteratorValue, iteratorDone } from './Iterator'; -import { IS_STACK_SYMBOL, isStack } from './predicates/isStack'; -import assertNotInfinite from './utils/assertNotInfinite'; -import { asImmutable } from './methods/asImmutable'; -import { asMutable } from './methods/asMutable'; -import { wasAltered } from './methods/wasAltered'; -import { withMutations } from './methods/withMutations'; - -export const Stack = (value) => - value === undefined || value === null - ? emptyStack() - : isStack(value) - ? value - : emptyStack().pushAll(value); -Stack.of = function (/*...values*/) { - return Stack(arguments); +import { probeIsStack } from './probe'; + +import { IS_STACK_SYMBOL, SHAPE_STACK } from './const'; + +import { utilAssertNotInfinite, utilFlagSpread } from './util'; + +const stackOpToString = (cx) => { + return cx.__toString('Stack [', ']'); }; -export class StackImpl extends IndexedCollectionImpl { - create(value) { - return Stack(value); +const stackOpGet = (cx, index, notSetValue) => { + let head = cx._head; + index = wrapIndex(cx, index); + while (head && index--) { + head = head.next; } + return head ? head.value : notSetValue; +}; - toString() { - return this.__toString('Stack [', ']'); - } +const stackOpPeek = (cx) => { + return cx._head && cx._head.value; +}; - // @pragma Access +// @pragma Modification - get(index, notSetValue) { - let head = this._head; - index = wrapIndex(this, index); - while (head && index--) { - head = head.next; - } - return head ? head.value : notSetValue; +const stackOpPush = (cx, values) => { + if (values.length === 0) { + return cx; + } + const newSize = cx.size + values.length; + let head = cx._head; + for (let ii = values.length - 1; ii >= 0; ii--) { + head = { + value: values[ii], + next: head, + }; } + if (cx.__ownerID) { + cx.size = newSize; + cx._head = head; + cx.__hash = undefined; + cx.__altered = true; + return cx; + } + return stackCreate(newSize, head); +}; - peek() { - return this._head && this._head.value; +const stackOpPushAll = (cx, iter) => { + iter = SeqIndexedWhenNotIndexed(iter); + if (iter.size === 0) { + return cx; } + if (cx.size === 0 && probeIsStack(iter)) { + return iter; + } + utilAssertNotInfinite(iter.size); + let newSize = cx.size; + let head = cx._head; + iter.__iterate((value) => { + newSize++; + head = { + value: value, + next: head, + }; + }, /* reverse */ true); + if (cx.__ownerID) { + cx.size = newSize; + cx._head = head; + cx.__hash = undefined; + cx.__altered = true; + return cx; + } + return stackCreate(newSize, head); +}; - // @pragma Modification +const stackOpPop = (cx) => { + return cx.slice(1); +}; - push(/*...values*/) { - if (arguments.length === 0) { - return this; - } - const newSize = this.size + arguments.length; - let head = this._head; - for (let ii = arguments.length - 1; ii >= 0; ii--) { - head = { - value: arguments[ii], - next: head, - }; - } - if (this.__ownerID) { - this.size = newSize; - this._head = head; - this.__hash = undefined; - this.__altered = true; - return this; - } - return makeStack(newSize, head); +const stackOpClear = (cx) => { + if (cx.size === 0) { + return cx; } - - pushAll(iter) { - iter = IndexedCollection(iter); - if (iter.size === 0) { - return this; - } - if (this.size === 0 && isStack(iter)) { - return iter; - } - assertNotInfinite(iter.size); - let newSize = this.size; - let head = this._head; - iter.__iterate((value) => { - newSize++; - head = { - value: value, - next: head, - }; - }, /* reverse */ true); - if (this.__ownerID) { - this.size = newSize; - this._head = head; - this.__hash = undefined; - this.__altered = true; - return this; - } - return makeStack(newSize, head); + if (cx.__ownerID) { + cx.size = 0; + cx._head = undefined; + cx.__hash = undefined; + cx.__altered = true; + return cx; } + return stackCreateEmpty(); +}; - pop() { - return this.slice(1); +const stackOpSlice = (cx, begin, end) => { + if (wholeSlice(begin, end, cx.size)) { + return cx; } - - clear() { - if (this.size === 0) { - return this; - } - if (this.__ownerID) { - this.size = 0; - this._head = undefined; - this.__hash = undefined; - this.__altered = true; - return this; - } - return emptyStack(); + let resolvedBegin = resolveBegin(begin, cx.size); + const resolvedEnd = resolveEnd(end, cx.size); + if (resolvedEnd !== cx.size) { + return cx.slice(begin, end) } - - slice(begin, end) { - if (wholeSlice(begin, end, this.size)) { - return this; - } - let resolvedBegin = resolveBegin(begin, this.size); - const resolvedEnd = resolveEnd(end, this.size); - if (resolvedEnd !== this.size) { - // super.slice(begin, end); - return IndexedCollectionImpl.prototype.slice.call(this, begin, end); - } - const newSize = this.size - resolvedBegin; - let head = this._head; - while (resolvedBegin--) { - head = head.next; - } - if (this.__ownerID) { - this.size = newSize; - this._head = head; - this.__hash = undefined; - this.__altered = true; - return this; - } - return makeStack(newSize, head); + const newSize = cx.size - resolvedBegin; + let head = cx._head; + while (resolvedBegin--) { + head = head.next; + } + if (cx.__ownerID) { + cx.size = newSize; + cx._head = head; + cx.__hash = undefined; + cx.__altered = true; + return cx; } + return stackCreate(newSize, head); +}; - // @pragma Mutability +// @pragma Mutability - __ensureOwner(ownerID) { - if (ownerID === this.__ownerID) { - return this; - } - if (!ownerID) { - if (this.size === 0) { - return emptyStack(); - } - this.__ownerID = ownerID; - this.__altered = false; - return this; +const stackOpEnsureOwner = (cx, ownerID) => { + if (ownerID === cx.__ownerID) { + return cx; + } + if (!ownerID) { + if (cx.size === 0) { + return stackCreateEmpty(); } - return makeStack(this.size, this._head, ownerID, this.__hash); + cx.__ownerID = ownerID; + cx.__altered = false; + return cx; } + return stackCreate(cx.size, cx._head, ownerID, cx.__hash); +}; - // @pragma Iteration +// @pragma Iteration - __iterate(fn, reverse) { - if (reverse) { - return new ArraySeq(this.toArray()).__iterate( - (v, k) => fn(v, k, this), - reverse - ); - } - let iterations = 0; - let node = this._head; - while (node) { - if (fn(node.value, iterations++, this) === false) { - break; - } - node = node.next; +const stackOpIterate = (cx, fn, reverse) => { + if (reverse) { + return SeqArray(cx.toArray()).__iterate((v, k) => fn(v, k, cx), reverse); + } + let iterations = 0; + let node = cx._head; + while (node) { + if (fn(node.value, iterations++, cx) === false) { + break; } - return iterations; + node = node.next; } + return iterations; +}; - __iterator(type, reverse) { - if (reverse) { - return new ArraySeq(this.toArray()).__iterator(type, reverse); +const stackOpIterator = (cx, type, reverse) => { + if (reverse) { + return SeqArray(cx.toArray()).__iterator(type, reverse); + } + let iterations = 0; + let node = cx._head; + return new Iterator(() => { + if (node) { + const value = node.value; + node = node.next; + return iteratorValue(type, iterations++, value); } - let iterations = 0; - let node = this._head; - return new Iterator(() => { - if (node) { - const value = node.value; - node = node.next; - return iteratorValue(type, iterations++, value); - } - return iteratorDone(); - }); - } -} - -Stack.isStack = isStack; - -const StackPrototype = StackImpl.prototype; -StackPrototype[IS_STACK_SYMBOL] = true; -StackPrototype.shift = StackPrototype.pop; -StackPrototype.unshift = StackPrototype.push; -StackPrototype.unshiftAll = StackPrototype.pushAll; -StackPrototype.withMutations = withMutations; -StackPrototype.wasAltered = wasAltered; -StackPrototype.asImmutable = asImmutable; -StackPrototype['@@transducer/init'] = StackPrototype.asMutable = asMutable; -StackPrototype['@@transducer/step'] = function (result, arr) { - return result.unshift(arr); + return iteratorDone(); + }); }; -StackPrototype['@@transducer/result'] = function (obj) { - return obj.asImmutable(); + +const stackPropertiesCreate = ((cache) => () => { + cache = + cache || + (cache = Object.assign( + {}, + collectionIndexedPropertiesCreate(), + { + [IS_STACK_SYMBOL]: true, + create: Stack, + ['@@transducer/init']: function () { + return collectionOpAsMutable(this); + }, + ['@@transducer/step']: function (result, arr) { + return result.unshift(arr); + }, + ['@@transducer/result']: function (obj) { + return collectionOpAsImmutable(obj); + }, + }, + transformToMethods({ + toString: stackOpToString, + get: stackOpGet, + peek: stackOpPeek, + push: utilFlagSpread(stackOpPush), + unshift: utilFlagSpread(stackOpPush), + pushAll: stackOpPushAll, + unshiftAll: stackOpPushAll, + pop: stackOpPop, + shift: stackOpPop, + clear: stackOpClear, + slice: stackOpSlice, + __ensureOwner: stackOpEnsureOwner, + __iterate: stackOpIterate, + __iterator: stackOpIterator, + }) + )); + + return cache; +})(); + +const stackCreate = (size, head, ownerID, hash) => { + const stack = Object.create(stackPropertiesCreate()); + stack.size = size; + stack._head = head; + stack.__ownerID = ownerID; + stack.__hash = hash; + stack.__altered = false; + stack.__shape = SHAPE_STACK; + return stack; }; -function makeStack(size, head, ownerID, hash) { - const map = Object.create(StackPrototype); - map.size = size; - map._head = head; - map.__ownerID = ownerID; - map.__hash = hash; - map.__altered = false; - return map; -} - -let EMPTY_STACK; -function emptyStack() { - return EMPTY_STACK || (EMPTY_STACK = makeStack(0)); -} +const stackCreateEmpty = ((cache) => () => { + return cache || (cache = stackCreate(0)); +})(); + +const Stack = (value) => + value === undefined || value === null + ? stackCreateEmpty() + : probeIsStack(value) + ? value + : stackCreateEmpty().pushAll(value); + +Stack.of = (...args) => Stack(args); +Stack.isStack = probeIsStack; + +export { Stack }; diff --git a/src/TrieUtils.ts b/src/TrieUtils.ts index d5c75c179b..852e92b447 100644 --- a/src/TrieUtils.ts +++ b/src/TrieUtils.ts @@ -1,17 +1,10 @@ import type { Collection } from '../type-definitions/immutable'; -// Used for setting prototype methods that IE8 chokes on. -export const DELETE = 'delete'; - // Constants describing the size of trie nodes. export const SHIFT = 5; // Resulted in best performance after ______? export const SIZE = 1 << SHIFT; export const MASK = SIZE - 1; -// A consistent shared value representing "not set" which equals nothing other -// than itself, and nothing that could be provided externally. -export const NOT_SET = {}; - type Ref = { value: boolean }; // Boolean references, Rough equivalent of `bool &`. diff --git a/src/Universe.js b/src/Universe.js new file mode 100644 index 0000000000..f949cb1549 --- /dev/null +++ b/src/Universe.js @@ -0,0 +1,217 @@ +import transformToMethods from './transformToMethods'; +import { toJS } from './toJS'; + +import { utilFlagSpread } from './util'; + +import { probeIsKeyed, probeIsIndexed } from './probe'; + +import { + SeqWhenNotCollection, + SeqIndexedWhenNotIndexed, + SeqKeyedWhenNotKeyed, +} from './Seq'; + +import { Stack } from './Stack'; +import { Set } from './Set'; +import { SetOrdered, setOrderedPropertiesCreate } from './SetOrdered'; +import { Map, mapCreateEmpty, mapPropertiesCreate } from './Map'; +import { MapOrdered } from './MapOrdered'; +import { collectionPropertiesCreate } from './collection/collection'; +import { collectionIndexedPropertiesCreate } from './collection/collectionIndexed'; +import { collectionKeyedPropertiesCreate } from './collection/collectionKeyed'; +import { + collectionCastKeyedSeqCreate, + collectionCastKeyedSeqPropertiesCreate, +} from './collection/collectionCastKeyedSeq'; +import { collectionKeyedSeqFromEntriesCreate } from './collection/collectionKeyedSeqFromEntries'; +import { collectionRecordPropertiesCreate } from './collection/collectionRecord'; + +import { + collectionXOrAnyOpUpdate, + collectionXOrAnyOpUpdateIn, + collectionXOrAnyOpRemoveIn, + collectionXOrAnyOpSetIn, + collectionXOpCountBy, + collectionXOpEntrySeq, + collectionXOpMap, + collectionXOpReverse, + collectionXOpSort, + collectionXOpSortBy, + collectionXOpSlice, + collectionXOpPartition, + collectionXOpFilter, + collectionXOpFilterNot, + collectionXOpFlip, + collectionXOpFlatMap, + collectionXOpFlatten, + collectionXOpGroupBy, + collectionXOpConcat, + collectionXOpTake, + collectionXOpTakeLast, + collectionXOpLastKeyOf, + collectionXOpMergeIn, + collectionXOpMergeWith, + collectionXOpMerge, + collectionXOpMergeDeep, + collectionXOpMergeDeepWith, + collectionXOpMergeDeepIn, + collectionXOpIsSubset, + collectionXOpIsSuperset, + collectionXIndexedOpZip, + collectionXIndexedOpZipWith, + collectionXIndexedOpZipAll, + collectionXIndexedOpInterleave, + collectionXIndexedOpInterpose, + collectionXIndexedOpFilter, + collectionXIndexedOpReverse, + collectionXIndexedOpSlice, + collectionXIndexedOpSplice, + collectionXKeyedOpFlip, + collectionXKeyedOpMapEntries, + collectionXKeyedOpMapKeys, + collectionXCastKeyedSequenceOpReverse, + collectionXCastKeyedSequenceOpMap, +} from './collection/collectionX'; + +import { collectionCastSetSeqCreate } from './collection/collectionCastSetSeq.js'; + +import { collectionCastIndexedSeqCreate } from './collection/collectionCastIndexedSeq.js'; + +import { Range } from './Range'; +import { listPropertiesCreate, List } from './List'; + +Object.assign( + collectionPropertiesCreate(), + { + // primarily used by recursive 'merge deep' functions + // not passed in as params now to leave interface un-changed + __SeqIndexedWhenNotIndexed: SeqIndexedWhenNotIndexed, + __SeqKeyedWhenNotKeyed: SeqKeyedWhenNotKeyed, + __mapCreateEmpty: mapCreateEmpty, + }, + transformToMethods({ + toJS, + toSeq: (cx) => + probeIsIndexed(cx) + ? cx.toIndexedSeq() + : probeIsKeyed(cx) + ? cx.toKeyedSeq() + : cx.toSetSeq(), + toSetSeq: collectionCastSetSeqCreate, + toOrderedMap: (cx) => MapOrdered(collectionCastKeyedSeqCreate(cx)), + toOrderedSet: (cx) => SetOrdered(probeIsKeyed(cx) ? cx.valueSeq() : cx), + toSet: (cx) => Set(probeIsKeyed(cx) ? cx.valueSeq() : cx), + toIndexedSeq: collectionCastIndexedSeqCreate, + toKeyedSeq: (cx) => collectionCastKeyedSeqCreate(cx, true), + toMap: (cx) => Map(collectionCastKeyedSeqCreate(cx, true)), + toList: (cx) => List(probeIsKeyed(cx) ? cx.valueSeq() : cx), + toStack: (cx) => Stack(probeIsKeyed(cx) ? cx.valueSeq() : cx), + entrySeq: collectionXOpEntrySeq, + fromEntrySeq: collectionKeyedSeqFromEntriesCreate, + countBy: collectionXOpCountBy, + flatMap: collectionXOpFlatMap, + groupBy: collectionXOpGroupBy, + flatten: collectionXOpFlatten, + valueSeq: (cx) => cx.toIndexedSeq(), + take: collectionXOpTake, + map: collectionXOpMap, + sortBy: collectionXOpSortBy, + sort: collectionXOpSort, + concat: utilFlagSpread(collectionXOpConcat), + flip: collectionXOpFlip, + filter: collectionXOpFilter, + filterNot: collectionXOpFilterNot, + partition: collectionXOpPartition, + slice: collectionXOpSlice, + takeLast: collectionXOpTakeLast, + reverse: collectionXOpReverse, + isSubset: collectionXOpIsSubset, + isSuperset: collectionXOpIsSuperset, + update: collectionXOrAnyOpUpdate, + updateIn: collectionXOrAnyOpUpdateIn, + setIn: collectionXOrAnyOpSetIn, + }) +); + +Object.assign( + collectionKeyedPropertiesCreate(), + transformToMethods({ + flip: collectionXKeyedOpFlip, + mapEntries: collectionXKeyedOpMapEntries, + mapKeys: collectionXKeyedOpMapKeys, + }) +); + +Object.assign( + collectionCastKeyedSeqPropertiesCreate(), + transformToMethods({ + reverse: collectionXCastKeyedSequenceOpReverse, + map: collectionXCastKeyedSequenceOpMap, + }) +); + +Object.assign( + setOrderedPropertiesCreate(), + transformToMethods({ + zip: utilFlagSpread(collectionXIndexedOpZip), + zipWith: utilFlagSpread(collectionXIndexedOpZipWith), + zipAll: utilFlagSpread(collectionXIndexedOpZipAll), + }) +); + +Object.assign( + collectionIndexedPropertiesCreate(), + transformToMethods({ + reverse: collectionXIndexedOpReverse, + filter: collectionXIndexedOpFilter, + slice: collectionXIndexedOpSlice, + lastKeyOf: collectionXOpLastKeyOf, + interpose: collectionXIndexedOpInterpose, + zip: utilFlagSpread(collectionXIndexedOpZip), + zipWith: utilFlagSpread(collectionXIndexedOpZipWith), + zipAll: utilFlagSpread(collectionXIndexedOpZipAll), + interleave: utilFlagSpread(collectionXIndexedOpInterleave), + splice: utilFlagSpread(collectionXIndexedOpSplice), + toKeyedSeq: (cx) => collectionCastKeyedSeqCreate(cx, false), + keySeq: (cx) => Range(0, cx.size), + }) +); + +Object.assign( + collectionRecordPropertiesCreate(), + transformToMethods({ + mergeWith: utilFlagSpread(collectionXOpMergeWith), + merge: utilFlagSpread(collectionXOpMerge), + mergeIn: utilFlagSpread(collectionXOpMergeIn), + mergeDeep: utilFlagSpread(collectionXOpMergeDeep), + mergeDeepWith: utilFlagSpread(collectionXOpMergeDeepWith), + mergeDeepIn: utilFlagSpread(collectionXOpMergeDeepIn), + deleteIn: collectionXOrAnyOpRemoveIn, + removeIn: collectionXOrAnyOpRemoveIn, + }) +); + +Object.assign( + listPropertiesCreate(), + transformToMethods({ + deleteIn: collectionXOrAnyOpRemoveIn, + removeIn: collectionXOrAnyOpRemoveIn, + }) +); + +Object.assign( + mapPropertiesCreate(), + transformToMethods({ + mergeWith: utilFlagSpread(collectionXOpMergeWith), + merge: utilFlagSpread(collectionXOpMerge), + concat: utilFlagSpread(collectionXOpMerge), + mergeIn: utilFlagSpread(collectionXOpMergeIn), + mergeDeep: utilFlagSpread(collectionXOpMergeDeep), + mergeDeepWith: utilFlagSpread(collectionXOpMergeDeepWith), + mergeDeepIn: utilFlagSpread(collectionXOpMergeDeepIn), + deleteIn: collectionXOrAnyOpRemoveIn, + removeIn: collectionXOrAnyOpRemoveIn, + }) +); + +export { SeqWhenNotCollection as Collection }; diff --git a/src/collection/collection.js b/src/collection/collection.js new file mode 100644 index 0000000000..8d4619be9c --- /dev/null +++ b/src/collection/collection.js @@ -0,0 +1,770 @@ +import transformToMethods from '../transformToMethods'; + +import { returnTrue, ensureSize, wrapIndex, OwnerID } from '../TrieUtils'; + +import { + utilCopyShallow, + utilQuoteString, + utilHasOwnProperty, + utilAssertNotInfinite, +} from '../util'; + +import { + NOT_SET, + IS_COLLECTION_SYMBOL, + ITERATE_VALUES, + ITERATE_ENTRIES, + ITERATE_KEYS, + ITERATOR_SYMBOL, +} from '../const'; + +import { + probeIsSame, + probeIsImmutable, + probeIsOrdered, + probeIsKeyed, + probeIsDataStructure, + probeIsSameDeep, + probeCoerceKeyPath, +} from '../probe'; + +import { factoryMax } from '../factory/factoryMax'; + +import { imul, smi } from '../Math'; +import { hash } from '../Hash'; + +function neg(predicate) { + return function () { + return -predicate.apply(this, arguments); + }; +} + +const collectionReduce = ( + cx, + reducer, + reduction, + context, + useFirst, + reverse +) => { + utilAssertNotInfinite(cx.size); + cx.__iterate((v, k, c) => { + if (useFirst) { + useFirst = false; + reduction = v; + } else { + reduction = reducer.call(context, reduction, v, k, c); + } + }, reverse); + return reduction; +}; + +function hashMerge(a, b) { + return (a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2))) | 0; // int +} + +function murmurHashOfSize(size, h) { + h = imul(h, 0xcc9e2d51); + h = imul((h << 15) | (h >>> -15), 0x1b873593); + h = imul((h << 13) | (h >>> -13), 5); + h = ((h + 0xe6546b64) | 0) ^ size; + h = imul(h ^ (h >>> 16), 0x85ebca6b); + h = imul(h ^ (h >>> 13), 0xc2b2ae35); + h = smi(h ^ (h >>> 16)); + return h; +} + +function hashCollection(collection) { + if (collection.size === Infinity) { + return 0; + } + const ordered = probeIsOrdered(collection); + const keyed = probeIsKeyed(collection); + let h = ordered ? 1 : 0; + + collection.__iterate( + keyed + ? ordered + ? (v, k) => { + h = (31 * h + hashMerge(hash(v), hash(k))) | 0; + } + : (v, k) => { + h = (h + hashMerge(hash(v), hash(k))) | 0; + } + : ordered + ? (v) => { + h = (31 * h + hash(v)) | 0; + } + : (v) => { + h = (h + hash(v)) | 0; + } + ); + + return murmurHashOfSize(collection.size, h); +} + +const collectionOpSize = (cx) => { + return cx._values.size; +}; + +const collectionOpRest = (cx) => { + return cx.slice(1); +}; + +const collectionOpReduce = (cx, reducer, initialReduction, context) => { + return collectionReduce( + cx, + reducer, + initialReduction, + context, + typeof initialReduction === 'undefined' && typeof context === 'undefined', + false + ); +}; + +const collectionOpReduceRight = (cx, reducer, initialReduction, context) => { + return collectionReduce( + cx, + reducer, + initialReduction, + context, + typeof initialReduction === 'undefined' && typeof context === 'undefined', + true + ); +}; + +const collectionOpKeys = (cx) => { + return cx.__iterator(ITERATE_KEYS); +}; + +const collectionOpKeySeq = (cx) => { + return cx + .toSeq() + .map((v, k) => k) + .toIndexedSeq(); +}; + +const collectionOpToStringMapper = (cx, value) => { + return utilQuoteString(value); +}; + +const collectionOpToArray = (cx) => { + utilAssertNotInfinite(cx.size); + const array = new Array(cx.size || 0); + const useTuples = probeIsKeyed(cx); + + let i = 0; + cx.__iterate((v, k) => { + array[i++] = useTuples ? [k, v] : v; + }); + return array; +}; + +const collectionOpToJSON = (cx) => { + return cx.toArray(); +}; + +const collectionOpIndexedHas = (cx, index) => { + index = wrapIndex(cx, index); + return ( + index >= 0 && + (cx.size !== undefined + ? cx.size === Infinity || index < cx.size + : cx.indexOf(index) !== -1) + ); +}; + +const collectionOpIndexedFindIndex = (cx, predicate, context) => { + const entry = cx.findEntry(predicate, context); + return entry ? entry[0] : -1; +}; + +const collectionOpIndexedIndexOf = (cx, searchValue) => { + const key = cx.keyOf(searchValue); + return key === undefined ? -1 : key; +}; + +const collectionOpIndexedLastIndexOf = (cx, searchValue) => { + const key = cx.lastKeyOf(searchValue); + return key === undefined ? -1 : key; +}; + +// ### More collection methods + +const collectionOpIndexedFindLastIndex = (cx, predicate, context) => { + const entry = cx.findLastEntry(predicate, context); + return entry ? entry[0] : -1; +}; + +const collectionOpFirst = (cx, notSetValue) => { + return cx.find(returnTrue, null, notSetValue); +}; + +const collectionOpIndexedFirst = (cx, notSetValue) => { + return cx.get(0, notSetValue); +}; + +const collectionOpIndexedGet = (cx, index, notSetValue) => { + index = wrapIndex(cx, index); + return index < 0 || + cx.size === Infinity || + (cx.size !== undefined && index > cx.size) + ? notSetValue + : cx.find((_, key) => key === index, undefined, notSetValue); +}; + +const collectionOpIndexedKeySeq = (cx) => { + return Range(0, cx.size); +}; + +const collectionOpIndexedLast = (cx, notSetValue) => { + return cx.get(-1, notSetValue); +}; + +const collectionOpSome = (cx, predicate, context) => { + utilAssertNotInfinite(cx.size); + let returnValue = false; + cx.__iterate((v, k, c) => { + if (predicate.call(context, v, k, c)) { + returnValue = true; + return false; + } + }); + return returnValue; +}; + +const collectionOpEvery = (cx, predicate, context) => { + utilAssertNotInfinite(cx.size); + let returnValue = true; + cx.__iterate((v, k, c) => { + if (!predicate.call(context, v, k, c)) { + returnValue = false; + return false; + } + }); + return returnValue; +}; + +const collectionOpForEach = (cx, sideEffect, context) => { + utilAssertNotInfinite(cx.size); + + return cx.__iterate(context ? sideEffect.bind(context) : sideEffect); +}; + +const collectionOpFindEntry = (cx, predicate, context, notSetValue) => { + let found = notSetValue; + cx.__iterate((v, k, c) => { + if (predicate.call(context, v, k, c)) { + found = [k, v]; + return false; + } + }); + return found; +}; + +const collectionOpFindLast = (cx, predicate, context, notSetValue) => { + return cx.toKeyedSeq().reverse().find(predicate, context, notSetValue); +}; + +const collectionOpFindLastEntry = (cx, predicate, context, notSetValue) => { + return cx.toKeyedSeq().reverse().findEntry(predicate, context, notSetValue); +}; + +const collectionOpFindLastKey = (cx, predicate, context) => { + return cx.toKeyedSeq().reverse().findKey(predicate, context); +}; + +const collectionOpFind = (cx, predicate, context, notSetValue) => { + const entry = collectionOpFindEntry(cx, predicate, context); + return entry ? entry[1] : notSetValue; +}; + +const collectionOpFindKey = (cx, predicate, context) => { + const entry = collectionOpFindEntry(cx, predicate, context); + + return entry && entry[0]; +}; + +const collectionOpTake = (cx, amount) => { + return cx.slice(0, Math.max(0, amount)); +}; + +const collectionOpTakeLast = (cx, amount) => { + return cx.slice(-Math.max(0, amount)); +}; + +const collectionOpButLast = (cx) => { + return cx.slice(0, -1); +}; + +const collectionOpIsEmpty = (cx) => { + return cx.size !== undefined ? cx.size === 0 : !cx.some(() => true); +}; + +const collectionOpGet = (cx, searchKey, notSetValue) => { + return collectionOpFind( + cx, + (_, key) => probeIsSame(key, searchKey), + undefined, + notSetValue + ); +}; + +/** + * Returns the value within the provided collection associated with the + * provided key, or notSetValue if the key is not defined in the collection. + * + * A functional alternative to `collection.get(key)` which will also work on + * plain Objects and Arrays as an alternative for `collection[key]`. + * + * + * ```js + * import { get } from 'immutable'; + * + * get([ 'dog', 'frog', 'cat' ], 1) // 'frog' + * get({ x: 123, y: 456 }, 'x') // 123 + * get({ x: 123, y: 456 }, 'z', 'ifNotSet') // 'ifNotSet' + * ``` + */ +const collectionOrAnyOpGet = (collection, key, notSetValue) => { + return probeIsImmutable(collection) + ? collection.get(key, notSetValue) + : !collectionOrAnyOpHas(collection, key) + ? notSetValue + : // @ts-expect-error weird "get" here, + typeof collection.get === 'function' + ? // @ts-expect-error weird "get" here, + collection.get(key) + : // @ts-expect-error key is unknown here, + collection[key]; +}; + +/** + * Returns the value at the provided key path starting at the provided + * collection, or notSetValue if the key path is not defined. + * + * A functional alternative to `collection.getIn(keypath)` which will also + * work with plain Objects and Arrays. + * + * + * ```js + * import { getIn } from 'immutable'; + * + * getIn({ x: { y: { z: 123 }}}, ['x', 'y', 'z']) // 123 + * getIn({ x: { y: { z: 123 }}}, ['x', 'q', 'p'], 'ifNotSet') // 'ifNotSet' + * ``` + */ +const collectionOrAnyOpGetIn = (cx, searchKeyPath, notSetValue) => { + const keyPath = probeCoerceKeyPath(searchKeyPath); + let i = 0; + while (i !== keyPath.length) { + // @ts-expect-error keyPath[i++] can not be undefined by design + cx = collectionOrAnyOpGet(cx, keyPath[i++], NOT_SET); + if (cx === NOT_SET) { + return notSetValue; + } + } + return cx; +}; + +/** + * Returns true if the key is defined in the provided collection. + * + * A functional alternative to `collection.has(key)` which will also work with + * plain Objects and Arrays as an alternative for + * `collection.hasOwnProperty(key)`. + * + * + * ```js + * import { has } from 'immutable'; + * + * has([ 'dog', 'frog', 'cat' ], 2) // true + * has([ 'dog', 'frog', 'cat' ], 5) // false + * has({ x: 123, y: 456 }, 'x') // true + * has({ x: 123, y: 456 }, 'z') // false + * ``` + */ + +const collectionOrAnyOpHas = (collection, key) => { + return probeIsImmutable(collection) + ? // @ts-expect-error key might be a number or symbol, which is not handled be Record key type + collection.has(key) + : // @ts-expect-error key might be anything else than PropertyKey, and will return false in that case but runtime is OK + probeIsDataStructure(collection) && + utilHasOwnProperty.call(collection, key); +}; + +const collectionOpHas = (cx, searchKey) => { + return collectionOpGet(cx, searchKey, NOT_SET) !== NOT_SET; +}; + +/** + * Returns true if the key path is defined in the provided collection. + * + * A functional alternative to `collection.hasIn(keypath)` which will also + * work with plain Objects and Arrays. + * + * + * ```js + * import { hasIn } from 'immutable'; + * + * hasIn({ x: { y: { z: 123 }}}, ['x', 'y', 'z']) // true + * hasIn({ x: { y: { z: 123 }}}, ['x', 'q', 'p']) // false + * ``` + */ +const collectionOrAnyOpHasIn = (cx, keyPath) => { + return collectionOrAnyOpGetIn(cx, keyPath, NOT_SET) !== NOT_SET; +}; + +const collectionOpKeyOf = (cx, searchValue) => { + return cx.findKey((value) => probeIsSame(value, searchValue)); +}; + +/** + * Returns a copy of the collection with the value at key set to the provided + * value. + * + * A functional alternative to `collection.set(key, value)` which will also + * work with plain Objects and Arrays as an alternative for + * `collectionCopy[key] = value`. + * + * + * ```js + * import { set } from 'immutable'; + * + * const originalArray = [ 'dog', 'frog', 'cat' ] + * set(originalArray, 1, 'cow') // [ 'dog', 'cow', 'cat' ] + * console.log(originalArray) // [ 'dog', 'frog', 'cat' ] + * const originalObject = { x: 123, y: 456 } + * set(originalObject, 'x', 789) // { x: 789, y: 456 } + * console.log(originalObject) // { x: 123, y: 456 } + * ``` + */ +const collectionOrAnyOpSet = (cx, key, value) => { + if (!probeIsDataStructure(cx)) { + throw new TypeError( + 'Cannot update non-data-structure value: ' + cx + ); + } + if (probeIsImmutable(cx)) { + // @ts-expect-error weird "set" here, + if (!cx.set) { + throw new TypeError( + 'Cannot update immutable value without .set() method: ' + + (cx._toString ? cx._toString() : cx) + ); + } + // @ts-expect-error weird "set" here, + return cx.set(key, value); + } + // @ts-expect-error mix of key and string here. Probably need a more fine type here + if (utilHasOwnProperty.call(cx, key) && value === cx[key]) { + return cx; + } + const collectionCopy = utilCopyShallow(cx); + // @ts-expect-error mix of key and string here. Probably need a more fine type here + collectionCopy[key] = value; + return collectionCopy; +}; + +/** + * Returns a copy of the collection with the value at key removed. + * + * A functional alternative to `collection.remove(key)` which will also work + * with plain Objects and Arrays as an alternative for + * `delete collectionCopy[key]`. + * + * + * ```js + * import { remove } from 'immutable'; + * + * const originalArray = [ 'dog', 'frog', 'cat' ] + * remove(originalArray, 1) // [ 'dog', 'cat' ] + * console.log(originalArray) // [ 'dog', 'frog', 'cat' ] + * const originalObject = { x: 123, y: 456 } + * remove(originalObject, 'x') // { y: 456 } + * console.log(originalObject) // { x: 123, y: 456 } + * ``` + */ +const collectionOrAnyOpRemove = (cx, key) => { + if (!probeIsDataStructure(cx)) { + throw new TypeError( + 'Cannot update non-data-structure value: ' + cx + ); + } + if (probeIsImmutable(cx)) { + // @ts-expect-error weird "remove" here, + if (!cx.remove) { + throw new TypeError( + 'Cannot update immutable value without .remove() method: ' + cx + ); + } + // @ts-expect-error weird "remove" here, + return cx.remove(key); + } + if (!utilHasOwnProperty.call(cx, key)) { + return cx; + } + const collectionCopy = utilCopyShallow(cx); + if (Array.isArray(collectionCopy)) { + // @ts-expect-error assert that key is a number here + collectionCopy.splice(key, 1); + } else { + delete collectionCopy[key]; + } + return collectionCopy; +}; + +const collectionOpToObject = (cx) => { + utilAssertNotInfinite(cx.size); + const object = {}; + cx.__iterate((v, k) => { + object[k] = v; + }); + return object; +}; + +const collectionOpToStringDetails = (cx, head, tail) => { + if (cx.size === 0) { + return head + tail; + } + return ( + head + ' ' + cx.toSeq().map(cx.__toStringMapper).join(', ') + ' ' + tail + ); +}; + +function defaultNegComparator(a, b) { + return a < b ? 1 : a > b ? -1 : 0; +} + +const collectionOpMax = (cx, comparator) => { + return factoryMax(cx, comparator); +}; + +const collectionOpMaxBy = (cx, mapper, comparator) => { + return factoryMax(cx, comparator, mapper); +}; + +const collectionOpMin = (cx, comparator) => { + return factoryMax(cx, comparator ? neg(comparator) : defaultNegComparator); +}; + +const collectionOpMinBy = (cx, mapper, comparator) => { + return factoryMax( + cx, + comparator ? neg(comparator) : defaultNegComparator, + mapper + ); +}; + +const collectionOpJoin = (cx, separator) => { + utilAssertNotInfinite(cx.size); + separator = separator !== undefined ? '' + separator : ','; + let joined = ''; + let isFirst = true; + cx.__iterate((v) => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here + isFirst ? (isFirst = false) : (joined += separator); + joined += v !== null && v !== undefined ? v.toString() : ''; + }); + return joined; +}; + +const collectionOpCount = (cx, predicate, context) => { + return ensureSize(predicate ? cx.toSeq().filter(predicate, context) : cx); +}; + +const collectionOpWasAltered = (cx) => { + return cx.__altered; +}; + +const collectionOpAsMutable = (cx) => { + return cx.__ownerID ? cx : cx.__ensureOwner(new OwnerID()); +}; + +const collectionOpAsImmutable = (cx) => { + return cx.__ensureOwner(); +}; + +const collectionOpWithMutations = (cx, fn) => { + const mutable = collectionOpAsMutable(cx); + fn(mutable); + return mutable.wasAltered() ? mutable.__ensureOwner(cx.__ownerID) : cx; +}; + +const collectionOpSkip = (cx, amount) => { + return amount === 0 ? cx : cx.slice(Math.max(0, amount)); +}; + +const collectionOpSkipLast = (cx, amount) => { + return amount === 0 ? cx : cx.slice(0, -Math.max(0, amount)); +}; + +const collectionOpValues = (collection) => { + return collection.__iterator(ITERATE_VALUES); +}; + +const collectionOpEquals = (cx, other) => { + return probeIsSameDeep(cx, other); +}; + +const collectionOpEntries = (collection) => { + return collection.__iterator(ITERATE_ENTRIES); +}; + +const collectionOpIncludes = (cx, searchValue) => { + return cx.some((value) => probeIsSame(value, searchValue)); +}; + +const collectionOpInspect = (cx) => { + return cx.toString(); +}; + +const collectionOpHashCode = (cx) => { + return cx.__hash || (cx.__hash = hashCollection(cx)); +}; + +const collectionOpCache = (cx) => { + if (!cx._cache && cx.__iterateUncached) { + cx._cache = cx.entrySeq().toArray(); + cx.size = cx._cache.length; + } + return cx; +}; + +const collectionOpCacheResultThrough = (cx) => { + if (cx._iter.cacheResult) { + cx._iter.cacheResult(); + cx.size = cx._iter.size; + return cx; + } + + // WARNING tests do not cover this area! + return collectionOpCache(cx); +}; + +const collectionPropertiesCreate = ((cache) => () => { + cache = + cache || + (cache = transformToMethods({ + [IS_COLLECTION_SYMBOL]: true, + toArray: collectionOpToArray, + forEach: collectionOpForEach, + values: collectionOpValues, + join: collectionOpJoin, + toJSON: collectionOpToJSON, + inspect: collectionOpInspect, + toSource: collectionOpInspect, + min: collectionOpMin, + minBy: collectionOpMinBy, + max: collectionOpMax, + maxBy: collectionOpMaxBy, + reduceRight: collectionOpReduceRight, + keyOf: collectionOpKeyOf, + first: collectionOpFirst, + find: collectionOpFind, + findKey: collectionOpFindKey, + findEntry: collectionOpFindEntry, + findLast: collectionOpFindLast, + findLastEntry: collectionOpFindLastEntry, + findLastKey: collectionOpFindLastKey, + isEmpty: collectionOpIsEmpty, + some: collectionOpSome, + count: collectionOpCount, + entries: collectionOpEntries, + skip: collectionOpSkip, + skipLast: collectionOpSkipLast, + equals: collectionOpEquals, + hashCode: collectionOpHashCode, + toObject: collectionOpToObject, + get: collectionOpGet, + every: collectionOpEvery, + includes: collectionOpIncludes, + keySeq: collectionOpKeySeq, + keys: collectionOpKeys, + getIn: collectionOrAnyOpGetIn, + hasIn: collectionOrAnyOpHasIn, + has: collectionOpHas, + asImmutable: collectionOpAsImmutable, + asMutable: collectionOpAsMutable, + withMutations: collectionOpWithMutations, + rest: collectionOpRest, + butLast: collectionOpButLast, + reduce: collectionOpReduce, + __toString: collectionOpToStringDetails, + __toStringMapper: collectionOpToStringMapper, + })); + + // existing tests require ref-equal + // cx.keys, cx.values, cx[ITERATOR_SYMBOL] + // ``` + // expect(l[Symbol.iterator]).toBe(l.values); + // ``` + cache[ITERATOR_SYMBOL] = cache.values; + + return cache; +})(); + +export { + collectionOrAnyOpSet, + collectionOrAnyOpGet, + collectionOrAnyOpGetIn, + collectionOrAnyOpHas, + collectionOrAnyOpHasIn, + collectionOrAnyOpRemove, + collectionOpCache, + collectionOpCacheResultThrough, + collectionOpToObject, + collectionOpGet, + collectionOpHas, + collectionOpMax, + collectionOpMaxBy, + collectionOpMin, + collectionOpMinBy, + collectionOpWithMutations, + collectionOpWasAltered, + collectionOpAsMutable, + collectionOpAsImmutable, + collectionOpToArray, + collectionOpToStringMapper, + collectionOpInspect, + collectionOpIncludes, + collectionOpFindEntry, + collectionOpFindKey, + collectionOpForEach, + collectionOpToStringDetails, + collectionOpFind, + collectionOpFindLast, + collectionOpFindLastEntry, + collectionOpFindLastKey, + collectionOpSize, + collectionOpRest, + collectionOpEvery, + collectionOpSome, + collectionOpReduce, + collectionOpReduceRight, + collectionOpKeys, + collectionOpKeySeq, + collectionOpIsEmpty, + collectionOpKeyOf, + collectionOpTake, + collectionOpTakeLast, + collectionOpSkip, + collectionOpSkipLast, + collectionOpValues, + collectionOpEntries, + collectionOpEquals, + collectionOpJoin, + collectionOpCount, + collectionOpButLast, + collectionPropertiesCreate, + collectionOpIndexedFindIndex, + collectionOpIndexedIndexOf, + collectionOpIndexedLastIndexOf, + collectionOpIndexedFindLastIndex, + collectionOpIndexedFirst, + collectionOpIndexedGet, + collectionOpIndexedHas, + collectionOpIndexedKeySeq, + collectionOpIndexedLast, +}; diff --git a/src/collection/collectionCastIndexedSeq.js b/src/collection/collectionCastIndexedSeq.js new file mode 100644 index 0000000000..abc73e07d4 --- /dev/null +++ b/src/collection/collectionCastIndexedSeq.js @@ -0,0 +1,60 @@ +import { ITERATE_VALUES } from '../const'; + +import transformToMethods from '../transformToMethods'; + +import { ensureSize } from '../TrieUtils'; + +import { Iterator, iteratorValue } from '../Iterator'; + +import { collectionIndexedSeqPropertiesCreate } from './collectionIndexedSeq.js'; + +const collectionCastIndexedSeqOpIncludes = (cx, value) => { + return cx._iter.includes(value); +}; + +const collectionCastIndexedSeqOpIterate = (cx, fn, reverse) => { + let i = 0; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here + reverse && ensureSize(cx); + return cx._iter.__iterate( + (v) => fn(v, reverse ? cx.size - ++i : i++, cx), + reverse + ); +}; + +const collectionCastIndexedSeqOpIterator = (cx, type, reverse) => { + const iterator = cx._iter.__iterator(ITERATE_VALUES, reverse); + let i = 0; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here + reverse && ensureSize(cx); + return new Iterator(() => { + const step = iterator.next(); + return step.done + ? step + : iteratorValue(type, reverse ? cx.size - ++i : i++, step.value, step); + }); +}; + +const collectionCastIndexedSeqCreate = ((cache) => (iter) => { + const castindexedseq = Object.create( + cache || + (cache = Object.assign( + {}, + collectionIndexedSeqPropertiesCreate(), + transformToMethods({ + name: 'castindexedseqcreate', + includes: collectionCastIndexedSeqOpIncludes, + __iterate: collectionCastIndexedSeqOpIterate, + __iterator: collectionCastIndexedSeqOpIterator, + }) + )) + ); + + castindexedseq._shape = 'castindexedseq'; + castindexedseq._iter = iter; + castindexedseq.size = iter.size; + + return castindexedseq; +})(); + +export { collectionCastIndexedSeqCreate }; diff --git a/src/collection/collectionCastKeyedSeq.js b/src/collection/collectionCastKeyedSeq.js new file mode 100644 index 0000000000..b78c4a87bc --- /dev/null +++ b/src/collection/collectionCastKeyedSeq.js @@ -0,0 +1,60 @@ +import transformToMethods from '../transformToMethods'; + +import { IS_ORDERED_SYMBOL } from '../const'; + +import { collectionKeyedSeqPropertiesCreate } from './collectionKeyedSeq'; + +const collectionCastKeyedSequenceOpToKeyedSeq = (cx) => { + return cx; +}; + +const collectionCastKeyedSequenceOpGet = (cx, key, notSetValue) => { + return cx._iter.get(key, notSetValue); +}; + +const collectionCastKeyedSequenceOpHas = (cx, key) => { + return cx._iter.has(key); +}; + +const collectionCastKeyedSequenceOpValueSeq = (cx) => { + return cx._iter.valueSeq(); +}; + +const collectionCastKeyedSequenceOpIterate = (cx, fn, reverse) => { + return cx._iter.__iterate((v, k) => fn(v, k, cx), reverse); +}; + +const collectionCastKeyedSequenceOpIterator = (cx, type, reverse) => { + return cx._iter.__iterator(type, reverse); +}; + +const collectionCastKeyedSeqPropertiesCreate = ((cache) => () => { + return ( + cache || + (cache = Object.assign( + {}, + collectionKeyedSeqPropertiesCreate(), + transformToMethods({ + [IS_ORDERED_SYMBOL]: true, + toKeyedSeq: collectionCastKeyedSequenceOpToKeyedSeq, + get: collectionCastKeyedSequenceOpGet, + has: collectionCastKeyedSequenceOpHas, + valueSeq: collectionCastKeyedSequenceOpValueSeq, + __iterate: collectionCastKeyedSequenceOpIterate, + __iterator: collectionCastKeyedSequenceOpIterator, + }) + )) + ); +})(); + +const collectionCastKeyedSeqCreate = (iter, useKeys) => { + const ksqh = Object.create(collectionCastKeyedSeqPropertiesCreate()); + + ksqh._iter = iter; + ksqh._useKeys = useKeys; + ksqh.size = iter.size; + + return ksqh; +}; + +export { collectionCastKeyedSeqPropertiesCreate, collectionCastKeyedSeqCreate }; diff --git a/src/collection/collectionCastSetSeq.js b/src/collection/collectionCastSetSeq.js new file mode 100644 index 0000000000..c4f6d3559f --- /dev/null +++ b/src/collection/collectionCastSetSeq.js @@ -0,0 +1,50 @@ +import transformToMethods from '../transformToMethods'; + +import { ITERATE_VALUES } from '../const'; + +import { + Iterator, + iteratorValue, +} from '../Iterator'; + +import { + collectionSeqPropertiesCreate, +} from './collectionSeq'; + +const collectionCastSetSeqOpHas = (cx, key) => { + return cx._iter.includes(key); +}; + +const collectionCastSetSeqOpIterate = (cx, fn, reverse) => { + return cx._iter.__iterate((v) => fn(v, v, cx), reverse); +}; + +const collectionCastSetSeqOpIterator = (cx, type, reverse) => { + const iterator = cx._iter.__iterator(ITERATE_VALUES, reverse); + return new Iterator(() => { + const step = iterator.next(); + return step.done ? step : iteratorValue(type, step.value, step.value, step); + }); +}; + +const collectionCastSetSeqCreate = ((cache) => (iter) => { + const ssq = Object.create( + cache || + (cache = Object.assign( + {}, + collectionSeqPropertiesCreate(), + transformToMethods({ + has: collectionCastSetSeqOpHas, + __iterate: collectionCastSetSeqOpIterate, + __iterator: collectionCastSetSeqOpIterator, + }) + )) + ); + + ssq._iter = iter; + ssq.size = iter.size; + + return ssq; +})(); + +export { collectionCastSetSeqCreate }; diff --git a/src/collection/collectionConcat.js b/src/collection/collectionConcat.js new file mode 100644 index 0000000000..a7fb060928 --- /dev/null +++ b/src/collection/collectionConcat.js @@ -0,0 +1,128 @@ +import transformToMethods from '../transformToMethods'; + +import { + IS_KEYED_SYMBOL, + IS_ORDERED_SYMBOL, + IS_INDEXED_SYMBOL, + ITERATE_ENTRIES, + ITERATE_VALUES, +} from '../const'; + +import { Iterator, iteratorDone } from '../Iterator'; + +import { probeIsKeyed } from '../probe'; + +import { + collectionSeqPropertiesCreate, +} from './collectionSeq'; + +const collectionConcatOpIterateUncached = (cx, fn, reverse) => { + if (cx._wrappedIterables.length === 0) { + return; + } + + if (reverse) { + return cx.cacheResult().__iterate(fn, reverse); + } + + let iterableIndex = 0; + const useKeys = probeIsKeyed(cx); + const iteratorType = useKeys ? ITERATE_ENTRIES : ITERATE_VALUES; + let currentIterator = cx._wrappedIterables[iterableIndex].__iterator( + iteratorType, + reverse + ); + + let keepGoing = true; + let index = 0; + while (keepGoing) { + let next = currentIterator.next(); + while (next.done) { + iterableIndex++; + if (iterableIndex === cx._wrappedIterables.length) { + return index; + } + currentIterator = cx._wrappedIterables[iterableIndex].__iterator( + iteratorType, + reverse + ); + next = currentIterator.next(); + } + const fnResult = useKeys + ? fn(next.value[1], next.value[0], cx) + : fn(next.value, index, cx); + keepGoing = fnResult !== false; + index++; + } + return index; +}; + +const collectionConcatOpIteratorUncached = (cx, type, reverse) => { + if (cx._wrappedIterables.length === 0) { + return new Iterator(iteratorDone); + } + + if (reverse) { + return cx.cacheResult().__iterator(type, reverse); + } + + let iterableIndex = 0; + let currentIterator = cx._wrappedIterables[iterableIndex].__iterator( + type, + reverse + ); + return new Iterator(() => { + let next = currentIterator.next(); + while (next.done) { + iterableIndex++; + if (iterableIndex === cx._wrappedIterables.length) { + return next; + } + currentIterator = cx._wrappedIterables[iterableIndex].__iterator( + type, + reverse + ); + next = currentIterator.next(); + } + return next; + }); +}; + +const collectionConcatCreate = ((cache) => (iterables) => { + const seqconcat = Object.create( + cache || + (cache = Object.assign( + {}, + collectionSeqPropertiesCreate(), + transformToMethods({ + __iteratorUncached: collectionConcatOpIteratorUncached, + __iterateUncached: collectionConcatOpIterateUncached, + }) + )) + ); + + seqconcat._shape = 'seqconcat'; + seqconcat._wrappedIterables = iterables.flatMap((iterable) => { + if (iterable._wrappedIterables) { + return iterable._wrappedIterables; + } + return [iterable]; + }); + seqconcat.size = seqconcat._wrappedIterables.reduce((sum, iterable) => { + if (sum !== undefined) { + const size = iterable.size; + if (size !== undefined) { + return sum + size; + } + } + }, 0); + seqconcat[IS_KEYED_SYMBOL] = seqconcat._wrappedIterables[0][IS_KEYED_SYMBOL]; + seqconcat[IS_INDEXED_SYMBOL] = + seqconcat._wrappedIterables[0][IS_INDEXED_SYMBOL]; + seqconcat[IS_ORDERED_SYMBOL] = + seqconcat._wrappedIterables[0][IS_ORDERED_SYMBOL]; + + return seqconcat; +})(); + +export { collectionConcatCreate }; diff --git a/src/collection/collectionIndexed.js b/src/collection/collectionIndexed.js new file mode 100644 index 0000000000..3872a304f6 --- /dev/null +++ b/src/collection/collectionIndexed.js @@ -0,0 +1,44 @@ +import transformToMethods from '../transformToMethods'; + +import { IS_ORDERED_SYMBOL, IS_INDEXED_SYMBOL } from '../const'; + +import { + collectionOpIndexedFindIndex, + collectionOpIndexedIndexOf, + collectionOpIndexedLastIndexOf, + collectionOpIndexedFindLastIndex, + collectionOpIndexedFirst, + collectionOpIndexedGet, + collectionOpIndexedHas, + collectionOpIndexedLast, + collectionPropertiesCreate, +} from './collection'; + +const collectionIndexedPropertiesCreate = ((cache) => () => { + return ( + cache || + (cache = Object.assign( + {}, + collectionPropertiesCreate(), + transformToMethods({ + [IS_INDEXED_SYMBOL]: true, + [IS_ORDERED_SYMBOL]: true, + findIndex: collectionOpIndexedFindIndex, + indexOf: collectionOpIndexedIndexOf, + lastIndexOf: collectionOpIndexedLastIndexOf, + findLastIndex: collectionOpIndexedFindLastIndex, + first: collectionOpIndexedFirst, + get: collectionOpIndexedGet, + has: collectionOpIndexedHas, + last: collectionOpIndexedLast, + }) + )) + ); +})(); + +const collectionIndexedCreate = () => { + const cxindexed = Object.create(collectionIndexedPropertiesCreate()); + return cxindexed; +}; + +export { collectionIndexedPropertiesCreate, collectionIndexedCreate }; diff --git a/src/collection/collectionIndexedSeq.js b/src/collection/collectionIndexedSeq.js new file mode 100644 index 0000000000..698e0a46ed --- /dev/null +++ b/src/collection/collectionIndexedSeq.js @@ -0,0 +1,43 @@ +import transformToMethods from '../transformToMethods'; + +import { IS_SEQ_SYMBOL, IS_INDEXED_SYMBOL } from '../const'; + +import { collectionIndexedPropertiesCreate } from './collectionIndexed'; + +import { + collectionSeqOpCacheResult, + collectionSeqOpIterateUncached, + collectionSeqOpIteratorUncached, + collectionSeqOpIterate, + collectionSeqOpIterator, +} from './collectionSeq'; + +const collectionIndexedSeqPropertiesCreate = ((cache) => () => { + return ( + cache || + (cache = Object.assign( + {}, + collectionIndexedPropertiesCreate(), + transformToMethods({ + [IS_SEQ_SYMBOL]: true, + [IS_INDEXED_SYMBOL]: true, + toString: (cx) => cx.__toString('Seq [', ']'), + toIndexedSeq: (cx) => cx, + toSeq: (cx) => cx, + cacheResult: collectionSeqOpCacheResult, + __iterateUncached: collectionSeqOpIterateUncached, + __iteratorUncached: collectionSeqOpIteratorUncached, + __iterate: collectionSeqOpIterate, + __iterator: collectionSeqOpIterator, + }) + )) + ); +})(); + +const collectionIndexedSeqCreate = () => { + const setseqidx = Object.create(collectionIndexedSeqPropertiesCreate()); + setseqidx._shape = 'indexedseq'; + return setseqidx; +}; + +export { collectionIndexedSeqCreate, collectionIndexedSeqPropertiesCreate }; diff --git a/src/collection/collectionIndexedSeqFromCollection.js b/src/collection/collectionIndexedSeqFromCollection.js new file mode 100644 index 0000000000..3846b793c6 --- /dev/null +++ b/src/collection/collectionIndexedSeqFromCollection.js @@ -0,0 +1,14 @@ +import { collectionIndexedSeqPropertiesCreate } from './collectionIndexedSeq'; + +const collectionIndexedSeqFromCollectionCreate = (cx) => { + const idxseqfromcollection = Object.create( + collectionIndexedSeqPropertiesCreate() + ); + + idxseqfromcollection._collection = cx; + idxseqfromcollection.size = cx.length || cx.size; + + return idxseqfromcollection; +}; + +export { collectionIndexedSeqFromCollectionCreate }; diff --git a/src/collection/collectionKeyed.js b/src/collection/collectionKeyed.js new file mode 100644 index 0000000000..180e8a730c --- /dev/null +++ b/src/collection/collectionKeyed.js @@ -0,0 +1,36 @@ +import transformToMethods from '../transformToMethods'; + +import { + IS_KEYED_SYMBOL, +} from '../const'; + +import { collectionPropertiesCreate } from './collection'; + +const collectionSetSeqKeyedOpToString = (cx) => { + return cx.__toString('Seq {', '}'); +}; + +const collectionKeyedPropertiesCreate = ((cache) => () => { + return ( + cache || + (cache = Object.assign( + {}, + collectionPropertiesCreate(), + transformToMethods({ + [IS_KEYED_SYMBOL]: true, + toString: collectionSetSeqKeyedOpToString, + valueSeq: (cx) => { + return cx.toIndexedSeq(); + } + }) + )) + ); +})(); + +const collectionKeyedCreate = () => { + const setseqkeyed = Object.create(collectionKeyedPropertiesCreate()); + + return setseqkeyed; +}; + +export { collectionKeyedPropertiesCreate, collectionKeyedCreate }; diff --git a/src/collection/collectionKeyedSeq.js b/src/collection/collectionKeyedSeq.js new file mode 100644 index 0000000000..61194d92a8 --- /dev/null +++ b/src/collection/collectionKeyedSeq.js @@ -0,0 +1,41 @@ +import transformToMethods from '../transformToMethods'; + +import { IS_SEQ_SYMBOL, IS_KEYED_SYMBOL } from '../const'; + +import { collectionKeyedPropertiesCreate } from './collectionKeyed'; + +import { + collectionSeqOpCacheResult, + collectionSeqOpIterateUncached, + collectionSeqOpIteratorUncached, + collectionSeqOpIterate, + collectionSeqOpIterator, +} from './collectionSeq'; + +const collectionKeyedSeqPropertiesCreate = ((cache) => () => { + return ( + cache || + (cache = Object.assign( + {}, + collectionKeyedPropertiesCreate(), + transformToMethods({ + [IS_SEQ_SYMBOL]: true, + [IS_KEYED_SYMBOL]: true, + toSeq: (cx) => cx, + cacheResult: collectionSeqOpCacheResult, + __iterateUncached: collectionSeqOpIterateUncached, + __iteratorUncached: collectionSeqOpIteratorUncached, + __iterate: collectionSeqOpIterate, + __iterator: collectionSeqOpIterator, + }) + )) + ); +})(); + +const collectionKeyedSeqCreate = () => { + const setseqidx = Object.create(collectionKeyedSeqPropertiesCreate()); + setseqidx._shape = 'keyedseq'; + return setseqidx; +}; + +export { collectionKeyedSeqCreate, collectionKeyedSeqPropertiesCreate }; diff --git a/src/collection/collectionKeyedSeqFromEntries.js b/src/collection/collectionKeyedSeqFromEntries.js new file mode 100644 index 0000000000..b17d0635d1 --- /dev/null +++ b/src/collection/collectionKeyedSeqFromEntries.js @@ -0,0 +1,87 @@ +import transformToMethods from '../transformToMethods'; + +import { Iterator, iteratorValue } from '../Iterator'; + +import { IS_ORDERED_SYMBOL, ITERATE_VALUES } from '../const'; + +import { probeIsCollection } from '../probe'; + +import { collectionKeyedSeqPropertiesCreate } from './collectionKeyedSeq'; + +const collectionKeyedSeqFromEntriesOpEntrySeq = (cx) => { + return cx._iter.toSeq(); +}; + +const validateEntry = (entry) => { + if (entry !== Object(entry)) { + throw new TypeError('Expected [K, V] tuple: ' + entry); + } +}; + +const collectionKeyedSeqFromEntriesOpIterate = (cx, fn, reverse) => { + return cx._iter.__iterate((entry) => { + // Check if entry exists first so array access doesn't throw for holes + // in the parent iteration. + if (entry) { + validateEntry(entry); + const indexedCollection = probeIsCollection(entry); + return fn( + indexedCollection ? entry.get(1) : entry[1], + indexedCollection ? entry.get(0) : entry[0], + cx + ); + } + }, reverse); +}; + +const collectionKeyedSeqFromEntriesOpIterator = (cx, type, reverse) => { + const iterator = cx._iter.__iterator(ITERATE_VALUES, reverse); + return new Iterator(() => { + while (true) { + const step = iterator.next(); + if (step.done) { + return step; + } + const entry = step.value; + // Check if entry exists first so array access doesn't throw for holes + // in the parent iteration. + if (entry) { + validateEntry(entry); + const indexedCollection = probeIsCollection(entry); + return iteratorValue( + type, + indexedCollection ? entry.get(0) : entry[0], + indexedCollection ? entry.get(1) : entry[1], + step + ); + } + } + }); +}; + +const collectionKeyedSeqFromEntriesCreate = ((cache) => (entries) => { + const setseqkeyxfromentries = Object.create( + cache || + (cache = Object.assign( + {}, + collectionKeyedSeqPropertiesCreate(), + transformToMethods({ + [IS_ORDERED_SYMBOL]: true, + entrySeq: collectionKeyedSeqFromEntriesOpEntrySeq, + __iterate: collectionKeyedSeqFromEntriesOpIterate, + __iterator: collectionKeyedSeqFromEntriesOpIterator, + }) + )) + ); + + setseqkeyxfromentries._iter = entries; + setseqkeyxfromentries.size = entries.size; + + return setseqkeyxfromentries; +})(); + +export { + collectionKeyedSeqFromEntriesOpEntrySeq, + collectionKeyedSeqFromEntriesOpIterate, + collectionKeyedSeqFromEntriesCreate, +}; diff --git a/src/collection/collectionRecord.js b/src/collection/collectionRecord.js new file mode 100644 index 0000000000..15688d7cd6 --- /dev/null +++ b/src/collection/collectionRecord.js @@ -0,0 +1,179 @@ +import transformToMethods from '../transformToMethods'; + +import { probeIsImmutable, probeIsRecord } from '../probe'; + +import { utilQuoteString } from '../util'; + +import { SeqKeyedFromValue } from '../Seq'; + +import { + ITERATOR_SYMBOL, + IS_COLLECTION_SYMBOL, + IS_RECORD_SYMBOL, + DELETE, +} from '../const'; + +import { + collectionPropertiesCreate, + collectionOrAnyOpGetIn, + collectionOrAnyOpHasIn, +} from './collection'; + +const collectionRecordAssertValidDefaultValues = (defaultValues) => { + if (probeIsRecord(defaultValues)) { + throw new Error( + 'Can not call `Record` with an immutable Record as default values. Use a plain javascript object instead.' + ); + } + + if (probeIsImmutable(defaultValues)) { + throw new Error( + 'Can not call `Record` with an immutable Collection as default values. Use a plain javascript object instead.' + ); + } + + if (defaultValues === null || typeof defaultValues !== 'object') { + throw new Error( + 'Can not call `Record` with a non-object as default values. Use a plain javascript object instead.' + ); + } +}; + +const recordOpCreateLike = (likeRecord, values, ownerID) => { + const record = Object.create(Object.getPrototypeOf(likeRecord)); + record._values = values; + record.__ownerID = ownerID; + return record; +}; + +const recordOpGetName = (record) => { + return record.constructor.displayName || record.constructor.name || 'Record'; +}; + +const recordOpEnsureOwner = (cx, ownerID) => { + if (ownerID === cx.__ownerID) { + return cx; + } + const newValues = cx._values.__ensureOwner(ownerID); + if (!ownerID) { + cx.__ownerID = ownerID; + cx._values = newValues; + return cx; + } + return recordOpCreateLike(cx, newValues, ownerID); +}; + +const recordOpIterator = (cx, type, reverse) => { + return recordOpToSeq(cx).__iterator(type, reverse); +}; + +const recordOpIterate = (cx, fn, reverse) => { + return recordOpToSeq(cx).__iterate(fn, reverse); +}; + +const recordOpHas = (cx, k) => { + return cx._indices.hasOwnProperty(k); +}; + +const recordOpSet = (cx, k, v) => { + if (cx.has(k)) { + const newValues = cx._values.set( + cx._indices[k], + v === cx._defaultValues[k] ? undefined : v + ); + if (newValues !== cx._values && !cx.__ownerID) { + return recordOpCreateLike(cx, newValues); + } + } + return cx; +}; + +const recordOpRemove = (cx, k) => { + return cx.set(k); +}; + +const recordOpClear = (cx) => { + const newValues = cx._values.clear().setSize(cx._keys.length); + + return cx.__ownerID ? cx : recordOpCreateLike(cx, newValues); +}; + +const recordOpWasAltered = (cx) => { + return cx._values.wasAltered(); +}; + +const recordOpGet = (cx, k, notSetValue) => { + if (!cx.has(k)) { + return notSetValue; + } + const index = cx._indices[k]; + const value = cx._values.get(index); + + return value === undefined ? cx._defaultValues[k] : value; +}; + +const recordOpToString = (cx) => { + let str = recordOpGetName(cx) + ' { '; + const keys = cx._keys; + let k; + for (let i = 0, l = keys.length; i !== l; i++) { + k = keys[i]; + str += (i ? ', ' : '') + k + ': ' + utilQuoteString(cx.get(k)); + } + return str + ' }'; +}; + +const recordOpEquals = (cx, other) => { + return ( + cx === other || + (probeIsRecord(other) && recordOpToSeq(cx).equals(recordOpToSeq(other))) + ); +}; + +const recordOpHashCode = (cx) => { + return recordOpToSeq(cx).hashCode(); +}; + +const recordOpToSeq = (record) => { + return SeqKeyedFromValue(record._keys.map((k) => [k, record.get(k)])); +}; + +const collectionRecordPropertiesCreate = ((cache) => () => { + cache = + cache || + (cache = Object.assign( + {}, + collectionPropertiesCreate(), + transformToMethods({ + [IS_COLLECTION_SYMBOL]: false, + [IS_RECORD_SYMBOL]: true, + toString: recordOpToString, + inspect: recordOpToString, + toSource: recordOpToString, + equals: recordOpEquals, + hashCode: recordOpHashCode, + has: recordOpHas, + get: recordOpGet, + set: recordOpSet, + remove: recordOpRemove, + [DELETE]: recordOpRemove, + clear: recordOpClear, + wasAltered: recordOpWasAltered, + toSeq: recordOpToSeq, + getIn: collectionOrAnyOpGetIn, + hasIn: collectionOrAnyOpHasIn, + __iterator: recordOpIterator, + __iterate: recordOpIterate, + __ensureOwner: recordOpEnsureOwner, + }) + )); + + cache[ITERATOR_SYMBOL] = cache.entries; + + return cache; +})(); + +export { + collectionRecordAssertValidDefaultValues, + collectionRecordPropertiesCreate, +}; diff --git a/src/collection/collectionSeq.js b/src/collection/collectionSeq.js new file mode 100644 index 0000000000..378626a65d --- /dev/null +++ b/src/collection/collectionSeq.js @@ -0,0 +1,125 @@ +import transformToMethods from '../transformToMethods'; + +import { IS_SEQ_SYMBOL } from '../const'; + +import { collectionPropertiesCreate } from './collection'; + +import { probeIsIterator } from '../probe'; + +import { + Iterator, + getIterator, + iteratorValue, + iteratorDone, +} from '../Iterator'; + +const collectionSeqOpIterateUncached = (cx, fn, reverse) => { + if (reverse) { + return cx.cacheResult().__iterate(fn, reverse); + } + const collection = cx._collection; + const iterator = getIterator(collection); + let iterations = 0; + if (probeIsIterator(iterator)) { + let step; + while (!(step = iterator.next()).done) { + if (fn(step.value, iterations++, cx) === false) { + break; + } + } + } + return iterations; +}; + +const collectionSeqOpIteratorUncached = (cx, type, reverse) => { + if (reverse) { + return cx.cacheResult().__iterator(type, reverse); + } + const collection = cx._collection; + const iterator = getIterator(collection); + if (!probeIsIterator(iterator)) { + return new Iterator(iteratorDone); + } + let iterations = 0; + return new Iterator(() => { + const step = iterator.next(); + return step.done ? step : iteratorValue(type, iterations++, step.value); + }); +}; + +const collectionSeqOpIterate = (seq, fn, reverse) => { + const cache = seq._cache; + if (cache) { + const size = cache.length; + let i = 0; + while (i !== size) { + const entry = cache[reverse ? size - ++i : i++]; + if (fn(entry[1], entry[0], seq) === false) { + break; + } + } + return i; + } + return seq.__iterateUncached(fn, reverse); +}; + +const collectionSeqOpIterator = (seq, type, reverse) => { + const cache = seq._cache; + if (cache) { + const size = cache.length; + let i = 0; + return new Iterator(() => { + if (i === size) { + return iteratorDone(); + } + const entry = cache[reverse ? size - ++i : i++]; + return iteratorValue(type, entry[0], entry[1]); + }); + } + return seq.__iteratorUncached(type, reverse); +}; + +const collectionSeqOpCacheResult = (seq) => { + if (!seq._cache && seq.__iterateUncached) { + seq._cache = seq.entrySeq().toArray(); + seq.size = seq._cache.length; + } + return seq; +}; + +const collectionSeqPropertiesCreate = ((cache) => () => { + return ( + cache || + (cache = Object.assign( + {}, + collectionPropertiesCreate(), + transformToMethods({ + [IS_SEQ_SYMBOL]: true, + toSeq: (cx) => cx, + cacheResult: collectionSeqOpCacheResult, + __iterateUncached: collectionSeqOpIterateUncached, + __iteratorUncached: collectionSeqOpIteratorUncached, + __iterate: collectionSeqOpIterate, + __iterator: collectionSeqOpIterator, + }) + )) + ); +})(); + +const collectionSeqCreate = () => { + const seq = Object.create(collectionSeqPropertiesCreate()); + + seq._shape = 'seq'; + + return seq; +}; + +export { + collectionSeqOpCacheResult, + collectionSeqOpIterate, + collectionSeqOpIterator, + collectionSeqOpIterateUncached, + collectionSeqOpIteratorUncached, + collectionSeqCreate, + collectionSeqPropertiesCreate, +}; diff --git a/src/collection/collectionSet.js b/src/collection/collectionSet.js new file mode 100644 index 0000000000..c7d1881d6b --- /dev/null +++ b/src/collection/collectionSet.js @@ -0,0 +1,11 @@ +import { collectionPropertiesCreate } from './collection'; + +const collectionSetCreate = () => { + const ssqh = Object.create(collectionPropertiesCreate()); + + ssqh.__shape = 'collectionSet'; + + return ssqh; +}; + +export { collectionSetCreate }; diff --git a/src/collection/collectionX.js b/src/collection/collectionX.js new file mode 100644 index 0000000000..5f3bf78e5e --- /dev/null +++ b/src/collection/collectionX.js @@ -0,0 +1,804 @@ +import { resolveBegin } from '../TrieUtils'; + +import { Map, mapCreateEmpty } from '../Map'; + +import { MapOrdered } from '../MapOrdered'; + +import { + utilCopyShallow, + utilHasOwnProperty, + utilArrCopy, + utilQuoteString, +} from '../util'; + +import { NOT_SET } from '../const'; + +import { + probeIsMergeable, + probeIsIndexed, + probeIsImmutable, + probeIsDataStructure, + probeCoerceKeyPath, + probeIsKeyed, + probeIsSeq, +} from '../probe'; + +import { + factoryGroupBy, + factoryInterpose, + factoryCountBy, + factoryPartition, + factoryFlatMap, + factorySort, + factoryFilter, +} from '../factory/factory'; + +import { factoryMap } from '../factory/factoryMap'; +import { factoryFlip } from '../factory/factoryFlip'; +import { factorySlice } from '../factory/factorySlice'; +import { factoryConcat } from '../factory/factoryConcat'; +import { factoryFlatten } from '../factory/factoryFlatten'; +import { factoryReverse } from '../factory/factoryReverse'; +import { factoryZipWith } from '../factory/factoryZipWith'; + +import { collectionSeqCreate } from './collectionSeq.js'; + +import { collectionIndexedSeqPropertiesCreate } from './collectionIndexedSeq.js'; +import { collectionKeyedSeqPropertiesCreate } from './collectionKeyedSeq.js'; + +import { + collectionOrAnyOpGet, + collectionOrAnyOpSet, + collectionOrAnyOpRemove, +} from './collection'; + +import { + SeqKeyed, + SeqKeyedFromValue, + SeqIndexed, + SeqIndexedFromValue, + SeqSet, + SeqWhenNotCollection, + SeqSetWhenNotAssociative, + SeqKeyedWhenNotKeyed, + SeqIndexedWhenNotIndexed, +} from '../Seq'; + +import { SeqArray } from '../SeqArray'; + +const collectionXReify = (cx, seq) => { + return cx === seq + ? cx + : probeIsSeq(cx) + ? seq + : cx.create + ? cx.create(seq) + : cx.constructor(seq); +}; + +const collectionXProbeCreator = (cx) => + probeIsKeyed(cx) + ? SeqKeyedWhenNotKeyed + : probeIsIndexed(cx) + ? SeqIndexedWhenNotIndexed + : SeqSetWhenNotAssociative; + +const collectionXMakeSequence = (cx) => { + return Object.create( + probeIsKeyed(cx) + ? collectionKeyedSeqPropertiesCreate() + : probeIsIndexed(cx) + ? collectionIndexedSeqPropertiesCreate() + : collectionSeqCreate() + ); +}; + +const collectionXCastKeyedSequenceOpReverse = (cx) => { + const reversedSequence = factoryReverse(cx, collectionXMakeSequence, true); + if (!cx._useKeys) { + reversedSequence.valueSeq = () => cx._iter.toSeq().reverse(); + } + return reversedSequence; +}; + +const collectionXCastKeyedSequenceOpMap = (cx, mapper, context) => { + const mappedSequence = factoryMap( + cx, + collectionXMakeSequence, + mapper, + context + ); + + if (!cx._useKeys) { + mappedSequence.valueSeq = () => cx._iter.toSeq().map(mapper, context); + } + return mappedSequence; +}; + +const collectionXOpValueSeq = (cx) => { + return cx.toIndexedSeq(); +}; + +const collectionXIndexedOpInterpose = (cx, separator) => { + return collectionXReify( + cx, + factoryInterpose(cx, collectionXMakeSequence, separator) + ); +}; + +const collectionXIndexedOpInterleave = (cx, collections) => { + const collectionsJoined = [cx].concat(utilArrCopy(collections)); + // const zipper = SeqIndexed.of + const zipper = SeqIndexed.of; + const zipped = factoryZipWith( + cx.toSeq(), + collectionXMakeSequence, + SeqWhenNotCollection, + SeqArray, + zipper, + collectionsJoined + ); + const interleaved = zipped.flatten(true); + if (zipped.size) { + interleaved.size = zipped.size * collectionsJoined.length; + } + return collectionXReify(cx, interleaved); +}; + +const collectionXOpToStringDetails = (cx, head, tail) => { + if (cx.size === 0) { + return head + tail; + } + return ( + head + + ' ' + + cx.toSeq().map(cx.__toStringMapper).join(', ') + + ' ' + + tail + ); +}; + +const collectionXOpIsSubset = (cx, iter) => { + iter = + typeof iter.includes === 'function' ? iter : SeqWhenNotCollection(iter); + return cx.every((value) => iter.includes(value)); +}; + +const collectionXOpIsSuperset = (cx, iter) => { + iter = + typeof iter.isSubset === 'function' ? iter : SeqWhenNotCollection(iter); + return iter.isSubset(cx); +}; + +const collectionXOpFlip = (cx) => { + return factoryFlip(cx, collectionXMakeSequence(cx)); +}; + +const collectionXOpMap = (cx, mapper, context) => { + return collectionXReify( + cx, + factoryMap(cx, collectionXMakeSequence, mapper, context) + ); +}; + +const collectionXOpReverse = (cx) => { + return collectionXReify( + cx, + factoryReverse(cx, collectionXMakeSequence, true) + ); +}; + +const collectionXIndexedOpReverse = (cx) => { + return collectionXReify( + cx, + factoryReverse(cx, collectionXMakeSequence, false) + ); +}; + +const collectionXOpSlice = (cx, begin, end) => { + return collectionXReify( + cx, + factorySlice(cx, collectionXMakeSequence, begin, end, true) + ); +}; + +const collectionXOpPartition = (cx, predicate, context) => { + return factoryPartition( + cx, + collectionXProbeCreator, + collectionXReify, + predicate, + context + ); +}; + +const collectionXIndexedOpSplice = (cx, index, removeNum, args) => { + const numArgs = + typeof index === 'undefined' + ? 0 + : args.length + ? 3 + : typeof removeNum === 'undefined' + ? 1 + : 2; + removeNum = Math.max(removeNum || 0, 0); + if (numArgs === 0 || (numArgs === 2 && !removeNum)) { + return cx; + } + // If index is negative, it should resolve relative to the size of the + // collection. However size may be expensive to compute if not cached, so + // only call count() if the number is in fact negative. + index = resolveBegin(index, index < 0 ? cx.count() : cx.size); + const spliced = cx.slice(0, index); + + return collectionXReify( + cx, + numArgs === 1 ? spliced : spliced.concat(args, cx.slice(index + removeNum)) + ); +}; + +const collectionXIndexedOpSlice = (cx, begin, end) => { + return collectionXReify( + cx, + factorySlice(cx, collectionXMakeSequence, begin, end, false) + ); +}; + +const collectionXOpConcat = (cx, values) => { + return collectionXReify( + cx, + factoryConcat(cx, SeqKeyed, SeqKeyedFromValue, SeqIndexedFromValue, values) + ); +}; + +const collectionXOpFlatMap = (cx, mapper, context) => { + return collectionXReify( + cx, + factoryFlatMap(cx, collectionXProbeCreator, mapper, context) + ); +}; + +const collectionXOpFlatten = (cx, depth) => { + return collectionXReify( + cx, + factoryFlatten(cx, collectionXMakeSequence, depth, false) + ); +}; + +const collectionXOpFilter = (cx, predicate, context) => { + return collectionXReify( + cx, + factoryFilter(cx, collectionXMakeSequence, predicate, context, true) + ); +}; + +function not(predicate) { + return function () { + return !predicate.apply(this, arguments); + }; +} + +const collectionXOpFilterNot = (cx, predicate, context) => { + return cx.filter(not(predicate), context); +}; + +const collectionXIndexedOpFilter = (cx, predicate, context) => { + return collectionXReify( + cx, + factoryFilter(cx, collectionXMakeSequence, predicate, context, false) + ); +}; + +const collectionXOpTake = (cx, amount) => { + return cx.slice(0, Math.max(0, amount)); +}; + +const collectionXOpTakeLast = (cx, amount) => { + return cx.slice(-Math.max(0, amount)); +}; + +const collectionXOpEntrySeq = (cx) => { + if (cx._cache) { + // We cache as an entries array, so we can just return the cache! + // + return SeqArray(cx._cache); + } + function entryMapper(v, k) { + return [k, v]; + } + + const entriesSequence = cx.toSeq().map(entryMapper).toIndexedSeq(); + entriesSequence.fromEntrySeq = () => cx.toSeq(); + return entriesSequence; +}; + +const collectionXOpMergeWithSources = ( + collection, + sources, + merger +) => { + if (!probeIsDataStructure(collection)) { + throw new TypeError( + 'Cannot merge into non-data-structure value: ' + collection + ); + } + if (probeIsImmutable(collection)) { + return typeof merger === 'function' && collection.mergeWith + ? collection.mergeWith(merger, ...sources) + : collection.merge + ? collection.merge(...sources) + : collection.concat(...sources); + } + const isArray = Array.isArray(collection); + let merged = collection; + + const mergeItem = isArray + ? (value) => { + // Copy on write + if (merged === collection) { + merged = utilCopyShallow(merged); + } + merged.push(value); + } + : (value, key) => { + const hasVal = utilHasOwnProperty.call(merged, key); + const nextVal = + hasVal && merger ? merger(merged[key], value, key) : value; + if (!hasVal || nextVal !== merged[key]) { + // Copy on write + if (merged === collection) { + merged = utilCopyShallow(merged); + } + merged[key] = nextVal; + } + }; + const Collection = isArray ? SeqIndexedWhenNotIndexed : SeqKeyedWhenNotKeyed; + for (let i = 0; i < sources.length; i++) { + Collection(sources[i]).forEach(mergeItem); + } + return merged; +}; + +/** + * It's unclear what the desired behavior is for merging two collections that + * fall into separate categories between keyed, indexed, or set-like, so we only + * consider them mergeable if they fall into the same category. + */ +const collectionXOpDeepMergerWith = (merger, collectionCreate) => { + function deepMerger(oldValue, newValue, key) { + return probeIsDataStructure(oldValue) && + probeIsDataStructure(newValue) && + probeIsMergeable(oldValue, newValue) + ? collectionXOpMergeWithSources( + oldValue, + [newValue], + deepMerger, + collectionCreate + ) + : merger + ? merger(oldValue, newValue, key) + : newValue; + } + return deepMerger; +}; + +const collectionXOpMergeDeepWith = (cx, merger, iters) => { + return collectionXOpMergeDeepWithSources(cx, iters, merger); +}; + +const collectionXOpMergeDeepWithSources = (cx, sources, merger) => { + return collectionXOpMergeWithSources( + cx, + sources, + collectionXOpDeepMergerWith(merger, cx), + cx // recursive calls maintain this value to use definitions from it + ); +}; + +const collectionXOpMergeDeep = (cx, iters) => { + return collectionXOpMergeDeepWithSources(cx, iters); +}; + +const collectionXOpMergeDeepIn = (cx, keyPath, iters) => { + return collectionXOrAnyOpUpdateIn(cx, keyPath, mapCreateEmpty(), (cx) => { + return collectionXOpMergeDeepWithSources(cx, iters); + }); +}; + +const collectionXOpMergeWith = (cx, merger, iters) => { + return collectionXOpMergeIntoKeyedWith( + cx, + iters, + merger, + SeqKeyedWhenNotKeyed + ); +}; + +const collectionXOpMerge = (cx, iters) => { + return collectionXOpMergeIntoKeyedWith( + cx, + iters, + undefined, + SeqKeyedWhenNotKeyed + ); +}; + +const collectionXOpMergeIn = (cx, keyPath, iters) => { + return collectionXOrAnyOpUpdateIn(cx, keyPath, mapCreateEmpty(), (m) => + collectionXOpMergeWithSources(m, iters) + ); +}; + +const collectionXKeyedOpMapEntries = (cx, mapper, context) => { + let iterations = 0; + return collectionXReify( + cx, + cx.toSeq() + .map((v, k) => mapper.call(context, [k, v], iterations++, cx)) + .fromEntrySeq() + ); +}; + +const collectionXKeyedOpFlip = (cx) => { + return collectionXReify(cx, factoryFlip(cx, collectionXMakeSequence(cx))); +}; + +const collectionXOpGroupBy = (cx, grouper, context) => { + return factoryGroupBy( + cx, + Map, + MapOrdered, + collectionXReify, + collectionXProbeCreator, + grouper, + context + ); +}; + +const collectionXKeyedOpMapKeys = (collection, mapper, context) => { + return collectionXReify( + collection, + collection + .toSeq() + .flip() + .map((k, v) => mapper.call(context, k, v, collection)) + .flip() + ); +}; + +const collectionXOpLastKeyOf = (cx, searchValue) => { + return cx.toKeyedSeq().reverse().keyOf(searchValue); +}; + +const collectionXOpSortBy = (cx, mapper, comparator) => { + return collectionXReify( + cx, + factorySort(cx, SeqKeyed, SeqIndexed, SeqSet, comparator, mapper) + ); +}; + +const collectionXOpSort = (cx, comparator) => { + return collectionXReify( + cx, + factorySort(cx, SeqKeyed, SeqIndexed, SeqSet, comparator) + ); +}; + +function defaultZipper(...args) { + return utilArrCopy(args); +} + +const collectionXIndexedOpZip = (cx, collections) => { + collections = [cx].concat(utilArrCopy(collections)); + return collectionXReify( + cx, + factoryZipWith( + cx, + collectionXMakeSequence, + SeqWhenNotCollection, + SeqArray, + defaultZipper, + collections + ) + ); +}; + +const collectionXIndexedOpZipAll = (cx, collections) => { + collections = [cx].concat(utilArrCopy(collections)); + return collectionXReify( + cx, + factoryZipWith( + cx, + collectionXMakeSequence, + SeqWhenNotCollection, + SeqArray, + defaultZipper, + collections, + true + ) + ); +}; + +const collectionXIndexedOpZipWith = (cx, zipper, collections) => { + collections = [cx].concat(utilArrCopy(collections)); + + return collectionXReify( + cx, + factoryZipWith( + cx, + collectionXMakeSequence, + SeqWhenNotCollection, + SeqArray, + zipper, + collections + ) + ); +}; + +const collectionXOpCountBy = (cx, grouper, context) => { + return factoryCountBy(cx, grouper, context, Map); +}; + +const collectionXOpMergeIntoKeyedWith = ( + collection, + collections, + merger, + keyedCollectionCreate +) => { + const iters = []; + for (let ii = 0; ii < collections.length; ii++) { + const collection = keyedCollectionCreate(collections[ii]); + if (collection.size !== 0) { + iters.push(collection); + } + } + if (iters.length === 0) { + return collection; + } + if ( + collection.toSeq().size === 0 && + !collection.__ownerID && + iters.length === 1 + ) { + return collection.create(iters[0]); + } + return collection.withMutations((collection) => { + const mergeIntoCollection = merger + ? (value, key) => { + collectionXOrAnyOpUpdate(collection, key, NOT_SET, (oldVal) => + oldVal === NOT_SET ? value : merger(oldVal, value, key) + ); + } + : (value, key) => { + collection.set(key, value); + }; + for (let ii = 0; ii < iters.length; ii++) { + iters[ii].forEach(mergeIntoCollection); + } + }); +}; + +/** + * Returns a copy of the collection with the value at key path set to the + * result of providing the existing value to the updating function. + * + * A functional alternative to `collection.updateIn(keypath)` which will also + * work with plain Objects and Arrays. + * + * + * ```js + * import { updateIn } from 'immutable' + * + * const original = { x: { y: { z: 123 }}} + * updateIn(original, ['x', 'y', 'z'], val => val * 6) // { x: { y: { z: 738 }}} + * console.log(original) // { x: { y: { z: 123 }}} + * ``` + */ +const collectionXOrAnyOpUpdateInDeeply = ( + inImmutable, + existing, + keyPath, + i, + notSetValue, + updater, + mapEmptyCreate +) => { + const wasNotSet = existing === NOT_SET; + if (i === keyPath.length) { + const existingValue = wasNotSet ? notSetValue : existing; + // @ts-expect-error mixed type with optional value + const newValue = updater(existingValue); + // @ts-expect-error mixed type + return newValue === existingValue ? existing : newValue; + } + if (!wasNotSet && !probeIsDataStructure(existing)) { + throw new TypeError( + 'Cannot update within non-data-structure value in path [' + + Array.from(keyPath).slice(0, i).map(utilQuoteString) + + ']: ' + + existing + ); + } + const key = keyPath[i]; + + if (typeof key === 'undefined') { + throw new TypeError( + 'Index can not be undefined in updateIn(). This should not happen' + ); + } + + const nextExisting = wasNotSet + ? NOT_SET + : collectionOrAnyOpGet(existing, key, NOT_SET); + const nextUpdated = collectionXOrAnyOpUpdateInDeeply( + nextExisting === NOT_SET ? inImmutable : probeIsImmutable(nextExisting), + // @ts-expect-error mixed type + nextExisting, + keyPath, + i + 1, + notSetValue, + updater, + mapEmptyCreate + ); + return nextUpdated === nextExisting + ? existing + : nextUpdated === NOT_SET + ? collectionOrAnyOpRemove(existing, key) + : collectionOrAnyOpSet( + wasNotSet ? (inImmutable ? mapEmptyCreate() : {}) : existing, + key, + nextUpdated + ); +}; + +const collectionXOrAnyOpUpdateIn = ( + cx, + keyPath, + notSetValue, + updater +) => { + if (!updater) { + // handle the fact that `notSetValue` is optional here, in that case `updater` is the updater function + // @ts-expect-error updater is a function here + updater = notSetValue; + notSetValue = undefined; + } + const updatedValue = collectionXOrAnyOpUpdateInDeeply( + probeIsImmutable(cx), + // @ts-expect-error type issues with Record and mixed types + cx, + probeCoerceKeyPath(keyPath), + 0, + notSetValue, + updater, + mapCreateEmpty + ); + // @ts-expect-error mixed return type + return updatedValue === NOT_SET ? notSetValue : updatedValue; +}; + +/** + * Returns a copy of the collection with the value at the key path removed. + * + * A functional alternative to `collection.removeIn(keypath)` which will also + * work with plain Objects and Arrays. + * + * + * ```js + * import { removeIn } from 'immutable'; + * + * const original = { x: { y: { z: 123 }}} + * removeIn(original, ['x', 'y', 'z']) // { x: { y: {}}} + * console.log(original) // { x: { y: { z: 123 }}} + * ``` + */ +const collectionXOrAnyOpRemoveIn = (cx, keyPath) => { + return collectionXOrAnyOpUpdateIn(cx, keyPath, () => NOT_SET); +}; + +/** + * Returns a copy of the collection with the value at key set to the result of + * providing the existing value to the updating function. + * + * A functional alternative to `collection.update(key, fn)` which will also + * work with plain Objects and Arrays as an alternative for + * `collectionCopy[key] = fn(collection[key])`. + * + * + * ```js + * import { update } from 'immutable'; + * + * const originalArray = [ 'dog', 'frog', 'cat' ] + * update(originalArray, 1, val => val.toUpperCase()) // [ 'dog', 'FROG', 'cat' ] + * console.log(originalArray) // [ 'dog', 'frog', 'cat' ] + * const originalObject = { x: 123, y: 456 } + * update(originalObject, 'x', val => val * 6) // { x: 738, y: 456 } + * console.log(originalObject) // { x: 123, y: 456 } + * ``` + */ +const collectionXOrAnyOpUpdate = (cx, key, notSetValue, updater) => { + return collectionXOrAnyOpUpdateIn( + // @ts-expect-error Index signature for type string is missing in type V[] + cx, + [key], + notSetValue, + updater, + null, + null + ); +}; + +/** + * Returns a copy of the collection with the value at the key path set to the + * provided value. + * + * A functional alternative to `collection.setIn(keypath)` which will also + * work with plain Objects and Arrays. + * + * + * ```js + * import { setIn } from 'immutable'; + * + * const original = { x: { y: { z: 123 }}} + * setIn(original, ['x', 'y', 'z'], 456) // { x: { y: { z: 456 }}} + * console.log(original) // { x: { y: { z: 123 }}} + * ``` + */ +const collectionXOrAnyOpSetIn = (cx, keyPath, value) => { + return collectionXOrAnyOpUpdateIn(cx, keyPath, NOT_SET, () => value); +}; + +export { + collectionXReify, + collectionXOrAnyOpUpdateInDeeply, + collectionXOrAnyOpUpdateIn, + collectionXOrAnyOpUpdate, + collectionXOrAnyOpRemoveIn, + collectionXOrAnyOpSetIn, + collectionXOpCountBy, + collectionXOpGroupBy, + collectionXOpLastKeyOf, + collectionXOpValueSeq, + collectionXOpEntrySeq, + collectionXOpToStringDetails, + collectionXOpSort, + collectionXOpSortBy, + collectionXOpMap, + collectionXOpMergeIntoKeyedWith, + collectionXOpMergeDeepWithSources, + collectionXOpMergeDeep, + collectionXOpMergeDeepIn, + collectionXOpDeepMergerWith, + collectionXOpReverse, + collectionXOpPartition, + collectionXOpConcat, + collectionXOpFilter, + collectionXOpFilterNot, + collectionXOpFlatMap, + collectionXOpFlatten, + collectionXOpFlip, + collectionXIndexedOpInterleave, + collectionXOpIsSubset, + collectionXOpIsSuperset, + collectionXOpSlice, + collectionXOpTake, + collectionXOpTakeLast, + collectionXOpMergeIn, + collectionXOpMergeWithSources, + collectionXOpMergeWith, + collectionXOpMergeDeepWith, + collectionXOpMerge, + collectionXIndexedOpInterpose, + collectionXIndexedOpReverse, + collectionXIndexedOpSlice, + collectionXIndexedOpSplice, + collectionXIndexedOpFilter, + collectionXIndexedOpZip, + collectionXIndexedOpZipAll, + collectionXIndexedOpZipWith, + collectionXKeyedOpMapEntries, + collectionXKeyedOpMapKeys, + collectionXKeyedOpFlip, + collectionXCastKeyedSequenceOpReverse, + collectionXCastKeyedSequenceOpMap, +}; diff --git a/src/collection/kernelIndexed.js b/src/collection/kernelIndexed.js new file mode 100644 index 0000000000..2e69e4197c --- /dev/null +++ b/src/collection/kernelIndexed.js @@ -0,0 +1,256 @@ +import { SHIFT, SIZE, MASK, SetRef } from '../TrieUtils'; + +import { SHAPE_NODELIST, DONE } from '../const'; + +const kernelIndexedVNodeCreate = (array, ownerID) => { + return { + array, + ownerID, + }; +}; + +const kernelIndexedVNodeOpEditable = (vnode, ownerID) => { + if (ownerID && vnode && ownerID === vnode.ownerID) { + return vnode; + } + return kernelIndexedVNodeCreate(vnode ? vnode.array.slice() : [], ownerID); +}; + +const kernelIndexedVNodeOpUpdate = ( + vnode, + ownerID, + level, + index, + value, + didAlter +) => { + const idx = (index >>> level) & MASK; + const nodeHas = vnode && idx < vnode.array.length; + if (!nodeHas && value === undefined) { + return vnode; + } + + let newNode; + + if (level > 0) { + const lowerNode = vnode && vnode.array[idx]; + const newLowerNode = kernelIndexedVNodeOpUpdate( + lowerNode, + ownerID, + level - SHIFT, + index, + value, + didAlter + ); + if (newLowerNode === lowerNode) { + return vnode; + } + newNode = kernelIndexedVNodeOpEditable(vnode, ownerID); + newNode.array[idx] = newLowerNode; + return newNode; + } + + if (nodeHas && vnode.array[idx] === value) { + return vnode; + } + + if (didAlter) { + SetRef(didAlter); + } + + newNode = kernelIndexedVNodeOpEditable(vnode, ownerID); + if (value === undefined && idx === newNode.array.length - 1) { + newNode.array.pop(); + } else { + newNode.array[idx] = value; + } + return newNode; +}; + +const kernelIndexedVNodeOpRemoveBefore = (vnode, ownerID, level, index) => { + if ( + (index & ((1 << (level + SHIFT)) - 1)) === 0 || + vnode.array.length === 0 + ) { + return vnode; + } + const originIndex = (index >>> level) & MASK; + if (originIndex >= vnode.array.length) { + return kernelIndexedVNodeCreate([], ownerID); + } + const removingFirst = originIndex === 0; + let newChild; + if (level > 0) { + const oldChild = vnode.array[originIndex]; + newChild = + oldChild && + kernelIndexedVNodeOpRemoveBefore(oldChild, ownerID, level - SHIFT, index); + if (newChild === oldChild && removingFirst) { + return vnode; + } + } + if (removingFirst && !newChild) { + return vnode; + } + const editable = kernelIndexedVNodeOpEditable(vnode, ownerID); + if (!removingFirst) { + for (let ii = 0; ii < originIndex; ii++) { + editable.array[ii] = undefined; + } + } + if (newChild) { + editable.array[originIndex] = newChild; + } + return editable; +}; + +const kernelIndexedVNodeOpRemoveAfter = (vnode, ownerID, level, index) => { + if ( + index === (level ? 1 << (level + SHIFT) : SIZE) || + vnode.array.length === 0 + ) { + return vnode; + } + const sizeIndex = ((index - 1) >>> level) & MASK; + if (sizeIndex >= vnode.array.length) { + return vnode; + } + + let newChild; + if (level > 0) { + const oldChild = vnode.array[sizeIndex]; + newChild = + oldChild && + kernelIndexedVNodeOpRemoveAfter(oldChild, ownerID, level - SHIFT, index); + if (newChild === oldChild && sizeIndex === vnode.array.length - 1) { + return vnode; + } + } + + const editable = kernelIndexedVNodeOpEditable(vnode, ownerID); + editable.array.splice(sizeIndex + 1); + if (newChild) { + editable.array[sizeIndex] = newChild; + } + return editable; +}; + +const kernelIndexedCreate = ( + origin, + capacity, + level, + root, + tail, + ownerID, + hash +) => { + const nls = {}; + nls.size = capacity - origin; + nls._origin = origin; + nls._capacity = capacity; + nls._level = level; + nls._root = root; + nls._tail = tail; + nls.__ownerID = ownerID; + nls.__hash = hash; + nls.__altered = false; + nls.__shape = SHAPE_NODELIST; + + return nls; +}; + +const kernelIndexedCreateEmpty = () => { + return kernelIndexedCreate(0, 0, SHIFT); +}; + +const kernelIndexedOpSizeTailOffset = (size) => { + return size < SIZE ? 0 : ((size - 1) >>> SHIFT) << SHIFT; +}; + +const kernelIndexedOpFindVNodeFor = (list, rawIndex) => { + if (rawIndex >= kernelIndexedOpSizeTailOffset(list._capacity)) { + return list._tail; + } + if (rawIndex < 1 << (list._level + SHIFT)) { + let node = list._root; + let level = list._level; + while (node && level > 0) { + node = node.array[(rawIndex >>> level) & MASK]; + level -= SHIFT; + } + return node; + } +}; + +const kernelIndexedOpIterate = (list, reverse) => { + const left = list._origin; + const right = list._capacity; + const tailPos = kernelIndexedOpSizeTailOffset(right); + const tail = list._tail; + + return iterateNodeOrLeaf(list._root, list._level, 0); + + function iterateNodeOrLeaf(node, level, offset) { + return level === 0 + ? iterateLeaf(node, offset) + : iterateNode(node, level, offset); + } + + function iterateLeaf(node, offset) { + const array = offset === tailPos ? tail && tail.array : node && node.array; + let from = offset > left ? 0 : left - offset; + let to = right - offset; + if (to > SIZE) { + to = SIZE; + } + return () => { + if (from === to) { + return DONE; + } + const idx = reverse ? --to : from++; + return array && array[idx]; + }; + } + + function iterateNode(node, level, offset) { + let values; + const array = node && node.array; + let from = offset > left ? 0 : (left - offset) >> level; + let to = ((right - offset) >> level) + 1; + if (to > SIZE) { + to = SIZE; + } + return () => { + while (true) { + if (values) { + const value = values(); + if (value !== DONE) { + return value; + } + values = null; + } + if (from === to) { + return DONE; + } + const idx = reverse ? --to : from++; + values = iterateNodeOrLeaf( + array && array[idx], + level - SHIFT, + offset + (idx << level) + ); + } + }; + } +}; + +export { + kernelIndexedVNodeCreate, + kernelIndexedVNodeOpEditable, + kernelIndexedVNodeOpUpdate, + kernelIndexedVNodeOpRemoveAfter, + kernelIndexedVNodeOpRemoveBefore, + kernelIndexedCreate, + kernelIndexedCreateEmpty, + kernelIndexedOpFindVNodeFor, + kernelIndexedOpIterate, +}; diff --git a/src/collection/kernelKeyed.js b/src/collection/kernelKeyed.js new file mode 100644 index 0000000000..e027355b1a --- /dev/null +++ b/src/collection/kernelKeyed.js @@ -0,0 +1,652 @@ +import { hash } from '../Hash'; +import { probeIsSame } from '../probe'; + +import { + utilArrCopy, + utilArrSetAt, + utilArrSpliceIn, + utilArrSpliceOut, +} from '../util'; + +import transformToMethods from '../transformToMethods'; + +import { SHIFT, SIZE, MASK, OwnerID, SetRef } from '../TrieUtils'; + +import { + NOT_SET, + SHAPE_NODEVALUE, + SHAPE_NODEARRAYMAP, + SHAPE_NODEHASHARRAYMAP, + SHAPE_NODEHASHCOLLISION, + SHAPE_NODEBITMAPINDEXED, +} from '../const'; + +const MAX_ARRAY_MAP_SIZE = SIZE / 4; +const MAX_BITMAP_INDEXED_SIZE = SIZE / 2; +const MIN_HASH_ARRAY_MAP_SIZE = SIZE / 4; + +const kernelKeyedHashArrayMapOpGet = ( + nham, + shift, + keyHash, + key, + notSetValue +) => { + if (keyHash === undefined) { + keyHash = hash(key); + } + const idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; + const node = nham.nodes[idx]; + return node + ? node.get(shift + SHIFT, keyHash, key, notSetValue) + : notSetValue; +}; + +const kernelKeyedHashArrayMapOpUpdate = ( + nham, + ownerID, + shift, + keyHash, + key, + value, + didChangeSize, + didAlter +) => { + if (keyHash === undefined) { + keyHash = hash(key); + } + const idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; + const removed = value === NOT_SET; + const nodes = nham.nodes; + const node = nodes[idx]; + + if (removed && !node) { + return nham; + } + + const newNode = kernelKeyedOpUpdateOrCreate( + node, + ownerID, + shift + SHIFT, + keyHash, + key, + value, + didChangeSize, + didAlter + ); + if (newNode === node) { + return nham; + } + + let newCount = nham.count; + if (!node) { + newCount++; + } else if (!newNode) { + newCount--; + if (newCount < MIN_HASH_ARRAY_MAP_SIZE) { + return kernelKeyedHashArrayMapOpNodesPack(ownerID, nodes, newCount, idx); + } + } + + const isEditable = ownerID && ownerID === nham.ownerID; + const newNodes = utilArrSetAt(nodes, idx, newNode, isEditable); + + if (isEditable) { + nham.count = newCount; + nham.nodes = newNodes; + return nham; + } + + return kernelKeyedHashArrayMapCreate(ownerID, newCount, newNodes); +}; + +const kernelKeyedHashCollisionOpUpdate = ( + nhc, + ownerID, + shift, + keyHash, + key, + value, + didChangeSize, + didAlter +) => { + if (keyHash === undefined) { + keyHash = hash(key); + } + + const removed = value === NOT_SET; + + if (keyHash !== nhc.keyHash) { + if (removed) { + return nhc; + } + SetRef(didAlter); + SetRef(didChangeSize); + return kernelKeyedOpMergeInto(nhc, ownerID, shift, keyHash, [key, value]); + } + + const entries = nhc.entries; + let idx = 0; + const len = entries.length; + for (; idx < len; idx++) { + if (probeIsSame(key, entries[idx][0])) { + break; + } + } + const exists = idx < len; + + if (exists ? entries[idx][1] === value : removed) { + return nhc; + } + + SetRef(didAlter); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here + (removed || !exists) && SetRef(didChangeSize); + + if (removed && len === 2) { + return kernelKeyedCreate(ownerID, nhc.keyHash, entries[idx ^ 1]); + } + + const isEditable = ownerID && ownerID === nhc.ownerID; + const newEntries = isEditable ? entries : utilArrCopy(entries); + + if (exists) { + if (removed) { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here + idx === len - 1 ? newEntries.pop() : (newEntries[idx] = newEntries.pop()); + } else { + newEntries[idx] = [key, value]; + } + } else { + newEntries.push([key, value]); + } + + if (isEditable) { + nhc.entries = newEntries; + return nhc; + } + + return kernelKeyedHashCollisionCreate(ownerID, nhc.keyHash, newEntries); +}; + +const kernelKeyedHashCollisionCreate = ( + (cache) => (ownerID, keyHash, entries) => { + const nhc = Object.create( + cache || + (cache = transformToMethods({ + shape: SHAPE_NODEHASHCOLLISION, + get: kernelKeyedArrayEntryNodeOpGet, + iterate: kernelKeyedArrayEntriesIterate, + update: kernelKeyedHashCollisionOpUpdate, + })) + ); + + nhc.ownerID = ownerID; + nhc.keyHash = keyHash; + nhc.entries = entries; + + return nhc; + } +)(); + +const kernelKeyedBitmapIndexedOpNodesExpand = ( + ownerID, + nodes, + bitmap, + including, + node +) => { + let count = 0; + const expandedNodes = new Array(SIZE); + for (let ii = 0; bitmap !== 0; ii++, bitmap >>>= 1) { + expandedNodes[ii] = bitmap & 1 ? nodes[count++] : undefined; + } + expandedNodes[including] = node; + + return kernelKeyedHashArrayMapCreate(ownerID, count + 1, expandedNodes); +}; + +const kernelKeyedOpMergeInto = (node, ownerID, shift, keyHash, entry) => { + if (node.keyHash === keyHash) { + return kernelKeyedHashCollisionCreate(ownerID, keyHash, [ + node.entry, + entry, + ]); + } + + const idx1 = (shift === 0 ? node.keyHash : node.keyHash >>> shift) & MASK; + const idx2 = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; + + let newNode; + const nodes = + idx1 === idx2 + ? [kernelKeyedOpMergeInto(node, ownerID, shift + SHIFT, keyHash, entry)] + : ((newNode = kernelKeyedCreate(ownerID, keyHash, entry)), + idx1 < idx2 ? [node, newNode] : [newNode, node]); + + return kernelKeyedBitmapIndexedCreate( + ownerID, + (1 << idx1) | (1 << idx2), + nodes + ); +}; + +const kernelKeyedOpGet = (nv, shift, keyHash, key, notSetValue) => { + return probeIsSame(key, nv.entry[0]) ? nv.entry[1] : notSetValue; +}; + +const kernelKeyedOpUpdate = ( + nv, + ownerID, + shift, + keyHash, + key, + value, + didChangeSize, + didAlter +) => { + const removed = value === NOT_SET; + const keyMatch = probeIsSame(key, nv.entry[0]); + if (keyMatch ? value === nv.entry[1] : removed) { + return nv; + } + + SetRef(didAlter); + + if (removed) { + SetRef(didChangeSize); + return; // undefined + } + + if (keyMatch) { + if (ownerID && ownerID === nv.ownerID) { + nv.entry[1] = value; + return nv; + } + + return kernelKeyedCreate(ownerID, nv.keyHash, [key, value]); + } + + SetRef(didChangeSize); + return kernelKeyedOpMergeInto(nv, ownerID, shift, hash(key), [key, value]); +}; + +const kernelKeyedOpIterate = (nv, fn) => { + return fn(nv.entry); +}; + +const kernelKeyedCreate = ((cache) => (ownerID, keyHash, entry) => { + const nv = Object.create( + cache || + (cache = transformToMethods({ + shape: SHAPE_NODEVALUE, + get: kernelKeyedOpGet, + iterate: kernelKeyedOpIterate, + update: kernelKeyedOpUpdate, + })) + ); + + nv.ownerID = ownerID; + nv.keyHash = keyHash; + nv.entry = entry; + + return nv; +})(); + +const kernelKeyedBitmapIndexedOpNodeIsLeaf = (node) => { + return ( + node.shape === SHAPE_NODEVALUE || node.shape === SHAPE_NODEHASHCOLLISION + ); +}; + +const kernelKeyedHashArrayMapOpNodesPack = ( + ownerID, + nodes, + count, + excluding +) => { + let bitmap = 0; + let packedII = 0; + const packedNodes = new Array(count); + for (let ii = 0, bit = 1, len = nodes.length; ii < len; ii++, bit <<= 1) { + const node = nodes[ii]; + if (node !== undefined && ii !== excluding) { + bitmap |= bit; + packedNodes[packedII++] = node; + } + } + + return kernelKeyedBitmapIndexedCreate(ownerID, bitmap, packedNodes); +}; + +const kernelKeyedOpUpdateOrCreate = ( + node, + ownerID, + shift, + keyHash, + key, + value, + didChangeSize, + didAlter +) => { + if (!node) { + if (value === NOT_SET) { + return node; + } + SetRef(didAlter); + SetRef(didChangeSize); + + return kernelKeyedCreate(ownerID, keyHash, [key, value]); + } + return node.update( + ownerID, + shift, + keyHash, + key, + value, + didChangeSize, + didAlter + ); +}; + +const popCount = (x) => { + x -= (x >> 1) & 0x55555555; + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0f0f0f0f; + x += x >> 8; + x += x >> 16; + return x & 0x7f; +}; + +const kernelKeyedBitmapIndexedGet = (nbi, shift, keyHash, key, notSetValue) => { + if (keyHash === undefined) { + keyHash = hash(key); + } + const bit = 1 << ((shift === 0 ? keyHash : keyHash >>> shift) & MASK); + const bitmap = nbi.bitmap; + return (bitmap & bit) === 0 + ? notSetValue + : nbi.nodes[popCount(bitmap & (bit - 1))].get( + shift + SHIFT, + keyHash, + key, + notSetValue + ); +}; + +const kernelKeyedBitmapIndexedOpUpdate = ( + nbi, + ownerID, + shift, + keyHash, + key, + value, + didChangeSize, + didAlter +) => { + if (keyHash === undefined) { + keyHash = hash(key); + } + const keyHashFrag = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; + const bit = 1 << keyHashFrag; + const bitmap = nbi.bitmap; + const exists = (bitmap & bit) !== 0; + + if (!exists && value === NOT_SET) { + return nbi; + } + + const idx = popCount(bitmap & (bit - 1)); + const nodes = nbi.nodes; + const node = exists ? nodes[idx] : undefined; + const newNode = kernelKeyedOpUpdateOrCreate( + node, + ownerID, + shift + SHIFT, + keyHash, + key, + value, + didChangeSize, + didAlter + ); + + if (newNode === node) { + return nbi; + } + + if (!exists && newNode && nodes.length >= MAX_BITMAP_INDEXED_SIZE) { + return kernelKeyedBitmapIndexedOpNodesExpand( + ownerID, + nodes, + bitmap, + keyHashFrag, + newNode + ); + } + + if ( + exists && + !newNode && + nodes.length === 2 && + kernelKeyedBitmapIndexedOpNodeIsLeaf(nodes[idx ^ 1]) + ) { + return nodes[idx ^ 1]; + } + + if ( + exists && + newNode && + nodes.length === 1 && + kernelKeyedBitmapIndexedOpNodeIsLeaf(newNode) + ) { + return newNode; + } + + const isEditable = ownerID && ownerID === nbi.ownerID; + const newBitmap = exists ? (newNode ? bitmap : bitmap ^ bit) : bitmap | bit; + const newNodes = exists + ? newNode + ? utilArrSetAt(nodes, idx, newNode, isEditable) + : utilArrSpliceOut(nodes, idx, isEditable) + : utilArrSpliceIn(nodes, idx, newNode, isEditable); + + if (isEditable) { + nbi.bitmap = newBitmap; + nbi.nodes = newNodes; + return nbi; + } + + return kernelKeyedBitmapIndexedCreate(ownerID, newBitmap, newNodes); +}; + +const kernelKeyedBitmapIndexedCreate = ((cache) => (ownerID, bitmap, nodes) => { + const nbi = Object.create( + cache || + (cache = transformToMethods({ + shape: SHAPE_NODEBITMAPINDEXED, + get: kernelKeyedBitmapIndexedGet, + iterate: kernelKeyedMapOpNodesIterate, + update: kernelKeyedBitmapIndexedOpUpdate, + })) + ); + + nbi.ownerID = ownerID; + nbi.bitmap = bitmap; + nbi.nodes = nodes; + + return nbi; +})(); + +const kernelKeyedArrayEntriesIterate = (node, fn, reverse) => { + const entries = node.entries; + for (let ii = 0, maxIndex = entries.length - 1; ii <= maxIndex; ii++) { + if (fn(entries[reverse ? maxIndex - ii : ii]) === false) { + return false; + } + } +}; + +const kernelKeyedArrayEntryNodeOpGet = ( + nam, + shift, + keyHash, + key, + notSetValue +) => { + const entries = nam.entries; + for (let ii = 0, len = entries.length; ii < len; ii++) { + if (probeIsSame(key, entries[ii][0])) { + return entries[ii][1]; + } + } + return notSetValue; +}; + +const kernelKeyedArrayMapOpUpdate = ( + nam, + ownerID, + shift, + keyHash, + key, + value, + didChangeSize, + didAlter +) => { + const removed = value === NOT_SET; + + const entries = nam.entries; + let idx = 0; + const len = entries.length; + for (; idx < len; idx++) { + if (probeIsSame(key, entries[idx][0])) { + break; + } + } + const exists = idx < len; + + if (exists ? entries[idx][1] === value : removed) { + return nam; + } + + SetRef(didAlter); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here + (removed || !exists) && SetRef(didChangeSize); + + if (removed && entries.length === 1) { + return; // undefined + } + + if (!exists && !removed && entries.length >= MAX_ARRAY_MAP_SIZE) { + return kernelKeyedArrayMapOpNodesCreate(ownerID, entries, key, value); + } + + const isEditable = ownerID && ownerID === nam.ownerID; + const newEntries = isEditable ? entries : utilArrCopy(entries); + + if (exists) { + if (removed) { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here + idx === len - 1 ? newEntries.pop() : (newEntries[idx] = newEntries.pop()); + } else { + newEntries[idx] = [key, value]; + } + } else { + newEntries.push([key, value]); + } + + if (isEditable) { + nam.entries = newEntries; + return nam; + } + + return kernelKeyedArrayMapCreate(ownerID, newEntries); +}; + +const kernelKeyedArrayMapCreate = ((cache) => (ownerID, entries) => { + const nam = Object.create( + cache || + (cache = transformToMethods({ + shape: SHAPE_NODEARRAYMAP, + get: kernelKeyedArrayEntryNodeOpGet, + iterate: kernelKeyedArrayEntriesIterate, + update: kernelKeyedArrayMapOpUpdate, + })) + ); + + nam.ownerID = ownerID; + nam.entries = entries; + + return nam; +})(); + +const kernelKeyedArrayMapOpNodesCreate = (ownerID, entries, key, value) => { + if (!ownerID) { + ownerID = new OwnerID(); + } + + let node = kernelKeyedCreate(ownerID, hash(key), [key, value]); + for (let ii = 0; ii < entries.length; ii++) { + const entry = entries[ii]; + node = node.update(ownerID, 0, undefined, entry[0], entry[1]); + } + return node; +}; + +const kernelKeyedMapOpNodesIterate = (collection, fn, reverse) => { + const nodes = collection.nodes; + for (let ii = 0, maxIndex = nodes.length - 1; ii <= maxIndex; ii++) { + const node = nodes[reverse ? maxIndex - ii : ii]; + if (node && node.iterate(fn, reverse) === false) { + return false; + } + } +}; + +const kernelKeyedHashArrayMapCreate = ((cache) => (ownerID, count, nodes) => { + const nham = Object.create( + cache || + (cache = transformToMethods({ + shape: SHAPE_NODEHASHARRAYMAP, + get: kernelKeyedHashArrayMapOpGet, + iterate: kernelKeyedMapOpNodesIterate, + update: kernelKeyedHashArrayMapOpUpdate, + })) + ); + + nham.ownerID = ownerID; + nham.count = count; + nham.nodes = nodes; + + return nham; +})(); + +const kernelKeyedOpHas = (cx, searchKey) => { + return cx.get(searchKey, NOT_SET) !== NOT_SET; +}; + +export { + kernelKeyedArrayMapCreate, + kernelKeyedArrayMapOpUpdate, + kernelKeyedArrayMapOpNodesCreate, + kernelKeyedHashArrayMapCreate, + kernelKeyedHashArrayMapOpGet, + kernelKeyedHashArrayMapOpUpdate, + kernelKeyedHashArrayMapOpNodesPack, + kernelKeyedHashCollisionCreate, + kernelKeyedHashCollisionOpUpdate, + kernelKeyedBitmapIndexedCreate, + kernelKeyedBitmapIndexedGet, + kernelKeyedBitmapIndexedOpUpdate, + kernelKeyedBitmapIndexedOpNodesExpand, + kernelKeyedBitmapIndexedOpNodeIsLeaf, + kernelKeyedMapOpNodesIterate, + kernelKeyedArrayEntryNodeOpGet, + kernelKeyedArrayEntriesIterate, + kernelKeyedCreate, + kernelKeyedOpGet, + kernelKeyedOpIterate, + kernelKeyedOpUpdate, + kernelKeyedOpUpdateOrCreate, + kernelKeyedOpMergeInto, + kernelKeyedOpHas, +}; diff --git a/src/const.js b/src/const.js new file mode 100644 index 0000000000..0fc035615d --- /dev/null +++ b/src/const.js @@ -0,0 +1,79 @@ +const IS_COLLECTION_SYMBOL = '@@__IMMUTABLE_ITERABLE__@@'; +const IS_ORDERED_SYMBOL = '@@__IMMUTABLE_ORDERED__@@'; +const IS_INDEXED_SYMBOL = '@@__IMMUTABLE_INDEXED__@@'; +const IS_RECORD_SYMBOL = '@@__IMMUTABLE_RECORD__@@'; +const IS_STACK_SYMBOL = '@@__IMMUTABLE_STACK__@@'; +const IS_KEYED_SYMBOL = '@@__IMMUTABLE_KEYED__@@'; +const IS_LIST_SYMBOL = '@@__IMMUTABLE_LIST__@@'; +const IS_SEQ_SYMBOL = '@@__IMMUTABLE_SEQ__@@'; +const IS_SET_SYMBOL = '@@__IMMUTABLE_SET__@@'; +const IS_MAP_SYMBOL = '@@__IMMUTABLE_MAP__@@'; + +const ITERATOR_SYMBOL_REAL = typeof Symbol === 'function' && Symbol.iterator; +const ITERATOR_SYMBOL_FAUX = '@@iterator'; +const ITERATOR_SYMBOL = ITERATOR_SYMBOL_REAL || ITERATOR_SYMBOL_FAUX; + +// Used for setting reserved keyword `delete` as named-property. +const DELETE = 'delete'; + +// A consistent shared value representing "not set" which equals nothing other +// than itself, and nothing that could be provided externally. +const NOT_SET = {}; +const DONE = {}; + +// TODO consider removing these +const SHAPE_MAP = 'map'; +const SHAPE_MAPORDERED = 'mapordered'; +const SHAPE_RANGE = 'range'; +const SHAPE_LIST = 'list'; +const SHAPE_SET = 'set'; +const SHAPE_SETORDERED = 'setordered'; +const SHAPE_STACK = 'stack'; +const SHAPE_NODELIST = 'nodeList'; +const SHAPE_NODEVALUE = 'nodeValue'; +const SHAPE_NODEARRAYMAP = 'nodeArrayMap'; +const SHAPE_NODEHASHARRAYMAP = 'nodeHashArrayMap'; +const SHAPE_NODEHASHCOLLISION = 'nodeHashCollision'; +const SHAPE_NODEBITMAPINDEXED = 'nodeBitmapIndexed'; + +const ITERATE_TYPE_KEYS = 0; +const ITERATE_TYPE_VALUES = 1; +const ITERATE_TYPE_ENTRIES = 2; + +export { + ITERATE_TYPE_KEYS, + ITERATE_TYPE_KEYS as ITERATE_KEYS, + ITERATE_TYPE_VALUES, + ITERATE_TYPE_VALUES as ITERATE_VALUES, + ITERATE_TYPE_ENTRIES, + ITERATE_TYPE_ENTRIES as ITERATE_ENTRIES, + IS_COLLECTION_SYMBOL, + IS_ORDERED_SYMBOL, + IS_SET_SYMBOL, + IS_LIST_SYMBOL, + IS_INDEXED_SYMBOL, + IS_RECORD_SYMBOL, + IS_STACK_SYMBOL, + IS_KEYED_SYMBOL, + IS_MAP_SYMBOL, + IS_SEQ_SYMBOL, + ITERATOR_SYMBOL_REAL, + ITERATOR_SYMBOL_FAUX, + ITERATOR_SYMBOL, + DELETE, + NOT_SET, + DONE, + SHAPE_MAP, + SHAPE_MAPORDERED, + SHAPE_SET, + SHAPE_SETORDERED, + SHAPE_RANGE, + SHAPE_LIST, + SHAPE_STACK, + SHAPE_NODELIST, + SHAPE_NODEVALUE, + SHAPE_NODEARRAYMAP, + SHAPE_NODEHASHARRAYMAP, + SHAPE_NODEHASHCOLLISION, + SHAPE_NODEBITMAPINDEXED, +}; diff --git a/src/factory/factory.js b/src/factory/factory.js new file mode 100644 index 0000000000..61422dab22 --- /dev/null +++ b/src/factory/factory.js @@ -0,0 +1,210 @@ +import { NOT_SET } from '../const'; + +import { + Iterator, + iteratorValue, + ITERATE_VALUES, + ITERATE_ENTRIES, +} from '../Iterator'; + +import { probeIsIndexed, probeIsOrdered, probeIsKeyed } from '../probe'; + +const factoryCountBy = (cx, grouper, context, mapCreate) => { + const groups = mapCreate().asMutable(); + cx.__iterate((v, k) => { + groups.update(grouper.call(context, v, k, cx), 0, (a) => a + 1); + }); + return groups.asImmutable(); +}; + +const factoryGroupBy = ( + cx, + Map, + MapOrdered, + reify, + collectionClass, + grouper, + context +) => { + const isKeyedIter = probeIsKeyed(cx); + const groups = (probeIsOrdered(cx) ? MapOrdered() : Map()).asMutable(); + cx.__iterate((v, k) => { + groups.update( + grouper.call(context, v, k, cx), + (a) => ((a = a || []), a.push(isKeyedIter ? [k, v] : v), a) + ); + }); + const coerce = collectionClass(cx); + return groups.map((arr) => reify(cx, coerce(arr))).asImmutable(); +}; + +const factoryInterpose = (collection, makeSequence, separator) => { + const interposedSequence = makeSequence(collection); + interposedSequence.size = collection.size && collection.size * 2 - 1; + interposedSequence.__iterateUncached = function (fn, reverse) { + let iterations = 0; + collection.__iterate( + (v) => + (!iterations || fn(separator, iterations++, this) !== false) && + fn(v, iterations++, this) !== false, + reverse + ); + return iterations; + }; + interposedSequence.__iteratorUncached = function (type, reverse) { + const iterator = collection.__iterator(ITERATE_VALUES, reverse); + let iterations = 0; + let step; + return new Iterator(() => { + if (!step || iterations % 2) { + step = iterator.next(); + if (step.done) { + return step; + } + } + return iterations % 2 + ? iteratorValue(type, iterations++, separator) + : iteratorValue(type, iterations++, step.value, step); + }); + }; + return interposedSequence; +}; + +const factoryPartition = ( + collection, + collectionClass, + reify, + predicate, + context +) => { + const isKeyedIter = probeIsKeyed(collection); + const groups = [[], []]; + collection.__iterate((v, k) => { + groups[predicate.call(context, v, k, collection) ? 1 : 0].push( + isKeyedIter ? [k, v] : v + ); + }); + const coerce = collectionClass(collection); + return groups.map((arr) => reify(collection, coerce(arr))); +}; + +const factoryFilter = ( + collection, + makeSequence, + predicate, + context, + useKeys +) => { + const filterSequence = makeSequence(collection); + if (useKeys) { + filterSequence.has = (key) => { + const v = collection.get(key, NOT_SET); + return v !== NOT_SET && !!predicate.call(context, v, key, collection); + }; + filterSequence.get = (key, notSetValue) => { + const v = collection.get(key, NOT_SET); + return v !== NOT_SET && predicate.call(context, v, key, collection) + ? v + : notSetValue; + }; + } + filterSequence.__iterateUncached = function (fn, reverse) { + let iterations = 0; + collection.__iterate((v, k, c) => { + if (predicate.call(context, v, k, c)) { + iterations++; + return fn(v, useKeys ? k : iterations - 1, this); + } + }, reverse); + return iterations; + }; + filterSequence.__iteratorUncached = function (type, reverse) { + const iterator = collection.__iterator(ITERATE_ENTRIES, reverse); + let iterations = 0; + return new Iterator(() => { + while (true) { + const step = iterator.next(); + if (step.done) { + return step; + } + const entry = step.value; + const key = entry[0]; + const value = entry[1]; + if (predicate.call(context, value, key, collection)) { + return iteratorValue(type, useKeys ? key : iterations++, value, step); + } + } + }); + }; + return filterSequence; +}; + +function defaultComparator(a, b) { + if (a === undefined && b === undefined) { + return 0; + } + + if (a === undefined) { + return 1; + } + + if (b === undefined) { + return -1; + } + + return a > b ? 1 : a < b ? -1 : 0; +} + +const factorySort = ( + collection, + SeqKeyed, + SeqIndexed, + SeqSet, + comparator, + mapper +) => { + if (!comparator) { + comparator = defaultComparator; + } + const isKeyedCollection = probeIsKeyed(collection); + let index = 0; + const entries = collection + .toSeq() + .map((v, k) => [k, v, index++, mapper ? mapper(v, k, collection) : v]) + .valueSeq() + .toArray(); + entries + .sort((a, b) => comparator(a[3], b[3]) || a[2] - b[2]) + .forEach( + isKeyedCollection + ? (v, i) => { + entries[i].length = 2; + } + : (v, i) => { + entries[i] = v[1]; + } + ); + return isKeyedCollection + ? SeqKeyed(entries) + : probeIsIndexed(collection) + ? SeqIndexed(entries) + : SeqSet(entries); +}; + +const factoryFlatMap = (cx, collectionClass, mapper, context) => { + const coerce = collectionClass(cx); + return cx + .toSeq() + .map((v, k) => coerce(mapper.call(context, v, k, cx))) + .flatten(true); +}; + +export { + factoryCountBy, + factoryGroupBy, + factoryInterpose, + factoryFlatMap, + factoryFilter, + factoryPartition, + factorySort, +}; diff --git a/src/factory/factoryConcat.js b/src/factory/factoryConcat.js new file mode 100644 index 0000000000..9102f695c4 --- /dev/null +++ b/src/factory/factoryConcat.js @@ -0,0 +1,46 @@ +import { probeIsIndexed, probeIsCollection, probeIsKeyed } from '../probe'; + +import { collectionConcatCreate } from '../collection/collectionConcat'; + +const factoryConcat = ( + collection, + SeqKeyed, + keyedseqfromval, + indexedseqfromval, + values +) => { + const isKeyedCollection = probeIsKeyed(collection); + const iters = [collection] + .concat(values) + .map((v) => { + if (!probeIsCollection(v)) { + v = isKeyedCollection + ? keyedseqfromval(v) + : indexedseqfromval(Array.isArray(v) ? v : [v]); + } else if (isKeyedCollection) { + v = SeqKeyed(v); + } + + return v; + }) + .filter((v) => v.size !== 0); + + if (iters.length === 0) { + return collection; + } + + if (iters.length === 1) { + const singleton = iters[0]; + if ( + singleton === collection || + (isKeyedCollection && probeIsKeyed(singleton)) || + (probeIsIndexed(collection) && probeIsIndexed(singleton)) + ) { + return singleton; + } + } + + return collectionConcatCreate(iters); +}; + +export { factoryConcat }; diff --git a/src/factory/factoryFlatten.js b/src/factory/factoryFlatten.js new file mode 100644 index 0000000000..51fb33b8d9 --- /dev/null +++ b/src/factory/factoryFlatten.js @@ -0,0 +1,65 @@ +import { + Iterator, + iteratorValue, + iteratorDone, + ITERATE_ENTRIES, +} from '../Iterator'; + +import { probeIsCollection } from '../probe'; + +const factoryFlatten = (cx, makeSequence, depth, useKeys) => { + const flatSequence = makeSequence(cx); + flatSequence.__iterateUncached = function (fn, reverse) { + if (reverse) { + return this.cacheResult().__iterate(fn, reverse); + } + let iterations = 0; + let stopped = false; + function flatDeep(iter, currentDepth) { + iter.__iterate((v, k) => { + if ((!depth || currentDepth < depth) && probeIsCollection(v)) { + flatDeep(v, currentDepth + 1); + } else { + iterations++; + if (fn(v, useKeys ? k : iterations - 1, flatSequence) === false) { + stopped = true; + } + } + return !stopped; + }, reverse); + } + flatDeep(cx, 0); + return iterations; + }; + flatSequence.__iteratorUncached = function (type, reverse) { + if (reverse) { + return this.cacheResult().__iterator(type, reverse); + } + let iterator = cx.__iterator(type, reverse); + const stack = []; + let iterations = 0; + return new Iterator(() => { + while (iterator) { + const step = iterator.next(); + if (step.done !== false) { + iterator = stack.pop(); + continue; + } + let v = step.value; + if (type === ITERATE_ENTRIES) { + v = v[1]; + } + if ((!depth || stack.length < depth) && probeIsCollection(v)) { + stack.push(iterator); + iterator = v.__iterator(type, reverse); + } else { + return useKeys ? step : iteratorValue(type, iterations++, v, step); + } + } + return iteratorDone(); + }); + }; + return flatSequence; +}; + +export { factoryFlatten }; diff --git a/src/factory/factoryFlip.js b/src/factory/factoryFlip.js new file mode 100644 index 0000000000..85d1f944e7 --- /dev/null +++ b/src/factory/factoryFlip.js @@ -0,0 +1,59 @@ +import { + Iterator, + ITERATE_KEYS, + ITERATE_VALUES, + ITERATE_ENTRIES, +} from '../Iterator'; + +import { collectionOpCacheResultThrough } from '../collection/collection'; + +const factoryFlipOpIterateUncached = (flipcx, collection, fn, reverse) => { + return collection.__iterate((v, k) => fn(k, v, this) !== false, reverse); +}; + +const factoryFlipOpIteratorUncached = (cx, type, reverse) => { + if (type === ITERATE_ENTRIES) { + const iterator = cx.__iterator(type, reverse); + return new Iterator(() => { + const step = iterator.next(); + if (!step.done) { + const k = step.value[0]; + step.value[0] = step.value[1]; + step.value[1] = k; + } + return step; + }); + } + return cx.__iterator( + type === ITERATE_VALUES ? ITERATE_KEYS : ITERATE_VALUES, + reverse + ); +}; + +const factoryFlip = (collection, xseq) => { + const flipSequence = xseq; + flipSequence._iter = collection; + flipSequence.size = collection.size; + + flipSequence.flip = () => collection; + flipSequence.reverse = function () { + const reversedSequence = collection.reverse.apply(this); // super.reverse() + reversedSequence.flip = () => collection.reverse(); + return reversedSequence; + }; + flipSequence.has = (key) => collection.includes(key); + flipSequence.includes = (key) => collection.has(key); + flipSequence.cacheResult = function () { + return collectionOpCacheResultThrough(this); + }; + flipSequence.__iterateUncached = function (fn, reverse) { + return factoryFlipOpIterateUncached(this, collection, fn, reverse); + }; + flipSequence.__iteratorUncached = function (type, reverse) { + return factoryFlipOpIteratorUncached(collection, type, reverse); + }; + + return flipSequence; +}; + +export { factoryFlip }; diff --git a/src/factory/factoryMap.js b/src/factory/factoryMap.js new file mode 100644 index 0000000000..75013e8df4 --- /dev/null +++ b/src/factory/factoryMap.js @@ -0,0 +1,41 @@ +import { NOT_SET } from '../const'; + +import { Iterator, iteratorValue, ITERATE_ENTRIES } from '../Iterator'; + +const factoryMap = (collection, makeSequence, mapper, context) => { + const mappedSequence = makeSequence(collection); + mappedSequence.size = collection.size; + mappedSequence.has = (key) => collection.has(key); + mappedSequence.get = (key, notSetValue) => { + const v = collection.get(key, NOT_SET); + return v === NOT_SET + ? notSetValue + : mapper.call(context, v, key, collection); + }; + mappedSequence.__iterateUncached = function (fn, reverse) { + return collection.__iterate( + (v, k, c) => fn(mapper.call(context, v, k, c), k, this) !== false, + reverse + ); + }; + mappedSequence.__iteratorUncached = function (type, reverse) { + const iterator = collection.__iterator(ITERATE_ENTRIES, reverse); + return new Iterator(() => { + const step = iterator.next(); + if (step.done) { + return step; + } + const entry = step.value; + const key = entry[0]; + return iteratorValue( + type, + key, + mapper.call(context, entry[1], key, collection), + step + ); + }); + }; + return mappedSequence; +}; + +export { factoryMap }; diff --git a/src/factory/factoryMax.js b/src/factory/factoryMax.js new file mode 100644 index 0000000000..e8c493f5d5 --- /dev/null +++ b/src/factory/factoryMax.js @@ -0,0 +1,38 @@ +const defaultComparator = (a, b) => { + if (a === undefined && b === undefined) return 0; + if (a === undefined) return 1; + if (b === undefined) return -1; + + return a > b ? 1 : a < b ? -1 : 0; +}; + +const maxCompare = (comparator, a, b) => { + const comp = comparator(b, a); + // b is considered the new max if the comparator declares them equal, but + // they are not equal and b is in fact a nullish value. + return ( + (comp === 0 && b !== a && (b === undefined || b === null || b !== b)) || + comp > 0 + ); +}; + +const factoryMax = (collection, comparator, mapper) => { + if (!comparator) { + comparator = defaultComparator; + } + if (mapper) { + const entry = collection + .toSeq() + .map((v, k) => [v, mapper(v, k, collection)]) + .reduce((a, b) => (maxCompare(comparator, a[1], b[1]) ? b : a)); + return entry && entry[0]; + } + + const res = collection.reduce((a, b) => + maxCompare(comparator, a, b) ? b : a + ); + + return res; +}; + +export { factoryMax }; diff --git a/src/factory/factoryReverse.js b/src/factory/factoryReverse.js new file mode 100644 index 0000000000..49b1a8a442 --- /dev/null +++ b/src/factory/factoryReverse.js @@ -0,0 +1,59 @@ +import { collectionOpCacheResultThrough } from '../collection/collection'; + +import { Iterator, iteratorValue, ITERATE_ENTRIES } from '../Iterator'; + +import { ensureSize } from '../TrieUtils'; + +import { factoryFlip } from './factoryFlip'; + +const factoryReverse = (collection, makeSequence, useKeys) => { + const reversedSequence = makeSequence(collection); + reversedSequence._iter = collection; + reversedSequence.size = collection.size; + reversedSequence.reverse = () => collection; + if (collection.flip) { + reversedSequence.flip = function () { + const flipSequence = factoryFlip(collection); + flipSequence.reverse = () => collection.flip(); + return flipSequence; + }; + } + reversedSequence.get = (key, notSetValue) => + collection.get(useKeys ? key : -1 - key, notSetValue); + reversedSequence.has = (key) => collection.has(useKeys ? key : -1 - key); + reversedSequence.includes = (value) => collection.includes(value); + reversedSequence.cacheResult = function () { + return collectionOpCacheResultThrough(this); + }; + reversedSequence.__iterate = function (fn, reverse) { + let i = 0; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here + reverse && ensureSize(collection); + return collection.__iterate( + (v, k) => fn(v, useKeys ? k : reverse ? this.size - ++i : i++, this), + !reverse + ); + }; + reversedSequence.__iterator = (type, reverse) => { + let i = 0; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here + reverse && ensureSize(collection); + const iterator = collection.__iterator(ITERATE_ENTRIES, !reverse); + return new Iterator(() => { + const step = iterator.next(); + if (step.done) { + return step; + } + const entry = step.value; + return iteratorValue( + type, + useKeys ? entry[0] : reverse ? this.size - ++i : i++, + entry[1], + step + ); + }); + }; + return reversedSequence; +}; + +export { factoryReverse }; diff --git a/src/factory/factorySlice.js b/src/factory/factorySlice.js new file mode 100644 index 0000000000..0f0a80f19e --- /dev/null +++ b/src/factory/factorySlice.js @@ -0,0 +1,115 @@ +import { + Iterator, + iteratorValue, + iteratorDone, + ITERATE_KEYS, + ITERATE_VALUES, +} from '../Iterator'; + +import { wrapIndex, wholeSlice, resolveBegin, resolveEnd } from '../TrieUtils'; + +import { probeIsSeq } from '../probe'; + +const factorySlice = (collection, makeSequence, begin, end, useKeys) => { + const originalSize = collection.size; + if (wholeSlice(begin, end, originalSize)) { + return collection; + } + + // begin or end can not be resolved if they were provided as negative numbers and + // this collection's size is unknown. In that case, cache first so there is + // a known size and these do not resolve to NaN. + if (typeof originalSize === 'undefined' && (begin < 0 || end < 0)) { + return factorySlice( + collection.toSeq().cacheResult(), + makeSequence, + begin, + end, + useKeys + ); + } + + const resolvedBegin = resolveBegin(begin, originalSize); + const resolvedEnd = resolveEnd(end, originalSize); + + // Note: resolvedEnd is undefined when the original sequence's length is + // unknown and this slice did not supply an end and should contain all + // elements after resolvedBegin. + // In that case, resolvedSize will be NaN and sliceSize will remain undefined. + const resolvedSize = resolvedEnd - resolvedBegin; + let sliceSize; + if (resolvedSize === resolvedSize) { + sliceSize = resolvedSize < 0 ? 0 : resolvedSize; + } + + const sliceSeq = makeSequence(collection); + // If collection.size is undefined, the size of the realized sliceSeq is + // unknown at this point unless the number of items to slice is 0 + sliceSeq.size = + sliceSize === 0 ? sliceSize : (collection.size && sliceSize) || undefined; + + if (!useKeys && probeIsSeq(collection) && sliceSize >= 0) { + sliceSeq.get = function (index, notSetValue) { + index = wrapIndex(this, index); + return index >= 0 && index < sliceSize + ? collection.get(index + resolvedBegin, notSetValue) + : notSetValue; + }; + } + + sliceSeq._shape = 'factoryslice'; + sliceSeq.__iterateUncached = function (fn, reverse) { + if (sliceSize === 0) { + return 0; + } + if (reverse) { + return this.cacheResult().__iterate(fn, reverse); + } + let skipped = 0; + let isSkipping = true; + let iterations = 0; + collection.__iterate((v, k) => { + if (!(isSkipping && (isSkipping = skipped++ < resolvedBegin))) { + iterations++; + return ( + fn(v, useKeys ? k : iterations - 1, sliceSeq) !== false && + iterations !== sliceSize + ); + } + }); + return iterations; + }; + + sliceSeq.__iteratorUncached = function (type, reverse) { + if (sliceSize !== 0 && reverse) { + return this.cacheResult().__iterator(type, reverse); + } + // Don't bother instantiating parent iterator if taking 0. + if (sliceSize === 0) { + return new Iterator(iteratorDone); + } + const iterator = collection.__iterator(type, reverse); + let skipped = 0; + let iterations = 0; + return new Iterator(() => { + while (skipped++ < resolvedBegin) { + iterator.next(); + } + if (++iterations > sliceSize) { + return iteratorDone(); + } + const step = iterator.next(); + if (useKeys || type === ITERATE_VALUES || step.done) { + return step; + } + if (type === ITERATE_KEYS) { + return iteratorValue(type, iterations - 1, undefined, step); + } + return iteratorValue(type, iterations - 1, step.value[1], step); + }); + }; + + return sliceSeq; +}; + +export { factorySlice }; diff --git a/src/factory/factoryZipWith.js b/src/factory/factoryZipWith.js new file mode 100644 index 0000000000..05ba5d36b0 --- /dev/null +++ b/src/factory/factoryZipWith.js @@ -0,0 +1,76 @@ +import { + getIterator, + Iterator, + iteratorValue, + iteratorDone, + ITERATE_VALUES, +} from '../Iterator'; + +const factoryZipWith = ( + keyIter, + makeSequence, + Collection, + ArraySeq, + zipper, + iters, + zipAll +) => { + const zipSequence = makeSequence(keyIter); + const sizes = ArraySeq(iters).map((i) => i.size); + zipSequence.size = zipAll ? sizes.max() : sizes.min(); + // Note: this a generic base implementation of __iterate in terms of + // __iterator which may be more generically useful in the future. + zipSequence.__iterate = function (fn, reverse) { + /* generic: + var iterator = this.__iterator(ITERATE_ENTRIES, reverse); + var step; + var iterations = 0; + while (!(step = iterator.next()).done) { + iterations++; + if (fn(step.value[1], step.value[0], this) === false) { + break; + } + } + return iterations; + */ + const iterator = this.__iterator(ITERATE_VALUES, reverse); + let step; + let iterations = 0; + while (!(step = iterator.next()).done) { + if (fn(step.value, iterations++, this) === false) { + break; + } + } + return iterations; + }; + zipSequence.__iteratorUncached = function (type, reverse) { + const iterators = iters.map( + (i) => ((i = Collection(i)), getIterator(reverse ? i.reverse() : i)) + ); + let iterations = 0; + let isDone = false; + return new Iterator(() => { + let steps; + if (!isDone) { + steps = iterators.map((i) => i.next()); + isDone = zipAll + ? steps.every((s) => s.done) + : steps.some((s) => s.done); + } + if (isDone) { + return iteratorDone(); + } + return iteratorValue( + type, + iterations++, + zipper.apply( + null, + steps.map((s) => s.value) + ) + ); + }); + }; + return zipSequence; +}; + +export { factoryZipWith }; diff --git a/src/fromJS.js b/src/fromJS.js index 7586c12a58..5d8aa9656d 100644 --- a/src/fromJS.js +++ b/src/fromJS.js @@ -1,10 +1,14 @@ import { Seq } from './Seq'; -import { hasIterator } from './Iterator'; -import { isImmutable } from './predicates/isImmutable'; -import { isIndexed } from './predicates/isIndexed'; -import { isKeyed } from './predicates/isKeyed'; -import isArrayLike from './utils/isArrayLike'; -import isPlainObj from './utils/isPlainObj'; +import { Map } from './Map'; + +import { + probeHasIterator, + probeIsArrayLike, + probeIsPlainObject, + probeIsKeyed, + probeIsImmutable, + probeIsIndexed, +} from './probe'; export function fromJS(value, converter) { return fromJSWith( @@ -20,8 +24,10 @@ export function fromJS(value, converter) { function fromJSWith(stack, converter, value, key, keyPath, parentValue) { if ( typeof value !== 'string' && - !isImmutable(value) && - (isArrayLike(value) || hasIterator(value) || isPlainObj(value)) + !probeIsImmutable(value) && + (probeIsArrayLike(value) || + probeHasIterator(value) || + probeIsPlainObject(value)) ) { if (~stack.indexOf(value)) { throw new TypeError('Cannot convert circular structure to Immutable'); @@ -47,5 +53,9 @@ function fromJSWith(stack, converter, value, key, keyPath, parentValue) { function defaultConverter(k, v) { // Effectively the opposite of "Collection.toSeq()" - return isIndexed(v) ? v.toList() : isKeyed(v) ? v.toMap() : v.toSet(); + return probeIsIndexed(v) + ? v.toList() + : probeIsKeyed(v) + ? v.toMap(Map) + : v.toSet(); } diff --git a/src/functional/get.ts b/src/functional/get.ts deleted file mode 100644 index 94171ccb55..0000000000 --- a/src/functional/get.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { Collection, Record } from '../../type-definitions/immutable'; -import { isImmutable } from '../predicates/isImmutable'; -import { has } from './has'; - -/** - * Returns the value within the provided collection associated with the - * provided key, or notSetValue if the key is not defined in the collection. - * - * A functional alternative to `collection.get(key)` which will also work on - * plain Objects and Arrays as an alternative for `collection[key]`. - * - * - * ```js - * import { get } from 'immutable'; - * - * get([ 'dog', 'frog', 'cat' ], 1) // 'frog' - * get({ x: 123, y: 456 }, 'x') // 123 - * get({ x: 123, y: 456 }, 'z', 'ifNotSet') // 'ifNotSet' - * ``` - */ -export function get(collection: Collection, key: K): V | undefined; -export function get( - collection: Collection, - key: K, - notSetValue: NSV -): V | NSV; -export function get( - record: Record, - key: K, - notSetValue: unknown -): TProps[K]; -export function get(collection: Array, key: number): V | undefined; -export function get( - collection: Array, - key: number, - notSetValue: NSV -): V | NSV; -export function get( - object: C, - key: K, - notSetValue: unknown -): C[K]; -export function get( - collection: { [key: string]: V }, - key: string -): V | undefined; -export function get( - collection: { [key: string]: V }, - key: string, - notSetValue: NSV -): V | NSV; -export function get( - collection: Collection | Array | { [key: string]: V }, - key: K, - notSetValue?: NSV -): V | NSV; -export function get( - collection: Collection | Array | { [key: string]: V }, - key: K, - notSetValue?: NSV -): V | NSV { - return isImmutable(collection) - ? collection.get(key, notSetValue) - : !has(collection, key) - ? notSetValue - : // @ts-expect-error weird "get" here, - typeof collection.get === 'function' - ? // @ts-expect-error weird "get" here, - collection.get(key) - : // @ts-expect-error key is unknown here, - collection[key]; -} diff --git a/src/functional/getIn.ts b/src/functional/getIn.ts deleted file mode 100644 index 11f9cdf384..0000000000 --- a/src/functional/getIn.ts +++ /dev/null @@ -1,41 +0,0 @@ -import coerceKeyPath from '../utils/coerceKeyPath'; -import { NOT_SET } from '../TrieUtils'; -import { get } from './get'; -import type { KeyPath } from '../../type-definitions/immutable'; - -type GetType = typeof get; -type GetTypeParameters = Parameters; -type CollectionType = GetTypeParameters[0]; -type Key = GetTypeParameters[1]; - -/** - * Returns the value at the provided key path starting at the provided - * collection, or notSetValue if the key path is not defined. - * - * A functional alternative to `collection.getIn(keypath)` which will also - * work with plain Objects and Arrays. - * - * - * ```js - * import { getIn } from 'immutable'; - * - * getIn({ x: { y: { z: 123 }}}, ['x', 'y', 'z']) // 123 - * getIn({ x: { y: { z: 123 }}}, ['x', 'q', 'p'], 'ifNotSet') // 'ifNotSet' - * ``` - */ -export function getIn( - collection: CollectionType, - searchKeyPath: KeyPath, - notSetValue?: GetTypeParameters[2] -): ReturnType { - const keyPath = coerceKeyPath(searchKeyPath); - let i = 0; - while (i !== keyPath.length) { - // @ts-expect-error keyPath[i++] can not be undefined by design - collection = get(collection, keyPath[i++], NOT_SET); - if (collection === NOT_SET) { - return notSetValue; - } - } - return collection; -} diff --git a/src/functional/has.ts b/src/functional/has.ts deleted file mode 100644 index 322370314e..0000000000 --- a/src/functional/has.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { isImmutable } from '../predicates/isImmutable'; -import hasOwnProperty from '../utils/hasOwnProperty'; -import isDataStructure from '../utils/isDataStructure'; - -/** - * Returns true if the key is defined in the provided collection. - * - * A functional alternative to `collection.has(key)` which will also work with - * plain Objects and Arrays as an alternative for - * `collection.hasOwnProperty(key)`. - * - * - * ```js - * import { has } from 'immutable'; - * - * has([ 'dog', 'frog', 'cat' ], 2) // true - * has([ 'dog', 'frog', 'cat' ], 5) // false - * has({ x: 123, y: 456 }, 'x') // true - * has({ x: 123, y: 456 }, 'z') // false - * ``` - */ -export function has(collection: object, key: unknown): boolean { - return isImmutable(collection) - ? // @ts-expect-error key might be a number or symbol, which is not handled be Record key type - collection.has(key) - : // @ts-expect-error key might be anything else than PropertyKey, and will return false in that case but runtime is OK - isDataStructure(collection) && hasOwnProperty.call(collection, key); -} diff --git a/src/functional/hasIn.ts b/src/functional/hasIn.ts deleted file mode 100644 index 28041a7323..0000000000 --- a/src/functional/hasIn.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { getIn } from './getIn'; -import { NOT_SET } from '../TrieUtils'; - -type GetInParameters = Parameters; - -/** - * Returns true if the key path is defined in the provided collection. - * - * A functional alternative to `collection.hasIn(keypath)` which will also - * work with plain Objects and Arrays. - * - * - * ```js - * import { hasIn } from 'immutable'; - * - * hasIn({ x: { y: { z: 123 }}}, ['x', 'y', 'z']) // true - * hasIn({ x: { y: { z: 123 }}}, ['x', 'q', 'p']) // false - * ``` - */ -export function hasIn( - collection: GetInParameters[0], - keyPath: GetInParameters[1] -): boolean { - return getIn(collection, keyPath, NOT_SET) !== NOT_SET; -} diff --git a/src/functional/merge.js b/src/functional/merge.js deleted file mode 100644 index b2d231a0c9..0000000000 --- a/src/functional/merge.js +++ /dev/null @@ -1,99 +0,0 @@ -import { isImmutable } from '../predicates/isImmutable'; -import { isIndexed } from '../predicates/isIndexed'; -import { isKeyed } from '../predicates/isKeyed'; -import { IndexedCollection, KeyedCollection } from '../Collection'; -import { Seq } from '../Seq'; -import hasOwnProperty from '../utils/hasOwnProperty'; -import isDataStructure from '../utils/isDataStructure'; -import shallowCopy from '../utils/shallowCopy'; - -export function merge(collection, ...sources) { - return mergeWithSources(collection, sources); -} - -export function mergeWith(merger, collection, ...sources) { - return mergeWithSources(collection, sources, merger); -} - -export function mergeDeep(collection, ...sources) { - return mergeDeepWithSources(collection, sources); -} - -export function mergeDeepWith(merger, collection, ...sources) { - return mergeDeepWithSources(collection, sources, merger); -} - -export function mergeDeepWithSources(collection, sources, merger) { - return mergeWithSources(collection, sources, deepMergerWith(merger)); -} - -export function mergeWithSources(collection, sources, merger) { - if (!isDataStructure(collection)) { - throw new TypeError( - 'Cannot merge into non-data-structure value: ' + collection - ); - } - if (isImmutable(collection)) { - return typeof merger === 'function' && collection.mergeWith - ? collection.mergeWith(merger, ...sources) - : collection.merge - ? collection.merge(...sources) - : collection.concat(...sources); - } - const isArray = Array.isArray(collection); - let merged = collection; - const Collection = isArray ? IndexedCollection : KeyedCollection; - const mergeItem = isArray - ? (value) => { - // Copy on write - if (merged === collection) { - merged = shallowCopy(merged); - } - merged.push(value); - } - : (value, key) => { - const hasVal = hasOwnProperty.call(merged, key); - const nextVal = - hasVal && merger ? merger(merged[key], value, key) : value; - if (!hasVal || nextVal !== merged[key]) { - // Copy on write - if (merged === collection) { - merged = shallowCopy(merged); - } - merged[key] = nextVal; - } - }; - for (let i = 0; i < sources.length; i++) { - Collection(sources[i]).forEach(mergeItem); - } - return merged; -} - -function deepMergerWith(merger) { - function deepMerger(oldValue, newValue, key) { - return isDataStructure(oldValue) && - isDataStructure(newValue) && - areMergeable(oldValue, newValue) - ? mergeWithSources(oldValue, [newValue], deepMerger) - : merger - ? merger(oldValue, newValue, key) - : newValue; - } - return deepMerger; -} - -/** - * It's unclear what the desired behavior is for merging two collections that - * fall into separate categories between keyed, indexed, or set-like, so we only - * consider them mergeable if they fall into the same category. - */ -function areMergeable(oldDataStructure, newDataStructure) { - const oldSeq = Seq(oldDataStructure); - const newSeq = Seq(newDataStructure); - // This logic assumes that a sequence can only fall into one of the three - // categories mentioned above (since there's no `isSetLike()` method). - return ( - isIndexed(oldSeq) === isIndexed(newSeq) && - isKeyed(oldSeq) === isKeyed(newSeq) - ); -} diff --git a/src/functional/remove.ts b/src/functional/remove.ts deleted file mode 100644 index a562864b82..0000000000 --- a/src/functional/remove.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { Collection, Record } from '../../type-definitions/immutable'; -import { isImmutable } from '../predicates/isImmutable'; -import hasOwnProperty from '../utils/hasOwnProperty'; -import isDataStructure from '../utils/isDataStructure'; -import shallowCopy from '../utils/shallowCopy'; - -/** - * Returns a copy of the collection with the value at key removed. - * - * A functional alternative to `collection.remove(key)` which will also work - * with plain Objects and Arrays as an alternative for - * `delete collectionCopy[key]`. - * - * - * ```js - * import { remove } from 'immutable'; - * - * const originalArray = [ 'dog', 'frog', 'cat' ] - * remove(originalArray, 1) // [ 'dog', 'cat' ] - * console.log(originalArray) // [ 'dog', 'frog', 'cat' ] - * const originalObject = { x: 123, y: 456 } - * remove(originalObject, 'x') // { y: 456 } - * console.log(originalObject) // { x: 123, y: 456 } - * ``` - */ -export function remove>( - collection: C, - key: K -): C; -export function remove< - TProps extends object, - C extends Record, - K extends keyof TProps, ->(collection: C, key: K): C; -export function remove>(collection: C, key: number): C; -export function remove(collection: C, key: K): C; -export function remove< - C extends { [key: PropertyKey]: unknown }, - K extends keyof C, ->(collection: C, key: K): C; -export function remove< - K extends PropertyKey, - C extends - | Collection - | Array - | { [key: PropertyKey]: unknown }, ->(collection: C, key: K): C; -export function remove( - collection: - | Collection - | Array - | { [key: PropertyKey]: unknown }, - key: K -) { - if (!isDataStructure(collection)) { - throw new TypeError( - 'Cannot update non-data-structure value: ' + collection - ); - } - if (isImmutable(collection)) { - // @ts-expect-error weird "remove" here, - if (!collection.remove) { - throw new TypeError( - 'Cannot update immutable value without .remove() method: ' + collection - ); - } - // @ts-expect-error weird "remove" here, - return collection.remove(key); - } - if (!hasOwnProperty.call(collection, key)) { - return collection; - } - const collectionCopy = shallowCopy(collection); - if (Array.isArray(collectionCopy)) { - // @ts-expect-error assert that key is a number here - collectionCopy.splice(key, 1); - } else { - delete collectionCopy[key]; - } - return collectionCopy; -} diff --git a/src/functional/removeIn.ts b/src/functional/removeIn.ts deleted file mode 100644 index 3b97be0dcd..0000000000 --- a/src/functional/removeIn.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { updateIn, type PossibleCollection } from './updateIn'; -import { NOT_SET } from '../TrieUtils'; -import type { KeyPath } from '../../type-definitions/immutable'; - -/** - * Returns a copy of the collection with the value at the key path removed. - * - * A functional alternative to `collection.removeIn(keypath)` which will also - * work with plain Objects and Arrays. - * - * - * ```js - * import { removeIn } from 'immutable'; - * - * const original = { x: { y: { z: 123 }}} - * removeIn(original, ['x', 'y', 'z']) // { x: { y: {}}} - * console.log(original) // { x: { y: { z: 123 }}} - * ``` - */ -export function removeIn< - K extends PropertyKey, - V, - TProps extends object, - C extends PossibleCollection, ->(collection: C, keyPath: KeyPath): C { - return updateIn(collection, keyPath, () => NOT_SET); -} diff --git a/src/functional/set.ts b/src/functional/set.ts deleted file mode 100644 index e8e688abd0..0000000000 --- a/src/functional/set.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { Collection, Record } from '../../type-definitions/immutable'; -import { isImmutable } from '../predicates/isImmutable'; -import hasOwnProperty from '../utils/hasOwnProperty'; -import isDataStructure from '../utils/isDataStructure'; -import shallowCopy from '../utils/shallowCopy'; - -/** - * Returns a copy of the collection with the value at key set to the provided - * value. - * - * A functional alternative to `collection.set(key, value)` which will also - * work with plain Objects and Arrays as an alternative for - * `collectionCopy[key] = value`. - * - * - * ```js - * import { set } from 'immutable'; - * - * const originalArray = [ 'dog', 'frog', 'cat' ] - * set(originalArray, 1, 'cow') // [ 'dog', 'cow', 'cat' ] - * console.log(originalArray) // [ 'dog', 'frog', 'cat' ] - * const originalObject = { x: 123, y: 456 } - * set(originalObject, 'x', 789) // { x: 789, y: 456 } - * console.log(originalObject) // { x: 123, y: 456 } - * ``` - */ -export function set>( - collection: C, - key: K, - value: V -): C; -export function set< - TProps extends object, - C extends Record, - K extends keyof TProps, ->(record: C, key: K, value: TProps[K]): C; -export function set>( - collection: C, - key: number, - value: V -): C; -export function set(object: C, key: K, value: C[K]): C; -export function set( - collection: C, - key: string, - value: V -): C; -export function set | { [key: string]: V }>( - collection: C, - key: K | string, - value: V -): C { - if (!isDataStructure(collection)) { - throw new TypeError( - 'Cannot update non-data-structure value: ' + collection - ); - } - if (isImmutable(collection)) { - // @ts-expect-error weird "set" here, - if (!collection.set) { - throw new TypeError( - 'Cannot update immutable value without .set() method: ' + collection - ); - } - // @ts-expect-error weird "set" here, - return collection.set(key, value); - } - // @ts-expect-error mix of key and string here. Probably need a more fine type here - if (hasOwnProperty.call(collection, key) && value === collection[key]) { - return collection; - } - const collectionCopy = shallowCopy(collection); - // @ts-expect-error mix of key and string here. Probably need a more fine type here - collectionCopy[key] = value; - return collectionCopy; -} diff --git a/src/functional/setIn.ts b/src/functional/setIn.ts deleted file mode 100644 index 7955e3e38d..0000000000 --- a/src/functional/setIn.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { updateIn, type PossibleCollection } from './updateIn'; -import { NOT_SET } from '../TrieUtils'; -import type { KeyPath } from '../../type-definitions/immutable'; - -/** - * Returns a copy of the collection with the value at the key path set to the - * provided value. - * - * A functional alternative to `collection.setIn(keypath)` which will also - * work with plain Objects and Arrays. - * - * - * ```js - * import { setIn } from 'immutable'; - * - * const original = { x: { y: { z: 123 }}} - * setIn(original, ['x', 'y', 'z'], 456) // { x: { y: { z: 456 }}} - * console.log(original) // { x: { y: { z: 123 }}} - * ``` - */ -export function setIn< - K extends PropertyKey, - V, - TProps extends object, - C extends PossibleCollection, ->(collection: C, keyPath: KeyPath, value: unknown): C { - return updateIn(collection, keyPath, NOT_SET, () => value); -} diff --git a/src/functional/update.ts b/src/functional/update.ts deleted file mode 100644 index 81d5426539..0000000000 --- a/src/functional/update.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { Collection, Record } from '../../type-definitions/immutable'; -import { updateIn, type PossibleCollection } from './updateIn'; - -type UpdaterFunction = (value: V | undefined) => V | undefined; -type UpdaterFunctionWithNSV = (value: V | NSV) => V; - -/** - * Returns a copy of the collection with the value at key set to the result of - * providing the existing value to the updating function. - * - * A functional alternative to `collection.update(key, fn)` which will also - * work with plain Objects and Arrays as an alternative for - * `collectionCopy[key] = fn(collection[key])`. - * - * - * ```js - * import { update } from 'immutable'; - * - * const originalArray = [ 'dog', 'frog', 'cat' ] - * update(originalArray, 1, val => val.toUpperCase()) // [ 'dog', 'FROG', 'cat' ] - * console.log(originalArray) // [ 'dog', 'frog', 'cat' ] - * const originalObject = { x: 123, y: 456 } - * update(originalObject, 'x', val => val * 6) // { x: 738, y: 456 } - * console.log(originalObject) // { x: 123, y: 456 } - * ``` - */ -export function update>( - collection: C, - key: K, - updater: (value: V | undefined) => V | undefined -): C; -export function update, NSV>( - collection: C, - key: K, - notSetValue: NSV, - updater: (value: V | NSV) => V -): C; -export function update< - TProps extends object, - C extends Record, - K extends keyof TProps, ->(record: C, key: K, updater: (value: TProps[K]) => TProps[K]): C; -export function update< - TProps extends object, - C extends Record, - K extends keyof TProps, - NSV, ->( - record: C, - key: K, - notSetValue: NSV, - updater: (value: TProps[K] | NSV) => TProps[K] -): C; -export function update>( - collection: C, - key: number, - updater: UpdaterFunction -): C; -export function update, NSV>( - collection: C, - key: number, - notSetValue: NSV, - updater: (value: V | NSV) => V -): C; -export function update( - object: C, - key: K, - updater: (value: C[K]) => C[K] -): C; -export function update( - object: C, - key: K, - notSetValue: NSV, - updater: (value: C[K] | NSV) => C[K] -): C; -export function update( - collection: C, - key: K, - updater: (value: V) => V -): { [key: string]: V }; -export function update< - V, - C extends { [key: string]: V }, - K extends keyof C, - NSV, ->( - collection: C, - key: K, - notSetValue: NSV, - updater: (value: V | NSV) => V -): { [key: string]: V }; - -export function update< - K, - V, - TProps extends object, - C extends PossibleCollection, - NSV, ->( - collection: C, - key: K, - notSetValue: NSV | UpdaterFunction, - updater?: UpdaterFunctionWithNSV -) { - return updateIn( - // @ts-expect-error Index signature for type string is missing in type V[] - collection, - [key], - notSetValue, - updater - ); -} diff --git a/src/functional/updateIn.ts b/src/functional/updateIn.ts deleted file mode 100644 index eab7df0b00..0000000000 --- a/src/functional/updateIn.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { isImmutable } from '../predicates/isImmutable'; -import coerceKeyPath from '../utils/coerceKeyPath'; -import isDataStructure from '../utils/isDataStructure'; -import quoteString from '../utils/quoteString'; -import { NOT_SET } from '../TrieUtils'; -import { emptyMap } from '../Map'; -import { get } from './get'; -import { remove } from './remove'; -import { set } from './set'; -import type { - Collection, - KeyPath, - Record, - RetrievePath, -} from '../../type-definitions/immutable'; - -/** - * Returns a copy of the collection with the value at key path set to the - * result of providing the existing value to the updating function. - * - * A functional alternative to `collection.updateIn(keypath)` which will also - * work with plain Objects and Arrays. - * - * - * ```js - * import { updateIn } from 'immutable' - * - * const original = { x: { y: { z: 123 }}} - * updateIn(original, ['x', 'y', 'z'], val => val * 6) // { x: { y: { z: 738 }}} - * console.log(original) // { x: { y: { z: 123 }}} - * ``` - */ - -export type PossibleCollection = - | Collection - | Record - | Array; - -type UpdaterFunction = ( - value: RetrievePath> | undefined -) => unknown | undefined; -type UpdaterFunctionWithNSV = ( - value: RetrievePath> | NSV -) => unknown; - -export function updateIn>( - collection: C, - keyPath: KeyPath, - updater: UpdaterFunction -): C; -export function updateIn< - K extends PropertyKey, - V, - C extends Collection, - NSV, ->( - collection: C, - keyPath: KeyPath, - notSetValue: NSV, - updater: UpdaterFunctionWithNSV -): C; -export function updateIn< - TProps extends object, - C extends Record, - K extends keyof TProps, ->(record: C, keyPath: KeyPath, updater: UpdaterFunction): C; -export function updateIn< - TProps extends object, - C extends Record, - K extends keyof TProps, - NSV, ->( - record: C, - keyPath: KeyPath, - notSetValue: NSV, - updater: UpdaterFunctionWithNSV -): C; -export function updateIn>( - collection: C, - keyPath: KeyPath, - updater: UpdaterFunction -): Array; -export function updateIn, NSV>( - collection: C, - keyPath: KeyPath, - notSetValue: NSV, - updater: UpdaterFunctionWithNSV -): Array; -export function updateIn( - object: C, - keyPath: KeyPath, - updater: UpdaterFunction -): C; -export function updateIn( - object: C, - keyPath: KeyPath, - notSetValue: NSV, - updater: UpdaterFunctionWithNSV -): C; -export function updateIn< - K extends PropertyKey, - V, - C extends { [key: PropertyKey]: V }, ->( - collection: C, - keyPath: KeyPath, - updater: UpdaterFunction -): { [key: PropertyKey]: V }; -export function updateIn< - K extends PropertyKey, - V, - C extends { [key: PropertyKey]: V }, - NSV, ->( - collection: C, - keyPath: KeyPath, - notSetValue: NSV, - updater: UpdaterFunction -): { [key: PropertyKey]: V }; - -export function updateIn< - K extends PropertyKey, - V, - TProps extends object, - C extends PossibleCollection, - NSV, ->( - collection: C, - keyPath: KeyPath, - notSetValue: NSV | UpdaterFunction | undefined, - updater?: UpdaterFunctionWithNSV -): C { - if (!updater) { - // handle the fact that `notSetValue` is optional here, in that case `updater` is the updater function - // @ts-expect-error updater is a function here - updater = notSetValue as UpdaterFunction; - notSetValue = undefined; - } - const updatedValue = updateInDeeply( - isImmutable(collection), - // @ts-expect-error type issues with Record and mixed types - collection, - coerceKeyPath(keyPath), - 0, - notSetValue, - updater - ); - // @ts-expect-error mixed return type - return updatedValue === NOT_SET ? notSetValue : updatedValue; -} - -function updateInDeeply< - K extends PropertyKey, - TProps extends object, - C extends PossibleCollection, - NSV, ->( - inImmutable: boolean, - existing: C, - keyPath: Array, - i: number, - notSetValue: NSV | undefined, - updater: UpdaterFunctionWithNSV | UpdaterFunction -): C { - const wasNotSet = existing === NOT_SET; - if (i === keyPath.length) { - const existingValue = wasNotSet ? notSetValue : existing; - // @ts-expect-error mixed type with optional value - const newValue = updater(existingValue); - // @ts-expect-error mixed type - return newValue === existingValue ? existing : newValue; - } - if (!wasNotSet && !isDataStructure(existing)) { - throw new TypeError( - 'Cannot update within non-data-structure value in path [' + - Array.from(keyPath).slice(0, i).map(quoteString) + - ']: ' + - existing - ); - } - const key = keyPath[i]; - - if (typeof key === 'undefined') { - throw new TypeError( - 'Index can not be undefined in updateIn(). This should not happen' - ); - } - - const nextExisting = wasNotSet ? NOT_SET : get(existing, key, NOT_SET); - const nextUpdated = updateInDeeply( - nextExisting === NOT_SET ? inImmutable : isImmutable(nextExisting), - // @ts-expect-error mixed type - nextExisting, - keyPath, - i + 1, - notSetValue, - updater - ); - return nextUpdated === nextExisting - ? existing - : nextUpdated === NOT_SET - ? remove(existing, key) - : set( - wasNotSet ? (inImmutable ? emptyMap() : {}) : existing, - key, - nextUpdated - ); -} diff --git a/src/is.ts b/src/is.ts deleted file mode 100644 index f4430c52fd..0000000000 --- a/src/is.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { isValueObject } from './predicates/isValueObject'; - -/** - * An extension of the "same-value" algorithm as [described for use by ES6 Map - * and Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Key_equality) - * - * NaN is considered the same as NaN, however -0 and 0 are considered the same - * value, which is different from the algorithm described by - * [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is). - * - * This is extended further to allow Objects to describe the values they - * represent, by way of `valueOf` or `equals` (and `hashCode`). - * - * Note: because of this extension, the key equality of Immutable.Map and the - * value equality of Immutable.Set will differ from ES6 Map and Set. - * - * ### Defining custom values - * - * The easiest way to describe the value an object represents is by implementing - * `valueOf`. For example, `Date` represents a value by returning a unix - * timestamp for `valueOf`: - * - * var date1 = new Date(1234567890000); // Fri Feb 13 2009 ... - * var date2 = new Date(1234567890000); - * date1.valueOf(); // 1234567890000 - * assert( date1 !== date2 ); - * assert( Immutable.is( date1, date2 ) ); - * - * Note: overriding `valueOf` may have other implications if you use this object - * where JavaScript expects a primitive, such as implicit string coercion. - * - * For more complex types, especially collections, implementing `valueOf` may - * not be performant. An alternative is to implement `equals` and `hashCode`. - * - * `equals` takes another object, presumably of similar type, and returns true - * if it is equal. Equality is symmetrical, so the same result should be - * returned if this and the argument are flipped. - * - * assert( a.equals(b) === b.equals(a) ); - * - * `hashCode` returns a 32bit integer number representing the object which will - * be used to determine how to store the value object in a Map or Set. You must - * provide both or neither methods, one must not exist without the other. - * - * Also, an important relationship between these methods must be upheld: if two - * values are equal, they *must* return the same hashCode. If the values are not - * equal, they might have the same hashCode; this is called a hash collision, - * and while undesirable for performance reasons, it is acceptable. - * - * if (a.equals(b)) { - * assert( a.hashCode() === b.hashCode() ); - * } - * - * All Immutable collections are Value Objects: they implement `equals()` - * and `hashCode()`. - */ -export function is(valueA: unknown, valueB: unknown): boolean { - if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) { - return true; - } - if (!valueA || !valueB) { - return false; - } - if ( - typeof valueA.valueOf === 'function' && - typeof valueB.valueOf === 'function' - ) { - valueA = valueA.valueOf(); - valueB = valueB.valueOf(); - if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) { - return true; - } - if (!valueA || !valueB) { - return false; - } - } - return !!( - isValueObject(valueA) && - isValueObject(valueB) && - valueA.equals(valueB) - ); -} diff --git a/src/methods/README.md b/src/methods/README.md deleted file mode 100644 index df2d26482f..0000000000 --- a/src/methods/README.md +++ /dev/null @@ -1,5 +0,0 @@ -These files represent common methods on Collection types, each will contain -references to "this" - expecting to be called as a prototypal method. - -They are separated into individual files to avoid circular dependencies when -possible, and to allow their use in multiple different Collections. diff --git a/src/methods/asImmutable.js b/src/methods/asImmutable.js deleted file mode 100644 index 71ba0d2bf5..0000000000 --- a/src/methods/asImmutable.js +++ /dev/null @@ -1,3 +0,0 @@ -export function asImmutable() { - return this.__ensureOwner(); -} diff --git a/src/methods/asMutable.js b/src/methods/asMutable.js deleted file mode 100644 index 2e7abf576a..0000000000 --- a/src/methods/asMutable.js +++ /dev/null @@ -1,5 +0,0 @@ -import { OwnerID } from '../TrieUtils'; - -export function asMutable() { - return this.__ownerID ? this : this.__ensureOwner(new OwnerID()); -} diff --git a/src/methods/deleteIn.js b/src/methods/deleteIn.js deleted file mode 100644 index 5a312ce8f9..0000000000 --- a/src/methods/deleteIn.js +++ /dev/null @@ -1,5 +0,0 @@ -import { removeIn } from '../functional/removeIn'; - -export function deleteIn(keyPath) { - return removeIn(this, keyPath); -} diff --git a/src/methods/getIn.js b/src/methods/getIn.js deleted file mode 100644 index e202bae92c..0000000000 --- a/src/methods/getIn.js +++ /dev/null @@ -1,5 +0,0 @@ -import { getIn as _getIn } from '../functional/getIn'; - -export function getIn(searchKeyPath, notSetValue) { - return _getIn(this, searchKeyPath, notSetValue); -} diff --git a/src/methods/hasIn.js b/src/methods/hasIn.js deleted file mode 100644 index 704dc5c80f..0000000000 --- a/src/methods/hasIn.js +++ /dev/null @@ -1,5 +0,0 @@ -import { hasIn as _hasIn } from '../functional/hasIn'; - -export function hasIn(searchKeyPath) { - return _hasIn(this, searchKeyPath); -} diff --git a/src/methods/merge.js b/src/methods/merge.js deleted file mode 100644 index c89dad4c3a..0000000000 --- a/src/methods/merge.js +++ /dev/null @@ -1,48 +0,0 @@ -import { KeyedCollection } from '../Collection'; -import { NOT_SET } from '../TrieUtils'; -import { update } from '../functional/update'; - -export function merge(...iters) { - return mergeIntoKeyedWith(this, iters); -} - -export function mergeWith(merger, ...iters) { - if (typeof merger !== 'function') { - throw new TypeError('Invalid merger function: ' + merger); - } - return mergeIntoKeyedWith(this, iters, merger); -} - -function mergeIntoKeyedWith(collection, collections, merger) { - const iters = []; - for (let ii = 0; ii < collections.length; ii++) { - const collection = KeyedCollection(collections[ii]); - if (collection.size !== 0) { - iters.push(collection); - } - } - if (iters.length === 0) { - return collection; - } - if ( - collection.toSeq().size === 0 && - !collection.__ownerID && - iters.length === 1 - ) { - return collection.create(iters[0]); - } - return collection.withMutations((collection) => { - const mergeIntoCollection = merger - ? (value, key) => { - update(collection, key, NOT_SET, (oldVal) => - oldVal === NOT_SET ? value : merger(oldVal, value, key) - ); - } - : (value, key) => { - collection.set(key, value); - }; - for (let ii = 0; ii < iters.length; ii++) { - iters[ii].forEach(mergeIntoCollection); - } - }); -} diff --git a/src/methods/mergeDeep.js b/src/methods/mergeDeep.js deleted file mode 100644 index e2238f664c..0000000000 --- a/src/methods/mergeDeep.js +++ /dev/null @@ -1,9 +0,0 @@ -import { mergeDeepWithSources } from '../functional/merge'; - -export function mergeDeep(...iters) { - return mergeDeepWithSources(this, iters); -} - -export function mergeDeepWith(merger, ...iters) { - return mergeDeepWithSources(this, iters, merger); -} diff --git a/src/methods/mergeDeepIn.js b/src/methods/mergeDeepIn.js deleted file mode 100644 index 119369b7ba..0000000000 --- a/src/methods/mergeDeepIn.js +++ /dev/null @@ -1,9 +0,0 @@ -import { mergeDeepWithSources } from '../functional/merge'; -import { updateIn } from '../functional/updateIn'; -import { emptyMap } from '../Map'; - -export function mergeDeepIn(keyPath, ...iters) { - return updateIn(this, keyPath, emptyMap(), (m) => - mergeDeepWithSources(m, iters) - ); -} diff --git a/src/methods/mergeIn.js b/src/methods/mergeIn.js deleted file mode 100644 index 9af239c4d0..0000000000 --- a/src/methods/mergeIn.js +++ /dev/null @@ -1,7 +0,0 @@ -import { mergeWithSources } from '../functional/merge'; -import { updateIn } from '../functional/updateIn'; -import { emptyMap } from '../Map'; - -export function mergeIn(keyPath, ...iters) { - return updateIn(this, keyPath, emptyMap(), (m) => mergeWithSources(m, iters)); -} diff --git a/src/methods/setIn.js b/src/methods/setIn.js deleted file mode 100644 index 3ea05b89d6..0000000000 --- a/src/methods/setIn.js +++ /dev/null @@ -1,5 +0,0 @@ -import { setIn as _setIn } from '../functional/setIn'; - -export function setIn(keyPath, v) { - return _setIn(this, keyPath, v); -} diff --git a/src/methods/toObject.js b/src/methods/toObject.js deleted file mode 100644 index fb927f0e48..0000000000 --- a/src/methods/toObject.js +++ /dev/null @@ -1,10 +0,0 @@ -import assertNotInfinite from '../utils/assertNotInfinite'; - -export function toObject() { - assertNotInfinite(this.size); - const object = {}; - this.__iterate((v, k) => { - object[k] = v; - }); - return object; -} diff --git a/src/methods/update.js b/src/methods/update.js deleted file mode 100644 index 0d533f7037..0000000000 --- a/src/methods/update.js +++ /dev/null @@ -1,7 +0,0 @@ -import { update as _update } from '../functional/update'; - -export function update(key, notSetValue, updater) { - return arguments.length === 1 - ? key(this) - : _update(this, key, notSetValue, updater); -} diff --git a/src/methods/updateIn.js b/src/methods/updateIn.js deleted file mode 100644 index 8df1860868..0000000000 --- a/src/methods/updateIn.js +++ /dev/null @@ -1,5 +0,0 @@ -import { updateIn as _updateIn } from '../functional/updateIn'; - -export function updateIn(keyPath, notSetValue, updater) { - return _updateIn(this, keyPath, notSetValue, updater); -} diff --git a/src/methods/wasAltered.js b/src/methods/wasAltered.js deleted file mode 100644 index 165a3e3ab2..0000000000 --- a/src/methods/wasAltered.js +++ /dev/null @@ -1,3 +0,0 @@ -export function wasAltered() { - return this.__altered; -} diff --git a/src/methods/withMutations.js b/src/methods/withMutations.js deleted file mode 100644 index 59c5cc83dc..0000000000 --- a/src/methods/withMutations.js +++ /dev/null @@ -1,5 +0,0 @@ -export function withMutations(fn) { - const mutable = this.asMutable(); - fn(mutable); - return mutable.wasAltered() ? mutable.__ensureOwner(this.__ownerID) : this; -} diff --git a/src/predicates/isAssociative.ts b/src/predicates/isAssociative.ts deleted file mode 100644 index e174616f7b..0000000000 --- a/src/predicates/isAssociative.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { isKeyed } from './isKeyed'; -import { isIndexed } from './isIndexed'; -import type { Collection } from '../../type-definitions/immutable'; - -/** - * True if `maybeAssociative` is either a Keyed or Indexed Collection. - * - * ```js - * import { isAssociative, Map, List, Stack, Set } from 'immutable'; - * - * isAssociative([]); // false - * isAssociative({}); // false - * isAssociative(Map()); // true - * isAssociative(List()); // true - * isAssociative(Stack()); // true - * isAssociative(Set()); // false - * ``` - */ -export function isAssociative( - maybeAssociative: unknown -): maybeAssociative is - | Collection.Keyed - | Collection.Indexed { - return isKeyed(maybeAssociative) || isIndexed(maybeAssociative); -} diff --git a/src/predicates/isCollection.ts b/src/predicates/isCollection.ts deleted file mode 100644 index 738a4614e0..0000000000 --- a/src/predicates/isCollection.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Collection } from '../../type-definitions/immutable'; - -// Note: value is unchanged to not break immutable-devtools. -export const IS_COLLECTION_SYMBOL = '@@__IMMUTABLE_ITERABLE__@@'; - -/** - * True if `maybeCollection` is a Collection, or any of its subclasses. - * - * ```js - * import { isCollection, Map, List, Stack } from 'immutable'; - * - * isCollection([]); // false - * isCollection({}); // false - * isCollection(Map()); // true - * isCollection(List()); // true - * isCollection(Stack()); // true - * ``` - */ -export function isCollection( - maybeCollection: unknown -): maybeCollection is Collection { - return Boolean( - maybeCollection && - // @ts-expect-error: maybeCollection is typed as `{}`, need to change in 6.0 to `maybeCollection && typeof maybeCollection === 'object' && IS_COLLECTION_SYMBOL in maybeCollection` - maybeCollection[IS_COLLECTION_SYMBOL] - ); -} diff --git a/src/predicates/isImmutable.ts b/src/predicates/isImmutable.ts deleted file mode 100644 index d01bd03afb..0000000000 --- a/src/predicates/isImmutable.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Collection, Record } from '../../type-definitions/immutable'; -import { isCollection } from './isCollection'; -import { isRecord } from './isRecord'; - -/** - * True if `maybeImmutable` is an Immutable Collection or Record. - * - * Note: Still returns true even if the collections is within a `withMutations()`. - * - * ```js - * import { isImmutable, Map, List, Stack } from 'immutable'; - * isImmutable([]); // false - * isImmutable({}); // false - * isImmutable(Map()); // true - * isImmutable(List()); // true - * isImmutable(Stack()); // true - * isImmutable(Map().asMutable()); // true - * ``` - */ -export function isImmutable( - maybeImmutable: unknown -): maybeImmutable is Collection | Record { - return isCollection(maybeImmutable) || isRecord(maybeImmutable); -} diff --git a/src/predicates/isIndexed.ts b/src/predicates/isIndexed.ts deleted file mode 100644 index 3e20595af5..0000000000 --- a/src/predicates/isIndexed.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Collection } from '../../type-definitions/immutable'; - -export const IS_INDEXED_SYMBOL = '@@__IMMUTABLE_INDEXED__@@'; - -/** - * True if `maybeIndexed` is a Collection.Indexed, or any of its subclasses. - * - * ```js - * import { isIndexed, Map, List, Stack, Set } from 'immutable'; - * - * isIndexed([]); // false - * isIndexed({}); // false - * isIndexed(Map()); // false - * isIndexed(List()); // true - * isIndexed(Stack()); // true - * isIndexed(Set()); // false - * ``` - */ -export function isIndexed( - maybeIndexed: unknown -): maybeIndexed is Collection.Indexed { - return Boolean( - maybeIndexed && - // @ts-expect-error: maybeIndexed is typed as `{}`, need to change in 6.0 to `maybeIndexed && typeof maybeIndexed === 'object' && IS_INDEXED_SYMBOL in maybeIndexed` - maybeIndexed[IS_INDEXED_SYMBOL] - ); -} diff --git a/src/predicates/isKeyed.ts b/src/predicates/isKeyed.ts deleted file mode 100644 index 35f7b7e9c8..0000000000 --- a/src/predicates/isKeyed.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { Collection } from '../../type-definitions/immutable'; - -export const IS_KEYED_SYMBOL = '@@__IMMUTABLE_KEYED__@@'; - -/** - * True if `maybeKeyed` is a Collection.Keyed, or any of its subclasses. - * - * ```js - * import { isKeyed, Map, List, Stack } from 'immutable'; - * - * isKeyed([]); // false - * isKeyed({}); // false - * isKeyed(Map()); // true - * isKeyed(List()); // false - * isKeyed(Stack()); // false - * ``` - */ -export function isKeyed( - maybeKeyed: unknown -): maybeKeyed is Collection.Keyed { - return Boolean( - maybeKeyed && - // @ts-expect-error: maybeKeyed is typed as `{}`, need to change in 6.0 to `maybeKeyed && typeof maybeKeyed === 'object' && IS_KEYED_SYMBOL in maybeKeyed` - maybeKeyed[IS_KEYED_SYMBOL] - ); -} diff --git a/src/predicates/isList.ts b/src/predicates/isList.ts deleted file mode 100644 index 080427eb2a..0000000000 --- a/src/predicates/isList.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { List } from '../../type-definitions/immutable'; - -export const IS_LIST_SYMBOL = '@@__IMMUTABLE_LIST__@@'; - -/** - * True if `maybeList` is a List. - */ -export function isList(maybeList: unknown): maybeList is List { - return Boolean( - maybeList && - // @ts-expect-error: maybeList is typed as `{}`, need to change in 6.0 to `maybeList && typeof maybeList === 'object' && IS_LIST_SYMBOL in maybeList` - maybeList[IS_LIST_SYMBOL] - ); -} diff --git a/src/predicates/isMap.ts b/src/predicates/isMap.ts deleted file mode 100644 index ef96c7e138..0000000000 --- a/src/predicates/isMap.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Map } from '../../type-definitions/immutable'; - -export const IS_MAP_SYMBOL = '@@__IMMUTABLE_MAP__@@'; - -/** - * True if `maybeMap` is a Map. - * - * Also true for OrderedMaps. - */ -export function isMap(maybeMap: unknown): maybeMap is Map { - return Boolean( - maybeMap && - // @ts-expect-error: maybeMap is typed as `{}`, need to change in 6.0 to `maybeMap && typeof maybeMap === 'object' && IS_MAP_SYMBOL in maybeMap` - maybeMap[IS_MAP_SYMBOL] - ); -} diff --git a/src/predicates/isOrdered.ts b/src/predicates/isOrdered.ts deleted file mode 100644 index 2e20d415ff..0000000000 --- a/src/predicates/isOrdered.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { OrderedCollection } from '../../type-definitions/immutable'; - -export const IS_ORDERED_SYMBOL = '@@__IMMUTABLE_ORDERED__@@'; - -/** - * True if `maybeOrdered` is a Collection where iteration order is well - * defined. True for Collection.Indexed as well as OrderedMap and OrderedSet. - * - * ```js - * import { isOrdered, Map, OrderedMap, List, Set } from 'immutable'; - * - * isOrdered([]); // false - * isOrdered({}); // false - * isOrdered(Map()); // false - * isOrdered(OrderedMap()); // true - * isOrdered(List()); // true - * isOrdered(Set()); // false - * ``` - */ -export function isOrdered( - maybeOrdered: Iterable -): maybeOrdered is OrderedCollection; -export function isOrdered( - maybeOrdered: unknown -): maybeOrdered is OrderedCollection { - return Boolean( - maybeOrdered && - // @ts-expect-error: maybeOrdered is typed as `{}`, need to change in 6.0 to `maybeOrdered && typeof maybeOrdered === 'object' && IS_ORDERED_SYMBOL in maybeOrdered` - maybeOrdered[IS_ORDERED_SYMBOL] - ); -} diff --git a/src/predicates/isOrderedMap.ts b/src/predicates/isOrderedMap.ts deleted file mode 100644 index ac56e200b5..0000000000 --- a/src/predicates/isOrderedMap.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { OrderedMap } from '../../type-definitions/immutable'; -import { isMap } from './isMap'; -import { isOrdered } from './isOrdered'; - -/** - * True if `maybeOrderedMap` is an OrderedMap. - */ -export function isOrderedMap( - maybeOrderedMap: unknown -): maybeOrderedMap is OrderedMap { - return isMap(maybeOrderedMap) && isOrdered(maybeOrderedMap); -} diff --git a/src/predicates/isOrderedSet.ts b/src/predicates/isOrderedSet.ts deleted file mode 100644 index 8ac58beb96..0000000000 --- a/src/predicates/isOrderedSet.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { isSet } from './isSet'; -import { isOrdered } from './isOrdered'; -import type { OrderedSet } from '../../type-definitions/immutable'; - -/** - * True if `maybeOrderedSet` is an OrderedSet. - */ -export function isOrderedSet( - maybeOrderedSet: unknown -): maybeOrderedSet is OrderedSet { - return isSet(maybeOrderedSet) && isOrdered(maybeOrderedSet); -} diff --git a/src/predicates/isRecord.ts b/src/predicates/isRecord.ts deleted file mode 100644 index ff9b1cdde8..0000000000 --- a/src/predicates/isRecord.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Record } from '../../type-definitions/immutable'; - -export const IS_RECORD_SYMBOL = '@@__IMMUTABLE_RECORD__@@'; - -/** - * True if `maybeRecord` is a Record. - */ -export function isRecord(maybeRecord: unknown): maybeRecord is Record { - return Boolean( - maybeRecord && - // @ts-expect-error: maybeRecord is typed as `{}`, need to change in 6.0 to `maybeRecord && typeof maybeRecord === 'object' && IS_RECORD_SYMBOL in maybeRecord` - maybeRecord[IS_RECORD_SYMBOL] - ); -} diff --git a/src/predicates/isSeq.ts b/src/predicates/isSeq.ts deleted file mode 100644 index 1b0f26f04a..0000000000 --- a/src/predicates/isSeq.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Seq } from '../../type-definitions/immutable'; - -export const IS_SEQ_SYMBOL = '@@__IMMUTABLE_SEQ__@@'; - -/** - * True if `maybeSeq` is a Seq. - */ -export function isSeq( - maybeSeq: unknown -): maybeSeq is - | Seq.Indexed - | Seq.Keyed - | Seq.Set { - return Boolean( - maybeSeq && - // @ts-expect-error: maybeSeq is typed as `{}`, need to change in 6.0 to `maybeSeq && typeof maybeSeq === 'object' && MAYBE_SEQ_SYMBOL in maybeSeq` - maybeSeq[IS_SEQ_SYMBOL] - ); -} diff --git a/src/predicates/isSet.ts b/src/predicates/isSet.ts deleted file mode 100644 index a9a59fe71c..0000000000 --- a/src/predicates/isSet.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Set } from '../../type-definitions/immutable'; - -export const IS_SET_SYMBOL = '@@__IMMUTABLE_SET__@@'; - -/** - * True if `maybeSet` is a Set. - * - * Also true for OrderedSets. - */ -export function isSet(maybeSet: unknown): maybeSet is Set { - return Boolean( - maybeSet && - // @ts-expect-error: maybeSet is typed as `{}`, need to change in 6.0 to `maybeSeq && typeof maybeSet === 'object' && MAYBE_SET_SYMBOL in maybeSet` - maybeSet[IS_SET_SYMBOL] - ); -} diff --git a/src/predicates/isStack.ts b/src/predicates/isStack.ts deleted file mode 100644 index b62768f88e..0000000000 --- a/src/predicates/isStack.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Stack } from '../../type-definitions/immutable'; - -export const IS_STACK_SYMBOL = '@@__IMMUTABLE_STACK__@@'; - -/** - * True if `maybeStack` is a Stack. - */ -export function isStack(maybeStack: unknown): maybeStack is Stack { - return Boolean( - maybeStack && - // @ts-expect-error: maybeStack is typed as `{}`, need to change in 6.0 to `maybeStack && typeof maybeStack === 'object' && MAYBE_STACK_SYMBOL in maybeStack` - maybeStack[IS_STACK_SYMBOL] - ); -} diff --git a/src/predicates/isValueObject.ts b/src/predicates/isValueObject.ts deleted file mode 100644 index f603b517ea..0000000000 --- a/src/predicates/isValueObject.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ValueObject } from '../../type-definitions/immutable'; - -/** - * True if `maybeValue` is a JavaScript Object which has *both* `equals()` - * and `hashCode()` methods. - * - * Any two instances of *value objects* can be compared for value equality with - * `Immutable.is()` and can be used as keys in a `Map` or members in a `Set`. - */ -export function isValueObject(maybeValue: unknown): maybeValue is ValueObject { - return Boolean( - maybeValue && - // @ts-expect-error: maybeValue is typed as `{}` - typeof maybeValue.equals === 'function' && - // @ts-expect-error: maybeValue is typed as `{}` - typeof maybeValue.hashCode === 'function' - ); -} diff --git a/src/probe.js b/src/probe.js new file mode 100644 index 0000000000..ad7cf342c4 --- /dev/null +++ b/src/probe.js @@ -0,0 +1,563 @@ +import { + NOT_SET, + IS_STACK_SYMBOL, + IS_LIST_SYMBOL, + IS_MAP_SYMBOL, + IS_ORDERED_SYMBOL, + IS_KEYED_SYMBOL, + IS_COLLECTION_SYMBOL, + IS_INDEXED_SYMBOL, + IS_RECORD_SYMBOL, + IS_SET_SYMBOL, + IS_SEQ_SYMBOL, + ITERATOR_SYMBOL_REAL, + ITERATOR_SYMBOL_FAUX, +} from './const'; + +/** + * True if `maybeOrderedMap` is an OrderedMap. + */ +const probeIsMapOrdered = (any) => { + return Boolean(any && any[IS_MAP_SYMBOL] && any[IS_ORDERED_SYMBOL]); +}; + +/** + * True if `maybeOrdered` is a Collection where iteration order is well + * defined. True for Collection.Indexed as well as OrderedMap and OrderedSet. + * + * ```js + * import { isOrdered, Map, OrderedMap, List, Set } from 'immutable'; + * + * isOrdered([]); // false + * isOrdered({}); // false + * isOrdered(Map()); // false + * isOrdered(OrderedMap()); // true + * isOrdered(List()); // true + * isOrdered(Set()); // false + * ``` + */ +const probeIsOrdered = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeOrdered is typed as `{}`, need to change in 6.0 to `maybeOrdered && typeof maybeOrdered === 'object' && IS_ORDERED_SYMBOL in maybeOrdered` + any[IS_ORDERED_SYMBOL] + ); +}; + +/** + * True if `maybeSeq` is a Seq. + */ +const probeIsSeq = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeSeq is typed as `{}`, need to change in 6.0 to `maybeSeq && typeof maybeSeq === 'object' && MAYBE_SEQ_SYMBOL in maybeSeq` + any[IS_SEQ_SYMBOL] + ); +}; + +/** + * True if `maybeCollection` is a Collection, or any of its subclasses. + * + * ```js + * import { isCollection, Map, List, Stack } from 'immutable'; + * + * isCollection([]); // false + * isCollection({}); // false + * isCollection(Map()); // true + * isCollection(List()); // true + * isCollection(Stack()); // true + * ``` + */ +const probeIsCollection = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeCollection is typed as `{}`, need to change in 6.0 to `maybeCollection && typeof maybeCollection === 'object' && IS_COLLECTION_SYMBOL in maybeCollection` + any[IS_COLLECTION_SYMBOL] + ); +}; + +/** + * True if `maybeOrderedSet` is an OrderedSet. + */ +const probeIsOrderedSet = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeOrdered is typed as `{}`, need to change in 6.0 to `maybeOrdered && typeof maybeOrdered === 'object' && IS_ORDERED_SYMBOL in maybeOrdered` + any[IS_SET_SYMBOL] && + any[IS_ORDERED_SYMBOL] + ); +}; + +/** + * True if `maybeSet` is a Set. + * + * Also true for OrderedSets. + */ +const probeIsSet = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeSet is typed as `{}`, need to change in 6.0 to `maybeSeq && typeof maybeSet === 'object' && MAYBE_SET_SYMBOL in maybeSet` + any[IS_SET_SYMBOL] + ); +}; + +/** + * True if `maybeValue` is a JavaScript Object which has *both* `equals()` + * and `hashCode()` methods. + * + * Any two instances of *value objects* can be compared for value equality with + * `Immutable.is()` and can be used as keys in a `Map` or members in a `Set`. + */ +const probeIsValueObject = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeValue is typed as `{}` + typeof any.equals === 'function' && + // @ts-expect-error: maybeValue is typed as `{}` + typeof any.hashCode === 'function' + ); +}; + +/** + * True if `maybeMap` is a Map. + * + * Also true for OrderedMaps. + */ +const probeIsMap = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeMap is typed as `{}`, need to change in 6.0 to `maybeMap && typeof maybeMap === 'object' && IS_MAP_SYMBOL in maybeMap` + any[IS_MAP_SYMBOL] + ); +}; + +/** + * True if `maybeStack` is a Stack. + */ +const probeIsStack = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeStack is typed as `{}`, need to change in 6.0 to `maybeStack && typeof maybeStack === 'object' && MAYBE_STACK_SYMBOL in maybeStack` + any[IS_STACK_SYMBOL] + ); +}; + +/** + * True if `maybeKeyed` is a Collection.Keyed, or any of its subclasses. + * + * ```js + * import { isKeyed, Map, List, Stack } from 'immutable'; + * + * isKeyed([]); // false + * isKeyed({}); // false + * isKeyed(Map()); // true + * isKeyed(List()); // false + * isKeyed(Stack()); // false + * ``` + */ +const probeIsKeyed = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeKeyed is typed as `{}`, need to change in 6.0 to `maybeKeyed && typeof maybeKeyed === 'object' && IS_KEYED_SYMBOL in maybeKeyed` + any[IS_KEYED_SYMBOL] + ); +}; + +/** + * True if `maybeIndexed` is a Collection.Indexed, or any of its subclasses. + * + * ```js + * import { isIndexed, Map, List, Stack, Set } from 'immutable'; + * + * isIndexed([]); // false + * isIndexed({}); // false + * isIndexed(Map()); // false + * isIndexed(List()); // true + * isIndexed(Stack()); // true + * isIndexed(Set()); // false + * ``` + */ +const probeIsIndexed = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeIndexed is typed as `{}`, need to change in 6.0 to `maybeIndexed && typeof maybeIndexed === 'object' && IS_INDEXED_SYMBOL in maybeIndexed` + any[IS_INDEXED_SYMBOL] + ); +}; + +/** + * True if `maybeAssociative` is either a Keyed or Indexed Collection. + * + * ```js + * import { isAssociative, Map, List, Stack, Set } from 'immutable'; + * + * isAssociative([]); // false + * isAssociative({}); // false + * isAssociative(Map()); // true + * isAssociative(List()); // true + * isAssociative(Stack()); // true + * isAssociative(Set()); // false + * ``` + */ +const probeIsAssociative = (any) => { + return Boolean(any && (any[IS_KEYED_SYMBOL] || any[IS_INDEXED_SYMBOL])); +}; + +const probeIsRepeat = (any) => { + return Boolean(any && any[IS_RECORD_SYMBOL]); +}; + +/** + * True if `maybeRecord` is a Record. + */ +const probeIsRecord = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeRecord is typed as `{}`, need to change in 6.0 to `maybeRecord && typeof maybeRecord === 'object' && IS_RECORD_SYMBOL in maybeRecord` + any[IS_RECORD_SYMBOL] + ); +}; + +/** + * True if `maybeList` is a List. + */ +const probeIsList = (any) => { + return Boolean( + any && + // @ts-expect-error: maybeList is typed as `{}`, need to change in 6.0 to `maybeList && typeof maybeList === 'object' && IS_LIST_SYMBOL in maybeList` + any[IS_LIST_SYMBOL] + ); +}; + +/** + * True if `maybeImmutable` is an Immutable Collection or Record. + * + * Note: Still returns true even if the collections is within a `withMutations()`. + * + * ```js + * import { isImmutable, Map, List, Stack } from 'immutable'; + * isImmutable([]); // false + * isImmutable({}); // false + * isImmutable(Map()); // true + * isImmutable(List()); // true + * isImmutable(Stack()); // true + * isImmutable(Map().asMutable()); // true + * ``` + */ +const probeIsImmutable = (any) => { + return Boolean(any && (any[IS_COLLECTION_SYMBOL] || any[IS_RECORD_SYMBOL])); +}; + +const probeIsPlainObject = ((toString) => (value) => { + // The base prototype's toString deals with Argument objects and native namespaces like Math + if ( + !value || + typeof value !== 'object' || + toString.call(value) !== '[object Object]' + ) { + return false; + } + + const proto = Object.getPrototypeOf(value); + if (proto === null) { + return true; + } + + // Iteratively going up the prototype chain is needed for cross-realm environments (differing contexts, iframes, etc) + let parentProto = proto; + let nextProto = Object.getPrototypeOf(proto); + while (nextProto !== null) { + parentProto = nextProto; + nextProto = Object.getPrototypeOf(parentProto); + } + return parentProto === proto; +})(Object.prototype.toString); + +/** + * Returns true if the value is a potentially-persistent data structure, either + * provided by Immutable.js or a plain Array or Object. + */ +const probeIsDataStructure = (any) => { + return ( + typeof any === 'object' && + (probeIsImmutable(any) || Array.isArray(any) || probeIsPlainObject(any)) + ); +}; + +const probeIsArrayLike = (value) => { + if (Array.isArray(value) || typeof value === 'string') { + return true; + } + + // @ts-expect-error "Type 'unknown' is not assignable to type 'boolean'" : convert to Boolean + return ( + value && + typeof value === 'object' && + // @ts-expect-error check that `'length' in value &&` + Number.isInteger(value.length) && + // @ts-expect-error check that `'length' in value &&` + value.length >= 0 && + // @ts-expect-error check that `'length' in value &&` + (value.length === 0 + ? // Only {length: 0} is considered Array-like. + Object.keys(value).length === 1 + : // An object is only Array-like if it has a property where the last value + // in the array-like may be found (which could be undefined). + // @ts-expect-error check that `'length' in value &&` + value.hasOwnProperty(value.length - 1)) + ); +}; + +const probeHasOwnProperty = Object.prototype.hasOwnProperty; + +const probeIsIterator = (maybeIterator) => { + return maybeIterator && typeof maybeIterator.next === 'function'; +}; + +const probeHasIterator = (iterable) => { + if (Array.isArray(iterable)) { + // IE11 trick as it does not support `Symbol.iterator` + return true; + } + + const iteratorFn = + iterable && + ((ITERATOR_SYMBOL_REAL && iterable[ITERATOR_SYMBOL_REAL]) || + iterable[ITERATOR_SYMBOL_FAUX]); + + return typeof iteratorFn === 'function'; +}; + +/** + * An extension of the "same-value" algorithm as [described for use by ES6 Map + * and Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Key_equality) + * + * NaN is considered the same as NaN, however -0 and 0 are considered the same + * value, which is different from the algorithm described by + * [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is). + * + * This is extended further to allow Objects to describe the values they + * represent, by way of `valueOf` or `equals` (and `hashCode`). + * + * Note: because of this extension, the key equality of Immutable.Map and the + * value equality of Immutable.Set will differ from ES6 Map and Set. + * + * ### Defining custom values + * + * The easiest way to describe the value an object represents is by implementing + * `valueOf`. For example, `Date` represents a value by returning a unix + * timestamp for `valueOf`: + * + * var date1 = new Date(1234567890000); // Fri Feb 13 2009 ... + * var date2 = new Date(1234567890000); + * date1.valueOf(); // 1234567890000 + * assert( date1 !== date2 ); + * assert( Immutable.is( date1, date2 ) ); + * + * Note: overriding `valueOf` may have other implications if you use this object + * where JavaScript expects a primitive, such as implicit string coercion. + * + * For more complex types, especially collections, implementing `valueOf` may + * not be performant. An alternative is to implement `equals` and `hashCode`. + * + * `equals` takes another object, presumably of similar type, and returns true + * if it is equal. Equality is symmetrical, so the same result should be + * returned if this and the argument are flipped. + * + * assert( a.equals(b) === b.equals(a) ); + * + * `hashCode` returns a 32bit integer number representing the object which will + * be used to determine how to store the value object in a Map or Set. You must + * provide both or neither methods, one must not exist without the other. + * + * Also, an important relationship between these methods must be upheld: if two + * values are equal, they *must* return the same hashCode. If the values are not + * equal, they might have the same hashCode; this is called a hash collision, + * and while undesirable for performance reasons, it is acceptable. + * + * if (a.equals(b)) { + * assert( a.hashCode() === b.hashCode() ); + * } + * + * All Immutable collections are Value Objects: they implement `equals()` + * and `hashCode()`. + */ +const probeIsSame = (valueA, valueB) => { + if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) { + return true; + } + if (!valueA || !valueB) { + return false; + } + if ( + typeof valueA.valueOf === 'function' && + typeof valueB.valueOf === 'function' + ) { + valueA = valueA.valueOf(); + valueB = valueB.valueOf(); + if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) { + return true; + } + if (!valueA || !valueB) { + return false; + } + } + + return !!( + probeIsValueObject(valueA) && + probeIsValueObject(valueB) && + valueA.equals(valueB) + ); +}; + +const probeIsKeyedLike = (any) => { + return Boolean( + any && + typeof any === 'object' && + !Array.isArray(any) && + (!probeIsImmutable(any) || any[IS_KEYED_SYMBOL] || any[IS_RECORD_SYMBOL]) + ); +}; + +const probeIsIndexedLike = (any) => { + return Boolean(any && (any[IS_INDEXED_SYMBOL] || Array.isArray(any))); +}; + +const probeIsMergeable = (a, b) => { + // This logic assumes that a sequence can only fall into one of the three + // categories mentioned above (since there's no `isSetLike()` method). + return ( + probeIsIndexedLike(a) === probeIsIndexedLike(b) && + probeIsKeyedLike(a) === probeIsKeyedLike(b) + ); +}; + +const probeIsSameDeep = (a, b) => { + if (a === b) { + return true; + } + + if ( + !probeIsCollection(b) || + // @ts-expect-error size should exists on Collection + (a.size !== undefined && b.size !== undefined && a.size !== b.size) || + // @ts-expect-error __hash exists on Collection + (a.__hash !== undefined && + // @ts-expect-error __hash exists on Collection + b.__hash !== undefined && + // @ts-expect-error __hash exists on Collection + a.__hash !== b.__hash) || + probeIsKeyed(a) !== probeIsKeyed(b) || + probeIsIndexed(a) !== probeIsIndexed(b) || + // @ts-expect-error Range extends Collection, which implements [Symbol.iterator], so it is valid + probeIsOrdered(a) !== probeIsOrdered(b) + ) { + return false; + } + + // @ts-expect-error size should exists on Collection + if (a.size === 0 && b.size === 0) { + return true; + } + + const notAssociative = !probeIsAssociative(a); + + // @ts-expect-error Range extends Collection, which implements [Symbol.iterator], so it is valid + if (probeIsOrdered(a)) { + const entries = a.entries(); + // @ts-expect-error need to cast as boolean + return ( + b.every((v, k) => { + const entry = entries.next().value; + return ( + entry && + probeIsSame(entry[1], v) && + (notAssociative || probeIsSame(entry[0], k)) + ); + }) && entries.next().done + ); + } + + let flipped = false; + + if (a.size === undefined) { + // @ts-expect-error size should exists on Collection + if (b.size === undefined) { + if (typeof a.cacheResult === 'function') { + a.cacheResult(); + } + } else { + flipped = true; + const _ = a; + a = b; + b = _; + } + } + + let allEqual = true; + const bSize = + // @ts-expect-error b is Range | Repeat | Collection as it may have been flipped, and __iterate is valid + b.__iterate((v, k) => { + if ( + notAssociative + ? // @ts-expect-error has exists on Collection + !a.has(v) + : flipped + ? // @ts-expect-error type of `get` does not "catch" the version with `notSetValue` + !probeIsSame(v, a.get(k, NOT_SET)) + : // @ts-expect-error type of `get` does not "catch" the version with `notSetValue` + !probeIsSame(a.get(k, NOT_SET), v) + ) { + allEqual = false; + return false; + } + }); + + return ( + allEqual && + // @ts-expect-error size should exists on Collection + a.size === bSize + ); +}; + +const probeCoerceKeyPath = (keyPath) => { + if (probeIsArrayLike(keyPath) && typeof keyPath !== 'string') { + return keyPath; + } + if (probeIsOrdered(keyPath)) { + return keyPath.toArray(); + } + throw new TypeError( + 'Invalid keyPath: expected Ordered Collection or Array: ' + keyPath + ); +}; + +export { + probeIsImmutable, + probeIsAssociative, + probeIsValueObject, + probeIsCollection, + probeIsOrderedSet, + probeIsOrdered, + probeIsSeq, + probeIsSet, + probeIsMap, + probeIsMapOrdered, + probeIsMapOrdered as probeIsOrderedMap, + probeIsList, + probeIsRecord, + probeIsRepeat, + probeIsStack, + probeIsKeyed, + probeIsIndexed, + probeIsPlainObject, + probeIsDataStructure, + probeIsArrayLike, + probeIsSame, + probeIsSameDeep, + probeHasOwnProperty, + probeHasIterator, + probeIsIterator, + probeIsMergeable, + probeCoerceKeyPath, +}; diff --git a/src/toJS.js b/src/toJS.js index 2d820416c8..2b4a9f38e0 100644 --- a/src/toJS.js +++ b/src/toJS.js @@ -1,19 +1,18 @@ import { Seq } from './Seq'; -import { isCollection } from './predicates/isCollection'; -import { isKeyed } from './predicates/isKeyed'; -import isDataStructure from './utils/isDataStructure'; + +import { probeIsKeyed, probeIsCollection, probeIsDataStructure } from './probe'; export function toJS(value) { if (!value || typeof value !== 'object') { return value; } - if (!isCollection(value)) { - if (!isDataStructure(value)) { + if (!probeIsCollection(value)) { + if (!probeIsDataStructure(value)) { return value; } value = Seq(value); } - if (isKeyed(value)) { + if (probeIsKeyed(value)) { const result = {}; value.__iterate((v, k) => { result[k] = toJS(v); diff --git a/src/transformToMethods.js b/src/transformToMethods.js new file mode 100644 index 0000000000..1771e516fd --- /dev/null +++ b/src/transformToMethods.js @@ -0,0 +1,100 @@ +// return object an object w/ functions transformed to resemble methods +// eg, +// ``` +// function = { +// getValue: (data, key) => data.get(key) +// } +// transformToMethods(functions) => ({ +// getValue: function (key) { return function.getValue(this, key) } +// }) +// ``` + +const methodArgs = [ + (fn) => + function () { + return fn(this); + }, + (fn) => + function (a1) { + return fn(this, a1); + }, + (fn) => + function (a1, a2) { + return fn(this, a1, a2); + }, + (fn) => + function (a1, a2, a3) { + return fn(this, a1, a2, a3); + }, + (fn) => + function (a1, a2, a3, a4) { + return fn(this, a1, a2, a3, a4); + }, + (fn) => + function (a1, a2, a3, a4, a5) { + return fn(this, a1, a2, a3, a4, a5); + }, + (fn) => + function (a1, a2, a3, a4, a5, a6) { + return fn(this, a1, a2, a3, a4, a5, a6); + }, + (fn) => + function (a1, a2, a3, a4, a5, a6, a7) { + return fn(this, a1, a2, a3, a4, a5, a6, a7); + }, + (fn) => + function (a1, a2, a3, a4, a5, a6, a7, a8) { + return fn(this, a1, a2, a3, a4, a5, a6, a7, a8); + }, +]; + +const methodArgsSpread = [ + // these wrappers for spread functions, eg + // + // concat(...values) {} + // splice(index, removeNum, ...values) {} + // interleave(...collections) {} + // zip(...collections) {} + // zipAll(...collections) {} + // zipWith(zipper, ...collections) {} + (fn) => + function (...a1) { + return fn(this, a1); + }, + (fn) => + function (a1, ...a2) { + return fn(this, a1, a2); + }, + (fn) => + function (a1, a2, ...a3) { + return fn(this, a1, a2, a3); + }, +]; + +const transformToMethod = ((cache) => (methodval) => { + const methodvalname = methodval.name; + + return ( + (methodvalname && cache[methodvalname]) || + (cache[methodvalname] = methodval.unspread + ? methodArgsSpread[methodval.length - 2](methodval) + : methodArgs[methodval.length](methodval)) + ); +})({}); + +const transformToMethods = (methodsmap, target = {}) => { + target = Object.keys(methodsmap).reduce((methodsObj, methodkey) => { + const methodval = methodsmap[methodkey]; + + methodsObj[methodkey] = + typeof methodval === 'function' + ? transformToMethod(methodval) + : methodval; + + return methodsObj; + }, target); + + return target; +}; + +export default transformToMethods; diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000000..6486771de5 --- /dev/null +++ b/src/util.js @@ -0,0 +1,149 @@ +const utilIsArrayLike = (value) => { + if (Array.isArray(value) || typeof value === 'string') { + return true; + } + + // @ts-expect-error "Type 'unknown' is not assignable to type 'boolean'" : convert to Boolean + return ( + value && + typeof value === 'object' && + // @ts-expect-error check that `'length' in value &&` + Number.isInteger(value.length) && + // @ts-expect-error check that `'length' in value &&` + value.length >= 0 && + // @ts-expect-error check that `'length' in value &&` + (value.length === 0 + ? // Only {length: 0} is considered Array-like. + Object.keys(value).length === 1 + : // An object is only Array-like if it has a property where the last value + // in the array-like may be found (which could be undefined). + // @ts-expect-error check that `'length' in value &&` + value.hasOwnProperty(value.length - 1)) + ); +}; + +const utilArrSpliceIn = (array, idx, val, canEdit) => { + const newLen = array.length + 1; + if (canEdit && idx + 1 === newLen) { + array[idx] = val; + return array; + } + const newArray = new Array(newLen); + let after = 0; + for (let ii = 0; ii < newLen; ii++) { + if (ii === idx) { + newArray[ii] = val; + after = -1; + } else { + newArray[ii] = array[ii + after]; + } + } + return newArray; +}; + +const utilArrSpliceOut = (array, idx, canEdit) => { + const newLen = array.length - 1; + if (canEdit && idx === newLen) { + array.pop(); + return array; + } + const newArray = new Array(newLen); + let after = 0; + for (let ii = 0; ii < newLen; ii++) { + if (ii === idx) { + after = 1; + } + newArray[ii] = array[ii + after]; + } + return newArray; +}; + +// http://jsperf.com/copy-array-inline +const utilArrCopy = (arr, offset) => { + offset = offset || 0; + const len = Math.max(0, arr.length - offset); + const newArr = new Array(len); + for (let ii = 0; ii < len; ii++) { + // @ts-expect-error We may want to guard for undefined values with `if (arr[ii + offset] !== undefined`, but ths should not happen by design + newArr[ii] = arr[ii + offset]; + } + return newArr; +}; + +const utilArrSetAt = (array, idx, val, canEdit) => { + const newArray = canEdit ? array : utilArrCopy(array); + newArray[idx] = val; + return newArray; +}; + +const utilFlagSpread = (fn) => ((fn.unspread = true), fn); + +/** + * Converts a value to a string, adding quotes if a string was provided. + */ +const utilQuoteString = (value) => { + try { + return typeof value === 'string' ? JSON.stringify(value) : String(value); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_ignoreError) { + return JSON.stringify(value); + } +}; + +const utilInvariant = (condition, error) => { + if (!condition) throw new Error(error); +}; + +const utilAssertNotInfinite = (size) => { + utilInvariant( + size !== Infinity, + 'Cannot perform this action with an infinite size.' + ); +}; + +const utilHasOwnProperty = Object.prototype.hasOwnProperty; + +const utilCopyShallow = (from) => { + if (Array.isArray(from)) { + return utilArrCopy(from); + } + const to = {}; + for (const key in from) { + if (utilHasOwnProperty.call(from, key)) { + to[key] = from[key]; + } + } + return to; +}; + +const utilAssignNamedPropAccessor = (prototype, name) => { + try { + Object.defineProperty(prototype, name, { + get: function () { + return this.get(name); + }, + set: function (value) { + utilInvariant(this.__ownerID, 'Cannot set on an immutable record.'); + this.set(name, value); + }, + }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO enable eslint here + } catch (error) { + // Object.defineProperty failed. Probably IE8. + } +}; + +export { + utilFlagSpread, + utilHasOwnProperty, + utilCopyShallow, + utilIsArrayLike, + utilArrSpliceIn, + utilArrSpliceOut, + utilArrCopy, + utilArrSetAt, + utilQuoteString, + utilInvariant, + utilAssertNotInfinite, + utilAssignNamedPropAccessor, +}; diff --git a/src/utils/arrCopy.ts b/src/utils/arrCopy.ts deleted file mode 100644 index e6cc653f98..0000000000 --- a/src/utils/arrCopy.ts +++ /dev/null @@ -1,12 +0,0 @@ -// http://jsperf.com/copy-array-inline - -export default function arrCopy(arr: Array, offset?: number): Array { - offset = offset || 0; - const len = Math.max(0, arr.length - offset); - const newArr: Array = new Array(len); - for (let ii = 0; ii < len; ii++) { - // @ts-expect-error We may want to guard for undefined values with `if (arr[ii + offset] !== undefined`, but ths should not happen by design - newArr[ii] = arr[ii + offset]; - } - return newArr; -} diff --git a/src/utils/assertNotInfinite.ts b/src/utils/assertNotInfinite.ts deleted file mode 100644 index 52abc8fc4c..0000000000 --- a/src/utils/assertNotInfinite.ts +++ /dev/null @@ -1,8 +0,0 @@ -import invariant from './invariant'; - -export default function assertNotInfinite(size: number): void { - invariant( - size !== Infinity, - 'Cannot perform this action with an infinite size.' - ); -} diff --git a/src/utils/coerceKeyPath.ts b/src/utils/coerceKeyPath.ts deleted file mode 100644 index 843981f0ea..0000000000 --- a/src/utils/coerceKeyPath.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { KeyPath } from '../../type-definitions/immutable'; -import { isOrdered } from '../predicates/isOrdered'; -import isArrayLike from './isArrayLike'; - -export default function coerceKeyPath(keyPath: KeyPath): ArrayLike { - if (isArrayLike(keyPath) && typeof keyPath !== 'string') { - return keyPath; - } - if (isOrdered(keyPath)) { - return keyPath.toArray(); - } - throw new TypeError( - 'Invalid keyPath: expected Ordered Collection or Array: ' + keyPath - ); -} diff --git a/src/utils/deepEqual.ts b/src/utils/deepEqual.ts deleted file mode 100644 index dd37e3d1d5..0000000000 --- a/src/utils/deepEqual.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { is } from '../is'; -import { NOT_SET } from '../TrieUtils'; -import { isCollection } from '../predicates/isCollection'; -import { isKeyed } from '../predicates/isKeyed'; -import { isIndexed } from '../predicates/isIndexed'; -import { isAssociative } from '../predicates/isAssociative'; -import { isOrdered } from '../predicates/isOrdered'; -import type { Collection } from '../../type-definitions/immutable'; -import type { RepeatImpl as Repeat } from '../Repeat'; -import type { RangeImpl as Range } from '../Range'; - -export default function deepEqual( - a: Range | Repeat | Collection, - b: unknown -): boolean { - if (a === b) { - return true; - } - - if ( - !isCollection(b) || - // @ts-expect-error size should exists on Collection - (a.size !== undefined && b.size !== undefined && a.size !== b.size) || - // @ts-expect-error __hash exists on Collection - (a.__hash !== undefined && - // @ts-expect-error __hash exists on Collection - b.__hash !== undefined && - // @ts-expect-error __hash exists on Collection - a.__hash !== b.__hash) || - isKeyed(a) !== isKeyed(b) || - isIndexed(a) !== isIndexed(b) || - // @ts-expect-error Range extends Collection, which implements [Symbol.iterator], so it is valid - isOrdered(a) !== isOrdered(b) - ) { - return false; - } - - // @ts-expect-error size should exists on Collection - if (a.size === 0 && b.size === 0) { - return true; - } - - const notAssociative = !isAssociative(a); - - // @ts-expect-error Range extends Collection, which implements [Symbol.iterator], so it is valid - if (isOrdered(a)) { - const entries = a.entries(); - // @ts-expect-error need to cast as boolean - return ( - b.every((v, k) => { - const entry = entries.next().value; - return entry && is(entry[1], v) && (notAssociative || is(entry[0], k)); - }) && entries.next().done - ); - } - - let flipped = false; - - if (a.size === undefined) { - // @ts-expect-error size should exists on Collection - if (b.size === undefined) { - if (typeof a.cacheResult === 'function') { - a.cacheResult(); - } - } else { - flipped = true; - const _ = a; - a = b; - b = _; - } - } - - let allEqual = true; - const bSize: number = - // @ts-expect-error b is Range | Repeat | Collection as it may have been flipped, and __iterate is valid - b.__iterate((v, k) => { - if ( - notAssociative - ? // @ts-expect-error has exists on Collection - !a.has(v) - : flipped - ? // @ts-expect-error type of `get` does not "catch" the version with `notSetValue` - !is(v, a.get(k, NOT_SET)) - : // @ts-expect-error type of `get` does not "catch" the version with `notSetValue` - !is(a.get(k, NOT_SET), v) - ) { - allEqual = false; - return false; - } - }); - - return ( - allEqual && - // @ts-expect-error size should exists on Collection - a.size === bSize - ); -} diff --git a/src/utils/hasOwnProperty.ts b/src/utils/hasOwnProperty.ts deleted file mode 100644 index cb5ba22368..0000000000 --- a/src/utils/hasOwnProperty.ts +++ /dev/null @@ -1 +0,0 @@ -export default Object.prototype.hasOwnProperty; diff --git a/src/utils/invariant.ts b/src/utils/invariant.ts deleted file mode 100644 index 958e6c977b..0000000000 --- a/src/utils/invariant.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default function invariant( - condition: unknown, - error: string -): asserts condition { - if (!condition) throw new Error(error); -} diff --git a/src/utils/isArrayLike.ts b/src/utils/isArrayLike.ts deleted file mode 100644 index 82659d6c54..0000000000 --- a/src/utils/isArrayLike.ts +++ /dev/null @@ -1,25 +0,0 @@ -export default function isArrayLike( - value: unknown -): value is ArrayLike { - if (Array.isArray(value) || typeof value === 'string') { - return true; - } - - // @ts-expect-error "Type 'unknown' is not assignable to type 'boolean'" : convert to Boolean - return ( - value && - typeof value === 'object' && - // @ts-expect-error check that `'length' in value &&` - Number.isInteger(value.length) && - // @ts-expect-error check that `'length' in value &&` - value.length >= 0 && - // @ts-expect-error check that `'length' in value &&` - (value.length === 0 - ? // Only {length: 0} is considered Array-like. - Object.keys(value).length === 1 - : // An object is only Array-like if it has a property where the last value - // in the array-like may be found (which could be undefined). - // @ts-expect-error check that `'length' in value &&` - value.hasOwnProperty(value.length - 1)) - ); -} diff --git a/src/utils/isDataStructure.ts b/src/utils/isDataStructure.ts deleted file mode 100644 index e71c55bb7d..0000000000 --- a/src/utils/isDataStructure.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Collection, Record } from '../../type-definitions/immutable'; -import { isImmutable } from '../predicates/isImmutable'; -import isPlainObj from './isPlainObj'; - -/** - * Returns true if the value is a potentially-persistent data structure, either - * provided by Immutable.js or a plain Array or Object. - */ -export default function isDataStructure( - value: unknown -): value is - | Collection - | Record - | Array - | object { - return ( - typeof value === 'object' && - (isImmutable(value) || Array.isArray(value) || isPlainObj(value)) - ); -} diff --git a/src/utils/isPlainObj.ts b/src/utils/isPlainObj.ts deleted file mode 100644 index 07e73e208a..0000000000 --- a/src/utils/isPlainObj.ts +++ /dev/null @@ -1,26 +0,0 @@ -const toString = Object.prototype.toString; - -export default function isPlainObject(value: unknown): value is object { - // The base prototype's toString deals with Argument objects and native namespaces like Math - if ( - !value || - typeof value !== 'object' || - toString.call(value) !== '[object Object]' - ) { - return false; - } - - const proto = Object.getPrototypeOf(value); - if (proto === null) { - return true; - } - - // Iteratively going up the prototype chain is needed for cross-realm environments (differing contexts, iframes, etc) - let parentProto = proto; - let nextProto = Object.getPrototypeOf(proto); - while (nextProto !== null) { - parentProto = nextProto; - nextProto = Object.getPrototypeOf(parentProto); - } - return parentProto === proto; -} diff --git a/src/utils/mixin.ts b/src/utils/mixin.ts deleted file mode 100644 index 3d4cee5fc6..0000000000 --- a/src/utils/mixin.ts +++ /dev/null @@ -1,20 +0,0 @@ -type Constructor = new (...args: unknown[]) => T; - -/** - * Contributes additional methods to a constructor - */ -export default function mixin( - ctor: C, - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - methods: Record -): C { - const keyCopier = (key: string | symbol): void => { - // @ts-expect-error how to handle symbol ? - ctor.prototype[key] = methods[key]; - }; - Object.keys(methods).forEach(keyCopier); - // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- TODO enable eslint here - Object.getOwnPropertySymbols && - Object.getOwnPropertySymbols(methods).forEach(keyCopier); - return ctor; -} diff --git a/src/utils/quoteString.ts b/src/utils/quoteString.ts deleted file mode 100644 index 8d9b825a38..0000000000 --- a/src/utils/quoteString.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Converts a value to a string, adding quotes if a string was provided. - */ -export default function quoteString(value: unknown): string { - try { - return typeof value === 'string' ? JSON.stringify(value) : String(value); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (_ignoreError) { - return JSON.stringify(value); - } -} diff --git a/src/utils/shallowCopy.ts b/src/utils/shallowCopy.ts deleted file mode 100644 index 37aad28015..0000000000 --- a/src/utils/shallowCopy.ts +++ /dev/null @@ -1,19 +0,0 @@ -import arrCopy from './arrCopy'; -import hasOwnProperty from './hasOwnProperty'; - -export default function shallowCopy(from: Array): Array; -export default function shallowCopy(from: O): O; -export default function shallowCopy( - from: Array | O -): Array | O { - if (Array.isArray(from)) { - return arrCopy(from); - } - const to: Partial = {}; - for (const key in from) { - if (hasOwnProperty.call(from, key)) { - to[key] = from[key]; - } - } - return to as O; -}