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 | GLib.stderr.printf("%s\n", err.message); | |
32 | return null; | |
33 | } | |
34 | } else { | |
35 | stdout.printf("\n"); | |
36 | eof = true; | |
37 | return null; | |
38 | } | |
39 | } | |
40 | ||
41 | public static Mal.Val eval_ast(Mal.Val ast, Mal.Env env) | |
42 | throws Mal.Error { | |
5ff3e2e8 ST |
43 | var roota = new GC.Root(ast); (void)roota; |
44 | var roote = new GC.Root(env); (void)roote; | |
213a3288 ST |
45 | if (ast is Mal.Sym) |
46 | return env.get(ast as Mal.Sym); | |
47 | if (ast is Mal.List) { | |
5ff3e2e8 ST |
48 | var result = new Mal.List.empty(); |
49 | var root = new GC.Root(result); (void)root; | |
213a3288 | 50 | foreach (var elt in (ast as Mal.List).vs) |
5ff3e2e8 ST |
51 | result.vs.append(EVAL(elt, env)); |
52 | return result; | |
213a3288 ST |
53 | } |
54 | if (ast is Mal.Vector) { | |
55 | var results = new GLib.List<Mal.Val>(); | |
54c4ae61 ST |
56 | for (var iter = (ast as Mal.Vector).iter(); |
57 | iter.nonempty(); iter.step()) | |
58 | results.append(EVAL(iter.deref(), env)); | |
213a3288 ST |
59 | return new Mal.Vector.from_list(results); |
60 | } | |
61 | if (ast is Mal.Hashmap) { | |
62 | var result = new Mal.Hashmap(); | |
5ff3e2e8 | 63 | var root = new GC.Root(result); (void)root; |
213a3288 ST |
64 | var map = (ast as Mal.Hashmap).vs; |
65 | foreach (var key in map.get_keys()) | |
66 | result.insert(key, EVAL(map[key], env)); | |
67 | return result; | |
68 | } | |
69 | return ast; | |
70 | } | |
71 | ||
72 | private static Mal.Val define_eval(Mal.Val key, Mal.Val value, | |
73 | Mal.Env eval_env, Mal.Env def_env) | |
74 | throws Mal.Error { | |
5ff3e2e8 ST |
75 | var rootk = new GC.Root(key); (void)rootk; |
76 | var roote = new GC.Root(def_env); (void)roote; | |
213a3288 ST |
77 | var symkey = key as Mal.Sym; |
78 | if (symkey == null) | |
79 | throw new Mal.Error.BAD_PARAMS( | |
80 | "let*: expected a symbol to define"); | |
81 | var val = EVAL(value, eval_env); | |
82 | def_env.set(symkey, val); | |
83 | return val; | |
84 | } | |
85 | ||
86 | public static Mal.Val EVAL(Mal.Val ast_, Mal.Env env_) | |
87 | throws Mal.Error { | |
88 | // Copy the implicitly 'unowned' function arguments into | |
89 | // ordinary owned variables which increment the objects' | |
90 | // reference counts. This is so that when we overwrite these | |
91 | // variables within the loop (for TCO) the objects we assign | |
92 | // into them don't immediately get garbage-collected. | |
93 | Mal.Val ast = ast_; | |
94 | Mal.Env env = env_; | |
5ff3e2e8 ST |
95 | var ast_root = new GC.Root(ast); (void)ast_root; |
96 | var env_root = new GC.Root(env); (void)env_root; | |
213a3288 | 97 | while (true) { |
5ff3e2e8 ST |
98 | ast_root.obj = ast; |
99 | env_root.obj = env; | |
100 | GC.Core.maybe_collect(); | |
213a3288 ST |
101 | if (ast is Mal.List) { |
102 | unowned GLib.List<Mal.Val> list = (ast as Mal.List).vs; | |
103 | if (list.first() == null) | |
104 | return ast; | |
105 | ||
106 | var first = list.first().data; | |
107 | if (first is Mal.Sym) { | |
108 | var sym = first as Mal.Sym; | |
109 | switch (sym.v) { | |
110 | case "def!": | |
111 | if (list.length() != 3) | |
112 | throw new Mal.Error.BAD_PARAMS( | |
113 | "def!: expected two values"); | |
114 | return define_eval(list.next.data, list.next.next.data, | |
115 | env, env); | |
116 | case "let*": | |
117 | if (list.length() != 3) | |
118 | throw new Mal.Error.BAD_PARAMS( | |
119 | "let*: expected two values"); | |
120 | var defns = list.nth(1).data; | |
121 | env = new Mal.Env.within(env); | |
122 | ||
123 | if (defns is Mal.List) { | |
124 | for (unowned GLib.List<Mal.Val> iter = | |
125 | (defns as Mal.List).vs; | |
126 | iter != null; iter = iter.next.next) { | |
127 | if (iter.next == null) | |
128 | throw new Mal.Error.BAD_PARAMS( | |
129 | "let*: expected an even-length list" + | |
130 | " of definitions"); | |
131 | define_eval(iter.data, iter.next.data, | |
132 | env, env); | |
133 | } | |
134 | } else if (defns is Mal.Vector) { | |
54c4ae61 | 135 | var vec = defns as Mal.Vector; |
213a3288 ST |
136 | if (vec.length % 2 != 0) |
137 | throw new Mal.Error.BAD_PARAMS( | |
138 | "let*: expected an even-length vector" + | |
139 | " of definitions"); | |
140 | for (var i = 0; i < vec.length; i += 2) | |
141 | define_eval(vec[i], vec[i+1], env, env); | |
142 | } else { | |
143 | throw new Mal.Error.BAD_PARAMS( | |
144 | "let*: expected a list or vector of definitions"); | |
145 | } | |
146 | ast = list.nth(2).data; | |
147 | continue; // tail-call optimisation | |
148 | case "do": | |
149 | Mal.Val result = null; | |
150 | for (list = list.next; list != null; list = list.next) | |
151 | result = EVAL(list.data, env); | |
152 | if (result == null) | |
153 | throw new Mal.Error.BAD_PARAMS( | |
154 | "do: expected at least one argument"); | |
155 | return result; | |
156 | case "if": | |
157 | if (list.length() != 3 && list.length() != 4) | |
158 | throw new Mal.Error.BAD_PARAMS( | |
159 | "if: expected two or three arguments"); | |
160 | list = list.next; | |
161 | var cond = EVAL(list.data, env); | |
162 | list = list.next; | |
163 | if (!cond.truth_value()) { | |
164 | // Skip to the else clause, which defaults to nil. | |
165 | list = list.next; | |
166 | if (list == null) | |
167 | return new Mal.Nil(); | |
168 | } | |
169 | ast = list.data; | |
170 | continue; // tail-call optimisation | |
171 | case "fn*": | |
172 | if (list.length() != 3) | |
173 | throw new Mal.Error.BAD_PARAMS( | |
174 | "fn*: expected two arguments"); | |
175 | var binds = list.next.data as Mal.Listlike; | |
176 | var body = list.next.next.data; | |
177 | if (binds == null) | |
178 | throw new Mal.Error.BAD_PARAMS( | |
179 | "fn*: expected a list of parameter names"); | |
180 | for (var iter = binds.iter(); iter.nonempty(); iter.step()) | |
181 | if (!(iter.deref() is Mal.Sym)) | |
182 | throw new Mal.Error.BAD_PARAMS( | |
183 | "fn*: expected parameter name to be "+ | |
184 | "symbol"); | |
185 | return new Mal.Function(binds, body, env); | |
186 | } | |
187 | } | |
188 | ||
189 | var newlist = eval_ast(ast, env) as Mal.List; | |
190 | unowned GLib.List<Mal.Val> firstlink = newlist.vs.first(); | |
191 | Mal.Val firstdata = firstlink.data; | |
192 | newlist.vs.remove_link(firstlink); | |
193 | ||
194 | if (firstdata is Mal.BuiltinFunction) { | |
195 | return (firstdata as Mal.BuiltinFunction).call(newlist); | |
196 | } else if (firstdata is Mal.Function) { | |
197 | var fn = firstdata as Mal.Function; | |
198 | env = new Mal.Env.funcall(fn.env, fn.parameters, newlist); | |
199 | ast = fn.body; | |
200 | continue; // tail-call optimisation | |
201 | } else { | |
202 | throw new Mal.Error.CANNOT_APPLY( | |
203 | "bad value at start of list"); | |
204 | } | |
205 | } else { | |
206 | return eval_ast(ast, env); | |
207 | } | |
208 | } | |
209 | } | |
210 | ||
211 | public static void PRINT(Mal.Val value) { | |
212 | stdout.printf("%s\n", pr_str(value)); | |
213 | } | |
214 | ||
215 | public static void rep(Mal.Env env) throws Mal.Error { | |
216 | Mal.Val? val = READ(); | |
217 | if (val != null) { | |
218 | val = EVAL(val, env); | |
219 | PRINT(val); | |
220 | } | |
221 | } | |
222 | ||
223 | public static void setup(string line, Mal.Env env) { | |
224 | try { | |
225 | EVAL(Reader.read_str(line), env); | |
226 | } catch (Mal.Error err) { | |
227 | assert(false); // shouldn't happen | |
228 | } | |
229 | } | |
230 | ||
231 | public static int main(string[] args) { | |
232 | var env = new Mal.Env(); | |
5ff3e2e8 | 233 | var root = new GC.Root(env); (void)root; |
213a3288 ST |
234 | |
235 | Mal.Core.make_ns(); | |
236 | foreach (var key in Mal.Core.ns.get_keys()) | |
237 | env.set(new Mal.Sym(key), Mal.Core.ns[key]); | |
238 | env.set(new Mal.Sym("eval"), new Mal.BuiltinFunctionEval(env)); | |
239 | ||
240 | setup("(def! not (fn* (a) (if a false true)))", env); | |
241 | setup("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env); | |
242 | ||
243 | var ARGV = new GLib.List<Mal.Val>(); | |
244 | if (args.length > 1) { | |
245 | for (int i = args.length - 1; i >= 2; i--) | |
246 | ARGV.prepend(new Mal.String(args[i])); | |
247 | } | |
248 | env.set(new Mal.Sym("*ARGV*"), new Mal.List(ARGV)); | |
249 | ||
250 | if (args.length > 1) { | |
251 | var contents = new GLib.List<Mal.Val>(); | |
252 | contents.prepend(new Mal.String(args[1])); | |
253 | contents.prepend(new Mal.Sym("load-file")); | |
254 | try { | |
255 | EVAL(new Mal.List(contents), env); | |
256 | } catch (Mal.Error err) { | |
257 | GLib.stderr.printf("%s\n", err.message); | |
258 | return 1; | |
259 | } | |
260 | } else { | |
261 | while (!eof) { | |
262 | try { | |
263 | rep(env); | |
264 | } catch (Mal.Error err) { | |
265 | GLib.stderr.printf("%s\n", err.message); | |
266 | } | |
267 | } | |
268 | } | |
269 | return 0; | |
270 | } | |
271 | } |