Compare commits

..

No commits in common. "main" and "pre-package-conversion" have entirely different histories.

53 changed files with 2432 additions and 7186 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
private-key.pem private-key.pem
server-cert.pem server-cert.pem
MarketplaceChat.app.zip
MarketplaceChat.app/
scratch/ scratch/
_site/ _site/
node_modules/

View File

@ -1,5 +1,21 @@
all: APP_NAME=MarketplaceChat.app
npm install . LIB_SOURCES=\
route.js \
marketplace.js \
spy.js \
dom-driver.js \
jquery-driver.js \
routing-table-widget.js \
routing-table-widget.css \
wake-detector.js \
websocket-driver.js
APP_SOURCES=\
examples/chat/index.html \
examples/chat/index.js \
examples/chat/style.css
RESOURCES=$(wildcard examples/chat/app-resources/*)
all: $(APP_NAME).zip
keys: private-key.pem server-cert.pem keys: private-key.pem server-cert.pem
@ -16,8 +32,22 @@ server-cert.pem: private-key.pem
clean-keys: clean-keys:
rm -f private-key.pem server-cert.pem rm -f private-key.pem server-cert.pem
clean: $(APP_NAME).zip: $(APP_NAME)
rm -f dist/*.js zip -r $@ $<
veryclean: clean $(APP_NAME): $(APP_SOURCES) $(LIB_SOURCES)
rm -rf node_modules/ echo RESOURCES $(RESOURCES)
rm -rf $@
mkdir -p $@/Contents/MacOS
mkdir -p $@/Contents/Resources
cp examples/chat/app-resources/Info.plist $@/Contents
cp examples/chat/app-resources/boot.sh $@/Contents/MacOS
cp examples/chat/app-resources/app.icns $@/Contents/Resources
cp -r third-party $@/Contents/Resources
cp $(LIB_SOURCES) $@/Contents/Resources
mkdir -p $@/Contents/Resources/examples/chat
cp $(APP_SOURCES) $@/Contents/Resources/examples/chat
chmod a+x $@/Contents/MacOS/boot.sh
clean:
rm -rf $(APP_NAME) $(APP_NAME).zip

View File

@ -14,7 +14,7 @@ To install the Racket server:
To run the Racket server: To run the Racket server:
racket -l minimart/examples/broker - `racket server.rkt` from the base directory of this repository.
The Racket server listens for tunnelled Network Calculus events via The Racket server listens for tunnelled Network Calculus events via
websocket on ports 8000 (HTTP) and 8443 (HTTPS, if you have a websocket on ports 8000 (HTTP) and 8443 (HTTPS, if you have a

1
dist/README.md vendored
View File

@ -1 +0,0 @@
Directory for build products, checked in to the repo for ease-of-use.

3215
dist/minimart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,51 +1,31 @@
// DOM fragment display driver // DOM fragment display driver
var Minimart = require("./minimart.js");
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
function spawnDOMDriver(domWrapFunction, jQueryWrapFunction) { function spawnDOMDriver() {
domWrapFunction = domWrapFunction || defaultWrapFunction; var d = new DemandMatcher(["DOM", _$, _$, _$]);
var d = new Minimart.DemandMatcher(domWrapFunction(_$, _$, _$));
d.onDemandIncrease = function (captures) { d.onDemandIncrease = function (captures) {
var selector = captures[0]; var selector = captures[0];
var fragmentClass = captures[1]; var fragmentClass = captures[1];
var fragmentSpec = captures[2]; var fragmentSpec = captures[2];
World.spawn(new DOMFragment(selector, World.spawn(new DOMFragment(selector, fragmentClass, fragmentSpec),
fragmentClass, [sub(["DOM", selector, fragmentClass, fragmentSpec]),
fragmentSpec, sub(["DOM", selector, fragmentClass, fragmentSpec], 0, 1)]);
domWrapFunction,
jQueryWrapFunction));
}; };
World.spawn(d); World.spawn(d);
} }
function defaultWrapFunction(selector, fragmentClass, fragmentSpec) { function DOMFragment(selector, fragmentClass, fragmentSpec) {
return ["DOM", selector, fragmentClass, fragmentSpec];
}
function DOMFragment(selector, fragmentClass, fragmentSpec, domWrapFunction, jQueryWrapFunction) {
this.selector = selector; this.selector = selector;
this.fragmentClass = fragmentClass; this.fragmentClass = fragmentClass;
this.fragmentSpec = fragmentSpec; this.fragmentSpec = fragmentSpec;
this.domWrapFunction = domWrapFunction;
this.jQueryWrapFunction = jQueryWrapFunction;
this.nodes = this.buildNodes(); this.nodes = this.buildNodes();
} }
DOMFragment.prototype.boot = function () { DOMFragment.prototype.boot = function () {
var self = this; var self = this;
var monitoring = var monitoring = sub(["DOM", self.selector, self.fragmentClass, self.fragmentSpec], 1, 2);
sub(self.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec), 1, 2);
World.spawn(new World(function () { World.spawn(new World(function () {
Minimart.JQuery.spawnJQueryDriver(self.selector+" > ."+self.fragmentClass, spawnJQueryDriver(self.selector+" > ."+self.fragmentClass, 1);
1,
self.jQueryWrapFunction);
World.spawn({ World.spawn({
boot: function () { return [monitoring] },
handleEvent: function (e) { handleEvent: function (e) {
if (e.type === "routes") { if (e.type === "routes") {
var level = e.gestalt.getLevel(1, 0); // find participant peers var level = e.gestalt.getLevel(1, 0); // find participant peers
@ -54,11 +34,8 @@ DOMFragment.prototype.boot = function () {
} }
} }
} }
}); }, [monitoring]);
})); }));
return [sub(self.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec)),
sub(self.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec), 0, 1)]
}; };
DOMFragment.prototype.handleEvent = function (e) { DOMFragment.prototype.handleEvent = function (e) {
@ -71,34 +48,30 @@ DOMFragment.prototype.handleEvent = function (e) {
} }
}; };
function isAttributes(x) {
return Array.isArray(x) && ((x.length === 0) || Array.isArray(x[0]));
}
DOMFragment.prototype.interpretSpec = function (spec) { DOMFragment.prototype.interpretSpec = function (spec) {
// Fragment specs are roughly JSON-equivalents of SXML. // Fragment specs are roughly JSON-equivalents of SXML.
// spec ::== ["tag", [["attr", "value"], ...], spec, spec, ...] // spec ::== ["tag", {"attr": "value", ...}, spec, spec, ...]
// | ["tag", spec, spec, ...] // | ["tag", spec, spec, ...]
// | "cdata" // | "cdata"
if (typeof(spec) === "string" || typeof(spec) === "number") { if (typeof(spec) === "string" || typeof(spec) === "number") {
return document.createTextNode(spec); return document.createTextNode(spec);
} else if ($.isArray(spec)) { } else if ($.isArray(spec)) {
var tagName = spec[0]; var tagName = spec[0];
var hasAttrs = isAttributes(spec[1]); var hasAttrs = $.isPlainObject(spec[1]);
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! // 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 attr in attrs) {
n.setAttribute(attrs[i][0], attrs[i][1]); if (attrs.hasOwnProperty(attr)) {
n.setAttribute(attr, attrs[attr]);
}
} }
for (var i = kidIndex; i < spec.length; i++) { for (var i = kidIndex; i < spec.length; i++) {
n.appendChild(this.interpretSpec(spec[i])); n.appendChild(this.interpretSpec(spec[i]));
} }
return n; return n;
} else {
throw new Error("Ill-formed DOM specification");
} }
}; };
@ -113,8 +86,3 @@ DOMFragment.prototype.buildNodes = function () {
}); });
return nodes; return nodes;
}; };
///////////////////////////////////////////////////////////////////////////
module.exports.spawnDOMDriver = spawnDOMDriver;
module.exports.defaultWrapFunction = defaultWrapFunction;

View File

@ -1,67 +0,0 @@
<!doctype html>
<html>
<head>
<title>JS Marketplace: Chat Example</title>
<meta charset="utf-8">
<link href="../../third-party/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="../../third-party/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet">
<link href="style.css" rel="stylesheet">
<script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../dist/minimart.js"></script>
<script src="index.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row-fluid">
<div class="span12">
<form class="form-horizontal" name="nym_form">
<fieldset>
<div class="control-group">
<label class="control-label" for="wsurl">Server:</label>
<div class="controls">
<input type="text" id="wsurl" name="wsurl" value="ws://localhost:8000/">
</div>
<label class="control-label" for="nym">Nym:</label>
<div class="controls">
<input type="text" id="nym" name="nym" value="">
</div>
<label class="control-label" for="status">Status:</label>
<div class="controls">
<input type="text" id="status" name="status" value="">
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="row-fluid">
<div class="span9">
<h2>Messages</h2>
<div id="chat_output"></div>
</div>
<div class="span3">
<h2>Active Users</h2>
<div id="nymlist"></div>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<form class="form-horizontal" name="chat_form">
<fieldset>
<div class="control-group">
<div class="controls">
<input type="text" id="chat_input" name="chat_input" value="" autocomplete="off">
<button id="send_chat">Send</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="row-fluid">
<div class="span12" id="spy-holder">
</div>
</div>
</div>
</body>
</html>

View File

@ -1,162 +0,0 @@
var Route = Minimart.Route;
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
function chatEvent(nym, status, utterance, stamp) {
return ["chatEvent", nym, status, utterance, stamp || +(new Date())];
}
function chatEventNym(c) { return c[1]; }
function chatEventStatus(c) { return c[2]; }
function chatEventUtterance(c) { return c[3]; }
function chatEventStamp(c) { return c[4]; }
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 updateNymList(g) {
var statuses = {};
var nymProj = ["broker", 0, ["chatEvent", _$, _$, __, __]];
var matchedNyms = Route.matcherKeys(g.project(Route.compileProjection(nymProj), true, 0, 0));
for (var i = 0; i < matchedNyms.length; i++) {
statuses[matchedNyms[i][0]] = matchedNyms[i][1];
}
var nyms = [];
for (var nym in statuses) { nyms.push(nym); }
nyms.sort();
var container = $("#nymlist");
container[0].innerHTML = ""; // remove all children
for (var i = 0; i < nyms.length; i++) {
var n = $("<span/>").text(nyms[i]).addClass("nym");
var s = statuses[nyms[i]];
if (s) {
container.append($("<div/>").append([n, $("<span/>").text(s).addClass("nym_status")]));
} else {
container.append($("<div/>").append(n));
}
}
}
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");
}
var G;
$(document).ready(function () {
$("#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)); }
G = new Minimart.Ground(function () {
console.log('starting ground boot');
// World.spawn(new Spy());
Minimart.JQuery.spawnJQueryDriver();
Minimart.DOM.spawnDOMDriver();
Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy");
World.spawn(new Minimart.WakeDetector());
var wsconn = new Minimart.WebSocket.WebSocketConnection("broker", $("#wsurl").val(), true);
World.spawn(wsconn);
World.spawn({
// Monitor connection, notifying connectivity changes
state: "crashed", // start with this to avoid spurious initial message print
boot: function () {
return [sub(["broker_state", __], 0, 1)];
},
handleEvent: function (e) {
if (e.type === "routes") {
var states =
Route.matcherKeys(e.gestalt.project(Route.compileProjection([__, _$]),
true, 0, 0));
var newState = states.length > 0 ? states[0][0] : "crashed";
if (this.state != newState) {
outputState(newState);
this.state = newState;
}
}
}
});
World.spawn({
// Actual chat functionality
boot: function () {
return this.subscriptions();
},
nym: function () { return $("#nym").val(); },
currentStatus: function () { return $("#status").val(); },
subscriptions: function () {
return [sub("wake"),
sub(["jQuery", "#send_chat", "click", __]),
sub(["jQuery", "#nym", "change", __]),
sub(["jQuery", "#status", "change", __]),
sub(["jQuery", "#wsurl", "change", __]),
pub(["broker", 0, chatEvent(this.nym(), this.currentStatus(), __, __)]),
sub(["broker", 0, chatEvent(__, __, __, __)], 0, 1)];
},
handleEvent: function (e) {
var self = this;
switch (e.type) {
case "routes":
updateNymList(e.gestalt);
break;
case "message":
if (e.message === "wake") {
wsconn.forceclose();
return;
}
switch (e.message[0]) {
case "jQuery":
switch (e.message[1]) {
case "#send_chat":
var inp = $("#chat_input");
var utterance = inp.val();
inp.val("");
if (utterance) {
World.send(["broker", 0, chatEvent(this.nym(),
this.currentStatus(),
utterance)]);
}
break;
case "#nym":
case "#status":
World.updateRoutes(this.subscriptions());
break;
case "#wsurl":
wsconn.forceclose();
wsconn.wsurl = $("#wsurl").val();
break;
default:
console.log("Got jquery event from as-yet-unhandled subscription",
e.message[2], e.message[3]);
}
break;
case "broker":
if (e.message[2][0] === "chatEvent") {
outputUtterance(chatEventNym(e.message[2]),
chatEventUtterance(e.message[2]));
}
break;
default:
break;
}
break;
}
}
});
});
G.startStepping();
});

View File

@ -1,73 +0,0 @@
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;
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>boot.sh</string>
<key>CFBundleIconFile</key>
<string>app.icns</string>
<key>CFBundleIdentifier</key>
<string>com.leastfixedpoint.js-marketplace.chat-demo</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>chat-demo</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>chat-demo 0.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.0.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.6</string>
</dict>
</plist>

Binary file not shown.

View File

@ -0,0 +1,3 @@
#!/bin/bash
cd "$(dirname "$0")"/../Resources
open examples/chat/index.html

View File

@ -3,15 +3,33 @@
<head> <head>
<title>JS Marketplace: Chat Example</title> <title>JS Marketplace: Chat Example</title>
<meta charset="utf-8"> <meta charset="utf-8">
<link href="../../third-party/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <link href="../../third-party/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="../../third-party/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet"> <link href="../../third-party/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet">
<link href="style.css" rel="stylesheet"> <link href="style.css" rel="stylesheet">
<!-- <script src="../../third-party/bootstrap/js/bootstrap.min.js"></script> -->
<script src="../../third-party/jquery-2.0.3.min.js"></script> <script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../dist/minimart.js"></script>
<script src="../../route.js"></script>
<script src="../../marketplace.js"></script>
<script src="../../spy.js"></script>
<script src="../../dom-driver.js"></script>
<script src="../../routing-table-widget.js"></script>
<link href="../../routing-table-widget.css" rel="stylesheet">
<script src="../../jquery-driver.js"></script>
<script src="../../wake-detector.js"></script>
<script src="../../websocket-driver.js"></script>
<script src="index.js"></script> <script src="index.js"></script>
</head> </head>
<body> <body>
<div class="container-fluid"> <div class="container-fluid">
<!-- <div class="row-fluid"> -->
<!-- <div class="span12"> -->
<!-- <h1>JS Marketplace</h1> -->
<!-- </div> -->
<!-- </div> -->
<div class="row-fluid"> <div class="row-fluid">
<div class="span12"> <div class="span12">
<form class="form-horizontal" name="nym_form"> <form class="form-horizontal" name="nym_form">

View File

@ -1,14 +1,10 @@
var Route = Minimart.Route;
var World = Minimart.World;
var Actor = Minimart.Actor;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
function chatEvent(nym, status, utterance, stamp) { function chatEvent(nym, status, utterance, stamp) {
return ["chatEvent", nym, status, utterance, stamp || +(new Date())]; return ["chatEvent", nym, status, utterance, stamp || +(new Date())];
} }
function chatEventNym(c) { return c[1]; }
function chatEventStatus(c) { return c[2]; }
function chatEventUtterance(c) { return c[3]; }
function chatEventStamp(c) { return c[4]; }
function outputItem(item) { function outputItem(item) {
var stamp = $("<span/>").text((new Date()).toGMTString()).addClass("timestamp"); var stamp = $("<span/>").text((new Date()).toGMTString()).addClass("timestamp");
@ -19,10 +15,12 @@ function outputItem(item) {
return item; return item;
} }
function updateNymList(allStatuses) { function updateNymList(g) {
var statuses = {}; var statuses = {};
for (var i = 0; i < allStatuses.length; i++) { var nymProj = ["broker", 0, ["chatEvent", _$, _$, __, __]];
statuses[allStatuses[i].nym] = allStatuses[i].status; var matchedNyms = route.matcherKeys(g.project(route.compileProjection(nymProj), true, 0, 0));
for (var i = 0; i < matchedNyms.length; i++) {
statuses[matchedNyms[i][0]] = matchedNyms[i][1];
} }
var nyms = []; var nyms = [];
for (var nym in statuses) { nyms.push(nym); } for (var nym in statuses) { nyms.push(nym); }
@ -57,81 +55,101 @@ $(document).ready(function () {
$("#nym_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)); } if (!($("#nym").val())) { $("#nym").val("nym" + Math.floor(Math.random() * 65536)); }
G = new Minimart.Ground(function () { G = new Ground(function () {
console.log('starting ground boot'); console.log('starting ground boot');
// World.spawn(new Spy()); // World.spawn(new Spy());
Minimart.JQuery.spawnJQueryDriver(); spawnJQueryDriver();
Minimart.DOM.spawnDOMDriver(); spawnDOMDriver();
Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy"); spawnRoutingTableWidget("#spy-holder", "spy");
World.spawn(new Minimart.WakeDetector()); World.spawn(new WakeDetector());
var wsconn = new Minimart.WebSocket.WebSocketConnection("broker", $("#wsurl").val(), true); var wsconn = new WebSocketConnection("broker", $("#wsurl").val(), true);
World.spawn(wsconn); World.spawn(wsconn);
World.spawn(new Actor(function () { World.spawn({
// Monitor connection, notifying connectivity changes // Monitor connection, notifying connectivity changes
this.state = "crashed"; // start with this to avoid spurious initial message print state: "crashed", // start with this to avoid spurious initial message print
boot: function () {
Actor.observeAdvertisers( World.updateRoutes([sub(["broker_state", __], 0, 1)]);
function () { return ["broker_state", _$("newState")]; }, },
{ name: "states" }, handleEvent: function (e) {
function () { if (e.type === "routes") {
var newState = this.states.length > 0 ? this.states[0].newState : "crashed"; var states =
route.matcherKeys(e.gestalt.project(route.compileProjection([__, _$]),
true, 0, 0));
var newState = states.length > 0 ? states[0][0] : "crashed";
if (this.state != newState) { if (this.state != newState) {
outputState(newState); outputState(newState);
this.state = newState; this.state = newState;
} }
}); }
})); }
World.spawn(new Actor(function () { });
World.spawn({
// Actual chat functionality // Actual chat functionality
this.nym = function () { return $("#nym").val(); }; boot: function () {
this.currentStatus = function () { return $("#status").val(); }; World.updateRoutes(this.subscriptions());
},
Actor.subscribe( nym: function () { return $("#nym").val(); },
function () { return "wake"; }, currentStatus: function () { return $("#status").val(); },
function () { wsconn.forceclose(); }); subscriptions: function () {
return [sub("wake"),
Actor.advertise( sub(["jQuery", "#send_chat", "click", __]),
function () { return ["broker", 0, sub(["jQuery", "#nym", "change", __]),
chatEvent(this.nym(), this.currentStatus(), __, __)]; }); sub(["jQuery", "#status", "change", __]),
Actor.observeAdvertisers( sub(["jQuery", "#wsurl", "change", __]),
function () { return ["broker", 0, pub(["broker", 0, chatEvent(this.nym(), this.currentStatus(), __, __)]),
chatEvent(_$("nym"), _$("status"), __, __)]; }, sub(["broker", 0, chatEvent(__, __, __, __)], 0, 1)];
{ name: "allStatuses" }, },
function () { updateNymList(this.allStatuses); }); handleEvent: function (e) {
var self = this;
Actor.subscribe( switch (e.type) {
function () { return ["jQuery", "#send_chat", "click", __]; }, case "routes":
function () { updateNymList(e.gestalt);
var inp = $("#chat_input"); break;
var utterance = inp.val(); case "message":
inp.val(""); if (e.message === "wake") {
if (utterance) { wsconn.forceclose();
World.send(["broker", 0, chatEvent(this.nym(), return;
this.currentStatus(),
utterance)]);
} }
}); switch (e.message[0]) {
case "jQuery":
Actor.subscribe( switch (e.message[1]) {
function () { return ["jQuery", "#nym", "change", __]; }, case "#send_chat":
function () { this.updateRoutes(); }); var inp = $("#chat_input");
var utterance = inp.val();
Actor.subscribe( inp.val("");
function () { return ["jQuery", "#status", "change", __]; }, if (utterance) {
function () { this.updateRoutes(); }); World.send(["broker", 0, chatEvent(this.nym(),
this.currentStatus(),
Actor.subscribe( utterance)]);
function () { return ["jQuery", "#wsurl", "change", __]; }, }
function () { break;
wsconn.forceclose(); case "#nym":
wsconn.wsurl = $("#wsurl").val(); case "#status":
}); World.updateRoutes(this.subscriptions());
break;
Actor.subscribe( case "#wsurl":
function () { return ["broker", 0, chatEvent(_$("who"), __, _$("what"), __)]; }, wsconn.forceclose();
function (who, what) { outputUtterance(who, what); }); wsconn.wsurl = $("#wsurl").val();
})); break;
default:
console.log("Got jquery event from as-yet-unhandled subscription",
e.message[2], e.message[3]);
}
break;
case "broker":
if (e.message[2][0] === "chatEvent") {
outputUtterance(chatEventNym(e.message[2]),
chatEventUtterance(e.message[2]));
}
break;
default:
break;
}
break;
}
}
});
}); });
G.startStepping(); G.startStepping();
}); });

View File

@ -1,27 +0,0 @@
<!doctype html>
<html>
<head>
<title>JS Marketplace: DOM Example</title>
<meta charset="utf-8">
<link href="../../third-party/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="../../third-party/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet">
<script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../dist/minimart.js"></script>
<script src="index.js"></script>
</head>
<body>
<h1>DOM example</h1>
<div class="container-fluid">
<div class="row-fluid">
<div class="span3 well" id="counter-holder">
</div>
<div class="span9 well" id="clicker-holder">
</div>
</div>
<div class="row-fluid">
<div class="span12" id="spy-holder">
</div>
</div>
</div>
</body>
</html>

View File

@ -1,49 +0,0 @@
var G;
$(document).ready(function () {
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
G = new Minimart.Ground(function () {
console.log('starting ground boot');
// World.spawn(new Spy("GROUND", true));
Minimart.DOM.spawnDOMDriver();
Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy");
World.spawn({
boot: function () {
return [pub(["DOM", "#clicker-holder", "clicker",
["button", ["span", [["style", "font-style: italic"]], "Click me!"]]]),
pub("bump_count"),
sub(["jQuery", "button.clicker", "click", __])];
},
handleEvent: function (e) {
if (e.type === "message" && e.message[0] === "jQuery") {
World.send("bump_count");
}
}
});
World.spawn({
counter: 0,
boot: function () {
this.updateState();
},
updateState: function () {
World.updateRoutes([sub("bump_count"),
pub(["DOM", "#counter-holder", "counter",
["div",
["p", "The current count is: ", this.counter]]])]);
},
handleEvent: function (e) {
if (e.type === "message" && e.message === "bump_count") {
this.counter++;
this.updateState();
}
}
});
});
G.startStepping();
});

View File

@ -1,27 +0,0 @@
<!doctype html>
<html>
<head>
<title>JS Marketplace: DOM Example</title>
<meta charset="utf-8">
<link href="../../third-party/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="../../third-party/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet">
<script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../dist/minimart.js"></script>
<script src="index.js"></script>
</head>
<body>
<h1>DOM example</h1>
<div class="container-fluid">
<div class="row-fluid">
<div class="span3 well" id="counter-holder">
</div>
<div class="span9 well" id="clicker-holder">
</div>
</div>
<div class="row-fluid">
<div class="span12" id="spy-holder">
</div>
</div>
</div>
</body>
</html>

View File

@ -1,35 +0,0 @@
var G;
$(document).ready(function () {
var World = Minimart.World;
var Actor = Minimart.Actor;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
G = new Minimart.Ground(function () {
console.log('starting ground boot');
// World.spawn(new Spy("GROUND", true));
Minimart.DOM.spawnDOMDriver();
Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy");
World.spawn(new Actor(function () {
Actor.subscribe(
function () { return ["jQuery", "button.clicker", "click", __]; },
function () {
World.send("bump_count");
});
Actor.advertise(
function () { return "bump_count"; });
Actor.advertise(
function () {
return ["DOM", "#clicker-holder", "clicker",
["button", ["span", [["style", "font-style: italic"]], "Click me!"]]];
});
}));
World.spawn(new Minimart.Worker("worker.js"));
});
G.startStepping();
});

View File

@ -1,31 +0,0 @@
importScripts("../../dist/minimart.js");
var World = Minimart.World;
var Actor = Minimart.Actor;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
new Minimart.WorkerGround(function () {
console.log('starting worker boot');
World.spawn(new Actor(function () {
this.counter = 0;
Actor.subscribe(
function () { return "bump_count"; },
{ metaLevel: 1},
function () {
this.counter++;
this.updateRoutes();
});
Actor.advertise(
function () {
return ["DOM", "#counter-holder", "counter",
["div",
["p", "The current count is: ", this.counter]]];
},
{ metaLevel: 1});
}));
}).startStepping();

View File

@ -3,10 +3,24 @@
<head> <head>
<title>JS Marketplace: DOM Example</title> <title>JS Marketplace: DOM Example</title>
<meta charset="utf-8"> <meta charset="utf-8">
<link href="../../third-party/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <link href="../../third-party/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="../../third-party/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet"> <link href="../../third-party/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet">
<link href="style.css" rel="stylesheet">
<!-- <script src="../../third-party/bootstrap/js/bootstrap.min.js"></script> -->
<script src="../../third-party/jquery-2.0.3.min.js"></script> <script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../dist/minimart.js"></script>
<script src="../../route.js"></script>
<script src="../../marketplace.js"></script>
<script src="../../spy.js"></script>
<script src="../../dom-driver.js"></script>
<script src="../../routing-table-widget.js"></script>
<link href="../../routing-table-widget.css" rel="stylesheet">
<script src="../../jquery-driver.js"></script>
<script src="../../wake-detector.js"></script>
<script src="../../websocket-driver.js"></script>
<script src="index.js"></script> <script src="index.js"></script>
</head> </head>
<body> <body>

View File

@ -1,51 +1,40 @@
var G; var G;
$(document).ready(function () { $(document).ready(function () {
var World = Minimart.World; G = new Ground(function () {
var Actor = Minimart.Actor;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
G = new Minimart.Ground(function () {
console.log('starting ground boot'); console.log('starting ground boot');
// World.spawn(new Spy("GROUND", true)); // World.spawn(new Spy("GROUND", true));
Minimart.DOM.spawnDOMDriver(); spawnDOMDriver();
Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy"); spawnRoutingTableWidget("#spy-holder", "spy");
World.spawn(new Actor(function () { World.spawn({
Actor.subscribe( handleEvent: function (e) {
function () { return ["jQuery", "button.clicker", "click", __]; }, if (e.type === "message" && e.message[0] === "jQuery") {
function () {
World.send("bump_count"); World.send("bump_count");
}); }
}
}, [pub(["DOM", "#clicker-holder", "clicker",
["button", ["span", {"style": "font-style: italic"}, "Click me!"]]]),
pub("bump_count"),
sub(["jQuery", "button.clicker", "click", __])]);
Actor.advertise( World.spawn({
function () { return "bump_count"; }); counter: 0,
Actor.advertise( boot: function () {
function () { this.updateState();
return ["DOM", "#clicker-holder", "clicker", },
["button", ["span", [["style", "font-style: italic"]], "Click me!"]]]; updateState: function () {
}); World.updateRoutes([sub("bump_count"),
})); pub(["DOM", "#counter-holder", "counter",
["div",
World.spawn(new Actor(function () { ["p", "The current count is: ", this.counter]]])]);
this.counter = 0; },
handleEvent: function (e) {
Actor.subscribe( if (e.type === "message" && e.message === "bump_count") {
function () { return "bump_count"; },
function () {
this.counter++; this.counter++;
this.updateRoutes(); this.updateState();
}); }
}
Actor.advertise( });
function () {
return ["DOM", "#counter-holder", "counter",
["div",
["p", "The current count is: ", this.counter]]];
});
}));
}); });
G.startStepping(); G.startStepping();
}); });

0
examples/dom/style.css Normal file
View File

View File

@ -1,26 +0,0 @@
<!doctype html>
<html>
<head>
<title>JS Marketplace: Multi-user DOM Example</title>
<meta charset="utf-8">
<link href="../../third-party/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../dist/minimart.js"></script>
<script src="index.js"></script>
</head>
<body>
<h1>DOM example</h1>
<div class="container-fluid">
<div class="row-fluid">
<div class="span3 well" id="counter-holder">
</div>
<div class="span9 well" id="clicker-holder">
</div>
</div>
<div class="row-fluid">
<div class="span12" id="spy-holder">
</div>
</div>
</div>
</body>
</html>

View File

@ -1,75 +0,0 @@
var G;
$(document).ready(function () {
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
G = new Minimart.Ground(function () {
var localId = "instance-" + Math.floor(Math.random() * 65536);
function domWrap(selector, fragmentClass, fragmentSpec) {
return ["broker", 0, ["multidom", "DOM", selector, fragmentClass, fragmentSpec]];
}
function jQueryWrap(selector, eventName, eventValue) {
var v = eventValue instanceof Event || eventValue instanceof $.Event
? Minimart.JQuery.simplifyDOMEvent(eventValue)
: eventValue;
return ["broker", 0, ["multidom", "jQuery", selector, eventName, v]];
}
var wsconn = new Minimart.WebSocket.WebSocketConnection(
"broker", "ws://server.minimart.leastfixedpoint.com:8000/", true);
World.spawn(wsconn);
Minimart.DOM.spawnDOMDriver(domWrap, jQueryWrap); // remote
Minimart.DOM.spawnDOMDriver(); // local
Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy"); // local
World.spawn({
boot: function () {
return [pub(domWrap("#clicker-holder", localId + "-clicker",
["button", ["span", [["style", "font-style: italic"]],
"Click me! (" + localId + ")"]])),
pub("bump_count"),
sub(jQueryWrap("button."+localId+"-clicker", "click", __))];
},
handleEvent: function (e) {
console.log(JSON.stringify(e));
if (e.type === "message"
&& e.message[0] === "broker"
&& Array.isArray(e.message[2])
&& e.message[2][0] === "multidom"
&& e.message[2][1] === "jQuery")
{
World.send("bump_count");
}
}
});
World.spawn({
counter: 0,
boot: function () {
this.updateState();
},
updateState: function () {
World.updateRoutes([sub("bump_count"),
pub(domWrap("#counter-holder", localId + "-counter",
["div",
["p", "The current count for ",
localId,
" is: ",
this.counter]]))]);
},
handleEvent: function (e) {
if (e.type === "message" && e.message === "bump_count") {
this.counter++;
this.updateState();
}
}
});
});
G.startStepping();
});

View File

@ -1,20 +0,0 @@
<!doctype html>
<html>
<head>
<title>JS Marketplace: Smoketest with Web Workers</title>
<meta charset="utf-8">
<script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../dist/minimart.js"></script>
<script src="index.js"></script>
<style>
#gestalt-display {
white-space: pre;
font-family: monospace;
}
</style>
</head>
<body>
<h1>Smoketest with Web Workers</h1>
<div id="gestalt-display"></div>
</body>
</html>

View File

@ -1,49 +0,0 @@
var G;
$(document).ready(function () {
var World = Minimart.World;
var Actor = Minimart.Actor;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
G = new Minimart.Ground(function () {
console.log('starting ground boot');
World.spawn({
name: 'GestaltDisplay',
boot: function () {
return [sub(__, 0, 10), pub(__, 0, 10)];
},
handleEvent: function (e) {
if (e.type === "routes") {
var gd = document.getElementById('gestalt-display');
var t = document.createTextNode(G.world.textProcessTree() + '\n' +
e.gestalt.pretty());
gd.innerHTML = '';
gd.appendChild(t);
}
}
});
World.spawn(new Actor(function () {
this.counter = 0;
this.step = function () {
if (this.listenerExists && this.counter < 10) {
World.send(["beep", this.counter++]);
return true;
} else {
return false;
}
};
Actor.advertise(function () { return ["beep", __]; });
Actor.observeSubscribers(
function () { return ["beep", __]; },
{ presence: "listenerExists" });
}));
World.spawn(new Minimart.Worker("worker.js"));
});
G.startStepping();
});

View File

@ -1,19 +0,0 @@
importScripts("../../dist/minimart.js");
var World = Minimart.World;
var Actor = Minimart.Actor;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
new Minimart.WorkerGround(function () {
console.log('starting worker boot');
World.spawn(new Actor(function () {
Actor.subscribe(
function () { return ["beep", _$("counter")]; },
{ metaLevel: 1 },
function (counter) {
console.log("beep!", counter);
});
}));
}).startStepping();

View File

@ -4,7 +4,9 @@
<title>JS Marketplace: Smoketest</title> <title>JS Marketplace: Smoketest</title>
<meta charset="utf-8"> <meta charset="utf-8">
<script src="../../third-party/jquery-2.0.3.min.js"></script> <script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../dist/minimart.js"></script> <script src="../../route.js"></script>
<script src="../../marketplace.js"></script>
<script src="../../spy.js"></script>
<script src="index.js"></script> <script src="index.js"></script>
</head> </head>
<body> <body>

View File

@ -1,33 +1,24 @@
var G; var G;
$(document).ready(function () { $(document).ready(function () {
var World = Minimart.World; G = new Ground(function () {
var Actor = Minimart.Actor;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
G = new Minimart.Ground(function () {
console.log('starting ground boot'); console.log('starting ground boot');
World.spawn(new Minimart.Spy("GROUND", true)); World.spawn(new Spy("GROUND", true));
World.spawn({
World.spawn(new Actor(function () { counter: 0,
this.counter = 0; handleEvent: function (e) {},
this.step = function () { step: function () {
World.send(["beep", this.counter++]); World.send(["beep", this.counter++]);
return this.counter <= 10; return this.counter <= 10;
}; }
}, [pub(["beep", __])]);
Actor.advertise(function () { return ["beep", __]; }); World.spawn({
})); handleEvent: function (e) {
if (e.type === "message" && e.message[0] === "beep") {
World.spawn(new Actor(function () { console.log("beep!", e.message[1]);
Actor.subscribe( }
function () { return ["beep", _$("counter")]; }, }
function (counter) { }, [sub(["beep", __])]);
console.log("beep!", counter);
});
}));
}); });
G.startStepping(); G.startStepping();
}); });

View File

@ -9,7 +9,16 @@
<link href="style.css" rel="stylesheet"> <link href="style.css" rel="stylesheet">
<script src="../../third-party/jquery-2.0.3.min.js"></script> <script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../dist/minimart.js"></script> <script src="../../route.js"></script>
<script src="../../marketplace.js"></script>
<script src="../../spy.js"></script>
<script src="../../dom-driver.js"></script>
<script src="../../routing-table-widget.js"></script>
<link href="../../routing-table-widget.css" rel="stylesheet">
<script src="../../jquery-driver.js"></script>
<script src="../../wake-detector.js"></script>
<script src="index.js"></script> <script src="index.js"></script>
</head> </head>
<body> <body>

View File

@ -1,13 +1,6 @@
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// GUI // GUI
var Actor = Minimart.Actor;
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
function piece(text, pos, lo, hi, cls) { function piece(text, pos, lo, hi, cls) {
return "<span class='"+cls+"'>"+ return "<span class='"+cls+"'>"+
((pos >= lo && pos < hi) ((pos >= lo && pos < hi)
@ -17,47 +10,53 @@ function piece(text, pos, lo, hi, cls) {
} }
function spawnGui() { function spawnGui() {
World.spawn(new Actor(function () { World.spawn({
Actor.subscribe( boot: function () {
function () { return ["jQuery", "#inputRow", "+keypress", _$("event")]; }, World.updateRoutes([sub(["jQuery", "#inputRow", "+keypress", __]),
function (event) { sub(["fieldContents", __, __], 0, 1),
var keycode = event.keyCode; sub(["highlight", __], 0, 1)]);
var character = String.fromCharCode(event.charCode); },
if (keycode === 37 /* left */) { fieldContentsSpec: route.compileProjection(["fieldContents", _$, _$]),
World.send(["fieldCommand", "cursorLeft"]); highlightSpec: route.compileProjection(["highlight", _$]),
} else if (keycode === 39 /* right */) { handleEvent: function (e) {
World.send(["fieldCommand", "cursorRight"]); switch (e.type) {
} else if (keycode === 9 /* tab */) { case "routes":
// ignore var text = "", pos = 0, highlight = false;
} else if (keycode === 8 /* backspace */) { // BUG: escape text!
World.send(["fieldCommand", "backspace"]); var fc = route.matcherKeys(e.gestalt.project(this.fieldContentsSpec, true));
} else if (character) { if (fc.length > 0) {
World.send(["fieldCommand", ["insert", character]]); text = fc[0][0];
} pos = fc[0][1];
}); }
var hl = route.matcherKeys(e.gestalt.project(this.highlightSpec, true));
Actor.observeAdvertisers( if (hl.length > 0) {
function () { return ["fieldContents", _$("text"), _$("pos")]; }, highlight = hl[0][0];
{ singleton: "field" }, }
updateDisplay); $("#fieldContents")[0].innerHTML = highlight
? piece(text, pos, 0, highlight[0], "normal") +
Actor.observeAdvertisers( piece(text, pos, highlight[0], highlight[1], "highlight") +
function () { return ["highlight", _$("state")]; }, piece(text, pos, highlight[1], text.length + 1, "normal")
{ singleton: "highlight" }, : piece(text, pos, 0, text.length + 1, "normal");
updateDisplay); break;
case "message":
function updateDisplay() { if (e.message[0] === "jQuery") { // it's a keypress event
// BUG: escape text! var keycode = e.message[3].keyCode;
var text = this.field ? this.field.text : ""; var character = String.fromCharCode(e.message[3].charCode);
var pos = this.field ? this.field.pos : 0; if (keycode === 37 /* left */) {
var highlight = this.highlight ? this.highlight.state : false; World.send(["fieldCommand", "cursorLeft"]);
$("#fieldContents")[0].innerHTML = highlight } else if (keycode === 39 /* right */) {
? piece(text, pos, 0, highlight[0], "normal") + World.send(["fieldCommand", "cursorRight"]);
piece(text, pos, highlight[0], highlight[1], "highlight") + } else if (keycode === 9 /* tab */) {
piece(text, pos, highlight[1], text.length + 1, "normal") // ignore
: piece(text, pos, 0, text.length + 1, "normal"); } else if (keycode === 8 /* backspace */) {
} World.send(["fieldCommand", "backspace"]);
})); } else if (character) {
World.send(["fieldCommand", ["insert", character]]);
}
}
}
}
});
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -65,80 +64,92 @@ function spawnGui() {
function spawnModel() { function spawnModel() {
var initialContents = "initial"; var initialContents = "initial";
World.spawn(new Actor(function () { World.spawn({
this.fieldContents = initialContents; fieldContents: initialContents,
this.cursorPos = initialContents.length; /* positions address gaps between characters */ cursorPos: initialContents.length, /* positions address gaps between characters */
boot: function () {
Actor.advertise( World.updateRoutes(this.subscriptions());
function () { return ["fieldContents", this.fieldContents, this.cursorPos]; }); },
subscriptions: function () {
Actor.subscribe( return [sub(["fieldCommand", __]),
function () { return ["fieldCommand", _$("command")]; }, pub(["fieldContents", this.fieldContents, this.cursorPos])];
function (command) { },
if (command === "cursorLeft") { handleEvent: function (e) {
this.cursorPos--; switch (e.type) {
if (this.cursorPos < 0) case "message":
this.cursorPos = 0; if (e.message[1] === "cursorLeft") {
} else if (command === "cursorRight") { this.cursorPos--;
this.cursorPos++; if (this.cursorPos < 0)
if (this.cursorPos > this.fieldContents.length) this.cursorPos = 0;
this.cursorPos = this.fieldContents.length; } else if (e.message[1] === "cursorRight") {
} else if (command === "backspace" && this.cursorPos > 0) { this.cursorPos++;
this.fieldContents = if (this.cursorPos > this.fieldContents.length)
this.fieldContents.substring(0, this.cursorPos - 1) + this.cursorPos = this.fieldContents.length;
this.fieldContents.substring(this.cursorPos); } else if (e.message[1] === "backspace" && this.cursorPos > 0) {
this.cursorPos--; this.fieldContents =
} else if (command.constructor === Array && command[0] === "insert") { this.fieldContents.substring(0, this.cursorPos - 1) +
var newText = command[1]; this.fieldContents.substring(this.cursorPos);
this.fieldContents = this.cursorPos--;
this.fieldContents.substring(0, this.cursorPos) + } else if (e.message[1].constructor === Array && e.message[1][0] === "insert") {
newText + var newText = e.message[1][1];
this.fieldContents.substring(this.cursorPos); this.fieldContents =
this.cursorPos += newText.length; this.fieldContents.substring(0, this.cursorPos) +
} newText +
this.updateRoutes(); this.fieldContents.substring(this.cursorPos);
}); this.cursorPos += newText.length;
})); }
World.updateRoutes(this.subscriptions());
break;
}
}
});
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Search engine // Search engine
function spawnSearch() { function spawnSearch() {
World.spawn(new Actor(function () { World.spawn({
var self = this; fieldContents: "",
self.fieldContents = ""; highlight: false,
self.highlight = false; fieldContentsSpec: route.compileProjection(["fieldContents", _$, _$]),
boot: function () {
Actor.advertise( World.updateRoutes(this.subscriptions());
function () { return ["highlight", self.highlight]; }); },
subscriptions: function () {
Actor.subscribe( return [sub(["jQuery", "#searchBox", "input", __]),
function () { return ["jQuery", "#searchBox", "input", _$("event")]; }, sub(["fieldContents", __, __], 0, 1),
search); pub(["highlight", this.highlight])];
},
Actor.observeAdvertisers( search: function () {
function () { return ["fieldContents", _$("text"), _$("pos")]; }, var searchtext = $("#searchBox")[0].value;
{ singleton: "field" }, var oldHighlight = this.highlight;
function () { if (searchtext) {
self.fieldContents = self.field ? self.field.text : ""; var pos = this.fieldContents.indexOf(searchtext);
search(); this.highlight = (pos !== -1) && [pos, pos + searchtext.length];
}); } else {
this.highlight = false;
function search() { }
var searchtext = $("#searchBox")[0].value; if (JSON.stringify(oldHighlight) !== JSON.stringify(this.highlight)) {
var oldHighlight = self.highlight; World.updateRoutes(this.subscriptions());
if (searchtext) { }
var pos = self.fieldContents.indexOf(searchtext); },
self.highlight = (pos !== -1) && [pos, pos + searchtext.length]; handleEvent: function (e) {
} else { switch (e.type) {
self.highlight = false; case "routes":
var fc = route.matcherKeys(e.gestalt.project(this.fieldContentsSpec, true));
if (fc.length > 0) {
this.fieldContents = fc[0][0];
}
this.search();
break;
case "message":
if (e.message[0] === "jQuery") { // it's a search box input event
this.search();
}
}
} }
if (JSON.stringify(oldHighlight) !== JSON.stringify(self.highlight)) { });
self.updateRoutes();
}
}
}));
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -146,10 +157,10 @@ function spawnSearch() {
var G; var G;
$(document).ready(function () { $(document).ready(function () {
G = new Minimart.Ground(function () { G = new Ground(function () {
Minimart.JQuery.spawnJQueryDriver(); spawnJQueryDriver();
Minimart.DOM.spawnDOMDriver(); spawnDOMDriver();
Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy"); spawnRoutingTableWidget("#spy-holder", "spy");
spawnGui(); spawnGui();
spawnModel(); spawnModel();

46
jquery-driver.js vendored Normal file
View File

@ -0,0 +1,46 @@
// JQuery event driver
function spawnJQueryDriver(baseSelector, metaLevel) {
metaLevel = metaLevel || 0;
var d = new DemandMatcher(["jQuery", _$, _$, __], metaLevel, {demandSideIsSubscription: true});
d.onDemandIncrease = function (captures) {
var selector = captures[0];
var eventName = captures[1];
World.spawn(new JQueryEventRouter(baseSelector, selector, eventName, metaLevel),
[pub(["jQuery", selector, eventName, __], metaLevel),
pub(["jQuery", selector, eventName, __], metaLevel, 1)]);
};
World.spawn(d);
}
function JQueryEventRouter(baseSelector, selector, eventName, metaLevel) {
var self = this;
this.baseSelector = baseSelector || null;
this.selector = selector;
this.eventName = eventName;
this.metaLevel = metaLevel || 0;
this.preventDefault = (this.eventName.charAt(0) !== "+");
this.handler =
World.wrap(function (e) {
World.send(["jQuery", self.selector, self.eventName, e], self.metaLevel);
if (self.preventDefault) e.preventDefault();
return !self.preventDefault;
});
this.computeNodes().on(this.preventDefault ? this.eventName : this.eventName.substring(1),
this.handler);
}
JQueryEventRouter.prototype.handleEvent = function (e) {
if (e.type === "routes" && e.gestalt.isEmpty()) {
this.computeNodes().off(this.eventName, this.handler);
World.exit();
}
};
JQueryEventRouter.prototype.computeNodes = function () {
if (this.baseSelector) {
return $(this.baseSelector).children(this.selector).addBack(this.selector);
} else {
return $(this.selector);
}
};

View File

@ -1,30 +1,27 @@
var Route = require("./route.js");
var Util = require("./util.js");
///////////////////////////////////////////////////////////////////////////
// TODO: trigger-guards as per minimart // TODO: trigger-guards as per minimart
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
/* Events and Actions */ /* Events and Actions */
var __ = Route.__; var __ = route.__;
var _$ = Route._$; var _$ = route._$;
function sub(pattern, metaLevel, level) { function sub(pattern, metaLevel, level) {
return Route.simpleGestalt(false, pattern, metaLevel, level); return route.simpleGestalt(false, pattern, metaLevel, level);
} }
function pub(pattern, metaLevel, level) { function pub(pattern, metaLevel, level) {
return Route.simpleGestalt(true, pattern, metaLevel, level); return route.simpleGestalt(true, pattern, metaLevel, level);
} }
function spawn(behavior) { function spawn(behavior, initialGestalts) {
return { type: "spawn", behavior: behavior }; return { type: "spawn",
behavior: behavior,
initialGestalt: route.gestaltUnion(initialGestalts || []) };
} }
function updateRoutes(gestalts) { function updateRoutes(gestalts) {
return { type: "routes", gestalt: Route.gestaltUnion(gestalts) }; return { type: "routes", gestalt: route.gestaltUnion(gestalts) };
} }
function pendingRoutingUpdate(aggregate, affectedSubgestalt, knownTarget) { function pendingRoutingUpdate(aggregate, affectedSubgestalt, knownTarget) {
@ -52,11 +49,11 @@ function World(bootFn) {
this.alive = true; this.alive = true;
this.eventQueue = []; this.eventQueue = [];
this.runnablePids = {}; this.runnablePids = {};
this.partialGestalt = Route.emptyGestalt; // Only gestalt from local processes this.partialGestalt = route.emptyGestalt; // Only gestalt from local processes
this.fullGestalt = Route.emptyGestalt ;; // partialGestalt unioned with downwardGestalt this.fullGestalt = route.emptyGestalt ;; // partialGestalt unioned with downwardGestalt
this.processTable = {}; this.processTable = {};
this.tombstones = {}; this.tombstones = {};
this.downwardGestalt = Route.emptyGestalt; this.downwardGestalt = route.emptyGestalt;
this.processActions = []; this.processActions = [];
this.asChild(-1, bootFn, true); this.asChild(-1, bootFn, true);
} }
@ -83,8 +80,8 @@ World.updateRoutes = function (gestalts) {
World.current().enqueueAction(World.activePid(), updateRoutes(gestalts)); World.current().enqueueAction(World.activePid(), updateRoutes(gestalts));
}; };
World.spawn = function (behavior) { World.spawn = function (behavior, initialGestalts) {
World.current().enqueueAction(World.activePid(), spawn(behavior)); World.current().enqueueAction(World.activePid(), spawn(behavior, initialGestalts));
}; };
World.exit = function (exn) { World.exit = function (exn) {
@ -137,7 +134,7 @@ World.prototype.enqueueAction = function (pid, action) {
World.prototype.isInert = function () { World.prototype.isInert = function () {
return this.eventQueue.length === 0 return this.eventQueue.length === 0
&& this.processActions.length === 0 && this.processActions.length === 0
&& Route.is_emptySet(this.runnablePids); && route.is_emptySet(this.runnablePids);
}; };
World.prototype.markPidRunnable = function (pid) { World.prototype.markPidRunnable = function (pid) {
@ -177,16 +174,16 @@ World.prototype.kill = function (pid, exn) {
console.log("Process exited", pid, exn); console.log("Process exited", pid, exn);
} }
var p = this.processTable[pid]; var p = this.processTable[pid];
if (p && p.behavior.trapexit) {
this.asChild(pid, function () { return p.behavior.trapexit(exn); });
}
delete this.processTable[pid]; delete this.processTable[pid];
if (p) { if (p) {
if (p.behavior.trapexit) {
this.asChild(pid, function () { return p.behavior.trapexit(exn); }, true);
}
if (exn) { if (exn) {
p.exitReason = exn; p.exitReason = exn;
this.tombstones[pid] = p; this.tombstones[pid] = p;
} }
this.applyAndIssueRoutingUpdate(p.gestalt, Route.emptyGestalt); this.applyAndIssueRoutingUpdate(p.gestalt, route.emptyGestalt);
} }
}; };
@ -224,18 +221,13 @@ World.prototype.performAction = function (pid, action) {
switch (action.type) { switch (action.type) {
case "spawn": case "spawn":
var pid = World.nextPid++; var pid = World.nextPid++;
var entry = { gestalt: Route.emptyGestalt, behavior: action.behavior }; var newGestalt = action.initialGestalt.label(pid);
this.processTable[pid] = entry; this.processTable[pid] = { gestalt: newGestalt, behavior: action.behavior };
if (entry.behavior.boot) { if (action.behavior.boot) {
var initialGestalts = this.asChild(pid, function () { return entry.behavior.boot() }); this.asChild(pid, function () { action.behavior.boot() });
if (initialGestalts) {
entry.gestalt = Route.gestaltUnion(initialGestalts).label(pid);
}
this.markPidRunnable(pid); this.markPidRunnable(pid);
} }
if (!Route.emptyGestalt.equals(entry.gestalt)) { this.applyAndIssueRoutingUpdate(route.emptyGestalt, newGestalt, pid);
this.applyAndIssueRoutingUpdate(Route.emptyGestalt, entry.gestalt, pid);
}
break; break;
case "routes": case "routes":
if (pid in this.processTable) { if (pid in this.processTable) {
@ -318,11 +310,6 @@ World.prototype.dispatchEvent = function (e) {
} }
}; };
World.prototype.boot = function () {
// Needed in order for the new World to be marked as "runnable", so
// its initial actions get performed.
};
World.prototype.handleEvent = function (e) { World.prototype.handleEvent = function (e) {
switch (e.type) { switch (e.type) {
case "routes": case "routes":
@ -356,7 +343,7 @@ World.prototype.processTree = function () {
for (var pid in this.tombstones) { for (var pid in this.tombstones) {
kids.push([pid, this.tombstones[pid]]); kids.push([pid, this.tombstones[pid]]);
} }
kids.sort(function (a, b) { return a[0] - b[0] }); kids.sort();
return kids; return kids;
}; };
@ -364,7 +351,7 @@ World.prototype.textProcessTree = function (ownPid) {
var lines = []; var lines = [];
function dumpProcess(prefix, pid, p) { function dumpProcess(prefix, pid, p) {
if (Array.isArray(p)) { if (p instanceof Array) {
lines.push(prefix + '--+ ' + pid); lines.push(prefix + '--+ ' + pid);
for (var i = 0; i < p.length; i++) { for (var i = 0; i < p.length; i++) {
dumpProcess(prefix + ' |', p[i][0], p[i][1]); dumpProcess(prefix + ' |', p[i][0], p[i][1]);
@ -373,16 +360,11 @@ World.prototype.textProcessTree = function (ownPid) {
} else { } else {
var label = p.behavior.name || p.behavior.constructor.name || ''; var label = p.behavior.name || p.behavior.constructor.name || '';
var tombstoneString = p.exitReason ? ' (EXITED: ' + p.exitReason + ') ' : ''; var tombstoneString = p.exitReason ? ' (EXITED: ' + p.exitReason + ') ' : '';
var stringifiedState; lines.push(prefix + '-- ' + pid + ': ' + label +
try { tombstoneString +
var rawState = p.behavior.debugState ? p.behavior.debugState() : p.behavior; JSON.stringify(p.behavior, function (k, v) {
stringifiedState = JSON.stringify(rawState, function (k, v) { return k === 'name' ? undefined : v;
return (k === 'name') ? undefined : v; }));
});
} catch (e) {
stringifiedState = "(cannot convert process state to JSON)";
}
lines.push(prefix + '-- ' + pid + ': ' + label + tombstoneString + stringifiedState);
} }
} }
@ -404,16 +386,13 @@ World.prototype.clearTombstones = function () {
/* Utilities: matching demand for some service */ /* Utilities: matching demand for some service */
function DemandMatcher(projection, metaLevel, options) { function DemandMatcher(projection, metaLevel, options) {
options = Util.extend({ options = $.extend({
demandLevel: 0, demandLevel: 0,
supplyLevel: 0, supplyLevel: 0,
demandSideIsSubscription: false, demandSideIsSubscription: false
supplyProjection: projection
}, options); }, options);
this.demandPattern = Route.projectionToPattern(projection); this.pattern = route.projectionToPattern(projection);
this.supplyPattern = Route.projectionToPattern(options.supplyProjection); this.projectionSpec = route.compileProjection(projection);
this.demandProjectionSpec = Route.compileProjection(projection);
this.supplyProjectionSpec = Route.compileProjection(options.supplyProjection);
this.metaLevel = metaLevel | 0; this.metaLevel = metaLevel | 0;
this.demandLevel = options.demandLevel; this.demandLevel = options.demandLevel;
this.supplyLevel = options.supplyLevel; this.supplyLevel = options.supplyLevel;
@ -428,24 +407,10 @@ function DemandMatcher(projection, metaLevel, options) {
this.currentSupply = {}; this.currentSupply = {};
} }
DemandMatcher.prototype.debugState = function () {
return {
demandPattern: this.demandPattern,
supplyPattern: this.supplyPattern,
metaLevel: this.metaLevel,
demandLevel: this.demandLevel,
supplyLevel: this.supplyLevel,
demandSideIsSubscription: this.demandSideIsSubscription
// , currentDemand: this.currentDemand
// , currentSupply: this.currentSupply
};
};
DemandMatcher.prototype.boot = function () { DemandMatcher.prototype.boot = function () {
var observerLevel = 1 + Math.max(this.demandLevel, this.supplyLevel); var observerLevel = 1 + Math.max(this.demandLevel, this.supplyLevel);
return [sub(this.demandPattern, this.metaLevel, observerLevel), World.updateRoutes([sub(this.pattern, this.metaLevel, observerLevel),
pub(this.supplyPattern, this.metaLevel, observerLevel)]; pub(this.pattern, this.metaLevel, observerLevel)]);
}; };
DemandMatcher.prototype.handleEvent = function (e) { DemandMatcher.prototype.handleEvent = function (e) {
@ -455,20 +420,20 @@ DemandMatcher.prototype.handleEvent = function (e) {
}; };
DemandMatcher.prototype.handleGestalt = function (gestalt) { DemandMatcher.prototype.handleGestalt = function (gestalt) {
var newDemandMatcher = gestalt.project(this.demandProjectionSpec, var newDemandMatcher = gestalt.project(this.projectionSpec,
!this.demandSideIsSubscription, !this.demandSideIsSubscription,
this.metaLevel, this.metaLevel,
this.demandLevel); this.demandLevel);
var newSupplyMatcher = gestalt.project(this.supplyProjectionSpec, var newSupplyMatcher = gestalt.project(this.projectionSpec,
this.demandSideIsSubscription, this.demandSideIsSubscription,
this.metaLevel, this.metaLevel,
this.supplyLevel); this.supplyLevel);
var newDemand = Route.arrayToSet(Route.matcherKeys(newDemandMatcher)); var newDemand = route.arrayToSet(route.matcherKeys(newDemandMatcher));
var newSupply = Route.arrayToSet(Route.matcherKeys(newSupplyMatcher)); var newSupply = route.arrayToSet(route.matcherKeys(newSupplyMatcher));
var demandDelta = Route.setSubtract(newDemand, this.currentDemand); var demandDelta = route.setSubtract(newDemand, this.currentDemand);
var supplyDelta = Route.setSubtract(this.currentSupply, newSupply); var supplyDelta = route.setSubtract(this.currentSupply, newSupply);
var demandIncr = Route.setSubtract(demandDelta, newSupply); var demandIncr = route.setSubtract(demandDelta, newSupply);
var supplyDecr = Route.setIntersect(supplyDelta, newDemand); var supplyDecr = route.setIntersect(supplyDelta, newDemand);
this.currentDemand = newDemand; this.currentDemand = newDemand;
this.currentSupply = newSupply; this.currentSupply = newSupply;
for (var k in demandIncr) this.onDemandIncrease(demandIncr[k]); for (var k in demandIncr) this.onDemandIncrease(demandIncr[k]);
@ -512,19 +477,59 @@ Deduplicator.prototype.expireMessages = function () {
} }
}; };
/////////////////////////////////////////////////////////////////////////// /*---------------------------------------------------------------------------*/
/* Ground interface */
module.exports.__ = __; function Ground(bootFn) {
module.exports._$ = _$; var self = this;
this.stepperId = null;
World.withWorldStack([[this, -1]], function () {
self.world = new World(bootFn);
});
}
module.exports.sub = sub; Ground.prototype.step = function () {
module.exports.pub = pub; var self = this;
module.exports.spawn = spawn; return World.withWorldStack([[this, -1]], function () {
module.exports.updateRoutes = updateRoutes; return self.world.step();
module.exports.sendMessage = sendMessage; });
module.exports.shutdownWorld = shutdownWorld; };
module.exports.World = World; Ground.prototype.checkPid = function (pid) {
module.exports.DemandMatcher = DemandMatcher; if (pid !== -1) console.error("Weird pid in Ground markPidRunnable", pid);
module.exports.Deduplicator = Deduplicator; };
module.exports.Route = Route;
Ground.prototype.markPidRunnable = function (pid) {
this.checkPid(pid);
this.startStepping();
};
Ground.prototype.startStepping = function () {
var self = this;
if (this.stepperId) return;
if (this.step()) {
this.stepperId = setTimeout(function () {
self.stepperId = null;
self.startStepping();
}, 0);
}
};
Ground.prototype.stopStepping = function () {
if (this.stepperId) {
clearTimeout(this.stepperId);
this.stepperId = null;
}
};
Ground.prototype.enqueueAction = function (pid, action) {
this.checkPid(pid);
if (action.type === 'routes') {
if (!action.gestalt.isEmpty()) {
console.error("You have subscribed to a nonexistent event source.",
action.gestalt.pretty());
}
} else {
console.error("You have sent a message into the outer void.", action);
}
};

View File

@ -1,24 +0,0 @@
{
"name": "js-marketplace",
"version": "0.0.0",
"description": "Network Calculus in the browser",
"homepage": "https://github.com/tonyg/js-marketplace",
"main": "src/main.js",
"scripts": {
"clean": "rm -f dist/*",
"build-debug": "browserify src/main.js -d -s Minimart -o dist/minimart.js",
"build-min": "browserify src/main.js -s Minimart -o dist/_minimart.js && uglifyjs dist/_minimart.js -o dist/minimart.min.js && rm dist/_minimart.js",
"build": "npm run build-debug && npm run build-min",
"watch": "watchify src/main.js -d -s Minimart -o dist/minimart.js",
"test": "mocha",
"prepublish": "npm run build"
},
"author": "Tony Garnock-Jones <tonyg@ccs.neu.edu>",
"devDependencies": {
"watchify": "^0.6.1",
"uglify-js": "^2.4.12",
"browserify": "^3.30.4",
"mocha": "^1.17.1",
"expect.js": "^0.3.1"
}
}

1531
route.js Normal file

File diff suppressed because it is too large Load Diff

49
routing-table-widget.css Normal file
View File

@ -0,0 +1,49 @@
.routing-table li { list-style-type: none; }
.routing-table .sub .pattern { background-color: lightblue; }
.routing-table .sub .level { background-color: lightblue; }
.routing-table .pub .pattern { background-color: lightgreen; }
.routing-table .pub .level { background-color: lightgreen; }
.routing-table .route {
display: inline-block;
height: 2em;
}
.routing-table .route .level { font-style: italic; line-height: 1em; padding: 0 0.5em; }
.routing-table .route .polarity { display: none; }
.routing-table .route .pattern { padding-right: 0.5em; }
.routing-table .route:before {
content: " ";
float: left;
}
.routing-table .pub:before {
border-right: 0.6em solid lightgreen;
border-top: 0.75em solid transparent;
border-bottom: 0.75em solid transparent;
}
.routing-table .sub:before {
border-left: 0.6em solid transparent;
border-top: 0.75em solid lightblue;
border-bottom: 0.75em solid lightblue;
}
.routing-table .route:after {
content: " ";
float: right;
}
.routing-table .pub:after {
border-left: 0.6em solid lightgreen;
border-top: 0.75em solid transparent;
border-bottom: 0.75em solid transparent;
}
.routing-table .sub:after {
border-right: 0.6em solid transparent;
border-top: 0.75em solid lightblue;
border-bottom: 0.75em solid lightblue;
}

View File

@ -1,24 +1,16 @@
var Minimart = require("./minimart.js"); function spawnRoutingTableWidget(selector, fragmentClass, observationLevel) {
var Route = Minimart.Route;
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
function spawnRoutingTableWidget(selector, fragmentClass, domWrap, observationLevel) {
observationLevel = observationLevel || 10; observationLevel = observationLevel || 10;
// ^ arbitrary: should be Infinity, when route.js supports it. TODO // ^ arbitrary: should be Infinity, when route.js supports it. TODO
domWrap = domWrap || Minimart.DOM.defaultWrapFunction;
World.spawn({ World.spawn({
boot: function () { this.updateState(); }, boot: function () { this.updateState(); },
state: Route.emptyGestalt.serialize(), state: route.emptyGestalt.serialize(),
nextState: Route.emptyGestalt.serialize(), nextState: route.emptyGestalt.serialize(),
timer: false, timer: false,
localGestalt: (sub( domWrap(selector, fragmentClass, __), 0, 2) localGestalt: (sub( ["DOM", selector, fragmentClass, __], 0, 2)
.union(pub(domWrap(selector, fragmentClass, __), 0, 2)) .union(pub(["DOM", selector, fragmentClass, __], 0, 2))
.telescoped()), .telescoped()),
digestGestalt: function (g) { digestGestalt: function (g) {
@ -26,10 +18,11 @@ function spawnRoutingTableWidget(selector, fragmentClass, domWrap, observationLe
}, },
updateState: function () { updateState: function () {
var elts = ["pre", Route.deserializeGestalt(this.state).pretty()]; var elts = ["ul", {"class": "routing-table"},
["li", ["pre", route.deserializeGestalt(this.state).pretty()]]];
World.updateRoutes([sub(__, 0, observationLevel), World.updateRoutes([sub(__, 0, observationLevel),
pub(__, 0, observationLevel), pub(__, 0, observationLevel),
pub(domWrap(selector, fragmentClass, elts))]); pub(["DOM", selector, fragmentClass, elts])]);
}, },
handleEvent: function (e) { handleEvent: function (e) {
@ -52,5 +45,3 @@ function spawnRoutingTableWidget(selector, fragmentClass, domWrap, observationLe
}); });
} }
module.exports.spawnRoutingTableWidget = spawnRoutingTableWidget;

View File

@ -1,9 +1,4 @@
// Generic Spy // Generic Spy
var Minimart = require("./minimart.js");
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
function Spy(label, useJson, observationLevel) { function Spy(label, useJson, observationLevel) {
this.label = label || "SPY"; this.label = label || "SPY";
@ -12,7 +7,7 @@ function Spy(label, useJson, observationLevel) {
} }
Spy.prototype.boot = function () { Spy.prototype.boot = function () {
return [sub(__, 0, this.observationLevel), pub(__, 0, this.observationLevel)]; World.updateRoutes([sub(__, 0, this.observationLevel), pub(__, 0, this.observationLevel)]);
}; };
Spy.prototype.handleEvent = function (e) { Spy.prototype.handleEvent = function (e) {
@ -34,5 +29,3 @@ Spy.prototype.handleEvent = function (e) {
break; break;
} }
}; };
module.exports.Spy = Spy;

View File

@ -1,268 +0,0 @@
var Minimart = require("./minimart.js");
var util = require("./util.js");
var World = Minimart.World;
var Route = Minimart.Route;
Actor._chunks = null;
function Actor(bootfn) {
return {
boot: function () {
delete this.boot;
var oldChunks = Actor._chunks;
try {
Actor._chunks = [];
bootfn.call(this);
var initialGestalt = finalizeActor(this, Actor._chunks);
Actor._chunks = oldChunks;
return [initialGestalt];
} catch (e) {
Actor._chunks = oldChunks;
throw e;
}
}
};
}
function checkChunks(type) {
if (!Actor._chunks) {
throw new Error("Call to Actor."+type+" outside of Actor constructor");
}
}
function extractChunk(type, kind, defaultOptions, args) {
var rawProjectionFn = args[0]
var options = null;
var handler = null;
if (typeof rawProjectionFn !== 'function') {
throw new Error("Actor."+type+" expects a function producing a pattern as first argument");
}
for (var i = 1; i < args.length; i++) { // NB: skip the first arg - it's rawProjectionFn
if (typeof args[i] === 'function') {
if (handler !== null) { throw new Error("Too many handler functions in Actor."+type); }
handler = args[i];
} else if (typeof args[i] === 'object') {
if (options !== null) { throw new Error("Too many options arguments in Actor."+type); }
options = args[i];
} else {
throw new Error("Unrecognised argument in Actor."+type);
}
}
options = options || {};
for (var k in options) {
if (!(k in defaultOptions)) {
throw new Error("Unrecognised option '"+k+"' in Actor."+type);
}
}
for (var k in defaultOptions) {
if (!(k in options)) {
options[k] = defaultOptions[k];
}
}
return {
type: type,
kind: kind,
rawProjectionFn: rawProjectionFn,
options: options,
handler: handler
};
}
function recordChunk(chunk) {
Actor._chunks.push(chunk);
}
function chunkExtractor(type, kind, defaultOptions) {
return function (/* ... */) {
checkChunks(type);
recordChunk(extractChunk(type,
kind,
defaultOptions,
Array.prototype.slice.call(arguments)));
};
}
var participantDefaults = {
metaLevel: 0,
when: function () { return true; }
};
var observerDefaults = {
metaLevel: 0,
level: 0,
when: function () { return true; },
presence: null,
name: null,
singleton: null,
set: null,
added: null,
removed: null
};
Actor.advertise = chunkExtractor('advertise', 'participant', participantDefaults);
Actor.subscribe = chunkExtractor('subscribe', 'participant', participantDefaults);
Actor.observeAdvertisers = chunkExtractor('observeAdvertisers', 'observer', observerDefaults);
Actor.observeSubscribers = chunkExtractor('observeSubscribers', 'observer', observerDefaults);
Actor.observeGestalt = function (gestaltFn, eventHandlerFn) {
checkChunks('observeGestalt');
recordChunk({
type: 'observeGestalt',
kind: 'raw',
gestaltFn: gestaltFn,
options: {
when: function () { return true; }
},
eventHandlerFn: eventHandlerFn
});
};
function finalizeActor(behavior, chunks) {
var oldHandleEvent = behavior.handleEvent;
var projections = {};
var compiledProjections = {};
var previousObjs = {};
behavior._computeRoutes = function () {
var newRoutes = Route.emptyGestalt;
for (var i = 0; i < chunks.length; i++) {
var chunk = chunks[i];
if (chunk.options.when.call(this)) {
switch (chunk.kind) {
case 'raw':
newRoutes = newRoutes.union(chunk.gestaltFn.call(this));
break;
case 'participant':
var proj = chunk.rawProjectionFn.call(this);
projections[i] = proj;
var g = Route.simpleGestalt(chunk.type === 'advertise',
Route.projectionToPattern(proj),
chunk.options.metaLevel,
0);
newRoutes = newRoutes.union(g);
break;
case 'observer':
var proj = chunk.rawProjectionFn.call(this);
projections[i] = proj;
compiledProjections[i] = Route.compileProjection(proj);
var g = Route.simpleGestalt(chunk.type === 'observeSubscribers',
Route.projectionToPattern(proj),
chunk.options.metaLevel,
chunk.options.level + 1);
newRoutes = newRoutes.union(g);
if (chunk.options.added || chunk.options.removed) {
previousObjs[i] = Route.arrayToSet([]);
}
break;
default:
throw new Error("Unsupported chunk type/kind: "+chunk.type+"/"+chunk.kind);
}
}
}
return newRoutes;
};
behavior.updateRoutes = function () {
World.updateRoutes([this._computeRoutes()]);
};
behavior.handleEvent = function (e) {
if (oldHandleEvent) { oldHandleEvent.call(this, e); }
for (var i = 0; i < chunks.length; i++) {
var chunk = chunks[i];
switch (chunk.kind) {
case 'raw':
chunk.eventHandlerFn.call(this, e);
break;
case 'participant':
if (chunk.handler
&& (e.type === 'message')
&& (e.metaLevel === chunk.options.metaLevel)
&& (e.isFeedback === (chunk.type === 'advertise')))
{
var matchResult = Route.matchPattern(e.message, projections[i]);
if (matchResult) {
util.kwApply(chunk.handler, this, matchResult);
}
}
break;
case 'observer':
if (e.type === 'routes') {
var projectionResult = e.gestalt.project(compiledProjections[i],
chunk.type !== 'observeSubscribers',
chunk.options.metaLevel,
chunk.options.level);
var isPresent = !Route.is_emptyMatcher(projectionResult);
if (chunk.options.presence) {
this[chunk.options.presence] = isPresent;
}
var objs = [];
if (isPresent) {
var keys = Route.matcherKeys(projectionResult);
if (keys === null) {
console.warn("Wildcard detected while projecting ("
+JSON.stringify(chunk.options)+")");
} else {
objs = Route.matcherKeysToObjects(keys, compiledProjections[i]);
if (chunk.options.set) {
for (var j = 0; j < objs.length; j++) {
objs[j] = chunk.options.set.call(this, objs[j]);
}
}
}
}
if (chunk.options.name) {
this[chunk.options.name] = objs;
}
if (chunk.options.singleton) {
this[chunk.options.singleton] = objs.length === 1 ? objs[0] : undefined;
}
if (chunk.options.added || chunk.options.removed) {
var objSet = Route.arrayToSet(objs);
if (chunk.options.added) {
this[chunk.options.added] =
Route.setToArray(Route.setSubtract(objSet, previousObjs[i]));
}
if (chunk.options.removed) {
this[chunk.options.removed] =
Route.setToArray(Route.setSubtract(previousObjs[i], objSet));
}
previousObjs[i] = objSet;
}
if (chunk.handler) {
chunk.handler.call(this);
}
}
break;
default:
throw new Error("Unsupported chunk type/kind: "+chunk.type+"/"+chunk.kind);
}
}
};
if (behavior.boot) { behavior.boot(); }
for (var i = 0; i < chunks.length; i++) {
var chunk = chunks[i];
if (chunk.kind === 'observer') {
if (chunk.options.presence) { behavior[chunk.options.presence] = false; }
if (chunk.options.name) { behavior[chunk.options.name] = []; }
if (chunk.options.singleton) { behavior[chunk.options.singleton] = undefined; }
if (chunk.options.added) { behavior[chunk.options.added] = []; }
if (chunk.options.removed) { behavior[chunk.options.removed] = []; }
}
}
return behavior._computeRoutes();
}
///////////////////////////////////////////////////////////////////////////
module.exports.Actor = Actor;

View File

@ -1,33 +0,0 @@
// Wire protocol representation of events and actions
var Route = require("./route.js");
function _encode(e) {
switch (e.type) {
case "routes":
return ["routes", e.gestalt.serialize(function (v) { return true; })];
case "message":
return ["message", e.message, e.metaLevel, e.isFeedback];
}
}
function _decode(what) {
return function (j) {
switch (j[0]) {
case "routes":
return Minimart.updateRoutes([
Route.deserializeGestalt(j[1], function (v) { return true; })]);
case "message":
return Minimart.sendMessage(j[1], j[2], j[3]);
default:
throw { message: "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");

View File

@ -1,61 +0,0 @@
/* Ground interface */
var Minimart = require("./minimart.js");
var World = Minimart.World;
function Ground(bootFn) {
var self = this;
this.stepperId = null;
World.withWorldStack([[this, -1]], function () {
self.world = new World(bootFn);
});
}
Ground.prototype.step = function () {
var self = this;
return World.withWorldStack([[this, -1]], function () {
return self.world.step();
});
};
Ground.prototype.checkPid = function (pid) {
if (pid !== -1) console.error("Weird pid in Ground markPidRunnable", pid);
};
Ground.prototype.markPidRunnable = function (pid) {
this.checkPid(pid);
this.startStepping();
};
Ground.prototype.startStepping = function () {
var self = this;
if (this.stepperId) return;
if (this.step()) {
this.stepperId = setTimeout(function () {
self.stepperId = null;
self.startStepping();
}, 0);
}
};
Ground.prototype.stopStepping = function () {
if (this.stepperId) {
clearTimeout(this.stepperId);
this.stepperId = null;
}
};
Ground.prototype.enqueueAction = function (pid, action) {
this.checkPid(pid);
if (action.type === 'routes') {
if (!action.gestalt.isEmpty()) {
console.error("You have subscribed to a nonexistent event source.",
action.gestalt.pretty());
}
} else {
console.error("You have sent a message into the outer void.", action);
}
};
///////////////////////////////////////////////////////////////////////////
module.exports.Ground = Ground;

88
src/jquery-driver.js vendored
View File

@ -1,88 +0,0 @@
// JQuery event driver
var Minimart = require("./minimart.js");
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
function spawnJQueryDriver(baseSelector, metaLevel, wrapFunction) {
metaLevel = metaLevel || 0;
wrapFunction = wrapFunction || defaultWrapFunction;
var d = new Minimart.DemandMatcher(wrapFunction(_$, _$, __), metaLevel,
{demandSideIsSubscription: true});
d.onDemandIncrease = function (captures) {
var selector = captures[0];
var eventName = captures[1];
World.spawn(new JQueryEventRouter(baseSelector,
selector,
eventName,
metaLevel,
wrapFunction));
};
World.spawn(d);
}
function defaultWrapFunction(selector, eventName, eventValue) {
return ["jQuery", selector, eventName, eventValue];
}
function JQueryEventRouter(baseSelector, selector, eventName, metaLevel, wrapFunction) {
var self = this;
this.baseSelector = baseSelector || null;
this.selector = selector;
this.eventName = eventName;
this.metaLevel = metaLevel || 0;
this.wrapFunction = wrapFunction || defaultWrapFunction;
this.preventDefault = (this.eventName.charAt(0) !== "+");
this.handler =
World.wrap(function (e) {
World.send(self.wrapFunction(self.selector, self.eventName, e), self.metaLevel);
if (self.preventDefault) e.preventDefault();
return !self.preventDefault;
});
this.computeNodes().on(this.preventDefault ? this.eventName : this.eventName.substring(1),
this.handler);
}
JQueryEventRouter.prototype.boot = function () {
return [pub(this.wrapFunction(this.selector, this.eventName, __), this.metaLevel),
pub(this.wrapFunction(this.selector, this.eventName, __), this.metaLevel, 1)];
};
JQueryEventRouter.prototype.handleEvent = function (e) {
if (e.type === "routes" && e.gestalt.isEmpty()) {
this.computeNodes().off(this.eventName, this.handler);
World.exit();
}
};
JQueryEventRouter.prototype.computeNodes = function () {
if (this.baseSelector) {
return $(this.baseSelector).children(this.selector).addBack(this.selector);
} else {
return $(this.selector);
}
};
function simplifyDOMEvent(e) {
var keys = [];
for (var k in e) {
var v = e[k];
if (typeof v === 'object') continue;
if (typeof v === 'function') continue;
keys.push(k);
}
keys.sort();
var simplified = [];
for (var i = 0; i < keys.length; i++) {
simplified.push([keys[i], e[keys[i]]]);
}
return simplified;
}
///////////////////////////////////////////////////////////////////////////
module.exports.spawnJQueryDriver = spawnJQueryDriver;
module.exports.simplifyDOMEvent = simplifyDOMEvent;
module.exports.defaultWrapFunction = defaultWrapFunction;

View File

@ -1,16 +0,0 @@
module.exports = require("./minimart.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.Reflect = require("./reflect.js");
module.exports.Ground = require("./ground.js").Ground;
module.exports.Actor = require("./actor.js").Actor;
module.exports.Spy = require("./spy.js").Spy;
module.exports.WakeDetector = require("./wake-detector.js").WakeDetector;
var Worker = require("./worker.js");
module.exports.Worker = Worker.Worker;
module.exports.WorkerGround = Worker.WorkerGround;

View File

@ -1,26 +0,0 @@
// Reflection on function formal parameter lists.
// This module is based on Angular's "injector" code,
// https://github.com/angular/angular.js/blob/master/src/auto/injector.js,
// MIT licensed, and hence:
// Copyright (c) 2010-2014 Google, Inc. http://angularjs.org
// Copyright (c) 2014 Tony Garnock-Jones
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function formalParameters(fn) {
var result = [];
var fnText = fn.toString().replace(STRIP_COMMENTS, '');
var argDecl = fnText.match(FN_ARGS);
var args = argDecl[1].split(FN_ARG_SPLIT);
for (var i = 0; i < args.length; i++) {
var trimmed = args[i].trim();
if (trimmed) { result.push(trimmed); }
}
return result;
}
module.exports.formalParameters = formalParameters;

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
var Reflect = require("./reflect.js");
module.exports.extend = function (what, _with) {
for (var prop in _with) {
if (_with.hasOwnProperty(prop)) {
what[prop] = _with[prop];
}
}
return what;
};
module.exports.kwApply = function (f, thisArg, args) {
var formals = Reflect.formalParameters(f);
var actuals = []
for (var i = 0; i < formals.length; i++) {
var formal = formals[i];
if (!(formal in args)) {
throw new Error("Function parameter '"+formal+"' not present in args");
}
actuals.push(args[formal]);
}
return f.apply(thisArg, actuals);
};

View File

@ -1,56 +0,0 @@
/* Web Worker interface */
var Ground = require("./ground.js").Ground;
var Util = require("./util.js");
var Codec = require("./codec.js");
var Minimart = require("./minimart.js");
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var BuiltinWorker = typeof window !== 'undefined' && window.Worker;
///////////////////////////////////////////////////////////////////////////
function Worker(scriptUrl) {
this.scriptUrl = scriptUrl;
this.w = new BuiltinWorker(scriptUrl);
}
Worker.prototype.boot = function () {
this.w.onmessage = World.wrap(function (e) {
console.log("Received from worker", JSON.stringify(e.data));
World.current().enqueueAction(World.activePid(), Codec.decodeAction(e.data));
});
};
Worker.prototype.handleEvent = function (e) {
console.log("Sending to worker", JSON.stringify(Codec.encodeEvent(e)));
this.w.postMessage(Codec.encodeEvent(e));
};
///////////////////////////////////////////////////////////////////////////
function WorkerGround(bootFn) {
var self = this;
Ground.call(this, bootFn);
onmessage = function (e) {
console.log("Received from main page", JSON.stringify(e.data));
self.world.handleEvent(Codec.decodeEvent(e.data));
self.startStepping();
};
}
WorkerGround.prototype = Util.extend({}, Ground.prototype);
WorkerGround.prototype.enqueueAction = function (pid, action) {
console.log("Sending to main page", JSON.stringify(Codec.encodeAction(action)));
postMessage(Codec.encodeAction(action));
console.log("Sent to main page");
};
///////////////////////////////////////////////////////////////////////////
module.exports.Worker = Worker;
module.exports.WorkerGround = WorkerGround;

View File

@ -1,99 +0,0 @@
var expect = require('expect.js');
var Minimart = require('../src/main.js');
var World = Minimart.World;
var Actor = Minimart.Actor;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
function configurationTrace(bootConfiguration) {
var eventLog = [];
function trace(item) {
eventLog.push(item);
}
var G = new Minimart.Ground(function () {
bootConfiguration(trace);
});
while (G.step()) {
// do nothing until G becomes inert
}
return eventLog;
}
function checkTrace(bootConfiguration, expected) {
expect(configurationTrace(bootConfiguration)).to.eql(expected);
}
describe("configurationTrace", function() {
describe("with an inert configuration", function () {
it("should yield an empty trace", function () {
checkTrace(function (trace) {}, []);
});
});
describe("with a single trace in an inert configuration", function () {
it("should yield that trace", function () {
checkTrace(function (trace) { trace(1) }, [1]);
});
});
describe("with some traced communication", function () {
it("should yield an appropriate trace", function () {
checkTrace(function (trace) {
World.spawn({
boot: function () { return [sub(__)] },
handleEvent: function (e) {
trace(e);
}
});
World.send(123);
World.send(234);
}, [Minimart.updateRoutes([]),
Minimart.sendMessage(123),
Minimart.sendMessage(234)]);
});
});
});
describe("nonempty initial routes", function () {
it("should be immediately signalled to the process", function () {
// Specifically, no Minimart.updateRoutes([]) first.
checkTrace(function (trace) {
World.spawn({
boot: function () { return [pub(["A", __])] },
handleEvent: function (e) {
World.spawn({
boot: function () { return [sub(["A", __], 0, 1)] },
handleEvent: trace
});
}
});
}, [Minimart.updateRoutes([pub(["A", __]).label(1)])]);
});
});
describe("actor with nonempty initial routes", function () {
it("shouldn't see initial empty conversational context", function () {
checkTrace(function (trace) {
World.spawn({
boot: function () { return [pub(["A", __])] },
handleEvent: function (e) {
World.spawn(new Actor(function () {
Actor.observeAdvertisers(
function () { return ["A", __] },
{ presence: "isPresent" },
function () {
trace(["isPresent", this.isPresent]);
});
}));
}
});
}, [["isPresent", true]]);
});
});

View File

@ -1,544 +0,0 @@
var expect = require('expect.js');
var util = require('util');
var r = require("../src/route.js");
function checkPrettyMatcher(m, expected) {
expect(r.prettyMatcher(m)).to.equal(expected.join('\n'));
}
function checkPrettyGestalt(g, expected) {
expect(g.pretty()).to.equal(expected.join('\n') + '\n');
}
describe("basic pattern compilation", function () {
var sAny = r.arrayToSet(['mAny']);
var sAAny = r.arrayToSet(['mAAny']);
var mAny = r.compilePattern(sAny, r.__);
var mAAny = r.compilePattern(sAAny, ['A', r.__]);
it("should print as expected", function () {
checkPrettyMatcher(mAny, [' ★ >{["mAny"]}']);
checkPrettyMatcher(mAAny, [' < "A" ★ > >{["mAAny"]}']);
});
describe("of wildcard", function () {
it("should match anything", function () {
expect(r.matchValue(mAny, 'hi')).to.eql(sAny);
expect(r.matchValue(mAny, ['A', 'hi'])).to.eql(sAny);
expect(r.matchValue(mAny, ['B', 'hi'])).to.eql(sAny);
expect(r.matchValue(mAny, ['A', [['hi']]])).to.eql(sAny);
});
});
describe("of A followed by wildcard", function () {
it("should match A followed by anything", function () {
expect(r.matchValue(mAAny, 'hi')).to.be(null);
expect(r.matchValue(mAAny, ['A', 'hi'])).to.eql(sAAny);
expect(r.matchValue(mAAny, ['B', 'hi'])).to.be(null);
expect(r.matchValue(mAAny, ['A', [['hi']]])).to.eql(sAAny);
});
});
it("should observe basic (in)equivalences", function () {
expect(r.matcherEquals(mAny, mAAny)).to.be(false);
expect(r.matcherEquals(mAny, mAny)).to.be(true);
expect(r.matcherEquals(mAAny, mAAny)).to.be(true);
});
});
describe("unions", function () {
it("should collapse common prefix wildcard", function () {
checkPrettyMatcher(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 'A']),
r.compilePattern(r.arrayToSet(['B']), [r.__, 'B'])),
[' < ★ "A" > >{["A"]}',
' "B" > >{["B"]}']);
});
it("should unroll wildcard unioned with nonwildcard", function () {
checkPrettyMatcher(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 'A']),
r.compilePattern(r.arrayToSet(['W']), r.__)),
[' ★ >{["W"]}',
' < ★ "A" ★...> >{["W"]}',
' > >{["A","W"]}',
' ★...> >{["W"]}',
' > >{["W"]}',
' > >{["W"]}']);
});
it("should properly multiply out", function () {
checkPrettyMatcher(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4])),
[' < 1 2 > >{["A"]}',
' 3 > >{["C"]}',
' 3 2 > >{["A"]}',
' 4 > >{["B"]}',
' ★ 2 > >{["A"]}']);
checkPrettyMatcher(r.union(r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4])),
[' < 1 3 > >{["C"]}',
' 3 4 > >{["B"]}']);
checkPrettyMatcher(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3])),
[' < 1 2 > >{["A"]}',
' 3 > >{["C"]}',
' ★ 2 > >{["A"]}']);
checkPrettyMatcher(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 2]),
r.compilePattern(r.arrayToSet(['B']), [3, 4])),
[' < 3 2 > >{["A"]}',
' 4 > >{["B"]}',
' ★ 2 > >{["A"]}']);
});
it("should correctly construct intermediate values", function () {
var MU = r.emptyMatcher;
MU = r.union(MU, r.compilePattern(r.arrayToSet(['A']), [r.__, 2]));
checkPrettyMatcher(MU, [' < ★ 2 > >{["A"]}']);
MU = r.union(MU, r.compilePattern(r.arrayToSet(['C']), [1, 3]));
checkPrettyMatcher(MU, [' < 1 2 > >{["A"]}',
' 3 > >{["C"]}',
' ★ 2 > >{["A"]}']);
MU = r.union(MU, r.compilePattern(r.arrayToSet(['B']), [3, 4]));
checkPrettyMatcher(MU, [' < 1 2 > >{["A"]}',
' 3 > >{["C"]}',
' 3 2 > >{["A"]}',
' 4 > >{["B"]}',
' ★ 2 > >{["A"]}']);
});
it("should handle identical patterns with different pids", function () {
var m = r.union(r.compilePattern(r.arrayToSet('B'), [2]),
r.compilePattern(r.arrayToSet('C'), [3]));
checkPrettyMatcher(m, [' < 2 > >{["B"]}',
' 3 > >{["C"]}']);
m = r.union(r.compilePattern(r.arrayToSet('A'), [2]), m);
checkPrettyMatcher(m, [' < 2 > >{["A","B"]}',
' 3 > >{["C"]}']);
});
});
describe("projections", function () {
describe("with picky structure", function () {
var proj = r.compileProjection(r._$("v", [[r.__]]));
it("should include things that match as well as wildcards", function () {
checkPrettyMatcher(r.project(r.union(r.compilePattern(r.arrayToSet(['A']), r.__),
r.compilePattern(r.arrayToSet(['B']), [['b']])),
proj),
[' < < "b" > > >{["B","A"]}',
' ★ > > >{["A"]}']);
});
it("should exclude things that lack the required structure", function () {
checkPrettyMatcher(r.project(r.union(r.compilePattern(r.arrayToSet(['A']), r.__),
r.compilePattern(r.arrayToSet(['B']), ['b'])),
proj),
[' < < ★ > > >{["A"]}']);
});
});
describe("simple positional", function () {
var proj = r.compileProjection([r._$, r._$]);
it("should collapse common prefixes", function () {
checkPrettyMatcher(r.project(r.union(r.compilePattern(r.arrayToSet(['A']), [1, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4])),
proj),
[' 1 2 >{["A"]}',
' 3 >{["C"]}',
' 3 4 >{["B"]}']);
});
it("should yield a correct set of results", function () {
expect(r.matcherKeys(r.project(r.union(r.compilePattern(r.arrayToSet(['A']), [1, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4])),
proj))).to.eql([[1, 2], [1, 3], [3, 4]]);
});
});
});
describe("erasePath after union", function () {
var R1 = r.compilePattern(r.arrayToSet(['A']), [r.__, "B"]);
var R2 = r.compilePattern(r.arrayToSet(['B']), ["A", r.__]);
var R12 = r.union(R1, R2);
it("should have sane preconditions", function () { // Am I doing this right?
checkPrettyMatcher(R1, [' < ★ "B" > >{["A"]}']);
checkPrettyMatcher(R2, [' < "A" ★ > >{["B"]}']);
checkPrettyMatcher(R12, [' < "A" "B" > >{["B","A"]}',
' ★ > >{["B"]}',
' ★ "B" > >{["A"]}']);
});
it("should yield the remaining ingredients of the union", function () {
expect(r.matcherEquals(r.erasePath(R12, R1), R2)).to.be(true);
expect(r.matcherEquals(r.erasePath(R12, R2), R1)).to.be(true);
expect(r.matcherEquals(r.erasePath(R12, R1), R1)).to.be(false);
});
});
describe("basic gestalt construction", function () {
it("should print as expected", function () {
checkPrettyGestalt(r.simpleGestalt(false, "A", 0, 0),
['GESTALT metalevel 0 level 0:',
' - subs: "A" >{true}']);
checkPrettyGestalt(r.simpleGestalt(true, "B", 0, 0),
['GESTALT metalevel 0 level 0:',
' - advs: "B" >{true}']);
checkPrettyGestalt(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0)),
['GESTALT metalevel 0 level 0:',
' - subs: "A" >{true}',
' - advs: "B" >{true}']);
checkPrettyGestalt(r.simpleGestalt(false, "A", 2, 2),
['GESTALT metalevel 2 level 2:',
' - subs: "A" >{true}']);
checkPrettyGestalt(r.simpleGestalt(true, "B", 2, 2),
['GESTALT metalevel 2 level 2:',
' - advs: "B" >{true}']);
checkPrettyGestalt(r.simpleGestalt(false, "A", 2, 2).union(r.simpleGestalt(true, "B", 2, 2)),
['GESTALT metalevel 2 level 2:',
' - subs: "A" >{true}',
' - advs: "B" >{true}']);
});
});
describe("matching", function () {
function check1(gMetalevel, level, mMetalevel) {
var g = r.simpleGestalt(false, "A", gMetalevel, level).label(123);
var result = g.matchValue("A", mMetalevel, false);
if (gMetalevel === mMetalevel) {
it("should match at level "+level, function () {
expect(result).to.eql([123]);
});
} else {
it("should not match at level "+level, function () {
expect(result).to.eql([]);
});
}
}
function gMetaLevelCheck(gMetalevel, mMetalevel) {
describe("at gestalt metalevel "+gMetalevel+", message metalevel "+mMetalevel, function () {
check1(gMetalevel, 0, mMetalevel);
check1(gMetalevel, 1, mMetalevel);
check1(gMetalevel, 2, mMetalevel);
});
}
function mMetaLevelCheck(mMetalevel) {
gMetaLevelCheck(0, mMetalevel);
gMetaLevelCheck(2, mMetalevel);
}
mMetaLevelCheck(0);
mMetaLevelCheck(1);
mMetaLevelCheck(2);
});
describe("gestalt filtering", function () {
function check1(metalevel, observedLevel, observerLevel) {
var observer = r.simpleGestalt(true, r.__, metalevel, observerLevel).label("observer");
var observed = r.simpleGestalt(false, "A", metalevel, observedLevel).label("observed");
var resultM = observed.filter(observer);
var resultL = observed.match(observer);
if (observedLevel < observerLevel) {
it("should be able to see gestalt at level "+observedLevel, function () {
expect(resultM.isEmpty()).to.be(false);
expect(resultL).to.eql(["observer"]);
});
} else {
it("should not be able to see gestalt at level "+observedLevel, function () {
expect(resultM.isEmpty()).to.be(true);
expect(resultL).to.eql([]);
});
}
}
function metalevelCheck(metalevel, observerLevel) {
describe("observer at level "+observerLevel+" in metalevel "+metalevel, function () {
check1(metalevel, 0, observerLevel);
check1(metalevel, 1, observerLevel);
check1(metalevel, 2, observerLevel);
});
}
function levelCheck(observerLevel) {
metalevelCheck(0, observerLevel);
metalevelCheck(2, observerLevel);
}
levelCheck(0);
levelCheck(1);
levelCheck(2);
});
describe("matcher equality", function () {
it("should not rely on object identity", function () {
expect(r.matcherEquals(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 'A']),
r.compilePattern(r.arrayToSet(['B']), [r.__, 'B'])),
r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 'A']),
r.compilePattern(r.arrayToSet(['B']), [r.__, 'B']))))
.to.be(true);
});
it("should respect commutativity of union", function () {
expect(r.matcherEquals(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 'A']),
r.compilePattern(r.arrayToSet(['B']), [r.__, 'B'])),
r.union(r.compilePattern(r.arrayToSet(['B']), [r.__, 'B']),
r.compilePattern(r.arrayToSet(['A']), [r.__, 'A']))))
.to.be(true);
});
});
describe("gestalt equality", function () {
it("should distinguish emptyGestalt reliably", function () {
expect(r.simpleGestalt(false, r.__, 0, 10)
.union(r.simpleGestalt(true, r.__, 0, 10))
.equals(r.emptyGestalt))
.to.be(false);
});
it("should not rely on object identity", function () {
expect(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0))
.equals(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0))))
.to.be(true);
});
it("should respect commutativity of union", function () {
expect(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0))
.equals(r.simpleGestalt(true, "B", 0, 0).union(r.simpleGestalt(false, "A", 0, 0))))
.to.be(true);
});
it("should discriminate between advs and subs", function () {
expect(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0))
.equals(r.simpleGestalt(false, "B", 0, 0).union(r.simpleGestalt(true, "A", 0, 0))))
.to.be(false);
});
});
describe("matcherKeys on wild matchers", function () {
var M = r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4]));
it("should yield null to signal an infinite result", function () {
expect(r.matcherKeys(r.project(M, r.compileProjection([r._$, r._$])))).to.be(null);
});
it("should extract just the second array element successfully", function () {
expect(r.matcherKeys(r.project(M, r.compileProjection([r.__, r._$])))).to.eql([[2],[3],[4]]);
});
var M2 = r.project(M, r.compileProjection([r._$, r._$]));
it("should survive double-projection", function () {
expect(r.matcherKeys(r.project(M2, r.compileProjection(r.__, r._$)))).to.eql([[2],[3],[4]]);
});
it("should survive embedding and reprojection", function () {
expect(r.matcherKeys(r.project(r.compilePattern(true, [r.embeddedMatcher(M2)]),
r.compileProjection([r.__, r._$])))).to.eql([[2],[3],[4]]);
expect(r.matcherKeys(r.project(r.compilePattern(true, [[r.embeddedMatcher(M2)]]),
r.compileProjection([[r.__, r._$]])))).to.eql([[2],[3],[4]]);
});
});
describe("matcherKeys using multiple-values in projections", function () {
var M = r.union(r.compilePattern(r.arrayToSet(['A']), [1, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4]));
var proj = r.compileProjection([r._$, r._$]);
var M2 = r.project(M, proj);
it("should be able to extract ordinary values", function () {
expect(r.matcherKeys(M2))
.to.eql([[1,2],[1,3],[3,4]]);
});
it("should be able to be reprojected as a sequence of more than one value", function () {
expect(r.matcherKeys(r.project(M2, r.compileProjection(r._$, r._$))))
.to.eql([[1,2],[1,3],[3,4]]);
});
it("should be convertible into objects with $-indexed fields", function () {
expect(r.matcherKeysToObjects(r.matcherKeys(M2), proj))
.to.eql([{'$0': 1, '$1': 2}, {'$0': 1, '$1': 3}, {'$0': 3, '$1': 4}]);
expect(r.projectObjects(M, proj))
.to.eql([{'$0': 1, '$1': 2}, {'$0': 1, '$1': 3}, {'$0': 3, '$1': 4}]);
});
});
describe("matcherKeys using multiple-values in projections, with names", function () {
var M = r.union(r.compilePattern(r.arrayToSet(['A']), [1, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4]));
it("should yield named fields", function () {
expect(r.projectObjects(M, r.compileProjection([r._$("fst"), r._$("snd")])))
.to.eql([{'fst': 1, 'snd': 2}, {'fst': 1, 'snd': 3}, {'fst': 3, 'snd': 4}]);
});
it("should yield numbered fields where names are missing", function () {
expect(r.projectObjects(M, r.compileProjection([r._$, r._$("snd")])))
.to.eql([{'$0': 1, 'snd': 2}, {'$0': 1, 'snd': 3}, {'$0': 3, 'snd': 4}]);
expect(r.projectObjects(M, r.compileProjection([r._$("fst"), r._$])))
.to.eql([{'fst': 1, '$1': 2}, {'fst': 1, '$1': 3}, {'fst': 3, '$1': 4}]);
});
});
describe("serializeMatcher", function () {
var M = r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['D']), [r.__, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4]));
var S = r.serializeMatcher(M, r.setToArray);
it("should basically work", function () {
expect(S).to.eql(
[ [ [ '(' ],
[ [ 1,
[ [ 2, [ [ [ ')' ], [ [ [ ')' ], [ '', [ 'A' ] ] ] ] ] ] ],
[ 3, [ [ [ ')' ], [ [ [ ')' ], [ '', [ 'C', 'D' ] ] ] ] ] ] ] ] ],
[ 3,
[ [ 2, [ [ [ ')' ], [ [ [ ')' ], [ '', [ 'A' ] ] ] ] ] ] ],
[ 3, [ [ [ ')' ], [ [ [ ')' ], [ '', [ 'D' ] ] ] ] ] ] ],
[ 4, [ [ [ ')' ], [ [ [ ')' ], [ '', [ 'B' ] ] ] ] ] ] ] ] ],
[ [ '__' ],
[ [ 2, [ [ [ ')' ], [ [ [ ')' ], [ '', [ 'A' ] ] ] ] ] ] ],
[ 3, [ [ [ ')' ], [ [ [ ')' ], [ '', [ 'D' ] ] ] ] ] ] ] ] ] ] ] ]);
});
it("should deserialize to something equivalent", function () {
expect(r.matcherEquals(M, r.deserializeMatcher(S, r.arrayToSet))).to.be(true);
});
});
describe("serialize Gestalts", function () {
var G = r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 2, 2));
var S = G.serialize();
it("should basically work", function () {
expect(S).to.eql(
[ 'gestalt',
[ [ [ [ [ 'A', [ [ [ ')' ], [ '', true ] ] ] ] ], [] ] ],
[],
[ [ [], [] ],
[ [], [] ],
[ [], [ [ 'B', [ [ [ ')' ], [ '', true ] ] ] ] ] ] ] ] ]);
});
it("should deserialize to something equivalent", function () {
expect(G.equals(r.deserializeGestalt(S))).to.be(true);
});
});
describe("complex erasure", function () {
var A = r.compilePattern(r.arrayToSet(['A']), r.__);
var B = r.union(r.compilePattern(r.arrayToSet(['B']), [[[["foo"]]]]),
r.compilePattern(r.arrayToSet(['B']), [[[["bar"]]]]));
describe("after a union", function () {
var R0 = r.union(A, B);
var R1a = r.erasePath(R0, B);
var R1b = r.erasePath(R0, A);
it("should yield the other parts of the union", function () {
expect(r.matcherEquals(R1a, A)).to.be(true);
expect(r.matcherEquals(R1b, B)).to.be(true);
});
});
});
describe("embedding matchers in patterns", function () {
var M1a =
r.compilePattern(r.arrayToSet(['A']),
[1, r.embeddedMatcher(r.compilePattern(r.arrayToSet(['B']), [2, 3])), 4]);
var M1b =
r.compilePattern(r.arrayToSet(['A']), [1, [2, 3], 4]);
var M2a =
r.compilePattern(r.arrayToSet(['A']),
[r.embeddedMatcher(r.compilePattern(r.arrayToSet(['B']), [1, 2])),
r.embeddedMatcher(r.compilePattern(r.arrayToSet(['C']), [3, 4]))]);
var M2b =
r.compilePattern(r.arrayToSet(['A']), [[1, 2], [3, 4]]);
it("should yield matchers equivalent to the original patterns", function () {
expect(r.matcherEquals(M1a, M1b)).to.be(true);
expect(r.matcherEquals(M2a, M2b)).to.be(true);
});
});
describe("calls to matchPattern", function () {
it("should yield appropriately-named/-numbered fields", function () {
expect(r.matchPattern([1, 2, 3], [r.__, 2, r._$])).to.eql({'$0': 3, 'length': 1});
expect(r.matchPattern([1, 2, 3], [r.__, 2, r._$("three")])).to.eql({'three': 3, 'length': 1});
expect(r.matchPattern([1, 2, 3], [r._$, 2, r._$("three")]))
.to.eql({'$0': 1, 'three': 3, 'length': 2});
expect(r.matchPattern([1, 2, 3], [r._$("one"), 2, r._$]))
.to.eql({'one': 1, '$1': 3, 'length': 2});
expect(r.matchPattern([1, 2, 3], [r._$("one"), 2, r._$("three")]))
.to.eql({'one': 1, 'three': 3, 'length': 2});
});
it("should fail on value mismatch", function () {
expect(r.matchPattern([1, 2, 3], [r.__, 999, r._$("three")])).to.be(null);
});
it("should fail on array length mismatch", function () {
expect(r.matchPattern([1, 2, 3], [r.__, 2, r._$("three"), 4])).to.be(null);
});
it("matches substructure", function () {
expect(r.matchPattern([1, [2, 999], 3], [r._$("one"), r._$(null, [2, r.__]), r._$("three")]))
.to.eql({ one: 1, '$1': [ 2, 999 ], three: 3, length: 3 });
expect(r.matchPattern([1, [2, 999], 3], [r._$("one"), r._$("two", [2, r.__]), r._$("three")]))
.to.eql({ one: 1, two: [ 2, 999 ], three: 3, length: 3 });
expect(r.matchPattern([1, [999, 2], 3], [r._$("one"), r._$(null, [2, r.__]), r._$("three")]))
.to.be(null);
expect(r.matchPattern([1, [999, 2], 3], [r._$("one"), r._$("two", [2, r.__]), r._$("three")]))
.to.be(null);
});
it("matches nested captures", function () {
expect(r.matchPattern([1, [2, 999], 3], [r._$("one"), r._$(null, [2, r._$]), r._$("three")]))
.to.eql({ one: 1, '$2': 999, '$1': [ 2, 999 ], three: 3, length: 4 });
expect(r.matchPattern([1, [2, 999], 3], [r._$("one"), r._$("two", [2, r._$]), r._$("three")]))
.to.eql({ one: 1, '$2': 999, two: [ 2, 999 ], three: 3, length: 4 });
});
});
describe("Projection with no captures", function () {
it("should yield the empty sequence when there's a match", function () {
var emptySequence = [' >{["A"]}'];
checkPrettyMatcher(r.project(r.compilePattern(r.arrayToSet(['A']), ["X", r.__]),
r.compileProjection(r.__)),
emptySequence);
checkPrettyMatcher(r.project(r.compilePattern(r.arrayToSet(['A']), ["X", r.__]),
r.compileProjection([r.__, r.__])),
emptySequence);
checkPrettyMatcher(r.project(r.compilePattern(r.arrayToSet(['A']), ["X", r.__]),
r.compileProjection(["X", r.__])),
emptySequence);
});
it("should yield null when there's no match", function () {
expect(r.project(r.compilePattern(r.arrayToSet(['A']), ["X", r.__]),
r.compileProjection(["Y", r.__]))).to.be(null);
});
it("should yield nonempty sequences when there are captures after all", function () {
checkPrettyMatcher(r.project(r.compilePattern(r.arrayToSet(['A']), ["X", r.__]),
r.compileProjection([r.__, r._$])),
[' ★ >{["A"]}']);
checkPrettyMatcher(r.project(r.compilePattern(r.arrayToSet(['A']), ["X", r.__]),
r.compileProjection([r._$, r._$])),
[' "X" ★ >{["A"]}']);
});
});

264
tr.js Normal file
View File

@ -0,0 +1,264 @@
var util = require('util');
var r = require("./route.js");
function dump(x) {
console.log(util.inspect(x, { depth: null }));
return x;
}
function dumpM(m) {
console.log(r.prettyMatcher(m));
console.log();
return m;
}
function dumpG(g) {
console.log(g.pretty());
console.log();
return g;
}
mAny = r.compilePattern(r.arrayToSet(['mAny']), r.__);
mAAny = r.compilePattern(r.arrayToSet(['mAAny']), ['A', r.__]);
dumpM(mAny);
dumpM(mAAny);
dump("mAny:");
dump(r.matchValue(mAny, 'hi'));
dump(r.matchValue(mAny, ['A', 'hi']));
dump(r.matchValue(mAny, ['B', 'hi']));
dump(r.matchValue(mAny, ['A', [['hi']]]));
dump("mAAny:");
dump(r.matchValue(mAAny, 'hi'));
dump(r.matchValue(mAAny, ['A', 'hi']));
dump(r.matchValue(mAAny, ['B', 'hi']));
dump(r.matchValue(mAAny, ['A', [['hi']]]));
console.log("unions");
dumpM(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 'A']),
r.compilePattern(r.arrayToSet(['B']), [r.__, 'B'])));
dumpM(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 'A']),
r.compilePattern(r.arrayToSet(['W']), r.__)));
console.log("projections");
dumpM(r.project(r.union(r.compilePattern(r.arrayToSet(['A']), r.__),
r.compilePattern(r.arrayToSet(['B']), ['b'])),
r.compileProjection(r._$([[r.__]]))));
dumpM(r.project(r.union(r.compilePattern(r.arrayToSet(['A']), [1, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4])),
r.compileProjection([r._$(), r._$()])));
dump(r.matcherKeys(r.project(r.union(r.compilePattern(r.arrayToSet(['A']), [1, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4])),
r.compileProjection([r._$(), r._$()]))));
var R1 = r.compilePattern(r.arrayToSet(['A']), [r.__, "B"]);
var R2 = r.compilePattern(r.arrayToSet(['B']), ["A", r.__]);
var R12 = r.union(R1, R2);
dumpM(R1);
dumpM(R2);
dumpM(R12);
dumpM(r.erasePath(R12, R1));
dumpM(r.erasePath(R12, R2));
console.log();
dumpG(r.simpleGestalt(false, "A", 0, 0));
dumpG(r.simpleGestalt(true, "B", 0, 0));
dumpG(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0)));
console.log();
dumpG(r.simpleGestalt(false, "A", 2, 2));
dumpG(r.simpleGestalt(true, "B", 2, 2));
dumpG(r.simpleGestalt(false, "A", 2, 2).union(r.simpleGestalt(true, "B", 2, 2)));
(function () {
function check1(i, j, n) {
var result = r.simpleGestalt(false, "A", i, j).label(123).matchValue("A", n, false);
dump([i === n ? result.length === 1 && result[0] === 123 : result.length === 0,
i, j, n, result]);
}
function metaLevelCheck(n) {
console.log("Checking message matching at metaLevel " + n);
check1(0, 0, n);
check1(0, 1, n);
check1(0, 2, n);
check1(2, 0, n);
check1(2, 1, n);
check1(2, 2, n);
console.log();
}
metaLevelCheck(0);
metaLevelCheck(1);
metaLevelCheck(2);
})();
(function () {
function check1(i, j, n) {
var observer = r.simpleGestalt(true, r.__, i, n).label("observer");
var observed = r.simpleGestalt(false, "A", i, j).label("observed");
var resultM = observed.filter(observer);
var resultL = observed.match(observer);
dump([ (j < n
? !resultM.isEmpty() && resultL.length === 1 && resultL[0] === "observer"
: resultM.isEmpty() && resultL.length === 0),
i, j, n, resultL]);
}
function levelCheck(n) {
console.log("Checking gestalt filtering at level " + n);
check1(0, 0, n);
check1(0, 1, n);
check1(0, 2, n);
check1(2, 0, n);
check1(2, 1, n);
check1(2, 2, n);
console.log();
}
levelCheck(0);
levelCheck(1);
levelCheck(2);
})();
console.log("Checking matcher equality");
dump(r.matcherEquals(mAny, mAAny) === false);
dump(r.matcherEquals(mAny, mAny) === true);
dump(r.matcherEquals(mAAny, mAAny) === true);
dump(r.matcherEquals(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 'A']),
r.compilePattern(r.arrayToSet(['B']), [r.__, 'B'])),
r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 'A']),
r.compilePattern(r.arrayToSet(['B']), [r.__, 'B'])))
=== true);
dump(r.matcherEquals(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 'A']),
r.compilePattern(r.arrayToSet(['B']), [r.__, 'B'])),
r.union(r.compilePattern(r.arrayToSet(['B']), [r.__, 'B']),
r.compilePattern(r.arrayToSet(['A']), [r.__, 'A'])))
=== true);
dump(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0))
.equals(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0)))
=== true);
dump(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0))
.equals(r.simpleGestalt(true, "B", 0, 0).union(r.simpleGestalt(false, "A", 0, 0)))
=== true);
dump(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 0, 0))
.equals(r.simpleGestalt(false, "B", 0, 0).union(r.simpleGestalt(true, "A", 0, 0)))
=== false);
console.log("debugging unions (1)");
dumpM(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4])));
dumpM(r.union(r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4])));
dumpM(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3])));
dumpM(r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 2]),
r.compilePattern(r.arrayToSet(['B']), [3, 4])));
console.log("debugging unions (2)");
var MU = r.emptyMatcher;
MU = r.union(MU, r.compilePattern(r.arrayToSet(['A']), [r.__, 2]));
dumpM(MU);
MU = r.union(MU, r.compilePattern(r.arrayToSet(['C']), [1, 3]));
dumpM(MU);
MU = r.union(MU, r.compilePattern(r.arrayToSet(['B']), [3, 4]));
dumpM(MU);
console.log("debugging unions (3)");
dumpM(r.union(r.compilePattern(r.arrayToSet('A'), [2]),
dumpM(r.union(r.compilePattern(r.arrayToSet('B'), [2]),
r.compilePattern(r.arrayToSet('C'), [3])))));
(function () {
console.log("matcherKeys on wild matchers");
var M = r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4]));
dump(r.matcherKeys(r.project(M, r.compileProjection([r._$(), r._$()]))));
dump(r.matcherKeys(r.project(M, r.compileProjection([r.__, r._$]))));
var M2 = r.project(M, r.compileProjection([r._$(), r._$()]));
dump(r.matcherKeys(r.project(M2,
r.compileProjection(r.__, r._$))));
dump(r.matcherKeys(r.project(r.compilePattern(true, [r.embeddedMatcher(M2)]),
r.compileProjection([r.__, r._$]))));
dump(r.matcherKeys(r.project(r.compilePattern(true, [[r.embeddedMatcher(M2)]]),
r.compileProjection([[r.__, r._$]]))));
})();
(function () {
console.log("matcherKeys using multiple-values in projections");
var M = r.union(r.compilePattern(r.arrayToSet(['A']), [1, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4]));
var M2 = r.project(M, r.compileProjection([r._$(), r._$()]));
dump(r.matcherKeys(M2));
dump(r.matcherKeys(r.project(M2, r.compileProjection(r._$(), r._$()))));
})();
(function () {
console.log("serializeMatcher");
var M = r.union(r.compilePattern(r.arrayToSet(['A']), [r.__, 2]),
r.compilePattern(r.arrayToSet(['C']), [1, 3]),
r.compilePattern(r.arrayToSet(['D']), [r.__, 3]),
r.compilePattern(r.arrayToSet(['B']), [3, 4]));
var S = r.serializeMatcher(M, r.setToArray);
dump(S);
console.log(JSON.stringify(S));
dumpM(r.deserializeMatcher(S, r.arrayToSet));
dump(r.matcherEquals(M, r.deserializeMatcher(S, r.arrayToSet)) === true);
})();
(function () {
console.log("serialize Gestalts");
var G = dumpG(r.simpleGestalt(false, "A", 0, 0).union(r.simpleGestalt(true, "B", 2, 2)));
var S = G.serialize();
dump(S);
console.log(JSON.stringify(S));
dumpG(r.deserializeGestalt(S));
dump(G.equals(r.deserializeGestalt(S)) === true);
})();
(function () {
console.log("complex erasure");
var A = r.compilePattern(r.arrayToSet(['A']), r.__);
var B = r.union(r.compilePattern(r.arrayToSet(['B']), [[[["foo"]]]]),
r.compilePattern(r.arrayToSet(['B']), [[[["bar"]]]]));
var R0 = r.union(A, B);
var R1a = r.erasePath(R0, B);
var R1b = r.erasePath(R0, A);
dumpM(R0);
dumpM(R1a);
dumpM(R1b);
dump(r.matcherEquals(R1a, A) === true);
dump(r.matcherEquals(R1b, B) === true);
})();
(function () {
console.log("Embedding matchers in patterns");
var M1a =
r.compilePattern(r.arrayToSet(['A']),
[1, r.embeddedMatcher(r.compilePattern(r.arrayToSet(['B']), [2, 3])), 4]);
var M1b =
r.compilePattern(r.arrayToSet(['A']), [1, [2, 3], 4]);
var M2a =
r.compilePattern(r.arrayToSet(['A']),
[r.embeddedMatcher(r.compilePattern(r.arrayToSet(['B']), [1, 2])),
r.embeddedMatcher(r.compilePattern(r.arrayToSet(['C']), [3, 4]))]);
var M2b =
r.compilePattern(r.arrayToSet(['A']), [[1, 2], [3, 4]]);
dumpM(M1a);
dumpM(M2a);
dump(r.matcherEquals(M1a, M1b) === true);
dump(r.matcherEquals(M2a, M2b) === true);
})();

View File

@ -2,11 +2,6 @@
// suspension/sleeping!) has caused periodic activities to be // suspension/sleeping!) has caused periodic activities to be
// interrupted, and warns others about it // interrupted, and warns others about it
// Inspired by http://blog.alexmaccaw.com/javascript-wake-event // Inspired by http://blog.alexmaccaw.com/javascript-wake-event
var Minimart = require("./minimart.js");
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
function WakeDetector(period) { function WakeDetector(period) {
this.message = "wake"; this.message = "wake";
@ -17,8 +12,8 @@ function WakeDetector(period) {
WakeDetector.prototype.boot = function () { WakeDetector.prototype.boot = function () {
var self = this; var self = this;
World.updateRoutes([pub(this.message)]);
this.timerId = setInterval(World.wrap(function () { self.trigger(); }), this.period); this.timerId = setInterval(World.wrap(function () { self.trigger(); }), this.period);
return [pub(this.message)];
}; };
WakeDetector.prototype.handleEvent = function (e) {}; WakeDetector.prototype.handleEvent = function (e) {};
@ -30,5 +25,3 @@ WakeDetector.prototype.trigger = function () {
} }
this.mostRecentTrigger = now; this.mostRecentTrigger = now;
}; };
module.exports.WakeDetector = WakeDetector;

View File

@ -1,12 +1,3 @@
var Minimart = require("./minimart.js");
var Codec = require("./codec.js");
var Route = Minimart.Route;
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// WebSocket client driver // WebSocket client driver
@ -24,11 +15,11 @@ function WebSocketConnection(label, wsurl, shouldReconnect) {
this.wsurl = wsurl; this.wsurl = wsurl;
this.shouldReconnect = shouldReconnect ? true : false; this.shouldReconnect = shouldReconnect ? true : false;
this.reconnectDelay = DEFAULT_RECONNECT_DELAY; this.reconnectDelay = DEFAULT_RECONNECT_DELAY;
this.localGestalt = Route.emptyGestalt; this.localGestalt = route.emptyGestalt;
this.peerGestalt = Route.emptyGestalt; this.peerGestalt = route.emptyGestalt;
this.prevLocalRoutesMessage = null; this.prevLocalRoutesMessage = null;
this.prevPeerRoutesMessage = null; this.prevPeerRoutesMessage = null;
this.deduplicator = new Minimart.Deduplicator(); this.deduplicator = new Deduplicator();
this.connectionCount = 0; this.connectionCount = 0;
this.activityTimestamp = 0; this.activityTimestamp = 0;
@ -38,20 +29,6 @@ function WebSocketConnection(label, wsurl, shouldReconnect) {
this.pingTimer = null; this.pingTimer = null;
} }
WebSocketConnection.prototype.debugState = function () {
return {
label: this.label,
sendsAttempted: this.sendsAttempted,
sendsTransmitted: this.sendsTransmitted,
receiveCount: this.receiveCount,
wsurl: this.wsurl,
shouldReconnect: this.shouldReconnect,
reconnectDelay: this.reconnectDelay,
connectionCount: this.connectionCount,
activityTimestamp: this.activityTimestamp
};
};
WebSocketConnection.prototype.clearHeartbeatTimers = function () { WebSocketConnection.prototype.clearHeartbeatTimers = function () {
if (this.idleTimer) { clearTimeout(this.idleTimer); this.idleTimer = null; } if (this.idleTimer) { clearTimeout(this.idleTimer); this.idleTimer = null; }
if (this.pingTimer) { clearTimeout(this.pingTimer); this.pingTimer = null; } if (this.pingTimer) { clearTimeout(this.pingTimer); this.pingTimer = null; }
@ -81,8 +58,8 @@ WebSocketConnection.prototype.relayGestalt = function () {
WebSocketConnection.prototype.aggregateGestalt = function () { WebSocketConnection.prototype.aggregateGestalt = function () {
var self = this; var self = this;
return this.peerGestalt.transform(function (m, metaLevel) { return this.peerGestalt.transform(function (m, metaLevel) {
return Route.compilePattern(true, return route.compilePattern(true,
[self.label, metaLevel, Route.embeddedMatcher(m)]); [self.label, metaLevel, route.embeddedMatcher(m)]);
}).union(this.relayGestalt()); }).union(this.relayGestalt());
}; };
@ -111,8 +88,7 @@ WebSocketConnection.prototype.safeSend = function (m) {
}; };
WebSocketConnection.prototype.sendLocalRoutes = function () { WebSocketConnection.prototype.sendLocalRoutes = function () {
var newLocalRoutesMessage = var newLocalRoutesMessage = JSON.stringify(encodeEvent(updateRoutes([this.localGestalt])));
JSON.stringify(Codec.encodeEvent(Minimart.updateRoutes([this.localGestalt])));
if (this.prevLocalRoutesMessage !== newLocalRoutesMessage) { if (this.prevLocalRoutesMessage !== newLocalRoutesMessage) {
this.prevLocalRoutesMessage = newLocalRoutesMessage; this.prevLocalRoutesMessage = newLocalRoutesMessage;
this.safeSend(newLocalRoutesMessage); this.safeSend(newLocalRoutesMessage);
@ -120,14 +96,14 @@ WebSocketConnection.prototype.sendLocalRoutes = function () {
}; };
WebSocketConnection.prototype.collectMatchers = function (getAdvertisements, level, g) { WebSocketConnection.prototype.collectMatchers = function (getAdvertisements, level, g) {
var extractMetaLevels = Route.compileProjection([this.label, _$, __]); var extractMetaLevels = route.compileProjection([this.label, _$, __]);
var mls = Route.matcherKeys(g.project(extractMetaLevels, getAdvertisements, 0, level)); var mls = route.matcherKeys(g.project(extractMetaLevels, getAdvertisements, 0, level));
for (var i = 0; i < mls.length; i++) { for (var i = 0; i < mls.length; i++) {
var metaLevel = mls[i][0]; // only one capture in the projection var metaLevel = mls[i][0]; // only one capture in the projection
var extractMatchers = Route.compileProjection([this.label, metaLevel, _$]); var extractMatchers = route.compileProjection([this.label, metaLevel, _$]);
var m = g.project(extractMatchers, getAdvertisements, 0, level); var m = g.project(extractMatchers, getAdvertisements, 0, level);
this.localGestalt = this.localGestalt.union(Route.simpleGestalt(getAdvertisements, this.localGestalt = this.localGestalt.union(route.simpleGestalt(getAdvertisements,
Route.embeddedMatcher(m), route.embeddedMatcher(m),
metaLevel, metaLevel,
level)); level));
} }
@ -139,9 +115,9 @@ WebSocketConnection.prototype.handleEvent = function (e) {
case "routes": case "routes":
// TODO: GROSS - erasing by pid! // TODO: GROSS - erasing by pid!
var nLevels = e.gestalt.levelCount(0); var nLevels = e.gestalt.levelCount(0);
var relayGestalt = Route.fullGestalt(1, nLevels).label(World.activePid()); var relayGestalt = route.fullGestalt(1, nLevels).label(World.activePid());
var g = e.gestalt.erasePath(relayGestalt); var g = e.gestalt.erasePath(relayGestalt);
this.localGestalt = Route.emptyGestalt; this.localGestalt = route.emptyGestalt;
for (var level = 0; level < nLevels; level++) { for (var level = 0; level < nLevels; level++) {
this.collectMatchers(false, level, g); this.collectMatchers(false, level, g);
this.collectMatchers(true, level, g); this.collectMatchers(true, level, g);
@ -153,8 +129,7 @@ WebSocketConnection.prototype.handleEvent = function (e) {
var m = e.message; var m = e.message;
if (m.length && m.length === 3 && m[0] === this.label) if (m.length && m.length === 3 && m[0] === this.label)
{ {
var encoded = JSON.stringify(Codec.encodeEvent( var encoded = JSON.stringify(encodeEvent(sendMessage(m[2], m[1], e.isFeedback)));
Minimart.sendMessage(m[2], m[1], e.isFeedback)));
if (this.deduplicator.accept(encoded)) { if (this.deduplicator.accept(encoded)) {
this.safeSend(encoded); this.safeSend(encoded);
} }
@ -207,7 +182,7 @@ WebSocketConnection.prototype.onmessage = function (wse) {
return; // recordActivity already took care of our timers return; // recordActivity already took care of our timers
} }
var e = Codec.decodeAction(j); var e = decodeAction(j);
switch (e.type) { switch (e.type) {
case "routes": case "routes":
if (this.prevPeerRoutesMessage !== wse.data) { if (this.prevPeerRoutesMessage !== wse.data) {
@ -243,5 +218,24 @@ WebSocketConnection.prototype.onclose = function (e) {
}; };
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Wire protocol representation of events and actions
module.exports.WebSocketConnection = WebSocketConnection; function encodeEvent(e) {
switch (e.type) {
case "routes":
return ["routes", e.gestalt.serialize(function (v) { return true; })];
case "message":
return ["message", e.message, e.metaLevel, e.isFeedback];
}
}
function decodeAction(j) {
switch (j[0]) {
case "routes":
return updateRoutes([route.deserializeGestalt(j[1], function (v) { return true; })]);
case "message":
return sendMessage(j[1], j[2], j[3]);
default:
throw { message: "Invalid JSON-encoded action: " + JSON.stringify(j) };
}
}