import { Token, Type } from './token.js'; import { decodeErrPrefix } from './common.js'; import { encodeUint } from './0uint.js'; const MINOR_FALSE = 20; const MINOR_TRUE = 21; const MINOR_NULL = 22; const MINOR_UNDEFINED = 23; export function decodeUndefined(_data, _pos, _minor, options) { if (options.allowUndefined === false) { throw new Error(`${ decodeErrPrefix } undefined values are not supported`); } else if (options.coerceUndefinedToNull === true) { return new Token(Type.null, null, 1); } return new Token(Type.undefined, undefined, 1); } export function decodeBreak(_data, _pos, _minor, options) { if (options.allowIndefinite === false) { throw new Error(`${ decodeErrPrefix } indefinite length items not allowed`); } return new Token(Type.break, undefined, 1); } function createToken(value, bytes, options) { if (options) { if (options.allowNaN === false && Number.isNaN(value)) { throw new Error(`${ decodeErrPrefix } NaN values are not supported`); } if (options.allowInfinity === false && (value === Infinity || value === -Infinity)) { throw new Error(`${ decodeErrPrefix } Infinity values are not supported`); } } return new Token(Type.float, value, bytes); } export function decodeFloat16(data, pos, _minor, options) { return createToken(readFloat16(data, pos + 1), 3, options); } export function decodeFloat32(data, pos, _minor, options) { return createToken(readFloat32(data, pos + 1), 5, options); } export function decodeFloat64(data, pos, _minor, options) { return createToken(readFloat64(data, pos + 1), 9, options); } export function encodeFloat(buf, token, options) { const float = token.value; if (float === false) { buf.push([Type.float.majorEncoded | MINOR_FALSE]); } else if (float === true) { buf.push([Type.float.majorEncoded | MINOR_TRUE]); } else if (float === null) { buf.push([Type.float.majorEncoded | MINOR_NULL]); } else if (float === undefined) { buf.push([Type.float.majorEncoded | MINOR_UNDEFINED]); } else { let decoded; let success = false; if (!options || options.float64 !== true) { encodeFloat16(float); decoded = readFloat16(ui8a, 1); if (float === decoded || Number.isNaN(float)) { ui8a[0] = 249; buf.push(ui8a.slice(0, 3)); success = true; } else { encodeFloat32(float); decoded = readFloat32(ui8a, 1); if (float === decoded) { ui8a[0] = 250; buf.push(ui8a.slice(0, 5)); success = true; } } } if (!success) { encodeFloat64(float); decoded = readFloat64(ui8a, 1); ui8a[0] = 251; buf.push(ui8a.slice(0, 9)); } } } encodeFloat.encodedSize = function encodedSize(token, options) { const float = token.value; if (float === false || float === true || float === null || float === undefined) { return 1; } if (!options || options.float64 !== true) { encodeFloat16(float); let decoded = readFloat16(ui8a, 1); if (float === decoded || Number.isNaN(float)) { return 3; } encodeFloat32(float); decoded = readFloat32(ui8a, 1); if (float === decoded) { return 5; } } return 9; }; const buffer = new ArrayBuffer(9); const dataView = new DataView(buffer, 1); const ui8a = new Uint8Array(buffer, 0); function encodeFloat16(inp) { if (inp === Infinity) { dataView.setUint16(0, 31744, false); } else if (inp === -Infinity) { dataView.setUint16(0, 64512, false); } else if (Number.isNaN(inp)) { dataView.setUint16(0, 32256, false); } else { dataView.setFloat32(0, inp); const valu32 = dataView.getUint32(0); const exponent = (valu32 & 2139095040) >> 23; const mantissa = valu32 & 8388607; if (exponent === 255) { dataView.setUint16(0, 31744, false); } else if (exponent === 0) { dataView.setUint16(0, (inp & 2147483648) >> 16 | mantissa >> 13, false); } else { const logicalExponent = exponent - 127; if (logicalExponent < -24) { dataView.setUint16(0, 0); } else if (logicalExponent < -14) { dataView.setUint16(0, (valu32 & 2147483648) >> 16 | 1 << 24 + logicalExponent, false); } else { dataView.setUint16(0, (valu32 & 2147483648) >> 16 | logicalExponent + 15 << 10 | mantissa >> 13, false); } } } } function readFloat16(ui8a, pos) { if (ui8a.length - pos < 2) { throw new Error(`${ decodeErrPrefix } not enough data for float16`); } const half = (ui8a[pos] << 8) + ui8a[pos + 1]; if (half === 31744) { return Infinity; } if (half === 64512) { return -Infinity; } if (half === 32256) { return NaN; } const exp = half >> 10 & 31; const mant = half & 1023; let val; if (exp === 0) { val = mant * 2 ** -24; } else if (exp !== 31) { val = (mant + 1024) * 2 ** (exp - 25); } else { val = mant === 0 ? Infinity : NaN; } return half & 32768 ? -val : val; } function encodeFloat32(inp) { dataView.setFloat32(0, inp, false); } function readFloat32(ui8a, pos) { if (ui8a.length - pos < 4) { throw new Error(`${ decodeErrPrefix } not enough data for float32`); } const offset = (ui8a.byteOffset || 0) + pos; return new DataView(ui8a.buffer, offset, 4).getFloat32(0, false); } function encodeFloat64(inp) { dataView.setFloat64(0, inp, false); } function readFloat64(ui8a, pos) { if (ui8a.length - pos < 8) { throw new Error(`${ decodeErrPrefix } not enough data for float64`); } const offset = (ui8a.byteOffset || 0) + pos; return new DataView(ui8a.buffer, offset, 8).getFloat64(0, false); } encodeFloat.compareTokens = encodeUint.compareTokens;