export class Map {
public static [Symbol.species] = Map;
public [Symbol.toStringTag] = "Map";
private items = new LuaTable();
public size = 0;
// Key-order variables
private firstKey: K | undefined;
private lastKey: K | undefined;
private nextKey = new LuaTable();
private previousKey = new LuaTable();
constructor(entries?: Iterable | Array) {
if (entries === undefined) return;
const iterable = entries as Iterable<[K, V]>;
if (iterable[Symbol.iterator]) {
// Iterate manually because Map is compiled with ES5 which doesn't support Iterables in for...of
const iterator = iterable[Symbol.iterator]();
while (true) {
const result = iterator.next();
if (result.done) {
break;
}
const value: [K, V] = result.value; // Ensures index is offset when tuple is accessed
this.set(value[0], value[1]);
}
} else {
const array = entries as Array<[K, V]>;
for (const kvp of array) {
this.set(kvp[0], kvp[1]);
}
}
}
public clear(): void {
this.items = new LuaTable();
this.nextKey = new LuaTable();
this.previousKey = new LuaTable();
this.firstKey = undefined;
this.lastKey = undefined;
this.size = 0;
}
public delete(key: K): boolean {
const contains = this.has(key);
if (contains) {
this.size--;
// Do order bookkeeping
const next = this.nextKey.get(key);
const previous = this.previousKey.get(key);
if (next !== undefined && previous !== undefined) {
this.nextKey.set(previous, next);
this.previousKey.set(next, previous);
} else if (next !== undefined) {
this.firstKey = next;
this.previousKey.set(next, undefined!);
} else if (previous !== undefined) {
this.lastKey = previous;
this.nextKey.set(previous, undefined!);
} else {
this.firstKey = undefined;
this.lastKey = undefined;
}
this.nextKey.set(key, undefined!);
this.previousKey.set(key, undefined!);
}
this.items.set(key, undefined!);
return contains;
}
public forEach(callback: (value: V, key: K, map: Map) => any): void {
for (const key of this.keys()) {
callback(this.items.get(key), key, this);
}
}
public get(key: K): V | undefined {
return this.items.get(key);
}
public has(key: K): boolean {
return this.nextKey.get(key) !== undefined || this.lastKey === key;
}
public set(key: K, value: V): this {
const isNewValue = !this.has(key);
if (isNewValue) {
this.size++;
}
this.items.set(key, value);
// Do order bookkeeping
if (this.firstKey === undefined) {
this.firstKey = key;
this.lastKey = key;
} else if (isNewValue) {
this.nextKey.set(this.lastKey!, key);
this.previousKey.set(key, this.lastKey!);
this.lastKey = key;
}
return this;
}
public [Symbol.iterator](): IterableIterator<[K, V]> {
return this.entries();
}
public entries(): IterableIterator<[K, V]> {
const { items, nextKey } = this;
let key = this.firstKey;
return {
[Symbol.iterator](): IterableIterator<[K, V]> {
return this;
},
next(): IteratorResult<[K, V]> {
const result = { done: !key, value: [key, items.get(key!)] as [K, V] };
key = nextKey.get(key!);
return result;
},
};
}
public keys(): IterableIterator {
const nextKey = this.nextKey;
let key = this.firstKey;
return {
[Symbol.iterator](): IterableIterator {
return this;
},
next(): IteratorResult {
const result = { done: !key, value: key };
key = nextKey.get(key!);
return result as IteratorResult;
},
};
}
public values(): IterableIterator {
const { items, nextKey } = this;
let key = this.firstKey;
return {
[Symbol.iterator](): IterableIterator {
return this;
},
next(): IteratorResult {
const result = { done: !key, value: items.get(key!) };
key = nextKey.get(key!);
return result;
},
};
}
}