Comparing version 0.0.5 to 0.0.6
@@ -1,1 +0,1 @@ | ||
{"processes":{"52a87e06-c81a-40bf-9c84-fa6e0982a66b":{"parent":null,"children":[]}},"files":{},"externalIds":{}} | ||
{"processes":{"317fc422-f87c-4e82-96aa-626977a5fda9":{"parent":null,"children":[]}},"files":{},"externalIds":{}} |
126
decoding.js
@@ -5,2 +5,3 @@ /** | ||
import * as buffer from './buffer.js' | ||
import * as binary from './binary.js' | ||
@@ -185,6 +186,6 @@ /** | ||
while (true) { | ||
let r = decoder.arr[decoder.pos++] | ||
num = num | ((r & 0b1111111) << len) | ||
const r = decoder.arr[decoder.pos++] | ||
num = num | ((r & binary.BITS7) << len) | ||
len += 7 | ||
if (r < 1 << 7) { | ||
if (r < binary.BIT8) { | ||
return num >>> 0 // return unsigned number! | ||
@@ -200,2 +201,35 @@ } | ||
/** | ||
* Read signed integer (32bit) with variable length. | ||
* 1/8th of the storage is used as encoding overhead. | ||
* * numbers < 2^7 is stored in one bytlength | ||
* * numbers < 2^14 is stored in two bylength | ||
* | ||
* @function | ||
* @param {Decoder} decoder | ||
* @return {number} An unsigned integer.length | ||
*/ | ||
export const readVarInt = decoder => { | ||
let r = decoder.arr[decoder.pos++] | ||
let num = r & binary.BITS6 | ||
let len = 6 | ||
const sign = (r & binary.BIT7) > 0 ? -1 : 1 | ||
if ((r & binary.BIT8) === 0) { | ||
// don't continue reading | ||
return sign * num | ||
} | ||
while (true) { | ||
r = decoder.arr[decoder.pos++] | ||
num = num | ((r & binary.BITS7) << len) | ||
len += 7 | ||
if (r < binary.BIT8) { | ||
return sign * num | ||
} | ||
/* istanbul ignore if */ | ||
if (len > 41) { | ||
throw new Error('Integer out of range!') | ||
} | ||
} | ||
} | ||
/** | ||
* Look ahead and read varUint without incrementing position | ||
@@ -215,2 +249,16 @@ * | ||
/** | ||
* Look ahead and read varUint without incrementing position | ||
* | ||
* @function | ||
* @param {Decoder} decoder | ||
* @return {number} | ||
*/ | ||
export const peekVarInt = decoder => { | ||
const pos = decoder.pos | ||
const s = readVarInt(decoder) | ||
decoder.pos = pos | ||
return s | ||
} | ||
/** | ||
* Read string of variable length | ||
@@ -256,1 +304,73 @@ * * varUint is used to store the length of the string | ||
} | ||
/** | ||
* @param {Decoder} decoder | ||
* @param {number} len | ||
* @return {DataView} | ||
*/ | ||
export const readFromDataView = (decoder, len) => { | ||
const dv = new DataView(decoder.arr.buffer, decoder.arr.byteOffset + decoder.pos, len) | ||
decoder.pos += len | ||
return dv | ||
} | ||
/** | ||
* @param {Decoder} decoder | ||
*/ | ||
export const readFloat32 = decoder => readFromDataView(decoder, 4).getFloat32(0) | ||
/** | ||
* @param {Decoder} decoder | ||
*/ | ||
export const readFloat64 = decoder => readFromDataView(decoder, 8).getFloat64(0) | ||
/** | ||
* @param {Decoder} decoder | ||
*/ | ||
export const readBigInt64 = decoder => readFromDataView(decoder, 8).getBigInt64(0) | ||
/** | ||
* @param {Decoder} decoder | ||
*/ | ||
export const readBigUint64 = decoder => readFromDataView(decoder, 8).getBigUint64(0) | ||
/** | ||
* @type {Array<function(Decoder):any>} | ||
*/ | ||
const readAnyLookupTable = [ | ||
decoder => undefined, // CASE 127: undefined | ||
decoder => null, // CASE 126: null | ||
readVarInt, // CASE 125: integer | ||
readFloat32, // CASE 124: float32 | ||
readFloat64, // CASE 123: float64 | ||
readBigInt64, // CASE 122: bigint | ||
decoder => false, // CASE 121: boolean (false) | ||
decoder => true, // CASE 120: boolean (true) | ||
readVarString, // CASE 119: string | ||
decoder => { // CASE 118: object<string,any> | ||
const len = readVarUint(decoder) | ||
/** | ||
* @type {Object<string,any>} | ||
*/ | ||
const obj = {} | ||
for (let i = 0; i < len; i++) { | ||
const key = readVarString(decoder) | ||
obj[key] = readAny(decoder) | ||
} | ||
return obj | ||
}, | ||
decoder => { // CASE 117: array<any> | ||
const len = readVarUint(decoder) | ||
const arr = [] | ||
for (let i = 0; i < len; i++) { | ||
arr.push(readAny(decoder)) | ||
} | ||
return arr | ||
}, | ||
readVarUint8Array // CASE 116: Uint8Array | ||
] | ||
/** | ||
* @param {Decoder} decoder | ||
*/ | ||
export const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder) |
@@ -5,2 +5,3 @@ 'use strict'; | ||
var binary = require('./binary.js'); | ||
require('./map.js'); | ||
@@ -193,6 +194,6 @@ require('./string.js'); | ||
while (true) { | ||
let r = decoder.arr[decoder.pos++]; | ||
num = num | ((r & 0b1111111) << len); | ||
const r = decoder.arr[decoder.pos++]; | ||
num = num | ((r & binary.BITS7) << len); | ||
len += 7; | ||
if (r < 1 << 7) { | ||
if (r < binary.BIT8) { | ||
return num >>> 0 // return unsigned number! | ||
@@ -208,2 +209,35 @@ } | ||
/** | ||
* Read signed integer (32bit) with variable length. | ||
* 1/8th of the storage is used as encoding overhead. | ||
* * numbers < 2^7 is stored in one bytlength | ||
* * numbers < 2^14 is stored in two bylength | ||
* | ||
* @function | ||
* @param {Decoder} decoder | ||
* @return {number} An unsigned integer.length | ||
*/ | ||
const readVarInt = decoder => { | ||
let r = decoder.arr[decoder.pos++]; | ||
let num = r & binary.BITS6; | ||
let len = 6; | ||
const sign = (r & binary.BIT7) > 0 ? -1 : 1; | ||
if ((r & binary.BIT8) === 0) { | ||
// don't continue reading | ||
return sign * num | ||
} | ||
while (true) { | ||
r = decoder.arr[decoder.pos++]; | ||
num = num | ((r & binary.BITS7) << len); | ||
len += 7; | ||
if (r < binary.BIT8) { | ||
return sign * num | ||
} | ||
/* istanbul ignore if */ | ||
if (len > 41) { | ||
throw new Error('Integer out of range!') | ||
} | ||
} | ||
}; | ||
/** | ||
* Look ahead and read varUint without incrementing position | ||
@@ -223,2 +257,16 @@ * | ||
/** | ||
* Look ahead and read varUint without incrementing position | ||
* | ||
* @function | ||
* @param {Decoder} decoder | ||
* @return {number} | ||
*/ | ||
const peekVarInt = decoder => { | ||
const pos = decoder.pos; | ||
const s = readVarInt(decoder); | ||
decoder.pos = pos; | ||
return s | ||
}; | ||
/** | ||
* Read string of variable length | ||
@@ -265,2 +313,74 @@ * * varUint is used to store the length of the string | ||
/** | ||
* @param {Decoder} decoder | ||
* @param {number} len | ||
* @return {DataView} | ||
*/ | ||
const readFromDataView = (decoder, len) => { | ||
const dv = new DataView(decoder.arr.buffer, decoder.arr.byteOffset + decoder.pos, len); | ||
decoder.pos += len; | ||
return dv | ||
}; | ||
/** | ||
* @param {Decoder} decoder | ||
*/ | ||
const readFloat32 = decoder => readFromDataView(decoder, 4).getFloat32(0); | ||
/** | ||
* @param {Decoder} decoder | ||
*/ | ||
const readFloat64 = decoder => readFromDataView(decoder, 8).getFloat64(0); | ||
/** | ||
* @param {Decoder} decoder | ||
*/ | ||
const readBigInt64 = decoder => readFromDataView(decoder, 8).getBigInt64(0); | ||
/** | ||
* @param {Decoder} decoder | ||
*/ | ||
const readBigUint64 = decoder => readFromDataView(decoder, 8).getBigUint64(0); | ||
/** | ||
* @type {Array<function(Decoder):any>} | ||
*/ | ||
const readAnyLookupTable = [ | ||
decoder => undefined, // CASE 127: undefined | ||
decoder => null, // CASE 126: null | ||
readVarInt, // CASE 125: integer | ||
readFloat32, // CASE 124: float32 | ||
readFloat64, // CASE 123: float64 | ||
readBigInt64, // CASE 122: bigint | ||
decoder => false, // CASE 121: boolean (false) | ||
decoder => true, // CASE 120: boolean (true) | ||
readVarString, // CASE 119: string | ||
decoder => { // CASE 118: object<string,any> | ||
const len = readVarUint(decoder); | ||
/** | ||
* @type {Object<string,any>} | ||
*/ | ||
const obj = {}; | ||
for (let i = 0; i < len; i++) { | ||
const key = readVarString(decoder); | ||
obj[key] = readAny(decoder); | ||
} | ||
return obj | ||
}, | ||
decoder => { // CASE 117: array<any> | ||
const len = readVarUint(decoder); | ||
const arr = []; | ||
for (let i = 0; i < len; i++) { | ||
arr.push(readAny(decoder)); | ||
} | ||
return arr | ||
}, | ||
readVarUint8Array // CASE 116: Uint8Array | ||
]; | ||
/** | ||
* @param {Decoder} decoder | ||
*/ | ||
const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder); | ||
exports.Decoder = Decoder; | ||
@@ -273,4 +393,11 @@ exports.clone = clone; | ||
exports.peekUint8 = peekUint8; | ||
exports.peekVarInt = peekVarInt; | ||
exports.peekVarString = peekVarString; | ||
exports.peekVarUint = peekVarUint; | ||
exports.readAny = readAny; | ||
exports.readBigInt64 = readBigInt64; | ||
exports.readBigUint64 = readBigUint64; | ||
exports.readFloat32 = readFloat32; | ||
exports.readFloat64 = readFloat64; | ||
exports.readFromDataView = readFromDataView; | ||
exports.readTailAsUint8Array = readTailAsUint8Array; | ||
@@ -281,2 +408,3 @@ exports.readUint16 = readUint16; | ||
exports.readUint8Array = readUint8Array; | ||
exports.readVarInt = readVarInt; | ||
exports.readVarString = readVarString; | ||
@@ -283,0 +411,0 @@ exports.readVarUint = readVarUint; |
@@ -5,2 +5,3 @@ 'use strict'; | ||
var binary = require('./binary.js'); | ||
require('./map.js'); | ||
@@ -10,2 +11,4 @@ require('./string.js'); | ||
var buffer = require('./buffer.js'); | ||
var math = require('./math.js'); | ||
var number = require('./number.js'); | ||
@@ -19,5 +22,2 @@ /** | ||
const bits7 = 0b1111111; | ||
const bits8 = 0b11111111; | ||
/** | ||
@@ -78,2 +78,18 @@ * A BinaryEncoder handles the encoding to an Uint8Array. | ||
/** | ||
* Verify that it is possible to write `len` bytes wtihout checking. If | ||
* necessary, a new Buffer with the required length is attached. | ||
* | ||
* @param {Encoder} encoder | ||
* @param {number} len | ||
*/ | ||
const verifyLen = (encoder, len) => { | ||
const bufferLen = encoder.cbuf.length; | ||
if (bufferLen - encoder.cpos < len) { | ||
encoder.bufs.push(buffer.createUint8ArrayViewFromArrayBuffer(encoder.cbuf.buffer, 0, encoder.cpos)); | ||
encoder.cbuf = new Uint8Array(math.max(bufferLen, len) * 2); | ||
encoder.cpos = 0; | ||
} | ||
}; | ||
/** | ||
* Write one byte to the encoder. | ||
@@ -86,5 +102,6 @@ * | ||
const write = (encoder, num) => { | ||
if (encoder.cpos === encoder.cbuf.length) { | ||
const bufferLen = encoder.cbuf.length; | ||
if (encoder.cpos === bufferLen) { | ||
encoder.bufs.push(encoder.cbuf); | ||
encoder.cbuf = new Uint8Array(encoder.cbuf.length * 2); | ||
encoder.cbuf = new Uint8Array(bufferLen * 2); | ||
encoder.cpos = 0; | ||
@@ -129,3 +146,3 @@ } | ||
*/ | ||
const writeUint8 = (encoder, num) => write(encoder, num & bits8); | ||
const writeUint8 = write; | ||
@@ -140,3 +157,3 @@ /** | ||
*/ | ||
const setUint8 = (encoder, pos, num) => set(encoder, pos, num & bits8); | ||
const setUint8 = set; | ||
@@ -151,4 +168,4 @@ /** | ||
const writeUint16 = (encoder, num) => { | ||
write(encoder, num & bits8); | ||
write(encoder, (num >>> 8) & bits8); | ||
write(encoder, num & binary.BITS8); | ||
write(encoder, (num >>> 8) & binary.BITS8); | ||
}; | ||
@@ -164,4 +181,4 @@ /** | ||
const setUint16 = (encoder, pos, num) => { | ||
set(encoder, pos, num & bits8); | ||
set(encoder, pos + 1, (num >>> 8) & bits8); | ||
set(encoder, pos, num & binary.BITS8); | ||
set(encoder, pos + 1, (num >>> 8) & binary.BITS8); | ||
}; | ||
@@ -178,3 +195,3 @@ | ||
for (let i = 0; i < 4; i++) { | ||
write(encoder, num & bits8); | ||
write(encoder, num & binary.BITS8); | ||
num >>>= 8; | ||
@@ -194,3 +211,3 @@ } | ||
for (let i = 0; i < 4; i++) { | ||
set(encoder, pos + i, num & bits8); | ||
set(encoder, pos + i, num & binary.BITS8); | ||
num >>>= 8; | ||
@@ -210,10 +227,36 @@ } | ||
const writeVarUint = (encoder, num) => { | ||
while (num >= 0b10000000) { | ||
write(encoder, 0b10000000 | (bits7 & num)); | ||
while (num > binary.BITS7) { | ||
write(encoder, binary.BIT8 | (binary.BITS7 & num)); | ||
num >>>= 7; | ||
} | ||
write(encoder, bits7 & num); | ||
write(encoder, binary.BITS7 & num); | ||
}; | ||
/** | ||
* Write a variable length integer. | ||
* | ||
* Encodes integers in the range from [-2147483648, -2147483647]. | ||
* | ||
* @function | ||
* @param {Encoder} encoder | ||
* @param {number} num The number that is to be encoded. | ||
*/ | ||
const writeVarInt = (encoder, num) => { | ||
let isNegative = false; | ||
if (num < 0) { | ||
num = -num; | ||
isNegative = true; | ||
} | ||
// |- whether to continue reading |- whether is negative |- number | ||
write(encoder, (num > binary.BITS6 ? binary.BIT8 : 0) | (isNegative ? binary.BIT7 : 0) | (binary.BITS6 & num)); | ||
num >>>= 6; | ||
// We don't need to consider the case of num === 0 so we can use a different | ||
// pattern here than above. | ||
while (num > 0) { | ||
write(encoder, (num > binary.BITS7 ? binary.BIT8 : 0) | (binary.BITS7 & num)); | ||
num >>>= 7; | ||
} | ||
}; | ||
/** | ||
* Write a variable length string. | ||
@@ -274,2 +317,165 @@ * | ||
/** | ||
* Create an DataView of the next `len` bytes. Use it to write data after | ||
* calling this function. | ||
* | ||
* @example | ||
* // write float32 using DataView | ||
* const dv = writeOnDataView(encoder, 4) | ||
* dv.setFloat32(0, 1.1) | ||
* // read float32 using DataView | ||
* const dv = readFromDataView(encoder, 4) | ||
* dv.getFloat32(0) // => 1.100000023841858 (leaving it to the reader to find out why this is the correct result) | ||
* | ||
* @param {Encoder} encoder | ||
* @param {number} len | ||
* @return {DataView} | ||
*/ | ||
const writeOnDataView = (encoder, len) => { | ||
verifyLen(encoder, len); | ||
const dview = new DataView(encoder.cbuf.buffer, encoder.cpos, len); | ||
encoder.cpos += len; | ||
return dview | ||
}; | ||
/** | ||
* @param {Encoder} encoder | ||
* @param {number} num | ||
*/ | ||
const writeFloat32 = (encoder, num) => writeOnDataView(encoder, 4).setFloat32(0, num); | ||
/** | ||
* @param {Encoder} encoder | ||
* @param {number} num | ||
*/ | ||
const writeFloat64 = (encoder, num) => writeOnDataView(encoder, 8).setFloat64(0, num); | ||
/** | ||
* @param {Encoder} encoder | ||
* @param {bigint} num | ||
*/ | ||
const writeBigInt64 = (encoder, num) => writeOnDataView(encoder, 8).setBigInt64(0, num); | ||
/** | ||
* @param {Encoder} encoder | ||
* @param {bigint} num | ||
*/ | ||
const writeBigUint64 = (encoder, num) => writeOnDataView(encoder, 8).setBigUint64(0, num); | ||
/** | ||
* Check if a number can be encoded as a 32 bit float. | ||
* | ||
* @param {number} num | ||
* @return {boolean} | ||
*/ | ||
const isFloat32 = num => { | ||
const dv = new DataView(new ArrayBuffer(4)); | ||
dv.setFloat32(0, num); | ||
return dv.getFloat32(0) === num | ||
}; | ||
/** | ||
* Encode data with efficient binary format. | ||
* | ||
* Differences to JSON: | ||
* • Transforms data to a binary format (not to a string) | ||
* • Encodes undefined, NaN, and ArrayBuffer (these can't be represented in JSON) | ||
* • Numbers are efficiently encoded either as a variable length integer, as a | ||
* 32 bit float, as a 64 bit float, or as a 64 bit bigint. | ||
* | ||
* Encoding table: | ||
* | ||
* | Data Type | Prefix | Encoding Method | Comment | | ||
* | ------------------- | -------- | ------------------ | ------- | | ||
* | undefined | 127 | | Functions, symbol, and everything that cannot be identified is encoded as undefined | | ||
* | null | 126 | | | | ||
* | integer | 125 | writeVarInt | Only encodes 32 bit signed integers | | ||
* | float32 | 124 | writeFloat32 | | | ||
* | float64 | 123 | writeFloat64 | | | ||
* | bigint | 122 | writeBigInt64 | | | ||
* | boolean (false) | 121 | | True and false are different data types so we save the following byte | | ||
* | boolean (true) | 120 | | - 0b01111000 so the last bit determines whether true or false | | ||
* | string | 119 | writeVarString | | | ||
* | object<string,any> | 118 | custom | Writes {length} then {length} key-value pairs | | ||
* | array<any> | 117 | custom | Writes {length} then {length} json values | | ||
* | Uint8Array | 116 | writeVarUint8Array | We use Uint8Array for any kind of binary data | | ||
* | ||
* Reasons for the decreasing prefix: | ||
* We need the first bit for extendability (later we may want to encode the | ||
* prefix with writeVarUint). The remaining 7 bits are divided as follows: | ||
* [0-30] the beginning of the data range is used for custom purposes | ||
* (defined by the function that uses this library) | ||
* [31-127] the end of the data range is used for data encoding by | ||
* lib0/encoding.js | ||
* | ||
* @param {Encoder} encoder | ||
* @param {undefined|null|number|bigint|boolean|string|Object|Array|ArrayBuffer} data | ||
*/ | ||
const writeAny = (encoder, data) => { | ||
switch (typeof data) { | ||
case 'string': | ||
// TYPE 119: STRING | ||
write(encoder, 119); | ||
writeVarString(encoder, data); | ||
break | ||
case 'number': | ||
if (number.isInteger(data) && data <= binary.BITS31) { | ||
// TYPE 125: INTEGER | ||
write(encoder, 125); | ||
writeVarInt(encoder, data); | ||
} else if (isFloat32(data)) { | ||
// TYPE 124: FLOAT32 | ||
write(encoder, 124); | ||
writeFloat32(encoder, data); | ||
} else { | ||
// TYPE 123: FLOAT64 | ||
write(encoder, 123); | ||
writeFloat64(encoder, data); | ||
} | ||
break | ||
case 'bigint': | ||
// TYPE 122: BigInt | ||
write(encoder, 122); | ||
writeBigInt64(encoder, data); | ||
break | ||
case 'object': | ||
if (data === null) { | ||
// TYPE 126: null | ||
write(encoder, 126); | ||
} else if (data instanceof Array) { | ||
// TYPE 117: Array | ||
write(encoder, 117); | ||
writeVarUint(encoder, data.length); | ||
for (let i = 0; i < data.length; i++) { | ||
writeAny(encoder, data[i]); | ||
} | ||
} else if (data instanceof Uint8Array) { | ||
// TYPE 116: ArrayBuffer | ||
write(encoder, 116); | ||
writeVarUint8Array(encoder, data); | ||
} else { | ||
// TYPE 118: Object | ||
write(encoder, 118); | ||
const keys = Object.keys(data); | ||
writeVarUint(encoder, keys.length); | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i]; | ||
writeVarString(encoder, key); | ||
writeAny(encoder, data[key]); | ||
} | ||
} | ||
break | ||
case 'boolean': | ||
// TYPE 120/121: boolean (true/false) | ||
write(encoder, data ? 120 : 121); | ||
break | ||
case 'symbol': | ||
case 'function': | ||
case 'undefined': | ||
default: | ||
// TYPE 127: undefined | ||
write(encoder, 127); | ||
} | ||
}; | ||
exports.Encoder = Encoder; | ||
@@ -284,3 +490,9 @@ exports.createEncoder = createEncoder; | ||
exports.write = write; | ||
exports.writeAny = writeAny; | ||
exports.writeBigInt64 = writeBigInt64; | ||
exports.writeBigUint64 = writeBigUint64; | ||
exports.writeBinaryEncoder = writeBinaryEncoder; | ||
exports.writeFloat32 = writeFloat32; | ||
exports.writeFloat64 = writeFloat64; | ||
exports.writeOnDataView = writeOnDataView; | ||
exports.writeUint16 = writeUint16; | ||
@@ -290,2 +502,3 @@ exports.writeUint32 = writeUint32; | ||
exports.writeUint8Array = writeUint8Array; | ||
exports.writeVarInt = writeVarInt; | ||
exports.writeVarString = writeVarString; | ||
@@ -292,0 +505,0 @@ exports.writeVarUint = writeVarUint; |
@@ -10,6 +10,6 @@ 'use strict'; | ||
var dom = require('./dom.js'); | ||
var math = require('./math.js'); | ||
var eventloop = require('./eventloop.js'); | ||
var json = require('./json.js'); | ||
var symbol = require('./symbol.js'); | ||
var math = require('./math.js'); | ||
@@ -16,0 +16,0 @@ const BOLD = symbol.create(); |
@@ -5,2 +5,5 @@ 'use strict'; | ||
var binary = require('./binary.js'); | ||
var math = require('./math.js'); | ||
/** | ||
@@ -13,4 +16,14 @@ * @module number | ||
const LOWEST_INT32 = 1 << 31; | ||
const HIGHEST_INT32 = binary.BITS31; | ||
const isInteger = Number.isInteger || (num => typeof num === 'number' && isFinite(num) && math.floor(num) === num); | ||
const isNaN = Number.isNaN; | ||
exports.HIGHEST_INT32 = HIGHEST_INT32; | ||
exports.LOWEST_INT32 = LOWEST_INT32; | ||
exports.MAX_SAFE_INTEGER = MAX_SAFE_INTEGER; | ||
exports.MIN_SAFE_INTEGER = MIN_SAFE_INTEGER; | ||
exports.isInteger = isInteger; | ||
exports.isNaN = isNaN; | ||
//# sourceMappingURL=number.js.map |
@@ -14,6 +14,6 @@ 'use strict'; | ||
require('./dom.js'); | ||
var math = require('./math.js'); | ||
require('./eventloop.js'); | ||
var json = require('./json.js'); | ||
require('./symbol.js'); | ||
var math = require('./math.js'); | ||
var logging = require('./logging.js'); | ||
@@ -20,0 +20,0 @@ var object = require('./object.js'); |
238
encoding.js
@@ -8,6 +8,6 @@ /** | ||
import * as buffer from './buffer.js' | ||
import * as math from './math.js' | ||
import * as number from './number.js' | ||
import * as binary from './binary.js' | ||
const bits7 = 0b1111111 | ||
const bits8 = 0b11111111 | ||
/** | ||
@@ -68,2 +68,18 @@ * A BinaryEncoder handles the encoding to an Uint8Array. | ||
/** | ||
* Verify that it is possible to write `len` bytes wtihout checking. If | ||
* necessary, a new Buffer with the required length is attached. | ||
* | ||
* @param {Encoder} encoder | ||
* @param {number} len | ||
*/ | ||
const verifyLen = (encoder, len) => { | ||
const bufferLen = encoder.cbuf.length | ||
if (bufferLen - encoder.cpos < len) { | ||
encoder.bufs.push(buffer.createUint8ArrayViewFromArrayBuffer(encoder.cbuf.buffer, 0, encoder.cpos)) | ||
encoder.cbuf = new Uint8Array(math.max(bufferLen, len) * 2) | ||
encoder.cpos = 0 | ||
} | ||
} | ||
/** | ||
* Write one byte to the encoder. | ||
@@ -76,5 +92,6 @@ * | ||
export const write = (encoder, num) => { | ||
if (encoder.cpos === encoder.cbuf.length) { | ||
const bufferLen = encoder.cbuf.length | ||
if (encoder.cpos === bufferLen) { | ||
encoder.bufs.push(encoder.cbuf) | ||
encoder.cbuf = new Uint8Array(encoder.cbuf.length * 2) | ||
encoder.cbuf = new Uint8Array(bufferLen * 2) | ||
encoder.cpos = 0 | ||
@@ -119,3 +136,3 @@ } | ||
*/ | ||
export const writeUint8 = (encoder, num) => write(encoder, num & bits8) | ||
export const writeUint8 = write | ||
@@ -130,3 +147,3 @@ /** | ||
*/ | ||
export const setUint8 = (encoder, pos, num) => set(encoder, pos, num & bits8) | ||
export const setUint8 = set | ||
@@ -141,4 +158,4 @@ /** | ||
export const writeUint16 = (encoder, num) => { | ||
write(encoder, num & bits8) | ||
write(encoder, (num >>> 8) & bits8) | ||
write(encoder, num & binary.BITS8) | ||
write(encoder, (num >>> 8) & binary.BITS8) | ||
} | ||
@@ -154,4 +171,4 @@ /** | ||
export const setUint16 = (encoder, pos, num) => { | ||
set(encoder, pos, num & bits8) | ||
set(encoder, pos + 1, (num >>> 8) & bits8) | ||
set(encoder, pos, num & binary.BITS8) | ||
set(encoder, pos + 1, (num >>> 8) & binary.BITS8) | ||
} | ||
@@ -168,3 +185,3 @@ | ||
for (let i = 0; i < 4; i++) { | ||
write(encoder, num & bits8) | ||
write(encoder, num & binary.BITS8) | ||
num >>>= 8 | ||
@@ -184,3 +201,3 @@ } | ||
for (let i = 0; i < 4; i++) { | ||
set(encoder, pos + i, num & bits8) | ||
set(encoder, pos + i, num & binary.BITS8) | ||
num >>>= 8 | ||
@@ -200,10 +217,36 @@ } | ||
export const writeVarUint = (encoder, num) => { | ||
while (num >= 0b10000000) { | ||
write(encoder, 0b10000000 | (bits7 & num)) | ||
while (num > binary.BITS7) { | ||
write(encoder, binary.BIT8 | (binary.BITS7 & num)) | ||
num >>>= 7 | ||
} | ||
write(encoder, bits7 & num) | ||
write(encoder, binary.BITS7 & num) | ||
} | ||
/** | ||
* Write a variable length integer. | ||
* | ||
* Encodes integers in the range from [-2147483648, -2147483647]. | ||
* | ||
* @function | ||
* @param {Encoder} encoder | ||
* @param {number} num The number that is to be encoded. | ||
*/ | ||
export const writeVarInt = (encoder, num) => { | ||
let isNegative = false | ||
if (num < 0) { | ||
num = -num | ||
isNegative = true | ||
} | ||
// |- whether to continue reading |- whether is negative |- number | ||
write(encoder, (num > binary.BITS6 ? binary.BIT8 : 0) | (isNegative ? binary.BIT7 : 0) | (binary.BITS6 & num)) | ||
num >>>= 6 | ||
// We don't need to consider the case of num === 0 so we can use a different | ||
// pattern here than above. | ||
while (num > 0) { | ||
write(encoder, (num > binary.BITS7 ? binary.BIT8 : 0) | (binary.BITS7 & num)) | ||
num >>>= 7 | ||
} | ||
} | ||
/** | ||
* Write a variable length string. | ||
@@ -263,1 +306,164 @@ * | ||
} | ||
/** | ||
* Create an DataView of the next `len` bytes. Use it to write data after | ||
* calling this function. | ||
* | ||
* @example | ||
* // write float32 using DataView | ||
* const dv = writeOnDataView(encoder, 4) | ||
* dv.setFloat32(0, 1.1) | ||
* // read float32 using DataView | ||
* const dv = readFromDataView(encoder, 4) | ||
* dv.getFloat32(0) // => 1.100000023841858 (leaving it to the reader to find out why this is the correct result) | ||
* | ||
* @param {Encoder} encoder | ||
* @param {number} len | ||
* @return {DataView} | ||
*/ | ||
export const writeOnDataView = (encoder, len) => { | ||
verifyLen(encoder, len) | ||
const dview = new DataView(encoder.cbuf.buffer, encoder.cpos, len) | ||
encoder.cpos += len | ||
return dview | ||
} | ||
/** | ||
* @param {Encoder} encoder | ||
* @param {number} num | ||
*/ | ||
export const writeFloat32 = (encoder, num) => writeOnDataView(encoder, 4).setFloat32(0, num) | ||
/** | ||
* @param {Encoder} encoder | ||
* @param {number} num | ||
*/ | ||
export const writeFloat64 = (encoder, num) => writeOnDataView(encoder, 8).setFloat64(0, num) | ||
/** | ||
* @param {Encoder} encoder | ||
* @param {bigint} num | ||
*/ | ||
export const writeBigInt64 = (encoder, num) => writeOnDataView(encoder, 8).setBigInt64(0, num) | ||
/** | ||
* @param {Encoder} encoder | ||
* @param {bigint} num | ||
*/ | ||
export const writeBigUint64 = (encoder, num) => writeOnDataView(encoder, 8).setBigUint64(0, num) | ||
/** | ||
* Check if a number can be encoded as a 32 bit float. | ||
* | ||
* @param {number} num | ||
* @return {boolean} | ||
*/ | ||
const isFloat32 = num => { | ||
const dv = new DataView(new ArrayBuffer(4)) | ||
dv.setFloat32(0, num) | ||
return dv.getFloat32(0) === num | ||
} | ||
/** | ||
* Encode data with efficient binary format. | ||
* | ||
* Differences to JSON: | ||
* • Transforms data to a binary format (not to a string) | ||
* • Encodes undefined, NaN, and ArrayBuffer (these can't be represented in JSON) | ||
* • Numbers are efficiently encoded either as a variable length integer, as a | ||
* 32 bit float, as a 64 bit float, or as a 64 bit bigint. | ||
* | ||
* Encoding table: | ||
* | ||
* | Data Type | Prefix | Encoding Method | Comment | | ||
* | ------------------- | -------- | ------------------ | ------- | | ||
* | undefined | 127 | | Functions, symbol, and everything that cannot be identified is encoded as undefined | | ||
* | null | 126 | | | | ||
* | integer | 125 | writeVarInt | Only encodes 32 bit signed integers | | ||
* | float32 | 124 | writeFloat32 | | | ||
* | float64 | 123 | writeFloat64 | | | ||
* | bigint | 122 | writeBigInt64 | | | ||
* | boolean (false) | 121 | | True and false are different data types so we save the following byte | | ||
* | boolean (true) | 120 | | - 0b01111000 so the last bit determines whether true or false | | ||
* | string | 119 | writeVarString | | | ||
* | object<string,any> | 118 | custom | Writes {length} then {length} key-value pairs | | ||
* | array<any> | 117 | custom | Writes {length} then {length} json values | | ||
* | Uint8Array | 116 | writeVarUint8Array | We use Uint8Array for any kind of binary data | | ||
* | ||
* Reasons for the decreasing prefix: | ||
* We need the first bit for extendability (later we may want to encode the | ||
* prefix with writeVarUint). The remaining 7 bits are divided as follows: | ||
* [0-30] the beginning of the data range is used for custom purposes | ||
* (defined by the function that uses this library) | ||
* [31-127] the end of the data range is used for data encoding by | ||
* lib0/encoding.js | ||
* | ||
* @param {Encoder} encoder | ||
* @param {undefined|null|number|bigint|boolean|string|Object|Array|ArrayBuffer} data | ||
*/ | ||
export const writeAny = (encoder, data) => { | ||
switch (typeof data) { | ||
case 'string': | ||
// TYPE 119: STRING | ||
write(encoder, 119) | ||
writeVarString(encoder, data) | ||
break | ||
case 'number': | ||
if (number.isInteger(data) && data <= binary.BITS31) { | ||
// TYPE 125: INTEGER | ||
write(encoder, 125) | ||
writeVarInt(encoder, data) | ||
} else if (isFloat32(data)) { | ||
// TYPE 124: FLOAT32 | ||
write(encoder, 124) | ||
writeFloat32(encoder, data) | ||
} else { | ||
// TYPE 123: FLOAT64 | ||
write(encoder, 123) | ||
writeFloat64(encoder, data) | ||
} | ||
break | ||
case 'bigint': | ||
// TYPE 122: BigInt | ||
write(encoder, 122) | ||
writeBigInt64(encoder, data) | ||
break | ||
case 'object': | ||
if (data === null) { | ||
// TYPE 126: null | ||
write(encoder, 126) | ||
} else if (data instanceof Array) { | ||
// TYPE 117: Array | ||
write(encoder, 117) | ||
writeVarUint(encoder, data.length) | ||
for (let i = 0; i < data.length; i++) { | ||
writeAny(encoder, data[i]) | ||
} | ||
} else if (data instanceof Uint8Array) { | ||
// TYPE 116: ArrayBuffer | ||
write(encoder, 116) | ||
writeVarUint8Array(encoder, data) | ||
} else { | ||
// TYPE 118: Object | ||
write(encoder, 118) | ||
const keys = Object.keys(data) | ||
writeVarUint(encoder, keys.length) | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i] | ||
writeVarString(encoder, key) | ||
writeAny(encoder, data[key]) | ||
} | ||
} | ||
break | ||
case 'boolean': | ||
// TYPE 120/121: boolean (true/false) | ||
write(encoder, data ? 120 : 121) | ||
break | ||
case 'symbol': | ||
case 'function': | ||
case 'undefined': | ||
default: | ||
// TYPE 127: undefined | ||
write(encoder, 127) | ||
} | ||
} |
@@ -0,1 +1,4 @@ | ||
import * as math from './math.js' | ||
import * as binary from './binary.js' | ||
/** | ||
@@ -7,1 +10,7 @@ * @module number | ||
export const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER | ||
export const LOWEST_INT32 = 1 << 31 | ||
export const HIGHEST_INT32 = binary.BITS31 | ||
export const isInteger = Number.isInteger || (num => typeof num === 'number' && isFinite(num) && math.floor(num) === num) | ||
export const isNaN = Number.isNaN |
{ | ||
"name": "lib0", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"description": "", | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"@types/node": "^11.13.0", | ||
"@types/node": "^11.13.18", | ||
"fake-indexeddb": "^2.0.5", | ||
@@ -35,3 +35,10 @@ "live-server": "^1.2.1", | ||
}, | ||
"homepage": "https://github.com/dmonad/lib0#readme" | ||
"homepage": "https://github.com/dmonad/lib0#readme", | ||
"standard": { | ||
"ignore": [ | ||
"/dist", | ||
"/node_modules", | ||
"/docs" | ||
] | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
832580
12037