From 062e4603af42574ac722879c628656a89be741b2 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 8 May 2016 11:11:16 -0400 Subject: [PATCH] Convert Syndicate/js to use prefix-style trie --- js/compiler/compiler.js | 20 +- js/examples/button/index.expanded.js | 32 +- js/examples/button/index.html | 1 - js/examples/button/index.js | 6 +- js/examples/dom/index.js | 21 +- js/examples/iot/index.js | 18 +- js/examples/jquery/index.js | 7 +- js/examples/smoketest-dsl/index.js | 4 +- js/examples/smoketest/index.js | 8 +- js/examples/textfield-dsl/index.js | 38 +- js/examples/textfield/index.js | 75 +- js/src/ack.js | 12 +- js/src/actor.js | 3 +- js/src/dataspace.js | 6 +- js/src/demand-matcher.js | 20 +- js/src/dom-driver.js | 21 +- js/src/jquery-driver.js | 21 +- js/src/main.js | 3 +- js/src/mux.js | 14 +- js/src/patch.js | 59 +- js/src/route.js | 1275 +++++++++++--------------- js/test/test-mux.js | 70 +- js/test/test-patch.js | 128 +-- js/test/test-route.js | 302 +++--- js/test/test-syndicate.js | 19 +- 25 files changed, 972 insertions(+), 1211 deletions(-) diff --git a/js/compiler/compiler.js b/js/compiler/compiler.js index 3d507bf..0e76a82 100644 --- a/js/compiler/compiler.js +++ b/js/compiler/compiler.js @@ -122,24 +122,8 @@ var modifiedSourceActions = { var label = maybeLabel.numChildren === 1 ? maybeLabel.children[0].interval.contents : JSON.stringify(typeName.interval.contents); - var fragments = []; - fragments.push( - 'var ' + typeName.asES5 + ' = (function() {', - ' var $SyndicateMeta$ = {', - ' label: ' + label + ',', - ' arguments: ' + JSON.stringify(formals), - ' };', - ' return function ' + typeName.asES5 + '(' + formalsRaw.asES5 + ') {', - ' return {'); - formals.forEach(function(f) { - fragments.push(' ' + JSON.stringify(f) + ': ' + f + ','); - }); - fragments.push( - ' "$SyndicateMeta$": $SyndicateMeta$', - ' };', - ' };', - '})();'); - return fragments.join('\n'); + return 'var ' + typeName.asES5 + ' = Syndicate.Route.makeStructureConstructor(' + + label + ', ' + JSON.stringify(formals) + ');'; }, SendMessageStatement: function(_colons, expr, sc) { diff --git a/js/examples/button/index.expanded.js b/js/examples/button/index.expanded.js index 1d21de6..3dfe4ee 100644 --- a/js/examples/button/index.expanded.js +++ b/js/examples/button/index.expanded.js @@ -1,32 +1,6 @@ "use strict"; -var DOM = (function() { - var $SyndicateMeta$ = { - label: "DOM", - arguments: ["containerSelector","fragmentClass","spec"] - }; - return function DOM(containerSelector, fragmentClass, spec) { - return { - "containerSelector": containerSelector, - "fragmentClass": fragmentClass, - "spec": spec, - "$SyndicateMeta$": $SyndicateMeta$ - }; - }; -})(); -var jQuery = (function() { - var $SyndicateMeta$ = { - label: "jQuery", - arguments: ["selector","eventType","event"] - }; - return function jQuery(selector, eventType, event) { - return { - "selector": selector, - "eventType": eventType, - "event": event, - "$SyndicateMeta$": $SyndicateMeta$ - }; - }; -})(); +var DOM = Syndicate.DOM.DOM; +var jQueryEvent = Syndicate.JQuery.jQueryEvent; $(document).ready(function() { new Syndicate.Ground(function () { @@ -37,7 +11,7 @@ $(document).ready(function() { this.counter = 0; Syndicate.Actor.createFacet() .addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(DOM('#button-label','',Syndicate.seal(this.counter)), 0); })) -.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(jQuery('#counter','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: jQuery('#counter','click',_), metalevel: 0 }; }), (function() { +.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(jQueryEvent('#counter','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: jQueryEvent('#counter','click',_), metalevel: 0 }; }), (function() { this.counter++; })).completeBuild(); }); diff --git a/js/examples/button/index.html b/js/examples/button/index.html index 184d544..1387309 100644 --- a/js/examples/button/index.html +++ b/js/examples/button/index.html @@ -3,7 +3,6 @@ Syndicate: Button Example - diff --git a/js/examples/button/index.js b/js/examples/button/index.js index fc85acf..7b6bfee 100644 --- a/js/examples/button/index.js +++ b/js/examples/button/index.js @@ -1,5 +1,5 @@ -assertion type DOM(containerSelector, fragmentClass, spec); -assertion type jQuery(selector, eventType, event); +var DOM = Syndicate.DOM.DOM; +var jQueryEvent = Syndicate.JQuery.jQueryEvent; $(document).ready(function() { ground dataspace { @@ -10,7 +10,7 @@ $(document).ready(function() { this.counter = 0; forever { assert DOM('#button-label', '', Syndicate.seal(this.counter)); - on message jQuery('#counter', 'click', _) { + on message jQueryEvent('#counter', 'click', _) { this.counter++; } } diff --git a/js/examples/dom/index.js b/js/examples/dom/index.js index 9f9122c..c66babe 100644 --- a/js/examples/dom/index.js +++ b/js/examples/dom/index.js @@ -12,15 +12,18 @@ $(document).ready(function () { console.log('starting ground boot'); Syndicate.DOM.spawnDOMDriver(); + var DOM = Syndicate.DOM.DOM; + var jQueryEvent = Syndicate.JQuery.jQueryEvent; Dataspace.spawn({ boot: function () { - return assert(["DOM", "#clicker-holder", "clicker", - seal(["button", ["span", [["style", "font-style: italic"]], "Click me!"]])]) - .andThen(sub(["jQuery", "button.clicker", "click", __])); + return assert(DOM("#clicker-holder", "clicker", + seal(["button", ["span", [["style", "font-style: italic"]], + "Click me!"]]))) + .andThen(sub(jQueryEvent("button.clicker", "click", __))); }, handleEvent: function (e) { - if (e.type === "message" && e.message[0] === "jQuery") { + if (e.type === "message" && jQueryEvent.isClassOf(e.message)) { Dataspace.send("bump_count"); } } @@ -33,11 +36,11 @@ $(document).ready(function () { return sub("bump_count"); }, updateState: function () { - Dataspace.stateChange(retract(["DOM", __, __, __]) - .andThen(assert(["DOM", "#counter-holder", "counter", - seal(["div", - ["p", "The current count is: ", - this.counter]])]))); + Dataspace.stateChange(retract(DOM.pattern) + .andThen(assert(DOM("#counter-holder", "counter", + seal(["div", + ["p", "The current count is: ", + this.counter]]))))); }, handleEvent: function (e) { if (e.type === "message" && e.message === "bump_count") { diff --git a/js/examples/iot/index.js b/js/examples/iot/index.js index 3d25e34..b6d56de 100644 --- a/js/examples/iot/index.js +++ b/js/examples/iot/index.js @@ -6,8 +6,8 @@ assertion type tvAlert(text); assertion type switchAction(on); assertion type componentPresent(name); -assertion type DOM(containerSelector, fragmentClass, spec); -assertion type jQuery(selector, eventType, event); +var DOM = Syndicate.DOM.DOM; +var jQueryEvent = Syndicate.JQuery.jQueryEvent; /////////////////////////////////////////////////////////////////////////// // TV @@ -48,7 +48,7 @@ function spawnRemoteControl() { actor { forever { assert componentPresent('remote control'); - on message jQuery('#remote-control', 'click', _) { + on message jQueryEvent('#remote-control', 'click', _) { :: remoteClick(); } } @@ -92,14 +92,14 @@ function spawnStoveSwitch() { "img/stove-coil-element-" + (this.powerOn ? "hot" : "cold") + ".jpg"]]])); - on message jQuery('#stove-switch-on', 'click', _) { this.powerOn = true; } - on message jQuery('#stove-switch-off', 'click', _) { this.powerOn = false; } + on message jQueryEvent('#stove-switch-on', 'click', _) { this.powerOn = true; } + on message jQueryEvent('#stove-switch-off', 'click', _) { this.powerOn = false; } on message switchAction($newState) { this.powerOn = newState; } } until { - case message jQuery('#kill-stove-switch', 'click', _); + case message jQueryEvent('#kill-stove-switch', 'click', _); } } } @@ -120,7 +120,7 @@ function spawnPowerDrawMonitor() { this.watts = on ? 1500 : 0; } } until { - case message jQuery('#kill-power-draw-monitor', 'click', _); + case message jQueryEvent('#kill-power-draw-monitor', 'click', _); } } } @@ -187,10 +187,10 @@ function spawnFailureMonitor() { function spawnChaosMonkey() { actor { forever { - on message jQuery('#spawn-power-draw-monitor', 'click', _) { + on message jQueryEvent('#spawn-power-draw-monitor', 'click', _) { spawnPowerDrawMonitor(); } - on message jQuery('#spawn-stove-switch', 'click', _) { + on message jQueryEvent('#spawn-stove-switch', 'click', _) { spawnStoveSwitch(); } } diff --git a/js/examples/jquery/index.js b/js/examples/jquery/index.js index f9436b7..d65ab5a 100644 --- a/js/examples/jquery/index.js +++ b/js/examples/jquery/index.js @@ -14,10 +14,13 @@ $(document).ready(function () { Dataspace.spawn({ boot: function () { - return sub(['jQuery', '#clicker', 'click', __]); + return sub(Syndicate.JQuery.jQueryEvent('#clicker', 'click', __)); }, handleEvent: function (e) { - if (e.type === 'message' && e.message[0] === 'jQuery' && e.message[1] === '#clicker') { + if (e.type === 'message' + && Syndicate.JQuery.jQueryEvent.isClassOf(e.message) + && e.message.selector === '#clicker') + { var r = $('#result'); r.html(Number(r.html()) + 1); } diff --git a/js/examples/smoketest-dsl/index.js b/js/examples/smoketest-dsl/index.js index 221f226..24d279a 100644 --- a/js/examples/smoketest-dsl/index.js +++ b/js/examples/smoketest-dsl/index.js @@ -23,8 +23,8 @@ ground dataspace { actor { forever { - on message $m { - console.log("Got message:", m); + on message beep($counter) { + console.log("beep!", counter); } } } diff --git a/js/examples/smoketest/index.js b/js/examples/smoketest/index.js index fa688d8..e67da12 100644 --- a/js/examples/smoketest/index.js +++ b/js/examples/smoketest/index.js @@ -1,5 +1,7 @@ "use strict"; +var beep = Syndicate.Route.makeStructureConstructor('beep', ['counter']); + var G; $(document).ready(function () { var Dataspace = Syndicate.Dataspace; @@ -15,16 +17,16 @@ $(document).ready(function () { boot: function () {}, handleEvent: function (e) {}, step: function () { - Dataspace.send(["beep", this.counter++]); + Dataspace.send(beep(this.counter++)); return this.counter <= 10; } }); Dataspace.spawn({ - boot: function () { return sub(["beep", __]); }, + boot: function () { return sub(beep.pattern); }, handleEvent: function (e) { if (e.type === 'message') { - console.log("beep!", e.message[1]); + console.log("beep!", e.message.counter); } } }); diff --git a/js/examples/textfield-dsl/index.js b/js/examples/textfield-dsl/index.js index 52c5f07..4effb36 100644 --- a/js/examples/textfield-dsl/index.js +++ b/js/examples/textfield-dsl/index.js @@ -1,7 +1,7 @@ /////////////////////////////////////////////////////////////////////////// // GUI -assertion type jQuery(selector, eventType, event); +var jQueryEvent = Syndicate.JQuery.jQueryEvent; assertion type fieldCommand(detail); assertion type fieldContents(text, pos); assertion type highlight(state); @@ -34,8 +34,8 @@ function spawnGui() { var text = this.text; var pos = this.pos; var highlight = this.highlightState; - var hLeft = highlight ? highlight.get(0) : 0; - var hRight = highlight ? highlight.get(1) : 0; + var hLeft = highlight ? highlight[0] : 0; + var hRight = highlight ? highlight[1] : 0; $("#fieldContents")[0].innerHTML = highlight ? piece(text, pos, 0, hLeft, "normal") + piece(text, pos, hLeft, hRight, "highlight") + @@ -44,7 +44,7 @@ function spawnGui() { }; forever { - on message jQuery("#inputRow", "+keypress", $event) { + on message jQueryEvent("#inputRow", "+keypress", $event) { var keycode = event.keyCode; var character = String.fromCharCode(event.charCode); if (keycode === 37 /* left */) { @@ -79,11 +79,11 @@ function spawnGui() { function spawnModel() { actor { - this.fieldContents = "initial"; - this.cursorPos = this.fieldContents.length; /* positions address gaps between characters */ + this.fieldValue = "initial"; + this.cursorPos = this.fieldValue.length; /* positions address gaps between characters */ forever { - assert fieldContents(this.fieldContents, this.cursorPos); + assert fieldContents(this.fieldValue, this.cursorPos); on message fieldCommand("cursorLeft") { this.cursorPos--; @@ -93,24 +93,24 @@ function spawnModel() { on message fieldCommand("cursorRight") { this.cursorPos++; - if (this.cursorPos > this.fieldContents.length) - this.cursorPos = this.fieldContents.length; + if (this.cursorPos > this.fieldValue.length) + this.cursorPos = this.fieldValue.length; } on message fieldCommand("backspace") { if (this.cursorPos > 0) { - this.fieldContents = - this.fieldContents.substring(0, this.cursorPos - 1) + - this.fieldContents.substring(this.cursorPos); + this.fieldValue = + this.fieldValue.substring(0, this.cursorPos - 1) + + this.fieldValue.substring(this.cursorPos); this.cursorPos--; } } on message fieldCommand(["insert", $newText]) { - this.fieldContents = - this.fieldContents.substring(0, this.cursorPos) + + this.fieldValue = + this.fieldValue.substring(0, this.cursorPos) + newText + - this.fieldContents.substring(this.cursorPos); + this.fieldValue.substring(this.cursorPos); this.cursorPos += newText.length; } } @@ -122,13 +122,13 @@ function spawnModel() { function spawnSearch() { actor { - this.fieldContents = ""; + this.fieldValue = ""; this.highlight = false; this.search = function () { var searchtext = $("#searchBox")[0].value; if (searchtext) { - var pos = this.fieldContents.indexOf(searchtext); + var pos = this.fieldValue.indexOf(searchtext); this.highlight = (pos !== -1) && [pos, pos + searchtext.length]; } else { this.highlight = false; @@ -138,12 +138,12 @@ function spawnSearch() { forever { assert highlight(this.highlight); - on message jQuery("#searchBox", "input", $event) { + on message jQueryEvent("#searchBox", "input", $event) { this.search(); } on asserted fieldContents($text, _) { - this.fieldContents = text; + this.fieldValue = text; this.search(); } } diff --git a/js/examples/textfield/index.js b/js/examples/textfield/index.js index 2fcdfd2..077eba6 100644 --- a/js/examples/textfield/index.js +++ b/js/examples/textfield/index.js @@ -7,6 +7,11 @@ var Patch = Syndicate.Patch; var __ = Syndicate.__; var _$ = Syndicate._$; +var jQueryEvent = Syndicate.JQuery.jQueryEvent; +var fieldContents = Route.makeStructureConstructor('fieldContents', ['text', 'pos']); +var highlight = Route.makeStructureConstructor('highlight', ['state']); +var fieldCommand = Route.makeStructureConstructor('fieldCommand', ['detail']); + function escapeText(text) { text = text.replace(/&/g, '&'); text = text.replace(/ this.fieldContents.length) - this.cursorPos = this.fieldContents.length; + if (this.cursorPos > this.fieldValue.length) + this.cursorPos = this.fieldValue.length; } else if (command === "backspace" && this.cursorPos > 0) { - this.fieldContents = - this.fieldContents.substring(0, this.cursorPos - 1) + - this.fieldContents.substring(this.cursorPos); + this.fieldValue = + this.fieldValue.substring(0, this.cursorPos - 1) + + this.fieldValue.substring(this.cursorPos); this.cursorPos--; } else if (command.constructor === Array && command[0] === "insert") { var newText = command[1]; - this.fieldContents = - this.fieldContents.substring(0, this.cursorPos) + + this.fieldValue = + this.fieldValue.substring(0, this.cursorPos) + newText + - this.fieldContents.substring(this.cursorPos); + this.fieldValue.substring(this.cursorPos); this.cursorPos += newText.length; } this.publishState(); @@ -128,8 +133,8 @@ function spawnModel() { publishState: function () { Dataspace.stateChange( - Patch.retract(["fieldContents", __, __]) - .andThen(Patch.assert(["fieldContents", this.fieldContents, this.cursorPos]))); + Patch.retract(fieldContents.pattern) + .andThen(Patch.assert(fieldContents(this.fieldValue, this.cursorPos)))); } }); } @@ -139,24 +144,24 @@ function spawnModel() { function spawnSearch() { Dataspace.spawn({ - fieldContents: "", + fieldValue: "", highlight: false, boot: function () { this.publishState(); - return Patch.sub(["jQuery", "#searchBox", "input", __]) - .andThen(Patch.sub(["fieldContents", __, __])); + return Patch.sub(jQueryEvent("#searchBox", "input", __)) + .andThen(Patch.sub(fieldContents.pattern)); }, - fieldContentsProjection: Route.compileProjection(["fieldContents", _$("text"), _$("pos")]), + fieldContentsProjection: fieldContents(_$("text"), _$("pos")), handleEvent: function (e) { var self = this; - if (e.type === "message" && e.message[0] === "jQuery") { + if (jQueryEvent.isClassOf(e.message)) { this.search(); } if (e.type === "stateChange") { Route.projectObjects(e.patch.added, this.fieldContentsProjection).forEach(function (c) { - self.fieldContents = c.text; + self.fieldValue = c.text; }); this.search(); } @@ -164,15 +169,15 @@ function spawnSearch() { publishState: function () { Dataspace.stateChange( - Patch.retract(["highlight", __]) - .andThen(Patch.assert(["highlight", this.highlight]))); + Patch.retract(highlight.pattern) + .andThen(Patch.assert(highlight(this.highlight)))); }, search: function () { var searchtext = $("#searchBox")[0].value; var oldHighlight = this.highlight; if (searchtext) { - var pos = this.fieldContents.indexOf(searchtext); + var pos = this.fieldValue.indexOf(searchtext); this.highlight = (pos !== -1) && [pos, pos + searchtext.length]; } else { this.highlight = false; diff --git a/js/src/ack.js b/js/src/ack.js index dd83ac9..01f0df0 100644 --- a/js/src/ack.js +++ b/js/src/ack.js @@ -5,7 +5,7 @@ var Dataspace = require('./dataspace.js').Dataspace; var Route = require('./route.js'); var Patch = require('./patch.js'); -var $Ack = new Route.$Special('ack'); +var ack = Route.makeStructureConstructor('ack', ['id']); function Ack(metaLevel, id) { this.metaLevel = metaLevel || 0; @@ -14,19 +14,19 @@ function Ack(metaLevel, id) { } Ack.prototype.arm = function () { - Dataspace.stateChange(Patch.sub([$Ack, this.id], this.metaLevel)); - Dataspace.send([$Ack, this.id], this.metaLevel); + Dataspace.stateChange(Patch.sub(ack(this.id), this.metaLevel)); + Dataspace.send(ack(this.id), this.metaLevel); }; Ack.prototype.disarm = function () { - Dataspace.stateChange(Patch.unsub([$Ack, this.id], this.metaLevel)); + Dataspace.stateChange(Patch.unsub(ack(this.id), this.metaLevel)); }; Ack.prototype.check = function (e) { if (!this.done) { if (e.type === 'message') { var m = Patch.stripAtMeta(e.message, this.metaLevel); - if (m && m[0] === $Ack && m[1] === this.id) { + if (ack.isClassOf(m) && m.id === this.id) { this.disarm(); this.done = true; } @@ -37,5 +37,5 @@ Ack.prototype.check = function (e) { /////////////////////////////////////////////////////////////////////////// -module.exports.$Ack = $Ack; +module.exports.ack = ack; module.exports.Ack = Ack; diff --git a/js/src/actor.js b/js/src/actor.js index 92239a2..e614108 100644 --- a/js/src/actor.js +++ b/js/src/actor.js @@ -94,11 +94,10 @@ Facet.prototype.onEvent = function(isTerminal, eventType, subscriptionFn, projec if (e.type === 'stateChange') { var proj = projectionFn.call(facet.actor.state); var spec = Patch.prependAtMeta(proj.assertion, proj.metalevel); - var compiledSpec = Route.compileProjection(spec); var objects = Route.projectObjects(eventType === 'asserted' ? e.patch.added : e.patch.removed, - compiledSpec); + spec); if (objects && objects.size > 0) { // console.log(objects.toArray()); if (isTerminal) { facet.terminate(); } diff --git a/js/src/dataspace.js b/js/src/dataspace.js index 4466bdd..f9121d1 100644 --- a/js/src/dataspace.js +++ b/js/src/dataspace.js @@ -229,11 +229,11 @@ Dataspace.prototype.interpretAction = function (pid, action) { return true; case 'message': - if (Patch.isObserve(action.message)) { + if (Patch.observe.isClassOf(action.message)) { console.warn('Process ' + pid + ' send message containing query', action.message); } - if (pid !== 'meta' && Patch.isAtMeta(action.message)) { - Dataspace.send(action.message[1]); + if (pid !== 'meta' && Patch.atMeta.isClassOf(action.message)) { + Dataspace.send(action.message.assertion); } else { this.mux.routeMessage(action.message).forEach(function (pid) { self.deliverEvent(pid, action); diff --git a/js/src/demand-matcher.js b/js/src/demand-matcher.js index d5bba33..dedb0a5 100644 --- a/js/src/demand-matcher.js +++ b/js/src/demand-matcher.js @@ -20,8 +20,10 @@ function DemandMatcher(demandSpec, supplySpec, options) { this.supplySpec = supplySpec; this.demandPattern = Route.projectionToPattern(demandSpec); this.supplyPattern = Route.projectionToPattern(supplySpec); - this.demandProjection = Route.compileProjection(Patch.prependAtMeta(demandSpec, this.metaLevel)); - this.supplyProjection = Route.compileProjection(Patch.prependAtMeta(supplySpec, this.metaLevel)); + this.demandProjection = Patch.prependAtMeta(demandSpec, this.metaLevel); + this.supplyProjection = Patch.prependAtMeta(supplySpec, this.metaLevel); + this.demandProjectionNames = Route.projectionNames(this.demandProjection); + this.supplyProjectionNames = Route.projectionNames(this.supplyProjection); this.currentDemand = Immutable.Set(); this.currentSupply = Immutable.Set(); } @@ -40,10 +42,12 @@ DemandMatcher.prototype.handleEvent = function (e) { DemandMatcher.prototype.handlePatch = function (p) { var self = this; - var addedDemand = Route.trieKeys(Route.project(p.added, self.demandProjection)); - var removedDemand = Route.trieKeys(Route.project(p.removed, self.demandProjection)); - var addedSupply = Route.trieKeys(Route.project(p.added, self.supplyProjection)); - var removedSupply = Route.trieKeys(Route.project(p.removed, self.supplyProjection)); + var dN = self.demandProjectionNames.length; + var sN = self.supplyProjectionNames.length; + var addedDemand = Route.trieKeys(Route.project(p.added, self.demandProjection), dN); + var removedDemand = Route.trieKeys(Route.project(p.removed, self.demandProjection), dN); + var addedSupply = Route.trieKeys(Route.project(p.added, self.supplyProjection), sN); + var removedSupply = Route.trieKeys(Route.project(p.removed, self.supplyProjection), sN); if (addedDemand === null) { throw new Error("Syndicate: wildcard demand detected:\n" + @@ -61,12 +65,12 @@ DemandMatcher.prototype.handlePatch = function (p) { removedSupply.forEach(function (captures) { if (self.currentDemand.has(captures)) { - self.onSupplyDecrease(Route.captureToObject(captures, self.supplyProjection)); + self.onSupplyDecrease(Route.captureToObject(captures, self.supplyProjectionNames)); } }); addedDemand.forEach(function (captures) { if (!self.currentSupply.has(captures)) { - self.onDemandIncrease(Route.captureToObject(captures, self.demandProjection)); + self.onDemandIncrease(Route.captureToObject(captures, self.demandProjectionNames)); } }); diff --git a/js/src/dom-driver.js b/js/src/dom-driver.js index 6b21767..3ba661c 100644 --- a/js/src/dom-driver.js +++ b/js/src/dom-driver.js @@ -1,6 +1,7 @@ // DOM fragment display driver var Patch = require("./patch.js"); var DemandMatcher = require('./demand-matcher.js').DemandMatcher; +var Route = require('./route.js'); var Ack = require('./ack.js').Ack; var Seal = require('./seal.js').Seal; @@ -9,27 +10,25 @@ var Dataspace = Dataspace_.Dataspace; var __ = Dataspace_.__; var _$ = Dataspace_._$; +var DOM = Route.makeStructureConstructor('DOM', ['selector', 'fragmentClass', 'fragmentSpec']); + function spawnDOMDriver(domWrapFunction, jQueryWrapFunction) { - domWrapFunction = domWrapFunction || defaultWrapFunction; + domWrapFunction = domWrapFunction || DOM; var spec = domWrapFunction(_$('selector'), _$('fragmentClass'), _$('fragmentSpec')); Dataspace.spawn( new DemandMatcher(spec, - Patch.advertise(spec), + Patch.advertise(spec), // TODO: are the embedded captures problematic here? If not, why not? { onDemandIncrease: function (c) { Dataspace.spawn(new DOMFragment(c.selector, - c.fragmentClass, - c.fragmentSpec, - domWrapFunction, - jQueryWrapFunction)); + c.fragmentClass, + c.fragmentSpec, + domWrapFunction, + jQueryWrapFunction)); } })); } -function defaultWrapFunction(selector, fragmentClass, fragmentSpec) { - return ["DOM", selector, fragmentClass, fragmentSpec]; -} - function DOMFragment(selector, fragmentClass, fragmentSpec, domWrapFunction, jQueryWrapFunction) { this.selector = selector; this.fragmentClass = fragmentClass; @@ -138,4 +137,4 @@ DOMFragment.prototype.buildNodes = function () { /////////////////////////////////////////////////////////////////////////// module.exports.spawnDOMDriver = spawnDOMDriver; -module.exports.defaultWrapFunction = defaultWrapFunction; +module.exports.DOM = DOM; diff --git a/js/src/jquery-driver.js b/js/src/jquery-driver.js index aca370f..81f8ca6 100644 --- a/js/src/jquery-driver.js +++ b/js/src/jquery-driver.js @@ -1,15 +1,18 @@ // JQuery event driver var Patch = require("./patch.js"); var DemandMatcher = require('./demand-matcher.js').DemandMatcher; +var Route = require('./route.js'); var Dataspace_ = require("./dataspace.js"); var Dataspace = Dataspace_.Dataspace; var __ = Dataspace_.__; var _$ = Dataspace_._$; +var jQueryEvent = Route.makeStructureConstructor('jQueryEvent', ['selector', 'eventName', 'eventValue']); + function spawnJQueryDriver(baseSelector, metaLevel, wrapFunction) { metaLevel = metaLevel || 0; - wrapFunction = wrapFunction || defaultWrapFunction; + wrapFunction = wrapFunction || jQueryEvent; Dataspace.spawn( new DemandMatcher(Patch.observe(wrapFunction(_$('selector'), _$('eventName'), __)), Patch.advertise(wrapFunction(_$('selector'), _$('eventName'), __)), @@ -17,25 +20,21 @@ function spawnJQueryDriver(baseSelector, metaLevel, wrapFunction) { metaLevel: metaLevel, onDemandIncrease: function (c) { Dataspace.spawn(new JQueryEventRouter(baseSelector, - c.selector, - c.eventName, - metaLevel, - wrapFunction)); + c.selector, + c.eventName, + metaLevel, + wrapFunction)); } })); } -function defaultWrapFunction(selector, eventName, eventValue) { - return ["jQuery", selector, eventName, eventValue]; -} - function JQueryEventRouter(baseSelector, selector, eventName, metaLevel, wrapFunction) { var self = this; this.baseSelector = baseSelector || null; this.selector = selector; this.eventName = eventName; this.metaLevel = metaLevel || 0; - this.wrapFunction = wrapFunction || defaultWrapFunction; + this.wrapFunction = wrapFunction || jQueryEvent; this.preventDefault = (this.eventName.charAt(0) !== "+"); this.handler = Dataspace.wrap(function (e) { @@ -88,4 +87,4 @@ function simplifyDOMEvent(e) { module.exports.spawnJQueryDriver = spawnJQueryDriver; module.exports.simplifyDOMEvent = simplifyDOMEvent; -module.exports.defaultWrapFunction = defaultWrapFunction; +module.exports.jQueryEvent = jQueryEvent; diff --git a/js/src/main.js b/js/src/main.js index 09edd73..71674b1 100644 --- a/js/src/main.js +++ b/js/src/main.js @@ -12,7 +12,7 @@ module.exports.Route = require("./route.js"); copyKeys(['__', '_$', '$Capture', '$Special', 'is_emptyTrie', 'emptyTrie', 'embeddedTrie', 'compilePattern', - 'compileProjection', 'project', 'projectObjects', + 'project', 'projectObjects', 'prettyTrie'], module.exports, module.exports.Route); @@ -35,7 +35,6 @@ module.exports.Reflect = require("./reflect.js"); module.exports.Patch = require("./patch.js"); copyKeys(['emptyPatch', 'observe', 'atMeta', 'advertise', - 'isObserve', 'isAtMeta', 'isAdvertise', 'assert', 'retract', 'sub', 'unsub', 'pub', 'unpub', 'patchSeq'], module.exports, diff --git a/js/src/mux.js b/js/src/mux.js index 70c274c..9089706 100644 --- a/js/src/mux.js +++ b/js/src/mux.js @@ -45,12 +45,12 @@ Mux.prototype.updateStream = function (pid, unclampedPatch) { }; var atMetaEverything = Route.compilePattern(true, Patch.atMeta(Route.__)); -var atMetaBranchKeys = Immutable.List([Route.SOA, Patch.$AtMeta]); -var onlyMeta = Immutable.Set.of("meta"); +var atMetaBranchKeys = Immutable.List([[Patch.atMeta.meta.arguments.length, Patch.atMeta.meta]]); +var onlyMeta = Route.trieSuccess(Immutable.Set.of("meta")); function echoCancelledTrie(t) { return Route.subtract(t, atMetaEverything, function (v1, v2) { - return v1.has("meta") ? onlyMeta : null; + return v1.has("meta") ? onlyMeta : Route.emptyTrie; }); } @@ -95,12 +95,10 @@ function computeEvents(oldMux, newMux, updateStreamResult) { function computeAffectedPids(routingTable, delta) { var cover = Route._union(delta.added, delta.removed); - routingTable = Route.trieStep(routingTable, Route.SOA); - routingTable = Route.trieStep(routingTable, Patch.$Observe); + routingTable = + Route.trieStep(routingTable, Patch.observe.meta.arguments.length, Patch.observe.meta); return Route.matchTrie(cover, routingTable, Immutable.Set(), - function (v, r, acc) { - return acc.union(Route.trieStep(r, Route.EOA).value); - }); + function (v1, v2, acc) { return acc.union(v2); }); } Mux.prototype.routeMessage = function (body) { diff --git a/js/src/patch.js b/js/src/patch.js index 93ed59f..52680ca 100644 --- a/js/src/patch.js +++ b/js/src/patch.js @@ -13,18 +13,11 @@ function Patch(added, removed) { var emptyPatch = new Patch(Route.emptyTrie, Route.emptyTrie); var removeEverythingPatch = new Patch(Route.emptyTrie, Route.compilePattern(true, __)); +var trueLabel = Route.trieSuccess(true); -var $Observe = new Route.$Special("$Observe"); -var $AtMeta = new Route.$Special("$AtMeta"); -var $Advertise = new Route.$Special("$Advertise"); - -function observe(p) { return [$Observe, p]; } -function atMeta(p) { return [$AtMeta, p]; } -function advertise(p) { return [$Advertise, p]; } - -function isObserve(p) { return p[0] === $Observe; } -function isAtMeta(p) { return p[0] === $AtMeta; } -function isAdvertise(p) { return p[0] === $Advertise; } +var observe = Route.makeStructureConstructor('observe', ['assertion']); +var atMeta = Route.makeStructureConstructor('atMeta', ['assertion']); +var advertise = Route.makeStructureConstructor('advertise', ['assertion']); function prependAtMeta(p, level) { while (level--) { @@ -35,8 +28,8 @@ function prependAtMeta(p, level) { function stripAtMeta(p, level) { while (level--) { - if (p.length === 2 && p[0] === $AtMeta) { - p = p[1]; + if (atMeta.isClassOf(p)) { + p = p.assertion; } else { return null; } @@ -115,7 +108,7 @@ Patch.prototype.lift = function () { Route.compilePattern(true, atMeta(Route.embeddedTrie(this.removed)))); }; -var atMetaProj = Route.compileProjection(atMeta(_$)); +var atMetaProj = atMeta(_$); Patch.prototype.drop = function () { return new Patch(Route.project(this.added, atMetaProj), Route.project(this.removed, atMetaProj)); @@ -132,8 +125,9 @@ Patch.prototype.label = function (labelValue) { }; Patch.prototype.limit = function (bound) { - return new Patch(Route.subtract(this.added, bound, function (v1, v2) { return null; }), - Route.intersect(this.removed, bound, function (v1, v2) { return v1; })); + return new Patch(Route.subtract(this.added, bound, function (v1, v2) { return Route.emptyTrie; }), + Route.intersect(this.removed, bound, + function (v1, v2) { return Route.trieSuccess(v1); })); }; var metaLabelSet = Immutable.Set(["meta"]); @@ -143,20 +137,20 @@ Patch.prototype.computeAggregate = function (label, base, removeMeta /* optional function addCombiner(v1, v2) { if (removeMeta && Immutable.is(v2, metaLabelSet)) { - return v1; + return Route.trieSuccess(v1); } else { - return null; + return Route.emptyTrie; } } function removeCombiner(v1, v2) { if (v2.size === 1) { - return v1; + return Route.trieSuccess(v1); } else { if (removeMeta && v2.size === 2 && v2.has("meta")) { - return v1; + return Route.trieSuccess(v1); } else { - return null; + return Route.emptyTrie; } } } @@ -167,9 +161,11 @@ Patch.prototype.applyTo = function (base) { }; Patch.prototype.updateInterests = function (base) { - return Route._union(Route.subtract(base, this.removed, function (v1, v2) { return null; }), + return Route._union(Route.subtract(base, + this.removed, + function (v1, v2) { return Route.emptyTrie; }), this.added, - function (v1, v2) { return true; }); + function (v1, v2) { return trueLabel; }); }; Patch.prototype.unapplyTo = function (base) { @@ -180,9 +176,9 @@ Patch.prototype.andThen = function (nextPatch) { return new Patch(nextPatch.updateInterests(this.added), Route._union(Route.subtract(this.removed, nextPatch.added, - function (v1, v2) { return null; }), + function (v1, v2) { return Route.emptyTrie; }), nextPatch.removed, - function (v1, v2) { return true; })); + function (v1, v2) { return trueLabel; })); }; function patchSeq(/* patch, patch, ... */) { @@ -199,11 +195,8 @@ function computePatch(oldBase, newBase) { } function biasedIntersection(object, subject) { - subject = Route.trieStep(subject, Route.SOA); - subject = Route.trieStep(subject, $Observe); - return Route.intersect(object, subject, - function (v1, v2) { return true; }, - function (v, r) { return Route.trieStep(r, Route.EOA); }); + subject = Route.trieStep(subject, observe.meta.arguments.length, observe.meta); + return Route.intersect(object, subject, function (v1, v2) { return Route.trieSuccess(v1); }); } Patch.prototype.viewFrom = function (interests) { @@ -240,15 +233,9 @@ module.exports.Patch = Patch; module.exports.emptyPatch = emptyPatch; module.exports.removeEverythingPatch = removeEverythingPatch; -module.exports.$Observe = $Observe; -module.exports.$AtMeta = $AtMeta; -module.exports.$Advertise = $Advertise; module.exports.observe = observe; module.exports.atMeta = atMeta; module.exports.advertise = advertise; -module.exports.isObserve = isObserve; -module.exports.isAtMeta = isAtMeta; -module.exports.isAdvertise = isAdvertise; module.exports.prependAtMeta = prependAtMeta; module.exports.stripAtMeta = stripAtMeta; diff --git a/js/src/route.js b/js/src/route.js index efdb218..c9e109d 100644 --- a/js/src/route.js +++ b/js/src/route.js @@ -2,26 +2,45 @@ var Immutable = require("immutable"); +/////////////////////////////////////////////////////////////////////////// +// "Structures": Simple named-tuple-like records. +// TODO: shore up $SyndicateMeta$, making it a proper object + +function instantiateStructure($SyndicateMeta$, argvals) { + var result = {"$SyndicateMeta$": $SyndicateMeta$}; + var argnames = $SyndicateMeta$.arguments; + for (var i = 0; i < argnames.length; i++) { + result[argnames[i]] = argvals[i]; + } + return result; +} + 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; + var ctor = function() { + return instantiateStructure($SyndicateMeta$, arguments); }; + ctor.meta = $SyndicateMeta$; + ctor.isClassOf = function (v) { return v && v.$SyndicateMeta$ === $SyndicateMeta$; }; + ctor.pattern = ctor.apply(null, Immutable.Repeat(__, argumentNames.length).toArray()); + return ctor; +} + +function isSyndicateMeta(m) { + // TODO: include more structure in $SyndicateMeta$ objects to make + // this judgement less sloppy. + return m && m.label && Array.isArray(m.arguments); } function isStructure(s) { return (s !== null) && (typeof s === 'object') && ("$SyndicateMeta$" in s); } -function structureToArray(s) { - var result = [s.$SyndicateMeta$.label]; +function structureToArray(s, excludeLabel) { + var result = excludeLabel ? [] : [s.$SyndicateMeta$.label]; var args = s.$SyndicateMeta$.arguments; for (var i = 0; i < args.length; i++) { result.push(s[args[i]]); @@ -29,25 +48,72 @@ function structureToArray(s) { return result; } +/////////////////////////////////////////////////////////////////////////// +// $Special: Builder of singletons. + 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 +/////////////////////////////////////////////////////////////////////////// +// Misc. utilities function die(message) { throw new Error(message); } -function $Embedded(trie) { +/////////////////////////////////////////////////////////////////////////// +// Trie representations +// +// A Trie is one of +// - emptyTrie +// - an instance of $Success +// - an instance of $Branch + +var emptyTrie = new $Special("Mt"); + +function is_emptyTrie(m) { + return m === emptyTrie; +} + +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 $Branch(wild, edges, count) { + this.wild = wild || emptyTrie; // Trie + this.edges = edges || Immutable.Map(); // Map from arity to Map from key-like-thing to Trie + // (Sigmas are 0-ary.) + this.count = count || 0; +} + +$Branch.prototype.equals = function (other) { + if (!(other instanceof $Branch)) return false; + return (this.count === other.count) && Immutable.is(this.wild, other.wild) && Immutable.is(this.edges, other.edges); +}; + +/////////////////////////////////////////////////////////////////////////// +// Patterns, projections and captures + +var __ = new $Special("wildcard"); /* wildcard marker */ +var SOA = new $Special("array"); /* key for start-of-array */ + +function $Embedded(trie, arrayLength) { this.trie = trie; + this.arrayLength = arrayLength; } function embeddedTrie(trie) { - return new $Embedded(trie); + return new $Embedded(trie, null); +} + +function embeddedTrieArray(trie, arrayLength) { + return new $Embedded(trie, arrayLength); } // The name argument should be a string or null; it defaults to null. @@ -66,92 +132,129 @@ 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); + return new $Success(v); } -function rseq(e, r) { - if (r === emptyTrie) return emptyTrie; - return emptyTrie.set(e, r); +function rseq(arity, key, r) { + if (is_emptyTrie(r)) return emptyTrie; + return new $Branch(emptyTrie, Immutable.Map.of(arity, Immutable.Map.of(key, r)), 1); } function rwild(r) { - return rseq(__, r); + if (is_emptyTrie(r)) return emptyTrie; + return new $Branch(r, Immutable.Map(), 0); } -function rwildseq(r) { - return (r === emptyTrie) ? emptyTrie : new $WildcardSequence(r); +function rcopybranch(r) { + return new $Branch(r.wild, r.edges, r.count); +} + +function prepend_wilds(n, r) { + while (n-- > 0) { r = rwild(r); } + return r; +} + +// true iff r1 could have been the output of prepend_wilds(n, r2). +function equal_upto_wilds(n, r1, r2) { + while (true) { + if (n === 0 || is_emptyTrie(r1)) { + return Immutable.is(r1, r2); + } + if (!(r1 instanceof $Branch) || r1.count > 0) { + return false; + } + n = n - 1; + r1 = r1.wild; + } +} + +function rupdate_inplace(r, arity, key, k) { + if (equal_upto_wilds(arity, k, r.wild)) { + var m = r.edges.get(arity); + if (!m) return; + if (m.has(key)) r.count--; + m = m.remove(key); + r.edges = m.isEmpty() ? r.edges.remove(arity) : r.edges.set(arity, m); + } else { + var m = r.edges.get(arity) || Immutable.Map(); + if (!m.has(key)) r.count++; + r.edges = r.edges.set(arity, m.set(key, k)); + } +} + +function rlookup(r, arity, key) { + var m = r.edges.get(arity); + m = m && m.get(key); + return m || prepend_wilds(arity, r.wild); +} + +/////////////////////////////////////////////////////////////////////////// + +function collapse(r) { + if ((r instanceof $Branch) && is_emptyTrie(r.wild) && (r.count === 0)) { + return emptyTrie; + } else { + return r; + } +} + +// use with extreme care +function newEmptyBranch() { + return new $Branch(emptyTrie, Immutable.Map(), 0); +} + +var canonicalExpandedEmpty = newEmptyBranch(); +function expand(r) { + if (is_emptyTrie(r)) { + return canonicalExpandedEmpty; + } else { + return r; + } } /////////////////////////////////////////////////////////////////////////// function compilePattern(v, p) { if (!p) die("compilePattern: missing pattern"); - return walk(p, rseq(EOA, rsuccess(v))); + return walk(p, 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.length, SOA, acc); } if (Immutable.List.isList(p)) { - acc = rseq(EOA, acc); p.reverse().forEach(function (element) { acc = walk(element, acc); }); - return rseq(SOA, acc); + return rseq(p.size, SOA, acc); } if (isStructure(p)) { 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); + return rseq(args.length, p.$SyndicateMeta$, acc); } if (p instanceof $Embedded) { - return appendTrie(p.trie, function (v) { return acc; }); + acc = appendTrie(p.trie, function (v) { return acc; }); + if (p.arrayLength !== null) { + acc = rseq(p.arrayLength, SOA, acc); + } + return acc; } else { - return rseq(p, acc); + return rseq(0, p, acc); } } } @@ -173,8 +276,13 @@ function matchPattern(v, p) { if (p === __) return; - if (isStructure(p)) { p = structureToArray(p); } - if (isStructure(v)) { v = structureToArray(v); } + if (isStructure(p) && isStructure(v) && (p.$SyndicateMeta$ === v.$SyndicateMeta$)) { + var args = p.$SyndicateMeta$.arguments; + for (var i = 0; i < args.length; i++) { + walk(v[args[i]], p[args[i]]); + } + return; + } if (Array.isArray(p) && Array.isArray(v) && p.length === v.length) { for (var i = 0; i < p.length; i++) { @@ -198,126 +306,95 @@ function matchPattern(v, p) { } } -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 combine(combineSuccess, leftEmpty, rightEmpty, leftBase, rightBase, r1, r2) { + return walk(r1, r2); + + function walk(r1, r2) { + if (is_emptyTrie(r1)) return collapse(leftEmpty(r2)); + if (is_emptyTrie(r2)) return collapse(rightEmpty(r1)); + + if ((r1 instanceof $Success) || (r2 instanceof $Success)) { + return collapse(combineSuccess(r1, r2)); + } + + if (!(r1 instanceof $Branch) || !(r2 instanceof $Branch)) { + die("Invalid trie given to combine"); + } + + /* fold-over-keys */ + var w = walk(r1.wild, r2.wild); + + var acc; + if (!is_emptyTrie(r1.wild) && !is_emptyTrie(r2.wild)) { + acc = rcopybranch(expand(rwild(w))); + var seen = Immutable.Map(); + r1.edges.forEach(function (keymap, arity) { + keymap.forEach(function (r1v, key) { + var r2v = rlookup(r2, arity, key); + rupdate_inplace(acc, arity, key, walk(r1v, r2v)); + seen = seen.set(arity, (seen.get(arity) || Immutable.Set()).add(key)); + }); + }); + r2.edges.forEach(function (keymap, arity) { + keymap.forEach(function (r2v, key) { + var r1v = rlookup(r1, arity, key); + var s = seen.get(arity); + s = s && s.has(key); + if (!s) rupdate_inplace(acc, arity, key, walk(r1v, r2v)); + }); + }); + } else if (!is_emptyTrie(r1.wild) || (is_emptyTrie(r2.wild) && (r1.count > r2.count))) { + acc = rcopybranch(expand(leftBase(r1))); + acc.wild = w; + r2.edges.forEach(function (keymap, arity) { + keymap.forEach(function (r2v, key) { + var r1v = rlookup(r1, arity, key); + rupdate_inplace(acc, arity, key, walk(r1v, r2v)); + }); + }); + } else { + acc = rcopybranch(expand(rightBase(r2))); + acc.wild = w; + r1.edges.forEach(function (keymap, arity) { + keymap.forEach(function (r1v, key) { + var r2v = rlookup(r2, arity, key); + rupdate_inplace(acc, arity, key, walk(r1v, r2v)); + }); + }); + } + + return collapse(acc); } } -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)); +function asymmetricTrieError(r1, r2) { + die("Asymmetric tries: " + r1 + ", " + r2); } /////////////////////////////////////////////////////////////////////////// var unionSuccessesDefault = function (v1, v2) { - if (v1 === true) return v2; - if (v2 === true) return v1; - return v1.union(v2); + return rsuccess(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); + return combine(unionCombiner, + function (x) { return x; }, + function (x) { return x; }, + function (x) { return x; }, + function (x) { return x; }, + 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 unionCombiner(r1, r2) { + if ((r1 instanceof $Success) && (r2 instanceof $Success)) + return unionSuccesses(r1.value, r2.value); + if (is_emptyTrie(r1)) return r2; + if (is_emptyTrie(r2)) return r1; + asymmetricTrieError(r1, r2); } } @@ -329,200 +406,71 @@ function unionN() { return acc; } -function intersect(o1, o2, intersectSuccessesOpt, leftShortOpt) { +/////////////////////////////////////////////////////////////////////////// + +var intersectSuccessesDefault = unionSuccessesDefault; + +function intersect(o1, o2, intersectSuccessesOpt) { var intersectSuccesses = intersectSuccessesOpt || intersectSuccessesDefault; - var leftShort = leftShortOpt || function (v, r) { - die("Route.intersect: left side short!"); - }; - return walk(o1, o2); + return combine(intersectCombiner, + function (x) { return emptyTrie; }, + function (x) { return emptyTrie; }, + function (x) { return emptyTrie; }, + function (x) { return emptyTrie; }, + 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. + function intersectCombiner(r1, r2) { + if ((r1 instanceof $Success) && (r2 instanceof $Success)) + return intersectSuccesses(r1.value, r2.value); 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; + asymmetricTrieError(r1, r2); } } -// The subtractSuccesses function should return null to signal "no -// remaining success values". +/////////////////////////////////////////////////////////////////////////// + +var subtractSuccessesDefault = function (v1, v2) { + var r = v1.subtract(v2); + if (r.isEmpty()) return emptyTrie; + return rsuccess(r); +}; + function subtract(o1, o2, subtractSuccessesOpt) { var subtractSuccesses = subtractSuccessesOpt || subtractSuccessesDefault; - return walk(o1, o2); + return combine(subtractCombiner, + function (x) { return emptyTrie; }, + function (x) { return x; }, + function (x) { return x; }, + function (x) { return emptyTrie; }, + 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 subtractCombiner(r1, r2) { + if ((r1 instanceof $Success) && (r2 instanceof $Success)) + return subtractSuccesses(r1.value, r2.value); + if (is_emptyTrie(r1)) return emptyTrie; + if (is_emptyTrie(r2)) return r1; + asymmetricTrieError(r1, r2); } } -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 +// Returns failureResult on failed match, otherwise the appropriate success // value contained in the trie r. -function matchValue(r, v) { - var failureResult = null; +function matchValue(r, v, failureResultOpt) { + var failureResult = failureResultOpt || 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; + return vs.isEmpty() ? r.value : failureResult; } + if (vs.isEmpty()) return failureResult; var v = vs.first(); vs = vs.shift(); @@ -531,124 +479,59 @@ function matchValue(r, v) { } if (Array.isArray(v)) { - if (r.has(SOA)) { - r = rlookup(r, SOA); - stack.push(vs); - vs = Immutable.List(v); - } else { - r = rlookup(r, __); - } + r = rlookup(r, v.length, SOA); + vs = Immutable.List(v).concat(vs); } else if (isStructure(v)) { - 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, __); - } + r = rlookup(r, v.$SyndicateMeta$.arguments.length, v.$SyndicateMeta$); + vs = Immutable.List(structureToArray(v, true)).concat(vs); } else { - if (r.has(v)) { - r = rlookup(r, v); - } else { - r = rlookup(r, __); - } + r = rlookup(r, 0, v); } } return failureResult; } -function matchTrie(o1, o2, seed, leftShortOpt) { +function matchTrie(o1, o2, seed, combiner) { 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); - } + if ((r1 instanceof $Success) && (r2 instanceof $Success)) { + acc = combiner(r1.value, r2.value, 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); - } - } - } + if (!(r1 instanceof $Branch) || !(r2 instanceof $Branch)) { + asymmetricTrieError(r1, r2); } - // Optimize similarly to intersect(). - if (is_emptyTrie(w1)) { - if (is_emptyTrie(w2)) { - (r1.size < r2.size ? r1 : r2).forEach(function (val, key) { examineKey(key) }); + walk(r1.wild, r2.wild); + + function examineKeys(keymap, arity) { + keymap.forEach(function (_val, key) { + walk(rlookup(r1, arity, key), rlookup(r2, arity, key)); + }); + } + + if (is_emptyTrie(r1.wild)) { + if (is_emptyTrie(r2.wild)) { + (r1.count < r2.count ? r1 : r2).edges.forEach(examineKeys); } else { - r1.forEach(function (val, key) { examineKey(key) }); + r1.edges.forEach(examineKeys); } } else { - if (is_emptyTrie(w2)) { - r2.forEach(function (val, key) { examineKey(key) }); + if (is_emptyTrie(r2.wild)) { + r2.edges.forEach(examineKeys); } else { - r1.forEach(function (val, key) { examineKey(key) }); - r2.forEach(function (val, key) { examineKey(key) }); + r1.edges.forEach(examineKeys); + r2.edges.forEach(examineKeys); } } } - - 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) { @@ -656,104 +539,79 @@ function appendTrie(m, mTailFn) { 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"); + if (m instanceof $Success) return mTailFn(m.value); - 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)); - } + var target = newEmptyBranch(); + target.wild = walk(m.wild); + m.edges.forEach(function (keymap, arity) { + keymap.forEach(function (k, key) { + rupdate_inplace(target, arity, key, walk(k)); + }); }); - return target; + return collapse(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 triePruneBranch(m, arityKeys) { + if (arityKeys.isEmpty()) return emptyTrie; + if (!(m instanceof $Branch)) return m; + var arityKey = arityKeys.first(); + var rest = arityKeys.shift(); + var arity = arityKey[0]; + var key = arityKey[1]; + m = rcopybranch(m); + rupdate_inplace(m, arity, key, triePruneBranch(rlookup(m, arity, key), rest)); + return collapse(m); } -function trieStep(m, key) { +function trieStep(m, arity, key) { + if (typeof key === 'undefined') { + // Cope with API change which would otherwise silently cause problems + die("trieStep: missing 'key' argument"); + } 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); + return rlookup(m, arity, 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; - } + return appendTrie(m, function (v) { + var v1 = f(v); + return v1 ? rsuccess(v1) : emptyTrie; + }); } -function compileProjection(/* projection, projection, ... */) { +/////////////////////////////////////////////////////////////////////////// + +function projectionNames(p) { var names = []; - var acc = []; - for (var i = 0; i < arguments.length; i++) { - walk(arguments[i]); - } - acc.push(EOA); - return {names: names, spec: acc}; + walk(p); + return names; 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); + for (var i = 0; i < p.length; i++) walk(p[i]); return; } if (isStructure(p)) { - 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); + for (var i = 0; i < args.length; i++) walk(p[args[i]]); return; } - - if (p instanceof $Embedded) { - die("Cannot embed trie in projection"); - } else { - acc.push(p); - } } } +function projectionArity(p) { + return projectionNames(p).length; +} + function projectionToPattern(p) { return walk(p); @@ -777,253 +635,176 @@ function projectionToPattern(p) { return result; } - if (p instanceof $Embedded) { - return p.trie; - } else { - return p; - } + return p; } } -function project(m, compiledProjection) { - var spec = compiledProjection.spec; - return walk(false, m, 0); +function project(t, wholeSpec, projectSuccessOpt, combinerOpt) { + return projectMany(t, Immutable.List.of(wholeSpec), projectSuccessOpt, combinerOpt); +} - 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; - } +function projectMany(t, wholeSpecs, projectSuccessOpt, combinerOpt) { + var projectSuccess = projectSuccessOpt || rsuccess; + var combiner = combinerOpt || unionSuccessesDefault; + + return walk(false, t, Immutable.List(wholeSpecs), function (t) { + if (t instanceof $Success) { + return projectSuccess(t.value); + } else { + return emptyTrie; } + }); - if (is_emptyTrie(m)) return emptyTrie; + function walk(isCapturing, t, specs, kont) { + if (specs.isEmpty()) return kont(t); + if (!(t instanceof $Branch)) return emptyTrie; - var item = spec[specIndex]; - var nextIndex = specIndex + 1; + var spec = specs.first(); + var specsRest = specs.rest(); - 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 (isCapture(spec)) { 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)); - } - } - }); + die("projectMany: nested capture in projection: " + wholeSpecs); + } + return walk(true, t, Immutable.List.of(capturePattern(spec)), function (intermediate) { + return walk(false, intermediate, specsRest, kont); + }); + } + + if (spec === __) { + if (isCapturing) { + var target = newEmptyBranch(); + target.wild = walk(isCapturing, t.wild, specsRest, kont); + t.edges.forEach(function (keymap, arity) { + var innerSpecs = Immutable.Repeat(__, arity); + keymap.forEach(function (k, key) { + rupdate_inplace(target, arity, key, + walk(isCapturing, k, innerSpecs, function (intermediate) { + return walk(isCapturing, intermediate, specsRest, kont); + })); + }); + }); + return collapse(target); } 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)); - } - } - }); + var seed = walk(isCapturing, t.wild, specsRest, kont); + t.edges.forEach(function (keymap, arity) { + var innerSpecs = Immutable.Repeat(__, arity); + keymap.forEach(function (k, key) { + seed = union(seed, + walk(isCapturing, k, innerSpecs, function (intermediate) { + return walk(isCapturing, intermediate, specsRest, kont); + }), + combiner); + }); + }); + return seed; } 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 (isStructure(spec)) { + var arity = spec.$SyndicateMeta$.arguments.length; + var key = spec.$SyndicateMeta$; + var intermediate = walk(isCapturing, + rlookup(t, arity, key), + Immutable.List(structureToArray(spec, true)), + function (intermediate) { + return walk(isCapturing, intermediate, specsRest, kont); + }); + return isCapturing ? rseq(arity, key, intermediate) : intermediate; } - if (isCapturing) { - result = rseq(item, result); + + if (Array.isArray(spec)) { + var intermediate = walk(isCapturing, + rlookup(t, spec.length, SOA), + Immutable.List(spec), + function (intermediate) { + return walk(isCapturing, intermediate, specsRest, kont); + }); + return isCapturing ? rseq(spec.length, SOA, intermediate) : intermediate; } + + if (spec instanceof $Embedded) { + die("$Embedded patterns not supported in projectMany()"); + } + + /* It is a normal atom */ + var intermediate = walk(isCapturing, rlookup(t, 0, spec), specsRest, kont); + return isCapturing ? rseq(0, spec, intermediate) : intermediate; + } +} + +function reconstructSequence(key, items) { + if (key === SOA) { + return items.toArray(); + } else { + return instantiateStructure(key, items); + } +} + +function trieKeys(m, takeCount0) { + if (typeof takeCount0 !== 'number') { + // Cope with API change which would otherwise silently cause problems + die("Missing mandatory argument 'takeCount' to Route.trieKeys"); + } + + if (is_emptyTrie(m)) return Immutable.Set(); + return walk(m, takeCount0, Immutable.List(), + function (items, tail) { + if (is_emptyTrie(tail)) return Immutable.Set(); + if (tail instanceof $Success) return Immutable.Set.of(items); + die("Trie contains more than the requested "+takeCount0+" items"); + }); + + function walk(m, takeCount, valsRev, kont) { + if (takeCount === 0) return kont(valsRev.reverse(), m); + + if (is_emptyTrie(m)) return Immutable.Set(); + if (m instanceof $Success) { + die("Trie contains fewer than the requested "+takeCount0+" items"); + } + + if (!is_emptyTrie(m.wild)) return false; + + var result = Immutable.Set(); + m.edges.forEach(function (keymap, arity) { + if (result === false) return false; // break out of iteration + keymap.forEach(function (k, key) { + if (result === false) return false; // break out of iteration + + var piece; + if (isSyndicateMeta(key) || key === SOA) { // TODO: this is sloppy + piece = walk(k, arity, Immutable.List(), function (items, m1) { + var item = reconstructSequence(key, items); + return walk(m1, takeCount - 1, valsRev.unshift(item), kont); + }); + } else { + piece = walk(k, takeCount - 1, valsRev.unshift(key), kont); + } + + result = (piece === false) ? false : result.union(piece); + }); + }); 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) { +function captureToObject(captures, names) { var d = {}; captures.forEach(function (key, index) { - d[compiledProjection.names[index] || ('$' + index)] = key; + d[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 trieKeysToObjects(trieKeysResult, names) { + if (trieKeysResult === false) return false; + return trieKeysResult.toList().map(function (e) { return captureToObject(e, names); }); } -function projectObjects(m, compiledProjection) { - return trieKeysToObjects(trieKeys(project(m, compiledProjection)), compiledProjection); +function projectObjects(m, projection) { + var names = projectionNames(projection); + return trieKeysToObjects(trieKeys(project(m, projection), names.length), names); } function prettyTrie(m, initialIndent) { @@ -1032,41 +813,49 @@ function prettyTrie(m, initialIndent) { 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) + "}"); + acc.push(" {" + JSON.stringify(v) + "}"); return; } - if (m.size === 0) { - acc.push("::: nothing"); + if (is_emptyTrie(m)) { + 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); + if (!is_emptyTrie(m.wild)) { + var key = "★"; + needSep = true; + acc.push(" "); + acc.push(key); + walk(i + key.length + 1, m.wild); + } + m.edges + .toOrderedMap() + .sortBy(function (keymap, arity) { return arity }) + .forEach(function (keymap, arity) { + keymap + .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 === SOA) key = '<' + arity + '>'; + else if (isSyndicateMeta(key)) key = key.label + '<' + arity + '>'; + else if (key instanceof $Special) key = key.name; + else if (typeof key === 'undefined') key = 'undefined'; + else key = JSON.stringify(key); + acc.push(key); + walk(i + key.length + 1, k); + }); }); } @@ -1079,7 +868,6 @@ function prettyTrie(m, initialIndent) { module.exports.__ = __; module.exports.SOA = SOA; -module.exports.EOA = EOA; module.exports.$Capture = $Capture; module.exports.$Special = $Special; module.exports.makeStructureConstructor = makeStructureConstructor; @@ -1087,6 +875,7 @@ module.exports._$ = _$; module.exports.is_emptyTrie = is_emptyTrie; module.exports.emptyTrie = emptyTrie; module.exports.embeddedTrie = embeddedTrie; +module.exports.embeddedTrieArray = embeddedTrieArray; module.exports.compilePattern = compilePattern; module.exports.matchPattern = matchPattern; module.exports._union = union; @@ -1098,10 +887,13 @@ module.exports.matchTrie = matchTrie; module.exports.appendTrie = appendTrie; module.exports.triePruneBranch = triePruneBranch; module.exports.trieStep = trieStep; +module.exports.trieSuccess = rsuccess; module.exports.relabel = relabel; -module.exports.compileProjection = compileProjection; +module.exports.projectionNames = projectionNames; +module.exports.projectionArity = projectionArity; module.exports.projectionToPattern = projectionToPattern; module.exports.project = project; +module.exports.projectMany = projectMany; module.exports.trieKeys = trieKeys; module.exports.captureToObject = captureToObject; module.exports.trieKeysToObjects = trieKeysToObjects; @@ -1112,6 +904,5 @@ module.exports.prettyTrie = prettyTrie; module.exports._testing = { rsuccess: rsuccess, rseq: rseq, - rwild: rwild, - rwildseq: rwildseq + rwild: rwild }; diff --git a/js/test/test-mux.js b/js/test/test-mux.js index 24eb348..23ed3cd 100644 --- a/js/test/test-mux.js +++ b/js/test/test-mux.js @@ -32,13 +32,13 @@ describe('mux stream', function () { describe('addition', function () { it('should union interests appropriately', function () { var m = getM(); - checkPrettyTrie(m.routingTable, [' 1 >{[0]}', - ' 2 >{[0,1]}', - ' 3 >{[1]}']); - checkPrettyTrie(m.interestsOf(0), [' 1 >{[0]}', - ' 2 >{[0]}']); - checkPrettyTrie(m.interestsOf(1), [' 2 >{[1]}', - ' 3 >{[1]}']); + checkPrettyTrie(m.routingTable, [' 1 {[0]}', + ' 2 {[0,1]}', + ' 3 {[1]}']); + checkPrettyTrie(m.interestsOf(0), [' 1 {[0]}', + ' 2 {[0]}']); + checkPrettyTrie(m.interestsOf(1), [' 2 {[1]}', + ' 3 {[1]}']); }); }); @@ -51,30 +51,30 @@ describe('mux stream', function () { .andThen(Patch.assert(4)) .andThen(Patch.retract(99)); checkPrettyPatch(rawPatch, - [' 1 >{true}', - ' 4 >{true}'], - [' 2 >{true}', - ' 3 >{true}', - ' 99 >{true}']); + [' 1 {true}', + ' 4 {true}'], + [' 2 {true}', + ' 3 {true}', + ' 99 {true}']); var m = getM(); var updateStreamResult = m.updateStream(1, rawPatch); expect(updateStreamResult.pid).to.equal(1); checkPrettyPatch(updateStreamResult.delta, - [' 1 >{[1]}', - ' 4 >{[1]}'], - [' 2 >{[1]}', - ' 3 >{[1]}']); - checkPrettyTrie(m.routingTable, [' 1 >{[0,1]}', - ' 2 >{[0]}', - ' 4 >{[1]}']); - checkPrettyTrie(m.interestsOf(0), [' 1 >{[0]}', - ' 2 >{[0]}']); - checkPrettyTrie(m.interestsOf(1), [' 1 >{[1]}', - ' 4 >{[1]}']); + [' 1 {[1]}', + ' 4 {[1]}'], + [' 2 {[1]}', + ' 3 {[1]}']); + checkPrettyTrie(m.routingTable, [' 1 {[0,1]}', + ' 2 {[0]}', + ' 4 {[1]}']); + checkPrettyTrie(m.interestsOf(0), [' 1 {[0]}', + ' 2 {[0]}']); + checkPrettyTrie(m.interestsOf(1), [' 1 {[1]}', + ' 4 {[1]}']); checkPrettyPatch(updateStreamResult.deltaAggregate, - [' 4 >{[1]}'], - [' 3 >{[1]}']); + [' 4 {[1]}'], + [' 3 {[1]}']); }); }); @@ -84,17 +84,17 @@ describe('mux stream', function () { var updateStreamResult = m.removeStream(1); expect(updateStreamResult.pid).to.equal(1); checkPrettyPatch(updateStreamResult.delta, - ['::: nothing'], - [' 2 >{[1]}', - ' 3 >{[1]}']); - checkPrettyTrie(m.routingTable, [' 1 >{[0]}', - ' 2 >{[0]}']); - checkPrettyTrie(m.interestsOf(0), [' 1 >{[0]}', - ' 2 >{[0]}']); - checkPrettyTrie(m.interestsOf(1), ['::: nothing']); + [' ::: nothing'], + [' 2 {[1]}', + ' 3 {[1]}']); + checkPrettyTrie(m.routingTable, [' 1 {[0]}', + ' 2 {[0]}']); + checkPrettyTrie(m.interestsOf(0), [' 1 {[0]}', + ' 2 {[0]}']); + checkPrettyTrie(m.interestsOf(1), [' ::: nothing']); checkPrettyPatch(updateStreamResult.deltaAggregate, - ['::: nothing'], - [' 3 >{[1]}']); + [' ::: nothing'], + [' 3 {[1]}']); }); }); }); diff --git a/js/test/test-patch.js b/js/test/test-patch.js index e622487..9c06d70 100644 --- a/js/test/test-patch.js +++ b/js/test/test-patch.js @@ -19,76 +19,76 @@ function checkPrettyPatch(p, expectedAdded, expectedRemoved) { describe('basic patch compilation', function () { it('should print as expected', function () { checkPrettyPatch(Patch.assert([1, 2]), - [' < 1 2 > >{true}'], - ['::: nothing']); + [' <2> 1 2 {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.assert(__), - [' ★ >{true}'], - ['::: nothing']); + [' ★ {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.sub(__), - [' < $Observe ★ > >{true}'], - ['::: nothing']); + [' observe<1> ★ {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.sub([1, 2]), - [' < $Observe < 1 2 > > >{true}'], - ['::: nothing']); + [' observe<1> <2> 1 2 {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.pub('x'), - [' < $Advertise "x" > >{true}'], - ['::: nothing']); + [' advertise<1> "x" {true}'], + [' ::: nothing']); }); it('should work at nonzero metalevel', function () { checkPrettyPatch(Patch.assert([1, 2], 0), - [' < 1 2 > >{true}'], - ['::: nothing']); + [' <2> 1 2 {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.assert([1, 2], 1), - [' < $AtMeta < 1 2 > > >{true}'], - ['::: nothing']); + [' atMeta<1> <2> 1 2 {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.assert([1, 2], 2), - [' < $AtMeta < $AtMeta < 1 2 > > > >{true}'], - ['::: nothing']); + [' atMeta<1> atMeta<1> <2> 1 2 {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.sub([1, 2], 0), - [' < $Observe < 1 2 > > >{true}'], - ['::: nothing']); + [' observe<1> <2> 1 2 {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.sub([1, 2], 1), - [' < $AtMeta < $Observe < 1 2 > > > >{true}', - ' $Observe < $AtMeta < 1 2 > > > >{true}'], - ['::: nothing']); + [' atMeta<1> observe<1> <2> 1 2 {true}', + ' observe<1> atMeta<1> <2> 1 2 {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.sub([1, 2], 2), - [' < $AtMeta < $AtMeta < $Observe < 1 2 > > > > >{true}', - ' $Observe < $AtMeta < 1 2 > > > > >{true}', - ' $Observe < $AtMeta < $AtMeta < 1 2 > > > > >{true}'], - ['::: nothing']); + [' atMeta<1> atMeta<1> observe<1> <2> 1 2 {true}', + ' observe<1> atMeta<1> <2> 1 2 {true}', + ' observe<1> atMeta<1> atMeta<1> <2> 1 2 {true}'], + [' ::: nothing']); }); }); describe('patch sequencing', function () { it('should do the right thing in simple cases', function () { checkPrettyPatch(Patch.assert(__).andThen(Patch.retract(3)), - [' ★ >{true}', - ' 3::: nothing'], - [' 3 >{true}']); + [' ★ {true}', + ' 3 ::: nothing'], + [' 3 {true}']); checkPrettyPatch(Patch.assert(3).andThen(Patch.retract(__)), - ['::: nothing'], - [' ★ >{true}']); + [' ::: nothing'], + [' ★ {true}']); checkPrettyPatch(Patch.assert(__).andThen(Patch.retract(__)), - ['::: nothing'], - [' ★ >{true}']); + [' ::: nothing'], + [' ★ {true}']); checkPrettyPatch(Patch.assert(3).andThen(Patch.retract(3)), - ['::: nothing'], - [' 3 >{true}']); + [' ::: nothing'], + [' 3 {true}']); checkPrettyPatch(Patch.sub([1, __]).andThen(Patch.unsub([1, 2])), - [' < $Observe < 1 ★ > > >{true}', - ' 2::: nothing'], - [' < $Observe < 1 2 > > >{true}']); + [' observe<1> <2> 1 ★ {true}', + ' 2 ::: nothing'], + [' observe<1> <2> 1 2 {true}']); checkPrettyPatch(Patch.sub([__, 2]).andThen(Patch.unsub([1, 2])), - [' < $Observe < ★ 2 > > >{true}', - ' 1::: nothing'], - [' < $Observe < 1 2 > > >{true}']); + [' observe<1> <2> ★ 2 {true}', + ' 1 ::: nothing'], + [' observe<1> <2> 1 2 {true}']); checkPrettyPatch(Patch.sub([__, __]).andThen(Patch.unsub([1, 2])), - [' < $Observe < ★ ★ > > >{true}', - ' 1 ★ > > >{true}', - ' 2::: nothing'], - [' < $Observe < 1 2 > > >{true}']); + [' observe<1> <2> ★ ★ {true}', + ' 1 ★ {true}', + ' 2 ::: nothing'], + [' observe<1> <2> 1 2 {true}']); }); it('works for longer chains of asserts and retracts', function () { @@ -99,11 +99,11 @@ describe('patch sequencing', function () { .andThen(Patch.assert(4)) .andThen(Patch.retract(99)); checkPrettyPatch(rawPatch, - [' 1 >{true}', - ' 4 >{true}'], - [' 2 >{true}', - ' 3 >{true}', - ' 99 >{true}']); + [' 1 {true}', + ' 4 {true}'], + [' 2 {true}', + ' 3 {true}', + ' 99 {true}']); }); }); @@ -111,31 +111,31 @@ describe('patch sequencing', function () { describe('patch lifting', function () { it('should basically work', function () { checkPrettyPatch(Patch.assert([1, 2]).lift(), - [' < $AtMeta < 1 2 > > >{true}'], - ['::: nothing']); + [' atMeta<1> <2> 1 2 {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.sub([1, 2]).lift(), - [' < $AtMeta < $Observe < 1 2 > > > >{true}'], - ['::: nothing']); + [' atMeta<1> observe<1> <2> 1 2 {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.assert([1, 2]).andThen(Patch.assert(Patch.atMeta([1, 2]))).lift(), - [' < $AtMeta < $AtMeta < 1 2 > > > >{true}', - ' 1 2 > > >{true}'], - ['::: nothing']); + [' atMeta<1> atMeta<1> <2> 1 2 {true}', + ' <2> 1 2 {true}'], + [' ::: nothing']); }); }); describe('patch dropping', function () { it('should basically work', function () { checkPrettyPatch(Patch.assert([1, 2]).drop(), - ['::: nothing'], - ['::: nothing']); + [' ::: nothing'], + [' ::: nothing']); checkPrettyPatch(Patch.sub([1, 2]).drop(), - ['::: nothing'], - ['::: nothing']); + [' ::: nothing'], + [' ::: nothing']); checkPrettyPatch(Patch.sub([1, 2], 1).drop(), - [' < $Observe < 1 2 > > >{true}'], - ['::: nothing']); + [' observe<1> <2> 1 2 {true}'], + [' ::: nothing']); checkPrettyPatch(Patch.assert([1, 2]).andThen(Patch.assert(Patch.atMeta([1, 2]))).drop(), - [' < 1 2 > >{true}'], - ['::: nothing']); + [' <2> 1 2 {true}'], + [' ::: nothing']); }); }); diff --git a/js/test/test-route.js b/js/test/test-route.js index 569a6ca..0776d4f 100644 --- a/js/test/test-route.js +++ b/js/test/test-route.js @@ -10,7 +10,20 @@ function checkPrettyTrie(m, expected) { } function checkTrieKeys(actual, expected) { - expect(actual.equals(Immutable.Set(expected).map(Immutable.List))).to.be(true); + expect(Immutable.is(actual, Immutable.Set(expected).map(Immutable.List))).to.be(true); +} + +function checkProjectedObjects(actual, expected) { + actual = Immutable.Set(actual); + expected = Immutable.Set(expected); + actual.forEach(function (e) { c(expected, e) }); + expected.forEach(function (e) { c(actual, e) }); + function c(s, e1) { + if (!s.find(function (e2) { return expect.eql(e1, e2) })) { + throw new Error("Comparison failed: actual " + JSON.stringify(actual.toArray()) + + "; expected " + JSON.stringify(expected.toArray())); + } + } } describe("basic pattern compilation", function () { @@ -20,8 +33,8 @@ describe("basic pattern compilation", function () { var mAAny = r.compilePattern(sAAny, ['A', r.__]); it("should print as expected", function () { - checkPrettyTrie(mAny, [' ★ >{["mAny"]}']); - checkPrettyTrie(mAAny, [' < "A" ★ > >{["mAAny"]}']); + checkPrettyTrie(mAny, [' ★ {["mAny"]}']); + checkPrettyTrie(mAAny, [' <2> "A" ★ {["mAAny"]}']); }); describe("of wildcard", function () { @@ -53,81 +66,78 @@ describe("unions", function () { it("should collapse common prefix wildcard", function () { checkPrettyTrie(r.union(r.compilePattern(Immutable.Set(['A']), [r.__, 'A']), r.compilePattern(Immutable.Set(['B']), [r.__, 'B'])), - [' < ★ "A" > >{["A"]}', - ' "B" > >{["B"]}']); + [' <2> ★ "A" {["A"]}', + ' "B" {["B"]}']); }); it("should unroll wildcard unioned with nonwildcard", function () { checkPrettyTrie(r.union(r.compilePattern(Immutable.Set(['A']), [r.__, 'A']), r.compilePattern(Immutable.Set(['W']), r.__)), - [' ★ >{["W"]}', - ' < ★ "A" ★...> >{["W"]}', - ' > >{["W","A"]}', - ' ★...> >{["W"]}', - ' > >{["W"]}', - ' > >{["W"]}']); + [' ★ {["W"]}', + ' <2> ★ ★ {["W"]}', + ' "A" {["A","W"]}']); }); it("should properly multiply out", function () { checkPrettyTrie(r.union(r.compilePattern(Immutable.Set(['A']), [r.__, 2]), r.compilePattern(Immutable.Set(['C']), [1, 3]), r.compilePattern(Immutable.Set(['B']), [3, 4])), - [' < ★ 2 > >{["A"]}', - ' 1 2 > >{["A"]}', - ' 3 > >{["C"]}', - ' 3 2 > >{["A"]}', - ' 4 > >{["B"]}']); + [' <2> ★ 2 {["A"]}', + ' 1 2 {["A"]}', + ' 3 {["C"]}', + ' 3 2 {["A"]}', + ' 4 {["B"]}']); checkPrettyTrie(r.union(r.compilePattern(Immutable.Set(['C']), [1, 3]), r.compilePattern(Immutable.Set(['B']), [3, 4])), - [' < 1 3 > >{["C"]}', - ' 3 4 > >{["B"]}']); + [' <2> 1 3 {["C"]}', + ' 3 4 {["B"]}']); checkPrettyTrie(r.union(r.compilePattern(Immutable.Set(['A']), [r.__, 2]), r.compilePattern(Immutable.Set(['C']), [1, 3])), - [' < ★ 2 > >{["A"]}', - ' 1 2 > >{["A"]}', - ' 3 > >{["C"]}']); + [' <2> ★ 2 {["A"]}', + ' 1 2 {["A"]}', + ' 3 {["C"]}']); checkPrettyTrie(r.union(r.compilePattern(Immutable.Set(['A']), [r.__, 2]), r.compilePattern(Immutable.Set(['B']), [3, 4])), - [' < ★ 2 > >{["A"]}', - ' 3 2 > >{["A"]}', - ' 4 > >{["B"]}']); + [' <2> ★ 2 {["A"]}', + ' 3 2 {["A"]}', + ' 4 {["B"]}']); }); it("should correctly construct intermediate values", function () { var MU = r.emptyTrie; MU = r.union(MU, r.compilePattern(Immutable.Set(['A']), [r.__, 2])); - checkPrettyTrie(MU, [' < ★ 2 > >{["A"]}']); + checkPrettyTrie(MU, [' <2> ★ 2 {["A"]}']); MU = r.union(MU, r.compilePattern(Immutable.Set(['C']), [1, 3])); - checkPrettyTrie(MU, [' < ★ 2 > >{["A"]}', - ' 1 2 > >{["A"]}', - ' 3 > >{["C"]}']); + checkPrettyTrie(MU, [' <2> ★ 2 {["A"]}', + ' 1 2 {["A"]}', + ' 3 {["C"]}']); MU = r.union(MU, r.compilePattern(Immutable.Set(['B']), [3, 4])); - checkPrettyTrie(MU, [' < ★ 2 > >{["A"]}', - ' 1 2 > >{["A"]}', - ' 3 > >{["C"]}', - ' 3 2 > >{["A"]}', - ' 4 > >{["B"]}']); + checkPrettyTrie(MU, [' <2> ★ 2 {["A"]}', + ' 1 2 {["A"]}', + ' 3 {["C"]}', + ' 3 2 {["A"]}', + ' 4 {["B"]}']); }); it("should handle identical patterns with different pids", function () { var m = r.union(r.compilePattern(Immutable.Set('B'), [2]), r.compilePattern(Immutable.Set('C'), [3])); - checkPrettyTrie(m, [' < 2 > >{["B"]}', - ' 3 > >{["C"]}']); + checkPrettyTrie(m, [' <1> 2 {["B"]}', + ' 3 {["C"]}']); m = r.union(r.compilePattern(Immutable.Set('A'), [2]), m); - checkPrettyTrie(m, [' < 2 > >{["A","B"]}', - ' 3 > >{["C"]}']); + checkPrettyTrie(m, [' <1> 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"]}']; + var expected = [' <1> ★ {["A"]}', + ' "Y" ::: nothing']; checkPrettyTrie(r.subtract(r.union(x, z), y), expected); checkPrettyTrie(r.union(r.subtract(x, y), z), expected); }); @@ -135,42 +145,42 @@ describe("unions", function () { describe("projections", function () { describe("with picky structure", function () { - var proj = r.compileProjection(r._$("v", [[r.__]])); + var proj = r._$("v", [[r.__]]); it("should include things that match as well as wildcards", function () { checkPrettyTrie(r.project(r.union(r.compilePattern(Immutable.Set(['A']), r.__), r.compilePattern(Immutable.Set(['B']), [['b']])), proj), - [' < < ★ > > >{["A"]}', - ' "b" > > >{["A","B"]}']); + [' <1> <1> ★ {["A"]}', + ' "b" {["A","B"]}']); }); it("should exclude things that lack the required structure", function () { checkPrettyTrie(r.project(r.union(r.compilePattern(Immutable.Set(['A']), r.__), r.compilePattern(Immutable.Set(['B']), ['b'])), proj), - [' < < ★ > > >{["A"]}']); + [' <1> <1> ★ {["A"]}']); }); }); describe("simple positional", function () { - var proj = r.compileProjection([r._$, r._$]); + var proj = [r._$, r._$]; it("should collapse common prefixes", function () { checkPrettyTrie(r.project(r.union(r.compilePattern(Immutable.Set(['A']), [1, 2]), r.compilePattern(Immutable.Set(['C']), [1, 3]), r.compilePattern(Immutable.Set(['B']), [3, 4])), proj), - [' 1 2 >{["A"]}', - ' 3 >{["C"]}', - ' 3 4 >{["B"]}']); + [' 1 2 {["A"]}', + ' 3 {["C"]}', + ' 3 4 {["B"]}']); }); it("should yield a correct set of results", function () { var u = r.union(r.compilePattern(Immutable.Set(['A']), [1, 2]), r.compilePattern(Immutable.Set(['C']), [1, 3]), r.compilePattern(Immutable.Set(['B']), [3, 4])); - checkTrieKeys(r.trieKeys(r.project(u, proj)), [[1, 2], [1, 3], [3, 4]]); + checkTrieKeys(r.trieKeys(r.project(u, proj), 2), [[1, 2], [1, 3], [3, 4]]); }); }); }); @@ -179,29 +189,27 @@ describe("subtraction", function () { it("should basically work", function () { checkPrettyTrie(r.subtract(r.compilePattern(true, r.__), r.compilePattern(true, 3), - function (v1, v2) { return null; }), - [" ★ >{true}", - " 3::: nothing"]); + function (v1, v2) { return r.emptyTrie; }), + [" ★ {true}", + " 3 ::: nothing"]); checkPrettyTrie(r.subtract(r.compilePattern(true, r.__), r.compilePattern(true, [3]), - function (v1, v2) { return null; }), - [" ★ >{true}", - " < ★...> >{true}", - " > >{true}", - " 3 ★...> >{true}", - " >::: nothing"]); + function (v1, v2) { return r.emptyTrie; }), + [" ★ {true}", + " <1> ★ {true}", + " 3 ::: nothing"]); }); it("should be idempotent if the subtrahend doesn't overlap the minuend", function () { checkPrettyTrie(r.compilePattern(true, 1), - [' 1 >{true}']); + [' 1 {true}']); checkPrettyTrie(r.subtract(r.compilePattern(true, 1), r.compilePattern(true, 2)), - [' 1 >{true}']); + [' 1 {true}']); checkPrettyTrie(r.subtract(r.compilePattern(true, 1), r.compilePattern(true, 2), function (v1, v2) { return null; }), - [' 1 >{true}']); + [' 1 {true}']); }); }); @@ -211,11 +219,11 @@ describe("subtract after union", function () { var R12 = r.union(R1, R2); it("should have sane preconditions", function () { // Am I doing this right? - checkPrettyTrie(R1, [' < ★ "B" > >{["A"]}']); - checkPrettyTrie(R2, [' < "A" ★ > >{["B"]}']); - checkPrettyTrie(R12, [' < "A" "B" > >{["B","A"]}', - ' ★ > >{["B"]}', - ' ★ "B" > >{["A"]}']); + checkPrettyTrie(R1, [' <2> ★ "B" {["A"]}']); + checkPrettyTrie(R2, [' <2> "A" ★ {["B"]}']); + checkPrettyTrie(R12, [' <2> ★ "B" {["A"]}', + ' "A" ★ {["B"]}', + ' "B" {["A","B"]}']); }); it("should yield the remaining ingredients of the union", function () { @@ -248,28 +256,30 @@ describe("trieKeys on wild tries", function () { r.compilePattern(Immutable.Set(['C']), [1, 3]), r.compilePattern(Immutable.Set(['B']), [3, 4])); - it("should yield null to signal an infinite result", function () { - expect(r.trieKeys(r.project(M, r.compileProjection([r._$, r._$])))).to.be(null); + it("should yield false to signal an infinite result", function () { + expect(r.trieKeys(r.project(M, [r._$, r._$]), 1)).to.be(false); }); it("should extract just the second array element successfully", function () { - checkTrieKeys(r.trieKeys(r.project(M, r.compileProjection([r.__, r._$]))), + checkTrieKeys(r.trieKeys(r.project(M, [r.__, r._$]), 1), [[2],[3],[4]]); }); - var M2 = r.project(M, r.compileProjection([r._$, r._$])); + var M2 = r.project(M, [r._$, r._$]); it("should survive double-projection", function () { - checkTrieKeys(r.trieKeys(r.project(M2, r.compileProjection(r.__, r._$))), + checkTrieKeys(r.trieKeys(r.projectMany(M2, [r.__, r._$]), 1), [[2],[3],[4]]); }); it("should survive embedding and reprojection", function () { - checkTrieKeys(r.trieKeys(r.project(r.compilePattern(true, [r.embeddedTrie(M2)]), - r.compileProjection([r.__, r._$]))), + checkTrieKeys(r.trieKeys(r.project(r.compilePattern(Immutable.Set(['A']), + r.embeddedTrieArray(M2, 2)), + [r.__, r._$]), 1), [[2],[3],[4]]); - checkTrieKeys(r.trieKeys(r.project(r.compilePattern(true, [[r.embeddedTrie(M2)]]), - r.compileProjection([[r.__, r._$]]))), + checkTrieKeys(r.trieKeys(r.project(r.compilePattern(Immutable.Set(['A']), + [r.embeddedTrieArray(M2, 2)]), + [[r.__, r._$]]), 1), [[2],[3],[4]]); }); }); @@ -278,23 +288,24 @@ describe("trieKeys using multiple-values in projections", function () { var M = r.union(r.compilePattern(Immutable.Set(['A']), [1, 2]), r.compilePattern(Immutable.Set(['C']), [1, 3]), r.compilePattern(Immutable.Set(['B']), [3, 4])); - var proj = r.compileProjection([r._$, r._$]); + var proj = [r._$, r._$]; var M2 = r.project(M, proj); it("should be able to extract ordinary values", function () { - checkTrieKeys(r.trieKeys(M2), [[1,2],[1,3],[3,4]]); + checkTrieKeys(r.trieKeys(M2, 2), [[1,2],[1,3],[3,4]]); }); it("should be able to be reprojected as a sequence of more than one value", function () { - checkTrieKeys(r.trieKeys(r.project(M2, r.compileProjection(r._$, r._$))), + checkTrieKeys(r.trieKeys(r.projectMany(M2, [r._$, r._$]), 2), [[1,2],[1,3],[3,4]]); }); it("should be convertible into objects with $-indexed fields", function () { - expect(r.trieKeysToObjects(r.trieKeys(M2), proj).toArray()) - .to.eql([{'$0': 3, '$1': 4}, {'$0': 1, '$1': 2}, {'$0': 1, '$1': 3}]); - expect(r.projectObjects(M, proj).toArray()) - .to.eql([{'$0': 3, '$1': 4}, {'$0': 1, '$1': 2}, {'$0': 1, '$1': 3}]); + var names = r.projectionNames(proj); + checkProjectedObjects(r.trieKeysToObjects(r.trieKeys(M2, names.length), names).toArray(), + [{'$0': 3, '$1': 4}, {'$0': 1, '$1': 2}, {'$0': 1, '$1': 3}]); + checkProjectedObjects(r.projectObjects(M, proj).toArray(), + [{'$0': 3, '$1': 4}, {'$0': 1, '$1': 2}, {'$0': 1, '$1': 3}]); }); }); @@ -304,15 +315,15 @@ describe("trieKeys using multiple-values in projections, with names", function ( r.compilePattern(Immutable.Set(['B']), [3, 4])); it("should yield named fields", function () { - expect(r.projectObjects(M, r.compileProjection([r._$("fst"), r._$("snd")])).toArray()) - .to.eql([{'fst': 3, 'snd': 4}, {'fst': 1, 'snd': 2}, {'fst': 1, 'snd': 3}]); + checkProjectedObjects(r.projectObjects(M, [r._$("fst"), r._$("snd")]).toArray(), + [{'fst': 3, 'snd': 4}, {'fst': 1, 'snd': 2}, {'fst': 1, 'snd': 3}]); }); it("should yield numbered fields where names are missing", function () { - expect(r.projectObjects(M, r.compileProjection([r._$, r._$("snd")])).toArray()) - .to.eql([{'$0': 3, 'snd': 4}, {'$0': 1, 'snd': 2}, {'$0': 1, 'snd': 3}]); - expect(r.projectObjects(M, r.compileProjection([r._$("fst"), r._$])).toArray()) - .to.eql([{'fst': 3, '$1': 4}, {'fst': 1, '$1': 2}, {'fst': 1, '$1': 3}]); + checkProjectedObjects(r.projectObjects(M, [r._$, r._$("snd")]).toArray(), + [{'$0': 3, 'snd': 4}, {'$0': 1, 'snd': 2}, {'$0': 1, 'snd': 3}]); + checkProjectedObjects(r.projectObjects(M, [r._$("fst"), r._$]).toArray(), + [{'fst': 3, '$1': 4}, {'fst': 1, '$1': 2}, {'fst': 1, '$1': 3}]); }); }); @@ -393,47 +404,51 @@ describe("calls to matchPattern", 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 }); + // Previously, structures were roughly the same as arrays: expect(r.matchPattern(["foo", 123, 234], ctor(r._$("bar"), r._$("zot")))) - .to.eql({ bar: 123, zot: 234, length: 2 }); + .to.be(null); expect(r.matchPattern(ctor(123, 234), ["foo", r._$("bar"), r._$("zot")])) - .to.eql({ bar: 123, zot: 234, length: 2 }); + .to.be(null); }); }); describe("Projection with no captures", function () { it("should yield the empty sequence when there's a match", function () { - var emptySequence = [' >{["A"]}']; + var emptySequence = [' {["A"]}']; - checkPrettyTrie(r.project(r.compilePattern(Immutable.Set(['A']), ["X", r.__]), - r.compileProjection(r.__)), + checkPrettyTrie(r.project(r.compilePattern(Immutable.Set(['A']), ["X", r.__]), r.__), emptySequence); - checkPrettyTrie(r.project(r.compilePattern(Immutable.Set(['A']), ["X", r.__]), - r.compileProjection([r.__, r.__])), + checkPrettyTrie(r.project(r.compilePattern(Immutable.Set(['A']), ["X", r.__]), [r.__, r.__]), emptySequence); - checkPrettyTrie(r.project(r.compilePattern(Immutable.Set(['A']), ["X", r.__]), - r.compileProjection(["X", r.__])), + checkPrettyTrie(r.project(r.compilePattern(Immutable.Set(['A']), ["X", r.__]), ["X", r.__]), emptySequence); }); it("should yield the empty trie when there's no match", function () { expect(r.project(r.compilePattern(Immutable.Set(['A']), ["X", r.__]), - r.compileProjection(["Y", r.__]))).to.be(r.emptyTrie); + ["Y", r.__])).to.be(r.emptyTrie); }); it("should yield nonempty sequences when there are captures after all", function () { checkPrettyTrie(r.project(r.compilePattern(Immutable.Set(['A']), ["X", r.__]), - r.compileProjection([r.__, r._$])), - [' ★ >{["A"]}']); + [r.__, r._$]), + [' ★ {["A"]}']); checkPrettyTrie(r.project(r.compilePattern(Immutable.Set(['A']), ["X", r.__]), - r.compileProjection([r._$, r._$])), - [' "X" ★ >{["A"]}']); + [r._$, r._$]), + [' "X" ★ {["A"]}']); }); }); describe('trieStep', function () { it('should expand wildcard when given SOA', function () { - expect(Immutable.is(r.trieStep(r.compilePattern(true, r.__), r.SOA), - r._testing.rwildseq(r._testing.rseq(r.EOA, r._testing.rsuccess(true))))) + expect(Immutable.is(r.trieStep(r.compilePattern(true, r.__), 0, r.SOA), + r._testing.rsuccess(true))) + .to.be(true); + expect(Immutable.is(r.trieStep(r.compilePattern(true, r.__), 1, r.SOA), + r._testing.rwild(r._testing.rsuccess(true)))) + .to.be(true); + expect(Immutable.is(r.trieStep(r.compilePattern(true, r.__), 2, r.SOA), + r._testing.rwild(r._testing.rwild(r._testing.rsuccess(true))))) .to.be(true); }); }); @@ -443,60 +458,61 @@ describe('intersect', 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']); + ' <3> "fieldContents" ★ ★ {[0]}', + ' "initial" ★ {[0]}', + ' 7 ::: nothing']); + 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']); + 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']); + checkPrettyTrie(r.triePruneBranch(full, Immutable.List([])), [' ::: nothing']); + checkPrettyTrie(r.triePruneBranch(full, Immutable.List([[0, r.SOA]])), + [' ★ {true}', + ' <0> ::: nothing']); + checkPrettyTrie(r.triePruneBranch(full, Immutable.List([[0, "x"]])), + [' ★ {true}', + ' "x" ::: nothing']); + checkPrettyTrie(r.triePruneBranch(full, Immutable.List([[2, r.SOA], [0, "x"]])), + [' ★ {true}', + ' <2> ★ ★ {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}']); + 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([[0, "z"]])), [' <1> "y" {true}']); + checkPrettyTrie(r.triePruneBranch(B, Immutable.List([[0, "z"]])), [' <1> "x" {true}', + ' "y" {true}']); + checkPrettyTrie(r.triePruneBranch(C, Immutable.List([[0, "z"]])), [' <1> "x" {true}', + ' "y" {true}']); + checkPrettyTrie(r.triePruneBranch(A, Immutable.List([[1, r.SOA]])), [' ::: nothing']); + checkPrettyTrie(r.triePruneBranch(B, Immutable.List([[1, r.SOA]])), [' ::: nothing']); + checkPrettyTrie(r.triePruneBranch(C, Immutable.List([[1, r.SOA]])), [' "z" {true}']); + var px = [[1, r.SOA], [0, "x"]]; + checkPrettyTrie(r.triePruneBranch(A, Immutable.List(px)), [' <1> "y" {true}']); + checkPrettyTrie(r.triePruneBranch(B, Immutable.List(px)), [' <1> "y" {true}']); + checkPrettyTrie(r.triePruneBranch(C, Immutable.List(px)), [' "z" {true}', + ' <1> "y" {true}']); + var py = [[1, r.SOA], [0, "y"]]; + checkPrettyTrie(r.triePruneBranch(A, Immutable.List(py)), [' ::: nothing']); + checkPrettyTrie(r.triePruneBranch(B, Immutable.List(py)), [' <1> "x" {true}']); + checkPrettyTrie(r.triePruneBranch(C, Immutable.List(py)), [' "z" {true}', + ' <1> "x" {true}']); }); }); @@ -520,7 +536,7 @@ describe('makeStructureConstructor', function () { var ctor = r.makeStructureConstructor('foo', ['bar', 'zot']); var inst = ctor(123, 234); var x = r.compilePattern(sA, ctor(123, r.__)); - checkPrettyTrie(x, [' < "foo" 123 ★ > >{["A"]}']); + checkPrettyTrie(x, [' foo<2> 123 ★ {["A"]}']); expect(r.matchValue(x, ctor(123, 234))).to.eql(sA); expect(r.matchValue(x, ctor(234, 123))).to.eql(null); }); diff --git a/js/test/test-syndicate.js b/js/test/test-syndicate.js index 252ab51..91b5edb 100644 --- a/js/test/test-syndicate.js +++ b/js/test/test-syndicate.js @@ -60,10 +60,9 @@ describe("configurationTrace", function() { Dataspace.send(123); Dataspace.send(234); }, ['<<<<<<<< Removed:\n'+ - '::: nothing\n'+ + ' ::: nothing\n'+ '======== Added:\n'+ - ' < $Observe ★ > >{[0]}\n' - +' >::: nothing\n'+ + ' observe<1> ★ {[0]}\n'+ '>>>>>>>>', Syndicate.message(123), Syndicate.message(234)]); @@ -84,9 +83,9 @@ describe("nonempty initial routes", function () { handleEvent: traceEvent(trace) }); }, ['<<<<<<<< Removed:\n'+ - '::: nothing\n'+ + ' ::: nothing\n'+ '======== Added:\n'+ - ' < "A" ★ > >{[1]}\n'+ + ' <2> "A" ★ {[0]}\n'+ '>>>>>>>>']); }); }); @@ -104,14 +103,14 @@ describe("nested actor with an echoey protocol", function () { }); })); }, ['<<<<<<<< Removed:\n'+ - '::: nothing\n'+ + ' ::: nothing\n'+ '======== Added:\n'+ - ' < $AtMeta "X" > >{[0]}\n'+ + ' atMeta<1> "X" {["meta"]}\n'+ '>>>>>>>>', '<<<<<<<< Removed:\n'+ - ' < $AtMeta "X" > >{[0]}\n'+ + ' atMeta<1> "X" {["meta"]}\n'+ '======== Added:\n'+ - '::: nothing\n'+ + ' ::: nothing\n'+ '>>>>>>>>']); }) it("shouldn't see an echoed message", function () { @@ -120,7 +119,7 @@ describe("nested actor with an echoey protocol", function () { Dataspace.spawn({ boot: function () { Dataspace.send("X", 1); // happens after subs on next line! - return Patch.sub("X", 1); + return Patch.sub("X", 1); }, handleEvent: traceEvent(trace) });