Files
tubestation/addon-sdk/source/lib/sdk/util/sequence.js

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;