UNPKG

@bare-ts/lib

Version:

TypeScript library for BARE, a compact and simple binary-serialization format

424 lines (423 loc) 10.1 kB
"use strict"; import { BareError } from "../core/bare-error.js"; import { check, reserve } from "../core/byte-cursor.js"; import { DEV, assert } from "../util/assert.js"; import { INT_SAFE_MAX_BYTE_COUNT, NON_CANONICAL_REPRESENTATION, TOO_LARGE_NUMBER, UINT_MAX_BYTE_COUNT, UINT_SAFE32_MAX_BYTE_COUNT } from "../util/constants.js"; import { isI8, isI16, isI32, isI64, isU8, isU16, isU32, isU64, isU64Safe } from "../util/validator.js"; export function readBool(bc) { const val = readU8(bc); if (val > 1) { bc.offset--; throw new BareError(bc.offset, "a bool must be equal to 0 or 1"); } return val !== 0; } export function writeBool(bc, x) { writeU8(bc, x ? 1 : 0); } export function readF32(bc) { check(bc, 4); const result = bc.view.getFloat32(bc.offset, true); bc.offset += 4; return result; } export function writeF32(bc, x) { reserve(bc, 4); bc.view.setFloat32(bc.offset, x, true); if (DEV) { assert( Number.isNaN(x) || Math.abs(bc.view.getFloat32(bc.offset, true) - x) <= Number.EPSILON, TOO_LARGE_NUMBER ); } bc.offset += 4; } export function readF64(bc) { check(bc, 8); const result = bc.view.getFloat64(bc.offset, true); bc.offset += 8; return result; } export function writeF64(bc, x) { reserve(bc, 8); bc.view.setFloat64(bc.offset, x, true); bc.offset += 8; } export function readI8(bc) { check(bc, 1); return bc.view.getInt8(bc.offset++); } export function writeI8(bc, x) { if (DEV) { assert(isI8(x), TOO_LARGE_NUMBER); } reserve(bc, 1); bc.view.setInt8(bc.offset++, x); } export function readI16(bc) { check(bc, 2); const result = bc.view.getInt16(bc.offset, true); bc.offset += 2; return result; } export function writeI16(bc, x) { if (DEV) { assert(isI16(x), TOO_LARGE_NUMBER); } reserve(bc, 2); bc.view.setInt16(bc.offset, x, true); bc.offset += 2; } export function readI32(bc) { check(bc, 4); const result = bc.view.getInt32(bc.offset, true); bc.offset += 4; return result; } export function writeI32(bc, x) { if (DEV) { assert(isI32(x), TOO_LARGE_NUMBER); } reserve(bc, 4); bc.view.setInt32(bc.offset, x, true); bc.offset += 4; } export function readI64(bc) { check(bc, 8); const result = bc.view.getBigInt64(bc.offset, true); bc.offset += 8; return result; } export function writeI64(bc, x) { if (DEV) { assert(isI64(x), TOO_LARGE_NUMBER); } reserve(bc, 8); bc.view.setBigInt64(bc.offset, x, true); bc.offset += 8; } export function readI64Safe(bc) { const result = readU32(bc) + readI32(bc) * /* 2**32 */ 4294967296; if (!Number.isSafeInteger(result)) { bc.offset -= 8; throw new BareError(bc.offset, TOO_LARGE_NUMBER); } return result; } export function writeI64Safe(bc, x) { if (DEV) { assert(Number.isSafeInteger(x), TOO_LARGE_NUMBER); } let lowest32 = x >>> 0; writeU32(bc, lowest32); let highest32 = x / /* 2**32 */ 4294967296 | 0; if (x < 0) { highest32 = ~(Math.abs(highest32) & /* 2**21-1 */ 2097151) >>> 0; if (lowest32 === 0) { if (highest32 === 2097151) { lowest32 = 1; } else { highest32++; } } } writeU32(bc, highest32); } export function readInt(bc) { const zigZag = readUint(bc); return zigZag >> BigInt(1) ^ -(zigZag & BigInt(1)); } export function writeInt(bc, x) { const truncated = BigInt.asIntN(64, x); if (DEV) { assert(truncated === x, TOO_LARGE_NUMBER); } const zigZag = truncated >> BigInt(63) ^ truncated << BigInt(1); writeTruncatedUint(bc, zigZag); } export function readIntSafe(bc) { const firstByte = readU8(bc); let result = (firstByte & 127) >> 1; if (firstByte >= 128) { let shiftMul = ( /* 2**6 */ 64 ); let byteCount = 1; let byte; do { byte = readU8(bc); result += (byte & 127) * shiftMul; shiftMul *= /* 2**7 */ 128; byteCount++; } while (byte >= 128 && byteCount < INT_SAFE_MAX_BYTE_COUNT); if (byte === 0) { bc.offset -= byteCount - 1; throw new BareError(bc.offset, "must be canonical"); } if (byteCount === INT_SAFE_MAX_BYTE_COUNT && (byte > 31 || firstByte === 255)) { bc.offset -= byteCount - 1; throw new BareError(bc.offset, TOO_LARGE_NUMBER); } } const isNeg = (firstByte & 1) === 1; if (isNeg) { result = -result - 1; } return result; } export function writeIntSafe(bc, x) { const sign = x < 0 ? 1 : 0; let zigZag = x < 0 ? -(x + 1) : x; let first7Bits = (zigZag & 63) << 1 | sign; zigZag = Math.floor(zigZag / /* 2**6 */ 64); if (zigZag > 0) { if (!Number.isSafeInteger(x)) { if (DEV) { assert(false, TOO_LARGE_NUMBER); } const low = zigZag & 32767; const high = (zigZag / 32768 >>> 0) * 32768; if (first7Bits === 127 && low === 32767 && high === 4294967295) { first7Bits &= ~2; } zigZag = high + low; } writeU8(bc, 128 | first7Bits); writeUintSafe(bc, zigZag); } else { writeU8(bc, first7Bits); } } export function readU8(bc) { check(bc, 1); return bc.bytes[bc.offset++]; } export function writeU8(bc, x) { if (DEV) { assert(isU8(x), TOO_LARGE_NUMBER); } reserve(bc, 1); bc.bytes[bc.offset++] = x; } export function readU16(bc) { check(bc, 2); const result = bc.view.getUint16(bc.offset, true); bc.offset += 2; return result; } export function writeU16(bc, x) { if (DEV) { assert(isU16(x), TOO_LARGE_NUMBER); } reserve(bc, 2); bc.view.setUint16(bc.offset, x, true); bc.offset += 2; } export function readU32(bc) { check(bc, 4); const result = bc.view.getUint32(bc.offset, true); bc.offset += 4; return result; } export function writeU32(bc, x) { if (DEV) { assert(isU32(x), TOO_LARGE_NUMBER); } reserve(bc, 4); bc.view.setUint32(bc.offset, x, true); bc.offset += 4; } export function readU64(bc) { check(bc, 8); const result = bc.view.getBigUint64(bc.offset, true); bc.offset += 8; return result; } export function writeU64(bc, x) { if (DEV) { assert(isU64(x), TOO_LARGE_NUMBER); } reserve(bc, 8); bc.view.setBigUint64(bc.offset, x, true); bc.offset += 8; } export function readU64Safe(bc) { const result = readU32(bc) + readU32(bc) * /* 2**32 */ 4294967296; if (!isU64Safe(result)) { bc.offset -= 8; throw new BareError(bc.offset, TOO_LARGE_NUMBER); } return result; } export function writeU64Safe(bc, x) { if (DEV) { assert(isU64Safe(x), TOO_LARGE_NUMBER); } writeU32(bc, x >>> 0); writeU32(bc, x / /* 2**32 */ 4294967296 & /* 2**21-1 */ 2097151); } export function readUint(bc) { let low = readU8(bc); if (low >= 128) { low &= 127; let shiftMul = 128; let byteCount = 1; let byte; do { byte = readU8(bc); low += (byte & 127) * shiftMul; shiftMul *= /* 2**7 */ 128; byteCount++; } while (byte >= 128 && byteCount < 7); let height = 0; shiftMul = 1; while (byte >= 128 && byteCount < UINT_MAX_BYTE_COUNT) { byte = readU8(bc); height += (byte & 127) * shiftMul; shiftMul *= /* 2**7 */ 128; byteCount++; } if (byte === 0 || byteCount === UINT_MAX_BYTE_COUNT && byte > 1) { bc.offset -= byteCount; throw new BareError(bc.offset, NON_CANONICAL_REPRESENTATION); } return BigInt(low) + (BigInt(height) << BigInt(7 * 7)); } return BigInt(low); } export function writeUint(bc, x) { const truncated = BigInt.asUintN(64, x); if (DEV) { assert(truncated === x, TOO_LARGE_NUMBER); } writeTruncatedUint(bc, truncated); } function writeTruncatedUint(bc, x) { let tmp = Number(BigInt.asUintN(7 * 7, x)); let rest = Number(x >> BigInt(7 * 7)); let byteCount = 0; while (tmp >= 128 || rest !== 0) { writeU8(bc, 128 | tmp & 127); tmp = Math.floor(tmp / /* 2**7 */ 128); byteCount++; if (byteCount === 7) { tmp = rest; rest = 0; } } writeU8(bc, tmp); } export function readUintSafe32(bc) { let result = readU8(bc); if (result >= 128) { result &= 127; let shift = 7; let byteCount = 1; let byte; do { byte = readU8(bc); result += (byte & 127) << shift >>> 0; shift += 7; byteCount++; } while (byte >= 128 && byteCount < UINT_SAFE32_MAX_BYTE_COUNT); if (byte === 0) { bc.offset -= byteCount - 1; throw new BareError( bc.offset - byteCount + 1, NON_CANONICAL_REPRESENTATION ); } if (byteCount === UINT_SAFE32_MAX_BYTE_COUNT && byte > 15) { bc.offset -= byteCount - 1; throw new BareError(bc.offset, TOO_LARGE_NUMBER); } } return result; } export function writeUintSafe32(bc, x) { if (DEV) { assert(isU32(x), TOO_LARGE_NUMBER); } let zigZag = x >>> 0; while (zigZag >= 128) { writeU8(bc, 128 | x & 127); zigZag >>>= 7; } writeU8(bc, zigZag); } export function readUintSafe(bc) { let result = readU8(bc); if (result >= 128) { result &= 127; let shiftMul = ( /* 2**7 */ 128 ); let byteCount = 1; let byte; do { byte = readU8(bc); result += (byte & 127) * shiftMul; shiftMul *= /* 2**7 */ 128; byteCount++; } while (byte >= 128 && byteCount < INT_SAFE_MAX_BYTE_COUNT); if (byte === 0) { bc.offset -= byteCount - 1; throw new BareError( bc.offset - byteCount + 1, NON_CANONICAL_REPRESENTATION ); } if (byteCount === INT_SAFE_MAX_BYTE_COUNT && byte > 15) { bc.offset -= byteCount - 1; throw new BareError(bc.offset, TOO_LARGE_NUMBER); } } return result; } export function writeUintSafe(bc, x) { if (DEV) { assert(isU64Safe(x), TOO_LARGE_NUMBER); } let byteCount = 1; let zigZag = x; while (zigZag >= 128 && byteCount < INT_SAFE_MAX_BYTE_COUNT) { writeU8(bc, 128 | zigZag & 127); zigZag = Math.floor(zigZag / /* 2**7 */ 128); byteCount++; } if (byteCount === INT_SAFE_MAX_BYTE_COUNT) { zigZag &= 15; } writeU8(bc, zigZag); }