TypeScriptの型を使って四則演算の式を評価する

3 min read読了の目安(約2800字

はじめに

TypeScriptのtype-challengesを進めていく中で、型の世界でテンプレートリテラルを使えることを学んだので遊んでみる。

準備

数値演算

毎回使用してる数値演算の型

type NumberToTuple<T extends number, U extends unknown[] = []> = U['length'] extends T ? U : NumberToTuple<T, [...U, unknown]>
type Add<T extends number, U extends number> = [...NumberToTuple<T>, ...NumberToTuple<U>]['length']
type Sub<T extends number, U extends number> = NumberToTuple<T> extends [...NumberToTuple<U>, ...infer S] ? S['length'] : never
type Mul<T extends number, U extends number> = T extends 0 ? 0 : U extends 0 ? 0 : Add<T, Mul<T, Sub<U, 1> extends never ? 0 : Sub<U, 1>>>
type Div<T extends number, U extends number> = T extends 0 ? 0 : U extends 0 ? never : Sub<T, U> extends never ? 0 : Add<1, Div<Sub<T, U> extends never ? 0 : Sub<T, U>, U>>
type Mod<T extends number, U extends number> = Sub<T, U> extends never ? T : Mod<Sub<T, U>, U>

前後の空白除去

まずここでTemplate Literal Typesを使用。S extends `${infer Head}${infer Tail}` とすることでstring型に対してリストのパターンマッチのように先頭の文字と残りの文字がそれぞれ取得することが可能。

type TrimStrings = ' ' | '\n' | '\t'
type TrimLeft<T extends string> = T extends `${TrimStrings}${infer U}` ? TrimLeft<U> : T
type TrimRight<T extends string> = T extends `${infer U}${TrimStrings}` ? TrimRight<U> : T
type Trim<S extends string> = TrimRight<TrimLeft<S>>

文字列を数値に変換

これはいい方法が思い浮かばずパワープレイ。

type StringToInt<T extends string> = 
  T extends '0' ? 0 
  : T extends '1' ? 1 
  : T extends '2' ? 2 
  : T extends '3' ? 3 
  : T extends '4' ? 4 
  : T extends '5' ? 5
  : T extends '6' ? 6 
  : T extends '7' ? 7 
  : T extends '8' ? 8 
  : T extends '9' ? 9 
  : never

四則演算の式を評価(数値1桁の2項演算のみ)

演算子の前後に数値があるのでTemplate Literal Typesを使って文字列のパターンマッチを行って、数値の前後にある空白を除去して数値の演算を行う。

type AddExpression<T extends string> = T extends `${infer M}+${infer N}` ? Add<StringToInt<Trim<M>>, StringToInt<Trim<N>>> : never
type SubExpression<T extends string> = T extends `${infer M}-${infer N}` ? Sub<StringToInt<Trim<M>>, StringToInt<Trim<N>>> : never
type MulExpression<T extends string> = T extends `${infer M}*${infer N}` ? Mul<StringToInt<Trim<M>>, StringToInt<Trim<N>>> : never
type DivExpression<T extends string> = T extends `${infer M}/${infer N}` ? Div<StringToInt<Trim<M>>, StringToInt<Trim<N>>> : never
type ModExpression<T extends string> = T extends `${infer M}%${infer N}` ? Mod<StringToInt<Trim<M>>, StringToInt<Trim<N>>> : never
type Expression<T extends string> = AddExpression<T> | SubExpression<T> | MulExpression<T> | DivExpression<T> | ModExpression<T>

type Ans = Expression<'5 * 3'>

さいごに

再帰制限のエラーが出てしまって、ひとまず2項演算しかできていないのがとても悔しい。
そもそも構文解析、評価の知識が足りてないので別の機会にきちんと勉強しよう。Scalaはそういうの得意ぽいのでちょうどいいかもしれない。