-
Notifications
You must be signed in to change notification settings - Fork 0
/
lisp.mjs
69 lines (53 loc) · 1.68 KB
/
lisp.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import _debug from './debug.mjs'
import { FurverExpressionError } from './error.mjs'
import curry from './curry.mjs'
const debug = _debug.extend('lisp')
const debugError = debug.extend('error')
const isFunction = x => typeof x === 'function'
const castFunction = x => isFunction(x) ? x : () => x
const exec = curry(async (env, expression) => {
debug('exec', expression)
if (!Array.isArray(expression)) {
return expression
}
let [operator, ...args] = expression
// Yey, resolved and all and ready to call.
if (isFunction(operator)) {
try {
return await operator.call(env, ...(await Promise.all(args.map(exec(env)))))
} catch (error) {
debugError(error)
throw error
}
}
// Use JSON value when toJSON is implemented.
if (operator?.toJSON) { operator = operator.toJSON() }
if (operator === 'fn') {
const [body] = args
return (fnEnv) => exec(Object.assign(Object.create(env), fnEnv), body)
}
if (operator === 'let') {
const letEnv = Object.create(env)
const [letBindings, letBody] = args
await Promise.all(letBindings.map(async ([name, letExpr]) => {
letEnv[name] = await exec(letEnv, letExpr)
}))
return exec(letEnv, letBody)
}
if (operator === 'ref') {
const [name] = args
return env[name]
}
if (Array.isArray(operator)) {
return exec(env, [await exec(env, operator), ...args])
}
// Throw error if operator is not in env or is not a function
if (!(operator in env)) {
const error = new FurverExpressionError(`Unknown expression: ${operator}`)
debugError(error)
throw error
}
return exec(env, [castFunction(env[operator]), ...args])
})
export { exec }
export default exec