Initial commit of port of minimart fastrouting
This commit is contained in:
parent
7ae53f5c21
commit
cdea1507c4
|
@ -0,0 +1,984 @@
|
|||
/* Routing */
|
||||
|
||||
function Routing(exports) {
|
||||
|
||||
var __ = "__"; /* wildcard marker */
|
||||
|
||||
var SOA = "__["; // start of array
|
||||
var EOA = "__]"; // end of array
|
||||
|
||||
function die(message) {
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
// The pattern argument defaults to wildcard, __.
|
||||
function $Capture(pattern) {
|
||||
this.pattern = (typeof pattern === 'undefined' ? __ : pattern);
|
||||
}
|
||||
|
||||
// Abbreviation: _$(x) <==> new $Capture(x)
|
||||
function _$(pattern) {
|
||||
return new $Capture(pattern);
|
||||
}
|
||||
|
||||
var SOC = "__{{"; // start of capture
|
||||
var EOC = "__}}"; // end of capture
|
||||
|
||||
function $Success(value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
function $WildcardSequence(matcher) {
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
function $Dict() {
|
||||
this.length = 0;
|
||||
this.entries = {};
|
||||
}
|
||||
|
||||
$Dict.prototype.get = function (key) {
|
||||
return this.entries[key] || emptyMatcher;
|
||||
};
|
||||
|
||||
$Dict.prototype.set = function (key, val) {
|
||||
if (!(key in this.entries)) this.length++;
|
||||
this.entries[key] = val;
|
||||
};
|
||||
|
||||
$Dict.prototype.clear = function (key) {
|
||||
if (key in this.entries) this.length--;
|
||||
delete this.entries[key];
|
||||
};
|
||||
|
||||
$Dict.prototype.isEmpty = function () {
|
||||
return this.length === 0;
|
||||
};
|
||||
|
||||
$Dict.prototype.copy = function () {
|
||||
var other = new $Dict();
|
||||
other.length = this.length;
|
||||
for (var key in this.entries) {
|
||||
if (this.entries.hasOwnProperty(key)) {
|
||||
other.entries[key] = this.entries[key];
|
||||
}
|
||||
}
|
||||
return other;
|
||||
};
|
||||
|
||||
$Dict.prototype.emptyGuard = function () {
|
||||
if (this.isEmpty()) return emptyMatcher;
|
||||
return this;
|
||||
};
|
||||
|
||||
$Dict.prototype.has = function (key) {
|
||||
return key in this.entries;
|
||||
};
|
||||
|
||||
$Dict.prototype.smallerOf = function (other) {
|
||||
return (other.length < this.length) ? other : this;
|
||||
};
|
||||
|
||||
$Dict.prototype.largerOf = function (other) {
|
||||
return (this.length < other.length) ? this : other;
|
||||
};
|
||||
|
||||
function is_emptyMatcher(m) {
|
||||
return (m === emptyMatcher);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Constructors
|
||||
|
||||
var emptyMatcher = null;
|
||||
|
||||
function rsuccess(v) {
|
||||
return (v === emptyMatcher) ? emptyMatcher : new $Success(v);
|
||||
}
|
||||
|
||||
function rseq(e, r) {
|
||||
if (r === emptyMatcher) return emptyMatcher;
|
||||
var s = new $Dict();
|
||||
s.set(e, r);
|
||||
return s;
|
||||
}
|
||||
|
||||
function rwild(r) {
|
||||
return rseq(__, r);
|
||||
}
|
||||
|
||||
function rwildseq(r) {
|
||||
return (r === emptyMatcher) ? emptyMatcher : 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);
|
||||
}
|
||||
|
||||
return rseq(p, acc);
|
||||
}
|
||||
}
|
||||
|
||||
function shallowCopyArray(s) {
|
||||
return s.slice();
|
||||
}
|
||||
|
||||
function rupdate(r, key, k) {
|
||||
if (is_emptyMatcher(k)) {
|
||||
if (is_emptyMatcher(r)) return r;
|
||||
r = r.copy();
|
||||
r.clear(key);
|
||||
return r.emptyGuard();
|
||||
} else {
|
||||
r = (is_emptyMatcher(r) ? new $Dict() : r.copy());
|
||||
r.set(key, k);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
function rupdateInplace(r, key, k) {
|
||||
if (is_emptyMatcher(k)) {
|
||||
r.clear(key);
|
||||
} else {
|
||||
r.set(key, k);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Enough of sets to get by with
|
||||
|
||||
function newSet() {
|
||||
var s = {};
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
s[arguments[i]] = arguments[i];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function setUnion(s1, s2) {
|
||||
var s = {};
|
||||
setUnionInplace(s, s1);
|
||||
setUnionInplace(s, s2);
|
||||
return s;
|
||||
}
|
||||
|
||||
function setEmpty(s) {
|
||||
for (var k in s) {
|
||||
if (s.hasOwnProperty(k))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function setSubtract(s1, s2) {
|
||||
var s = {};
|
||||
for (var key in s1) {
|
||||
if (s1.hasOwnProperty(key) && !s2.hasOwnProperty(key)) {
|
||||
s[key] = s1[key];
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function setUnionInplace(acc, s) {
|
||||
for (var key in s) {
|
||||
if (s.hasOwnProperty(key)) {
|
||||
acc[key] = s[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var unionSuccesses = function (v1, v2) {
|
||||
if (v1 === true) return v2;
|
||||
if (v2 === true) return v1;
|
||||
return setUnion(v1, v2);
|
||||
};
|
||||
|
||||
var intersectSuccesses = function (v1, v2) {
|
||||
return v1;
|
||||
};
|
||||
|
||||
var erasePathSuccesses = function (v1, v2) {
|
||||
var r = setSubtract(v1, v2);
|
||||
if (setEmpty(r)) return null;
|
||||
return r;
|
||||
};
|
||||
|
||||
var matchMatcherSuccesses = function (v1, v2, acc) {
|
||||
setUnionInplace(acc, 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_emptyMatcher(o1)) return o2;
|
||||
if (is_emptyMatcher(o2)) return o1;
|
||||
return walk(o1, o2);
|
||||
}
|
||||
|
||||
function walk(r1, r2) {
|
||||
if (r1 instanceof $WildcardSequence) {
|
||||
if (r2 instanceof $WildcardSequence) {
|
||||
return rwildseq(walk(r1.matcher, r2.matcher));
|
||||
}
|
||||
r1 = expandWildseq(r1.matcher);
|
||||
} else if (r2 instanceof $WildcardSequence) {
|
||||
r2 = expandWildseq(r2.matcher);
|
||||
}
|
||||
|
||||
if (r1 instanceof $Success && r2 instanceof $Success) {
|
||||
return rsuccess(unionSuccesses(r1.value, r2.value));
|
||||
}
|
||||
|
||||
var w = merge(r1.get(__), r2.get(__));
|
||||
if (is_emptyMatcher(w)) {
|
||||
var smaller = r1.smallerOf(r2);
|
||||
var larger = r1.largerOf(r2);
|
||||
var target = larger.copy();
|
||||
for (var key in smaller.entries) {
|
||||
var k = merge(smaller.get(key), larger.get(key));
|
||||
rupdateInplace(target, key, k);
|
||||
}
|
||||
return target.emptyGuard();
|
||||
} else {
|
||||
var target = rwild(w).copy();
|
||||
for (var key in r1.entries) { examineKey(r1, key, r2); }
|
||||
for (var key in r2.entries) { examineKey(r2, key, r1); }
|
||||
return target;
|
||||
function examineKey(rA, key, rB) {
|
||||
if ((key !== __) && !target.has(key)) {
|
||||
var k = merge(rA.get(key), rB.get(key));
|
||||
if (is_keyOpen(key)) {
|
||||
rupdateInplace(target, key, merge(rwildseq(w), k));
|
||||
} else if (is_keyClose(key)) {
|
||||
if (w instanceof $WildcardSequence) {
|
||||
rupdateInplace(target, key, merge(w.matcher, k));
|
||||
} else {
|
||||
rupdateInplace(target, key, k);
|
||||
}
|
||||
} else {
|
||||
rupdateInplace(target, key, merge(w, k));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unionN() {
|
||||
var acc = emptyMatcher;
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
acc = union(acc, arguments[i]);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
function intersect(o1, o2) {
|
||||
if (is_emptyMatcher(o1)) return emptyMatcher;
|
||||
if (is_emptyMatcher(o2)) return emptyMatcher;
|
||||
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_emptyMatcher(r1)) return emptyMatcher;
|
||||
if (is_emptyMatcher(r2)) return emptyMatcher;
|
||||
|
||||
if (r1 instanceof $WildcardSequence) {
|
||||
if (r2 instanceof $WildcardSequence) {
|
||||
return rwildseq(walk(r1.matcher, r2.matcher));
|
||||
}
|
||||
r1 = expandWildseq(r1.matcher);
|
||||
} else if (r2 instanceof $WildcardSequence) {
|
||||
r2 = expandWildseq(r2.matcher);
|
||||
}
|
||||
|
||||
if (r1 instanceof $Success && r2 instanceof $Success) {
|
||||
return rsuccess(intersectSuccesses(r1.value, r2.value));
|
||||
}
|
||||
|
||||
var w1 = r1.get(__);
|
||||
var w2 = r2.get(__);
|
||||
var w = walk(w1, w2);
|
||||
|
||||
var target = new $Dict();
|
||||
if (is_emptyMatcher(w1)) {
|
||||
if (is_emptyMatcher(w2)) {
|
||||
for (var key in r1.smallerOf(r2).entries) examineKey(key);
|
||||
} else {
|
||||
for (var key in r1.entries) examineKey(key);
|
||||
}
|
||||
} else {
|
||||
if (is_emptyMatcher(w2)) {
|
||||
for (var key in r2.entries) examineKey(key);
|
||||
} else {
|
||||
rupdateInplace(target, __, w);
|
||||
for (var key in r1.entries) examineKey(key);
|
||||
for (var key in r2.entries) examineKey(key);
|
||||
}
|
||||
}
|
||||
return target.emptyGuard();
|
||||
|
||||
function examineKey(key) {
|
||||
if ((key !== __) && !target.has(key)) {
|
||||
var k1 = r1.get(key);
|
||||
var k2 = r2.get(key);
|
||||
if (is_emptyMatcher(k1)) {
|
||||
if (is_emptyMatcher(k2)) {
|
||||
rupdateInplace(target, key, emptyMatcher);
|
||||
} else {
|
||||
rupdateInplace(target, key, walkWild(walk, w1, key, k2));
|
||||
}
|
||||
} else {
|
||||
if (is_emptyMatcher(k2)) {
|
||||
rupdateInplace(target, key, walkWild(walkFlipped, w2, key, k1));
|
||||
} else {
|
||||
rupdateInplace(target, key, walk(k1, k2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function walkWild(walker, w, key, k) {
|
||||
if (is_emptyMatcher(w)) return emptyMatcher;
|
||||
if (is_keyOpen(key)) return walker(rwildseq(w), k);
|
||||
if (is_keyClose(key)) {
|
||||
if (w instanceof $WildcardSequence) return walker(w.matcher, k);
|
||||
return emptyMatcher;
|
||||
}
|
||||
return walker(w, k);
|
||||
}
|
||||
}
|
||||
|
||||
// Removes r2's mappings from r1. Assumes r2 has previously been
|
||||
// union'd into r1. The erasePathSuccesses function should return
|
||||
// null to signal "no remaining success values".
|
||||
function erasePath(o1, o2) {
|
||||
return walk(o1, o2);
|
||||
|
||||
function cofinitePattern() {
|
||||
die("Cofinite pattern required");
|
||||
}
|
||||
|
||||
function walk(r1, r2) {
|
||||
if (is_emptyMatcher(r1)) {
|
||||
if (is_emptyMatcher(r2)) {
|
||||
return emptyMatcher;
|
||||
} else {
|
||||
cofinitePattern();
|
||||
}
|
||||
} else {
|
||||
if (is_emptyMatcher(r2)) {
|
||||
return r1;
|
||||
}
|
||||
}
|
||||
|
||||
if (r1 instanceof $WildcardSequence) {
|
||||
if (r2 instanceof $WildcardSequence) {
|
||||
return rwildseq(walk(r1.matcher, r2.matcher));
|
||||
}
|
||||
cofinitePattern();
|
||||
} else if (r2 instanceof $WildcardSequence) {
|
||||
r2 = expandWildseq(r2.matcher);
|
||||
}
|
||||
|
||||
if (r1 instanceof $Success && r2 instanceof $Success) {
|
||||
return rsuccess(erasePathSuccesses(r1.value, r2.value));
|
||||
}
|
||||
|
||||
var w1 = r1.get(__);
|
||||
var w2 = r2.get(__);
|
||||
var w = walk(w1, w2);
|
||||
var target;
|
||||
|
||||
if (is_emptyMatcher(w2)) {
|
||||
target = r1.copy();
|
||||
for (var key in r2.entries) examineKey(key);
|
||||
} else {
|
||||
target = rwild(w);
|
||||
for (var key in r1.entries) examineKey(key);
|
||||
for (var key in r2.entries) examineKey(key);
|
||||
}
|
||||
return target.emptyGuard();
|
||||
|
||||
function examineKey(key) {
|
||||
if ((key !== __) && !target.has(key)) {
|
||||
var k1 = r1.get(key);
|
||||
var k2 = r2.get(key);
|
||||
var updatedK;
|
||||
if (is_emptyMatcher(k1)) {
|
||||
if (is_emptyMatcher(k2)) {
|
||||
updatedK = emptyMatcher;
|
||||
} else {
|
||||
cofinitePattern();
|
||||
}
|
||||
} else {
|
||||
if (is_emptyMatcher(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 requal check may be
|
||||
// expensive. If so, how can it be made cheaper?
|
||||
rupdateInplace(target, key, (requal(updatedK, w) ? emptyMatcher : updatedK));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function walkWild(key, k, w) {
|
||||
if (is_emptyMatcher(w)) return k;
|
||||
if (is_keyOpen(key)) return walk(k, rwildseq(w));
|
||||
if (is_keyClose(key)) {
|
||||
if (w instanceof $WildcardSequence) return walk(k, w.matcher);
|
||||
return k;
|
||||
}
|
||||
return walk(k, w);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns null on failed match, otherwise the appropriate success
|
||||
// value contained in the matcher r.
|
||||
function matchValue(r, v) {
|
||||
var failureResult = null;
|
||||
|
||||
var vs = [v];
|
||||
var stack = [[]];
|
||||
|
||||
while (!is_emptyMatcher(r)) {
|
||||
if (r instanceof $WildcardSequence) {
|
||||
if (stack.length === 0) return failureResult;
|
||||
vs = stack.pop();
|
||||
r = r.matcher;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r instanceof $Success) {
|
||||
if (vs.length === 0 && stack.length === 0) return r.value;
|
||||
return failureResult;
|
||||
}
|
||||
|
||||
if (vs.length === 0) {
|
||||
if (stack.length === 0) return failureResult;
|
||||
vs = stack.pop();
|
||||
r = r.get(EOA);
|
||||
continue;
|
||||
}
|
||||
|
||||
var v = vs.shift();
|
||||
|
||||
if (typeof v === 'string' && v.substring(0, 2) === '__') {
|
||||
die("Cannot match special string starting with __");
|
||||
}
|
||||
|
||||
if (Array.isArray(v)) {
|
||||
if (SOA in r.entries) {
|
||||
r = r.get(SOA);
|
||||
stack.push(vs);
|
||||
vs = shallowCopyArray(v);
|
||||
} else {
|
||||
r = r.get(__);
|
||||
}
|
||||
} else {
|
||||
if (v in r.entries) {
|
||||
r = r.get(v);
|
||||
} else {
|
||||
r = r.get(__);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return failureResult;
|
||||
}
|
||||
|
||||
// TODO: better name for this
|
||||
function matchMatcher(o1, o2, seed) {
|
||||
var acc = seed || newSet(); // will be modified in place
|
||||
walk(o1, o2);
|
||||
return acc;
|
||||
|
||||
function walkFlipped(r2, r1) { return walk(r1, r2); }
|
||||
|
||||
function walk(r1, r2) {
|
||||
if (is_emptyMatcher(r1) || is_emptyMatcher(r2)) return;
|
||||
|
||||
if (r1 instanceof $WildcardSequence) {
|
||||
if (r2 instanceof $WildcardSequence) {
|
||||
walk(r1.matcher, r2.matcher);
|
||||
return;
|
||||
}
|
||||
r1 = expandWildseq(r1.matcher);
|
||||
} else if (r2 instanceof $WildcardSequence) {
|
||||
r2 = expandWildseq(r2.matcher);
|
||||
}
|
||||
|
||||
if (r1 instanceof $Success && r2 instanceof $Success) {
|
||||
matchMatcherSuccesses(r1.value, r2.value, acc);
|
||||
return;
|
||||
}
|
||||
|
||||
var w1 = r1.get(__);
|
||||
var w2 = r2.get(__);
|
||||
walk(w1, w2);
|
||||
|
||||
// Optimize similarly to intersect().
|
||||
if (is_emptyMatcher(w1)) {
|
||||
if (is_emptyMatcher(w2)) {
|
||||
for (var key in r1.smallerOf(r2).entries) examineKey(key);
|
||||
} else {
|
||||
for (var key in r1.entries) examineKey(key);
|
||||
}
|
||||
} else {
|
||||
if (is_emptyMatcher(w2)) {
|
||||
for (var key in r2.entries) examineKey(key);
|
||||
} else {
|
||||
for (var key in r1.entries) examineKey(key);
|
||||
for (var key in r2.entries) examineKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
function examineKey(key) {
|
||||
if (key !== __) {
|
||||
var k1 = r1.get(key);
|
||||
var k2 = r2.get(key);
|
||||
if (is_emptyMatcher(k1)) {
|
||||
if (is_emptyMatcher(k2)) {
|
||||
return;
|
||||
} else {
|
||||
walkWild(walk, w1, key, k2);
|
||||
}
|
||||
} else {
|
||||
if (is_emptyMatcher(k2)) {
|
||||
walkWild(walkFlipped, w2, key, k1);
|
||||
} else {
|
||||
walk(k1, k2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function walkWild(walker, w, key, k) {
|
||||
if (is_emptyMatcher(w)) return;
|
||||
if (is_keyOpen(key)) {
|
||||
walker(rwildseq(w), k);
|
||||
return;
|
||||
}
|
||||
if (is_keyClose(key)) {
|
||||
if (w instanceof $WildcardSequence) walker(w.matcher, k);
|
||||
return;
|
||||
}
|
||||
walker(w, k);
|
||||
}
|
||||
}
|
||||
|
||||
function relabel(m, f) {
|
||||
return walk(m);
|
||||
|
||||
function walk(m) {
|
||||
if (is_emptyMatcher(m)) return emptyMatcher;
|
||||
if (m instanceof $WildcardSequence) return rwildseq(walk(m.matcher));
|
||||
if (m instanceof $Success) return rsuccess(f(m.value));
|
||||
|
||||
var target = new $Dict();
|
||||
for (var key in m.entries) {
|
||||
rupdateInplace(target, key, walk(m.get(key)));
|
||||
}
|
||||
return target.emptyGuard();
|
||||
}
|
||||
}
|
||||
|
||||
function compileProjection(p) {
|
||||
var acc = [];
|
||||
walk(p);
|
||||
acc.push(EOA);
|
||||
return acc;
|
||||
|
||||
function walk(p) {
|
||||
if (p instanceof $Capture) {
|
||||
acc.push(SOC);
|
||||
walk(p.pattern);
|
||||
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;
|
||||
}
|
||||
|
||||
acc.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
function projectionToPattern(p) {
|
||||
return walk(p);
|
||||
|
||||
function walk(p) {
|
||||
if (p instanceof $Capture) return walk(p.pattern);
|
||||
|
||||
if (Array.isArray(p)) {
|
||||
var result = [];
|
||||
for (var i = 0; i < p.length; i++) {
|
||||
result.push(walk(p[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
function project(m, spec) {
|
||||
return rseq(SOA, 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, rseq(EOA, rsuccess(projectSuccess(m.value))));
|
||||
} else {
|
||||
return emptyMatcher;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_emptyMatcher(m)) return emptyMatcher;
|
||||
|
||||
var item = spec[specIndex];
|
||||
var nextIndex = specIndex + 1;
|
||||
|
||||
if (item === EOC) {
|
||||
if (!isCapturing) die("Bad specification: unepxected 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 emptyMatcher;
|
||||
}
|
||||
|
||||
var target;
|
||||
if (isCapturing) {
|
||||
target = new $Dict();
|
||||
rupdateInplace(target, __, walk(isCapturing, m.get(__), nextIndex));
|
||||
for (var key in m.entries) {
|
||||
if (key !== __) {
|
||||
var mk = m.get(key);
|
||||
if (is_keyOpen(key)) {
|
||||
function cont(mk2) { return walk(isCapturing, mk2, nextIndex); }
|
||||
rupdateInplace(target, key, captureNested(mk, cont));
|
||||
} else if (is_keyClose(key)) {
|
||||
// do nothing
|
||||
} else {
|
||||
rupdateInplace(target, key, walk(isCapturing, mk, nextIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
target = walk(isCapturing, m.get(__), nextIndex);
|
||||
for (var key in m.entries) {
|
||||
if (key !== __) {
|
||||
var mk = m.get(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.matcher, nextIndex);
|
||||
} else {
|
||||
result = walk(isCapturing, m, nextIndex);
|
||||
}
|
||||
} else if (m instanceof $Success) {
|
||||
result = emptyMatcher;
|
||||
} else {
|
||||
if (is_keyOpen(item)) {
|
||||
result = walk(isCapturing, rwildseq(m.get(__)), nextIndex);
|
||||
} else if (is_keyClose(item)) {
|
||||
result = emptyMatcher;
|
||||
} else {
|
||||
result = walk(isCapturing, m.get(__), nextIndex);
|
||||
}
|
||||
result = union(result, walk(isCapturing, m.get(item), nextIndex));
|
||||
}
|
||||
if (isCapturing) {
|
||||
result = rseq(item, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function captureNested(m, cont) {
|
||||
if (m instanceof $WildcardSequence) {
|
||||
return rwildseq(cont(m.matcher));
|
||||
}
|
||||
|
||||
if (is_emptyMatcher(m) || (m instanceof $Success)) {
|
||||
return emptyMatcher;
|
||||
}
|
||||
|
||||
var target = new $Dict();
|
||||
rupdateInplace(target, __, captureNested(m.get(__), cont));
|
||||
for (var key in m.entries) {
|
||||
if (key !== __) {
|
||||
var mk = m.get(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.emptyGuard();
|
||||
}
|
||||
|
||||
function skipNested(m, cont) {
|
||||
if (m instanceof $WildcardSequence) {
|
||||
return cont(m.matcher);
|
||||
}
|
||||
|
||||
if (is_emptyMatcher(m) || (m instanceof $Success)) {
|
||||
return emptyMatcher;
|
||||
}
|
||||
|
||||
var target = skipNested(m.get(__), cont);
|
||||
for (var key in m.entries) {
|
||||
if (key !== __) {
|
||||
var mk = m.get(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 matcherKeys(m) {
|
||||
return walk(m, function (v, k) { return [v]; });
|
||||
|
||||
function walk(m, k) {
|
||||
if (m instanceof $WildcardSequence) return null;
|
||||
if (m instanceof $Success) return [];
|
||||
if (m.has(__)) return null;
|
||||
var acc = [];
|
||||
for (var key in m.entries) {
|
||||
var mk = m.get(key);
|
||||
var piece;
|
||||
if (is_keyOpen(key)) {
|
||||
piece = walkSeq(mk, seqK);
|
||||
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;
|
||||
}
|
||||
} else if (is_keyClose(key)) {
|
||||
die("matcherKeys: 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([], emptyMatcher); // TODO: ??
|
||||
if (m.has(__)) return null;
|
||||
var acc = [];
|
||||
for (var key in m.entries) {
|
||||
var mk = m.get(key);
|
||||
var piece;
|
||||
if (is_keyClose(key)) {
|
||||
piece = k([[]], mk);
|
||||
} else {
|
||||
piece = walk(rseq(key, mk), outerK);
|
||||
function outerK(v, vk) {
|
||||
return walkSeq(vk, innerK);
|
||||
function innerK(vss, vsk) {
|
||||
var acc = [];
|
||||
for (var i = 0; i < vss.length; i++) {
|
||||
var vs = shallowCopyArray(vss[i]);
|
||||
vs.unshift(v);
|
||||
acc.push(vs);
|
||||
}
|
||||
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 prettyMatcher(m, initialIndent) {
|
||||
var acc = [];
|
||||
walk(initialIndent || 0, m);
|
||||
return acc.join('');
|
||||
|
||||
function walk(i, m) {
|
||||
if (is_emptyMatcher(m)) {
|
||||
acc.push("::: no further matches possible");
|
||||
return;
|
||||
}
|
||||
if (m instanceof $WildcardSequence) {
|
||||
acc.push("...>");
|
||||
walk(i + 4, m.matcher);
|
||||
return;
|
||||
}
|
||||
if (m instanceof $Success) {
|
||||
acc.push("{" + JSON.stringify(m.value) + "}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m.length === 0) {
|
||||
acc.push(" ::: empty hash!");
|
||||
return;
|
||||
}
|
||||
|
||||
var needSep = false;
|
||||
for (var key in m.entries) {
|
||||
if (key === '__length') continue;
|
||||
var k = m.entries[key];
|
||||
if (needSep) {
|
||||
acc.push("\n");
|
||||
acc.push(indentStr(i));
|
||||
} else {
|
||||
needSep = true;
|
||||
}
|
||||
acc.push(" ");
|
||||
if (key === __) key = '★';
|
||||
if (key === SOA) key = '<';
|
||||
if (key === EOA) key = '>';
|
||||
acc.push(key);
|
||||
walk(i + key.length + 1, k);
|
||||
}
|
||||
}
|
||||
|
||||
function indentStr(i) {
|
||||
return new Array(i + 1).join(' '); // eww
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
exports.__ = __;
|
||||
exports.newSet = newSet;
|
||||
exports.$Capture = $Capture;
|
||||
exports._$ = _$;
|
||||
exports.is_emptyMatcher = is_emptyMatcher;
|
||||
exports.emptyMatcher = emptyMatcher;
|
||||
exports.compilePattern = compilePattern;
|
||||
exports.union = unionN;
|
||||
exports.intersect = intersect;
|
||||
exports.erasePath = erasePath;
|
||||
exports.matchValue = matchValue;
|
||||
exports.matchMatcher = matchMatcher;
|
||||
exports.relabel = relabel;
|
||||
exports.compileProjection = compileProjection;
|
||||
exports.projectionToPattern = projectionToPattern;
|
||||
exports.project = project;
|
||||
exports.matcherKeys = matcherKeys;
|
||||
exports.prettyMatcher = prettyMatcher;
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
Routing(module.exports);
|
||||
} else if (window) {
|
||||
Routing(window);
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
var util = require('util');
|
||||
var r = require("./route.js");
|
||||
|
||||
function dump(x) {
|
||||
console.log(util.inspect(x, { depth: null }));
|
||||
return x;
|
||||
}
|
||||
|
||||
function dumpM(m) {
|
||||
console.log(r.prettyMatcher(m));
|
||||
console.log();
|
||||
return m;
|
||||
}
|
||||
|
||||
mAny = r.compilePattern(r.newSet('mAny'), r.__);
|
||||
mAAny = r.compilePattern(r.newSet('mAAny'), ['A', r.__]);
|
||||
dumpM(mAny);
|
||||
dumpM(mAAny);
|
||||
|
||||
dump("mAny:");
|
||||
dump(r.matchValue(mAny, 'hi'));
|
||||
dump(r.matchValue(mAny, ['A', 'hi']));
|
||||
dump(r.matchValue(mAny, ['B', 'hi']));
|
||||
dump(r.matchValue(mAny, ['A', [['hi']]]));
|
||||
|
||||
dump("mAAny:");
|
||||
dump(r.matchValue(mAAny, 'hi'));
|
||||
dump(r.matchValue(mAAny, ['A', 'hi']));
|
||||
dump(r.matchValue(mAAny, ['B', 'hi']));
|
||||
dump(r.matchValue(mAAny, ['A', [['hi']]]));
|
||||
|
||||
console.log("unions");
|
||||
|
||||
dumpM(r.union(r.compilePattern(r.newSet('A'), [r.__, 'A']),
|
||||
r.compilePattern(r.newSet('B'), [r.__, 'B'])));
|
||||
|
||||
dumpM(r.union(r.compilePattern(r.newSet('A'), [r.__, 'A']),
|
||||
r.compilePattern(r.newSet('W'), r.__)));
|
||||
|
||||
console.log("projections");
|
||||
|
||||
dumpM(r.project(r.union(r.compilePattern(r.newSet('A'), r.__),
|
||||
r.compilePattern(r.newSet('B'), ['b'])),
|
||||
r.compileProjection(r._$([[r.__]]))));
|
||||
|
||||
dumpM(r.project(r.union(r.compilePattern(r.newSet('A'), [1, 2]),
|
||||
r.compilePattern(r.newSet('C'), [1, 3]),
|
||||
r.compilePattern(r.newSet('B'), [3, 4])),
|
||||
r.compileProjection([r._$(), r._$()])));
|
||||
|
||||
dump(r.matcherKeys(r.project(r.union(r.compilePattern(r.newSet('A'), [1, 2]),
|
||||
r.compilePattern(r.newSet('C'), [1, 3]),
|
||||
r.compilePattern(r.newSet('B'), [3, 4])),
|
||||
r.compileProjection([r._$(), r._$()]))));
|
Loading…
Reference in New Issue