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
server-cert.pem
MarketplaceChat.app.zip
MarketplaceChat.app/
scratch/
_site/
node_modules/

View File

@ -1,21 +1,5 @@
APP_NAME=MarketplaceChat.app
LIB_SOURCES=\
route.js \
marketplace.js \
spy.js \
dom-driver.js \
jquery-driver.js \
routing-table-widget.js \
routing-table-widget.css \
wake-detector.js \
websocket-driver.js
APP_SOURCES=\
examples/chat/index.html \
examples/chat/index.js \
examples/chat/style.css
RESOURCES=$(wildcard examples/chat/app-resources/*)
all: $(APP_NAME).zip
all:
npm install .
keys: private-key.pem server-cert.pem
@ -32,22 +16,8 @@ server-cert.pem: private-key.pem
clean-keys:
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:
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:
- `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
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>
<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/bootstrap/js/bootstrap.min.js"></script> -->
<script src="../../third-party/jquery-2.0.3.min.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="../../dist/minimart.js"></script>
<script src="index.js"></script>
</head>
<body>
<div class="container-fluid">
<!-- <div class="row-fluid"> -->
<!-- <div class="span12"> -->
<!-- <h1>JS Marketplace</h1> -->
<!-- </div> -->
<!-- </div> -->
<div class="row-fluid">
<div class="span12">
<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) {
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");
@ -15,12 +19,10 @@ function outputItem(item) {
return item;
}
function updateNymList(g) {
function updateNymList(allStatuses) {
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];
for (var i = 0; i < allStatuses.length; i++) {
statuses[allStatuses[i].nym] = allStatuses[i].status;
}
var nyms = [];
for (var nym in statuses) { nyms.push(nym); }
@ -55,101 +57,81 @@ $(document).ready(function () {
$("#nym_form").submit(function (e) { e.preventDefault(); return false; });
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');
// World.spawn(new Spy());
spawnJQueryDriver();
spawnDOMDriver();
spawnRoutingTableWidget("#spy-holder", "spy");
Minimart.JQuery.spawnJQueryDriver();
Minimart.DOM.spawnDOMDriver();
Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy");
World.spawn(new WakeDetector());
var wsconn = new WebSocketConnection("broker", $("#wsurl").val(), true);
World.spawn(new Minimart.WakeDetector());
var wsconn = new Minimart.WebSocket.WebSocketConnection("broker", $("#wsurl").val(), true);
World.spawn(wsconn);
World.spawn({
World.spawn(new Actor(function () {
// Monitor connection, notifying connectivity changes
state: "crashed", // start with this to avoid spurious initial message print
boot: function () {
World.updateRoutes([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";
this.state = "crashed"; // start with this to avoid spurious initial message print
Actor.observeAdvertisers(
function () { return ["broker_state", _$("newState")]; },
{ name: "states" },
function () {
var newState = this.states.length > 0 ? this.states[0].newState : "crashed";
if (this.state != newState) {
outputState(newState);
this.state = newState;
}
}
}
});
World.spawn({
});
}));
World.spawn(new Actor(function () {
// Actual chat functionality
boot: function () {
World.updateRoutes(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;
this.nym = function () { return $("#nym").val(); };
this.currentStatus = function () { return $("#status").val(); };
Actor.subscribe(
function () { return "wake"; },
function () { wsconn.forceclose(); });
Actor.advertise(
function () { return ["broker", 0,
chatEvent(this.nym(), this.currentStatus(), __, __)]; });
Actor.observeAdvertisers(
function () { return ["broker", 0,
chatEvent(_$("nym"), _$("status"), __, __)]; },
{ name: "allStatuses" },
function () { updateNymList(this.allStatuses); });
Actor.subscribe(
function () { return ["jQuery", "#send_chat", "click", __]; },
function () {
var inp = $("#chat_input");
var utterance = inp.val();
inp.val("");
if (utterance) {
World.send(["broker", 0, chatEvent(this.nym(),
this.currentStatus(),
utterance)]);
}
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;
}
}
});
});
Actor.subscribe(
function () { return ["jQuery", "#nym", "change", __]; },
function () { this.updateRoutes(); });
Actor.subscribe(
function () { return ["jQuery", "#status", "change", __]; },
function () { this.updateRoutes(); });
Actor.subscribe(
function () { return ["jQuery", "#wsurl", "change", __]; },
function () {
wsconn.forceclose();
wsconn.wsurl = $("#wsurl").val();
});
Actor.subscribe(
function () { return ["broker", 0, chatEvent(_$("who"), __, _$("what"), __)]; },
function (who, what) { outputUtterance(who, what); });
}));
});
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>
<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">
<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="../../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="../../dist/minimart.js"></script>
<script src="index.js"></script>
</head>
<body>

View File

@ -1,40 +1,51 @@
var G;
$(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');
// World.spawn(new Spy("GROUND", true));
spawnDOMDriver();
spawnRoutingTableWidget("#spy-holder", "spy");
Minimart.DOM.spawnDOMDriver();
Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy");
World.spawn({
handleEvent: function (e) {
if (e.type === "message" && e.message[0] === "jQuery") {
World.spawn(new Actor(function () {
Actor.subscribe(
function () { return ["jQuery", "button.clicker", "click", __]; },
function () {
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({
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") {
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 Actor(function () {
this.counter = 0;
Actor.subscribe(
function () { return "bump_count"; },
function () {
this.counter++;
this.updateState();
}
}
});
this.updateRoutes();
});
Actor.advertise(
function () {
return ["DOM", "#counter-holder", "counter",
["div",
["p", "The current count is: ", this.counter]]];
});
}));
});
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>
<meta charset="utf-8">
<script src="../../third-party/jquery-2.0.3.min.js"></script>
<script src="../../route.js"></script>
<script src="../../marketplace.js"></script>
<script src="../../spy.js"></script>
<script src="../../dist/minimart.js"></script>
<script src="index.js"></script>
</head>
<body>

View File

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

View File

@ -9,16 +9,7 @@
<link href="style.css" rel="stylesheet">
<script src="../../third-party/jquery-2.0.3.min.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="../../dist/minimart.js"></script>
<script src="index.js"></script>
</head>
<body>

View File

@ -1,6 +1,13 @@
///////////////////////////////////////////////////////////////////////////
// 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) {
return "<span class='"+cls+"'>"+
((pos >= lo && pos < hi)
@ -10,53 +17,47 @@ function piece(text, pos, lo, hi, cls) {
}
function spawnGui() {
World.spawn({
boot: function () {
World.updateRoutes([sub(["jQuery", "#inputRow", "+keypress", __]),
sub(["fieldContents", __, __], 0, 1),
sub(["highlight", __], 0, 1)]);
},
fieldContentsSpec: route.compileProjection(["fieldContents", _$, _$]),
highlightSpec: route.compileProjection(["highlight", _$]),
handleEvent: function (e) {
switch (e.type) {
case "routes":
var text = "", pos = 0, highlight = false;
// BUG: escape text!
var fc = route.matcherKeys(e.gestalt.project(this.fieldContentsSpec, true));
if (fc.length > 0) {
text = fc[0][0];
pos = fc[0][1];
}
var hl = route.matcherKeys(e.gestalt.project(this.highlightSpec, true));
if (hl.length > 0) {
highlight = hl[0][0];
}
$("#fieldContents")[0].innerHTML = highlight
? piece(text, pos, 0, highlight[0], "normal") +
piece(text, pos, highlight[0], highlight[1], "highlight") +
piece(text, pos, highlight[1], text.length + 1, "normal")
: piece(text, pos, 0, text.length + 1, "normal");
break;
case "message":
if (e.message[0] === "jQuery") { // it's a keypress event
var keycode = e.message[3].keyCode;
var character = String.fromCharCode(e.message[3].charCode);
if (keycode === 37 /* left */) {
World.send(["fieldCommand", "cursorLeft"]);
} else if (keycode === 39 /* right */) {
World.send(["fieldCommand", "cursorRight"]);
} else if (keycode === 9 /* tab */) {
// ignore
} else if (keycode === 8 /* backspace */) {
World.send(["fieldCommand", "backspace"]);
} else if (character) {
World.send(["fieldCommand", ["insert", character]]);
}
}
}
}
});
World.spawn(new Actor(function () {
Actor.subscribe(
function () { return ["jQuery", "#inputRow", "+keypress", _$("event")]; },
function (event) {
var keycode = event.keyCode;
var character = String.fromCharCode(event.charCode);
if (keycode === 37 /* left */) {
World.send(["fieldCommand", "cursorLeft"]);
} else if (keycode === 39 /* right */) {
World.send(["fieldCommand", "cursorRight"]);
} else if (keycode === 9 /* tab */) {
// ignore
} else if (keycode === 8 /* backspace */) {
World.send(["fieldCommand", "backspace"]);
} else if (character) {
World.send(["fieldCommand", ["insert", character]]);
}
});
Actor.observeAdvertisers(
function () { return ["fieldContents", _$("text"), _$("pos")]; },
{ singleton: "field" },
updateDisplay);
Actor.observeAdvertisers(
function () { return ["highlight", _$("state")]; },
{ singleton: "highlight" },
updateDisplay);
function updateDisplay() {
// BUG: escape text!
var text = this.field ? this.field.text : "";
var pos = this.field ? this.field.pos : 0;
var highlight = this.highlight ? this.highlight.state : false;
$("#fieldContents")[0].innerHTML = highlight
? piece(text, pos, 0, highlight[0], "normal") +
piece(text, pos, highlight[0], highlight[1], "highlight") +
piece(text, pos, highlight[1], text.length + 1, "normal")
: piece(text, pos, 0, text.length + 1, "normal");
}
}));
}
///////////////////////////////////////////////////////////////////////////
@ -64,92 +65,80 @@ function spawnGui() {
function spawnModel() {
var initialContents = "initial";
World.spawn({
fieldContents: initialContents,
cursorPos: initialContents.length, /* positions address gaps between characters */
boot: function () {
World.updateRoutes(this.subscriptions());
},
subscriptions: function () {
return [sub(["fieldCommand", __]),
pub(["fieldContents", this.fieldContents, this.cursorPos])];
},
handleEvent: function (e) {
switch (e.type) {
case "message":
if (e.message[1] === "cursorLeft") {
this.cursorPos--;
if (this.cursorPos < 0)
this.cursorPos = 0;
} else if (e.message[1] === "cursorRight") {
this.cursorPos++;
if (this.cursorPos > this.fieldContents.length)
this.cursorPos = this.fieldContents.length;
} else if (e.message[1] === "backspace" && this.cursorPos > 0) {
this.fieldContents =
this.fieldContents.substring(0, this.cursorPos - 1) +
this.fieldContents.substring(this.cursorPos);
this.cursorPos--;
} else if (e.message[1].constructor === Array && e.message[1][0] === "insert") {
var newText = e.message[1][1];
this.fieldContents =
this.fieldContents.substring(0, this.cursorPos) +
newText +
this.fieldContents.substring(this.cursorPos);
this.cursorPos += newText.length;
}
World.updateRoutes(this.subscriptions());
break;
}
}
});
World.spawn(new Actor(function () {
this.fieldContents = initialContents;
this.cursorPos = initialContents.length; /* positions address gaps between characters */
Actor.advertise(
function () { return ["fieldContents", this.fieldContents, this.cursorPos]; });
Actor.subscribe(
function () { return ["fieldCommand", _$("command")]; },
function (command) {
if (command === "cursorLeft") {
this.cursorPos--;
if (this.cursorPos < 0)
this.cursorPos = 0;
} else if (command === "cursorRight") {
this.cursorPos++;
if (this.cursorPos > this.fieldContents.length)
this.cursorPos = this.fieldContents.length;
} else if (command === "backspace" && this.cursorPos > 0) {
this.fieldContents =
this.fieldContents.substring(0, this.cursorPos - 1) +
this.fieldContents.substring(this.cursorPos);
this.cursorPos--;
} else if (command.constructor === Array && command[0] === "insert") {
var newText = command[1];
this.fieldContents =
this.fieldContents.substring(0, this.cursorPos) +
newText +
this.fieldContents.substring(this.cursorPos);
this.cursorPos += newText.length;
}
this.updateRoutes();
});
}));
}
///////////////////////////////////////////////////////////////////////////
// Search engine
function spawnSearch() {
World.spawn({
fieldContents: "",
highlight: false,
fieldContentsSpec: route.compileProjection(["fieldContents", _$, _$]),
boot: function () {
World.updateRoutes(this.subscriptions());
},
subscriptions: function () {
return [sub(["jQuery", "#searchBox", "input", __]),
sub(["fieldContents", __, __], 0, 1),
pub(["highlight", this.highlight])];
},
search: function () {
var searchtext = $("#searchBox")[0].value;
var oldHighlight = this.highlight;
if (searchtext) {
var pos = this.fieldContents.indexOf(searchtext);
this.highlight = (pos !== -1) && [pos, pos + searchtext.length];
} else {
this.highlight = false;
}
if (JSON.stringify(oldHighlight) !== JSON.stringify(this.highlight)) {
World.updateRoutes(this.subscriptions());
}
},
handleEvent: function (e) {
switch (e.type) {
case "routes":
var fc = route.matcherKeys(e.gestalt.project(this.fieldContentsSpec, true));
if (fc.length > 0) {
this.fieldContents = fc[0][0];
}
this.search();
break;
case "message":
if (e.message[0] === "jQuery") { // it's a search box input event
this.search();
}
}
World.spawn(new Actor(function () {
var self = this;
self.fieldContents = "";
self.highlight = false;
Actor.advertise(
function () { return ["highlight", self.highlight]; });
Actor.subscribe(
function () { return ["jQuery", "#searchBox", "input", _$("event")]; },
search);
Actor.observeAdvertisers(
function () { return ["fieldContents", _$("text"), _$("pos")]; },
{ singleton: "field" },
function () {
self.fieldContents = self.field ? self.field.text : "";
search();
});
function search() {
var searchtext = $("#searchBox")[0].value;
var oldHighlight = self.highlight;
if (searchtext) {
var pos = self.fieldContents.indexOf(searchtext);
self.highlight = (pos !== -1) && [pos, pos + searchtext.length];
} else {
self.highlight = false;
}
});
if (JSON.stringify(oldHighlight) !== JSON.stringify(self.highlight)) {
self.updateRoutes();
}
}
}));
}
///////////////////////////////////////////////////////////////////////////
@ -157,10 +146,10 @@ function spawnSearch() {
var G;
$(document).ready(function () {
G = new Ground(function () {
spawnJQueryDriver();
spawnDOMDriver();
spawnRoutingTableWidget("#spy-holder", "spy");
G = new Minimart.Ground(function () {
Minimart.JQuery.spawnJQueryDriver();
Minimart.DOM.spawnDOMDriver();
Minimart.RoutingTableWidget.spawnRoutingTableWidget("#spy-holder", "spy");
spawnGui();
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
var Minimart = require("./minimart.js");
var World = Minimart.World;
var sub = Minimart.sub;
var pub = Minimart.pub;
var __ = Minimart.__;
var _$ = Minimart._$;
function spawnDOMDriver() {
var d = new DemandMatcher(["DOM", _$, _$, _$]);
function spawnDOMDriver(domWrapFunction, jQueryWrapFunction) {
domWrapFunction = domWrapFunction || defaultWrapFunction;
var d = new Minimart.DemandMatcher(domWrapFunction(_$, _$, _$));
d.onDemandIncrease = function (captures) {
var selector = captures[0];
var fragmentClass = captures[1];
var fragmentSpec = captures[2];
World.spawn(new DOMFragment(selector, fragmentClass, fragmentSpec),
[sub(["DOM", selector, fragmentClass, fragmentSpec]),
sub(["DOM", selector, fragmentClass, fragmentSpec], 0, 1)]);
World.spawn(new DOMFragment(selector,
fragmentClass,
fragmentSpec,
domWrapFunction,
jQueryWrapFunction));
};
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.fragmentClass = fragmentClass;
this.fragmentSpec = fragmentSpec;
this.domWrapFunction = domWrapFunction;
this.jQueryWrapFunction = jQueryWrapFunction;
this.nodes = this.buildNodes();
}
DOMFragment.prototype.boot = function () {
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 () {
spawnJQueryDriver(self.selector+" > ."+self.fragmentClass, 1);
Minimart.JQuery.spawnJQueryDriver(self.selector+" > ."+self.fragmentClass,
1,
self.jQueryWrapFunction);
World.spawn({
boot: function () { return [monitoring] },
handleEvent: function (e) {
if (e.type === "routes") {
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) {
@ -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) {
// Fragment specs are roughly JSON-equivalents of SXML.
// spec ::== ["tag", {"attr": "value", ...}, spec, spec, ...]
// spec ::== ["tag", [["attr", "value"], ...], spec, spec, ...]
// | ["tag", spec, spec, ...]
// | "cdata"
if (typeof(spec) === "string" || typeof(spec) === "number") {
return document.createTextNode(spec);
} else if ($.isArray(spec)) {
var tagName = spec[0];
var hasAttrs = $.isPlainObject(spec[1]);
var hasAttrs = isAttributes(spec[1]);
var attrs = hasAttrs ? spec[1] : {};
var kidIndex = hasAttrs ? 2 : 1;
// Wow! Such XSS! Many hacks! So vulnerability! Amaze!
var n = document.createElement(tagName);
for (var attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
n.setAttribute(attr, attrs[attr]);
}
for (var i = 0; i < attrs.length; i++) {
n.setAttribute(attrs[i][0], attrs[i][1]);
}
for (var i = kidIndex; i < spec.length; i++) {
n.appendChild(this.interpretSpec(spec[i]));
}
return n;
} else {
throw new Error("Ill-formed DOM specification");
}
};
@ -86,3 +113,8 @@ DOMFragment.prototype.buildNodes = function () {
});
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
/*---------------------------------------------------------------------------*/
/* Events and Actions */
var __ = route.__;
var _$ = route._$;
var __ = Route.__;
var _$ = Route._$;
function sub(pattern, metaLevel, level) {
return route.simpleGestalt(false, pattern, metaLevel, level);
return Route.simpleGestalt(false, 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) {
return { type: "spawn",
behavior: behavior,
initialGestalt: route.gestaltUnion(initialGestalts || []) };
function spawn(behavior) {
return { type: "spawn", behavior: behavior };
}
function updateRoutes(gestalts) {
return { type: "routes", gestalt: route.gestaltUnion(gestalts) };
return { type: "routes", gestalt: Route.gestaltUnion(gestalts) };
}
function pendingRoutingUpdate(aggregate, affectedSubgestalt, knownTarget) {
@ -49,11 +52,11 @@ function World(bootFn) {
this.alive = true;
this.eventQueue = [];
this.runnablePids = {};
this.partialGestalt = route.emptyGestalt; // Only gestalt from local processes
this.fullGestalt = route.emptyGestalt ;; // partialGestalt unioned with downwardGestalt
this.partialGestalt = Route.emptyGestalt; // Only gestalt from local processes
this.fullGestalt = Route.emptyGestalt ;; // partialGestalt unioned with downwardGestalt
this.processTable = {};
this.tombstones = {};
this.downwardGestalt = route.emptyGestalt;
this.downwardGestalt = Route.emptyGestalt;
this.processActions = [];
this.asChild(-1, bootFn, true);
}
@ -80,8 +83,8 @@ World.updateRoutes = function (gestalts) {
World.current().enqueueAction(World.activePid(), updateRoutes(gestalts));
};
World.spawn = function (behavior, initialGestalts) {
World.current().enqueueAction(World.activePid(), spawn(behavior, initialGestalts));
World.spawn = function (behavior) {
World.current().enqueueAction(World.activePid(), spawn(behavior));
};
World.exit = function (exn) {
@ -134,7 +137,7 @@ World.prototype.enqueueAction = function (pid, action) {
World.prototype.isInert = function () {
return this.eventQueue.length === 0
&& this.processActions.length === 0
&& route.is_emptySet(this.runnablePids);
&& Route.is_emptySet(this.runnablePids);
};
World.prototype.markPidRunnable = function (pid) {
@ -174,16 +177,16 @@ World.prototype.kill = function (pid, exn) {
console.log("Process exited", pid, exn);
}
var p = this.processTable[pid];
if (p && p.behavior.trapexit) {
this.asChild(pid, function () { return p.behavior.trapexit(exn); });
}
delete this.processTable[pid];
if (p) {
if (p.behavior.trapexit) {
this.asChild(pid, function () { return p.behavior.trapexit(exn); }, true);
}
if (exn) {
p.exitReason = exn;
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) {
case "spawn":
var pid = World.nextPid++;
var newGestalt = action.initialGestalt.label(pid);
this.processTable[pid] = { gestalt: newGestalt, behavior: action.behavior };
if (action.behavior.boot) {
this.asChild(pid, function () { action.behavior.boot() });
var entry = { gestalt: Route.emptyGestalt, behavior: action.behavior };
this.processTable[pid] = entry;
if (entry.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.applyAndIssueRoutingUpdate(route.emptyGestalt, newGestalt, pid);
if (!Route.emptyGestalt.equals(entry.gestalt)) {
this.applyAndIssueRoutingUpdate(Route.emptyGestalt, entry.gestalt, pid);
}
break;
case "routes":
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) {
switch (e.type) {
case "routes":
@ -343,7 +356,7 @@ World.prototype.processTree = function () {
for (var pid in this.tombstones) {
kids.push([pid, this.tombstones[pid]]);
}
kids.sort();
kids.sort(function (a, b) { return a[0] - b[0] });
return kids;
};
@ -351,7 +364,7 @@ World.prototype.textProcessTree = function (ownPid) {
var lines = [];
function dumpProcess(prefix, pid, p) {
if (p instanceof Array) {
if (Array.isArray(p)) {
lines.push(prefix + '--+ ' + pid);
for (var i = 0; i < p.length; i++) {
dumpProcess(prefix + ' |', p[i][0], p[i][1]);
@ -360,11 +373,16 @@ World.prototype.textProcessTree = function (ownPid) {
} else {
var label = p.behavior.name || p.behavior.constructor.name || '';
var tombstoneString = p.exitReason ? ' (EXITED: ' + p.exitReason + ') ' : '';
lines.push(prefix + '-- ' + pid + ': ' + label +
tombstoneString +
JSON.stringify(p.behavior, function (k, v) {
return k === 'name' ? undefined : v;
}));
var stringifiedState;
try {
var rawState = p.behavior.debugState ? p.behavior.debugState() : p.behavior;
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 */
function DemandMatcher(projection, metaLevel, options) {
options = $.extend({
options = Util.extend({
demandLevel: 0,
supplyLevel: 0,
demandSideIsSubscription: false
demandSideIsSubscription: false,
supplyProjection: projection
}, options);
this.pattern = route.projectionToPattern(projection);
this.projectionSpec = route.compileProjection(projection);
this.demandPattern = Route.projectionToPattern(projection);
this.supplyPattern = Route.projectionToPattern(options.supplyProjection);
this.demandProjectionSpec = Route.compileProjection(projection);
this.supplyProjectionSpec = Route.compileProjection(options.supplyProjection);
this.metaLevel = metaLevel | 0;
this.demandLevel = options.demandLevel;
this.supplyLevel = options.supplyLevel;
@ -407,10 +428,24 @@ function DemandMatcher(projection, metaLevel, options) {
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 () {
var observerLevel = 1 + Math.max(this.demandLevel, this.supplyLevel);
World.updateRoutes([sub(this.pattern, this.metaLevel, observerLevel),
pub(this.pattern, this.metaLevel, observerLevel)]);
return [sub(this.demandPattern, this.metaLevel, observerLevel),
pub(this.supplyPattern, this.metaLevel, observerLevel)];
};
DemandMatcher.prototype.handleEvent = function (e) {
@ -420,20 +455,20 @@ DemandMatcher.prototype.handleEvent = function (e) {
};
DemandMatcher.prototype.handleGestalt = function (gestalt) {
var newDemandMatcher = gestalt.project(this.projectionSpec,
var newDemandMatcher = gestalt.project(this.demandProjectionSpec,
!this.demandSideIsSubscription,
this.metaLevel,
this.demandLevel);
var newSupplyMatcher = gestalt.project(this.projectionSpec,
var newSupplyMatcher = gestalt.project(this.supplyProjectionSpec,
this.demandSideIsSubscription,
this.metaLevel,
this.supplyLevel);
var newDemand = route.arrayToSet(route.matcherKeys(newDemandMatcher));
var newSupply = route.arrayToSet(route.matcherKeys(newSupplyMatcher));
var demandDelta = route.setSubtract(newDemand, this.currentDemand);
var supplyDelta = route.setSubtract(this.currentSupply, newSupply);
var demandIncr = route.setSubtract(demandDelta, newSupply);
var supplyDecr = route.setIntersect(supplyDelta, newDemand);
var newDemand = Route.arrayToSet(Route.matcherKeys(newDemandMatcher));
var newSupply = Route.arrayToSet(Route.matcherKeys(newSupplyMatcher));
var demandDelta = Route.setSubtract(newDemand, this.currentDemand);
var supplyDelta = Route.setSubtract(this.currentSupply, newSupply);
var demandIncr = Route.setSubtract(demandDelta, newSupply);
var supplyDecr = Route.setIntersect(supplyDelta, newDemand);
this.currentDemand = newDemand;
this.currentSupply = newSupply;
for (var k in demandIncr) this.onDemandIncrease(demandIncr[k]);
@ -477,59 +512,19 @@ Deduplicator.prototype.expireMessages = function () {
}
};
/*---------------------------------------------------------------------------*/
/* Ground interface */
///////////////////////////////////////////////////////////////////////////
function Ground(bootFn) {
var self = this;
this.stepperId = null;
World.withWorldStack([[this, -1]], function () {
self.world = new World(bootFn);
});
}
module.exports.__ = __;
module.exports._$ = _$;
Ground.prototype.step = function () {
var self = this;
return World.withWorldStack([[this, -1]], function () {
return self.world.step();
});
};
module.exports.sub = sub;
module.exports.pub = pub;
module.exports.spawn = spawn;
module.exports.updateRoutes = updateRoutes;
module.exports.sendMessage = sendMessage;
module.exports.shutdownWorld = shutdownWorld;
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.World = World;
module.exports.DemandMatcher = DemandMatcher;
module.exports.Deduplicator = Deduplicator;
module.exports.Route = Route;

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

View File

@ -1,4 +1,9 @@
// 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) {
this.label = label || "SPY";
@ -7,7 +12,7 @@ function Spy(label, useJson, observationLevel) {
}
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) {
@ -29,3 +34,5 @@ Spy.prototype.handleEvent = function (e) {
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
// interrupted, and warns others about it
// 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) {
this.message = "wake";
@ -12,8 +17,8 @@ function WakeDetector(period) {
WakeDetector.prototype.boot = function () {
var self = this;
World.updateRoutes([pub(this.message)]);
this.timerId = setInterval(World.wrap(function () { self.trigger(); }), this.period);
return [pub(this.message)];
};
WakeDetector.prototype.handleEvent = function (e) {};
@ -25,3 +30,5 @@ WakeDetector.prototype.trigger = function () {
}
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
@ -15,11 +24,11 @@ function WebSocketConnection(label, wsurl, shouldReconnect) {
this.wsurl = wsurl;
this.shouldReconnect = shouldReconnect ? true : false;
this.reconnectDelay = DEFAULT_RECONNECT_DELAY;
this.localGestalt = route.emptyGestalt;
this.peerGestalt = route.emptyGestalt;
this.localGestalt = Route.emptyGestalt;
this.peerGestalt = Route.emptyGestalt;
this.prevLocalRoutesMessage = null;
this.prevPeerRoutesMessage = null;
this.deduplicator = new Deduplicator();
this.deduplicator = new Minimart.Deduplicator();
this.connectionCount = 0;
this.activityTimestamp = 0;
@ -29,6 +38,20 @@ function WebSocketConnection(label, wsurl, shouldReconnect) {
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 () {
if (this.idleTimer) { clearTimeout(this.idleTimer); this.idleTimer = null; }
if (this.pingTimer) { clearTimeout(this.pingTimer); this.pingTimer = null; }
@ -58,8 +81,8 @@ WebSocketConnection.prototype.relayGestalt = function () {
WebSocketConnection.prototype.aggregateGestalt = function () {
var self = this;
return this.peerGestalt.transform(function (m, metaLevel) {
return route.compilePattern(true,
[self.label, metaLevel, route.embeddedMatcher(m)]);
return Route.compilePattern(true,
[self.label, metaLevel, Route.embeddedMatcher(m)]);
}).union(this.relayGestalt());
};
@ -88,7 +111,8 @@ WebSocketConnection.prototype.safeSend = function (m) {
};
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) {
this.prevLocalRoutesMessage = newLocalRoutesMessage;
this.safeSend(newLocalRoutesMessage);
@ -96,14 +120,14 @@ WebSocketConnection.prototype.sendLocalRoutes = function () {
};
WebSocketConnection.prototype.collectMatchers = function (getAdvertisements, level, g) {
var extractMetaLevels = route.compileProjection([this.label, _$, __]);
var mls = route.matcherKeys(g.project(extractMetaLevels, getAdvertisements, 0, level));
var extractMetaLevels = Route.compileProjection([this.label, _$, __]);
var mls = Route.matcherKeys(g.project(extractMetaLevels, getAdvertisements, 0, level));
for (var i = 0; i < mls.length; i++) {
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);
this.localGestalt = this.localGestalt.union(route.simpleGestalt(getAdvertisements,
route.embeddedMatcher(m),
this.localGestalt = this.localGestalt.union(Route.simpleGestalt(getAdvertisements,
Route.embeddedMatcher(m),
metaLevel,
level));
}
@ -115,9 +139,9 @@ WebSocketConnection.prototype.handleEvent = function (e) {
case "routes":
// TODO: GROSS - erasing by pid!
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);
this.localGestalt = route.emptyGestalt;
this.localGestalt = Route.emptyGestalt;
for (var level = 0; level < nLevels; level++) {
this.collectMatchers(false, level, g);
this.collectMatchers(true, level, g);
@ -129,7 +153,8 @@ WebSocketConnection.prototype.handleEvent = function (e) {
var m = e.message;
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)) {
this.safeSend(encoded);
}
@ -182,7 +207,7 @@ WebSocketConnection.prototype.onmessage = function (wse) {
return; // recordActivity already took care of our timers
}
var e = decodeAction(j);
var e = Codec.decodeAction(j);
switch (e.type) {
case "routes":
if (this.prevPeerRoutesMessage !== wse.data) {
@ -218,24 +243,5 @@ WebSocketConnection.prototype.onclose = function (e) {
};
///////////////////////////////////////////////////////////////////////////
// Wire protocol representation of events and actions
function encodeEvent(e) {
switch (e.type) {
case "routes":
return ["routes", e.gestalt.serialize(function (v) { return true; })];
case "message":
return ["message", e.message, e.metaLevel, e.isFeedback];
}
}
function decodeAction(j) {
switch (j[0]) {
case "routes":
return updateRoutes([route.deserializeGestalt(j[1], function (v) { return true; })]);
case "message":
return sendMessage(j[1], j[2], j[3]);
default:
throw { message: "Invalid JSON-encoded action: " + JSON.stringify(j) };
}
}
module.exports.WebSocketConnection = WebSocketConnection;

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);
})();