Utilities for PseudoBigInt

This commit is contained in:
Caleb Sander
2018-09-29 12:48:07 -07:00
parent 0c1bf316a2
commit ece27eb177
6 changed files with 172 additions and 0 deletions

View File

@@ -5761,4 +5761,10 @@ namespace ts {
readonly importModuleSpecifierEnding?: "minimal" | "index" | "js";
readonly allowTextChangesInNewFiles?: boolean;
}
/** Represents a bigint literal value without requiring bigint support */
export interface PseudoBigInt {
negative: boolean;
base10Value: string;
}
}

View File

@@ -8427,4 +8427,88 @@ namespace ts {
return got;
}
}
// TODO: remove once tslint allows tsconfig.json to include lib "esnext.bigint"
/* @internal */
declare const BigInt: ((value: string) => number) | undefined;
/**
* Converts a bigint literal string, e.g. `0x1234n`,
* to its decimal string representation, e.g. `4660`.
*/
export function parsePseudoBigInt(stringValue: string): string {
// Use native BigInt if available
if (typeof BigInt !== "undefined") {
return "" + BigInt(stringValue.slice(0, -1)); // omit trailing "n"
}
let log2Base: number;
switch (stringValue.charCodeAt(1)) { // "x" in "0x123"
case CharacterCodes.b:
case CharacterCodes.B: // 0b or 0B
log2Base = 1;
break;
case CharacterCodes.o:
case CharacterCodes.O: // 0o or 0O
log2Base = 3;
break;
case CharacterCodes.x:
case CharacterCodes.X: // 0x or 0X
log2Base = 4;
break;
default: // already in decimal; omit trailing "n"
const nIndex = stringValue.length - 1;
// Skip leading 0s
let nonZeroStart = 0;
while (stringValue.charCodeAt(nonZeroStart) === CharacterCodes._0) {
nonZeroStart++;
}
return stringValue.slice(nonZeroStart, nIndex) || "0";
}
// Omit leading "0b", "0o", or "0x", and trailing "n"
const startIndex = 2, endIndex = stringValue.length - 1;
const bitsNeeded = (endIndex - startIndex) * log2Base;
// Stores the value specified by the string as a LE array of 16-bit integers
// using Uint16 instead of Uint32 so combining steps can use bitwise operators
const segments = new Uint16Array((bitsNeeded >>> 4) + (bitsNeeded & 15 ? 1 : 0));
// Add the digits, one at a time
for (let i = endIndex - 1, bitOffset = 0; i >= startIndex; i--, bitOffset += log2Base) {
const segment = bitOffset >>> 4;
const digitChar = stringValue.charCodeAt(i);
// Find character range: 0-9 < A-F < a-f
const digit = digitChar <= CharacterCodes._9
? digitChar - CharacterCodes._0
: 10 + digitChar -
(digitChar <= CharacterCodes.F ? CharacterCodes.A : CharacterCodes.a);
const shiftedDigit = digit << (bitOffset & 15);
segments[segment] |= shiftedDigit;
const residual = shiftedDigit >>> 16;
if (residual) segments[segment + 1] |= residual; // overflows segment
}
// Repeatedly divide segments by 10 and add remainder to base10Value
let base10Value = "";
let firstNonzeroSegment = segments.length - 1;
let segmentsRemaining = true;
while (segmentsRemaining) {
let mod10 = 0;
segmentsRemaining = false;
for (let segment = firstNonzeroSegment; segment >= 0; segment--) {
const newSegment = mod10 << 16 | segments[segment];
const segmentValue = (newSegment / 10) | 0;
segments[segment] = segmentValue;
mod10 = newSegment - segmentValue * 10;
if (segmentValue && !segmentsRemaining) {
firstNonzeroSegment = segment;
segmentsRemaining = true;
}
}
base10Value = mod10 + base10Value;
}
return base10Value;
}
export function pseudoBigIntToString({negative, base10Value}: PseudoBigInt): string {
return (negative && base10Value !== "0" ? "-" : "") + base10Value;
}
}

View File

@@ -65,6 +65,7 @@
"unittests/matchFiles.ts",
"unittests/moduleResolution.ts",
"unittests/organizeImports.ts",
"unittests/parsePseudoBigInt.ts",
"unittests/paths.ts",
"unittests/printer.ts",
"unittests/programMissingFiles.ts",

View File

@@ -0,0 +1,71 @@
namespace ts {
describe("BigInt literal base conversions", () => {
describe("parsePseudoBigInt", () => {
const testNumbers: number[] = [];
for (let i = 0; i < 1e3; i++) testNumbers.push(i);
for (let bits = 0; bits <= 52; bits++) {
testNumbers.push(2 ** bits, 2 ** bits - 1);
}
it("can strip base-10 strings", () => {
for (const testNumber of testNumbers) {
for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) {
assert.equal(
parsePseudoBigInt("0".repeat(leadingZeros) + testNumber + "n"),
String(testNumber)
);
}
}
});
it("can parse binary literals", () => {
for (const testNumber of testNumbers) {
for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) {
const binary = "0".repeat(leadingZeros) + testNumber.toString(2) + "n";
for (const prefix of ["0b", "0B"]) {
assert.equal(parsePseudoBigInt(prefix + binary), String(testNumber));
}
}
}
});
it("can parse octal literals", () => {
for (const testNumber of testNumbers) {
for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) {
const octal = "0".repeat(leadingZeros) + testNumber.toString(8) + "n";
for (const prefix of ["0o", "0O"]) {
assert.equal(parsePseudoBigInt(prefix + octal), String(testNumber));
}
}
}
});
it("can parse hex literals", () => {
for (const testNumber of testNumbers) {
for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) {
const hex = "0".repeat(leadingZeros) + testNumber.toString(16) + "n";
for (const prefix of ["0x", "0X"]) {
for (const hexCase of [hex.toLowerCase(), hex.toUpperCase()]) {
assert.equal(parsePseudoBigInt(prefix + hexCase), String(testNumber));
}
}
}
}
});
it("can parse large literals", () => {
assert.equal(
parsePseudoBigInt("123456789012345678901234567890n"),
"123456789012345678901234567890"
);
assert.equal(
parsePseudoBigInt("0b1100011101110100100001111111101101100001101110011111000001110111001001110001111110000101011010010n"),
"123456789012345678901234567890"
);
assert.equal(
parsePseudoBigInt("0o143564417755415637016711617605322n"),
"123456789012345678901234567890"
);
assert.equal(
parsePseudoBigInt("0x18ee90ff6c373e0ee4e3f0ad2n"),
"123456789012345678901234567890"
);
});
});
});
}

View File

@@ -3013,6 +3013,11 @@ declare namespace ts {
readonly importModuleSpecifierEnding?: "minimal" | "index" | "js";
readonly allowTextChangesInNewFiles?: boolean;
}
/** Represents a bigint literal value without requiring bigint support */
interface PseudoBigInt {
negative: boolean;
base10Value: string;
}
}
declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any;
declare function clearTimeout(handle: any): void;

View File

@@ -3013,6 +3013,11 @@ declare namespace ts {
readonly importModuleSpecifierEnding?: "minimal" | "index" | "js";
readonly allowTextChangesInNewFiles?: boolean;
}
/** Represents a bigint literal value without requiring bigint support */
interface PseudoBigInt {
negative: boolean;
base10Value: string;
}
}
declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any;
declare function clearTimeout(handle: any): void;