Commit | Line | Data |
---|---|---|
8de9f308 | 1 | import rdstdin, tables, sequtils, os, types, reader, printer, env, core |
2 | ||
3 | proc read(str: string): MalType = str.read_str | |
4 | ||
5 | proc is_pair(x: MalType): bool = | |
6 | x.kind in {List, Vector} and x.list.len > 0 | |
7 | ||
8 | proc quasiquote(ast: MalType): MalType = | |
9 | if not ast.is_pair: | |
10 | return list(symbol "quote", ast) | |
11 | elif ast.list[0] == symbol "unquote": | |
12 | return ast.list[1] | |
13 | elif ast.list[0].is_pair and ast.list[0].list[0] == symbol "splice-unquote": | |
14 | return list(symbol "concat", ast.list[0].list[1], | |
3f429bf4 | 15 | quasiquote(list ast.list[1 .. ^1])) |
8de9f308 | 16 | else: |
3f429bf4 | 17 | return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. ^1]))) |
8de9f308 | 18 | |
19 | proc is_macro_call(ast: MalType, env: Env): bool = | |
dd7a4f55 | 20 | ast.kind == List and ast.list.len > 0 and ast.list[0].kind == Symbol and |
c1709fad | 21 | env.find(ast.list[0].str) != nil and env.get(ast.list[0].str).fun_is_macro |
8de9f308 | 22 | |
23 | proc macroexpand(ast: MalType, env: Env): MalType = | |
24 | result = ast | |
25 | while result.is_macro_call(env): | |
26 | let mac = env.get(result.list[0].str) | |
3f429bf4 | 27 | result = mac.malfun.fn(result.list[1 .. ^1]).macroexpand(env) |
8de9f308 | 28 | |
29 | proc eval(ast: MalType, env: Env): MalType | |
30 | ||
31 | proc eval_ast(ast: MalType, env: var Env): MalType = | |
32 | case ast.kind | |
33 | of Symbol: | |
34 | result = env.get(ast.str) | |
35 | of List: | |
41558f01 | 36 | result = list ast.list.mapIt(it.eval(env)) |
8de9f308 | 37 | of Vector: |
41558f01 | 38 | result = vector ast.list.mapIt(it.eval(env)) |
8de9f308 | 39 | of HashMap: |
40 | result = hash_map() | |
41 | for k, v in ast.hash_map.pairs: | |
42 | result.hash_map[k] = v.eval(env) | |
43 | else: | |
44 | result = ast | |
45 | ||
46 | proc eval(ast: MalType, env: Env): MalType = | |
47 | var ast = ast | |
48 | var env = env | |
49 | ||
50 | template defaultApply = | |
51 | let el = ast.eval_ast(env) | |
52 | let f = el.list[0] | |
53 | case f.kind | |
54 | of MalFun: | |
55 | ast = f.malfun.ast | |
3f429bf4 | 56 | env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. ^1])) |
8de9f308 | 57 | else: |
3f429bf4 | 58 | return f.fun(el.list[1 .. ^1]) |
8de9f308 | 59 | |
60 | while true: | |
61 | if ast.kind != List: return ast.eval_ast(env) | |
62 | ||
63 | ast = ast.macroexpand(env) | |
6c94cd3e | 64 | if ast.kind != List: return ast.eval_ast(env) |
8de9f308 | 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 | res = a2.eval(env) | |
76 | return env.set(a1.str, res) | |
77 | ||
78 | of "let*": | |
79 | let | |
80 | a1 = ast.list[1] | |
81 | a2 = ast.list[2] | |
fc7f8a4b | 82 | var let_env = initEnv(env) |
8de9f308 | 83 | case a1.kind |
84 | of List, Vector: | |
85 | for i in countup(0, a1.list.high, 2): | |
86 | let_env.set(a1.list[i].str, a1.list[i+1].eval(let_env)) | |
87 | else: raise newException(ValueError, "Illegal kind in let*") | |
88 | ast = a2 | |
89 | env = let_env | |
90 | # Continue loop (TCO) | |
91 | ||
92 | of "quote": | |
93 | return ast.list[1] | |
94 | ||
95 | of "quasiquote": | |
96 | ast = ast.list[1].quasiquote | |
97 | # Continue loop (TCO) | |
98 | ||
99 | of "defmacro!": | |
100 | var fun = ast.list[2].eval(env) | |
101 | fun.malfun.is_macro = true | |
102 | return env.set(ast.list[1].str, fun) | |
103 | ||
104 | of "macroexpand": | |
105 | return ast.list[1].macroexpand(env) | |
106 | ||
107 | of "try*": | |
41558f01 | 108 | let a1 = ast.list[1] |
dd7a4f55 JM |
109 | if ast.list.len <= 2: |
110 | return a1.eval(env) | |
41558f01 | 111 | let a2 = ast.list[2] |
8de9f308 | 112 | if a2.list[0].str == "catch*": |
113 | try: | |
114 | return a1.eval(env) | |
115 | except MalError: | |
116 | let exc = (ref MalError) getCurrentException() | |
117 | var catchEnv = initEnv(env, list a2.list[1], exc.t) | |
118 | return a2.list[2].eval(catchEnv) | |
119 | except: | |
120 | let exc = getCurrentExceptionMsg() | |
121 | var catchEnv = initEnv(env, list a2.list[1], list str(exc)) | |
122 | return a2.list[2].eval(catchEnv) | |
123 | else: | |
124 | return a1.eval(env) | |
125 | ||
126 | of "do": | |
127 | let last = ast.list.high | |
41558f01 | 128 | discard (list ast.list[1 ..< last]).eval_ast(env) |
2800f318 | 129 | ast = ast.list[last] |
8de9f308 | 130 | # Continue loop (TCO) |
131 | ||
132 | of "if": | |
133 | let | |
134 | a1 = ast.list[1] | |
135 | a2 = ast.list[2] | |
136 | cond = a1.eval(env) | |
137 | ||
138 | if cond.kind in {Nil, False}: | |
139 | if ast.list.len > 3: ast = ast.list[3] | |
140 | else: ast = nilObj | |
141 | else: ast = a2 | |
142 | ||
143 | of "fn*": | |
144 | let | |
145 | a1 = ast.list[1] | |
146 | a2 = ast.list[2] | |
147 | var env2 = env | |
148 | let fn = proc(a: varargs[MalType]): MalType = | |
149 | var newEnv = initEnv(env2, a1, list(a)) | |
150 | a2.eval(newEnv) | |
151 | return malfun(fn, a2, a1, env) | |
152 | ||
dd7a4f55 | 153 | else: defaultApply() |
8de9f308 | 154 | |
dd7a4f55 | 155 | else: defaultApply() |
8de9f308 | 156 | |
157 | proc print(exp: MalType): string = exp.pr_str | |
158 | ||
159 | var repl_env = initEnv() | |
160 | ||
161 | for k, v in ns.items: | |
162 | repl_env.set(k, v) | |
163 | repl_env.set("eval", fun(proc(xs: varargs[MalType]): MalType = eval(xs[0], repl_env))) | |
164 | var ps = commandLineParams() | |
165 | repl_env.set("*ARGV*", list((if paramCount() > 1: ps[1..ps.high] else: @[]).map(str))) | |
166 | ||
167 | ||
168 | # core.nim: defined using nim | |
169 | proc rep(str: string): string {.discardable.} = | |
170 | str.read.eval(repl_env).print | |
171 | ||
172 | # core.mal: defined using mal itself | |
173 | rep "(def! not (fn* (a) (if a false true)))" | |
e6d41de4 | 174 | rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))" |
8de9f308 | 175 | 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)))))))" |
8de9f308 | 176 | |
177 | if paramCount() >= 1: | |
178 | rep "(load-file \"" & paramStr(1) & "\")" | |
179 | quit() | |
180 | ||
181 | while true: | |
182 | try: | |
183 | let line = readLineFromStdin("user> ") | |
184 | echo line.rep | |
185 | except Blank: discard | |
dd7a4f55 JM |
186 | except IOError: quit() |
187 | except MalError: | |
188 | let exc = (ref MalError) getCurrentException() | |
189 | echo "Error: " & exc.t.list[0].pr_str | |
8de9f308 | 190 | except: |
dd7a4f55 | 191 | stdout.write "Error: " |
8de9f308 | 192 | echo getCurrentExceptionMsg() |
193 | echo getCurrentException().getStackTrace() |