1 | /*
|
---|
2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
---|
3 | Author Tobias Koppers @sokra
|
---|
4 | */
|
---|
5 |
|
---|
6 | "use strict";
|
---|
7 |
|
---|
8 | const TOMBSTONE = Symbol("tombstone");
|
---|
9 | const UNDEFINED_MARKER = Symbol("undefined");
|
---|
10 |
|
---|
11 | /**
|
---|
12 | * @template T
|
---|
13 | * @typedef {T | undefined} Cell<T>
|
---|
14 | */
|
---|
15 |
|
---|
16 | /**
|
---|
17 | * @template T
|
---|
18 | * @typedef {T | typeof TOMBSTONE | typeof UNDEFINED_MARKER} InternalCell<T>
|
---|
19 | */
|
---|
20 |
|
---|
21 | /**
|
---|
22 | * @template K
|
---|
23 | * @template V
|
---|
24 | * @param {[K, InternalCell<V>]} pair the internal cell
|
---|
25 | * @returns {[K, Cell<V>]} its “safe” representation
|
---|
26 | */
|
---|
27 | const extractPair = pair => {
|
---|
28 | const key = pair[0];
|
---|
29 | const val = pair[1];
|
---|
30 | if (val === UNDEFINED_MARKER || val === TOMBSTONE) {
|
---|
31 | return [key, undefined];
|
---|
32 | } else {
|
---|
33 | return /** @type {[K, Cell<V>]} */ (pair);
|
---|
34 | }
|
---|
35 | };
|
---|
36 |
|
---|
37 | /**
|
---|
38 | * @template K
|
---|
39 | * @template V
|
---|
40 | */
|
---|
41 | class StackedMap {
|
---|
42 | /**
|
---|
43 | * @param {Map<K, InternalCell<V>>[]=} parentStack an optional parent
|
---|
44 | */
|
---|
45 | constructor(parentStack) {
|
---|
46 | /** @type {Map<K, InternalCell<V>>} */
|
---|
47 | this.map = new Map();
|
---|
48 | /** @type {Map<K, InternalCell<V>>[]} */
|
---|
49 | this.stack = parentStack === undefined ? [] : parentStack.slice();
|
---|
50 | this.stack.push(this.map);
|
---|
51 | }
|
---|
52 |
|
---|
53 | /**
|
---|
54 | * @param {K} item the key of the element to add
|
---|
55 | * @param {V} value the value of the element to add
|
---|
56 | * @returns {void}
|
---|
57 | */
|
---|
58 | set(item, value) {
|
---|
59 | this.map.set(item, value === undefined ? UNDEFINED_MARKER : value);
|
---|
60 | }
|
---|
61 |
|
---|
62 | /**
|
---|
63 | * @param {K} item the item to delete
|
---|
64 | * @returns {void}
|
---|
65 | */
|
---|
66 | delete(item) {
|
---|
67 | if (this.stack.length > 1) {
|
---|
68 | this.map.set(item, TOMBSTONE);
|
---|
69 | } else {
|
---|
70 | this.map.delete(item);
|
---|
71 | }
|
---|
72 | }
|
---|
73 |
|
---|
74 | /**
|
---|
75 | * @param {K} item the item to test
|
---|
76 | * @returns {boolean} true if the item exists in this set
|
---|
77 | */
|
---|
78 | has(item) {
|
---|
79 | const topValue = this.map.get(item);
|
---|
80 | if (topValue !== undefined) {
|
---|
81 | return topValue !== TOMBSTONE;
|
---|
82 | }
|
---|
83 | if (this.stack.length > 1) {
|
---|
84 | for (let i = this.stack.length - 2; i >= 0; i--) {
|
---|
85 | const value = this.stack[i].get(item);
|
---|
86 | if (value !== undefined) {
|
---|
87 | this.map.set(item, value);
|
---|
88 | return value !== TOMBSTONE;
|
---|
89 | }
|
---|
90 | }
|
---|
91 | this.map.set(item, TOMBSTONE);
|
---|
92 | }
|
---|
93 | return false;
|
---|
94 | }
|
---|
95 |
|
---|
96 | /**
|
---|
97 | * @param {K} item the key of the element to return
|
---|
98 | * @returns {Cell<V>} the value of the element
|
---|
99 | */
|
---|
100 | get(item) {
|
---|
101 | const topValue = this.map.get(item);
|
---|
102 | if (topValue !== undefined) {
|
---|
103 | return topValue === TOMBSTONE || topValue === UNDEFINED_MARKER
|
---|
104 | ? undefined
|
---|
105 | : topValue;
|
---|
106 | }
|
---|
107 | if (this.stack.length > 1) {
|
---|
108 | for (let i = this.stack.length - 2; i >= 0; i--) {
|
---|
109 | const value = this.stack[i].get(item);
|
---|
110 | if (value !== undefined) {
|
---|
111 | this.map.set(item, value);
|
---|
112 | return value === TOMBSTONE || value === UNDEFINED_MARKER
|
---|
113 | ? undefined
|
---|
114 | : value;
|
---|
115 | }
|
---|
116 | }
|
---|
117 | this.map.set(item, TOMBSTONE);
|
---|
118 | }
|
---|
119 | return undefined;
|
---|
120 | }
|
---|
121 |
|
---|
122 | _compress() {
|
---|
123 | if (this.stack.length === 1) return;
|
---|
124 | this.map = new Map();
|
---|
125 | for (const data of this.stack) {
|
---|
126 | for (const pair of data) {
|
---|
127 | if (pair[1] === TOMBSTONE) {
|
---|
128 | this.map.delete(pair[0]);
|
---|
129 | } else {
|
---|
130 | this.map.set(pair[0], pair[1]);
|
---|
131 | }
|
---|
132 | }
|
---|
133 | }
|
---|
134 | this.stack = [this.map];
|
---|
135 | }
|
---|
136 |
|
---|
137 | asArray() {
|
---|
138 | this._compress();
|
---|
139 | return Array.from(this.map.keys());
|
---|
140 | }
|
---|
141 |
|
---|
142 | asSet() {
|
---|
143 | this._compress();
|
---|
144 | return new Set(this.map.keys());
|
---|
145 | }
|
---|
146 |
|
---|
147 | asPairArray() {
|
---|
148 | this._compress();
|
---|
149 | return Array.from(this.map.entries(), extractPair);
|
---|
150 | }
|
---|
151 |
|
---|
152 | asMap() {
|
---|
153 | return new Map(this.asPairArray());
|
---|
154 | }
|
---|
155 |
|
---|
156 | get size() {
|
---|
157 | this._compress();
|
---|
158 | return this.map.size;
|
---|
159 | }
|
---|
160 |
|
---|
161 | createChild() {
|
---|
162 | return new StackedMap(this.stack);
|
---|
163 | }
|
---|
164 | }
|
---|
165 |
|
---|
166 | module.exports = StackedMap;
|
---|