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