aboutsummaryrefslogtreecommitdiff
path: root/node_modules/concordance/lib/encoder.js
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-08-14 05:01:11 +0200
committerFlorian Dold <florian.dold@gmail.com>2017-08-14 05:02:09 +0200
commit363723fc84f7b8477592e0105aeb331ec9a017af (patch)
tree29f92724f34131bac64d6a318dd7e30612e631c7 /node_modules/concordance/lib/encoder.js
parent5634e77ad96bfe1818f6b6ee70b7379652e5487f (diff)
node_modules
Diffstat (limited to 'node_modules/concordance/lib/encoder.js')
-rw-r--r--node_modules/concordance/lib/encoder.js293
1 files changed, 293 insertions, 0 deletions
diff --git a/node_modules/concordance/lib/encoder.js b/node_modules/concordance/lib/encoder.js
new file mode 100644
index 000000000..ea1be3855
--- /dev/null
+++ b/node_modules/concordance/lib/encoder.js
@@ -0,0 +1,293 @@
+'use strict'
+
+const flattenDeep = require('lodash.flattendeep')
+
+// Indexes are hexadecimal to make reading the binary output easier.
+const valueTypes = {
+ zero: 0x00,
+ int8: 0x01, // Note that the hex value equals the number of bytes required
+ int16: 0x02, // to store the integer.
+ int24: 0x03,
+ int32: 0x04,
+ int40: 0x05,
+ int48: 0x06,
+ // Leave room for int56 and int64
+ numberString: 0x09,
+ negativeZero: 0x0A,
+ notANumber: 0x0B,
+ infinity: 0x0C,
+ negativeInfinity: 0x0D,
+ undefined: 0x0E,
+ null: 0x0F,
+ true: 0x10,
+ false: 0x11,
+ utf8: 0x12,
+ bytes: 0x13,
+ list: 0x14,
+ descriptor: 0x15
+}
+
+const descriptorSymbol = Symbol('descriptor')
+exports.descriptorSymbol = descriptorSymbol
+
+function encodeInteger (type, value) {
+ const encoded = Buffer.alloc(type)
+ encoded.writeIntLE(value, 0, type)
+ return [type, encoded]
+}
+
+function encodeValue (value) {
+ if (Object.is(value, 0)) return valueTypes.zero
+ if (Object.is(value, -0)) return valueTypes.negativeZero
+ if (Object.is(value, NaN)) return valueTypes.notANumber
+ if (value === Infinity) return valueTypes.infinity
+ if (value === -Infinity) return valueTypes.negativeInfinity
+ if (value === undefined) return valueTypes.undefined
+ if (value === null) return valueTypes.null
+ if (value === true) return valueTypes.true
+ if (value === false) return valueTypes.false
+
+ const type = typeof value
+ if (type === 'number') {
+ if (Number.isInteger(value)) {
+ // The integer types are signed, so int8 can only store 7 bits, int16
+ // only 15, etc.
+ if (value >= -0x80 && value < 0x80) return encodeInteger(valueTypes.int8, value)
+ if (value >= -0x8000 && value < 0x8000) return encodeInteger(valueTypes.int16, value)
+ if (value >= -0x800000 && value < 0x800000) return encodeInteger(valueTypes.int24, value)
+ if (value >= -0x80000000 && value < 0x80000000) return encodeInteger(valueTypes.int32, value)
+ if (value >= -0x8000000000 && value < 0x8000000000) return encodeInteger(valueTypes.int40, value)
+ if (value >= -0x800000000000 && value < 0x800000000000) return encodeInteger(valueTypes.int48, value)
+ // Fall through to encoding the value as a number string.
+ }
+
+ const encoded = Buffer.from(String(value), 'utf8')
+ return [valueTypes.numberString, encodeValue(encoded.length), encoded]
+ }
+
+ if (type === 'string') {
+ const encoded = Buffer.from(value, 'utf8')
+ return [valueTypes.utf8, encodeValue(encoded.length), encoded]
+ }
+
+ if (Buffer.isBuffer(value)) {
+ return [valueTypes.bytes, encodeValue(value.byteLength), value]
+ }
+
+ if (Array.isArray(value)) {
+ return [
+ value[descriptorSymbol] === true ? valueTypes.descriptor : valueTypes.list,
+ encodeValue(value.length),
+ value.map(encodeValue)
+ ]
+ }
+
+ const hex = `0x${type.toString(16).toUpperCase()}`
+ throw new TypeError(`Unexpected value with type ${hex}`)
+}
+
+function decodeValue (buffer, byteOffset) {
+ const type = buffer.readUInt8(byteOffset)
+ byteOffset += 1
+
+ if (type === valueTypes.zero) return { byteOffset, value: 0 }
+ if (type === valueTypes.negativeZero) return { byteOffset, value: -0 }
+ if (type === valueTypes.notANumber) return { byteOffset, value: NaN }
+ if (type === valueTypes.infinity) return { byteOffset, value: Infinity }
+ if (type === valueTypes.negativeInfinity) return { byteOffset, value: -Infinity }
+ if (type === valueTypes.undefined) return { byteOffset, value: undefined }
+ if (type === valueTypes.null) return { byteOffset, value: null }
+ if (type === valueTypes.true) return { byteOffset, value: true }
+ if (type === valueTypes.false) return { byteOffset, value: false }
+
+ if (
+ type === valueTypes.int8 || type === valueTypes.int16 || type === valueTypes.int24 ||
+ type === valueTypes.int32 || type === valueTypes.int40 || type === valueTypes.int48
+ ) {
+ const value = buffer.readIntLE(byteOffset, type)
+ byteOffset += type
+ return { byteOffset, value }
+ }
+
+ if (type === valueTypes.numberString || type === valueTypes.utf8 || type === valueTypes.bytes) {
+ const length = decodeValue(buffer, byteOffset)
+ const start = length.byteOffset
+ const end = start + length.value
+
+ if (type === valueTypes.numberString) {
+ const value = Number(buffer.toString('utf8', start, end))
+ return { byteOffset: end, value }
+ }
+
+ if (type === valueTypes.utf8) {
+ const value = buffer.toString('utf8', start, end)
+ return { byteOffset: end, value }
+ }
+
+ const value = buffer.slice(start, end)
+ return { byteOffset: end, value }
+ }
+
+ if (type === valueTypes.list || type === valueTypes.descriptor) {
+ const length = decodeValue(buffer, byteOffset)
+ byteOffset = length.byteOffset
+
+ const value = new Array(length.value)
+ if (type === valueTypes.descriptor) {
+ value[descriptorSymbol] = true
+ }
+
+ for (let index = 0; index < length.value; index++) {
+ const item = decodeValue(buffer, byteOffset)
+ byteOffset = item.byteOffset
+ value[index] = item.value
+ }
+
+ return { byteOffset, value }
+ }
+
+ const hex = `0x${type.toString(16).toUpperCase()}`
+ throw new TypeError(`Could not decode type ${hex}`)
+}
+
+function buildBuffer (numberOrArray) {
+ if (typeof numberOrArray === 'number') {
+ const byte = Buffer.alloc(1)
+ byte.writeUInt8(numberOrArray)
+ return byte
+ }
+
+ const array = flattenDeep(numberOrArray)
+ const buffers = new Array(array.length)
+ let byteLength = 0
+ for (let index = 0; index < array.length; index++) {
+ if (typeof array[index] === 'number') {
+ byteLength += 1
+ const byte = Buffer.alloc(1)
+ byte.writeUInt8(array[index])
+ buffers[index] = byte
+ } else {
+ byteLength += array[index].byteLength
+ buffers[index] = array[index]
+ }
+ }
+ return Buffer.concat(buffers, byteLength)
+}
+
+function encode (serializerVersion, rootRecord, usedPlugins) {
+ const buffers = []
+ let byteOffset = 0
+
+ const versionHeader = Buffer.alloc(2)
+ versionHeader.writeUInt16LE(serializerVersion)
+ buffers.push(versionHeader)
+ byteOffset += versionHeader.byteLength
+
+ const rootOffset = Buffer.alloc(4)
+ buffers.push(rootOffset)
+ byteOffset += rootOffset.byteLength
+
+ const numPlugins = buildBuffer(encodeValue(usedPlugins.size))
+ buffers.push(numPlugins)
+ byteOffset += numPlugins.byteLength
+
+ for (const name of usedPlugins.keys()) {
+ const plugin = usedPlugins.get(name)
+ const record = buildBuffer([
+ encodeValue(name),
+ encodeValue(plugin.serializerVersion)
+ ])
+ buffers.push(record)
+ byteOffset += record.byteLength
+ }
+
+ const queue = [rootRecord]
+ const pointers = [rootOffset]
+ while (queue.length > 0) {
+ pointers.shift().writeUInt32LE(byteOffset, 0)
+
+ const record = queue.shift()
+ const recordHeader = buildBuffer([
+ encodeValue(record.pluginIndex),
+ encodeValue(record.id),
+ encodeValue(record.children.length)
+ ])
+ buffers.push(recordHeader)
+ byteOffset += recordHeader.byteLength
+
+ // Add pointers before encoding the state. This allows, if it ever becomes
+ // necessary, for records to be extracted from a buffer without having to
+ // parse the (variable length) state field.
+ for (const child of record.children) {
+ queue.push(child)
+
+ const pointer = Buffer.alloc(4)
+ pointers.push(pointer)
+ buffers.push(pointer)
+ byteOffset += 4
+ }
+
+ const state = buildBuffer(encodeValue(record.state))
+ buffers.push(state)
+ byteOffset += state.byteLength
+ }
+
+ return Buffer.concat(buffers, byteOffset)
+}
+exports.encode = encode
+
+function decodePlugins (buffer) {
+ const $numPlugins = decodeValue(buffer, 0)
+ let byteOffset = $numPlugins.byteOffset
+
+ const usedPlugins = new Map()
+ const lastIndex = $numPlugins.value
+ for (let index = 1; index <= lastIndex; index++) {
+ const $name = decodeValue(buffer, byteOffset)
+ const name = $name.value
+ byteOffset = $name.byteOffset
+
+ const serializerVersion = decodeValue(buffer, byteOffset).value
+ usedPlugins.set(index, {name, serializerVersion})
+ }
+
+ return usedPlugins
+}
+exports.decodePlugins = decodePlugins
+
+function decodeRecord (buffer, byteOffset) {
+ const $pluginIndex = decodeValue(buffer, byteOffset)
+ const pluginIndex = $pluginIndex.value
+ byteOffset = $pluginIndex.byteOffset
+
+ const $id = decodeValue(buffer, byteOffset)
+ const id = $id.value
+ byteOffset = $id.byteOffset
+
+ const $numPointers = decodeValue(buffer, byteOffset)
+ const numPointers = $numPointers.value
+ byteOffset = $numPointers.byteOffset
+
+ const pointerAddresses = new Array(numPointers)
+ for (let index = 0; index < numPointers; index++) {
+ pointerAddresses[index] = buffer.readUInt32LE(byteOffset)
+ byteOffset += 4
+ }
+
+ const state = decodeValue(buffer, byteOffset).value
+ return {id, pluginIndex, state, pointerAddresses}
+}
+exports.decodeRecord = decodeRecord
+
+function extractVersion (buffer) {
+ return buffer.readUInt16LE(0)
+}
+exports.extractVersion = extractVersion
+
+function decode (buffer) {
+ const rootOffset = buffer.readUInt32LE(2)
+ const pluginBuffer = buffer.slice(6, rootOffset)
+ const rootRecord = decodeRecord(buffer, rootOffset)
+ return {pluginBuffer, rootRecord}
+}
+exports.decode = decode