/* control character types: 1 - metadata 2 - symbols 6 - false 7 - true 8- 16 - negative doubles 16-24 positive doubles 27 - String starts with a character 27 or less or is an empty string 0 - multipart separator > 27 normal string characters */ /* * Convert arbitrary scalar values to buffer bytes with type preservation and type-appropriate ordering */ const float64Array = new Float64Array(2) const int32Array = new Int32Array(float64Array.buffer, 0, 4) const uint8Array6 = new Uint8Array(float64Array.buffer, 2, 6) const uint8Array8 = new Uint8Array(float64Array.buffer, 0, 8) let nullTerminate = false /* * Convert arbitrary scalar values to buffer bytes with type preservation and type-appropriate ordering */ export function writeKey(key, target, position, inSequence) { let targetView = target.dataView if (!targetView) targetView = target.dataView = new DataView(target.buffer, target.byteOffset, ((target.byteLength + 3) >> 2) << 2) switch (typeof key) { case 'string': let strLength = key.length let c1 = key.charCodeAt(0) if (c1 < 28) // escape character target[position++] = 27 if (strLength < 0x20) { let i, c2 for (i = 0; i < strLength; i++) { c1 = key.charCodeAt(i) if (c1 < 0x80) { target[position++] = c1 } else if (c1 < 0x800) { target[position++] = c1 >> 6 | 0xc0 target[position++] = c1 & 0x3f | 0x80 } else if ( (c1 & 0xfc00) === 0xd800 && ((c2 = key.charCodeAt(i + 1)) & 0xfc00) === 0xdc00 ) { c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff) i++ target[position++] = c1 >> 18 | 0xf0 target[position++] = c1 >> 12 & 0x3f | 0x80 target[position++] = c1 >> 6 & 0x3f | 0x80 target[position++] = c1 & 0x3f | 0x80 } else { target[position++] = c1 >> 12 | 0xe0 target[position++] = c1 >> 6 & 0x3f | 0x80 target[position++] = c1 & 0x3f | 0x80 } } } else { position += target.utf8Write(key, position, 2000) } break case 'number': float64Array[0] = key let lowInt = int32Array[0] let highInt = int32Array[1] let length if (key < 0) { targetView.setInt32(position + 4, ~((lowInt >>> 4) | (highInt << 28))) targetView.setInt32(position + 0, (highInt ^ 0x7fffffff) >>> 4) targetView.setInt32(position + 8, ((lowInt & 0xf) ^ 0xf) << 4, true) // just always do the null termination here return position + 9 } else if ((lowInt & 0xf) || inSequence) { length = 9 } else if (lowInt & 0xfffff) length = 8 else if (lowInt || (highInt & 0xf)) length = 6 else length = 4 // switching order to go to little endian targetView.setInt32(position + 0, (highInt >>> 4) | 0x10000000) targetView.setInt32(position + 4, (lowInt >>> 4) | (highInt << 28)) // if (length == 9 || nullTerminate) targetView.setInt32(position + 8, (lowInt & 0xf) << 4, true) return position + length; case 'object': if (key) { if (key instanceof Array) { for (let i = 0, l = key.length; i < l; i++) { if (i > 0) target[position++] = 0 position = writeKey(key[i], target, position, true) } break } else if (key instanceof Uint8Array) { target.set(key, position) position += key.length break } else { throw new Error('Unable to serialize object as a key') } } else // null target[position++] = 0 break case 'boolean': targetView.setUint32(position++, key ? 7 : 6, true) return position case 'bigint': return writeKey(Number(key), target, position, inSequence) case 'undefined': return position // undefined is interpreted as the absence of a key, signified by zero length case 'symbol': target[position++] = 2 return writeKey(key.description, target, position, inSequence) default: throw new Error('Can not serialize key of type ' + typeof key) } if (nullTerminate && !inSequence) targetView.setUint32(position, 0) return position } let position export function readKey(buffer, start, end, inSequence) { buffer[end] = 0 // make sure it is null terminated position = start let controlByte = buffer[position] let value if (controlByte < 24) { if (controlByte < 8) { position++ if (controlByte == 6) { value = false } else if (controlByte == 7) { value = true } else if (controlByte == 0) { value = null } else if (controlByte == 2) { value = Symbol.for(readString(buffer)) } else return Uint8Array.prototype.slice.call(buffer, start, end) } else { let dataView = buffer.dataView || (buffer.dataView = new DataView(buffer.buffer, buffer.byteOffset, ((buffer.byteLength + 3) >> 2) << 2)) let highInt = dataView.getInt32(position) << 4 let size = end - position let lowInt if (size > 4) { lowInt = dataView.getInt32(position + 4) highInt |= lowInt >>> 28 if (size <= 6) { // clear the last bits lowInt &= -0x1000 } lowInt = lowInt << 4 if (size > 8) { lowInt = lowInt | buffer[position + 8] >> 4 } } else lowInt = 0 if (controlByte < 16) { // negative gets negated highInt = highInt ^ 0x7fffffff lowInt = ~lowInt } int32Array[1] = highInt int32Array[0] = lowInt value = float64Array[0] position += 9 } } else { if (controlByte == 27) { position++ } value = readString(buffer) /*let strStart = position let strEnd = end for (; position < end; position++) { if (buffer[position] == 0) { break } } value = buffer.toString('utf8', strStart, position++)*/ } while (position < end) { if (buffer[position] === 0) position++ if (inSequence) { encoder.position = position return value } let nextValue = readKey(buffer, position, end, true) if (value instanceof Array) { value.push(nextValue) } else value = [ value, nextValue ] } return value } export const encoder = { writeKey, readKey, } export const toBufferKey = (key) => { let buffer = Buffer.alloc(2048) return buffer.slice(0, writeKey(key, buffer, 0, 2048) + 1) } export const fromBufferKey = (sourceBuffer) => { return readKey(sourceBuffer, 0, sourceBuffer.length - 1) } const fromCharCode = String.fromCharCode function makeStringBuilder() { let stringBuildCode = '(source) => {' let previous = [] for (let i = 0; i < 0x30; i++) { let v = fromCharCode((i & 0xf) + 97) + fromCharCode((i >> 4) + 97) stringBuildCode += ` let ${v} = source[position++] if (${v} === 0) return fromCharCode(${previous}) else if (${v} >= 0x80) ${v} = finishUtf8(${v}, source) ` previous.push(v) if (i == 1000000) // this just exists to prevent rollup from doing dead code elimination on finishUtf8 finishUtf8() } stringBuildCode += `return fromCharCode(${previous}) + readString(source)}` return stringBuildCode } let pendingSurrogate function finishUtf8(byte1, src) { if ((byte1 & 0xe0) === 0xc0) { // 2 bytes const byte2 = src[position++] & 0x3f return ((byte1 & 0x1f) << 6) | byte2 } else if ((byte1 & 0xf0) === 0xe0) { // 3 bytes const byte2 = src[position++] & 0x3f const byte3 = src[position++] & 0x3f return ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3 } else if ((byte1 & 0xf8) === 0xf0) { // 4 bytes if (pendingSurrogate) { byte1 = pendingSurrogate pendingSurrogate = null position += 3 return byte1 } const byte2 = src[position++] & 0x3f const byte3 = src[position++] & 0x3f const byte4 = src[position++] & 0x3f let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4 if (unit > 0xffff) { unit -= 0x10000 unit = 0xdc00 | (unit & 0x3ff) pendingSurrogate = ((unit >>> 10) & 0x3ff) | 0xd800 position -= 4 // reset so we can return the next part of the surrogate pair } return unit } else { return byte1 } } export const enableNullTermination = () => nullTerminate = true const readString = eval(makeStringBuilder()) export function compareKeys(a, b) { // compare with type consistency that matches ordered-binary if (typeof a == 'object') { if (!a) { return b == null ? 0 : -1 } if (a.compare) { if (b == null) { return 1 } else if (b.compare) { return a.compare(b) } else { return -1 } } let arrayComparison if (b instanceof Array) { let i = 0 while((arrayComparison = compareKeys(a[i], b[i])) == 0 && i <= a.length) { i++ } return arrayComparison } arrayComparison = compareKeys(a[0], b) if (arrayComparison == 0 && a.length > 1) return 1 return arrayComparison } else if (typeof a == typeof b) { if (typeof a === 'symbol') { a = Symbol.keyFor(a) b = Symbol.keyFor(b) } return a < b ? -1 : a === b ? 0 : 1 } else if (typeof b == 'object') { if (b instanceof Array) return -compareKeys(b, a) return 1 } else { return typeOrder[typeof a] < typeOrder[typeof b] ? -1 : 1 } } const typeOrder = { symbol: 0, undefined: 1, boolean: 2, number: 3, string: 4 }