scontainers
Version:
A container/collection/iterator library for JavaScript, comfortable to use, performant and versatile.
238 lines (181 loc) • 5.6 kB
JavaScript
;
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;
}