UNPKG

@cartbc/codecs-numbers

Version:

Codecs for numbers of different sizes and endianness

302 lines (297 loc) 10.4 kB
import { combineCodec, assertByteArrayIsNotEmptyForCodec, assertByteArrayHasEnoughBytesForCodec } from '@cartbc/codecs-core'; // src/assertions.ts function assertNumberIsBetweenForCodec(codecDescription, min, max, value) { if (value < min || value > max) { throw new Error( `Codec [${codecDescription}] expected number to be in the range [${min}, ${max}], got ${value}.` ); } } // src/common.ts var Endian = /* @__PURE__ */ ((Endian2) => { Endian2[Endian2["LITTLE"] = 0] = "LITTLE"; Endian2[Endian2["BIG"] = 1] = "BIG"; return Endian2; })(Endian || {}); function sharedNumberFactory(input) { let littleEndian; let defaultDescription = input.name; if (input.size > 1) { littleEndian = !("endian" in input.options) || input.options.endian === 0 /* LITTLE */; defaultDescription += littleEndian ? "(le)" : "(be)"; } return { description: input.options.description ?? defaultDescription, fixedSize: input.size, littleEndian, maxSize: input.size }; } function numberEncoderFactory(input) { const codecData = sharedNumberFactory(input); return { description: codecData.description, encode(value) { if (input.range) { assertNumberIsBetweenForCodec(input.name, input.range[0], input.range[1], value); } const arrayBuffer = new ArrayBuffer(input.size); input.set(new DataView(arrayBuffer), value, codecData.littleEndian); return new Uint8Array(arrayBuffer); }, fixedSize: codecData.fixedSize, maxSize: codecData.maxSize }; } function numberDecoderFactory(input) { const codecData = sharedNumberFactory(input); return { decode(bytes, offset = 0) { assertByteArrayIsNotEmptyForCodec(codecData.description, bytes, offset); assertByteArrayHasEnoughBytesForCodec(codecData.description, input.size, bytes, offset); const view = new DataView(toArrayBuffer(bytes, offset, input.size)); return [input.get(view, codecData.littleEndian), offset + input.size]; }, description: codecData.description, fixedSize: codecData.fixedSize, maxSize: codecData.maxSize }; } function toArrayBuffer(bytes, offset, length) { const bytesOffset = bytes.byteOffset + (offset ?? 0); const bytesLength = length ?? bytes.byteLength; return bytes.buffer.slice(bytesOffset, bytesOffset + bytesLength); } // src/f32.ts var getF32Encoder = (options = {}) => numberEncoderFactory({ name: "f32", options, set: (view, value, le) => view.setFloat32(0, value, le), size: 4 }); var getF32Decoder = (options = {}) => numberDecoderFactory({ get: (view, le) => view.getFloat32(0, le), name: "f32", options, size: 4 }); var getF32Codec = (options = {}) => combineCodec(getF32Encoder(options), getF32Decoder(options)); var getF64Encoder = (options = {}) => numberEncoderFactory({ name: "f64", options, set: (view, value, le) => view.setFloat64(0, value, le), size: 8 }); var getF64Decoder = (options = {}) => numberDecoderFactory({ get: (view, le) => view.getFloat64(0, le), name: "f64", options, size: 8 }); var getF64Codec = (options = {}) => combineCodec(getF64Encoder(options), getF64Decoder(options)); var getI128Encoder = (options = {}) => numberEncoderFactory({ name: "i128", options, range: [-BigInt("0x7fffffffffffffffffffffffffffffff") - 1n, BigInt("0x7fffffffffffffffffffffffffffffff")], set: (view, value, le) => { const leftOffset = le ? 8 : 0; const rightOffset = le ? 0 : 8; const rightMask = 0xffffffffffffffffn; view.setBigInt64(leftOffset, BigInt(value) >> 64n, le); view.setBigUint64(rightOffset, BigInt(value) & rightMask, le); }, size: 16 }); var getI128Decoder = (options = {}) => numberDecoderFactory({ get: (view, le) => { const leftOffset = le ? 8 : 0; const rightOffset = le ? 0 : 8; const left = view.getBigInt64(leftOffset, le); const right = view.getBigUint64(rightOffset, le); return (left << 64n) + right; }, name: "i128", options, size: 16 }); var getI128Codec = (options = {}) => combineCodec(getI128Encoder(options), getI128Decoder(options)); var getI16Encoder = (options = {}) => numberEncoderFactory({ name: "i16", options, range: [-Number("0x7fff") - 1, Number("0x7fff")], set: (view, value, le) => view.setInt16(0, value, le), size: 2 }); var getI16Decoder = (options = {}) => numberDecoderFactory({ get: (view, le) => view.getInt16(0, le), name: "i16", options, size: 2 }); var getI16Codec = (options = {}) => combineCodec(getI16Encoder(options), getI16Decoder(options)); var getI32Encoder = (options = {}) => numberEncoderFactory({ name: "i32", options, range: [-Number("0x7fffffff") - 1, Number("0x7fffffff")], set: (view, value, le) => view.setInt32(0, value, le), size: 4 }); var getI32Decoder = (options = {}) => numberDecoderFactory({ get: (view, le) => view.getInt32(0, le), name: "i32", options, size: 4 }); var getI32Codec = (options = {}) => combineCodec(getI32Encoder(options), getI32Decoder(options)); var getI64Encoder = (options = {}) => numberEncoderFactory({ name: "i64", options, range: [-BigInt("0x7fffffffffffffff") - 1n, BigInt("0x7fffffffffffffff")], set: (view, value, le) => view.setBigInt64(0, BigInt(value), le), size: 8 }); var getI64Decoder = (options = {}) => numberDecoderFactory({ get: (view, le) => view.getBigInt64(0, le), name: "i64", options, size: 8 }); var getI64Codec = (options = {}) => combineCodec(getI64Encoder(options), getI64Decoder(options)); var getI8Encoder = (options = {}) => numberEncoderFactory({ name: "i8", options, range: [-Number("0x7f") - 1, Number("0x7f")], set: (view, value) => view.setInt8(0, value), size: 1 }); var getI8Decoder = (options = {}) => numberDecoderFactory({ get: (view) => view.getInt8(0), name: "i8", options, size: 1 }); var getI8Codec = (options = {}) => combineCodec(getI8Encoder(options), getI8Decoder(options)); var getShortU16Encoder = (options = {}) => ({ description: options.description ?? "shortU16", encode: (value) => { assertNumberIsBetweenForCodec("shortU16", 0, 65535, value); const bytes = [0]; for (let ii = 0; ; ii += 1) { const alignedValue = value >> ii * 7; if (alignedValue === 0) { break; } const nextSevenBits = 127 & alignedValue; bytes[ii] = nextSevenBits; if (ii > 0) { bytes[ii - 1] |= 128; } } return new Uint8Array(bytes); }, fixedSize: null, maxSize: 3 }); var getShortU16Decoder = (options = {}) => ({ decode: (bytes, offset = 0) => { let value = 0; let byteCount = 0; while (++byteCount) { const byteIndex = byteCount - 1; const currentByte = bytes[offset + byteIndex]; const nextSevenBits = 127 & currentByte; value |= nextSevenBits << byteIndex * 7; if ((currentByte & 128) === 0) { break; } } return [value, offset + byteCount]; }, description: options.description ?? "shortU16", fixedSize: null, maxSize: 3 }); var getShortU16Codec = (options = {}) => combineCodec(getShortU16Encoder(options), getShortU16Decoder(options)); var getU128Encoder = (options = {}) => numberEncoderFactory({ name: "u128", options, range: [0, BigInt("0xffffffffffffffffffffffffffffffff")], set: (view, value, le) => { const leftOffset = le ? 8 : 0; const rightOffset = le ? 0 : 8; const rightMask = 0xffffffffffffffffn; view.setBigUint64(leftOffset, BigInt(value) >> 64n, le); view.setBigUint64(rightOffset, BigInt(value) & rightMask, le); }, size: 16 }); var getU128Decoder = (options = {}) => numberDecoderFactory({ get: (view, le) => { const leftOffset = le ? 8 : 0; const rightOffset = le ? 0 : 8; const left = view.getBigUint64(leftOffset, le); const right = view.getBigUint64(rightOffset, le); return (left << 64n) + right; }, name: "u128", options, size: 16 }); var getU128Codec = (options = {}) => combineCodec(getU128Encoder(options), getU128Decoder(options)); var getU16Encoder = (options = {}) => numberEncoderFactory({ name: "u16", options, range: [0, Number("0xffff")], set: (view, value, le) => view.setUint16(0, value, le), size: 2 }); var getU16Decoder = (options = {}) => numberDecoderFactory({ get: (view, le) => view.getUint16(0, le), name: "u16", options, size: 2 }); var getU16Codec = (options = {}) => combineCodec(getU16Encoder(options), getU16Decoder(options)); var getU32Encoder = (options = {}) => numberEncoderFactory({ name: "u32", options, range: [0, Number("0xffffffff")], set: (view, value, le) => view.setUint32(0, value, le), size: 4 }); var getU32Decoder = (options = {}) => numberDecoderFactory({ get: (view, le) => view.getUint32(0, le), name: "u32", options, size: 4 }); var getU32Codec = (options = {}) => combineCodec(getU32Encoder(options), getU32Decoder(options)); var getU64Encoder = (options = {}) => numberEncoderFactory({ name: "u64", options, range: [0, BigInt("0xffffffffffffffff")], set: (view, value, le) => view.setBigUint64(0, BigInt(value), le), size: 8 }); var getU64Decoder = (options = {}) => numberDecoderFactory({ get: (view, le) => view.getBigUint64(0, le), name: "u64", options, size: 8 }); var getU64Codec = (options = {}) => combineCodec(getU64Encoder(options), getU64Decoder(options)); var getU8Encoder = (options = {}) => numberEncoderFactory({ name: "u8", options, range: [0, Number("0xff")], set: (view, value) => view.setUint8(0, value), size: 1 }); var getU8Decoder = (options = {}) => numberDecoderFactory({ get: (view) => view.getUint8(0), name: "u8", options, size: 1 }); var getU8Codec = (options = {}) => combineCodec(getU8Encoder(options), getU8Decoder(options)); export { Endian, assertNumberIsBetweenForCodec, getF32Codec, getF32Decoder, getF32Encoder, getF64Codec, getF64Decoder, getF64Encoder, getI128Codec, getI128Decoder, getI128Encoder, getI16Codec, getI16Decoder, getI16Encoder, getI32Codec, getI32Decoder, getI32Encoder, getI64Codec, getI64Decoder, getI64Encoder, getI8Codec, getI8Decoder, getI8Encoder, getShortU16Codec, getShortU16Decoder, getShortU16Encoder, getU128Codec, getU128Decoder, getU128Encoder, getU16Codec, getU16Decoder, getU16Encoder, getU32Codec, getU32Decoder, getU32Encoder, getU64Codec, getU64Decoder, getU64Encoder, getU8Codec, getU8Decoder, getU8Encoder };