Implement step 9
[jackhill/mal.git] / chuck / step7_quote.ck
CommitLineData
34f7b3db
VS
1// @import types/boxed/*.ck
2// @import types/MalObject.ck
98c1ecf2
VS
3// @import types/mal/MalAtom.ck
4// @import types/mal/MalError.ck
5// @import types/mal/MalNil.ck
6// @import types/mal/MalFalse.ck
7// @import types/mal/MalTrue.ck
8// @import types/mal/MalInt.ck
9// @import types/mal/MalString.ck
10// @import types/mal/MalSymbol.ck
11// @import types/mal/MalKeyword.ck
12// @import types/mal/MalList.ck
13// @import types/mal/MalVector.ck
14// @import types/mal/MalHashMap.ck
34f7b3db
VS
15// @import util/*.ck
16// @import reader.ck
17// @import printer.ck
18// @import env.ck
19// @import types/MalSubr.ck
20// @import types/subr/*.ck
21// @import core.ck
22// @import func.ck
23
24fun MalObject READ(string input)
25{
26 return Reader.read_str(input);
27}
28
29fun int isPair(MalObject m)
30{
31 if( (m.type == "list" || m.type == "vector") &&
32 Util.sequenceToMalObjectArray(m).size() > 0 )
33 {
34 return true;
35 }
36 else
37 {
38 return false;
39 }
40}
41
42fun MalObject quasiquote(MalObject ast)
43{
44 if( !isPair(ast) )
45 {
46 return MalList.create([MalSymbol.create("quote"), ast]);
47 }
48
49 Util.sequenceToMalObjectArray(ast) @=> MalObject a[];
50 a[0] @=> MalObject a0;
51
52 if( a0.type == "symbol" && (a0$MalSymbol).value() == "unquote" )
53 {
54 return a[1];
55 }
56
57 if( isPair(a0) )
58 {
59 Util.sequenceToMalObjectArray(a0) @=> MalObject a0_[];
60 a0_[0] @=> MalObject a0_0;
61
62 if( a0_0.type == "symbol" && (a0_0$MalSymbol).value() == "splice-unquote" )
63 {
64 return MalList.create(
65 [MalSymbol.create("concat"), a0_[1],
66 quasiquote(MalList.create(MalObject.slice(a, 1)))]);
67 }
68 }
69
70 return MalList.create(
71 [MalSymbol.create("cons"), quasiquote(a[0]),
72 quasiquote(MalList.create(MalObject.slice(a, 1)))]);
73}
74
75fun MalObject EVAL(MalObject m, Env env)
76{
77 while( true )
78 {
79 if( m.type != "list" )
80 {
81 return eval_ast(m, env);
82 }
83
84 if( (m$MalList).value().size() == 0 )
85 {
86 return m;
87 }
88
89 (m$MalList).value() @=> MalObject ast[];
90
91 if( ast[0].type == "symbol" )
92 {
93 (ast[0]$MalSymbol).value() => string a0;
94
95 if( a0 == "def!" )
96 {
97 (ast[1]$MalSymbol).value() => string a1;
98
99 EVAL(ast[2], env) @=> MalObject value;
100 if( value.type == "error" )
101 {
102 return value;
103 }
104
105 env.set(a1, value);
106 return value;
107 }
108 else if( a0 == "let*" )
109 {
110 Env.create(env) @=> Env let_env;
111 Util.sequenceToMalObjectArray(ast[1]) @=> MalObject bindings[];
112
113 for( 0 => int i; i < bindings.size(); 2 +=> i)
114 {
115 (bindings[i]$MalSymbol).value() => string symbol;
116 EVAL(bindings[i+1], let_env) @=> MalObject value;
117
118 if( value.type == "error" )
119 {
120 return value;
121 }
122
123 let_env.set(symbol, value);
124 }
125
126 let_env @=> env;
127 ast[2] @=> m;
128 continue; // TCO
129 }
130 else if( a0 == "quote" )
131 {
132 return ast[1];
133 }
134 else if( a0 == "quasiquote" )
135 {
136 quasiquote(ast[1]) @=> m;
137 continue; // TCO
138 }
139 else if( a0 == "do" )
140 {
98c1ecf2 141 MalObject.slice(ast, 1, ast.size()-1) @=> MalObject forms[];
34f7b3db
VS
142 eval_ast(MalList.create(forms), env) @=> MalObject value;
143
144 if( value.type == "error" )
145 {
146 return value;
147 }
148
149 // HACK: this assumes do gets at least one argument...
150 ast[ast.size()-1] @=> m;
151 continue; // TCO
152 }
153 else if( a0 == "if" )
154 {
155 EVAL(ast[1], env) @=> MalObject condition;
156
157 if( condition.type == "error" )
158 {
159 return condition;
160 }
161
162 if( !(condition.type == "nil") && !(condition.type == "false") )
163 {
164 ast[2] @=> m;
165 continue; // TCO
166 }
167 else
168 {
169 if( ast.size() < 4 )
170 {
171 return Constants.NIL;
172 }
173 else
174 {
175 ast[3] @=> m;
176 continue; // TCO
177 }
178 }
179 }
180 else if( a0 == "fn*" )
181 {
182 (ast[1]$MalList).value() @=> MalObject arg_values[];
183 string args[arg_values.size()];
184
185 for( 0 => int i; i < arg_values.size(); i++ )
186 {
187 (arg_values[i]$MalSymbol).value() => args[i];
188 }
189
190 ast[2] @=> MalObject _ast;
191
192 return Func.create(env, args, _ast);
193 }
194 }
195
196 eval_ast(m, env) @=> MalObject result;
197 if( result.type == "error" )
198 {
199 return result;
200 }
201
202 (result$MalList).value() @=> MalObject values[];
203 values[0].type => string type;
204 MalObject.slice(values, 1) @=> MalObject args[];
205
206 if( type == "subr" )
207 {
208 values[0]$MalSubr @=> MalSubr subr;
98c1ecf2 209 return subr.call(args);
34f7b3db
VS
210 }
211 else // type == "func"
212 {
213 values[0]$Func @=> Func func;
214 Env.create(func.env, func.args, args) @=> Env eval_env;
215 eval_env @=> env;
216 func.ast @=> m;
217 continue; // TCO
218 }
219 }
220}
221
222fun MalObject eval_ast(MalObject m, Env env)
223{
224 m.type => string type;
225
226 if( type == "symbol" )
227 {
228 (m$MalSymbol).value() => string symbol;
229 return env.get(symbol);
230 }
231 else if( type == "list" || type == "vector" || type == "hashmap" )
232 {
233 (m$MalList).value() @=> MalObject values[];
234 MalObject results[values.size()];
235
236 if( type != "hashmap" )
237 {
238 for( 0 => int i; i < values.size(); i++ )
239 {
240 EVAL(values[i], env) @=> MalObject result;
241
242 if( result.type == "error" )
243 {
244 return result;
245 }
246
247 result @=> results[i];
248 }
249 }
250 else
251 {
252 for( 0 => int i; i < values.size(); i++ )
253 {
254 if( i % 2 == 0 )
255 {
256 values[i] @=> results[i];
257 }
258 else
259 {
260 EVAL(values[i], env) @=> results[i];
261 }
262 }
263 }
264
265 if( type == "list" )
266 {
267 return MalList.create(results);
268 }
269 else if( type == "vector" )
270 {
271 return MalVector.create(results);
272 }
273 else if( type == "hashmap" )
274 {
275 return MalHashMap.create(results);
276 }
277 }
278 else
279 {
280 return m;
281 }
282}
283
284fun string PRINT(MalObject m)
285{
286 return Printer.pr_str(m, true);
287}
288
289Env.create(null) @=> Env repl_env;
290for( 0 => int i; i < Core.names.size(); i++ )
291{
292 Core.names[i] => string name;
293 repl_env.set(name, Core.ns[name]);
294}
295
98c1ecf2
VS
296// HACK, HACK, HACK
297class MalEval extends MalSubr
298{
299 fun MalObject call(MalObject args[])
300 {
301 args[0] @=> MalObject m;
302 return EVAL(args[0], repl_env);
303 }
304
305 fun MalObject apply(MalObject f, MalObject args[])
306 {
307 if( f.type == "subr" )
308 {
309 return (f$MalSubr).call(args);
310 }
311 else // f.type == "func"
312 {
313 f$Func @=> Func func;
314 Env.create(func.env, func.args, args) @=> Env eval_env;
315 return EVAL(func.ast, eval_env);
316 }
317 }
318}
319
320new MalEval @=> MalEval eval;
321repl_env.set("eval", new MalEval);
322eval @=> (repl_env.get("swap!")$MalSubr).eval;
34f7b3db
VS
323
324fun MalObject[] MalArgv(string args[])
325{
326 MalObject values[args.size()-1];
327
328 for( 1 => int i; i < args.size(); i++ )
329 {
330 MalString.create(args[i]) @=> values[i-1];
331 }
332
333 return values;
334}
335
336// NOTE: normally I'd use \0, but strings are null-terminated...
337String.split(Std.getenv("CHUCK_ARGS"), "\a") @=> string args[];
338repl_env.set("*ARGV*", MalList.create(MalArgv(args)));
339
98c1ecf2
VS
340fun string errorMessage(MalObject m)
341{
342 (m$MalError).value() @=> MalObject value;
343
344 if( value.type == "string" )
345 {
346 return Printer.pr_str(value, false);
347 }
348 else
349 {
350 return "exception: " + Printer.pr_str(value, true);
351 }
352}
353
34f7b3db
VS
354fun string rep(string input)
355{
356 READ(input) @=> MalObject m;
357
358 if( m.type == "error" )
359 {
98c1ecf2 360 return errorMessage(m);
34f7b3db
VS
361 }
362
363 EVAL(m, repl_env) @=> MalObject result;
364 if( result.type == "error" )
365 {
98c1ecf2 366 return errorMessage(result);
34f7b3db
VS
367 }
368
369 return PRINT(result);
370}
371
372rep("(def! not (fn* (a) (if a false true)))");
373rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
374
375fun void main()
376{
377 ConsoleInput stdin;
378 string input;
379
380 while( true )
381 {
382 stdin.prompt("user>") => now;
383 stdin.getLine() => input;
384 rep(input) => string output;
385
386 if( output == "empty input" )
387 {
388 // proceed immediately with prompt
389 }
390 else
391 {
392 Util.println(output);
393 }
394 }
395}
396
397if( args.size() > 1 )
398{
399 args[1] => string filename;
400 rep("(load-file \"" + filename + "\")");
401}
402else
403{
404 main();
405}