From ece27eb1774fbe53920b65a880262cac986550b8 Mon Sep 17 00:00:00 2001 From: Caleb Sander Date: Sat, 29 Sep 2018 12:48:07 -0700 Subject: [PATCH] Utilities for PseudoBigInt --- src/compiler/types.ts | 6 ++ src/compiler/utilities.ts | 84 +++++++++++++++++++ src/testRunner/tsconfig.json | 1 + src/testRunner/unittests/parsePseudoBigInt.ts | 71 ++++++++++++++++ .../reference/api/tsserverlibrary.d.ts | 5 ++ tests/baselines/reference/api/typescript.d.ts | 5 ++ 6 files changed, 172 insertions(+) create mode 100644 src/testRunner/unittests/parsePseudoBigInt.ts diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a39d098443c..4a25a9f9eef 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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; + } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5d9a77c2a98..34d80992153 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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; + } } diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index af25aba408d..32772ed311b 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -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", diff --git a/src/testRunner/unittests/parsePseudoBigInt.ts b/src/testRunner/unittests/parsePseudoBigInt.ts new file mode 100644 index 00000000000..0ffbee6345e --- /dev/null +++ b/src/testRunner/unittests/parsePseudoBigInt.ts @@ -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" + ); + }); + }); + }); +} \ No newline at end of file diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index b5ef0cac750..9f447a12c35 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -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; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 8f047351750..d2ec3ab1d12 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -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;