Haxe: update README, fix macro eval, add conj.
[jackhill/mal.git] / d / step5_tco.d
1 module main;
2
3 import std.algorithm;
4 import std.array;
5 import std.range;
6 import std.stdio;
7 import std.string;
8 import env;
9 import mal_core;
10 import readline;
11 import reader;
12 import printer;
13 import types;
14
15 MalType READ(string str)
16 {
17 return read_str(str);
18 }
19
20 MalType eval_ast(MalType ast, Env env)
21 {
22 if (typeid(ast) == typeid(MalSymbol))
23 {
24 auto sym = verify_cast!MalSymbol(ast);
25 return env.get(sym);
26 }
27 else if (typeid(ast) == typeid(MalList))
28 {
29 auto lst = verify_cast!MalList(ast);
30 auto el = array(lst.elements.map!(e => EVAL(e, env)));
31 return new MalList(el);
32 }
33 else if (typeid(ast) == typeid(MalVector))
34 {
35 auto lst = verify_cast!MalVector(ast);
36 auto el = array(lst.elements.map!(e => EVAL(e, env)));
37 return new MalVector(el);
38 }
39 else if (typeid(ast) == typeid(MalHashmap))
40 {
41 auto hm = verify_cast!MalHashmap(ast);
42 typeof(hm.data) new_data;
43 foreach (string k, MalType v; hm.data)
44 {
45 new_data[k] = EVAL(v, env);
46 }
47 return new MalHashmap(new_data);
48 }
49 else
50 {
51 return ast;
52 }
53 }
54
55 MalType EVAL(MalType ast, Env env)
56 {
57 for (;;)
58 {
59 MalList ast_list = cast(MalList) ast;
60 if (ast_list is null)
61 {
62 return eval_ast(ast, env);
63 }
64
65 auto aste = ast_list.elements;
66 auto a0_sym = cast(MalSymbol) aste[0];
67 auto sym_name = a0_sym is null ? "" : a0_sym.name;
68 switch (sym_name)
69 {
70 case "def!":
71 auto a1 = verify_cast!MalSymbol(aste[1]);
72 return env.set(a1, EVAL(aste[2], env));
73
74 case "let*":
75 auto a1 = verify_cast!MalSequential(aste[1]);
76 auto let_env = new Env(env);
77 foreach (kv; chunks(a1.elements, 2))
78 {
79 if (kv.length < 2) throw new Exception("let* requires even number of elements");
80 auto var_name = verify_cast!MalSymbol(kv[0]);
81 let_env.set(var_name, EVAL(kv[1], let_env));
82 }
83 ast = aste[2];
84 env = let_env;
85 continue; // TCO
86
87 case "do":
88 auto all_but_last = new MalList(aste[1..$-1]);
89 eval_ast(all_but_last, env);
90 ast = aste[$-1];
91 continue; // TCO
92
93 case "if":
94 auto cond = EVAL(aste[1], env);
95 if (cond.is_truthy())
96 {
97 ast = aste[2];
98 continue; // TCO
99 }
100 else
101 if (aste.length > 3)
102 {
103 ast = aste[3];
104 continue; // TCO
105 }
106 else
107 {
108 return mal_nil;
109 }
110
111 case "fn*":
112 auto args_list = verify_cast!MalSequential(aste[1]);
113 return new MalFunc(args_list.elements, aste[2], env);
114
115 default:
116 auto el = verify_cast!MalList(eval_ast(ast, env));
117 if (el.elements.length == 0)
118 {
119 throw new Exception("Expected a non-empty list");
120 }
121 auto first = el.elements[0];
122 auto rest = el.elements[1..$];
123 if (typeid(first) == typeid(MalFunc))
124 {
125 auto funcobj = verify_cast!MalFunc(first);
126 auto callenv = new Env(funcobj.def_env, funcobj.arg_names, rest);
127 ast = funcobj.func_body;
128 env = callenv;
129 continue; // TCO
130 }
131 else if (typeid(first) == typeid(MalBuiltinFunc))
132 {
133 auto builtinfuncobj = verify_cast!MalBuiltinFunc(first);
134 return builtinfuncobj.fn(rest);
135 }
136 else
137 {
138 throw new Exception("Expected a function");
139 }
140 }
141 }
142 }
143
144 string PRINT(MalType ast)
145 {
146 return pr_str(ast);
147 }
148
149 MalType re(string str, Env env)
150 {
151 return EVAL(READ(str), env);
152 }
153
154 string rep(string str, Env env)
155 {
156 return PRINT(re(str, env));
157 }
158
159 void main()
160 {
161 auto repl_env = new Env(null);
162 foreach (string sym_name, BuiltinStaticFuncType f; core_ns)
163 {
164 repl_env.set(new MalSymbol(sym_name), new MalBuiltinFunc(f, sym_name));
165 }
166
167 // core.mal: defined using the language itself
168 re("(def! not (fn* (a) (if a false true)))", repl_env);
169
170 for (;;)
171 {
172 string line = _readline("user> ");
173 if (line is null) break;
174 if (line.length == 0) continue;
175 try
176 {
177 writeln(rep(line, repl_env));
178 }
179 catch (Exception e)
180 {
181 writeln("Error: ", e.msg);
182 }
183 }
184 writeln("");
185 }