Current state of mal for Clojure West lighting talk.
[jackhill/mal.git] / java / src / main / java / mal / stepA_more.java
1 package mal;
2
3 import java.io.IOException;
4 import java.io.FileNotFoundException;
5
6 import java.util.Scanner;
7 import java.io.File;
8 import java.io.StringWriter;
9 import java.io.PrintWriter;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.HashMap;
13 import java.util.Iterator;
14 import mal.types.*;
15 import mal.readline;
16 import mal.reader;
17
18 public class stepA_more {
19 // read
20 public static MalVal READ(String str) throws MalThrowable {
21 return reader.read_str(str);
22 }
23
24 // eval
25 public static Boolean is_pair(MalVal x) {
26 return x instanceof 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).nth(0);
34 if ((a0 instanceof MalSymbol) &&
35 (((MalSymbol)a0).getName() == "unquote")) {
36 return ((MalList)ast).nth(1);
37 } else if (is_pair(a0)) {
38 MalVal a00 = ((MalList)a0).nth(0);
39 if ((a00 instanceof MalSymbol) &&
40 (((MalSymbol)a00).getName() == "splice-unquote")) {
41 return new MalList(new MalSymbol("concat"),
42 ((MalList)a0).nth(1),
43 quasiquote(types._rest((MalList)ast)));
44 }
45 }
46 return new MalList(new MalSymbol("cons"),
47 quasiquote(a0),
48 quasiquote(types._rest((MalList)ast)));
49 }
50 }
51
52 public static Boolean is_macro_call(MalVal ast, Env env)
53 throws MalThrowable {
54 if (ast instanceof MalList) {
55 MalVal a0 = ((MalList)ast).nth(0);
56 if (a0 instanceof MalSymbol &&
57 env.find(((MalSymbol)a0).getName()) != null) {
58 MalVal mac = env.get(((MalSymbol)a0).getName());
59 if (mac instanceof MalFunction &&
60 ((MalFunction)mac).isMacro()) {
61 return true;
62 }
63 }
64 }
65 return false;
66 }
67
68 public static MalVal macroexpand(MalVal ast, Env env)
69 throws MalThrowable {
70 while (is_macro_call(ast, env)) {
71 MalSymbol a0 = (MalSymbol)((MalList)ast).nth(0);
72 MalFunction mac = (MalFunction) env.get(a0.getName());
73 ast = mac.apply(types._rest((MalList)ast));
74 }
75 return ast;
76 }
77
78 public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
79 if (ast instanceof MalSymbol) {
80 MalSymbol sym = (MalSymbol)ast;
81 return env.get(sym.getName());
82 } else if (ast instanceof MalList) {
83 MalList old_lst = (MalList)ast;
84 MalList new_lst = types._list_Q(ast) ? new MalList()
85 : (MalList)new MalVector();
86 for (MalVal mv : (List<MalVal>)old_lst.value) {
87 new_lst.conj_BANG(EVAL(mv, env));
88 }
89 return new_lst;
90 } else if (ast instanceof MalHashMap) {
91 MalHashMap new_hm = new MalHashMap();
92 Iterator it = ((MalHashMap)ast).value.entrySet().iterator();
93 while (it.hasNext()) {
94 Map.Entry entry = (Map.Entry)it.next();
95 new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env));
96 }
97 return new_hm;
98 } else {
99 return ast;
100 }
101 }
102
103 public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable {
104 MalVal a1,a2, a3, res;
105 MalList el;
106
107 while (true) {
108
109 //System.out.println("EVAL: " + types._pr_str(orig_ast, true));
110 if (!(types._list_Q(orig_ast))) {
111 return eval_ast(orig_ast, env);
112 }
113
114 // apply list
115 MalVal expanded = macroexpand(orig_ast, env);
116 if (!types._list_Q(expanded)) { return expanded; }
117 MalList ast = (MalList) expanded;
118 if (ast.size() == 0) { return ast; }
119 MalVal a0 = ast.nth(0);
120 String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName()
121 : "__<*fn*>__";
122 switch (a0sym) {
123 case "def!":
124 a1 = ast.nth(1);
125 a2 = ast.nth(2);
126 res = EVAL(a2, env);
127 env.set(((MalSymbol)a1).getName(), res);
128 return res;
129 case "let*":
130 a1 = ast.nth(1);
131 a2 = ast.nth(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).nth(i);
137 val = ((MalList)a1).nth(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.nth(1);
143 case "quasiquote":
144 return EVAL(quasiquote(ast.nth(1)), env);
145 case "defmacro!":
146 a1 = ast.nth(1);
147 a2 = ast.nth(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.nth(1);
154 return macroexpand(a1, env);
155 case "try*":
156 try {
157 return EVAL(ast.nth(1), env);
158 } catch (Throwable t) {
159 if (ast.size() > 2) {
160 MalVal exc;
161 a2 = ast.nth(2);
162 MalVal a20 = ((MalList)a2).nth(0);
163 if (((MalSymbol)a20).getName().equals("catch*")) {
164 if (t instanceof MalException) {
165 exc = ((MalException)t).getValue();
166 } else {
167 StringWriter sw = new StringWriter();
168 t.printStackTrace(new PrintWriter(sw));
169 String tstr = sw.toString();
170 exc = new MalString(t.getMessage() + ": " + tstr);
171 }
172 return EVAL(((MalList)a2).nth(2),
173 new Env(env, ((MalList)a2).slice(1,2),
174 new MalList(exc)));
175 }
176 }
177 throw t;
178 }
179 case "do":
180 eval_ast(ast.slice(1, ast.size()-1), env);
181 orig_ast = ast.nth(ast.size()-1);
182 break;
183 case "if":
184 a1 = ast.nth(1);
185 MalVal cond = EVAL(a1, env);
186 if (cond == types.Nil || cond == types.False) {
187 // eval false slot form
188 if (ast.size() > 3) {
189 orig_ast = ast.nth(3);
190 } else {
191 return types.Nil;
192 }
193 } else {
194 // eval true slot form
195 orig_ast = ast.nth(2);
196 }
197 break;
198 case "fn*":
199 final MalList a1f = (MalList)ast.nth(1);
200 final MalVal a2f = ast.nth(2);
201 final Env cur_env = env;
202 return new MalFunction (a2f, (mal.types.Env)env, a1f) {
203 public MalVal apply(MalList args) throws MalThrowable {
204 return EVAL(a2f, new Env(cur_env, a1f, args));
205 }
206 };
207 default:
208 el = (MalList)eval_ast(ast, env);
209 MalFunction f = (MalFunction)el.nth(0);
210 MalVal fnast = f.getAst();
211 if (fnast != null) {
212 orig_ast = fnast;
213 env = new Env(f.getEnv(), f.getParams(), el.slice(1));
214 } else {
215 return f.apply(types._rest(el));
216 }
217 }
218
219 }
220 }
221
222 // print
223 public static String PRINT(MalVal exp) {
224 return types._pr_str(exp, true);
225 }
226
227 // REPL
228 public static MalVal RE(Env env, String str) throws MalThrowable {
229 return EVAL(READ(str), env);
230 }
231 public static Env _ref(Env env, String name, MalVal mv) {
232 return env.set(name, mv);
233 }
234 public static String slurp(String fname) throws MalThrowable {
235 try {
236 return new Scanner(new File(fname))
237 .useDelimiter("\\Z").next();
238 } catch (FileNotFoundException e) {
239 throw new MalError(e.getMessage());
240 }
241 }
242
243 public static void main(String[] args) throws MalThrowable {
244 String prompt = "user> ";
245
246 final Env repl_env = new Env(null);
247 for (String key : types.types_ns.keySet()) {
248 _ref(repl_env, key, types.types_ns.get(key));
249 }
250 _ref(repl_env, "readline", new MalFunction() {
251 public MalVal apply(MalList args) throws MalThrowable {
252 String prompt = ((MalString)args.nth(0)).getValue();
253 try {
254 return new MalString(readline.readline(prompt));
255 } catch (IOException e) {
256 throw new MalException(new MalString(e.getMessage()));
257 } catch (readline.EOFException e) {
258 throw new MalException(new MalString(e.getMessage()));
259 }
260 }
261 });
262 _ref(repl_env, "read-string", new MalFunction() {
263 public MalVal apply(MalList args) throws MalThrowable {
264 try {
265 return reader.read_str(((MalString)args.nth(0)).getValue());
266 } catch (MalContinue c) {
267 return types.Nil;
268 }
269 }
270 });
271 _ref(repl_env, "eval", new MalFunction() {
272 public MalVal apply(MalList args) throws MalThrowable {
273 return EVAL(args.nth(0), repl_env);
274 }
275 });
276 _ref(repl_env, "slurp", new MalFunction() {
277 public MalVal apply(MalList args) throws MalThrowable {
278 String fname = ((MalString)args.nth(0)).getValue();
279 return new MalString(slurp(fname));
280 }
281 });
282 _ref(repl_env, "slurp-do", new MalFunction() {
283 public MalVal apply(MalList args) throws MalThrowable {
284 String fname = ((MalString)args.nth(0)).getValue();
285 return new MalString("(do " + slurp(fname) + ")");
286 }
287 });
288
289 RE(repl_env, "(def! not (fn* (a) (if a false true)))");
290 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)))))))");
291 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))))))))");
292
293 RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))");
294
295 Integer fileIdx = 0;
296 if (args.length > 0 && args[0].equals("--raw")) {
297 readline.mode = readline.Mode.JAVA;
298 fileIdx = 1;
299 }
300 if (args.length > fileIdx) {
301 for(Integer i=fileIdx; i<args.length; i++) {
302 RE(repl_env, "(load-file \"" + args[i] + "\")");
303 }
304 return;
305 }
306 while (true) {
307 String line;
308 try {
309 line = readline.readline(prompt);
310 if (line == null) { continue; }
311 } catch (readline.EOFException e) {
312 break;
313 } catch (IOException e) {
314 System.out.println("IOException: " + e.getMessage());
315 break;
316 }
317 try {
318 System.out.println(PRINT(RE(repl_env, line)));
319 } catch (MalContinue e) {
320 continue;
321 } catch (reader.ParseError e) {
322 System.out.println(e.getMessage());
323 continue;
324 } catch (MalException e) {
325 System.out.println("Error: " + types._pr_str(e.getValue(), false));
326 continue;
327 } catch (MalThrowable t) {
328 System.out.println("Error: " + t.getMessage());
329 continue;
330 }
331 }
332 }
333 }