"use strict"; // Preserves Values. // Uses Immutable.js for many things; adds immutable values of its own for the rest. const Immutable = require('immutable'); const { List, isList, Map, Set } = 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 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.hashCode = function () { return this.value | 0; // TODO: something better? }; 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 (typeof maybeByteIterable.size === 'number') { _installView.call(this, new ArrayBuffer(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; } } Bytes.from = Bytes; 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; }; 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.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.toString = function () { return decoder.decode(this._view); }; 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); }; function _installView(view) { Object.defineProperty(this, '_view', { value: view, writable: false }); Object.defineProperty(this, 'size', { value: view.length, writable: false, enumerable: true }); } function Record(label, fields) { if (!(this instanceof Record)) return new Record(label, fields); 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) && Immutable.is(this.label, other.label) && Immutable.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) { if (Immutable.is(encoder.shortForms[0], this.label)) { encoder.header(2, 0, this.fields.size); } else if (Immutable.is(encoder.shortForms[1], this.label)) { encoder.header(2, 1, this.fields.size); } else if (Immutable.is(encoder.shortForms[2], this.label)) { encoder.header(2, 2, this.fields.size); } else { encoder.header(2, 3, this.fields.size + 1); encoder.push(this.label); } for (const field of this.fields) { encoder.push(field); } }; 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.meta = label; ctor.isClassOf = (v) => ((v instanceof Record) && Immutable.is(label, v.label)); return ctor; }; Object.assign(module.exports, { fromJS, List, Map, Set, Float, Single, Double, Bytes, Record, });