/* 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;