Merge pull request #383 from asarhaddon/ada2tco-do
[jackhill/mal.git] / vala / step6_file.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 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}