]> Lady’s Gitweb - Pisces/blob - binary.js
272775fd2939b05959610ba12917e5906ceae2f9
[Pisces] / binary.js
1 // ♓🌟 Piscēs ∷ binary.js
2 // ====================================================================
3 //
4 // Copyright © 2020–2022 Lady [@ Lady’s Computer].
5 //
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
9
10 import { fill, map, reduce } from "./collection.js";
11 import { bind, call } from "./function.js";
12 import { ceil, floor } from "./numeric.js";
13 import { hasOwnProperty, objectCreate } from "./object.js";
14 import {
15 getCodeUnit,
16 rawString,
17 stringFromCodeUnits,
18 stringReplace,
19 } from "./string.js";
20
21 const Buffer = ArrayBuffer;
22 const View = DataView;
23 const TypedArray = Object.getPrototypeOf(Uint8Array);
24 const { prototype: arrayPrototype } = Array;
25 const { prototype: bufferPrototype } = Buffer;
26 const { iterator: iteratorSymbol } = Symbol;
27 const { prototype: rePrototype } = RegExp;
28 const { prototype: typedArrayPrototype } = TypedArray;
29 const { prototype: viewPrototype } = View;
30
31 const { [iteratorSymbol]: arrayIterator } = arrayPrototype;
32 const {
33 next: arrayIteratorNext,
34 } = Object.getPrototypeOf([][iteratorSymbol]());
35 const argumentIterablePrototype = {
36 [iteratorSymbol]() {
37 return {
38 next: bind(
39 arrayIteratorNext,
40 call(arrayIterator, this.args, []),
41 [],
42 ),
43 };
44 },
45 };
46 const binaryCodeUnitIterablePrototype = {
47 [iteratorSymbol]() {
48 return {
49 next: bind(
50 arrayIteratorNext,
51 call(arrayIterator, this, []),
52 [],
53 ),
54 };
55 },
56 };
57
58 const getBufferByteLength =
59 Object.getOwnPropertyDescriptor(bufferPrototype, "byteLength").get;
60 const getTypedArrayBuffer =
61 Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
62 const getViewBuffer =
63 Object.getOwnPropertyDescriptor(viewPrototype, "buffer").get;
64 const { exec: reExec } = rePrototype;
65 const {
66 getUint8: viewGetUint8,
67 setUint8: viewSetUint8,
68 setUint16: viewSetUint16,
69 } = viewPrototype;
70
71 const bufferFromArgs = ($, $s) =>
72 $ instanceof Buffer
73 ? $
74 : $ instanceof View
75 ? call(getViewBuffer, $, [])
76 : $ instanceof TypedArray
77 ? call(getTypedArrayBuffer, $, [])
78 : ((string) =>
79 call(
80 getViewBuffer,
81 reduce(
82 string,
83 (result, ucsCharacter, index) => (
84 call(viewSetUint16, result, [
85 index * 2,
86 getCodeUnit(ucsCharacter, 0),
87 ]), result
88 ),
89 new View(new Buffer(string.length * 2)),
90 ),
91 [],
92 ))(
93 typeof $ == "string"
94 ? $
95 : hasOwnProperty($, "raw")
96 ? rawString(
97 $,
98 ...objectCreate(argumentIterablePrototype, {
99 args: { value: $s },
100 }),
101 )
102 : `${$}`,
103 );
104
105 /**
106 * Returns the result of decoding the provided base64 string into an
107 * ArrayBuffer.
108 *
109 * ※ This function is not exposed.
110 */
111 const decodeBase64 = (source, safe = false) => {
112 const u6s = map(
113 source.length % 4 == 0
114 ? stringReplace(source, /={1,2}$/u, "")
115 : source,
116 (ucsCharacter) => {
117 const code = getCodeUnit(ucsCharacter, 0);
118 const result = code >= 0x41 && code <= 0x5A
119 ? code - 65
120 : code >= 0x61 && code <= 0x7A
121 ? code - 71
122 : code >= 0x30 && code <= 0x39
123 ? code + 4
124 : code == (safe ? 0x2D : 0x2B)
125 ? 62
126 : code == (safe ? 0x5F : 0x2F)
127 ? 63
128 : -1;
129 if (result < 0) {
130 throw new RangeError(
131 `Piscēs: Invalid character in Base64: ${character}.`,
132 );
133 } else {
134 return result;
135 }
136 },
137 );
138 const { length } = u6s;
139 const dataView = new View(new Buffer(floor(length * 3 / 4)));
140 for (let index = 0; index < length - 1;) {
141 const dataIndex = ceil(index * 3 / 4);
142 const remainder = index % 3;
143 if (remainder == 0) {
144 call(viewSetUint8, dataView, [
145 dataIndex,
146 (u6s[index] << 2) + (u6s[++index] >> 4),
147 ]);
148 } else if (remainder == 1) {
149 call(viewSetUint8, dataView, [
150 dataIndex,
151 ((u6s[index] & 0xF) << 4) + (u6s[++index] >> 2),
152 ]);
153 } else {
154 call(viewSetUint8, dataView, [
155 dataIndex,
156 ((u6s[index] & 0x3) << 6) + u6s[++index],
157 ]);
158 }
159 }
160 return call(getViewBuffer, dataView, []);
161 };
162
163 /**
164 * Returns the result of encoding the provided ArrayBuffer into a
165 * base64 string.
166 *
167 * ※ This function is not exposed.
168 */
169 const encodeBase64 = (buffer, safe = false) => {
170 const dataView = new View(buffer);
171 const byteLength = call(getBufferByteLength, buffer, []);
172 const minimumLengthOfResults = ceil(byteLength * 4 / 3);
173 const resultingCodeUnits = fill(
174 objectCreate(
175 binaryCodeUnitIterablePrototype,
176 {
177 length: {
178 value: minimumLengthOfResults +
179 (4 - (minimumLengthOfResults % 4)) % 4,
180 },
181 },
182 ),
183 0x3D,
184 );
185 for (let index = 0; index < byteLength;) {
186 const codeUnitIndex = ceil(index * 4 / 3);
187 const currentIndex = codeUnitIndex + +(
188 index % 3 == 0 && resultingCodeUnits[codeUnitIndex] != 0x3D
189 );
190 const remainder = currentIndex % 4;
191 const u6 = remainder == 0
192 ? call(viewGetUint8, dataView, [index]) >> 2
193 : remainder == 1
194 ? ((call(viewGetUint8, dataView, [index++]) & 0x3) << 4) +
195 (index < byteLength
196 ? call(viewGetUint8, dataView, [index]) >> 4
197 : 0)
198 : remainder == 2
199 ? ((call(viewGetUint8, dataView, [index++]) & 0xF) << 2) +
200 (index < byteLength
201 ? call(viewGetUint8, dataView, [index]) >> 6
202 : 0)
203 : call(viewGetUint8, dataView, [index++]) & 0x3F;
204 const result = u6 < 26
205 ? u6 + 65
206 : u6 < 52
207 ? u6 + 71
208 : u6 < 62
209 ? u6 - 4
210 : u6 < 63
211 ? (safe ? 0x2D : 0x2B)
212 : u6 < 64
213 ? (safe ? 0x5F : 0x2F)
214 : -1;
215 if (result < 0) {
216 throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
217 } else {
218 resultingCodeUnits[currentIndex] = result;
219 }
220 }
221 return stringFromCodeUnits(...resultingCodeUnits);
222 };
223
224 /**
225 * Returns a source string generated from the arguments passed to a
226 * tag function.
227 *
228 * ※ This function is not exposed.
229 */
230 const sourceFromArgs = ($, $s) =>
231 stringReplace(
232 typeof $ == "string" ? $ : hasOwnProperty($, "raw")
233 ? rawString(
234 $,
235 ...objectCreate(argumentIterablePrototype, {
236 args: { value: $s },
237 }),
238 )
239 : `${$}`,
240 /[\t\n\f\r ]+/gu,
241 "",
242 );
243
244 /**
245 * Returns an ArrayBuffer generated from the provided base64 string.
246 *
247 * This function can also be used as a tag for a template literal. The
248 * literal will be interpreted akin to `String.raw`.
249 */
250 export const base64Binary = ($, ...$s) =>
251 decodeBase64(sourceFromArgs($, $s));
252
253 /**
254 * Returns a (big‐endian) base64 string created from the provided typed
255 * array, buffer, or (16‐bit) string.
256 *
257 * This function can also be used as a tag for a template literal. The
258 * literal will be interpreted akin to `String.raw`.
259 */
260 export const base64String = ($, ...$s) =>
261 encodeBase64(bufferFromArgs($, $s));
262
263 /**
264 * Returns an ArrayBuffer generated from the provided filename‐safe
265 * base64 string.
266 *
267 * This function can also be used as a tag for a template literal. The
268 * literal will be interpreted akin to `String.raw`.
269 */
270 export const filenameSafeBase64Binary = ($, ...$s) =>
271 decodeBase64(sourceFromArgs($, $s), true);
272
273 /**
274 * Returns a (big‐endian) filename‐safe base64 string created from the
275 * provided typed array, buffer, or (16‐bit) string.
276 *
277 * This function can also be used as a tag for a template literal. The
278 * literal will be interpreted akin to `String.raw`.
279 */
280 export const filenameSafeBase64String = ($, ...$s) =>
281 encodeBase64(bufferFromArgs($, $s), true);
282
283 /**
284 * Returns whether the provided value is a Base64 string.
285 *
286 * ※ This function returns false if the provided value is not a string
287 * primitive.
288 */
289 export const isBase64 = ($) => {
290 if (typeof $ !== "string") {
291 return false;
292 } else {
293 const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
294 const trimmed = source.length % 4 == 0
295 ? stringReplace(source, /={1,2}$/u, "")
296 : source;
297 return trimmed.length % 4 != 1 &&
298 call(reExec, /[^0-9A-Za-z+\/]/u, [trimmed]) == null;
299 }
300 };
301
302 /**
303 * Returns whether the provided value is a filename‐safe base64 string.
304 *
305 * ※ This function returns false if the provided value is not a string
306 * primitive.
307 */
308 export const isFilenameSafeBase64 = ($) => {
309 if (typeof $ !== "string") {
310 return false;
311 } else {
312 const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
313 const trimmed = source.length % 4 == 0
314 ? stringReplace(source, /={1,2}$/u, "")
315 : source;
316 return trimmed.length % 4 != 1 &&
317 call(reExec, /[^0-9A-Za-z_-]/u, [trimmed]) == null;
318 }
319 };
This page took 0.072486 seconds and 3 git commands to generate.