Merge pull request #361 from asarhaddon/exercise-native-implementations
[jackhill/mal.git] / d / stepA_mal.d
CommitLineData
f82cb965
DM
1module main;
2
3import std.algorithm;
2eb69541 4import std.compiler;
f82cb965
DM
5import std.array;
6import std.range;
7import std.stdio;
8import std.string;
2eb69541 9import core.stdc.stdlib;
f82cb965
DM
10import env;
11import mal_core;
12import readline;
13import reader;
14import printer;
15import types;
16
17bool is_pair(MalType ast)
18{
19 auto lst = cast(MalSequential) ast;
20 if (lst is null) return false;
21 return lst.elements.length > 0;
22}
23
24MalType quasiquote(MalType ast)
25{
26 if (!is_pair(ast))
27 {
28 return new MalList([sym_quote, ast]);
29 }
30 auto ast_seq = verify_cast!MalSequential(ast);
31 auto aste = ast_seq.elements;
32 if (aste[0] == sym_unquote)
33 {
34 return aste[1];
35 }
36
37 if (is_pair(aste[0]))
38 {
39 auto ast0_seq = verify_cast!MalSequential(aste[0]);
40 if (ast0_seq.elements[0] == sym_splice_unquote)
41 {
42 return new MalList([new MalSymbol("concat"), ast0_seq.elements[1], quasiquote(new MalList(aste[1..$]))]);
43 }
44 }
45
46 return new MalList([new MalSymbol("cons"), quasiquote(aste[0]), quasiquote(new MalList(aste[1..$]))]);
47}
48
49bool is_macro_call(MalType ast, Env env)
50{
51 auto lst = cast(MalList) ast;
52 if (lst is null) return false;
53 if (lst.elements.length == 0) return false;
54 auto sym0 = cast(MalSymbol) lst.elements[0];
55 if (sym0 is null) return false;
56 if (env.find(sym0) is null) return false;
57 auto val = env.get(sym0);
58 auto val_func = cast(MalFunc) val;
59 if (val_func is null) return false;
60 return val_func.is_macro;
61}
62
63MalType macroexpand(MalType ast, Env env)
64{
65 while (is_macro_call(ast, env))
66 {
67 auto ast_list = verify_cast!MalList(ast);
68 auto sym0 = verify_cast!MalSymbol(ast_list.elements[0]);
69 auto macrofunc = verify_cast!MalFunc(env.get(sym0));
70 auto rest = ast_list.elements[1..$];
71 auto callenv = new Env(macrofunc.def_env, macrofunc.arg_names, rest);
72 ast = EVAL(macrofunc.func_body, callenv);
73 }
74 return ast;
75}
76
77MalType READ(string str)
78{
79 return read_str(str);
80}
81
82MalType eval_ast(MalType ast, Env env)
83{
2eb69541 84 if (auto sym = cast(MalSymbol)ast)
f82cb965 85 {
f82cb965
DM
86 return env.get(sym);
87 }
2eb69541 88 else if (auto lst = cast(MalList)ast)
f82cb965 89 {
f82cb965
DM
90 auto el = array(lst.elements.map!(e => EVAL(e, env)));
91 return new MalList(el);
92 }
2eb69541 93 else if (auto lst = cast(MalVector)ast)
f82cb965 94 {
f82cb965
DM
95 auto el = array(lst.elements.map!(e => EVAL(e, env)));
96 return new MalVector(el);
97 }
2eb69541 98 else if (auto hm = cast(MalHashmap)ast)
f82cb965 99 {
f82cb965
DM
100 typeof(hm.data) new_data;
101 foreach (string k, MalType v; hm.data)
102 {
103 new_data[k] = EVAL(v, env);
104 }
105 return new MalHashmap(new_data);
106 }
107 else
108 {
109 return ast;
110 }
111}
112
113MalType EVAL(MalType ast, Env env)
114{
115 for (;;)
116 {
117 MalList ast_list = cast(MalList) ast;
118 if (ast_list is null)
119 {
120 return eval_ast(ast, env);
121 }
122
123 ast = macroexpand(ast, env);
124 ast_list = cast(MalList) ast;
125 if (ast_list is null)
126 {
6c94cd3e 127 return eval_ast(ast, env);
f82cb965
DM
128 }
129
130 auto aste = ast_list.elements;
ece4cad1
DM
131 if (aste.length == 0)
132 {
133 return ast;
134 }
f82cb965
DM
135 auto a0_sym = cast(MalSymbol) aste[0];
136 auto sym_name = a0_sym is null ? "" : a0_sym.name;
137 switch (sym_name)
138 {
139 case "def!":
140 auto a1 = verify_cast!MalSymbol(aste[1]);
141 return env.set(a1, EVAL(aste[2], env));
142
143 case "let*":
144 auto a1 = verify_cast!MalSequential(aste[1]);
145 auto let_env = new Env(env);
146 foreach (kv; chunks(a1.elements, 2))
147 {
148 if (kv.length < 2) throw new Exception("let* requires even number of elements");
149 auto var_name = verify_cast!MalSymbol(kv[0]);
150 let_env.set(var_name, EVAL(kv[1], let_env));
151 }
152 ast = aste[2];
153 env = let_env;
154 continue; // TCO
155
156 case "quote":
157 return aste[1];
158
159 case "quasiquote":
160 ast = quasiquote(aste[1]);
161 continue; // TCO
162
163 case "defmacro!":
164 auto a1 = verify_cast!MalSymbol(aste[1]);
165 auto mac = verify_cast!MalFunc(EVAL(aste[2], env));
166 mac.is_macro = true;
167 return env.set(a1, mac);
168
169 case "macroexpand":
170 return macroexpand(aste[1], env);
171
172 case "try*":
2eb69541 173 if (aste.length < 2) return mal_nil;
2b2ba8fc
DM
174 if (aste.length < 3)
175 {
2eb69541
UK
176 ast = aste[1];
177 continue; // TCO
2b2ba8fc 178 }
f82cb965
DM
179 MalType exc;
180 try
181 {
2eb69541
UK
182 // d seems to do erroneous tco all by itself without this
183 // little distraction
184 pr_str(aste[1]);
f82cb965
DM
185 return EVAL(aste[1], env);
186 }
187 catch (MalException e)
188 {
189 exc = e.data;
190 }
191 catch (Exception e)
192 {
193 exc = new MalString(e.msg);
194 }
f82cb965
DM
195 auto catch_clause = verify_cast!MalList(aste[2]);
196 auto catch_env = new Env(env, [catch_clause.elements[1]], [exc]);
2eb69541
UK
197 ast = catch_clause.elements[2];
198 env = catch_env;
199 continue; // TCO
f82cb965
DM
200
201 case "do":
202 auto all_but_last = new MalList(aste[1..$-1]);
203 eval_ast(all_but_last, env);
204 ast = aste[$-1];
205 continue; // TCO
206
207 case "if":
208 auto cond = EVAL(aste[1], env);
209 if (cond.is_truthy())
210 {
211 ast = aste[2];
212 continue; // TCO
213 }
214 else
215 if (aste.length > 3)
216 {
217 ast = aste[3];
218 continue; // TCO
219 }
220 else
221 {
222 return mal_nil;
223 }
224
225 case "fn*":
226 auto args_list = verify_cast!MalSequential(aste[1]);
227 return new MalFunc(args_list.elements, aste[2], env);
228
229 default:
230 auto el = verify_cast!MalList(eval_ast(ast, env));
231 if (el.elements.length == 0)
232 {
233 throw new Exception("Expected a non-empty list");
234 }
235 auto first = el.elements[0];
236 auto rest = el.elements[1..$];
2eb69541 237 if (auto funcobj = cast(MalFunc)first)
f82cb965 238 {
f82cb965
DM
239 auto callenv = new Env(funcobj.def_env, funcobj.arg_names, rest);
240 ast = funcobj.func_body;
241 env = callenv;
242 continue; // TCO
243 }
2eb69541 244 else if (auto builtinfuncobj = cast(MalBuiltinFunc)first)
f82cb965 245 {
f82cb965
DM
246 return builtinfuncobj.fn(rest);
247 }
248 else
249 {
250 throw new Exception("Expected a function");
251 }
252 }
253 }
254}
255
256string PRINT(MalType ast)
257{
258 return pr_str(ast);
259}
260
261MalType re(string str, Env env)
262{
263 return EVAL(READ(str), env);
264}
265
266string rep(string str, Env env)
267{
268 return PRINT(re(str, env));
269}
270
271static MalList create_argv_list(string[] args)
272{
273 if (args.length <= 2) return new MalList([]);
274 return new MalList(array(args[2..$].map!(s => cast(MalType)(new MalString(s)))));
275}
276
277void main(string[] args)
278{
279 Env repl_env = new Env(null);
280 foreach (string sym_name, BuiltinStaticFuncType f; core_ns)
281 {
282 repl_env.set(new MalSymbol(sym_name), new MalBuiltinFunc(f, sym_name));
283 }
284
285 BuiltinFuncType eval_func = (a ...) {
286 verify_args_count(a, 1);
287 return EVAL(a[0], repl_env);
288 };
289 repl_env.set(new MalSymbol("eval"), new MalBuiltinFunc(eval_func, "eval"));
290 repl_env.set(new MalSymbol("*ARGV*"), create_argv_list(args));
291
292 // core.mal: defined using the language itself
2eb69541 293 re("(def! *host-language* \"" ~ std.compiler.name ~ "\")", repl_env);
f82cb965
DM
294 re("(def! not (fn* (a) (if a false true)))", repl_env);
295 re("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env);
296 re("(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)))))))", repl_env);
f82cb965
DM
297
298 if (args.length > 1)
299 {
300 try
301 {
302 rep("(load-file \"" ~ args[1] ~ "\")", repl_env);
303 return;
304 }
305 catch (Exception e)
306 {
307 writeln("Error: ", e.msg);
2eb69541 308 exit(1);
f82cb965
DM
309 }
310 }
311
312 re("(println (str \"Mal [\" *host-language* \"]\"))", repl_env);
313 for (;;)
314 {
315 string line = _readline("user> ");
316 if (line is null) break;
317 if (line.length == 0) continue;
318 try
319 {
320 writeln(rep(line, repl_env));
321 }
dd7a4f55
JM
322 catch (MalException e)
323 {
324 writeln("Error: ", pr_str(e.data));
325 }
f82cb965
DM
326 catch (Exception e)
327 {
328 writeln("Error: ", e.msg);
329 }
330 }
331 writeln("");
332}