• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace collections {
2    export interface SortOptions<T> {
3        comparer: (a: T, b: T) => number;
4        sort: "insertion" | "comparison";
5    }
6
7    export class SortedMap<K, V> {
8        private _comparer: (a: K, b: K) => number;
9        private _keys: K[] = [];
10        private _values: V[] = [];
11        private _order: number[] | undefined;
12        private _version = 0;
13        private _copyOnWrite = false;
14
15        constructor(comparer: ((a: K, b: K) => number) | SortOptions<K>, iterable?: Iterable<[K, V]>) {
16            this._comparer = typeof comparer === "object" ? comparer.comparer : comparer;
17            this._order = typeof comparer === "object" && comparer.sort === "insertion" ? [] : undefined;
18            if (iterable) {
19                const iterator = getIterator(iterable);
20                try {
21                    for (let i = nextResult(iterator); i; i = nextResult(iterator)) {
22                        const [key, value] = i.value;
23                        this.set(key, value);
24                    }
25                }
26                finally {
27                    closeIterator(iterator);
28                }
29            }
30        }
31
32        public get size() {
33            return this._keys.length;
34        }
35
36        public get comparer() {
37            return this._comparer;
38        }
39
40        public get [Symbol.toStringTag]() {
41            return "SortedMap";
42        }
43
44        public has(key: K) {
45            return ts.binarySearch(this._keys, key, ts.identity, this._comparer) >= 0;
46        }
47
48        public get(key: K) {
49            const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
50            return index >= 0 ? this._values[index] : undefined;
51        }
52
53        public getEntry(key: K): [ K, V ] | undefined {
54            const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
55            return index >= 0 ? [ this._keys[index], this._values[index] ] : undefined;
56        }
57
58        public set(key: K, value: V) {
59            const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
60            if (index >= 0) {
61                this._values[index] = value;
62            }
63            else {
64                this.writePreamble();
65                insertAt(this._keys, ~index, key);
66                insertAt(this._values, ~index, value);
67                if (this._order) insertAt(this._order, ~index, this._version);
68                this.writePostScript();
69            }
70            return this;
71        }
72
73        public delete(key: K) {
74            const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
75            if (index >= 0) {
76                this.writePreamble();
77                ts.orderedRemoveItemAt(this._keys, index);
78                ts.orderedRemoveItemAt(this._values, index);
79                if (this._order) ts.orderedRemoveItemAt(this._order, index);
80                this.writePostScript();
81                return true;
82            }
83            return false;
84        }
85
86        public clear() {
87            if (this.size > 0) {
88                this.writePreamble();
89                this._keys.length = 0;
90                this._values.length = 0;
91                if (this._order) this._order.length = 0;
92                this.writePostScript();
93            }
94        }
95
96        public forEach(callback: (value: V, key: K, collection: this) => void, thisArg?: any) {
97            const keys = this._keys;
98            const values = this._values;
99            const indices = this.getIterationOrder();
100            const version = this._version;
101            this._copyOnWrite = true;
102            try {
103                if (indices) {
104                    for (const i of indices) {
105                        callback.call(thisArg, values[i], keys[i], this);
106                    }
107                }
108                else {
109                    for (let i = 0; i < keys.length; i++) {
110                        callback.call(thisArg, values[i], keys[i], this);
111                    }
112                }
113            }
114            finally {
115                if (version === this._version) {
116                    this._copyOnWrite = false;
117                }
118            }
119        }
120
121        public * keys() {
122            const keys = this._keys;
123            const indices = this.getIterationOrder();
124            const version = this._version;
125            this._copyOnWrite = true;
126            try {
127                if (indices) {
128                    for (const i of indices) {
129                        yield keys[i];
130                    }
131                }
132                else {
133                    yield* keys;
134                }
135            }
136            finally {
137                if (version === this._version) {
138                    this._copyOnWrite = false;
139                }
140            }
141        }
142
143        public * values() {
144            const values = this._values;
145            const indices = this.getIterationOrder();
146            const version = this._version;
147            this._copyOnWrite = true;
148            try {
149                if (indices) {
150                    for (const i of indices) {
151                        yield values[i];
152                    }
153                }
154                else {
155                    yield* values;
156                }
157            }
158            finally {
159                if (version === this._version) {
160                    this._copyOnWrite = false;
161                }
162            }
163        }
164
165        public * entries() {
166            const keys = this._keys;
167            const values = this._values;
168            const indices = this.getIterationOrder();
169            const version = this._version;
170            this._copyOnWrite = true;
171            try {
172                if (indices) {
173                    for (const i of indices) {
174                        yield [keys[i], values[i]] as [K, V];
175                    }
176                }
177                else {
178                    for (let i = 0; i < keys.length; i++) {
179                        yield [keys[i], values[i]] as [K, V];
180                    }
181                }
182            }
183            finally {
184                if (version === this._version) {
185                    this._copyOnWrite = false;
186                }
187            }
188        }
189
190        public [Symbol.iterator]() {
191            return this.entries();
192        }
193
194        private writePreamble() {
195            if (this._copyOnWrite) {
196                this._keys = this._keys.slice();
197                this._values = this._values.slice();
198                if (this._order) this._order = this._order.slice();
199                this._copyOnWrite = false;
200            }
201        }
202
203        private writePostScript() {
204            this._version++;
205        }
206
207        private getIterationOrder() {
208            if (this._order) {
209                const order = this._order;
210                return this._order
211                    .map((_, i) => i)
212                    .sort((x, y) => order[x] - order[y]);
213            }
214            return undefined;
215        }
216    }
217
218    export function insertAt<T>(array: T[], index: number, value: T): void {
219        if (index === 0) {
220            array.unshift(value);
221        }
222        else if (index === array.length) {
223            array.push(value);
224        }
225        else {
226            for (let i = array.length; i > index; i--) {
227                array[i] = array[i - 1];
228            }
229            array[index] = value;
230        }
231    }
232
233    export function getIterator<T>(iterable: Iterable<T>): Iterator<T> {
234        return iterable[Symbol.iterator]();
235    }
236
237    export function nextResult<T>(iterator: Iterator<T>): IteratorResult<T> | undefined {
238        const result = iterator.next();
239        return result.done ? undefined : result;
240    }
241
242    export function closeIterator<T>(iterator: Iterator<T>) {
243        const fn = iterator.return;
244        if (typeof fn === "function") fn.call(iterator);
245    }
246
247    /**
248     * A collection of metadata that supports inheritance.
249     */
250    export class Metadata {
251        private static readonly _undefinedValue = {};
252        private _parent: Metadata | undefined;
253        private _map: { [key: string]: any };
254        private _version = 0;
255        private _size = -1;
256        private _parentVersion: number | undefined;
257
258        constructor(parent?: Metadata) {
259            this._parent = parent;
260            this._map = Object.create(parent ? parent._map : null); // eslint-disable-line no-null/no-null
261        }
262
263        public get size(): number {
264            if (this._size === -1 || (this._parent && this._parent._version !== this._parentVersion)) {
265                let size = 0;
266                for (const _ in this._map) size++;
267                this._size = size;
268                if (this._parent) {
269                    this._parentVersion = this._parent._version;
270                }
271            }
272            return this._size;
273        }
274
275        public get parent() {
276            return this._parent;
277        }
278
279        public has(key: string): boolean {
280            return this._map[Metadata._escapeKey(key)] !== undefined;
281        }
282
283        public get(key: string): any {
284            const value = this._map[Metadata._escapeKey(key)];
285            return value === Metadata._undefinedValue ? undefined : value;
286        }
287
288        public set(key: string, value: any): this {
289            this._map[Metadata._escapeKey(key)] = value === undefined ? Metadata._undefinedValue : value;
290            this._size = -1;
291            this._version++;
292            return this;
293        }
294
295        public delete(key: string): boolean {
296            const escapedKey = Metadata._escapeKey(key);
297            if (this._map[escapedKey] !== undefined) {
298                delete this._map[escapedKey];
299                this._size = -1;
300                this._version++;
301                return true;
302            }
303            return false;
304        }
305
306        public clear(): void {
307            this._map = Object.create(this._parent ? this._parent._map : null); // eslint-disable-line no-null/no-null
308            this._size = -1;
309            this._version++;
310        }
311
312        public forEach(callback: (value: any, key: string, map: this) => void) {
313            for (const key in this._map) {
314                callback(this._map[key], Metadata._unescapeKey(key), this);
315            }
316        }
317
318        private static _escapeKey(text: string) {
319            return (text.length >= 2 && text.charAt(0) === "_" && text.charAt(1) === "_" ? "_" + text : text);
320        }
321
322        private static _unescapeKey(text: string) {
323            return (text.length >= 3 && text.charAt(0) === "_" && text.charAt(1) === "_" && text.charAt(2) === "_" ? text.slice(1) : text);
324        }
325    }
326}
327