| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Ivan Kopeykin @vankop
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | /**
|
|---|
| 9 | * Tracks values across a stack of nested sets where child scopes can add new
|
|---|
| 10 | * values without mutating the sets created by their parents.
|
|---|
| 11 | * @template T
|
|---|
| 12 | */
|
|---|
| 13 | class AppendOnlyStackedSet {
|
|---|
| 14 | /**
|
|---|
| 15 | * Seeds the stacked set with an optional chain of previously created scope
|
|---|
| 16 | * layers.
|
|---|
| 17 | * @param {Set<T>[]} sets an optional array of sets
|
|---|
| 18 | */
|
|---|
| 19 | constructor(sets = []) {
|
|---|
| 20 | /** @type {Set<T>[]} */
|
|---|
| 21 | this._sets = sets;
|
|---|
| 22 | /** @type {Set<T> | undefined} */
|
|---|
| 23 | this._current = undefined;
|
|---|
| 24 | }
|
|---|
| 25 |
|
|---|
| 26 | /**
|
|---|
| 27 | * Adds a value to the current scope layer, creating that layer lazily when
|
|---|
| 28 | * the first write occurs.
|
|---|
| 29 | * @param {T} el element
|
|---|
| 30 | */
|
|---|
| 31 | add(el) {
|
|---|
| 32 | if (!this._current) {
|
|---|
| 33 | this._current = new Set();
|
|---|
| 34 | this._sets.push(this._current);
|
|---|
| 35 | }
|
|---|
| 36 | this._current.add(el);
|
|---|
| 37 | }
|
|---|
| 38 |
|
|---|
| 39 | /**
|
|---|
| 40 | * Checks whether a value is present in any scope layer currently visible to
|
|---|
| 41 | * this stacked set.
|
|---|
| 42 | * @param {T} el element
|
|---|
| 43 | * @returns {boolean} result
|
|---|
| 44 | */
|
|---|
| 45 | has(el) {
|
|---|
| 46 | for (const set of this._sets) {
|
|---|
| 47 | if (set.has(el)) return true;
|
|---|
| 48 | }
|
|---|
| 49 | return false;
|
|---|
| 50 | }
|
|---|
| 51 |
|
|---|
| 52 | /**
|
|---|
| 53 | * Removes every scope layer and any values accumulated in them.
|
|---|
| 54 | */
|
|---|
| 55 | clear() {
|
|---|
| 56 | this._sets = [];
|
|---|
| 57 | if (this._current) {
|
|---|
| 58 | this._current = undefined;
|
|---|
| 59 | }
|
|---|
| 60 | }
|
|---|
| 61 |
|
|---|
| 62 | /**
|
|---|
| 63 | * Creates a child stacked set that shares the existing scope history while
|
|---|
| 64 | * allowing subsequent additions to be recorded in its own new layer.
|
|---|
| 65 | * @returns {AppendOnlyStackedSet<T>} child
|
|---|
| 66 | */
|
|---|
| 67 | createChild() {
|
|---|
| 68 | return new AppendOnlyStackedSet(this._sets.length ? [...this._sets] : []);
|
|---|
| 69 | }
|
|---|
| 70 |
|
|---|
| 71 | /**
|
|---|
| 72 | * Iterates over the stacked sets from newest to oldest so consumers can
|
|---|
| 73 | * inspect recently added values first.
|
|---|
| 74 | * @returns {Iterator<T>} iterable iterator
|
|---|
| 75 | */
|
|---|
| 76 | [Symbol.iterator]() {
|
|---|
| 77 | const iterators = this._sets.map((map) => map[Symbol.iterator]());
|
|---|
| 78 | let current = iterators.pop();
|
|---|
| 79 | return {
|
|---|
| 80 | next() {
|
|---|
| 81 | if (!current) return { done: true, value: undefined };
|
|---|
| 82 | let result = current.next();
|
|---|
| 83 | while (result.done && iterators.length > 0) {
|
|---|
| 84 | current = /** @type {SetIterator<T>} */ (iterators.pop());
|
|---|
| 85 | result = current.next();
|
|---|
| 86 | }
|
|---|
| 87 | return result;
|
|---|
| 88 | }
|
|---|
| 89 | };
|
|---|
| 90 | }
|
|---|
| 91 | }
|
|---|
| 92 |
|
|---|
| 93 | module.exports = AppendOnlyStackedSet;
|
|---|