Skip to content

Instantly share code, notes, and snippets.

@jcreedcmu
Created November 26, 2020 18:01
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 jcreedcmu/24859af7dd5bc7b102496eeccbd6a9fc to your computer and use it in GitHub Desktop.
Save jcreedcmu/24859af7dd5bc7b102496eeccbd6a9fc to your computer and use it in GitHub Desktop.
Parsing sexps in at the type level in typescript
// Parsing sexpressions and turning them into nested array types,
// using template literal types.
// Cons a head onto an array type, assuming TL is actually an array type
type Cons<H, TL> = TL extends any[] ? [H, ...TL] : unknown;
// Interpret type names as types
type Interpret<TOK extends string> =
TOK extends 'number' ? number :
TOK extends 'string' ? string :
TOK extends 'boolean' ? boolean : unknown;
// Interpret and append a type identifier TOK to an array A of tokens
type TokCons<TOK, A> = TOK extends string ? Cons<Interpret<TOK>, A> : A;
// Concatenate a character C onto a token TOK (or start a new token if TOK is unknown)
type TokConcat<TOK, C extends string> = TOK extends string ? `${TOK}${C}` : C;
// Lex<unknown, '(number string (boolean string))'> = ["(", number, string, "(", boolean, string, ")", ")"];
// The type parameter T is a string to be tokenized
// The type parameter TOK is an accumulator for the current identifier token, unknown if there is none.
type Lex<TOK, T extends string> =
T extends `(${infer X}` ? TokCons<TOK, Cons<'(', Lex<unknown, X>>> :
T extends `)${infer X}` ? TokCons<TOK, Cons<')', Lex<unknown, X>>> :
T extends ` ${infer X}` ? TokCons<TOK, Lex<unknown, X>> :
T extends `${infer C}${infer X}` ? Lex<TokConcat<TOK, C>, X> :
T extends `` ? [] : unknown;
// Parse<["(", number, string, "(", boolean, string, ")", ")"], [[]]> = [number, string, [boolean, string]]
// The type parameter TOKS is an array type of tokens to be parsed
// The type parameter STACKS accumulates sexp arrays
type Parse<TOKS, STACK> =
TOKS extends ['(', ...infer TL] ? (STACK extends [...infer SBODY] ? Parse<TL, [[], ...SBODY]> : unknown) :
TOKS extends [')', ...infer TL] ? (STACK extends [[...infer ACC], [...infer ACC2], ...infer STL] ? Parse<TL, [[...ACC2, ACC], ...STL]> : unknown) :
TOKS extends [infer H, ...infer TL] ? (STACK extends [[...infer ACC], ...infer STL] ? Parse<TL, [[...ACC, H], ...STL]> : unknown) :
TOKS extends [] ? (STACK extends [[infer FINAL]] ? FINAL : unknown) : unknown;
// Convert an sexp string into a nested array type
type FromSexp<T extends string> = Parse<Lex<unknown, T>, [[]]>;
// Example of use
type SexpType = FromSexp<'(number string (boolean string))'>; // [number, string, [boolean, string]]
const value: SexpType = [3, 'foo', [true, 'bar']];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment