Commit | Line | Data |
---|---|---|
213a3288 ST |
1 | class 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 | ||
15 | class 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 | } |