Created
July 7, 2025 17:40
-
-
Save ryangoree/720fb32e57322914cfe74622974b9344 to your computer and use it in GitHub Desktop.
π§ WIP π§ - Parse a number-like value in TypeScript types.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//--- Tuple operations ---// | |
type BuildTuple< | |
L extends number, | |
T extends any[] = [], | |
F = unknown, | |
> = `${L}` extends `-${number}` | |
? never | |
: T["length"] extends L | |
? T | |
: BuildTuple<L, [...T, F], F>; | |
type IsNegative<T extends number> = `${T}` extends `-${number}` ? true : false; | |
type Abs<T extends number> = `${T}` extends `-${infer N extends number}` | |
? N | |
: T; | |
type FlipSign<T extends number> = `${T}` extends `${0}` | |
? 0 | |
: `${T}` extends `-${infer N extends number}` | |
? N | |
: `-${T}` extends `${infer N extends number}` | |
? N | |
: never; | |
type CutStarting<T extends any[], N extends number> = T extends [ | |
...BuildTuple<N>, | |
...infer Rest, | |
] | |
? Rest | |
: []; | |
type CutEnding<T extends any[], N extends number> = T extends [ | |
...infer Rest, | |
...BuildTuple<N>, | |
] | |
? Rest | |
: []; | |
type GetEnding<T extends any[], N extends number> = T extends [ | |
...CutEnding<T, N>, | |
...infer Rest, | |
] | |
? Rest | |
: []; | |
type GetStarting<T extends any[], N extends number> = T extends [ | |
...infer Rest, | |
...CutStarting<T, N>, | |
] | |
? Rest | |
: []; | |
type SliceStart<Tuple extends any[], StartIndex extends number> = | |
IsNegative<StartIndex> extends true | |
? GetEnding<Tuple, Abs<StartIndex>> | |
: CutStarting<Tuple, StartIndex>; | |
type SliceEnd<Tuple extends any[], EndIndex extends number> = | |
IsNegative<EndIndex> extends true | |
? CutEnding<Tuple, Abs<EndIndex>> | |
: GetStarting<Tuple, EndIndex>; | |
type Slice< | |
Tuple extends any[], | |
StartIndex extends number, | |
EndIndex extends number = Tuple["length"], | |
> = | |
SliceStart<Tuple, StartIndex> extends infer S extends any[] | |
? IsNegative<EndIndex> extends true | |
? SliceEnd<S, Abs<EndIndex>> | |
: Subtract<Tuple["length"], EndIndex> extends 0 | |
? S | |
: SliceEnd<S, ParseNumber<`-${Subtract<Tuple["length"], EndIndex>}`>> | |
: []; | |
type Nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | |
// [].slice(4, 7) | |
type Sliced = Slice<Nums, 4, 7>; // => [4, 5, 6] | |
// [].slice(-2) | |
type Sliced2 = Slice<Nums, -3, 10>; // => [9, 10] | |
// [].slice(-2, -1) | |
type Sliced3 = Slice<Nums, -2, -1>; // => [9] | |
//--- String operations ---// | |
type StringToTuple< | |
T extends string, | |
Result extends any[] = [], | |
> = T extends `${infer F}${infer R}` | |
? StringToTuple<R, [...Result, F]> | |
: Result; | |
type TupleToString<T extends any[], Result extends string = ""> = T extends [ | |
infer F extends string, | |
...infer R, | |
] | |
? TupleToString<R, `${Result}${F}`> | |
: Result; | |
type StringLength< | |
T extends string, | |
Acc extends any[] = [], | |
> = T extends `${string}${infer R}` | |
? StringLength<R, [...Acc, unknown]> | |
: Acc["length"]; | |
type SliceString< | |
T extends string, | |
Start extends number, | |
End extends number = StringLength<T>, | |
> = TupleToString<Slice<StringToTuple<T>, Start, End>>; | |
type RepeatString< | |
T extends string, | |
C extends number, | |
Result extends string = "", | |
Acc extends any[] = [], | |
> = Acc["length"] extends C | |
? Result | |
: RepeatString<T, C, `${Result}${T}`, [...Acc, unknown]>; | |
//--- Number operations ---// | |
type NumberPrimitive = number | bigint; | |
type SerializablePrimitive = | |
| NumberPrimitive | |
| string | |
| boolean | |
| null | |
| undefined; | |
type ParseBigInt<T extends SerializablePrimitive> = | |
`${T}` extends `${infer N extends bigint}` ? N : never; | |
type ParseNumber<T extends SerializablePrimitive> = | |
`${T}` extends `${infer N extends number}` ? N : never; | |
type Add<A extends number, B extends number> = [ | |
...BuildTuple<A>, | |
...BuildTuple<B>, | |
]["length"] & | |
number; | |
// ]["length"]; | |
type Subtract<A extends number, B extends number> = | |
BuildTuple<A> extends [...BuildTuple<B>, ...infer Rest] | |
? Rest["length"] | |
: ParseNumber<`-${Subtract<B, A>}`>; | |
type SubTest = Subtract<5, 2>; // => | |
// M = Mantissa | |
// E = Exponent | |
// I = Integer part | |
// F = Fractional part | |
// D = Fractional decimals | |
type ParseNumberish<T extends SerializablePrimitive> = T extends NumberPrimitive | |
? T | |
: // Not a number primitive | |
number extends ParseNumber<T> | |
? // Complex number expression | |
T extends `${infer M}e${infer E extends number}` | |
? // Scientific notation | |
M extends `${infer I}.${infer F}` | |
? // Decimal number | |
StringLength<F> extends infer D extends number | |
? // Successfully calculated decimal length | |
IsNegative<E> extends true | |
? // Negative exponent | |
Add<D, Abs<E>> extends infer S extends number | |
? // Added decimal length to exponent to get new decimal index from right | |
ParseNumber<`${SliceString<`${I}${F}`, 0, ParseNumber<`-${S}`>>}.${SliceString<F, E>}`> | |
: // Failed to ddd decimal length to exponent | |
never | |
: Subtract<E, D> extends infer S extends number | |
? IsNegative<S> extends true | |
? // Decimal with exponent, e.g. 1.123e2 | |
ParseNumber<`${SliceString<`${I}${F}`, 0, Add<D, E>>}.${SliceString<F, E>}`> | |
: // // Debug | |
// { | |
// slice: SliceString<`${I}${F}`, 0, Add<D, E>>; | |
// result: ParseNumber<`${SliceString<`${I}${F}`, 0, Add<D, E>>}.${SliceString<F, E>}`>; | |
// interger: I; | |
// fraction: F; | |
// decimals: D; | |
// exponent: E; | |
// } | |
// Integer expressed as a decimal number and exponent, e.g. 1.5e18 | |
ParseBigInt<`${I}${F}${RepeatString<"0", S>}`> | |
: // Failed to subtract decimal length from exponent | |
never | |
: // Failed to calculate string length | |
never | |
: // Integer with exponent | |
ParseBigInt<`${M}${RepeatString<"0", E>}`> | |
: // NaN or unsupported number expression | |
number | |
: // Simple number or integer expression | |
T extends `${string}.${string}` | |
? // Parsable number | |
ParseNumber<T> | |
: // Parsable Integer | |
ParseBigInt<T>; | |
type s = ParseNumberish<"123">; | |
// ^? | |
type b1 = ParseNumberish<123n>; | |
// ^? | |
type n1 = ParseNumberish<123>; | |
// ^? | |
type d = ParseNumberish<1.23>; | |
// ^? | |
type ds = ParseNumberish<"1.23">; | |
// ^? | |
type eds = ParseNumberish<"12e3">; | |
// ^? | |
type eds2 = ParseNumberish<"1.12e4">; | |
// ^? | |
type eds3 = ParseNumberish<"-1.12e18">; | |
// ^? | |
//FIXME: should be 112.3 | |
type eds4 = ParseNumberish<"1.123e2">; | |
// ^? | |
//FIXME: should be 1.231 | |
type eds5 = ParseNumberish<"123.1e-2">; | |
// ^? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment