"use strict"; // Preserves Values. // Uses Immutable.js for many things; adds immutable values of its own for the rest. if (require('./singletonmodule.js')('leastfixedpoint.com/preserves', require('../package.json').version, 'values.js', module)) return; const util = require('util'); const Immutable = require('immutable'); const { List, Map, Set, is, hash } = Immutable; const { PreserveOn, AsPreserve } = require('./symbols.js'); function _decode(bs) { return Buffer.from(bs.buffer, bs.byteOffset, bs.byteLength).toString(); } const encoder = (typeof TextEncoder === 'undefined') ? { encode: Buffer.from } : new TextEncoder(); const decoder = (typeof TextDecoder === 'undefined') ? { decode: _decode } : new TextDecoder(); function fromJS(x) { switch (typeof x) { case 'number': if (!Number.isInteger(x)) { // We require that clients be explicit about integer vs. non-integer types. throw new TypeError("Refusing to autoconvert non-integer number to Single or Double"); } // FALL THROUGH case 'string': case 'symbol': case 'boolean': return x; case 'undefined': throw new TypeError("Cannot represent JavaScript undefined value as Preserves"); case 'function': // We are relaxed about these, for now, even though Preserves can't serialize them. return x; case 'object': if (x === null) { // We are relaxed about these, for now, even though null is // strictly speaking not a Preserves Value. return x; } if (typeof x[AsPreserve] === 'function') { return x[AsPreserve](); } if (Array.isArray(x)) { return List().withMutations((l) => { for (let i = 0; i < x.length; i++) { l.push(fromJS(x[i])); } }); } if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) { return Bytes(x); } return Immutable.fromJS(x); } } function _Float(value) { Object.defineProperty(this, 'value', { value: value, writable: false, enumerable: true }); } function Float(value) { if (!(this instanceof Float)) return new Float(value); _Float.call(this, value); } Float.prototype.toString = function () { return '' + this.value; }; Float.prototype.hashCode = function () { return this.value | 0; // TODO: something better? }; Float.unwrap = function (v) { if (typeof v === 'number') return v; if (v instanceof Float) return v.value; { const e = new TypeError("Float.unwrap failed"); e.irritant = v; throw e; } }; function Single(value) { if (!(this instanceof Single)) return new Single(value); _Float.call(this, value); } Single.prototype = Float(NaN); Single.prototype.equals = function (other) { return (other instanceof Single) && (other.value === this.value); }; Single.prototype[PreserveOn] = function (encoder) { encoder.leadbyte(0, 0, 2); encoder.makeroom(4); encoder.view.setFloat32(encoder.index, this.value, false); encoder.index += 4; }; function Double(value) { if (!(this instanceof Double)) return new Double(value); _Float.call(this, value); } Double.prototype = Float(NaN); Double.prototype.equals = function (other) { return (other instanceof Double) && (other.value === this.value); }; Double.prototype[PreserveOn] = function (encoder) { encoder.leadbyte(0, 0, 3); encoder.makeroom(8); encoder.view.setFloat64(encoder.index, this.value, false); encoder.index += 8; }; function Bytes(maybeByteIterable) { if (!(this instanceof Bytes)) return new Bytes(maybeByteIterable); if (maybeByteIterable === void 0) { _installView.call(this, new Uint8Array()); } else if (ArrayBuffer.isView(maybeByteIterable)) { _installView.call(this, new Uint8Array(maybeByteIterable.buffer, maybeByteIterable.byteOffset, maybeByteIterable.byteLength)); } else if (maybeByteIterable instanceof ArrayBuffer) { _installView.call(this, new Uint8Array(maybeByteIterable.slice())); } else if (typeof maybeByteIterable === 'string') { _installView.call(this, encoder.encode(maybeByteIterable)); } else if (typeof maybeByteIterable === 'number') { _installView.call(this, new Uint8Array(maybeByteIterable)); } else if (typeof maybeByteIterable.length === 'number') { _installView.call(this, Uint8Array.from(maybeByteIterable)); } else if (maybeByteIterable instanceof Bytes) { _installView.call(this, maybeByteIterable._view); } else if (typeof maybeByteIterable.size === 'number') { _installView.call(this, new Uint8Array(maybeByteIterable.size)); for (let i = 0; i < this.size; i++) { this._view[i] = maybeByteIterable.get(i); } } else { const e = new TypeError("Attempt to initialize Bytes from unsupported value"); e.irritant = maybeByteIterable; throw e; } } function _installView(view) { Object.defineProperty(this, '_view', { value: view, writable: false }); Object.defineProperty(this, 'size', { value: view.length, writable: false, enumerable: true }); } Bytes.from = Bytes; Bytes.of = function (...args) { return Bytes(Uint8Array.of(...args)); }; function unhexDigit(asciiCode) { if (asciiCode >= 48 && asciiCode <= 57) return asciiCode - 48; if (asciiCode >= 97 && asciiCode <= 102) return asciiCode - 97 + 10; if (asciiCode >= 65 && asciiCode <= 70) return asciiCode - 65 + 10; throw new Error("Invalid hex digit: " + String.fromCharCode(asciiCode)); } Bytes.fromHex = function (s) { if (s.length & 1) throw new Error("Cannot decode odd-length hexadecimal string"); const len = s.length >> 1; const result = Bytes(len); for (let i = 0; i < len; i++) { result._view[i] = (unhexDigit(s.charCodeAt(i << 1)) << 4) | unhexDigit(s.charCodeAt((i << 1) + 1)); } return result; }; Bytes.fromIO = function (io) { if (typeof io === 'string') return io; if (io instanceof Bytes) return io; if (io instanceof Uint8Array) return Bytes.from(io); { const e = new TypeError("Bytes.fromIO: unsupported value"); e.irritant = io; throw e; } }; Bytes.toIO = function (b) { if (typeof b === 'string') return b; if (b instanceof Bytes) return b._view; if (b instanceof Uint8Array) return b; { const e = new TypeError("Bytes.toIO: unsupported value"); e.irritant = b; throw e; } }; function underlying(b) { return b._view || b; } Bytes.concat = function (bss) { let len = 0; for (let i = 0; i < bss.length; i++) { len += underlying(bss[i]).length; } const result = Bytes(len); let index = 0; for (let i = 0; i < bss.length; i++) { const bs = underlying(bss[i]); result._view.set(bs, index); index += bs.length; } return result; }; Bytes.prototype.get = function (index) { return this._view[index]; }; Bytes.prototype.equals = function (other) { if (!(other instanceof Bytes)) return false; if (other.size !== this.size) return false; const va = this._view; const vb = other._view; for (let i = 0; i < va.length; i++) { if (va[i] !== vb[i]) return false; } return true; }; Bytes.prototype.hashCode = function () { // Immutable.js uses this function for strings. const v = this._view; let hash = 0; for (let i = 0; i < v.length; i++) { hash = ((31 * hash) + v[i]) | 0; } return hash; }; Bytes.decodeUtf8 = function (bs) { return decoder.decode(underlying(bs)); }; Bytes.prototype.fromUtf8 = function () { return decoder.decode(this._view); }; Bytes.prototype.toString = function () { return '#"' + this.__asciify() + '"'; }; if (util.inspect) { Bytes.prototype[util.inspect.custom] = function (depth, options) { return this.toString(); }; } Bytes.prototype.__asciify = function () { const pieces = []; const v = this._view; for (let i = 0; i < v.length; i++) { const b = v[i]; if (b === 92 || b === 34) { pieces.push('\\' + String.fromCharCode(b)); } else if (b >= 32 && b <= 126) { pieces.push(String.fromCharCode(b)); } else { pieces.push('\\x' + hexDigit(b >> 4) + hexDigit(b & 15)); } } return pieces.join(''); }; function hexDigit(n) { return '0123456789abcdef'[n]; } Bytes.prototype.toHex = function () { var nibbles = []; for (let i = 0; i < this.size; i++) { nibbles.push(hexDigit(this._view[i] >> 4)); nibbles.push(hexDigit(this._view[i] & 15)); } return nibbles.join(''); }; Bytes.prototype[PreserveOn] = function (encoder) { encoder.header(1, 2, this.size); encoder.emitbytes(this._view); }; // Uint8Array / TypedArray methods (function () { for (const k of `entries every find findIndex forEach includes indexOf join keys lastIndexOf reduce reduceRight some toLocaleString values`.split(/\s+/)) { Bytes.prototype[k] = function (...args) { return this._view[k](...args); }; } for (const k of `filter map slice subarray`.split(/\s+/)) { Bytes.prototype[k] = function (...args) { return Bytes(this._view[k](...args)); }; } for (const k of `reverse sort`.split(/\s+/)) { Bytes.prototype[k] = function (...args) { return Bytes(this._view.slice()[k](...args)); }; } Bytes.prototype[Symbol.iterator] = function () { return this._view[Symbol.iterator](); }; })(); function Record(label, fields) { if (!(this instanceof Record)) { throw new TypeError("Class constructor Record cannot be invoked without 'new'"); } Object.defineProperty(this, 'label', { value: fromJS(label), writable: false, enumerable: true }); Object.defineProperty(this, 'fields', { value: fromJS(fields), writable: false, enumerable: true }); } Record.prototype.equals = function (other) { return (other instanceof Record) && is(this.label, other.label) && is(this.fields, other.fields); }; Record.prototype.hashCode = function () { return Immutable.List([this.label, this.fields]).hashCode(); }; Record.prototype.get = function (index, defaultValue) { return this.fields.get(index, defaultValue); }; Record.prototype.set = function (index, newValue) { return new Record(this.label, this.fields.set(index, newValue)); }; Record.prototype[PreserveOn] = function (encoder) { encoder.header(2, 0, this.fields.size + 1); encoder.push(this.label); for (const field of this.fields) { encoder.push(field); } }; Record.prototype.getConstructorInfo = function () { return new RecordConstructorInfo(this.label, this.fields.size); }; Record.prototype.toString = function () { return this.label.toString().replace(/^Symbol\((.*)\)$/, '$1') + '(' + this.fields.map((f) => { try { return "" + f; } catch (e) { return (util.inspect || ((f) => ''))(f); } }).join(', ') + ')'; }; Record.makeConstructor = function (labelSymbolText, fieldNames) { return Record.makeBasicConstructor(Symbol.for(labelSymbolText), fieldNames); }; Record.makeBasicConstructor = function (label, fieldNames) { const arity = fieldNames.length; const ctor = (...fields) => { if (fields.length !== arity) { throw new Error("Record: cannot instantiate " + (label && label.toString()) + " expecting " + arity + " fields with " + fields.length + " fields"); } return new Record(label, fields); }; ctor.constructorInfo = new RecordConstructorInfo(label, arity); ctor.isClassOf = (v) => ((v instanceof Record) && is(label, v.label) && v.fields.size === arity); fieldNames.forEach((name, i) => { ctor['_'+name] = function (r) { if (!ctor.isClassOf(r)) { throw new Error("Record: attempt to retrieve field "+label.toString()+"."+name+ " from non-"+label.toString()+": "+(r && r.toString())); } return r.get(i); }; }); return ctor; }; function RecordConstructorInfo(label, arity) { this.label = label; this.arity = arity; } RecordConstructorInfo.prototype.equals = function (other) { return (other instanceof RecordConstructorInfo) && is(this.label, other.label) && (this.arity === other.arity); }; RecordConstructorInfo.prototype.hashCode = function () { return Immutable.List([this.label, this.arity]).hashCode(); }; RecordConstructorInfo.prototype.isClassOf = function (v) { return (v instanceof Record) && is(this.label, v.label) && (this.arity === v.fields.size); }; Object.assign(module.exports, { fromJS, List, Map, Set, is, hash, Float, Single, Double, Bytes, Record, RecordConstructorInfo, });