preserves/implementations/javascript/packages/core/src/bytes.ts

260 lines
8.5 KiB
TypeScript

import { Tag } from './constants';
import { AsPreserve, PreserveOn } from './symbols';
import { Encoder, Preservable } from './encoder';
import { Value } from './values';
import { GenericEmbedded } from './embedded';
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
export const IsPreservesBytes = Symbol.for('IsPreservesBytes');
export type BytesLike = Bytes | Uint8Array;
export class Bytes implements Preservable<never> {
readonly _view: Uint8Array;
constructor(maybeByteIterable: any = new Uint8Array()) {
if (Bytes.isBytes(maybeByteIterable)) {
this._view = maybeByteIterable._view;
} else if (ArrayBuffer.isView(maybeByteIterable)) {
this._view = new Uint8Array(maybeByteIterable.buffer,
maybeByteIterable.byteOffset,
maybeByteIterable.byteLength);
} else if (maybeByteIterable instanceof ArrayBuffer) {
this._view = new Uint8Array(maybeByteIterable);
} else if (typeof maybeByteIterable === 'string') {
this._view = textEncoder.encode(maybeByteIterable);
} else if (typeof maybeByteIterable === 'number') {
this._view = new Uint8Array(maybeByteIterable);
} else if (typeof maybeByteIterable.length === 'number') {
this._view = Uint8Array.from(maybeByteIterable);
} else {
throw new TypeError("Attempt to initialize Bytes from unsupported value: " +
maybeByteIterable);
}
}
get length(): number {
return this._view.length;
}
static from(x: any): Bytes {
return new Bytes(x);
}
static of(...bytes: number[]): Bytes {
return new Bytes(Uint8Array.of(...bytes));
}
static fromHex(s: string): Bytes {
if (s.length & 1) throw new Error("Cannot decode odd-length hexadecimal string");
const len = s.length >> 1;
const result = new Bytes(len);
for (let i = 0; i < len; i++) {
result._view[i] =
(unhexDigit(s.charCodeAt(i << 1)) << 4) | unhexDigit(s.charCodeAt((i << 1) + 1));
}
return result;
}
static fromIO(io: string | BytesLike): string | Bytes {
if (typeof io === 'string') return io;
if (Bytes.isBytes(io)) return io;
return new Bytes(io);
}
static toIO(b : string | BytesLike): string | Uint8Array {
if (typeof b === 'string') return b;
if (Bytes.isBytes(b)) return b._view;
return b;
}
static concat = function (bss: BytesLike[]): Bytes {
let len = 0;
for (let i = 0; i < bss.length; i++) { len += underlying(bss[i]).length; }
const result = new Bytes(len);
let index = 0;
for (let i = 0; i < bss.length; i++) {
const bs = underlying(bss[i]);
result._view.set(bs, index);
index += bs.length;
}
return result;
}
get(index: number): number {
return this._view[index];
}
equals(other: any): boolean {
if (!Bytes.isBytes(other)) return false;
if (other.length !== this.length) return false;
const va = this._view;
const vb = other._view;
for (let i = 0; i < va.length; i++) {
if (va[i] !== vb[i]) return false;
}
return true;
}
hashCode(): number {
// Immutable.js uses this function for strings.
const v = this._view;
let hash = 0;
for (let i = 0; i < v.length; i++) {
hash = ((31 * hash) + v[i]) | 0;
}
return hash;
}
static compare(a: Bytes, b: Bytes): number {
if (a < b) return -1;
if (b < a) return 1;
return 0;
}
static decodeUtf8(bs: Bytes | Uint8Array): string {
return textDecoder.decode(underlying(bs));
}
fromUtf8(): string {
return textDecoder.decode(this._view);
}
toString(): string {
return this.asPreservesText();
}
[AsPreserve]<T = GenericEmbedded>(): Value<T> {
return this;
}
asPreservesText(): string {
return '#"' + this.__asciify() + '"';
}
__asciify(): string {
const pieces = [];
const v = this._view;
for (let i = 0; i < v.length; i++) {
const b = v[i];
if (b === 92 || b === 34) {
pieces.push('\\' + String.fromCharCode(b));
} else if (b >= 32 && b <= 126) {
pieces.push(String.fromCharCode(b));
} else {
pieces.push('\\x' + hexDigit(b >> 4) + hexDigit(b & 15));
}
}
return pieces.join('');
}
toHex(): string {
var nibbles = [];
for (let i = 0; i < this.length; i++) {
nibbles.push(hexDigit(this._view[i] >> 4));
nibbles.push(hexDigit(this._view[i] & 15));
}
return nibbles.join('');
}
[PreserveOn](encoder: Encoder<never>) {
encoder.state.emitbyte(Tag.ByteString);
encoder.state.varint(this.length);
encoder.state.emitbytes(this._view);
}
get [IsPreservesBytes](): boolean {
return true;
}
static isBytes(x: any): x is Bytes {
return !!x?.[IsPreservesBytes];
}
}
export function hexDigit(n: number): string {
return '0123456789abcdef'[n];
}
export function unhexDigit(asciiCode: number) {
if (asciiCode >= 48 && asciiCode <= 57) return asciiCode - 48;
if (asciiCode >= 97 && asciiCode <= 102) return asciiCode - 97 + 10;
if (asciiCode >= 65 && asciiCode <= 70) return asciiCode - 65 + 10;
throw new Error("Invalid hex digit: " + String.fromCharCode(asciiCode));
}
export function underlying(b: Bytes | Uint8Array): Uint8Array {
return (b instanceof Uint8Array) ? b : b._view;
}
// Uint8Array / TypedArray methods
export interface Bytes {
entries(): IterableIterator<[number, number]>;
every(predicate: (value: number, index: number, array: Uint8Array) => unknown,
thisArg?: any): boolean;
find(predicate: (value: number, index: number, obj: Uint8Array) => boolean,
thisArg?: any): number;
findIndex(predicate: (value: number, index: number, obj: Uint8Array) => boolean,
thisArg?: any): number;
forEach(callbackfn: (value: number, index: number, array: Uint8Array) => void,
thisArg?: any): void;
includes(searchElement: number, fromIndex?: number): boolean;
indexOf(searchElement: number, fromIndex?: number): number;
join(separator?: string): string;
keys(): IterableIterator<number>;
lastIndexOf(searchElement: number, fromIndex?: number): number;
reduce(callbackfn: (previousValue: number,
currentValue: number,
currentIndex: number,
array: Uint8Array) => number,
initialValue?: number): number;
reduceRight(callbackfn: (previousValue: number,
currentValue: number,
currentIndex: number,
array: Uint8Array) => number,
initialValue?: number): number;
some(predicate: (value: number, index: number, array: Uint8Array) => unknown,
thisArg?: any): boolean;
toLocaleString(): string;
values(): IterableIterator<number>;
filter(predicate: (value: number, index: number, array: Uint8Array) => any,
thisArg?: any): Bytes;
map(callbackfn: (value: number, index: number, array: Uint8Array) => number,
thisArg?: any): Bytes;
slice(start?: number, end?: number): Bytes;
subarray(begin?: number, end?: number): Bytes;
reverse(): Bytes;
sort(compareFn?: (a: number, b: number) => number): Bytes;
[Symbol.iterator](): IterableIterator<number>;
}
(function () {
for (const k of `entries every find findIndex forEach includes indexOf join
keys lastIndexOf reduce reduceRight some toLocaleString values`.split(/\s+/))
{
(Bytes as any).prototype[k] =
function (...args: any[]) { return this._view[k](...args); };
}
for (const k of `filter map slice subarray`.split(/\s+/))
{
(Bytes as any).prototype[k] =
function (...args: any[]) { return new Bytes(this._view[k](...args)); };
}
for (const k of `reverse sort`.split(/\s+/))
{
(Bytes as any).prototype[k] =
function (...args: any[]) { return new Bytes(this._view.slice()[k](...args)); };
}
Bytes.prototype[Symbol.iterator] = function () { return this._view[Symbol.iterator](); };
})();