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