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