"use strict"; //--------------------------------------------------------------------------- // @syndicate-lang/core, an implementation of Syndicate dataspaces for JS. // Copyright (C) 2016-2018 Tony Garnock-Jones // // 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 . //--------------------------------------------------------------------------- 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;