// 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.
* 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
},
);
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, []);
};
/**
* 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
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);
};
/**
* 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;
+ }
};
// 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",
),
};
-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(
($) => $.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,
"abc_",
"a",
"abc==",
- ].forEach((value) => assert(!isBase64(value))),
+ ].forEach((value) => assert(!isBase64(value)));
+ });
});