Fix two major subtraction-related bugs in Route union and intersect.

Moved a bunch of lookup logic into rlookupWild, which let me delete a
lot of special-purpose and flawed code. It is clearly heading toward
being properly-refactored, like the Racket implementation (and the
ESOP2016 paper's presentation) is. The performance problems may
interrupt this gradual evolution before it is complete, though:
hopefully I will be able to move to an explicitly memory-managed
scheme soon.
This commit is contained in:
Tony Garnock-Jones 2016-02-07 14:18:57 -05:00
parent df0ff273b1
commit 49d11b1a73
3 changed files with 44 additions and 80 deletions

View File

@ -172,7 +172,12 @@ function rlookup(r, key) {
}
function rlookupWild(r, key) {
return r.get(key, false);
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) {
@ -252,20 +257,9 @@ function union(o1, o2, unionSuccessesOpt) {
var w = merge(rlookup(r1, __), rlookup(r2, __));
var target;
function examineKey(rA, key, rB) {
function examineKey(key) {
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));
}
target = rupdate(target, key, merge(rlookupWild(r1, key), rlookupWild(r2, key)));
}
}
@ -279,8 +273,8 @@ function union(o1, o2, unionSuccessesOpt) {
});
} else {
target = rwild(w);
r1.forEach(function (val, key) { examineKey(r1, key, r2) });
r2.forEach(function (val, key) { examineKey(r2, key, r1) });
r1.forEach(function (val, key) { examineKey(key) });
r2.forEach(function (val, key) { examineKey(key) });
}
return target;
@ -337,21 +331,7 @@ function intersect(o1, o2, intersectSuccessesOpt, leftShortOpt) {
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));
}
}
target = rupdate(target, key, walk(rlookupWild(r1, key), rlookupWild(r2, key)));
}
}
@ -372,16 +352,6 @@ function intersect(o1, o2, intersectSuccessesOpt, leftShortOpt) {
}
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);
}
}
// The subtractSuccesses function should return null to signal "no
@ -423,27 +393,8 @@ function subtract(o1, o2, subtractSuccessesOpt) {
if (key !== __) {
var k1 = rlookupWild(r1, key);
var k2 = rlookupWild(r2, key);
var updatedK;
if (!k1) {
if (!k2) {
return;
}
// There is an entry in r2 but not r1 for our key.
updatedK =
is_emptyTrie(w1) ? emptyTrie :
is_keyOpen(key) ? walk(rwildseq(w1), k2) :
is_keyClose(key) ? ((w1 instanceof $WildcardSequence) ? walk(w1.trie, k2) : emptyTrie) :
walk(w1, k2);
} else if (!k2) {
// There is an entry in r1 but not r2 for our key.
updatedK =
is_emptyTrie(w2) ? k1 :
is_keyOpen(key) ? walk(k1, rwildseq(w2)) :
is_keyClose(key) ? ((w2 instanceof $WildcardSequence) ? walk(k1, w2.trie) : k1) :
walk(k1, w2);
} else {
updatedK = walk(k1, k2);
}
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
@ -669,12 +620,7 @@ 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;
var result = rlookupWild(m, key);
if (result) return result;
var wildEdge = rlookup(m, __);
if (is_keyOpen(key)) return rwildseq(wildEdge);
if (is_keyClose(key)) return (wildEdge instanceof $WildcardSequence) ? wildEdge.trie : emptyTrie;
return wildEdge;
return rlookupWild(m, key);
}
function relabel(m, f) {

View File

@ -262,18 +262,14 @@ describe('computeEvents', function () {
var pid1 = oldM.addStream(Patch.assert(["fieldContents", "initial", 7])).pid;
var pid2 = oldM.addStream(Patch.sub(["fieldContents", __, __])).pid;
var newM = oldM.shallowCopy();
var updateStreamResult =
newM.updateStream(pid1,
Patch.retract(["fieldContents", __, __])
.andThen(Patch.assert(["fieldContents", "initial", 7])));
var unclampedPatch =
Patch.retract(["fieldContents", __, __])
.andThen(Patch.assert(["fieldContents", "initial", 7]));
var updateStreamResult = newM.updateStream(pid1, unclampedPatch);
var events = Mux.computeEvents(oldM, newM, updateStreamResult);
it('should send an empty patch to the subscriber', function () {
expect(events.eventMap.size).to.be(1);
expect(events.eventMap.has(pid2)).to.be(true);
checkPrettyPatch(events.eventMap.get(pid2).strip(),
['::: nothing'],
['::: nothing']);
it('should send no patch to the subscriber', function () {
expect(events.eventMap.size).to.be(0);
});
});
});

View File

@ -62,7 +62,7 @@ describe("unions", function () {
r.compilePattern(Immutable.Set(['W']), r.__)),
[' ★ >{["W"]}',
' < ★ "A" ★...> >{["W"]}',
' > >{["A","W"]}',
' > >{["W","A"]}',
' ★...> >{["W"]}',
' > >{["W"]}',
' > >{["W"]}']);
@ -121,6 +121,16 @@ describe("unions", function () {
checkPrettyTrie(m, [' < 2 > >{["A","B"]}',
' 3 > >{["C"]}']);
});
it('should work with subtraction and wildcards', function () {
var x = r.compilePattern(Immutable.Set(["A"]), [r.__]);
var y = r.compilePattern(Immutable.Set(["A"]), ["Y"]);
var z = r.compilePattern(Immutable.Set(["A"]), ["Z"]);
var expected = [' < "Y"::: nothing',
' ★ > >{["A"]}'];
checkPrettyTrie(r.subtract(r.union(x, z), y), expected);
checkPrettyTrie(r.union(r.subtract(x, y), z), expected);
});
});
describe("projections", function () {
@ -132,7 +142,7 @@ describe("projections", function () {
r.compilePattern(Immutable.Set(['B']), [['b']])),
proj),
[' < < ★ > > >{["A"]}',
' "b" > > >{["B","A"]}']);
' "b" > > >{["A","B"]}']);
});
it("should exclude things that lack the required structure", function () {
@ -417,3 +427,15 @@ describe('trieStep', function () {
.to.be(true);
});
});
describe('intersect', function () {
it('should compute no-op patch limits properly', function () {
var x = r.compilePattern(Immutable.Set([0]), ["fieldContents", r.__, r.__]);
var y = r.compilePattern(Immutable.Set([0]), ["fieldContents", "initial", 7]);
checkPrettyTrie(r.subtract(x, y), [
' < "fieldContents" ★ ★ > >{[0]}',
' "initial" ★ > >{[0]}',
' 7::: nothing']);
checkPrettyTrie(r.intersect(r.subtract(x, y), y), ['::: nothing']);
});
});