syndicate-2017/js/src/route.js

1005 lines
24 KiB
JavaScript

var Immutable = require("immutable");
var __ = "__"; /* wildcard marker */
var SOA = "__["; // start of array
var EOA = "__]"; // end of array
function die(message) {
throw new Error(message);
}
function $Embedded(trie) {
this.trie = trie;
}
function embeddedTrie(trie) {
return new $Embedded(trie);
}
// The name argument should be a string or null; it defaults to null.
// The pattern argument defaults to wildcard, __.
function $Capture(name, pattern) {
this.name = name || null;
this.pattern = (typeof pattern === 'undefined' ? __ : pattern);
}
// Abbreviation: _$(...) <==> new $Capture(...)
function _$(name, pattern) {
return new $Capture(name, pattern);
}
function isCapture(x) { return x instanceof $Capture || x === _$; }
function captureName(x) { return x instanceof $Capture ? x.name : null; }
function capturePattern(x) { return x instanceof $Capture ? x.pattern : __; }
var SOC = "__{{"; // start of capture
var EOC = "__}}"; // end of capture
function $Success(value) {
this.value = value;
}
$Success.prototype.equals = function (other) {
if (!(other instanceof $Success)) return false;
return Immutable.is(this.value, other.value);
}
function $WildcardSequence(trie) {
this.trie = trie;
}
$WildcardSequence.prototype.equals = function (other) {
if (!(other instanceof $WildcardSequence)) return false;
return Immutable.is(this.trie, other.trie);
}
function is_emptyTrie(m) {
return Object.is(m, emptyTrie);
}
///////////////////////////////////////////////////////////////////////////
// Constructors
var emptyTrie = Immutable.Map();
function rsuccess(v) {
return (v === null) ? emptyTrie : new $Success(v);
}
function rseq(e, r) {
if (r === emptyTrie) return emptyTrie;
return emptyTrie.set(e, r);
}
function rwild(r) {
return rseq(__, r);
}
function rwildseq(r) {
return (r === emptyTrie) ? emptyTrie : new $WildcardSequence(r);
}
///////////////////////////////////////////////////////////////////////////
function compilePattern(v, p) {
if (!p) die("compilePattern: missing pattern");
return walk(p, rseq(EOA, rsuccess(v)));
function walk(p, acc) {
if (p === __) return rwild(acc);
if (Array.isArray(p)) {
acc = rseq(EOA, acc);
for (var i = p.length - 1; i >= 0; i--) {
acc = walk(p[i], acc);
}
return rseq(SOA, acc);
}
if (p instanceof $Embedded) {
return appendTrie(p.trie, function (v) { return acc; });
} else {
return rseq(p, acc);
}
}
}
function matchPattern(v, p) {
var captureCount = 0;
var result = {};
try {
walk(v, p);
} catch (e) {
if (e.matchPatternFailed) return null;
throw e;
}
result.length = captureCount;
return result;
function walk(v, p) {
if (p === v) return;
if (p === __) return;
if (Array.isArray(p) && Array.isArray(v) && p.length === v.length) {
for (var i = 0; i < p.length; i++) {
walk(v[i], p[i]);
}
return;
}
if (isCapture(p)) {
var thisCapture = captureCount++;
walk(v, capturePattern(p));
result[captureName(p) || ('$' + thisCapture)] = v;
return;
}
if (p instanceof $Embedded) {
die("$Embedded patterns not supported in matchPattern()");
}
throw {matchPatternFailed: true};
}
}
function rupdate(r, key, k) {
if (is_emptyTrie(k)) {
return r.remove(key);
} else {
return r.set(key, k);
}
}
function rlookup(r, key) {
return r.get(key, emptyTrie);
}
function is_keyOpen(k) {
return k === SOA;
}
function is_keyClose(k) {
return k === EOA;
}
function is_keyNormal(k) {
return !(is_keyOpen(k) || is_keyClose(k));
}
///////////////////////////////////////////////////////////////////////////
var unionSuccesses = function (v1, v2) {
if (v1 === true) return v2;
if (v2 === true) return v1;
return v1.union(v2);
};
var intersectSuccesses = function (v1, v2) {
return v1;
};
var subtractSuccesses = function (v1, v2) {
var r = v1.subtract(v2);
if (r.isEmpty()) return null;
return r;
};
var matchTrieSuccesses = function (v1, v2, acc) {
return acc.union(v2);
};
var projectSuccess = function (v) {
return v;
};
///////////////////////////////////////////////////////////////////////////
function expandWildseq(r) {
return union(rwild(rwildseq(r)), rseq(EOA, r));
}
function union(o1, o2) {
return merge(o1, o2);
function merge(o1, o2) {
if (is_emptyTrie(o1)) return o2;
if (is_emptyTrie(o2)) return o1;
return walk(o1, o2);
}
function walk(r1, r2) {
if (r1 instanceof $WildcardSequence) {
if (r2 instanceof $WildcardSequence) {
return rwildseq(walk(r1.trie, r2.trie));
}
r1 = expandWildseq(r1.trie);
} else if (r2 instanceof $WildcardSequence) {
r2 = expandWildseq(r2.trie);
}
if (r1 instanceof $Success && r2 instanceof $Success) {
return rsuccess(unionSuccesses(r1.value, r2.value));
}
var w = merge(rlookup(r1, __), rlookup(r2, __));
if (is_emptyTrie(w)) {
var smaller = r1.size < r2.size ? r1 : r2;
var larger = r1.size < r2.size ? r2 : r1;
var target = larger;
smaller.forEach(function (val, key) {
var k = merge(rlookup(smaller, key), rlookup(larger, key));
target = rupdate(target, key, k);
});
return target;
} else {
var target = rwild(w);
function examineKey(rA, key, rB) {
if ((key !== __) && !target.has(key)) {
var k = merge(rlookup(rA, key), rlookup(rB, key));
if (is_keyOpen(key)) {
target = rupdate(target, key, merge(rwildseq(w), k));
} else if (is_keyClose(key)) {
if (w instanceof $WildcardSequence) {
target = rupdate(target, key, merge(w.trie, k));
} else {
target = rupdate(target, key, k);
}
} else {
target = rupdate(target, key, merge(w, k));
}
}
}
r1.forEach(function (val, key) { examineKey(r1, key, r2) });
r2.forEach(function (val, key) { examineKey(r2, key, r1) });
return target;
}
}
}
function unionN() {
var acc = emptyTrie;
for (var i = 0; i < arguments.length; i++) {
acc = union(acc, arguments[i]);
}
return acc;
}
function intersect(o1, o2) {
return walk(o1, o2);
function walkFlipped(r2, r1) { return walk(r1, r2); }
function walk(r1, r2) {
// INVARIANT: r1 is a part of the original o1, and
// likewise for r2. This is so that the first arg to
// intersectSuccesses always comes from r1, and the second
// from r2.
if (is_emptyTrie(r1)) return emptyTrie;
if (is_emptyTrie(r2)) return emptyTrie;
if (r1 instanceof $WildcardSequence) {
if (r2 instanceof $WildcardSequence) {
return rwildseq(walk(r1.trie, r2.trie));
}
r1 = expandWildseq(r1.trie);
} else if (r2 instanceof $WildcardSequence) {
r2 = expandWildseq(r2.trie);
}
if (r1 instanceof $Success && r2 instanceof $Success) {
return rsuccess(intersectSuccesses(r1.value, r2.value));
}
var w1 = rlookup(r1, __);
var w2 = rlookup(r2, __);
var w = walk(w1, w2);
var target = emptyTrie;
function examineKey(key) {
if ((key !== __) && !target.has(key)) {
var k1 = rlookup(r1, key);
var k2 = rlookup(r2, key);
if (is_emptyTrie(k1)) {
if (is_emptyTrie(k2)) {
target = rupdate(target, key, emptyTrie);
} else {
target = rupdate(target, key, walkWild(walk, w1, key, k2));
}
} else {
if (is_emptyTrie(k2)) {
target = rupdate(target, key, walkWild(walkFlipped, w2, key, k1));
} else {
target = rupdate(target, key, walk(k1, k2));
}
}
}
}
if (is_emptyTrie(w1)) {
if (is_emptyTrie(w2)) {
(r1.size < r2.size ? r1 : r2).forEach(function (val, key) { examineKey(key) });
} else {
r1.forEach(function (val, key) { examineKey(key) });
}
} else {
if (is_emptyTrie(w2)) {
r2.forEach(function (val, key) { examineKey(key) });
} else {
target = rupdateInplace(target, __, w);
r1.forEach(function (val, key) { examineKey(key) });
r2.forEach(function (val, key) { examineKey(key) });
}
}
return target;
}
function walkWild(walker, w, key, k) {
if (is_emptyTrie(w)) return emptyTrie;
if (is_keyOpen(key)) return walker(rwildseq(w), k);
if (is_keyClose(key)) {
if (w instanceof $WildcardSequence) return walker(w.trie, k);
return emptyTrie;
}
return walker(w, k);
}
}
// Removes r2's mappings from r1. Assumes r2 has previously been
// union'd into r1. The subtractSuccesses function should return
// null to signal "no remaining success values".
function subtract(o1, o2) {
return walk(o1, o2);
function walk(r1, r2) {
if (is_emptyTrie(r1)) {
return emptyTrie;
} else {
if (is_emptyTrie(r2)) {
return r1;
}
}
if (r1 instanceof $WildcardSequence) {
if (r2 instanceof $WildcardSequence) {
return rwildseq(walk(r1.trie, r2.trie));
}
r1 = expandWildseq(r1.trie);
} else if (r2 instanceof $WildcardSequence) {
r2 = expandWildseq(r2.trie);
}
if (r1 instanceof $Success && r2 instanceof $Success) {
return rsuccess(subtractSuccesses(r1.value, r2.value));
}
var w1 = rlookup(r1, __);
var w2 = rlookup(r2, __);
var w = walk(w1, w2);
var target;
function examineKey(key) {
if (key !== __) {
var k1 = rlookup(r1, key);
var k2 = rlookup(r2, key);
var updatedK;
if (is_emptyTrie(k2)) {
updatedK = walkWild(key, k1, w2);
} else {
updatedK = walk(k1, k2);
}
// Here we ensure a "minimal" remainder in cases
// where after an erasure, a particular key's
// continuation is the same as the wildcard's
// continuation. TODO: the equals check may
// be expensive. If so, how can it be made
// cheaper?
if (is_keyOpen(key)) {
target = rupdate(target, key,
((updatedK instanceof $WildcardSequence) &&
Immutable.is(updatedK.trie, w))
? emptyTrie
: updatedK);
} else if (is_keyClose(key)) {
// We take care of this case later, after the
// target is fully constructed/rebuilt.
target = rupdate(target, key, updatedK);
} else {
target = rupdate(target, key,
(Immutable.is(updatedK, w) ? emptyTrie : updatedK));
}
}
}
if (is_emptyTrie(w2)) {
target = r1;
r2.forEach(function (val, key) { examineKey(key) });
} else {
target = emptyTrie;
target = rupdate(target, __, w);
r1.forEach(function (val, key) { examineKey(key) });
r2.forEach(function (val, key) { examineKey(key) });
}
// Here, the target is complete. If it has only two keys,
// one wild and one is_keyClose, and wild's continuation
// is a $WildcardSequence and the other continuation is
// identical to the sequence's continuation, then replace
// the whole thing with a nested $WildcardSequence.
// (We know w === rlookup(target, __) from before.)
//
// TODO: I suspect actually this applies even if there are
// more than two keys, so long as all their continuations
// are identical and there's at least one is_keyClose
// alongside a wild.
if (target.size === 2) {
var finalW = rlookup(target, __);
if (finalW instanceof $WildcardSequence) {
target.forEach(function (k, key) {
if ((key !== __) && is_keyClose(key)) {
if (Immutable.is(k, finalW.trie)) {
target = finalW;
return false; // terminate the iteration early
}
}
});
}
}
return target;
}
function walkWild(key, k, w) {
if (is_emptyTrie(w)) return k;
if (is_keyOpen(key)) return walk(k, rwildseq(w));
if (is_keyClose(key)) {
if (w instanceof $WildcardSequence) return walk(k, w.trie);
return k;
}
return walk(k, w);
}
}
// Returns null on failed match, otherwise the appropriate success
// value contained in the trie r.
function matchValue(r, v) {
var failureResult = null;
var vs = Immutable.List.of(v);
var stack = [Immutable.List()];
while (!is_emptyTrie(r)) {
if (r instanceof $WildcardSequence) {
if (stack.length === 0) return failureResult;
vs = stack.pop();
r = r.trie;
continue;
}
if (r instanceof $Success) {
if (vs.size === 0 && stack.length === 0) return r.value;
return failureResult;
}
if (vs.size === 0) {
if (stack.length === 0) return failureResult;
vs = stack.pop();
r = rlookup(r, EOA);
continue;
}
var v = vs.first();
vs = vs.shift();
if (typeof v === 'string' && v.substring(0, 2) === '__') {
die("Cannot match special string starting with __");
}
if (Array.isArray(v)) {
if (r.has(SOA)) {
r = rlookup(r, SOA);
stack.push(vs);
vs = Immutable.List(v);
} else {
r = rlookup(r, __);
}
} else {
if (r.has(v)) {
r = rlookup(r, v);
} else {
r = rlookup(r, __);
}
}
}
return failureResult;
}
function matchTrie(o1, o2, seed) {
var acc = typeof seed === 'undefined' ? Immutable.Set() : seed; // variable updated imperatively
walk(o1, o2);
return acc;
function walkFlipped(r2, r1) { return walk(r1, r2); }
function walk(r1, r2) {
if (is_emptyTrie(r1) || is_emptyTrie(r2)) return;
if (r1 instanceof $WildcardSequence) {
if (r2 instanceof $WildcardSequence) {
walk(r1.trie, r2.trie);
return;
}
r1 = expandWildseq(r1.trie);
} else if (r2 instanceof $WildcardSequence) {
r2 = expandWildseq(r2.trie);
}
if (r1 instanceof $Success && r2 instanceof $Success) {
acc = matchTrieSuccesses(r1.value, r2.value, acc);
return;
}
var w1 = rlookup(r1, __);
var w2 = rlookup(r2, __);
walk(w1, w2);
function examineKey(key) {
if (key !== __) {
var k1 = rlookup(r1, key);
var k2 = rlookup(r2, key);
if (is_emptyTrie(k1)) {
if (is_emptyTrie(k2)) {
return;
} else {
walkWild(walk, w1, key, k2);
}
} else {
if (is_emptyTrie(k2)) {
walkWild(walkFlipped, w2, key, k1);
} else {
walk(k1, k2);
}
}
}
}
// Optimize similarly to intersect().
if (is_emptyTrie(w1)) {
if (is_emptyTrie(w2)) {
(r1.size < r2.size ? r1 : r2).forEach(function (val, key) { examineKey(key) });
} else {
r1.forEach(function (val, key) { examineKey(key) });
}
} else {
if (is_emptyTrie(w2)) {
r2.forEach(function (val, key) { examineKey(key) });
} else {
r1.forEach(function (val, key) { examineKey(key) });
r2.forEach(function (val, key) { examineKey(key) });
}
}
}
function walkWild(walker, w, key, k) {
if (is_emptyTrie(w)) return;
if (is_keyOpen(key)) {
walker(rwildseq(w), k);
return;
}
if (is_keyClose(key)) {
if (w instanceof $WildcardSequence) walker(w.trie, k);
return;
}
walker(w, k);
}
}
function appendTrie(m, mTailFn) {
return walk(m);
function walk(m) {
if (is_emptyTrie(m)) return emptyTrie;
if (m instanceof $WildcardSequence) return rwildseq(walk(m.trie));
if (m instanceof $Success) die("Ill-formed trie");
var target = emptyTrie;
m.forEach(function (k, key) {
if (is_keyClose(key) && (k instanceof $Success)) {
target = union(target, mTailFn(k.value));
} else {
target = rupdate(target, key, walk(k));
}
});
return target;
}
}
function relabel(m, f) {
return walk(m);
function walk(m) {
if (is_emptyTrie(m)) return emptyTrie;
if (m instanceof $WildcardSequence) return rwildseq(walk(m.trie));
if (m instanceof $Success) return rsuccess(f(m.value));
var target = emptyTrie;
m.forEach(function (k, key) {
target = rupdate(target, key, walk(k));
});
return target;
}
}
function compileProjection(/* projection, projection, ... */) {
var names = [];
var acc = [];
for (var i = 0; i < arguments.length; i++) {
walk(arguments[i]);
}
acc.push(EOA);
return {names: names, spec: acc};
function walk(p) {
if (isCapture(p)) {
names.push(captureName(p));
acc.push(SOC);
walk(capturePattern(p));
acc.push(EOC);
return;
}
if (Array.isArray(p)) {
acc.push(SOA);
for (var i = 0; i < p.length; i++) {
walk(p[i]);
}
acc.push(EOA);
return;
}
if (p instanceof $Embedded) {
die("Cannot embed trie in projection");
} else {
acc.push(p);
}
}
}
function projectionToPattern(p) {
return walk(p);
function walk(p) {
if (isCapture(p)) return walk(capturePattern(p));
if (Array.isArray(p)) {
var result = [];
for (var i = 0; i < p.length; i++) {
result.push(walk(p[i]));
}
return result;
}
if (p instanceof $Embedded) {
return p.trie;
} else {
return p;
}
}
}
function project(m, compiledProjection) {
var spec = compiledProjection.spec;
return walk(false, m, 0);
function walk(isCapturing, m, specIndex) {
if (specIndex >= spec.length) {
if (isCapturing) die("Bad specification: unclosed capture");
if (m instanceof $Success) {
return rseq(EOA, rsuccess(projectSuccess(m.value)));
} else {
return emptyTrie;
}
}
if (is_emptyTrie(m)) return emptyTrie;
var item = spec[specIndex];
var nextIndex = specIndex + 1;
if (item === EOC) {
if (!isCapturing) die("Bad specification: unexpected EOC");
return walk(false, m, nextIndex);
}
if (item === SOC) {
if (isCapturing) die("Bad specification: nested capture");
return walk(true, m, nextIndex);
}
if (item === __) {
if (m instanceof $WildcardSequence) {
if (isCapturing) {
return rwild(walk(isCapturing, m, nextIndex));
} else {
return walk(isCapturing, m, nextIndex);
}
}
if (m instanceof $Success) {
return emptyTrie;
}
var target;
if (isCapturing) {
target = emptyTrie;
target = rupdate(target, __, walk(isCapturing, rlookup(m, __), nextIndex));
m.forEach(function (mk, key) {
if (key !== __) {
if (is_keyOpen(key)) {
function cont(mk2) { return walk(isCapturing, mk2, nextIndex); }
target = rupdate(target, key, captureNested(mk, cont));
} else if (is_keyClose(key)) {
// do nothing
} else {
target = rupdate(target, key, walk(isCapturing, mk, nextIndex));
}
}
});
} else {
target = walk(isCapturing, rlookup(m, __), nextIndex);
m.forEach(function (mk, key) {
if (key !== __) {
if (is_keyOpen(key)) {
function cont(mk2) { return walk(isCapturing, mk2, nextIndex); }
target = union(target, skipNested(mk, cont));
} else if (is_keyClose(key)) {
// do nothing
} else {
target = union(target, walk(isCapturing, mk, nextIndex));
}
}
});
}
return target;
}
var result;
if (m instanceof $WildcardSequence) {
if (is_keyOpen(item)) {
result = walk(isCapturing, rwildseq(m), nextIndex);
} else if (is_keyClose(item)) {
result = walk(isCapturing, m.trie, nextIndex);
} else {
result = walk(isCapturing, m, nextIndex);
}
} else if (m instanceof $Success) {
result = emptyTrie;
} else {
if (is_keyOpen(item)) {
result = walk(isCapturing, rwildseq(rlookup(m, __)), nextIndex);
} else if (is_keyClose(item)) {
result = emptyTrie;
} else {
result = walk(isCapturing, rlookup(m, __), nextIndex);
}
result = union(result, walk(isCapturing, rlookup(m, item), nextIndex));
}
if (isCapturing) {
result = rseq(item, result);
}
return result;
}
function captureNested(m, cont) {
if (m instanceof $WildcardSequence) {
return rwildseq(cont(m.trie));
}
if (is_emptyTrie(m) || (m instanceof $Success)) {
return emptyTrie;
}
var target = emptyTrie;
target = rupdate(target, __, captureNested(rlookup(m, __), cont));
m.forEach(function (mk, key) {
if (key !== __) {
if (is_keyOpen(key)) {
function cont2(mk2) { return captureNested(mk2, cont); }
rupdateInplace(target, key, captureNested(mk, cont2));
} else if (is_keyClose(key)) {
rupdateInplace(target, key, cont(mk));
} else {
rupdateInplace(target, key, captureNested(mk, cont));
}
}
});
return target;
}
function skipNested(m, cont) {
if (m instanceof $WildcardSequence) {
return cont(m.trie);
}
if (is_emptyTrie(m) || (m instanceof $Success)) {
return emptyTrie;
}
var target = skipNested(rlookup(m, __), cont);
m.forEach(function (mk, key) {
if (key !== __) {
if (is_keyOpen(key)) {
function cont2(mk2) { return skipNested(mk2, cont); }
target = union(target, skipNested(mk, cont2));
} else if (is_keyClose(key)) {
target = union(target, cont(mk));
} else {
target = union(target, skipNested(mk, cont));
}
}
});
return target;
}
}
function trieKeys(m) {
if (is_emptyTrie(m)) return [];
var result = walkSeq(m, function (vss, vsk) { return vss; });
if (result === null) return null;
return result.map(function (vs) { return vs.toArray() });
function walk(m, k) {
if (m instanceof $WildcardSequence) return null;
if (m instanceof $Success) return [];
if (m.has(__)) return null;
var acc = [];
m.forEach(function (mk, key) {
var piece;
if (is_keyOpen(key)) {
function seqK(vss, vsk) {
var acc = [];
for (var i = 0; i < vss.length; i++) {
var vs = vss[i];
acc = acc.concat(k(transformSeqs(vs, key), vsk));
}
return acc;
}
piece = walkSeq(mk, seqK);
} else if (is_keyClose(key)) {
die("trieKeys: internal error: unexpected key-close");
} else {
piece = k(key, mk);
}
if (piece === null) return null;
acc = acc.concat(piece);
});
return acc;
}
function walkSeq(m, k) {
if (m instanceof $WildcardSequence) return null;
if (m instanceof $Success) return k([], emptyTrie); // TODO: ??
if (m.has(__)) return null;
var acc = [];
m.forEach(function (mk, key) {
var piece;
if (is_keyClose(key)) {
piece = k([Immutable.List()], mk);
} else {
function outerK(v, vk) {
return walkSeq(vk, function (vss, vsk) {
var acc = [];
for (var i = 0; i < vss.length; i++) {
acc.push(vss[i].unshift(v));
}
return k(acc, vsk);
});
}
piece = walk(rseq(key, mk), outerK);
}
if (piece === null) return null;
acc = acc.concat(piece);
});
return acc;
}
function transformSeqs(vs, opener) {
if (opener === SOA) return vs;
die("Internal error: unknown opener " + opener);
}
}
function trieKeysToObjects(trieKeysResult, compiledProjection) {
if (trieKeysResult === null) return null;
var result = [];
for (var i = 0; i < trieKeysResult.length; i++) {
var e = trieKeysResult[i];
var d = {};
for (var j = 0; j < e.length; j++) {
d[compiledProjection.names[j] || ('$' + j)] = e[j];
}
result.push(d);
}
return result;
}
function projectObjects(m, compiledProjection) {
return trieKeysToObjects(trieKeys(project(m, compiledProjection)), compiledProjection);
}
function prettyTrie(m, initialIndent) {
var acc = [];
walk(initialIndent || 0, m);
return acc.join('');
function walk(i, m) {
if (m instanceof $WildcardSequence) {
acc.push("...>");
walk(i + 4, m.trie);
return;
}
if (m instanceof $Success) {
var v = m.value;
if (Immutable.Set.isSet(v)) { v = v.toArray(); }
acc.push("{" + JSON.stringify(v) + "}");
return;
}
if (m.size === 0) {
acc.push("::: no further matches possible");
return;
}
var needSep = false;
m.toOrderedMap()
.sortBy(function (k, key) { return key })
.forEach(function (k, key) {
if (needSep) {
acc.push("\n");
acc.push(indentStr(i));
} else {
needSep = true;
}
acc.push(" ");
if (key === __) key = '★';
else if (key === SOA) key = '<';
else if (key === EOA) key = '>';
else key = JSON.stringify(key);
acc.push(key);
walk(i + key.length + 1, k);
});
}
function indentStr(i) {
return new Array(i + 1).join(' '); // eww
}
}
///////////////////////////////////////////////////////////////////////////
module.exports.__ = __;
module.exports.$Capture = $Capture;
module.exports._$ = _$;
module.exports.is_emptyTrie = is_emptyTrie;
module.exports.emptyTrie = emptyTrie;
module.exports.embeddedTrie = embeddedTrie;
module.exports.compilePattern = compilePattern;
module.exports.matchPattern = matchPattern;
module.exports.union = unionN;
module.exports.intersect = intersect;
module.exports.subtract = subtract;
module.exports.matchValue = matchValue;
module.exports.matchTrie = matchTrie;
module.exports.appendTrie = appendTrie;
module.exports.relabel = relabel;
module.exports.compileProjection = compileProjection;
module.exports.projectionToPattern = projectionToPattern;
module.exports.project = project;
module.exports.trieKeys = trieKeys;
module.exports.trieKeysToObjects = trieKeysToObjects;
module.exports.projectObjects = projectObjects;
module.exports.prettyTrie = prettyTrie;