Implement Syndicate/js broker-client and chat app.
Support capturing with a pattern in the Syndicate/js DSL: ($foo = bar()) Struct has been cleaned up, and now offers proper Javascript objects for its prefab-like structures. These can serialize and deserialize themselves to/from JSON. They behave like prefabs in that two StructureTypes created with the same label and arity behave identically wrt Dataspaces and Tries. Sadly, prefab field names had to go in order to support this. Facets now track and terminate their children upon termination. This is experimental; I suspect it is required for nested durings. DemandMatcher can now support multiple specs, but this is less useful than you might think since it tracks supply and demand quite naively. It would have to have (surprise, surprise!) a mux-like structure to do the job properly! Added WakeDetector to main.js; adding the broker client will have to wait until it is turned into a proper module in the src/ directory.
This commit is contained in:
parent
bbca582b98
commit
8546e93e5d
|
@ -122,7 +122,7 @@ var modifiedSourceActions = {
|
||||||
var label = maybeLabel.numChildren === 1
|
var label = maybeLabel.numChildren === 1
|
||||||
? maybeLabel.children[0].interval.contents
|
? maybeLabel.children[0].interval.contents
|
||||||
: JSON.stringify(typeName.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) + ');';
|
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!
|
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) {
|
identifier: function(_name) {
|
||||||
var i = this.interval.contents;
|
var i = this.interval.contents;
|
||||||
if (i[0] === '$') {
|
if (i[0] === '$' && i.length > 1) {
|
||||||
switch (this.args.mode) {
|
switch (this.args.mode) {
|
||||||
case 'pattern': this.args.acc.push('_'); break;
|
case 'pattern': this.args.acc.push('_'); break;
|
||||||
case 'instantiated': this.args.acc.push(i.slice(1)); break;
|
case 'instantiated': this.args.acc.push(i.slice(1)); break;
|
||||||
case 'projection': this.args.acc.push('(Syndicate._$(' + JSON.stringify(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);
|
default: throw new Error('Unexpected buildSubscription mode ' + this.args.mode);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.args.acc.push(i);
|
this.args.acc.push(i);
|
||||||
|
@ -328,7 +349,7 @@ semantics.addAttribute('bindings', {
|
||||||
semantics.addOperation('pushBindings(accumulator)', {
|
semantics.addOperation('pushBindings(accumulator)', {
|
||||||
identifier: function(_name) {
|
identifier: function(_name) {
|
||||||
var i = this.interval.contents;
|
var i = this.interval.contents;
|
||||||
if (i[0] === '$') {
|
if (i[0] === '$' && i.length > 1) {
|
||||||
this.args.accumulator.push(i.slice(1));
|
this.args.accumulator.push(i.slice(1));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
|
@ -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")
|
||||||
|
};
|
|
@ -0,0 +1,55 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Syndicate: Chat</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="style.css" rel="stylesheet">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script> <!-- TODO: ??? -->
|
||||||
|
<script src="../../third-party/jquery-2.2.0.min.js"></script>
|
||||||
|
<script src="../../dist/syndicatecompiler.js"></script>
|
||||||
|
<script src="../../dist/syndicate.js"></script>
|
||||||
|
<script src="codec.js"></script>
|
||||||
|
<script src="broker-client.js"></script>
|
||||||
|
<script type="text/syndicate-js" src="index.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section>
|
||||||
|
<form name="nym_form">
|
||||||
|
<fieldset>
|
||||||
|
<label class="control-label" for="wsurl">Server:</label>
|
||||||
|
<input type="text" id="wsurl" name="wsurl" value="ws://localhost:8000/">
|
||||||
|
|
||||||
|
<label class="control-label" for="nym">Nym:</label>
|
||||||
|
<input type="text" id="nym" name="nym" value="">
|
||||||
|
|
||||||
|
<label class="control-label" for="status">Status:</label>
|
||||||
|
<input type="text" id="status" name="status" value="">
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<section id="messages">
|
||||||
|
<h1>Messages</h1>
|
||||||
|
<div id="chat_output"></div>
|
||||||
|
</section>
|
||||||
|
<section id="active_users">
|
||||||
|
<h1>Active Users</h1>
|
||||||
|
<ul id="nymlist"></ul>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<form id="chat_form" name="chat_form">
|
||||||
|
<fieldset>
|
||||||
|
<input type="text" id="chat_input" name="chat_input" value="" autocomplete="off">
|
||||||
|
<button id="send_chat">Send</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<pre id="ds-state"></pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -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 = $("<span/>").text((new Date()).toGMTString()).addClass("timestamp");
|
||||||
|
var item = $("<div/>").append([stamp].concat(item));
|
||||||
|
var o = $("#chat_output");
|
||||||
|
o.append(item);
|
||||||
|
o[0].scrollTop = o[0].scrollHeight;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function outputState(state) {
|
||||||
|
outputItem([$("<span/>").text(state).addClass(state).addClass("state")])
|
||||||
|
.addClass("state_" + state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function outputUtterance(who, what) {
|
||||||
|
outputItem([$("<span/>").text(who).addClass("nym"),
|
||||||
|
$("<span/>").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));
|
||||||
|
// });
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ $(document).ready(function () {
|
||||||
handleEvent: function (e) {
|
handleEvent: function (e) {
|
||||||
if (e.type === 'message'
|
if (e.type === 'message'
|
||||||
&& Syndicate.JQuery.jQueryEvent.isClassOf(e.message)
|
&& Syndicate.JQuery.jQueryEvent.isClassOf(e.message)
|
||||||
&& e.message.selector === '#clicker')
|
&& e.message[0] === '#clicker')
|
||||||
{
|
{
|
||||||
var r = $('#result');
|
var r = $('#result');
|
||||||
r.html(Number(r.html()) + 1);
|
r.html(Number(r.html()) + 1);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var beep = Syndicate.Struct.makeStructureConstructor('beep', ['counter']);
|
var beep = Syndicate.Struct.makeConstructor('beep', ['counter']);
|
||||||
|
|
||||||
var G;
|
var G;
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
@ -26,7 +26,7 @@ $(document).ready(function () {
|
||||||
boot: function () { return sub(beep.pattern); },
|
boot: function () { return sub(beep.pattern); },
|
||||||
handleEvent: function (e) {
|
handleEvent: function (e) {
|
||||||
if (e.type === 'message') {
|
if (e.type === 'message') {
|
||||||
console.log("beep!", e.message.counter);
|
console.log("beep!", e.message[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,9 +8,9 @@ var __ = Syndicate.__;
|
||||||
var _$ = Syndicate._$;
|
var _$ = Syndicate._$;
|
||||||
|
|
||||||
var jQueryEvent = Syndicate.JQuery.jQueryEvent;
|
var jQueryEvent = Syndicate.JQuery.jQueryEvent;
|
||||||
var fieldContents = Syndicate.Struct.makeStructureConstructor('fieldContents', ['text', 'pos']);
|
var fieldContents = Syndicate.Struct.makeConstructor('fieldContents', ['text', 'pos']);
|
||||||
var highlight = Syndicate.Struct.makeStructureConstructor('highlight', ['state']);
|
var highlight = Syndicate.Struct.makeConstructor('highlight', ['state']);
|
||||||
var fieldCommand = Syndicate.Struct.makeStructureConstructor('fieldCommand', ['detail']);
|
var fieldCommand = Syndicate.Struct.makeConstructor('fieldCommand', ['detail']);
|
||||||
|
|
||||||
function escapeText(text) {
|
function escapeText(text) {
|
||||||
text = text.replace(/&/g, '&');
|
text = text.replace(/&/g, '&');
|
||||||
|
@ -47,7 +47,7 @@ function spawnGui() {
|
||||||
var self = this;
|
var self = this;
|
||||||
switch (e.type) {
|
switch (e.type) {
|
||||||
case "message":
|
case "message":
|
||||||
var event = e.message.eventValue;
|
var event = e.message[2];
|
||||||
var keycode = event.keyCode;
|
var keycode = event.keyCode;
|
||||||
var character = String.fromCharCode(event.charCode);
|
var character = String.fromCharCode(event.charCode);
|
||||||
if (keycode === 37 /* left */) {
|
if (keycode === 37 /* left */) {
|
||||||
|
@ -105,7 +105,7 @@ function spawnModel() {
|
||||||
|
|
||||||
handleEvent: function (e) {
|
handleEvent: function (e) {
|
||||||
if (e.type === "message" && fieldCommand.isClassOf(e.message)) {
|
if (e.type === "message" && fieldCommand.isClassOf(e.message)) {
|
||||||
var command = e.message.detail;
|
var command = e.message[0];
|
||||||
if (command === "cursorLeft") {
|
if (command === "cursorLeft") {
|
||||||
this.cursorPos--;
|
this.cursorPos--;
|
||||||
if (this.cursorPos < 0)
|
if (this.cursorPos < 0)
|
||||||
|
|
|
@ -5,7 +5,7 @@ var Dataspace = require('./dataspace.js').Dataspace;
|
||||||
var Struct = require('./struct.js');
|
var Struct = require('./struct.js');
|
||||||
var Patch = require('./patch.js');
|
var Patch = require('./patch.js');
|
||||||
|
|
||||||
var ack = Struct.makeStructureConstructor('ack', ['id']);
|
var ack = Struct.makeConstructor('ack', ['id']);
|
||||||
|
|
||||||
function Ack(metaLevel, id) {
|
function Ack(metaLevel, id) {
|
||||||
this.metaLevel = metaLevel || 0;
|
this.metaLevel = metaLevel || 0;
|
||||||
|
@ -26,7 +26,7 @@ Ack.prototype.check = function (e) {
|
||||||
if (!this.done) {
|
if (!this.done) {
|
||||||
if (e.type === 'message') {
|
if (e.type === 'message') {
|
||||||
var m = Patch.stripAtMeta(e.message, this.metaLevel);
|
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.disarm();
|
||||||
this.done = true;
|
this.done = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,17 @@ function Actor(state, bootFn) {
|
||||||
this.mux = new Mux.Mux();
|
this.mux = new Mux.Mux();
|
||||||
|
|
||||||
this.boot = function() {
|
this.boot = function() {
|
||||||
bootFn.call(this.state);
|
var self = this;
|
||||||
this.checkForTermination();
|
withCurrentFacet(null, function () {
|
||||||
|
bootFn.call(self.state);
|
||||||
|
});
|
||||||
|
self.checkForTermination();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Actor.prototype.handleEvent = function(e) {
|
Actor.prototype.handleEvent = function(e) {
|
||||||
this.facets.forEach(function (f) {
|
this.facets.forEach(function (f) {
|
||||||
f.handleEvent(e);
|
withCurrentFacet(f, function () { f.handleEvent(e); });
|
||||||
});
|
});
|
||||||
this.checkForTermination();
|
this.checkForTermination();
|
||||||
};
|
};
|
||||||
|
@ -56,14 +59,32 @@ function Facet(actor) {
|
||||||
this.endpoints = Immutable.Map();
|
this.endpoints = Immutable.Map();
|
||||||
this.initBlocks = Immutable.List();
|
this.initBlocks = Immutable.List();
|
||||||
this.doneBlocks = 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) {
|
Facet.prototype.handleEvent = function(e) {
|
||||||
var facet = this;
|
var facet = this;
|
||||||
this.endpoints.forEach(function(endpoint) {
|
facet.endpoints.forEach(function(endpoint) {
|
||||||
endpoint.handlerFn.call(facet.actor.state, e);
|
endpoint.handlerFn.call(facet.actor.state, e);
|
||||||
});
|
});
|
||||||
this.refresh();
|
facet.refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
Facet.prototype.addAssertion = function(assertionFn) {
|
Facet.prototype.addAssertion = function(assertionFn) {
|
||||||
|
@ -157,6 +178,9 @@ Facet.prototype.refresh = function() {
|
||||||
Facet.prototype.completeBuild = function() {
|
Facet.prototype.completeBuild = function() {
|
||||||
var facet = this;
|
var facet = this;
|
||||||
this.actor.addFacet(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); });
|
this.initBlocks.forEach(function(b) { b.call(facet.actor.state); });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,8 +193,14 @@ Facet.prototype.terminate = function() {
|
||||||
});
|
});
|
||||||
Dataspace.stateChange(aggregate);
|
Dataspace.stateChange(aggregate);
|
||||||
this.endpoints = Immutable.Map();
|
this.endpoints = Immutable.Map();
|
||||||
|
if (this.parent) {
|
||||||
|
this.parent.children = this.parent.children.remove(this);
|
||||||
|
}
|
||||||
this.actor.removeFacet(this);
|
this.actor.removeFacet(this);
|
||||||
this.doneBlocks.forEach(function(b) { b.call(facet.actor.state); });
|
this.doneBlocks.forEach(function(b) { b.call(facet.actor.state); });
|
||||||
|
this.children.forEach(function (child) {
|
||||||
|
child.terminate();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
|
@ -41,6 +41,7 @@ function Dataspace(bootFn) {
|
||||||
|
|
||||||
// Class state and methods
|
// Class state and methods
|
||||||
|
|
||||||
|
Dataspace.noisy = false;
|
||||||
Dataspace.stack = Immutable.List();
|
Dataspace.stack = Immutable.List();
|
||||||
|
|
||||||
Dataspace.current = function () {
|
Dataspace.current = function () {
|
||||||
|
@ -140,7 +141,7 @@ Dataspace.prototype.asChild = function (pid, f, omitLivenessCheck) {
|
||||||
Dataspace.prototype.kill = function (pid, exn) {
|
Dataspace.prototype.kill = function (pid, exn) {
|
||||||
if (exn && exn.stack) {
|
if (exn && exn.stack) {
|
||||||
console.log("Process exiting", pid, exn, exn.stack);
|
console.log("Process exiting", pid, exn, exn.stack);
|
||||||
} else {
|
} else if (exn || Dataspace.noisy) {
|
||||||
console.log("Process exiting", pid, exn);
|
console.log("Process exiting", pid, exn);
|
||||||
}
|
}
|
||||||
var p = this.processTable.get(pid);
|
var p = this.processTable.get(pid);
|
||||||
|
@ -222,53 +223,53 @@ Dataspace.prototype.interpretAction = function (pid, action) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'stateChange':
|
case 'stateChange':
|
||||||
var oldMux = this.mux.shallowCopy();
|
var oldMux = this.mux.shallowCopy();
|
||||||
this.deliverPatches(oldMux, this.mux.updateStream(pid, action.patch));
|
this.deliverPatches(oldMux, this.mux.updateStream(pid, action.patch));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 'message':
|
case 'message':
|
||||||
if (Patch.observe.isClassOf(action.message)) {
|
if (Patch.observe.isClassOf(action.message)) {
|
||||||
console.warn('Process ' + pid + ' send message containing query', action.message);
|
console.warn('Process ' + pid + ' send message containing query', action.message);
|
||||||
}
|
}
|
||||||
if (pid !== 'meta' && Patch.atMeta.isClassOf(action.message)) {
|
if (pid !== 'meta' && Patch.atMeta.isClassOf(action.message)) {
|
||||||
Dataspace.send(action.message.assertion);
|
Dataspace.send(action.message[0]);
|
||||||
} else {
|
} else {
|
||||||
this.mux.routeMessage(action.message).forEach(function (pid) {
|
this.mux.routeMessage(action.message).forEach(function (pid) {
|
||||||
self.deliverEvent(pid, action);
|
self.deliverEvent(pid, action);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 'spawn':
|
case 'spawn':
|
||||||
var oldMux = this.mux.shallowCopy();
|
var oldMux = this.mux.shallowCopy();
|
||||||
var p = { behavior: action.behavior };
|
var p = { behavior: action.behavior };
|
||||||
var pid = this.mux.nextPid;
|
var pid = this.mux.nextPid;
|
||||||
this.processTable = this.processTable.set(pid, p);
|
this.processTable = this.processTable.set(pid, p);
|
||||||
var initialPatch = Patch.emptyPatch;
|
var initialPatch = Patch.emptyPatch;
|
||||||
if (p.behavior.boot) {
|
if (p.behavior.boot) {
|
||||||
initialPatch = this.asChild(pid, function () { return p.behavior.boot() });
|
initialPatch = this.asChild(pid, function () { return p.behavior.boot() });
|
||||||
initialPatch = initialPatch || Patch.emptyPatch;
|
initialPatch = initialPatch || Patch.emptyPatch;
|
||||||
this.markRunnable(pid);
|
this.markRunnable(pid);
|
||||||
}
|
}
|
||||||
this.deliverPatches(oldMux, this.mux.addStream(initialPatch));
|
this.deliverPatches(oldMux, this.mux.addStream(initialPatch));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 'terminate':
|
case 'terminate':
|
||||||
var oldMux = this.mux.shallowCopy();
|
var oldMux = this.mux.shallowCopy();
|
||||||
this.deliverPatches(oldMux, this.mux.removeStream(pid));
|
this.deliverPatches(oldMux, this.mux.removeStream(pid));
|
||||||
console.log("Process exit complete", pid);
|
if (Dataspace.noisy) console.log("Process exit complete", pid);
|
||||||
this.processTable = this.processTable.remove(pid);
|
this.processTable = this.processTable.remove(pid);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 'terminateDataspace':
|
case 'terminateDataspace':
|
||||||
Dataspace.exit();
|
Dataspace.exit();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var exn = new Error("Action type " + action.type + " not understood");
|
var exn = new Error("Action type " + action.type + " not understood");
|
||||||
exn.action = action;
|
exn.action = action;
|
||||||
throw exn;
|
throw exn;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,34 +3,78 @@ var Trie = require('./trie.js');
|
||||||
var Patch = require('./patch.js');
|
var Patch = require('./patch.js');
|
||||||
var Util = require('./util.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({
|
options = Util.extend({
|
||||||
metaLevel: 0,
|
metaLevel: 0,
|
||||||
onDemandIncrease: function (captures) {
|
demandMetaLevel: null,
|
||||||
console.error("Syndicate: Unhandled increase in demand", captures);
|
supplyMetaLevel: null,
|
||||||
},
|
onDemandIncrease: defaultHandler('demand', 'increase'),
|
||||||
onSupplyDecrease: function (captures) {
|
onDemandDecrease: function (captures) {},
|
||||||
console.error("Syndicate: Unhandled decrease in supply", captures);
|
onSupplyIncrease: function (captures) {},
|
||||||
}
|
onSupplyDecrease: defaultHandler('supply', 'decrease')
|
||||||
}, options);
|
}, 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.onDemandIncrease = options.onDemandIncrease;
|
||||||
|
this.onDemandDecrease = options.onDemandDecrease;
|
||||||
|
this.onSupplyIncrease = options.onSupplyIncrease;
|
||||||
this.onSupplyDecrease = options.onSupplyDecrease;
|
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.currentDemand = Immutable.Set();
|
||||||
this.currentSupply = Immutable.Set();
|
this.currentSupply = Immutable.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
DemandMatcher.prototype.boot = function () {
|
DemandMatcher.prototype.boot = function () {
|
||||||
return Patch.sub(this.demandPattern, this.metaLevel)
|
var p = Patch.emptyPatch;
|
||||||
.andThen(Patch.sub(this.supplyPattern, this.metaLevel));
|
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) {
|
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) {
|
DemandMatcher.prototype.handlePatch = function (p) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var dN = self.demandProjectionNames.length;
|
var dN = self.demandProjectionNames.length;
|
||||||
var sN = self.supplyProjectionNames.length;
|
var sN = self.supplyProjectionNames.length;
|
||||||
var addedDemand = Trie.trieKeys(Trie.project(p.added, self.demandProjection), dN);
|
var addedDemand = this.extractKeys(p.added, self.demandProjections, dN, 'demand');
|
||||||
var removedDemand = Trie.trieKeys(Trie.project(p.removed, self.demandProjection), dN);
|
var removedDemand = this.extractKeys(p.removed, self.demandProjections, dN, 'demand');
|
||||||
var addedSupply = Trie.trieKeys(Trie.project(p.added, self.supplyProjection), sN);
|
var addedSupply = this.extractKeys(p.added, self.supplyProjections, sN, 'supply');
|
||||||
var removedSupply = Trie.trieKeys(Trie.project(p.removed, self.supplyProjection), sN);
|
var removedSupply = this.extractKeys(p.removed, self.supplyProjections, sN, 'supply');
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.currentSupply = self.currentSupply.union(addedSupply);
|
self.currentSupply = self.currentSupply.union(addedSupply);
|
||||||
self.currentDemand = self.currentDemand.subtract(removedDemand);
|
self.currentDemand = self.currentDemand.subtract(removedDemand);
|
||||||
|
@ -68,6 +115,17 @@ DemandMatcher.prototype.handlePatch = function (p) {
|
||||||
self.onSupplyDecrease(Trie.captureToObject(captures, self.supplyProjectionNames));
|
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) {
|
addedDemand.forEach(function (captures) {
|
||||||
if (!self.currentSupply.has(captures)) {
|
if (!self.currentSupply.has(captures)) {
|
||||||
self.onDemandIncrease(Trie.captureToObject(captures, self.demandProjectionNames));
|
self.onDemandIncrease(Trie.captureToObject(captures, self.demandProjectionNames));
|
||||||
|
|
|
@ -10,14 +10,14 @@ var Dataspace = Dataspace_.Dataspace;
|
||||||
var __ = Dataspace_.__;
|
var __ = 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) {
|
function spawnDOMDriver(domWrapFunction, jQueryWrapFunction) {
|
||||||
domWrapFunction = domWrapFunction || DOM;
|
domWrapFunction = domWrapFunction || DOM;
|
||||||
var spec = domWrapFunction(_$('selector'), _$('fragmentClass'), _$('fragmentSpec'));
|
var spec = domWrapFunction(_$('selector'), _$('fragmentClass'), _$('fragmentSpec'));
|
||||||
Dataspace.spawn(
|
Dataspace.spawn(
|
||||||
new DemandMatcher(spec,
|
new DemandMatcher([spec],
|
||||||
Patch.advertise(spec), // TODO: are the embedded captures problematic here? If not, why not?
|
[Patch.advertise(spec)],
|
||||||
{
|
{
|
||||||
onDemandIncrease: function (c) {
|
onDemandIncrease: function (c) {
|
||||||
Dataspace.spawn(new DOMFragment(c.selector,
|
Dataspace.spawn(new DOMFragment(c.selector,
|
||||||
|
@ -106,7 +106,7 @@ DOMFragment.prototype.interpretSpec = function (spec) {
|
||||||
var attrs = hasAttrs ? spec[1] : {};
|
var attrs = hasAttrs ? spec[1] : {};
|
||||||
var kidIndex = hasAttrs ? 2 : 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);
|
var n = document.createElement(tagName);
|
||||||
for (var i = 0; i < attrs.length; i++) {
|
for (var i = 0; i < attrs.length; i++) {
|
||||||
n.setAttribute(attrs[i][0], attrs[i][1]);
|
n.setAttribute(attrs[i][0], attrs[i][1]);
|
||||||
|
@ -124,6 +124,9 @@ DOMFragment.prototype.buildNodes = function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
var nodes = [];
|
var nodes = [];
|
||||||
$(self.selector).each(function (index, domNode) {
|
$(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);
|
var n = self.interpretSpec(self.fragmentSpec.sealContents);
|
||||||
if ('classList' in n) {
|
if ('classList' in n) {
|
||||||
n.classList.add(self.fragmentClass);
|
n.classList.add(self.fragmentClass);
|
||||||
|
|
|
@ -8,14 +8,14 @@ var Dataspace = Dataspace_.Dataspace;
|
||||||
var __ = Dataspace_.__;
|
var __ = 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) {
|
function spawnJQueryDriver(baseSelector, metaLevel, wrapFunction) {
|
||||||
metaLevel = metaLevel || 0;
|
metaLevel = metaLevel || 0;
|
||||||
wrapFunction = wrapFunction || jQueryEvent;
|
wrapFunction = wrapFunction || jQueryEvent;
|
||||||
Dataspace.spawn(
|
Dataspace.spawn(
|
||||||
new DemandMatcher(Patch.observe(wrapFunction(_$('selector'), _$('eventName'), __)),
|
new DemandMatcher([Patch.observe(wrapFunction(_$('selector'), _$('eventName'), __))],
|
||||||
Patch.advertise(wrapFunction(_$('selector'), _$('eventName'), __)),
|
[Patch.advertise(wrapFunction(_$('selector'), _$('eventName'), __))],
|
||||||
{
|
{
|
||||||
metaLevel: metaLevel,
|
metaLevel: metaLevel,
|
||||||
onDemandIncrease: function (c) {
|
onDemandIncrease: function (c) {
|
||||||
|
|
|
@ -29,8 +29,7 @@ module.exports.Ack = require('./ack.js').Ack;
|
||||||
module.exports.RandomID = require('./randomid.js');
|
module.exports.RandomID = require('./randomid.js');
|
||||||
module.exports.DOM = require("./dom-driver.js");
|
module.exports.DOM = require("./dom-driver.js");
|
||||||
module.exports.JQuery = require("./jquery-driver.js");
|
module.exports.JQuery = require("./jquery-driver.js");
|
||||||
// module.exports.RoutingTableWidget = require("./routing-table-widget.js");
|
module.exports.WakeDetector = require("./wake-detector-driver.js");
|
||||||
// module.exports.WebSocket = require("./websocket-driver.js");
|
|
||||||
module.exports.Reflect = require("./reflect.js");
|
module.exports.Reflect = require("./reflect.js");
|
||||||
|
|
||||||
module.exports.Patch = require("./patch.js");
|
module.exports.Patch = require("./patch.js");
|
||||||
|
|
|
@ -45,7 +45,7 @@ Mux.prototype.updateStream = function (pid, unclampedPatch) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var atMetaEverything = Trie.compilePattern(true, Patch.atMeta(Trie.__));
|
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"));
|
var onlyMeta = Trie.trieSuccess(Immutable.Set.of("meta"));
|
||||||
|
|
||||||
function echoCancelledTrie(t) {
|
function echoCancelledTrie(t) {
|
||||||
|
@ -60,8 +60,7 @@ function computeEvents(oldMux, newMux, updateStreamResult) {
|
||||||
var deltaAggregate = updateStreamResult.deltaAggregate;
|
var deltaAggregate = updateStreamResult.deltaAggregate;
|
||||||
var deltaAggregateNoEcho = (actingPid === "meta")
|
var deltaAggregateNoEcho = (actingPid === "meta")
|
||||||
? delta // because echo-cancellation means that meta-SCNs are always new information
|
? delta // because echo-cancellation means that meta-SCNs are always new information
|
||||||
: new Patch.Patch(Trie.triePruneBranch(deltaAggregate.added, atMetaBranchKeys),
|
: deltaAggregate.withoutAtMeta();
|
||||||
Trie.triePruneBranch(deltaAggregate.removed, atMetaBranchKeys));
|
|
||||||
var oldRoutingTable = oldMux.routingTable;
|
var oldRoutingTable = oldMux.routingTable;
|
||||||
var newRoutingTable = newMux.routingTable;
|
var newRoutingTable = newMux.routingTable;
|
||||||
var affectedPids =
|
var affectedPids =
|
||||||
|
@ -95,8 +94,7 @@ function computeEvents(oldMux, newMux, updateStreamResult) {
|
||||||
|
|
||||||
function computeAffectedPids(routingTable, delta) {
|
function computeAffectedPids(routingTable, delta) {
|
||||||
var cover = Trie._union(delta.added, delta.removed);
|
var cover = Trie._union(delta.added, delta.removed);
|
||||||
routingTable =
|
routingTable = Trie.trieStep(routingTable, Patch.observe.meta.arity, Patch.observe.meta);
|
||||||
Trie.trieStep(routingTable, Patch.observe.meta.arguments.length, Patch.observe.meta);
|
|
||||||
return Trie.matchTrie(cover, routingTable, Immutable.Set(),
|
return Trie.matchTrie(cover, routingTable, Immutable.Set(),
|
||||||
function (v1, v2, acc) { return acc.union(v2); });
|
function (v1, v2, acc) { return acc.union(v2); });
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ var emptyPatch = new Patch(Trie.emptyTrie, Trie.emptyTrie);
|
||||||
var removeEverythingPatch = new Patch(Trie.emptyTrie, Trie.compilePattern(true, __));
|
var removeEverythingPatch = new Patch(Trie.emptyTrie, Trie.compilePattern(true, __));
|
||||||
var trueLabel = Trie.trieSuccess(true);
|
var trueLabel = Trie.trieSuccess(true);
|
||||||
|
|
||||||
var observe = Struct.makeStructureConstructor('observe', ['assertion']);
|
var observe = Struct.makeConstructor('observe', ['assertion']);
|
||||||
var atMeta = Struct.makeStructureConstructor('atMeta', ['assertion']);
|
var atMeta = Struct.makeConstructor('at-meta', ['assertion']);
|
||||||
var advertise = Struct.makeStructureConstructor('advertise', ['assertion']);
|
var advertise = Struct.makeConstructor('advertise', ['assertion']);
|
||||||
|
|
||||||
function prependAtMeta(p, level) {
|
function prependAtMeta(p, level) {
|
||||||
while (level--) {
|
while (level--) {
|
||||||
|
@ -30,7 +30,7 @@ function prependAtMeta(p, level) {
|
||||||
function stripAtMeta(p, level) {
|
function stripAtMeta(p, level) {
|
||||||
while (level--) {
|
while (level--) {
|
||||||
if (atMeta.isClassOf(p)) {
|
if (atMeta.isClassOf(p)) {
|
||||||
p = p.assertion;
|
p = p[0];
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -97,11 +97,11 @@ Patch.prototype.isNonEmpty = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
Patch.prototype.hasAdded = function () {
|
Patch.prototype.hasAdded = function () {
|
||||||
return this.added !== Trie.emptyTrie;
|
return !Trie.is_emptyTrie(this.added);
|
||||||
};
|
};
|
||||||
|
|
||||||
Patch.prototype.hasRemoved = function () {
|
Patch.prototype.hasRemoved = function () {
|
||||||
return this.removed !== Trie.emptyTrie;
|
return !Trie.is_emptyTrie(this.removed);
|
||||||
};
|
};
|
||||||
|
|
||||||
Patch.prototype.lift = function () {
|
Patch.prototype.lift = function () {
|
||||||
|
@ -196,7 +196,7 @@ function computePatch(oldBase, newBase) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function biasedIntersection(object, subject) {
|
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); });
|
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" +
|
return ("<<<<<<<< Removed:\n" + Trie.prettyTrie(this.removed) + "\n" +
|
||||||
"======== Added:\n" + Trie.prettyTrie(this.added) + "\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.patchSeq = patchSeq;
|
||||||
module.exports.computePatch = computePatch;
|
module.exports.computePatch = computePatch;
|
||||||
module.exports.biasedIntersection = biasedIntersection;
|
module.exports.biasedIntersection = biasedIntersection;
|
||||||
|
|
||||||
|
module.exports.fromJSON = fromJSON;
|
||||||
|
|
123
js/src/struct.js
123
js/src/struct.js
|
@ -1,61 +1,106 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
// "Structures": Simple named-tuple-like records.
|
// "Structures": Simple named-tuple-like records.
|
||||||
// TODO: shore up $SyndicateMeta$, making it a proper object
|
|
||||||
|
|
||||||
var Immutable = require("immutable");
|
var Immutable = require("immutable");
|
||||||
var $Special = require('./special.js');
|
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 */
|
var __ = new $Special("wildcard"); /* wildcard marker */
|
||||||
|
|
||||||
function instantiateStructure($SyndicateMeta$, argvals) {
|
function StructureType(label, arity) {
|
||||||
var result = {"$SyndicateMeta$": $SyndicateMeta$};
|
this.label = label;
|
||||||
var argnames = $SyndicateMeta$.arguments;
|
this.arity = arity;
|
||||||
for (var i = 0; i < argnames.length; i++) {
|
this.pattern = this.instantiate(Immutable.Repeat(__, arity).toArray());
|
||||||
result[argnames[i]] = argvals[i];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeStructureConstructor(label, argumentNames) {
|
var self = this;
|
||||||
var $SyndicateMeta$ = {
|
this.ctor = function () {
|
||||||
label: label,
|
return self.instantiate(Array.prototype.slice.call(arguments));
|
||||||
arguments: argumentNames
|
|
||||||
};
|
};
|
||||||
var ctor = function() {
|
this.ctor.meta = this;
|
||||||
return instantiateStructure($SyndicateMeta$, arguments);
|
this.ctor.pattern = this.pattern;
|
||||||
};
|
this.ctor.isClassOf = function (v) { return self.isClassOf(v); };
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSyndicateMeta(m) {
|
function makeConstructor(label, fieldNames) {
|
||||||
// TODO: include more structure in $SyndicateMeta$ objects to make
|
return new StructureType(label, fieldNames.length).ctor;
|
||||||
// this judgement less sloppy.
|
|
||||||
return m && m.label && Array.isArray(m.arguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isStructure(s) {
|
StructureType.prototype.equals = function (other) {
|
||||||
return (s !== null) && (typeof s === 'object') && ("$SyndicateMeta$" in s);
|
if (!(other instanceof StructureType)) return false;
|
||||||
}
|
return this.arity === other.arity && this.label === other.label;
|
||||||
|
};
|
||||||
|
|
||||||
function structureToArray(s, excludeLabel) {
|
StructureType.prototype.instantiate = function (fields) {
|
||||||
var result = excludeLabel ? [] : [s.$SyndicateMeta$.label];
|
return new Structure(this, fields);
|
||||||
var args = s.$SyndicateMeta$.arguments;
|
};
|
||||||
for (var i = 0; i < args.length; i++) {
|
|
||||||
result.push(s[args[i]]);
|
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.__ = __;
|
||||||
module.exports.instantiateStructure = instantiateStructure;
|
module.exports.StructureType = StructureType;
|
||||||
module.exports.makeStructureConstructor = makeStructureConstructor;
|
module.exports.makeConstructor = makeConstructor;
|
||||||
module.exports.isSyndicateMeta = isSyndicateMeta;
|
module.exports.Structure = Structure;
|
||||||
|
module.exports.reviveStructs = reviveStructs;
|
||||||
|
module.exports.reviver = reviver;
|
||||||
|
module.exports.isStructureType = isStructureType;
|
||||||
module.exports.isStructure = isStructure;
|
module.exports.isStructure = isStructure;
|
||||||
module.exports.structureToArray = structureToArray;
|
|
||||||
|
|
151
js/src/trie.js
151
js/src/trie.js
|
@ -186,11 +186,10 @@ function compilePattern(v, p) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Struct.isStructure(p)) {
|
if (Struct.isStructure(p)) {
|
||||||
var args = p.$SyndicateMeta$.arguments;
|
for (var i = p.meta.arity - 1; i >= 0; i--) {
|
||||||
for (var i = args.length - 1; i >= 0; i--) {
|
acc = walk(p[i], acc);
|
||||||
acc = walk(p[args[i]], acc);
|
|
||||||
}
|
}
|
||||||
return rseq(args.length, p.$SyndicateMeta$, acc);
|
return rseq(p.meta.arity, p.meta, acc);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p instanceof $Embedded) {
|
if (p instanceof $Embedded) {
|
||||||
|
@ -222,13 +221,10 @@ function matchPattern(v, p) {
|
||||||
|
|
||||||
if (p === __) return;
|
if (p === __) return;
|
||||||
|
|
||||||
if (Struct.isStructure(p)
|
if (Struct.isStructure(p) && Struct.isStructure(v) && (p.meta.equals(v.meta)))
|
||||||
&& Struct.isStructure(v)
|
|
||||||
&& (p.$SyndicateMeta$ === v.$SyndicateMeta$))
|
|
||||||
{
|
{
|
||||||
var args = p.$SyndicateMeta$.arguments;
|
for (var i = 0; i < p.meta.arity; i++) {
|
||||||
for (var i = 0; i < args.length; i++) {
|
walk(v[i], p[i]);
|
||||||
walk(v[args[i]], p[args[i]]);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -431,8 +427,8 @@ function matchValue(r, v, failureResultOpt) {
|
||||||
r = rlookup(r, v.length, SOA);
|
r = rlookup(r, v.length, SOA);
|
||||||
vs = Immutable.List(v).concat(vs);
|
vs = Immutable.List(v).concat(vs);
|
||||||
} else if (Struct.isStructure(v)) {
|
} else if (Struct.isStructure(v)) {
|
||||||
r = rlookup(r, v.$SyndicateMeta$.arguments.length, v.$SyndicateMeta$);
|
r = rlookup(r, v.meta.arity, v.meta);
|
||||||
vs = Immutable.List(Struct.structureToArray(v, true)).concat(vs);
|
vs = Immutable.List(v.fields).concat(vs);
|
||||||
} else {
|
} else {
|
||||||
r = rlookup(r, 0, v);
|
r = rlookup(r, 0, v);
|
||||||
}
|
}
|
||||||
|
@ -501,17 +497,19 @@ function appendTrie(m, mTailFn) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function triePruneBranch(m, arityKeys) {
|
// DANGEROUS: prefer subtract() instead.
|
||||||
if (arityKeys.isEmpty()) return emptyTrie;
|
//
|
||||||
if (!(m instanceof $Branch)) return m;
|
// function triePruneBranch(m, arityKeys) {
|
||||||
var arityKey = arityKeys.first();
|
// if (arityKeys.isEmpty()) return emptyTrie;
|
||||||
var rest = arityKeys.shift();
|
// if (!(m instanceof $Branch)) return m;
|
||||||
var arity = arityKey[0];
|
// var arityKey = arityKeys.first();
|
||||||
var key = arityKey[1];
|
// var rest = arityKeys.shift();
|
||||||
m = rcopybranch(m);
|
// var arity = arityKey[0];
|
||||||
rupdate_inplace(m, arity, key, triePruneBranch(rlookup(m, arity, key), rest));
|
// var key = arityKey[1];
|
||||||
return collapse(m);
|
// m = rcopybranch(m);
|
||||||
}
|
// rupdate_inplace(m, arity, key, triePruneBranch(rlookup(m, arity, key), rest));
|
||||||
|
// return collapse(m);
|
||||||
|
// }
|
||||||
|
|
||||||
function trieStep(m, arity, key) {
|
function trieStep(m, arity, key) {
|
||||||
if (typeof key === 'undefined') {
|
if (typeof key === 'undefined') {
|
||||||
|
@ -550,8 +548,7 @@ function projectionNames(p) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Struct.isStructure(p)) {
|
if (Struct.isStructure(p)) {
|
||||||
var args = p.$SyndicateMeta$.arguments;
|
for (var i = 0; i < p.meta.arity; i++) walk(p[i]);
|
||||||
for (var i = 0; i < args.length; i++) walk(p[args[i]]);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -576,12 +573,11 @@ function projectionToPattern(p) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Struct.isStructure(p)) {
|
if (Struct.isStructure(p)) {
|
||||||
var result = {"$SyndicateMeta$": p.$SyndicateMeta$};
|
var resultFields = [];
|
||||||
var args = p.$SyndicateMeta$.arguments;
|
for (var i = 0; i < p.meta.arity; i++) {
|
||||||
for (var i = 0; i < args.length; i++) {
|
resultFields[i] = walk(p[i]);
|
||||||
result[args[i]] = walk(p[args[i]]);
|
|
||||||
}
|
}
|
||||||
return result;
|
return new Struct.Structure(p.meta, resultFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
|
@ -652,15 +648,13 @@ function projectMany(t, wholeSpecs, projectSuccessOpt, combinerOpt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Struct.isStructure(spec)) {
|
if (Struct.isStructure(spec)) {
|
||||||
var arity = spec.$SyndicateMeta$.arguments.length;
|
|
||||||
var key = spec.$SyndicateMeta$;
|
|
||||||
var intermediate = walk(isCapturing,
|
var intermediate = walk(isCapturing,
|
||||||
rlookup(t, arity, key),
|
rlookup(t, spec.meta.arity, spec.meta),
|
||||||
Immutable.List(Struct.structureToArray(spec, true)),
|
Immutable.List(spec.fields),
|
||||||
function (intermediate) {
|
function (intermediate) {
|
||||||
return walk(isCapturing, intermediate, specsRest, kont);
|
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)) {
|
if (Array.isArray(spec)) {
|
||||||
|
@ -687,7 +681,7 @@ function reconstructSequence(key, items) {
|
||||||
if (key === SOA) {
|
if (key === SOA) {
|
||||||
return items.toArray();
|
return items.toArray();
|
||||||
} else {
|
} 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
|
if (result === false) return false; // break out of iteration
|
||||||
|
|
||||||
var piece;
|
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) {
|
piece = walk(k, arity, Immutable.List(), function (items, m1) {
|
||||||
var item = reconstructSequence(key, items);
|
var item = reconstructSequence(key, items);
|
||||||
return walk(m1, takeCount - 1, valsRev.unshift(item), kont);
|
return walk(m1, takeCount - 1, valsRev.unshift(item), kont);
|
||||||
|
@ -798,10 +792,11 @@ function prettyTrie(m, initialIndent) {
|
||||||
}
|
}
|
||||||
acc.push(" ");
|
acc.push(" ");
|
||||||
if (key === SOA) key = '<' + arity + '>';
|
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 (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);
|
acc.push(key);
|
||||||
walk(i + key.length + 1, k);
|
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.__ = __;
|
||||||
module.exports.SOA = SOA;
|
module.exports.SOA = SOA;
|
||||||
module.exports.$Capture = $Capture;
|
module.exports.$Capture = $Capture;
|
||||||
|
@ -833,7 +900,7 @@ module.exports.subtract = subtract;
|
||||||
module.exports.matchValue = matchValue;
|
module.exports.matchValue = matchValue;
|
||||||
module.exports.matchTrie = matchTrie;
|
module.exports.matchTrie = matchTrie;
|
||||||
module.exports.appendTrie = appendTrie;
|
module.exports.appendTrie = appendTrie;
|
||||||
module.exports.triePruneBranch = triePruneBranch;
|
// module.exports.triePruneBranch = triePruneBranch;
|
||||||
module.exports.trieStep = trieStep;
|
module.exports.trieStep = trieStep;
|
||||||
module.exports.trieSuccess = rsuccess;
|
module.exports.trieSuccess = rsuccess;
|
||||||
module.exports.relabel = relabel;
|
module.exports.relabel = relabel;
|
||||||
|
@ -847,6 +914,8 @@ module.exports.captureToObject = captureToObject;
|
||||||
module.exports.trieKeysToObjects = trieKeysToObjects;
|
module.exports.trieKeysToObjects = trieKeysToObjects;
|
||||||
module.exports.projectObjects = projectObjects;
|
module.exports.projectObjects = projectObjects;
|
||||||
module.exports.prettyTrie = prettyTrie;
|
module.exports.prettyTrie = prettyTrie;
|
||||||
|
module.exports.trieToJSON = trieToJSON;
|
||||||
|
module.exports.trieFromJSON = trieFromJSON;
|
||||||
|
|
||||||
// For testing
|
// For testing
|
||||||
module.exports._testing = {
|
module.exports._testing = {
|
||||||
|
|
|
@ -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;
|
|
@ -40,23 +40,23 @@ describe('basic patch compilation', function () {
|
||||||
[' <2> 1 2 {true}'],
|
[' <2> 1 2 {true}'],
|
||||||
[' ::: nothing']);
|
[' ::: nothing']);
|
||||||
checkPrettyPatch(Patch.assert([1, 2], 1),
|
checkPrettyPatch(Patch.assert([1, 2], 1),
|
||||||
[' atMeta<1> <2> 1 2 {true}'],
|
[' at-meta<1> <2> 1 2 {true}'],
|
||||||
[' ::: nothing']);
|
[' ::: nothing']);
|
||||||
checkPrettyPatch(Patch.assert([1, 2], 2),
|
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']);
|
[' ::: nothing']);
|
||||||
|
|
||||||
checkPrettyPatch(Patch.sub([1, 2], 0),
|
checkPrettyPatch(Patch.sub([1, 2], 0),
|
||||||
[' observe<1> <2> 1 2 {true}'],
|
[' observe<1> <2> 1 2 {true}'],
|
||||||
[' ::: nothing']);
|
[' ::: nothing']);
|
||||||
checkPrettyPatch(Patch.sub([1, 2], 1),
|
checkPrettyPatch(Patch.sub([1, 2], 1),
|
||||||
[' atMeta<1> observe<1> <2> 1 2 {true}',
|
[' at-meta<1> observe<1> <2> 1 2 {true}',
|
||||||
' observe<1> atMeta<1> <2> 1 2 {true}'],
|
' observe<1> at-meta<1> <2> 1 2 {true}'],
|
||||||
[' ::: nothing']);
|
[' ::: nothing']);
|
||||||
checkPrettyPatch(Patch.sub([1, 2], 2),
|
checkPrettyPatch(Patch.sub([1, 2], 2),
|
||||||
[' atMeta<1> atMeta<1> observe<1> <2> 1 2 {true}',
|
[' at-meta<1> at-meta<1> observe<1> <2> 1 2 {true}',
|
||||||
' observe<1> atMeta<1> <2> 1 2 {true}',
|
' observe<1> at-meta<1> <2> 1 2 {true}',
|
||||||
' observe<1> atMeta<1> atMeta<1> <2> 1 2 {true}'],
|
' observe<1> at-meta<1> at-meta<1> <2> 1 2 {true}'],
|
||||||
[' ::: nothing']);
|
[' ::: nothing']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -111,14 +111,14 @@ describe('patch sequencing', function () {
|
||||||
describe('patch lifting', function () {
|
describe('patch lifting', function () {
|
||||||
it('should basically work', function () {
|
it('should basically work', function () {
|
||||||
checkPrettyPatch(Patch.assert([1, 2]).lift(),
|
checkPrettyPatch(Patch.assert([1, 2]).lift(),
|
||||||
[' atMeta<1> <2> 1 2 {true}'],
|
[' at-meta<1> <2> 1 2 {true}'],
|
||||||
[' ::: nothing']);
|
[' ::: nothing']);
|
||||||
checkPrettyPatch(Patch.sub([1, 2]).lift(),
|
checkPrettyPatch(Patch.sub([1, 2]).lift(),
|
||||||
[' atMeta<1> observe<1> <2> 1 2 {true}'],
|
[' at-meta<1> observe<1> <2> 1 2 {true}'],
|
||||||
[' ::: nothing']);
|
[' ::: nothing']);
|
||||||
checkPrettyPatch(Patch.assert([1, 2]).andThen(Patch.assert(Patch.atMeta([1, 2]))).lift(),
|
checkPrettyPatch(Patch.assert([1, 2]).andThen(Patch.assert(Patch.atMeta([1, 2]))).lift(),
|
||||||
[' atMeta<1> atMeta<1> <2> 1 2 {true}',
|
[' at-meta<1> at-meta<1> <2> 1 2 {true}',
|
||||||
' <2> 1 2 {true}'],
|
' <2> 1 2 {true}'],
|
||||||
[' ::: nothing']);
|
[' ::: nothing']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -402,7 +402,7 @@ describe("calls to matchPattern", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches structures", 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"))))
|
expect(r.matchPattern(ctor(123, 234), ctor(r._$("bar"), r._$("zot"))))
|
||||||
.to.eql({ bar: 123, zot: 234, length: 2 });
|
.to.eql({ bar: 123, zot: 234, length: 2 });
|
||||||
// Previously, structures were roughly the same as arrays:
|
// Previously, structures were roughly the same as arrays:
|
||||||
|
@ -466,75 +466,76 @@ describe('intersect', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('triePruneBranch', function () {
|
// describe('triePruneBranch', function () {
|
||||||
it('should not affect empty trie', function () {
|
// it('should not affect empty trie', function () {
|
||||||
checkPrettyTrie(r.triePruneBranch(r.emptyTrie, Immutable.List([])), [' ::: 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([r.SOA])), [' ::: nothing']);
|
||||||
checkPrettyTrie(r.triePruneBranch(r.emptyTrie, Immutable.List(["x"])), [' ::: 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([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 () {
|
describe('makeConstructor', 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 () {
|
|
||||||
it('should produce the right metadata', 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);
|
var inst = ctor(123, 234);
|
||||||
expect(inst.$SyndicateMeta$.label).to.equal('foo');
|
expect(inst.meta.label).to.equal('foo');
|
||||||
expect(inst.$SyndicateMeta$.arguments).to.eql(['bar', 'zot']);
|
expect(inst.meta.arity).to.equal(2);
|
||||||
|
expect(ctor.meta).to.equal(inst.meta);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should produce the right instance data', function () {
|
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);
|
var inst = ctor(123, 234);
|
||||||
expect(inst.bar).to.equal(123);
|
expect(inst[0]).to.equal(123);
|
||||||
expect(inst.zot).to.equal(234);
|
expect(inst[1]).to.equal(234);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with compilePattern and matchValue', function () {
|
it('should work with compilePattern and matchValue', function () {
|
||||||
var sA = Immutable.Set(["A"]);
|
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 inst = ctor(123, 234);
|
||||||
var x = r.compilePattern(sA, ctor(123, r.__));
|
var x = r.compilePattern(sA, ctor(123, r.__));
|
||||||
checkPrettyTrie(x, [' foo<2> 123 ★ {["A"]}']);
|
checkPrettyTrie(x, [' foo<2> 123 ★ {["A"]}']);
|
||||||
|
|
|
@ -105,10 +105,10 @@ describe("nested actor with an echoey protocol", function () {
|
||||||
}, ['<<<<<<<< Removed:\n'+
|
}, ['<<<<<<<< Removed:\n'+
|
||||||
' ::: nothing\n'+
|
' ::: nothing\n'+
|
||||||
'======== Added:\n'+
|
'======== Added:\n'+
|
||||||
' atMeta<1> "X" {["meta"]}\n'+
|
' at-meta<1> "X" {["meta"]}\n'+
|
||||||
'>>>>>>>>',
|
'>>>>>>>>',
|
||||||
'<<<<<<<< Removed:\n'+
|
'<<<<<<<< Removed:\n'+
|
||||||
' atMeta<1> "X" {["meta"]}\n'+
|
' at-meta<1> "X" {["meta"]}\n'+
|
||||||
'======== Added:\n'+
|
'======== Added:\n'+
|
||||||
' ::: nothing\n'+
|
' ::: nothing\n'+
|
||||||
'>>>>>>>>']);
|
'>>>>>>>>']);
|
||||||
|
|
Loading…
Reference in New Issue