diff --git a/js/src/route.js b/js/src/route.js index 92b9de8..fe33a0e 100644 --- a/js/src/route.js +++ b/js/src/route.js @@ -423,35 +423,38 @@ function subtract(o1, o2, subtractSuccessesOpt) { 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; + 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) { @@ -616,6 +619,20 @@ function appendTrie(m, mTailFn) { } } +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); @@ -1006,6 +1023,7 @@ 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; diff --git a/js/test/test-route.js b/js/test/test-route.js index 1d99e48..50975db 100644 --- a/js/test/test-route.js +++ b/js/test/test-route.js @@ -439,3 +439,53 @@ describe('intersect', function () { checkPrettyTrie(r.intersect(r.subtract(x, y), y), ['::: nothing']); }); }); + +describe('triePruneBranch', function () { + it('should not affect empty trie', function () { + checkPrettyTrie(r.triePruneBranch(r.emptyTrie, Immutable.List([])), ['::: nothing']); + checkPrettyTrie(r.triePruneBranch(r.emptyTrie, Immutable.List([r.SOA])), ['::: nothing']); + checkPrettyTrie(r.triePruneBranch(r.emptyTrie, Immutable.List(["x"])), ['::: nothing']); + checkPrettyTrie(r.triePruneBranch(r.emptyTrie, Immutable.List([r.SOA, "x"])), ['::: nothing']); + }); + + it('should leave a hole in a full trie', function () { + var full = r.compilePattern(true, r.__); + checkPrettyTrie(r.triePruneBranch(full, Immutable.List([])), ['::: nothing']); + checkPrettyTrie(r.triePruneBranch(full, Immutable.List([r.SOA])), + [' ★ >{true}', + ' <::: nothing']); + checkPrettyTrie(r.triePruneBranch(full, Immutable.List(["x"])), + [' ★ >{true}', + ' "x"::: nothing']); + checkPrettyTrie(r.triePruneBranch(full, Immutable.List([r.SOA, "x"])), + [' ★ >{true}', + ' < ★...> >{true}', + ' > >{true}', + ' "x"::: nothing']); + }); + + it('should prune in a finite tree and leave the rest alone', function () { + var A = r.compilePattern(true, ["y"]) + var B = r.union(r.compilePattern(true, ["x"]), A); + var C = r.union(r.compilePattern(true, "z"), B); + checkPrettyTrie(r.triePruneBranch(A, Immutable.List([])), ['::: nothing']); + checkPrettyTrie(r.triePruneBranch(B, Immutable.List([])), ['::: nothing']); + checkPrettyTrie(r.triePruneBranch(C, Immutable.List([])), ['::: nothing']); + checkPrettyTrie(r.triePruneBranch(A, Immutable.List(["z"])), [' < "y" > >{true}']); + checkPrettyTrie(r.triePruneBranch(B, Immutable.List(["z"])), [' < "x" > >{true}', + ' "y" > >{true}']); + checkPrettyTrie(r.triePruneBranch(C, Immutable.List(["z"])), [' < "x" > >{true}', + ' "y" > >{true}']); + checkPrettyTrie(r.triePruneBranch(A, Immutable.List([r.SOA])), ['::: nothing']); + checkPrettyTrie(r.triePruneBranch(B, Immutable.List([r.SOA])), ['::: nothing']); + checkPrettyTrie(r.triePruneBranch(C, Immutable.List([r.SOA])), [' "z" >{true}']); + checkPrettyTrie(r.triePruneBranch(A, Immutable.List([r.SOA, "x"])), [' < "y" > >{true}']); + checkPrettyTrie(r.triePruneBranch(B, Immutable.List([r.SOA, "x"])), [' < "y" > >{true}']); + checkPrettyTrie(r.triePruneBranch(C, Immutable.List([r.SOA, "x"])), [' < "y" > >{true}', + ' "z" >{true}']); + checkPrettyTrie(r.triePruneBranch(A, Immutable.List([r.SOA, "y"])), ['::: nothing']); + checkPrettyTrie(r.triePruneBranch(B, Immutable.List([r.SOA, "y"])), [' < "x" > >{true}']); + checkPrettyTrie(r.triePruneBranch(C, Immutable.List([r.SOA, "y"])), [' < "x" > >{true}', + ' "z" >{true}']); + }); +});