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