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, _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' | |
5024b694 JM |
8 | |
9 | // read | |
4f8c7db9 | 10 | const READ = (str) => read_str(str) |
5024b694 JM |
11 | |
12 | // eval | |
13 | const eval_ast = (ast, env) => { | |
4f8c7db9 | 14 | if (_symbol_Q(ast)) { |
5024b694 JM |
15 | return env_get(env, ast) |
16 | } else if (_list_Q(ast)) { | |
4f8c7db9 | 17 | return ast.map((x) => EVAL(x, env)) |
afa79313 | 18 | } else if (_vector_Q(ast)) { |
4f8c7db9 | 19 | return _vector(...ast.map((x) => EVAL(x, env))) |
afa79313 | 20 | } else if (_hash_map_Q(ast)) { |
4f8c7db9 | 21 | let new_hm = new Map() |
afa79313 | 22 | for (let [k, v] of ast) { |
4f8c7db9 | 23 | new_hm.set(EVAL(k, env), EVAL(v, env)) |
afa79313 | 24 | } |
4f8c7db9 | 25 | return new_hm |
5024b694 | 26 | } else { |
4f8c7db9 | 27 | return ast |
5024b694 JM |
28 | } |
29 | } | |
30 | ||
31 | const EVAL = (ast, env) => { | |
32 | while (true) { | |
4f8c7db9 | 33 | //console.log('EVAL:', pr_str(ast, true)) |
5024b694 JM |
34 | if (!_list_Q(ast)) { return eval_ast(ast, env) } |
35 | ||
4f8c7db9 JM |
36 | const [a0, a1, a2, a3] = ast |
37 | const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') | |
5024b694 JM |
38 | switch (a0sym) { |
39 | case 'def!': | |
4f8c7db9 | 40 | return env_set(env, a1, EVAL(a2, env)) |
5024b694 | 41 | case 'let*': |
4f8c7db9 | 42 | let let_env = new_env(env) |
5024b694 | 43 | for (let i=0; i < a1.length; i+=2) { |
4f8c7db9 | 44 | env_set(let_env, a1[i], EVAL(a1[i+1], let_env)) |
5024b694 | 45 | } |
4f8c7db9 JM |
46 | env = let_env |
47 | ast = a2 | |
5024b694 | 48 | break; // continue TCO loop |
4f8c7db9 JM |
49 | case 'do': |
50 | eval_ast(ast.slice(1,-1), env) | |
51 | ast = ast[ast.length-1] | |
5024b694 | 52 | break; // continue TCO loop |
4f8c7db9 JM |
53 | case 'if': |
54 | let cond = EVAL(a1, env) | |
5024b694 | 55 | if (cond === null || cond === false) { |
4f8c7db9 | 56 | ast = (typeof a3 !== 'undefined') ? a3 : null |
5024b694 | 57 | } else { |
4f8c7db9 | 58 | ast = a2 |
5024b694 JM |
59 | } |
60 | break; // continue TCO loop | |
4f8c7db9 | 61 | case 'fn*': |
5024b694 | 62 | return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), |
4f8c7db9 | 63 | a2, env, a1) |
5024b694 | 64 | default: |
4f8c7db9 | 65 | let [f, ...args] = eval_ast(ast, env) |
5024b694 | 66 | if (_malfunc_Q(f)) { |
4f8c7db9 JM |
67 | env = new_env(f.env, f.params, args) |
68 | ast = f.ast | |
5024b694 JM |
69 | break; // continue TCO loop |
70 | } else { | |
4f8c7db9 | 71 | return f(...args) |
5024b694 JM |
72 | } |
73 | } | |
74 | } | |
75 | } | |
76 | ||
77 | ||
4f8c7db9 | 78 | const PRINT = (exp) => pr_str(exp, true) |
5024b694 JM |
79 | |
80 | // repl | |
4f8c7db9 JM |
81 | let repl_env = new_env() |
82 | const REP = (str) => PRINT(EVAL(READ(str), repl_env)) | |
5024b694 JM |
83 | |
84 | // core.EXT: defined using ES6 | |
4f8c7db9 | 85 | for (let [k, v] of core_ns) { env_set(repl_env, _symbol(k), v) } |
5024b694 JM |
86 | |
87 | // core.mal: defined using language itself | |
4f8c7db9 | 88 | REP('(def! not (fn* (a) (if a false true)))') |
5024b694 JM |
89 | |
90 | while (true) { | |
4f8c7db9 JM |
91 | let line = readline('user> ') |
92 | if (line == null) break | |
5024b694 JM |
93 | try { |
94 | if (line) { console.log(REP(line)); } | |
95 | } catch (exc) { | |
96 | if (exc instanceof BlankException) { continue; } | |
97 | if (exc.stack) { console.log(exc.stack); } | |
4f8c7db9 | 98 | else { console.log(`Error: ${exc}`); } |
5024b694 JM |
99 | } |
100 | } |