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