Change quasiquote algorithm
[jackhill/mal.git] / impls / cs / step9_try.cs
CommitLineData
faee4d12
JM
1using System;
2using System.IO;
3using System.Collections;
4using System.Collections.Generic;
5using Mal;
faee4d12
JM
6using MalVal = Mal.types.MalVal;
7using MalString = Mal.types.MalString;
8using MalSymbol = Mal.types.MalSymbol;
c3b508af 9using MalInt = Mal.types.MalInt;
faee4d12
JM
10using MalList = Mal.types.MalList;
11using MalVector = Mal.types.MalVector;
12using MalHashMap = Mal.types.MalHashMap;
c3b508af 13using MalFunc = Mal.types.MalFunc;
faee4d12
JM
14using Env = Mal.env.Env;
15
16namespace 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 // print
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}