From 5faef796d4e772d045a6dda43bb7115f86b4157e Mon Sep 17 00:00:00 2001 From: rowan Date: Wed, 30 Apr 2025 21:41:23 -0500 Subject: [PATCH] initial --- .gitignore | 1 + dist/index.js | 550 +++++++++++++++++++++++++++++++ package-lock.json | 489 +++++++++++++++++++++++++++ package.json | 18 + src/index.ts | 709 ++++++++++++++++++++++++++++++++++++++++ test/index.js | 7 + test/unit/enumerable.js | 257 +++++++++++++++ test/unit/index.js | 2 + 8 files changed, 2033 insertions(+) create mode 100644 .gitignore create mode 100644 dist/index.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/index.ts create mode 100755 test/index.js create mode 100644 test/unit/enumerable.js create mode 100644 test/unit/index.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..bc59b2b --- /dev/null +++ b/dist/index.js @@ -0,0 +1,550 @@ +// src/index.ts +var DoneIteratorResult = Object.freeze({ value: void 0, done: true }); +function hasMethods(methods, obj) { + return methods.every((method) => typeof obj[method] === "function"); +} +function isIterable(value) { + return typeof value[Symbol.iterator] === "function"; +} +function isIterator(value) { + return typeof value.next === "function"; +} +function isBuffer(value) { + return ArrayBuffer.isView(value); +} +function isArrayLike(value) { + return Array.isArray(value) || value instanceof Array; +} +var IteratorEnumerator = class _IteratorEnumerator { + _iterator; + _consumed = false; + _current; + get current() { + return this._current; + } + constructor(iterator) { + this._iterator = iterator; + } + static from(iterator) { + return new _IteratorEnumerator(iterator); + } + moveNext() { + if (!this._consumed) { + const { value, done } = this._iterator.next(); + this._current = value; + if (done) { + this._consumed = true; + } + return !done; + } else { + return false; + } + } + reset() { + } + toIterator() { + return this; + } + next(...[_value]) { + const done = this.moveNext(); + return { value: this.current, done }; + } + return(value) { + return this._iterator.return?.(value) ?? DoneIteratorResult; + } + throw(e) { + return this._iterator.throw?.(e) ?? DoneIteratorResult; + } +}; +var CachedIteratorEnumerator = class _CachedIteratorEnumerator { + _iterator; + _cache = []; + _index = -1; + get current() { + return this._cache[this._index]; + } + constructor(iterator) { + this._iterator = new IteratorEnumerator(iterator); + } + static from(iterator) { + return new _CachedIteratorEnumerator(iterator); + } + moveNext() { + this._index += 1; + if (this._cache.length > this._index) { + return true; + } else if (this._iterator.moveNext()) { + this._cache.push(this._iterator.current); + return true; + } else { + return false; + } + } + reset() { + this._index = -1; + } + toIterator() { + return this; + } + next(...[_value]) { + const done = this.moveNext(); + return { value: this.current, done }; + } + return(value) { + return this._iterator.return(value); + } + throw(e) { + return this._iterator.throw(e); + } +}; +var IterableEnumerator = class { + _iterable; + _factory; + _enumerator; + get current() { + return this._enumerator?.current; + } + constructor(iterable, factory = IteratorEnumerator.from) { + this._iterable = iterable; + this._factory = factory; + this._enumerator = this._createEnumerator(); + } + static fromIterable(iterable, factory) { + return new this(iterable, factory); + } + moveNext() { + return this._enumerator?.moveNext() ?? false; + } + _createIterator() { + return this._iterable[Symbol.iterator](); + } + _createEnumerator() { + return this._factory(this._createIterator()); + } + reset() { + this._enumerator = this._createEnumerator(); + } + toIterator() { + return this._createIterator(); + } + next(...[_value]) { + const done = !this.moveNext(); + return { value: this.current, done }; + } + return(value) { + if (isIterator(this._enumerator)) { + return this._enumerator?.return?.(value) || DoneIteratorResult; + } else { + return DoneIteratorResult; + } + } + throw(e) { + if (isIterator(this._enumerator)) { + return this._enumerator?.throw?.(e) || DoneIteratorResult; + } else { + return DoneIteratorResult; + } + } +}; +function toArrayLikeBuffer(buffer) { + return Object.defineProperty(buffer, "length", { + get: function() { + return buffer.byteLength; + } + }); +} +var ArrayEnumerator = class _ArrayEnumerator { + _array; + _index = -1; + get current() { + return this._array[this._index]; + } + constructor(array) { + this._array = array; + } + static from(array) { + if (ArrayBuffer.isView(array)) { + return new _ArrayEnumerator(toArrayLikeBuffer(array)); + } else { + return new _ArrayEnumerator(array); + } + } + [Symbol.iterator]() { + return this._array[Symbol.iterator](); + } + setIndex(index) { + this._index = index; + } + moveNext() { + this._index += 1; + return this._index < this._array.length; + } + reset() { + this._index = -1; + } + toIterator() { + return this._array[Symbol.iterator](); + } + next(...[_value]) { + const done = this.moveNext(); + return { value: this.current, done }; + } + return(_value) { + return DoneIteratorResult; + } + throw(_e) { + return DoneIteratorResult; + } +}; +var Enumerator = class _Enumerator { + _enumerator; + _index = 0; + get current() { + return this._enumerator.current; + } + constructor(enumerator) { + this._enumerator = enumerator; + } + static fromIterable(iterable) { + if (isArrayLike(iterable) || ArrayBuffer.isView(iterable)) { + return ArrayEnumerator.from(iterable); + } else { + return new this(new IterableEnumerator(iterable)); + } + } + static fromIterator(iterator, cache = true) { + if (cache) { + return new CachedIteratorEnumerator(iterator); + } else { + return new IteratorEnumerator(iterator); + } + } + static toIterator(enumerator) { + return new this(enumerator); + } + [Symbol.iterator]() { + return this.toIterator(); + } + toIterator() { + return _Enumerator.toIterator(this); + } + moveNext() { + this._index += 1; + return this._enumerator.moveNext(); + } + reset() { + this._enumerator.reset(); + } + next(...[_value]) { + const done = this.moveNext(); + return { value: this.current, done }; + } + return(_value) { + return DoneIteratorResult; + } + throw(_e) { + return DoneIteratorResult; + } +}; +var HelperEnumerator = class _HelperEnumerator { + _enumerator; + _index = 0; + get current() { + return this._enumerator.current; + } + constructor(enumerator) { + this._enumerator = enumerator; + } + static toIterator(enumerator) { + return new this(enumerator); + } + [Symbol.iterator]() { + return this.toIterator(); + } + toIterator() { + return _HelperEnumerator.toIterator(this); + } + moveNext() { + this._index += 1; + return this._enumerator.moveNext(); + } + reset() { + this._enumerator.reset(); + } + next(...[_value]) { + const done = this.moveNext(); + return { value: this.current, done }; + } + return(_value) { + return DoneIteratorResult; + } + throw(_e) { + return DoneIteratorResult; + } +}; +var DropEnumerator = class extends HelperEnumerator { + _limit; + constructor(enumerator, limit) { + super(enumerator); + this._limit = limit; + } + moveNext() { + let next = super.moveNext(); + while (this._limit > 0 && next) { + next = super.moveNext(); + this._limit -= 1; + } + return next; + } +}; +var FilterEnumerator = class extends HelperEnumerator { + _filter; + constructor(enumerator, filter) { + super(enumerator); + this._filter = filter; + } + moveNext() { + let next = super.moveNext(); + while (next && !this._filter(this.current, this._index)) { + next = super.moveNext(); + } + return next; + } +}; +var FlatMapEnumerator = class { + _enumerator; + _flatMap; + _inner; + _index = -1; + constructor(enumerator, flatMap) { + this._enumerator = enumerator; + this._flatMap = flatMap; + } + get current() { + return this._inner?.current; + } + moveNext() { + if (this._inner && this._inner.moveNext()) { + return true; + } + const next = this._enumerator.moveNext(); + if (!next) { + return false; + } + this._index += 1; + this._inner = this._flatMap(this._enumerator.current, this._index); + return this._inner.moveNext(); + } + reset() { + this._index = -1; + this._inner = void 0; + this._enumerator.reset(); + } + toIterator() { + return HelperEnumerator.toIterator(this); + } +}; +var MapEnumerator = class { + _enumerator; + _map; + _current; + _index = -1; + get current() { + return this._current; + } + constructor(enumerator, map) { + this._enumerator = enumerator; + this._map = map; + } + toIterator() { + return HelperEnumerator.toIterator(this); + } + moveNext() { + this._index += 1; + const next = this._enumerator.moveNext(); + if (next) { + this._current = this._map(this._enumerator.current, this._index); + } + return next; + } + reset() { + this._index = -1; + this._enumerator.reset(); + } +}; +var TakeEnumerator = class extends HelperEnumerator { + _limit; + constructor(enumerator, limit) { + super(enumerator); + this._limit = limit; + } + moveNext() { + if (this._limit < 0) { + return false; + } else { + super.moveNext(); + this._limit -= 1; + return true; + } + } +}; +var FusedEnumerator = class { + _enumerators; + _index = 0; + get current() { + return this._cur()?.current; + } + constructor(enumerators) { + this._enumerators = enumerators; + } + _cur() { + return this._enumerators[this._index]; + } + _done() { + return this._index >= this._enumerators.length; + } + toIterator() { + return HelperEnumerator.toIterator(this); + } + moveNext() { + while (!this._done() && !this._cur().moveNext()) { + this._index += 1; + } + return this._done(); + } + reset() { + const len = this._enumerators.length; + for (let i = 0; i < len; i++) { + this._enumerators[i].reset(); + } + this._index = 0; + } +}; +var Enumerable = class _Enumerable { + _enumerator; + constructor(enumerator) { + this._enumerator = enumerator; + } + static from(value) { + if (this.isEnumerable(value)) { + return value; + } else if (this.isEnumerator(value)) { + return new _Enumerable(value); + } else if (isIterable(value)) { + const enumerator = Enumerator.fromIterable(value); + if (isArrayLike(value)) { + return new ArrayEnumerble(enumerator); + } else { + return new _Enumerable(enumerator); + } + } else if (isIterator(value)) { + return new _Enumerable( + Enumerator.fromIterator(value) + ); + } else { + throw new TypeError("value is not enumerable"); + } + } + static isEnumerable(value) { + return typeof value["enumerator"] === "function"; + } + static isEnumerator(value) { + return hasMethods(["moveNext", "reset", "toIterator"], value); + } + [Symbol.iterator]() { + return this._enumerator.toIterator(); + } + at(index) { + while (index >= 0 && this._enumerator.moveNext()) { + index -= 1; + } + const value = this._enumerator.current; + this._enumerator.reset(); + return value; + } + atOrDefault(index, defaultValue) { + const value = this.at(index); + return value || defaultValue; + } + atOrElse(index, defaultValue) { + const value = this.at(index); + return value || defaultValue(); + } + concat(other) { + return new _Enumerable( + new FusedEnumerator([this._enumerator, other.enumerator()]) + ); + } + drop(limit) { + return new _Enumerable(new DropEnumerator(this._enumerator, limit)); + } + enumerator() { + return this._enumerator; + } + //entries(): IEnumerator { + //} + every(predicate) { + let index = 0; + while (this._enumerator.moveNext()) { + if (!predicate(this._enumerator.current, index)) { + return false; + } + index += 1; + } + this._enumerator.reset(); + return true; + } + filter(predicate) { + return new _Enumerable(new FilterEnumerator(this._enumerator, predicate)); + } + flatMap(fn) { + return new _Enumerable(new FlatMapEnumerator(this._enumerator, fn)); + } + map(fn) { + return new _Enumerable(new MapEnumerator(this._enumerator, fn)); + } + some(predicate) { + let index = 0; + while (this._enumerator.moveNext()) { + if (predicate(this._enumerator.current, index)) { + return true; + } + index += 1; + } + this._enumerator.reset(); + return false; + } + take(limit) { + return new _Enumerable(new TakeEnumerator(this._enumerator, limit)); + } +}; +var ArrayEnumerble = class extends Enumerable { + at(index) { + if (this._enumerator instanceof ArrayEnumerator) { + this._enumerator.setIndex(index); + return this._enumerator.current; + } else { + return super.at(index); + } + } +}; +export { + ArrayEnumerator, + ArrayEnumerble, + CachedIteratorEnumerator, + DropEnumerator, + Enumerable, + Enumerator, + FilterEnumerator, + FlatMapEnumerator, + FusedEnumerator, + HelperEnumerator, + IterableEnumerator, + IteratorEnumerator, + MapEnumerator, + TakeEnumerator, + isArrayLike, + isBuffer, + isIterable, + isIterator +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..83f32e8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,489 @@ +{ + "name": "enumerable-ts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "enumerable-ts", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "esbuild": "^0.25.3", + "folktest": "git+https://git.kitsu.cafe/rowan/folktest.git" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" + } + }, + "node_modules/folktest": { + "version": "1.0.0", + "resolved": "git+https://git.kitsu.cafe/rowan/folktest.git#cbf48ff3b1334eb883f202a77a5bc89d24534520", + "dev": true, + "license": "GPL-3.0-or-later" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6f7cd4b --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "enumerable-ts", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=./dist/index.js", + "test": "./test/index.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "module", + "devDependencies": { + "esbuild": "^0.25.3", + "folktest": "git+https://git.kitsu.cafe/rowan/folktest.git" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..eab8ab3 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,709 @@ +const DoneIteratorResult = Object.freeze({ value: undefined, done: true }) + +type Nullable = T | null | undefined + +export interface Predicate { + (value: Nullable, index: number): boolean +} + +export interface Reducer { + (accumulator: U, value: Nullable, index: number): U +} + +export interface Morphism { + (value: Nullable, index: number): U +} + +export interface IEnumerator { + get current(): Nullable + moveNext(): boolean + reset(): void + toIterator(): Iterator +} + +export interface IEnumerable { + enumerator(): IEnumerator +} + +export interface IEnumeratorFactory { + (iterator: Iterator): IEnumerator +} + +function hasMethods(methods: PropertyKey[], obj: object): boolean { + return methods.every(method => typeof obj[method] === 'function') +} + +export function isIterable(value: any): value is Iterable { + return typeof value[Symbol.iterator] === 'function' +} + +export function isIterator(value: any): value is Iterator { + return typeof value.next === 'function' +} + +export function isBuffer(value: any): value is ArrayBufferLike { + return ArrayBuffer.isView(value) +} + +export function isArrayLike(value: any): value is ArrayLike { + return Array.isArray(value) || value instanceof Array +} + +export class IteratorEnumerator implements IEnumerator, Iterator { + private _iterator: Iterator + private _consumed: boolean = false + private _current: Nullable + + get current(): Nullable { + return this._current + } + + constructor(iterator: Iterator) { + this._iterator = iterator + } + + static from(iterator: Iterator): IteratorEnumerator { + return new IteratorEnumerator(iterator) + } + + moveNext(): boolean { + if (!this._consumed) { + const { value, done } = this._iterator.next() + + this._current = value + if (done) { + this._consumed = true + } + + return !done + } else { + return false + } + } + + reset(): void { } + + toIterator(): Iterator { + return this as Iterator + } + + next(...[_value]: [] | [any]): IteratorResult { + const done = this.moveNext() + return { value: this.current, done } as IteratorResult + } + + return(value?: any): IteratorResult { + return this._iterator.return?.(value) ?? DoneIteratorResult + } + + throw(e?: any): IteratorResult { + return this._iterator.throw?.(e) ?? DoneIteratorResult + } +} + +export class CachedIteratorEnumerator implements IEnumerator, Iterator { + private _iterator: IteratorEnumerator + private _cache: Nullable[] = [] + private _index: number = -1 + + get current(): Nullable { + return this._cache[this._index] + } + + constructor(iterator: Iterator) { + this._iterator = new IteratorEnumerator(iterator) + } + + static from(iterator: Iterator): CachedIteratorEnumerator { + return new CachedIteratorEnumerator(iterator) + } + + moveNext(): boolean { + this._index += 1 + + if (this._cache.length > this._index) { + return true + } else if (this._iterator.moveNext()) { + this._cache.push(this._iterator.current) + + return true + } else { + return false + } + } + + reset(): void { + this._index = -1 + } + + toIterator(): Iterator { + return this as Iterator + } + + next(...[_value]: [] | [any]): IteratorResult { + const done = this.moveNext() + return { value: this.current, done } as IteratorResult + } + + return?(value?: any): IteratorResult { + return this._iterator.return(value) + } + + throw(e?: any): IteratorResult { + return this._iterator.throw(e) + } +} + +export class IterableEnumerator implements IEnumerator, Iterator { + private _iterable: Iterable + private _factory: IEnumeratorFactory + private _enumerator: Nullable> + + get current(): Nullable { + return this._enumerator?.current + } + + constructor(iterable: Iterable, factory: IEnumeratorFactory = IteratorEnumerator.from) { + this._iterable = iterable + this._factory = factory + this._enumerator = this._createEnumerator() + } + + static fromIterable(iterable: Iterable, factory?: IEnumeratorFactory): IEnumerator { + return new this(iterable, factory) + } + + moveNext(): boolean { + return this._enumerator?.moveNext() ?? false + } + + _createIterator(): Iterator { + return this._iterable[Symbol.iterator]() + } + + _createEnumerator(): IEnumerator { + return this._factory(this._createIterator()) + } + + reset(): void { + this._enumerator = this._createEnumerator() + } + + toIterator(): Iterator { + return this._createIterator() as Iterator + } + + next(...[_value]: [] | [any]): IteratorResult { + const done = !this.moveNext() + return { value: this.current, done } as IteratorResult + } + + return?(value?: any): IteratorResult { + if (isIterator(this._enumerator)) { + return this._enumerator?.return?.(value) || DoneIteratorResult + } else { + return DoneIteratorResult + } + } + + throw?(e?: any): IteratorResult { + if (isIterator(this._enumerator)) { + return this._enumerator?.throw?.(e) || DoneIteratorResult + } else { + return DoneIteratorResult + } + } +} + +// ugly hack to stamp arraybuffers to be arraylike +function toArrayLikeBuffer(buffer: T): T & ArrayLike { + return Object.defineProperty(buffer, 'length', { + get: function() { return buffer.byteLength } + }) as T & ArrayLike +} + +type ArrayType | ArrayBufferView> = T extends ArrayLike ? U : T extends ArrayBufferTypes ? number : never + +export class ArrayEnumerator implements IEnumerator, Iterator, Iterable { + private _array: ArrayLike + private _index: number = -1 + + get current(): Nullable { + return this._array[this._index] + } + + constructor(array: ArrayLike) { + this._array = array + } + + static from | ArrayBufferView>(array: A): ArrayEnumerator> { + if (ArrayBuffer.isView(array)) { + return new ArrayEnumerator(toArrayLikeBuffer(array)) + } else { + return new ArrayEnumerator(array) + } + } + + [Symbol.iterator](): Iterator { + return this._array[Symbol.iterator]() + } + + setIndex(index: number) { + this._index = index + } + + moveNext(): boolean { + this._index += 1 + return this._index < this._array.length + } + + reset(): void { + this._index = -1 + } + + toIterator(): Iterator { + return this._array[Symbol.iterator]() + } + + next(...[_value]: [] | [any]): IteratorResult { + const done = this.moveNext() + return { value: this.current, done } as IteratorResult + } + + return?(_value?: any): IteratorResult { + return DoneIteratorResult + } + + throw?(_e?: any): IteratorResult { + return DoneIteratorResult + } +} + +export class Enumerator implements IEnumerator, Iterator { + protected _enumerator: IEnumerator + protected _index: number = 0 + + get current(): Nullable { + return this._enumerator.current + } + + constructor(enumerator: IEnumerator) { + this._enumerator = enumerator + } + + static fromIterable(iterable: Iterable): IEnumerator { + if (isArrayLike(iterable) || ArrayBuffer.isView(iterable)) { + return ArrayEnumerator.from(iterable) + } else { + return new this(new IterableEnumerator(iterable)) + } + } + + static fromIterator(iterator: Iterator, cache: boolean = true): IEnumerator { + if (cache) { + return new CachedIteratorEnumerator(iterator) + } else { + return new IteratorEnumerator(iterator) + } + } + + static toIterator(enumerator: IEnumerator): Iterator { + return new this(enumerator) + } + + [Symbol.iterator]() { + return this.toIterator() + } + + toIterator(): Iterator { + return Enumerator.toIterator(this) as Iterator + } + + moveNext(): boolean { + this._index += 1 + return this._enumerator.moveNext() + } + + reset(): void { + this._enumerator.reset() + } + + next(...[_value]: [] | [any]): IteratorResult { + const done = this.moveNext() + + return { value: this.current, done } as IteratorResult + } + + return?(_value?: any): IteratorResult { + return DoneIteratorResult + } + + throw?(_e?: any): IteratorResult { + return DoneIteratorResult + } +} + +export class HelperEnumerator implements IEnumerator, Iterator { + protected _enumerator: IEnumerator + protected _index: number = 0 + + get current(): Nullable { + return this._enumerator.current + } + + constructor(enumerator: IEnumerator) { + this._enumerator = enumerator + } + + static toIterator(enumerator: IEnumerator): Iterator { + return new this(enumerator) + } + + [Symbol.iterator]() { + return this.toIterator() + } + + toIterator(): Iterator { + return HelperEnumerator.toIterator(this) as Iterator + } + + moveNext(): boolean { + this._index += 1 + return this._enumerator.moveNext() + } + + reset(): void { + this._enumerator.reset() + } + + next(...[_value]: [] | [any]): IteratorResult { + const done = this.moveNext() + + return { value: this.current, done } as IteratorResult + } + + return?(_value?: any): IteratorResult { + return DoneIteratorResult + } + + throw?(_e?: any): IteratorResult { + return DoneIteratorResult + } +} + +export class DropEnumerator extends HelperEnumerator { + private _limit: number + + constructor(enumerator: IEnumerator, limit: number) { + super(enumerator) + this._limit = limit + } + + moveNext(): boolean { + let next = super.moveNext() + + while (this._limit > 0 && next) { + next = super.moveNext() + this._limit -= 1 + } + + return next + } +} + +export class FilterEnumerator extends HelperEnumerator { + private _filter: Predicate + + constructor(enumerator: IEnumerator, filter: Predicate) { + super(enumerator) + this._filter = filter + } + + moveNext(): boolean { + let next = super.moveNext() + + while (next && !this._filter(this.current, this._index)) { + next = super.moveNext() + } + + return next + } +} + +export class FlatMapEnumerator implements IEnumerator { + private _enumerator: IEnumerator + private _flatMap: Morphism> + private _inner?: IEnumerator + private _index: number = -1 + + constructor(enumerator: IEnumerator, flatMap: Morphism>) { + this._enumerator = enumerator + this._flatMap = flatMap + } + + get current(): Nullable { + return this._inner?.current + + } + + moveNext(): boolean { + if (this._inner && this._inner.moveNext()) { + return true + } + + const next = this._enumerator.moveNext() + + if (!next) { + return false + } + + this._index += 1 + this._inner = this._flatMap(this._enumerator.current, this._index) + return this._inner.moveNext() + } + + reset(): void { + this._index = -1 + this._inner = undefined + this._enumerator.reset() + } + + toIterator(): Iterator { + return HelperEnumerator.toIterator(this) as Iterator + } +} + +export class MapEnumerator implements IEnumerator { + private _enumerator: IEnumerator + private _map: Morphism + private _current!: U + private _index: number = -1 + + get current(): U { + return this._current + } + + constructor(enumerator: IEnumerator, map: Morphism) { + this._enumerator = enumerator + this._map = map + } + + toIterator(): Iterator { + return HelperEnumerator.toIterator(this) as Iterator + } + + moveNext(): boolean { + this._index += 1 + const next = this._enumerator.moveNext() + + if (next) { + this._current = this._map(this._enumerator.current, this._index) + } + + return next + } + + reset(): void { + this._index = -1 + this._enumerator.reset() + } +} + +export class TakeEnumerator extends HelperEnumerator { + private _limit: number + + constructor(enumerator: IEnumerator, limit: number) { + super(enumerator) + this._limit = limit + } + + moveNext(): boolean { + if (this._limit < 0) { + return false + } else { + super.moveNext() + this._limit -= 1 + return true + } + } +} + +export class FusedEnumerator implements IEnumerator { + private _enumerators: IEnumerator[] + private _index: number = 0 + + get current(): Nullable { + return this._cur()?.current + } + + constructor(enumerators: IEnumerator[]) { + this._enumerators = enumerators + } + + private _cur() { + return this._enumerators[this._index] + } + + private _done() { + return this._index >= this._enumerators.length + } + + toIterator(): Iterator { + return HelperEnumerator.toIterator(this) as Iterator + } + + moveNext(): boolean { + while (!this._done() && !this._cur().moveNext()) { + this._index += 1 + } + + return this._done() + } + + reset(): void { + const len = this._enumerators.length + + for (let i = 0; i < len; i++) { + this._enumerators[i].reset() + } + + this._index = 0 + } +} + +export class Enumerable implements IEnumerable, Iterable { + protected _enumerator: IEnumerator + + constructor(enumerator: IEnumerator) { + this._enumerator = enumerator + } + + static from(value: IEnumerable | IEnumerator | Iterable | Iterator): Enumerable { + if (this.isEnumerable(value)) { + return value + } else if (this.isEnumerator(value)) { + return new Enumerable(value) + } else if (isIterable(value)) { + const enumerator = Enumerator.fromIterable(value) + + if (isArrayLike(value)) { + return new ArrayEnumerble(enumerator) + } else { + return new Enumerable(enumerator) + } + } else if (isIterator(value)) { + return new Enumerable( + Enumerator.fromIterator(value) + ) + } else { + throw new TypeError('value is not enumerable') + } + } + + static isEnumerable(value: object): value is Enumerable { + return typeof value['enumerator'] === 'function' + } + + static isEnumerator(value: object): value is IEnumerator { + return hasMethods(['moveNext', 'reset', 'toIterator'], value) + } + + [Symbol.iterator](): Iterator { + return this._enumerator.toIterator() + } + + at(index: number): Nullable { + while (index >= 0 && this._enumerator.moveNext()) { + index -= 1 + } + + const value = this._enumerator.current + this._enumerator.reset() + return value + } + + atOrDefault(index: number, defaultValue: T) { + const value = this.at(index) + return value || defaultValue + } + + atOrElse(index: number, defaultValue: () => T) { + const value = this.at(index) + return value || defaultValue() + } + + concat(other: IEnumerable): IEnumerable { + return new Enumerable( + new FusedEnumerator([this._enumerator, other.enumerator()]) + ) + } + + drop(limit: number) { + return new Enumerable(new DropEnumerator(this._enumerator, limit)) + } + + enumerator(): IEnumerator { + return this._enumerator + } + + //entries(): IEnumerator { + + //} + + every(predicate: Predicate): boolean { + let index = 0 + while (this._enumerator.moveNext()) { + if (!predicate(this._enumerator.current, index)) { + return false + } + + index += 1 + } + + this._enumerator.reset() + return true + } + + filter(predicate: Predicate): IEnumerable { + return new Enumerable(new FilterEnumerator(this._enumerator, predicate)) + } + + flatMap(fn: Morphism>): IEnumerable { + return new Enumerable(new FlatMapEnumerator(this._enumerator, fn)) + } + + map(fn: Morphism): IEnumerable { + return new Enumerable(new MapEnumerator(this._enumerator, fn)) + } + + some(predicate: Predicate): boolean { + let index = 0 + while (this._enumerator.moveNext()) { + if (predicate(this._enumerator.current, index)) { + return true + } + + index += 1 + } + + this._enumerator.reset() + return false + } + + take(limit: number): IEnumerable { + return new Enumerable(new TakeEnumerator(this._enumerator, limit)) + } +} + +export class ArrayEnumerble extends Enumerable { + at(index: number): Nullable { + if (this._enumerator instanceof ArrayEnumerator) { + this._enumerator.setIndex(index) + return this._enumerator.current + } else { + return super.at(index) + } + } +} + + diff --git a/test/index.js b/test/index.js new file mode 100755 index 0000000..bd957a9 --- /dev/null +++ b/test/index.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node + +import { TerminalRunner } from 'folktest' +import * as Tests from './unit/index.js' + +console.log(TerminalRunner(Tests).toString()) + diff --git a/test/unit/enumerable.js b/test/unit/enumerable.js new file mode 100644 index 0000000..a7c4ed7 --- /dev/null +++ b/test/unit/enumerable.js @@ -0,0 +1,257 @@ +import { it, assert, assertEq, assertErr } from 'folktest' +import { ArrayEnumerator, CachedIteratorEnumerator, DropEnumerator, Enumerable, Enumerator, FilterEnumerator, FlatMapEnumerator, FusedEnumerator, IterableEnumerator, IteratorEnumerator, MapEnumerator, TakeEnumerator } from '../../dist/index.js' + +const createIterator = (arr = []) => { + let index = 0 + return { + next() { + return index < arr.length ? { + value: arr[index++], + done: false + } : { done: true } + } + } +} + +const createIterable = arr => ({ + [Symbol.iterator]() { + return createIterator(arr) + } +}) + +const createEnumerator = arr => Enumerator.fromIterable(createIterable(arr)) + +const createEnumerable = arr => Enumerable.from(createIterator(arr)) + +const helpers = { + concat: { + prepare: () => { + const e1 = createEnumerable([1, 2, 3]) + const e2 = createEnumerable([10, 20, 30]) + return e1.concat(e2)._enumerator + }, + expected: FusedEnumerator + }, + drop: { + prepare: () => createEnumerable().drop(0)._enumerator, + expected: DropEnumerator + }, + filter: { + prepare: () => createEnumerable().filter(x => x)._enumerator, + expected: FilterEnumerator + }, + flatMap: { + prepare: () => { + const e = createEnumerable() + return e.flatMap(() => createEnumerator([1]))._enumerator + }, + expected: FlatMapEnumerator + }, + map: { + prepare: () => createEnumerable().map(x => x)._enumerator, + expected: MapEnumerator + }, + take: { + prepare: () => createEnumerable().take(0)._enumerator, + expected: TakeEnumerator + } +} + +export const EnumerableFrom = [ + it('from iterator', () => { + const iterator = createIterator() + + const enumerator = Enumerable.from(iterator)._enumerator + assert(enumerator instanceof CachedIteratorEnumerator, `${enumerator.constructor.name} is not an instance of 'CachedIterableEnumerator'`) + }), + + it('from iterable', () => { + const iterable = createIterable() + + const enumerator = Enumerable.from(iterable)._enumerator._enumerator + assert(enumerator instanceof IterableEnumerator, `${enumerator.constructor.name} is not an instance of 'IterableEnumerator'`) + }), + + it('from array', () => { + const arr = [1, 2, 3] + const enumerator = Enumerable.from(arr)._enumerator + assert(enumerator instanceof ArrayEnumerator, `${enumerator.constructor.name} is not an instance of 'ArrayBuffer'`) + }), + + it('from arraybuffer', () => { + const ab = new Uint8Array(1) + const enumerator = Enumerable.from(ab)._enumerator + assert(enumerator instanceof ArrayEnumerator, `${enumerator.constructor.name} is not an instance of 'ArrayBuffer'`) + }), + + it('from enumerator', () => { + const iterable = createIterable() + + const enumerator1 = Enumerator.fromIterable(iterable) + const enumerable1 = Enumerable.from(enumerator1) + assert(enumerable1._enumerator._enumerator instanceof IterableEnumerator, `${enumerable1._enumerator._enumerator.constructor.name} is not an instance of IterableEnumerator`) + + const iterator = createIterator() + + const enumerator2 = Enumerator.fromIterator(iterator, false) + const enumerable2 = Enumerable.from(enumerator2) + assert(enumerable2._enumerator instanceof IteratorEnumerator, `${enumerable2._enumerator.constructor.name} is not an instance of IteratorEnumerator`) + + const enumerator3 = Enumerator.fromIterator(iterator, true) + const enumerable3 = Enumerable.from(enumerator3) + assert(enumerable3._enumerator instanceof CachedIteratorEnumerator, `${enumerable3._enumerator.constructor.name} is not an instance of CachedIteratorEnumerator`) + }), + + it('from enumerable', () => { + const enumerator = { + get current() { return undefined }, + moveNext() { return false }, + reset() { }, + } + + const enumerable1 = new Enumerable(enumerator) + const enumerable2 = Enumerable.from(enumerable1) + assertEq(enumerable1, enumerable2) + }), + + it('from invalid should error', () => { + const expected = /is not enumerable/g + + assertErr(() => { + Enumerable.from(1) + }, expected) + + assertErr(() => { + Enumerable.from(Symbol()) + }, expected) + }) +] + +export const EnumerableMethod = [ + it('at', () => { + const arr = [3, 2, 1] + const e1 = Enumerable.from(arr) + + const iterator = createIterator(arr) + const e2 = Enumerable.from(iterator) + + const iterable = createIterator(arr) + const e3 = Enumerable.from(iterable) + + const enums = [e1, e2, e3] + + enums.forEach(e => { + arr.forEach((value, index) => { + assertEq(e.at(index), value) + }) + }) + }), + + it('atOrDefault', () => { + const arr = [3] + const e = Enumerable.from(arr) + assertEq(e.atOrDefault(0, 12), 3) + assertEq(e.atOrDefault(1, 12), 12) + }), + + it('atOrElse', () => { + const arr = [3] + const e = Enumerable.from(arr) + const orElse = () => 12 + assertEq(e.atOrElse(0, orElse), 3) + assertEq(e.atOrElse(1, orElse), 12) + }), + + it('enumerator', () => { + assert( + Enumerable.from([]).enumerator() instanceof ArrayEnumerator + ) + }), + + it('every', () => { + const e = Enumerable.from([1, 1, 1]) + + assert(e.every(n => Number.isInteger(n)), `${e.toString()}.every failed`) + assert(!e.every(n => n > 1), `${e.toString()}.every passed when it should have failed`) + }), + + it('enumerator helpers', () => { + Object.entries(helpers).forEach(([name, { prepare, expected }]) => { + const enumerator = prepare() + assert(enumerator instanceof expected, `Enumerable.${name}(): ${enumerator.constructor.name} is not an instance of ${expected.name}`) + }) + }), + + it('some', () => { + const e = createEnumerable([1, 2, 3]) + + assert(e.some(n => n === 1), `${e.toString()}.some failed`) + assert(!e.some(n => n === 4), `${e.toString()}.some passed when it should have failed`) + }) +] + +export const EnumerableHelpers = [ + it('FusedEnumerator', () => { + const expected = [1, 2, 3, 10, 20, 30] + const e1 = createEnumerator([1, 2, 3]) + const e2 = createEnumerator([10, 20, 30]) + const fe = new FusedEnumerator([e1, e2]) + expected.forEach(n => { + fe.moveNext() + assertEq(fe.current, n) + }) + }), + + it('DropEnumerator', () => { + const expected = [4, 5, 6] + const e = new DropEnumerator(createEnumerator([1, 2, 3, 4, 5, 6]), 3) + expected.forEach(n => { + e.moveNext() + assertEq(e.current, n) + }) + }), + + it('FilterEnumerator', () => { + const expected = [1, 3, 5] + const e = new FilterEnumerator(createEnumerator([1, 2, 3, 4, 5, 6]), x => x % 2 !== 0) + expected.forEach(n => { + e.moveNext() + assertEq(e.current, n) + }) + }), + + it('FlatMapEnumerator', () => { + const expected = [1, 2, 2, 3, 3, 3] + const e = new FlatMapEnumerator( + createEnumerator([1, 2, 3]), + n => { + const arr = Array.from({ length: n }).fill(n) + return createEnumerator(arr) + } + ) + + expected.forEach(n => { + e.moveNext() + assertEq(e.current, n) + }) + }), + + it('MapEnumerator', () => { + const expected = [1, 4, 9] + const e = new MapEnumerator(createEnumerator([1, 2, 3]), n => n * n) + expected.forEach(n => { + e.moveNext() + assertEq(e.current, n) + }) + }), + + it('TakeEnumerator', () => { + const expected = [1, 2, 3] + const e = new TakeEnumerator(createEnumerator([1, 2, 3, 4, 5, 6]), 3) + expected.forEach(n => { + e.moveNext() + assertEq(e.current, n) + }) + }) +] + diff --git a/test/unit/index.js b/test/unit/index.js new file mode 100644 index 0000000..ea657d2 --- /dev/null +++ b/test/unit/index.js @@ -0,0 +1,2 @@ +export * from './enumerable.js' +