Step zero of work toward imperative-syndicate/js

This commit is contained in:
Tony Garnock-Jones 2018-09-09 18:12:50 +01:00
commit e3b91a9d47
7 changed files with 424 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
scratch/
node_modules/

13
Makefile Normal file
View File

@ -0,0 +1,13 @@
.PHONY: all clean veryclean test
all:
npm install .
clean:
rm -f dist/*.js
veryclean: clean
rm -rf node_modules/
test:
npm test

166
package-lock.json generated Normal file
View File

@ -0,0 +1,166 @@
{
"name": "syndicate-ijs",
"version": "0.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"commander": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/commander/-/commander-2.3.0.tgz",
"integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=",
"dev": true
},
"debug": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
"dev": true,
"requires": {
"ms": "0.7.1"
}
},
"diff": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz",
"integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz",
"integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=",
"dev": true
},
"expect.js": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/expect.js/-/expect.js-0.3.1.tgz",
"integrity": "sha1-sKWaDS7/VDdUTr8M6qYBWEHQm1s=",
"dev": true
},
"glob": {
"version": "3.2.11",
"resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
"integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=",
"dev": true,
"requires": {
"inherits": "2",
"minimatch": "0.3"
}
},
"growl": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz",
"integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=",
"dev": true
},
"immutable": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
"integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=",
"dev": true
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"jade": {
"version": "0.26.3",
"resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz",
"integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=",
"dev": true,
"requires": {
"commander": "0.6.1",
"mkdirp": "0.3.0"
},
"dependencies": {
"commander": {
"version": "0.6.1",
"resolved": "http://registry.npmjs.org/commander/-/commander-0.6.1.tgz",
"integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=",
"dev": true
},
"mkdirp": {
"version": "0.3.0",
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
"integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=",
"dev": true
}
}
},
"lru-cache": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
"integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=",
"dev": true
},
"minimatch": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
"integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=",
"dev": true,
"requires": {
"lru-cache": "2",
"sigmund": "~1.0.0"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "0.0.8"
}
},
"mocha": {
"version": "2.5.3",
"resolved": "http://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz",
"integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=",
"dev": true,
"requires": {
"commander": "2.3.0",
"debug": "2.2.0",
"diff": "1.4.0",
"escape-string-regexp": "1.0.2",
"glob": "3.2.11",
"growl": "1.9.2",
"jade": "0.26.3",
"mkdirp": "0.5.1",
"supports-color": "1.2.0",
"to-iso-string": "0.0.2"
}
},
"ms": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
"dev": true
},
"sigmund": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
"integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=",
"dev": true
},
"supports-color": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz",
"integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=",
"dev": true
},
"to-iso-string": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz",
"integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=",
"dev": true
}
}
}

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "syndicate-ijs",
"version": "0.0.0",
"description": "Imperative Syndicate in the browser",
"homepage": "https://github.com/tonyg/syndicate",
"main": "src/main.js",
"repository": {
"type": "git",
"url": "git://github.com/tonyg/syndicate"
},
"directories": {
"bin": "./bin"
},
"scripts": {
"clean": "rm -f dist/*",
"test": "mocha"
},
"author": "Tony Garnock-Jones <tonyg@ccs.neu.edu>",
"devDependencies": {
"expect.js": "^0.3.1",
"immutable": "^3.8.2",
"mocha": "^2.5.3"
}
}

101
src/bag.js Normal file
View File

@ -0,0 +1,101 @@
"use strict";
// Bags and Deltas (which are Bags where item-counts can be negative).
const Immutable = require("immutable");
const PRESENT_TO_ABSENT = -1;
const ABSENT_TO_ABSENT = 0;
const ABSENT_TO_PRESENT = 1;
const PRESENT_TO_PRESENT = 2;
///////////////////////////////////////////////////////////////////////////
function MutableBag(s) {
this._items = s ? fromSet(s) : Immutable.Map();
}
MutableBag.prototype.change = function (key, delta) {
var net;
({bag: this._items, net: net} = change(this._items, key, delta));
return net;
};
MutableBag.prototype.get = function (key) {
return get(this._items, key);
};
MutableBag.prototype.clear = function () {
this._items = Immutable.Map();
};
MutableBag.prototype.includes = function (key) {
return includes(this._items, key);
};
MutableBag.prototype.isEmpty = function () {
return this._items.isEmpty();
};
MutableBag.prototype.count = function () {
return this._items.count();
};
MutableBag.prototype.keys = function () {
return this._items.keys();
};
MutableBag.prototype.entries = function () {
return this._items.entries();
};
///////////////////////////////////////////////////////////////////////////
const Bag = Immutable.Map;
function fromSet(s) {
return Bag().withMutations(function (b) {
for (let v of Immutable.Set(s)) {
b = b.set(v, 1);
}
});
}
function change(bag, key, delta, clamp) {
let oldCount = get(bag, key);
let newCount = oldCount + delta;
if (clamp) {
newCount = Math.max(0, newCount);
}
if (newCount === 0) {
return {
bag: bag.remove(key),
net: (oldCount === 0) ? ABSENT_TO_ABSENT : PRESENT_TO_ABSENT
};
} else {
return {
bag: bag.set(key, newCount),
net: (oldCount === 0) ? ABSENT_TO_PRESENT : PRESENT_TO_PRESENT
};
}
}
function get(bag, key) {
return bag.get(key, 0);
}
function includes(bag, key) {
return get(bag, key) > 0;
}
///////////////////////////////////////////////////////////////////////////
module.exports.PRESENT_TO_ABSENT = PRESENT_TO_ABSENT;
module.exports.ABSENT_TO_ABSENT = ABSENT_TO_ABSENT;
module.exports.ABSENT_TO_PRESENT = ABSENT_TO_PRESENT;
module.exports.PRESENT_TO_PRESENT = PRESENT_TO_PRESENT;
module.exports.MutableBag = MutableBag;
module.exports.Bag = Bag;
module.exports.fromSet = fromSet;
module.exports.change = change;
module.exports.get = get;
module.exports.includes = includes;

4
src/main.js Normal file
View File

@ -0,0 +1,4 @@
"use strict";
module.exports.Bag = require("./bag.js");
// module.exports.Skeleton = require("./skeleton.js");

114
test/test-bag.js Normal file
View File

@ -0,0 +1,114 @@
"use strict";
var expect = require('expect.js');
var Immutable = require('immutable');
var Bag = require('../src/bag.js');
describe('immutable bag', function () {
it('should be initializable from a set', function () {
var b = Bag.fromSet(Immutable.Set(['a', 'b', 'c']));
expect(b.count()).to.equal(3);
expect(Bag.get(b, 'a')).to.equal(1);
expect(Bag.get(b, 'z')).to.equal(0);
});
it('should be initializable from an array', function () {
var b = Bag.fromSet(['a', 'b', 'c', 'a']);
expect(b.count()).to.equal(3);
expect(Bag.get(b, 'a')).to.equal(1);
expect(Bag.get(b, 'z')).to.equal(0);
});
it('should be immutable', function () {
var b = Bag.Bag();
Bag.change(b, 'a', 1);
Bag.change(b, 'a', 1);
expect(b).to.equal(Bag.Bag());
});
it('should count up', function () {
var b = Bag.Bag();
var change1, change2;
({bag: b, net: change1} = Bag.change(b, 'a', 1));
({bag: b, net: change2} = Bag.change(b, 'a', 1));
expect(change1).to.equal(Bag.ABSENT_TO_PRESENT);
expect(change2).to.equal(Bag.PRESENT_TO_PRESENT);
expect(Bag.get(b, 'a')).to.equal(2);
expect(Bag.get(b, 'z')).to.equal(0);
});
it('should count down', function () {
var b = Bag.fromSet(['a']);
var c1, c2, c3, c4;
({bag: b, net: c1} = Bag.change(b, 'a', 1));
({bag: b, net: c2} = Bag.change(b, 'a', -1));
expect(b.count()).to.equal(1);
expect(c1).to.equal(Bag.PRESENT_TO_PRESENT);
expect(c2).to.equal(Bag.PRESENT_TO_PRESENT);
({bag: b, net: c3} = Bag.change(b, 'a', -1));
expect(b.count()).to.equal(0);
expect(c3).to.equal(Bag.PRESENT_TO_ABSENT);
expect(Bag.get(b, 'a')).to.equal(0);
expect(Bag.get(b, 'z')).to.equal(0);
({bag: b, net: c4} = Bag.change(b, 'a', -1));
expect(b.count()).to.equal(1);
expect(c4).to.equal(Bag.ABSENT_TO_PRESENT);
expect(Bag.get(b, 'a')).to.equal(-1);
});
it('should be clamped', function() {
var b = Bag.fromSet(['a']);
({bag: b} = Bag.change(b, 'a', -1, true));
({bag: b} = Bag.change(b, 'a', -1, true));
({bag: b} = Bag.change(b, 'a', -1, true));
({bag: b} = Bag.change(b, 'a', -1, true));
expect(b.count()).to.equal(0);
expect(Bag.get(b, 'a')).to.equal(0);
});
});
describe('mutable bag', function () {
it('should be initializable from a set', function () {
var b = new Bag.MutableBag(Immutable.Set(['a', 'b', 'c']));
expect(b.count()).to.equal(3);
expect(b.get('a')).to.equal(1);
expect(b.get('z')).to.equal(0);
});
it('should be initializable from an array', function () {
var b = new Bag.MutableBag(['a', 'b', 'c', 'a']);
expect(b.count()).to.equal(3);
expect(b.get('a')).to.equal(1);
expect(b.get('z')).to.equal(0);
});
it('should be mutable', function () {
var b = new Bag.MutableBag();
b.change('a', 1);
b.change('a', 1);
expect(b.get('a')).to.equal(2);
expect(b.get('z')).to.equal(0);
});
it('should count up', function () {
var b = new Bag.MutableBag();
expect(b.change('a', 1)).to.equal(Bag.ABSENT_TO_PRESENT);
expect(b.change('a', 1)).to.equal(Bag.PRESENT_TO_PRESENT);
expect(b.get('a')).to.equal(2);
expect(b.get('z')).to.equal(0);
});
it('should count down', function () {
var b = new Bag.MutableBag(['a']);
expect(b.change('a', 1)).to.equal(Bag.PRESENT_TO_PRESENT);
expect(b.change('a', -1)).to.equal(Bag.PRESENT_TO_PRESENT);
expect(b.count()).to.equal(1);
expect(b.change('a', -1)).to.equal(Bag.PRESENT_TO_ABSENT);
expect(b.count()).to.equal(0);
expect(b.get('a')).to.equal(0);
expect(b.get('z')).to.equal(0);
expect(b.change('a', -1)).to.equal(Bag.ABSENT_TO_PRESENT);
expect(b.count()).to.equal(1);
expect(b.get('a')).to.equal(-1);
});
});