| 1 | import rdstdin, tables, sequtils, os, types, reader, printer, env, core |
| 2 | |
| 3 | proc read(str: string): MalType = str.read_str |
| 4 | |
| 5 | proc quasiquote(ast: MalType): MalType |
| 6 | |
| 7 | proc quasiquote_loop(xs: seq[MalType]): MalType = |
| 8 | result = list() |
| 9 | for i in countdown(xs.high, 0): |
| 10 | var elt = xs[i] |
| 11 | if elt.kind == List and 0 < elt.list.len and elt.list[0] == symbol "splice-unquote": |
| 12 | result = list(symbol "concat", elt.list[1], result) |
| 13 | else: |
| 14 | result = list(symbol "cons", quasiquote(elt), result) |
| 15 | |
| 16 | proc quasiquote(ast: MalType): MalType = |
| 17 | case ast.kind |
| 18 | of List: |
| 19 | if ast.list.len == 2 and ast.list[0] == symbol "unquote": |
| 20 | result = ast.list[1] |
| 21 | else: |
| 22 | result = quasiquote_loop(ast.list) |
| 23 | of Vector: |
| 24 | result = list(symbol "vec", quasiquote_loop(ast.list)) |
| 25 | of Symbol: |
| 26 | result = list(symbol "quote", ast) |
| 27 | of HashMap: |
| 28 | result = list(symbol "quote", ast) |
| 29 | else: |
| 30 | result = ast |
| 31 | |
| 32 | proc eval(ast: MalType, env: Env): MalType |
| 33 | |
| 34 | proc eval_ast(ast: MalType, env: var Env): MalType = |
| 35 | case ast.kind |
| 36 | of Symbol: |
| 37 | result = env.get(ast.str) |
| 38 | of List: |
| 39 | result = list ast.list.mapIt(it.eval(env)) |
| 40 | of Vector: |
| 41 | result = vector ast.list.mapIt(it.eval(env)) |
| 42 | of HashMap: |
| 43 | result = hash_map() |
| 44 | for k, v in ast.hash_map.pairs: |
| 45 | result.hash_map[k] = v.eval(env) |
| 46 | else: |
| 47 | result = ast |
| 48 | |
| 49 | proc eval(ast: MalType, env: Env): MalType = |
| 50 | var ast = ast |
| 51 | var env = env |
| 52 | |
| 53 | template defaultApply = |
| 54 | let el = ast.eval_ast(env) |
| 55 | let f = el.list[0] |
| 56 | case f.kind |
| 57 | of MalFun: |
| 58 | ast = f.malfun.ast |
| 59 | env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. ^1])) |
| 60 | else: |
| 61 | return f.fun(el.list[1 .. ^1]) |
| 62 | |
| 63 | while true: |
| 64 | if ast.kind != List: return ast.eval_ast(env) |
| 65 | if ast.list.len == 0: return ast |
| 66 | |
| 67 | let a0 = ast.list[0] |
| 68 | case a0.kind |
| 69 | of Symbol: |
| 70 | case a0.str |
| 71 | of "def!": |
| 72 | let |
| 73 | a1 = ast.list[1] |
| 74 | a2 = ast.list[2] |
| 75 | return env.set(a1.str, a2.eval(env)) |
| 76 | |
| 77 | of "let*": |
| 78 | let |
| 79 | a1 = ast.list[1] |
| 80 | a2 = ast.list[2] |
| 81 | var let_env = initEnv(env) |
| 82 | case a1.kind |
| 83 | of List, Vector: |
| 84 | for i in countup(0, a1.list.high, 2): |
| 85 | let_env.set(a1.list[i].str, a1.list[i+1].eval(let_env)) |
| 86 | else: raise newException(ValueError, "Illegal kind in let*") |
| 87 | ast = a2 |
| 88 | env = let_env |
| 89 | # Continue loop (TCO) |
| 90 | |
| 91 | of "quote": |
| 92 | return ast.list[1] |
| 93 | |
| 94 | of "quasiquoteexpand": |
| 95 | return ast.list[1].quasiquote |
| 96 | |
| 97 | of "quasiquote": |
| 98 | ast = ast.list[1].quasiquote |
| 99 | # Continue loop (TCO) |
| 100 | |
| 101 | of "do": |
| 102 | let last = ast.list.high |
| 103 | discard (list ast.list[1 ..< last]).eval_ast(env) |
| 104 | ast = ast.list[last] |
| 105 | # Continue loop (TCO) |
| 106 | |
| 107 | of "if": |
| 108 | let |
| 109 | a1 = ast.list[1] |
| 110 | a2 = ast.list[2] |
| 111 | cond = a1.eval(env) |
| 112 | |
| 113 | if cond.kind in {Nil, False}: |
| 114 | if ast.list.len > 3: ast = ast.list[3] |
| 115 | else: ast = nilObj |
| 116 | else: ast = a2 |
| 117 | |
| 118 | of "fn*": |
| 119 | let |
| 120 | a1 = ast.list[1] |
| 121 | a2 = ast.list[2] |
| 122 | var env2 = env |
| 123 | let fn = proc(a: varargs[MalType]): MalType = |
| 124 | var newEnv = initEnv(env2, a1, list(a)) |
| 125 | a2.eval(newEnv) |
| 126 | return malfun(fn, a2, a1, env) |
| 127 | |
| 128 | else: |
| 129 | defaultApply() |
| 130 | |
| 131 | else: |
| 132 | defaultApply() |
| 133 | |
| 134 | proc print(exp: MalType): string = exp.pr_str |
| 135 | |
| 136 | var repl_env = initEnv() |
| 137 | |
| 138 | for k, v in ns.items: |
| 139 | repl_env.set(k, v) |
| 140 | repl_env.set("eval", fun(proc(xs: varargs[MalType]): MalType = eval(xs[0], repl_env))) |
| 141 | var ps = commandLineParams() |
| 142 | repl_env.set("*ARGV*", list((if paramCount() > 1: ps[1..ps.high] else: @[]).map(str))) |
| 143 | |
| 144 | |
| 145 | # core.nim: defined using nim |
| 146 | proc rep(str: string): string {.discardable.} = |
| 147 | str.read.eval(repl_env).print |
| 148 | |
| 149 | # core.mal: defined using mal itself |
| 150 | rep "(def! not (fn* (a) (if a false true)))" |
| 151 | rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))" |
| 152 | |
| 153 | if paramCount() >= 1: |
| 154 | rep "(load-file \"" & paramStr(1) & "\")" |
| 155 | quit() |
| 156 | |
| 157 | while true: |
| 158 | try: |
| 159 | let line = readLineFromStdin("user> ") |
| 160 | echo line.rep |
| 161 | except Blank: discard |
| 162 | except: |
| 163 | echo getCurrentExceptionMsg() |
| 164 | echo getCurrentException().getStackTrace() |