Basic dynamic assertion analysis
This commit is contained in:
parent
b8c967f941
commit
35350dc740
|
@ -1,5 +1,10 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
const Struct = require('./struct.js');
|
||||||
|
const Skeleton = require('./skeleton.js');
|
||||||
|
|
||||||
module.exports.Bag = require("./bag.js");
|
module.exports.Bag = require("./bag.js");
|
||||||
module.exports.Struct = require("./struct.js");
|
module.exports.Struct = Struct;
|
||||||
module.exports.Skeleton = require("./skeleton.js");
|
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 Bag = require('./bag.js');
|
||||||
const Assertions = require('./assertions.js');
|
const Assertions = require('./assertions.js');
|
||||||
|
|
||||||
|
const __ = Struct.__;
|
||||||
|
|
||||||
const EVENT_ADDED = +1;
|
const EVENT_ADDED = +1;
|
||||||
const EVENT_REMOVED = -1;
|
const EVENT_REMOVED = -1;
|
||||||
const EVENT_MESSAGE = 0;
|
const EVENT_MESSAGE = 0;
|
||||||
|
@ -101,7 +103,8 @@ Node.prototype.extend = function(skeleton) {
|
||||||
return finalNode.continuation;
|
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 continuation = this.root.extend(skeleton);
|
||||||
let constValMap = continuation.leafMap.get(constPaths, false);
|
let constValMap = continuation.leafMap.get(constPaths, false);
|
||||||
if (!constValMap) {
|
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 continuation = this.root.extend(skeleton);
|
||||||
let constValMap = continuation.leafMap.get(constPaths, false);
|
let constValMap = continuation.leafMap.get(constPaths, false);
|
||||||
if (!constValMap) return;
|
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_ADDED = EVENT_ADDED;
|
||||||
module.exports.EVENT_REMOVED = EVENT_REMOVED;
|
module.exports.EVENT_REMOVED = EVENT_REMOVED;
|
||||||
module.exports.EVENT_MESSAGE = EVENT_MESSAGE;
|
module.exports.EVENT_MESSAGE = EVENT_MESSAGE;
|
||||||
module.exports.Index = Index;
|
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";
|
"use strict";
|
||||||
// "Structures": Simple named-tuple-like records.
|
// "Structures": Simple named-tuple-like records.
|
||||||
|
|
||||||
var Immutable = require("immutable");
|
const Immutable = require("immutable");
|
||||||
var $Special = require('./special.js');
|
const $Special = require('./special.js');
|
||||||
|
|
||||||
/* Defined here rather than elsewhere because we need it in makeConstructor. */
|
/* 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) {
|
function StructureType(label, arity) {
|
||||||
this.label = label;
|
this.label = label;
|
||||||
|
|
|
@ -9,6 +9,8 @@ const Immutable = require('immutable');
|
||||||
const Syndicate = require('../src/main.js');
|
const Syndicate = require('../src/main.js');
|
||||||
const Skeleton = Syndicate.Skeleton;
|
const Skeleton = Syndicate.Skeleton;
|
||||||
const Struct = Syndicate.Struct;
|
const Struct = Syndicate.Struct;
|
||||||
|
const __ = Syndicate.__;
|
||||||
|
const _$ = Syndicate._$;
|
||||||
|
|
||||||
const Event = Struct.makeConstructor('Event', ['label', 'type', 'values']);
|
const Event = Struct.makeConstructor('Event', ['label', 'type', 'values']);
|
||||||
|
|
||||||
|
@ -26,34 +28,85 @@ function skeletonTrace(f) {
|
||||||
return traceHolder.trace;
|
return traceHolder.trace;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('skeleton tests', () => {
|
describe('skeleton', () => {
|
||||||
|
|
||||||
const A = Struct.makeConstructor('A', ['x', 'y']);
|
const A = Struct.makeConstructor('A', ['x', 'y']);
|
||||||
const B = Struct.makeConstructor('B', ['v']);
|
const B = Struct.makeConstructor('B', ['v']);
|
||||||
const C = Struct.makeConstructor('C', ['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', () => {
|
describe('nested structs', () => {
|
||||||
let trace = skeletonTrace((i, traceHolder) => {
|
let trace = skeletonTrace((i, traceHolder) => {
|
||||||
i.addHandler([A.meta, [B.meta, null], null],
|
i.addHandler(Skeleton.analyzeAssertion(A(B(_$), _$)), eventCallback(traceHolder, "AB"));
|
||||||
Immutable.fromJS([]),
|
i.addHandler(Skeleton.analyzeAssertion(A(B("x"), _$)), eventCallback(traceHolder, "ABx"));
|
||||||
Immutable.fromJS([]),
|
let complexConstantPattern1 = {skeleton: [A.meta, null, [C.meta, null]],
|
||||||
Immutable.fromJS([[0, 0], [1]]),
|
constPaths: Immutable.fromJS([[0]]),
|
||||||
eventCallback(traceHolder, "AB"));
|
constVals: Immutable.fromJS([B("y")]),
|
||||||
i.addHandler([A.meta, [B.meta, null], null],
|
capturePaths: Immutable.fromJS([[1, 0]])};
|
||||||
Immutable.fromJS([[0, 0]]),
|
// ^ See comment in 'should handle complex constants (1)' test above (marked (***)).
|
||||||
Immutable.fromJS(["x"]),
|
i.addHandler(complexConstantPattern1, eventCallback(traceHolder, "AByC"));
|
||||||
Immutable.fromJS([[1]]),
|
let complexConstantPattern2 = {skeleton: [A.meta, [B.meta, null], [C.meta, null]],
|
||||||
eventCallback(traceHolder, "ABx"));
|
constPaths: Immutable.fromJS([[0, 0]]),
|
||||||
i.addHandler([A.meta, null, [C.meta, null]],
|
constVals: Immutable.fromJS([B("y")]),
|
||||||
Immutable.fromJS([[0]]),
|
capturePaths: Immutable.fromJS([[1]])};
|
||||||
Immutable.fromJS([B("y")]),
|
i.addHandler(complexConstantPattern2, eventCallback(traceHolder, "ABByC"));
|
||||||
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.addAssertion(A(B("x"),C(1)));
|
i.addAssertion(A(B("x"),C(1)));
|
||||||
i.addAssertion(A(B("y"),C(2)));
|
i.addAssertion(A(B("y"),C(2)));
|
||||||
|
@ -76,11 +129,7 @@ describe('skeleton tests', () => {
|
||||||
|
|
||||||
describe('simple detail-erasing trace', () => {
|
describe('simple detail-erasing trace', () => {
|
||||||
let trace = skeletonTrace((i, traceHolder) => {
|
let trace = skeletonTrace((i, traceHolder) => {
|
||||||
i.addHandler([2, null, null],
|
i.addHandler(Skeleton.analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT"));
|
||||||
Immutable.fromJS([]),
|
|
||||||
Immutable.fromJS([]),
|
|
||||||
Immutable.fromJS([]),
|
|
||||||
eventCallback(traceHolder, "2-EVENT"));
|
|
||||||
|
|
||||||
i.addAssertion(["hi", 123]);
|
i.addAssertion(["hi", 123]);
|
||||||
i.addAssertion(["hi", 234]);
|
i.addAssertion(["hi", 234]);
|
||||||
|
@ -98,16 +147,8 @@ describe('skeleton tests', () => {
|
||||||
|
|
||||||
describe('simple list assertions trace', () => {
|
describe('simple list assertions trace', () => {
|
||||||
let trace = skeletonTrace((i, traceHolder) => {
|
let trace = skeletonTrace((i, traceHolder) => {
|
||||||
i.addHandler([3, null, null, null],
|
i.addHandler(Skeleton.analyzeAssertion(["hi", _$, _$]), eventCallback(traceHolder, "3-EVENT"));
|
||||||
Immutable.fromJS([[0]]),
|
i.addHandler(Skeleton.analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT"));
|
||||||
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.addAssertion(["hi", 123, 234]);
|
i.addAssertion(["hi", 123, 234]);
|
||||||
i.addAssertion(["hi", 999, 999]);
|
i.addAssertion(["hi", 999, 999]);
|
||||||
|
|
Loading…
Reference in New Issue