First JavaScript steps, based on HOWITWORKS.md

This commit is contained in:
Tony Garnock-Jones 2018-10-21 00:58:40 +01:00
parent da121230b9
commit c26186f871
7 changed files with 391 additions and 432 deletions

73
package-lock.json generated
View File

@ -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
} }
} }
} }

View File

@ -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"
} }

View File

@ -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");

View File

@ -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)
)

View File

@ -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) {

View File

@ -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 () {

View File

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