]> Lady’s Gitweb - Pisces/blob - binary.js
71057312fc07d1557e0d6e95c0cf3ba0efd6d772
[Pisces] / binary.js
1 // ♓🌟 Piscēs ∷ binary.js
2 // ====================================================================
3 //
4 // Copyright © 2020–2023 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 import { ITERATOR } from "./value.js";
21
22 const Buffer = ArrayBuffer;
23 const View = DataView;
24 const TypedArray = Object.getPrototypeOf(Uint8Array);
25 const { prototype: arrayPrototype } = Array;
26 const { prototype: bufferPrototype } = Buffer;
27 const { prototype: sharedBufferPrototype } = SharedArrayBuffer;
28 const { prototype: rePrototype } = RegExp;
29 const { prototype: typedArrayPrototype } = TypedArray;
30 const { prototype: viewPrototype } = View;
31
32 const { [ITERATOR]: arrayIterator } = arrayPrototype;
33 const {
34 next: arrayIteratorNext,
35 } = Object.getPrototypeOf([][ITERATOR]());
36 const argumentIterablePrototype = {
37 [ITERATOR]() {
38 return {
39 next: bind(
40 arrayIteratorNext,
41 call(arrayIterator, this.args, []),
42 [],
43 ),
44 };
45 },
46 };
47 const binaryCodeUnitIterablePrototype = {
48 [ITERATOR]() {
49 return {
50 next: bind(
51 arrayIteratorNext,
52 call(arrayIterator, this, []),
53 [],
54 ),
55 };
56 },
57 };
58
59 const { exec: reExec } = rePrototype;
60 const { slice: bufferSlice } = bufferPrototype;
61 const getBufferByteLength =
62 Object.getOwnPropertyDescriptor(bufferPrototype, "byteLength").get;
63 const { slice: sharedBufferSlice } = sharedBufferPrototype;
64 const getSharedBufferByteLength =
65 Object.getOwnPropertyDescriptor(sharedBufferPrototype, "byteLength")
66 .get;
67 const getTypedArrayBuffer =
68 Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
69 const getTypedArrayByteLength =
70 Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength")
71 .get;
72 const getTypedArrayByteOffset =
73 Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteOffset")
74 .get;
75 const getViewBuffer =
76 Object.getOwnPropertyDescriptor(viewPrototype, "buffer").get;
77 const getViewByteLength =
78 Object.getOwnPropertyDescriptor(viewPrototype, "byteLength").get;
79 const getViewByteOffset =
80 Object.getOwnPropertyDescriptor(viewPrototype, "byteOffset").get;
81 const {
82 getUint8: viewGetUint8,
83 setUint8: viewSetUint8,
84 setUint16: viewSetUint16,
85 } = viewPrototype;
86
87 /**
88 * Returns an ArrayBuffer for encoding generated from the provided
89 * arguments.
90 */
91 const bufferFromArgs = ($, $s) => {
92 try {
93 // Try just getting the array buffer associated with the first
94 // argument and returning it if possible.
95 return toArrayBuffer($);
96 } catch {
97 // There is no array buffer associated with the first argument.
98 //
99 // Construct a string and convert it to an array buffer instead.
100 return ((string) =>
101 call(
102 getViewBuffer,
103 reduce(
104 string,
105 (result, ucsCharacter, index) => (
106 call(viewSetUint16, result, [
107 index * 2,
108 getCodeUnit(ucsCharacter, 0),
109 ]), result
110 ),
111 new View(new Buffer(string.length * 2)),
112 ),
113 [],
114 ))(
115 typeof $ === "string"
116 ? $
117 : hasOwnProperty($, "raw")
118 ? rawString(
119 $,
120 ...objectCreate(argumentIterablePrototype, {
121 args: { value: $s },
122 }),
123 )
124 : `${$}`,
125 );
126 }
127 };
128
129 /**
130 * Returns the result of decoding the provided base16 string into an
131 * ArrayBuffer.
132 *
133 * ※ This function is not exposed.
134 */
135 const decodeBase16 = (source) => {
136 const u4s = map(
137 source,
138 (ucsCharacter) => {
139 const code = getCodeUnit(ucsCharacter, 0);
140 const result = code >= 0x30 && code <= 0x39
141 ? code - 48
142 : code >= 0x41 && code <= 0x46
143 ? code - 55
144 : code >= 0x61 && code <= 0x66
145 ? code - 87
146 : -1;
147 if (result < 0) {
148 // The source contains an invalid character.
149 throw new RangeError(
150 `Piscēs: Invalid character in Base64: ${ucsCharacter}.`,
151 );
152 } else {
153 // The source contains a valid character with a recognized
154 // mapping.
155 return result;
156 }
157 },
158 );
159 const { length } = u4s;
160 if (length % 2 === 1) {
161 // The length is such that an entire letter would be dropped during
162 // a forgiving decode.
163 throw new RangeError(
164 `Piscēs: Base16 string has invalid length: ${source}.`,
165 );
166 } else {
167 // Every letter contributes at least some bits to the result.
168 const dataView = new View(new Buffer(floor(length / 2)));
169 for (let index = 0; index < length - 1;) {
170 // Iterate over the characters and assign their bits to the
171 // buffer.
172 call(viewSetUint8, dataView, [
173 floor(index / 2),
174 (u4s[index] << 4) | u4s[++index, index++],
175 ]);
176 }
177 return call(getViewBuffer, dataView, []);
178 }
179 };
180
181 /**
182 * Returns the result of decoding the provided base32 string into an
183 * ArrayBuffer.
184 *
185 * If the second argument is truthy, uses Crockford’s encoding rather
186 * than the RFC’s (see <https://www.crockford.com/base32.html>). This
187 * is more human‐friendly and tolerant. Check digits are not supported.
188 *
189 * ※ This function is not exposed.
190 */
191 const decodeBase32 = (source, wrmg) => {
192 const u5s = map(
193 wrmg
194 ? stringReplace(source, /-/gu, "")
195 : source.length % 8 === 0
196 ? stringReplace(source, /(?:=|={3,4}|={6})$/u, "")
197 : source,
198 (ucsCharacter) => {
199 const code = getCodeUnit(ucsCharacter, 0);
200 const result = wrmg
201 ? code >= 0x30 && code <= 0x39
202 ? code - 48
203 : code >= 0x41 && code <= 0x48
204 ? code - 55
205 : code === 0x49
206 ? 1 // I
207 : code >= 0x4A && code <= 0x4B
208 ? code - 56
209 : code === 0x4C
210 ? 1 // L
211 : code >= 0x4D && code <= 0x4E
212 ? code - 57
213 : code === 0x4F
214 ? 0 // O
215 : code >= 0x50 && code <= 0x54
216 ? code - 58
217 // U is skipped
218 : code >= 0x56 && code <= 0x5A
219 ? code - 59
220 : code >= 0x61 && code <= 0x68
221 ? code - 87
222 : code === 0x69
223 ? 1 // i
224 : code >= 0x6A && code <= 0x6B
225 ? code - 88
226 : code === 0x6C
227 ? 1 // l
228 : code >= 0x6D && code <= 0x6E
229 ? code - 89
230 : code === 0x6F
231 ? 0 // o
232 : code >= 0x70 && code <= 0x74
233 ? code - 90
234 // u is skipped
235 : code >= 0x76 && code <= 0x7A
236 ? code - 91
237 : -1
238 : code >= 0x41 && code <= 0x5A
239 ? code - 65
240 : code >= 0x61 && code <= 0x7A
241 ? code - 97 // same result as above; case insensitive
242 : code >= 0x32 && code <= 0x37
243 ? code - 24 // digits 2–7 map to 26–31
244 : -1;
245 if (result < 0) {
246 // The source contains an invalid character.
247 throw new RangeError(
248 `Piscēs: Invalid character in Base32: ${ucsCharacter}.`,
249 );
250 } else {
251 // The source contains a valid character with a recognized
252 // mapping.
253 return result;
254 }
255 },
256 );
257 const { length } = u5s;
258 const lengthMod8 = length % 8;
259 if (lengthMod8 === 1 || lengthMod8 === 3 || lengthMod8 === 6) {
260 // The length is such that an entire letter would be dropped during
261 // a forgiving decode.
262 throw new RangeError(
263 `Piscēs: Base32 string has invalid length: ${source}.`,
264 );
265 } else {
266 // Every letter contributes at least some bits to the result.
267 const dataView = new View(new Buffer(floor(length * 5 / 8)));
268 for (let index = 0; index < length - 1;) {
269 // Iterate over the characters and assign their bits to the
270 // buffer.
271 //
272 // The final index is not handled; if the string is not divisible
273 // by 8, some bits might be dropped. This matches the “forgiving
274 // decode” behaviour specified by WhatW·G for base64.
275 const dataIndex = ceil(index * 5 / 8);
276 const remainder = index % 8;
277 call(viewSetUint8, dataView, [
278 dataIndex,
279 remainder === 0
280 ? u5s[index] << 3 | u5s[++index] >> 2
281 : remainder === 1
282 ? u5s[index] << 6 | u5s[++index] << 1 | u5s[++index] >> 4
283 : remainder === 3
284 ? u5s[index] << 4 | u5s[++index] >> 1
285 : remainder === 4
286 ? u5s[index] << 7 | u5s[++index] << 2 | u5s[++index] >> 3
287 : u5s[index] << 5 | u5s[++index, index++], // remainder === 6
288 ]);
289 }
290 return call(getViewBuffer, dataView, []);
291 }
292 };
293
294 /**
295 * Returns the result of decoding the provided base64 string into an
296 * ArrayBuffer.
297 *
298 * ※ This function is not exposed.
299 */
300 const decodeBase64 = (source, safe = false) => {
301 const u6s = map(
302 source.length % 4 === 0
303 ? stringReplace(source, /={1,2}$/u, "")
304 : source,
305 (ucsCharacter) => {
306 const code = getCodeUnit(ucsCharacter, 0);
307 const result = code >= 0x41 && code <= 0x5A
308 ? code - 65
309 : code >= 0x61 && code <= 0x7A
310 ? code - 71
311 : code >= 0x30 && code <= 0x39
312 ? code + 4
313 : code === (safe ? 0x2D : 0x2B)
314 ? 62
315 : code === (safe ? 0x5F : 0x2F)
316 ? 63
317 : -1;
318 if (result < 0) {
319 // The source contains an invalid character.
320 throw new RangeError(
321 `Piscēs: Invalid character in Base64: ${ucsCharacter}.`,
322 );
323 } else {
324 // The source contains a valid character with a recognized
325 // mapping.
326 return result;
327 }
328 },
329 );
330 const { length } = u6s;
331 if (length % 4 === 1) {
332 // The length is such that an entire letter would be dropped during
333 // a forgiving decode.
334 throw new RangeError(
335 `Piscēs: Base64 string has invalid length: ${source}.`,
336 );
337 } else {
338 // Every letter contributes at least some bits to the result.
339 const dataView = new View(new Buffer(floor(length * 3 / 4)));
340 for (let index = 0; index < length - 1;) {
341 // Iterate over the characters and assign their bits to the
342 // buffer.
343 //
344 // The final index is not handled; if the string is not divisible
345 // by 4, some bits might be dropped. This matches the “forgiving
346 // decode” behaviour specified by WhatW·G for base64.
347 const dataIndex = ceil(index * 3 / 4);
348 const remainder = index % 4;
349 call(viewSetUint8, dataView, [
350 dataIndex,
351 remainder === 0
352 ? u6s[index] << 2 | u6s[++index] >> 4
353 : remainder === 1
354 ? u6s[index] << 4 | u6s[++index] >> 2
355 : u6s[index] << 6 | u6s[++index, index++], // remainder === 2
356 ]);
357 }
358 return call(getViewBuffer, dataView, []);
359 }
360 };
361
362 /**
363 * Returns the result of encoding the provided ArrayBuffer into a
364 * base16 string.
365 *
366 * ※ This function is not exposed.
367 */
368 const encodeBase16 = (buffer) => {
369 const dataView = new View(buffer);
370 const byteLength = call(getBufferByteLength, buffer, []);
371 const minimumLengthOfResults = byteLength * 2;
372 const resultingCodeUnits = fill(
373 objectCreate(
374 binaryCodeUnitIterablePrototype,
375 { length: { value: minimumLengthOfResults } },
376 ),
377 0x3D,
378 );
379 for (let index = 0; index < byteLength;) {
380 // Iterate over the bytes and generate code units for them.
381 const codeUnitIndex = index * 2;
382 const datum = call(viewGetUint8, dataView, [index++]);
383 const u4s = [datum >> 4, datum & 0xF];
384 for (let u4i = 0; u4i < 2; ++u4i) {
385 // Handle the high four bits, then the low four bits.
386 const u4 = u4s[u4i];
387 const result = u4 < 10 ? u4 + 48 : u4 < 16 ? u4 + 55 : -1;
388 if (result < 0) {
389 // No mapping exists for these four bits.
390 //
391 // ※ This shouldn’t be possible!
392 throw new RangeError(
393 `Piscēs: Unexpected Base16 value: ${u4}.`,
394 );
395 } else {
396 // A mapping exists for the bits.
397 resultingCodeUnits[codeUnitIndex + u4i] = result;
398 }
399 }
400 }
401 return stringFromCodeUnits(...resultingCodeUnits);
402 };
403
404 /**
405 * Returns the result of encoding the provided ArrayBuffer into a
406 * base32 string.
407 *
408 * ※ This function is not exposed.
409 */
410 const encodeBase32 = (buffer, wrmg = false) => {
411 const dataView = new View(buffer);
412 const byteLength = call(getBufferByteLength, buffer, []);
413 const minimumLengthOfResults = ceil(byteLength * 8 / 5);
414 const fillByte = wrmg ? 0x2D : 0x3D;
415 const resultingCodeUnits = fill(
416 objectCreate(
417 binaryCodeUnitIterablePrototype,
418 {
419 length: {
420 value: minimumLengthOfResults +
421 (8 - (minimumLengthOfResults % 8)) % 8,
422 },
423 },
424 ),
425 fillByte,
426 );
427 for (let index = 0; index < byteLength;) {
428 // Iterate over the bytes and generate code units for them.
429 const codeUnitIndex = ceil(index * 8 / 5);
430 const currentIndex = codeUnitIndex + +(
431 0b01011 & 1 << index % 5 &&
432 resultingCodeUnits[codeUnitIndex] != fillByte
433 ); // bytes 0, 1 & 3 handle two letters; this is for the second
434 const remainder = currentIndex % 8;
435 const currentByte = call(viewGetUint8, dataView, [index]);
436 const nextByte =
437 0b01011010 & 1 << remainder && ++index < byteLength
438 // digits 1, 3, 4 & 6 span multiple bytes
439 ? call(viewGetUint8, dataView, [index])
440 : 0;
441 const u5 = remainder === 0
442 ? currentByte >> 3
443 : remainder === 1
444 ? (currentByte & 0b00000111) << 2 | nextByte >> 6
445 : remainder === 2
446 ? (currentByte & 0b00111111) >> 1
447 : remainder === 3
448 ? (currentByte & 0b00000001) << 4 | nextByte >> 4
449 : remainder === 4
450 ? (currentByte & 0b00001111) << 1 | nextByte >> 7
451 : remainder === 5
452 ? (currentByte & 0b01111111) >> 2
453 : remainder === 6
454 ? (currentByte & 0b00000011) << 3 | nextByte >> 5
455 : (++index, currentByte & 0b00011111); // remainder === 7
456 const result = wrmg
457 ? u5 < 10 ? u5 + 48 : u5 < 18
458 ? u5 + 55
459 // skip I
460 : u5 < 20
461 ? u5 + 56
462 // skip L
463 : u5 < 22
464 ? u5 + 57
465 // skip O
466 : u5 < 27
467 ? u5 + 58
468 // skip U
469 : u5 < 32
470 ? u5 + 59
471 : -1
472 : u5 < 26
473 ? u5 + 65
474 : u5 < 32
475 ? u5 + 24
476 : -1;
477 if (result < 0) {
478 // No mapping exists for these five bits.
479 //
480 // ※ This shouldn’t be possible!
481 throw new RangeError(`Piscēs: Unexpected Base32 value: ${u5}.`);
482 } else {
483 // A mapping exists for the bits.
484 resultingCodeUnits[currentIndex] = result;
485 }
486 }
487 const answer = stringFromCodeUnits(...resultingCodeUnits);
488 return wrmg ? answer.replace(/-+$/u, "") : answer;
489 };
490
491 /**
492 * Returns the result of encoding the provided ArrayBuffer into a
493 * base64 string.
494 *
495 * ※ This function is not exposed.
496 */
497 const encodeBase64 = (buffer, safe = false) => {
498 const dataView = new View(buffer);
499 const byteLength = call(getBufferByteLength, buffer, []);
500 const minimumLengthOfResults = ceil(byteLength * 4 / 3);
501 const resultingCodeUnits = fill(
502 objectCreate(
503 binaryCodeUnitIterablePrototype,
504 {
505 length: {
506 value: minimumLengthOfResults +
507 (4 - (minimumLengthOfResults % 4)) % 4,
508 },
509 },
510 ),
511 0x3D,
512 );
513 for (let index = 0; index < byteLength;) {
514 // Iterate over the bytes and generate code units for them.
515 const codeUnitIndex = ceil(index * 4 / 3);
516 const currentIndex = codeUnitIndex + +(
517 index % 3 === 0 && resultingCodeUnits[codeUnitIndex] != 0x3D
518 ); // every third byte handles two letters; this is for the second
519 const remainder = currentIndex % 4;
520 const currentByte = call(viewGetUint8, dataView, [index]);
521 const nextByte = remainder % 3 && ++index < byteLength
522 // digits 1 & 2 span multiple bytes
523 ? call(viewGetUint8, dataView, [index])
524 : 0;
525 const u6 = remainder === 0
526 ? currentByte >> 2
527 : remainder === 1
528 ? (currentByte & 0b00000011) << 4 | nextByte >> 4
529 : remainder === 2
530 ? (currentByte & 0b00001111) << 2 | nextByte >> 6
531 : (++index, currentByte & 0b00111111); // remainder === 3
532 const result = u6 < 26
533 ? u6 + 65
534 : u6 < 52
535 ? u6 + 71
536 : u6 < 62
537 ? u6 - 4
538 : u6 < 63
539 ? (safe ? 0x2D : 0x2B)
540 : u6 < 64
541 ? (safe ? 0x5F : 0x2F)
542 : -1;
543 if (result < 0) {
544 // No mapping exists for these six bits.
545 //
546 // ※ This shouldn’t be possible!
547 throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
548 } else {
549 // A mapping exists for the bits.
550 resultingCodeUnits[currentIndex] = result;
551 }
552 }
553 return stringFromCodeUnits(...resultingCodeUnits);
554 };
555
556 /**
557 * Returns a source string generated from the arguments passed to a
558 * tag function.
559 *
560 * ※ This function is not exposed.
561 */
562 const sourceFromArgs = ($, $s) =>
563 stringReplace(
564 typeof $ === "string" ? $ : hasOwnProperty($, "raw")
565 ? rawString(
566 $,
567 ...objectCreate(argumentIterablePrototype, {
568 args: { value: $s },
569 }),
570 )
571 : `${$}`,
572 /[\t\n\f\r ]+/gu,
573 "",
574 );
575
576 /**
577 * Returns a slice of the provided value according to the algorithm of
578 * `ArrayBuffer::slice` (or `SharedArrayBuffer::slice`).
579 *
580 * ☡ This function throws if the provided value is not an array buffer.
581 */
582 export const arrayBufferSlice = ($, start, end, ...args) =>
583 call(
584 isSharedArrayBuffer($) ? sharedBufferSlice : bufferSlice,
585 $,
586 [
587 start,
588 end,
589 ...objectCreate(
590 argumentIterablePrototype,
591 { args: { value: args } },
592 ),
593 ],
594 );
595
596 /**
597 * Returns an ArrayBuffer generated from the provided base16 string.
598 *
599 * This function can also be used as a tag for a template literal. The
600 * literal will be interpreted akin to `String.raw`.
601 *
602 * ☡ This function throws if the provided string is not a valid base16
603 * string.
604 */
605 export const base16Binary = ($, ...$s) =>
606 decodeBase16(sourceFromArgs($, $s));
607
608 /**
609 * Returns a (big‐endian) base16 string created from the provided typed
610 * array, buffer, or (16‐bit) string.
611 *
612 * This function can also be used as a tag for a template literal. The
613 * literal will be interpreted akin to `String.raw`.
614 */
615 export const base16String = ($, ...$s) =>
616 encodeBase16(bufferFromArgs($, $s));
617
618 /**
619 * Returns an ArrayBuffer generated from the provided base32 string.
620 *
621 * This function can also be used as a tag for a template literal. The
622 * literal will be interpreted akin to `String.raw`.
623 *
624 * ☡ This function throws if the provided string is not a valid base32
625 * string.
626 */
627 export const base32Binary = ($, ...$s) =>
628 decodeBase32(sourceFromArgs($, $s));
629
630 /**
631 * Returns a (big‐endian) base32 string created from the provided typed
632 * array, buffer, or (16‐bit) string.
633 *
634 * This function can also be used as a tag for a template literal. The
635 * literal will be interpreted akin to `String.raw`.
636 */
637 export const base32String = ($, ...$s) =>
638 encodeBase32(bufferFromArgs($, $s));
639
640 /**
641 * Returns an ArrayBuffer generated from the provided base64 string.
642 *
643 * This function can also be used as a tag for a template literal. The
644 * literal will be interpreted akin to `String.raw`.
645 *
646 * ☡ This function throws if the provided string is not a valid base64
647 * string.
648 */
649 export const base64Binary = ($, ...$s) =>
650 decodeBase64(sourceFromArgs($, $s));
651
652 /**
653 * Returns a (big‐endian) base64 string created from the provided typed
654 * array, buffer, or (16‐bit) string.
655 *
656 * This function can also be used as a tag for a template literal. The
657 * literal will be interpreted akin to `String.raw`.
658 */
659 export const base64String = ($, ...$s) =>
660 encodeBase64(bufferFromArgs($, $s));
661
662 /**
663 * Returns an ArrayBuffer generated from the provided filename‐safe
664 * base64 string.
665 *
666 * This function can also be used as a tag for a template literal. The
667 * literal will be interpreted akin to `String.raw`.
668 *
669 * ☡ This function throws if the provided string is not a valid
670 * filename‐safe base64 string.
671 */
672 export const filenameSafeBase64Binary = ($, ...$s) =>
673 decodeBase64(sourceFromArgs($, $s), true);
674
675 /**
676 * Returns a (big‐endian) filename‐safe base64 string created from the
677 * provided typed array, buffer, or (16‐bit) string.
678 *
679 * This function can also be used as a tag for a template literal. The
680 * literal will be interpreted akin to `String.raw`.
681 */
682 export const filenameSafeBase64String = ($, ...$s) =>
683 encodeBase64(bufferFromArgs($, $s), true);
684
685 /**
686 * Returns the byte length for the provided array buffer or array
687 * buffer view.
688 *
689 * ☡ This function throws if the provided value is not an array buffer,
690 * data view, or typed array.
691 */
692 export const getByteLength = ($) => {
693 try {
694 // Attempt to get the byte length from the provided value as an
695 // `ArrayBuffer`.
696 return call(getBufferByteLength, $, []);
697 } catch {
698 // The provided value is not an `ArrayBuffer`.
699 /* do nothing */
700 }
701 try {
702 // Attempt to get the byte length from the provided value as a
703 // `SharedArrayBuffer`.
704 return call(getSharedBufferByteLength, $, []);
705 } catch {
706 // The provided value is not a `SharedArrayBuffer`.
707 /* do nothing */
708 }
709 try {
710 // Attempt to get the byte length from the provided value as a
711 // data view.
712 return call(getViewByteLength, $, []);
713 } catch {
714 // The provided value is not a data view.
715 /* do nothing */
716 }
717 try {
718 // Attempt to get the byte length from the provided value as a
719 // typed array.
720 return call(getTypedArrayByteLength, $, []);
721 } catch {
722 // The provided value is not a typed array.
723 /* do nothing */
724 }
725 throw new TypeError(`Piscēs: Not an array buffer or view: ${$}.`);
726 };
727
728 /**
729 * Returns the byte offset for the provided array buffer or array
730 * buffer view.
731 *
732 * ※ This function always returns `0` for array buffers.
733 *
734 * ☡ This function throws if the provided value is not an array buffer,
735 * data view, or typed array.
736 */
737 export const getByteOffset = ($) => {
738 if (isArrayBuffer($)) {
739 // The provided value is an array buffer.
740 return 0;
741 } else {
742 try {
743 // Attempt to get the byte offset from the provided value as a
744 // data view.
745 return call(getViewByteOffset, $, []);
746 } catch {
747 // The provided value is not a data view.
748 /* do nothing */
749 }
750 try {
751 // Attempt to get the byte offset from the provided value as a
752 // typed array.
753 return call(getTypedArrayByteOffset, $, []);
754 } catch {
755 // The provided value is not a typed array.
756 /* do nothing */
757 }
758 throw new TypeError(`Piscēs: Not an array buffer or view: ${$}.`);
759 }
760 };
761
762 /**
763 * Returns whether the provided value is a view on an underlying array
764 * buffer.
765 *
766 * ※ This function returns true for typed arrays and data views.
767 */
768 export const { isView: isArrayBufferView } = Buffer;
769
770 /**
771 * Returns whether the provided value is an array buffer.
772 *
773 * ※ This function returns true for both `ArrayBuffer`s and
774 * `SharedArrayBuffer`s.
775 */
776 export const isArrayBuffer = ($) => {
777 try {
778 // Try to see if the provided argument has array buffer internal
779 // slots and return true if so.
780 return call(getBufferByteLength, $, []), true;
781 } catch {
782 // The provided argument does not have array buffer internal slots.
783 /* do nothing */
784 }
785 try {
786 // Try to see if the provided argument has array buffer internal
787 // slots and return true if so.
788 return call(getSharedBufferByteLength, $, []), true;
789 } catch {
790 // The provided argument does not have array buffer internal slots.
791 /* do nothing */
792 }
793 return false;
794 };
795
796 /**
797 * Returns whether the provided value is a base16 string.
798 *
799 * ※ This function returns false if the provided value is not a string
800 * primitive.
801 */
802 export const isBase16 = ($) => {
803 if (typeof $ !== "string") {
804 // The provided value is not a string.
805 return false;
806 } else {
807 // The provided value is a string.
808 const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
809 return source.length % 2 !== 1 &&
810 call(reExec, /[^0-9A-F]/iu, [source]) === null;
811 }
812 };
813
814 /**
815 * Returns whether the provided value is a base32 string.
816 *
817 * ※ This function returns false if the provided value is not a string
818 * primitive.
819 */
820 export const isBase32 = ($) => {
821 if (typeof $ !== "string") {
822 // The provided value is not a string.
823 return false;
824 } else {
825 // The provided value is a string.
826 const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
827 const trimmed = source.length % 8 === 0
828 ? stringReplace(source, /(?:=|={3,4}|={6})$/u, "")
829 : source;
830 return trimmed.length % 8 !== 1 &&
831 call(reExec, /[^2-7A-Z/]/iu, [trimmed]) === null;
832 }
833 };
834
835 /**
836 * Returns whether the provided value is a Base64 string.
837 *
838 * ※ This function returns false if the provided value is not a string
839 * primitive.
840 */
841 export const isBase64 = ($) => {
842 if (typeof $ !== "string") {
843 // The provided value is not a string.
844 return false;
845 } else {
846 // The provided value is a string.
847 const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
848 const trimmed = source.length % 4 === 0
849 ? stringReplace(source, /={1,2}$/u, "")
850 : source;
851 return trimmed.length % 4 !== 1 &&
852 call(reExec, /[^0-9A-Za-z+\/]/u, [trimmed]) === null;
853 }
854 };
855
856 /**
857 * Returns whether the provided value is a filename‐safe base64 string.
858 *
859 * ※ This function returns false if the provided value is not a string
860 * primitive.
861 */
862 export const isFilenameSafeBase64 = ($) => {
863 if (typeof $ !== "string") {
864 // The provided value is not a string.
865 return false;
866 } else {
867 // The provided value is a string.
868 const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
869 const trimmed = source.length % 4 === 0
870 ? stringReplace(source, /={1,2}$/u, "")
871 : source;
872 return trimmed.length % 4 !== 1 &&
873 call(reExec, /[^0-9A-Za-z_-]/u, [trimmed]) === null;
874 }
875 };
876
877 /** Returns whether the provided value is a shared array buffer. */
878 export const isSharedArrayBuffer = ($) => {
879 try {
880 // Try to see if the provided argument has shared array buffer
881 // internal slots and return true if so.
882 return call(getSharedBufferByteLength, $, []), true;
883 } catch {
884 // The provided argument does not have data view internal slots.
885 return false;
886 }
887 };
888
889 /** Returns whether the provided value is a typed array. */
890 export const isTypedArray = ($) => {
891 try {
892 // Try to see if the provided argument has typed array internal
893 // slots and return true if so.
894 return call(getTypedArrayBuffer, $, []), true;
895 } catch {
896 // The provided argument does not have typed array internal slots.
897 return false;
898 }
899 };
900
901 /**
902 * Returns whether the provided value is a W·R·M·G (Crockford) base32
903 * string. Check digits are not supported.
904 *
905 * ※ This function returns false if the provided value is not a string
906 * primitive.
907 */
908 export const isWRMGBase32 = ($) => {
909 if (typeof $ !== "string") {
910 // The provided value is not a string.
911 return false;
912 } else {
913 // The provided value is a string.
914 const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
915 const trimmed = stringReplace(source, /-/gu, "");
916 return trimmed.length % 8 !== 1 &&
917 call(reExec, /[^0-9A-TV-Z]/iu, [trimmed]) === null;
918 }
919 };
920
921 /**
922 * Returns the array buffer associated with the provided object.
923 *
924 * ☡ This function throws if the provided object is not a data view or
925 * typed array.
926 */
927 export const toArrayBuffer = ($) => {
928 if (isArrayBuffer($)) {
929 // The provided argument has array buffer internal slots.
930 return $;
931 } else {
932 // The provided argument does not have array buffer internal slots.
933 try {
934 // The provided argument has typed array internal slots.
935 return call(getTypedArrayBuffer, $, []);
936 } catch {
937 /* do nothing */
938 }
939 try {
940 // The provided argument has data view internal slots.
941 return call(getViewBuffer, $, []);
942 } catch {
943 /* do nothing */
944 }
945 throw new TypeError(`Piscēs: Not an array buffer or view: ${$}.`);
946 }
947 };
948
949 /**
950 * Returns an ArrayBuffer generated from the provided W·R·M·G
951 * (Crockford) base32 string.
952 *
953 * This function can also be used as a tag for a template literal. The
954 * literal will be interpreted akin to `String.raw`.
955 *
956 * ☡ This function throws if the provided string is not a valid W·R·M·G
957 * base32 string.
958 */
959 export const wrmgBase32Binary = ($, ...$s) =>
960 decodeBase32(sourceFromArgs($, $s), true);
961
962 /**
963 * Returns a (big‐endian) W·R·M·G (Crockford) base32 string created
964 * from the provided typed array, buffer, or (16‐bit) string.
965 *
966 * This function can also be used as a tag for a template literal. The
967 * literal will be interpreted akin to `String.raw`.
968 */
969 export const wrmgBase32String = ($, ...$s) =>
970 encodeBase32(bufferFromArgs($, $s), true);
This page took 0.138567 seconds and 3 git commands to generate.