Be stricter about permissible assertions; repair error in Structure instantiation which led to raw arrays sneaking in

This commit is contained in:
Tony Garnock-Jones 2018-11-04 19:52:53 +00:00
parent 0552de7987
commit 2688c09639
5 changed files with 75 additions and 55 deletions

View File

@ -77,7 +77,7 @@ const Bag = Immutable.Map;
function fromSet(s) {
return Bag().withMutations(function (b) {
for (let v of Immutable.Set(s)) {
b = b.set(v, 1);
b = b.set(Immutable.fromJS(v), 1);
}
});
}

View File

@ -344,6 +344,20 @@ Actor.prototype.pendingPatch = function () {
Actor.prototype.assert = function (a) { this.pendingPatch().adjust(a, +1); };
Actor.prototype.retract = function (a) { this.pendingPatch().adjust(a, -1); };
Actor.prototype.adhocRetract = function (a) {
a = Immutable.fromJS(a);
if (this.adhocAssertions.change(a, -1, true) === Bag.PRESENT_TO_ABSENT) {
this.retract(a);
}
};
Actor.prototype.adhocAssert = function (a) {
a = Immutable.fromJS(a);
if (this.adhocAssertions.change(a, +1) === Bag.ABSENT_TO_PRESENT) {
this.assert(a);
}
};
Actor.prototype.toString = function () {
let s = 'Actor(' + this.id;
if (this.name !== void 0) s = s + ',' + JSON.stringify(this.name);
@ -360,7 +374,7 @@ Patch.prototype.perform = function (ds, ac) {
Patch.prototype.adjust = function (a, count) {
var _net;
({bag: this.changes, net: _net} = Bag.change(this.changes, a, count));
({bag: this.changes, net: _net} = Bag.change(this.changes, Immutable.fromJS(a), count));
};
function Message(body) {
@ -369,7 +383,7 @@ function Message(body) {
Message.prototype.perform = function (ds, ac) {
if (this.body !== void 0) {
ds.index.sendMessage(this.body);
ds.index.sendMessage(Immutable.fromJS(this.body));
}
};

View File

@ -249,7 +249,6 @@ function del_from_handler(h, vs) {
Index.prototype.adjustAssertion = function(outerValue, delta) {
let net;
outerValue = Immutable.fromJS(outerValue);
({bag: this.allAssertions, net: net} = Bag.change(this.allAssertions, outerValue, delta));
switch (net) {
case Bag.ABSENT_TO_PRESENT:
@ -265,7 +264,7 @@ Index.prototype.addAssertion = function(v) { this.adjustAssertion(v, +1); };
Index.prototype.removeAssertion = function (v) { this.adjustAssertion(v, -1); };
Index.prototype.sendMessage = function(v) {
this.root.modify(Immutable.fromJS(v), ()=>{}, ()=>{}, (h, vs) => {
this.root.modify(v, ()=>{}, ()=>{}, (h, vs) => {
h.callbacks.forEach((cb) => {
cb(EVENT_MESSAGE, vs);
return true;
@ -321,7 +320,7 @@ function analyzeAssertion(a) {
}
}
let skeleton = walk(Immutable.List(), Immutable.fromJS(a));
let skeleton = walk(Immutable.List(), a);
return { skeleton, constPaths, constVals, capturePaths };
}

View File

@ -28,14 +28,14 @@ const __ = new $Special("wildcard"); /* wildcard marker */
function StructureType(label, arity) {
this.label = label;
this.arity = arity;
this.pattern = this.instantiate(Immutable.Repeat(__, arity).toArray());
// this.pattern = this.instantiate(Immutable.Repeat(__, arity).toArray());
var self = this;
this.ctor = function () {
return self.instantiate(Array.prototype.slice.call(arguments));
};
this.ctor.meta = this;
this.ctor.pattern = this.pattern;
// this.ctor.pattern = this.pattern;
this.ctor.isClassOf = function (v) { return self.isClassOf(v); };
}
@ -68,9 +68,10 @@ function Structure(meta, fields) {
throw new Error("Structure: cannot instantiate meta "+JSON.stringify(meta.label)+
" expecting "+meta.arity+" fields with "+fields.length+" fields");
}
fields = fields.slice(0);
this.meta = meta;
this.length = meta.arity;
this.fields = fields.slice(0);
this.fields = fields;
for (var i = 0; i < fields.length; i++) {
this[i] = fields[i] = Immutable.fromJS(fields[i]);
if (this[i] === void 0) {
@ -98,9 +99,11 @@ Structure.prototype.equals = function (other) {
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 (typeof this[i].equals !== 'function') return false;
if (!this[i].equals(other[i])) return false;
const a = this[i];
const b = other[i];
if (a === b) continue;
if (!a || typeof a.equals !== 'function') return false;
if (!a.equals(b)) return false;
}
return true;
};

View File

@ -45,6 +45,10 @@ function skeletonTrace(f) {
return traceHolder.trace;
}
function _analyzeAssertion(a) {
return Skeleton.analyzeAssertion(Immutable.fromJS(a));
}
describe('skeleton', () => {
const A = Struct.makeConstructor('A', ['x', 'y']);
@ -53,14 +57,14 @@ describe('skeleton', () => {
describe('pattern analysis', () => {
it('should handle leaf captures', () => {
expect(Immutable.fromJS(Skeleton.analyzeAssertion(A(B(_$), _$))))
expect(Immutable.fromJS(_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"), _$))))
expect(Immutable.fromJS(_analyzeAssertion(A(B("x"), _$))))
.to.equal(Immutable.fromJS({skeleton: [A.meta, [B.meta, null], null],
constPaths: Immutable.fromJS([[0, 0]]),
constVals: Immutable.fromJS(["x"]),
@ -74,7 +78,7 @@ describe('skeleton', () => {
// that situation without the static analysis half of the code.
// TODO later.
let complexPlaceholder = new Object();
expect(Immutable.fromJS(Skeleton.analyzeAssertion(A(complexPlaceholder, C(_$)))))
expect(Immutable.fromJS(_analyzeAssertion(A(complexPlaceholder, C(_$)))))
.to.equal(Immutable.fromJS({skeleton: [A.meta, null, [C.meta, null]],
constPaths: Immutable.fromJS([[0]]),
constVals: Immutable.fromJS([complexPlaceholder]),
@ -87,21 +91,21 @@ describe('skeleton', () => {
// 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(__))))))
expect(Immutable.fromJS(_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([__, __])))
expect(Immutable.fromJS(_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", _$, _$])))
expect(Immutable.fromJS(_analyzeAssertion(["hi", _$, _$])))
.to.equal(Immutable.fromJS({skeleton: [3, null, null, null],
constPaths: Immutable.fromJS([[0]]),
constVals: Immutable.fromJS(["hi"]),
@ -111,8 +115,8 @@ describe('skeleton', () => {
describe('nested structs', () => {
let trace = skeletonTrace((i, traceHolder) => {
i.addHandler(Skeleton.analyzeAssertion(A(B(_$), _$)), eventCallback(traceHolder, "AB"));
i.addHandler(Skeleton.analyzeAssertion(A(B("x"), _$)), eventCallback(traceHolder, "ABx"));
i.addHandler(_analyzeAssertion(A(B(_$), _$)), eventCallback(traceHolder, "AB"));
i.addHandler(_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")]),
@ -125,10 +129,10 @@ describe('skeleton', () => {
capturePaths: Immutable.fromJS([[1]])};
i.addHandler(complexConstantPattern2, eventCallback(traceHolder, "ABByC"));
i.addAssertion(A(B("x"),C(1)));
i.addAssertion(A(B("y"),C(2)));
i.addAssertion(A(B(B("y")),C(2)));
i.addAssertion(A(B("z"),C(3)));
i.addAssertion(Immutable.fromJS(A(B("x"),C(1))));
i.addAssertion(Immutable.fromJS(A(B("y"),C(2))));
i.addAssertion(Immutable.fromJS(A(B(B("y")),C(2))));
i.addAssertion(Immutable.fromJS(A(B("z"),C(3))));
});
// trace.forEach((e) => { console.log(e.toString()) });
@ -146,12 +150,12 @@ describe('skeleton', () => {
describe('simple detail-erasing trace', () => {
let trace = skeletonTrace((i, traceHolder) => {
i.addHandler(Skeleton.analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT"));
i.addHandler(_analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT"));
i.addAssertion(["hi", 123]);
i.addAssertion(["hi", 234]);
i.removeAssertion(["hi", 123]);
i.removeAssertion(["hi", 234]);
i.addAssertion(Immutable.fromJS(["hi", 123]));
i.addAssertion(Immutable.fromJS(["hi", 234]));
i.removeAssertion(Immutable.fromJS(["hi", 123]));
i.removeAssertion(Immutable.fromJS(["hi", 234]));
});
it('should have one add and one remove', () => {
@ -164,9 +168,9 @@ describe('skeleton', () => {
describe('handler added after assertion (1)', () => {
let trace = skeletonTrace((i, traceHolder) => {
i.addAssertion(["hi", 123, 234]);
i.addHandler(Skeleton.analyzeAssertion(["hi", _$, _$]), eventCallback(traceHolder, "X"));
i.removeAssertion(["hi", 123, 234]);
i.addAssertion(Immutable.fromJS(["hi", 123, 234]));
i.addHandler(_analyzeAssertion(["hi", _$, _$]), eventCallback(traceHolder, "X"));
i.removeAssertion(Immutable.fromJS(["hi", 123, 234]));
});
it('should get two events', () => {
@ -178,9 +182,9 @@ describe('skeleton', () => {
describe('handler added after assertion (2)', () => {
let trace = skeletonTrace((i, traceHolder) => {
i.addAssertion(["hi", 123, 234]);
i.addHandler(Skeleton.analyzeAssertion(_$), eventCallback(traceHolder, "X"));
i.removeAssertion(["hi", 123, 234]);
i.addAssertion(Immutable.fromJS(["hi", 123, 234]));
i.addHandler(_analyzeAssertion(_$), eventCallback(traceHolder, "X"));
i.removeAssertion(Immutable.fromJS(["hi", 123, 234]));
});
it('should get two events', () => {
@ -192,12 +196,12 @@ describe('skeleton', () => {
describe('handler removed before assertion removed', () => {
let trace = skeletonTrace((i, traceHolder) => {
i.addAssertion(["hi", 123, 234]);
let h = Skeleton.analyzeAssertion(["hi", _$, _$]);
i.addAssertion(Immutable.fromJS(["hi", 123, 234]));
let h = _analyzeAssertion(["hi", _$, _$]);
h.callback = eventCallback(traceHolder, "X")
i.addHandler(h, h.callback);
i.removeHandler(h, h.callback);
i.removeAssertion(["hi", 123, 234]);
i.removeAssertion(Immutable.fromJS(["hi", 123, 234]));
});
it('should get one event', () => {
@ -208,24 +212,24 @@ describe('skeleton', () => {
describe('simple list assertions trace', () => {
let trace = skeletonTrace((i, traceHolder) => {
i.addHandler(Skeleton.analyzeAssertion(["hi", _$, _$]), eventCallback(traceHolder, "3-EVENT"));
i.addHandler(Skeleton.analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT"));
i.addHandler(_analyzeAssertion(["hi", _$, _$]), eventCallback(traceHolder, "3-EVENT"));
i.addHandler(_analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT"));
i.addAssertion(["hi", 123, 234]);
i.addAssertion(["hi", 999, 999]);
i.addAssertion(["hi", 123]);
i.addAssertion(["hi", 123, 234]);
i.sendMessage(["hi", 303]);
i.sendMessage(["hi", 303, 404]);
i.sendMessage(["hi", 303, 404, 808]);
i.removeAssertion(["hi", 123, 234]);
i.removeAssertion(["hi", 999, 999]);
i.removeAssertion(["hi", 123, 234]);
i.addAssertion(["hi", 123]);
i.addAssertion(["hi", 234]);
i.removeAssertion(["hi", 123]);
i.removeAssertion(["hi", 123]);
i.removeAssertion(["hi", 234]);
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', () => {