Skip to content

Instantly share code, notes, and snippets.

@isao
Forked from intercaetera/lisp.js
Created March 27, 2025 01:22
Show Gist options
  • Save isao/f0c2c463ce5c3311a53b8324363a6276 to your computer and use it in GitHub Desktop.
Save isao/f0c2c463ce5c3311a53b8324363a6276 to your computer and use it in GitHub Desktop.
Lisp in JS
const symbols = [
// special forms
'lambda', 'when', 'rec', 'define',
// standard library
'inc', 'dec', 'add', 'mul', 'eq', 'head', 'tail', 'cons', 'list', 'isNil',
// variables
'a', 'b', 'c', 'k', 'l', 'm', 'n', 'x', 'y', 'z',
'as', 'xs',
'fn', 'double', 'map',
]
const createSymbol = name => {
globalThis[name] = Symbol(name)
}
symbols.forEach(createSymbol)
const defaultEnv = a => {
if (a === inc) return x => x + 1
if (a === dec) return x => x - 1
if (a === add) return (x, y) => x + y
if (a === mul) return (x, y) => x * y
if (a === eq) return (x, y) => x === y
if (a === head) return ([x]) => x
if (a === tail) return ([_x, ...xs]) => xs
if (a === cons) return (x, xs) => ([x, ...xs])
if (a === list) return (...xs) => xs
if (a === isNil) return x => Array.isArray(x) && x.length === 0
throw new Error(`unbound symbol: ${a.toString()}`)
}
const evaluate = (expr, env = defaultEnv) => {
if (['number', 'boolean', 'function', 'string'].includes(typeof expr)) return expr
if (typeof expr === 'symbol') return env(expr)
if (expr[0] === lambda) return evalLambda(expr, env)
if (expr[0] === when) return evalWhen(expr, env)
if (expr[0] === define) return evalDefine(expr, env)
return apply(expr, env)
}
const evalLambda = ([_lambda, params, body], env) => {
const fn = (...args) => {
const newEnv = b => {
const index = params.indexOf(b)
if (index !== -1) return args[index]
if (b === rec) return fn
return env(b)
}
return evaluate(body, newEnv)
}
return fn
}
const evalWhen = ([_when, cond, then, otherwise], env) => {
if (evaluate(cond, env)) {
return evaluate(then, env)
} else {
return evaluate(otherwise, env)
}
}
const evalDefine = ([_define, definitions, body], env) => {
const newEnv = definitions.reduce((env, [symbol, definition]) => {
return b => {
if (b === symbol) return evaluate(definition, env)
return env(b)
}
}, env)
return evaluate(body, newEnv)
}
const apply = ([operator, ...operands], env) => {
const fn = evaluate(operator, env)
if (typeof fn !== 'function') {
console.log(operator)
throw new Error(`not a function, ${operator.toString()}`)
}
const args = operands.map(operand => evaluate(operand, env))
return fn(...args)
}
const program =
[define, [
[double, [lambda, [a], [mul, a, 2]]],
[xs, [list, 1, 2, 3, 4, 5]],
[map, [lambda, [fn, as],
[when, [isNil, as],
[list],
[cons, [fn, [head, as]], [rec, fn, [tail, as]]]
]]]
],
[map, double, xs]]
console.log(evaluate(program))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment