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