From fbbad85b045c11c4c9c7b27972c9ac23677ed428 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 18 Mar 2016 15:02:44 -0400 Subject: [PATCH] Put matchPattern back: actor.js will need it. --- js/src/route.js | 56 +++++++++++++++++++++++++++++++++++++++++++ js/test/test-route.js | 49 +++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/js/src/route.js b/js/src/route.js index abd69ae..854a93f 100644 --- a/js/src/route.js +++ b/js/src/route.js @@ -16,6 +16,19 @@ function makeStructureConstructor(label, argumentNames) { }; } +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; } @@ -143,6 +156,48 @@ function compilePattern(v, p) { } } +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)) { @@ -1033,6 +1088,7 @@ 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; diff --git a/js/test/test-route.js b/js/test/test-route.js index 429de30..569a6ca 100644 --- a/js/test/test-route.js +++ b/js/test/test-route.js @@ -351,6 +351,55 @@ describe("embedding tries in patterns", function () { }); }); +describe("calls to matchPattern", function () { + it("should yield appropriately-named/-numbered fields", function () { + expect(r.matchPattern([1, 2, 3], [r.__, 2, r._$])).to.eql({'$0': 3, 'length': 1}); + expect(r.matchPattern([1, 2, 3], [r.__, 2, r._$("three")])).to.eql({'three': 3, 'length': 1}); + expect(r.matchPattern([1, 2, 3], [r._$, 2, r._$("three")])) + .to.eql({'$0': 1, 'three': 3, 'length': 2}); + expect(r.matchPattern([1, 2, 3], [r._$("one"), 2, r._$])) + .to.eql({'one': 1, '$1': 3, 'length': 2}); + expect(r.matchPattern([1, 2, 3], [r._$("one"), 2, r._$("three")])) + .to.eql({'one': 1, 'three': 3, 'length': 2}); + }); + + it("should fail on value mismatch", function () { + expect(r.matchPattern([1, 2, 3], [r.__, 999, r._$("three")])).to.be(null); + }); + + it("should fail on array length mismatch", function () { + expect(r.matchPattern([1, 2, 3], [r.__, 2, r._$("three"), 4])).to.be(null); + }); + + it("matches substructure", function () { + expect(r.matchPattern([1, [2, 999], 3], [r._$("one"), r._$(null, [2, r.__]), r._$("three")])) + .to.eql({ one: 1, '$1': [ 2, 999 ], three: 3, length: 3 }); + expect(r.matchPattern([1, [2, 999], 3], [r._$("one"), r._$("two", [2, r.__]), r._$("three")])) + .to.eql({ one: 1, two: [ 2, 999 ], three: 3, length: 3 }); + expect(r.matchPattern([1, [999, 2], 3], [r._$("one"), r._$(null, [2, r.__]), r._$("three")])) + .to.be(null); + expect(r.matchPattern([1, [999, 2], 3], [r._$("one"), r._$("two", [2, r.__]), r._$("three")])) + .to.be(null); + }); + + it("matches nested captures", function () { + expect(r.matchPattern([1, [2, 999], 3], [r._$("one"), r._$(null, [2, r._$]), r._$("three")])) + .to.eql({ one: 1, '$2': 999, '$1': [ 2, 999 ], three: 3, length: 4 }); + expect(r.matchPattern([1, [2, 999], 3], [r._$("one"), r._$("two", [2, r._$]), r._$("three")])) + .to.eql({ one: 1, '$2': 999, two: [ 2, 999 ], three: 3, length: 4 }); + }); + + it("matches structures", function () { + var ctor = r.makeStructureConstructor('foo', ['bar', 'zot']); + expect(r.matchPattern(ctor(123, 234), ctor(r._$("bar"), r._$("zot")))) + .to.eql({ bar: 123, zot: 234, length: 2 }); + expect(r.matchPattern(["foo", 123, 234], ctor(r._$("bar"), r._$("zot")))) + .to.eql({ bar: 123, zot: 234, length: 2 }); + expect(r.matchPattern(ctor(123, 234), ["foo", r._$("bar"), r._$("zot")])) + .to.eql({ bar: 123, zot: 234, length: 2 }); + }); +}); + describe("Projection with no captures", function () { it("should yield the empty sequence when there's a match", function () { var emptySequence = [' >{["A"]}'];