Compare commits

...

50 Commits

Author SHA1 Message Date
Tony Garnock-Jones 382391b518 Update build products 2014-08-30 14:52:33 -07:00
Tony Garnock-Jones 5621685052 Make boot() return (optional) initialGestalts instead of having a separate argument to spawn(). Fixes failing test case for initial actor route signalling. 2014-08-30 14:52:33 -07:00
Tony Garnock-Jones 85c6c228a3 Move kwApply from actor.js to util.js 2014-08-30 14:52:27 -07:00
Tony Garnock-Jones 18c4b184e5 Add failing test for actor initial route signalling 2014-08-30 13:19:04 -07:00
Tony Garnock-Jones 9fdf90db68 Add test case for initial route signalling 2014-08-30 13:18:37 -07:00
Tony Garnock-Jones 26245951ad Simple test harness for actors 2014-08-30 13:07:39 -07:00
Tony Garnock-Jones 7cf9dabca4 Rename tr.js to test-route.js and make it a proper mocha/expect.js suite 2014-08-30 12:50:14 -07:00
Tony Garnock-Jones 0584b4d6d3 Update build products 2014-08-25 18:39:55 -07:00
Tony Garnock-Jones d777418d5c Rearrange Actor instances so exceptions during construction are correctly blamed on the new actor 2014-08-25 18:39:45 -07:00
Tony Garnock-Jones 160938cec8 Update build products 2014-08-25 16:12:03 -07:00
Tony Garnock-Jones 1b3b355fea Invoke trapexit after removing the pid, in case of exn in the trapexit fn 2014-08-25 16:11:25 -07:00
Tony Garnock-Jones 440c91b4d7 Update build products 2014-08-25 13:02:04 -07:00
Tony Garnock-Jones 52bdc2eb3c Redacted debugState for WebSocketConnection and DemandMatcher 2014-08-25 13:01:49 -07:00
Tony Garnock-Jones 10abaa1724 Support separate supply-projection from demand-projection 2014-08-25 13:01:35 -07:00
Tony Garnock-Jones 7bb57e2c39 Permit actors to control what portion of their state is displayed in debug output 2014-08-25 13:01:10 -07:00
Tony Garnock-Jones 66670e7f6f Throw exception on ill-formed DOM specification 2014-08-25 13:00:33 -07:00
Tony Garnock-Jones 20d0f5d58d Correct documentation of interpretSpec 2014-08-25 13:00:18 -07:00
Tony Garnock-Jones f5e59bfcd8 Update textfield example to use Actor 2014-08-25 11:44:59 -07:00
Tony Garnock-Jones a0a6a3dbfe Update build products 2014-08-25 11:44:18 -07:00
Tony Garnock-Jones d080494664 Add singleton feature to Actor observation 2014-08-25 11:44:09 -07:00
Tony Garnock-Jones c5f7e9db2a Update build products 2014-08-22 17:12:50 -07:00
Tony Garnock-Jones a62f00b8a3 Sort process tree by numeric PID, not by PID string 2014-08-22 17:12:40 -07:00
Tony Garnock-Jones e9697c8171 Update build products 2014-08-22 17:06:11 -07:00
Tony Garnock-Jones d7baec744e Smarter process-tree-printing for Chrome 2014-08-22 17:06:06 -07:00
Tony Garnock-Jones 216935d60f Webworkerized DOM example 2014-08-05 17:25:40 -07:00
Tony Garnock-Jones 66709fe58e Update build products 2014-08-04 11:32:03 -07:00
Tony Garnock-Jones 318770e301 Fix missing Codec module reference 2014-08-04 11:28:47 -07:00
Tony Garnock-Jones bd06a8a09e Update build products 2014-08-02 09:00:44 -07:00
Tony Garnock-Jones 659ee24105 Experimental Web Worker support 2014-08-02 00:32:46 -07:00
Tony Garnock-Jones 2bfacbfc7b Split veryclean out from clean to avoid foolish mistakes 2014-08-02 00:31:18 -07:00
Tony Garnock-Jones e78696c621 Split out Ground, Codec 2014-08-02 00:30:52 -07:00
Tony Garnock-Jones f46fd52239 Filter out metaLevel mismatches 2014-08-02 00:25:18 -07:00
Tony Garnock-Jones e0def26f6c Initialize Actor presence/name/added/removed state variables 2014-07-28 17:43:08 -07:00
Tony Garnock-Jones 9965ce760e Update build products 2014-07-25 16:57:16 -07:00
Tony Garnock-Jones 76044b539e Update chat example to Actor; preserve non-Actor version; remove
currently-useless OSX app resources
2014-07-25 16:57:16 -07:00
Tony Garnock-Jones 55c9fa1d49 Convert DOM and smoketest examples to actor.js 2014-07-25 16:57:16 -07:00
Tony Garnock-Jones 95a4bc3c93 Finish actor.js 2014-07-25 16:57:09 -07:00
Tony Garnock-Jones 9ffbec107f More experimentation in test/tr.js 2014-07-25 16:56:06 -07:00
Tony Garnock-Jones fba2ee91a8 actor.js (WIP) 2014-07-25 16:56:06 -07:00
Tony Garnock-Jones 0b361137a2 Reflect.formalParameters 2014-07-25 16:56:06 -07:00
Tony Garnock-Jones e0b476cc0a matchPattern, projectObjects 2014-07-25 16:56:06 -07:00
Tony Garnock-Jones cfeb46ef62 Prefer Array.isArray() to "instanceof Array" tests 2014-07-25 16:56:06 -07:00
Tony Garnock-Jones 80befd60eb Named captures 2014-07-25 16:56:06 -07:00
Tony Garnock-Jones 28405b544f Remove unused bindings 2014-07-25 16:56:05 -07:00
Tony Garnock-Jones a605a438cf Multidom example (buggy!) 2014-07-25 16:56:05 -07:00
Tony Garnock-Jones 96d577b2d7 Permit overriding of DOM/jQuery patterns (enables remote connectivity) 2014-07-24 16:21:54 -07:00
Tony Garnock-Jones f7baa65a2d Use nested-arrays (sexpr style) instead of hashes for DOM fragments 2014-07-24 16:21:07 -07:00
Tony Garnock-Jones 8fde23d187 Fix module path in tests 2014-07-23 17:25:29 -07:00
Tony Garnock-Jones 96d3d3c621 Makefile tweaks 2014-07-23 17:24:48 -07:00
Tony Garnock-Jones 7bab9be492 Some modularity; general cleanup of codebase 2014-07-23 17:21:51 -07:00
53 changed files with 7186 additions and 2432 deletions

3
.gitignore vendored
View File

@ -1,6 +1,5 @@
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,21 +1,5 @@
APP_NAME=MarketplaceChat.app all:
LIB_SOURCES=\ npm install .
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
@ -32,22 +16,8 @@ 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
$(APP_NAME).zip: $(APP_NAME)
zip -r $@ $<
$(APP_NAME): $(APP_SOURCES) $(LIB_SOURCES)
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: clean:
rm -rf $(APP_NAME) $(APP_NAME).zip rm -f dist/*.js
veryclean: clean
rm -rf node_modules/

View File

@ -14,7 +14,7 @@ To install the Racket server:
To run the Racket server: To run the Racket server:
- `racket server.rkt` from the base directory of this repository. racket -l minimart/examples/broker
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 Normal file
View File

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

3215
dist/minimart.js vendored Normal file

File diff suppressed because one or more lines are too long

3
dist/minimart.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,67 @@
<!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>

162
examples/chat-raw/index.js Normal file
View File

@ -0,0 +1,162 @@
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

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

@ -1,28 +0,0 @@
<?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

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

View File

@ -3,33 +3,15 @@
<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,10 +1,14 @@
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");
@ -15,12 +19,10 @@ function outputItem(item) {
return item; return item;
} }
function updateNymList(g) { function updateNymList(allStatuses) {
var statuses = {}; var statuses = {};
var nymProj = ["broker", 0, ["chatEvent", _$, _$, __, __]]; for (var i = 0; i < allStatuses.length; i++) {
var matchedNyms = route.matcherKeys(g.project(route.compileProjection(nymProj), true, 0, 0)); statuses[allStatuses[i].nym] = allStatuses[i].status;
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); }
@ -55,101 +57,81 @@ $(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 Ground(function () { G = new Minimart.Ground(function () {
console.log('starting ground boot'); console.log('starting ground boot');
// World.spawn(new Spy()); // World.spawn(new Spy());
spawnJQueryDriver(); Minimart.JQuery.spawnJQueryDriver();
spawnDOMDriver(); Minimart.DOM.spawnDOMDriver();
spawnRoutingTableWidget("#spy-holder", "spy"); Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy");
World.spawn(new WakeDetector()); World.spawn(new Minimart.WakeDetector());
var wsconn = new WebSocketConnection("broker", $("#wsurl").val(), true); var wsconn = new Minimart.WebSocket.WebSocketConnection("broker", $("#wsurl").val(), true);
World.spawn(wsconn); World.spawn(wsconn);
World.spawn({ World.spawn(new Actor(function () {
// Monitor connection, notifying connectivity changes // Monitor connection, notifying connectivity changes
state: "crashed", // start with this to avoid spurious initial message print this.state = "crashed"; // start with this to avoid spurious initial message print
boot: function () {
World.updateRoutes([sub(["broker_state", __], 0, 1)]); Actor.observeAdvertisers(
}, function () { return ["broker_state", _$("newState")]; },
handleEvent: function (e) { { name: "states" },
if (e.type === "routes") { function () {
var states = var newState = this.states.length > 0 ? this.states[0].newState : "crashed";
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
boot: function () { this.nym = function () { return $("#nym").val(); };
World.updateRoutes(this.subscriptions()); this.currentStatus = function () { return $("#status").val(); };
},
nym: function () { return $("#nym").val(); }, Actor.subscribe(
currentStatus: function () { return $("#status").val(); }, function () { return "wake"; },
subscriptions: function () { function () { wsconn.forceclose(); });
return [sub("wake"),
sub(["jQuery", "#send_chat", "click", __]), Actor.advertise(
sub(["jQuery", "#nym", "change", __]), function () { return ["broker", 0,
sub(["jQuery", "#status", "change", __]), chatEvent(this.nym(), this.currentStatus(), __, __)]; });
sub(["jQuery", "#wsurl", "change", __]), Actor.observeAdvertisers(
pub(["broker", 0, chatEvent(this.nym(), this.currentStatus(), __, __)]), function () { return ["broker", 0,
sub(["broker", 0, chatEvent(__, __, __, __)], 0, 1)]; chatEvent(_$("nym"), _$("status"), __, __)]; },
}, { name: "allStatuses" },
handleEvent: function (e) { function () { updateNymList(this.allStatuses); });
var self = this;
switch (e.type) { Actor.subscribe(
case "routes": function () { return ["jQuery", "#send_chat", "click", __]; },
updateNymList(e.gestalt); function () {
break; var inp = $("#chat_input");
case "message": var utterance = inp.val();
if (e.message === "wake") { inp.val("");
wsconn.forceclose(); if (utterance) {
return; World.send(["broker", 0, chatEvent(this.nym(),
this.currentStatus(),
utterance)]);
} }
switch (e.message[0]) { });
case "jQuery":
switch (e.message[1]) { Actor.subscribe(
case "#send_chat": function () { return ["jQuery", "#nym", "change", __]; },
var inp = $("#chat_input"); function () { this.updateRoutes(); });
var utterance = inp.val();
inp.val(""); Actor.subscribe(
if (utterance) { function () { return ["jQuery", "#status", "change", __]; },
World.send(["broker", 0, chatEvent(this.nym(), function () { this.updateRoutes(); });
this.currentStatus(),
utterance)]); Actor.subscribe(
} function () { return ["jQuery", "#wsurl", "change", __]; },
break; function () {
case "#nym": wsconn.forceclose();
case "#status": wsconn.wsurl = $("#wsurl").val();
World.updateRoutes(this.subscriptions()); });
break;
case "#wsurl": Actor.subscribe(
wsconn.forceclose(); function () { return ["broker", 0, chatEvent(_$("who"), __, _$("what"), __)]; },
wsconn.wsurl = $("#wsurl").val(); function (who, what) { outputUtterance(who, what); });
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

@ -0,0 +1,27 @@
<!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>

49
examples/dom-raw/index.js Normal file
View File

@ -0,0 +1,49 @@
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

@ -0,0 +1,27 @@
<!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

@ -0,0 +1,35 @@
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

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

View File

View File

@ -0,0 +1,26 @@
<!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

@ -0,0 +1,75 @@
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

@ -0,0 +1,20 @@
<!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

@ -0,0 +1,49 @@
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

@ -0,0 +1,19 @@
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,9 +4,7 @@
<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="../../route.js"></script> <script src="../../dist/minimart.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,24 +1,33 @@
var G; var G;
$(document).ready(function () { $(document).ready(function () {
G = new Ground(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'); console.log('starting ground boot');
World.spawn(new Spy("GROUND", true)); World.spawn(new Minimart.Spy("GROUND", true));
World.spawn({
counter: 0, World.spawn(new Actor(function () {
handleEvent: function (e) {}, this.counter = 0;
step: function () { this.step = function () {
World.send(["beep", this.counter++]); World.send(["beep", this.counter++]);
return this.counter <= 10; return this.counter <= 10;
} };
}, [pub(["beep", __])]);
World.spawn({ Actor.advertise(function () { return ["beep", __]; });
handleEvent: function (e) { }));
if (e.type === "message" && e.message[0] === "beep") {
console.log("beep!", e.message[1]); World.spawn(new Actor(function () {
} Actor.subscribe(
} function () { return ["beep", _$("counter")]; },
}, [sub(["beep", __])]); function (counter) {
console.log("beep!", counter);
});
}));
}); });
G.startStepping(); G.startStepping();
}); });

View File

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

46
jquery-driver.js vendored
View File

@ -1,46 +0,0 @@
// 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);
}
};

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"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

File diff suppressed because it is too large Load Diff

View File

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

268
src/actor.js Normal file
View File

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

33
src/codec.js Normal file
View File

@ -0,0 +1,33 @@
// 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,31 +1,51 @@
// 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() { function spawnDOMDriver(domWrapFunction, jQueryWrapFunction) {
var d = new DemandMatcher(["DOM", _$, _$, _$]); domWrapFunction = domWrapFunction || defaultWrapFunction;
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, fragmentClass, fragmentSpec), World.spawn(new DOMFragment(selector,
[sub(["DOM", selector, fragmentClass, fragmentSpec]), fragmentClass,
sub(["DOM", selector, fragmentClass, fragmentSpec], 0, 1)]); fragmentSpec,
domWrapFunction,
jQueryWrapFunction));
}; };
World.spawn(d); World.spawn(d);
} }
function DOMFragment(selector, fragmentClass, fragmentSpec) { function defaultWrapFunction(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 = sub(["DOM", self.selector, self.fragmentClass, self.fragmentSpec], 1, 2); var monitoring =
sub(self.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec), 1, 2);
World.spawn(new World(function () { World.spawn(new World(function () {
spawnJQueryDriver(self.selector+" > ."+self.fragmentClass, 1); Minimart.JQuery.spawnJQueryDriver(self.selector+" > ."+self.fragmentClass,
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
@ -34,8 +54,11 @@ 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) {
@ -48,30 +71,34 @@ 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 = $.isPlainObject(spec[1]); var hasAttrs = isAttributes(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 attr in attrs) { for (var i = 0; i < attrs.length; i++) {
if (attrs.hasOwnProperty(attr)) { n.setAttribute(attrs[i][0], attrs[i][1]);
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");
} }
}; };
@ -86,3 +113,8 @@ DOMFragment.prototype.buildNodes = function () {
}); });
return nodes; return nodes;
}; };
///////////////////////////////////////////////////////////////////////////
module.exports.spawnDOMDriver = spawnDOMDriver;
module.exports.defaultWrapFunction = defaultWrapFunction;

61
src/ground.js Normal file
View File

@ -0,0 +1,61 @@
/* 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 Normal file
View File

@ -0,0 +1,88 @@
// 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;

16
src/main.js Normal file
View File

@ -0,0 +1,16 @@
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,27 +1,30 @@
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, initialGestalts) { function spawn(behavior) {
return { type: "spawn", return { type: "spawn", behavior: behavior };
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) {
@ -49,11 +52,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);
} }
@ -80,8 +83,8 @@ World.updateRoutes = function (gestalts) {
World.current().enqueueAction(World.activePid(), updateRoutes(gestalts)); World.current().enqueueAction(World.activePid(), updateRoutes(gestalts));
}; };
World.spawn = function (behavior, initialGestalts) { World.spawn = function (behavior) {
World.current().enqueueAction(World.activePid(), spawn(behavior, initialGestalts)); World.current().enqueueAction(World.activePid(), spawn(behavior));
}; };
World.exit = function (exn) { World.exit = function (exn) {
@ -134,7 +137,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) {
@ -174,16 +177,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);
} }
}; };
@ -221,13 +224,18 @@ 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 newGestalt = action.initialGestalt.label(pid); var entry = { gestalt: Route.emptyGestalt, behavior: action.behavior };
this.processTable[pid] = { gestalt: newGestalt, behavior: action.behavior }; this.processTable[pid] = entry;
if (action.behavior.boot) { if (entry.behavior.boot) {
this.asChild(pid, function () { action.behavior.boot() }); var initialGestalts = this.asChild(pid, function () { return entry.behavior.boot() });
if (initialGestalts) {
entry.gestalt = Route.gestaltUnion(initialGestalts).label(pid);
}
this.markPidRunnable(pid); this.markPidRunnable(pid);
} }
this.applyAndIssueRoutingUpdate(route.emptyGestalt, newGestalt, pid); if (!Route.emptyGestalt.equals(entry.gestalt)) {
this.applyAndIssueRoutingUpdate(Route.emptyGestalt, entry.gestalt, pid);
}
break; break;
case "routes": case "routes":
if (pid in this.processTable) { if (pid in this.processTable) {
@ -310,6 +318,11 @@ 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":
@ -343,7 +356,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(); kids.sort(function (a, b) { return a[0] - b[0] });
return kids; return kids;
}; };
@ -351,7 +364,7 @@ World.prototype.textProcessTree = function (ownPid) {
var lines = []; var lines = [];
function dumpProcess(prefix, pid, p) { function dumpProcess(prefix, pid, p) {
if (p instanceof Array) { if (Array.isArray(p)) {
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]);
@ -360,11 +373,16 @@ 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 + ') ' : '';
lines.push(prefix + '-- ' + pid + ': ' + label + var stringifiedState;
tombstoneString + try {
JSON.stringify(p.behavior, function (k, v) { var rawState = p.behavior.debugState ? p.behavior.debugState() : p.behavior;
return k === 'name' ? undefined : v; stringifiedState = JSON.stringify(rawState, function (k, v) {
})); return (k === 'name') ? undefined : v;
});
} catch (e) {
stringifiedState = "(cannot convert process state to JSON)";
}
lines.push(prefix + '-- ' + pid + ': ' + label + tombstoneString + stringifiedState);
} }
} }
@ -386,13 +404,16 @@ 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 = $.extend({ options = Util.extend({
demandLevel: 0, demandLevel: 0,
supplyLevel: 0, supplyLevel: 0,
demandSideIsSubscription: false demandSideIsSubscription: false,
supplyProjection: projection
}, options); }, options);
this.pattern = route.projectionToPattern(projection); this.demandPattern = Route.projectionToPattern(projection);
this.projectionSpec = route.compileProjection(projection); this.supplyPattern = Route.projectionToPattern(options.supplyProjection);
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;
@ -407,10 +428,24 @@ 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);
World.updateRoutes([sub(this.pattern, this.metaLevel, observerLevel), return [sub(this.demandPattern, this.metaLevel, observerLevel),
pub(this.pattern, this.metaLevel, observerLevel)]); pub(this.supplyPattern, this.metaLevel, observerLevel)];
}; };
DemandMatcher.prototype.handleEvent = function (e) { DemandMatcher.prototype.handleEvent = function (e) {
@ -420,20 +455,20 @@ DemandMatcher.prototype.handleEvent = function (e) {
}; };
DemandMatcher.prototype.handleGestalt = function (gestalt) { DemandMatcher.prototype.handleGestalt = function (gestalt) {
var newDemandMatcher = gestalt.project(this.projectionSpec, var newDemandMatcher = gestalt.project(this.demandProjectionSpec,
!this.demandSideIsSubscription, !this.demandSideIsSubscription,
this.metaLevel, this.metaLevel,
this.demandLevel); this.demandLevel);
var newSupplyMatcher = gestalt.project(this.projectionSpec, var newSupplyMatcher = gestalt.project(this.supplyProjectionSpec,
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]);
@ -477,59 +512,19 @@ Deduplicator.prototype.expireMessages = function () {
} }
}; };
/*---------------------------------------------------------------------------*/ ///////////////////////////////////////////////////////////////////////////
/* Ground interface */
function Ground(bootFn) { module.exports.__ = __;
var self = this; module.exports._$ = _$;
this.stepperId = null;
World.withWorldStack([[this, -1]], function () {
self.world = new World(bootFn);
});
}
Ground.prototype.step = function () { module.exports.sub = sub;
var self = this; module.exports.pub = pub;
return World.withWorldStack([[this, -1]], function () { module.exports.spawn = spawn;
return self.world.step(); module.exports.updateRoutes = updateRoutes;
}); module.exports.sendMessage = sendMessage;
}; module.exports.shutdownWorld = shutdownWorld;
Ground.prototype.checkPid = function (pid) { module.exports.World = World;
if (pid !== -1) console.error("Weird pid in Ground markPidRunnable", pid); module.exports.DemandMatcher = DemandMatcher;
}; 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);
}
};

26
src/reflect.js Normal file
View File

@ -0,0 +1,26 @@
// 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;

1585
src/route.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,24 @@
function spawnRoutingTableWidget(selector, fragmentClass, observationLevel) { var Minimart = require("./minimart.js");
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( ["DOM", selector, fragmentClass, __], 0, 2) localGestalt: (sub( domWrap(selector, fragmentClass, __), 0, 2)
.union(pub(["DOM", selector, fragmentClass, __], 0, 2)) .union(pub(domWrap(selector, fragmentClass, __), 0, 2))
.telescoped()), .telescoped()),
digestGestalt: function (g) { digestGestalt: function (g) {
@ -18,11 +26,10 @@ function spawnRoutingTableWidget(selector, fragmentClass, observationLevel) {
}, },
updateState: function () { updateState: function () {
var elts = ["ul", {"class": "routing-table"}, var elts = ["pre", Route.deserializeGestalt(this.state).pretty()];
["li", ["pre", route.deserializeGestalt(this.state).pretty()]]];
World.updateRoutes([sub(__, 0, observationLevel), World.updateRoutes([sub(__, 0, observationLevel),
pub(__, 0, observationLevel), pub(__, 0, observationLevel),
pub(["DOM", selector, fragmentClass, elts])]); pub(domWrap(selector, fragmentClass, elts))]);
}, },
handleEvent: function (e) { handleEvent: function (e) {
@ -45,3 +52,5 @@ function spawnRoutingTableWidget(selector, fragmentClass, observationLevel) {
}); });
} }
module.exports.spawnRoutingTableWidget = spawnRoutingTableWidget;

View File

@ -1,4 +1,9 @@
// 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";
@ -7,7 +12,7 @@ function Spy(label, useJson, observationLevel) {
} }
Spy.prototype.boot = function () { Spy.prototype.boot = function () {
World.updateRoutes([sub(__, 0, this.observationLevel), pub(__, 0, this.observationLevel)]); return [sub(__, 0, this.observationLevel), pub(__, 0, this.observationLevel)];
}; };
Spy.prototype.handleEvent = function (e) { Spy.prototype.handleEvent = function (e) {
@ -29,3 +34,5 @@ Spy.prototype.handleEvent = function (e) {
break; break;
} }
}; };
module.exports.Spy = Spy;

23
src/util.js Normal file
View File

@ -0,0 +1,23 @@
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

@ -2,6 +2,11 @@
// 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";
@ -12,8 +17,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) {};
@ -25,3 +30,5 @@ WakeDetector.prototype.trigger = function () {
} }
this.mostRecentTrigger = now; this.mostRecentTrigger = now;
}; };
module.exports.WakeDetector = WakeDetector;

View File

@ -1,3 +1,12 @@
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
@ -15,11 +24,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 Deduplicator(); this.deduplicator = new Minimart.Deduplicator();
this.connectionCount = 0; this.connectionCount = 0;
this.activityTimestamp = 0; this.activityTimestamp = 0;
@ -29,6 +38,20 @@ 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; }
@ -58,8 +81,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());
}; };
@ -88,7 +111,8 @@ WebSocketConnection.prototype.safeSend = function (m) {
}; };
WebSocketConnection.prototype.sendLocalRoutes = function () { WebSocketConnection.prototype.sendLocalRoutes = function () {
var newLocalRoutesMessage = JSON.stringify(encodeEvent(updateRoutes([this.localGestalt]))); var newLocalRoutesMessage =
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);
@ -96,14 +120,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));
} }
@ -115,9 +139,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);
@ -129,7 +153,8 @@ 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(encodeEvent(sendMessage(m[2], m[1], e.isFeedback))); var encoded = JSON.stringify(Codec.encodeEvent(
Minimart.sendMessage(m[2], m[1], e.isFeedback)));
if (this.deduplicator.accept(encoded)) { if (this.deduplicator.accept(encoded)) {
this.safeSend(encoded); this.safeSend(encoded);
} }
@ -182,7 +207,7 @@ WebSocketConnection.prototype.onmessage = function (wse) {
return; // recordActivity already took care of our timers return; // recordActivity already took care of our timers
} }
var e = decodeAction(j); var e = Codec.decodeAction(j);
switch (e.type) { switch (e.type) {
case "routes": case "routes":
if (this.prevPeerRoutesMessage !== wse.data) { if (this.prevPeerRoutesMessage !== wse.data) {
@ -218,24 +243,5 @@ WebSocketConnection.prototype.onclose = function (e) {
}; };
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Wire protocol representation of events and actions
function encodeEvent(e) { module.exports.WebSocketConnection = WebSocketConnection;
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) };
}
}

56
src/worker.js Normal file
View File

@ -0,0 +1,56 @@
/* 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;

99
test/test-actor.js Normal file
View File

@ -0,0 +1,99 @@
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]]);
});
});

544
test/test-route.js Normal file
View File

@ -0,0 +1,544 @@
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
View File

@ -1,264 +0,0 @@
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);
})();