inital upload
This commit is contained in:
104
node_modules/@wry/trie/src/index.ts
generated
vendored
Normal file
104
node_modules/@wry/trie/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// A [trie](https://en.wikipedia.org/wiki/Trie) data structure that holds
|
||||
// object keys weakly, yet can also hold non-object keys, unlike the
|
||||
// native `WeakMap`.
|
||||
|
||||
// If no makeData function is supplied, the looked-up data will be an empty,
|
||||
// null-prototype Object.
|
||||
const defaultMakeData = () => Object.create(null);
|
||||
|
||||
// Useful for processing arguments objects as well as arrays.
|
||||
const { forEach, slice } = Array.prototype;
|
||||
const { hasOwnProperty } = Object.prototype;
|
||||
|
||||
export class Trie<Data> {
|
||||
// Since a `WeakMap` cannot hold primitive values as keys, we need a
|
||||
// backup `Map` instance to hold primitive keys. Both `this._weakMap`
|
||||
// and `this._strongMap` are lazily initialized.
|
||||
private weak?: WeakMap<any, Trie<Data>>;
|
||||
private strong?: Map<any, Trie<Data>>;
|
||||
private data?: Data;
|
||||
|
||||
constructor(
|
||||
private weakness = true,
|
||||
private makeData: (array: any[]) => Data = defaultMakeData,
|
||||
) {}
|
||||
|
||||
public lookup<T extends any[]>(...array: T): Data;
|
||||
public lookup(): Data {
|
||||
return this.lookupArray(arguments);
|
||||
}
|
||||
|
||||
public lookupArray<T extends IArguments | any[]>(array: T): Data {
|
||||
let node: Trie<Data> = this;
|
||||
forEach.call(array, key => node = node.getChildTrie(key));
|
||||
return hasOwnProperty.call(node, "data")
|
||||
? node.data as Data
|
||||
: node.data = this.makeData(slice.call(array));
|
||||
}
|
||||
|
||||
public peek<T extends any[]>(...array: T): Data | undefined;
|
||||
public peek(): Data | undefined {
|
||||
return this.peekArray(arguments);
|
||||
}
|
||||
|
||||
public peekArray<T extends IArguments | any[]>(array: T): Data | undefined {
|
||||
let node: Trie<Data> | undefined = this;
|
||||
|
||||
for (let i = 0, len = array.length; node && i < len; ++i) {
|
||||
const map = node.mapFor(array[i], false);
|
||||
node = map && map.get(array[i]);
|
||||
}
|
||||
|
||||
return node && node.data;
|
||||
}
|
||||
|
||||
public remove(...array: any[]): Data | undefined;
|
||||
public remove(): Data | undefined {
|
||||
return this.removeArray(arguments);
|
||||
}
|
||||
|
||||
public removeArray<T extends IArguments | any[]>(array: T): Data | undefined {
|
||||
let data: Data | undefined;
|
||||
|
||||
if (array.length) {
|
||||
const head = array[0];
|
||||
const map = this.mapFor(head, false);
|
||||
const child = map && map.get(head);
|
||||
if (child) {
|
||||
data = child.removeArray(slice.call(array, 1));
|
||||
if (!child.data && !child.weak && !(child.strong && child.strong.size)) {
|
||||
map.delete(head);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = this.data;
|
||||
delete this.data;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private getChildTrie(key: any) {
|
||||
const map = this.mapFor(key, true)!;
|
||||
let child = map.get(key);
|
||||
if (!child) map.set(key, child = new Trie<Data>(this.weakness, this.makeData));
|
||||
return child;
|
||||
}
|
||||
|
||||
private mapFor(key: any, create: boolean): Trie<Data>["weak" | "strong"] | undefined {
|
||||
return this.weakness && isObjRef(key)
|
||||
? this.weak || (create ? this.weak = new WeakMap : void 0)
|
||||
: this.strong || (create ? this.strong = new Map : void 0);
|
||||
}
|
||||
}
|
||||
|
||||
function isObjRef(value: any) {
|
||||
switch (typeof value) {
|
||||
case "object":
|
||||
if (value === null) break;
|
||||
// Fall through to return true...
|
||||
case "function":
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
207
node_modules/@wry/trie/src/tests/main.ts
generated
vendored
Normal file
207
node_modules/@wry/trie/src/tests/main.ts
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
import * as assert from "assert";
|
||||
import { Trie } from "../index.js";
|
||||
|
||||
describe("Trie", function () {
|
||||
it("can be imported", function () {
|
||||
assert.strictEqual(typeof Trie, "function");
|
||||
});
|
||||
|
||||
it("can hold objects weakly", function () {
|
||||
const trie = new Trie<object>(true);
|
||||
assert.strictEqual((trie as any).weakness, true);
|
||||
const obj1 = {};
|
||||
assert.strictEqual(
|
||||
trie.lookup(obj1, 2, 3),
|
||||
trie.lookup(obj1, 2, 3),
|
||||
);
|
||||
const obj2 = {};
|
||||
assert.notStrictEqual(
|
||||
trie.lookup(1, obj2),
|
||||
trie.lookup(1, obj2, 3),
|
||||
);
|
||||
assert.strictEqual((trie as any).weak.has(obj1), true);
|
||||
assert.strictEqual((trie as any).strong.has(obj1), false);
|
||||
assert.strictEqual((trie as any).strong.get(1).weak.has(obj2), true);
|
||||
assert.strictEqual((trie as any).strong.get(1).weak.get(obj2).strong.has(3), true);
|
||||
});
|
||||
|
||||
it("can disable WeakMap", function () {
|
||||
const trie = new Trie<object>(false);
|
||||
assert.strictEqual((trie as any).weakness, false);
|
||||
const obj1 = {};
|
||||
assert.strictEqual(
|
||||
trie.lookup(obj1, 2, 3),
|
||||
trie.lookup(obj1, 2, 3),
|
||||
);
|
||||
const obj2 = {};
|
||||
assert.notStrictEqual(
|
||||
trie.lookup(1, obj2),
|
||||
trie.lookup(1, obj2, 3),
|
||||
);
|
||||
assert.strictEqual(typeof (trie as any).weak, "undefined");
|
||||
assert.strictEqual((trie as any).strong.has(obj1), true);
|
||||
assert.strictEqual((trie as any).strong.has(1), true);
|
||||
assert.strictEqual((trie as any).strong.get(1).strong.has(obj2), true);
|
||||
assert.strictEqual((trie as any).strong.get(1).strong.get(obj2).strong.has(3), true);
|
||||
});
|
||||
|
||||
it("can produce data types other than Object", function () {
|
||||
const symbolTrie = new Trie(true, args => Symbol.for(args.join(".")));
|
||||
const s123 = symbolTrie.lookup(1, 2, 3);
|
||||
assert.strictEqual(s123.toString(), "Symbol(1.2.3)");
|
||||
assert.strictEqual(s123, symbolTrie.lookup(1, 2, 3));
|
||||
assert.strictEqual(s123, symbolTrie.lookupArray([1, 2, 3]));
|
||||
const sNull = symbolTrie.lookup();
|
||||
assert.strictEqual(sNull.toString(), "Symbol()");
|
||||
|
||||
const regExpTrie = new Trie(true, args => new RegExp("^(" + args.join("|") + ")$"));
|
||||
const rXYZ = regExpTrie.lookup("x", "y", "z");
|
||||
assert.strictEqual(rXYZ.test("w"), false);
|
||||
assert.strictEqual(rXYZ.test("x"), true);
|
||||
assert.strictEqual(rXYZ.test("y"), true);
|
||||
assert.strictEqual(rXYZ.test("z"), true);
|
||||
assert.strictEqual(String(rXYZ), "/^(x|y|z)$/");
|
||||
|
||||
class Data {
|
||||
constructor(public readonly args: any[]) {}
|
||||
}
|
||||
const dataTrie = new Trie(true, args => new Data(args));
|
||||
function checkData(...args: any[]) {
|
||||
const data = dataTrie.lookupArray(args);
|
||||
assert.strictEqual(data instanceof Data, true);
|
||||
assert.notStrictEqual(data.args, args);
|
||||
assert.deepStrictEqual(data.args, args);
|
||||
assert.strictEqual(data, dataTrie.lookup(...args));
|
||||
assert.strictEqual(data, dataTrie.lookupArray(arguments));
|
||||
return data;
|
||||
}
|
||||
const datas = [
|
||||
checkData(),
|
||||
checkData(1),
|
||||
checkData(1, 2),
|
||||
checkData(2),
|
||||
checkData(2, 3),
|
||||
checkData(true, "a"),
|
||||
checkData(/asdf/i, "b", function oyez() {}),
|
||||
];
|
||||
// Verify that all Data objects are distinct.
|
||||
assert.strictEqual(new Set(datas).size, datas.length);
|
||||
});
|
||||
|
||||
it("can peek at values", function () {
|
||||
const trie = new Trie(true, (args) => args);
|
||||
|
||||
const obj = {};
|
||||
assert.strictEqual(trie.peek(1, 2, 'x'), undefined);
|
||||
assert.strictEqual(trie.peek(1, 2, obj), undefined);
|
||||
assert.strictEqual(trie.peekArray([1, 2, 'x']), undefined);
|
||||
assert.strictEqual(trie.peekArray([1, 2, obj]), undefined);
|
||||
// peek/peekArray should not create anything on its own
|
||||
assert.strictEqual(trie['weak'], undefined);
|
||||
assert.strictEqual(trie['strong'], undefined);
|
||||
assert.strictEqual(trie['data'], undefined);
|
||||
|
||||
const data1 = trie.lookup(1, 2, 'x');
|
||||
const data2 = trie.lookup(1, 2, obj);
|
||||
|
||||
assert.strictEqual(trie.peek(1, 2, 'x'), data1);
|
||||
assert.strictEqual(trie.peek(1, 2, obj), data2);
|
||||
assert.strictEqual(trie.peekArray([1, 2, 'x']), data1);
|
||||
assert.strictEqual(trie.peekArray([1, 2, obj]), data2);
|
||||
});
|
||||
|
||||
describe("can remove values", function () {
|
||||
it("will remove values", () => {
|
||||
const trie = new Trie(true, (args) => args);
|
||||
|
||||
trie.lookup(1, 2, "x");
|
||||
trie.remove(1, 2, "x");
|
||||
assert.strictEqual(trie.peek(1, 2, "x"), undefined);
|
||||
});
|
||||
|
||||
it("removing will return the value", () => {
|
||||
const trie = new Trie(true, (args) => args);
|
||||
|
||||
const data = trie.lookup(1, 2, "x");
|
||||
assert.strictEqual(trie.remove(1, 2, "x"), data);
|
||||
});
|
||||
|
||||
it("will remove empty parent nodes", () => {
|
||||
const trie = new Trie(true, (args) => args);
|
||||
|
||||
const data = trie.lookup(1, 2, "x");
|
||||
assert.strictEqual(trie.peek(1, 2, "x"), data);
|
||||
assert.equal(pathExistsInTrie(trie, 1, 2, "x"), true);
|
||||
assert.strictEqual(trie.remove(1, 2, "x"), data);
|
||||
assert.equal(pathExistsInTrie(trie, 1), false);
|
||||
});
|
||||
|
||||
it("will not remove parent nodes with other children", () => {
|
||||
const trie = new Trie(true, (args) => args);
|
||||
|
||||
trie.lookup(1, 2, "x");
|
||||
const data = trie.lookup(1, 2);
|
||||
trie.remove(1, 2, "x");
|
||||
assert.strictEqual(trie.peek(1, 2, "x"), undefined);
|
||||
assert.strictEqual(trie.peek(1, 2), data);
|
||||
});
|
||||
|
||||
it("will remove data, not the full node, if a node still has children", () => {
|
||||
const trie = new Trie(true, (args) => args);
|
||||
|
||||
trie.lookup(1, 2);
|
||||
const data = trie.lookup(1, 2, "x");
|
||||
trie.remove(1, 2);
|
||||
assert.strictEqual(trie.peek(1, 2), undefined);
|
||||
assert.strictEqual(trie.peek(1, 2, "x"), data);
|
||||
});
|
||||
|
||||
it("will remove direct children", () => {
|
||||
const trie = new Trie(true, (args) => args);
|
||||
|
||||
trie.lookup(1);
|
||||
trie.remove(1);
|
||||
assert.strictEqual(trie.peek(1), undefined);
|
||||
});
|
||||
|
||||
it("will remove nodes from WeakMaps", () => {
|
||||
const trie = new Trie(true, (args) => args);
|
||||
const obj = {};
|
||||
const data = trie.lookup(1, obj, "x");
|
||||
assert.equal(pathExistsInTrie(trie, 1), true);
|
||||
assert.strictEqual(trie.remove(1, obj, "x"), data);
|
||||
assert.strictEqual(trie.peek(1, obj, "x"), undefined);
|
||||
assert.equal(pathExistsInTrie(trie, 1, obj), false);
|
||||
});
|
||||
|
||||
it("will not remove nodes if they contain an (even empty) WeakMap", () => {
|
||||
const trie = new Trie(true, (args) => args);
|
||||
const obj = {};
|
||||
|
||||
const data = trie.lookup(1, 2, "x");
|
||||
trie.lookup(1, obj);
|
||||
trie.remove(1, obj);
|
||||
|
||||
assert.strictEqual(trie.peek(1, 2, "x"), data);
|
||||
assert.equal(pathExistsInTrie(trie, 1), true);
|
||||
assert.equal(pathExistsInTrie(trie, 1, 2), true);
|
||||
assert.strictEqual(trie.remove(1, 2, "x"), data);
|
||||
assert.equal(pathExistsInTrie(trie, 1), true);
|
||||
assert.equal(pathExistsInTrie(trie, 1, 2), false);
|
||||
});
|
||||
});
|
||||
|
||||
function pathExistsInTrie(trie: Trie<unknown>, ...path: any[]) {
|
||||
return (
|
||||
path.reduce((node: Trie<unknown> | undefined, key: any) => {
|
||||
const map: Trie<unknown>["weak" | "strong"] =
|
||||
// not the full implementation but enough for a test
|
||||
trie["weakness"] && typeof key === "object"
|
||||
? node?.["weak"]
|
||||
: node?.["strong"];
|
||||
|
||||
return map?.get(key);
|
||||
}, trie) !== undefined
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user