Add gensym and clean `or` macro to stepA of 19 implementations (part 3)
[jackhill/mal.git] / es6 / stepA_mal.js
CommitLineData
4f8c7db9
JM
1import { readline } from './node_readline'
2import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q,
3 _hash_map_Q, _sequential_Q, _malfunc, _malfunc_Q } from './types'
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
4f8c7db9 10const READ = (str) => read_str(str)
e5c4e656
JM
11
12// eval
13const is_pair = x => _sequential_Q(x) && x.length > 0
14
15const 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
27function 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
34function 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
43const 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
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)
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// print
4f8c7db9 133const PRINT = (exp) => pr_str(exp, true)
e5c4e656
JM
134
135// repl
4f8c7db9
JM
136let repl_env = new_env()
137const REP = (str) => PRINT(EVAL(READ(str), repl_env))
e5c4e656
JM
138
139// core.EXT: defined using ES6
4f8c7db9
JM
140for (let [k, v] of core_ns) { env_set(repl_env, _symbol(k), v) }
141env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env))
142env_set(repl_env, _symbol('*ARGV*'), [])
e5c4e656
JM
143
144// core.mal: defined using language itself
4f8c7db9
JM
145REP('(def! *host-language* "ecmascript6")')
146REP('(def! not (fn* (a) (if a false true)))')
147REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))')
148REP('(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
149REP('(def! *gensym-counter* (atom 0))')
150REP('(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))')
151REP('(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
153if (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 159REP('(println (str "Mal [" *host-language* "]"))')
e5c4e656 160while (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}