First JavaScript steps, based on HOWITWORKS.md
This commit is contained in:
parent
da121230b9
commit
c26186f871
|
@ -4,6 +4,38 @@
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"assertion-error": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"chai": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"assertion-error": "1.1.0",
|
||||||
|
"check-error": "1.0.2",
|
||||||
|
"deep-eql": "3.0.1",
|
||||||
|
"get-func-name": "2.0.0",
|
||||||
|
"pathval": "1.1.0",
|
||||||
|
"type-detect": "4.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chai-immutable": {
|
||||||
|
"version": "2.0.0-rc.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chai-immutable/-/chai-immutable-2.0.0-rc.2.tgz",
|
||||||
|
"integrity": "sha512-9DAbH5PsOSZGSJDskYb88GkFj2j5C9EbKMdR6apnF6iC0rIStwXH05EvcyruqHYe5Y1vQYmxOLFXyISS5L2KVw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"check-error": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"commander": {
|
"commander": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "http://registry.npmjs.org/commander/-/commander-2.3.0.tgz",
|
"resolved": "http://registry.npmjs.org/commander/-/commander-2.3.0.tgz",
|
||||||
|
@ -12,13 +44,22 @@
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
"resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
||||||
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
|
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "0.7.1"
|
"ms": "0.7.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"deep-eql": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"type-detect": "4.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"diff": {
|
"diff": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz",
|
||||||
|
@ -31,10 +72,10 @@
|
||||||
"integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=",
|
"integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"expect.js": {
|
"get-func-name": {
|
||||||
"version": "0.3.1",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/expect.js/-/expect.js-0.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
|
||||||
"integrity": "sha1-sKWaDS7/VDdUTr8M6qYBWEHQm1s=",
|
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"glob": {
|
"glob": {
|
||||||
|
@ -43,8 +84,8 @@
|
||||||
"integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=",
|
"integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "2",
|
"inherits": "2.0.3",
|
||||||
"minimatch": "0.3"
|
"minimatch": "0.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"growl": {
|
"growl": {
|
||||||
|
@ -101,8 +142,8 @@
|
||||||
"integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=",
|
"integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"lru-cache": "2",
|
"lru-cache": "2.7.3",
|
||||||
"sigmund": "~1.0.0"
|
"sigmund": "1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
|
@ -140,10 +181,16 @@
|
||||||
},
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "0.7.1",
|
"version": "0.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
|
"resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
|
||||||
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
|
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"pathval": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
|
||||||
|
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"sigmund": {
|
"sigmund": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
|
||||||
|
@ -161,6 +208,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz",
|
||||||
"integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=",
|
"integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"type-detect": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
},
|
},
|
||||||
"author": "Tony Garnock-Jones <tonyg@ccs.neu.edu>",
|
"author": "Tony Garnock-Jones <tonyg@ccs.neu.edu>",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"expect.js": "^0.3.1",
|
"chai": "^4.2.0",
|
||||||
|
"chai-immutable": "^2.0.0-rc.2",
|
||||||
"immutable": "^3.8.2",
|
"immutable": "^3.8.2",
|
||||||
"mocha": "^2.5.3"
|
"mocha": "^2.5.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
module.exports.Bag = require("./bag.js");
|
module.exports.Bag = require("./bag.js");
|
||||||
// module.exports.Skeleton = require("./skeleton.js");
|
module.exports.Struct = require("./struct.js");
|
||||||
|
module.exports.Skeleton = require("./skeleton.js");
|
||||||
|
|
628
src/skeleton.js
628
src/skeleton.js
|
@ -1,444 +1,244 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var Immutable = require("immutable");
|
const Immutable = require("immutable");
|
||||||
var Struct = require('./struct.js');
|
const Struct = require('./struct.js');
|
||||||
var $Special = require('./special.js');
|
const $Special = require('./special.js');
|
||||||
var Bag = require('./bag.js');
|
const Bag = require('./bag.js');
|
||||||
var Assertions = require('./assertions.js');
|
const Assertions = require('./assertions.js');
|
||||||
|
|
||||||
function die(message) {
|
const EVENT_ADDED = +1;
|
||||||
throw new Error(message);
|
const EVENT_REMOVED = -1;
|
||||||
|
const EVENT_MESSAGE = 0;
|
||||||
|
|
||||||
|
function Index() {
|
||||||
|
this.allAssertions = Bag.Bag();
|
||||||
|
this.root = new Node(new Continuation(Immutable.Set()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Skeletons" describe the indexed structure of a dataspace.
|
function Node(continuation) {
|
||||||
// In particular, they efficiently connect assertions to matching interests.
|
this.continuation = continuation;
|
||||||
|
|
||||||
function emptySkeleton(cache) {
|
|
||||||
return new SkNode(new SkCont(cache));
|
|
||||||
}
|
|
||||||
|
|
||||||
function SkNode(cont) {
|
|
||||||
this.cont = cont;
|
|
||||||
this.edges = Immutable.Map();
|
this.edges = Immutable.Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
function SkCont(cache) {
|
function Selector(popCount, index) {
|
||||||
this.cache = cache || Immutable.Map();
|
this.popCount = popCount;
|
||||||
this.table = Immutable.Map();
|
this.index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SkInterest(desc, constProj, key, varProj, handler,
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
// A VisibilityRestriction describes ... TODO
|
function projectPath(v, path) {
|
||||||
// (visibility-restriction SkProj Assertion)
|
path.forEach((index) => { v = v.get(index); return true; });
|
||||||
(struct visibility-restriction (path term) #:transparent)
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
// A ScopedAssertion is a VisibilityRestriction or an Assertion.
|
function projectPaths(v, paths) {
|
||||||
// (Corollary: Instances of `visibility-restriction` can never be assertions.)
|
return paths.map((path) => { return projectPath(v, path) });
|
||||||
|
}
|
||||||
|
|
||||||
// A `Skeleton` is a structural guard on an assertion: essentially,
|
function classOf(v) {
|
||||||
// specification of (the outline of) its shape; its silhouette.
|
if (v instanceof Struct.Structure) {
|
||||||
// Following a skeleton's structure leads to zero or more `SkCont`s.
|
return v.meta;
|
||||||
//
|
} else {
|
||||||
// Skeleton = (skeleton-node SkCont (AListof SkSelector (MutableHash SkClass SkNode)))
|
return v.size;
|
||||||
// SkSelector = (skeleton-selector Nat Nat)
|
}
|
||||||
// SkClass = StructType | (list-type Nat) | (vector-type Nat)
|
}
|
||||||
//
|
|
||||||
(struct skeleton-node (continuation [edges #:mutable]) #:transparent)
|
|
||||||
(struct skeleton-selector (pop-count index) #:transparent)
|
|
||||||
(struct list-type (arity) #:transparent)
|
|
||||||
(struct vector-type (arity) #:transparent)
|
|
||||||
//
|
|
||||||
// A `SkDesc` is a single assertion silhouette, usually the
|
|
||||||
// evaluation-result of `desc->skeleton-stx` from `pattern.rkt`.
|
|
||||||
//
|
|
||||||
// A `SkCont` is a *skeleton continuation*, a collection of "next
|
|
||||||
// steps" after a `Skeleton` has matched the general outline of an
|
|
||||||
// assertion.
|
|
||||||
//
|
|
||||||
// INVARIANT: At each level, the caches correspond to the
|
|
||||||
// appropriately filtered and projected contents of the dataspace
|
|
||||||
// containing the structures.
|
|
||||||
//
|
|
||||||
// SkCont = (skeleton-continuation
|
|
||||||
// (MutableSet ScopedAssertion)
|
|
||||||
// (MutableHash SkProj (MutableHash SkKey SkConst)))
|
|
||||||
// SkConst = (skeleton-matched-constant
|
|
||||||
// (MutableSet ScopedAssertion)
|
|
||||||
// (MutableHash SkProj SkAcc))
|
|
||||||
// SkAcc = (skeleton-accumulator
|
|
||||||
// (MutableBag SkKey)
|
|
||||||
// (MutableSeteq (... -> Any)))
|
|
||||||
//
|
|
||||||
(struct skeleton-continuation (cache table) #:transparent)
|
|
||||||
(struct skeleton-matched-constant (cache table) #:transparent)
|
|
||||||
(struct skeleton-accumulator (cache handlers) #:transparent)
|
|
||||||
//
|
|
||||||
// A `SkProj` is a *skeleton projection*, a specification of loci
|
|
||||||
// within a tree-shaped assertion to collect into a flat list.
|
|
||||||
//
|
|
||||||
// SkProj = (Listof (Listof Nat))
|
|
||||||
//
|
|
||||||
// The outer list specifies elements of the flat list; the inner lists
|
|
||||||
// specify paths via zero-indexed links to child nodes in the
|
|
||||||
// tree-shaped assertion being examined. A precondition for use of a
|
|
||||||
// `SkProj` is that the assertion being examined has been checked for
|
|
||||||
// conformance to the skeleton being projected.
|
|
||||||
//
|
|
||||||
// A `SkKey` is the result of running a `SkProj` over a term,
|
|
||||||
// extracting the values at the denoted locations.
|
|
||||||
//
|
|
||||||
// SkKey = (Listof Any)
|
|
||||||
//
|
|
||||||
// Each `SkProj` in `SkCont` selects *constant* portions of the term
|
|
||||||
// for more matching against the `SkKey`s in the table associated with
|
|
||||||
// the `SkProj`. Each `SkProj` in `SkConst`, if any, selects
|
|
||||||
// *variable* portions of the term to be given to the handler
|
|
||||||
// functions in the associated `SkAcc`.
|
|
||||||
|
|
||||||
// A `SkInterest` is a specification for an addition to or removal
|
Node.prototype.extend = function(skeleton) {
|
||||||
// from an existing `Skeleton`.
|
function walkNode(path, node, popCount, index, skeleton) {
|
||||||
//
|
if (skeleton === null) {
|
||||||
// SkInterest = (skeleton-interest SkDesc
|
return [popCount, node];
|
||||||
// SkProj
|
} else {
|
||||||
// SkKey
|
let selector = new Selector(popCount, index);
|
||||||
// SkProj
|
let cls = skeleton[0];
|
||||||
// (... -> Any)
|
let table = node.edges.get(selector, false);
|
||||||
// (Option ((MutableBag SkKey) -> Any)))
|
if (!table) {
|
||||||
//
|
table = Immutable.Map();
|
||||||
// The `SkDesc` gives the silhouette. The first `SkProj` is the
|
node.edges = node.edges.set(selector, table);
|
||||||
// constant-portion selector, to be matched against the `SkKey`. The
|
}
|
||||||
// second `SkProj` is used on matching assertions to extract the
|
let nextNode = table.get(cls, false);
|
||||||
// variable portions, to be passed to the handler function.
|
if (!nextNode) {
|
||||||
//
|
nextNode = new Node(new Continuation(
|
||||||
(struct skeleton-interest (desc
|
node.continuation.cachedAssertions.filter((a) => {
|
||||||
const-selector
|
return classOf(project(a, path)) === cls;
|
||||||
const-value
|
})));
|
||||||
var-selector
|
table = table.set(cls, nextNode);
|
||||||
handler
|
node.edges = node.edges.set(selector, table);
|
||||||
cleanup
|
}
|
||||||
) #:transparent)
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(define (make-empty-skeleton/cache cache)
|
let [_popCount, finalNode] = walkNode(Immutable.List(), this, 0, 0, skeleton);
|
||||||
(skeleton-node (skeleton-continuation cache
|
return finalNode.continuation;
|
||||||
(make-hash))
|
};
|
||||||
'()))
|
|
||||||
|
|
||||||
(define (make-empty-skeleton)
|
Index.prototype.addHandler = function(skeleton, constPaths, constVals, capturePaths, callback) {
|
||||||
(make-empty-skeleton/cache (make-hash)))
|
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) => {
|
||||||
|
return 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((captures) => {
|
||||||
|
callback(EVENT_ADDED, captures);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
(define (skcont-add! c i)
|
Index.prototype.removeHandler = function(skeleton, constPaths, constVals, capturePaths, callback) {
|
||||||
(match-define (skeleton-interest _desc cs cv vs h _cleanup) i)
|
let continuation = this.root.extend(skeleton);
|
||||||
(define (make-matched-constant)
|
let constValMap = continuation.leafMap.get(constPaths, false);
|
||||||
(define assertions (make-hash))
|
if (!constValMap) return;
|
||||||
(hash-for-each (skeleton-continuation-cache c)
|
let leaf = constValMap.get(constVals, false);
|
||||||
(lambda (a _)
|
if (!leaf) return;
|
||||||
(when (equal? (apply-projection (unscope-assertion a) cs) cv)
|
let handler = leaf.handlerMap.get(capturePaths, false);
|
||||||
(hash-set! assertions a #t))))
|
if (!handler) return;
|
||||||
(skeleton-matched-constant assertions (make-hash)))
|
handler.callbacks = handler.callbacks.remove(callback);
|
||||||
(define cvt (hash-ref! (skeleton-continuation-table c) cs make-hash))
|
if (handler.callbacks.isEmpty()) {
|
||||||
(define sc (hash-ref! cvt cv make-matched-constant))
|
leaf.handlerMap = leaf.handlerMap.remove(capturePaths);
|
||||||
(define (make-accumulator)
|
}
|
||||||
(define cache (make-bag))
|
if (leaf.handlerMap.isEmpty()) {
|
||||||
(hash-for-each (skeleton-matched-constant-cache sc)
|
constValMap = constValMap.remove(constVals);
|
||||||
(lambda (a _)
|
}
|
||||||
(unpack-scoped-assertion [restriction-path term] a)
|
if (constValMap.isEmpty()) {
|
||||||
(when (or (not restriction-path) (equal? restriction-path vs))
|
continuation.leafMap.remove(constPaths);
|
||||||
(bag-change! cache (apply-projection term vs) 1))))
|
} else {
|
||||||
(skeleton-accumulator cache (make-hasheq)))
|
continuation.leafMap.set(constPaths, constValMap);
|
||||||
(define acc (hash-ref! (skeleton-matched-constant-table sc) vs make-accumulator))
|
}
|
||||||
(hash-set! (skeleton-accumulator-handlers acc) h #t)
|
};
|
||||||
(for [(vars (in-bag (skeleton-accumulator-cache acc)))] (apply h '+ vars)))
|
|
||||||
|
|
||||||
(define (skcont-remove! c i)
|
Node.prototype.modify = function(outerValue, m_cont, m_leaf, m_handler) {
|
||||||
(match-define (skeleton-interest _desc cs cv vs h cleanup) i)
|
function walkNode(node, termStack) {
|
||||||
(define cvt (hash-ref (skeleton-continuation-table c) cs #f))
|
walkContinuation(node.continuation);
|
||||||
(when cvt
|
node.edges.forEach((table, selector) => {
|
||||||
(define sc (hash-ref cvt cv #f))
|
let nextStack = termStack.withMutations((mutable) => {
|
||||||
(when sc
|
let i = selector.popCount;
|
||||||
(define acc (hash-ref (skeleton-matched-constant-table sc) vs #f))
|
while (i--) { mutable.pop(); }
|
||||||
(when acc
|
});
|
||||||
(when (and cleanup (hash-has-key? (skeleton-accumulator-handlers acc) h))
|
let nextValue = nextStack.first().get(selector.index);
|
||||||
(cleanup (skeleton-accumulator-cache acc)))
|
let cls = classOf(nextValue);
|
||||||
(hash-remove! (skeleton-accumulator-handlers acc) h)
|
let nextNode = table.get(cls, false);
|
||||||
(when (hash-empty? (skeleton-accumulator-handlers acc))
|
if (nextNode) {
|
||||||
(hash-remove! (skeleton-matched-constant-table sc) vs)))
|
walkNode(nextNode, nextStack.push(nextValue));
|
||||||
(when (hash-empty? (skeleton-matched-constant-table sc))
|
}
|
||||||
(hash-remove! cvt cv)))
|
return true;
|
||||||
(when (hash-empty? cvt)
|
});
|
||||||
(hash-remove! (skeleton-continuation-table c) cs))))
|
}
|
||||||
|
|
||||||
(define (term-matches-class? term class)
|
function walkContinuation(continuation) {
|
||||||
(cond
|
m_cont(continuation, outerValue);
|
||||||
[(list-type? class) (and (list? term) (= (length term) (list-type-arity class)))]
|
continuation.leafMap.forEach((constValMap, constPaths) => {
|
||||||
[(vector-type? class) (and (vector? term) (= (vector-length term) (vector-type-arity class)))]
|
let constVals = projectPaths(outerValue, constPaths);
|
||||||
[(struct-type? class) (and (non-object-struct? term) (eq? (struct->struct-type term) class))]
|
let leaf = constValMap.get(constVals, false);
|
||||||
[else (error 'term-matches-class? "Invalid class: ~v" class)]))
|
if (leaf) {
|
||||||
|
m_leaf(leaf, outerValue);
|
||||||
|
leaf.handlerMap.forEach((handler, capturePaths) => {
|
||||||
|
m_handler(handler, projectPaths(outerValue, capturePaths));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
(define (subterm-matches-class? term path class)
|
walkNode(this, Immutable.Stack().push(Immutable.List([outerValue])));
|
||||||
(term-matches-class? (apply-projection-path (unscope-assertion term) path) class))
|
};
|
||||||
|
|
||||||
(define (unscope-assertion scoped-assertion)
|
function add_to_cont(c, v) { c.cachedAssertions = c.cachedAssertions.add(v); }
|
||||||
(match scoped-assertion
|
function add_to_leaf(l, v) { l.cachedAssertions = l.cachedAssertions.add(v); }
|
||||||
[(visibility-restriction _ term) term]
|
function add_to_handler(h, vs) {
|
||||||
[term term]))
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(define-syntax-rule (unpack-scoped-assertion [path term] expr)
|
function del_from_cont(c, v) { c.cachedAssertions = c.cachedAssertions.remove(v); }
|
||||||
(define-values (path term)
|
function del_from_leaf(l, v) { l.cachedAssertions = l.cachedAssertions.remove(v); }
|
||||||
(match expr
|
function del_from_handler(h, vs) {
|
||||||
[(visibility-restriction p t) (values p t)]
|
h.cachedCaptures = Bag.change(h.cachedCaptures, vs, -1).bag;
|
||||||
[other (values #f other)])))
|
h.callbacks.forEach((cb) => {
|
||||||
|
cb(EVENT_REMOVED, vs);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
(define (update-path path pop-count index)
|
Index.prototype.adjustAssertion = function(outerValue, delta) {
|
||||||
(append (drop-right path pop-count) (list index)))
|
let net;
|
||||||
|
({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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
(define (extend-skeleton! sk desc)
|
Index.prototype.addAssertion = function(v) { this.adjustAssertion(v, +1); };
|
||||||
(define (walk-node! path sk pop-count index desc)
|
Index.prototype.removeAssertion = function (v) { this.adjustAssertion(v, -1); };
|
||||||
(match desc
|
|
||||||
[(list class-desc pieces ...)
|
|
||||||
(define class
|
|
||||||
(cond [(struct-type? class-desc) class-desc]
|
|
||||||
[(eq? class-desc 'list) (list-type (length pieces))]
|
|
||||||
[(eq? class-desc 'vector) (vector-type (length pieces))]
|
|
||||||
[else (error 'extend-skeleton! "Invalid class-desc: ~v" class-desc)]))
|
|
||||||
(define selector (skeleton-selector pop-count index))
|
|
||||||
(define table
|
|
||||||
(match (assoc selector (skeleton-node-edges sk))
|
|
||||||
[#f (let ((table (make-hash)))
|
|
||||||
(set-skeleton-node-edges! sk (cons (cons selector table) (skeleton-node-edges sk)))
|
|
||||||
table)]
|
|
||||||
[(cons _selector table) table]))
|
|
||||||
(define (make-skeleton-node-with-cache)
|
|
||||||
(define unfiltered (skeleton-continuation-cache (skeleton-node-continuation sk)))
|
|
||||||
(define filtered (make-hash))
|
|
||||||
(hash-for-each unfiltered
|
|
||||||
(lambda (a _)
|
|
||||||
(when (subterm-matches-class? a path class)
|
|
||||||
(hash-set! filtered a #t))))
|
|
||||||
(make-empty-skeleton/cache filtered))
|
|
||||||
(define next (hash-ref! table class make-skeleton-node-with-cache))
|
|
||||||
(walk-edge! (update-path path pop-count 0) next 0 0 pieces)]
|
|
||||||
[_
|
|
||||||
(values pop-count sk)]))
|
|
||||||
(define (walk-edge! path sk pop-count index pieces)
|
|
||||||
(match pieces
|
|
||||||
['()
|
|
||||||
(values (+ pop-count 1) sk)]
|
|
||||||
[(cons p pieces)
|
|
||||||
(let-values (((pop-count sk) (walk-node! path sk pop-count index p)))
|
|
||||||
(walk-edge! (update-path path 1 (+ index 1)) sk pop-count (+ index 1) pieces))]))
|
|
||||||
(let-values (((_pop-count sk) (walk-edge! '(0) sk 0 0 (list desc))))
|
|
||||||
sk))
|
|
||||||
|
|
||||||
(define (add-interest! sk i)
|
Index.prototype.sendMessage = function(v) {
|
||||||
(let ((sk (extend-skeleton! sk (skeleton-interest-desc i))))
|
this.root.modify(v, ()=>{}, ()=>{}, (h, vs) => {
|
||||||
(skcont-add! (skeleton-node-continuation sk) i)))
|
h.callbacks.forEach((cb) => {
|
||||||
|
cb(EVENT_MESSAGE, vs);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
(define (remove-interest! sk i)
|
///////////////////////////////////////////////////////////////////////////
|
||||||
(let ((sk (extend-skeleton! sk (skeleton-interest-desc i))))
|
|
||||||
(skcont-remove! (skeleton-node-continuation sk) i)))
|
|
||||||
|
|
||||||
(define (skeleton-modify! sk term0 modify-skcont! modify-skconst! modify-skacc!)
|
module.exports.EVENT_ADDED = EVENT_ADDED;
|
||||||
(unpack-scoped-assertion [restriction-path term0-term] term0)
|
module.exports.EVENT_REMOVED = EVENT_REMOVED;
|
||||||
|
module.exports.EVENT_MESSAGE = EVENT_MESSAGE;
|
||||||
(define (walk-node! sk term-stack)
|
module.exports.Index = Index;
|
||||||
(match-define (skeleton-node continuation edges) sk)
|
|
||||||
|
|
||||||
(modify-skcont! continuation term0)
|
|
||||||
(hash-for-each (skeleton-continuation-table continuation)
|
|
||||||
(lambda (constant-proj key-proj-handler)
|
|
||||||
(define constants (apply-projection term0-term constant-proj))
|
|
||||||
(define proj-handler (hash-ref key-proj-handler constants #f))
|
|
||||||
(when proj-handler
|
|
||||||
(modify-skconst! proj-handler term0)
|
|
||||||
(hash-for-each (skeleton-matched-constant-table proj-handler)
|
|
||||||
(lambda (variable-proj acc)
|
|
||||||
// (when restriction-path
|
|
||||||
// (log-info "Restriction path ~v in effect; variable-proj is ~v, and term is ~v"
|
|
||||||
// restriction-path
|
|
||||||
// variable-proj
|
|
||||||
// term0))
|
|
||||||
(when (or (not restriction-path)
|
|
||||||
(equal? restriction-path variable-proj))
|
|
||||||
(define variables (apply-projection term0-term variable-proj))
|
|
||||||
(modify-skacc! acc variables term0)))))))
|
|
||||||
|
|
||||||
(for [(edge (in-list edges))]
|
|
||||||
(match-define (cons (skeleton-selector pop-count index) table) edge)
|
|
||||||
(define popped-stack (drop term-stack pop-count))
|
|
||||||
(define pieces (car popped-stack))
|
|
||||||
(define term (vector-ref pieces (+ index 1))) // adjust for struct identifier at beginning
|
|
||||||
(define entry (hash-ref table
|
|
||||||
(cond [(non-object-struct? term) (struct->struct-type term)]
|
|
||||||
[(list? term) (list-type (length term))]
|
|
||||||
[(vector? term) (vector-type (vector-length term))]
|
|
||||||
[else #f])
|
|
||||||
#f))
|
|
||||||
(when entry
|
|
||||||
(define new-pieces
|
|
||||||
(cond [(non-object-struct? term) (struct->vector term)]
|
|
||||||
[(list? term) (list->vector (cons 'list term))]
|
|
||||||
[(vector? term) (list->vector (cons 'list (vector->list term)))]))
|
|
||||||
(walk-node! entry (cons new-pieces popped-stack)))))
|
|
||||||
|
|
||||||
(walk-node! sk (list (vector 'list term0-term))))
|
|
||||||
|
|
||||||
(define (add-term-to-skcont! skcont term)
|
|
||||||
(hash-set! (skeleton-continuation-cache skcont) term #t))
|
|
||||||
(define (add-term-to-skconst! skconst term)
|
|
||||||
(hash-set! (skeleton-matched-constant-cache skconst) term #t))
|
|
||||||
(define (add-term-to-skacc! skacc vars _term)
|
|
||||||
// (log-info ">>>>>> At addition time for ~v, cache has ~v"
|
|
||||||
// _term
|
|
||||||
// (hash-ref (skeleton-accumulator-cache skacc) vars 0))
|
|
||||||
(match (bag-change! (skeleton-accumulator-cache skacc) vars 1)
|
|
||||||
['absent->present
|
|
||||||
(hash-for-each (skeleton-accumulator-handlers skacc)
|
|
||||||
(lambda (handler _) (apply handler '+ vars)))]
|
|
||||||
// 'present->absent and 'absent->absent absurd
|
|
||||||
['present->present
|
|
||||||
(void)]))
|
|
||||||
|
|
||||||
(define (add-assertion! sk term)
|
|
||||||
(skeleton-modify! sk
|
|
||||||
term
|
|
||||||
add-term-to-skcont!
|
|
||||||
add-term-to-skconst!
|
|
||||||
add-term-to-skacc!))
|
|
||||||
|
|
||||||
(define (remove-term-from-skcont! skcont term)
|
|
||||||
(hash-remove! (skeleton-continuation-cache skcont) term))
|
|
||||||
(define (remove-term-from-skconst! skconst term)
|
|
||||||
(hash-remove! (skeleton-matched-constant-cache skconst) term))
|
|
||||||
(define (remove-term-from-skacc! skacc vars _term)
|
|
||||||
(define cache (skeleton-accumulator-cache skacc))
|
|
||||||
// (log-info ">>>>>> At removal time for ~v, cache has ~v" _term (hash-ref cache vars 0))
|
|
||||||
(if (bag-member? cache vars)
|
|
||||||
(match (bag-change! cache vars -1)
|
|
||||||
['present->absent
|
|
||||||
(hash-for-each (skeleton-accumulator-handlers skacc)
|
|
||||||
(lambda (handler _) (apply handler '- vars)))]
|
|
||||||
// 'absent->absent and 'absent->present absurd
|
|
||||||
['present->present
|
|
||||||
(void)])
|
|
||||||
(log-warning "Removing assertion not previously added: ~v" _term)))
|
|
||||||
|
|
||||||
(define (remove-assertion! sk term)
|
|
||||||
(skeleton-modify! sk
|
|
||||||
term
|
|
||||||
remove-term-from-skcont!
|
|
||||||
remove-term-from-skconst!
|
|
||||||
remove-term-from-skacc!))
|
|
||||||
|
|
||||||
(define (send-assertion! sk term)
|
|
||||||
(skeleton-modify! sk
|
|
||||||
term
|
|
||||||
void
|
|
||||||
void
|
|
||||||
(lambda (skacc vars _term)
|
|
||||||
(hash-for-each (skeleton-accumulator-handlers skacc)
|
|
||||||
(lambda (handler _) (apply handler '! vars))))))
|
|
||||||
|
|
||||||
// TODO: avoid repeated descent into `term` by factoring out prefixes of paths in `proj`
|
|
||||||
(define (apply-projection term proj)
|
|
||||||
(for/list [(path (in-list proj))]
|
|
||||||
(apply-projection-path term path)))
|
|
||||||
|
|
||||||
(define (apply-projection-path term path)
|
|
||||||
(for/fold [(term (list term))] [(index (in-list path))]
|
|
||||||
(cond [(non-object-struct? term) (vector-ref (struct->vector term) (+ index 1))]
|
|
||||||
[(list? term) (list-ref term index)]
|
|
||||||
[(vector? term) (vector-ref term index)]
|
|
||||||
[else (error 'apply-projection "Term representation not supported: ~v" term)])))
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
(module+ test
|
|
||||||
(struct a (x y) #:transparent)
|
|
||||||
(struct b (v) #:transparent)
|
|
||||||
(struct c (v) #:transparent)
|
|
||||||
(struct d (x y z) #:transparent)
|
|
||||||
|
|
||||||
(define sk
|
|
||||||
(make-empty-skeleton/cache
|
|
||||||
(make-hash (for/list [(x (list (a (b 'bee) (b 'cat))
|
|
||||||
(a (b 'foo) (c 'bar))
|
|
||||||
(a (b 'foo) (c 'BAR))
|
|
||||||
(a (c 'bar) (b 'foo))
|
|
||||||
(a (c 'dog) (c 'fox))
|
|
||||||
(d (b 'DBX) (b 'DBY) (b 'DBZ))
|
|
||||||
(d (c 'DCX) (c 'DCY) (c 'DCZ))
|
|
||||||
(b 'zot)
|
|
||||||
123))]
|
|
||||||
(cons x #t)))))
|
|
||||||
|
|
||||||
(define i1
|
|
||||||
(skeleton-interest (list struct:a (list struct:b #f) #f)
|
|
||||||
'((0 0 0))
|
|
||||||
'(foo)
|
|
||||||
'((0 1))
|
|
||||||
(lambda (op . bindings)
|
|
||||||
(printf "xAB HANDLER: ~v ~v\n" op bindings))
|
|
||||||
(lambda (vars)
|
|
||||||
(printf "xAB CLEANUP: ~v\n" vars))))
|
|
||||||
|
|
||||||
(add-interest! sk i1)
|
|
||||||
|
|
||||||
(void (extend-skeleton! sk (list struct:a (list struct:b #f) #f)))
|
|
||||||
(void (extend-skeleton! sk (list struct:a #f (list struct:c #f))))
|
|
||||||
(void (extend-skeleton! sk (list struct:a #f (list struct:c (list struct:b #f)))))
|
|
||||||
(void (extend-skeleton! sk (list struct:a #f #f)))
|
|
||||||
(void (extend-skeleton! sk (list struct:c #f)))
|
|
||||||
(void (extend-skeleton! sk (list struct:b #f)))
|
|
||||||
(void (extend-skeleton! sk (list struct:d (list struct:b #f) #f (list struct:b #f))))
|
|
||||||
(void (extend-skeleton! sk (list struct:d (list struct:b #f) #f (list struct:c #f))))
|
|
||||||
(void (extend-skeleton! sk (list struct:d (list struct:c #f) #f (list struct:b #f))))
|
|
||||||
(void (extend-skeleton! sk (list struct:d (list struct:c #f) #f (list struct:c #f))))
|
|
||||||
(check-eq? sk (extend-skeleton! sk #f))
|
|
||||||
|
|
||||||
(add-interest! sk
|
|
||||||
(skeleton-interest (list struct:d (list struct:b #f) #f (list struct:c #f))
|
|
||||||
'((0 2 0))
|
|
||||||
'(DCZ)
|
|
||||||
'((0) (0 0) (0 0 0) (0 1))
|
|
||||||
(lambda (op . bindings)
|
|
||||||
(printf "DBC HANDLER: ~v ~v\n" op bindings))
|
|
||||||
(lambda (vars)
|
|
||||||
(printf "DBC CLEANUP: ~v\n" vars))))
|
|
||||||
|
|
||||||
(remove-assertion! sk (a (b 'foo) (c 'bar)))
|
|
||||||
(remove-assertion! sk (d (b 'B1) (b 'DBY) (c 'DCZ)))
|
|
||||||
(add-assertion! sk (d (b 'B1) (b 'DBY) (c 'DCZ)))
|
|
||||||
(add-assertion! sk (d (b 'BX) (b 'DBY) (c 'DCZ)))
|
|
||||||
(add-assertion! sk (d (b 'B1) (b 'DBY) (c 'CX)))
|
|
||||||
(add-assertion! sk (d (b 'B1) (b 'DBY) (c 'DCZ)))
|
|
||||||
(add-assertion! sk (d (b 'BX) (b 'DBY) (c 'DCZ)))
|
|
||||||
(add-assertion! sk (d (b 'B1) (b 'DBY) (c 'CX)))
|
|
||||||
|
|
||||||
(add-interest! sk
|
|
||||||
(skeleton-interest (list struct:d #f (list struct:b #f) #f)
|
|
||||||
'((0 1 0))
|
|
||||||
'(DBY)
|
|
||||||
'((0 0) (0 2))
|
|
||||||
(lambda (op . bindings)
|
|
||||||
(printf "xDB HANDLER: ~v ~v\n" op bindings))
|
|
||||||
(lambda (vars)
|
|
||||||
(printf "xDB CLEANUP: ~v\n" vars))))
|
|
||||||
|
|
||||||
(send-assertion! sk (d (b 'BX) (b 'DBY) (c 'DCZ)))
|
|
||||||
(send-assertion! sk (d (b 'BX) (b 'DBY) (c 'DCZ)))
|
|
||||||
|
|
||||||
(remove-assertion! sk (d (b 'B1) (b 'DBY) (c 'DCZ)))
|
|
||||||
(remove-assertion! sk (d (b 'BX) (b 'DBY) (c 'DCZ)))
|
|
||||||
(remove-assertion! sk (d (b 'B1) (b 'DBY) (c 'CX)))
|
|
||||||
(remove-assertion! sk (d (b 'B1) (b 'DBY) (c 'DCZ)))
|
|
||||||
(remove-assertion! sk (d (b 'BX) (b 'DBY) (c 'DCZ)))
|
|
||||||
(remove-assertion! sk (d (b 'B1) (b 'DBY) (c 'CX)))
|
|
||||||
// sk
|
|
||||||
|
|
||||||
(remove-interest! sk i1)
|
|
||||||
)
|
|
||||||
|
|
|
@ -30,6 +30,10 @@ StructureType.prototype.equals = function (other) {
|
||||||
return this.arity === other.arity && this.label === other.label;
|
return this.arity === other.arity && this.label === other.label;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
StructureType.prototype.hashCode = function () {
|
||||||
|
return Immutable.List([this.label, this.arity]).hashCode();
|
||||||
|
};
|
||||||
|
|
||||||
StructureType.prototype.instantiate = function (fields) {
|
StructureType.prototype.instantiate = function (fields) {
|
||||||
return new Structure(this, fields);
|
return new Structure(this, fields);
|
||||||
};
|
};
|
||||||
|
@ -65,6 +69,33 @@ Structure.prototype.get = function (index) {
|
||||||
Structure.prototype.set = function (index, value) {
|
Structure.prototype.set = function (index, value) {
|
||||||
var s = this.clone();
|
var s = this.clone();
|
||||||
s[index] = s.fields[index] = value;
|
s[index] = s.fields[index] = value;
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
Structure.prototype.equals = function (other) {
|
||||||
|
if (!other) return false;
|
||||||
|
if (!(other instanceof Structure)) return false;
|
||||||
|
if (!other.meta.equals(this.meta)) return false;
|
||||||
|
for (let i = 0; i < this.length; i++) {
|
||||||
|
if (this[i] === other[i]) continue;
|
||||||
|
if (!this[i].equals(other[i])) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Structure.prototype.hashCode = function () {
|
||||||
|
return Immutable.List(this.fields).unshift(this.meta).hashCode();
|
||||||
|
};
|
||||||
|
|
||||||
|
Structure.prototype.toString = function () {
|
||||||
|
let b = this.meta.label + "(";
|
||||||
|
let needComma = false;
|
||||||
|
for (let v of this.fields) {
|
||||||
|
if (needComma) b = b + ", ";
|
||||||
|
needComma = true;
|
||||||
|
b = b + JSON.stringify(v);
|
||||||
|
}
|
||||||
|
return b + ")";
|
||||||
};
|
};
|
||||||
|
|
||||||
function reviveStructs(j) {
|
function reviveStructs(j) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var expect = require('expect.js');
|
const expect = require('chai').expect;
|
||||||
var Immutable = require('immutable');
|
const Immutable = require('immutable');
|
||||||
var Bag = require('../src/bag.js');
|
const Bag = require('../src/bag.js');
|
||||||
|
|
||||||
describe('immutable bag', function () {
|
describe('immutable bag', function () {
|
||||||
it('should be initializable from a set', function () {
|
it('should be initializable from a set', function () {
|
||||||
|
|
|
@ -1,6 +1,79 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var expect = require('expect.js');
|
const chai = require('chai');
|
||||||
var Immutable = require('immutable');
|
const expect = chai.expect;
|
||||||
|
chai.use(require('chai-immutable'));
|
||||||
|
|
||||||
var Syndicate = require('../src/main.js');
|
const Immutable = require('immutable');
|
||||||
|
|
||||||
|
const Syndicate = require('../src/main.js');
|
||||||
|
const Skeleton = Syndicate.Skeleton;
|
||||||
|
const Struct = Syndicate.Struct;
|
||||||
|
|
||||||
|
const Event = Struct.makeConstructor('Event', ['label', 'type', 'values']);
|
||||||
|
|
||||||
|
describe('simple list assertions trace', () => {
|
||||||
|
let trace = Immutable.List();
|
||||||
|
let i = new Skeleton.Index();
|
||||||
|
i.addHandler([3, null, null, null],
|
||||||
|
Immutable.fromJS([[0]]),
|
||||||
|
Immutable.fromJS(["hi"]),
|
||||||
|
Immutable.fromJS([[1],[2]]),
|
||||||
|
(e, vs) => {
|
||||||
|
trace = trace.push(Event("3-EVENT", e, vs));
|
||||||
|
});
|
||||||
|
i.addHandler([2, null, null],
|
||||||
|
Immutable.fromJS([]),
|
||||||
|
Immutable.fromJS([]),
|
||||||
|
Immutable.fromJS([]),
|
||||||
|
(e, vs) => {
|
||||||
|
trace = trace.push(Event("2-EVENT", e, vs));
|
||||||
|
});
|
||||||
|
i.addAssertion(Immutable.fromJS(["hi", 123, 234]));
|
||||||
|
i.addAssertion(Immutable.fromJS(["hi", 999, 999]));
|
||||||
|
i.addAssertion(Immutable.fromJS(["hi", 123]));
|
||||||
|
i.addAssertion(Immutable.fromJS(["hi", 123, 234]));
|
||||||
|
i.sendMessage(Immutable.fromJS(["hi", 303]));
|
||||||
|
i.sendMessage(Immutable.fromJS(["hi", 303, 404]));
|
||||||
|
i.sendMessage(Immutable.fromJS(["hi", 303, 404, 808]));
|
||||||
|
i.removeAssertion(Immutable.fromJS(["hi", 123, 234]));
|
||||||
|
i.removeAssertion(Immutable.fromJS(["hi", 999, 999]));
|
||||||
|
i.removeAssertion(Immutable.fromJS(["hi", 123, 234]));
|
||||||
|
i.addAssertion(Immutable.fromJS(["hi", 123]));
|
||||||
|
i.addAssertion(Immutable.fromJS(["hi", 234]));
|
||||||
|
i.removeAssertion(Immutable.fromJS(["hi", 123]));
|
||||||
|
i.removeAssertion(Immutable.fromJS(["hi", 123]));
|
||||||
|
i.removeAssertion(Immutable.fromJS(["hi", 234]));
|
||||||
|
it('should have 8 entries', () => {
|
||||||
|
expect(trace.size).to.equal(8);
|
||||||
|
});
|
||||||
|
it('should have two 3-EVENT adds', () => {
|
||||||
|
expect(trace.filter((e) => { return e[0] === "3-EVENT" && e[1] === Skeleton.EVENT_ADDED; }))
|
||||||
|
.to.equal(Immutable.List([
|
||||||
|
Event("3-EVENT", Skeleton.EVENT_ADDED, Immutable.List([123, 234])),
|
||||||
|
Event("3-EVENT", Skeleton.EVENT_ADDED, Immutable.List([999, 999]))]));
|
||||||
|
});
|
||||||
|
it('should have two 3-EVENT removals', () => {
|
||||||
|
expect(trace.filter((e) => { return e[0] === "3-EVENT" && e[1] === Skeleton.EVENT_REMOVED; }))
|
||||||
|
.to.equal(Immutable.List([
|
||||||
|
Event("3-EVENT", Skeleton.EVENT_REMOVED, Immutable.List([999, 999])),
|
||||||
|
Event("3-EVENT", Skeleton.EVENT_REMOVED, Immutable.List([123, 234]))]));
|
||||||
|
});
|
||||||
|
it('should have one 2-EVENT add', () => {
|
||||||
|
expect(trace.filter((e) => { return e[0] === "2-EVENT" && e[1] === Skeleton.EVENT_ADDED; }))
|
||||||
|
.to.equal(Immutable.List([
|
||||||
|
Event("2-EVENT", Skeleton.EVENT_ADDED, Immutable.List([]))]));
|
||||||
|
});
|
||||||
|
it('should have one 2-EVENT removal', () => {
|
||||||
|
expect(trace.filter((e) => { return e[0] === "2-EVENT" && e[1] === Skeleton.EVENT_REMOVED; }))
|
||||||
|
.to.equal(Immutable.List([
|
||||||
|
Event("2-EVENT", Skeleton.EVENT_REMOVED, Immutable.List([]))]));
|
||||||
|
});
|
||||||
|
it('should have two messages', () => {
|
||||||
|
expect(trace.filter((e) => { return e[1] === Skeleton.EVENT_MESSAGE; }))
|
||||||
|
.to.equal(Immutable.List([
|
||||||
|
Event("2-EVENT", Skeleton.EVENT_MESSAGE, Immutable.List([])),
|
||||||
|
Event("3-EVENT", Skeleton.EVENT_MESSAGE, Immutable.List([303, 404]))]));
|
||||||
|
});
|
||||||
|
trace.forEach((e) => { console.log(e.toString()) });
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue