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
|
||||
? 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));
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
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);
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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); });
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
123
js/src/struct.js
123
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;
|
||||
|
|
151
js/src/trie.js
151
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 = {
|
||||
|
|
|
@ -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}'],
|
||||
[' ::: 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']);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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"]}']);
|
||||
|
|
|
@ -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'+
|
||||
'>>>>>>>>']);
|
||||
|
|
Loading…
Reference in New Issue