diff --git a/jspack.js b/jspack.js index 7e7965b..5d68f12 100644 --- a/jspack.js +++ b/jspack.js @@ -1,285 +1,294 @@ -/* ! - * Copyright © 2008 Fair Oaks Labs, Inc. - * All rights reserved. - */ - -// Utility object: Encode / Decode C-style binary primitives to / from octet arrays -function JSPack() { - // Module-level (private) variables - var el; - var bBE = false; - var m = this; - - // Raw byte arrays - m._DeArray = function (a, p, l) { - return [a.slice(p, p + l)]; - }; - - m._EnArray = function (a, p, l, v) { - for (var i = 0; i < l; a[p + i] = v[i] ? v[i] : 0, i++) { } - }; - - // ASCII characters - m._DeChar = function (a, p) { - return String.fromCharCode(a[p]); - }; - - m._EnChar = function (a, p, v) { - a[p] = v.charCodeAt(0); - }; - - // Little-endian (un)signed N-byte integers - m._DeInt = function (a, p) { - var lsb = bBE ? (el.len - 1) : 0; - var nsb = bBE ? -1 : 1; - var stop = lsb + nsb * el.len; - var rv, i, f; - for (rv = 0, i = lsb, f = 1; i != stop; rv += (a[p + i] * f), i += nsb, f *= 256) { } - if (el.bSigned && (rv & Math.pow(2, el.len * 8 - 1))) { rv -= Math.pow(2, el.len * 8); } - return rv; - }; - - m._EnInt = function (a, p, v) { - var lsb = bBE ? (el.len - 1) : 0; - var nsb = bBE ? -1 : 1; - var stop = lsb + nsb * el.len; - var i; - v = (v < el.min) ? el.min : (v > el.max) ? el.max : v; - for (i = lsb; i != stop; a[p + i] = v & 0xff, i += nsb, v >>= 8) { } - }; - - // ASCII character strings - m._DeString = function (a, p, l) { - for (var rv = new Array(l), i = 0; i < l; rv[i] = String.fromCharCode(a[p + i]), i++) { } - return rv.join(''); - }; - m._EnString = function (a, p, l, v) { - - for (var t, i = 0; i < l; a[p + i] = (t = v.charCodeAt(i)) ? t : 0, i++) { } - }; - - // Little-endian N-bit IEEE 754 floating point - m._De754 = function (a, p) { - var s, e, m, i, d, nBits, mLen, eLen, eBias, eMax; - mLen = el.mLen, eLen = el.len * 8 - el.mLen - 1, eMax = (1 << eLen) - 1, eBias = eMax >> 1; - - i = bBE ? 0 : (el.len - 1); d = bBE ? 1 : -1; s = a[p + i]; i += d; nBits = -7; - for (e = s & ((1 << (-nBits)) - 1), s >>= (-nBits), nBits += eLen; - nBits > 0; e = e * 256 + a[p + i], i += d, nBits -= 8) { } - for (m = e & ((1 << (-nBits)) - 1), e >>= (-nBits), nBits += mLen; nBits > 0; - m = m * 256 + a[p + i], i += d, nBits -= 8) { } - - switch (e) { - - case 0: - // Zero, or denormalized number - e = 1 - eBias; - break; - case eMax: - // NaN, or +/-Infinity - return m ? NaN : ((s ? -1 : 1) * Infinity); - default: - // Normalized number - m = m + Math.pow(2, mLen); - e = e - eBias; - break; - } - return (s ? -1 : 1) * m * Math.pow(2, e - mLen); - }; - m._En754 = function (a, p, v) { - - var s, e, m, i, d, c, mLen, eLen, eBias, eMax; - mLen = el.mLen, eLen = el.len * 8 - el.mLen - 1, eMax = (1 << eLen) - 1, eBias = eMax >> 1; - - s = v < 0 ? 1 : 0; - v = Math.abs(v); - if (isNaN(v) || (v == Infinity)) { - - m = isNaN(v) ? 1 : 0; - e = eMax; - } - else { - e = Math.floor(Math.log(v) / Math.LN2); // Calculate log2 of the value - if (v * (c = Math.pow(2, -e)) < 1) { e--; c *= 2; } // Math.log() isn't 100% reliable - - // Round by adding 1 / 2 the significand's LSD - if (e + eBias >= 1) { - // Normalized: mLen significand digits - v += el.rt / c; - } - else { - // Denormalized: <= mLen significand digits - v += el.rt * Math.pow(2, 1 - eBias); - } - if (v * c >= 2) { - // Rounding can increment the exponent - e++; c /= 2; +"use strict"; + +(function() { + /* ! + * Copyright © 2008 Fair Oaks Labs, Inc. + * All rights reserved. + */ + + // Utility object: Encode / Decode C-style binary primitives to / from octet arrays + function JSPack() { + // Module-level (private) variables + var el; + var bBE = false; + var m = this; + + // Raw byte arrays + m._DeArray = function (a, p, l) { + return [a.slice(p, p + l)]; + }; + + m._EnArray = function (a, p, l, v) { + for (var i = 0; i < l; a[p + i] = v[i] ? v[i] : 0, i++) { } + }; + + // ASCII characters + m._DeChar = function (a, p) { + return String.fromCharCode(a[p]); + }; + + m._EnChar = function (a, p, v) { + a[p] = v.charCodeAt(0); + }; + + // Little-endian (un)signed N-byte integers + m._DeInt = function (a, p) { + var lsb = bBE ? (el.len - 1) : 0; + var nsb = bBE ? -1 : 1; + var stop = lsb + nsb * el.len; + var rv, i, f; + for (rv = 0, i = lsb, f = 1; i != stop; rv += (a[p + i] * f), i += nsb, f *= 256) { } + if (el.bSigned && (rv & Math.pow(2, el.len * 8 - 1))) { rv -= Math.pow(2, el.len * 8); } + return rv; + }; + + m._EnInt = function (a, p, v) { + var lsb = bBE ? (el.len - 1) : 0; + var nsb = bBE ? -1 : 1; + var stop = lsb + nsb * el.len; + var i; + v = (v < el.min) ? el.min : (v > el.max) ? el.max : v; + for (i = lsb; i != stop; a[p + i] = v & 0xff, i += nsb, v >>= 8) { } + }; + + // ASCII character strings + m._DeString = function (a, p, l) { + for (var rv = new Array(l), i = 0; i < l; rv[i] = String.fromCharCode(a[p + i]), i++) { } + return rv.join(''); + }; + m._EnString = function (a, p, l, v) { + + for (var t, i = 0; i < l; a[p + i] = (t = v.charCodeAt(i)) ? t : 0, i++) { } + }; + + // Little-endian N-bit IEEE 754 floating point + m._De754 = function (a, p) { + var s, e, m, i, d, nBits, mLen, eLen, eBias, eMax; + mLen = el.mLen, eLen = el.len * 8 - el.mLen - 1, eMax = (1 << eLen) - 1, eBias = eMax >> 1; + + i = bBE ? 0 : (el.len - 1); d = bBE ? 1 : -1; s = a[p + i]; i += d; nBits = -7; + for (e = s & ((1 << (-nBits)) - 1), s >>= (-nBits), nBits += eLen; + nBits > 0; e = e * 256 + a[p + i], i += d, nBits -= 8) { } + for (m = e & ((1 << (-nBits)) - 1), e >>= (-nBits), nBits += mLen; nBits > 0; + m = m * 256 + a[p + i], i += d, nBits -= 8) { } + + switch (e) { + + case 0: + // Zero, or denormalized number + e = 1 - eBias; + break; + case eMax: + // NaN, or +/-Infinity + return m ? NaN : ((s ? -1 : 1) * Infinity); + default: + // Normalized number + m = m + Math.pow(2, mLen); + e = e - eBias; + break; } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen); + }; + m._En754 = function (a, p, v) { - if (e + eBias >= eMax) { - // Overflow - m = 0; - e = eMax; - } - else if (e + eBias >= 1) { + var s, e, m, i, d, c, mLen, eLen, eBias, eMax; + mLen = el.mLen, eLen = el.len * 8 - el.mLen - 1, eMax = (1 << eLen) - 1, eBias = eMax >> 1; - // Normalized - term order matters, as Math.pow(2, 52 - e) and v * Math.pow(2, 52) can overflow - m = (v * c - 1) * Math.pow(2, mLen); - e = e + eBias; - } - else { + s = v < 0 ? 1 : 0; + v = Math.abs(v); + if (isNaN(v) || (v == Infinity)) { - // Denormalized - also catches the '0' case, somewhat by chance - m = v * Math.pow(2, eBias - 1) * Math.pow(2, mLen); - e = 0; + m = isNaN(v) ? 1 : 0; + e = eMax; } - } - - for (i = bBE ? (el.len - 1) : 0, d = bBE ? -1 : 1; mLen >= 8; a[p + i] = m & 0xff, i += d, m /= 256, mLen -= 8) { } - for (e = (e << mLen) | m, eLen += mLen; eLen > 0; a[p + i] = e & 0xff, i += d, e /= 256, eLen -= 8) { } - a[p + i - d] |= s * 128; - }; - - // Convert int64 to array with 3 elements: [lowBits, highBits, unsignedFlag] - // '>>>' trick to convert signed 32bit int to unsigned int (because << always results in a signed 32bit int) - m._DeInt64 = function (a, p) { - var start = bBE ? 0 : 7; - var nsb = bBE ? 1 : -1; - var stop = start + nsb * 8; - var rv = [0, 0, !el.bSigned]; - var i, f, rvi; - for (i = start, rvi = 1, f = 0; - i != stop; - rv[rvi] = (((rv[rvi] << 8) >>> 0) + a[p + i]), i += nsb, f++, rvi = (f < 4 ? 1 : 0)) {} - return rv; - }; - - m._EnInt64 = function (a, p, v) { - var start = bBE ? 0 : 7; - var nsb = bBE ? 1 : -1; - var stop = start + nsb * 8; - var i, f, rvi, s; - for (i = start, rvi = 1, f = 0, s = 24; - i != stop; - a[p + i] = v[rvi] >> s & 0xff, i += nsb, f++, rvi = (f < 4 ? 1 : 0), s = 24 - (8 * (f % 4))) { } - }; - - // Class data - m._sPattern = '(\\d + )?([AxcbBhHsfdiIlLqQ])'; - m._lenLut = {'A': 1, 'x': 1, 'c': 1, 'b': 1, 'B': 1, 'h': 2, 'H': 2, 's': 1, - 'f': 4, 'd': 8, 'i': 4, 'I': 4, 'l': 4, 'L': 4, 'q': 8, 'Q': 8 - }; - m._elLut = {'A': {en: m._EnArray, de: m._DeArray}, - 's': {en: m._EnString, de: m._DeString}, - 'c': {en: m._EnChar, de: m._DeChar}, - 'b': {en: m._EnInt, de: m._DeInt, len: 1, bSigned: true, min: -Math.pow(2, 7), max: Math.pow(2, 7) - 1}, - 'B': {en: m._EnInt, de: m._DeInt, len: 1, bSigned: false, min: 0, max: Math.pow(2, 8) - 1}, - 'h': {en: m._EnInt, de: m._DeInt, len: 2, bSigned: true, min: -Math.pow(2, 15), max: Math.pow(2, 15) - 1}, - 'H': {en: m._EnInt, de: m._DeInt, len: 2, bSigned: false, min: 0, max: Math.pow(2, 16) - 1}, - 'i': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: true, min: -Math.pow(2, 31), max: Math.pow(2, 31) - 1}, - 'I': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: false, min: 0, max: Math.pow(2, 32) - 1}, - 'l': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: true, min: -Math.pow(2, 31), max: Math.pow(2, 31) - 1}, - 'L': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: false, min: 0, max: Math.pow(2, 32) - 1}, - 'f': {en: m._En754, de: m._De754, len: 4, mLen: 23, rt: Math.pow(2, -24) - Math.pow(2, -77)}, - 'd': {en: m._En754, de: m._De754, len: 8, mLen: 52, rt: 0}, - 'q': {en: m._EnInt64, de: m._DeInt64, bSigned: true}, - 'Q': {en: m._EnInt64, de: m._DeInt64, bSigned: false}}; - - // Unpack a series of n elements of size s from array a at offset p with fxn - m._UnpackSeries = function (n, s, a, p) { - for (var fxn = el.de, rv = [], i = 0; i < n; rv.push(fxn(a, p + i * s)), i++) { } - return rv; - }; - - // Pack a series of n elements of size s from array v at offset i to array a at offset p with fxn - m._PackSeries = function (n, s, a, p, v, i) { - for (var fxn = el.en, o = 0; o < n; fxn(a, p + o * s, v[i + o]), o++) { } - }; - - // Unpack the octet array a, beginning at offset p, according to the fmt string - m.Unpack = function (fmt, a, p) { - // Set the private bBE flag based on the format string - assume big-endianness - bBE = (fmt.charAt(0) != '<'); - - p = p ? p : 0; - var re = new RegExp(this._sPattern, 'g'); - var m, n, s; - var rv = []; - while (m = re.exec(fmt)) { - - n = ((m[1] == undefined) || (m[1] == '')) ? 1 : parseInt(m[1]); - s = this._lenLut[m[2]]; - if ((p + n * s) > a.length) { - - return undefined; + else { + e = Math.floor(Math.log(v) / Math.LN2); // Calculate log2 of the value + if (v * (c = Math.pow(2, -e)) < 1) { e--; c *= 2; } // Math.log() isn't 100% reliable + + // Round by adding 1 / 2 the significand's LSD + if (e + eBias >= 1) { + // Normalized: mLen significand digits + v += el.rt / c; + } + else { + // Denormalized: <= mLen significand digits + v += el.rt * Math.pow(2, 1 - eBias); + } + if (v * c >= 2) { + // Rounding can increment the exponent + e++; c /= 2; + } + + if (e + eBias >= eMax) { + // Overflow + m = 0; + e = eMax; + } + else if (e + eBias >= 1) { + + // Normalized - term order matters, as Math.pow(2, 52 - e) and v * Math.pow(2, 52) can overflow + m = (v * c - 1) * Math.pow(2, mLen); + e = e + eBias; + } + else { + + // Denormalized - also catches the '0' case, somewhat by chance + m = v * Math.pow(2, eBias - 1) * Math.pow(2, mLen); + e = 0; + } } - switch (m[2]) { - case 'A': case 's': - rv.push(this._elLut[m[2]].de(a, p, n)); - break; - case 'c': case 'b': case 'B': case 'h': case 'H': - case 'i': case 'I': case 'l': case 'L': case 'f': case 'd': case 'q': case 'Q': - el = this._elLut[m[2]]; - rv.push(this._UnpackSeries(n, s, a, p)); - break; + for (i = bBE ? (el.len - 1) : 0, d = bBE ? -1 : 1; mLen >= 8; a[p + i] = m & 0xff, i += d, m /= 256, mLen -= 8) { } + for (e = (e << mLen) | m, eLen += mLen; eLen > 0; a[p + i] = e & 0xff, i += d, e /= 256, eLen -= 8) { } + a[p + i - d] |= s * 128; + }; + + // Convert int64 to array with 3 elements: [lowBits, highBits, unsignedFlag] + // '>>>' trick to convert signed 32bit int to unsigned int (because << always results in a signed 32bit int) + m._DeInt64 = function (a, p) { + var start = bBE ? 0 : 7; + var nsb = bBE ? 1 : -1; + var stop = start + nsb * 8; + var rv = [0, 0, !el.bSigned]; + var i, f, rvi; + for (i = start, rvi = 1, f = 0; + i != stop; + rv[rvi] = (((rv[rvi] << 8) >>> 0) + a[p + i]), i += nsb, f++, rvi = (f < 4 ? 1 : 0)) {} + return rv; + }; + + m._EnInt64 = function (a, p, v) { + var start = bBE ? 0 : 7; + var nsb = bBE ? 1 : -1; + var stop = start + nsb * 8; + var i, f, rvi, s; + for (i = start, rvi = 1, f = 0, s = 24; + i != stop; + a[p + i] = v[rvi] >> s & 0xff, i += nsb, f++, rvi = (f < 4 ? 1 : 0), s = 24 - (8 * (f % 4))) { } + }; + + // Class data + m._sPattern = '(\\d+)?([AxcbBhHsfdiIlLqQ])'; + m._lenLut = {'A': 1, 'x': 1, 'c': 1, 'b': 1, 'B': 1, 'h': 2, 'H': 2, 's': 1, + 'f': 4, 'd': 8, 'i': 4, 'I': 4, 'l': 4, 'L': 4, 'q': 8, 'Q': 8 + }; + m._elLut = {'A': {en: m._EnArray, de: m._DeArray}, + 's': {en: m._EnString, de: m._DeString}, + 'c': {en: m._EnChar, de: m._DeChar}, + 'b': {en: m._EnInt, de: m._DeInt, len: 1, bSigned: true, min: -Math.pow(2, 7), max: Math.pow(2, 7) - 1}, + 'B': {en: m._EnInt, de: m._DeInt, len: 1, bSigned: false, min: 0, max: Math.pow(2, 8) - 1}, + 'h': {en: m._EnInt, de: m._DeInt, len: 2, bSigned: true, min: -Math.pow(2, 15), max: Math.pow(2, 15) - 1}, + 'H': {en: m._EnInt, de: m._DeInt, len: 2, bSigned: false, min: 0, max: Math.pow(2, 16) - 1}, + 'i': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: true, min: -Math.pow(2, 31), max: Math.pow(2, 31) - 1}, + 'I': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: false, min: 0, max: Math.pow(2, 32) - 1}, + 'l': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: true, min: -Math.pow(2, 31), max: Math.pow(2, 31) - 1}, + 'L': {en: m._EnInt, de: m._DeInt, len: 4, bSigned: false, min: 0, max: Math.pow(2, 32) - 1}, + 'f': {en: m._En754, de: m._De754, len: 4, mLen: 23, rt: Math.pow(2, -24) - Math.pow(2, -77)}, + 'd': {en: m._En754, de: m._De754, len: 8, mLen: 52, rt: 0}, + 'q': {en: m._EnInt64, de: m._DeInt64, bSigned: true}, + 'Q': {en: m._EnInt64, de: m._DeInt64, bSigned: false}}; + + // Unpack a series of n elements of size s from array a at offset p with fxn + m._UnpackSeries = function (n, s, a, p) { + for (var fxn = el.de, rv = [], i = 0; i < n; rv.push(fxn(a, p + i * s)), i++) { } + return rv; + }; + + // Pack a series of n elements of size s from array v at offset i to array a at offset p with fxn + m._PackSeries = function (n, s, a, p, v, i) { + for (var fxn = el.en, o = 0; o < n; fxn(a, p + o * s, v[i + o]), o++) { } + }; + + // Unpack the octet array a, beginning at offset p, according to the fmt string + m.Unpack = function (fmt, a, p) { + // Set the private bBE flag based on the format string - assume big-endianness + bBE = (fmt.charAt(0) != '<'); + + p = p ? p : 0; + var re = new RegExp(this._sPattern, 'g'); + var m, n, s; + var rv = []; + while (m = re.exec(fmt)) { + + n = ((m[1] == undefined) || (m[1] == '')) ? 1 : parseInt(m[1]); + s = this._lenLut[m[2]]; + if ((p + n * s) > a.length) { + + return undefined; + } + switch (m[2]) { + + case 'A': case 's': + rv.push(this._elLut[m[2]].de(a, p, n)); + break; + case 'c': case 'b': case 'B': case 'h': case 'H': + case 'i': case 'I': case 'l': case 'L': case 'f': case 'd': case 'q': case 'Q': + el = this._elLut[m[2]]; + rv.push(this._UnpackSeries(n, s, a, p)); + break; + } + p += n * s; } - p += n * s; - } - return Array.prototype.concat.apply([], rv); - }; - - // Pack the supplied values into the octet array a, beginning at offset p, according to the fmt string - m.PackTo = function (fmt, a, p, values) { - // Set the private bBE flag based on the format string - assume big-endianness - bBE = (fmt.charAt(0) != '<'); - - var re = new RegExp(this._sPattern, 'g'); - var m, n, s, j; - var i = 0; - while (m = re.exec(fmt)) { - n = ((m[1] == undefined) || (m[1] == '')) ? 1 : parseInt(m[1]); - s = this._lenLut[m[2]]; - if ((p + n * s) > a.length) { - return false; + return Array.prototype.concat.apply([], rv); + }; + + // Pack the supplied values into the octet array a, beginning at offset p, according to the fmt string + m.PackTo = function (fmt, a, p, values) { + // Set the private bBE flag based on the format string - assume big-endianness + bBE = (fmt.charAt(0) != '<'); + + var re = new RegExp(this._sPattern, 'g'); + var m, n, s, j; + var i = 0; + while (m = re.exec(fmt)) { + n = ((m[1] == undefined) || (m[1] == '')) ? 1 : parseInt(m[1]); + s = this._lenLut[m[2]]; + if ((p + n * s) > a.length) { + return false; + } + switch (m[2]) { + case 'A': case 's': + if ((i + 1) > values.length) { return false; } + this._elLut[m[2]].en(a, p, n, values[i]); + i += 1; + break; + case 'c': case 'b': case 'B': case 'h': case 'H': + case 'i': case 'I': case 'l': case 'L': case 'f': case 'd': case 'q': case 'Q': + el = this._elLut[m[2]]; + if ((i + n) > values.length) { return false; } + this._PackSeries(n, s, a, p, values, i); + i += n; + break; + case 'x': + for (j = 0; j < n; j++) { a[p + j] = 0; } + break; + } + p += n * s; } - switch (m[2]) { - case 'A': case 's': - if ((i + 1) > values.length) { return false; } - this._elLut[m[2]].en(a, p, n, values[i]); - i += 1; - break; - case 'c': case 'b': case 'B': case 'h': case 'H': - case 'i': case 'I': case 'l': case 'L': case 'f': case 'd': case 'q': case 'Q': - el = this._elLut[m[2]]; - if ((i + n) > values.length) { return false; } - this._PackSeries(n, s, a, p, values, i); - i += n; - break; - case 'x': - for (j = 0; j < n; j++) { a[p + j] = 0; } - break; + return a; + }; + + // Pack the supplied values into a new octet array, according to the fmt string + m.Pack = function (fmt, values) { + return this.PackTo(fmt, new Array(this.CalcLength(fmt)), 0, values); + }; + + // Determine the number of bytes represented by the format string + m.CalcLength = function (fmt) { + var re = new RegExp(this._sPattern, 'g'); + var sum = 0; + var m; + while (m = re.exec(fmt)) { + sum += (((m[1] == undefined) || (m[1] == '')) ? 1 : parseInt(m[1])) * this._lenLut[m[2]]; } - p += n * s; - } - return a; - }; - - // Pack the supplied values into a new octet array, according to the fmt string - m.Pack = function (fmt, values) { - return this.PackTo(fmt, new Array(this.CalcLength(fmt)), 0, values); - }; - - // Determine the number of bytes represented by the format string - m.CalcLength = function (fmt) { - var re = new RegExp(this._sPattern, 'g'); - var sum = 0; - var m; - while (m = re.exec(fmt)) { - sum += (((m[1] == undefined) || (m[1] == '')) ? 1 : parseInt(m[1])) * this._lenLut[m[2]]; - } - return sum; - }; -} - -exports.jspack = new JSPack(); + return sum; + }; + } + + if (typeof module !== 'undefined') { + module.exports = new JSPack; + } else { + this.jspack = new JSPack; + } + +}).call(this); diff --git a/test/int64.js b/test/int64.js index 00394fc..f5f2ce6 100644 --- a/test/int64.js +++ b/test/int64.js @@ -1,5 +1,5 @@ var should = require('should'); -var jspack = require('../jspack.js').jspack; +var jspack = require('../jspack.js'); var Long = require('long'); describe('Test long integration (examples):', function () {