summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/util/sequence.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/jetpack/sdk/util/sequence.js')
-rw-r--r--toolkit/jetpack/sdk/util/sequence.js593
1 files changed, 593 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/util/sequence.js b/toolkit/jetpack/sdk/util/sequence.js
new file mode 100644
index 000000000..28e3de255
--- /dev/null
+++ b/toolkit/jetpack/sdk/util/sequence.js
@@ -0,0 +1,593 @@
+/* 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;