Lee Thanh
Upload 3012 files
5641073
raw
history blame
10.3 kB
/**
* @author jdiaz5513
*/
// LINT: a lot of the util functions need the any type.
/* tslint:disable:no-any no-unsafe-any */
import initTrace from "debug";
import { MAX_BUFFER_DUMP_BYTES, MAX_INT32, MAX_UINT32 } from "./constants";
import { RANGE_INT32_OVERFLOW, RANGE_INVALID_UTF8, RANGE_UINT32_OVERFLOW } from "./errors";
const trace = initTrace("capnp:util");
trace("load");
/**
* Dump a hex string from the given buffer.
*
* @export
* @param {ArrayBuffer} buffer The buffer to convert.
* @returns {string} A hexadecimal string representing the buffer.
*/
export function bufferToHex(buffer: ArrayBuffer): string {
const a = new Uint8Array(buffer);
const h = [];
for (let i = 0; i < a.byteLength; i++) h.push(pad(a[i].toString(16), 2));
return `[${h.join(" ")}]`;
}
/**
* Throw an error if the provided value cannot be represented as a 32-bit integer.
*
* @export
* @param {number} value The number to check.
* @returns {number} The same number if it is valid.
*/
export function checkInt32(value: number): number {
if (value > MAX_INT32 || value < -MAX_INT32) {
throw new RangeError(RANGE_INT32_OVERFLOW);
}
return value;
}
export function checkUint32(value: number): number {
if (value < 0 || value > MAX_UINT32) {
throw new RangeError(RANGE_UINT32_OVERFLOW);
}
return value;
}
/**
* Decode a UTF-8 encoded byte array into a JavaScript string (UCS-2).
*
* @export
* @param {Uint8Array} src A utf-8 encoded byte array.
* @returns {string} A string representation of the byte array.
*/
export function decodeUtf8(src: Uint8Array): string {
// This ain't for the faint of heart, kids. If you suffer from seizures, heart palpitations, or have had a history of
// stroke you may want to look away now.
const l = src.byteLength;
let dst = "";
let i = 0;
let cp = 0;
let a = 0;
let b = 0;
let c = 0;
let d = 0;
while (i < l) {
a = src[i++];
if ((a & 0b10000000) === 0) {
cp = a;
} else if ((a & 0b11100000) === 0b11000000) {
if (i >= l) throw new RangeError(RANGE_INVALID_UTF8);
b = src[i++];
cp = ((a & 0b00011111) << 6) | (b & 0b00111111);
} else if ((a & 0b11110000) === 0b11100000) {
if (i + 1 >= l) throw new RangeError(RANGE_INVALID_UTF8);
b = src[i++];
c = src[i++];
cp = ((a & 0b00001111) << 12) | ((b & 0b00111111) << 6) | (c & 0b00111111);
} else if ((a & 0b11111000) === 0b11110000) {
if (i + 2 >= l) throw new RangeError(RANGE_INVALID_UTF8);
b = src[i++];
c = src[i++];
d = src[i++];
cp = ((a & 0b00000111) << 18) | ((b & 0b00111111) << 12) | ((c & 0b00111111) << 6) | (d & 0b00111111);
} else {
throw new RangeError(RANGE_INVALID_UTF8);
}
if (cp <= 0xd7ff || (cp >= 0xe000 && cp <= 0xffff)) {
dst += String.fromCharCode(cp);
} else {
// We must reach into the astral plane and construct the surrogate pair!
cp -= 0x00010000;
const hi = (cp >>> 10) + 0xd800;
const lo = (cp & 0x03ff) + 0xdc00;
if (hi < 0xd800 || hi > 0xdbff) throw new RangeError(RANGE_INVALID_UTF8);
dst += String.fromCharCode(hi, lo);
}
}
return dst;
}
export function dumpBuffer(buffer: ArrayBuffer | ArrayBufferView): string {
const b =
buffer instanceof ArrayBuffer
? new Uint8Array(buffer)
: new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
const byteLength = Math.min(b.byteLength, MAX_BUFFER_DUMP_BYTES);
let r = format("\n=== buffer[%d] ===", byteLength);
for (let j = 0; j < byteLength; j += 16) {
r += `\n${pad(j.toString(16), 8)}: `;
let s = "";
let k;
for (k = 0; k < 16 && j + k < b.byteLength; k++) {
const v = b[j + k];
r += `${pad(v.toString(16), 2)} `;
// Printable ASCII range.
s += v > 31 && v < 255 ? String.fromCharCode(v) : "·";
if (k === 7) r += " ";
}
r += `${repeat((17 - k) * 3, " ")}${s}`;
}
r += "\n";
if (byteLength !== b.byteLength) {
r += format("=== (truncated %d bytes) ===\n", b.byteLength - byteLength);
}
return r;
}
/**
* Encode a JavaScript string (UCS-2) to a UTF-8 encoded string inside a Uint8Array.
*
* Note that the underlying buffer for the array will likely be larger than the actual contents; ignore the extra bytes.
*
* @export
* @param {string} src The input string.
* @returns {Uint8Array} A UTF-8 encoded buffer with the string's contents.
*/
export function encodeUtf8(src: string): Uint8Array {
const l = src.length;
const dst = new Uint8Array(new ArrayBuffer(l * 4));
let j = 0;
for (let i = 0; i < l; i++) {
const c = src.charCodeAt(i);
if (c <= 0x7f) {
dst[j++] = c;
} else if (c <= 0x07ff) {
dst[j++] = 0b11000000 | (c >>> 6);
dst[j++] = 0b10000000 | ((c >>> 0) & 0b00111111);
} else if (c <= 0xd7ff || c >= 0xe000) {
dst[j++] = 0b11100000 | (c >>> 12);
dst[j++] = 0b10000000 | ((c >>> 6) & 0b00111111);
dst[j++] = 0b10000000 | ((c >>> 0) & 0b00111111);
} else {
// Make sure the surrogate pair is complete.
/* istanbul ignore next */
if (i + 1 >= l) throw new RangeError(RANGE_INVALID_UTF8);
// I cast thee back into the astral plane.
const hi = c - 0xd800;
const lo = src.charCodeAt(++i) - 0xdc00;
const cp = ((hi << 10) | lo) + 0x00010000;
dst[j++] = 0b11110000 | (cp >>> 18);
dst[j++] = 0b10000000 | ((cp >>> 12) & 0b00111111);
dst[j++] = 0b10000000 | ((cp >>> 6) & 0b00111111);
dst[j++] = 0b10000000 | ((cp >>> 0) & 0b00111111);
}
}
return dst.subarray(0, j);
}
/**
* Produce a `printf`-style string. Nice for providing arguments to `assert` without paying the cost for string
* concatenation up front. Precision is supported for floating point numbers.
*
* @param {string} s The format string. Supported format specifiers: b, c, d, f, j, o, s, x, and X.
* @param {...any} args Values to be formatted in the string. Arguments beyond what are consumed by the format string
* are ignored.
* @returns {string} The formatted string.
*/
export function format(s: string, ...args: unknown[]): string {
const n = s.length;
let arg: unknown;
let argIndex = 0;
let c: string;
let escaped = false;
let i = 0;
let leadingZero = false;
let precision: number | null;
let result = "";
function nextArg() {
return args[argIndex++];
}
function slurpNumber() {
let digits = "";
while (/\d/.test(s[i])) {
digits += s[i++];
c = s[i];
}
return digits.length > 0 ? parseInt(digits, 10) : null;
}
for (; i < n; ++i) {
c = s[i];
if (escaped) {
escaped = false;
if (c === ".") {
leadingZero = false;
c = s[++i];
} else if (c === "0" && s[i + 1] === ".") {
leadingZero = true;
i += 2;
c = s[i];
} else {
leadingZero = true;
}
precision = slurpNumber();
switch (c) {
case "a": // number in hex with padding
result += "0x" + pad(parseInt(String(nextArg()), 10).toString(16), 8);
break;
case "b": // number in binary
result += parseInt(String(nextArg()), 10).toString(2);
break;
case "c": // character
arg = nextArg();
if (typeof arg === "string" || arg instanceof String) {
result += arg;
} else {
result += String.fromCharCode(parseInt(String(arg), 10));
}
break;
case "d": // number in decimal
result += parseInt(String(nextArg()), 10);
break;
case "f": {
// floating point number
const tmp = parseFloat(String(nextArg())).toFixed(precision || 6);
result += leadingZero ? tmp : tmp.replace(/^0/, "");
break;
}
case "j": // JSON
result += JSON.stringify(nextArg());
break;
case "o": // number in octal
result += "0" + parseInt(String(nextArg()), 10).toString(8);
break;
case "s": // string
result += nextArg();
break;
case "x": // lowercase hexadecimal
result += "0x" + parseInt(String(nextArg()), 10).toString(16);
break;
case "X": // uppercase hexadecimal
result += "0x" + parseInt(String(nextArg()), 10).toString(16).toUpperCase();
break;
default:
result += c;
break;
}
} else if (c === "%") {
escaped = true;
} else {
result += c;
}
}
return result;
}
/**
* Return the thing that was passed in. Yaaaaawn.
*
* @export
* @template T
* @param {T} x A thing.
* @returns {T} The same thing.
*/
export function identity<T>(x: T): T {
return x;
}
export function pad(v: string, width: number, pad = "0"): string {
return v.length >= width ? v : new Array(width - v.length + 1).join(pad) + v;
}
/**
* Add padding to a number to make it divisible by 8. Typically used to pad byte sizes so they align to a word boundary.
*
* @export
* @param {number} size The number to pad.
* @returns {number} The padded number.
*/
export function padToWord(size: number): number {
return (size + 7) & ~7;
}
/**
* Repeat a string n times. Shamelessly copied from lodash.repeat.
*
* @param {number} times Number of times to repeat.
* @param {string} str The string to repeat.
* @returns {string} The repeated string.
*/
export function repeat(times: number, str: string): string {
let out = "";
let n = times;
let s = str;
if (n < 1 || n > Number.MAX_VALUE) return out;
// https://en.wikipedia.org/wiki/Exponentiation_by_squaring
do {
if (n % 2) out += s;
n = Math.floor(n / 2);
if (n) s += s;
} while (n);
return out;
}
const hex = (v: unknown) => parseInt(String(v)).toString(16);
// Set up custom debug formatters.
/* istanbul ignore next */
initTrace.formatters["h"] = hex;
/* istanbul ignore next */
initTrace.formatters["x"] = (v: unknown) => `0x${hex(v)}`;
/* istanbul ignore next */
initTrace.formatters["a"] = (v: unknown) => `0x${pad(hex(v), 8)}`;
/* istanbul ignore next */
initTrace.formatters["X"] = (v: unknown) => `0x${hex(v).toUpperCase()}`;