]> Lady’s Gitweb - Pisces/commitdiff
Refactor base64; remove runtime dependencies
authorLady <redacted>
Sat, 23 Jul 2022 08:17:12 +0000 (01:17 -0700)
committerLady <redacted>
Fri, 12 May 2023 03:56:47 +0000 (20:56 -0700)
base64.js
base64.test.js

index ff58328a0846705d6c86a0ee08d97d4b1d5c2bb2..e9661b7577e2e7a7445a07098985f322c30f506b 100644 (file)
--- a/base64.js
+++ b/base64.js
@@ -7,7 +7,55 @@
 // 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 { getPrototype, hasOwnProperty } from "./object.js";
+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 base64CodeUnitIterablePrototype = {
+  [iteratorSymbol]() {
+    return {
+      next: bind(
+        arrayIteratorNext,
+        call(arrayIterator, this, []),
+        [],
+      ),
+    };
+  },
+};
 
 /**
  * Returns an ArrayBuffer generated from the provided Base64.
@@ -16,17 +64,21 @@ import { getPrototype, hasOwnProperty } from "./object.js";
  * literal will be interpreted akin to `String.raw`.
  */
 export const a2b = ($, ...$s) => {
-  const source = (
+  const source = stringReplace(
     typeof $ == "string"
       ? $
       : hasOwnProperty($, "raw")
-      ? String.raw($, ...$s)
-      : `${$}`
-  ).replace(/[\t\n\f\r ]+/gu, "");
-  const u6s = Array.prototype.map.call(
-    source.length % 4 == 0 ? source.replace(/={1,2}$/u, "") : source,
-    (character) => {
-      const code = character.charCodeAt(0);
+      ? rawString($, ...$s)
+      : `${$}`,
+    /[\t\n\f\r ]+/gu,
+    "",
+  );
+  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
@@ -48,30 +100,28 @@ export const a2b = ($, ...$s) => {
     },
   );
   const { length } = u6s;
-  const dataView = new DataView(
-    new ArrayBuffer(Math.floor(length * 3 / 4)),
-  );
+  const dataView = new View(new Buffer(floor(length * 3 / 4)));
   for (let index = 0; index < length - 1;) {
-    const dataIndex = Math.ceil(index * 3 / 4);
+    const dataIndex = ceil(index * 3 / 4);
     const remainder = index % 3;
     if (remainder == 0) {
-      dataView.setUint8(
+      call(viewSetUint8, dataView, [
         dataIndex,
         (u6s[index] << 2) + (u6s[++index] >> 4),
-      );
+      ]);
     } else if (remainder == 1) {
-      dataView.setUint8(
+      call(viewSetUint8, dataView, [
         dataIndex,
         ((u6s[index] & 0xF) << 4) + (u6s[++index] >> 2),
-      );
+      ]);
     } else {
-      dataView.setUint8(
+      call(viewSetUint8, dataView, [
         dataIndex,
         ((u6s[index] & 0x3) << 6) + u6s[++index],
-      );
+      ]);
     }
   }
-  return dataView.buffer;
+  return call(getViewBuffer, dataView, []);
 };
 
 /**
@@ -82,45 +132,67 @@ export const a2b = ($, ...$s) => {
  * literal will be interpreted akin to `String.raw`.
  */
 export const b2a = ($, ...$s) => {
-  const buffer = $ instanceof ArrayBuffer
+  const buffer = $ instanceof Buffer
     ? $
-    : $ instanceof DataView || $ instanceof getPrototype(Uint8Array)
-    ? $.buffer
+    : $ instanceof View
+    ? call(getViewBuffer, $, [])
+    : $ instanceof TypedArray
+    ? call(getTypedArrayBuffer, $, [])
     : ((string) =>
-      Array.prototype.reduce.call(
-        string,
-        (result, code·point, index) => (
-          result.setUint16(index * 2, code·point.charCodeAt(0)), result
+      call(
+        getViewBuffer,
+        reduce(
+          string,
+          (result, ucsCharacter, index) => (
+            call(viewSetUint16, result, [
+              index * 2,
+              getCodeUnit(ucsCharacter, 0),
+            ]), result
+          ),
+          new View(new Buffer(string.length * 2)),
         ),
-        new DataView(new ArrayBuffer(string.length * 2)),
-      ).buffer)(
+        [],
+      ))(
         typeof $ == "string"
           ? $
-          : Object.hasOwn($, "raw")
-          ? String.raw($, ...$s)
+          : hasOwnProperty($, "raw")
+          ? rawString($, ...$s)
           : `${$}`,
       );
-  const dataView = new DataView(buffer);
-  const { byteLength } = buffer;
-  const minimumLengthOfResults = Math.ceil(byteLength * 4 / 3);
-  const resultingCode·points = new Array(
-    minimumLengthOfResults + (4 - (minimumLengthOfResults % 4)) % 4,
-  ).fill(0x3D);
+  const dataView = new View(buffer);
+  const byteLength = call(getBufferByteLength, buffer, []);
+  const minimumLengthOfResults = ceil(byteLength * 4 / 3);
+  const resultingCodeUnits = fill(
+    objectCreate(
+      base64CodeUnitIterablePrototype,
+      {
+        length: {
+          value: minimumLengthOfResults +
+            (4 - (minimumLengthOfResults % 4)) % 4,
+        },
+      },
+    ),
+    0x3D,
+  );
   for (let index = 0; index < byteLength;) {
-    const code·pointIndex = Math.ceil(index * 4 / 3);
-    const currentIndex = code·pointIndex + +(
-      index % 3 == 0 && resultingCode·points[code·pointIndex] != 0x3D
+    const codeUnitIndex = ceil(index * 4 / 3);
+    const currentIndex = codeUnitIndex + +(
+      index % 3 == 0 && resultingCodeUnits[codeUnitIndex] != 0x3D
     );
     const remainder = currentIndex % 4;
     const u6 = remainder == 0
-      ? dataView.getUint8(index) >> 2
+      ? call(viewGetUint8, dataView, [index]) >> 2
       : remainder == 1
-      ? ((dataView.getUint8(index++) & 0x3) << 4) +
-        (index < byteLength ? dataView.getUint8(index) >> 4 : 0)
+      ? ((call(viewGetUint8, dataView, [index++]) & 0x3) << 4) +
+        (index < byteLength
+          ? call(viewGetUint8, dataView, [index]) >> 4
+          : 0)
       : remainder == 2
-      ? ((dataView.getUint8(index++) & 0xF) << 2) +
-        (index < byteLength ? dataView.getUint8(index) >> 6 : 0)
-      : dataView.getUint8(index++) & 0x3F;
+      ? ((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
@@ -135,10 +207,10 @@ export const b2a = ($, ...$s) => {
     if (result < 0) {
       throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
     } else {
-      resultingCode·points[currentIndex] = result;
+      resultingCodeUnits[currentIndex] = result;
     }
   }
-  return String.fromCodePoint(...resultingCode·points);
+  return stringFromCodeUnits(...resultingCodeUnits);
 };
 
 /**
@@ -147,11 +219,14 @@ export const b2a = ($, ...$s) => {
  * Returns false if the provided value is not a string primitive.
  */
 export const isBase64 = ($) => {
-  if (typeof $ != "string") return false;
-  const source = $.replace(/[\t\n\f\r ]+/gu, "");
-  const trimmed = source.length % 4 == 0
-    ? source.replace(/={1,2}$/u, "")
-    : source;
-  return trimmed.length % 4 != 1 &&
-    !/[^0-9A-Za-z+\/]/u.test(trimmed);
+  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;
+  }
 };
index e6361a45e037c6f68bc5efbcdcac441bcaed6f35..84d9057e11282cf35d3c19ee6edca9a1844ded24 100755 (executable)
@@ -7,13 +7,15 @@
 // 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 { a2b, b2a, isBase64 } from "./base64.js";
 import {
   assert,
   assertEquals,
   assertStrictEquals,
   assertThrows,
+  describe,
+  it,
 } from "./dev-deps.js";
+import { a2b, b2a, isBase64 } from "./base64.js";
 
 const base64s = {
   "AGIAYQBzAGUANgA0": "base64",
@@ -24,17 +26,8 @@ const base64s = {
   ),
 };
 
-Deno.test({
-  name: "Calling b2a returns the correct string.",
-  fn: () =>
-    Object.entries(base64s).forEach(([key, value]) =>
-      assertStrictEquals(b2a(value), key)
-    ),
-});
-
-Deno.test({
-  name: "Calling a2b returns the correct data.",
-  fn: () => {
+describe("a2b", () => {
+  it("[[Call]] returns the correct data", () => {
     assertEquals(
       a2b("AGIAYQBzAGUANgA0"),
       new Uint8Array(
@@ -43,50 +36,53 @@ Deno.test({
           ($) => $.charCodeAt(0),
         ),
       ).buffer,
+      "AGIAYQBzAGUANgA0",
     );
     assertEquals(
       a2b("R/Q="),
       new Uint16Array([62535]).buffer,
+      "R/Q=",
     );
     assertEquals(
       a2b("S0lCSQ=="),
       new Uint8ClampedArray([75, 73, 66, 73]).buffer,
+      "S0lCSQ==",
     );
     assertEquals(
       a2b("YmFzZTY0"),
       new Uint8Array([98, 97, 115, 101, 54, 52]).buffer,
+      "YmFzZTY0",
     );
-  },
-});
+  });
 
-Deno.test({
-  name: "Calling a2b throws when provided with an invalid character.",
-  fn: () => assertThrows(() => a2b("abc_")),
-});
+  it("[[Call]] throws when provided with an invalid character", () => {
+    assertThrows(() => a2b("abc_"));
+  });
 
-Deno.test({
-  name: "Calling a2b throws when provided with an invalid equals.",
-  fn: () => assertThrows(() => a2b("abc==")),
-});
+  it("[[Call]] throws when provided with an invalid equals", () => {
+    assertThrows(() => a2b("abc=="));
+  });
 
-Deno.test({
-  name:
-    "Calling a2b throws when provided with a length with a remainder of 1 when divided by 4.",
-  fn: () => {
+  it("[[Call]] throws when provided with a length with a remainder of 1 when divided by 4", () => {
     assertThrows(() => a2b("abcde"));
     assertThrows(() => a2b("abcde==="));
-  },
+  });
 });
 
-Deno.test({
-  name: "isBase64 returns true for base64 strings.",
-  fn: () =>
-    Object.keys(base64s).forEach((key) => assert(isBase64(key))),
+describe("b2a", () => {
+  it("[[Call]] returns the correct string", () => {
+    Object.entries(base64s).forEach(([key, value]) =>
+      assertStrictEquals(b2a(value), key)
+    );
+  });
 });
 
-Deno.test({
-  name: "isBase64 returns false for others.",
-  fn: () =>
+describe("isBase64", () => {
+  it("[[Call]] returns true for base64 strings", () => {
+    Object.keys(base64s).forEach((key) => assert(isBase64(key)));
+  });
+
+  it("[[Call]] returns false for others", () => {
     [
       undefined,
       null,
@@ -101,5 +97,6 @@ Deno.test({
       "abc_",
       "a",
       "abc==",
-    ].forEach((value) => assert(!isBase64(value))),
+    ].forEach((value) => assert(!isBase64(value)));
+  });
 });
This page took 0.04762 seconds and 4 git commands to generate.