type Take2<T extends string> = T extends `${infer h1}${infer h2}${infer tail}` ? `${h1}${h2}` : never;
type Drop<T extends string> = T extends `${infer head}${infer tail}` ? tail : never;
type Drop2<T extends string> = T extends `${infer h1}${infer h2}${infer tail}` ? tail : never;
type ToFunction<T, R> = (t: T) = > R;
type StringToType<T extends string> = T extends '%s' ? string : T extends '%d' ? number : never;
type FormatEnd<T extends string> = 
    T extends ' '
    ? true
    : (Drop<T> extends ' ' ? true : false)

type Format<S extends string> =
    FormatEnd<S> extends true
    ? string
    : ( StringToType<Take2<S>> extends never ? Format<Drop<S>> : ToFunction<StringToType<Take2<S>>, Format<Drop2<S>>> )

function print(s: string, prev: string = ' ') :any {
    if (s.length <= 1) return prev + s;
    const [h1, h2, ...tail] = s.split(' ');
    if (h1 + h2 === '%s' || h1 + h2 === '%d') {
        return (s: string | number) = > print(tail.join(' '), prev + s);
    }
    return print([h2, ...tail].join(' '), prev + h1);
}

function safePrint<T extends string> (input: T) :Format<T> {
    return print(input);
}

safePrint('11%d222%s888%d')
Copy the code

This is the final code. The first time to write an article, if there are improper words or misunderstanding of the place to ask the leaders correct.

New version features used

  1. Template Literal Types
  2. Recursive Conditional Types

What do you think of the new TypeScript feature

In my implementation code, the type utility functions Take, Drop apply this new feature

type Take2<T extends string> = T extends `${infer h1}${infer h2}${infer tail}` ? `${h1}${h2}` : never;
type Drop<T extends string> = T extends `${infer head}${infer tail}` ? tail : never;
type Drop2<T extends string> = T extends `${infer h1}${infer h2}${infer tail}` ? tail : never;

type test1 = Take2<' '> // never
type test2 = Take2<'1'> // never
type test3 = Take2<'12'> / / '12'
type test4 = Drop<' '> // never
type test5 = Drop<'1'> / /"
type test6 = Drop2<'12'> / /"
Copy the code

The second new feature is described on the official website as follows:

In TypeScript 4.1, conditional types can now immediately reference themselves within their branches, making it easier to write recursive type aliases.

In a condition type, we can refer to ourselves on its branches. In the type implementation of the SafaPrintf function, the most important format function uses this feature

type Format<S extends string> =
    FormatEnd<S> extends true
    ? string
    : ( StringToType<Take2<S>> extends never ? Format<Drop<S>> : ToFunction<StringToType<Take2<S>>, Format<Drop2<S>>> )
Copy the code

To simplify, the recursive construction of a type is terminated by determining whether it is an empty string or a string of length 1. If the length is greater than 1, the first two characters are the target string

type StringToType<T extends string> = T extends '%s' ? string : T extends '%d' ? number : never;
Copy the code

If StringToType returns never, the format

> recursively removes the first character. If it is not never, the input string contains the target string to be replaced and needs to be converted to a function type.

ToFunction<StringToType<take2<S>>, Format<drop2<S>>>
Copy the code

The return type of the function is the recursive format type after the target character is retrieved.

(Since T is generic, we don’t know the type of Format at all when we implement the safePrint function, but we can use any to get around type checking. We only know the type of Format when we use the safePrint function.)

function safePrint<T extends string> (input: T) :format<T> {
    return print(input);
}
Copy the code

What is left is the JavaScript implementation of the Print function

function print(s: string, prev: string = ' ') :any {
    if (s.length <= 1) return prev + s;
    const [h1, h2, ...tail] = s.split(' ');
    if (h1 + h2 === '%s' || h1 + h2 === '%d') {
        return (s: string | number) = > print(tail.join(' '), prev + s);
    }
    return print([h2, ...tail].join(' '), prev + h1);
}
Copy the code

Here we keep safePrint’s type and implementation separate, and because typescript has recursion-deep checking, format accepts a limited number of characters.

The final result

Finally, the safePrintf function of IDRIS is attached

You can use idRIS to implement the printf function

module Printf

%default total

data Format = FInt Format
            | FString Format
            | FOther Char Format
            | Fend

format : List Char -> Format
format ('%'::'d': :cs) = FInt (format cs)
format ('%'::'s': :cs) = FString (format cs)
format (c:cs) = FOther c (format cs)
format [] = FEnd

interpFormat : Format -> Type
interpFormat (FInt f) = Int -> interpFormat f
interpFormat (FString f) = String -> interpFormat f
interpFormat (FOther _ f) = interpFormat f
interpFormat FEnd = String

formatString : String -> Format
formatString s = format (unpack s)

toFunction : (fmt : Format) -> String -> interpFormat fmt
toFunction (FInt f) a => \i -> toFunction f (a ++ show i)
toFunction (FString f) a => \s -> toFunction f (a ++ s)
toFunction (FOther c f) a => toFunction f (a ++ singleton c)
toFunction FEnd a = a

printf : (s : String) -> interpFormat (formatString s)
printf s = toFunction (formatString s) ""
Copy the code