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