diff --git a/src/main.js b/src/main.js index 86d989e..07e05dd 100644 --- a/src/main.js +++ b/src/main.js @@ -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._$; diff --git a/src/skeleton.js b/src/skeleton.js index 73f485b..6d59391 100644 --- a/src/skeleton.js +++ b/src/skeleton.js @@ -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; diff --git a/src/struct.js b/src/struct.js index 5a8be83..909e6e3 100644 --- a/src/struct.js +++ b/src/struct.js @@ -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; diff --git a/test/test-skeleton.js b/test/test-skeleton.js index 81f439c..ce66ae4 100644 --- a/test/test-skeleton.js +++ b/test/test-skeleton.js @@ -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]);