Percentage Parser
Проблема
Реализовать тип PercentageParser<T extends string>
. Этот тип должен разбить
строку на три элемента, согласно /^(\+|\-)?(\d*)?(\%)?$/
.
Структура этих элементов выглядит следующим образом: [+ или -, число, %]
. В
случае, если совпадения нет, нужно вернуть пустую строку. Например:
type PString1 = "";
type PString2 = "+85%";
type PString3 = "-85%";
type PString4 = "85%";
type PString5 = "85";
type R1 = PercentageParser<PString1>; // expected ['', '', '']
type R2 = PercentageParser<PString2>; // expected ["+", "85", "%"]
type R3 = PercentageParser<PString3>; // expected ["-", "85", "%"]
type R4 = PercentageParser<PString4>; // expected ["", "85", "%"]
type R5 = PercentageParser<PString5>; // expected ["", "85", ""]
Решение
Синтаксический разбор строк это очень интересная задача (как для меня). Жаль только, что мы не сможем добиться хорошего решения в этом случае. Так как всё что у нас есть это только система типов TypeScript.
Нам нужно разбить строку на три компонента: знак числа, число, знак процента. Чтобы упростить решение, давайте реализуем их как отдельные типы. Первый тип будет возвращать знак числа. Второй будет возвращать само число, а третий - знак процента.
Начнём с первого типа. Нам нужно проверить, что первый символ в строке это знак плюса или минуса. Чтобы этого достичь, нам нужно сначала вывести этот первый символ.
type ParseSign<T extends string> = T extends `${infer S}${any}` ? never : never;
Имея первый символ в тип параметре S
, мы можем проверить плюс это или минус.
Если это плюс или минус, то возвращаем тип параметр S
, возвращаем знак, что мы
вывели. Во всех остальных случаях возвращаем пустую строку, согласно постановке
задачи.
type ParseSign<T extends string> = T extends `${infer S}${any}`
? S extends "+" | "-"
? S
: ""
: "";
Таким образом, мы реализовали тип, который может распознать и вернуть знак числа. Теперь, сделаем то же самое со знаком процента.
Для начала, проверим, а есть ли символ процента в конце строки.
type ParsePercent<T extends string> = T extends `${any}%` ? never : never;
В случае, если символ процента присутствует в конце строки - возвращаем символ процента. Во всех остальных случаях возвращаем пустую строку.
type ParsePercent<T extends string> = T extends `${any}%` ? "%" : "";
Имея два типа, два анализатора, которые возвращают знаки, мы можем начать думать о числе. Нам нужно вывести число, которое стоит между этими знаками. Проблема заключается в том, что эти знаки опциональные.
Чтобы реализовать поддержку опциональных знаков, нам нужно проверять их наличие. То есть нам нужно проверять, если присутствует знак числа, то пропустить его и взять число. И так далее. А проблема в том, что если мы этого не сделаем, то в наше число попадут ненужные нам знаки.
Но у нас же эта логика уже реализована в наших других типах. Всё что нам нужно это правильно их совместить.
type ParseNumber<T extends string> =
T extends `${ParseSign<T>}${infer N}${ParsePercent<T>}` ? never : never;
Видите что происходит? Сначала, мы анализируем случай со знаком числа, используя ранее написанный тип. Если знак числа присутствует, тип его возвращает и делает частью тип литерала. А значит, он не попадет в часть, которая выводит число.
То же самое мы проделываем и со знаком процента. Если процент присутствует, тип его нам возвращает и делает частью строчного тип литерала. Это не дает ему попасть в часть с выведением числа.
В результате мы остаемся только с самим числом, которое мы и получаем через выведение типов. Нам остается его только вернуть из условного типа.
type ParseNumber<T extends string> =
T extends `${ParseSign<T>}${infer N}${ParsePercent<T>}` ? N : "";
Вы, наверняка, уже догадываетесь, как мы можем это использовать для решения задачи. Как сказано в условии, нам нужно вернуть кортеж с тремя элементами. А у нас как раз есть три типа!
type PercentageParser<A extends string> = [
ParseSign<A>,
ParseNumber<A>,
ParsePercent<A>,
];
Мои поздравления! Мы получили простейший “синтаксический анализатор” с использованием системы типов.
Комментарии