594 lines
16 KiB
JavaScript
594 lines
16 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
"use strict";
|
|
|
|
module.metadata = {
|
|
"stability": "experimental"
|
|
};
|
|
|
|
// Disclamer:
|
|
// In this module we'll have some common argument / variable names
|
|
// to hint their type or behavior.
|
|
//
|
|
// - `f` stands for "function" that is intended to be side effect
|
|
// free.
|
|
// - `p` stands for "predicate" that is function which returns logical
|
|
// true or false and is intended to be side effect free.
|
|
// - `x` / `y` single item of the sequence.
|
|
// - `xs` / `ys` sequence of `x` / `y` items where `x` / `y` signifies
|
|
// type of the items in sequence, so sequence is not of the same item.
|
|
// - `_` used for argument(s) or variable(s) who's values are ignored.
|
|
|
|
const { complement, flip, identity } = require("../lang/functional");
|
|
const { isArray, isArguments, isMap, isSet, isGenerator,
|
|
isString, isBoolean, isNumber } = require("../lang/type");
|
|
|
|
const Sequence = function Sequence(iterator) {
|
|
if (!isGenerator(iterator)) {
|
|
throw TypeError("Expected generator argument");
|
|
}
|
|
|
|
this[Symbol.iterator] = iterator;
|
|
};
|
|
exports.Sequence = Sequence;
|
|
|
|
const polymorphic = dispatch => x =>
|
|
x === null ? dispatch.null(null) :
|
|
x === void(0) ? dispatch.void(void(0)) :
|
|
isArray(x) ? (dispatch.array || dispatch.indexed)(x) :
|
|
isString(x) ? (dispatch.string || dispatch.indexed)(x) :
|
|
isArguments(x) ? (dispatch.arguments || dispatch.indexed)(x) :
|
|
isMap(x) ? dispatch.map(x) :
|
|
isSet(x) ? dispatch.set(x) :
|
|
isNumber(x) ? dispatch.number(x) :
|
|
isBoolean(x) ? dispatch.boolean(x) :
|
|
dispatch.default(x);
|
|
|
|
const nogen = function*() {};
|
|
const empty = () => new Sequence(nogen);
|
|
exports.empty = empty;
|
|
|
|
const seq = polymorphic({
|
|
null: empty,
|
|
void: empty,
|
|
array: identity,
|
|
string: identity,
|
|
arguments: identity,
|
|
map: identity,
|
|
set: identity,
|
|
default: x => x instanceof Sequence ? x : new Sequence(x)
|
|
});
|
|
exports.seq = seq;
|
|
|
|
// Function to cast seq to string.
|
|
const string = (...etc) => "".concat(...etc);
|
|
exports.string = string;
|
|
|
|
// Function for casting seq to plain object.
|
|
const object = (...pairs) => {
|
|
let result = {};
|
|
for (let [key, value] of pairs)
|
|
result[key] = value;
|
|
|
|
return result;
|
|
};
|
|
exports.object = object;
|
|
|
|
// Takes `getEnumerator` function that returns `nsISimpleEnumerator`
|
|
// and creates lazy sequence of it's items. Note that function does
|
|
// not take `nsISimpleEnumerator` itslef because that would allow
|
|
// single iteration, which would not be consistent with rest of the
|
|
// lazy sequences.
|
|
const fromEnumerator = getEnumerator => seq(function* () {
|
|
const enumerator = getEnumerator();
|
|
while (enumerator.hasMoreElements())
|
|
yield enumerator.getNext();
|
|
});
|
|
exports.fromEnumerator = fromEnumerator;
|
|
|
|
// Takes `object` and returns lazy sequence of own `[key, value]`
|
|
// pairs (does not include inherited and non enumerable keys).
|
|
const pairs = polymorphic({
|
|
null: empty,
|
|
void: empty,
|
|
map: identity,
|
|
indexed: indexed => seq(function* () {
|
|
const count = indexed.length;
|
|
let index = 0;
|
|
while (index < count) {
|
|
yield [index, indexed[index]];
|
|
index = index + 1;
|
|
}
|
|
}),
|
|
default: object => seq(function* () {
|
|
for (let key of Object.keys(object))
|
|
yield [key, object[key]];
|
|
})
|
|
});
|
|
exports.pairs = pairs;
|
|
|
|
const names = polymorphic({
|
|
null: empty,
|
|
void: empty,
|
|
default: object => seq(function*() {
|
|
for (let name of Object.getOwnPropertyNames(object)) {
|
|
yield name;
|
|
}
|
|
})
|
|
});
|
|
exports.names = names;
|
|
|
|
const symbols = polymorphic({
|
|
null: empty,
|
|
void: empty,
|
|
default: object => seq(function* () {
|
|
for (let symbol of Object.getOwnPropertySymbols(object)) {
|
|
yield symbol;
|
|
}
|
|
})
|
|
});
|
|
exports.symbols = symbols;
|
|
|
|
const keys = polymorphic({
|
|
null: empty,
|
|
void: empty,
|
|
indexed: indexed => seq(function* () {
|
|
const count = indexed.length;
|
|
let index = 0;
|
|
while (index < count) {
|
|
yield index;
|
|
index = index + 1;
|
|
}
|
|
}),
|
|
map: map => seq(function* () {
|
|
for (let [key, _] of map)
|
|
yield key;
|
|
}),
|
|
default: object => seq(function* () {
|
|
for (let key of Object.keys(object))
|
|
yield key;
|
|
})
|
|
});
|
|
exports.keys = keys;
|
|
|
|
|
|
const values = polymorphic({
|
|
null: empty,
|
|
void: empty,
|
|
set: identity,
|
|
indexed: indexed => seq(function* () {
|
|
const count = indexed.length;
|
|
let index = 0;
|
|
while (index < count) {
|
|
yield indexed[index];
|
|
index = index + 1;
|
|
}
|
|
}),
|
|
map: map => seq(function* () {
|
|
for (let [_, value] of map) yield value;
|
|
}),
|
|
default: object => seq(function* () {
|
|
for (let key of Object.keys(object)) yield object[key];
|
|
})
|
|
});
|
|
exports.values = values;
|
|
|
|
|
|
|
|
// Returns a lazy sequence of `x`, `f(x)`, `f(f(x))` etc.
|
|
// `f` must be free of side-effects. Note that returned
|
|
// sequence is infinite so it must be consumed partially.
|
|
//
|
|
// Implements clojure iterate:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/iterate
|
|
const iterate = (f, x) => seq(function* () {
|
|
let state = x;
|
|
while (true) {
|
|
yield state;
|
|
state = f(state);
|
|
}
|
|
});
|
|
exports.iterate = iterate;
|
|
|
|
// Returns a lazy sequence of the items in sequence for which `p(item)`
|
|
// returns `true`. `p` must be free of side-effects.
|
|
//
|
|
// Implements clojure filter:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/filter
|
|
const filter = (p, sequence) => seq(function* () {
|
|
if (sequence !== null && sequence !== void(0)) {
|
|
for (let item of sequence) {
|
|
if (p(item))
|
|
yield item;
|
|
}
|
|
}
|
|
});
|
|
exports.filter = filter;
|
|
|
|
// Returns a lazy sequence consisting of the result of applying `f` to the
|
|
// set of first items of each sequence, followed by applying f to the set
|
|
// of second items in each sequence, until any one of the sequences is
|
|
// exhausted. Any remaining items in other sequences are ignored. Function
|
|
// `f` should accept number-of-sequences arguments.
|
|
//
|
|
// Implements clojure map:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/map
|
|
const map = (f, ...sequences) => seq(function* () {
|
|
const count = sequences.length;
|
|
// Optimize a single sequence case
|
|
if (count === 1) {
|
|
let [sequence] = sequences;
|
|
if (sequence !== null && sequence !== void(0)) {
|
|
for (let item of sequence)
|
|
yield f(item);
|
|
}
|
|
}
|
|
else {
|
|
// define args array that will be recycled on each
|
|
// step to aggregate arguments to be passed to `f`.
|
|
let args = [];
|
|
// define inputs to contain started generators.
|
|
let inputs = [];
|
|
|
|
let index = 0;
|
|
while (index < count) {
|
|
inputs[index] = sequences[index][Symbol.iterator]();
|
|
index = index + 1;
|
|
}
|
|
|
|
// Run loop yielding of applying `f` to the set of
|
|
// items at each step until one of the `inputs` is
|
|
// exhausted.
|
|
let done = false;
|
|
while (!done) {
|
|
let index = 0;
|
|
let value = void(0);
|
|
while (index < count && !done) {
|
|
({ done, value } = inputs[index].next());
|
|
|
|
// If input is not exhausted yet store value in args.
|
|
if (!done) {
|
|
args[index] = value;
|
|
index = index + 1;
|
|
}
|
|
}
|
|
|
|
// If none of the inputs is exhasted yet, `args` contain items
|
|
// from each input so we yield application of `f` over them.
|
|
if (!done)
|
|
yield f(...args);
|
|
}
|
|
}
|
|
});
|
|
exports.map = map;
|
|
|
|
// Returns a lazy sequence of the intermediate values of the reduction (as
|
|
// per reduce) of sequence by `f`, starting with `initial` value if provided.
|
|
//
|
|
// Implements clojure reductions:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/reductions
|
|
const reductions = (...params) => {
|
|
const count = params.length;
|
|
let hasInitial = false;
|
|
let f, initial, source;
|
|
if (count === 2) {
|
|
[f, source] = params;
|
|
}
|
|
else if (count === 3) {
|
|
[f, initial, source] = params;
|
|
hasInitial = true;
|
|
}
|
|
else {
|
|
throw Error("Invoked with wrong number of arguments: " + count);
|
|
}
|
|
|
|
const sequence = seq(source);
|
|
|
|
return seq(function* () {
|
|
let started = hasInitial;
|
|
let result = void(0);
|
|
|
|
// If initial is present yield it.
|
|
if (hasInitial)
|
|
yield (result = initial);
|
|
|
|
// For each item of the sequence accumulate new result.
|
|
for (let item of sequence) {
|
|
// If nothing has being yield yet set result to first
|
|
// item and yield it.
|
|
if (!started) {
|
|
started = true;
|
|
yield (result = item);
|
|
}
|
|
// Otherwise accumulate new result and yield it.
|
|
else {
|
|
yield (result = f(result, item));
|
|
}
|
|
}
|
|
|
|
// If nothing has being yield yet it's empty sequence and no
|
|
// `initial` was provided in which case we need to yield `f()`.
|
|
if (!started)
|
|
yield f();
|
|
});
|
|
};
|
|
exports.reductions = reductions;
|
|
|
|
// `f` should be a function of 2 arguments. If `initial` is not supplied,
|
|
// returns the result of applying `f` to the first 2 items in sequence, then
|
|
// applying `f` to that result and the 3rd item, etc. If sequence contains no
|
|
// items, `f` must accept no arguments as well, and reduce returns the
|
|
// result of calling f with no arguments. If sequence has only 1 item, it
|
|
// is returned and `f` is not called. If `initial` is supplied, returns the
|
|
// result of applying `f` to `initial` and the first item in sequence, then
|
|
// applying `f` to that result and the 2nd item, etc. If sequence contains no
|
|
// items, returns `initial` and `f` is not called.
|
|
//
|
|
// Implements clojure reduce:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/reduce
|
|
const reduce = (...args) => {
|
|
const xs = reductions(...args);
|
|
let x;
|
|
for (x of xs) void(0);
|
|
return x;
|
|
};
|
|
exports.reduce = reduce;
|
|
|
|
const each = (f, sequence) => {
|
|
for (let x of seq(sequence)) void(f(x));
|
|
};
|
|
exports.each = each;
|
|
|
|
|
|
const inc = x => x + 1;
|
|
// Returns the number of items in the sequence. `count(null)` && `count()`
|
|
// returns `0`. Also works on strings, arrays, Maps & Sets.
|
|
|
|
// Implements clojure count:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/count
|
|
const count = polymorphic({
|
|
null: _ => 0,
|
|
void: _ => 0,
|
|
indexed: indexed => indexed.length,
|
|
map: map => map.size,
|
|
set: set => set.size,
|
|
default: xs => reduce(inc, 0, xs)
|
|
});
|
|
exports.count = count;
|
|
|
|
// Returns `true` if sequence has no items.
|
|
|
|
// Implements clojure empty?:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/empty_q
|
|
const isEmpty = sequence => {
|
|
// Treat `null` and `undefined` as empty sequences.
|
|
if (sequence === null || sequence === void(0))
|
|
return true;
|
|
|
|
// If contains any item non empty so return `false`.
|
|
for (let _ of sequence)
|
|
return false;
|
|
|
|
// If has not returned yet, there was nothing to iterate
|
|
// so it's empty.
|
|
return true;
|
|
};
|
|
exports.isEmpty = isEmpty;
|
|
|
|
const and = (a, b) => a && b;
|
|
|
|
// Returns true if `p(x)` is logical `true` for every `x` in sequence, else
|
|
// `false`.
|
|
//
|
|
// Implements clojure every?:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/every_q
|
|
const isEvery = (p, sequence) => {
|
|
if (sequence !== null && sequence !== void(0)) {
|
|
for (let item of sequence) {
|
|
if (!p(item))
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
exports.isEvery = isEvery;
|
|
|
|
// Returns the first logical true value of (p x) for any x in sequence,
|
|
// else `null`.
|
|
//
|
|
// Implements clojure some:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/some
|
|
const some = (p, sequence) => {
|
|
if (sequence !== null && sequence !== void(0)) {
|
|
for (let item of sequence) {
|
|
if (p(item))
|
|
return true;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
exports.some = some;
|
|
|
|
// Returns a lazy sequence of the first `n` items in sequence, or all items if
|
|
// there are fewer than `n`.
|
|
//
|
|
// Implements clojure take:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/take
|
|
const take = (n, sequence) => n <= 0 ? empty() : seq(function* () {
|
|
let count = n;
|
|
for (let item of sequence) {
|
|
yield item;
|
|
count = count - 1;
|
|
if (count === 0) break;
|
|
}
|
|
});
|
|
exports.take = take;
|
|
|
|
// Returns a lazy sequence of successive items from sequence while
|
|
// `p(item)` returns `true`. `p` must be free of side-effects.
|
|
//
|
|
// Implements clojure take-while:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/take-while
|
|
const takeWhile = (p, sequence) => seq(function* () {
|
|
for (let item of sequence) {
|
|
if (!p(item))
|
|
break;
|
|
|
|
yield item;
|
|
}
|
|
});
|
|
exports.takeWhile = takeWhile;
|
|
|
|
// Returns a lazy sequence of all but the first `n` items in
|
|
// sequence.
|
|
//
|
|
// Implements clojure drop:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/drop
|
|
const drop = (n, sequence) => seq(function* () {
|
|
if (sequence !== null && sequence !== void(0)) {
|
|
let count = n;
|
|
for (let item of sequence) {
|
|
if (count > 0)
|
|
count = count - 1;
|
|
else
|
|
yield item;
|
|
}
|
|
}
|
|
});
|
|
exports.drop = drop;
|
|
|
|
// Returns a lazy sequence of the items in sequence starting from the
|
|
// first item for which `p(item)` returns falsy value.
|
|
//
|
|
// Implements clojure drop-while:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/drop-while
|
|
const dropWhile = (p, sequence) => seq(function* () {
|
|
let keep = false;
|
|
for (let item of sequence) {
|
|
keep = keep || !p(item);
|
|
if (keep) yield item;
|
|
}
|
|
});
|
|
exports.dropWhile = dropWhile;
|
|
|
|
// Returns a lazy sequence representing the concatenation of the
|
|
// suplied sequences.
|
|
//
|
|
// Implements clojure conact:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/concat
|
|
const concat = (...sequences) => seq(function* () {
|
|
for (let sequence of sequences)
|
|
for (let item of sequence)
|
|
yield item;
|
|
});
|
|
exports.concat = concat;
|
|
|
|
// Returns the first item in the sequence.
|
|
//
|
|
// Implements clojure first:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/first
|
|
const first = sequence => {
|
|
if (sequence !== null && sequence !== void(0)) {
|
|
for (let item of sequence)
|
|
return item;
|
|
}
|
|
return null;
|
|
};
|
|
exports.first = first;
|
|
|
|
// Returns a possibly empty sequence of the items after the first.
|
|
//
|
|
// Implements clojure rest:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/rest
|
|
const rest = sequence => drop(1, sequence);
|
|
exports.rest = rest;
|
|
|
|
// Returns the value at the index. Returns `notFound` or `undefined`
|
|
// if index is out of bounds.
|
|
const nth = (xs, n, notFound) => {
|
|
if (n >= 0) {
|
|
if (isArray(xs) || isArguments(xs) || isString(xs)) {
|
|
return n < xs.length ? xs[n] : notFound;
|
|
}
|
|
else if (xs !== null && xs !== void(0)) {
|
|
let count = n;
|
|
for (let x of xs) {
|
|
if (count <= 0)
|
|
return x;
|
|
|
|
count = count - 1;
|
|
}
|
|
}
|
|
}
|
|
return notFound;
|
|
};
|
|
exports.nth = nth;
|
|
|
|
// Return the last item in sequence, in linear time.
|
|
// If `sequence` is an array or string or arguments
|
|
// returns in constant time.
|
|
// Implements clojure last:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/last
|
|
const last = polymorphic({
|
|
null: _ => null,
|
|
void: _ => null,
|
|
indexed: indexed => indexed[indexed.length - 1],
|
|
map: xs => reduce((_, x) => x, xs),
|
|
set: xs => reduce((_, x) => x, xs),
|
|
default: xs => reduce((_, x) => x, xs)
|
|
});
|
|
exports.last = last;
|
|
|
|
// Return a lazy sequence of all but the last `n` (default 1) items
|
|
// from the give `xs`.
|
|
//
|
|
// Implements clojure drop-last:
|
|
// http://clojuredocs.org/clojure_core/clojure.core/drop-last
|
|
const dropLast = flip((xs, n=1) => seq(function* () {
|
|
let ys = [];
|
|
for (let x of xs) {
|
|
ys.push(x);
|
|
if (ys.length > n)
|
|
yield ys.shift();
|
|
}
|
|
}));
|
|
exports.dropLast = dropLast;
|
|
|
|
// Returns a lazy sequence of the elements of `xs` with duplicates
|
|
// removed
|
|
//
|
|
// Implements clojure distinct
|
|
// http://clojuredocs.org/clojure_core/clojure.core/distinct
|
|
const distinct = sequence => seq(function* () {
|
|
let items = new Set();
|
|
for (let item of sequence) {
|
|
if (!items.has(item)) {
|
|
items.add(item);
|
|
yield item;
|
|
}
|
|
}
|
|
});
|
|
exports.distinct = distinct;
|
|
|
|
// Returns a lazy sequence of the items in `xs` for which
|
|
// `p(x)` returns false. `p` must be free of side-effects.
|
|
//
|
|
// Implements clojure remove
|
|
// http://clojuredocs.org/clojure_core/clojure.core/remove
|
|
const remove = (p, xs) => filter(complement(p), xs);
|
|
exports.remove = remove;
|
|
|
|
// Returns the result of applying concat to the result of
|
|
// `map(f, xs)`. Thus function `f` should return a sequence.
|
|
//
|
|
// Implements clojure mapcat
|
|
// http://clojuredocs.org/clojure_core/clojure.core/mapcat
|
|
const mapcat = (f, sequence) => seq(function* () {
|
|
const sequences = map(f, sequence);
|
|
for (let sequence of sequences)
|
|
for (let item of sequence)
|
|
yield item;
|
|
});
|
|
exports.mapcat = mapcat;
|