VB.Net, C#: fix cmd line arg handling with --raw
[jackhill/mal.git] / cs / stepA_interop.cs
1 using System;
2 using System.IO;
3 using System.Collections;
4 using System.Collections.Generic;
5 using Mal;
6 using MalVal = Mal.types.MalVal;
7 using MalString = Mal.types.MalString;
8 using MalSymbol = Mal.types.MalSymbol;
9 using MalInt = Mal.types.MalInt;
10 using MalList = Mal.types.MalList;
11 using MalVector = Mal.types.MalVector;
12 using MalHashMap = Mal.types.MalHashMap;
13 using MalFunc = Mal.types.MalFunc;
14 using Env = Mal.env.Env;
15
16 namespace Mal {
17 class stepA_interop {
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 MalFunc &&
58 ((MalFunc)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 MalFunc mac = (MalFunc) 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 }
139 orig_ast = a2;
140 env = let_env;
141 break;
142 case "quote":
143 return ast[1];
144 case "quasiquote":
145 orig_ast = quasiquote(ast[1]);
146 break;
147 case "defmacro!":
148 a1 = ast[1];
149 a2 = ast[2];
150 res = EVAL(a2, env);
151 ((MalFunc)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 "try*":
158 try {
159 return EVAL(ast[1], env);
160 } catch (Exception e) {
161 if (ast.size() > 2) {
162 MalVal exc;
163 a2 = ast[2];
164 MalVal a20 = ((MalList)a2)[0];
165 if (((MalSymbol)a20).getName() == "catch*") {
166 if (e is Mal.types.MalException) {
167 exc = ((Mal.types.MalException)e).getValue();
168 } else {
169 exc = new MalString(e.StackTrace);
170 }
171 return EVAL(((MalList)a2)[2],
172 new Env(env, ((MalList)a2).slice(1,2),
173 new MalList(exc)));
174 }
175 }
176 throw e;
177 }
178 case "do":
179 eval_ast(ast.slice(1, ast.size()-1), env);
180 orig_ast = ast[ast.size()-1];
181 break;
182 case "if":
183 a1 = ast[1];
184 MalVal cond = EVAL(a1, env);
185 if (cond == Mal.types.Nil || cond == Mal.types.False) {
186 // eval false slot form
187 if (ast.size() > 3) {
188 orig_ast = ast[3];
189 } else {
190 return Mal.types.Nil;
191 }
192 } else {
193 // eval true slot form
194 orig_ast = ast[2];
195 }
196 break;
197 case "fn*":
198 MalList a1f = (MalList)ast[1];
199 MalVal a2f = ast[2];
200 Env cur_env = env;
201 return new MalFunc(a2f, env, a1f,
202 args => EVAL(a2f, new Env(cur_env, a1f, args)) );
203 default:
204 el = (MalList)eval_ast(ast, env);
205 var f = (MalFunc)el[0];
206 MalVal fnast = f.getAst();
207 if (fnast != null) {
208 orig_ast = fnast;
209 env = f.genEnv(el.rest());
210 } else {
211 return f.apply(el.rest());
212 }
213 break;
214 }
215
216 }
217 }
218
219 // print
220 static string PRINT(MalVal exp) {
221 return printer._pr_str(exp, true);
222 }
223
224 // repl
225 static void Main(string[] args) {
226 var repl_env = new Mal.env.Env(null);
227 Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env);
228
229 // core.cs: defined using C#
230 foreach (var entry in core.ns) {
231 repl_env.set(entry.Key, entry.Value);
232 }
233 repl_env.set("eval", new MalFunc(a => EVAL(a[0], repl_env)));
234 int fileIdx = 1;
235 if (args.Length > 0 && args[0] == "--raw") {
236 Mal.readline.mode = Mal.readline.Mode.Raw;
237 fileIdx = 2;
238 }
239 MalList _argv = new MalList();
240 for (int i=fileIdx; i < args.Length; i++) {
241 _argv.conj_BANG(new MalString(args[i]));
242 }
243 repl_env.set("*ARGV*", _argv);
244
245 // core.mal: defined using the language itself
246 RE("(def! *host-language* \"c#\")");
247 RE("(def! not (fn* (a) (if a false true)))");
248 RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
249 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)))))))");
250 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))))))))");
251
252 if (args.Length > fileIdx) {
253 RE("(load-file \"" + args[fileIdx] + "\")");
254 return;
255 }
256
257 // repl loop
258 RE("(println (str \"Mal [\" *host-language* \"]\"))");
259 while (true) {
260 string line;
261 try {
262 line = Mal.readline.Readline("user> ");
263 if (line == null) { break; }
264 if (line == "") { continue; }
265 } catch (IOException e) {
266 Console.WriteLine("IOException: " + e.Message);
267 break;
268 }
269 try {
270 Console.WriteLine(PRINT(RE(line)));
271 } catch (Mal.types.MalContinue) {
272 continue;
273 } catch (Mal.types.MalException e) {
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 }