Merge pull request #361 from asarhaddon/exercise-native-implementations
[jackhill/mal.git] / d / step7_quote.d
CommitLineData
f82cb965
DM
1module main;
2
3import std.algorithm;
4import std.array;
5import std.range;
6import std.stdio;
7import std.string;
bc33c480 8import core.stdc.stdlib;
f82cb965
DM
9import env;
10import mal_core;
11import readline;
12import reader;
13import printer;
14import types;
15
16bool is_pair(MalType ast)
17{
18 auto lst = cast(MalSequential) ast;
19 if (lst is null) return false;
20 return lst.elements.length > 0;
21}
22
23MalType quasiquote(MalType ast)
24{
25 if (!is_pair(ast))
26 {
27 return new MalList([sym_quote, ast]);
28 }
29 auto ast_seq = verify_cast!MalSequential(ast);
30 auto aste = ast_seq.elements;
31 if (aste[0] == sym_unquote)
32 {
33 return aste[1];
34 }
35
36 if (is_pair(aste[0]))
37 {
38 auto ast0_seq = verify_cast!MalSequential(aste[0]);
39 if (ast0_seq.elements[0] == sym_splice_unquote)
40 {
41 return new MalList([new MalSymbol("concat"), ast0_seq.elements[1], quasiquote(new MalList(aste[1..$]))]);
42 }
43 }
44
45 return new MalList([new MalSymbol("cons"), quasiquote(aste[0]), quasiquote(new MalList(aste[1..$]))]);
46}
47
48MalType READ(string str)
49{
50 return read_str(str);
51}
52
53MalType eval_ast(MalType ast, Env env)
54{
2eb69541 55 if (auto sym = cast(MalSymbol)ast)
f82cb965 56 {
f82cb965
DM
57 return env.get(sym);
58 }
2eb69541 59 else if (auto lst = cast(MalList)ast)
f82cb965 60 {
f82cb965
DM
61 auto el = array(lst.elements.map!(e => EVAL(e, env)));
62 return new MalList(el);
63 }
2eb69541 64 else if (auto lst = cast(MalVector)ast)
f82cb965 65 {
f82cb965
DM
66 auto el = array(lst.elements.map!(e => EVAL(e, env)));
67 return new MalVector(el);
68 }
2eb69541 69 else if (auto hm = cast(MalHashmap)ast)
f82cb965 70 {
f82cb965
DM
71 typeof(hm.data) new_data;
72 foreach (string k, MalType v; hm.data)
73 {
74 new_data[k] = EVAL(v, env);
75 }
76 return new MalHashmap(new_data);
77 }
78 else
79 {
80 return ast;
81 }
82}
83
84MalType EVAL(MalType ast, Env env)
85{
86 for (;;)
87 {
88 MalList ast_list = cast(MalList) ast;
89 if (ast_list is null)
90 {
91 return eval_ast(ast, env);
92 }
93
94 auto aste = ast_list.elements;
ece4cad1
DM
95 if (aste.length == 0)
96 {
97 return ast;
98 }
f82cb965
DM
99 auto a0_sym = cast(MalSymbol) aste[0];
100 auto sym_name = a0_sym is null ? "" : a0_sym.name;
101 switch (sym_name)
102 {
103 case "def!":
104 auto a1 = verify_cast!MalSymbol(aste[1]);
105 return env.set(a1, EVAL(aste[2], env));
106
107 case "let*":
108 auto a1 = verify_cast!MalSequential(aste[1]);
109 auto let_env = new Env(env);
110 foreach (kv; chunks(a1.elements, 2))
111 {
112 if (kv.length < 2) throw new Exception("let* requires even number of elements");
113 auto var_name = verify_cast!MalSymbol(kv[0]);
114 let_env.set(var_name, EVAL(kv[1], let_env));
115 }
116 ast = aste[2];
117 env = let_env;
118 continue; // TCO
119
120 case "quote":
121 return aste[1];
122
123 case "quasiquote":
124 ast = quasiquote(aste[1]);
125 continue; // TCO
126
127 case "do":
128 auto all_but_last = new MalList(aste[1..$-1]);
129 eval_ast(all_but_last, env);
130 ast = aste[$-1];
131 continue; // TCO
132
133 case "if":
134 auto cond = EVAL(aste[1], env);
135 if (cond.is_truthy())
136 {
137 ast = aste[2];
138 continue; // TCO
139 }
140 else
141 if (aste.length > 3)
142 {
143 ast = aste[3];
144 continue; // TCO
145 }
146 else
147 {
148 return mal_nil;
149 }
150
151 case "fn*":
152 auto args_list = verify_cast!MalSequential(aste[1]);
153 return new MalFunc(args_list.elements, aste[2], env);
154
155 default:
156 auto el = verify_cast!MalList(eval_ast(ast, env));
157 if (el.elements.length == 0)
158 {
159 throw new Exception("Expected a non-empty list");
160 }
161 auto first = el.elements[0];
162 auto rest = el.elements[1..$];
2eb69541 163 if (auto funcobj = cast(MalFunc)first)
f82cb965 164 {
f82cb965
DM
165 auto callenv = new Env(funcobj.def_env, funcobj.arg_names, rest);
166 ast = funcobj.func_body;
167 env = callenv;
168 continue; // TCO
169 }
2eb69541 170 else if (auto builtinfuncobj = cast(MalBuiltinFunc)first)
f82cb965 171 {
f82cb965
DM
172 return builtinfuncobj.fn(rest);
173 }
174 else
175 {
176 throw new Exception("Expected a function");
177 }
178 }
179 }
180}
181
182string PRINT(MalType ast)
183{
184 return pr_str(ast);
185}
186
187MalType re(string str, Env env)
188{
189 return EVAL(READ(str), env);
190}
191
192string rep(string str, Env env)
193{
194 return PRINT(re(str, env));
195}
196
197static MalList create_argv_list(string[] args)
198{
199 if (args.length <= 2) return new MalList([]);
200 return new MalList(array(args[2..$].map!(s => cast(MalType)(new MalString(s)))));
201}
202
203void main(string[] args)
204{
205 Env repl_env = new Env(null);
206 foreach (string sym_name, BuiltinStaticFuncType f; core_ns)
207 {
208 repl_env.set(new MalSymbol(sym_name), new MalBuiltinFunc(f, sym_name));
209 }
210
211 BuiltinFuncType eval_func = (a ...) {
212 verify_args_count(a, 1);
213 return EVAL(a[0], repl_env);
214 };
215 repl_env.set(new MalSymbol("eval"), new MalBuiltinFunc(eval_func, "eval"));
216 repl_env.set(new MalSymbol("*ARGV*"), create_argv_list(args));
217
218 // core.mal: defined using the language itself
219 re("(def! not (fn* (a) (if a false true)))", repl_env);
220 re("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env);
221
222 if (args.length > 1)
223 {
224 try
225 {
226 rep("(load-file \"" ~ args[1] ~ "\")", repl_env);
227 return;
228 }
229 catch (Exception e)
230 {
231 writeln("Error: ", e.msg);
bc33c480 232 exit(1);
f82cb965
DM
233 }
234 }
235
236 for (;;)
237 {
238 string line = _readline("user> ");
239 if (line is null) break;
240 if (line.length == 0) continue;
241 try
242 {
243 writeln(rep(line, repl_env));
244 }
245 catch (Exception e)
246 {
247 writeln("Error: ", e.msg);
248 }
249 }
250 writeln("");
251}