diff --git a/js/compiler/compiler.js b/js/compiler/compiler.js
index e1f742d..0ce2aae 100644
--- a/js/compiler/compiler.js
+++ b/js/compiler/compiler.js
@@ -122,7 +122,7 @@ var modifiedSourceActions = {
var label = maybeLabel.numChildren === 1
? maybeLabel.children[0].interval.contents
: JSON.stringify(typeName.interval.contents);
- return 'var ' + typeName.asES5 + ' = Syndicate.Struct.makeStructureConstructor(' +
+ return 'var ' + typeName.asES5 + ' = Syndicate.Struct.makeConstructor(' +
label + ', ' + JSON.stringify(formals) + ');';
},
@@ -293,14 +293,35 @@ semantics.addOperation('buildSubscription(acc,mode)', {
v.children[0].buildSubscription(this.args.acc, this.args.mode); // both branches!
},
+ AssignmentExpression_assignment: function (lhsExpr, _assignmentOperator, rhsExpr) {
+ var i = lhsExpr.interval.contents;
+ if (i[0] === '$' && i.length > 1) {
+ switch (this.args.mode) {
+ case 'pattern': rhsExpr.buildSubscription(this.args.acc, this.args.mode); break;
+ case 'instantiated': lhsExpr.buildSubscription(this.args.acc, this.args.mode); break;
+ case 'projection': {
+ this.args.acc.push('(Syndicate._$(' + JSON.stringify(i.slice(1)) + ',');
+ rhsExpr.buildSubscription(this.args.acc, this.args.mode);
+ this.args.acc.push('))');
+ break;
+ }
+ default: throw new Error('Unexpected buildSubscription mode ' + this.args.mode);
+ }
+ } else {
+ lhsExpr.buildSubscription(this.args.acc, this.args.mode);
+ _assignmentOperator.buildSubscription(this.args.acc, this.args.mode);
+ rhsExpr.buildSubscription(this.args.acc, this.args.mode);
+ }
+ },
+
identifier: function(_name) {
var i = this.interval.contents;
- if (i[0] === '$') {
+ if (i[0] === '$' && i.length > 1) {
switch (this.args.mode) {
- case 'pattern': this.args.acc.push('_'); break;
- case 'instantiated': this.args.acc.push(i.slice(1)); break;
- case 'projection': this.args.acc.push('(Syndicate._$(' + JSON.stringify(i.slice(1)) + '))'); break;
- default: throw new Error('Unexpected buildSubscription mode ' + this.args.mode);
+ case 'pattern': this.args.acc.push('_'); break;
+ case 'instantiated': this.args.acc.push(i.slice(1)); break;
+ case 'projection': this.args.acc.push('(Syndicate._$(' + JSON.stringify(i.slice(1)) + '))'); break;
+ default: throw new Error('Unexpected buildSubscription mode ' + this.args.mode);
}
} else {
this.args.acc.push(i);
@@ -328,7 +349,7 @@ semantics.addAttribute('bindings', {
semantics.addOperation('pushBindings(accumulator)', {
identifier: function(_name) {
var i = this.interval.contents;
- if (i[0] === '$') {
+ if (i[0] === '$' && i.length > 1) {
this.args.accumulator.push(i.slice(1));
}
},
diff --git a/js/examples/chat/broker-client.js b/js/examples/chat/broker-client.js
new file mode 100644
index 0000000..eafd665
--- /dev/null
+++ b/js/examples/chat/broker-client.js
@@ -0,0 +1,237 @@
+"use strict";
+// WebSocket-based Syndicate broker client
+
+var Trie = Syndicate.Trie;
+var Patch = Syndicate.Patch;
+var Dataspace = Syndicate.Dataspace;
+var Struct = Syndicate.Struct;
+var DemandMatcher = Syndicate.DemandMatcher;
+var __ = Syndicate.__;
+var _$ = Syndicate._$;
+
+var DEFAULT_RECONNECT_DELAY = 100; // ms
+var MAX_RECONNECT_DELAY = 30000; // ms
+var DEFAULT_IDLE_TIMEOUT = 300000; // ms; i.e., 5 minutes
+var DEFAULT_PING_INTERVAL = DEFAULT_IDLE_TIMEOUT - 10000; // ms
+
+var toBroker = Struct.makeConstructor('toBroker', ['url', 'assertion']);
+var fromBroker = Struct.makeConstructor('fromBroker', ['url', 'assertion']);
+var brokerConnection = Struct.makeConstructor('brokerConnection', ['url']);
+var brokerConnected = Struct.makeConstructor('brokerConnected', ['url']);
+var forceBrokerDisconnect = Struct.makeConstructor('forceBrokerDisconnect', ['url']);
+
+function spawnBrokerClientDriver() {
+ var URL = _$('url'); // capture used to extract URL
+ Dataspace.spawn(
+ new Dataspace(function () {
+ Dataspace.spawn(
+ new DemandMatcher([brokerConnection(URL)],
+ [brokerConnection(URL)],
+ {
+ demandMetaLevel: 1,
+ supplyMetaLevel: 0,
+ onDemandIncrease: function (c) {
+ Dataspace.spawn(new BrokerClientConnection(c.url));
+ }
+ }));
+ }));
+}
+
+function BrokerClientConnection(wsurl) {
+ this.wsurl = wsurl;
+ this.sock = null;
+
+ this.sendsAttempted = 0;
+ this.sendsTransmitted = 0;
+ this.receiveCount = 0;
+ this.connectionCount = 0;
+
+ this.reconnectDelay = DEFAULT_RECONNECT_DELAY;
+ this.idleTimeout = DEFAULT_IDLE_TIMEOUT;
+ this.pingInterval = DEFAULT_PING_INTERVAL;
+
+ this.localAssertions = Trie.emptyTrie;
+ this.remoteAssertions = Trie.emptyTrie;
+
+ this.activityTimestamp = 0;
+ this.idleTimer = null;
+ this.pingTimer = null;
+}
+
+BrokerClientConnection.prototype.clearHeartbeatTimers = function () {
+ if (this.idleTimer) { clearTimeout(this.idleTimer); this.idleTimer = null; }
+ if (this.pingTimer) { clearTimeout(this.pingTimer); this.pingTimer = null; }
+};
+
+BrokerClientConnection.prototype.recordActivity = function () {
+ var self = this;
+ this.activityTimestamp = +(new Date());
+ this.clearHeartbeatTimers();
+ this.idleTimer = setTimeout(function () { self.forceclose(); }, this.idleTimeout);
+ this.pingTimer = setTimeout(function () { self.safeSend(JSON.stringify("ping")) },
+ this.pingInterval);
+};
+
+BrokerClientConnection.prototype.boot = function () {
+ this.reconnect();
+ var initialAssertions =
+ Patch.sub(toBroker(this.wsurl, __), 1) // read assertions to go out
+ .andThen(Patch.sub(Patch.observe(fromBroker(this.wsurl, __)), 1)) // and monitor interests
+ .andThen(Patch.assert(brokerConnection(this.wsurl))) // signal to DemandMatcher that we exist
+ .andThen(Patch.sub(brokerConnection(this.wsurl), 1)) // track demand
+ .andThen(Patch.sub(forceBrokerDisconnect(this.wsurl), 1))
+ ;
+ return initialAssertions;
+};
+
+BrokerClientConnection.prototype.trapexit = function () {
+ this.forceclose();
+};
+
+BrokerClientConnection.prototype.isConnected = function () {
+ return this.sock && this.sock.readyState === this.sock.OPEN;
+};
+
+BrokerClientConnection.prototype.safeSend = function (m) {
+ // console.log('safeSend', m);
+ try {
+ this.sendsAttempted++;
+ if (this.isConnected()) {
+ this.sock.send(m);
+ this.sendsTransmitted++;
+ }
+ } catch (e) {
+ console.warn("Trapped exn while sending", e);
+ }
+};
+
+BrokerClientConnection.prototype.sendPatch = function (p) {
+ var j = JSON.stringify(Codec.encodeEvent(Syndicate.stateChange(p)));
+ this.safeSend(j);
+};
+
+BrokerClientConnection.prototype.handleEvent = function (e) {
+ // console.log("BrokerClientConnection.handleEvent", e);
+ switch (e.type) {
+ case "stateChange":
+ if (e.patch.project(Patch.atMeta(brokerConnection(_$))).hasRemoved()) {
+ // console.log("Client is no longer interested in this connection", this.wsurl);
+ Dataspace.exit();
+ }
+
+ var pTo = e.patch.project(Patch.atMeta(toBroker(__, _$)));
+
+ var pObsFrom = e.patch.project(Patch.atMeta(Patch.observe(fromBroker(__, _$))));
+ pObsFrom = new Patch.Patch(
+ Trie.compilePattern(true, Patch.observe(Trie.embeddedTrie(pObsFrom.added))),
+ Trie.compilePattern(true, Patch.observe(Trie.embeddedTrie(pObsFrom.removed))));
+
+ var newLocalAssertions = this.localAssertions;
+ newLocalAssertions = pTo.label(Immutable.Set.of("to")).applyTo(newLocalAssertions);
+ newLocalAssertions = pObsFrom.label(Immutable.Set.of("obsFrom")).applyTo(newLocalAssertions);
+
+ var trueSet = Immutable.Set.of(true);
+ var alwaysTrueSet = function (v) { return trueSet; };
+ var p = Patch.computePatch(Trie.relabel(this.localAssertions, alwaysTrueSet),
+ Trie.relabel(newLocalAssertions, alwaysTrueSet));
+
+ this.localAssertions = newLocalAssertions;
+ // console.log("localAssertions");
+ // console.log(Trie.prettyTrie(this.localAssertions));
+ // console.log(p.pretty());
+ this.sendPatch(p);
+ break;
+
+ case "message":
+ var m = e.message;
+ if (Patch.atMeta.isClassOf(m)) {
+ m = m[0];
+ if (toBroker.isClassOf(m)) {
+ var j = JSON.stringify(Codec.encodeEvent(Syndicate.message(m[1])));
+ this.safeSend(j);
+ } else if (forceBrokerDisconnect.isClassOf(m)) {
+ this.forceclose();
+ }
+ }
+ break;
+ }
+};
+
+BrokerClientConnection.prototype.forceclose = function (keepReconnectDelay) {
+ if (!keepReconnectDelay) {
+ this.reconnectDelay = DEFAULT_RECONNECT_DELAY;
+ }
+ this.clearHeartbeatTimers();
+ if (this.sock) {
+ console.log("BrokerClientConnection.forceclose called");
+ this.sock.close();
+ this.sock = null;
+ }
+};
+
+BrokerClientConnection.prototype.reconnect = function () {
+ var self = this;
+ this.forceclose(true);
+ this.connectionCount++;
+ this.sock = new WebSocket(this.wsurl);
+ this.sock.onopen = Dataspace.wrap(function (e) { return self.onopen(e); });
+ this.sock.onmessage = Dataspace.wrap(function (e) {
+ self.receiveCount++;
+ return self.onmessage(e);
+ });
+ this.sock.onclose = Dataspace.wrap(function (e) { return self.onclose(e); });
+};
+
+BrokerClientConnection.prototype.onopen = function (e) {
+ console.log("connected to " + this.sock.url);
+ this.recordActivity();
+ Dataspace.stateChange(Patch.assert(brokerConnected(this.wsurl), 1));
+ this.reconnectDelay = DEFAULT_RECONNECT_DELAY;
+ this.sendPatch((new Patch.Patch(this.localAssertions, Trie.emptyTrie)).strip());
+};
+
+BrokerClientConnection.prototype.onmessage = function (wse) {
+ // console.log("onmessage", wse);
+ this.recordActivity();
+
+ var j = JSON.parse(wse.data, Struct.reviver);
+ if (j === "ping") {
+ this.safeSend(JSON.stringify("pong"));
+ return;
+ } else if (j === "pong") {
+ return; // recordActivity already took care of our timers
+ }
+
+ var e = Codec.decodeAction(j);
+ switch (e.type) {
+ case "stateChange": {
+ var added = fromBroker(this.wsurl, Trie.embeddedTrie(e.patch.added));
+ var removed = fromBroker(this.wsurl, Trie.embeddedTrie(e.patch.removed));
+ var p = Patch.assert(added, 1).andThen(Patch.retract(removed, 1));
+ // console.log('incoming stateChange');
+ // console.log(p.pretty());
+ Dataspace.stateChange(p);
+ break;
+ }
+ case "message": {
+ Dataspace.send(fromBroker(this.wsurl, e.message), 1);
+ break;
+ }
+ }
+};
+
+BrokerClientConnection.prototype.onclose = function (e) {
+ var self = this;
+
+ // console.log("onclose", e);
+ Dataspace.stateChange(Patch.retract(brokerConnected(this.wsurl), 1));
+
+ console.log("reconnecting to " + this.wsurl + " in " + this.reconnectDelay + "ms");
+ setTimeout(Dataspace.wrap(function () { self.reconnect(); }), this.reconnectDelay);
+
+ this.reconnectDelay = this.reconnectDelay * 1.618 + (Math.random() * 1000);
+ this.reconnectDelay =
+ this.reconnectDelay > MAX_RECONNECT_DELAY
+ ? MAX_RECONNECT_DELAY + (Math.random() * 1000)
+ : this.reconnectDelay;
+};
diff --git a/js/examples/chat/codec.js b/js/examples/chat/codec.js
new file mode 100644
index 0000000..43489ec
--- /dev/null
+++ b/js/examples/chat/codec.js
@@ -0,0 +1,41 @@
+"use strict";
+// Wire protocol representation of events and actions
+
+var Trie = Syndicate.Trie;
+var Struct = Syndicate.Struct;
+
+function _encode(e) {
+ switch (e.type) {
+ case "stateChange":
+ return ["patch", e.patch.toJSON()];
+ case "message":
+ return ["message", e.message];
+ }
+}
+
+function _decode(what) {
+ return function (j) {
+ switch (j[0]) {
+ case "patch":
+ return Syndicate.stateChange(Patch.fromJSON(j[1]));
+ case "message":
+ return Syndicate.message(j[1]);
+ default:
+ throw new Error("Invalid JSON-encoded " + what + ": " + JSON.stringify(j));
+ }
+ };
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+// module.exports.encodeEvent = _encode;
+// module.exports.decodeEvent = _decode("event");
+// module.exports.encodeAction = _encode;
+// module.exports.decodeAction = _decode("action");
+
+var Codec = {
+ encodeEvent: _encode,
+ decodeEvent: _decode("event"),
+ encodeAction: _encode,
+ decodeAction: _decode("action")
+};
diff --git a/js/examples/chat/index.html b/js/examples/chat/index.html
new file mode 100644
index 0000000..f8632af
--- /dev/null
+++ b/js/examples/chat/index.html
@@ -0,0 +1,55 @@
+
+
+
+ Syndicate: Chat
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/examples/chat/index.js b/js/examples/chat/index.js
new file mode 100644
index 0000000..0c95747
--- /dev/null
+++ b/js/examples/chat/index.js
@@ -0,0 +1,116 @@
+assertion type present(name, status);
+assertion type says(who, message);
+
+var DOM = Syndicate.DOM.DOM;
+var jQueryEvent = Syndicate.JQuery.jQueryEvent;
+
+///////////////////////////////////////////////////////////////////////////
+// Application
+
+function spawnChatApp() {
+ $("#chat_form").submit(function (e) { e.preventDefault(); return false; });
+ $("#nym_form").submit(function (e) { e.preventDefault(); return false; });
+ if (!($("#nym").val())) { $("#nym").val("nym" + Math.floor(Math.random() * 65536)); }
+
+ actor {
+ forever {
+ on asserted jQueryInput('#nym', $v) { this.nym = v; }
+ on asserted jQueryInput('#status', $v) { this.status = v; }
+
+ on asserted brokerConnected($url) { outputState('connected to ' + url); }
+ on retracted brokerConnected($url) { outputState('disconnected from ' + url); }
+
+ during jQueryInput('#wsurl', $url) {
+ assert brokerConnection(url);
+
+ on message Syndicate.WakeDetector.wakeEvent() {
+ :: forceBrokerDisconnect(url);
+ }
+
+ assert toBroker(url, present(this.nym, this.status));
+ during fromBroker(url, present($who, $status)) {
+ assert DOM('#nymlist', 'present-nym', Syndicate.seal(
+ ["li",
+ ["span", [["class", "nym"]], who],
+ ["span", [["class", "nym_status"]], status]]));
+ }
+
+ on message jQueryEvent('#send_chat', 'click', _) {
+ var inp = $("#chat_input");
+ var utterance = inp.val();
+ inp.val("");
+ if (utterance) :: toBroker(url, says(this.nym, utterance));
+ }
+
+ on message fromBroker(url, says($who, $what)) {
+ outputUtterance(who, what);
+ }
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Adding items to the transcript panel (plain Javascript/jQuery)
+
+function outputItem(item) {
+ var stamp = $("").text((new Date()).toGMTString()).addClass("timestamp");
+ var item = $("").append([stamp].concat(item));
+ var o = $("#chat_output");
+ o.append(item);
+ o[0].scrollTop = o[0].scrollHeight;
+ return item;
+}
+
+function outputState(state) {
+ outputItem([$("").text(state).addClass(state).addClass("state")])
+ .addClass("state_" + state);
+}
+
+function outputUtterance(who, what) {
+ outputItem([$("").text(who).addClass("nym"),
+ $("").text(what).addClass("utterance")]).addClass("utterance");
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Input control value monitoring
+
+assertion type jQueryInput(selector, value);
+
+function spawnInputChangeMonitor() {
+ actor {
+ forever {
+ on asserted Syndicate.observe(jQueryInput($selector, _)) {
+ actor {
+ this.value = $(selector).val();
+ state {
+ assert jQueryInput(selector, this.value);
+ on message jQueryEvent(selector, 'change', $e) {
+ this.value = e.target.value;
+ }
+ } until {
+ case retracted Syndicate.observe(jQueryInput(selector, _));
+ }
+ }
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Main
+
+$(document).ready(function () {
+ ground dataspace G {
+ Syndicate.JQuery.spawnJQueryDriver();
+ Syndicate.DOM.spawnDOMDriver();
+ Syndicate.WakeDetector.spawnWakeDetector();
+ spawnBrokerClientDriver();
+ spawnInputChangeMonitor();
+ spawnChatApp();
+ }
+
+ // G.dataspace.setOnStateChange(function (mux, patch) {
+ // $("#ds-state").text(Syndicate.prettyTrie(mux.routingTable));
+ // });
+});
diff --git a/js/examples/chat/style.css b/js/examples/chat/style.css
new file mode 100644
index 0000000..a0fe84b
--- /dev/null
+++ b/js/examples/chat/style.css
@@ -0,0 +1,97 @@
+h1 {
+ background: lightgrey;
+}
+
+body > section {
+ display: flex;
+}
+
+body > section > section {
+ margin: 1em;
+}
+
+section#messages {
+ flex-grow: 3;
+}
+
+section#active_users {
+ flex-grow: 1;
+}
+
+form#chat_form {
+ flex: 1 100%;
+}
+
+span.timestamp {
+ color: #d0d0d0;
+}
+
+span.timestamp:after {
+ content: " ";
+}
+
+.utterance span.nym:after {
+ content: ": ";
+}
+
+span.arrived:after {
+ content: " arrived";
+}
+
+span.departed:after {
+ content: " departed";
+}
+
+div.notification {
+ background-color: #eeeeff;
+}
+
+span.state.connected, span.arrived {
+ color: #00c000;
+}
+
+span.state.disconnected, span.departed {
+ color: #c00000;
+}
+
+span.state.crashed {
+ color: white;
+ background: red;
+}
+
+span.state.crashed:after {
+ content: "; please reload the page";
+}
+
+div.state_disconnected {
+ background-color: #ffeeee;
+}
+
+div.state_connected {
+ background-color: #eeffee;
+}
+
+#chat_output {
+ height: 15em;
+ overflow-y: scroll;
+}
+
+#chat_input {
+ width: 80%;
+}
+
+.nym {
+ color: #00c000;
+}
+
+.nym_status:before {
+ content: " (";
+}
+
+.nym_status:after {
+ content: ")";
+}
+
+.nym_status {
+ font-size: smaller;
+}
diff --git a/js/examples/jquery/index.js b/js/examples/jquery/index.js
index 41d24ea..891cc0b 100644
--- a/js/examples/jquery/index.js
+++ b/js/examples/jquery/index.js
@@ -19,7 +19,7 @@ $(document).ready(function () {
handleEvent: function (e) {
if (e.type === 'message'
&& Syndicate.JQuery.jQueryEvent.isClassOf(e.message)
- && e.message.selector === '#clicker')
+ && e.message[0] === '#clicker')
{
var r = $('#result');
r.html(Number(r.html()) + 1);
diff --git a/js/examples/smoketest/index.js b/js/examples/smoketest/index.js
index 3d4cae5..a960659 100644
--- a/js/examples/smoketest/index.js
+++ b/js/examples/smoketest/index.js
@@ -1,6 +1,6 @@
"use strict";
-var beep = Syndicate.Struct.makeStructureConstructor('beep', ['counter']);
+var beep = Syndicate.Struct.makeConstructor('beep', ['counter']);
var G;
$(document).ready(function () {
@@ -26,7 +26,7 @@ $(document).ready(function () {
boot: function () { return sub(beep.pattern); },
handleEvent: function (e) {
if (e.type === 'message') {
- console.log("beep!", e.message.counter);
+ console.log("beep!", e.message[0]);
}
}
});
diff --git a/js/examples/textfield/index.js b/js/examples/textfield/index.js
index a29361b..ec8ff04 100644
--- a/js/examples/textfield/index.js
+++ b/js/examples/textfield/index.js
@@ -8,9 +8,9 @@ var __ = Syndicate.__;
var _$ = Syndicate._$;
var jQueryEvent = Syndicate.JQuery.jQueryEvent;
-var fieldContents = Syndicate.Struct.makeStructureConstructor('fieldContents', ['text', 'pos']);
-var highlight = Syndicate.Struct.makeStructureConstructor('highlight', ['state']);
-var fieldCommand = Syndicate.Struct.makeStructureConstructor('fieldCommand', ['detail']);
+var fieldContents = Syndicate.Struct.makeConstructor('fieldContents', ['text', 'pos']);
+var highlight = Syndicate.Struct.makeConstructor('highlight', ['state']);
+var fieldCommand = Syndicate.Struct.makeConstructor('fieldCommand', ['detail']);
function escapeText(text) {
text = text.replace(/&/g, '&');
@@ -47,7 +47,7 @@ function spawnGui() {
var self = this;
switch (e.type) {
case "message":
- var event = e.message.eventValue;
+ var event = e.message[2];
var keycode = event.keyCode;
var character = String.fromCharCode(event.charCode);
if (keycode === 37 /* left */) {
@@ -105,7 +105,7 @@ function spawnModel() {
handleEvent: function (e) {
if (e.type === "message" && fieldCommand.isClassOf(e.message)) {
- var command = e.message.detail;
+ var command = e.message[0];
if (command === "cursorLeft") {
this.cursorPos--;
if (this.cursorPos < 0)
diff --git a/js/src/ack.js b/js/src/ack.js
index 9db2da4..32057bc 100644
--- a/js/src/ack.js
+++ b/js/src/ack.js
@@ -5,7 +5,7 @@ var Dataspace = require('./dataspace.js').Dataspace;
var Struct = require('./struct.js');
var Patch = require('./patch.js');
-var ack = Struct.makeStructureConstructor('ack', ['id']);
+var ack = Struct.makeConstructor('ack', ['id']);
function Ack(metaLevel, id) {
this.metaLevel = metaLevel || 0;
@@ -26,7 +26,7 @@ Ack.prototype.check = function (e) {
if (!this.done) {
if (e.type === 'message') {
var m = Patch.stripAtMeta(e.message, this.metaLevel);
- if (ack.isClassOf(m) && m.id === this.id) {
+ if (ack.isClassOf(m) && m[0] === this.id) {
this.disarm();
this.done = true;
}
diff --git a/js/src/actor.js b/js/src/actor.js
index 8e71383..ba963fc 100644
--- a/js/src/actor.js
+++ b/js/src/actor.js
@@ -19,14 +19,17 @@ function Actor(state, bootFn) {
this.mux = new Mux.Mux();
this.boot = function() {
- bootFn.call(this.state);
- this.checkForTermination();
+ var self = this;
+ withCurrentFacet(null, function () {
+ bootFn.call(self.state);
+ });
+ self.checkForTermination();
};
}
Actor.prototype.handleEvent = function(e) {
this.facets.forEach(function (f) {
- f.handleEvent(e);
+ withCurrentFacet(f, function () { f.handleEvent(e); });
});
this.checkForTermination();
};
@@ -56,14 +59,32 @@ function Facet(actor) {
this.endpoints = Immutable.Map();
this.initBlocks = Immutable.List();
this.doneBlocks = Immutable.List();
+ this.children = Immutable.Set();
+ this.parent = Facet.current;
+}
+
+Facet.current = null;
+
+function withCurrentFacet(facet, f) {
+ var previous = Facet.current;
+ Facet.current = facet;
+ var result;
+ try {
+ result = f();
+ } catch (e) {
+ Facet.current = previous;
+ throw e;
+ }
+ Facet.current = previous;
+ return result;
}
Facet.prototype.handleEvent = function(e) {
var facet = this;
- this.endpoints.forEach(function(endpoint) {
+ facet.endpoints.forEach(function(endpoint) {
endpoint.handlerFn.call(facet.actor.state, e);
});
- this.refresh();
+ facet.refresh();
};
Facet.prototype.addAssertion = function(assertionFn) {
@@ -157,6 +178,9 @@ Facet.prototype.refresh = function() {
Facet.prototype.completeBuild = function() {
var facet = this;
this.actor.addFacet(this);
+ if (this.parent) {
+ this.parent.children = this.parent.children.add(this);
+ }
this.initBlocks.forEach(function(b) { b.call(facet.actor.state); });
};
@@ -169,8 +193,14 @@ Facet.prototype.terminate = function() {
});
Dataspace.stateChange(aggregate);
this.endpoints = Immutable.Map();
+ if (this.parent) {
+ this.parent.children = this.parent.children.remove(this);
+ }
this.actor.removeFacet(this);
this.doneBlocks.forEach(function(b) { b.call(facet.actor.state); });
+ this.children.forEach(function (child) {
+ child.terminate();
+ });
};
//---------------------------------------------------------------------------
diff --git a/js/src/dataspace.js b/js/src/dataspace.js
index 3cf5e9e..6234748 100644
--- a/js/src/dataspace.js
+++ b/js/src/dataspace.js
@@ -41,6 +41,7 @@ function Dataspace(bootFn) {
// Class state and methods
+Dataspace.noisy = false;
Dataspace.stack = Immutable.List();
Dataspace.current = function () {
@@ -140,7 +141,7 @@ Dataspace.prototype.asChild = function (pid, f, omitLivenessCheck) {
Dataspace.prototype.kill = function (pid, exn) {
if (exn && exn.stack) {
console.log("Process exiting", pid, exn, exn.stack);
- } else {
+ } else if (exn || Dataspace.noisy) {
console.log("Process exiting", pid, exn);
}
var p = this.processTable.get(pid);
@@ -222,53 +223,53 @@ Dataspace.prototype.interpretAction = function (pid, action) {
var self = this;
switch (action.type) {
- case 'stateChange':
- var oldMux = this.mux.shallowCopy();
- this.deliverPatches(oldMux, this.mux.updateStream(pid, action.patch));
- return true;
+ case 'stateChange':
+ var oldMux = this.mux.shallowCopy();
+ this.deliverPatches(oldMux, this.mux.updateStream(pid, action.patch));
+ return true;
- case 'message':
- if (Patch.observe.isClassOf(action.message)) {
- console.warn('Process ' + pid + ' send message containing query', action.message);
- }
- 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);
- });
- }
- return true;
+ case 'message':
+ if (Patch.observe.isClassOf(action.message)) {
+ console.warn('Process ' + pid + ' send message containing query', action.message);
+ }
+ if (pid !== 'meta' && Patch.atMeta.isClassOf(action.message)) {
+ Dataspace.send(action.message[0]);
+ } else {
+ this.mux.routeMessage(action.message).forEach(function (pid) {
+ self.deliverEvent(pid, action);
+ });
+ }
+ return true;
- case 'spawn':
- var oldMux = this.mux.shallowCopy();
- var p = { behavior: action.behavior };
- var pid = this.mux.nextPid;
- this.processTable = this.processTable.set(pid, p);
- var initialPatch = Patch.emptyPatch;
- if (p.behavior.boot) {
- initialPatch = this.asChild(pid, function () { return p.behavior.boot() });
- initialPatch = initialPatch || Patch.emptyPatch;
- this.markRunnable(pid);
- }
- this.deliverPatches(oldMux, this.mux.addStream(initialPatch));
- return true;
+ case 'spawn':
+ var oldMux = this.mux.shallowCopy();
+ var p = { behavior: action.behavior };
+ var pid = this.mux.nextPid;
+ this.processTable = this.processTable.set(pid, p);
+ var initialPatch = Patch.emptyPatch;
+ if (p.behavior.boot) {
+ initialPatch = this.asChild(pid, function () { return p.behavior.boot() });
+ initialPatch = initialPatch || Patch.emptyPatch;
+ this.markRunnable(pid);
+ }
+ this.deliverPatches(oldMux, this.mux.addStream(initialPatch));
+ return true;
- case 'terminate':
- var oldMux = this.mux.shallowCopy();
- this.deliverPatches(oldMux, this.mux.removeStream(pid));
- console.log("Process exit complete", pid);
- this.processTable = this.processTable.remove(pid);
- return true;
+ case 'terminate':
+ var oldMux = this.mux.shallowCopy();
+ this.deliverPatches(oldMux, this.mux.removeStream(pid));
+ if (Dataspace.noisy) console.log("Process exit complete", pid);
+ this.processTable = this.processTable.remove(pid);
+ return true;
- case 'terminateDataspace':
- Dataspace.exit();
- return false;
+ case 'terminateDataspace':
+ Dataspace.exit();
+ return false;
- default:
- var exn = new Error("Action type " + action.type + " not understood");
- exn.action = action;
- throw exn;
+ default:
+ var exn = new Error("Action type " + action.type + " not understood");
+ exn.action = action;
+ throw exn;
}
};
diff --git a/js/src/demand-matcher.js b/js/src/demand-matcher.js
index 73f6729..c6cdd55 100644
--- a/js/src/demand-matcher.js
+++ b/js/src/demand-matcher.js
@@ -3,34 +3,78 @@ var Trie = require('./trie.js');
var Patch = require('./patch.js');
var Util = require('./util.js');
-function DemandMatcher(demandSpec, supplySpec, options) {
+function ensureMatchingProjectionNames(specs) {
+ if (!(specs.length > 0)) {
+ throw new Error("Syndicate: DemandMatcher needs at least one spec");
+ }
+
+ var names = null;
+ specs.forEach(function (spec) {
+ if (names === null) {
+ names = Trie.projectionNames(spec);
+ } else {
+ if (JSON.stringify(names) !== JSON.stringify(Trie.projectionNames(spec))) {
+ throw new Error("Syndicate: DemandMatcher needs identical capture names");
+ }
+ }
+ });
+ return names;
+}
+
+function defaultHandler(side, movement) {
+ return function (captures) {
+ console.error("Syndicate: Unhandled "+movement+" in "+side, captures);
+ };
+}
+
+function DemandMatcher(demandSpecs, supplySpecs, options) {
options = Util.extend({
metaLevel: 0,
- onDemandIncrease: function (captures) {
- console.error("Syndicate: Unhandled increase in demand", captures);
- },
- onSupplyDecrease: function (captures) {
- console.error("Syndicate: Unhandled decrease in supply", captures);
- }
+ demandMetaLevel: null,
+ supplyMetaLevel: null,
+ onDemandIncrease: defaultHandler('demand', 'increase'),
+ onDemandDecrease: function (captures) {},
+ onSupplyIncrease: function (captures) {},
+ onSupplyDecrease: defaultHandler('supply', 'decrease')
}, options);
- this.metaLevel = options.metaLevel;
+
+ this.demandProjectionNames = ensureMatchingProjectionNames(demandSpecs);
+ this.supplyProjectionNames = ensureMatchingProjectionNames(supplySpecs);
+
+ this.demandSpecs = demandSpecs;
+ this.supplySpecs = supplySpecs;
+
+ this.demandPatterns = demandSpecs.map(function (s) { return Trie.projectionToPattern(s); });
+ this.supplyPatterns = supplySpecs.map(function (s) { return Trie.projectionToPattern(s); });
+
+ this.demandMetaLevel =
+ (options.demandMetaLevel === null) ? options.metaLevel : options.demandMetaLevel;
+ this.supplyMetaLevel =
+ (options.supplyMetaLevel === null) ? options.metaLevel : options.supplyMetaLevel;
+
+ function metaWrap(n) {
+ return function (s) { return Patch.prependAtMeta(s, n); };
+ }
+ this.demandProjections = demandSpecs.map(metaWrap(this.demandMetaLevel));
+ this.supplyProjections = supplySpecs.map(metaWrap(this.supplyMetaLevel));
+
this.onDemandIncrease = options.onDemandIncrease;
+ this.onDemandDecrease = options.onDemandDecrease;
+ this.onSupplyIncrease = options.onSupplyIncrease;
this.onSupplyDecrease = options.onSupplyDecrease;
- this.demandSpec = demandSpec;
- this.supplySpec = supplySpec;
- this.demandPattern = Trie.projectionToPattern(demandSpec);
- this.supplyPattern = Trie.projectionToPattern(supplySpec);
- this.demandProjection = Patch.prependAtMeta(demandSpec, this.metaLevel);
- this.supplyProjection = Patch.prependAtMeta(supplySpec, this.metaLevel);
- this.demandProjectionNames = Trie.projectionNames(this.demandProjection);
- this.supplyProjectionNames = Trie.projectionNames(this.supplyProjection);
+
this.currentDemand = Immutable.Set();
this.currentSupply = Immutable.Set();
}
DemandMatcher.prototype.boot = function () {
- return Patch.sub(this.demandPattern, this.metaLevel)
- .andThen(Patch.sub(this.supplyPattern, this.metaLevel));
+ var p = Patch.emptyPatch;
+ function extend(ml) {
+ return function (pat) { p = p.andThen(Patch.sub(pat, ml)); };
+ }
+ this.demandPatterns.forEach(extend(this.demandMetaLevel));
+ this.supplyPatterns.forEach(extend(this.supplyMetaLevel));
+ return p;
};
DemandMatcher.prototype.handleEvent = function (e) {
@@ -39,26 +83,29 @@ DemandMatcher.prototype.handleEvent = function (e) {
}
};
+DemandMatcher.prototype.extractKeys = function (trie, projections, keyCount, whichSide) {
+ var ks = Immutable.Set();
+ projections.forEach(function (proj) {
+ var moreKs = Trie.trieKeys(Trie.project(trie, proj), keyCount);
+ if (!moreKs) {
+ throw new Error("Syndicate: wildcard "+whichSide+" detected:\n" +
+ JSON.stringify(proj) + "\n" +
+ Trie.prettyTrie(trie));
+ }
+ ks = ks.union(moreKs);
+ });
+ return ks;
+};
+
DemandMatcher.prototype.handlePatch = function (p) {
var self = this;
var dN = self.demandProjectionNames.length;
var sN = self.supplyProjectionNames.length;
- var addedDemand = Trie.trieKeys(Trie.project(p.added, self.demandProjection), dN);
- var removedDemand = Trie.trieKeys(Trie.project(p.removed, self.demandProjection), dN);
- var addedSupply = Trie.trieKeys(Trie.project(p.added, self.supplyProjection), sN);
- var removedSupply = Trie.trieKeys(Trie.project(p.removed, self.supplyProjection), sN);
-
- if (addedDemand === null) {
- throw new Error("Syndicate: wildcard demand detected:\n" +
- self.demandSpec + "\n" +
- p.pretty());
- }
- if (addedSupply === null) {
- throw new Error("Syndicate: wildcard supply detected:\n" +
- self.supplySpec + "\n" +
- p.pretty());
- }
+ var addedDemand = this.extractKeys(p.added, self.demandProjections, dN, 'demand');
+ var removedDemand = this.extractKeys(p.removed, self.demandProjections, dN, 'demand');
+ var addedSupply = this.extractKeys(p.added, self.supplyProjections, sN, 'supply');
+ var removedSupply = this.extractKeys(p.removed, self.supplyProjections, sN, 'supply');
self.currentSupply = self.currentSupply.union(addedSupply);
self.currentDemand = self.currentDemand.subtract(removedDemand);
@@ -68,6 +115,17 @@ DemandMatcher.prototype.handlePatch = function (p) {
self.onSupplyDecrease(Trie.captureToObject(captures, self.supplyProjectionNames));
}
});
+ addedSupply.forEach(function (captures) {
+ if (!self.currentDemand.has(captures)) {
+ self.onSupplyIncrease(Trie.captureToObject(captures, self.supplyProjectionNames));
+ }
+ });
+
+ removedDemand.forEach(function (captures) {
+ if (self.currentSupply.has(captures)) {
+ self.onDemandDecrease(Trie.captureToObject(captures, self.demandProjectionNames));
+ }
+ });
addedDemand.forEach(function (captures) {
if (!self.currentSupply.has(captures)) {
self.onDemandIncrease(Trie.captureToObject(captures, self.demandProjectionNames));
diff --git a/js/src/dom-driver.js b/js/src/dom-driver.js
index cc31969..e63dcfa 100644
--- a/js/src/dom-driver.js
+++ b/js/src/dom-driver.js
@@ -10,14 +10,14 @@ var Dataspace = Dataspace_.Dataspace;
var __ = Dataspace_.__;
var _$ = Dataspace_._$;
-var DOM = Struct.makeStructureConstructor('DOM', ['selector', 'fragmentClass', 'fragmentSpec']);
+var DOM = Struct.makeConstructor('DOM', ['selector', 'fragmentClass', 'fragmentSpec']);
function spawnDOMDriver(domWrapFunction, jQueryWrapFunction) {
domWrapFunction = domWrapFunction || DOM;
var spec = domWrapFunction(_$('selector'), _$('fragmentClass'), _$('fragmentSpec'));
Dataspace.spawn(
- new DemandMatcher(spec,
- Patch.advertise(spec), // TODO: are the embedded captures problematic here? If not, why not?
+ new DemandMatcher([spec],
+ [Patch.advertise(spec)],
{
onDemandIncrease: function (c) {
Dataspace.spawn(new DOMFragment(c.selector,
@@ -106,7 +106,7 @@ DOMFragment.prototype.interpretSpec = function (spec) {
var attrs = hasAttrs ? spec[1] : {};
var kidIndex = hasAttrs ? 2 : 1;
- // Wow! Such XSS! Many hacks! So vulnerability! Amaze!
+ // TODO: Wow! Such XSS! Many hacks! So vulnerability! Amaze!
var n = document.createElement(tagName);
for (var i = 0; i < attrs.length; i++) {
n.setAttribute(attrs[i][0], attrs[i][1]);
@@ -124,6 +124,9 @@ DOMFragment.prototype.buildNodes = function () {
var self = this;
var nodes = [];
$(self.selector).each(function (index, domNode) {
+ if (!(self.fragmentSpec instanceof Syndicate.Seal)) {
+ throw new Error("DOM fragmentSpec not contained in a Syndicate.Seal: " + JSON.stringify(self.fragmentSpec));
+ }
var n = self.interpretSpec(self.fragmentSpec.sealContents);
if ('classList' in n) {
n.classList.add(self.fragmentClass);
diff --git a/js/src/jquery-driver.js b/js/src/jquery-driver.js
index 9a7c883..8f6ab2d 100644
--- a/js/src/jquery-driver.js
+++ b/js/src/jquery-driver.js
@@ -8,14 +8,14 @@ var Dataspace = Dataspace_.Dataspace;
var __ = Dataspace_.__;
var _$ = Dataspace_._$;
-var jQueryEvent = Struct.makeStructureConstructor('jQueryEvent', ['selector', 'eventName', 'eventValue']);
+var jQueryEvent = Struct.makeConstructor('jQueryEvent', ['selector', 'eventName', 'eventValue']);
function spawnJQueryDriver(baseSelector, metaLevel, wrapFunction) {
metaLevel = metaLevel || 0;
wrapFunction = wrapFunction || jQueryEvent;
Dataspace.spawn(
- new DemandMatcher(Patch.observe(wrapFunction(_$('selector'), _$('eventName'), __)),
- Patch.advertise(wrapFunction(_$('selector'), _$('eventName'), __)),
+ new DemandMatcher([Patch.observe(wrapFunction(_$('selector'), _$('eventName'), __))],
+ [Patch.advertise(wrapFunction(_$('selector'), _$('eventName'), __))],
{
metaLevel: metaLevel,
onDemandIncrease: function (c) {
diff --git a/js/src/main.js b/js/src/main.js
index 5cf862b..f2130ee 100644
--- a/js/src/main.js
+++ b/js/src/main.js
@@ -29,8 +29,7 @@ module.exports.Ack = require('./ack.js').Ack;
module.exports.RandomID = require('./randomid.js');
module.exports.DOM = require("./dom-driver.js");
module.exports.JQuery = require("./jquery-driver.js");
-// module.exports.RoutingTableWidget = require("./routing-table-widget.js");
-// module.exports.WebSocket = require("./websocket-driver.js");
+module.exports.WakeDetector = require("./wake-detector-driver.js");
module.exports.Reflect = require("./reflect.js");
module.exports.Patch = require("./patch.js");
diff --git a/js/src/mux.js b/js/src/mux.js
index ff4ba4e..5058bf1 100644
--- a/js/src/mux.js
+++ b/js/src/mux.js
@@ -45,7 +45,7 @@ Mux.prototype.updateStream = function (pid, unclampedPatch) {
};
var atMetaEverything = Trie.compilePattern(true, Patch.atMeta(Trie.__));
-var atMetaBranchKeys = Immutable.List([[Patch.atMeta.meta.arguments.length, Patch.atMeta.meta]]);
+var atMetaBranchKeys = Immutable.List([[Patch.atMeta.meta.arity, Patch.atMeta.meta]]);
var onlyMeta = Trie.trieSuccess(Immutable.Set.of("meta"));
function echoCancelledTrie(t) {
@@ -60,8 +60,7 @@ function computeEvents(oldMux, newMux, updateStreamResult) {
var deltaAggregate = updateStreamResult.deltaAggregate;
var deltaAggregateNoEcho = (actingPid === "meta")
? delta // because echo-cancellation means that meta-SCNs are always new information
- : new Patch.Patch(Trie.triePruneBranch(deltaAggregate.added, atMetaBranchKeys),
- Trie.triePruneBranch(deltaAggregate.removed, atMetaBranchKeys));
+ : deltaAggregate.withoutAtMeta();
var oldRoutingTable = oldMux.routingTable;
var newRoutingTable = newMux.routingTable;
var affectedPids =
@@ -95,8 +94,7 @@ function computeEvents(oldMux, newMux, updateStreamResult) {
function computeAffectedPids(routingTable, delta) {
var cover = Trie._union(delta.added, delta.removed);
- routingTable =
- Trie.trieStep(routingTable, Patch.observe.meta.arguments.length, Patch.observe.meta);
+ routingTable = Trie.trieStep(routingTable, Patch.observe.meta.arity, Patch.observe.meta);
return Trie.matchTrie(cover, routingTable, Immutable.Set(),
function (v1, v2, acc) { return acc.union(v2); });
}
diff --git a/js/src/patch.js b/js/src/patch.js
index 60973f3..8ee3339 100644
--- a/js/src/patch.js
+++ b/js/src/patch.js
@@ -16,9 +16,9 @@ var emptyPatch = new Patch(Trie.emptyTrie, Trie.emptyTrie);
var removeEverythingPatch = new Patch(Trie.emptyTrie, Trie.compilePattern(true, __));
var trueLabel = Trie.trieSuccess(true);
-var observe = Struct.makeStructureConstructor('observe', ['assertion']);
-var atMeta = Struct.makeStructureConstructor('atMeta', ['assertion']);
-var advertise = Struct.makeStructureConstructor('advertise', ['assertion']);
+var observe = Struct.makeConstructor('observe', ['assertion']);
+var atMeta = Struct.makeConstructor('at-meta', ['assertion']);
+var advertise = Struct.makeConstructor('advertise', ['assertion']);
function prependAtMeta(p, level) {
while (level--) {
@@ -30,7 +30,7 @@ function prependAtMeta(p, level) {
function stripAtMeta(p, level) {
while (level--) {
if (atMeta.isClassOf(p)) {
- p = p.assertion;
+ p = p[0];
} else {
return null;
}
@@ -97,11 +97,11 @@ Patch.prototype.isNonEmpty = function () {
};
Patch.prototype.hasAdded = function () {
- return this.added !== Trie.emptyTrie;
+ return !Trie.is_emptyTrie(this.added);
};
Patch.prototype.hasRemoved = function () {
- return this.removed !== Trie.emptyTrie;
+ return !Trie.is_emptyTrie(this.removed);
};
Patch.prototype.lift = function () {
@@ -196,7 +196,7 @@ function computePatch(oldBase, newBase) {
}
function biasedIntersection(object, subject) {
- subject = Trie.trieStep(subject, observe.meta.arguments.length, observe.meta);
+ subject = Trie.trieStep(subject, observe.meta.arity, observe.meta);
return Trie.intersect(object, subject, function (v1, v2) { return Trie.trieSuccess(v1); });
}
@@ -226,6 +226,27 @@ Patch.prototype.pretty = function () {
return ("<<<<<<<< Removed:\n" + Trie.prettyTrie(this.removed) + "\n" +
"======== Added:\n" + Trie.prettyTrie(this.added) + "\n" +
">>>>>>>>");
+};
+
+// Completely ignores success-values in t.
+Patch.prototype.prunedBy = function (t) {
+ return new Patch(Trie.subtract(this.added, t, function (v1, v2) { return Trie.emptyTrie; }),
+ Trie.subtract(this.removed, t, function (v1, v2) { return Trie.emptyTrie; }));
+};
+
+var atMetaEverything = Trie.compilePattern(true, atMeta.pattern);
+Patch.prototype.withoutAtMeta = function () {
+ return this.prunedBy(atMetaEverything);
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+Patch.prototype.toJSON = function () {
+ return [Trie.trieToJSON(this.added), Trie.trieToJSON(this.removed)];
+};
+
+function fromJSON(j) {
+ return new Patch(Trie.trieFromJSON(j[0]), Trie.trieFromJSON(j[1]));
}
///////////////////////////////////////////////////////////////////////////
@@ -251,3 +272,5 @@ module.exports.unpub = unpub;
module.exports.patchSeq = patchSeq;
module.exports.computePatch = computePatch;
module.exports.biasedIntersection = biasedIntersection;
+
+module.exports.fromJSON = fromJSON;
diff --git a/js/src/struct.js b/js/src/struct.js
index fddb12b..935dfce 100644
--- a/js/src/struct.js
+++ b/js/src/struct.js
@@ -1,61 +1,106 @@
"use strict";
// "Structures": Simple named-tuple-like records.
-// TODO: shore up $SyndicateMeta$, making it a proper object
var Immutable = require("immutable");
var $Special = require('./special.js');
-/* Defined here rather than in trie.js because we need it in makeStructureConstructor. */
+/* Defined here rather than in trie.js because we need it in makeConstructor. */
var __ = new $Special("wildcard"); /* wildcard marker */
-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 StructureType(label, arity) {
+ this.label = label;
+ this.arity = arity;
+ this.pattern = this.instantiate(Immutable.Repeat(__, arity).toArray());
-function makeStructureConstructor(label, argumentNames) {
- var $SyndicateMeta$ = {
- label: label,
- arguments: argumentNames
+ var self = this;
+ this.ctor = function () {
+ return self.instantiate(Array.prototype.slice.call(arguments));
};
- var ctor = function() {
- return instantiateStructure($SyndicateMeta$, arguments);
- };
- ctor.prototype._get = function(i) { return this[this.$SyndicateMeta$.arguments[i]]; };
- ctor.meta = $SyndicateMeta$;
- ctor.isClassOf = function (v) { return v && v.$SyndicateMeta$ === $SyndicateMeta$; };
- ctor.pattern = ctor.apply(null, Immutable.Repeat(__, argumentNames.length).toArray());
- return ctor;
+ this.ctor.meta = this;
+ this.ctor.pattern = this.pattern;
+ this.ctor.isClassOf = function (v) { return self.isClassOf(v); };
}
-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 makeConstructor(label, fieldNames) {
+ return new StructureType(label, fieldNames.length).ctor;
}
-function isStructure(s) {
- return (s !== null) && (typeof s === 'object') && ("$SyndicateMeta$" in s);
-}
+StructureType.prototype.equals = function (other) {
+ if (!(other instanceof StructureType)) return false;
+ return this.arity === other.arity && this.label === other.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]]);
+StructureType.prototype.instantiate = function (fields) {
+ return new Structure(this, fields);
+};
+
+StructureType.prototype.isClassOf = function (v) {
+ return v && (v instanceof Structure) && (v.meta.equals(this));
+};
+
+function Structure(meta, fields) {
+ if (!isStructureType(meta)) {
+ throw new Error("Structure: requires structure type");
}
- return result;
+ if (fields.length !== meta.arity) {
+ throw new Error("Structure: cannot instantiate meta "+JSON.stringify(meta.label)+
+ " expecting "+meta.arity+" fields with "+fields.length+" fields");
+ }
+ this.meta = meta;
+ this.length = meta.arity;
+ this.fields = fields.slice(0);
+ for (var i = 0; i < fields.length; i++) {
+ this[i] = fields[i];
+ }
+}
+
+function reviveStructs(j) {
+ if (Array.isArray(j)) {
+ return j.map(reviveStructs);
+ }
+
+ if ((j !== null) && typeof j === 'object') {
+ if ((typeof j['@type'] === 'string') && Array.isArray(j['fields'])) {
+ return (new StructureType(j['@type'], j['fields'].length)).instantiate(j['fields']);
+ } else {
+ for (var k in j) {
+ if (Object.prototype.hasOwnProperty.call(j, k)) {
+ j[k] = reviveStructs(j[k]);
+ }
+ }
+ return j;
+ }
+ }
+
+ return j;
+}
+
+function reviver(k, v) {
+ if (k === '') {
+ return reviveStructs(v);
+ }
+ return v;
+};
+
+Structure.prototype.toJSON = function () {
+ return { '@type': this.meta.label, 'fields': this.fields };
+};
+
+function isStructureType(v) {
+ return v && (v instanceof StructureType);
+}
+
+function isStructure(v) {
+ return v && (v instanceof Structure);
}
///////////////////////////////////////////////////////////////////////////
module.exports.__ = __;
-module.exports.instantiateStructure = instantiateStructure;
-module.exports.makeStructureConstructor = makeStructureConstructor;
-module.exports.isSyndicateMeta = isSyndicateMeta;
+module.exports.StructureType = StructureType;
+module.exports.makeConstructor = makeConstructor;
+module.exports.Structure = Structure;
+module.exports.reviveStructs = reviveStructs;
+module.exports.reviver = reviver;
+module.exports.isStructureType = isStructureType;
module.exports.isStructure = isStructure;
-module.exports.structureToArray = structureToArray;
diff --git a/js/src/trie.js b/js/src/trie.js
index bca7154..629c060 100644
--- a/js/src/trie.js
+++ b/js/src/trie.js
@@ -186,11 +186,10 @@ function compilePattern(v, p) {
}
if (Struct.isStructure(p)) {
- var args = p.$SyndicateMeta$.arguments;
- for (var i = args.length - 1; i >= 0; i--) {
- acc = walk(p[args[i]], acc);
+ for (var i = p.meta.arity - 1; i >= 0; i--) {
+ acc = walk(p[i], acc);
}
- return rseq(args.length, p.$SyndicateMeta$, acc);
+ return rseq(p.meta.arity, p.meta, acc);
}
if (p instanceof $Embedded) {
@@ -222,13 +221,10 @@ function matchPattern(v, p) {
if (p === __) return;
- if (Struct.isStructure(p)
- && Struct.isStructure(v)
- && (p.$SyndicateMeta$ === v.$SyndicateMeta$))
+ if (Struct.isStructure(p) && Struct.isStructure(v) && (p.meta.equals(v.meta)))
{
- var args = p.$SyndicateMeta$.arguments;
- for (var i = 0; i < args.length; i++) {
- walk(v[args[i]], p[args[i]]);
+ for (var i = 0; i < p.meta.arity; i++) {
+ walk(v[i], p[i]);
}
return;
}
@@ -431,8 +427,8 @@ function matchValue(r, v, failureResultOpt) {
r = rlookup(r, v.length, SOA);
vs = Immutable.List(v).concat(vs);
} else if (Struct.isStructure(v)) {
- r = rlookup(r, v.$SyndicateMeta$.arguments.length, v.$SyndicateMeta$);
- vs = Immutable.List(Struct.structureToArray(v, true)).concat(vs);
+ r = rlookup(r, v.meta.arity, v.meta);
+ vs = Immutable.List(v.fields).concat(vs);
} else {
r = rlookup(r, 0, v);
}
@@ -501,17 +497,19 @@ function appendTrie(m, mTailFn) {
}
}
-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);
-}
+// DANGEROUS: prefer subtract() instead.
+//
+// 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, arity, key) {
if (typeof key === 'undefined') {
@@ -550,8 +548,7 @@ function projectionNames(p) {
}
if (Struct.isStructure(p)) {
- var args = p.$SyndicateMeta$.arguments;
- for (var i = 0; i < args.length; i++) walk(p[args[i]]);
+ for (var i = 0; i < p.meta.arity; i++) walk(p[i]);
return;
}
}
@@ -576,12 +573,11 @@ function projectionToPattern(p) {
}
if (Struct.isStructure(p)) {
- var result = {"$SyndicateMeta$": p.$SyndicateMeta$};
- var args = p.$SyndicateMeta$.arguments;
- for (var i = 0; i < args.length; i++) {
- result[args[i]] = walk(p[args[i]]);
+ var resultFields = [];
+ for (var i = 0; i < p.meta.arity; i++) {
+ resultFields[i] = walk(p[i]);
}
- return result;
+ return new Struct.Structure(p.meta, resultFields);
}
return p;
@@ -652,15 +648,13 @@ function projectMany(t, wholeSpecs, projectSuccessOpt, combinerOpt) {
}
if (Struct.isStructure(spec)) {
- var arity = spec.$SyndicateMeta$.arguments.length;
- var key = spec.$SyndicateMeta$;
var intermediate = walk(isCapturing,
- rlookup(t, arity, key),
- Immutable.List(Struct.structureToArray(spec, true)),
+ rlookup(t, spec.meta.arity, spec.meta),
+ Immutable.List(spec.fields),
function (intermediate) {
return walk(isCapturing, intermediate, specsRest, kont);
});
- return isCapturing ? rseq(arity, key, intermediate) : intermediate;
+ return isCapturing ? rseq(spec.meta.arity, spec.meta, intermediate) : intermediate;
}
if (Array.isArray(spec)) {
@@ -687,7 +681,7 @@ function reconstructSequence(key, items) {
if (key === SOA) {
return items.toArray();
} else {
- return Struct.instantiateStructure(key, items);
+ return key.instantiate(items);
}
}
@@ -722,7 +716,7 @@ function trieKeys(m, takeCount0) {
if (result === false) return false; // break out of iteration
var piece;
- if (Struct.isSyndicateMeta(key) || key === SOA) { // TODO: this is sloppy
+ if (Struct.isStructureType(key) || key === SOA) {
piece = walk(k, arity, Immutable.List(), function (items, m1) {
var item = reconstructSequence(key, items);
return walk(m1, takeCount - 1, valsRev.unshift(item), kont);
@@ -798,10 +792,11 @@ function prettyTrie(m, initialIndent) {
}
acc.push(" ");
if (key === SOA) key = '<' + arity + '>';
- else if (Struct.isSyndicateMeta(key)) key = key.label + '<' + arity + '>';
+ else if (Struct.isStructureType(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);
+ else key = JSON.stringify(key);
+
+ if (typeof key === 'undefined') key = 'undefined';
acc.push(key);
walk(i + key.length + 1, k);
});
@@ -815,6 +810,78 @@ function prettyTrie(m, initialIndent) {
///////////////////////////////////////////////////////////////////////////
+function parenTypeToString(key) {
+ if (Struct.isStructureType(key)) {
+ return ':' + key.label;
+ } else {
+ return 'L';
+ }
+}
+
+function stringToParenType(arity, key) {
+ if (key[0] === ':') {
+ return new Struct.StructureType(key.slice(1), arity);
+ } else if (key === 'L') {
+ return SOA;
+ }
+ throw new Error("Unsupported JSON trie paren type: "+key);
+}
+
+function trieToJSON(t) {
+ if (is_emptyTrie(t)) { return []; }
+ if (t instanceof $Success) { return [true]; } // TODO: consider generalizing
+
+ // It's a $Branch.
+
+ var jParens = [];
+ var jAtoms = [];
+ t.edges.forEach(function (keymap, arity) {
+ keymap.forEach(function (k, key) {
+ var jk = trieToJSON(k);
+ if (Struct.isStructureType(key) || key === SOA) {
+ jParens.push([arity, parenTypeToString(key), jk]);
+ } else {
+ jAtoms.push([key, jk]);
+ }
+ });
+ });
+ return [jParens, trieToJSON(t.wild), jAtoms];
+}
+
+function badJSON(j) {
+ die("Cannot deserialize JSON trie: " + JSON.stringify(j));
+}
+
+function trieFromJSON(j) {
+ return decode(j);
+
+ function decode(j) {
+ if (!Array.isArray(j)) badJSON(j);
+
+ switch (j.length) {
+ case 0: return emptyTrie;
+ case 1: return rsuccess(true); // TODO: consider generalizing
+ case 3: {
+ var result = rcopybranch(expand(rwild(decode(j[1]))));
+ j[0].forEach(function (entry) {
+ var arity = entry[0];
+ if (typeof arity !== 'number') badJSON(j);
+ var key = stringToParenType(arity, entry[1]);
+ rupdate_inplace(result, arity, key, decode(entry[2]));
+ });
+ j[2].forEach(function (entry) {
+ var key = entry[0];
+ rupdate_inplace(result, 0, key, decode(entry[1]));
+ });
+ return collapse(result);
+ }
+ default: badJSON(j);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+
module.exports.__ = __;
module.exports.SOA = SOA;
module.exports.$Capture = $Capture;
@@ -833,7 +900,7 @@ module.exports.subtract = subtract;
module.exports.matchValue = matchValue;
module.exports.matchTrie = matchTrie;
module.exports.appendTrie = appendTrie;
-module.exports.triePruneBranch = triePruneBranch;
+// module.exports.triePruneBranch = triePruneBranch;
module.exports.trieStep = trieStep;
module.exports.trieSuccess = rsuccess;
module.exports.relabel = relabel;
@@ -847,6 +914,8 @@ module.exports.captureToObject = captureToObject;
module.exports.trieKeysToObjects = trieKeysToObjects;
module.exports.projectObjects = projectObjects;
module.exports.prettyTrie = prettyTrie;
+module.exports.trieToJSON = trieToJSON;
+module.exports.trieFromJSON = trieFromJSON;
// For testing
module.exports._testing = {
diff --git a/js/src/wake-detector-driver.js b/js/src/wake-detector-driver.js
new file mode 100644
index 0000000..1534e60
--- /dev/null
+++ b/js/src/wake-detector-driver.js
@@ -0,0 +1,47 @@
+"use strict";
+// Wake detector - notices when something (such as
+// suspension/sleeping!) has caused periodic activities to be
+// interrupted, and warns others about it
+// Inspired by http://blog.alexmaccaw.com/javascript-wake-event
+
+var Patch = require("./patch.js");
+var Struct = require('./struct.js');
+
+var Dataspace_ = require("./dataspace.js");
+var Dataspace = Dataspace_.Dataspace;
+var __ = Dataspace_.__;
+var _$ = Dataspace_._$;
+
+var wakeEvent = Struct.makeConstructor('wakeEvent', []);
+
+function spawnWakeDetector(periodOpt) {
+ Dataspace.spawn(new WakeDetector(periodOpt));
+}
+
+function WakeDetector(periodOpt) {
+ this.period = periodOpt || 10000;
+ this.mostRecentTrigger = +(new Date());
+ this.timerId = null;
+}
+
+WakeDetector.prototype.boot = function () {
+ var self = this;
+ this.timerId = setInterval(Dataspace.wrap(function () { self.trigger(); }), this.period);
+ return Patch.pub(wakeEvent());
+};
+
+WakeDetector.prototype.handleEvent = function (e) {};
+
+WakeDetector.prototype.trigger = function () {
+ var now = +(new Date());
+ if (now - this.mostRecentTrigger > this.period * 1.5) {
+ Dataspace.send(wakeEvent());
+ }
+ this.mostRecentTrigger = now;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+module.exports.spawnWakeDetector = spawnWakeDetector;
+module.exports.WakeDetector = WakeDetector;
+module.exports.wakeEvent = wakeEvent;
diff --git a/js/test/test-patch.js b/js/test/test-patch.js
index 4fe7ba6..6bca0c0 100644
--- a/js/test/test-patch.js
+++ b/js/test/test-patch.js
@@ -40,23 +40,23 @@ describe('basic patch compilation', function () {
[' <2> 1 2 {true}'],
[' ::: nothing']);
checkPrettyPatch(Patch.assert([1, 2], 1),
- [' atMeta<1> <2> 1 2 {true}'],
+ [' at-meta<1> <2> 1 2 {true}'],
[' ::: nothing']);
checkPrettyPatch(Patch.assert([1, 2], 2),
- [' atMeta<1> atMeta<1> <2> 1 2 {true}'],
+ [' at-meta<1> at-meta<1> <2> 1 2 {true}'],
[' ::: nothing']);
checkPrettyPatch(Patch.sub([1, 2], 0),
[' observe<1> <2> 1 2 {true}'],
[' ::: nothing']);
checkPrettyPatch(Patch.sub([1, 2], 1),
- [' atMeta<1> observe<1> <2> 1 2 {true}',
- ' observe<1> atMeta<1> <2> 1 2 {true}'],
+ [' at-meta<1> observe<1> <2> 1 2 {true}',
+ ' observe<1> at-meta<1> <2> 1 2 {true}'],
[' ::: nothing']);
checkPrettyPatch(Patch.sub([1, 2], 2),
- [' 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}'],
+ [' at-meta<1> at-meta<1> observe<1> <2> 1 2 {true}',
+ ' observe<1> at-meta<1> <2> 1 2 {true}',
+ ' observe<1> at-meta<1> at-meta<1> <2> 1 2 {true}'],
[' ::: nothing']);
});
});
@@ -111,14 +111,14 @@ describe('patch sequencing', function () {
describe('patch lifting', function () {
it('should basically work', function () {
checkPrettyPatch(Patch.assert([1, 2]).lift(),
- [' atMeta<1> <2> 1 2 {true}'],
+ [' at-meta<1> <2> 1 2 {true}'],
[' ::: nothing']);
checkPrettyPatch(Patch.sub([1, 2]).lift(),
- [' atMeta<1> observe<1> <2> 1 2 {true}'],
+ [' at-meta<1> observe<1> <2> 1 2 {true}'],
[' ::: nothing']);
checkPrettyPatch(Patch.assert([1, 2]).andThen(Patch.assert(Patch.atMeta([1, 2]))).lift(),
- [' atMeta<1> atMeta<1> <2> 1 2 {true}',
- ' <2> 1 2 {true}'],
+ [' at-meta<1> at-meta<1> <2> 1 2 {true}',
+ ' <2> 1 2 {true}'],
[' ::: nothing']);
});
});
diff --git a/js/test/test-route.js b/js/test/test-route.js
index ef1d7dc..5e5b6c1 100644
--- a/js/test/test-route.js
+++ b/js/test/test-route.js
@@ -402,7 +402,7 @@ describe("calls to matchPattern", function () {
});
it("matches structures", function () {
- var ctor = Struct.makeStructureConstructor('foo', ['bar', 'zot']);
+ var ctor = Struct.makeConstructor('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:
@@ -466,75 +466,76 @@ describe('intersect', function () {
});
});
-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']);
- });
+// describe('triePruneBranch', function () {
+// it('should not affect empty trie', function () {
+// checkPrettyTrie(r.triePruneBranch(r.emptyTrie, Immutable.List([])), [' ::: nothing']);
+// checkPrettyTrie(r.triePruneBranch(r.emptyTrie, Immutable.List([r.SOA])), [' ::: nothing']);
+// checkPrettyTrie(r.triePruneBranch(r.emptyTrie, Immutable.List(["x"])), [' ::: nothing']);
+// checkPrettyTrie(r.triePruneBranch(r.emptyTrie, Immutable.List([r.SOA, "x"])), [' ::: nothing']);
+// });
+//
+// it('should leave a hole in a full trie', function () {
+// var full = r.compilePattern(true, r.__);
+// checkPrettyTrie(r.triePruneBranch(full, Immutable.List([])), [' ::: nothing']);
+// checkPrettyTrie(r.triePruneBranch(full, Immutable.List([[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([[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}']);
+// });
+// });
- 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([[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([[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}']);
- });
-});
-
-describe('makeStructureConstructor', function () {
+describe('makeConstructor', function () {
it('should produce the right metadata', function () {
- var ctor = Struct.makeStructureConstructor('foo', ['bar', 'zot']);
+ var ctor = Struct.makeConstructor('foo', ['bar', 'zot']);
var inst = ctor(123, 234);
- expect(inst.$SyndicateMeta$.label).to.equal('foo');
- expect(inst.$SyndicateMeta$.arguments).to.eql(['bar', 'zot']);
+ expect(inst.meta.label).to.equal('foo');
+ expect(inst.meta.arity).to.equal(2);
+ expect(ctor.meta).to.equal(inst.meta);
});
it('should produce the right instance data', function () {
- var ctor = Struct.makeStructureConstructor('foo', ['bar', 'zot']);
+ var ctor = Struct.makeConstructor('foo', ['bar', 'zot']);
var inst = ctor(123, 234);
- expect(inst.bar).to.equal(123);
- expect(inst.zot).to.equal(234);
+ expect(inst[0]).to.equal(123);
+ expect(inst[1]).to.equal(234);
});
it('should work with compilePattern and matchValue', function () {
var sA = Immutable.Set(["A"]);
- var ctor = Struct.makeStructureConstructor('foo', ['bar', 'zot']);
+ var ctor = Struct.makeConstructor('foo', ['bar', 'zot']);
var inst = ctor(123, 234);
var x = r.compilePattern(sA, ctor(123, r.__));
checkPrettyTrie(x, [' foo<2> 123 ★ {["A"]}']);
diff --git a/js/test/test-syndicate.js b/js/test/test-syndicate.js
index 91b5edb..7588c04 100644
--- a/js/test/test-syndicate.js
+++ b/js/test/test-syndicate.js
@@ -105,10 +105,10 @@ describe("nested actor with an echoey protocol", function () {
}, ['<<<<<<<< Removed:\n'+
' ::: nothing\n'+
'======== Added:\n'+
- ' atMeta<1> "X" {["meta"]}\n'+
+ ' at-meta<1> "X" {["meta"]}\n'+
'>>>>>>>>',
'<<<<<<<< Removed:\n'+
- ' atMeta<1> "X" {["meta"]}\n'+
+ ' at-meta<1> "X" {["meta"]}\n'+
'======== Added:\n'+
' ::: nothing\n'+
'>>>>>>>>']);