superpack
Version:
JavaScript implementation of the SuperPack extensible schemaless binary encoding format
280 lines (246 loc) • 10.2 kB
JavaScript
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/* global Uint8Array */
import tags from './type-tags.js';
import Extendable from './extendable.js';
/*
todo:
mapl, bmapl
timestamp
extension
factor out various common threads
*/
var Decoder = function (_Extendable) {
_inherits(Decoder, _Extendable);
function Decoder() {
_classCallCheck(this, Decoder);
var _this = _possibleConstructorReturn(this, (Decoder.__proto__ || Object.getPrototypeOf(Decoder)).call(this));
_this.strings = [];
_this.keysets = [];
_this.buffer = [];
_this.ptr = 0;
return _this;
}
_createClass(Decoder, [{
key: 'decode',
value: function decode(buffer) {
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
this.buffer = buffer;
if (buffer[0] === tags.STRLUT) {
this.strings = this.decodeValue();
this.keysets = this.decodeValue();
}
if (options.omittedKeysets != null) {
var k = [];
[].push.apply(k, options.omittedKeysets);
[].push.apply(k, this.keysets);
this.keysets = k;
}
return this.decodeValue();
}
}, {
key: 'readUInt32',
value: function readUInt32() {
return this.buffer[this.ptr++] * 0x1000000 + (this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]);
}
}, {
key: 'readFloat',
value: function readFloat(eBits, mBits) {
var bias = (1 << eBits - 1) - 1;
var byteLength = (eBits + mBits + 1) / 8;
var bytes = this.buffer.slice(this.ptr, this.ptr + byteLength);
this.ptr += byteLength;
// read sign, exponent, and beginning of mantissa from first two bytes
var sign = bytes[0] >>> 7 > 0 ? -1 : 1;
var leadingBytes = bytes[0] << 8 | bytes[1];
var leadingMBits = 16 - (eBits + 1);
var exp = leadingBytes >>> leadingMBits & (1 << eBits) - 1;
var mantissa = leadingBytes & (1 << leadingMBits) - 1;
// read remainder of mantissa
for (var i = 2; i < byteLength; ++i) {
mantissa = mantissa * 256 + bytes[i];
}
if (exp === (1 << eBits) - 1) {
// NaN and +/- Infinity
return (mantissa === 0 ? sign : 0) / 0;
} else if (exp > 0) {
// normal
return sign * Math.pow(2, exp - bias) * (1 + mantissa / Math.pow(2, mBits));
} else if (mantissa !== 0) {
// subnormal
return sign * Math.pow(2, -(bias - 1)) * (mantissa / Math.pow(2, mBits));
}
return sign * 0;
}
}, {
key: 'readString',
value: function readString(length) {
var str = '';
for (var i = 0; i < length; ++i) {
str += String.fromCharCode(this.buffer[this.ptr++]);
}
return decodeURIComponent(escape(str));
}
}, {
key: 'readArray',
value: function readArray(length) {
var out = [];
for (var i = 0; i < length; ++i) {
out.push(this.decodeValue());
}
return out;
}
}, {
key: 'readBooleanArray',
value: function readBooleanArray(length) {
var out = [];
for (var i = 0; i < length; ++i) {
out[i] = (this.buffer[this.ptr + (i >> 3)] & 0x80 >> i % 8) > 0;
}
this.ptr += (length >> 3) + (length % 8 !== 0);
return out;
}
// todo: factor out "read type_ length value"
// readUInt() { ... }
}, {
key: 'decodeValue',
value: function decodeValue() {
var _this2 = this;
var type = this.buffer[this.ptr++];
if (type < tags.UINT14_BASE) {
return type;
} else if (type < tags.NINT4_BASE) {
return (type ^ tags.UINT14_BASE) << 8 | this.buffer[this.ptr++];
} else if (type < tags.BARRAY4_BASE) {
return -(type ^ tags.NINT4_BASE);
} else if (type < tags.ARRAY5_BASE) {
return this.readBooleanArray(type ^ tags.BARRAY4_BASE);
} else if (type < tags.STR5_BASE) {
return this.readArray(type ^ tags.ARRAY5_BASE);
} else if (type < tags.FALSE) {
return this.readString(type ^ tags.STR5_BASE);
}
switch (type) {
case tags.FALSE:
return false;
case tags.TRUE:
return true;
case tags.NULL:
return null;
case tags.UNDEFINED:
return void 0;
case tags.UINT16:
return this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++];
case tags.UINT24:
return this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++];
case tags.UINT32:
return this.readUInt32();
case tags.UINT64:
return this.readUInt32() * 0x100000000 + this.readUInt32();
case tags.NINT8:
return -this.buffer[this.ptr++];
case tags.NINT16:
return -(this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]);
case tags.NINT32:
return -this.readUInt32();
case tags.NINT64:
return -(this.readUInt32() * 0x100000000 + this.readUInt32());
case tags.FLOAT32:
return this.readFloat(8, 23);
case tags.DOUBLE64:
return this.readFloat(11, 52);
case tags.TIMESTAMP:
return new Date((this.buffer[this.ptr] & 0x80) > 0 ? -((~(this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]) & 0xFFFFFF) * 0x1000000 + (~(this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]) & 0xFFFFFF) + 1) : (this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]) * 0x1000000 + (this.buffer[this.ptr++] << 16 | this.buffer[this.ptr++] << 8 | this.buffer[this.ptr++]));
case tags.BINARY_:
{
var length = this.decodeValue();
// alternative to Uint8Array.from(...) for old browsers
var out = new Uint8Array(length);
for (var i = 0; i < length; ++i) {
out[i] = this.buffer[this.ptr + i];
}
this.ptr += length;
return out.buffer;
}
case tags.CSTRING:
{
var str = '';
while (this.buffer[this.ptr] !== 0) {
str += String.fromCharCode(this.buffer[this.ptr++]);
}
this.ptr++;
return decodeURIComponent(escape(str));
}
case tags.STR8:
return this.readString(this.buffer[this.ptr++]);
case tags.STR_:
return this.readString(this.decodeValue());
case tags.STRREF:
return this.strings[this.buffer[this.ptr++]];
case tags.ARRAY8:
case tags.STRLUT:
return this.readArray(this.buffer[this.ptr++]);
case tags.ARRAY_:
return this.readArray(this.decodeValue());
case tags.BARRAY8:
return this.readBooleanArray(this.buffer[this.ptr++]);
case tags.BARRAY_:
return this.readBooleanArray(this.decodeValue());
case tags.MAP_:
{
var _ret = function () {
var out = {};
var keysetIndex = _this2.decodeValue();
var keys = _this2.keysets[keysetIndex];
keys.forEach(function (key) {
out[key] = _this2.decodeValue();
});
return {
v: out
};
}();
if (typeof _ret === "object") return _ret.v;
}
case tags.BMAP_:
{
var _out = {};
var _keysetIndex = this.decodeValue();
var _keys = this.keysets[_keysetIndex];
var bools = this.readBooleanArray(_keys.length);
for (var _i = 0; _i < _keys.length; ++_i) {
_out[_keys[_i]] = bools[_i];
}
return _out;
}
case tags.EXTENSION:
{
var ext = this.decodeValue();
return this.extensions[ext].deserialiser(this.decodeValue());
}
default:
// This should never happen.
throw new Error('Unknown case');
}
}
}], [{
key: 'decode',
value: function decode(buffer) {
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var d = new Decoder();
var extensions = options.extensions;
if (extensions != null) {
// $FlowFixMe: flow doesn't understand that ext is an ExtensionPoint
Object.keys(extensions).forEach(function (ext) {
var extension = extensions[ext];
d.extend(ext, extension.detector, extension.serialiser, extension.deserialiser);
});
}
return d.decode(buffer, options);
}
}]);
return Decoder;
}(Extendable);
export default Decoder;
export var decode = Decoder.decode;