Basic dynamic assertion analysis

This commit is contained in:
Tony Garnock-Jones 2018-10-21 16:00:29 +01:00
parent b8c967f941
commit 35350dc740
4 changed files with 154 additions and 43 deletions

View File

@ -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._$;

View File

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

View File

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

View File

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