2014-07-24 00:21:51 +00:00
|
|
|
!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Minimart=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
|
2014-07-24 23:22:22 +00:00
|
|
|
var Reflect = _dereq_("./reflect.js");
|
|
|
|
var Minimart = _dereq_("./minimart.js");
|
2014-07-25 23:20:27 +00:00
|
|
|
var World = Minimart.World;
|
2014-07-24 23:22:22 +00:00
|
|
|
var Route = Minimart.Route;
|
|
|
|
|
|
|
|
Actor._chunks = null;
|
|
|
|
|
2014-08-26 01:39:55 +00:00
|
|
|
function Actor(bootfn) {
|
|
|
|
return {
|
|
|
|
boot: function () {
|
|
|
|
delete this.boot;
|
|
|
|
var oldChunks = Actor._chunks;
|
|
|
|
try {
|
2014-07-24 23:22:22 +00:00
|
|
|
Actor._chunks = [];
|
2014-08-26 01:39:55 +00:00
|
|
|
bootfn.call(this);
|
|
|
|
finalizeActor(this, Actor._chunks);
|
|
|
|
Actor._chunks = oldChunks;
|
|
|
|
} catch (e) {
|
2014-07-24 23:22:22 +00:00
|
|
|
Actor._chunks = oldChunks;
|
|
|
|
throw e;
|
2014-08-26 01:39:55 +00:00
|
|
|
}
|
2014-07-24 23:22:22 +00:00
|
|
|
}
|
2014-08-26 01:39:55 +00:00
|
|
|
};
|
2014-07-24 23:22:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function checkChunks(type) {
|
|
|
|
if (!Actor._chunks) {
|
|
|
|
throw new Error("Call to Actor."+type+" outside of Actor constructor");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
function extractChunk(type, kind, defaultOptions, args) {
|
2014-07-24 23:22:22 +00:00
|
|
|
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,
|
2014-08-02 16:00:28 +00:00
|
|
|
kind: kind,
|
2014-07-24 23:22:22 +00:00
|
|
|
rawProjectionFn: rawProjectionFn,
|
|
|
|
options: options,
|
|
|
|
handler: handler
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function recordChunk(chunk) {
|
|
|
|
Actor._chunks.push(chunk);
|
|
|
|
}
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
function chunkExtractor(type, kind, defaultOptions) {
|
2014-07-24 23:22:22 +00:00
|
|
|
return function (/* ... */) {
|
|
|
|
checkChunks(type);
|
|
|
|
recordChunk(extractChunk(type,
|
2014-08-02 16:00:28 +00:00
|
|
|
kind,
|
2014-07-24 23:22:22 +00:00
|
|
|
defaultOptions,
|
|
|
|
Array.prototype.slice.call(arguments)));
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
var participantDefaults = {
|
2014-07-24 23:22:22 +00:00
|
|
|
metaLevel: 0,
|
|
|
|
when: function () { return true; }
|
|
|
|
};
|
|
|
|
|
|
|
|
var observerDefaults = {
|
|
|
|
metaLevel: 0,
|
|
|
|
level: 0,
|
|
|
|
when: function () { return true; },
|
|
|
|
presence: null,
|
|
|
|
name: null,
|
2014-08-25 18:44:18 +00:00
|
|
|
singleton: null,
|
2014-07-24 23:22:22 +00:00
|
|
|
set: null,
|
|
|
|
added: null,
|
|
|
|
removed: null
|
|
|
|
};
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
Actor.advertise = chunkExtractor('advertise', 'participant', participantDefaults);
|
|
|
|
Actor.subscribe = chunkExtractor('subscribe', 'participant', participantDefaults);
|
2014-07-24 23:22:22 +00:00
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
Actor.observeAdvertisers = chunkExtractor('observeAdvertisers', 'observer', observerDefaults);
|
|
|
|
Actor.observeSubscribers = chunkExtractor('observeSubscribers', 'observer', observerDefaults);
|
2014-07-24 23:22:22 +00:00
|
|
|
|
|
|
|
Actor.observeGestalt = function (gestaltFn, eventHandlerFn) {
|
|
|
|
checkChunks('observeGestalt');
|
|
|
|
recordChunk({
|
|
|
|
type: 'observeGestalt',
|
2014-08-02 16:00:28 +00:00
|
|
|
kind: 'raw',
|
2014-07-24 23:22:22 +00:00
|
|
|
gestaltFn: gestaltFn,
|
|
|
|
options: {
|
|
|
|
when: function () { return true; }
|
|
|
|
},
|
|
|
|
eventHandlerFn: eventHandlerFn
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
function finalizeActor(behavior, chunks) {
|
|
|
|
var oldHandleEvent = behavior.handleEvent;
|
|
|
|
var projections = {};
|
|
|
|
var compiledProjections = {};
|
|
|
|
var previousObjs = {};
|
|
|
|
|
|
|
|
behavior.updateRoutes = function () {
|
|
|
|
var newRoutes = Route.emptyGestalt;
|
|
|
|
for (var i = 0; i < chunks.length; i++) {
|
|
|
|
var chunk = chunks[i];
|
|
|
|
if (chunk.options.when.call(this)) {
|
2014-08-02 16:00:28 +00:00
|
|
|
switch (chunk.kind) {
|
|
|
|
case 'raw':
|
2014-07-24 23:22:22 +00:00
|
|
|
newRoutes = newRoutes.union(chunk.gestaltFn.call(this));
|
|
|
|
break;
|
2014-08-02 16:00:28 +00:00
|
|
|
case 'participant':
|
2014-07-24 23:22:22 +00:00
|
|
|
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;
|
2014-08-02 16:00:28 +00:00
|
|
|
case 'observer':
|
2014-07-24 23:22:22 +00:00
|
|
|
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([]);
|
|
|
|
}
|
2014-07-25 23:20:27 +00:00
|
|
|
break;
|
2014-07-24 23:22:22 +00:00
|
|
|
default:
|
2014-08-02 16:00:28 +00:00
|
|
|
throw new Error("Unsupported chunk type/kind: "+chunk.type+"/"+chunk.kind);
|
2014-07-24 23:22:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-07-25 23:20:27 +00:00
|
|
|
World.updateRoutes([newRoutes]);
|
2014-07-24 23:22:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
behavior.handleEvent = function (e) {
|
|
|
|
if (oldHandleEvent) { oldHandleEvent.call(this, e); }
|
|
|
|
for (var i = 0; i < chunks.length; i++) {
|
|
|
|
var chunk = chunks[i];
|
2014-08-02 16:00:28 +00:00
|
|
|
switch (chunk.kind) {
|
|
|
|
case 'raw':
|
2014-07-24 23:22:22 +00:00
|
|
|
chunk.eventHandlerFn.call(this, e);
|
|
|
|
break;
|
2014-08-02 16:00:28 +00:00
|
|
|
case 'participant':
|
2014-07-24 23:22:22 +00:00
|
|
|
if (chunk.handler
|
|
|
|
&& (e.type === 'message')
|
2014-08-02 16:00:28 +00:00
|
|
|
&& (e.metaLevel === chunk.options.metaLevel)
|
2014-07-24 23:22:22 +00:00
|
|
|
&& (e.isFeedback === (chunk.type === 'advertise')))
|
|
|
|
{
|
|
|
|
var matchResult = Route.matchPattern(e.message, projections[i]);
|
|
|
|
if (matchResult) {
|
|
|
|
kwApply(chunk.handler, this, matchResult);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2014-08-02 16:00:28 +00:00
|
|
|
case 'observer':
|
2014-07-25 23:20:27 +00:00
|
|
|
if (e.type === 'routes') {
|
|
|
|
var projectionResult = e.gestalt.project(compiledProjections[i],
|
2014-07-24 23:22:22 +00:00
|
|
|
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 {
|
2014-07-25 23:20:27 +00:00
|
|
|
objs = Route.matcherKeysToObjects(keys, compiledProjections[i]);
|
2014-07-24 23:22:22 +00:00
|
|
|
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;
|
|
|
|
}
|
2014-08-25 18:44:18 +00:00
|
|
|
if (chunk.options.singleton) {
|
|
|
|
this[chunk.options.singleton] = objs.length === 1 ? objs[0] : undefined;
|
|
|
|
}
|
2014-07-24 23:22:22 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2014-07-25 23:20:27 +00:00
|
|
|
break;
|
|
|
|
default:
|
2014-08-02 16:00:28 +00:00
|
|
|
throw new Error("Unsupported chunk type/kind: "+chunk.type+"/"+chunk.kind);
|
2014-07-24 23:22:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2014-07-25 23:20:27 +00:00
|
|
|
|
2014-08-26 01:39:55 +00:00
|
|
|
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] = []; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
behavior.updateRoutes();
|
2014-07-24 23:22:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function kwApply(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)) {
|
2014-07-25 23:20:27 +00:00
|
|
|
throw new Error("Function parameter '"+formal+"' not present in args");
|
2014-07-24 23:22:22 +00:00
|
|
|
}
|
|
|
|
actuals.push(args[formal]);
|
|
|
|
}
|
|
|
|
return f.apply(thisArg, actuals);
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
module.exports.Actor = Actor;
|
|
|
|
module.exports.kwApply = kwApply;
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
},{"./minimart.js":7,"./reflect.js":8}],2:[function(_dereq_,module,exports){
|
|
|
|
// Wire protocol representation of events and actions
|
|
|
|
|
|
|
|
var Route = _dereq_("./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");
|
|
|
|
|
|
|
|
},{"./route.js":9}],3:[function(_dereq_,module,exports){
|
2014-07-24 00:21:51 +00:00
|
|
|
// DOM fragment display driver
|
|
|
|
var Minimart = _dereq_("./minimart.js");
|
|
|
|
var World = Minimart.World;
|
|
|
|
var sub = Minimart.sub;
|
|
|
|
var pub = Minimart.pub;
|
|
|
|
var __ = Minimart.__;
|
|
|
|
var _$ = Minimart._$;
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
function spawnDOMDriver(domWrapFunction, jQueryWrapFunction) {
|
|
|
|
domWrapFunction = domWrapFunction || defaultWrapFunction;
|
|
|
|
var d = new Minimart.DemandMatcher(domWrapFunction(_$, _$, _$));
|
2014-07-24 00:21:51 +00:00
|
|
|
d.onDemandIncrease = function (captures) {
|
|
|
|
var selector = captures[0];
|
|
|
|
var fragmentClass = captures[1];
|
|
|
|
var fragmentSpec = captures[2];
|
2014-07-24 23:22:22 +00:00
|
|
|
World.spawn(new DOMFragment(selector,
|
|
|
|
fragmentClass,
|
|
|
|
fragmentSpec,
|
|
|
|
domWrapFunction,
|
|
|
|
jQueryWrapFunction),
|
|
|
|
[sub(domWrapFunction(selector, fragmentClass, fragmentSpec)),
|
|
|
|
sub(domWrapFunction(selector, fragmentClass, fragmentSpec), 0, 1)]);
|
2014-07-24 00:21:51 +00:00
|
|
|
};
|
|
|
|
World.spawn(d);
|
|
|
|
}
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
function defaultWrapFunction(selector, fragmentClass, fragmentSpec) {
|
|
|
|
return ["DOM", selector, fragmentClass, fragmentSpec];
|
|
|
|
}
|
|
|
|
|
|
|
|
function DOMFragment(selector, fragmentClass, fragmentSpec, domWrapFunction, jQueryWrapFunction) {
|
2014-07-24 00:21:51 +00:00
|
|
|
this.selector = selector;
|
|
|
|
this.fragmentClass = fragmentClass;
|
|
|
|
this.fragmentSpec = fragmentSpec;
|
2014-07-24 23:22:22 +00:00
|
|
|
this.domWrapFunction = domWrapFunction;
|
|
|
|
this.jQueryWrapFunction = jQueryWrapFunction;
|
2014-07-24 00:21:51 +00:00
|
|
|
this.nodes = this.buildNodes();
|
|
|
|
}
|
|
|
|
|
|
|
|
DOMFragment.prototype.boot = function () {
|
|
|
|
var self = this;
|
2014-07-24 23:22:22 +00:00
|
|
|
var monitoring =
|
|
|
|
sub(this.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec), 1, 2);
|
2014-07-24 00:21:51 +00:00
|
|
|
World.spawn(new World(function () {
|
2014-07-24 23:22:22 +00:00
|
|
|
Minimart.JQuery.spawnJQueryDriver(self.selector+" > ."+self.fragmentClass,
|
|
|
|
1,
|
|
|
|
self.jQueryWrapFunction);
|
2014-07-24 00:21:51 +00:00
|
|
|
World.spawn({
|
|
|
|
handleEvent: function (e) {
|
|
|
|
if (e.type === "routes") {
|
|
|
|
var level = e.gestalt.getLevel(1, 0); // find participant peers
|
|
|
|
if (!e.gestalt.isEmpty() && level.isEmpty()) {
|
|
|
|
World.shutdownWorld();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [monitoring]);
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
|
|
|
DOMFragment.prototype.handleEvent = function (e) {
|
|
|
|
if (e.type === "routes" && e.gestalt.isEmpty()) {
|
|
|
|
for (var i = 0; i < this.nodes.length; i++) {
|
|
|
|
var n = this.nodes[i];
|
|
|
|
n.parentNode.removeChild(n);
|
|
|
|
}
|
|
|
|
World.exit();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
function isAttributes(x) {
|
|
|
|
return Array.isArray(x) && ((x.length === 0) || Array.isArray(x[0]));
|
|
|
|
}
|
|
|
|
|
2014-07-24 00:21:51 +00:00
|
|
|
DOMFragment.prototype.interpretSpec = function (spec) {
|
|
|
|
// Fragment specs are roughly JSON-equivalents of SXML.
|
2014-08-25 20:02:04 +00:00
|
|
|
// spec ::== ["tag", [["attr", "value"], ...], spec, spec, ...]
|
2014-07-24 00:21:51 +00:00
|
|
|
// | ["tag", spec, spec, ...]
|
|
|
|
// | "cdata"
|
|
|
|
if (typeof(spec) === "string" || typeof(spec) === "number") {
|
|
|
|
return document.createTextNode(spec);
|
|
|
|
} else if ($.isArray(spec)) {
|
|
|
|
var tagName = spec[0];
|
2014-07-24 23:22:22 +00:00
|
|
|
var hasAttrs = isAttributes(spec[1]);
|
2014-07-24 00:21:51 +00:00
|
|
|
var attrs = hasAttrs ? spec[1] : {};
|
|
|
|
var kidIndex = hasAttrs ? 2 : 1;
|
|
|
|
|
|
|
|
// Wow! Such XSS! Many hacks! So vulnerability! Amaze!
|
|
|
|
var n = document.createElement(tagName);
|
2014-07-24 23:22:22 +00:00
|
|
|
for (var i = 0; i < attrs.length; i++) {
|
|
|
|
n.setAttribute(attrs[i][0], attrs[i][1]);
|
2014-07-24 00:21:51 +00:00
|
|
|
}
|
|
|
|
for (var i = kidIndex; i < spec.length; i++) {
|
|
|
|
n.appendChild(this.interpretSpec(spec[i]));
|
|
|
|
}
|
|
|
|
return n;
|
2014-08-25 20:02:04 +00:00
|
|
|
} else {
|
|
|
|
throw new Error("Ill-formed DOM specification");
|
2014-07-24 00:21:51 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
DOMFragment.prototype.buildNodes = function () {
|
|
|
|
var self = this;
|
|
|
|
var nodes = [];
|
|
|
|
$(self.selector).each(function (index, domNode) {
|
|
|
|
var n = self.interpretSpec(self.fragmentSpec);
|
|
|
|
n.classList.add(self.fragmentClass);
|
|
|
|
domNode.appendChild(n);
|
|
|
|
nodes.push(n);
|
|
|
|
});
|
|
|
|
return nodes;
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
module.exports.spawnDOMDriver = spawnDOMDriver;
|
2014-07-24 23:22:22 +00:00
|
|
|
module.exports.defaultWrapFunction = defaultWrapFunction;
|
2014-07-24 00:21:51 +00:00
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
},{"./minimart.js":7}],4:[function(_dereq_,module,exports){
|
|
|
|
/* Ground interface */
|
|
|
|
var Minimart = _dereq_("./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;
|
|
|
|
|
|
|
|
},{"./minimart.js":7}],5:[function(_dereq_,module,exports){
|
2014-07-24 00:21:51 +00:00
|
|
|
// JQuery event driver
|
|
|
|
var Minimart = _dereq_("./minimart.js");
|
|
|
|
var World = Minimart.World;
|
|
|
|
var sub = Minimart.sub;
|
|
|
|
var pub = Minimart.pub;
|
|
|
|
var __ = Minimart.__;
|
|
|
|
var _$ = Minimart._$;
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
function spawnJQueryDriver(baseSelector, metaLevel, wrapFunction) {
|
2014-07-24 00:21:51 +00:00
|
|
|
metaLevel = metaLevel || 0;
|
2014-07-24 23:22:22 +00:00
|
|
|
wrapFunction = wrapFunction || defaultWrapFunction;
|
|
|
|
var d = new Minimart.DemandMatcher(wrapFunction(_$, _$, __), metaLevel,
|
2014-07-24 00:21:51 +00:00
|
|
|
{demandSideIsSubscription: true});
|
|
|
|
d.onDemandIncrease = function (captures) {
|
|
|
|
var selector = captures[0];
|
|
|
|
var eventName = captures[1];
|
2014-07-24 23:22:22 +00:00
|
|
|
World.spawn(new JQueryEventRouter(baseSelector,
|
|
|
|
selector,
|
|
|
|
eventName,
|
|
|
|
metaLevel,
|
|
|
|
wrapFunction),
|
|
|
|
[pub(wrapFunction(selector, eventName, __), metaLevel),
|
|
|
|
pub(wrapFunction(selector, eventName, __), metaLevel, 1)]);
|
2014-07-24 00:21:51 +00:00
|
|
|
};
|
|
|
|
World.spawn(d);
|
|
|
|
}
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
function defaultWrapFunction(selector, eventName, eventValue) {
|
|
|
|
return ["jQuery", selector, eventName, eventValue];
|
|
|
|
}
|
|
|
|
|
|
|
|
function JQueryEventRouter(baseSelector, selector, eventName, metaLevel, wrapFunction) {
|
2014-07-24 00:21:51 +00:00
|
|
|
var self = this;
|
|
|
|
this.baseSelector = baseSelector || null;
|
|
|
|
this.selector = selector;
|
|
|
|
this.eventName = eventName;
|
|
|
|
this.metaLevel = metaLevel || 0;
|
2014-07-24 23:22:22 +00:00
|
|
|
this.wrapFunction = wrapFunction || defaultWrapFunction;
|
2014-07-24 00:21:51 +00:00
|
|
|
this.preventDefault = (this.eventName.charAt(0) !== "+");
|
|
|
|
this.handler =
|
|
|
|
World.wrap(function (e) {
|
2014-07-24 23:22:22 +00:00
|
|
|
World.send(self.wrapFunction(self.selector, self.eventName, e), self.metaLevel);
|
2014-07-24 00:21:51 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-07-24 00:21:51 +00:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
module.exports.spawnJQueryDriver = spawnJQueryDriver;
|
2014-07-24 23:22:22 +00:00
|
|
|
module.exports.simplifyDOMEvent = simplifyDOMEvent;
|
|
|
|
module.exports.defaultWrapFunction = defaultWrapFunction;
|
2014-07-24 00:21:51 +00:00
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
},{"./minimart.js":7}],6:[function(_dereq_,module,exports){
|
2014-07-24 00:21:51 +00:00
|
|
|
module.exports = _dereq_("./minimart.js");
|
|
|
|
|
|
|
|
module.exports.DOM = _dereq_("./dom-driver.js");
|
|
|
|
module.exports.JQuery = _dereq_("./jquery-driver.js");
|
|
|
|
module.exports.RoutingTableWidget = _dereq_("./routing-table-widget.js");
|
|
|
|
module.exports.WebSocket = _dereq_("./websocket-driver.js");
|
2014-07-24 23:22:22 +00:00
|
|
|
module.exports.Reflect = _dereq_("./reflect.js");
|
2014-07-24 00:21:51 +00:00
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
module.exports.Ground = _dereq_("./ground.js").Ground;
|
|
|
|
module.exports.Actor = _dereq_("./actor.js").Actor;
|
2014-07-24 00:21:51 +00:00
|
|
|
module.exports.Spy = _dereq_("./spy.js").Spy;
|
|
|
|
module.exports.WakeDetector = _dereq_("./wake-detector.js").WakeDetector;
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
var Worker = _dereq_("./worker.js");
|
|
|
|
module.exports.Worker = Worker.Worker;
|
|
|
|
module.exports.WorkerGround = Worker.WorkerGround;
|
|
|
|
|
|
|
|
},{"./actor.js":1,"./dom-driver.js":3,"./ground.js":4,"./jquery-driver.js":5,"./minimart.js":7,"./reflect.js":8,"./routing-table-widget.js":10,"./spy.js":11,"./wake-detector.js":13,"./websocket-driver.js":14,"./worker.js":15}],7:[function(_dereq_,module,exports){
|
2014-07-24 00:21:51 +00:00
|
|
|
var Route = _dereq_("./route.js");
|
2014-08-02 16:00:28 +00:00
|
|
|
var Util = _dereq_("./util.js");
|
2014-07-24 00:21:51 +00:00
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// TODO: trigger-guards as per minimart
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Events and Actions */
|
|
|
|
|
|
|
|
var __ = Route.__;
|
|
|
|
var _$ = Route._$;
|
|
|
|
|
|
|
|
function sub(pattern, metaLevel, level) {
|
|
|
|
return Route.simpleGestalt(false, pattern, metaLevel, level);
|
|
|
|
}
|
|
|
|
|
|
|
|
function pub(pattern, metaLevel, level) {
|
|
|
|
return Route.simpleGestalt(true, pattern, metaLevel, level);
|
|
|
|
}
|
|
|
|
|
|
|
|
function spawn(behavior, initialGestalts) {
|
|
|
|
return { type: "spawn",
|
|
|
|
behavior: behavior,
|
|
|
|
initialGestalt: Route.gestaltUnion(initialGestalts || []) };
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateRoutes(gestalts) {
|
|
|
|
return { type: "routes", gestalt: Route.gestaltUnion(gestalts) };
|
|
|
|
}
|
|
|
|
|
|
|
|
function pendingRoutingUpdate(aggregate, affectedSubgestalt, knownTarget) {
|
|
|
|
return { type: "pendingRoutingUpdate",
|
|
|
|
aggregate: aggregate,
|
|
|
|
affectedSubgestalt: affectedSubgestalt,
|
|
|
|
knownTarget: knownTarget };
|
|
|
|
}
|
|
|
|
|
|
|
|
function sendMessage(m, metaLevel, isFeedback) {
|
|
|
|
return { type: "message",
|
|
|
|
metaLevel: (metaLevel === undefined) ? 0 : metaLevel,
|
|
|
|
message: m,
|
|
|
|
isFeedback: (isFeedback === undefined) ? false : isFeedback };
|
|
|
|
}
|
|
|
|
|
|
|
|
function shutdownWorld() {
|
|
|
|
return { type: "shutdownWorld" };
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Configurations */
|
|
|
|
|
|
|
|
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.processTable = {};
|
|
|
|
this.tombstones = {};
|
|
|
|
this.downwardGestalt = Route.emptyGestalt;
|
|
|
|
this.processActions = [];
|
|
|
|
this.asChild(-1, bootFn, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Class state / methods */
|
|
|
|
|
|
|
|
World.nextPid = 0;
|
|
|
|
|
|
|
|
World.stack = [];
|
|
|
|
|
|
|
|
World.current = function () {
|
|
|
|
return World.stack[World.stack.length - 1][0];
|
|
|
|
};
|
|
|
|
|
|
|
|
World.activePid = function () {
|
|
|
|
return World.stack[World.stack.length - 1][1];
|
|
|
|
};
|
|
|
|
|
|
|
|
World.send = function (m, metaLevel, isFeedback) {
|
|
|
|
World.current().enqueueAction(World.activePid(), sendMessage(m, metaLevel, isFeedback));
|
|
|
|
};
|
|
|
|
|
|
|
|
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.exit = function (exn) {
|
|
|
|
World.current().kill(World.activePid(), exn);
|
|
|
|
};
|
|
|
|
|
|
|
|
World.shutdownWorld = function () {
|
|
|
|
World.current().enqueueAction(World.activePid(), shutdownWorld());
|
|
|
|
};
|
|
|
|
|
|
|
|
World.withWorldStack = function (stack, f) {
|
|
|
|
var oldStack = World.stack;
|
|
|
|
World.stack = stack;
|
|
|
|
var result = null;
|
|
|
|
try {
|
|
|
|
result = f();
|
|
|
|
} catch (e) {
|
|
|
|
World.stack = oldStack;
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
World.stack = oldStack;
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
World.wrap = function (f) {
|
|
|
|
var savedStack = World.stack.slice();
|
|
|
|
return function () {
|
|
|
|
var actuals = arguments;
|
|
|
|
return World.withWorldStack(savedStack, function () {
|
|
|
|
var result = World.current().asChild(World.activePid(), function () {
|
|
|
|
return f.apply(null, actuals);
|
|
|
|
});
|
|
|
|
for (var i = World.stack.length - 1; i >= 0; i--) {
|
|
|
|
World.stack[i][0].markPidRunnable(World.stack[i][1]);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Instance methods */
|
|
|
|
|
|
|
|
World.prototype.enqueueAction = function (pid, action) {
|
|
|
|
this.processActions.push([pid, action]);
|
|
|
|
};
|
|
|
|
|
|
|
|
// The code is written to maintain the runnablePids set carefully, to
|
|
|
|
// ensure we can locally decide whether we're inert or not without
|
|
|
|
// having to search the whole deep process tree.
|
|
|
|
World.prototype.isInert = function () {
|
|
|
|
return this.eventQueue.length === 0
|
|
|
|
&& this.processActions.length === 0
|
|
|
|
&& Route.is_emptySet(this.runnablePids);
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.markPidRunnable = function (pid) {
|
|
|
|
this.runnablePids[pid] = [pid];
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.step = function () {
|
|
|
|
this.dispatchEvents();
|
|
|
|
this.performActions();
|
|
|
|
this.stepChildren();
|
|
|
|
return this.alive && !this.isInert();
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.asChild = function (pid, f, omitLivenessCheck) {
|
|
|
|
if (!(pid in this.processTable) && !omitLivenessCheck) {
|
|
|
|
console.warn("World.asChild eliding invocation of dead process", pid);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
World.stack.push([this, pid]);
|
|
|
|
var result = null;
|
|
|
|
try {
|
|
|
|
result = f();
|
|
|
|
} catch (e) {
|
|
|
|
this.kill(pid, e);
|
|
|
|
}
|
|
|
|
if (World.stack.pop()[0] !== this) {
|
|
|
|
throw new Error("Internal error: World stack imbalance");
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.kill = function (pid, exn) {
|
|
|
|
if (exn && exn.stack) {
|
|
|
|
console.log("Process exited", pid, exn, exn.stack);
|
|
|
|
} else {
|
|
|
|
console.log("Process exited", pid, exn);
|
|
|
|
}
|
|
|
|
var p = this.processTable[pid];
|
|
|
|
delete this.processTable[pid];
|
|
|
|
if (p) {
|
2014-08-25 23:12:03 +00:00
|
|
|
if (p.behavior.trapexit) {
|
|
|
|
this.asChild(pid, function () { return p.behavior.trapexit(exn); }, true);
|
|
|
|
}
|
2014-07-24 00:21:51 +00:00
|
|
|
if (exn) {
|
|
|
|
p.exitReason = exn;
|
|
|
|
this.tombstones[pid] = p;
|
|
|
|
}
|
|
|
|
this.applyAndIssueRoutingUpdate(p.gestalt, Route.emptyGestalt);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.stepChildren = function () {
|
|
|
|
var pids = this.runnablePids;
|
|
|
|
this.runnablePids = {};
|
|
|
|
for (var pid in pids) {
|
|
|
|
var p = this.processTable[pid];
|
|
|
|
if (p && p.behavior.step /* exists, haven't called it yet */) {
|
|
|
|
var childBusy = this.asChild(pid | 0, function () { return p.behavior.step() });
|
|
|
|
if (childBusy) this.markPidRunnable(pid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.performActions = function () {
|
|
|
|
var queue = this.processActions;
|
|
|
|
this.processActions = [];
|
|
|
|
var item;
|
|
|
|
while ((item = queue.shift()) && this.alive) {
|
|
|
|
this.performAction(item[0], item[1]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.dispatchEvents = function () {
|
|
|
|
var queue = this.eventQueue;
|
|
|
|
this.eventQueue = [];
|
|
|
|
var item;
|
|
|
|
while ((item = queue.shift())) {
|
|
|
|
this.dispatchEvent(item);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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() });
|
|
|
|
this.markPidRunnable(pid);
|
|
|
|
}
|
|
|
|
this.applyAndIssueRoutingUpdate(Route.emptyGestalt, newGestalt, pid);
|
|
|
|
break;
|
|
|
|
case "routes":
|
|
|
|
if (pid in this.processTable) {
|
|
|
|
// it may not be: this might be the routing update from a
|
|
|
|
// kill of the process
|
|
|
|
var oldGestalt = this.processTable[pid].gestalt;
|
|
|
|
var newGestalt = action.gestalt.label(pid|0);
|
|
|
|
// ^ pid|0: convert pid from string (table key!) to integer
|
|
|
|
this.processTable[pid].gestalt = newGestalt;
|
|
|
|
this.applyAndIssueRoutingUpdate(oldGestalt, newGestalt, pid);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "message":
|
|
|
|
if (action.metaLevel === 0) {
|
|
|
|
this.eventQueue.push(action);
|
|
|
|
} else {
|
|
|
|
World.send(action.message, action.metaLevel - 1, action.isFeedback);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "shutdownWorld":
|
|
|
|
this.alive = false; // force us to stop doing things immediately
|
|
|
|
World.exit();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
var exn = new Error("Action type " + action.type + " not understood");
|
|
|
|
exn.action = action;
|
|
|
|
throw exn;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.updateFullGestalt = function () {
|
|
|
|
this.fullGestalt = this.partialGestalt.union(this.downwardGestalt);
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.issueLocalRoutingUpdate = function (affectedSubgestalt, knownTarget) {
|
|
|
|
this.eventQueue.push(pendingRoutingUpdate(this.fullGestalt,
|
|
|
|
affectedSubgestalt,
|
|
|
|
knownTarget));
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.applyAndIssueRoutingUpdate = function (oldg, newg, knownTarget) {
|
|
|
|
knownTarget = typeof knownTarget === 'undefined' ? null : knownTarget;
|
|
|
|
this.partialGestalt = this.partialGestalt.erasePath(oldg).union(newg);
|
|
|
|
this.updateFullGestalt();
|
|
|
|
this.issueLocalRoutingUpdate(oldg.union(newg), knownTarget);
|
|
|
|
World.updateRoutes([this.partialGestalt.drop()]);
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.dispatchEvent = function (e) {
|
|
|
|
switch (e.type) {
|
|
|
|
case "pendingRoutingUpdate":
|
|
|
|
var pids = e.affectedSubgestalt.match(e.aggregate);
|
|
|
|
if (e.knownTarget !== null) pids.unshift(e.knownTarget);
|
|
|
|
for (var i = 0; i < pids.length; i++) {
|
|
|
|
var pid = pids[i];
|
|
|
|
if (pid === "out") console.warn("Would have delivered a routing update to environment");
|
|
|
|
var p = this.processTable[pid];
|
|
|
|
if (p) {
|
|
|
|
var g = e.aggregate.filter(p.gestalt);
|
|
|
|
this.asChild(pid, function () { p.behavior.handleEvent(updateRoutes([g])) });
|
|
|
|
this.markPidRunnable(pid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "message":
|
|
|
|
var pids = this.partialGestalt.matchValue(e.message, e.metaLevel, e.isFeedback);
|
|
|
|
for (var i = 0; i < pids.length; i++) {
|
|
|
|
var pid = pids[i];
|
|
|
|
var p = this.processTable[pid];
|
|
|
|
this.asChild(pid, function () { p.behavior.handleEvent(e) });
|
|
|
|
this.markPidRunnable(pid);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
var exn = new Error("Event type " + e.type + " not dispatchable");
|
|
|
|
exn.event = e;
|
|
|
|
throw exn;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.handleEvent = function (e) {
|
|
|
|
switch (e.type) {
|
|
|
|
case "routes":
|
|
|
|
var oldDownward = this.downwardGestalt;
|
|
|
|
this.downwardGestalt = e.gestalt.label("out").lift();
|
|
|
|
this.updateFullGestalt();
|
|
|
|
this.issueLocalRoutingUpdate(oldDownward.union(this.downwardGestalt), null);
|
|
|
|
break;
|
|
|
|
case "message":
|
|
|
|
this.eventQueue.push(sendMessage(e.message, e.metaLevel + 1, e.isFeedback));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
var exn = new Error("Event type " + e.type + " not understood");
|
|
|
|
exn.event = e;
|
|
|
|
throw exn;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Debugging, management, and monitoring */
|
|
|
|
|
|
|
|
World.prototype.processTree = function () {
|
|
|
|
var kids = [];
|
|
|
|
for (var pid in this.processTable) {
|
|
|
|
var p = this.processTable[pid];
|
|
|
|
if (p.behavior instanceof World) {
|
|
|
|
kids.push([pid, p.behavior.processTree()]);
|
|
|
|
} else {
|
|
|
|
kids.push([pid, p]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (var pid in this.tombstones) {
|
|
|
|
kids.push([pid, this.tombstones[pid]]);
|
|
|
|
}
|
2014-08-23 00:12:50 +00:00
|
|
|
kids.sort(function (a, b) { return a[0] - b[0] });
|
2014-07-24 00:21:51 +00:00
|
|
|
return kids;
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.textProcessTree = function (ownPid) {
|
|
|
|
var lines = [];
|
|
|
|
|
|
|
|
function dumpProcess(prefix, pid, p) {
|
2014-07-24 23:22:22 +00:00
|
|
|
if (Array.isArray(p)) {
|
2014-07-24 00:21:51 +00:00
|
|
|
lines.push(prefix + '--+ ' + pid);
|
|
|
|
for (var i = 0; i < p.length; i++) {
|
|
|
|
dumpProcess(prefix + ' |', p[i][0], p[i][1]);
|
|
|
|
}
|
|
|
|
lines.push(prefix);
|
|
|
|
} else {
|
|
|
|
var label = p.behavior.name || p.behavior.constructor.name || '';
|
|
|
|
var tombstoneString = p.exitReason ? ' (EXITED: ' + p.exitReason + ') ' : '';
|
2014-08-22 23:57:52 +00:00
|
|
|
var stringifiedState;
|
|
|
|
try {
|
2014-08-25 20:02:04 +00:00
|
|
|
var rawState = p.behavior.debugState ? p.behavior.debugState() : p.behavior;
|
|
|
|
stringifiedState = JSON.stringify(rawState, function (k, v) {
|
2014-08-22 23:57:52 +00:00
|
|
|
return (k === 'name') ? undefined : v;
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
stringifiedState = "(cannot convert process state to JSON)";
|
|
|
|
}
|
|
|
|
lines.push(prefix + '-- ' + pid + ': ' + label + tombstoneString + stringifiedState);
|
2014-07-24 00:21:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dumpProcess('', ownPid || '', this.processTree());
|
|
|
|
return lines.join('\n');
|
|
|
|
};
|
|
|
|
|
|
|
|
World.prototype.clearTombstones = function () {
|
|
|
|
this.tombstones = {};
|
|
|
|
for (var pid in this.processTable) {
|
|
|
|
var p = this.processTable[pid];
|
|
|
|
if (p.behavior instanceof World) {
|
|
|
|
p.behavior.clearTombstones();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Utilities: matching demand for some service */
|
|
|
|
|
|
|
|
function DemandMatcher(projection, metaLevel, options) {
|
2014-08-02 16:00:28 +00:00
|
|
|
options = Util.extend({
|
2014-07-24 00:21:51 +00:00
|
|
|
demandLevel: 0,
|
|
|
|
supplyLevel: 0,
|
2014-08-25 20:02:04 +00:00
|
|
|
demandSideIsSubscription: false,
|
|
|
|
supplyProjection: projection
|
2014-07-24 00:21:51 +00:00
|
|
|
}, options);
|
2014-08-25 20:02:04 +00:00
|
|
|
this.demandPattern = Route.projectionToPattern(projection);
|
|
|
|
this.supplyPattern = Route.projectionToPattern(options.supplyProjection);
|
|
|
|
this.demandProjectionSpec = Route.compileProjection(projection);
|
|
|
|
this.supplyProjectionSpec = Route.compileProjection(options.supplyProjection);
|
2014-07-24 00:21:51 +00:00
|
|
|
this.metaLevel = metaLevel | 0;
|
|
|
|
this.demandLevel = options.demandLevel;
|
|
|
|
this.supplyLevel = options.supplyLevel;
|
|
|
|
this.demandSideIsSubscription = options.demandSideIsSubscription;
|
|
|
|
this.onDemandIncrease = function (captures) {
|
|
|
|
console.error("Unhandled increase in demand for route", captures);
|
|
|
|
};
|
|
|
|
this.onSupplyDecrease = function (captures) {
|
|
|
|
console.error("Unhandled decrease in supply for route", captures);
|
|
|
|
};
|
|
|
|
this.currentDemand = {};
|
|
|
|
this.currentSupply = {};
|
|
|
|
}
|
|
|
|
|
2014-08-25 20:02:04 +00:00
|
|
|
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
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2014-07-24 00:21:51 +00:00
|
|
|
DemandMatcher.prototype.boot = function () {
|
|
|
|
var observerLevel = 1 + Math.max(this.demandLevel, this.supplyLevel);
|
2014-08-25 20:02:04 +00:00
|
|
|
World.updateRoutes([sub(this.demandPattern, this.metaLevel, observerLevel),
|
|
|
|
pub(this.supplyPattern, this.metaLevel, observerLevel)]);
|
2014-07-24 00:21:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
DemandMatcher.prototype.handleEvent = function (e) {
|
|
|
|
if (e.type === "routes") {
|
|
|
|
this.handleGestalt(e.gestalt);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
DemandMatcher.prototype.handleGestalt = function (gestalt) {
|
2014-08-25 20:02:04 +00:00
|
|
|
var newDemandMatcher = gestalt.project(this.demandProjectionSpec,
|
2014-07-24 00:21:51 +00:00
|
|
|
!this.demandSideIsSubscription,
|
|
|
|
this.metaLevel,
|
|
|
|
this.demandLevel);
|
2014-08-25 20:02:04 +00:00
|
|
|
var newSupplyMatcher = gestalt.project(this.supplyProjectionSpec,
|
2014-07-24 00:21:51 +00:00
|
|
|
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);
|
|
|
|
this.currentDemand = newDemand;
|
|
|
|
this.currentSupply = newSupply;
|
|
|
|
for (var k in demandIncr) this.onDemandIncrease(demandIncr[k]);
|
|
|
|
for (var k in supplyDecr) this.onSupplyDecrease(supplyDecr[k]);
|
|
|
|
};
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Utilities: deduplicator */
|
|
|
|
|
|
|
|
function Deduplicator(ttl_ms) {
|
|
|
|
this.ttl_ms = ttl_ms || 10000;
|
|
|
|
this.queue = [];
|
|
|
|
this.map = {};
|
|
|
|
this.timerId = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
Deduplicator.prototype.accept = function (m) {
|
|
|
|
var s = JSON.stringify(m);
|
|
|
|
if (s in this.map) return false;
|
|
|
|
var entry = [(+new Date()) + this.ttl_ms, s, m];
|
|
|
|
this.map[s] = entry;
|
|
|
|
this.queue.push(entry);
|
|
|
|
|
|
|
|
if (this.timerId === null) {
|
|
|
|
var self = this;
|
|
|
|
this.timerId = setInterval(function () { self.expireMessages(); },
|
|
|
|
this.ttl_ms > 1000 ? 1000 : this.ttl_ms);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
Deduplicator.prototype.expireMessages = function () {
|
|
|
|
var now = +new Date();
|
|
|
|
while (this.queue.length > 0 && this.queue[0][0] <= now) {
|
|
|
|
var entry = this.queue.shift();
|
|
|
|
delete this.map[entry[1]];
|
|
|
|
}
|
|
|
|
if (this.queue.length === 0) {
|
|
|
|
clearInterval(this.timerId);
|
|
|
|
this.timerId = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
module.exports.__ = __;
|
|
|
|
module.exports._$ = _$;
|
|
|
|
|
|
|
|
module.exports.sub = sub;
|
|
|
|
module.exports.pub = pub;
|
|
|
|
module.exports.spawn = spawn;
|
|
|
|
module.exports.updateRoutes = updateRoutes;
|
|
|
|
module.exports.sendMessage = sendMessage;
|
|
|
|
module.exports.shutdownWorld = shutdownWorld;
|
|
|
|
|
|
|
|
module.exports.World = World;
|
|
|
|
module.exports.DemandMatcher = DemandMatcher;
|
|
|
|
module.exports.Deduplicator = Deduplicator;
|
|
|
|
module.exports.Route = Route;
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
},{"./route.js":9,"./util.js":12}],8:[function(_dereq_,module,exports){
|
2014-07-24 23:22:22 +00:00
|
|
|
// 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++) {
|
2014-07-25 23:20:27 +00:00
|
|
|
var trimmed = args[i].trim();
|
|
|
|
if (trimmed) { result.push(trimmed); }
|
2014-07-24 23:22:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.formalParameters = formalParameters;
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
},{}],9:[function(_dereq_,module,exports){
|
2014-07-24 00:21:51 +00:00
|
|
|
var __ = "__"; /* wildcard marker */
|
|
|
|
|
|
|
|
var SOA = "__["; // start of array
|
|
|
|
var EOA = "__]"; // end of array
|
|
|
|
|
|
|
|
function die(message) {
|
|
|
|
throw new Error(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
function $Embedded(matcher) {
|
|
|
|
this.matcher = matcher;
|
|
|
|
}
|
|
|
|
|
|
|
|
function embeddedMatcher(matcher) {
|
|
|
|
return new $Embedded(matcher);
|
|
|
|
}
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
// The name argument should be a string or null; it defaults to null.
|
2014-07-24 00:21:51 +00:00
|
|
|
// The pattern argument defaults to wildcard, __.
|
2014-07-24 23:22:22 +00:00
|
|
|
function $Capture(name, pattern) {
|
|
|
|
this.name = name || null;
|
2014-07-24 00:21:51 +00:00
|
|
|
this.pattern = (typeof pattern === 'undefined' ? __ : pattern);
|
|
|
|
}
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
// Abbreviation: _$(...) <==> new $Capture(...)
|
|
|
|
function _$(name, pattern) {
|
|
|
|
return new $Capture(name, pattern);
|
2014-07-24 00:21:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function isCapture(x) { return x instanceof $Capture || x === _$; }
|
2014-07-24 23:22:22 +00:00
|
|
|
function captureName(x) { return x instanceof $Capture ? x.name : null; }
|
2014-07-24 00:21:51 +00:00
|
|
|
function capturePattern(x) { return x instanceof $Capture ? x.pattern : __; }
|
|
|
|
|
|
|
|
var SOC = "__{{"; // start of capture
|
|
|
|
var EOC = "__}}"; // end of capture
|
|
|
|
|
|
|
|
function $Success(value) {
|
|
|
|
this.value = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
function $WildcardSequence(matcher) {
|
|
|
|
this.matcher = matcher;
|
|
|
|
}
|
|
|
|
|
|
|
|
function $Dict() {
|
|
|
|
this.length = 0;
|
|
|
|
this.entries = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
$Dict.prototype.get = function (key) {
|
|
|
|
return this.entries[key] || emptyMatcher;
|
|
|
|
};
|
|
|
|
|
|
|
|
$Dict.prototype.set = function (key, val) {
|
|
|
|
if (!(key in this.entries)) this.length++;
|
|
|
|
this.entries[key] = val;
|
|
|
|
};
|
|
|
|
|
|
|
|
$Dict.prototype.clear = function (key) {
|
|
|
|
if (key in this.entries) this.length--;
|
|
|
|
delete this.entries[key];
|
|
|
|
};
|
|
|
|
|
|
|
|
$Dict.prototype.isEmpty = function () {
|
|
|
|
return this.length === 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
$Dict.prototype.copy = function () {
|
|
|
|
var other = new $Dict();
|
|
|
|
other.length = this.length;
|
|
|
|
for (var key in this.entries) {
|
|
|
|
if (this.entries.hasOwnProperty(key)) {
|
|
|
|
other.entries[key] = this.entries[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return other;
|
|
|
|
};
|
|
|
|
|
|
|
|
$Dict.prototype.emptyGuard = function () {
|
|
|
|
if (this.isEmpty()) return emptyMatcher;
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
$Dict.prototype.has = function (key) {
|
|
|
|
return key in this.entries;
|
|
|
|
};
|
|
|
|
|
|
|
|
$Dict.prototype.sortedKeys = function () {
|
|
|
|
var ks = [];
|
|
|
|
for (var k in this.entries) ks.push(k);
|
|
|
|
ks.sort();
|
|
|
|
return ks;
|
|
|
|
}
|
|
|
|
|
|
|
|
function is_emptyMatcher(m) {
|
|
|
|
return (m === emptyMatcher);
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Constructors
|
|
|
|
|
|
|
|
var emptyMatcher = null;
|
|
|
|
|
|
|
|
function rsuccess(v) {
|
|
|
|
return (v === emptyMatcher) ? emptyMatcher : new $Success(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
function rseq(e, r) {
|
|
|
|
if (r === emptyMatcher) return emptyMatcher;
|
|
|
|
var s = new $Dict();
|
|
|
|
s.set(e, r);
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
function rwild(r) {
|
|
|
|
return rseq(__, r);
|
|
|
|
}
|
|
|
|
|
|
|
|
function rwildseq(r) {
|
|
|
|
return (r === emptyMatcher) ? emptyMatcher : new $WildcardSequence(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
function compilePattern(v, p) {
|
|
|
|
if (!p) die("compilePattern: missing pattern");
|
|
|
|
return walk(p, rseq(EOA, rsuccess(v)));
|
|
|
|
|
|
|
|
function walk(p, acc) {
|
|
|
|
if (p === __) return rwild(acc);
|
|
|
|
|
|
|
|
if (Array.isArray(p)) {
|
|
|
|
acc = rseq(EOA, acc);
|
|
|
|
for (var i = p.length - 1; i >= 0; i--) {
|
|
|
|
acc = walk(p[i], acc);
|
|
|
|
}
|
|
|
|
return rseq(SOA, acc);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p instanceof $Embedded) {
|
|
|
|
return appendMatcher(p.matcher, function (v) { return acc; });
|
|
|
|
} else {
|
|
|
|
return rseq(JSON.stringify(p), acc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
function matchPattern(v, p) {
|
|
|
|
var captureCount = 0;
|
|
|
|
var result = {};
|
|
|
|
try {
|
|
|
|
walk(v, p);
|
|
|
|
} catch (e) {
|
|
|
|
if (e.matchPatternFailed) return null;
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
result.length = captureCount;
|
|
|
|
return result;
|
|
|
|
|
|
|
|
function walk(v, p) {
|
|
|
|
if (p === v) return;
|
|
|
|
|
|
|
|
if (p === __) return;
|
|
|
|
|
|
|
|
if (Array.isArray(p) && Array.isArray(v) && p.length === v.length) {
|
|
|
|
for (var i = 0; i < p.length; i++) {
|
|
|
|
walk(v[i], p[i]);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isCapture(p)) {
|
|
|
|
var thisCapture = captureCount++;
|
|
|
|
walk(v, capturePattern(p));
|
|
|
|
result[captureName(p) || ('$' + thisCapture)] = v;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p instanceof $Embedded) {
|
|
|
|
die("$Embedded patterns not supported in matchPattern()");
|
|
|
|
}
|
|
|
|
|
|
|
|
throw {matchPatternFailed: true};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-24 00:21:51 +00:00
|
|
|
function shallowCopyArray(s) {
|
|
|
|
return s.slice();
|
|
|
|
}
|
|
|
|
|
|
|
|
function rupdateInplace(r, key, k) {
|
|
|
|
if (is_emptyMatcher(k)) {
|
|
|
|
r.clear(key);
|
|
|
|
} else {
|
|
|
|
r.set(key, k);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function matcherEquals(a, b) {
|
|
|
|
if (a === null) {
|
|
|
|
return (b === null);
|
|
|
|
}
|
|
|
|
if (b === null) return false;
|
|
|
|
|
|
|
|
if (a instanceof $WildcardSequence) {
|
|
|
|
if (!(b instanceof $WildcardSequence)) return false;
|
|
|
|
a = a.matcher;
|
|
|
|
b = b.matcher;
|
|
|
|
} else if (b instanceof $WildcardSequence) return false;
|
|
|
|
|
|
|
|
if (a instanceof $Success) {
|
|
|
|
if (!(b instanceof $Success)) return false;
|
|
|
|
return valuesEqual(a.value, b.value);
|
|
|
|
}
|
|
|
|
if (b instanceof $Success) return false;
|
|
|
|
|
|
|
|
for (var key in a.entries) {
|
|
|
|
if (!b.has(key)) return false;
|
|
|
|
if (!matcherEquals(a.entries[key], b.entries[key])) return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function is_keyOpen(k) {
|
|
|
|
return k === SOA;
|
|
|
|
}
|
|
|
|
|
|
|
|
function is_keyClose(k) {
|
|
|
|
return k === EOA;
|
|
|
|
}
|
|
|
|
|
|
|
|
function is_keyNormal(k) {
|
|
|
|
return !(is_keyOpen(k) || is_keyClose(k));
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Enough of sets to get by with
|
|
|
|
|
|
|
|
function arrayToSet(xs) {
|
|
|
|
var s = {};
|
|
|
|
for (var i = 0; i < xs.length; i++) {
|
|
|
|
s[JSON.stringify(xs[i])] = xs[i];
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
function setToArray(s) {
|
|
|
|
var r = [];
|
|
|
|
for (var k in s) r.push(s[k]);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
function setUnion(s1, s2) {
|
|
|
|
var s = {};
|
|
|
|
setUnionInplace(s, s1);
|
|
|
|
setUnionInplace(s, s2);
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
function is_emptySet(s) {
|
|
|
|
for (var k in s) {
|
|
|
|
if (s.hasOwnProperty(k))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function setSubtract(s1, s2) {
|
|
|
|
var s = {};
|
|
|
|
for (var key in s1) {
|
|
|
|
if (s1.hasOwnProperty(key) && !s2.hasOwnProperty(key)) {
|
|
|
|
s[key] = s1[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
function setIntersect(s1, s2) {
|
|
|
|
var s = {};
|
|
|
|
for (var key in s1) {
|
|
|
|
if (s1.hasOwnProperty(key) && s2.hasOwnProperty(key)) {
|
|
|
|
s[key] = s1[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
function setUnionInplace(acc, s) {
|
|
|
|
for (var key in s) {
|
|
|
|
if (s.hasOwnProperty(key)) {
|
|
|
|
acc[key] = s[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setEqual(s1, s2) {
|
|
|
|
for (var key in s1) {
|
|
|
|
if (s1.hasOwnProperty(key)) {
|
|
|
|
if (s1[key] !== s2[key]) return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (var key in s2) {
|
|
|
|
if (s2.hasOwnProperty(key)) {
|
|
|
|
if (s1[key] !== s2[key]) return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
var unionSuccesses = function (v1, v2) {
|
|
|
|
if (v1 === true) return v2;
|
|
|
|
if (v2 === true) return v1;
|
|
|
|
return setUnion(v1, v2);
|
|
|
|
};
|
|
|
|
|
|
|
|
var intersectSuccesses = function (v1, v2) {
|
|
|
|
return v1;
|
|
|
|
};
|
|
|
|
|
|
|
|
var erasePathSuccesses = function (v1, v2) {
|
|
|
|
var r = setSubtract(v1, v2);
|
|
|
|
if (is_emptySet(r)) return null;
|
|
|
|
return r;
|
|
|
|
};
|
|
|
|
|
|
|
|
var matchMatcherSuccesses = function (v1, v2, acc) {
|
|
|
|
setUnionInplace(acc, v2);
|
|
|
|
};
|
|
|
|
|
|
|
|
var projectSuccess = function (v) {
|
|
|
|
return v;
|
|
|
|
};
|
|
|
|
|
|
|
|
var valuesEqual = function (a, b) {
|
|
|
|
return setEqual(a, b);
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
function expandWildseq(r) {
|
|
|
|
return union(rwild(rwildseq(r)), rseq(EOA, r));
|
|
|
|
}
|
|
|
|
|
|
|
|
function union(o1, o2) {
|
|
|
|
return merge(o1, o2);
|
|
|
|
|
|
|
|
function merge(o1, o2) {
|
|
|
|
if (is_emptyMatcher(o1)) return o2;
|
|
|
|
if (is_emptyMatcher(o2)) return o1;
|
|
|
|
return walk(o1, o2);
|
|
|
|
}
|
|
|
|
|
|
|
|
function walk(r1, r2) {
|
|
|
|
if (r1 instanceof $WildcardSequence) {
|
|
|
|
if (r2 instanceof $WildcardSequence) {
|
|
|
|
return rwildseq(walk(r1.matcher, r2.matcher));
|
|
|
|
}
|
|
|
|
r1 = expandWildseq(r1.matcher);
|
|
|
|
} else if (r2 instanceof $WildcardSequence) {
|
|
|
|
r2 = expandWildseq(r2.matcher);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r1 instanceof $Success && r2 instanceof $Success) {
|
|
|
|
return rsuccess(unionSuccesses(r1.value, r2.value));
|
|
|
|
}
|
|
|
|
|
|
|
|
var w = merge(r1.get(__), r2.get(__));
|
|
|
|
if (is_emptyMatcher(w)) {
|
|
|
|
var smaller = r1.length < r2.length ? r1 : r2;
|
|
|
|
var larger = r1.length < r2.length ? r2 : r1;
|
|
|
|
var target = larger.copy();
|
|
|
|
for (var key in smaller.entries) {
|
|
|
|
var k = merge(smaller.get(key), larger.get(key));
|
|
|
|
rupdateInplace(target, key, k);
|
|
|
|
}
|
|
|
|
return target.emptyGuard();
|
|
|
|
} else {
|
|
|
|
function examineKey(rA, key, rB) {
|
|
|
|
if ((key !== __) && !target.has(key)) {
|
|
|
|
var k = merge(rA.get(key), rB.get(key));
|
|
|
|
if (is_keyOpen(key)) {
|
|
|
|
rupdateInplace(target, key, merge(rwildseq(w), k));
|
|
|
|
} else if (is_keyClose(key)) {
|
|
|
|
if (w instanceof $WildcardSequence) {
|
|
|
|
rupdateInplace(target, key, merge(w.matcher, k));
|
|
|
|
} else {
|
|
|
|
rupdateInplace(target, key, k);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rupdateInplace(target, key, merge(w, k));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var target = rwild(w).copy();
|
|
|
|
for (var key in r1.entries) { examineKey(r1, key, r2); }
|
|
|
|
for (var key in r2.entries) { examineKey(r2, key, r1); }
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function unionN() {
|
|
|
|
var acc = emptyMatcher;
|
|
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
|
|
acc = union(acc, arguments[i]);
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
|
|
|
|
function intersect(o1, o2) {
|
|
|
|
if (is_emptyMatcher(o1)) return emptyMatcher;
|
|
|
|
if (is_emptyMatcher(o2)) return emptyMatcher;
|
|
|
|
return walk(o1, o2);
|
|
|
|
|
|
|
|
function walkFlipped(r2, r1) { return walk(r1, r2); }
|
|
|
|
|
|
|
|
function walk(r1, r2) {
|
|
|
|
// INVARIANT: r1 is a part of the original o1, and
|
|
|
|
// likewise for r2. This is so that the first arg to
|
|
|
|
// intersectSuccesses always comes from r1, and the second
|
|
|
|
// from r2.
|
|
|
|
if (is_emptyMatcher(r1)) return emptyMatcher;
|
|
|
|
if (is_emptyMatcher(r2)) return emptyMatcher;
|
|
|
|
|
|
|
|
if (r1 instanceof $WildcardSequence) {
|
|
|
|
if (r2 instanceof $WildcardSequence) {
|
|
|
|
return rwildseq(walk(r1.matcher, r2.matcher));
|
|
|
|
}
|
|
|
|
r1 = expandWildseq(r1.matcher);
|
|
|
|
} else if (r2 instanceof $WildcardSequence) {
|
|
|
|
r2 = expandWildseq(r2.matcher);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r1 instanceof $Success && r2 instanceof $Success) {
|
|
|
|
return rsuccess(intersectSuccesses(r1.value, r2.value));
|
|
|
|
}
|
|
|
|
|
|
|
|
var w1 = r1.get(__);
|
|
|
|
var w2 = r2.get(__);
|
|
|
|
var w = walk(w1, w2);
|
|
|
|
|
|
|
|
var target = new $Dict();
|
|
|
|
|
|
|
|
function examineKey(key) {
|
|
|
|
if ((key !== __) && !target.has(key)) {
|
|
|
|
var k1 = r1.get(key);
|
|
|
|
var k2 = r2.get(key);
|
|
|
|
if (is_emptyMatcher(k1)) {
|
|
|
|
if (is_emptyMatcher(k2)) {
|
|
|
|
rupdateInplace(target, key, emptyMatcher);
|
|
|
|
} else {
|
|
|
|
rupdateInplace(target, key, walkWild(walk, w1, key, k2));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (is_emptyMatcher(k2)) {
|
|
|
|
rupdateInplace(target, key, walkWild(walkFlipped, w2, key, k1));
|
|
|
|
} else {
|
|
|
|
rupdateInplace(target, key, walk(k1, k2));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_emptyMatcher(w1)) {
|
|
|
|
if (is_emptyMatcher(w2)) {
|
|
|
|
for (var key in (r1.length < r2.length ? r1 : r2).entries) examineKey(key);
|
|
|
|
} else {
|
|
|
|
for (var key in r1.entries) examineKey(key);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (is_emptyMatcher(w2)) {
|
|
|
|
for (var key in r2.entries) examineKey(key);
|
|
|
|
} else {
|
|
|
|
rupdateInplace(target, __, w);
|
|
|
|
for (var key in r1.entries) examineKey(key);
|
|
|
|
for (var key in r2.entries) examineKey(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return target.emptyGuard();
|
|
|
|
}
|
|
|
|
|
|
|
|
function walkWild(walker, w, key, k) {
|
|
|
|
if (is_emptyMatcher(w)) return emptyMatcher;
|
|
|
|
if (is_keyOpen(key)) return walker(rwildseq(w), k);
|
|
|
|
if (is_keyClose(key)) {
|
|
|
|
if (w instanceof $WildcardSequence) return walker(w.matcher, k);
|
|
|
|
return emptyMatcher;
|
|
|
|
}
|
|
|
|
return walker(w, k);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Removes r2's mappings from r1. Assumes r2 has previously been
|
|
|
|
// union'd into r1. The erasePathSuccesses function should return
|
|
|
|
// null to signal "no remaining success values".
|
|
|
|
function erasePath(o1, o2) {
|
|
|
|
return walk(o1, o2);
|
|
|
|
|
|
|
|
function walk(r1, r2) {
|
|
|
|
if (is_emptyMatcher(r1)) {
|
|
|
|
return emptyMatcher;
|
|
|
|
} else {
|
|
|
|
if (is_emptyMatcher(r2)) {
|
|
|
|
return r1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r1 instanceof $WildcardSequence) {
|
|
|
|
if (r2 instanceof $WildcardSequence) {
|
|
|
|
return rwildseq(walk(r1.matcher, r2.matcher));
|
|
|
|
}
|
|
|
|
r1 = expandWildseq(r1.matcher);
|
|
|
|
} else if (r2 instanceof $WildcardSequence) {
|
|
|
|
r2 = expandWildseq(r2.matcher);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r1 instanceof $Success && r2 instanceof $Success) {
|
|
|
|
return rsuccess(erasePathSuccesses(r1.value, r2.value));
|
|
|
|
}
|
|
|
|
|
|
|
|
var w1 = r1.get(__);
|
|
|
|
var w2 = r2.get(__);
|
|
|
|
var w = walk(w1, w2);
|
|
|
|
var target;
|
|
|
|
|
|
|
|
function examineKey(key) {
|
|
|
|
if (key !== __) {
|
|
|
|
var k1 = r1.get(key);
|
|
|
|
var k2 = r2.get(key);
|
|
|
|
var updatedK;
|
|
|
|
if (is_emptyMatcher(k2)) {
|
|
|
|
updatedK = walkWild(key, k1, w2);
|
|
|
|
} else {
|
|
|
|
updatedK = walk(k1, k2);
|
|
|
|
}
|
|
|
|
// Here we ensure a "minimal" remainder in cases
|
|
|
|
// where after an erasure, a particular key's
|
|
|
|
// continuation is the same as the wildcard's
|
|
|
|
// continuation. TODO: the matcherEquals check may
|
|
|
|
// be expensive. If so, how can it be made
|
|
|
|
// cheaper?
|
|
|
|
if (is_keyOpen(key)) {
|
|
|
|
rupdateInplace(target, key,
|
|
|
|
((updatedK instanceof $WildcardSequence) &&
|
|
|
|
matcherEquals(updatedK.matcher, w))
|
|
|
|
? emptyMatcher
|
|
|
|
: updatedK);
|
|
|
|
} else if (is_keyClose(key)) {
|
|
|
|
// We take care of this case later, after the
|
|
|
|
// target is fully constructed/rebuilt.
|
|
|
|
rupdateInplace(target, key, updatedK);
|
|
|
|
} else {
|
|
|
|
rupdateInplace(target, key,
|
|
|
|
(matcherEquals(updatedK, w) ? emptyMatcher : updatedK));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_emptyMatcher(w2)) {
|
|
|
|
target = r1.copy();
|
|
|
|
for (var key in r2.entries) examineKey(key);
|
|
|
|
} else {
|
|
|
|
target = new $Dict();
|
|
|
|
rupdateInplace(target, __, w);
|
|
|
|
for (var key in r1.entries) examineKey(key);
|
|
|
|
for (var key in r2.entries) examineKey(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Here, the target is complete. If it has only two keys,
|
|
|
|
// one wild and one is_keyClose, and wild's continuation
|
|
|
|
// is a $WildcardSequence and the other continuation is
|
|
|
|
// identical to the sequence's continuation, then replace
|
|
|
|
// the whole thing with a nested $WildcardSequence.
|
|
|
|
// (We know w === target.get(__) from before.)
|
|
|
|
//
|
|
|
|
// TODO: I suspect actually this applies even if there are
|
|
|
|
// more than two keys, so long as all their continuations
|
|
|
|
// are identical and there's at least one is_keyClose
|
|
|
|
// alongside a wild.
|
|
|
|
if (target.length === 2) {
|
|
|
|
var finalW = target.get(__);
|
|
|
|
if (finalW instanceof $WildcardSequence) {
|
|
|
|
for (var key in target.entries) {
|
|
|
|
if ((key !== __) && is_keyClose(key)) {
|
|
|
|
var k = target.get(key);
|
|
|
|
if (matcherEquals(k, finalW.matcher)) {
|
|
|
|
return finalW;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return target.emptyGuard();
|
|
|
|
}
|
|
|
|
|
|
|
|
function walkWild(key, k, w) {
|
|
|
|
if (is_emptyMatcher(w)) return k;
|
|
|
|
if (is_keyOpen(key)) return walk(k, rwildseq(w));
|
|
|
|
if (is_keyClose(key)) {
|
|
|
|
if (w instanceof $WildcardSequence) return walk(k, w.matcher);
|
|
|
|
return k;
|
|
|
|
}
|
|
|
|
return walk(k, w);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns null on failed match, otherwise the appropriate success
|
|
|
|
// value contained in the matcher r.
|
|
|
|
function matchValue(r, v) {
|
|
|
|
var failureResult = null;
|
|
|
|
|
|
|
|
var vs = [v];
|
|
|
|
var stack = [[]];
|
|
|
|
|
|
|
|
while (!is_emptyMatcher(r)) {
|
|
|
|
if (r instanceof $WildcardSequence) {
|
|
|
|
if (stack.length === 0) return failureResult;
|
|
|
|
vs = stack.pop();
|
|
|
|
r = r.matcher;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r instanceof $Success) {
|
|
|
|
if (vs.length === 0 && stack.length === 0) return r.value;
|
|
|
|
return failureResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vs.length === 0) {
|
|
|
|
if (stack.length === 0) return failureResult;
|
|
|
|
vs = stack.pop();
|
|
|
|
r = r.get(EOA);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
var v = vs.shift();
|
|
|
|
|
|
|
|
if (typeof v === 'string' && v.substring(0, 2) === '__') {
|
|
|
|
die("Cannot match special string starting with __");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(v)) {
|
|
|
|
if (SOA in r.entries) {
|
|
|
|
r = r.get(SOA);
|
|
|
|
stack.push(vs);
|
|
|
|
vs = shallowCopyArray(v);
|
|
|
|
} else {
|
|
|
|
r = r.get(__);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var key;
|
|
|
|
try {
|
|
|
|
key = JSON.stringify(v);
|
|
|
|
} catch (exn) {
|
|
|
|
// For example, v might be cyclic, as in DOM events.
|
|
|
|
key = null;
|
|
|
|
}
|
|
|
|
if (key in r.entries) {
|
|
|
|
r = r.get(key);
|
|
|
|
} else {
|
|
|
|
r = r.get(__);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return failureResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: better name for this
|
|
|
|
function matchMatcher(o1, o2, seed) {
|
|
|
|
var acc = typeof seed === 'undefined' ? {} : seed; // will be modified in place
|
|
|
|
walk(o1, o2);
|
|
|
|
return acc;
|
|
|
|
|
|
|
|
function walkFlipped(r2, r1) { return walk(r1, r2); }
|
|
|
|
|
|
|
|
function walk(r1, r2) {
|
|
|
|
if (is_emptyMatcher(r1) || is_emptyMatcher(r2)) return;
|
|
|
|
|
|
|
|
if (r1 instanceof $WildcardSequence) {
|
|
|
|
if (r2 instanceof $WildcardSequence) {
|
|
|
|
walk(r1.matcher, r2.matcher);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
r1 = expandWildseq(r1.matcher);
|
|
|
|
} else if (r2 instanceof $WildcardSequence) {
|
|
|
|
r2 = expandWildseq(r2.matcher);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r1 instanceof $Success && r2 instanceof $Success) {
|
|
|
|
matchMatcherSuccesses(r1.value, r2.value, acc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var w1 = r1.get(__);
|
|
|
|
var w2 = r2.get(__);
|
|
|
|
walk(w1, w2);
|
|
|
|
|
|
|
|
function examineKey(key) {
|
|
|
|
if (key !== __) {
|
|
|
|
var k1 = r1.get(key);
|
|
|
|
var k2 = r2.get(key);
|
|
|
|
if (is_emptyMatcher(k1)) {
|
|
|
|
if (is_emptyMatcher(k2)) {
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
walkWild(walk, w1, key, k2);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (is_emptyMatcher(k2)) {
|
|
|
|
walkWild(walkFlipped, w2, key, k1);
|
|
|
|
} else {
|
|
|
|
walk(k1, k2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Optimize similarly to intersect().
|
|
|
|
if (is_emptyMatcher(w1)) {
|
|
|
|
if (is_emptyMatcher(w2)) {
|
|
|
|
for (var key in (r1.length < r2.length ? r1 : r2).entries) examineKey(key);
|
|
|
|
} else {
|
|
|
|
for (var key in r1.entries) examineKey(key);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (is_emptyMatcher(w2)) {
|
|
|
|
for (var key in r2.entries) examineKey(key);
|
|
|
|
} else {
|
|
|
|
for (var key in r1.entries) examineKey(key);
|
|
|
|
for (var key in r2.entries) examineKey(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function walkWild(walker, w, key, k) {
|
|
|
|
if (is_emptyMatcher(w)) return;
|
|
|
|
if (is_keyOpen(key)) {
|
|
|
|
walker(rwildseq(w), k);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (is_keyClose(key)) {
|
|
|
|
if (w instanceof $WildcardSequence) walker(w.matcher, k);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
walker(w, k);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function appendMatcher(m, mTailFn) {
|
|
|
|
return walk(m);
|
|
|
|
|
|
|
|
function walk(m) {
|
|
|
|
if (is_emptyMatcher(m)) return emptyMatcher;
|
|
|
|
if (m instanceof $WildcardSequence) return rwildseq(walk(m.matcher));
|
|
|
|
if (m instanceof $Success) die("Ill-formed matcher");
|
|
|
|
|
|
|
|
var target = new $Dict();
|
|
|
|
for (var key in m.entries) {
|
|
|
|
var k = m.get(key);
|
|
|
|
if (is_keyClose(key) && (k instanceof $Success)) {
|
|
|
|
target = union(target, mTailFn(k.value));
|
|
|
|
} else {
|
|
|
|
rupdateInplace(target, key, walk(k));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return target.emptyGuard();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function relabel(m, f) {
|
|
|
|
return walk(m);
|
|
|
|
|
|
|
|
function walk(m) {
|
|
|
|
if (is_emptyMatcher(m)) return emptyMatcher;
|
|
|
|
if (m instanceof $WildcardSequence) return rwildseq(walk(m.matcher));
|
|
|
|
if (m instanceof $Success) return rsuccess(f(m.value));
|
|
|
|
|
|
|
|
var target = new $Dict();
|
|
|
|
for (var key in m.entries) {
|
|
|
|
rupdateInplace(target, key, walk(m.get(key)));
|
|
|
|
}
|
|
|
|
return target.emptyGuard();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function compileProjection(/* projection, projection, ... */) {
|
2014-07-24 23:22:22 +00:00
|
|
|
var names = [];
|
2014-07-24 00:21:51 +00:00
|
|
|
var acc = [];
|
|
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
|
|
walk(arguments[i]);
|
|
|
|
}
|
|
|
|
acc.push(EOA);
|
2014-07-24 23:22:22 +00:00
|
|
|
return {names: names, spec: acc};
|
2014-07-24 00:21:51 +00:00
|
|
|
|
|
|
|
function walk(p) {
|
|
|
|
if (isCapture(p)) {
|
2014-07-24 23:22:22 +00:00
|
|
|
names.push(captureName(p));
|
2014-07-24 00:21:51 +00:00
|
|
|
acc.push(SOC);
|
|
|
|
walk(capturePattern(p));
|
|
|
|
acc.push(EOC);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(p)) {
|
|
|
|
acc.push(SOA);
|
|
|
|
for (var i = 0; i < p.length; i++) {
|
|
|
|
walk(p[i]);
|
|
|
|
}
|
|
|
|
acc.push(EOA);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p instanceof $Embedded) {
|
|
|
|
die("Cannot embed matcher in projection");
|
|
|
|
} else {
|
|
|
|
if (p === __) {
|
|
|
|
acc.push(p);
|
|
|
|
} else {
|
|
|
|
acc.push(JSON.stringify(p));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function projectionToPattern(p) {
|
|
|
|
return walk(p);
|
|
|
|
|
|
|
|
function walk(p) {
|
|
|
|
if (isCapture(p)) return walk(capturePattern(p));
|
|
|
|
|
|
|
|
if (Array.isArray(p)) {
|
|
|
|
var result = [];
|
|
|
|
for (var i = 0; i < p.length; i++) {
|
|
|
|
result.push(walk(p[i]));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p instanceof $Embedded) {
|
|
|
|
return p.matcher;
|
|
|
|
} else {
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
function project(m, compiledProjection) {
|
|
|
|
var spec = compiledProjection.spec;
|
2014-07-24 00:21:51 +00:00
|
|
|
return walk(false, m, 0);
|
|
|
|
|
|
|
|
function walk(isCapturing, m, specIndex) {
|
|
|
|
if (specIndex >= spec.length) {
|
|
|
|
if (isCapturing) die("Bad specification: unclosed capture");
|
|
|
|
if (m instanceof $Success) {
|
|
|
|
return rseq(EOA, rsuccess(projectSuccess(m.value)));
|
|
|
|
} else {
|
|
|
|
return emptyMatcher;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_emptyMatcher(m)) return emptyMatcher;
|
|
|
|
|
|
|
|
var item = spec[specIndex];
|
|
|
|
var nextIndex = specIndex + 1;
|
|
|
|
|
|
|
|
if (item === EOC) {
|
|
|
|
if (!isCapturing) die("Bad specification: unepxected EOC");
|
|
|
|
return walk(false, m, nextIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item === SOC) {
|
|
|
|
if (isCapturing) die("Bad specification: nested capture");
|
|
|
|
return walk(true, m, nextIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item === __) {
|
|
|
|
if (m instanceof $WildcardSequence) {
|
|
|
|
if (isCapturing) {
|
|
|
|
return rwild(walk(isCapturing, m, nextIndex));
|
|
|
|
} else {
|
|
|
|
return walk(isCapturing, m, nextIndex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m instanceof $Success) {
|
|
|
|
return emptyMatcher;
|
|
|
|
}
|
|
|
|
|
|
|
|
var target;
|
|
|
|
if (isCapturing) {
|
|
|
|
target = new $Dict();
|
|
|
|
rupdateInplace(target, __, walk(isCapturing, m.get(__), nextIndex));
|
|
|
|
for (var key in m.entries) {
|
|
|
|
if (key !== __) {
|
|
|
|
var mk = m.get(key);
|
|
|
|
if (is_keyOpen(key)) {
|
|
|
|
function cont(mk2) { return walk(isCapturing, mk2, nextIndex); }
|
|
|
|
rupdateInplace(target, key, captureNested(mk, cont));
|
|
|
|
} else if (is_keyClose(key)) {
|
|
|
|
// do nothing
|
|
|
|
} else {
|
|
|
|
rupdateInplace(target, key, walk(isCapturing, mk, nextIndex));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
target = walk(isCapturing, m.get(__), nextIndex);
|
|
|
|
for (var key in m.entries) {
|
|
|
|
if (key !== __) {
|
|
|
|
var mk = m.get(key);
|
|
|
|
if (is_keyOpen(key)) {
|
|
|
|
function cont(mk2) { return walk(isCapturing, mk2, nextIndex); }
|
|
|
|
target = union(target, skipNested(mk, cont));
|
|
|
|
} else if (is_keyClose(key)) {
|
|
|
|
// do nothing
|
|
|
|
} else {
|
|
|
|
target = union(target, walk(isCapturing, mk, nextIndex));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
|
|
|
|
var result;
|
|
|
|
if (m instanceof $WildcardSequence) {
|
|
|
|
if (is_keyOpen(item)) {
|
|
|
|
result = walk(isCapturing, rwildseq(m), nextIndex);
|
|
|
|
} else if (is_keyClose(item)) {
|
|
|
|
result = walk(isCapturing, m.matcher, nextIndex);
|
|
|
|
} else {
|
|
|
|
result = walk(isCapturing, m, nextIndex);
|
|
|
|
}
|
|
|
|
} else if (m instanceof $Success) {
|
|
|
|
result = emptyMatcher;
|
|
|
|
} else {
|
|
|
|
if (is_keyOpen(item)) {
|
|
|
|
result = walk(isCapturing, rwildseq(m.get(__)), nextIndex);
|
|
|
|
} else if (is_keyClose(item)) {
|
|
|
|
result = emptyMatcher;
|
|
|
|
} else {
|
|
|
|
result = walk(isCapturing, m.get(__), nextIndex);
|
|
|
|
}
|
|
|
|
result = union(result, walk(isCapturing, m.get(item), nextIndex));
|
|
|
|
}
|
|
|
|
if (isCapturing) {
|
|
|
|
result = rseq(item, result);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function captureNested(m, cont) {
|
|
|
|
if (m instanceof $WildcardSequence) {
|
|
|
|
return rwildseq(cont(m.matcher));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_emptyMatcher(m) || (m instanceof $Success)) {
|
|
|
|
return emptyMatcher;
|
|
|
|
}
|
|
|
|
|
|
|
|
var target = new $Dict();
|
|
|
|
rupdateInplace(target, __, captureNested(m.get(__), cont));
|
|
|
|
for (var key in m.entries) {
|
|
|
|
if (key !== __) {
|
|
|
|
var mk = m.get(key);
|
|
|
|
if (is_keyOpen(key)) {
|
|
|
|
function cont2(mk2) { return captureNested(mk2, cont); }
|
|
|
|
rupdateInplace(target, key, captureNested(mk, cont2));
|
|
|
|
} else if (is_keyClose(key)) {
|
|
|
|
rupdateInplace(target, key, cont(mk));
|
|
|
|
} else {
|
|
|
|
rupdateInplace(target, key, captureNested(mk, cont));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return target.emptyGuard();
|
|
|
|
}
|
|
|
|
|
|
|
|
function skipNested(m, cont) {
|
|
|
|
if (m instanceof $WildcardSequence) {
|
|
|
|
return cont(m.matcher);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_emptyMatcher(m) || (m instanceof $Success)) {
|
|
|
|
return emptyMatcher;
|
|
|
|
}
|
|
|
|
|
|
|
|
var target = skipNested(m.get(__), cont);
|
|
|
|
for (var key in m.entries) {
|
|
|
|
if (key !== __) {
|
|
|
|
var mk = m.get(key);
|
|
|
|
if (is_keyOpen(key)) {
|
|
|
|
function cont2(mk2) { return skipNested(mk2, cont); }
|
|
|
|
target = union(target, skipNested(mk, cont2));
|
|
|
|
} else if (is_keyClose(key)) {
|
|
|
|
target = union(target, cont(mk));
|
|
|
|
} else {
|
|
|
|
target = union(target, skipNested(mk, cont));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function matcherKeys(m) {
|
|
|
|
if (is_emptyMatcher(m)) return [];
|
|
|
|
return walkSeq(m, function (vss, vsk) { return vss; });
|
|
|
|
|
|
|
|
function walk(m, k) {
|
|
|
|
if (m instanceof $WildcardSequence) return null;
|
|
|
|
if (m instanceof $Success) return [];
|
|
|
|
if (m.has(__)) return null;
|
|
|
|
var acc = [];
|
|
|
|
for (var key in m.entries) {
|
|
|
|
var mk = m.get(key);
|
|
|
|
var piece;
|
|
|
|
if (is_keyOpen(key)) {
|
|
|
|
function seqK(vss, vsk) {
|
|
|
|
var acc = [];
|
|
|
|
for (var i = 0; i < vss.length; i++) {
|
|
|
|
var vs = vss[i];
|
|
|
|
acc = acc.concat(k(transformSeqs(vs, key), vsk));
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
piece = walkSeq(mk, seqK);
|
|
|
|
} else if (is_keyClose(key)) {
|
|
|
|
die("matcherKeys: internal error: unexpected key-close");
|
|
|
|
} else {
|
|
|
|
piece = k(JSON.parse(key), mk);
|
|
|
|
}
|
|
|
|
if (piece == null) return null;
|
|
|
|
acc = acc.concat(piece);
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
|
|
|
|
function walkSeq(m, k) {
|
|
|
|
if (m instanceof $WildcardSequence) return null;
|
|
|
|
if (m instanceof $Success) return k([], emptyMatcher); // TODO: ??
|
|
|
|
if (m.has(__)) return null;
|
|
|
|
var acc = [];
|
|
|
|
for (var key in m.entries) {
|
|
|
|
var mk = m.get(key);
|
|
|
|
var piece;
|
|
|
|
if (is_keyClose(key)) {
|
|
|
|
piece = k([[]], mk);
|
|
|
|
} else {
|
|
|
|
function outerK(v, vk) {
|
|
|
|
return walkSeq(vk, innerK);
|
|
|
|
function innerK(vss, vsk) {
|
|
|
|
var acc = [];
|
|
|
|
for (var i = 0; i < vss.length; i++) {
|
|
|
|
var vs = shallowCopyArray(vss[i]);
|
|
|
|
vs.unshift(v);
|
|
|
|
acc.push(vs);
|
|
|
|
}
|
|
|
|
return k(acc, vsk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
piece = walk(rseq(key, mk), outerK);
|
|
|
|
}
|
|
|
|
if (piece == null) return null;
|
|
|
|
acc = acc.concat(piece);
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
|
|
|
|
function transformSeqs(vs, opener) {
|
|
|
|
if (opener === SOA) return vs;
|
|
|
|
die("Internal error: unknown opener " + opener);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
function matcherKeysToObjects(matcherKeysResult, compiledProjection) {
|
|
|
|
if (matcherKeysResult === null) return null;
|
|
|
|
var result = [];
|
|
|
|
for (var i = 0; i < matcherKeysResult.length; i++) {
|
|
|
|
var e = matcherKeysResult[i];
|
|
|
|
var d = {};
|
|
|
|
for (var j = 0; j < e.length; j++) {
|
|
|
|
d[compiledProjection.names[j] || ('$' + j)] = e[j];
|
|
|
|
}
|
|
|
|
result.push(d);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function projectObjects(m, compiledProjection) {
|
|
|
|
return matcherKeysToObjects(matcherKeys(project(m, compiledProjection)), compiledProjection);
|
|
|
|
}
|
|
|
|
|
2014-07-24 00:21:51 +00:00
|
|
|
function prettyMatcher(m, initialIndent) {
|
|
|
|
var acc = [];
|
|
|
|
walk(initialIndent || 0, m);
|
|
|
|
return acc.join('');
|
|
|
|
|
|
|
|
function walk(i, m) {
|
|
|
|
if (is_emptyMatcher(m)) {
|
|
|
|
acc.push("::: no further matches possible");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (m instanceof $WildcardSequence) {
|
|
|
|
acc.push("...>");
|
|
|
|
walk(i + 4, m.matcher);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (m instanceof $Success) {
|
|
|
|
var vs = JSON.stringify(typeof m.value === 'object'
|
|
|
|
? setToArray(m.value)
|
|
|
|
: m.value);
|
|
|
|
acc.push("{" + vs + "}");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m.length === 0) {
|
|
|
|
acc.push(" ::: empty hash!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var needSep = false;
|
|
|
|
var keys = m.sortedKeys();
|
|
|
|
for (var keyi = 0; keyi < keys.length; keyi++) {
|
|
|
|
var key = keys[keyi];
|
|
|
|
var k = m.entries[key];
|
|
|
|
if (needSep) {
|
|
|
|
acc.push("\n");
|
|
|
|
acc.push(indentStr(i));
|
|
|
|
} else {
|
|
|
|
needSep = true;
|
|
|
|
}
|
|
|
|
acc.push(" ");
|
|
|
|
if (key === __) key = '★';
|
|
|
|
if (key === SOA) key = '<';
|
|
|
|
if (key === EOA) key = '>';
|
|
|
|
acc.push(key);
|
|
|
|
walk(i + key.length + 1, k);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function indentStr(i) {
|
|
|
|
return new Array(i + 1).join(' '); // eww
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function serializeMatcher(m, serializeSuccess) {
|
|
|
|
return walk(m);
|
|
|
|
function walk(m) {
|
|
|
|
if (is_emptyMatcher(m)) return [];
|
|
|
|
if (m instanceof $WildcardSequence) {
|
|
|
|
return ["...)", walk(m.matcher)];
|
|
|
|
}
|
|
|
|
if (m instanceof $Success) {
|
|
|
|
return ["", serializeSuccess(m.value)];
|
|
|
|
}
|
|
|
|
var acc = [];
|
|
|
|
for (var key in m.entries) {
|
|
|
|
var k = m.entries[key];
|
|
|
|
if (key === __) key = ["__"];
|
|
|
|
else if (key === SOA) key = ["("];
|
|
|
|
else if (key === EOA) key = [")"];
|
|
|
|
else key = JSON.parse(key);
|
|
|
|
acc.push([key, walk(k)]);
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function deserializeMatcher(r, deserializeSuccess) {
|
|
|
|
return walk(r);
|
|
|
|
function walk(r) {
|
|
|
|
if (r.length === 0) return emptyMatcher;
|
|
|
|
if (r[0] === "...)") return rwildseq(walk(r[1]));
|
|
|
|
if (r[0] === "") return rsuccess(deserializeSuccess(r[1]));
|
|
|
|
var acc = new $Dict();
|
|
|
|
for (var i = 0; i < r.length; i++) {
|
|
|
|
var rkey = r[i][0];
|
|
|
|
var rk = r[i][1];
|
|
|
|
var key;
|
|
|
|
if (Array.isArray(rkey)) {
|
|
|
|
switch (rkey[0]) {
|
|
|
|
case "__": key = __; break;
|
|
|
|
case "(": key = SOA; break;
|
|
|
|
case ")": key = EOA; break;
|
|
|
|
default: die("Invalid serialized special key: " + rkey[0]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
key = JSON.stringify(rkey);
|
|
|
|
}
|
|
|
|
rupdateInplace(acc, key, walk(rk));
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Gestalts.
|
|
|
|
// TODO: support Infinity as a level number
|
|
|
|
|
|
|
|
function GestaltLevel(subs, advs) {
|
|
|
|
this.subscriptions = subs;
|
|
|
|
this.advertisements = advs;
|
|
|
|
}
|
|
|
|
|
|
|
|
GestaltLevel.prototype.isEmpty = function () {
|
|
|
|
return is_emptyMatcher(this.subscriptions) && is_emptyMatcher(this.advertisements);
|
|
|
|
};
|
|
|
|
|
|
|
|
GestaltLevel.prototype.equals = function (other) {
|
|
|
|
return matcherEquals(this.subscriptions, other.subscriptions)
|
|
|
|
&& matcherEquals(this.advertisements, other.advertisements);
|
|
|
|
};
|
|
|
|
|
|
|
|
GestaltLevel.prototype.pretty = function () {
|
|
|
|
var acc = [];
|
|
|
|
if (!is_emptyMatcher(this.subscriptions)) {
|
|
|
|
acc.push(" - subs:");
|
|
|
|
acc.push(prettyMatcher(this.subscriptions, 9));
|
|
|
|
acc.push("\n");
|
|
|
|
}
|
|
|
|
if (!is_emptyMatcher(this.advertisements)) {
|
|
|
|
acc.push(" - advs:");
|
|
|
|
acc.push(prettyMatcher(this.advertisements, 9));
|
|
|
|
acc.push("\n");
|
|
|
|
}
|
|
|
|
return acc.join('');
|
|
|
|
};
|
|
|
|
|
|
|
|
function straightGestaltLevelOp(op) {
|
|
|
|
return function (p1, p2) {
|
|
|
|
return new GestaltLevel(op(p1.subscriptions, p2.subscriptions),
|
|
|
|
op(p1.advertisements, p2.advertisements));
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
var emptyLevel = new GestaltLevel(emptyMatcher, emptyMatcher);
|
|
|
|
var emptyMetaLevel = [];
|
|
|
|
|
|
|
|
function Gestalt(metaLevels) {
|
|
|
|
this.metaLevels = metaLevels;
|
|
|
|
}
|
|
|
|
|
|
|
|
Gestalt.prototype.getMetaLevel = function (n) {
|
|
|
|
return this.metaLevels[n] || emptyMetaLevel;
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.getLevel = function (metaLevel, level) {
|
|
|
|
return this.getMetaLevel(metaLevel)[level] || emptyLevel;
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.metaLevelCount = function () { return this.metaLevels.length; };
|
|
|
|
Gestalt.prototype.levelCount = function (n) { return this.getMetaLevel(n).length; };
|
|
|
|
|
|
|
|
Gestalt.prototype.matchValue = function (body, metaLevel, isFeedback) {
|
|
|
|
var levels = this.getMetaLevel(metaLevel);
|
|
|
|
var pids = {};
|
|
|
|
for (var i = 0; i < levels.length; i++) {
|
|
|
|
var matcher = (isFeedback ? levels[i].advertisements : levels[i].subscriptions);
|
|
|
|
setUnionInplace(pids, matchValue(matcher, body));
|
|
|
|
}
|
|
|
|
return setToArray(pids);
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.project = function (spec, getAdvertisements, metaLevel, level) {
|
|
|
|
var l = this.getLevel(metaLevel | 0, level | 0);
|
|
|
|
var matcher = (getAdvertisements ? l.advertisements : l.subscriptions);
|
|
|
|
return project(matcher, spec);
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.drop = function () {
|
|
|
|
var mls = shallowCopyArray(this.metaLevels);
|
|
|
|
mls.shift();
|
|
|
|
return new Gestalt(mls);
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.lift = function () {
|
|
|
|
var mls = shallowCopyArray(this.metaLevels);
|
|
|
|
mls.unshift(emptyMetaLevel);
|
|
|
|
return new Gestalt(mls);
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.equals = function (other) {
|
|
|
|
if (this.metaLevels.length !== other.metaLevels.length) return false;
|
|
|
|
for (var i = 0; i < this.metaLevels.length; i++) {
|
|
|
|
var ls1 = this.metaLevels[i];
|
|
|
|
var ls2 = other.metaLevels[i];
|
|
|
|
if (ls1.length !== ls2.length) return false;
|
|
|
|
for (var j = 0; j < ls1.length; j++) {
|
|
|
|
var p1 = ls1[j];
|
|
|
|
var p2 = ls2[j];
|
|
|
|
if (!p1.equals(p2)) return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
function simpleGestalt(isAdv, pat, metaLevel, level) {
|
|
|
|
metaLevel = metaLevel || 0;
|
|
|
|
level = level || 0;
|
|
|
|
var matcher = compilePattern(true, pat);
|
|
|
|
var l = new GestaltLevel(isAdv ? emptyMatcher : matcher,
|
|
|
|
isAdv ? matcher : emptyMatcher);
|
|
|
|
var levels = [l];
|
|
|
|
while (level--) { levels.unshift(emptyLevel); }
|
|
|
|
var metaLevels = [levels];
|
|
|
|
while (metaLevel--) { metaLevels.unshift(emptyMetaLevel); }
|
|
|
|
return new Gestalt(metaLevels);
|
|
|
|
}
|
|
|
|
|
|
|
|
var emptyGestalt = new Gestalt([]);
|
|
|
|
|
|
|
|
// Not quite what it says on the tin - the true fullGestalt
|
|
|
|
// wouldn't be parameterized on the number of levels and
|
|
|
|
// metalevels, but instead would be full at *all* levels and
|
|
|
|
// metalevels. Our representation leaks through into the interface
|
|
|
|
// here :-/
|
|
|
|
function fullGestalt(nMetalevels, nLevels) {
|
|
|
|
var matcher = compilePattern(true, __);
|
|
|
|
var l = new GestaltLevel(matcher, matcher);
|
|
|
|
var levels = [];
|
|
|
|
while (nLevels--) { levels.push(l); }
|
|
|
|
var metaLevels = [];
|
|
|
|
while (nMetalevels--) { metaLevels.push(levels); }
|
|
|
|
return new Gestalt(metaLevels);
|
|
|
|
}
|
|
|
|
|
|
|
|
Gestalt.prototype.isEmpty = function () {
|
|
|
|
for (var i = 0; i < this.metaLevels.length; i++) {
|
|
|
|
var levels = this.metaLevels[i];
|
|
|
|
for (var j = 0; j < levels.length; j++) {
|
|
|
|
if (!levels[j].isEmpty()) return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
function maybePushLevel(levels, i, level) {
|
|
|
|
if (!level.isEmpty()) {
|
|
|
|
while (levels.length < i) levels.push(emptyLevel);
|
|
|
|
levels.push(level);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function maybePushMetaLevel(metaLevels, i, metaLevel) {
|
|
|
|
if (metaLevel.length > 0) {
|
|
|
|
while (metaLevels.length < i) metaLevels.push(emptyMetaLevel);
|
|
|
|
metaLevels.push(metaLevel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Gestalt.prototype.mapZip = function (other, lengthCombiner, f) {
|
|
|
|
var metaLevels = [];
|
|
|
|
var mls1 = this.metaLevels;
|
|
|
|
var mls2 = other.metaLevels;
|
|
|
|
var nm = lengthCombiner(mls1.length, mls2.length);
|
|
|
|
for (var i = 0; i < nm; i++) {
|
|
|
|
var levels = [];
|
|
|
|
var ls1 = mls1[i] || emptyMetaLevel;
|
|
|
|
var ls2 = mls2[i] || emptyMetaLevel;
|
|
|
|
var nl = lengthCombiner(ls1.length, ls2.length);
|
|
|
|
for (var j = 0; j < nl; j++) {
|
|
|
|
var p1 = ls1[j] || emptyLevel;
|
|
|
|
var p2 = ls2[j] || emptyLevel;
|
|
|
|
var p = f(p1, p2);
|
|
|
|
maybePushLevel(levels, j, p);
|
|
|
|
}
|
|
|
|
maybePushMetaLevel(metaLevels, i, levels);
|
|
|
|
}
|
|
|
|
return new Gestalt(metaLevels);
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.union1 = function (other) {
|
|
|
|
return this.mapZip(other, Math.max, straightGestaltLevelOp(union));
|
|
|
|
};
|
|
|
|
|
|
|
|
function gestaltUnion(gs) {
|
|
|
|
if (gs.length === 0) return emptyGestalt;
|
|
|
|
var acc = gs[0];
|
|
|
|
for (var i = 1; i < gs.length; i++) {
|
|
|
|
acc = acc.union1(gs[i]);
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
|
|
|
|
Gestalt.prototype.union = function () {
|
|
|
|
return arguments.length > 0 ? this.union1(gestaltUnion(arguments)) : this;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Accumulates matchers from higher-numbered levels into
|
|
|
|
// lower-numbered levels.
|
|
|
|
function telescopeLevels(levels) {
|
|
|
|
var result = shallowCopyArray(levels);
|
|
|
|
for (var i = result.length - 2; i >= 0; i--) {
|
|
|
|
result[i] =
|
|
|
|
new GestaltLevel(union(result[i].subscriptions, result[i+1].subscriptions),
|
|
|
|
union(result[i].advertisements, result[i+1].advertisements));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.telescoped = function () {
|
|
|
|
var mls = [];
|
|
|
|
for (var i = 0; i < this.metaLevels.length; i++) {
|
|
|
|
mls.push(telescopeLevels(this.metaLevels[i]));
|
|
|
|
}
|
|
|
|
return new Gestalt(mls);
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.filter = function (perspective) {
|
|
|
|
var metaLevels = [];
|
|
|
|
var mls1 = this.metaLevels;
|
|
|
|
var mls2 = perspective.metaLevels;
|
|
|
|
var nm = Math.min(mls1.length, mls2.length);
|
|
|
|
for (var i = 0; i < nm; i++) {
|
|
|
|
var levels = [];
|
|
|
|
var ls1 = mls1[i] || emptyMetaLevel;
|
|
|
|
var ls2 = mls2[i] || emptyMetaLevel;
|
|
|
|
var nl = Math.min(ls1.length, ls2.length - 1);
|
|
|
|
for (var j = 0; j < nl; j++) {
|
|
|
|
var p1 = ls1[j] || emptyLevel;
|
|
|
|
var subs = emptyMatcher;
|
|
|
|
var advs = emptyMatcher;
|
|
|
|
for (var k = j + 1; k < ls2.length; k++) {
|
|
|
|
var p2 = ls2[k] || emptyLevel;
|
|
|
|
subs = union(subs, intersect(p1.subscriptions, p2.advertisements));
|
|
|
|
advs = union(advs, intersect(p1.advertisements, p2.subscriptions));
|
|
|
|
}
|
|
|
|
maybePushLevel(levels, j, new GestaltLevel(subs, advs));
|
|
|
|
}
|
|
|
|
maybePushMetaLevel(metaLevels, i, levels);
|
|
|
|
}
|
|
|
|
return new Gestalt(metaLevels);
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.match = function (perspective) {
|
|
|
|
var pids = {};
|
|
|
|
var nm = Math.min(this.metaLevels.length, perspective.metaLevels.length);
|
|
|
|
for (var i = 0; i < nm; i++) {
|
|
|
|
var ls1 = this.metaLevels[i] || emptyMetaLevel;
|
|
|
|
var ls2 = perspective.metaLevels[i] || emptyMetaLevel;
|
|
|
|
var nl = Math.min(ls1.length, ls2.length - 1);
|
|
|
|
for (var j = 0; j < nl; j++) {
|
|
|
|
var p1 = ls1[j] || emptyLevel;
|
|
|
|
for (var k = j + 1; k < ls2.length; k++) {
|
|
|
|
var p2 = ls2[k] || emptyLevel;
|
|
|
|
matchMatcher(p1.subscriptions, p2.advertisements, pids);
|
|
|
|
matchMatcher(p1.advertisements, p2.subscriptions, pids);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return setToArray(pids);
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.erasePath = function (path) {
|
|
|
|
return this.mapZip(path, Math.max, straightGestaltLevelOp(erasePath));
|
|
|
|
};
|
|
|
|
|
|
|
|
function mapLevels(inputMetaLevels, f, emptyCheck, inputEmptyLevel, outputEmptyLevel) {
|
|
|
|
var outputMetaLevels = [];
|
|
|
|
for (var i = 0; i < inputMetaLevels.length; i++) {
|
|
|
|
var ls = inputMetaLevels[i];
|
|
|
|
var levels = [];
|
|
|
|
for (var j = 0; j < ls.length; j++) {
|
|
|
|
var p = f(ls[j] || inputEmptyLevel, i, j);
|
|
|
|
if (!emptyCheck(p, i, j)) {
|
|
|
|
while (levels.length < j) levels.push(outputEmptyLevel);
|
|
|
|
levels.push(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (levels.length > 0) {
|
|
|
|
while (outputMetaLevels.length < i) outputMetaLevels.push(emptyMetaLevel);
|
|
|
|
outputMetaLevels.push(levels);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return outputMetaLevels;
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.transform = function (f) {
|
|
|
|
return new Gestalt(mapLevels(this.metaLevels, function (p, ml, l) {
|
|
|
|
return new GestaltLevel(f(p.subscriptions, ml, l, false),
|
|
|
|
f(p.advertisements, ml, l, true));
|
|
|
|
}, function (p) {
|
|
|
|
return p.isEmpty();
|
|
|
|
}, emptyLevel, emptyLevel));
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.stripLabel = function () {
|
|
|
|
return this.transform(function (m) { return relabel(m, function (v) { return true; }); });
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.label = function (pid) {
|
|
|
|
var pids = arrayToSet([pid]);
|
|
|
|
return this.transform(function (m) { return relabel(m, function (v) { return pids; }); });
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.pretty = function () {
|
|
|
|
var acc = [];
|
|
|
|
if (this.isEmpty()) {
|
|
|
|
acc.push("EMPTY GESTALT\n");
|
|
|
|
} else {
|
|
|
|
for (var i = 0; i < this.metaLevels.length; i++) {
|
|
|
|
var ls = this.metaLevels[i];
|
|
|
|
for (var j = 0; j < ls.length; j++) {
|
|
|
|
var p = ls[j];
|
|
|
|
if (!p.isEmpty()) {
|
|
|
|
acc.push("GESTALT metalevel " + i + " level " + j + ":\n");
|
|
|
|
acc.push(p.pretty());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return acc.join('');
|
|
|
|
};
|
|
|
|
|
|
|
|
Gestalt.prototype.serialize = function (serializeSuccess) {
|
|
|
|
if (typeof serializeSuccess === 'undefined') {
|
|
|
|
serializeSuccess = function (v) { return v === true ? true : setToArray(v); };
|
|
|
|
}
|
|
|
|
return ["gestalt", mapLevels(this.metaLevels, function (p) {
|
|
|
|
return [serializeMatcher(p.subscriptions, serializeSuccess),
|
|
|
|
serializeMatcher(p.advertisements, serializeSuccess)];
|
|
|
|
}, function (pr) {
|
|
|
|
return pr.length === 2 && pr[0].length === 0 && pr[1].length === 0;
|
|
|
|
}, emptyLevel, [[],[]])];
|
|
|
|
};
|
|
|
|
|
|
|
|
function deserializeGestalt(r, deserializeSuccess) {
|
|
|
|
if (typeof deserializeSuccess === 'undefined') {
|
|
|
|
deserializeSuccess = function (v) { return v === true ? true : arrayToSet(v); };
|
|
|
|
}
|
|
|
|
if (r[0] !== "gestalt") die("Invalid gestalt serialization: " + r);
|
|
|
|
return new Gestalt(mapLevels(r[1], function (pr) {
|
|
|
|
return new GestaltLevel(deserializeMatcher(pr[0], deserializeSuccess),
|
|
|
|
deserializeMatcher(pr[1], deserializeSuccess));
|
|
|
|
}, function (p) {
|
|
|
|
return p.isEmpty();
|
|
|
|
}, [[],[]], emptyLevel));
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
module.exports.__ = __;
|
|
|
|
module.exports.arrayToSet = arrayToSet;
|
|
|
|
module.exports.setToArray = setToArray;
|
|
|
|
module.exports.setUnion = setUnion;
|
|
|
|
module.exports.setSubtract = setSubtract;
|
|
|
|
module.exports.setIntersect = setIntersect;
|
|
|
|
module.exports.setEqual = setEqual;
|
|
|
|
module.exports.is_emptySet = is_emptySet;
|
|
|
|
module.exports.$Capture = $Capture;
|
|
|
|
module.exports._$ = _$;
|
|
|
|
module.exports.is_emptyMatcher = is_emptyMatcher;
|
|
|
|
module.exports.emptyMatcher = emptyMatcher;
|
|
|
|
module.exports.embeddedMatcher = embeddedMatcher;
|
|
|
|
module.exports.compilePattern = compilePattern;
|
2014-07-24 23:22:22 +00:00
|
|
|
module.exports.matchPattern = matchPattern;
|
2014-07-24 00:21:51 +00:00
|
|
|
module.exports.union = unionN;
|
|
|
|
module.exports.intersect = intersect;
|
|
|
|
module.exports.erasePath = erasePath;
|
|
|
|
module.exports.matchValue = matchValue;
|
|
|
|
module.exports.matchMatcher = matchMatcher;
|
|
|
|
module.exports.appendMatcher = appendMatcher;
|
|
|
|
module.exports.relabel = relabel;
|
|
|
|
module.exports.compileProjection = compileProjection;
|
|
|
|
module.exports.projectionToPattern = projectionToPattern;
|
|
|
|
module.exports.project = project;
|
|
|
|
module.exports.matcherKeys = matcherKeys;
|
2014-07-24 23:22:22 +00:00
|
|
|
module.exports.matcherKeysToObjects = matcherKeysToObjects;
|
|
|
|
module.exports.projectObjects = projectObjects;
|
2014-07-24 00:21:51 +00:00
|
|
|
module.exports.matcherEquals = matcherEquals;
|
|
|
|
module.exports.prettyMatcher = prettyMatcher;
|
|
|
|
module.exports.serializeMatcher = serializeMatcher;
|
|
|
|
module.exports.deserializeMatcher = deserializeMatcher;
|
|
|
|
|
|
|
|
module.exports.GestaltLevel = GestaltLevel;
|
|
|
|
module.exports.Gestalt = Gestalt;
|
|
|
|
module.exports.simpleGestalt = simpleGestalt;
|
|
|
|
module.exports.emptyGestalt = emptyGestalt;
|
|
|
|
module.exports.fullGestalt = fullGestalt;
|
|
|
|
module.exports.gestaltUnion = gestaltUnion;
|
|
|
|
module.exports.deserializeGestalt = deserializeGestalt;
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
},{}],10:[function(_dereq_,module,exports){
|
2014-07-24 00:21:51 +00:00
|
|
|
var Minimart = _dereq_("./minimart.js");
|
|
|
|
var Route = Minimart.Route;
|
|
|
|
var World = Minimart.World;
|
|
|
|
var sub = Minimart.sub;
|
|
|
|
var pub = Minimart.pub;
|
|
|
|
var __ = Minimart.__;
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
function spawnRoutingTableWidget(selector, fragmentClass, domWrap, observationLevel) {
|
2014-07-24 00:21:51 +00:00
|
|
|
observationLevel = observationLevel || 10;
|
|
|
|
// ^ arbitrary: should be Infinity, when route.js supports it. TODO
|
2014-07-24 23:22:22 +00:00
|
|
|
domWrap = domWrap || Minimart.DOM.defaultWrapFunction;
|
2014-07-24 00:21:51 +00:00
|
|
|
|
|
|
|
World.spawn({
|
|
|
|
boot: function () { this.updateState(); },
|
|
|
|
|
|
|
|
state: Route.emptyGestalt.serialize(),
|
|
|
|
nextState: Route.emptyGestalt.serialize(),
|
|
|
|
timer: false,
|
|
|
|
|
2014-07-24 23:22:22 +00:00
|
|
|
localGestalt: (sub( domWrap(selector, fragmentClass, __), 0, 2)
|
|
|
|
.union(pub(domWrap(selector, fragmentClass, __), 0, 2))
|
2014-07-24 00:21:51 +00:00
|
|
|
.telescoped()),
|
|
|
|
|
|
|
|
digestGestalt: function (g) {
|
|
|
|
return g.stripLabel().erasePath(this.localGestalt).serialize();
|
|
|
|
},
|
|
|
|
|
|
|
|
updateState: function () {
|
|
|
|
var elts = ["pre", Route.deserializeGestalt(this.state).pretty()];
|
|
|
|
World.updateRoutes([sub(__, 0, observationLevel),
|
|
|
|
pub(__, 0, observationLevel),
|
2014-07-24 23:22:22 +00:00
|
|
|
pub(domWrap(selector, fragmentClass, elts))]);
|
2014-07-24 00:21:51 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
handleEvent: function (e) {
|
|
|
|
var self = this;
|
|
|
|
if (e.type === "routes") {
|
|
|
|
self.nextState = self.digestGestalt(e.gestalt);
|
|
|
|
if (self.timer) {
|
|
|
|
clearTimeout(self.timer);
|
|
|
|
self.timer = false;
|
|
|
|
}
|
|
|
|
self.timer = setTimeout(World.wrap(function () {
|
|
|
|
if (JSON.stringify(self.nextState) !== JSON.stringify(self.state)) {
|
|
|
|
self.state = self.nextState;
|
|
|
|
self.updateState();
|
|
|
|
}
|
|
|
|
self.timer = false;
|
|
|
|
}), 50);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.spawnRoutingTableWidget = spawnRoutingTableWidget;
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
},{"./minimart.js":7}],11:[function(_dereq_,module,exports){
|
2014-07-24 00:21:51 +00:00
|
|
|
// Generic Spy
|
|
|
|
var Minimart = _dereq_("./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";
|
|
|
|
this.observationLevel = observationLevel || 10; // arbitrary. Should be Infinity. TODO
|
|
|
|
this.useJson = useJson;
|
|
|
|
}
|
|
|
|
|
|
|
|
Spy.prototype.boot = function () {
|
|
|
|
World.updateRoutes([sub(__, 0, this.observationLevel), pub(__, 0, this.observationLevel)]);
|
|
|
|
};
|
|
|
|
|
|
|
|
Spy.prototype.handleEvent = function (e) {
|
|
|
|
switch (e.type) {
|
|
|
|
case "routes":
|
|
|
|
console.log(this.label, "routes", e.gestalt.pretty());
|
|
|
|
break;
|
|
|
|
case "message":
|
|
|
|
var messageRepr;
|
|
|
|
try {
|
|
|
|
messageRepr = this.useJson ? JSON.stringify(e.message) : e.message;
|
|
|
|
} catch (exn) {
|
|
|
|
messageRepr = e.message;
|
|
|
|
}
|
|
|
|
console.log(this.label, "message", messageRepr, e.metaLevel, e.isFeedback);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
console.log(this.label, "unknown", e);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports.Spy = Spy;
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
},{"./minimart.js":7}],12:[function(_dereq_,module,exports){
|
|
|
|
// Minimal jQueryish utilities. Reimplemented because jQuery needs
|
|
|
|
// window to exist, and we want to run in Web Worker context as well.
|
|
|
|
|
|
|
|
function extend(what, _with) {
|
|
|
|
for (var prop in _with) {
|
|
|
|
if (_with.hasOwnProperty(prop)) {
|
|
|
|
what[prop] = _with[prop];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return what;
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.extend = extend;
|
|
|
|
|
|
|
|
},{}],13:[function(_dereq_,module,exports){
|
2014-07-24 00:21:51 +00:00
|
|
|
// Wake detector - notices when something (such as
|
|
|
|
// suspension/sleeping!) has caused periodic activities to be
|
|
|
|
// interrupted, and warns others about it
|
|
|
|
// Inspired by http://blog.alexmaccaw.com/javascript-wake-event
|
|
|
|
var Minimart = _dereq_("./minimart.js");
|
|
|
|
var World = Minimart.World;
|
|
|
|
var sub = Minimart.sub;
|
|
|
|
var pub = Minimart.pub;
|
|
|
|
var __ = Minimart.__;
|
|
|
|
|
|
|
|
function WakeDetector(period) {
|
|
|
|
this.message = "wake";
|
|
|
|
this.period = period || 10000;
|
|
|
|
this.mostRecentTrigger = +(new Date());
|
|
|
|
this.timerId = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
WakeDetector.prototype.boot = function () {
|
|
|
|
var self = this;
|
|
|
|
World.updateRoutes([pub(this.message)]);
|
|
|
|
this.timerId = setInterval(World.wrap(function () { self.trigger(); }), this.period);
|
|
|
|
};
|
|
|
|
|
|
|
|
WakeDetector.prototype.handleEvent = function (e) {};
|
|
|
|
|
|
|
|
WakeDetector.prototype.trigger = function () {
|
|
|
|
var now = +(new Date());
|
|
|
|
if (now - this.mostRecentTrigger > this.period * 1.5) {
|
|
|
|
World.send(this.message);
|
|
|
|
}
|
|
|
|
this.mostRecentTrigger = now;
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports.WakeDetector = WakeDetector;
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
},{"./minimart.js":7}],14:[function(_dereq_,module,exports){
|
2014-07-24 00:21:51 +00:00
|
|
|
var Minimart = _dereq_("./minimart.js");
|
2014-08-02 16:00:28 +00:00
|
|
|
var Codec = _dereq_("./codec.js");
|
2014-07-24 00:21:51 +00:00
|
|
|
var Route = Minimart.Route;
|
|
|
|
var World = Minimart.World;
|
|
|
|
var sub = Minimart.sub;
|
|
|
|
var pub = Minimart.pub;
|
|
|
|
var __ = Minimart.__;
|
|
|
|
var _$ = Minimart._$;
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// WebSocket client driver
|
|
|
|
|
|
|
|
var DEFAULT_RECONNECT_DELAY = 100;
|
|
|
|
var MAX_RECONNECT_DELAY = 30000;
|
|
|
|
var DEFAULT_IDLE_TIMEOUT = 300000; // 5 minutes
|
|
|
|
var DEFAULT_PING_INTERVAL = DEFAULT_IDLE_TIMEOUT - 10000;
|
|
|
|
|
|
|
|
function WebSocketConnection(label, wsurl, shouldReconnect) {
|
|
|
|
this.label = label;
|
|
|
|
this.sendsAttempted = 0;
|
|
|
|
this.sendsTransmitted = 0;
|
|
|
|
this.receiveCount = 0;
|
|
|
|
this.sock = null;
|
|
|
|
this.wsurl = wsurl;
|
|
|
|
this.shouldReconnect = shouldReconnect ? true : false;
|
|
|
|
this.reconnectDelay = DEFAULT_RECONNECT_DELAY;
|
|
|
|
this.localGestalt = Route.emptyGestalt;
|
|
|
|
this.peerGestalt = Route.emptyGestalt;
|
|
|
|
this.prevLocalRoutesMessage = null;
|
|
|
|
this.prevPeerRoutesMessage = null;
|
|
|
|
this.deduplicator = new Minimart.Deduplicator();
|
|
|
|
this.connectionCount = 0;
|
|
|
|
|
|
|
|
this.activityTimestamp = 0;
|
|
|
|
this.idleTimeout = DEFAULT_IDLE_TIMEOUT;
|
|
|
|
this.pingInterval = DEFAULT_PING_INTERVAL;
|
|
|
|
this.idleTimer = null;
|
|
|
|
this.pingTimer = null;
|
|
|
|
}
|
|
|
|
|
2014-08-25 20:02:04 +00:00
|
|
|
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
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2014-07-24 00:21:51 +00:00
|
|
|
WebSocketConnection.prototype.clearHeartbeatTimers = function () {
|
|
|
|
if (this.idleTimer) { clearTimeout(this.idleTimer); this.idleTimer = null; }
|
|
|
|
if (this.pingTimer) { clearTimeout(this.pingTimer); this.pingTimer = null; }
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.recordActivity = function () {
|
|
|
|
var self = this;
|
|
|
|
this.activityTimestamp = +(new Date());
|
|
|
|
this.clearHeartbeatTimers();
|
|
|
|
this.idleTimer = setTimeout(function () { self.forceclose(); },
|
|
|
|
this.idleTimeout);
|
|
|
|
this.pingTimer = setTimeout(function () { self.safeSend(JSON.stringify("ping")) },
|
|
|
|
this.pingInterval);
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.statusRoute = function (status) {
|
|
|
|
return pub([this.label + "_state", status]);
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.relayGestalt = function () {
|
|
|
|
return this.statusRoute(this.isConnected() ? "connected" : "disconnected")
|
|
|
|
.union(pub([this.label, __, __], 0, 10))
|
|
|
|
.union(sub([this.label, __, __], 0, 10));
|
|
|
|
// TODO: level 10 is ad-hoc; support infinity at some point in future
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.aggregateGestalt = function () {
|
|
|
|
var self = this;
|
|
|
|
return this.peerGestalt.transform(function (m, metaLevel) {
|
|
|
|
return Route.compilePattern(true,
|
|
|
|
[self.label, metaLevel, Route.embeddedMatcher(m)]);
|
|
|
|
}).union(this.relayGestalt());
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.boot = function () {
|
|
|
|
this.reconnect();
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.trapexit = function () {
|
|
|
|
this.forceclose();
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.isConnected = function () {
|
|
|
|
return this.sock && this.sock.readyState === this.sock.OPEN;
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.safeSend = function (m) {
|
|
|
|
try {
|
|
|
|
this.sendsAttempted++;
|
|
|
|
if (this.isConnected()) {
|
|
|
|
this.sock.send(m);
|
|
|
|
this.sendsTransmitted++;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.warn("Trapped exn while sending", e);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.sendLocalRoutes = function () {
|
|
|
|
var newLocalRoutesMessage =
|
2014-08-02 16:00:28 +00:00
|
|
|
JSON.stringify(Codec.encodeEvent(Minimart.updateRoutes([this.localGestalt])));
|
2014-07-24 00:21:51 +00:00
|
|
|
if (this.prevLocalRoutesMessage !== newLocalRoutesMessage) {
|
|
|
|
this.prevLocalRoutesMessage = newLocalRoutesMessage;
|
|
|
|
this.safeSend(newLocalRoutesMessage);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.collectMatchers = function (getAdvertisements, level, g) {
|
|
|
|
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 m = g.project(extractMatchers, getAdvertisements, 0, level);
|
|
|
|
this.localGestalt = this.localGestalt.union(Route.simpleGestalt(getAdvertisements,
|
|
|
|
Route.embeddedMatcher(m),
|
|
|
|
metaLevel,
|
|
|
|
level));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.handleEvent = function (e) {
|
|
|
|
// console.log("WebSocketConnection.handleEvent", e);
|
|
|
|
switch (e.type) {
|
|
|
|
case "routes":
|
|
|
|
// TODO: GROSS - erasing by pid!
|
|
|
|
var nLevels = e.gestalt.levelCount(0);
|
|
|
|
var relayGestalt = Route.fullGestalt(1, nLevels).label(World.activePid());
|
|
|
|
var g = e.gestalt.erasePath(relayGestalt);
|
|
|
|
this.localGestalt = Route.emptyGestalt;
|
|
|
|
for (var level = 0; level < nLevels; level++) {
|
|
|
|
this.collectMatchers(false, level, g);
|
|
|
|
this.collectMatchers(true, level, g);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.sendLocalRoutes();
|
|
|
|
break;
|
|
|
|
case "message":
|
|
|
|
var m = e.message;
|
|
|
|
if (m.length && m.length === 3 && m[0] === this.label)
|
|
|
|
{
|
2014-08-02 16:00:28 +00:00
|
|
|
var encoded = JSON.stringify(Codec.encodeEvent(
|
2014-07-24 00:21:51 +00:00
|
|
|
Minimart.sendMessage(m[2], m[1], e.isFeedback)));
|
|
|
|
if (this.deduplicator.accept(encoded)) {
|
|
|
|
this.safeSend(encoded);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.forceclose = function (keepReconnectDelay) {
|
|
|
|
if (!keepReconnectDelay) {
|
|
|
|
this.reconnectDelay = DEFAULT_RECONNECT_DELAY;
|
|
|
|
}
|
|
|
|
this.clearHeartbeatTimers();
|
|
|
|
if (this.sock) {
|
|
|
|
console.log("WebSocketConnection.forceclose called");
|
|
|
|
this.sock.close();
|
|
|
|
this.sock = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.reconnect = function () {
|
|
|
|
var self = this;
|
|
|
|
this.forceclose(true);
|
|
|
|
this.connectionCount++;
|
|
|
|
this.sock = new WebSocket(this.wsurl);
|
|
|
|
this.sock.onopen = World.wrap(function (e) { return self.onopen(e); });
|
|
|
|
this.sock.onmessage = World.wrap(function (e) {
|
|
|
|
self.receiveCount++;
|
|
|
|
return self.onmessage(e);
|
|
|
|
});
|
|
|
|
this.sock.onclose = World.wrap(function (e) { return self.onclose(e); });
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.onopen = function (e) {
|
|
|
|
console.log("connected to " + this.sock.url);
|
|
|
|
this.reconnectDelay = DEFAULT_RECONNECT_DELAY;
|
|
|
|
this.prevLocalRoutesMessage = null;
|
|
|
|
this.sendLocalRoutes();
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.onmessage = function (wse) {
|
|
|
|
// console.log("onmessage", wse);
|
|
|
|
this.recordActivity();
|
|
|
|
|
|
|
|
var j = JSON.parse(wse.data);
|
|
|
|
if (j === "ping") {
|
|
|
|
this.safeSend(JSON.stringify("pong"));
|
|
|
|
return;
|
|
|
|
} else if (j === "pong") {
|
|
|
|
return; // recordActivity already took care of our timers
|
|
|
|
}
|
|
|
|
|
2014-08-04 18:32:03 +00:00
|
|
|
var e = Codec.decodeAction(j);
|
2014-07-24 00:21:51 +00:00
|
|
|
switch (e.type) {
|
|
|
|
case "routes":
|
|
|
|
if (this.prevPeerRoutesMessage !== wse.data) {
|
|
|
|
this.prevPeerRoutesMessage = wse.data;
|
|
|
|
this.peerGestalt = e.gestalt;
|
|
|
|
World.updateRoutes([this.aggregateGestalt()]);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "message":
|
|
|
|
if (this.deduplicator.accept(wse.data)) {
|
|
|
|
World.send([this.label, e.metaLevel, e.message], 0, e.isFeedback);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
WebSocketConnection.prototype.onclose = function (e) {
|
|
|
|
var self = this;
|
|
|
|
console.log("onclose", e);
|
|
|
|
|
|
|
|
// Update routes to give clients some indication of the discontinuity
|
|
|
|
World.updateRoutes([this.aggregateGestalt()]);
|
|
|
|
|
|
|
|
if (this.shouldReconnect) {
|
|
|
|
console.log("reconnecting to " + this.wsurl + " in " + this.reconnectDelay + "ms");
|
|
|
|
setTimeout(World.wrap(function () { self.reconnect(); }), this.reconnectDelay);
|
|
|
|
this.reconnectDelay = this.reconnectDelay * 1.618 + (Math.random() * 1000);
|
|
|
|
this.reconnectDelay =
|
|
|
|
this.reconnectDelay > MAX_RECONNECT_DELAY
|
|
|
|
? MAX_RECONNECT_DELAY + (Math.random() * 1000)
|
|
|
|
: this.reconnectDelay;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
module.exports.WebSocketConnection = WebSocketConnection;
|
|
|
|
|
|
|
|
},{"./codec.js":2,"./minimart.js":7}],15:[function(_dereq_,module,exports){
|
|
|
|
/* Web Worker interface */
|
|
|
|
var Ground = _dereq_("./ground.js").Ground;
|
|
|
|
var Util = _dereq_("./util.js");
|
|
|
|
var Codec = _dereq_("./codec.js");
|
|
|
|
|
|
|
|
var Minimart = _dereq_("./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);
|
2014-07-24 00:21:51 +00:00
|
|
|
}
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
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();
|
|
|
|
};
|
2014-07-24 00:21:51 +00:00
|
|
|
}
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
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");
|
|
|
|
};
|
|
|
|
|
2014-07-24 00:21:51 +00:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
module.exports.Worker = Worker;
|
|
|
|
module.exports.WorkerGround = WorkerGround;
|
2014-07-24 00:21:51 +00:00
|
|
|
|
2014-08-02 16:00:28 +00:00
|
|
|
},{"./codec.js":2,"./ground.js":4,"./minimart.js":7,"./util.js":12}]},{},[6])
|
2014-08-26 01:39:55 +00:00
|
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy90b255Zy9zcmMvanMtbWFya2V0cGxhY2Uvbm9kZV9tb2R1bGVzL2Jyb3dzZXJpZnkvbm9kZV9tb2R1bGVzL2Jyb3dzZXItcGFjay9fcHJlbHVkZS5qcyIsIi9Vc2Vycy90b255Zy9zcmMvanMtbWFya2V0cGxhY2Uvc3JjL2FjdG9yLmpzIiwiL1VzZXJzL3RvbnlnL3NyYy9qcy1tYXJrZXRwbGFjZS9zcmMvY29kZWMuanMiLCIvVXNlcnMvdG9ueWcvc3JjL2pzLW1hcmtldHBsYWNlL3NyYy9kb20tZHJpdmVyLmpzIiwiL1VzZXJzL3RvbnlnL3NyYy9qcy1tYXJrZXRwbGFjZS9zcmMvZ3JvdW5kLmpzIiwiL1VzZXJzL3RvbnlnL3NyYy9qcy1tYXJrZXRwbGFjZS9zcmMvanF1ZXJ5LWRyaXZlci5qcyIsIi9Vc2Vycy90b255Zy9zcmMvanMtbWFya2V0cGxhY2Uvc3JjL21haW4uanMiLCIvVXNlcnMvdG9ueWcvc3JjL2pzLW1hcmtldHBsYWNlL3NyYy9taW5pbWFydC5qcyIsIi9Vc2Vycy90b255Zy9zcmMvanMtbWFya2V0cGxhY2Uvc3JjL3JlZmxlY3QuanMiLCIvVXNlcnMvdG9ueWcvc3JjL2pzLW1hcmtldHBsYWNlL3NyYy9yb3V0ZS5qcyIsIi9Vc2Vycy90b255Zy9zcmMvanMtbWFya2V0cGxhY2Uvc3JjL3JvdXRpbmctdGFibGUtd2lkZ2V0LmpzIiwiL1VzZXJzL3RvbnlnL3NyYy9qcy1tYXJrZXRwbGFjZS9zcmMvc3B5LmpzIiwiL1VzZXJzL3RvbnlnL3NyYy9qcy1tYXJrZXRwbGFjZS9zcmMvdXRpbC5qcyIsIi9Vc2Vycy90b255Zy9zcmMvanMtbWFya2V0cGxhY2Uvc3JjL3dha2UtZGV0ZWN0b3IuanMiLCIvVXNlcnMvdG9ueWcvc3JjL2pzLW1hcmtldHBsYWNlL3NyYy93ZWJzb2NrZXQtZHJpdmVyLmpzIiwiL1VzZXJzL3RvbnlnL3NyYy9qcy1tYXJrZXRwbGFjZS9zcmMvd29ya2VyLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcFJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2pDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Q
|
2014-08-02 16:00:28 +00:00
|
|
|
(6)
|
2014-07-24 00:21:51 +00:00
|
|
|
});
|