syndicate-2017/js/src/route.js

1118 lines
28 KiB
JavaScript

"use strict";
var Immutable = require("immutable");
function makeStructureConstructor(label, argumentNames) {
var $SyndicateMeta$ = {
label: label,
arguments: argumentNames
};
return function() {
var result = {"$SyndicateMeta$": $SyndicateMeta$};
for (var i = 0; i < argumentNames.length; i++) {
result[argumentNames[i]] = arguments[i];
}
return result;
};
}
function isStructure(s) {
return !!(s.$SyndicateMeta$);
}
function structureToArray(s) {
var result = [s.$SyndicateMeta$.label];
var args = s.$SyndicateMeta$.arguments;
for (var i = 0; i < args.length; i++) {
result.push(s[args[i]]);
}
return result;
}
function $Special(name) {
this.name = name;
}
var __ = new $Special("wildcard"); /* wildcard marker */
var SOA = new $Special("["); // start of array
var EOA = new $Special("]"); // 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 = new $Special("{"); // start of capture
var EOC = new $Special("}"); // 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 Immutable.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 (Immutable.List.isList(p)) {
acc = rseq(EOA, acc);
p.reverse().forEach(function (element) {
acc = walk(element, acc);
});
return rseq(SOA, acc);
}
if (p.$SyndicateMeta$) {
var args = p.$SyndicateMeta$.arguments;
acc = rseq(EOA, acc);
for (var i = args.length - 1; i >= 0; i--) {
acc = walk(p[args[i]], acc);
}
acc = rseq(p.$SyndicateMeta$.label, 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 (isStructure(p)) { p = structureToArray(p); }
if (isStructure(v)) { v = structureToArray(v); }
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) {
var oldWild = r.get(__, emptyTrie);
if (Immutable.is(k, oldWild)) {
return r.remove(key);
} else {
return r.set(key, k);
}
}
function rlookup(r, key) {
return r.get(key, emptyTrie);
}
function rlookupWild(r, key) {
var result = r.get(key, false);
if (result) return result;
var wildEdge = rlookup(r, __);
if (is_keyOpen(key)) return rwildseq(wildEdge);
if (is_keyClose(key)) return (wildEdge instanceof $WildcardSequence) ? wildEdge.trie : emptyTrie;
return wildEdge;
}
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 unionSuccessesDefault = function (v1, v2) {
if (v1 === true) return v2;
if (v2 === true) return v1;
return v1.union(v2);
};
var intersectSuccessesDefault = function (v1, v2) {
return v1;
};
var subtractSuccessesDefault = 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, unionSuccessesOpt) {
var unionSuccesses = unionSuccessesOpt || unionSuccessesDefault;
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) {
if (r2 instanceof $Success) {
return rsuccess(unionSuccesses(r1.value, r2.value));
} else {
die("Route.union: left short!");
}
} else if (r2 instanceof $Success) {
die("Route.union: right short!");
}
var w = merge(rlookup(r1, __), rlookup(r2, __));
var target;
function examineKey(key) {
if ((key !== __) && !target.has(key)) {
target = rupdate(target, key, merge(rlookupWild(r1, key), rlookupWild(r2, key)));
}
}
if (is_emptyTrie(w)) {
var smaller = r1.size < r2.size ? r1 : r2;
var larger = r1.size < r2.size ? r2 : r1;
target = larger;
smaller.forEach(function (val, key) {
var k = merge(rlookup(smaller, key), rlookup(larger, key));
target = rupdate(target, key, k);
});
} else {
target = rwild(w);
r1.forEach(function (val, key) { examineKey(key) });
r2.forEach(function (val, key) { examineKey(key) });
}
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, intersectSuccessesOpt, leftShortOpt) {
var intersectSuccesses = intersectSuccessesOpt || intersectSuccessesDefault;
var leftShort = leftShortOpt || function (v, r) {
die("Route.intersect: left side short!");
};
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) {
if (r2 instanceof $Success) {
return rsuccess(intersectSuccesses(r1.value, r2.value));
} else {
return leftShort(r1.value, r2);
}
}
var w1 = rlookup(r1, __);
var w2 = rlookup(r2, __);
var w = walk(w1, w2);
var target = emptyTrie;
function examineKey(key) {
if ((key !== __) && !target.has(key)) {
target = rupdate(target, key, walk(rlookupWild(r1, key), rlookupWild(r2, key)));
}
}
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 = rupdate(target, __, w);
r1.forEach(function (val, key) { examineKey(key) });
r2.forEach(function (val, key) { examineKey(key) });
}
}
return target;
}
}
// The subtractSuccesses function should return null to signal "no
// remaining success values".
function subtract(o1, o2, subtractSuccessesOpt) {
var subtractSuccesses = subtractSuccessesOpt || subtractSuccessesDefault;
return walk(o1, o2);
function walkFlipped(r2, r1) { return walk(r1, r2); }
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 = rlookupWild(r1, key);
var k2 = rlookupWild(r2, key);
var 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))
? w
: updatedK);
} else {
target = rupdate(target, key, 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) });
}
return collapseWildcardSequences(target);
}
}
function collapseWildcardSequences(target) {
// 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;
}
// 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 (v.$SyndicateMeta$) {
if (r.has(SOA)) {
r = rlookup(r, SOA);
stack.push(vs);
vs = Immutable.List.of(v.$SyndicateMeta$.label);
var args = v.$SyndicateMeta$.arguments;
for (var i = 0; i < args.length; i++) {
vs = vs.push(v[args[i]]);
}
} else {
r = rlookup(r, __);
}
} else {
if (r.has(v)) {
r = rlookup(r, v);
} else {
r = rlookup(r, __);
}
}
}
return failureResult;
}
function matchTrie(o1, o2, seed, leftShortOpt) {
var acc = typeof seed === 'undefined' ? Immutable.Set() : seed; // variable updated imperatively
var leftShort = leftShortOpt || function (v, r, acc) {
die("Route.matchTrie: left side short!");
};
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) {
if (r2 instanceof $Success) {
acc = matchTrieSuccesses(r1.value, r2.value, acc);
} else {
acc = leftShort(r1.value, r2, acc);
}
return;
} else if (r2 instanceof $Success) {
die("Route.matchTrie: right side short!");
}
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 triePruneBranch(m, keys) {
if (keys.isEmpty()) return emptyTrie;
if (is_emptyTrie(m)) return emptyTrie;
if (m instanceof $WildcardSequence) {
return collapseWildcardSequences(triePruneBranch(expandWildseq(m.trie), keys));
}
if (m instanceof $Success) return m;
var key = keys.first();
var rest = keys.shift();
return rupdate(m, key, triePruneBranch(rlookupWild(m, key), rest));
}
function trieStep(m, key) {
if (is_emptyTrie(m)) return emptyTrie;
if (m instanceof $WildcardSequence) return (is_keyClose(key) ? m.trie : m);
if (m instanceof $Success) return emptyTrie;
return rlookupWild(m, key);
}
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.$SyndicateMeta$) {
acc.push(SOA);
acc.push(p.$SyndicateMeta$.label);
var args = p.$SyndicateMeta$.arguments;
for (var i = 0; i < args.length; i++) {
walk(p[args[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.$SyndicateMeta$) {
var result = {"$SyndicateMeta$": p.$SyndicateMeta$};
var args = p.$SyndicateMeta$.arguments;
for (var i = 0; i < args.length; i++) {
result[args[i]] = walk(p[args[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)) {
target = rupdate(target, key, captureNested(mk, function (mk2) {
return walk(isCapturing, mk2, nextIndex);
}));
} 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)) {
target = union(target, skipNested(mk, function (mk2) {
return walk(isCapturing, mk2, nextIndex);
}));
} 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)) {
target = rupdate(target, key, captureNested(mk, function (mk2) {
return captureNested(mk2, cont);
}));
} else if (is_keyClose(key)) {
target = rupdate(target, key, cont(mk));
} else {
target = rupdate(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)) {
target = union(target, skipNested(mk, function (mk2) {
return skipNested(mk2, cont)
}));
} 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 Immutable.Set();
var result = walkSeq(m, function (vss, vsk) { return vss; });
if (result === null) return null;
return Immutable.Set(result);
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)) {
piece = walkSeq(mk, function (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;
});
} 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 {
piece = walk(rseq(key, mk), function (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);
});
});
}
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 captureToObject(captures, compiledProjection) {
var d = {};
captures.forEach(function (key, index) {
d[compiledProjection.names[index] || ('$' + index)] = key;
});
return d;
}
function trieKeysToObjects(trieKeysResult, compiledProjection) {
if (trieKeysResult === null) return null;
return trieKeysResult.toList().map(function (e) { return captureToObject(e, compiledProjection); });
}
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("::: nothing");
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 if (key instanceof $Special) key = key.name;
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.SOA = SOA;
module.exports.EOA = EOA;
module.exports.$Capture = $Capture;
module.exports.$Special = $Special;
module.exports.makeStructureConstructor = makeStructureConstructor;
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 = union;
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.triePruneBranch = triePruneBranch;
module.exports.trieStep = trieStep;
module.exports.relabel = relabel;
module.exports.compileProjection = compileProjection;
module.exports.projectionToPattern = projectionToPattern;
module.exports.project = project;
module.exports.trieKeys = trieKeys;
module.exports.captureToObject = captureToObject;
module.exports.trieKeysToObjects = trieKeysToObjects;
module.exports.projectObjects = projectObjects;
module.exports.prettyTrie = prettyTrie;
// For testing
module.exports._testing = {
rsuccess: rsuccess,
rseq: rseq,
rwild: rwild,
rwildseq: rwildseq
};