diff --git a/src/special.js b/src/special.js new file mode 100644 index 0000000..39f337a --- /dev/null +++ b/src/special.js @@ -0,0 +1,8 @@ +"use strict"; +// $Special: Builder of singleton "atoms". + +function $Special(name) { + this.name = name; +} + +module.exports = $Special; diff --git a/src/struct.js b/src/struct.js new file mode 100644 index 0000000..6717711 --- /dev/null +++ b/src/struct.js @@ -0,0 +1,119 @@ +"use strict"; +// "Structures": Simple named-tuple-like records. + +var Immutable = require("immutable"); +var $Special = require('./special.js'); + +/* Defined here rather than elsewhere because we need it in makeConstructor. */ +var __ = new $Special("wildcard"); /* wildcard marker */ + +function StructureType(label, arity) { + this.label = label; + this.arity = arity; + 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.isClassOf = function (v) { return self.isClassOf(v); }; +} + +function makeConstructor(label, fieldNames) { + return new StructureType(label, fieldNames.length).ctor; +} + +StructureType.prototype.equals = function (other) { + if (!(other instanceof StructureType)) return false; + return this.arity === other.arity && this.label === other.label; +}; + +StructureType.prototype.instantiate = function (fields) { + return new Structure(this, fields); +}; + +StructureType.prototype.isClassOf = function (v) { + return v && (v instanceof Structure) && (v.meta.equals(this)); +}; + +function Structure(meta, fields) { + if (!isStructureType(meta)) { + throw new Error("Structure: requires structure type"); + } + if (fields.length !== meta.arity) { + throw new Error("Structure: cannot instantiate meta "+JSON.stringify(meta.label)+ + " expecting "+meta.arity+" fields with "+fields.length+" fields"); + } + this.meta = meta; + this.length = meta.arity; + this.fields = fields.slice(0); + for (var i = 0; i < fields.length; i++) { + this[i] = fields[i]; + } +} + +Structure.prototype.clone = function () { + return new Structure(this.meta, this.fields); +}; + +Structure.prototype.get = function (index) { + return this[index]; +}; + +Structure.prototype.set = function (index, value) { + var s = this.clone(); + s[index] = s.fields[index] = value; +}; + +function reviveStructs(j) { + if (Array.isArray(j)) { + return j.map(reviveStructs); + } + + if ((j !== null) && typeof j === 'object') { + if ((typeof j['@type'] === 'string') && Array.isArray(j['fields'])) { + return (new StructureType(j['@type'], j['fields'].length)).instantiate(j['fields']); + } else { + for (var k in j) { + if (Object.prototype.hasOwnProperty.call(j, k)) { + j[k] = reviveStructs(j[k]); + } + } + return j; + } + } + + return j; +} + +function reviver(k, v) { + if (k === '') { + return reviveStructs(v); + } + return v; +}; + +Structure.prototype.toJSON = function () { + return { '@type': this.meta.label, 'fields': this.fields }; +}; + +function isStructureType(v) { + return v && (v instanceof StructureType); +} + +function isStructure(v) { + return v && (v instanceof Structure); +} + +/////////////////////////////////////////////////////////////////////////// + +module.exports.__ = __; +module.exports.StructureType = StructureType; +module.exports.makeConstructor = makeConstructor; +module.exports.Structure = Structure; +module.exports.reviveStructs = reviveStructs; +module.exports.reviver = reviver; +module.exports.isStructureType = isStructureType; +module.exports.isStructure = isStructure;