Change quasiquote algorithm
[jackhill/mal.git] / impls / es6 / stepA_mal.mjs
CommitLineData
e10ceff5
JM
1import rl from './node_readline.js'
2const readline = rl.readline
fbfe6784 3import { _clone, _list_Q, _malfunc, _malfunc_Q, Vector } from './types'
4f8c7db9
JM
4import { BlankException, read_str } from './reader'
5import { pr_str } from './printer'
6import { new_env, env_set, env_get } from './env'
7import { core_ns } from './core'
e5c4e656
JM
8
9// read
a05c086f 10const READ = str => read_str(str)
e5c4e656
JM
11
12// eval
fbfe6784
NB
13const qq_loop = (acc, elt) => {
14 if (_list_Q(elt) && elt.length == 2
15 && elt[0] === Symbol.for('splice-unquote')) {
16 return [Symbol.for('concat'), elt[1], acc]
17 } else {
18 return [Symbol.for('cons'), quasiquote (elt), acc]
19 }
20}
e5c4e656 21const quasiquote = ast => {
fbfe6784
NB
22 if (_list_Q(ast)) {
23 if (ast.length == 2 && ast[0] === Symbol.for('unquote')) {
24 return ast[1]
25 } else {
26 return ast.reduceRight(qq_loop, [])
27 }
28 } else if (ast instanceof Vector) {
29 return [Symbol.for('vec'), ast.reduceRight(qq_loop, [])]
30 } else if (typeof ast === 'symbol' || ast instanceof Map) {
a05c086f 31 return [Symbol.for('quote'), ast]
e5c4e656 32 } else {
fbfe6784 33 return ast
e5c4e656
JM
34 }
35}
36
e5c4e656 37function macroexpand(ast, env) {
a05c086f
JM
38 while (_list_Q(ast) && typeof ast[0] === 'symbol' && ast[0] in env) {
39 let f = env_get(env, ast[0])
40 if (!f.ismacro) { break }
41 ast = f(...ast.slice(1))
e5c4e656 42 }
4f8c7db9 43 return ast
e5c4e656
JM
44}
45
46
47const eval_ast = (ast, env) => {
a05c086f 48 if (typeof ast === 'symbol') {
e5c4e656 49 return env_get(env, ast)
a05c086f
JM
50 } else if (ast instanceof Array) {
51 return ast.map(x => EVAL(x, env))
52 } else if (ast instanceof Map) {
4f8c7db9 53 let new_hm = new Map()
a05c086f 54 ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env)))
4f8c7db9 55 return new_hm
e5c4e656 56 } else {
4f8c7db9 57 return ast
e5c4e656
JM
58 }
59}
60
61const EVAL = (ast, env) => {
62 while (true) {
4f8c7db9 63 //console.log('EVAL:', pr_str(ast, true))
e5c4e656
JM
64 if (!_list_Q(ast)) { return eval_ast(ast, env) }
65
4f8c7db9 66 ast = macroexpand(ast, env)
bfa3dd35 67 if (!_list_Q(ast)) { return eval_ast(ast, env) }
f8665761 68 if (ast.length === 0) { return ast }
e5c4e656 69
4f8c7db9 70 const [a0, a1, a2, a3] = ast
a05c086f
JM
71 switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) {
72 case 'def!':
4f8c7db9 73 return env_set(env, a1, EVAL(a2, env))
e5c4e656 74 case 'let*':
4f8c7db9 75 let let_env = new_env(env)
e5c4e656 76 for (let i=0; i < a1.length; i+=2) {
4f8c7db9 77 env_set(let_env, a1[i], EVAL(a1[i+1], let_env))
e5c4e656 78 }
4f8c7db9
JM
79 env = let_env
80 ast = a2
a05c086f 81 break // continue TCO loop
4f8c7db9
JM
82 case 'quote':
83 return a1
fbfe6784
NB
84 case 'quasiquoteexpand':
85 return quasiquote(a1)
4f8c7db9
JM
86 case 'quasiquote':
87 ast = quasiquote(a1)
a05c086f 88 break // continue TCO loop
4f8c7db9 89 case 'defmacro!':
c1da7517 90 let func = _clone(EVAL(a2, env))
4f8c7db9
JM
91 func.ismacro = true
92 return env_set(env, a1, func)
93 case 'macroexpand':
94 return macroexpand(a1, env)
95 case 'try*':
e5c4e656 96 try {
4f8c7db9 97 return EVAL(a1, env)
e5c4e656 98 } catch (exc) {
a05c086f
JM
99 if (a2 && a2[0] === Symbol.for('catch*')) {
100 if (exc instanceof Error) { exc = exc.message }
4f8c7db9 101 return EVAL(a2[2], new_env(env, [a2[1]], [exc]))
e5c4e656 102 } else {
4f8c7db9 103 throw exc
e5c4e656
JM
104 }
105 }
4f8c7db9
JM
106 case 'do':
107 eval_ast(ast.slice(1,-1), env)
108 ast = ast[ast.length-1]
a05c086f 109 break // continue TCO loop
4f8c7db9
JM
110 case 'if':
111 let cond = EVAL(a1, env)
e5c4e656 112 if (cond === null || cond === false) {
4f8c7db9 113 ast = (typeof a3 !== 'undefined') ? a3 : null
e5c4e656 114 } else {
4f8c7db9 115 ast = a2
e5c4e656 116 }
a05c086f 117 break // continue TCO loop
4f8c7db9 118 case 'fn*':
e5c4e656 119 return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)),
a05c086f 120 a2, env, a1)
e5c4e656 121 default:
4f8c7db9 122 let [f, ...args] = eval_ast(ast, env)
e5c4e656 123 if (_malfunc_Q(f)) {
4f8c7db9
JM
124 env = new_env(f.env, f.params, args)
125 ast = f.ast
a05c086f 126 break // continue TCO loop
e5c4e656 127 } else {
4f8c7db9 128 return f(...args)
e5c4e656
JM
129 }
130 }
131 }
132}
133
134// print
a05c086f 135const PRINT = exp => pr_str(exp, true)
e5c4e656
JM
136
137// repl
4f8c7db9 138let repl_env = new_env()
a05c086f 139const REP = str => PRINT(EVAL(READ(str), repl_env))
e5c4e656
JM
140
141// core.EXT: defined using ES6
a05c086f
JM
142for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) }
143env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env))
144env_set(repl_env, Symbol.for('*ARGV*'), [])
e5c4e656
JM
145
146// core.mal: defined using language itself
4f8c7db9
JM
147REP('(def! *host-language* "ecmascript6")')
148REP('(def! not (fn* (a) (if a false true)))')
e6d41de4 149REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))')
4f8c7db9 150REP('(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list \'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons \'cond (rest (rest xs)))))))')
e5c4e656 151
a05c086f
JM
152if (process.argv.length > 2) {
153 env_set(repl_env, Symbol.for('*ARGV*'), process.argv.slice(3))
4f8c7db9
JM
154 REP(`(load-file "${process.argv[2]}")`)
155 process.exit(0)
e5c4e656
JM
156}
157
4f8c7db9 158REP('(println (str "Mal [" *host-language* "]"))')
e5c4e656 159while (true) {
4f8c7db9
JM
160 let line = readline('user> ')
161 if (line == null) break
e5c4e656 162 try {
a05c086f 163 if (line) { console.log(REP(line)) }
e5c4e656 164 } catch (exc) {
a05c086f 165 if (exc instanceof BlankException) { continue }
dd7a4f55
JM
166 if (exc instanceof Error) { console.warn(exc.stack) }
167 else { console.warn(`Error: ${pr_str(exc, true)}`) }
e5c4e656
JM
168 }
169}