20-line DSL in typescript or js without a library
Luke Harold Miles
Posted on June 1, 2022
It's insanely easy. Here's a small lisp-like example:
const s = Symbol
const keywords = [s('add'), s('sub'), s('if'), s('less')] as const
type Keyword = (typeof keywords)[number]
const [add, sub, if_, less] = keywords
const funcs = {
[add]: (a, b) => valuate(a) + valuate(b),
[sub]: (a, b) => valuate(a) - valuate(b),
[if_]: (cond, a, b) => valuate(cond) ? valuate(a) : valuate(b),
[less]: (a, b) => valuate(a) < valuate(b),
} as const
type Expression = boolean | number | symbol | [Keyword, ...Expression[]]
function valuate(expr: Expression): any {
if (Array.isArray(expr)) {
const args = expr.slice(1).map(valuate)
// @ts-expect-error
return funcs[expr[0]](...args)
}
return expr
}
const expr1: Expression = [add, [sub, [if_, [less, 1, 2], 3, 4], 5], 6]
console.log(valuate(expr1))
// (3 - 5) + 6 == 4
If you want full type checking on your DSL, it's just a bit more typing to define the recursive structure. (e.g. type Add = [add, NumberExpr, NumberExpr
) Unfortunately, you'll probably have to use strings or singleton classes for your keywords because typescript doesn't support symbol literals.
And if you want array types, infix notation, and other nice-to-haves, you're probably better off using a library like angu
💖 💪 🙅 🚩
Luke Harold Miles
Posted on June 1, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.