scontainers
Version:
A container/collection/iterator library for JavaScript, comfortable to use, performant and versatile.
730 lines (578 loc) • 17.9 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,
toStr
} = require('../utils.js');
_testTraitSet(traits.utils);
_testTraitSet(traits.scontainers);
const _nth = _getSymbol("nth", traits.utils, traits.scontainers);
const _len = _getSymbol("len", traits.utils, traits.scontainers);
const _addTraitFactories = _getSymbol("addTraitFactories", traits.utils, traits.scontainers);
const _setNth = _getSymbol("setNth", traits.utils, traits.scontainers);
const _reverse = _getSymbol("reverse", traits.utils, traits.scontainers);
const _truncate = _getSymbol("truncate", traits.utils, traits.scontainers);
const _kvIterator = _getSymbol("kvIterator", traits.utils, traits.scontainers);
const _kvReorderedIterator = _getSymbol("kvReorderedIterator", traits.utils, traits.scontainers);
const _whileEach = _getSymbol("whileEach", traits.utils, traits.scontainers);
const _forEach = _getSymbol("forEach", traits.utils, traits.scontainers);
const _reduce = _getSymbol("reduce", traits.utils, traits.scontainers);
const _nthKVN = _getSymbol("nthKVN", traits.utils, traits.scontainers);
const _first = _getSymbol("first", traits.utils, traits.scontainers);
const _find = _getSymbol("find", traits.utils, traits.scontainers);
const _get = _getSymbol("get", traits.utils, traits.scontainers);
const _set = _getSymbol("set", traits.utils, traits.scontainers);
const _sum = _getSymbol("sum", traits.utils, traits.scontainers);
const _reduceFirst = _getSymbol("reduceFirst", traits.utils, traits.scontainers);
const _add = _getSymbol("add", traits.utils, traits.scontainers);
const _flatten = _getSymbol("flatten", traits.utils, traits.scontainers);
const _hasKey = _getSymbol("hasKey", traits.utils, traits.scontainers);
const _untilEach = _getSymbol("untilEach", traits.utils, traits.scontainers);
const _map = _getSymbol("map", traits.utils, traits.scontainers);
const _collect = _getSymbol("collect", traits.utils, traits.scontainers);
const _has = _getSymbol("has", traits.utils, traits.scontainers);
const _from = _getSymbol("from", traits.utils, traits.scontainers);
const _values = _getSymbol("values", traits.utils, traits.scontainers);
const _wrapScontainer = _getSymbol("wrapScontainer", traits.utils, traits.scontainers);
const _flattenDeep = _getSymbol("flattenDeep", traits.utils, traits.scontainers);
const _filter = _getSymbol("filter", traits.utils, traits.scontainers);
const _slice = _getSymbol("slice", traits.utils, traits.scontainers);
const _skipWhile = _getSymbol("skipWhile", traits.utils, traits.scontainers);
const _takeWhile = _getSymbol("takeWhile", traits.utils, traits.scontainers);
const Values = require('../decorators/values.js');
const Entries = require('../decorators/entries.js');
const Filter = require('../decorators/filter.js');
const Slice = require('../decorators/slice.js');
const Chunk = require('../decorators/chunk.js');
const Map = require('../decorators/map.js');
const MapKey = require('../decorators/map_key.js');
const Cache = require('../decorators/cache.js');
const Iter = require('../decorators/iter.js');
const Reordered = require('../decorators/reordered.js');
const GroupBy = require('../decorators/group_by.js');
const Cow = require('../decorators/cow.js');
const Flatten = require('../decorators/flatten.js');
const Reverse = require('../decorators/reverse.js');
const SkipWhile = require('../decorators/skip_while.js');
const TakeWhile = require('../decorators/take_while.js');
function deriveProtocols() {
assert(this, `deriveProtocols() must be called on an object`);
const Collection = this;
const proto = Collection.prototype; // checking that the collection is OK
{
if (proto[_nth]) {
assert(proto[_len], `${Collection.name} enumerable, but unknown \`len\``);
}
}
traits.scontainers[_addTraitFactories](proto, {
// TODO: move these away from here! they're core traits...
push() {
if (proto[_setNth] && proto[_len]) {
return function push(value) {
const n = this[_len]();
this[_setNth](n, value);
};
}
},
unshift() {
if (proto[_setNth] && proto[_len]) {
return function push(value) {
this[_reverse]().forEach((v, k, n) => {
this[_setNth](n + 1, v);
});
this[_setNth](0, value);
};
}
},
remove() {
if (proto[_setNth] && proto[_len]) {
return function remove(n) {
const len = this[_len]();
for (let i = n; i < len; ++i) {
this[_setNth](i - 1, this[_nth](i));
}
this[_truncate](len - 1);
};
}
},
forEach() {
if (proto[_kvIterator]) {
return function forEach(fn) {
const it = this[_kvIterator]();
let next = it.next();
while (next) {
const {
value,
key,
n
} = next;
fn(value, key, n);
next = it.next();
}
};
}
if (proto[_kvReorderedIterator]) {
return function forEach(fn) {
const rit = this[_kvReorderedIterator]();
rit.onNext(({
key,
value,
n
}) => void fn(value, key, n));
rit.proceed();
};
}
},
whileEach() {
if (proto[_kvIterator]) {
return function forEach(fn) {
const it = this[_kvIterator]();
let next = it.next();
while (next) {
const {
value,
key,
n
} = next;
if (!fn(value, key, n)) {
return next;
}
next = it.next();
}
};
}
if (proto[_kvReorderedIterator]) {
return function (fn) {
let result;
{
const rit = this[_kvReorderedIterator]();
rit.onNext(kvn => {
if (!fn(kvn.value, kvn.key, kvn.n)) {
result = kvn;
rit.stop();
}
});
rit.proceed();
}
return result;
};
}
},
untilEach() {
if (proto[_whileEach]) {
return function untilEach(fn) {
return this[_whileEach]((value, key, n) => !fn(value, key, n));
};
}
},
reduce() {
if (proto[_forEach]) {
return function reduce(fn, start) {
let state = start;
this[_forEach]((value, key, n) => {
state = fn(state, value, key, n);
});
return state;
};
}
},
reduceFirst() {
if (proto[_kvIterator]) {
return function reduceFirst(fn) {
const it = this[_kvIterator]();
let next = it.next();
if (!next) {
return;
}
let state = next.value;
next = it.next();
while (next) {
const {
value,
key,
n
} = next;
state = fn(state, value, key, n);
next = it.next();
}
return state;
};
}
if (proto[_reduce]) {
return function reduceFirst(fn) {
let first = true;
return this[_reduce]((state, value, key, n) => {
if (first) {
first = false;
return value;
}
return fn(state, value, key, n);
});
};
}
},
count() {
if (proto[_len]) {
return function count() {
return this[_len]();
};
}
if (proto[_reduce]) {
return function count() {
return this[_reduce](n => n + 1, 0);
};
}
},
isEmpty() {
if (proto[_len]) {
return function isEmpty() {
return !this[_len]();
};
}
if (proto[_whileEach]) {
return function isEmpty() {
return !this[_whileEach](() => false);
};
}
},
only() {
if (proto[_nthKVN]) {
return function only() {
assert(this[_len]() === 1, `${this}.only()'s collection has ${this[_len]()} items`);
return this[_nthKVN](0);
};
}
if (proto[_len]) {
return function only() {
assert(this[_len]() === 1, `${this}.only()'s collection has ${this[_len]()} items`);
return this[_kvIterator]().next();
};
}
if (proto[_kvIterator]) {
return function only() {
const it = this[_kvIterator]();
let next = it.next();
assert(next, `${this}.only()'s collection is empty`);
assert(!it.next(), `${this}.only()'s collection has multiple items`);
return next;
};
}
if (proto[_whileEach]) {
return function only() {
let count = 0;
this[_whileEach](() => {
assert(!count, `${this}.only()'s collection has multiple items`);
++count;
return true;
});
assert(count === 1, `${this}.only()'s collection is empty`);
return this[_whileEach](() => false);
};
}
},
first() {
if (proto[_nthKVN]) {
return function first() {
return this[_nthKVN](0);
};
}
if (proto[_kvIterator]) {
return function first() {
return this[_kvIterator]().next();
};
}
if (proto[_whileEach]) {
return function first() {
return this[_whileEach](() => false);
};
}
},
last() {
if (proto[_nthKVN]) {
return function last() {
const n = this[_len]() - 1;
return this[_nthKVN](n);
};
} else if (proto[_reverse]) {
return function last() {
return this[_reverse]()[_first]();
};
}
},
random() {
if (proto[_nthKVN]) {
return function random() {
const n = Math.floor(Math.random() * this[_len]());
return this[_nthKVN](n);
};
}
},
find() {
if (proto[_whileEach]) {
return function find(fn) {
return this[_whileEach]((v, k, n) => {
return !fn(v, k, n);
});
};
}
},
findLast(fn) {
if (proto[_reverse] && proto[_find]) {
return function findLast(fn) {
return this[_reverse]()[_find](fn);
};
}
},
swapNs() {
if (proto[_nth] && proto[_setNth]) {
return function (n1, n2) {
const value1 = this[_nth](n1);
this[_setNth](n1, this[_nth](n2));
this[_setNth](n2, value1);
};
}
},
swapKeys() {
if (proto[_get] && proto[_set]) {
return function (key1, key2) {
const value1 = this[_get](key1);
this[_set](key1, this[_get](key2));
this[_set](key2, value1);
};
}
},
sum() {
if (proto[_reduce]) {
return function sum() {
return this[_reduce]((sum, n) => sum + n, 0);
};
}
},
avg() {
if (proto[_len]) {
return function avg() {
return this[_sum]() / this[_len]();
};
}
if (proto[_reduce]) {
return function avg() {
const [sum, count] = this[_reduce](([sum, count], n) => [sum + n, count + 1], [0, 0]);
return sum / count;
};
}
},
min() {
// TODO: `reduce` could be more performant in some cases...
if (proto[_reduceFirst]) {
return function () {
return this[_reduceFirst]((min, n) => Math.min(min, n));
};
}
},
max() {
// TODO: `reduce` could be more performant in some cases...
if (proto[_reduceFirst]) {
return function () {
return this[_reduceFirst]((max, n) => Math.max(max, n));
};
}
},
join() {
// TODO: `reduce` could be more performant in some cases...
if (proto[_reduceFirst]) {
return function join(sep = '') {
return this[_reduceFirst]((str, substr) => str + sep + substr, '');
};
}
},
assign() {
if (proto[_add]) {
return function assign(...collections) {
return collections[_flatten]()[_forEach](value => this[_add](value));
};
}
if (proto[_set]) {
return function assign(...collections) {
return collections[_flatten]()[_forEach]((value, key) => this[_set](key, value));
};
}
},
defaults() {
if (proto[_set]) {
return function defaults(...collections) {
return collections[_flatten]()[_forEach]((value, key) => {
if (!this[_hasKey](key)) {
this[_set](key, value);
}
});
};
}
},
every() {
if (proto[_whileEach]) {
return function every(fn) {
return !this[_whileEach](fn);
};
}
},
some() {
if (proto[_untilEach]) {
return function some(fn) {
return !!this[_untilEach](fn);
};
}
},
toString() {
if (proto[_nth]) {
return function toString() {
//this._Straits.map( (value)=>value::toStr() )._Straits.collect( Array );
// this._Straits.iterator();
// console.log( this._Straits.len(), this.constructor.name );
// console.log( this._Straits.kvIterator.toString() );
const out = this[_map](value => toStr.call(value))[_collect](Array);
return `*[${out.join(', ')}]`;
};
}
if (proto[_forEach] && proto[_has]) {
return function toString() {
const out = this[_map](item => toStr.call(item))[_collect](Array);
return `*{${out.join(', ')}}`;
};
}
if (proto[_forEach]) {
return function toString() {
const out = this[_map]((value, key) => `${toStr.call(key)}:${toStr.call(value)}`)[_collect](Array);
return `*{${out.join(', ')}}`;
};
}
console.warn(`${Collection.name} not printable`);
},
consume() {
return function consume(TargetCollection) {
assert(TargetCollection[_from], `No ${TargetCollection.name}.*from()`);
return TargetCollection[_from](this);
};
},
collect() {
return function collect(TargetCollection) {
assert(TargetCollection[_from], `No ${TargetCollection.name}.*from()`);
return TargetCollection[_from](this);
};
}
});
traits.scontainers[_addTraitFactories](proto, {
keys() {
return function keys() {
return this[_map](function key(value, key) {
return key;
})[_values]();
};
},
values: Collection[_wrapScontainer](Values),
entries: Collection[_wrapScontainer](Entries),
enumerate() {
return function enumerate() {
global.TODO();
let count = 0;
return this[_map](value => [count++, value]);
};
},
filter: Collection[_wrapScontainer](Filter),
slice: Collection[_wrapScontainer](Slice),
chunk: Collection[_wrapScontainer](Chunk),
map: Collection[_wrapScontainer](Map),
mapKey: Collection[_wrapScontainer](MapKey),
cache: Collection[_wrapScontainer](Cache),
iter: Collection[_wrapScontainer](Iter),
reordered: Collection[_wrapScontainer](Reordered),
groupBy: Collection[_wrapScontainer](GroupBy),
cow: Collection[_wrapScontainer](Cow),
flatten: Collection[_wrapScontainer](Flatten),
reverse: Collection[_wrapScontainer](Reverse),
flattenDeep() {
if (proto[_map]) {
return function () {
return this[_map](value => value[_flattenDeep] ? value[_flattenDeep]() : value)[_flatten]();
};
}
},
concat() {
return function concat(...collections) {
return [this, ...collections][_flatten]();
};
},
uniq() {
if (proto[_filter]) {
return function uniq(eq = (a, b) => a === b) {
let last = NaN;
return this[_filter]((value, key) => {
const result = eq(last, value);
last = value;
return !result;
});
};
}
},
skipWhile: Collection[_wrapScontainer](SkipWhile),
takeWhile: Collection[_wrapScontainer](TakeWhile),
skip() {
if (proto[_slice]) {
return function skip(n) {
return this[_slice](n);
};
}
return function skip(n) {
let counter = 0;
return this[_skipWhile](() => {
++counter;
return counter < n;
});
};
},
take() {
if (proto[_slice]) {
return function take(n) {
return this[_slice](0, n);
};
}
return function take(n) {
let counter = 0;
return this[_takeWhile](() => {
++counter;
return counter < n;
});
};
}
});
}
module.exports = {
deriveProtocols
};