Commit | Line | Data |
---|---|---|
32d0a1cf | 1 | import Compat; |
1d166495 JM |
2 | import types.Types.MalType; |
3 | import types.Types.*; | |
4 | import types.MalException; | |
5 | import reader.*; | |
6 | import printer.*; | |
7 | import env.*; | |
8 | import core.*; | |
9 | import haxe.rtti.Meta; | |
10 | ||
11 | class Step9_try { | |
12 | // READ | |
13 | static function READ(str:String):MalType { | |
14 | return Reader.read_str(str); | |
15 | } | |
16 | ||
17 | // EVAL | |
18 | static function is_pair(ast:MalType) { | |
19 | return switch (ast) { | |
20 | case MalList(l) | MalVector(l): l.length > 0; | |
21 | case _: false; | |
22 | } | |
23 | } | |
24 | ||
25 | static function quasiquote(ast:MalType) { | |
26 | if (!is_pair(ast)) { | |
27 | return MalList([MalSymbol("quote"), ast]); | |
28 | } else { | |
29 | var a0 = first(ast); | |
30 | if (_equal_Q(a0, MalSymbol("unquote"))) { | |
31 | return _nth(ast, 1); | |
32 | } else if (is_pair(a0)) { | |
33 | var a00 = first(a0); | |
34 | if (_equal_Q(a00, MalSymbol("splice-unquote"))) { | |
35 | return MalList([MalSymbol("concat"), | |
36 | _nth(a0, 1), | |
37 | quasiquote(rest(ast))]); | |
38 | } | |
39 | } | |
40 | return MalList([MalSymbol("cons"), | |
41 | quasiquote(a0), | |
42 | quasiquote(rest(ast))]); | |
43 | } | |
44 | } | |
45 | ||
46 | static function is_macro(ast:MalType, env:Env) { | |
47 | return switch(ast) { | |
d3ec2994 | 48 | case MalList([]): false; |
1d166495 JM |
49 | case MalList(a): |
50 | var a0 = a[0]; | |
51 | return symbol_Q(a0) && | |
52 | env.find(a0) != null && | |
53 | _macro_Q(env.get(a0)); | |
54 | case _: false; | |
55 | } | |
56 | } | |
57 | ||
58 | static function macroexpand(ast:MalType, env:Env) { | |
59 | while (is_macro(ast, env)) { | |
60 | var mac = env.get(first(ast)); | |
61 | switch (mac) { | |
62 | case MalFunc(f,_,_,_,_,_): | |
63 | ast = f(_list(ast).slice(1)); | |
64 | case _: break; | |
65 | } | |
66 | } | |
67 | return ast; | |
68 | } | |
69 | ||
70 | static function eval_ast(ast:MalType, env:Env) { | |
71 | return switch (ast) { | |
72 | case MalSymbol(s): env.get(ast); | |
73 | case MalList(l): | |
74 | MalList(l.map(function(x) { return EVAL(x, env); })); | |
75 | case MalVector(l): | |
76 | MalVector(l.map(function(x) { return EVAL(x, env); })); | |
77 | case MalHashMap(m): | |
78 | var new_map = new Map<String,MalType>(); | |
79 | for (k in m.keys()) { | |
80 | new_map[k] = EVAL(m[k], env); | |
81 | } | |
82 | MalHashMap(new_map); | |
83 | case _: ast; | |
84 | } | |
85 | } | |
86 | ||
87 | static function EVAL(ast:MalType, env:Env):MalType { | |
88 | while (true) { | |
89 | if (!list_Q(ast)) { return eval_ast(ast, env); } | |
90 | ||
91 | // apply | |
92 | ast = macroexpand(ast, env); | |
33dec7af | 93 | if (!list_Q(ast)) { return eval_ast(ast, env); } |
1d166495 JM |
94 | |
95 | var alst = _list(ast); | |
d3ec2994 | 96 | if (alst.length == 0) { return ast; } |
1d166495 JM |
97 | switch (alst[0]) { |
98 | case MalSymbol("def!"): | |
99 | return env.set(alst[1], EVAL(alst[2], env)); | |
100 | case MalSymbol("let*"): | |
101 | var let_env = new Env(env); | |
102 | switch (alst[1]) { | |
103 | case MalList(l) | MalVector(l): | |
104 | for (i in 0...l.length) { | |
105 | if ((i%2) > 0) { continue; } | |
106 | let_env.set(l[i], EVAL(l[i+1], let_env)); | |
107 | } | |
108 | case _: throw "Invalid let*"; | |
109 | } | |
110 | ast = alst[2]; | |
111 | env = let_env; | |
112 | continue; // TCO | |
113 | case MalSymbol("quote"): | |
114 | return alst[1]; | |
115 | case MalSymbol("quasiquote"): | |
116 | ast = quasiquote(alst[1]); | |
117 | continue; // TCO | |
118 | case MalSymbol("defmacro!"): | |
119 | var func = EVAL(alst[2], env); | |
120 | return switch (func) { | |
121 | case MalFunc(f,ast,e,params,_,_): | |
122 | env.set(alst[1], MalFunc(f,ast,e,params,true,nil)); | |
123 | case _: | |
124 | throw "Invalid defmacro! call"; | |
125 | } | |
126 | case MalSymbol("macroexpand"): | |
127 | return macroexpand(alst[1], env); | |
128 | case MalSymbol("try*"): | |
129 | try { | |
130 | return EVAL(alst[1], env); | |
131 | } catch (err:Dynamic) { | |
132 | if (alst.length > 2) { | |
133 | switch (alst[2]) { | |
134 | case MalList([MalSymbol("catch*"), a21, a22]): | |
135 | var exc; | |
136 | if (Type.getClass(err) == MalException) { | |
137 | exc = err.obj; | |
138 | } else { | |
139 | exc = MalString(Std.string(err)); | |
140 | }; | |
141 | return EVAL(a22, new Env(env, [a21], [exc])); | |
142 | case _: | |
143 | throw err; | |
144 | } | |
145 | } else { | |
146 | throw err; | |
147 | } | |
148 | } | |
149 | case MalSymbol("do"): | |
150 | var el = eval_ast(MalList(alst.slice(1, alst.length-1)), env); | |
151 | ast = last(ast); | |
152 | continue; // TCO | |
153 | case MalSymbol("if"): | |
154 | var cond = EVAL(alst[1], env); | |
155 | if (cond != MalFalse && cond != MalNil) { | |
156 | ast = alst[2]; | |
157 | } else if (alst.length > 3) { | |
158 | ast = alst[3]; | |
159 | } else { | |
160 | return MalNil; | |
161 | } | |
162 | continue; // TCO | |
163 | case MalSymbol("fn*"): | |
164 | return MalFunc(function (args) { | |
165 | return EVAL(alst[2], new Env(env, _list(alst[1]), args)); | |
166 | },alst[2],env,alst[1],false,nil); | |
167 | case _: | |
168 | var el = eval_ast(ast, env); | |
169 | var lst = _list(el); | |
170 | switch (first(el)) { | |
171 | case MalFunc(f,a,e,params,_,_): | |
172 | var args = _list(el).slice(1); | |
173 | if (a != null) { | |
174 | ast = a; | |
175 | env = new Env(e, _list(params), args); | |
176 | continue; // TCO | |
177 | } else { | |
178 | return f(args); | |
179 | } | |
180 | case _: throw "Call of non-function"; | |
181 | } | |
182 | } | |
183 | } | |
184 | } | |
185 | ||
186 | ||
187 | static function PRINT(exp:MalType):String { | |
188 | return Printer.pr_str(exp, true); | |
189 | } | |
190 | ||
191 | // repl | |
192 | static var repl_env = new Env(null); | |
193 | ||
194 | static function rep(line:String):String { | |
195 | return PRINT(EVAL(READ(line), repl_env)); | |
196 | } | |
197 | ||
198 | public static function main() { | |
1d166495 JM |
199 | // core.EXT: defined using Haxe |
200 | for (k in Core.ns.keys()) { | |
201 | repl_env.set(MalSymbol(k), MalFunc(Core.ns[k],null,null,null,false,nil)); | |
202 | } | |
203 | ||
204 | var evalfn = MalFunc(function(args) { | |
205 | return EVAL(args[0], repl_env); | |
206 | },null,null,null,false,nil); | |
207 | repl_env.set(MalSymbol("eval"), evalfn); | |
208 | ||
32d0a1cf JM |
209 | var cmdargs = Compat.cmdline_args(); |
210 | var argarray = cmdargs.map(function(a) { return MalString(a); }); | |
17946efb | 211 | repl_env.set(MalSymbol("*ARGV*"), MalList(argarray.slice(1))); |
1d166495 JM |
212 | |
213 | // core.mal: defined using the language itself | |
214 | rep("(def! not (fn* (a) (if a false true)))"); | |
215 | rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); | |
216 | 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)))))))"); | |
217 | rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); | |
218 | ||
219 | ||
220 | if (cmdargs.length > 0) { | |
32d0a1cf JM |
221 | rep('(load-file "${cmdargs[0]}")'); |
222 | Compat.exit(0); | |
1d166495 JM |
223 | } |
224 | ||
225 | while (true) { | |
226 | try { | |
32d0a1cf | 227 | var line = Compat.readline("user> "); |
1d166495 | 228 | if (line == "") { continue; } |
32d0a1cf | 229 | Compat.println(rep(line)); |
1d166495 JM |
230 | } catch (exc:BlankLine) { |
231 | continue; | |
232 | } catch (exc:haxe.io.Eof) { | |
32d0a1cf | 233 | Compat.exit(0); |
1d166495 | 234 | } catch (exc:Dynamic) { |
dd7a4f55 JM |
235 | if (Type.getClass(exc) == MalException) { |
236 | Compat.println("Error: " + Printer.pr_str(exc.obj, true)); | |
237 | } else { | |
238 | Compat.println("Error: " + exc); | |
239 | }; | |
1d166495 JM |
240 | } |
241 | } | |
242 | } | |
243 | } |