scontainers
Version:
A container/collection/iterator library for JavaScript, comfortable to use, performant and versatile.
526 lines (406 loc) • 14.4 kB
JavaScript
"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 {
deriveProtocols
} = require('./derived_traits.js');
const {
assert,
traits,
options,
ReorderedIterator,
KVN,
KVIt,
Done
} = require('../utils.js');
_testTraitSet(traits.utils);
_testTraitSet(traits.descriptors);
_testTraitSet(traits.scontainers);
const _InnerCollection = _getSymbol("InnerCollection", traits.utils, traits.descriptors, traits.scontainers);
const _extractKeys = _getSymbol("extractKeys", traits.utils, traits.descriptors, traits.scontainers);
const _keyToN = _getSymbol("keyToN", traits.utils, traits.descriptors, traits.scontainers);
const _implScontainer = _getSymbol("implScontainer", traits.utils, traits.descriptors, traits.scontainers);
const _addTraitFactories = _getSymbol("addTraitFactories", traits.utils, traits.descriptors, traits.scontainers);
const _len = _getSymbol("len", traits.utils, traits.descriptors, traits.scontainers);
const _nToKey = _getSymbol("nToKey", traits.utils, traits.descriptors, traits.scontainers);
const _hasKey = _getSymbol("hasKey", traits.utils, traits.descriptors, traits.scontainers);
const _innerCollectionKey = _getSymbol("innerCollectionKey", traits.utils, traits.descriptors, traits.scontainers);
const _mappingOnly = _getSymbol("mappingOnly", traits.utils, traits.descriptors, traits.scontainers);
const _nthKVN = _getSymbol("nthKVN", traits.utils, traits.descriptors, traits.scontainers);
const _getKVN = _getSymbol("getKVN", traits.utils, traits.descriptors, traits.scontainers);
const _nth = _getSymbol("nth", traits.utils, traits.descriptors, traits.scontainers);
const _transformStream = _getSymbol("transformStream", traits.utils, traits.descriptors, traits.scontainers);
const _kvIterator = _getSymbol("kvIterator", traits.utils, traits.descriptors, traits.scontainers);
const _kvReorderedIterator = _getSymbol("kvReorderedIterator", traits.utils, traits.descriptors, traits.scontainers);
const _setNth = _getSymbol("setNth", traits.utils, traits.descriptors, traits.scontainers);
const _has = _getSymbol("has", traits.utils, traits.descriptors, traits.scontainers);
const _values = _getSymbol("values", traits.utils, traits.descriptors, traits.scontainers);
const _iterator = _getSymbol("iterator", traits.utils, traits.descriptors, traits.scontainers);
const _implCoreTraits = _getSymbol("implCoreTraits", traits.utils, traits.descriptors, traits.scontainers);
_implSymbol(Object.prototype, _implCoreTraits, function (compilerConfiguration) {
if (!options.derivation) {
return;
}
if (this[_InnerCollection]) {
deriveProtocolsForTransformation.call(this, compilerConfiguration);
} else {
deriveProtocolsForRootType.call(this, compilerConfiguration);
} // deriving all the core protocols again, in case something depends on the newly added iterators...
deriveCoreProtocols.call(this); // deriving non-core protocols
deriveProtocols.call(this);
});
function deriveProtocolsForRootType(configuration = {}) {
assert(this, `deriveProtocolsForRootType() must be called on an object`);
const check = (cond, err) => {
assert(cond, `${this.name}.compileProtocolsForTransformation(): ${err}`);
}; // taking the non-protocol data from `configuration` (e.g. `nStage` and `stage`)
let {
nthUnchecked,
getUnchecked
} = configuration[_extractKeys](Object.keys({
nthUnchecked: null,
getUnchecked: null
})); // deriving the missing non-protocol functions we can derive
{
if (nthUnchecked) {
check(!getUnchecked, `either supply \`nthUnchecked\` or \`getUnchecked\``);
getUnchecked = function (key) {
const n = this[_keyToN](key);
return nthUnchecked.call(this, n);
};
}
} // everything in `compilerConfiguration` should be protocol generator factories: assigning them to `this`
this.prototype[_implScontainer](configuration); // deriving the other core protocol generator factories we can derive from the non-protocol data
{
const proto = this.prototype;
traits.scontainers[_addTraitFactories](this.prototype, {
inplace() {
return function inplace() {
return this;
};
},
hasKey() {
if (nthUnchecked) {
return function (key) {
const n = this[_keyToN](key);
return Number.isInteger(n) && n >= 0 && n < this[_len]();
};
}
},
nthKVN() {
if (nthUnchecked) {
return function (n) {
return new KVN(this[_nToKey](n), nthUnchecked.call(this, n), n);
};
}
},
getKVN() {
if (getUnchecked) {
if (proto[_keyToN]) {
return function (key) {
if (this[_hasKey](key)) {
return new KVN(key, getUnchecked.call(this, key), this[_keyToN](key));
}
};
} else {
return function (key) {
if (this[_hasKey](key)) {
return new KVN(key, getUnchecked.call(this, key));
}
};
}
}
},
nth() {
if (nthUnchecked) {
return function (n) {
assert(Number.isInteger(n), `${this.constructor.name}.nth(${n}): ${n} not valid`);
if (n >= 0 && n < this[_len]()) {
return nthUnchecked.call(this, n);
}
};
}
},
get() {
if (getUnchecked && proto[_hasKey]) {
return function (key) {
if (!this[_hasKey](key)) {
return;
}
return getUnchecked.call(this, key);
};
}
},
kvIterator() {
if (nthUnchecked) {
return function kvIterator() {
return {
collection: this,
i: 0,
next() {
const coll = this.collection;
if (this.i < coll[_len]()) {
const n = this.i++;
return new KVN(coll[_nToKey](n), nthUnchecked.call(coll, n), n);
}
}
};
};
}
}
});
}
}
function deriveProtocolsForTransformation(configuration = {}) {
assert(this, `deriveProtocolsForTransformation() must be called on an object`);
const check = (cond, err) => {
assert(cond, `${this.name}.compileProtocolsForTransformation(): ${err}`);
};
const Collection = this;
const ParentCollection = this[_InnerCollection];
check(ParentCollection, `need to specify the ParentType`);
const parentProto = ParentCollection.prototype;
const innerCollectionKey = this[_innerCollectionKey]; // TODO: `stage` (and its specializations) could be improved...
// most collections don't do anything *before* `stage` returns from recursion.
// it would be more efficient (and easier to write) if we had a `stageEnd` that doesn't recurse, like the old `propagator.next`.
// taking the non-protocol data from `configuration` (e.g. `nStage` and `stage`)
let {
stage,
nStage,
kStage,
indexToParentIndex,
nToParentN,
keyToParentKey
} = configuration[_extractKeys](Object.keys({
stage: null,
nStage: null,
kStage: null,
indexToParentIndex: null,
nToParentN: null,
keyToParentKey: null
})); // deriving the missing non-protocol functions we can derive
{
check(!!stage === !!indexToParentIndex && !!nStage === !!nToParentN && !!kStage === !!keyToParentKey, `\`*Stage\` needs to match \`*ToParent*\``);
if (stage) {
check(!nStage && !kStage, `either supply \`stage\` or \`nStage\` or \`kStage\``);
nStage = stage;
kStage = stage;
}
if (indexToParentIndex) {
check(!nToParentN && !keyToParentKey, `either supply \`indexToParentIndex\` or \`nToParentN\` or \`keyToParentKey\``);
nToParentN = indexToParentIndex;
keyToParentKey = indexToParentIndex;
}
} // all the remaining stuff in `compilerConfiguration` should be protocol generator factories: assigning them to `this`
traits.scontainers[_addTraitFactories](this.prototype, configuration); // deriving the other core protocol factories we can derive from the non-protocol data
traits.scontainers[_addTraitFactories](this.prototype, {
len() {
if (Collection[_mappingOnly] && parentProto[_len]) {
return function () {
return this[innerCollectionKey][_len]();
};
}
},
nToKey() {
if (nToParentN && parentProto[_nToKey]) {
return function (n) {
const parentN = nToParentN.call(this, n);
return this[innerCollectionKey][_nToKey](parentN);
};
}
},
keyToN() {
if (keyToParentKey && parentProto[_keyToN]) {
return function (key) {
const innerKey = keyToParentKey.call(this, key);
return this[innerCollectionKey][_keyToN](innerKey);
};
}
},
nthKVN() {
if (nStage && parentProto[_nthKVN]) {
return function (n) {
const parentN = nToParentN.call(this, n);
const parentKVN = this[innerCollectionKey][_nthKVN](parentN);
parentKVN.n = n;
if (parentKVN) {
return nStage.call(this, parentKVN);
}
};
}
},
getKVN() {
if (kStage && parentProto[_getKVN]) {
return function (key) {
const innerKey = keyToParentKey.call(this, key);
const parentKVN = this[innerCollectionKey][_getKVN](innerKey);
if (parentKVN) {
return kStage.call(this, parentKVN);
}
};
}
},
hasKey() {
if (kStage && parentProto[_nth]) {
return function (key) {
return kStage.call(this, c => {
this[innerCollectionKey][_nth](c, c.key);
});
};
}
if (Collection[_mappingOnly] && parentProto[_hasKey]) {
return function (key) {
return this[innerCollectionKey][_hasKey](keyToParentKey(key));
};
}
}
}); // deriving all the remaining core protocols
deriveCoreProtocols.call(this); // if there are no iterators, we can derived them starting from the parent collection...
// TODO NOTE FIXME: BROKEN
// 1. we have no way to say when to start and when to stop for `Slice`
// get rid of the above `this::deriveCoreProtocols()` to see whether it's fixed
traits.scontainers[_addTraitFactories](this.prototype, {
kvIterator() {
if (Collection[_transformStream] && parentProto[_kvIterator] && kStage) {
return function () {
const self = this;
const parentCollection = this[innerCollectionKey];
const it = parentCollection[_kvIterator]();
return {
next() {
const next = it.next();
if (!next) {
return;
}
const kvn = kStage.call(self, next);
if (!kvn) {
return this.next();
}
return new KVN(kvn.key, kvn.value, kvn.n);
}
};
};
}
},
kvReorderedIterator() {
if (parentProto[_kvReorderedIterator] && kStage) {
return function () {
const innerRIt = this[innerCollectionKey][_kvReorderedIterator]();
return new ReorderedIterator.MapReorderedIterator(innerRIt, kStage.bind(this));
};
}
}
});
}
function deriveCoreProtocols() {
assert(this, `deriveCoreProtocols() must be called on an object`);
const Collection = this;
const proto = Collection.prototype;
traits.scontainers[_addTraitFactories](proto, {
nth() {
if (proto[_nthKVN]) {
return function (n) {
const kvn = this[_nthKVN](n);
if (kvn) {
return kvn.value;
}
};
}
},
get() {
if (proto[_getKVN]) {
return function (key) {
const kvn = this[_getKVN](key);
if (kvn) {
return kvn.value;
}
};
}
},
set() {
if (proto[_setNth]) {
return function (key, value) {
const n = this[_keyToN](key);
this[_setNth](n, value);
};
}
},
kvIterator() {
if (proto[_nthKVN]) {
return function kvIterator() {
return {
collection: this,
i: 0,
next() {
const coll = this.collection;
if (this.i < coll[_len]()) {
const n = this.i++;
const kvn = coll[_nthKVN](n);
assert(kvn, `${Collection.name}.nth(${n}) not there?! :F`);
return kvn;
}
}
};
};
}
},
kvReorderedIterator() {
if (proto[_kvIterator]) {
return function kvReorderedIterator() {
return new ReorderedIterator.FromIterator(this[_kvIterator]());
};
}
},
iterator() {
if (proto[_nth] || proto[_has]) {
return function () {
return this[_values]()[_iterator]();
};
}
if (proto[_kvIterator]) {
return function iterator() {
return {
it: this[_kvIterator](),
next() {
const next = this.it.next();
if (!next) {
return new Done();
}
const {
key,
value
} = next;
return new KVIt(key, value);
}
};
};
}
}
});
}