Basic dynamic assertion analysis
This commit is contained in:
parent
b8c967f941
commit
35350dc740
|
@ -1,5 +1,10 @@
|
|||
"use strict";
|
||||
|
||||
const Struct = require('./struct.js');
|
||||
const Skeleton = require('./skeleton.js');
|
||||
|
||||
module.exports.Bag = require("./bag.js");
|
||||
module.exports.Struct = require("./struct.js");
|
||||
module.exports.Skeleton = require("./skeleton.js");
|
||||
module.exports.Struct = Struct;
|
||||
module.exports.Skeleton = Skeleton;
|
||||
module.exports.__ = Struct.__;
|
||||
module.exports._$ = Skeleton._$;
|
||||
|
|
|
@ -6,6 +6,8 @@ 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;
|
||||
|
@ -101,7 +103,8 @@ Node.prototype.extend = function(skeleton) {
|
|||
return finalNode.continuation;
|
||||
};
|
||||
|
||||
Index.prototype.addHandler = function(skeleton, constPaths, constVals, capturePaths, callback) {
|
||||
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) {
|
||||
|
@ -135,7 +138,8 @@ Index.prototype.addHandler = function(skeleton, constPaths, constVals, capturePa
|
|||
});
|
||||
};
|
||||
|
||||
Index.prototype.removeHandler = function(skeleton, constPaths, constVals, capturePaths, callback) {
|
||||
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;
|
||||
|
@ -248,7 +252,68 @@ Index.prototype.sendMessage = function(v) {
|
|||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 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 = (typeof pattern === 'undefined' ? __ : 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;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
"use strict";
|
||||
// "Structures": Simple named-tuple-like records.
|
||||
|
||||
var Immutable = require("immutable");
|
||||
var $Special = require('./special.js');
|
||||
const Immutable = require("immutable");
|
||||
const $Special = require('./special.js');
|
||||
|
||||
/* Defined here rather than elsewhere because we need it in makeConstructor. */
|
||||
var __ = new $Special("wildcard"); /* wildcard marker */
|
||||
const __ = new $Special("wildcard"); /* wildcard marker */
|
||||
|
||||
function StructureType(label, arity) {
|
||||
this.label = label;
|
||||
|
|
|
@ -9,6 +9,8 @@ const Immutable = require('immutable');
|
|||
const Syndicate = require('../src/main.js');
|
||||
const Skeleton = Syndicate.Skeleton;
|
||||
const Struct = Syndicate.Struct;
|
||||
const __ = Syndicate.__;
|
||||
const _$ = Syndicate._$;
|
||||
|
||||
const Event = Struct.makeConstructor('Event', ['label', 'type', 'values']);
|
||||
|
||||
|
@ -26,34 +28,85 @@ function skeletonTrace(f) {
|
|||
return traceHolder.trace;
|
||||
}
|
||||
|
||||
describe('skeleton tests', () => {
|
||||
describe('skeleton', () => {
|
||||
|
||||
const A = Struct.makeConstructor('A', ['x', 'y']);
|
||||
const B = Struct.makeConstructor('B', ['v']);
|
||||
const C = Struct.makeConstructor('C', ['v']);
|
||||
|
||||
describe('pattern analysis', () => {
|
||||
it('should handle leaf captures', () => {
|
||||
expect(Immutable.fromJS(Skeleton.analyzeAssertion(A(B(_$), _$))))
|
||||
.to.equal(Immutable.fromJS({skeleton: [A.meta, [B.meta, null], null],
|
||||
constPaths: Immutable.fromJS([]),
|
||||
constVals: Immutable.fromJS([]),
|
||||
capturePaths: Immutable.fromJS([[0, 0], [1]])}));
|
||||
});
|
||||
it('should handle atomic constants', () => {
|
||||
expect(Immutable.fromJS(Skeleton.analyzeAssertion(A(B("x"), _$))))
|
||||
.to.equal(Immutable.fromJS({skeleton: [A.meta, [B.meta, null], null],
|
||||
constPaths: Immutable.fromJS([[0, 0]]),
|
||||
constVals: Immutable.fromJS(["x"]),
|
||||
capturePaths: Immutable.fromJS([[1]])}));
|
||||
});
|
||||
it('should handle complex constants (1)', () => {
|
||||
// Marker: (***)
|
||||
// Really this comes about when compiled code has no static
|
||||
// visibility into the value of a constant, and that constant
|
||||
// will end up being complex at runtime. We can't properly test
|
||||
// that situation without the static analysis half of the code.
|
||||
// TODO later.
|
||||
let complexPlaceholder = new Object();
|
||||
expect(Immutable.fromJS(Skeleton.analyzeAssertion(A(complexPlaceholder, C(_$)))))
|
||||
.to.equal(Immutable.fromJS({skeleton: [A.meta, null, [C.meta, null]],
|
||||
constPaths: Immutable.fromJS([[0]]),
|
||||
constVals: Immutable.fromJS([complexPlaceholder]),
|
||||
capturePaths: Immutable.fromJS([[1, 0]])}));
|
||||
});
|
||||
it('should handle complex constants (2)', () => {
|
||||
// Marker: (***)
|
||||
// Really this comes about when compiled code has no static
|
||||
// visibility into the value of a constant, and that constant
|
||||
// will end up being complex at runtime. We can't properly test
|
||||
// that situation without the static analysis half of the code.
|
||||
// TODO later.
|
||||
expect(Immutable.fromJS(Skeleton.analyzeAssertion(A(B(B("y")), _$("rhs", C(__))))))
|
||||
.to.equal(Immutable.fromJS({skeleton: [A.meta, [B.meta, [B.meta, null]], [C.meta, null]],
|
||||
constPaths: Immutable.fromJS([[0, 0, 0]]),
|
||||
constVals: Immutable.fromJS(["y"]),
|
||||
capturePaths: Immutable.fromJS([[1]])}));
|
||||
});
|
||||
it('should handle list patterns with discards', () => {
|
||||
expect(Immutable.fromJS(Skeleton.analyzeAssertion([__, __])))
|
||||
.to.equal(Immutable.fromJS({skeleton: [2, null, null],
|
||||
constPaths: Immutable.fromJS([]),
|
||||
constVals: Immutable.fromJS([]),
|
||||
capturePaths: Immutable.fromJS([])}));
|
||||
});
|
||||
it('should handle list patterns with constants and captures', () => {
|
||||
expect(Immutable.fromJS(Skeleton.analyzeAssertion(["hi", _$, _$])))
|
||||
.to.equal(Immutable.fromJS({skeleton: [3, null, null, null],
|
||||
constPaths: Immutable.fromJS([[0]]),
|
||||
constVals: Immutable.fromJS(["hi"]),
|
||||
capturePaths: Immutable.fromJS([[1],[2]])}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('nested structs', () => {
|
||||
let trace = skeletonTrace((i, traceHolder) => {
|
||||
i.addHandler([A.meta, [B.meta, null], null],
|
||||
Immutable.fromJS([]),
|
||||
Immutable.fromJS([]),
|
||||
Immutable.fromJS([[0, 0], [1]]),
|
||||
eventCallback(traceHolder, "AB"));
|
||||
i.addHandler([A.meta, [B.meta, null], null],
|
||||
Immutable.fromJS([[0, 0]]),
|
||||
Immutable.fromJS(["x"]),
|
||||
Immutable.fromJS([[1]]),
|
||||
eventCallback(traceHolder, "ABx"));
|
||||
i.addHandler([A.meta, null, [C.meta, null]],
|
||||
Immutable.fromJS([[0]]),
|
||||
Immutable.fromJS([B("y")]),
|
||||
Immutable.fromJS([[1, 0]]),
|
||||
eventCallback(traceHolder, "AByC"));
|
||||
i.addHandler([A.meta, [B.meta, null], [C.meta, null]],
|
||||
Immutable.fromJS([[0, 0]]),
|
||||
Immutable.fromJS([B("y")]),
|
||||
Immutable.fromJS([[1]]),
|
||||
eventCallback(traceHolder, "ABByC"));
|
||||
i.addHandler(Skeleton.analyzeAssertion(A(B(_$), _$)), eventCallback(traceHolder, "AB"));
|
||||
i.addHandler(Skeleton.analyzeAssertion(A(B("x"), _$)), eventCallback(traceHolder, "ABx"));
|
||||
let complexConstantPattern1 = {skeleton: [A.meta, null, [C.meta, null]],
|
||||
constPaths: Immutable.fromJS([[0]]),
|
||||
constVals: Immutable.fromJS([B("y")]),
|
||||
capturePaths: Immutable.fromJS([[1, 0]])};
|
||||
// ^ See comment in 'should handle complex constants (1)' test above (marked (***)).
|
||||
i.addHandler(complexConstantPattern1, eventCallback(traceHolder, "AByC"));
|
||||
let complexConstantPattern2 = {skeleton: [A.meta, [B.meta, null], [C.meta, null]],
|
||||
constPaths: Immutable.fromJS([[0, 0]]),
|
||||
constVals: Immutable.fromJS([B("y")]),
|
||||
capturePaths: Immutable.fromJS([[1]])};
|
||||
i.addHandler(complexConstantPattern2, eventCallback(traceHolder, "ABByC"));
|
||||
|
||||
i.addAssertion(A(B("x"),C(1)));
|
||||
i.addAssertion(A(B("y"),C(2)));
|
||||
|
@ -76,11 +129,7 @@ describe('skeleton tests', () => {
|
|||
|
||||
describe('simple detail-erasing trace', () => {
|
||||
let trace = skeletonTrace((i, traceHolder) => {
|
||||
i.addHandler([2, null, null],
|
||||
Immutable.fromJS([]),
|
||||
Immutable.fromJS([]),
|
||||
Immutable.fromJS([]),
|
||||
eventCallback(traceHolder, "2-EVENT"));
|
||||
i.addHandler(Skeleton.analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT"));
|
||||
|
||||
i.addAssertion(["hi", 123]);
|
||||
i.addAssertion(["hi", 234]);
|
||||
|
@ -98,16 +147,8 @@ describe('skeleton tests', () => {
|
|||
|
||||
describe('simple list assertions trace', () => {
|
||||
let trace = skeletonTrace((i, traceHolder) => {
|
||||
i.addHandler([3, null, null, null],
|
||||
Immutable.fromJS([[0]]),
|
||||
Immutable.fromJS(["hi"]),
|
||||
Immutable.fromJS([[1],[2]]),
|
||||
eventCallback(traceHolder, "3-EVENT"));
|
||||
i.addHandler([2, null, null],
|
||||
Immutable.fromJS([]),
|
||||
Immutable.fromJS([]),
|
||||
Immutable.fromJS([]),
|
||||
eventCallback(traceHolder, "2-EVENT"));
|
||||
i.addHandler(Skeleton.analyzeAssertion(["hi", _$, _$]), eventCallback(traceHolder, "3-EVENT"));
|
||||
i.addHandler(Skeleton.analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT"));
|
||||
|
||||
i.addAssertion(["hi", 123, 234]);
|
||||
i.addAssertion(["hi", 999, 999]);
|
||||
|
|
Loading…
Reference in New Issue