DEV Community

Luke Miles
Luke Miles

Posted on

20-line DSL in typescript or js without a library

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 
Enter fullscreen mode Exit fullscreen mode

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

Top comments (1)

Collapse
 
rxliuli profile image
rxliuli

I've been learning sicp recently and have therefore built a simple lisp interpreter and runtime, the implementation is about 300 lines long. The parsing part is ugly because I haven't optimized it using a strategy pattern similar to the one above.
Finally I was able to run the code, all code refer to the gists link below

expect(evalLisp(parseLisp('("liuli")'))).toBe('liuli') expect(evalLisp(parseLisp('(+ 1 2)'))).toBe(3) expect(evalLisp(parseLisp('(+ 1.1 3.3)'))).toBe(4.4) expect(evalLisp(parseLisp('(true)'))).toBe(true) expect(evalLisp(parseLisp('(begin (define x 1) (define y 2) (+ x y))'))).toBe(3) expect(evalLisp(parseLisp('(if (true) 0 1)'))).toBe(0) expect(evalLisp(parseLisp('(if (false) 0 1)'))).toBe(1) expect(evalLisp(parseLisp('(cond (false 0) (false 1))'))).toBe(null) expect(evalLisp(parseLisp('(cond (false 0) (false 1) (else 2))'))).toBe(2) expect(evalLisp(parseLisp('(cond (false 0) (true 1) (else 2))'))).toBe(1) expect(evalLisp(parseLisp('((lambda (x y) (+ x y)) 1 2)'))).toBe(3) expect(evalLisp(parseLisp('(begin (define (add x y) (+ x y)) (add 1 2))'))).toBe(3) expect( evalLisp( parseLisp(` ( begin (define (cons a b) ( lambda (action) ( cond ((= action "car") a) ((= action "cdr") b) ) )) (define (car cons) (cons "car")) (define (cdr cons) (cons "cdr")) (define v (cons 1 2)) (+ (car v) (cdr v)) ) `), ), ).toBe(3) 
Enter fullscreen mode Exit fullscreen mode

gist.github.com/rxliuli/9ee90a7ce7...