510 lines
16 KiB
JavaScript
510 lines
16 KiB
JavaScript
/******************************************************************************
|
|
* Copyright 2021 TypeFox GmbH
|
|
* This program and the accompanying materials are made available under the
|
|
* terms of the MIT License, which is available in the project root.
|
|
******************************************************************************/
|
|
/**
|
|
* The default implementation of `Stream` works with two input functions:
|
|
* - The first function creates the initial state of an iteration.
|
|
* - The second function gets the current state as argument and returns an `IteratorResult`.
|
|
*/
|
|
export class StreamImpl {
|
|
constructor(startFn, nextFn) {
|
|
this.startFn = startFn;
|
|
this.nextFn = nextFn;
|
|
}
|
|
iterator() {
|
|
const iterator = {
|
|
state: this.startFn(),
|
|
next: () => this.nextFn(iterator.state),
|
|
[Symbol.iterator]: () => iterator
|
|
};
|
|
return iterator;
|
|
}
|
|
[Symbol.iterator]() {
|
|
return this.iterator();
|
|
}
|
|
isEmpty() {
|
|
const iterator = this.iterator();
|
|
return Boolean(iterator.next().done);
|
|
}
|
|
count() {
|
|
const iterator = this.iterator();
|
|
let count = 0;
|
|
let next = iterator.next();
|
|
while (!next.done) {
|
|
count++;
|
|
next = iterator.next();
|
|
}
|
|
return count;
|
|
}
|
|
toArray() {
|
|
const result = [];
|
|
const iterator = this.iterator();
|
|
let next;
|
|
do {
|
|
next = iterator.next();
|
|
if (next.value !== undefined) {
|
|
result.push(next.value);
|
|
}
|
|
} while (!next.done);
|
|
return result;
|
|
}
|
|
toSet() {
|
|
return new Set(this);
|
|
}
|
|
toMap(keyFn, valueFn) {
|
|
const entryStream = this.map(element => [
|
|
keyFn ? keyFn(element) : element,
|
|
valueFn ? valueFn(element) : element
|
|
]);
|
|
return new Map(entryStream);
|
|
}
|
|
toString() {
|
|
return this.join();
|
|
}
|
|
concat(other) {
|
|
return new StreamImpl(() => ({ first: this.startFn(), firstDone: false, iterator: other[Symbol.iterator]() }), state => {
|
|
let result;
|
|
if (!state.firstDone) {
|
|
do {
|
|
result = this.nextFn(state.first);
|
|
if (!result.done) {
|
|
return result;
|
|
}
|
|
} while (!result.done);
|
|
state.firstDone = true;
|
|
}
|
|
do {
|
|
result = state.iterator.next();
|
|
if (!result.done) {
|
|
return result;
|
|
}
|
|
} while (!result.done);
|
|
return DONE_RESULT;
|
|
});
|
|
}
|
|
join(separator = ',') {
|
|
const iterator = this.iterator();
|
|
let value = '';
|
|
let result;
|
|
let addSeparator = false;
|
|
do {
|
|
result = iterator.next();
|
|
if (!result.done) {
|
|
if (addSeparator) {
|
|
value += separator;
|
|
}
|
|
value += toString(result.value);
|
|
}
|
|
addSeparator = true;
|
|
} while (!result.done);
|
|
return value;
|
|
}
|
|
indexOf(searchElement, fromIndex = 0) {
|
|
const iterator = this.iterator();
|
|
let index = 0;
|
|
let next = iterator.next();
|
|
while (!next.done) {
|
|
if (index >= fromIndex && next.value === searchElement) {
|
|
return index;
|
|
}
|
|
next = iterator.next();
|
|
index++;
|
|
}
|
|
return -1;
|
|
}
|
|
every(predicate) {
|
|
const iterator = this.iterator();
|
|
let next = iterator.next();
|
|
while (!next.done) {
|
|
if (!predicate(next.value)) {
|
|
return false;
|
|
}
|
|
next = iterator.next();
|
|
}
|
|
return true;
|
|
}
|
|
some(predicate) {
|
|
const iterator = this.iterator();
|
|
let next = iterator.next();
|
|
while (!next.done) {
|
|
if (predicate(next.value)) {
|
|
return true;
|
|
}
|
|
next = iterator.next();
|
|
}
|
|
return false;
|
|
}
|
|
forEach(callbackfn) {
|
|
const iterator = this.iterator();
|
|
let index = 0;
|
|
let next = iterator.next();
|
|
while (!next.done) {
|
|
callbackfn(next.value, index);
|
|
next = iterator.next();
|
|
index++;
|
|
}
|
|
}
|
|
map(callbackfn) {
|
|
return new StreamImpl(this.startFn, (state) => {
|
|
const { done, value } = this.nextFn(state);
|
|
if (done) {
|
|
return DONE_RESULT;
|
|
}
|
|
else {
|
|
return { done: false, value: callbackfn(value) };
|
|
}
|
|
});
|
|
}
|
|
filter(predicate) {
|
|
return new StreamImpl(this.startFn, state => {
|
|
let result;
|
|
do {
|
|
result = this.nextFn(state);
|
|
if (!result.done && predicate(result.value)) {
|
|
return result;
|
|
}
|
|
} while (!result.done);
|
|
return DONE_RESULT;
|
|
});
|
|
}
|
|
nonNullable() {
|
|
return this.filter(e => e !== undefined && e !== null);
|
|
}
|
|
reduce(callbackfn, initialValue) {
|
|
const iterator = this.iterator();
|
|
let previousValue = initialValue;
|
|
let next = iterator.next();
|
|
while (!next.done) {
|
|
if (previousValue === undefined) {
|
|
previousValue = next.value;
|
|
}
|
|
else {
|
|
previousValue = callbackfn(previousValue, next.value);
|
|
}
|
|
next = iterator.next();
|
|
}
|
|
return previousValue;
|
|
}
|
|
reduceRight(callbackfn, initialValue) {
|
|
return this.recursiveReduce(this.iterator(), callbackfn, initialValue);
|
|
}
|
|
recursiveReduce(iterator, callbackfn, initialValue) {
|
|
const next = iterator.next();
|
|
if (next.done) {
|
|
return initialValue;
|
|
}
|
|
const previousValue = this.recursiveReduce(iterator, callbackfn, initialValue);
|
|
if (previousValue === undefined) {
|
|
return next.value;
|
|
}
|
|
return callbackfn(previousValue, next.value);
|
|
}
|
|
find(predicate) {
|
|
const iterator = this.iterator();
|
|
let next = iterator.next();
|
|
while (!next.done) {
|
|
if (predicate(next.value)) {
|
|
return next.value;
|
|
}
|
|
next = iterator.next();
|
|
}
|
|
return undefined;
|
|
}
|
|
findIndex(predicate) {
|
|
const iterator = this.iterator();
|
|
let index = 0;
|
|
let next = iterator.next();
|
|
while (!next.done) {
|
|
if (predicate(next.value)) {
|
|
return index;
|
|
}
|
|
next = iterator.next();
|
|
index++;
|
|
}
|
|
return -1;
|
|
}
|
|
includes(searchElement) {
|
|
const iterator = this.iterator();
|
|
let next = iterator.next();
|
|
while (!next.done) {
|
|
if (next.value === searchElement) {
|
|
return true;
|
|
}
|
|
next = iterator.next();
|
|
}
|
|
return false;
|
|
}
|
|
flatMap(callbackfn) {
|
|
return new StreamImpl(() => ({ this: this.startFn() }), (state) => {
|
|
do {
|
|
if (state.iterator) {
|
|
const next = state.iterator.next();
|
|
if (next.done) {
|
|
state.iterator = undefined;
|
|
}
|
|
else {
|
|
return next;
|
|
}
|
|
}
|
|
const { done, value } = this.nextFn(state.this);
|
|
if (!done) {
|
|
const mapped = callbackfn(value);
|
|
if (isIterable(mapped)) {
|
|
state.iterator = mapped[Symbol.iterator]();
|
|
}
|
|
else {
|
|
return { done: false, value: mapped };
|
|
}
|
|
}
|
|
} while (state.iterator);
|
|
return DONE_RESULT;
|
|
});
|
|
}
|
|
flat(depth) {
|
|
if (depth === undefined) {
|
|
depth = 1;
|
|
}
|
|
if (depth <= 0) {
|
|
return this;
|
|
}
|
|
const stream = depth > 1 ? this.flat(depth - 1) : this;
|
|
return new StreamImpl(() => ({ this: stream.startFn() }), (state) => {
|
|
do {
|
|
if (state.iterator) {
|
|
const next = state.iterator.next();
|
|
if (next.done) {
|
|
state.iterator = undefined;
|
|
}
|
|
else {
|
|
return next;
|
|
}
|
|
}
|
|
const { done, value } = stream.nextFn(state.this);
|
|
if (!done) {
|
|
if (isIterable(value)) {
|
|
state.iterator = value[Symbol.iterator]();
|
|
}
|
|
else {
|
|
return { done: false, value: value };
|
|
}
|
|
}
|
|
} while (state.iterator);
|
|
return DONE_RESULT;
|
|
});
|
|
}
|
|
head() {
|
|
const iterator = this.iterator();
|
|
const result = iterator.next();
|
|
if (result.done) {
|
|
return undefined;
|
|
}
|
|
return result.value;
|
|
}
|
|
tail(skipCount = 1) {
|
|
return new StreamImpl(() => {
|
|
const state = this.startFn();
|
|
for (let i = 0; i < skipCount; i++) {
|
|
const next = this.nextFn(state);
|
|
if (next.done) {
|
|
return state;
|
|
}
|
|
}
|
|
return state;
|
|
}, this.nextFn);
|
|
}
|
|
limit(maxSize) {
|
|
return new StreamImpl(() => ({ size: 0, state: this.startFn() }), state => {
|
|
state.size++;
|
|
if (state.size > maxSize) {
|
|
return DONE_RESULT;
|
|
}
|
|
return this.nextFn(state.state);
|
|
});
|
|
}
|
|
distinct(by) {
|
|
return new StreamImpl(() => ({ set: new Set(), internalState: this.startFn() }), state => {
|
|
let result;
|
|
do {
|
|
result = this.nextFn(state.internalState);
|
|
if (!result.done) {
|
|
const value = by ? by(result.value) : result.value;
|
|
if (!state.set.has(value)) {
|
|
state.set.add(value);
|
|
return result;
|
|
}
|
|
}
|
|
} while (!result.done);
|
|
return DONE_RESULT;
|
|
});
|
|
}
|
|
exclude(other, key) {
|
|
const otherKeySet = new Set();
|
|
for (const item of other) {
|
|
const value = key ? key(item) : item;
|
|
otherKeySet.add(value);
|
|
}
|
|
return this.filter(e => {
|
|
const ownKey = key ? key(e) : e;
|
|
return !otherKeySet.has(ownKey);
|
|
});
|
|
}
|
|
}
|
|
function toString(item) {
|
|
if (typeof item === 'string') {
|
|
return item;
|
|
}
|
|
if (typeof item === 'undefined') {
|
|
return 'undefined';
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
if (typeof item.toString === 'function') {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
return item.toString();
|
|
}
|
|
return Object.prototype.toString.call(item);
|
|
}
|
|
function isIterable(obj) {
|
|
return !!obj && typeof obj[Symbol.iterator] === 'function';
|
|
}
|
|
/**
|
|
* An empty stream of any type.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
export const EMPTY_STREAM = new StreamImpl(() => undefined, () => DONE_RESULT);
|
|
/**
|
|
* Use this `IteratorResult` when implementing a `StreamImpl` to indicate that there are no more elements in the stream.
|
|
*/
|
|
export const DONE_RESULT = Object.freeze({ done: true, value: undefined });
|
|
/**
|
|
* Create a stream from one or more iterables or array-likes.
|
|
*/
|
|
export function stream(...collections) {
|
|
if (collections.length === 1) {
|
|
const collection = collections[0];
|
|
if (collection instanceof StreamImpl) {
|
|
return collection;
|
|
}
|
|
if (isIterable(collection)) {
|
|
return new StreamImpl(() => collection[Symbol.iterator](), (iterator) => iterator.next());
|
|
}
|
|
if (typeof collection.length === 'number') {
|
|
return new StreamImpl(() => ({ index: 0 }), (state) => {
|
|
if (state.index < collection.length) {
|
|
return { done: false, value: collection[state.index++] };
|
|
}
|
|
else {
|
|
return DONE_RESULT;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
if (collections.length > 1) {
|
|
return new StreamImpl(() => ({ collIndex: 0, arrIndex: 0 }), (state) => {
|
|
do {
|
|
if (state.iterator) {
|
|
const next = state.iterator.next();
|
|
if (!next.done) {
|
|
return next;
|
|
}
|
|
state.iterator = undefined;
|
|
}
|
|
if (state.array) {
|
|
if (state.arrIndex < state.array.length) {
|
|
return { done: false, value: state.array[state.arrIndex++] };
|
|
}
|
|
state.array = undefined;
|
|
state.arrIndex = 0;
|
|
}
|
|
if (state.collIndex < collections.length) {
|
|
const collection = collections[state.collIndex++];
|
|
if (isIterable(collection)) {
|
|
state.iterator = collection[Symbol.iterator]();
|
|
}
|
|
else if (collection && typeof collection.length === 'number') {
|
|
state.array = collection;
|
|
}
|
|
}
|
|
} while (state.iterator || state.array || state.collIndex < collections.length);
|
|
return DONE_RESULT;
|
|
});
|
|
}
|
|
return EMPTY_STREAM;
|
|
}
|
|
/**
|
|
* The default implementation of `TreeStream` takes a root element and a function that computes the
|
|
* children of its argument. Whether the root node included in the stream is controlled with the
|
|
* `includeRoot` option, which defaults to `false`.
|
|
*/
|
|
export class TreeStreamImpl extends StreamImpl {
|
|
constructor(root, children, options) {
|
|
super(() => ({
|
|
iterators: (options === null || options === void 0 ? void 0 : options.includeRoot) ? [[root][Symbol.iterator]()] : [children(root)[Symbol.iterator]()],
|
|
pruned: false
|
|
}), state => {
|
|
if (state.pruned) {
|
|
state.iterators.pop();
|
|
state.pruned = false;
|
|
}
|
|
while (state.iterators.length > 0) {
|
|
const iterator = state.iterators[state.iterators.length - 1];
|
|
const next = iterator.next();
|
|
if (next.done) {
|
|
state.iterators.pop();
|
|
}
|
|
else {
|
|
state.iterators.push(children(next.value)[Symbol.iterator]());
|
|
return next;
|
|
}
|
|
}
|
|
return DONE_RESULT;
|
|
});
|
|
}
|
|
iterator() {
|
|
const iterator = {
|
|
state: this.startFn(),
|
|
next: () => this.nextFn(iterator.state),
|
|
prune: () => {
|
|
iterator.state.pruned = true;
|
|
},
|
|
[Symbol.iterator]: () => iterator
|
|
};
|
|
return iterator;
|
|
}
|
|
}
|
|
/**
|
|
* A set of utility functions that reduce a stream to a single value.
|
|
*/
|
|
export var Reduction;
|
|
(function (Reduction) {
|
|
/**
|
|
* Compute the sum of a number stream.
|
|
*/
|
|
function sum(stream) {
|
|
return stream.reduce((a, b) => a + b, 0);
|
|
}
|
|
Reduction.sum = sum;
|
|
/**
|
|
* Compute the product of a number stream.
|
|
*/
|
|
function product(stream) {
|
|
return stream.reduce((a, b) => a * b, 0);
|
|
}
|
|
Reduction.product = product;
|
|
/**
|
|
* Compute the minimum of a number stream. Returns `undefined` if the stream is empty.
|
|
*/
|
|
function min(stream) {
|
|
return stream.reduce((a, b) => Math.min(a, b));
|
|
}
|
|
Reduction.min = min;
|
|
/**
|
|
* Compute the maximum of a number stream. Returns `undefined` if the stream is empty.
|
|
*/
|
|
function max(stream) {
|
|
return stream.reduce((a, b) => Math.max(a, b));
|
|
}
|
|
Reduction.max = max;
|
|
})(Reduction || (Reduction = {}));
|
|
//# sourceMappingURL=stream.js.map
|