]> Lady’s Gitweb - Pisces/blobdiff - binary.js
Rename base64.js to binary.js; reorganize contents
[Pisces] / binary.js
diff --git a/binary.js b/binary.js
new file mode 100644 (file)
index 0000000..dc4b612
--- /dev/null
+++ b/binary.js
@@ -0,0 +1,280 @@
+// ♓🌟 Piscēs ∷ binary.js
+// ====================================================================
+//
+// Copyright © 2020–2022 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
+// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
+
+import { fill, map, reduce } from "./collection.js";
+import { bind, call } from "./function.js";
+import { ceil, floor } from "./numeric.js";
+import { hasOwnProperty, objectCreate } from "./object.js";
+import {
+  getCodeUnit,
+  rawString,
+  stringFromCodeUnits,
+  stringReplace,
+} from "./string.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: rePrototype } = RegExp;
+const { prototype: typedArrayPrototype } = TypedArray;
+const { prototype: viewPrototype } = View;
+
+const { [iteratorSymbol]: arrayIterator } = arrayPrototype;
+const {
+  next: arrayIteratorNext,
+} = Object.getPrototypeOf([][iteratorSymbol]());
+const getBufferByteLength =
+  Object.getOwnPropertyDescriptor(bufferPrototype, "byteLength").get;
+const getTypedArrayBuffer =
+  Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
+const getViewBuffer =
+  Object.getOwnPropertyDescriptor(viewPrototype, "buffer").get;
+const { exec: reExec } = rePrototype;
+const {
+  getUint8: viewGetUint8,
+  setUint8: viewSetUint8,
+  setUint16: viewSetUint16,
+} = viewPrototype;
+
+const bufferFromArgs = ($, $s) =>
+  $ instanceof Buffer
+    ? $
+    : $ instanceof View
+    ? call(getViewBuffer, $, [])
+    : $ instanceof TypedArray
+    ? call(getTypedArrayBuffer, $, [])
+    : ((string) =>
+      call(
+        getViewBuffer,
+        reduce(
+          string,
+          (result, ucsCharacter, index) => (
+            call(viewSetUint16, result, [
+              index * 2,
+              getCodeUnit(ucsCharacter, 0),
+            ]), result
+          ),
+          new View(new Buffer(string.length * 2)),
+        ),
+        [],
+      ))(
+        typeof $ == "string"
+          ? $
+          : hasOwnProperty($, "raw")
+          ? rawString($, ...$s)
+          : `${$}`,
+      );
+
+const binaryCodeUnitIterablePrototype = {
+  [iteratorSymbol]() {
+    return {
+      next: bind(
+        arrayIteratorNext,
+        call(arrayIterator, this, []),
+        [],
+      ),
+    };
+  },
+};
+
+const decodeBase64 = (source, safe = false) => {
+  const u6s = map(
+    source.length % 4 == 0
+      ? stringReplace(source, /={1,2}$/u, "")
+      : source,
+    (ucsCharacter) => {
+      const code = getCodeUnit(ucsCharacter, 0);
+      const result = code >= 0x41 && code <= 0x5A
+        ? code - 65
+        : code >= 0x61 && code <= 0x7A
+        ? code - 71
+        : code >= 0x30 && code <= 0x39
+        ? code + 4
+        : code == (safe ? 0x2D : 0x2B)
+        ? 62
+        : code == (safe ? 0x5F : 0x2F)
+        ? 63
+        : -1;
+      if (result < 0) {
+        throw new RangeError(
+          `Piscēs: Invalid character in Base64: ${character}.`,
+        );
+      } else {
+        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) {
+      call(viewSetUint8, dataView, [
+        dataIndex,
+        ((u6s[index] & 0xF) << 4) + (u6s[++index] >> 2),
+      ]);
+    } else {
+      call(viewSetUint8, dataView, [
+        dataIndex,
+        ((u6s[index] & 0x3) << 6) + u6s[++index],
+      ]);
+    }
+  }
+  return call(getViewBuffer, dataView, []);
+};
+
+const encodeBase64 = (buffer, safe = false) => {
+  const dataView = new View(buffer);
+  const byteLength = call(getBufferByteLength, buffer, []);
+  const minimumLengthOfResults = ceil(byteLength * 4 / 3);
+  const resultingCodeUnits = fill(
+    objectCreate(
+      binaryCodeUnitIterablePrototype,
+      {
+        length: {
+          value: minimumLengthOfResults +
+            (4 - (minimumLengthOfResults % 4)) % 4,
+        },
+      },
+    ),
+    0x3D,
+  );
+  for (let index = 0; index < byteLength;) {
+    const codeUnitIndex = ceil(index * 4 / 3);
+    const currentIndex = codeUnitIndex + +(
+      index % 3 == 0 && resultingCodeUnits[codeUnitIndex] != 0x3D
+    );
+    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 result = u6 < 26
+      ? u6 + 65
+      : u6 < 52
+      ? u6 + 71
+      : u6 < 62
+      ? u6 - 4
+      : u6 < 63
+      ? (safe ? 0x2D : 0x2B)
+      : u6 < 64
+      ? (safe ? 0x5F : 0x2F)
+      : -1;
+    if (result < 0) {
+      throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
+    } else {
+      resultingCodeUnits[currentIndex] = result;
+    }
+  }
+  return stringFromCodeUnits(...resultingCodeUnits);
+};
+
+const sourceFromArgs = ($, $s) =>
+  stringReplace(
+    typeof $ == "string"
+      ? $
+      : hasOwnProperty($, "raw")
+      ? rawString($, ...$s)
+      : `${$}`,
+    /[\t\n\f\r ]+/gu,
+    "",
+  );
+
+/**
+ * Returns an ArrayBuffer generated from the provided Base64.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const base64Binary = ($, ...$s) =>
+  decodeBase64(sourceFromArgs($, $s));
+
+/**
+ * Returns a (big‐endian) base64 string created from a 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 base64String = ($, ...$s) =>
+  encodeBase64(bufferFromArgs($, $s));
+
+/**
+ * Returns an ArrayBuffer generated from the provided filename‐safe
+ * Base64.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+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.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const filenameSafeBase64String = ($, ...$s) =>
+  encodeBase64(bufferFromArgs($, $s), true);
+
+/**
+ * Returns whether the provided value is a Base64 string.
+ *
+ * Returns false if the provided value is not a string primitive.
+ */
+export const isBase64 = ($) => {
+  if (typeof $ !== "string") {
+    return false;
+  } else {
+    const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
+    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;
+  }
+};
+
+/**
+ * Returns whether the provided value is a filename‐safe base64 string.
+ *
+ * Returns false if the provided value is not a string primitive.
+ */
+export const isFilenameSafeBase64 = ($) => {
+  if (typeof $ !== "string") {
+    return false;
+  } else {
+    const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
+    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;
+  }
+};
This page took 0.026503 seconds and 4 git commands to generate.