Commit | Line | Data |
---|---|---|
e10ceff5 JM |
1 | import rl from './node_readline.js' |
2 | const readline = rl.readline | |
fbfe6784 | 3 | import { _clone, _list_Q, _malfunc, _malfunc_Q, Vector } from './types' |
4f8c7db9 JM |
4 | import { BlankException, read_str } from './reader' |
5 | import { pr_str } from './printer' | |
6 | import { new_env, env_set, env_get } from './env' | |
7 | import { core_ns } from './core' | |
e5c4e656 JM |
8 | |
9 | // read | |
a05c086f | 10 | const READ = str => read_str(str) |
e5c4e656 JM |
11 | |
12 | // eval | |
fbfe6784 NB |
13 | const 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 | 21 | const 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 | 37 | function 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 | ||
47 | const 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 | ||
61 | const 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 | ||
a05c086f | 135 | const PRINT = exp => pr_str(exp, true) |
e5c4e656 JM |
136 | |
137 | // repl | |
4f8c7db9 | 138 | let repl_env = new_env() |
a05c086f | 139 | const REP = str => PRINT(EVAL(READ(str), repl_env)) |
e5c4e656 JM |
140 | |
141 | // core.EXT: defined using ES6 | |
a05c086f JM |
142 | for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } |
143 | env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env)) | |
144 | env_set(repl_env, Symbol.for('*ARGV*'), []) | |
e5c4e656 JM |
145 | |
146 | // core.mal: defined using language itself | |
4f8c7db9 JM |
147 | REP('(def! *host-language* "ecmascript6")') |
148 | REP('(def! not (fn* (a) (if a false true)))') | |
e6d41de4 | 149 | REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))') |
4f8c7db9 | 150 | REP('(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 |
152 | if (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 | 158 | REP('(println (str "Mal [" *host-language* "]"))') |
e5c4e656 | 159 | while (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 | } |