Merge commit 'e47ddca2f8d80145386a377fc81a738d89c46cf0'
[jackhill/mal.git] / cs / step8_macros.cs
CommitLineData
faa20db2
JM
1using System;
2using System.IO;
3using System.Collections;
4using System.Collections.Generic;
5using Mal;
6using MalVal = Mal.types.MalVal;
7using MalString = Mal.types.MalString;
8using MalSymbol = Mal.types.MalSymbol;
9using MalInteger = Mal.types.MalInteger;
10using MalList = Mal.types.MalList;
11using MalVector = Mal.types.MalVector;
12using MalHashMap = Mal.types.MalHashMap;
13using MalFunction = Mal.types.MalFunction;
14using Env = Mal.env.Env;
15
16namespace Mal {
8cb5cda4 17 class step8_macros {
faa20db2
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 &&
55 env.find(((MalSymbol)a0).getName()) != null) {
56 MalVal mac = env.get(((MalSymbol)a0).getName());
57 if (mac is MalFunction &&
58 ((MalFunction)mac).isMacro()) {
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];
69 MalFunction mac = (MalFunction) env.get(a0.getName());
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) {
77 MalSymbol sym = (MalSymbol)ast;
78 return env.get(sym.getName());
79 } else if (ast is MalList) {
80 MalList old_lst = (MalList)ast;
81 MalList new_lst = ast.list_Q() ? new MalList()
82 : (MalList)new MalVector();
83 foreach (MalVal mv in old_lst.getValue()) {
84 new_lst.conj_BANG(EVAL(mv, env));
85 }
86 return new_lst;
87 } else if (ast is MalHashMap) {
88 var new_dict = new Dictionary<string, MalVal>();
89 foreach (var entry in ((MalHashMap)ast).getValue()) {
90 new_dict.Add(entry.Key, EVAL((MalVal)entry.Value, env));
91 }
92 return new MalHashMap(new_dict);
93 } else {
94 return ast;
95 }
96 }
97
98
99 static MalVal EVAL(MalVal orig_ast, Env env) {
100 MalVal a0, a1, a2, res;
101 MalList el;
102
103 while (true) {
104
105 //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
106 if (!orig_ast.list_Q()) {
107 return eval_ast(orig_ast, env);
108 }
109
110 // apply list
111 MalVal expanded = macroexpand(orig_ast, env);
112 if (!expanded.list_Q()) { return expanded; }
113 MalList ast = (MalList) expanded;
114
115 if (ast.size() == 0) { return ast; }
116 a0 = ast[0];
117
118 String a0sym = a0 is MalSymbol ? ((MalSymbol)a0).getName()
119 : "__<*fn*>__";
120
121 switch (a0sym) {
122 case "def!":
123 a1 = ast[1];
124 a2 = ast[2];
125 res = EVAL(a2, env);
126 env.set(((MalSymbol)a1).getName(), res);
127 return res;
128 case "let*":
129 a1 = ast[1];
130 a2 = ast[2];
131 MalSymbol key;
132 MalVal val;
133 Env let_env = new Env(env);
134 for(int i=0; i<((MalList)a1).size(); i+=2) {
135 key = (MalSymbol)((MalList)a1)[i];
136 val = ((MalList)a1)[i+1];
137 let_env.set(key.getName(), EVAL(val, let_env));
138 }
6301e0b6
JM
139 orig_ast = a2;
140 env = let_env;
141 break;
faa20db2
JM
142 case "quote":
143 return ast[1];
144 case "quasiquote":
6301e0b6
JM
145 orig_ast = quasiquote(ast[1]);
146 break;
faa20db2
JM
147 case "defmacro!":
148 a1 = ast[1];
149 a2 = ast[2];
150 res = EVAL(a2, env);
151 ((MalFunction)res).setMacro();
152 env.set(((MalSymbol)a1).getName(), res);
153 return res;
154 case "macroexpand":
155 a1 = ast[1];
156 return macroexpand(a1, env);
157 case "do":
158 eval_ast(ast.slice(1, ast.size()-1), env);
159 orig_ast = ast[ast.size()-1];
160 break;
161 case "if":
162 a1 = ast[1];
163 MalVal cond = EVAL(a1, env);
164 if (cond == Mal.types.Nil || cond == Mal.types.False) {
165 // eval false slot form
166 if (ast.size() > 3) {
167 orig_ast = ast[3];
168 } else {
169 return Mal.types.Nil;
170 }
171 } else {
172 // eval true slot form
173 orig_ast = ast[2];
174 }
175 break;
176 case "fn*":
177 MalList a1f = (MalList)ast[1];
178 MalVal a2f = ast[2];
179 Env cur_env = env;
180 return new MalFunction(a2f, env, a1f,
181 args => EVAL(a2f, new Env(cur_env, a1f, args)) );
182 default:
183 el = (MalList)eval_ast(ast, env);
184 var f = (MalFunction)el[0];
185 MalVal fnast = f.getAst();
186 if (fnast != null) {
187 orig_ast = fnast;
188 env = f.genEnv(el.rest());
189 } else {
190 return f.apply(el.rest());
191 }
192 break;
193 }
194
195 }
196 }
197
198 // print
199 static string PRINT(MalVal exp) {
200 return printer._pr_str(exp, true);
201 }
202
86b689f3 203 // repl
faa20db2
JM
204 static MalVal RE(Env env, string str) {
205 return EVAL(READ(str), env);
206 }
faa20db2
JM
207
208 static void Main(string[] args) {
209 string prompt = "user> ";
210
8cb5cda4
JM
211 // core.cs: defined using C#
212 var repl_env = new env.Env(null);
213 foreach (var entry in core.ns) {
214 repl_env.set(entry.Key, entry.Value);
faa20db2 215 }
8cb5cda4 216 repl_env.set("eval", new MalFunction(a => EVAL(a[0], repl_env)));
86b689f3
JM
217 MalList _argv = new MalList();
218 for (int i=1; i < args.Length; i++) {
219 _argv.conj_BANG(new MalString(args[i]));
220 }
221 repl_env.set("*ARGV*", _argv);
faa20db2 222
8cb5cda4 223 // core.mal: defined using the language itself
faa20db2
JM
224 RE(repl_env, "(def! not (fn* (a) (if a false true)))");
225 RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
8cb5cda4
JM
226 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)))))))");
227 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))))))))");
faa20db2
JM
228
229 int fileIdx = 0;
230 if (args.Length > 0 && args[0] == "--raw") {
231 Mal.readline.mode = Mal.readline.Mode.Raw;
232 fileIdx = 1;
233 }
234 if (args.Length > fileIdx) {
86b689f3 235 RE(repl_env, "(load-file \"" + args[fileIdx] + "\")");
faa20db2
JM
236 return;
237 }
86b689f3
JM
238
239 // repl loop
faa20db2
JM
240 while (true) {
241 string line;
242 try {
243 line = Mal.readline.Readline(prompt);
244 if (line == null) { break; }
245 } catch (IOException e) {
246 Console.WriteLine("IOException: " + e.Message);
247 break;
248 }
249 try {
250 Console.WriteLine(PRINT(RE(repl_env, line)));
251 } catch (Mal.types.MalContinue) {
252 continue;
faa20db2
JM
253 } catch (Exception e) {
254 Console.WriteLine("Error: " + e.Message);
255 Console.WriteLine(e.StackTrace);
256 continue;
257 }
258 }
259 }
260 }
261}