syndicate-js/src/skeleton.js

343 lines
11 KiB
JavaScript

"use strict";
//---------------------------------------------------------------------------
// @syndicate-lang/core, an implementation of Syndicate dataspaces for JS.
// Copyright (C) 2016-2018 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//---------------------------------------------------------------------------
const Immutable = require("immutable");
const Struct = require('./struct.js');
const $Special = require('./special.js');
const Bag = require('./bag.js');
const Assertions = require('./assertions.js');
const __ = Struct.__;
const EVENT_ADDED = +1;
const EVENT_REMOVED = -1;
const EVENT_MESSAGE = 0;
function Index() {
this.allAssertions = Bag.Bag();
this.root = new Node(new Continuation(Immutable.Set()));
}
function Node(continuation) {
this.continuation = continuation;
this.edges = Immutable.Map();
}
function Selector(popCount, index) {
this.popCount = popCount;
this.index = index;
}
Selector.prototype.equals = function (other) {
return (this.popCount === other.popCount) && (this.index === other.index);
};
Selector.prototype.hashCode = function () {
return (this.popCount * 5) + this.index;
};
function Continuation(cachedAssertions) {
this.cachedAssertions = cachedAssertions;
this.leafMap = Immutable.Map();
}
function Leaf(cachedAssertions) {
this.cachedAssertions = cachedAssertions;
this.handlerMap = Immutable.Map();
}
function Handler(cachedCaptures) {
this.cachedCaptures = cachedCaptures;
this.callbacks = Immutable.Set();
}
function classOf(v) {
if (v instanceof Struct.Structure) {
return v.meta;
} else if (v instanceof Immutable.List) {
return v.size;
} else {
return null;
}
}
function step(v, index) {
return v.get(index);
}
function projectPath(v, path) {
path.forEach((index) => { v = step(v, index); return true; });
return v;
}
function projectPaths(v, paths) {
return paths.map((path) => projectPath(v, path));
}
Node.prototype.extend = function(skeleton) {
function walkNode(path, node, popCount, index, skeleton) {
if (skeleton === null) {
return [popCount, node];
} else {
let selector = new Selector(popCount, index);
let cls = skeleton[0];
let table = node.edges.get(selector, false);
if (!table) {
table = Immutable.Map();
node.edges = node.edges.set(selector, table);
}
let nextNode = table.get(cls, false);
if (!nextNode) {
nextNode = new Node(new Continuation(
node.continuation.cachedAssertions.filter(
(a) => classOf(projectPath(a, path)) === cls)));
table = table.set(cls, nextNode);
node.edges = node.edges.set(selector, table);
}
popCount = 0;
index = 0;
path = path.push(index);
for (let i = 1; i < skeleton.length; i++) {
[popCount, nextNode] = walkNode(path, nextNode, popCount, index, skeleton[i]);
index++;
path = path.pop().push(index);
}
return [popCount + 1, nextNode];
}
}
let [_popCount, finalNode] = walkNode(Immutable.List(), this, 0, 0, skeleton);
return finalNode.continuation;
};
Index.prototype.addHandler = function(analysisResults, callback) {
let {skeleton, constPaths, constVals, capturePaths} = analysisResults;
let continuation = this.root.extend(skeleton);
let constValMap = continuation.leafMap.get(constPaths, false);
if (!constValMap) {
constValMap = Immutable.Map();
continuation.leafMap = continuation.leafMap.set(constPaths, constValMap);
}
let leaf = constValMap.get(constVals, false);
if (!leaf) {
leaf = new Leaf(continuation.cachedAssertions.filter(
(a) => projectPaths(a, constPaths).equals(constVals)));
constValMap = constValMap.set(constVals, leaf);
continuation.leafMap = continuation.leafMap.set(constPaths, constValMap);
}
let handler = leaf.handlerMap.get(capturePaths, false);
if (!handler) {
let cachedCaptures = Bag.Bag().withMutations((mutable) => {
leaf.cachedAssertions.forEach((a) => {
let captures = projectPaths(a, capturePaths);
mutable.set(captures, mutable.get(captures, 0) + 1);
return true;
})
});
handler = new Handler(cachedCaptures);
leaf.handlerMap = leaf.handlerMap.set(capturePaths, handler);
}
handler.callbacks = handler.callbacks.add(callback);
handler.cachedCaptures.forEach((_count, captures) => {
callback(EVENT_ADDED, captures);
return true;
});
};
Index.prototype.removeHandler = function(analysisResults, callback) {
let {skeleton, constPaths, constVals, capturePaths} = analysisResults;
let continuation = this.root.extend(skeleton);
let constValMap = continuation.leafMap.get(constPaths, false);
if (!constValMap) return;
let leaf = constValMap.get(constVals, false);
if (!leaf) return;
let handler = leaf.handlerMap.get(capturePaths, false);
if (!handler) return;
handler.callbacks = handler.callbacks.remove(callback);
if (handler.callbacks.isEmpty()) {
leaf.handlerMap = leaf.handlerMap.remove(capturePaths);
}
if (leaf.handlerMap.isEmpty()) {
constValMap = constValMap.remove(constVals);
}
if (constValMap.isEmpty()) {
continuation.leafMap.remove(constPaths);
} else {
continuation.leafMap.set(constPaths, constValMap);
}
};
Node.prototype.modify = function(outerValue, m_cont, m_leaf, m_handler) {
function walkNode(node, termStack) {
walkContinuation(node.continuation);
node.edges.forEach((table, selector) => {
let nextStack = termStack.withMutations((mutable) => {
let i = selector.popCount;
while (i--) { mutable.pop(); }
});
let nextValue = step(nextStack.first(), selector.index);
let cls = classOf(nextValue);
let nextNode = table.get(cls, false);
if (nextNode) {
walkNode(nextNode, nextStack.push(nextValue));
}
return true;
});
}
function walkContinuation(continuation) {
m_cont(continuation, outerValue);
continuation.leafMap.forEach((constValMap, constPaths) => {
let constVals = projectPaths(outerValue, constPaths);
let leaf = constValMap.get(constVals, false);
if (leaf) {
m_leaf(leaf, outerValue);
leaf.handlerMap.forEach((handler, capturePaths) => {
m_handler(handler, projectPaths(outerValue, capturePaths));
return true;
});
}
return true;
});
}
walkNode(this, Immutable.Stack().push(Immutable.List([outerValue])));
};
function add_to_cont(c, v) { c.cachedAssertions = c.cachedAssertions.add(v); }
function add_to_leaf(l, v) { l.cachedAssertions = l.cachedAssertions.add(v); }
function add_to_handler(h, vs) {
let net;
({bag: h.cachedCaptures, net: net} = Bag.change(h.cachedCaptures, vs, +1));
if (net === Bag.ABSENT_TO_PRESENT) {
h.callbacks.forEach((cb) => {
cb(EVENT_ADDED, vs);
return true;
});
}
}
function del_from_cont(c, v) { c.cachedAssertions = c.cachedAssertions.remove(v); }
function del_from_leaf(l, v) { l.cachedAssertions = l.cachedAssertions.remove(v); }
function del_from_handler(h, vs) {
let net;
({bag: h.cachedCaptures, net: net} = Bag.change(h.cachedCaptures, vs, -1));
if (net === Bag.PRESENT_TO_ABSENT) {
h.callbacks.forEach((cb) => {
cb(EVENT_REMOVED, vs);
return true;
});
}
}
Index.prototype.adjustAssertion = function(outerValue, delta) {
let net;
outerValue = Immutable.fromJS(outerValue);
({bag: this.allAssertions, net: net} = Bag.change(this.allAssertions, outerValue, delta));
switch (net) {
case Bag.ABSENT_TO_PRESENT:
this.root.modify(outerValue, add_to_cont, add_to_leaf, add_to_handler);
break;
case Bag.PRESENT_TO_ABSENT:
this.root.modify(outerValue, del_from_cont, del_from_leaf, del_from_handler);
break;
}
};
Index.prototype.addAssertion = function(v) { this.adjustAssertion(v, +1); };
Index.prototype.removeAssertion = function (v) { this.adjustAssertion(v, -1); };
Index.prototype.sendMessage = function(v) {
this.root.modify(Immutable.fromJS(v), ()=>{}, ()=>{}, (h, vs) => {
h.callbacks.forEach((cb) => {
cb(EVENT_MESSAGE, vs);
return true;
});
});
};
///////////////////////////////////////////////////////////////////////////
// The name argument should be a string or null; it defaults to null.
// The pattern argument defaults to wildcard, __.
function Capture(name, pattern) {
this.name = name;
this.pattern = (pattern === void 0 ? __ : pattern);
}
// Abbreviation: _$(...) <==> new Capture(...)
function _$(name, pattern) {
return new Capture(name, pattern);
}
function isCapture(x) { return x instanceof Capture || x === _$; }
function captureName(x) { return x instanceof Capture ? x.name : null; }
function capturePattern(x) { return x instanceof Capture ? x.pattern : __; }
///////////////////////////////////////////////////////////////////////////
function analyzeAssertion(a) {
let constPaths = Immutable.List();
let constVals = Immutable.List();
let capturePaths = Immutable.List();
function walk(path, a) {
let cls = classOf(a);
if (cls !== null) {
let arity = (typeof cls === 'number') ? cls : cls.arity;
let result = [cls];
for (let i = 0; i < arity; i++) {
result.push(walk(path.push(i), step(a, i)));
}
return result;
} else {
if (isCapture(a)) {
capturePaths = capturePaths.push(path);
return walk(path, capturePattern(a));
} else if (a === __) {
return null;
} else {
constPaths = constPaths.push(path);
constVals = constVals.push(a);
return null;
}
}
}
let skeleton = walk(Immutable.List(), Immutable.fromJS(a));
return { skeleton, constPaths, constVals, capturePaths };
}
///////////////////////////////////////////////////////////////////////////
module.exports.EVENT_ADDED = EVENT_ADDED;
module.exports.EVENT_REMOVED = EVENT_REMOVED;
module.exports.EVENT_MESSAGE = EVENT_MESSAGE;
module.exports.Index = Index;
module.exports.Capture = Capture;
module.exports._$ = _$;
module.exports.isCapture = isCapture;
module.exports.captureName = captureName;
module.exports.capturePattern = capturePattern;
module.exports.analyzeAssertion = analyzeAssertion;