Skip to content

Instantly share code, notes, and snippets.

@jspears
Last active August 5, 2022 22:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jspears/81e46e66d3c3a84a417389f180fd26d0 to your computer and use it in GitHub Desktop.
Save jspears/81e46e66d3c3a84a417389f180fd26d0 to your computer and use it in GitHub Desktop.
Simple Regex Engine in Typescript Types

I've been wanting to implement a real regex engine in TypeScript types, but alas its a lot of work. So Instead I implemented a simple one from Nick Drane

// https://nickdrane.com/build-your-own-regex/
// https://github.com/nadrane/build-your-own-regex

type Empty = '' | null | undefined;
type Eq<L, R, T=true, F=false> = L extends R ? R extends L ? T : F : F;
type StartsWith<P extends string, T extends string> = T extends `${infer F}${infer Rest}` ? Eq<F,P,Rest> : never;
type First<T> = T extends `${infer F}${string}` ? F : never;
type Falsy = never | false | undefined | null;
type And<L,R> = L extends Falsy ? false : R extends Falsy ? false : true ;
type Extends<T,E> = T extends E ? true : false;

type S1 = StartsWith< '^', '^12'>;
type Slice2<T extends string> = Slice<T,2>extends [infer Left, infer Right extends string] ? Right : '';
type Slice1<T> = T extends `${infer F}${infer R}` ? R : never;
type StrToTuple<S extends string> = S extends '' ? [] : S extends `${infer F}${infer Rest}` ? [F, ...StrToTuple<Rest>] : never;

type _Slice<T extends readonly any[], N extends number, Ret extends readonly any[] = []> = Ret['length'] extends N ? [ Ret, T] : T extends [ infer F, ...infer Rest] ? _Slice<Rest, N, [...Ret, F]> : [Ret, T]

type TupleToStr<T extends readonly string[], Ret extends string = ''> = T extends [infer F extends string, ...infer Rest extends readonly string[]] ?  Rest extends readonly string[] ? Rest['length'] extends 0 ? `${Ret}${F}` : TupleToStr<Rest, `${Ret}${F}`> : [] : [];

type Slice<T extends string, N extends number> = _Slice<StrToTuple<T>, N> extends [infer Left extends readonly string[], infer Right extends readonly string[]]  ? [TupleToStr<Left>, TupleToStr<Right>] : never ;



type OpString = string | null | undefined;


type MatchOne<P, T> = P extends Empty ? true : T extends Empty ? false : P extends '.' ? true : Eq<P,T>;

type MatchQuestion<P extends string,T extends string> = And<MatchOne<First<P>, First<T>>, Match<Slice2<P>, Slice1<T>>> extends true ? true : Match<Slice2<P>,T>;

type MatchStar<P extends string,T extends string> = And<MatchOne<First<P>, First<T>>, Match<P, Slice1<T>>> extends true ? true : never;


 type MatchGroup<P extends string, T extends string> =
 P extends `${infer GroupPattern})${infer GroupEnd}` ?
  GroupEnd extends `?${infer RemainderPattern }` ? 
    Slice<T, GroupPattern['length']> extends [infer Left extends string, infer Right extends string] ? 
        And<Match<GroupPattern, Left>, Match<RemainderPattern, Right>> extends true ? true : Match<RemainderPattern, T> : never :
        
  GroupEnd extends `*${infer RemainderPattern}` ? 
   Slice<T, GroupPattern['length']> extends [infer Left extends string, infer Right extends string] ? 
        And<Match<GroupPattern, Left>, Match<P,Right>> extends true ? true : Match<RemainderPattern, T>  :
        never :
           Slice<T, GroupPattern['length']> extends [infer Left extends string, infer Right extends string] ? 

            And<Match<GroupPattern, Left>, Match<GroupEnd, Right>> : never : never
        ; 



type Match<P extends string, T extends string> = 
    P extends Empty ? true :
    And<Extends<T, Empty>, Extends<P,'$'>> extends true ? true :
    P extends `${infer F}?${string}` ? MatchQuestion<P, T> :
    P extends `${infer F}*${string}` ? MatchStar<P, T> :
    P extends `(${string}` ? MatchGroup<P,T> :
     Slice<P, 1> extends [infer Pat0 extends string, infer Pat1 extends string] ?
     Slice<T, 1> extends [infer Text0 extends string, infer Text1 extends string] ?
      And<MatchOne<Pat0,Text0>, Match<Pat1, Text1>> : false : true;
      

type Search<P extends string, T extends string> = P extends `^${infer Rest}` ? Match<Rest, T> : Match<`.*${P}`, T>;

type Expect<Result,Expected,Message> = Result extends Expected ? true : {Result:Result, Expected:Expected, Message:Message};
type Assert<S,M> = Expect<S, true, M>;

type MatchOne1 = Assert<MatchOne<'a', 'a'>, 'single letter match'>;
type MatchOne1_1 = Assert<MatchOne<'.', 'a'>, 'single letter match'>;
type MatchOne2 = Assert<MatchOne<'', ''>, 'should return true when the pattern is empty or undefined'>;
type MatchOne3 = Expect<MatchOne<'a', 'c'>, false, 'should return false when the text is an empty string or undefined but the pattern is defined'>;


type Match1 = Assert<Match<'', 'abc'>,"should return true if given an empty pattern">;
type Match2 = Assert<Match<'$', ''>,"should return true if given an empty pattern">;
type Match3 = Expect<Match<'$', 'abc'>,false,"should match an empty string to the end of line pattern">;
type Match4 = Assert<Match<'ab', 'ab'>, "exact match">;
type Match5 = Assert<Match<'abc', 'abc'>, "exact match">;
type Match6 = Expect<Match<'abc', 'ab'>,false, "exact match">;

type Search1 = Expect<Search<"i am (the) hulk", "i am the hulk">, true, 'matches all characters placed within grouping operators'>
type Search2 = Assert<Search<"b*", "aaaaa">,'should match 0 or more characters following an *'>;
type Search3 = Assert<Search<"this* i*s the str*ing", "thissss s the strrring">, 'should match 0 or more characters following an *'>;

Playground Link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment