Commit | Line | Data |
---|---|---|
4f8c7db9 JM |
1 | import { readline } from './node_readline' |
2 | import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, | |
3 | _hash_map_Q, _sequential_Q, _malfunc, _malfunc_Q } from './types' | |
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 | |
4f8c7db9 | 10 | const READ = (str) => read_str(str) |
e5c4e656 JM |
11 | |
12 | // eval | |
13 | const is_pair = x => _sequential_Q(x) && x.length > 0 | |
14 | ||
15 | const quasiquote = ast => { | |
16 | if (!is_pair(ast)) { | |
4f8c7db9 JM |
17 | return [_symbol('quote'), ast] |
18 | } else if (ast[0] === _symbol('unquote')) { | |
19 | return ast[1] | |
20 | } else if (is_pair(ast[0]) && ast[0][0] === _symbol('splice-unquote')) { | |
21 | return [_symbol('concat'), ast[0][1], quasiquote(ast.slice(1))] | |
e5c4e656 | 22 | } else { |
4f8c7db9 | 23 | return [_symbol('cons'), quasiquote(ast[0]), quasiquote(ast.slice(1))] |
e5c4e656 JM |
24 | } |
25 | } | |
26 | ||
27 | function is_macro_call(ast, env) { | |
28 | return _list_Q(ast) && | |
4f8c7db9 | 29 | _symbol_Q(ast[0]) && |
e5c4e656 | 30 | ast[0] in env && |
4f8c7db9 | 31 | env_get(env, ast[0]).ismacro |
e5c4e656 JM |
32 | } |
33 | ||
34 | function macroexpand(ast, env) { | |
35 | while (is_macro_call(ast, env)) { | |
4f8c7db9 JM |
36 | let mac = env_get(env, ast[0]) |
37 | ast = mac(...ast.slice(1)) | |
e5c4e656 | 38 | } |
4f8c7db9 | 39 | return ast |
e5c4e656 JM |
40 | } |
41 | ||
42 | ||
43 | const eval_ast = (ast, env) => { | |
4f8c7db9 | 44 | if (_symbol_Q(ast)) { |
e5c4e656 JM |
45 | return env_get(env, ast) |
46 | } else if (_list_Q(ast)) { | |
4f8c7db9 | 47 | return ast.map((x) => EVAL(x, env)) |
afa79313 | 48 | } else if (_vector_Q(ast)) { |
4f8c7db9 | 49 | return _vector(...ast.map((x) => EVAL(x, env))) |
afa79313 | 50 | } else if (_hash_map_Q(ast)) { |
4f8c7db9 | 51 | let new_hm = new Map() |
afa79313 | 52 | for (let [k, v] of ast) { |
4f8c7db9 | 53 | new_hm.set(EVAL(k, env), EVAL(v, env)) |
afa79313 | 54 | } |
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) |
e5c4e656 JM |
67 | if (!_list_Q(ast)) { return ast; } |
68 | ||
4f8c7db9 JM |
69 | const [a0, a1, a2, a3] = ast |
70 | const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') | |
e5c4e656 JM |
71 | switch (a0sym) { |
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 | |
e5c4e656 | 81 | break; // continue TCO loop |
4f8c7db9 JM |
82 | case 'quote': |
83 | return a1 | |
84 | case 'quasiquote': | |
85 | ast = quasiquote(a1) | |
e5c4e656 | 86 | break; // continue TCO loop |
4f8c7db9 JM |
87 | case 'defmacro!': |
88 | let func = EVAL(a2, env) | |
89 | func.ismacro = true | |
90 | return env_set(env, a1, func) | |
91 | case 'macroexpand': | |
92 | return macroexpand(a1, env) | |
93 | case 'try*': | |
e5c4e656 | 94 | try { |
4f8c7db9 | 95 | return EVAL(a1, env) |
e5c4e656 | 96 | } catch (exc) { |
4f8c7db9 | 97 | if (a2 && a2[0] === _symbol('catch*')) { |
e5c4e656 | 98 | if (exc instanceof Error) { exc = exc.message; } |
4f8c7db9 | 99 | return EVAL(a2[2], new_env(env, [a2[1]], [exc])) |
e5c4e656 | 100 | } else { |
4f8c7db9 | 101 | throw exc |
e5c4e656 JM |
102 | } |
103 | } | |
4f8c7db9 JM |
104 | case 'do': |
105 | eval_ast(ast.slice(1,-1), env) | |
106 | ast = ast[ast.length-1] | |
e5c4e656 | 107 | break; // continue TCO loop |
4f8c7db9 JM |
108 | case 'if': |
109 | let cond = EVAL(a1, env) | |
e5c4e656 | 110 | if (cond === null || cond === false) { |
4f8c7db9 | 111 | ast = (typeof a3 !== 'undefined') ? a3 : null |
e5c4e656 | 112 | } else { |
4f8c7db9 | 113 | ast = a2 |
e5c4e656 JM |
114 | } |
115 | break; // continue TCO loop | |
4f8c7db9 | 116 | case 'fn*': |
e5c4e656 | 117 | return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), |
4f8c7db9 | 118 | a2, env, a1) |
e5c4e656 | 119 | default: |
4f8c7db9 | 120 | let [f, ...args] = eval_ast(ast, env) |
e5c4e656 | 121 | if (_malfunc_Q(f)) { |
4f8c7db9 JM |
122 | env = new_env(f.env, f.params, args) |
123 | ast = f.ast | |
e5c4e656 JM |
124 | break; // continue TCO loop |
125 | } else { | |
4f8c7db9 | 126 | return f(...args) |
e5c4e656 JM |
127 | } |
128 | } | |
129 | } | |
130 | } | |
131 | ||
132 | ||
4f8c7db9 | 133 | const PRINT = (exp) => pr_str(exp, true) |
e5c4e656 JM |
134 | |
135 | // repl | |
4f8c7db9 JM |
136 | let repl_env = new_env() |
137 | const REP = (str) => PRINT(EVAL(READ(str), repl_env)) | |
e5c4e656 JM |
138 | |
139 | // core.EXT: defined using ES6 | |
4f8c7db9 JM |
140 | for (let [k, v] of core_ns) { env_set(repl_env, _symbol(k), v) } |
141 | env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env)) | |
142 | env_set(repl_env, _symbol('*ARGV*'), []) | |
e5c4e656 JM |
143 | |
144 | // core.mal: defined using language itself | |
4f8c7db9 JM |
145 | REP('(def! *host-language* "ecmascript6")') |
146 | REP('(def! not (fn* (a) (if a false true)))') | |
147 | REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') | |
148 | 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)))))))') | |
29ba1fb6 DM |
149 | REP('(def! *gensym-counter* (atom 0))') |
150 | REP('(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))') | |
151 | REP('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))') | |
e5c4e656 JM |
152 | |
153 | if (process.argv.length > 2) { | |
4f8c7db9 JM |
154 | env_set(repl_env, '*ARGV*', process.argv.slice(3)) |
155 | REP(`(load-file "${process.argv[2]}")`) | |
156 | process.exit(0) | |
e5c4e656 JM |
157 | } |
158 | ||
4f8c7db9 | 159 | REP('(println (str "Mal [" *host-language* "]"))') |
e5c4e656 | 160 | while (true) { |
4f8c7db9 JM |
161 | let line = readline('user> ') |
162 | if (line == null) break | |
e5c4e656 JM |
163 | try { |
164 | if (line) { console.log(REP(line)); } | |
165 | } catch (exc) { | |
166 | if (exc instanceof BlankException) { continue; } | |
167 | if (exc.stack) { console.log(exc.stack); } | |
4f8c7db9 | 168 | else { console.log(`Error: ${exc}`); } |
e5c4e656 JM |
169 | } |
170 | } |