@freeword/meta
Version:
Meta package for Freeword: exports all core types, constants, and utilities from the src/ directory.
302 lines • 10.6 kB
JavaScript
import _ /**/ from 'lodash';
import { throwable } from "./Outcome.js";
export function* keyStar(obj) {
if (_.isArray(obj)) {
for (let seq = 0; seq < obj.length; seq++) {
yield seq;
}
return undefined;
}
yield* _.keys(obj);
return undefined;
}
export function* valuesStar(obj) {
for (const key of keyStar(obj)) {
yield obj[key];
}
return undefined;
}
export function* entriesStar(obj) {
let seq = 0;
for (const key of keyStar(obj)) {
yield [key, obj[key], seq++];
}
return undefined;
}
export function classifyIterable(obj) {
if (!obj) {
return false;
}
if (_.isFunction(obj?.next) && _.isFunction(obj?.return)) {
return 'iterator';
}
if (_.isFunction(obj[Symbol.iterator])) {
return 'iterable';
}
if (_.isFunction(obj[Symbol.asyncIterator])) {
return 'asyncIterator';
}
return false;
}
export function isAnyIter(obj) {
return (!!classifyIterable(obj));
}
/** @returns true if `obj` is a sync iterable -- @see {@link isAsyncIterable}, {@link iteratorFor} */
export function isSyncIterable(obj) { return obj && (_.isFunction(obj[Symbol.iterator])); }
/** @returns true if `obj` is an async iterable -- @see {@link isSyncIterable}, {@link asyncIteratorFor} */
export function isAsyncIterable(obj) { return obj && (_.isFunction(obj[Symbol.asyncIterator])); }
export function* sliceStar(obj, beg, end, { len, treatAs } = {}) {
if (_.isArray(obj)) {
yield* obj.slice(beg, end);
return ['array', beg, end, obj.length];
}
if ((treatAs === 'stream') || (isAnyIter(obj) && (treatAs !== 'object'))) {
yield* sliceStream(obj, beg, end, len);
return ['iterable', beg, end, len];
}
yield* sliceStream(valuesStar(obj), beg, end, len);
return ['object', beg, end, len];
}
function saneSliceArg0(arg0, len) {
if (!arg0) {
return 0;
}
if (isNegInfinity(arg0)) {
return 0;
}
if (arg0 >= 0) {
return arg0;
}
if (len !== undefined) {
return (len + arg0);
}
return arg0;
}
function isPosInfinity(val) {
return val === Number.POSITIVE_INFINITY;
}
function isNegInfinity(val) {
return val === Number.NEGATIVE_INFINITY;
}
/** yields objects from the beg'th to the end'th index, using the same sematics as @see {@link Array.slice}.
* NOTE: if either index is negative, and you do not provide a length, this will have to buffer some or all of the
* result set (though not the entire input set), as we won't know when the indices have been reached.
*/
export function* sliceStream(stream, arg0, arg1, len) {
if ((arg1 === 0) || (isPosInfinity(arg0) || isNegInfinity(arg1))) {
return ['empty', arg0, arg1, 0];
}
if ((len !== undefined) && ((arg0 >= len) || (len <= 0))) {
return ['empty', arg0, arg1, len];
}
if (_.isArray(stream)) {
yield* stream.slice(arg0, arg1);
return ['array', arg0, arg1, stream.length];
}
const ii = saneSliceArg0(arg0, len);
if (ii === 0 && ((arg1 === undefined) || (isPosInfinity(arg1)))) {
yield* stream;
return ['full', arg0, arg1];
}
const jj = ((!arg1) ? Infinity : (arg1 >= 0) ? arg1 : len ? (len + arg1) : arg1);
if (ii >= 0) { // "slice from beg'th index ..."
if (jj >= 0) { // "slice from beg'th index, stopping at end'th index"
if (jj <= ii) {
return ['empty', arg0, arg1, ii, jj];
}
yield* _sliceStarPosPos(stream, ii, jj);
return ['pospos', arg0, arg1, ii, jj];
} // // "slice from beg'th index, dropping dropend items"
yield* _sliceStarPosNeg(stream, ii, -jj);
return ['posneg', arg0, arg1, ii, -jj];
} // else: // "keep at most the last horizon items..."
if (jj >= 0) { // "keep at most the last horizon items, stopping at end'th index"
yield* _sliceStarNegPos(stream, -ii, jj);
return ['negpos', arg0, arg1, -ii, jj];
} // // "keep at most the last horizon items, dropping dropend items"
if (ii >= jj) {
return ['empty', arg0, arg1, ii, jj];
}
yield* _sliceStarNegNeg(stream, -ii, -jj);
return ['negneg', arg0, arg1, -ii, -jj];
}
/** Return items starting from the beg'th index and ending at the end'th index
*
* Ensure that `items` is not an array, is sync iterable,
* has beg >= 0, and end > beg.
*
*/
export function* _sliceStarPosPos(stream, beg, end) {
let seq = 0;
for (const item of stream) { // order of the below is important. Incrementing first makes the comparisons less intuitive but saves an if
seq += 1;
if (seq <= beg) {
continue;
}
yield item;
if (seq >= end) {
break;
}
}
return ['_sliceStarPosPos', seq, beg, end];
}
/** Keep at most the last `horizon` items in the stream, additionally dropping the last `dropend` of them
* NOTE: this will have to buffer some or all of the result set:
* * buffer size: `horizon` -- (or stream length, if less)
* * result size: `min(horizon, stream len, horizon + dropend - len)`
*
* Ensure that `items` is not an array, is sync iterable, if horizon >= 0, and if dropend > 0
*
* Strategy: examine all items
* * push each item into the buffer
* * if seq > horizon, shift and discard the first item; it will be too old
* the buffer will be at most `horizon` items
* once the stream is exhausted,
* yield * all but the last `dropend` items in the buffer
*/
export function* _sliceStarNegNeg(items, horizon, dropend) {
let bucket = [];
for (const item of items) {
bucket.push(item);
if (bucket.length > horizon) {
bucket.shift();
}
}
yield* bucket.slice(0, -dropend);
return ['_sliceStarNegNeg', horizon, dropend, bucket.length, bucket.length - dropend];
}
/** Yield items starting from the beg'th index, but always omitting the last `dropend` items.
* Unless more than `dropend` items are available, none will be streamed.
* NOTE: this will have to buffer some or all of the result set:
* * buffer size: `dropend`
* * result size: `len - dropend - beg` -- if negative, none will be streamed
*
* Ensure that `items` is not an array, is sync iterable, if beg >= 0, and if omit > 0
*
* * ignore the first `beg` items
* * push each item after that into the buffer
* * if more than `dropend` items are in the buffer,
* shift+yield the earliest item. (the buffer stops growing at this point)
* * once the end is reached, the buffer (holding the last `dropend` items) is discarded
*/
export function* _sliceStarPosNeg(items, beg, dropend) {
let bucket = [];
let seq = 0;
for (const item of items) {
seq += 1;
if (seq <= beg) {
continue;
} // gte `<=` is because we did the `++` increment first
bucket.push(item);
if (bucket.length > dropend) {
yield bucket.shift();
}
}
// last `dropend` items are discarded
return ['_sliceStarPosNeg', seq, beg, dropend, bucket.length, bucket.length - dropend];
}
/** Yield items starting from the horizon'th before the end,
* but always stopping at and discarding the end'th item.
* This will have to buffer up to `horizon` items:
* * buffer size: `horizon`
* * result size: `min([horizon, len, horizon + len - end])` -- if negative, none will be streamed
*
* Ensure that `items` is not an array, is sync iterable,
* has horizon > 0, and end > 0.
*
* 0123456789 horz endx len begx stopx 01234 56789
* ABCDEFGHIJ 3 12 10 7 9 xxxxx xxHIJ
* ABCDEFGHIJ 3 >=10 10 7 9 xxxxx xxHIJ
* ABCDEFGHIJ 3 9 10 7 8 xxxxx xxHIx
* ABCDEFGHIJ 3 8 10 7 7 xxxxx xxHxx
* ABCDEFGHIJ 8 >=10 10 2 9 xxCDE FGHIJ
* ABCDEFGHIJ 8 4 10 2 4 xxCDx xxxxx
* ABCDEFGHIJ 10 >=10 10 0 9 ABCDE FGHIJ
* ABCDEFGHIJ 10 9 10 0 8 ABCDE FGHIx
* ABCDEFGHIJ 3 4 10 2 4 xxCDE xxxxx
* ABCD 3 4 4 1 3 xBCD
* ABCD 4 10 4 1 3 xBCD
* ABC 3 4 3 0 2 ABC
* A 3 4 1 0 0 A
* ABCDEFGHIJ 3 3 (not a valid call)
*
* Strategy:
* * if seq < end, push the item (it might age out later)
* * if seq > horizon, shift the first item; it will be too old
* * if seq > end + horizon, break.
* yield * all the items in the buffer (we'll have already
* aged out those past the horizon, and never added the ones past the end)
*
*/
export function* _sliceStarNegPos(items, horizon, end) {
let bucket = [];
let seq = 0;
const maxseq = end + horizon;
for (const item of items) {
if (seq >= horizon) {
bucket.shift();
}
if (seq < end) {
bucket.push(item);
}
if (seq >= maxseq) {
break;
}
seq += 1;
}
yield* bucket;
return ['_sliceStarNegPos', seq, horizon, end, bucket.length];
}
/** @returns an array of all the values in the iterable; works if iterable is async or sync */
export async function slurp(iter) {
const vals = [];
for await (const val of iter) {
vals.push(val);
}
return vals;
}
export function iteratorFor(iter) {
if (!_.isObject(iter)) {
throw throwable('iter must be an object', 'mistype', { given: iter });
}
if (isSyncIterable(iter)) {
return iter[Symbol.iterator]();
}
if (isAsyncIterable(iter)) {
return iter[Symbol.asyncIterator]();
}
return iter;
}
/** @returns an array of all the values in the iterable */
export async function slurpWithResult(streamable) {
const vals = [];
let ret;
const stream = iteratorFor(streamable);
while (true) {
const { done, value } = await stream.next();
if (done) {
ret = value;
stream.return?.(undefined);
break;
}
vals.push(value);
}
return { vals, ret };
}
/** @returns an array of all the values in the iterable */
export function slurpWithResultSync(streamable) {
const stream = iteratorFor(streamable);
const vals = [];
let ret;
while (true) {
const { done, value } = stream.next();
if (done) {
ret = value;
stream.return?.(undefined);
break;
}
vals.push(value);
}
return { vals, ret };
}
//# sourceMappingURL=Streaming.js.map