Commit | Line | Data |
---|---|---|
faee4d12 JM |
1 | using System; |
2 | using System.IO; | |
3 | using System.Collections; | |
4 | using System.Collections.Generic; | |
5 | using Mal; | |
faee4d12 JM |
6 | using MalVal = Mal.types.MalVal; |
7 | using MalString = Mal.types.MalString; | |
8 | using MalSymbol = Mal.types.MalSymbol; | |
c3b508af | 9 | using MalInt = Mal.types.MalInt; |
faee4d12 JM |
10 | using MalList = Mal.types.MalList; |
11 | using MalVector = Mal.types.MalVector; | |
12 | using MalHashMap = Mal.types.MalHashMap; | |
c3b508af | 13 | using MalFunc = Mal.types.MalFunc; |
faee4d12 JM |
14 | using Env = Mal.env.Env; |
15 | ||
16 | namespace Mal { | |
01c97316 | 17 | class step9_try { |
faee4d12 JM |
18 | // read |
19 | static MalVal READ(string str) { | |
20 | return reader.read_str(str); | |
21 | } | |
22 | ||
23 | // eval | |
fbfe6784 NB |
24 | public static bool starts_with(MalVal ast, string sym) { |
25 | if (ast is MalList && !(ast is MalVector)) { | |
26 | MalList list = (MalList)ast; | |
27 | if (list.size() == 2 && list[0] is MalSymbol) { | |
28 | MalSymbol a0 = (MalSymbol)list[0]; | |
29 | return a0.getName() == sym; | |
30 | } | |
31 | } | |
32 | return false; | |
faee4d12 JM |
33 | } |
34 | ||
fbfe6784 NB |
35 | public static MalVal qq_loop(MalList ast) { |
36 | MalVal acc = new MalList(); | |
37 | for(int i=ast.size()-1; 0<=i; i-=1) { | |
38 | MalVal elt = ast[i]; | |
39 | if (starts_with(elt, "splice-unquote")) { | |
40 | acc = new MalList(new MalSymbol("concat"), ((MalList)elt)[1], acc); | |
41 | } else { | |
42 | acc = new MalList(new MalSymbol("cons"), quasiquote(elt), acc); | |
43 | } | |
44 | } | |
45 | return acc; | |
46 | } | |
faee4d12 | 47 | public static MalVal quasiquote(MalVal ast) { |
fbfe6784 NB |
48 | // Check Vector subclass before List. |
49 | if (ast is MalVector) { | |
50 | return new MalList(new MalSymbol("vec"), qq_loop(((MalList)ast))); | |
51 | } else if (starts_with(ast, "unquote")) { | |
52 | return ((MalList)ast)[1]; | |
53 | } else if (ast is MalList) { | |
54 | return qq_loop((MalList)ast); | |
55 | } else if (ast is MalSymbol || ast is MalHashMap) { | |
faee4d12 JM |
56 | return new MalList(new MalSymbol("quote"), ast); |
57 | } else { | |
fbfe6784 | 58 | return ast; |
faee4d12 JM |
59 | } |
60 | } | |
61 | ||
62 | public static bool is_macro_call(MalVal ast, Env env) { | |
63 | if (ast is MalList) { | |
64 | MalVal a0 = ((MalList)ast)[0]; | |
65 | if (a0 is MalSymbol && | |
b8ee29b2 JM |
66 | env.find((MalSymbol)a0) != null) { |
67 | MalVal mac = env.get((MalSymbol)a0); | |
c3b508af JM |
68 | if (mac is MalFunc && |
69 | ((MalFunc)mac).isMacro()) { | |
faee4d12 JM |
70 | return true; |
71 | } | |
72 | } | |
73 | } | |
74 | return false; | |
75 | } | |
76 | ||
77 | public static MalVal macroexpand(MalVal ast, Env env) { | |
78 | while (is_macro_call(ast, env)) { | |
79 | MalSymbol a0 = (MalSymbol)((MalList)ast)[0]; | |
b8ee29b2 | 80 | MalFunc mac = (MalFunc) env.get(a0); |
faee4d12 JM |
81 | ast = mac.apply(((MalList)ast).rest()); |
82 | } | |
83 | return ast; | |
84 | } | |
85 | ||
86 | static MalVal eval_ast(MalVal ast, Env env) { | |
87 | if (ast is MalSymbol) { | |
b8ee29b2 | 88 | return env.get((MalSymbol)ast); |
faee4d12 JM |
89 | } else if (ast is MalList) { |
90 | MalList old_lst = (MalList)ast; | |
91 | MalList new_lst = ast.list_Q() ? new MalList() | |
92 | : (MalList)new MalVector(); | |
93 | foreach (MalVal mv in old_lst.getValue()) { | |
94 | new_lst.conj_BANG(EVAL(mv, env)); | |
95 | } | |
96 | return new_lst; | |
97 | } else if (ast is MalHashMap) { | |
98 | var new_dict = new Dictionary<string, MalVal>(); | |
99 | foreach (var entry in ((MalHashMap)ast).getValue()) { | |
100 | new_dict.Add(entry.Key, EVAL((MalVal)entry.Value, env)); | |
101 | } | |
102 | return new MalHashMap(new_dict); | |
103 | } else { | |
104 | return ast; | |
105 | } | |
106 | } | |
107 | ||
108 | ||
109 | static MalVal EVAL(MalVal orig_ast, Env env) { | |
110 | MalVal a0, a1, a2, res; | |
111 | MalList el; | |
112 | ||
113 | while (true) { | |
114 | ||
b8ee29b2 | 115 | //Console.WriteLine("EVAL: " + printer._pr_str(orig_ast, true)); |
faee4d12 JM |
116 | if (!orig_ast.list_Q()) { |
117 | return eval_ast(orig_ast, env); | |
118 | } | |
119 | ||
120 | // apply list | |
121 | MalVal expanded = macroexpand(orig_ast, env); | |
36e287b5 JM |
122 | if (!expanded.list_Q()) { |
123 | return eval_ast(expanded, env); | |
124 | } | |
faee4d12 JM |
125 | MalList ast = (MalList) expanded; |
126 | ||
127 | if (ast.size() == 0) { return ast; } | |
128 | a0 = ast[0]; | |
129 | ||
130 | String a0sym = a0 is MalSymbol ? ((MalSymbol)a0).getName() | |
131 | : "__<*fn*>__"; | |
132 | ||
133 | switch (a0sym) { | |
134 | case "def!": | |
135 | a1 = ast[1]; | |
136 | a2 = ast[2]; | |
137 | res = EVAL(a2, env); | |
b8ee29b2 | 138 | env.set((MalSymbol)a1, res); |
faee4d12 JM |
139 | return res; |
140 | case "let*": | |
141 | a1 = ast[1]; | |
142 | a2 = ast[2]; | |
143 | MalSymbol key; | |
144 | MalVal val; | |
145 | Env let_env = new Env(env); | |
146 | for(int i=0; i<((MalList)a1).size(); i+=2) { | |
147 | key = (MalSymbol)((MalList)a1)[i]; | |
148 | val = ((MalList)a1)[i+1]; | |
b8ee29b2 | 149 | let_env.set(key, EVAL(val, let_env)); |
faee4d12 | 150 | } |
6301e0b6 JM |
151 | orig_ast = a2; |
152 | env = let_env; | |
153 | break; | |
faee4d12 JM |
154 | case "quote": |
155 | return ast[1]; | |
fbfe6784 NB |
156 | case "quasiquoteexpand": |
157 | return quasiquote(ast[1]); | |
faee4d12 | 158 | case "quasiquote": |
6301e0b6 JM |
159 | orig_ast = quasiquote(ast[1]); |
160 | break; | |
faee4d12 JM |
161 | case "defmacro!": |
162 | a1 = ast[1]; | |
163 | a2 = ast[2]; | |
164 | res = EVAL(a2, env); | |
c3b508af | 165 | ((MalFunc)res).setMacro(); |
b8ee29b2 | 166 | env.set(((MalSymbol)a1), res); |
faee4d12 JM |
167 | return res; |
168 | case "macroexpand": | |
169 | a1 = ast[1]; | |
170 | return macroexpand(a1, env); | |
171 | case "try*": | |
172 | try { | |
173 | return EVAL(ast[1], env); | |
174 | } catch (Exception e) { | |
175 | if (ast.size() > 2) { | |
176 | MalVal exc; | |
177 | a2 = ast[2]; | |
178 | MalVal a20 = ((MalList)a2)[0]; | |
179 | if (((MalSymbol)a20).getName() == "catch*") { | |
8cb5cda4 JM |
180 | if (e is Mal.types.MalException) { |
181 | exc = ((Mal.types.MalException)e).getValue(); | |
faee4d12 | 182 | } else { |
6c4cc8ad | 183 | exc = new MalString(e.Message); |
faee4d12 JM |
184 | } |
185 | return EVAL(((MalList)a2)[2], | |
186 | new Env(env, ((MalList)a2).slice(1,2), | |
187 | new MalList(exc))); | |
188 | } | |
189 | } | |
190 | throw e; | |
191 | } | |
192 | case "do": | |
193 | eval_ast(ast.slice(1, ast.size()-1), env); | |
194 | orig_ast = ast[ast.size()-1]; | |
195 | break; | |
196 | case "if": | |
197 | a1 = ast[1]; | |
198 | MalVal cond = EVAL(a1, env); | |
8cb5cda4 | 199 | if (cond == Mal.types.Nil || cond == Mal.types.False) { |
faee4d12 JM |
200 | // eval false slot form |
201 | if (ast.size() > 3) { | |
202 | orig_ast = ast[3]; | |
203 | } else { | |
8cb5cda4 | 204 | return Mal.types.Nil; |
faee4d12 JM |
205 | } |
206 | } else { | |
207 | // eval true slot form | |
208 | orig_ast = ast[2]; | |
209 | } | |
210 | break; | |
211 | case "fn*": | |
212 | MalList a1f = (MalList)ast[1]; | |
213 | MalVal a2f = ast[2]; | |
214 | Env cur_env = env; | |
c3b508af | 215 | return new MalFunc(a2f, env, a1f, |
faee4d12 JM |
216 | args => EVAL(a2f, new Env(cur_env, a1f, args)) ); |
217 | default: | |
218 | el = (MalList)eval_ast(ast, env); | |
c3b508af | 219 | var f = (MalFunc)el[0]; |
faee4d12 JM |
220 | MalVal fnast = f.getAst(); |
221 | if (fnast != null) { | |
222 | orig_ast = fnast; | |
223 | env = f.genEnv(el.rest()); | |
224 | } else { | |
225 | return f.apply(el.rest()); | |
226 | } | |
227 | break; | |
228 | } | |
229 | ||
230 | } | |
231 | } | |
232 | ||
233 | ||
234 | static string PRINT(MalVal exp) { | |
235 | return printer._pr_str(exp, true); | |
236 | } | |
237 | ||
86b689f3 | 238 | // repl |
faee4d12 | 239 | static void Main(string[] args) { |
c3b508af JM |
240 | var repl_env = new Mal.env.Env(null); |
241 | Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env); | |
faee4d12 | 242 | |
8cb5cda4 | 243 | // core.cs: defined using C# |
faee4d12 | 244 | foreach (var entry in core.ns) { |
b8ee29b2 | 245 | repl_env.set(new MalSymbol(entry.Key), entry.Value); |
faee4d12 | 246 | } |
b8ee29b2 JM |
247 | repl_env.set(new MalSymbol("eval"), new MalFunc( |
248 | a => EVAL(a[0], repl_env))); | |
dbbac62f | 249 | int fileIdx = 0; |
aaba2493 JM |
250 | if (args.Length > 0 && args[0] == "--raw") { |
251 | Mal.readline.mode = Mal.readline.Mode.Raw; | |
dbbac62f | 252 | fileIdx = 1; |
aaba2493 | 253 | } |
86b689f3 | 254 | MalList _argv = new MalList(); |
dbbac62f | 255 | for (int i=fileIdx+1; i < args.Length; i++) { |
86b689f3 JM |
256 | _argv.conj_BANG(new MalString(args[i])); |
257 | } | |
b8ee29b2 | 258 | repl_env.set(new MalSymbol("*ARGV*"), _argv); |
faee4d12 | 259 | |
8cb5cda4 | 260 | // core.mal: defined using the language itself |
c3b508af | 261 | RE("(def! not (fn* (a) (if a false true)))"); |
e6d41de4 | 262 | RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))"); |
c3b508af | 263 | 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)))))))"); |
faee4d12 | 264 | |
faee4d12 | 265 | if (args.Length > fileIdx) { |
c3b508af | 266 | RE("(load-file \"" + args[fileIdx] + "\")"); |
faee4d12 JM |
267 | return; |
268 | } | |
c3b508af | 269 | |
86b689f3 | 270 | // repl loop |
faee4d12 JM |
271 | while (true) { |
272 | string line; | |
273 | try { | |
c3b508af | 274 | line = Mal.readline.Readline("user> "); |
faee4d12 | 275 | if (line == null) { break; } |
c3b508af | 276 | if (line == "") { continue; } |
faee4d12 JM |
277 | } catch (IOException e) { |
278 | Console.WriteLine("IOException: " + e.Message); | |
279 | break; | |
280 | } | |
281 | try { | |
c3b508af | 282 | Console.WriteLine(PRINT(RE(line))); |
8cb5cda4 | 283 | } catch (Mal.types.MalContinue) { |
faee4d12 | 284 | continue; |
8cb5cda4 | 285 | } catch (Mal.types.MalException e) { |
faee4d12 JM |
286 | Console.WriteLine("Error: " + |
287 | printer._pr_str(e.getValue(), false)); | |
288 | continue; | |
289 | } catch (Exception e) { | |
290 | Console.WriteLine("Error: " + e.Message); | |
291 | Console.WriteLine(e.StackTrace); | |
292 | continue; | |
293 | } | |
294 | } | |
295 | } | |
296 | } | |
297 | } |