Merge pull request #383 from asarhaddon/ada2tco-do
[jackhill/mal.git] / vala / stepA_mal.vala
CommitLineData
213a3288
ST
1class Mal.BuiltinFunctionEval : Mal.BuiltinFunction {
2 public Mal.Env env;
3 public BuiltinFunctionEval(Mal.Env env_) { env = env_; }
4 public override Mal.ValWithMetadata copy() {
5 return new Mal.BuiltinFunctionEval(env);
6 }
7 public override string name() { return "eval"; }
8 public override Mal.Val call(Mal.List args) throws Mal.Error {
9 if (args.vs.length() != 1)
10 throw new Mal.Error.BAD_PARAMS("%s: expected one argument", name());
11 return Mal.Main.EVAL(args.vs.data, env);
12 }
13}
14
15class Mal.Main : GLib.Object {
16 static bool eof;
17
18 static construct {
19 eof = false;
20 }
21
22 public static Mal.Val? READ() {
23 string? line = Readline.readline("user> ");
24 if (line != null) {
25 if (line.length > 0)
26 Readline.History.add(line);
27
28 try {
29 return Reader.read_str(line);
30 } catch (Mal.Error err) {
31 Mal.BuiltinFunctionThrow.clear();
32 GLib.stderr.printf("%s\n", err.message);
33 return null;
34 }
35 } else {
36 stdout.printf("\n");
37 eof = true;
38 return null;
39 }
40 }
41
42 public static Mal.Val eval_ast(Mal.Val ast, Mal.Env env)
43 throws Mal.Error {
5ff3e2e8
ST
44 var roota = new GC.Root(ast); (void)roota;
45 var roote = new GC.Root(env); (void)roote;
213a3288
ST
46 if (ast is Mal.Sym)
47 return env.get(ast as Mal.Sym);
48 if (ast is Mal.List) {
5ff3e2e8
ST
49 var result = new Mal.List.empty();
50 var root = new GC.Root(result); (void)root;
213a3288 51 foreach (var elt in (ast as Mal.List).vs)
5ff3e2e8
ST
52 result.vs.append(EVAL(elt, env));
53 return result;
213a3288
ST
54 }
55 if (ast is Mal.Vector) {
56 var results = new GLib.List<Mal.Val>();
54c4ae61
ST
57 for (var iter = (ast as Mal.Vector).iter();
58 iter.nonempty(); iter.step())
59 results.append(EVAL(iter.deref(), env));
213a3288
ST
60 return new Mal.Vector.from_list(results);
61 }
62 if (ast is Mal.Hashmap) {
63 var result = new Mal.Hashmap();
5ff3e2e8 64 var root = new GC.Root(result); (void)root;
213a3288
ST
65 var map = (ast as Mal.Hashmap).vs;
66 foreach (var key in map.get_keys())
67 result.insert(key, EVAL(map[key], env));
68 return result;
69 }
70 return ast;
71 }
72
73 private static Mal.Val define_eval(Mal.Val key, Mal.Val value,
74 Mal.Env eval_env, Mal.Env def_env,
75 bool is_macro = false)
76 throws Mal.Error {
5ff3e2e8
ST
77 var rootk = new GC.Root(key); (void)rootk;
78 var roote = new GC.Root(def_env); (void)roote;
213a3288
ST
79 var symkey = key as Mal.Sym;
80 if (symkey == null)
81 throw new Mal.Error.BAD_PARAMS(
82 "let*: expected a symbol to define");
83 var val = EVAL(value, eval_env);
84 if (val is Mal.Function)
85 (val as Mal.Function).is_macro = is_macro;
86 def_env.set(symkey, val);
87 return val;
88 }
89
90 public static Mal.Val quasiquote(Mal.Val ast)
91 throws Mal.Error {
92 if (!is_pair(ast)) {
93 var list = new GLib.List<Mal.Val>();
94 list.append(new Mal.Sym("quote"));
95 list.append(ast);
96 return new Mal.List(list);
97 }
98
99 var iter = (ast as Mal.Listlike).iter();
100 var first = iter.deref();
101 if (first is Mal.Sym && (first as Mal.Sym).v == "unquote") {
102 if (iter.step().empty())
103 throw new Mal.Error.BAD_PARAMS(
104 "unquote: expected two values");
105 return iter.deref();
106 }
107
108 if (is_pair(first)) {
109 var fiter = (first as Mal.Listlike).iter();
110 var ffirst = fiter.deref();
111 if (ffirst is Mal.Sym &&
112 (ffirst as Mal.Sym).v == "splice-unquote") {
113 var list = new GLib.List<Mal.Val>();
114 list.append(new Mal.Sym("concat"));
115 if (fiter.step().empty())
116 throw new Mal.Error.BAD_PARAMS(
117 "unquote: expected two values");
118 list.append(fiter.deref());
119 var sublist = new GLib.List<Mal.Val>();
120 while (!iter.step().empty())
121 sublist.append(iter.deref());
122 list.append(quasiquote(new Mal.List(sublist)));
123 return new Mal.List(list);
124 }
125 }
126
127 var list = new GLib.List<Mal.Val>();
128 list.append(new Mal.Sym("cons"));
129 list.append(quasiquote(first));
130 var sublist = new GLib.List<Mal.Val>();
131 while (!iter.step().empty())
132 sublist.append(iter.deref());
133 list.append(quasiquote(new Mal.List(sublist)));
134 return new Mal.List(list);
135 }
136
137 public static bool is_macro_call(Mal.Val v, Mal.Env env)
138 throws Mal.Error {
139 var list = v as Mal.List;
140 if (list == null || list.vs == null || !(list.vs.data is Mal.Sym))
141 return false;
142 try {
143 var fn = env.get(list.vs.data as Mal.Sym) as Mal.Function;
144 return (fn != null && fn.is_macro);
145 } catch (Mal.Error.ENV_LOOKUP_FAILED err) {
146 return false;
147 }
148 }
149
150 public static Mal.Val macroexpand(Mal.Val ast_, Mal.Env env)
151 throws Mal.Error {
152 // Copy the parameter into an owned variable (see comment in EVAL).
153 Mal.Val ast = ast_;
154 while (is_macro_call(ast, env)) {
155 var call = ast as Mal.List;
156 var macro = (env.get(call.vs.data as Mal.Sym) as Mal.Function);
157 var macroargs = new Mal.List(call.vs.copy());
158 macroargs.vs.remove_link(macroargs.vs.first());
159 var fnenv = new Mal.Env.funcall(
160 macro.env, macro.parameters, macroargs);
161 ast = Mal.Main.EVAL(macro.body, fnenv);
162 }
163 return ast;
164 }
165
166 public static Mal.Val EVAL(Mal.Val ast_, Mal.Env env_)
167 throws Mal.Error {
168 // Copy the implicitly 'unowned' function arguments into
169 // ordinary owned variables which increment the objects'
170 // reference counts. This is so that when we overwrite these
171 // variables within the loop (for TCO) the objects we assign
172 // into them don't immediately get garbage-collected.
173 Mal.Val ast = ast_;
174 Mal.Env env = env_;
5ff3e2e8
ST
175 var ast_root = new GC.Root(ast); (void)ast_root;
176 var env_root = new GC.Root(env); (void)env_root;
213a3288 177 while (true) {
5ff3e2e8
ST
178 ast_root.obj = ast;
179 env_root.obj = env;
180 GC.Core.maybe_collect();
213a3288 181 ast = macroexpand(ast, env);
5ff3e2e8 182 ast_root.obj = ast;
213a3288
ST
183 if (ast is Mal.List) {
184 unowned GLib.List<Mal.Val> list = (ast as Mal.List).vs;
185 if (list.first() == null)
186 return ast;
187
188 var first = list.first().data;
189 if (first is Mal.Sym) {
190 var sym = first as Mal.Sym;
191 switch (sym.v) {
192 case "def!":
193 case "defmacro!":
194 if (list.length() != 3)
195 throw new Mal.Error.BAD_PARAMS(
196 "def!: expected two values");
197 return define_eval(list.next.data, list.next.next.data,
198 env, env, sym.v == "defmacro!");
199 case "let*":
200 if (list.length() != 3)
201 throw new Mal.Error.BAD_PARAMS(
202 "let*: expected two values");
203 var defns = list.nth(1).data;
204 env = new Mal.Env.within(env);
205
206 if (defns is Mal.List) {
207 for (unowned GLib.List<Mal.Val> iter =
208 (defns as Mal.List).vs;
209 iter != null; iter = iter.next.next) {
210 if (iter.next == null)
211 throw new Mal.Error.BAD_PARAMS(
212 "let*: expected an even-length list" +
213 " of definitions");
214 define_eval(iter.data, iter.next.data,
215 env, env);
216 }
217 } else if (defns is Mal.Vector) {
54c4ae61 218 var vec = defns as Mal.Vector;
213a3288
ST
219 if (vec.length % 2 != 0)
220 throw new Mal.Error.BAD_PARAMS(
221 "let*: expected an even-length vector" +
222 " of definitions");
223 for (var i = 0; i < vec.length; i += 2)
224 define_eval(vec[i], vec[i+1], env, env);
225 } else {
226 throw new Mal.Error.BAD_PARAMS(
227 "let*: expected a list or vector of definitions");
228 }
229 ast = list.nth(2).data;
230 continue; // tail-call optimisation
231 case "do":
232 Mal.Val result = null;
233 for (list = list.next; list != null; list = list.next)
234 result = EVAL(list.data, env);
235 if (result == null)
236 throw new Mal.Error.BAD_PARAMS(
237 "do: expected at least one argument");
238 return result;
239 case "if":
240 if (list.length() != 3 && list.length() != 4)
241 throw new Mal.Error.BAD_PARAMS(
242 "if: expected two or three arguments");
243 list = list.next;
244 var cond = EVAL(list.data, env);
245 list = list.next;
246 if (!cond.truth_value()) {
247 // Skip to the else clause, which defaults to nil.
248 list = list.next;
249 if (list == null)
250 return new Mal.Nil();
251 }
252 ast = list.data;
253 continue; // tail-call optimisation
254 case "fn*":
255 if (list.length() != 3)
256 throw new Mal.Error.BAD_PARAMS(
257 "fn*: expected two arguments");
258 var binds = list.next.data as Mal.Listlike;
259 var body = list.next.next.data;
260 if (binds == null)
261 throw new Mal.Error.BAD_PARAMS(
262 "fn*: expected a list of parameter names");
263 for (var iter = binds.iter(); iter.nonempty();
264 iter.step())
265 if (!(iter.deref() is Mal.Sym))
266 throw new Mal.Error.BAD_PARAMS(
267 "fn*: expected parameter name to be "+
268 "symbol");
269 return new Mal.Function(binds, body, env);
270 case "quote":
271 if (list.length() != 2)
272 throw new Mal.Error.BAD_PARAMS(
273 "quote: expected one argument");
274 return list.next.data;
275 case "quasiquote":
276 if (list.length() != 2)
277 throw new Mal.Error.BAD_PARAMS(
278 "quasiquote: expected one argument");
279 ast = quasiquote(list.next.data);
280 continue; // tail-call optimisation
281 case "macroexpand":
282 if (list.length() != 2)
283 throw new Mal.Error.BAD_PARAMS(
284 "macroexpand: expected one argument");
285 return macroexpand(list.next.data, env);
286 case "try*":
287 if (list.length() != 2 && list.length() != 3)
288 throw new Mal.Error.BAD_PARAMS(
289 "try*: expected one or two arguments");
290 var trybody = list.next.data;
291 if (list.length() == 2) {
292 // Trivial catchless form of try
293 ast = trybody;
294 continue; // tail-call optimisation
295 }
296 var catchclause = list.next.next.data as Mal.List;
297 if (!(catchclause.vs.data is Mal.Sym) ||
298 (catchclause.vs.data as Mal.Sym).v != "catch*")
299 throw new Mal.Error.BAD_PARAMS(
300 "try*: expected catch*");
301 if (catchclause.vs.length() != 3)
302 throw new Mal.Error.BAD_PARAMS(
303 "catch*: expected two arguments");
304 var catchparam = catchclause.vs.next.data as Mal.Sym;
305 if (catchparam == null)
306 throw new Mal.Error.BAD_PARAMS(
307 "catch*: expected a parameter name");
308 var catchbody = catchclause.vs.next.next.data;
309 try {
310 return EVAL(trybody, env);
311 } catch (Mal.Error exc) {
312 var catchenv = new Mal.Env.within(env);
313 catchenv.set(catchparam, Mal.BuiltinFunctionThrow.
314 thrown_value(exc));
315 ast = catchbody;
316 env = catchenv;
317 continue; // tail-call optimisation
318 }
319 }
320 }
321
322 var newlist = eval_ast(ast, env) as Mal.List;
323 unowned GLib.List<Mal.Val> firstlink = newlist.vs.first();
324 Mal.Val firstdata = firstlink.data;
325 newlist.vs.remove_link(firstlink);
326
327 if (firstdata is Mal.BuiltinFunction) {
328 return (firstdata as Mal.BuiltinFunction).call(newlist);
329 } else if (firstdata is Mal.Function) {
330 var fn = firstdata as Mal.Function;
331 env = new Mal.Env.funcall(fn.env, fn.parameters, newlist);
332 ast = fn.body;
333 continue; // tail-call optimisation
334 } else {
335 throw new Mal.Error.CANNOT_APPLY(
336 "bad value at start of list");
337 }
338 } else {
339 return eval_ast(ast, env);
340 }
341 }
342 }
343
344 public static void PRINT(Mal.Val value) {
345 stdout.printf("%s\n", pr_str(value));
346 }
347
348 public static void rep(Mal.Env env) throws Mal.Error {
349 Mal.Val? val = READ();
350 if (val != null) {
351 val = EVAL(val, env);
352 PRINT(val);
353 }
354 }
355
356 public static void setup(string line, Mal.Env env) {
357 try {
358 EVAL(Reader.read_str(line), env);
359 } catch (Mal.Error err) {
360 stderr.printf("Error during setup:\n%s\n-> %s\n",
361 line, err.message);
362 GLib.Process.exit(1);
363 }
364 }
365
366 public static int main(string[] args) {
367 var env = new Mal.Env();
5ff3e2e8 368 var root = new GC.Root(env); (void)root;
213a3288
ST
369
370 Mal.Core.make_ns();
371 foreach (var key in Mal.Core.ns.get_keys())
372 env.set(new Mal.Sym(key), Mal.Core.ns[key]);
373 env.set(new Mal.Sym("eval"), new Mal.BuiltinFunctionEval(env));
374 env.set(new Mal.Sym("*host-language*"), new Mal.String("vala"));
375
376 setup("(def! not (fn* (a) (if a false true)))", env);
377 setup("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env);
378 setup("(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)))))))", env);
14ab099c
NB
379 setup("(def! inc (fn* [x] (+ x 1)))", env);
380 setup("(def! gensym (let* [counter (atom 0)] (fn* [] (symbol (str \"G__\" (swap! counter inc))))))", env);
213a3288
ST
381 setup("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))", env);
382
383 var ARGV = new GLib.List<Mal.Val>();
384 if (args.length > 1) {
385 for (int i = args.length - 1; i >= 2; i--)
386 ARGV.prepend(new Mal.String(args[i]));
387 }
388 env.set(new Mal.Sym("*ARGV*"), new Mal.List(ARGV));
389
390 if (args.length > 1) {
391 var contents = new GLib.List<Mal.Val>();
392 contents.prepend(new Mal.String(args[1]));
393 contents.prepend(new Mal.Sym("load-file"));
394 try {
395 EVAL(new Mal.List(contents), env);
396 } catch (Mal.Error.EXCEPTION_THROWN exc) {
397 GLib.stderr.printf(
398 "uncaught exception: %s\n",
399 pr_str(Mal.BuiltinFunctionThrow.thrown_value(exc)));
400 } catch (Mal.Error err) {
401 GLib.stderr.printf("%s\n", err.message);
402 return 1;
403 }
404 } else {
405 setup("(println (str \"Mal [\" *host-language* \"]\"))", env);
406 while (!eof) {
407 try {
408 rep(env);
409 } catch (Mal.Error.EXCEPTION_THROWN exc) {
410 GLib.stderr.printf(
411 "uncaught exception: %s\n",
412 pr_str(Mal.BuiltinFunctionThrow.thrown_value(exc)));
413 } catch (Mal.Error err) {
414 GLib.stderr.printf("%s\n", err.message);
415 }
416 }
417 }
418 return 0;
419 }
420}