From: Lady Date: Tue, 27 Jun 2023 01:07:19 +0000 (-0700) Subject: Add buffer getters and setters to binary.js X-Git-Tag: 0.4.0 X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/83f6aae0d1b8181dc2b0c6ccdba9f2fe2fdba3e6 Add buffer getters and setters to binary.js These are rough equivalents to the `DataView` methods, but can be called with any kind of array buffer or array buffer view. Internally, they (weakly) remember a `DataView` instance for each buffer and use that for accessing values (to ensure correct endianness behaviour). --- diff --git a/binary.js b/binary.js index 7105731..c149daf 100644 --- a/binary.js +++ b/binary.js @@ -79,9 +79,22 @@ const getViewByteLength = const getViewByteOffset = Object.getOwnPropertyDescriptor(viewPrototype, "byteOffset").get; const { + getBigInt64: viewGetInt64, + getBigUint64: viewGetUint64, + getFloat32: viewGetFloat32, + getFloat64: viewGetFloat64, + getInt8: viewGetInt8, + getInt16: viewGetInt16, + getInt32: viewGetInt32, getUint8: viewGetUint8, + getUint16: viewGetUint16, + getUint32: viewGetUint32, + setFloat32: viewSetFloat32, + setFloat64: viewSetFloat64, setUint8: viewSetUint8, setUint16: viewSetUint16, + setUint32: viewSetUint32, + setBigUint64: viewSetUint64, } = viewPrototype; /** @@ -682,6 +695,437 @@ export const filenameSafeBase64Binary = ($, ...$s) => export const filenameSafeBase64String = ($, ...$s) => encodeBase64(bufferFromArgs($, $s), true); +export const { + /** + * Returns the signed 8‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getInt8`, but works on all array + * buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get8BitSignedIntegralItem, + + /** + * Returns the unsigned 8‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getUint8`, but works on all array + * buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get8BitUnsignedIntegralItem, + + /** + * Returns the signed 16‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getInt16`, but works on all array + * buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get16BitSignedIntegralItem, + + /** + * Returns the unsigned 16‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getUint16`, but works on all + * array buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get16BitUnsignedIntegralItem, + + /** + * Returns the 32‐bit floating point value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getFloat32`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get32BitFloatingPointItem, + + /** + * Returns the signed 32‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getInt32`, but works on all array + * buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get32BitSignedIntegralItem, + + /** + * Returns the unsigned 32‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getUint32`, but works on all + * array buffers and array buffer views and returns a big·int. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get32BitUnsignedIntegralItem, + + /** + * Returns the 64‐bit floating point value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getFloat64`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get64BitFloatingPointItem, + + /** + * Returns the signed 64‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getBigInt64`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get64BitSignedIntegralItem, + + /** + * Returns the unsigned 64‐bit integral value in the provided array + * buffer or array buffer view at the provided byte offset. + * + * ※ The retrieved value will be big·endian unless a third argument + * is specified and truthy. + * + * ※ This is similar to `DataView::getBigUint64`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + get64BitUnsignedIntegralItem, + + /** + * Sets the 8‐bit integral value in the provided array buffer or + * array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setInt8`, but works on all array + * buffers and array buffer views and accepts both numeric and + * big·int values. + * + * ※ It doesn’t matter whether the provided value is signed or + * unsigned, as the algorithm will cast one to the other. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + set8BitIntegralItem, + + /** + * Sets the 16‐bit integral value in the provided array buffer or + * array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setInt16`, but works on all array + * buffers and array buffer views and accepts both numeric and + * big·int values. + * + * ※ It doesn’t matter whether the provided value is signed or + * unsigned, as the algorithm will cast one to the other. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + set16BitIntegralItem, + + /** + * Sets the 32‐bit floating point value in the provided array buffer + * or array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setFloat32`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + set32BitFloatingPointItem, + + /** + * Sets the 32‐bit integral value in the provided array buffer or + * array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setInt32`, but works on all array + * buffers and array buffer views and accepts both numeric and + * big·int values. + * + * ※ It doesn’t matter whether the provided value is signed or + * unsigned, as the algorithm will cast one to the other. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + set32BitIntegralItem, + + /** + * Sets the 64‐bit floating point value in the provided array buffer + * or array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setFloat64`, but works on all + * array buffers and array buffer views. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array. + */ + set64BitFloatingPointItem, + + /** + * Sets the 64‐bit integral value in the provided array buffer or + * array buffer view at the provided byte offset to the provided + * value. + * + * ※ The value will be set as big·endian unless a fourth argument is + * specified and truthy. + * + * ※ This is similar to `DataView::setInt32`, but works on all array + * buffers and array buffer views. + * + * ※ It doesn’t matter whether the provided value is signed or + * unsigned, as the algorithm will cast one to the other. + * + * ☡ This function throws if the first argument is not an array + * buffer, data view, or typed array, or if the third argument is not + * a big·int. + */ + set64BitIntegralItem, +} = (() => { + const makeBigInt = BigInt; + const { asUintN } = BigInt; + const makeNumber = Number; + + const viewMap = new WeakMap(); + const view = ($) => { + const buffer = toArrayBuffer($); + if (viewMap.has(buffer)) { + // A view has already been allocated for this buffer; use it. + return viewMap.get(buffer); + } else { + // No view has been created for this buffer yet. + const result = new View(buffer); + viewMap.set(buffer, result); + return result; + } + }; + + return { + get8BitSignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetInt8, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get8BitUnsignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetUint8, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get16BitSignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetInt16, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get16BitUnsignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetUint16, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get32BitFloatingPointItem: ($, byteOffset, ...args) => + call(viewGetFloat32, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + get32BitSignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetInt32, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get32BitUnsignedIntegralItem: ($, byteOffset, ...args) => + makeBigInt( + call(viewGetUint32, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + ), + get64BitFloatingPointItem: ($, byteOffset, ...args) => + call(viewGetFloat64, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + get64BitSignedIntegralItem: ($, byteOffset, ...args) => + call(viewGetInt64, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + get64BitUnsignedIntegralItem: ($, byteOffset, ...args) => + call(viewGetUint64, view($), [ + getByteOffset($) + byteOffset, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set8BitIntegralItem: ($, byteOffset, value, ...args) => + call(viewSetUint8, view($), [ + getByteOffset($) + byteOffset, + makeNumber(value), + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set16BitIntegralItem: ($, byteOffset, value, ...args) => + call(viewSetUint16, view($), [ + getByteOffset($) + byteOffset, + makeNumber(value), + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set32BitFloatingPointItem: ($, byteOffset, value, ...args) => + call(viewSetFloat32, view($), [ + getByteOffset($) + byteOffset, + value, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set32BitIntegralItem: ($, byteOffset, value, ...args) => + call(viewSetUint32, view($), [ + getByteOffset($) + byteOffset, + makeNumber(value), + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set64BitFloatingPointItem: ($, byteOffset, value, ...args) => + call(viewSetFloat64, view($), [ + getByteOffset($) + byteOffset, + value, + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + set64BitIntegralItem: ($, byteOffset, value, ...args) => + call(viewSetUint64, view($), [ + getByteOffset($) + byteOffset, + asUintN(64, value), + ...objectCreate( + argumentIterablePrototype, + { args: { value: args } }, + ), + ]), + }; +})(); + /** * Returns the byte length for the provided array buffer or array * buffer view. diff --git a/binary.test.js b/binary.test.js index c5c2600..98f441a 100644 --- a/binary.test.js +++ b/binary.test.js @@ -25,6 +25,16 @@ import { base64String, filenameSafeBase64Binary, filenameSafeBase64String, + get16BitSignedIntegralItem, + get16BitUnsignedIntegralItem, + get32BitFloatingPointItem, + get32BitSignedIntegralItem, + get32BitUnsignedIntegralItem, + get64BitFloatingPointItem, + get64BitSignedIntegralItem, + get64BitUnsignedIntegralItem, + get8BitSignedIntegralItem, + get8BitUnsignedIntegralItem, isArrayBuffer, isArrayBufferView, isBase16, @@ -34,6 +44,12 @@ import { isSharedArrayBuffer, isTypedArray, isWRMGBase32, + set16BitIntegralItem, + set32BitFloatingPointItem, + set32BitIntegralItem, + set64BitFloatingPointItem, + set64BitIntegralItem, + set8BitIntegralItem, toArrayBuffer, wrmgBase32Binary, wrmgBase32String, @@ -623,6 +639,191 @@ describe("filenameSafeBase64String", () => { }); }); +describe("get8BitSignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from([0, -1, 0]).buffer; + assertStrictEquals(get8BitSignedIntegralItem(buffer, 1), -1n); + assertStrictEquals( + get8BitSignedIntegralItem(new DataView(buffer), 1), + -1n, + ); + assertStrictEquals( + get8BitSignedIntegralItem(new Uint8Array(buffer), 1), + -1n, + ); + }); +}); + +describe("get8BitUnsignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from([0, -1, 0]).buffer; + assertStrictEquals(get8BitUnsignedIntegralItem(buffer, 1), 255n); + assertStrictEquals( + get8BitUnsignedIntegralItem(new DataView(buffer), 1), + 255n, + ); + assertStrictEquals( + get8BitUnsignedIntegralItem(new Int8Array(buffer), 1), + 255n, + ); + }); +}); + +describe("get16BitSignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from([0, 0, -1, -1, 0, 0]).buffer; + assertStrictEquals(get16BitSignedIntegralItem(buffer, 2), -1n); + assertStrictEquals( + get16BitSignedIntegralItem(new DataView(buffer), 2), + -1n, + ); + assertStrictEquals( + get16BitSignedIntegralItem(new Uint16Array(buffer), 2), + -1n, + ); + }); +}); + +describe("get16BitUnsignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from([0, 0, -1, -1, 0, 0]).buffer; + assertStrictEquals( + get16BitUnsignedIntegralItem(buffer, 2), + (1n << 16n) - 1n, + ); + assertStrictEquals( + get16BitUnsignedIntegralItem(new DataView(buffer), 2), + (1n << 16n) - 1n, + ); + assertStrictEquals( + get16BitUnsignedIntegralItem(new Int16Array(buffer), 2), + (1n << 16n) - 1n, + ); + }); +}); + +describe("get32BitFloatingPointItem", () => { + it("[[Call]] gets the item", () => { + const buffer = new ArrayBuffer(12); + const view = new DataView(buffer); + view.setFloat32(0, NaN); + view.setFloat32(4, -Infinity); + view.setFloat32(8, -0); + assertStrictEquals(get32BitFloatingPointItem(buffer, 0), NaN); + assertStrictEquals( + get32BitFloatingPointItem(buffer, 4), + -Infinity, + ); + assertStrictEquals(get32BitFloatingPointItem(buffer, 8), -0); + assertStrictEquals( + get32BitFloatingPointItem(new DataView(buffer), 4), + -Infinity, + ); + assertStrictEquals( + get32BitFloatingPointItem(new Uint32Array(buffer), 4), + -Infinity, + ); + }); +}); + +describe("get32BitSignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from( + [0, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0], + ).buffer; + assertStrictEquals(get32BitSignedIntegralItem(buffer, 4), -1n); + assertStrictEquals( + get32BitSignedIntegralItem(new DataView(buffer), 4), + -1n, + ); + assertStrictEquals( + get32BitSignedIntegralItem(new Uint32Array(buffer), 4), + -1n, + ); + }); +}); + +describe("get32BitUnsignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from( + [0, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0], + ).buffer; + assertStrictEquals( + get32BitUnsignedIntegralItem(buffer, 4), + (1n << 32n) - 1n, + ); + assertStrictEquals( + get32BitUnsignedIntegralItem(new DataView(buffer), 4), + (1n << 32n) - 1n, + ); + assertStrictEquals( + get32BitUnsignedIntegralItem(new Int32Array(buffer), 4), + (1n << 32n) - 1n, + ); + }); +}); + +describe("get64BitFloatingPointItem", () => { + it("[[Call]] gets the item", () => { + const buffer = new ArrayBuffer(24); + const view = new DataView(buffer); + view.setFloat64(0, NaN); + view.setFloat64(8, -Infinity); + view.setFloat64(16, -0); + assertStrictEquals(get64BitFloatingPointItem(buffer, 0), NaN); + assertStrictEquals( + get64BitFloatingPointItem(buffer, 8), + -Infinity, + ); + assertStrictEquals(get64BitFloatingPointItem(buffer, 16), -0); + assertStrictEquals( + get64BitFloatingPointItem(new DataView(buffer), 8), + -Infinity, + ); + assertStrictEquals( + get64BitFloatingPointItem(new BigUint64Array(buffer), 8), + -Infinity, + ); + }); +}); + +describe("get64BitSignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from( + [0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1], + ).buffer; + assertStrictEquals(get64BitSignedIntegralItem(buffer, 8), -1n); + assertStrictEquals( + get64BitSignedIntegralItem(new DataView(buffer), 8), + -1n, + ); + assertStrictEquals( + get64BitSignedIntegralItem(new BigUint64Array(buffer), 8), + -1n, + ); + }); +}); + +describe("get64BitUnsignedIntegralItem", () => { + it("[[Call]] gets the item", () => { + const buffer = Int8Array.from( + [0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1], + ).buffer; + assertStrictEquals( + get64BitUnsignedIntegralItem(buffer, 8), + (1n << 64n) - 1n, + ); + assertStrictEquals( + get64BitUnsignedIntegralItem(new DataView(buffer), 8), + (1n << 64n) - 1n, + ); + assertStrictEquals( + get64BitUnsignedIntegralItem(new BigInt64Array(buffer), 8), + (1n << 64n) - 1n, + ); + }); +}); + describe("isArrayBuffer", () => { it("[[Call]] returns true for array buffers", () => { assertStrictEquals( @@ -938,6 +1139,89 @@ describe("isWRMGBase32", () => { }); }); +describe("set8BitIntegralItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(3); + set8BitIntegralItem(buffer, 1, -1n); + set8BitIntegralItem(buffer, 2, (1 << 8) - 1); + assertEquals( + new Int8Array(buffer), + Int8Array.from([0, -1, -1]), + ); + }); +}); + +describe("set16BitIntegralItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(6); + set16BitIntegralItem(buffer, 2, -1n); + set16BitIntegralItem(buffer, 4, (1 << 16) - 1); + assertEquals( + new Int8Array(buffer), + Int8Array.from([0, 0, -1, -1, -1, -1]), + ); + }); +}); + +describe("set32BitFloatingPointItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(12); + const expected = new ArrayBuffer(12); + const view = new DataView(expected); + view.setFloat32(0, NaN); + set32BitFloatingPointItem(buffer, 0, NaN); + view.setFloat32(4, -Infinity); + set32BitFloatingPointItem(buffer, 4, -Infinity); + view.setFloat32(8, -0); + set32BitFloatingPointItem(buffer, 8, -0); + assertEquals( + new Uint8Array(buffer), + new Uint8Array(expected), + ); + }); +}); + +describe("set32BitIntegralItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(12); + set32BitIntegralItem(buffer, 4, -1n); + set32BitIntegralItem(buffer, 8, -1 >>> 0); + assertEquals( + new Int8Array(buffer), + Int8Array.from([0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1]), + ); + }); +}); + +describe("set64BitFloatingPointItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(24); + const expected = new ArrayBuffer(24); + const view = new DataView(expected); + view.setFloat64(0, NaN); + set64BitFloatingPointItem(buffer, 0, NaN); + view.setFloat64(4, -Infinity); + set64BitFloatingPointItem(buffer, 4, -Infinity); + view.setFloat64(8, -0); + set64BitFloatingPointItem(buffer, 8, -0); + assertEquals( + new Uint8Array(buffer), + new Uint8Array(expected), + ); + }); +}); + +describe("set64BitIntegralItem", () => { + it("[[Call]] sets the item", () => { + const buffer = new ArrayBuffer(12); + set64BitIntegralItem(buffer, 4, -1n); + assertEquals( + new Int8Array(buffer), + Int8Array.from([0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1]), + ); + }); +}); + describe("toArrayBuffer", () => { it("[[Call]] returns the argument for array buffers", () => { const buffer = new ArrayBuffer();