X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/8c8953d378b20e194535e22214750367961b6963..fb3e0d562e2dbe9e3ea911a80bfdabc8851f92b2:/base64.js?ds=sidebyside diff --git a/base64.js b/base64.js index 707e8e6..e9661b7 100644 --- a/base64.js +++ b/base64.js @@ -7,6 +7,56 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at . +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. * @@ -14,17 +64,21 @@ * literal will be interpreted akin to `String.raw`. */ export const a2b = ($, ...$s) => { - const source = ( + const source = stringReplace( typeof $ == "string" ? $ - : Object.hasOwn($, "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); + : hasOwnProperty($, "raw") + ? 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 @@ -46,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, []); }; /** @@ -80,46 +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 Object.getPrototypeOf(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 @@ -134,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); }; /** @@ -146,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; + } };