]> Lady’s Gitweb - Pisces/blobdiff - binary.js
Add methods for own entries and values to object.js
[Pisces] / binary.js
index dc4b6120ebfffb0e5ed845de73808b40c937b90a..c149daf82d8245c78c418bda46b1175b1e284b22 100644 (file)
--- a/binary.js
+++ b/binary.js
@@ -1,7 +1,7 @@
 // ♓🌟 Piscēs ∷ binary.js
 // ====================================================================
 //
-// Copyright © 2020–2022 Lady [@ Lady’s Computer].
+// Copyright © 2020–2023 Lady [@ Lady’s Computer].
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -17,42 +17,100 @@ import {
   stringFromCodeUnits,
   stringReplace,
 } from "./string.js";
+import { ITERATOR } from "./value.js";
 
 const Buffer = ArrayBuffer;
 const View = DataView;
 const TypedArray = Object.getPrototypeOf(Uint8Array);
 const { prototype: arrayPrototype } = Array;
 const { prototype: bufferPrototype } = Buffer;
-const { iterator: iteratorSymbol } = Symbol;
+const { prototype: sharedBufferPrototype } = SharedArrayBuffer;
 const { prototype: rePrototype } = RegExp;
 const { prototype: typedArrayPrototype } = TypedArray;
 const { prototype: viewPrototype } = View;
 
-const { [iteratorSymbol]: arrayIterator } = arrayPrototype;
+const { [ITERATOR]: arrayIterator } = arrayPrototype;
 const {
   next: arrayIteratorNext,
-} = Object.getPrototypeOf([][iteratorSymbol]());
+} = Object.getPrototypeOf([][ITERATOR]());
+const argumentIterablePrototype = {
+  [ITERATOR]() {
+    return {
+      next: bind(
+        arrayIteratorNext,
+        call(arrayIterator, this.args, []),
+        [],
+      ),
+    };
+  },
+};
+const binaryCodeUnitIterablePrototype = {
+  [ITERATOR]() {
+    return {
+      next: bind(
+        arrayIteratorNext,
+        call(arrayIterator, this, []),
+        [],
+      ),
+    };
+  },
+};
+
+const { exec: reExec } = rePrototype;
+const { slice: bufferSlice } = bufferPrototype;
 const getBufferByteLength =
   Object.getOwnPropertyDescriptor(bufferPrototype, "byteLength").get;
+const { slice: sharedBufferSlice } = sharedBufferPrototype;
+const getSharedBufferByteLength =
+  Object.getOwnPropertyDescriptor(sharedBufferPrototype, "byteLength")
+    .get;
 const getTypedArrayBuffer =
   Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
+const getTypedArrayByteLength =
+  Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength")
+    .get;
+const getTypedArrayByteOffset =
+  Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteOffset")
+    .get;
 const getViewBuffer =
   Object.getOwnPropertyDescriptor(viewPrototype, "buffer").get;
-const { exec: reExec } = rePrototype;
+const getViewByteLength =
+  Object.getOwnPropertyDescriptor(viewPrototype, "byteLength").get;
+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;
 
-const bufferFromArgs = ($, $s) =>
-  $ instanceof Buffer
-    ? $
-    : $ instanceof View
-    ? call(getViewBuffer, $, [])
-    : $ instanceof TypedArray
-    ? call(getTypedArrayBuffer, $, [])
-    : ((string) =>
+/**
+ * Returns an ArrayBuffer for encoding generated from the provided
+ * arguments.
+ */
+const bufferFromArgs = ($, $s) => {
+  try {
+    // Try just getting the array buffer associated with the first
+    // argument and returning it if possible.
+    return toArrayBuffer($);
+  } catch {
+    // There is no array buffer associated with the first argument.
+    //
+    // Construct a string and convert it to an array buffer instead.
+    return ((string) =>
       call(
         getViewBuffer,
         reduce(
@@ -67,28 +125,194 @@ const bufferFromArgs = ($, $s) =>
         ),
         [],
       ))(
-        typeof $ == "string"
+        typeof $ === "string"
           ? $
           : hasOwnProperty($, "raw")
-          ? rawString($, ...$s)
+          ? rawString(
+            $,
+            ...objectCreate(argumentIterablePrototype, {
+              args: { value: $s },
+            }),
+          )
           : `${$}`,
       );
+  }
+};
 
-const binaryCodeUnitIterablePrototype = {
-  [iteratorSymbol]() {
-    return {
-      next: bind(
-        arrayIteratorNext,
-        call(arrayIterator, this, []),
-        [],
-      ),
-    };
-  },
+/**
+ * Returns the result of decoding the provided base16 string into an
+ * ArrayBuffer.
+ *
+ * ※ This function is not exposed.
+ */
+const decodeBase16 = (source) => {
+  const u4s = map(
+    source,
+    (ucsCharacter) => {
+      const code = getCodeUnit(ucsCharacter, 0);
+      const result = code >= 0x30 && code <= 0x39
+        ? code - 48
+        : code >= 0x41 && code <= 0x46
+        ? code - 55
+        : code >= 0x61 && code <= 0x66
+        ? code - 87
+        : -1;
+      if (result < 0) {
+        // The source contains an invalid character.
+        throw new RangeError(
+          `Piscēs: Invalid character in Base64: ${ucsCharacter}.`,
+        );
+      } else {
+        // The source contains a valid character with a recognized
+        // mapping.
+        return result;
+      }
+    },
+  );
+  const { length } = u4s;
+  if (length % 2 === 1) {
+    // The length is such that an entire letter would be dropped during
+    // a forgiving decode.
+    throw new RangeError(
+      `Piscēs: Base16 string has invalid length: ${source}.`,
+    );
+  } else {
+    // Every letter contributes at least some bits to the result.
+    const dataView = new View(new Buffer(floor(length / 2)));
+    for (let index = 0; index < length - 1;) {
+      // Iterate over the characters and assign their bits to the
+      // buffer.
+      call(viewSetUint8, dataView, [
+        floor(index / 2),
+        (u4s[index] << 4) | u4s[++index, index++],
+      ]);
+    }
+    return call(getViewBuffer, dataView, []);
+  }
+};
+
+/**
+ * Returns the result of decoding the provided base32 string into an
+ * ArrayBuffer.
+ *
+ * If the second argument is truthy, uses Crockford’s encoding rather
+ * than the RFC’s (see <https://www.crockford.com/base32.html>). This
+ * is more human‐friendly and tolerant. Check digits are not supported.
+ *
+ * ※ This function is not exposed.
+ */
+const decodeBase32 = (source, wrmg) => {
+  const u5s = map(
+    wrmg
+      ? stringReplace(source, /-/gu, "")
+      : source.length % 8 === 0
+      ? stringReplace(source, /(?:=|={3,4}|={6})$/u, "")
+      : source,
+    (ucsCharacter) => {
+      const code = getCodeUnit(ucsCharacter, 0);
+      const result = wrmg
+        ? code >= 0x30 && code <= 0x39
+          ? code - 48
+          : code >= 0x41 && code <= 0x48
+          ? code - 55
+          : code === 0x49
+          ? 1 // I
+          : code >= 0x4A && code <= 0x4B
+          ? code - 56
+          : code === 0x4C
+          ? 1 // L
+          : code >= 0x4D && code <= 0x4E
+          ? code - 57
+          : code === 0x4F
+          ? 0 // O
+          : code >= 0x50 && code <= 0x54
+          ? code - 58
+          // U is skipped
+          : code >= 0x56 && code <= 0x5A
+          ? code - 59
+          : code >= 0x61 && code <= 0x68
+          ? code - 87
+          : code === 0x69
+          ? 1 // i
+          : code >= 0x6A && code <= 0x6B
+          ? code - 88
+          : code === 0x6C
+          ? 1 // l
+          : code >= 0x6D && code <= 0x6E
+          ? code - 89
+          : code === 0x6F
+          ? 0 // o
+          : code >= 0x70 && code <= 0x74
+          ? code - 90
+          // u is skipped
+          : code >= 0x76 && code <= 0x7A
+          ? code - 91
+          : -1
+        : code >= 0x41 && code <= 0x5A
+        ? code - 65
+        : code >= 0x61 && code <= 0x7A
+        ? code - 97 // same result as above; case insensitive
+        : code >= 0x32 && code <= 0x37
+        ? code - 24 // digits 2–7 map to 26–31
+        : -1;
+      if (result < 0) {
+        // The source contains an invalid character.
+        throw new RangeError(
+          `Piscēs: Invalid character in Base32: ${ucsCharacter}.`,
+        );
+      } else {
+        // The source contains a valid character with a recognized
+        // mapping.
+        return result;
+      }
+    },
+  );
+  const { length } = u5s;
+  const lengthMod8 = length % 8;
+  if (lengthMod8 === 1 || lengthMod8 === 3 || lengthMod8 === 6) {
+    // The length is such that an entire letter would be dropped during
+    // a forgiving decode.
+    throw new RangeError(
+      `Piscēs: Base32 string has invalid length: ${source}.`,
+    );
+  } else {
+    // Every letter contributes at least some bits to the result.
+    const dataView = new View(new Buffer(floor(length * 5 / 8)));
+    for (let index = 0; index < length - 1;) {
+      // Iterate over the characters and assign their bits to the
+      // buffer.
+      //
+      // The final index is not handled; if the string is not divisible
+      // by 8, some bits might be dropped. This matches the “forgiving
+      // decode” behaviour specified by WhatW·G for base64.
+      const dataIndex = ceil(index * 5 / 8);
+      const remainder = index % 8;
+      call(viewSetUint8, dataView, [
+        dataIndex,
+        remainder === 0
+          ? u5s[index] << 3 | u5s[++index] >> 2
+          : remainder === 1
+          ? u5s[index] << 6 | u5s[++index] << 1 | u5s[++index] >> 4
+          : remainder === 3
+          ? u5s[index] << 4 | u5s[++index] >> 1
+          : remainder === 4
+          ? u5s[index] << 7 | u5s[++index] << 2 | u5s[++index] >> 3
+          : u5s[index] << 5 | u5s[++index, index++], // remainder === 6
+      ]);
+    }
+    return call(getViewBuffer, dataView, []);
+  }
 };
 
+/**
+ * Returns the result of decoding the provided base64 string into an
+ * ArrayBuffer.
+ *
+ * ※ This function is not exposed.
+ */
 const decodeBase64 = (source, safe = false) => {
   const u6s = map(
-    source.length % 4 == 0
+    source.length % 4 === 0
       ? stringReplace(source, /={1,2}$/u, "")
       : source,
     (ucsCharacter) => {
@@ -99,45 +323,190 @@ const decodeBase64 = (source, safe = false) => {
         ? code - 71
         : code >= 0x30 && code <= 0x39
         ? code + 4
-        : code == (safe ? 0x2D : 0x2B)
+        : code === (safe ? 0x2D : 0x2B)
         ? 62
-        : code == (safe ? 0x5F : 0x2F)
+        : code === (safe ? 0x5F : 0x2F)
         ? 63
         : -1;
       if (result < 0) {
+        // The source contains an invalid character.
         throw new RangeError(
-          `Piscēs: Invalid character in Base64: ${character}.`,
+          `Piscēs: Invalid character in Base64: ${ucsCharacter}.`,
         );
       } else {
+        // The source contains a valid character with a recognized
+        // mapping.
         return result;
       }
     },
   );
   const { length } = u6s;
-  const dataView = new View(new Buffer(floor(length * 3 / 4)));
-  for (let index = 0; index < length - 1;) {
-    const dataIndex = ceil(index * 3 / 4);
-    const remainder = index % 3;
-    if (remainder == 0) {
-      call(viewSetUint8, dataView, [
-        dataIndex,
-        (u6s[index] << 2) + (u6s[++index] >> 4),
-      ]);
-    } else if (remainder == 1) {
+  if (length % 4 === 1) {
+    // The length is such that an entire letter would be dropped during
+    // a forgiving decode.
+    throw new RangeError(
+      `Piscēs: Base64 string has invalid length: ${source}.`,
+    );
+  } else {
+    // Every letter contributes at least some bits to the result.
+    const dataView = new View(new Buffer(floor(length * 3 / 4)));
+    for (let index = 0; index < length - 1;) {
+      // Iterate over the characters and assign their bits to the
+      // buffer.
+      //
+      // The final index is not handled; if the string is not divisible
+      // by 4, some bits might be dropped. This matches the “forgiving
+      // decode” behaviour specified by WhatW·G for base64.
+      const dataIndex = ceil(index * 3 / 4);
+      const remainder = index % 4;
       call(viewSetUint8, dataView, [
         dataIndex,
-        ((u6s[index] & 0xF) << 4) + (u6s[++index] >> 2),
+        remainder === 0
+          ? u6s[index] << 2 | u6s[++index] >> 4
+          : remainder === 1
+          ? u6s[index] << 4 | u6s[++index] >> 2
+          : u6s[index] << 6 | u6s[++index, index++], // remainder === 2
       ]);
+    }
+    return call(getViewBuffer, dataView, []);
+  }
+};
+
+/**
+ * Returns the result of encoding the provided ArrayBuffer into a
+ * base16 string.
+ *
+ * ※ This function is not exposed.
+ */
+const encodeBase16 = (buffer) => {
+  const dataView = new View(buffer);
+  const byteLength = call(getBufferByteLength, buffer, []);
+  const minimumLengthOfResults = byteLength * 2;
+  const resultingCodeUnits = fill(
+    objectCreate(
+      binaryCodeUnitIterablePrototype,
+      { length: { value: minimumLengthOfResults } },
+    ),
+    0x3D,
+  );
+  for (let index = 0; index < byteLength;) {
+    // Iterate over the bytes and generate code units for them.
+    const codeUnitIndex = index * 2;
+    const datum = call(viewGetUint8, dataView, [index++]);
+    const u4s = [datum >> 4, datum & 0xF];
+    for (let u4i = 0; u4i < 2; ++u4i) {
+      // Handle the high four bits, then the low four bits.
+      const u4 = u4s[u4i];
+      const result = u4 < 10 ? u4 + 48 : u4 < 16 ? u4 + 55 : -1;
+      if (result < 0) {
+        // No mapping exists for these four bits.
+        //
+        // ※ This shouldn’t be possible!
+        throw new RangeError(
+          `Piscēs: Unexpected Base16 value: ${u4}.`,
+        );
+      } else {
+        // A mapping exists for the bits.
+        resultingCodeUnits[codeUnitIndex + u4i] = result;
+      }
+    }
+  }
+  return stringFromCodeUnits(...resultingCodeUnits);
+};
+
+/**
+ * Returns the result of encoding the provided ArrayBuffer into a
+ * base32 string.
+ *
+ * ※ This function is not exposed.
+ */
+const encodeBase32 = (buffer, wrmg = false) => {
+  const dataView = new View(buffer);
+  const byteLength = call(getBufferByteLength, buffer, []);
+  const minimumLengthOfResults = ceil(byteLength * 8 / 5);
+  const fillByte = wrmg ? 0x2D : 0x3D;
+  const resultingCodeUnits = fill(
+    objectCreate(
+      binaryCodeUnitIterablePrototype,
+      {
+        length: {
+          value: minimumLengthOfResults +
+            (8 - (minimumLengthOfResults % 8)) % 8,
+        },
+      },
+    ),
+    fillByte,
+  );
+  for (let index = 0; index < byteLength;) {
+    // Iterate over the bytes and generate code units for them.
+    const codeUnitIndex = ceil(index * 8 / 5);
+    const currentIndex = codeUnitIndex + +(
+      0b01011 & 1 << index % 5 &&
+      resultingCodeUnits[codeUnitIndex] != fillByte
+    ); // bytes 0, 1 & 3 handle two letters; this is for the second
+    const remainder = currentIndex % 8;
+    const currentByte = call(viewGetUint8, dataView, [index]);
+    const nextByte =
+      0b01011010 & 1 << remainder && ++index < byteLength
+        // digits 1, 3, 4 & 6 span multiple bytes
+        ? call(viewGetUint8, dataView, [index])
+        : 0;
+    const u5 = remainder === 0
+      ? currentByte >> 3
+      : remainder === 1
+      ? (currentByte & 0b00000111) << 2 | nextByte >> 6
+      : remainder === 2
+      ? (currentByte & 0b00111111) >> 1
+      : remainder === 3
+      ? (currentByte & 0b00000001) << 4 | nextByte >> 4
+      : remainder === 4
+      ? (currentByte & 0b00001111) << 1 | nextByte >> 7
+      : remainder === 5
+      ? (currentByte & 0b01111111) >> 2
+      : remainder === 6
+      ? (currentByte & 0b00000011) << 3 | nextByte >> 5
+      : (++index, currentByte & 0b00011111); // remainder === 7
+    const result = wrmg
+      ? u5 < 10 ? u5 + 48 : u5 < 18
+        ? u5 + 55
+        // skip I
+        : u5 < 20
+        ? u5 + 56
+        // skip L
+        : u5 < 22
+        ? u5 + 57
+        // skip O
+        : u5 < 27
+        ? u5 + 58
+        // skip U
+        : u5 < 32
+        ? u5 + 59
+        : -1
+      : u5 < 26
+      ? u5 + 65
+      : u5 < 32
+      ? u5 + 24
+      : -1;
+    if (result < 0) {
+      // No mapping exists for these five bits.
+      //
+      // ※ This shouldn’t be possible!
+      throw new RangeError(`Piscēs: Unexpected Base32 value: ${u5}.`);
     } else {
-      call(viewSetUint8, dataView, [
-        dataIndex,
-        ((u6s[index] & 0x3) << 6) + u6s[++index],
-      ]);
+      // A mapping exists for the bits.
+      resultingCodeUnits[currentIndex] = result;
     }
   }
-  return call(getViewBuffer, dataView, []);
+  const answer = stringFromCodeUnits(...resultingCodeUnits);
+  return wrmg ? answer.replace(/-+$/u, "") : answer;
 };
 
+/**
+ * Returns the result of encoding the provided ArrayBuffer into a
+ * base64 string.
+ *
+ * ※ This function is not exposed.
+ */
 const encodeBase64 = (buffer, safe = false) => {
   const dataView = new View(buffer);
   const byteLength = call(getBufferByteLength, buffer, []);
@@ -155,24 +524,24 @@ const encodeBase64 = (buffer, safe = false) => {
     0x3D,
   );
   for (let index = 0; index < byteLength;) {
+    // Iterate over the bytes and generate code units for them.
     const codeUnitIndex = ceil(index * 4 / 3);
     const currentIndex = codeUnitIndex + +(
-      index % 3 == 0 && resultingCodeUnits[codeUnitIndex] != 0x3D
-    );
+      index % 3 === 0 && resultingCodeUnits[codeUnitIndex] != 0x3D
+    ); // every third byte handles two letters; this is for the second
     const remainder = currentIndex % 4;
-    const u6 = remainder == 0
-      ? call(viewGetUint8, dataView, [index]) >> 2
-      : remainder == 1
-      ? ((call(viewGetUint8, dataView, [index++]) & 0x3) << 4) +
-        (index < byteLength
-          ? call(viewGetUint8, dataView, [index]) >> 4
-          : 0)
-      : remainder == 2
-      ? ((call(viewGetUint8, dataView, [index++]) & 0xF) << 2) +
-        (index < byteLength
-          ? call(viewGetUint8, dataView, [index]) >> 6
-          : 0)
-      : call(viewGetUint8, dataView, [index++]) & 0x3F;
+    const currentByte = call(viewGetUint8, dataView, [index]);
+    const nextByte = remainder % 3 && ++index < byteLength
+      // digits 1 & 2 span multiple bytes
+      ? call(viewGetUint8, dataView, [index])
+      : 0;
+    const u6 = remainder === 0
+      ? currentByte >> 2
+      : remainder === 1
+      ? (currentByte & 0b00000011) << 4 | nextByte >> 4
+      : remainder === 2
+      ? (currentByte & 0b00001111) << 2 | nextByte >> 6
+      : (++index, currentByte & 0b00111111); // remainder === 3
     const result = u6 < 26
       ? u6 + 65
       : u6 < 52
@@ -185,37 +554,117 @@ const encodeBase64 = (buffer, safe = false) => {
       ? (safe ? 0x5F : 0x2F)
       : -1;
     if (result < 0) {
+      // No mapping exists for these six bits.
+      //
+      // ※ This shouldn’t be possible!
       throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
     } else {
+      // A mapping exists for the bits.
       resultingCodeUnits[currentIndex] = result;
     }
   }
   return stringFromCodeUnits(...resultingCodeUnits);
 };
 
+/**
+ * Returns a source string generated from the arguments passed to a
+ * tag function.
+ *
+ * ※ This function is not exposed.
+ */
 const sourceFromArgs = ($, $s) =>
   stringReplace(
-    typeof $ == "string"
-      ? $
-      : hasOwnProperty($, "raw")
-      ? rawString($, ...$s)
+    typeof $ === "string" ? $ : hasOwnProperty($, "raw")
+      ? rawString(
+        $,
+        ...objectCreate(argumentIterablePrototype, {
+          args: { value: $s },
+        }),
+      )
       : `${$}`,
     /[\t\n\f\r ]+/gu,
     "",
   );
 
 /**
- * Returns an ArrayBuffer generated from the provided Base64.
+ * Returns a slice of the provided value according to the algorithm of
+ * `ArrayBuffer::slice` (or `SharedArrayBuffer::slice`).
+ *
+ * ☡ This function throws if the provided value is not an array buffer.
+ */
+export const arrayBufferSlice = ($, start, end, ...args) =>
+  call(
+    isSharedArrayBuffer($) ? sharedBufferSlice : bufferSlice,
+    $,
+    [
+      start,
+      end,
+      ...objectCreate(
+        argumentIterablePrototype,
+        { args: { value: args } },
+      ),
+    ],
+  );
+
+/**
+ * Returns an ArrayBuffer generated from the provided base16 string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ *
+ * ☡ This function throws if the provided string is not a valid base16
+ * string.
+ */
+export const base16Binary = ($, ...$s) =>
+  decodeBase16(sourceFromArgs($, $s));
+
+/**
+ * Returns a (big‐endian) base16 string created from the provided typed
+ * array, buffer, or (16‐bit) string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const base16String = ($, ...$s) =>
+  encodeBase16(bufferFromArgs($, $s));
+
+/**
+ * Returns an ArrayBuffer generated from the provided base32 string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ *
+ * ☡ This function throws if the provided string is not a valid base32
+ * string.
+ */
+export const base32Binary = ($, ...$s) =>
+  decodeBase32(sourceFromArgs($, $s));
+
+/**
+ * Returns a (big‐endian) base32 string created from the provided typed
+ * array, buffer, or (16‐bit) string.
  *
  * This function can also be used as a tag for a template literal. The
  * literal will be interpreted akin to `String.raw`.
  */
+export const base32String = ($, ...$s) =>
+  encodeBase32(bufferFromArgs($, $s));
+
+/**
+ * Returns an ArrayBuffer generated from the provided base64 string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ *
+ * ☡ This function throws if the provided string is not a valid base64
+ * string.
+ */
 export const base64Binary = ($, ...$s) =>
   decodeBase64(sourceFromArgs($, $s));
 
 /**
- * Returns a (big‐endian) base64 string created from a typed array,
- * buffer, or (16‐bit) string.
+ * Returns a (big‐endian) base64 string created from the provided typed
+ * array, buffer, or (16‐bit) string.
  *
  * This function can also be used as a tag for a template literal. The
  * literal will be interpreted akin to `String.raw`.
@@ -225,17 +674,20 @@ export const base64String = ($, ...$s) =>
 
 /**
  * Returns an ArrayBuffer generated from the provided filename‐safe
- * Base64.
+ * base64 string.
  *
  * This function can also be used as a tag for a template literal. The
  * literal will be interpreted akin to `String.raw`.
+ *
+ * ☡ This function throws if the provided string is not a valid
+ * filename‐safe base64 string.
  */
 export const filenameSafeBase64Binary = ($, ...$s) =>
   decodeBase64(sourceFromArgs($, $s), true);
 
 /**
- * Returns a (big‐endian) filename‐safe base64 string created from a
- * typed array, buffer, or (16‐bit) string.
+ * Returns a (big‐endian) filename‐safe base64 string created from the
+ * provided typed array, buffer, or (16‐bit) string.
  *
  * This function can also be used as a tag for a template literal. The
  * literal will be interpreted akin to `String.raw`.
@@ -243,38 +695,720 @@ 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.
+ *
+ * ☡ This function throws if the provided value is not an array buffer,
+ * data view, or typed array.
+ */
+export const getByteLength = ($) => {
+  try {
+    // Attempt to get the byte length from the provided value as an
+    // `ArrayBuffer`.
+    return call(getBufferByteLength, $, []);
+  } catch {
+    // The provided value is not an `ArrayBuffer`.
+    /* do nothing */
+  }
+  try {
+    // Attempt to get the byte length from the provided value as a
+    // `SharedArrayBuffer`.
+    return call(getSharedBufferByteLength, $, []);
+  } catch {
+    // The provided value is not a `SharedArrayBuffer`.
+    /* do nothing */
+  }
+  try {
+    // Attempt to get the byte length from the provided value as a
+    // data view.
+    return call(getViewByteLength, $, []);
+  } catch {
+    // The provided value is not a data view.
+    /* do nothing */
+  }
+  try {
+    // Attempt to get the byte length from the provided value as a
+    // typed array.
+    return call(getTypedArrayByteLength, $, []);
+  } catch {
+    // The provided value is not a typed array.
+    /* do nothing */
+  }
+  throw new TypeError(`Piscēs: Not an array buffer or view: ${$}.`);
+};
+
+/**
+ * Returns the byte offset for the provided array buffer or array
+ * buffer view.
+ *
+ * ※ This function always returns `0` for array buffers.
+ *
+ * ☡ This function throws if the provided value is not an array buffer,
+ * data view, or typed array.
+ */
+export const getByteOffset = ($) => {
+  if (isArrayBuffer($)) {
+    // The provided value is an array buffer.
+    return 0;
+  } else {
+    try {
+      // Attempt to get the byte offset from the provided value as a
+      // data view.
+      return call(getViewByteOffset, $, []);
+    } catch {
+      // The provided value is not a data view.
+      /* do nothing */
+    }
+    try {
+      // Attempt to get the byte offset from the provided value as a
+      // typed array.
+      return call(getTypedArrayByteOffset, $, []);
+    } catch {
+      // The provided value is not a typed array.
+      /* do nothing */
+    }
+    throw new TypeError(`Piscēs: Not an array buffer or view: ${$}.`);
+  }
+};
+
+/**
+ * Returns whether the provided value is a view on an underlying array
+ * buffer.
+ *
+ * ※ This function returns true for typed arrays and data views.
+ */
+export const { isView: isArrayBufferView } = Buffer;
+
+/**
+ * Returns whether the provided value is an array buffer.
+ *
+ * ※ This function returns true for both `ArrayBuffer`s and
+ * `SharedArrayBuffer`s.
+ */
+export const isArrayBuffer = ($) => {
+  try {
+    // Try to see if the provided argument has array buffer internal
+    // slots and return true if so.
+    return call(getBufferByteLength, $, []), true;
+  } catch {
+    // The provided argument does not have array buffer internal slots.
+    /* do nothing */
+  }
+  try {
+    // Try to see if the provided argument has array buffer internal
+    // slots and return true if so.
+    return call(getSharedBufferByteLength, $, []), true;
+  } catch {
+    // The provided argument does not have array buffer internal slots.
+    /* do nothing */
+  }
+  return false;
+};
+
+/**
+ * Returns whether the provided value is a base16 string.
+ *
+ * ※ This function returns false if the provided value is not a string
+ * primitive.
+ */
+export const isBase16 = ($) => {
+  if (typeof $ !== "string") {
+    // The provided value is not a string.
+    return false;
+  } else {
+    // The provided value is a string.
+    const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
+    return source.length % 2 !== 1 &&
+      call(reExec, /[^0-9A-F]/iu, [source]) === null;
+  }
+};
+
+/**
+ * Returns whether the provided value is a base32 string.
+ *
+ * ※ This function returns false if the provided value is not a string
+ * primitive.
+ */
+export const isBase32 = ($) => {
+  if (typeof $ !== "string") {
+    // The provided value is not a string.
+    return false;
+  } else {
+    // The provided value is a string.
+    const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
+    const trimmed = source.length % 8 === 0
+      ? stringReplace(source, /(?:=|={3,4}|={6})$/u, "")
+      : source;
+    return trimmed.length % 8 !== 1 &&
+      call(reExec, /[^2-7A-Z/]/iu, [trimmed]) === null;
+  }
+};
+
 /**
  * Returns whether the provided value is a Base64 string.
  *
- * Returns false if the provided value is not a string primitive.
+ * ※ This function returns false if the provided value is not a string
+ * primitive.
  */
 export const isBase64 = ($) => {
   if (typeof $ !== "string") {
+    // The provided value is not a string.
     return false;
   } else {
+    // The provided value is a string.
     const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
-    const trimmed = source.length % 4 == 0
+    const trimmed = source.length % 4 === 0
       ? stringReplace(source, /={1,2}$/u, "")
       : source;
-    return trimmed.length % 4 != 1 &&
-      call(reExec, /[^0-9A-Za-z+\/]/u, [trimmed]) == null;
+    return trimmed.length % 4 !== 1 &&
+      call(reExec, /[^0-9A-Za-z+\/]/u, [trimmed]) === null;
   }
 };
 
 /**
  * Returns whether the provided value is a filename‐safe base64 string.
  *
- * Returns false if the provided value is not a string primitive.
+ * ※ This function returns false if the provided value is not a string
+ * primitive.
  */
 export const isFilenameSafeBase64 = ($) => {
   if (typeof $ !== "string") {
+    // The provided value is not a string.
     return false;
   } else {
+    // The provided value is a string.
     const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
-    const trimmed = source.length % 4 == 0
+    const trimmed = source.length % 4 === 0
       ? stringReplace(source, /={1,2}$/u, "")
       : source;
-    return trimmed.length % 4 != 1 &&
-      call(reExec, /[^0-9A-Za-z_-]/u, [trimmed]) == null;
+    return trimmed.length % 4 !== 1 &&
+      call(reExec, /[^0-9A-Za-z_-]/u, [trimmed]) === null;
+  }
+};
+
+/** Returns whether the provided value is a shared array buffer. */
+export const isSharedArrayBuffer = ($) => {
+  try {
+    // Try to see if the provided argument has shared array buffer
+    // internal slots and return true if so.
+    return call(getSharedBufferByteLength, $, []), true;
+  } catch {
+    // The provided argument does not have data view internal slots.
+    return false;
+  }
+};
+
+/** Returns whether the provided value is a typed array. */
+export const isTypedArray = ($) => {
+  try {
+    // Try to see if the provided argument has typed array internal
+    // slots and return true if so.
+    return call(getTypedArrayBuffer, $, []), true;
+  } catch {
+    // The provided argument does not have typed array internal slots.
+    return false;
+  }
+};
+
+/**
+ * Returns whether the provided value is a W·R·M·G (Crockford) base32
+ * string. Check digits are not supported.
+ *
+ * ※ This function returns false if the provided value is not a string
+ * primitive.
+ */
+export const isWRMGBase32 = ($) => {
+  if (typeof $ !== "string") {
+    // The provided value is not a string.
+    return false;
+  } else {
+    // The provided value is a string.
+    const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
+    const trimmed = stringReplace(source, /-/gu, "");
+    return trimmed.length % 8 !== 1 &&
+      call(reExec, /[^0-9A-TV-Z]/iu, [trimmed]) === null;
   }
 };
+
+/**
+ * Returns the array buffer associated with the provided object.
+ *
+ * ☡ This function throws if the provided object is not a data view or
+ * typed array.
+ */
+export const toArrayBuffer = ($) => {
+  if (isArrayBuffer($)) {
+    // The provided argument has array buffer internal slots.
+    return $;
+  } else {
+    // The provided argument does not have array buffer internal slots.
+    try {
+      // The provided argument has typed array internal slots.
+      return call(getTypedArrayBuffer, $, []);
+    } catch {
+      /* do nothing */
+    }
+    try {
+      // The provided argument has data view internal slots.
+      return call(getViewBuffer, $, []);
+    } catch {
+      /* do nothing */
+    }
+    throw new TypeError(`Piscēs: Not an array buffer or view: ${$}.`);
+  }
+};
+
+/**
+ * Returns an ArrayBuffer generated from the provided W·R·M·G
+ * (Crockford) base32 string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ *
+ * ☡ This function throws if the provided string is not a valid W·R·M·G
+ * base32 string.
+ */
+export const wrmgBase32Binary = ($, ...$s) =>
+  decodeBase32(sourceFromArgs($, $s), true);
+
+/**
+ * Returns a (big‐endian) W·R·M·G (Crockford) base32 string created
+ * from the provided typed array, buffer, or (16‐bit) string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const wrmgBase32String = ($, ...$s) =>
+  encodeBase32(bufferFromArgs($, $s), true);
This page took 0.048998 seconds and 4 git commands to generate.