UNPKG

scontainers

Version:

A container/collection/iterator library for JavaScript, comfortable to use, performant and versatile.

238 lines (181 loc) 5.6 kB
"use strict"; function _implSymbol(target, sym, value) { Object.defineProperty(target, sym, { value, configurable: true }); return target[sym]; } function _getSymbol(targetSymName, ...traitSets) { let symbol; traitSets.forEach(traitSet => { const sym = traitSet[targetSymName]; if (typeof sym === 'symbol') { if (!!symbol && symbol !== sym) { throw new Error(`Symbol ${targetSymName} offered by multiple trait sets.`); } symbol = sym; } }); if (!symbol) { throw new Error(`No trait set is providing symbol ${targetSymName}.`); } return symbol; } function _testTraitSet(traitSet) { if (!traitSet || typeof traitSet === 'boolean' || typeof traitSet === 'number' || typeof traitSet === 'string') { throw new Error(`${traitSet} cannot be used as a trait set.`); } } const { assert, traits, id, ReorderedIterator, KVN } = require('../utils.js'); _testTraitSet(traits.utils); _testTraitSet(traits.scontainers); _testTraitSet(traits.semantics); const _kvReorderedIterator = _getSymbol("kvReorderedIterator", traits.utils, traits.scontainers, traits.semantics); const _describeScontainer = _getSymbol("describeScontainer", traits.utils, traits.scontainers, traits.semantics); const _implCoreTraits = _getSymbol("implCoreTraits", traits.utils, traits.scontainers, traits.semantics); const _hasKey = _getSymbol("hasKey", traits.utils, traits.scontainers, traits.semantics); const _set = _getSymbol("set", traits.utils, traits.scontainers, traits.semantics); const _get = _getSymbol("get", traits.utils, traits.scontainers, traits.semantics); const _toString = _getSymbol("toString", traits.utils, traits.scontainers, traits.semantics); module.exports = function (ParentCollection) { const parentProto = ParentCollection.prototype; if (!parentProto[_kvReorderedIterator]) { return; } if (!Group) { makeGroup(); } return function () { class GroupBy { static get name() { return `${ParentCollection.name}::GroupBy`; } constructor(coll, groupByFn) { this.wrapped = coll; this.groupByFn = groupByFn; } toString() { return `${this.wrapped}.groupBy(${this.groupByFn.name || 'ƒ'})`; } } GroupBy[_describeScontainer]({ InnerCollection: ParentCollection, innerCollectionKey: id`wrapped`, argKeys: [id`groupByFn`] }); GroupBy[_implCoreTraits]({ kvReorderedIterator() { if (parentProto[_kvReorderedIterator]) { return function kvReorderedIterator() { const rit = this.wrapped[_kvReorderedIterator](); return new GroupBy.ReorderedIterator(rit, this.groupByFn); }; } } }); GroupBy.ReorderedIterator = class extends ReorderedIterator { constructor(rit, groupByFn) { super(); this.rit = rit; this.groupByFn = groupByFn; this.groups = new Map(); this.rit.onNext(kvn => { const groupName = this.groupByFn(kvn.value, kvn.key, kvn.n); if (!this.groups[_hasKey](groupName)) { const group = new Group(this, kvn); this.groups[_set](groupName, group); this._pushNext(new KVN(groupName, group)); } else { const group = this.groups[_get](groupName); group.rit._pushNext(kvn); } }); } proceed() { super.proceed(); this.rit.proceed(); } resume() { super.resume(); this.rit.resume(); } stop() { super.stop(); global.TODO(`We should stop creating new groups, but keep iterating for old ones - but only if some are still active...`); } }; return GroupBy; }; }; let Group; function makeGroup() { Group = class Group { static get name() { return `GroupBy.Group`; } constructor(groupByRIt, firstKVN) { this.state = Group.state.ready; this.rit = new Group.ReorderedIterator(groupByRIt, firstKVN); } toString() { return `GroupBy.Group{state:${this.state[_toString]()}}`; } }; Group.state = { ready: Symbol(`ready`), done: Symbol(`done`), preproceeding: Symbol(`preproceeding`), proceeding: Symbol(`proceeding`), stopped: Symbol(`stopped`) }; Group.ReorderedIterator = class extends ReorderedIterator { constructor(groupByRIt, firstKVN) { super(); this.groupByRIt = groupByRIt; this.firstKVN = firstKVN; } proceed() { super.proceed(); this._pushNext(this.firstKVN); this.firstKVN = undefined; this._work(); } resume() { super.resume(); this._work(); } stop() { super.stop(); } _work() { this.groupByRIt.resume(); } _pushNext(kvn) { if (this.state === ReorderedIterator.state.ready) { // if the main iterator (this.`groupByRIt`) pushes a new item before were told to proceed, we want to stop: // we don't want to let the user `proceed` on a group iterator some iterations after the group was created. this.stop(); } else if (this.state === ReorderedIterator.state.proceeding) { super._pushNext(kvn); } } }; Group[_describeScontainer]({ argKeys: [id`state`] }); Group[_implCoreTraits]({ kvReorderedIterator() { assert(this.state === Group.state.ready, `A GroupBy.Group is iterable only once`); this.state = Group.state.done; return this.rit; } }); return Group; }