123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576 |
- /* 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 { iteratorSymbol } = require("../util/iteration");
- const { isArray, isArguments, isMap, isSet,
- isString, isBoolean, isNumber } = require("../lang/type");
- const Sequence = function Sequence(iterator) {
- if (iterator.isGenerator && iterator.isGenerator())
- this[iteratorSymbol] = iterator;
- else
- throw TypeError("Expected generator argument");
- };
- 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 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][iteratorSymbol]();
- 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;
|