Commit | Line | Data |
---|---|---|
2539e6af KR |
1 | //****************************************************************************** |
2 | // MAL - step 8 - macros | |
3 | //****************************************************************************** | |
4 | // This file is automatically generated from templates/step.swift. Rather than | |
5 | // editing it directly, it's probably better to edit templates/step.swift and | |
6 | // regenerate this file. Otherwise, your change might be lost if/when someone | |
7 | // else performs that process. | |
8 | //****************************************************************************** | |
9 | ||
10 | import Foundation | |
11 | ||
12 | // The number of times EVAL has been entered recursively. We keep track of this | |
13 | // so that we can protect against overrunning the stack. | |
14 | // | |
425305df | 15 | private var EVAL_level = 0 |
2539e6af KR |
16 | |
17 | // The maximum number of times we let EVAL recurse before throwing an exception. | |
18 | // Testing puts this at some place between 1800 and 1900. Let's keep it at 500 | |
19 | // for safety's sake. | |
20 | // | |
425305df | 21 | private let EVAL_leval_max = 500 |
2539e6af KR |
22 | |
23 | // Control whether or not tail-call optimization (TCO) is enabled. We want it | |
24 | // `true` most of the time, but may disable it for debugging purposes (it's | |
25 | // easier to get a meaningful backtrace that way). | |
26 | // | |
425305df | 27 | private let TCO = true |
2539e6af KR |
28 | |
29 | // Control whether or not we emit debugging statements in EVAL. | |
30 | // | |
425305df | 31 | private let DEBUG_EVAL = false |
2539e6af | 32 | |
b3ec2290 KR |
33 | // String used to prefix information logged in EVAL. Increasing lengths of the |
34 | // string are used the more EVAL is recursed. | |
35 | // | |
425305df | 36 | private let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" + |
2539e6af KR |
37 | "----|----|----|----|----|----|----|----|----|----|----|" + |
38 | "----|----|----|----|----|----|----|----|----|----|----|" + | |
39 | "----|----|----|----|----|----|----|----|----|----|----|" + | |
40 | "----|----|----|----|----|----|----|----|----|----|----|" + | |
41 | "----|----|----|----|----|----|----|----|----|----|----|" + | |
42 | "----|----|----|----|----|----|----|----|----|----|----|" + | |
43 | "----|----|----|----|----|----|----|----|----|----|----|" + | |
44 | "----|----|----|----|----|----|----|----|----|----|----|" + | |
45 | "----|----|----|----|----|----|----|----|----|----|----|" + | |
46 | "----|----|----|----|----|----|----|----|----|----|----|" | |
b3ec2290 KR |
47 | |
48 | // Holds the prefix of INDENT_TEMPLATE used for actual logging. | |
49 | // | |
425305df | 50 | private var indent = String() |
2539e6af | 51 | |
b3ec2290 KR |
52 | // Symbols used in this module. |
53 | // | |
425305df KR |
54 | private let kValArgv = make_symbol("*ARGV*") |
55 | private let kValConcat = make_symbol("concat") | |
56 | private let kValCons = make_symbol("cons") | |
57 | private let kValDef = make_symbol("def!") | |
58 | private let kValDefMacro = make_symbol("defmacro!") | |
59 | private let kValDo = make_symbol("do") | |
60 | private let kValEval = make_symbol("eval") | |
61 | private let kValFn = make_symbol("fn*") | |
62 | private let kValIf = make_symbol("if") | |
63 | private let kValLet = make_symbol("let*") | |
64 | private let kValMacroExpand = make_symbol("macroexpand") | |
65 | private let kValQuasiQuote = make_symbol("quasiquote") | |
66 | private let kValQuote = make_symbol("quote") | |
67 | private let kValSpliceUnquote = make_symbol("splice-unquote") | |
68 | private let kValUnquote = make_symbol("unquote") | |
69 | private let kValTry = make_symbol("try*") | |
70 | ||
71 | private let kSymbolArgv = as_symbol(kValArgv) | |
72 | private let kSymbolConcat = as_symbol(kValConcat) | |
73 | private let kSymbolCons = as_symbol(kValCons) | |
74 | private let kSymbolDef = as_symbol(kValDef) | |
75 | private let kSymbolDefMacro = as_symbol(kValDefMacro) | |
76 | private let kSymbolDo = as_symbol(kValDo) | |
77 | private let kSymbolEval = as_symbol(kValEval) | |
78 | private let kSymbolFn = as_symbol(kValFn) | |
79 | private let kSymbolIf = as_symbol(kValIf) | |
80 | private let kSymbolLet = as_symbol(kValLet) | |
81 | private let kSymbolMacroExpand = as_symbol(kValMacroExpand) | |
82 | private let kSymbolQuasiQuote = as_symbol(kValQuasiQuote) | |
83 | private let kSymbolQuote = as_symbol(kValQuote) | |
84 | private let kSymbolSpliceUnquote = as_symbol(kValSpliceUnquote) | |
85 | private let kSymbolUnquote = as_symbol(kValUnquote) | |
86 | ||
87 | func substring(s: String, _ begin: Int, _ end: Int) -> String { | |
88 | return s[s.startIndex.advancedBy(begin) ..< s.startIndex.advancedBy(end)] | |
2539e6af KR |
89 | } |
90 | ||
91 | // Parse the string into an AST. | |
92 | // | |
425305df KR |
93 | private func READ(str: String) throws -> MalVal { |
94 | return try read_str(str) | |
2539e6af KR |
95 | } |
96 | ||
97 | // Return whether or not `val` is a non-empty list. | |
98 | // | |
425305df KR |
99 | private func is_pair(val: MalVal) -> Bool { |
100 | if let seq = as_sequenceQ(val) { | |
101 | return !seq.isEmpty | |
102 | } | |
103 | return false | |
2539e6af KR |
104 | } |
105 | ||
106 | // Expand macros for as long as the expression looks like a macro invocation. | |
107 | // | |
425305df | 108 | private func macroexpand(var ast: MalVal, _ env: Environment) throws -> MalVal { |
2539e6af | 109 | while true { |
425305df KR |
110 | if let ast_as_list = as_listQ(ast) where !ast_as_list.isEmpty, |
111 | let macro_name = as_symbolQ(ast_as_list.first()), | |
112 | let obj = env.get(macro_name), | |
113 | let macro = as_macroQ(obj) | |
114 | { | |
115 | let new_env = Environment(outer: macro.env) | |
116 | let rest = as_sequence(ast_as_list.rest()) | |
117 | let _ = try new_env.set_bindings(macro.args, with_exprs: rest) | |
118 | ast = try EVAL(macro.body, new_env) | |
119 | continue | |
120 | } | |
121 | return ast | |
2539e6af | 122 | } |
2539e6af KR |
123 | } |
124 | ||
125 | // Evaluate `quasiquote`, possibly recursing in the process. | |
126 | // | |
127 | // As with quote, unquote, and splice-unquote, quasiquote takes a single | |
128 | // parameter, typically a list. In the general case, this list is processed | |
129 | // recursively as: | |
130 | // | |
131 | // (quasiquote (first rest...)) -> (cons (quasiquote first) (quasiquote rest)) | |
132 | // | |
133 | // In the processing of the parameter passed to it, quasiquote handles three | |
134 | // special cases: | |
135 | // | |
136 | // * If the parameter is an atom or an empty list, the following expression | |
137 | // is formed and returned for evaluation: | |
138 | // | |
139 | // (quasiquote atom-or-empty-list) -> (quote atom-or-empty-list) | |
140 | // | |
141 | // * If the first element of the non-empty list is the symbol "unquote" | |
142 | // followed by a second item, the second item is returned as-is: | |
143 | // | |
144 | // (quasiquote (unquote fred)) -> fred | |
145 | // | |
146 | // * If the first element of the non-empty list is another list containing | |
147 | // the symbol "splice-unquote" followed by a list, that list is catenated | |
148 | // with the quasiquoted result of the remaining items in the non-empty | |
149 | // parent list: | |
150 | // | |
151 | // (quasiquote (splice-unquote list) rest...) -> (items-from-list items-from-quasiquote(rest...)) | |
152 | // | |
153 | // Note the inconsistent handling between "quote" and "splice-quote". The former | |
154 | // is handled when this function is handed a list that starts with "quote", | |
155 | // whereas the latter is handled when this function is handled a list whose | |
156 | // first element is a list that starts with "splice-quote". The handling of the | |
157 | // latter is forced by the need to incorporate the results of (splice-quote | |
158 | // list) with the remaining items of the list containing that splice-quote | |
159 | // expression. However, it's not clear to me why the handling of "unquote" is | |
160 | // not handled similarly, for consistency's sake. | |
425305df KR |
161 | // |
162 | private func quasiquote(qq_arg: MalVal) throws -> MalVal { | |
2539e6af KR |
163 | |
164 | // If the argument is an atom or empty list: | |
165 | // | |
166 | // Return: (quote <argument>) | |
167 | ||
168 | if !is_pair(qq_arg) { | |
425305df | 169 | return make_list_from(kValQuote, qq_arg) |
2539e6af KR |
170 | } |
171 | ||
172 | // The argument is a non-empty list -- that is (item rest...) | |
173 | ||
174 | // If the first item from the list is a symbol and it's "unquote" -- that | |
175 | // is, (unquote item ignored...): | |
176 | // | |
177 | // Return: item | |
178 | ||
425305df KR |
179 | let qq_list = as_sequence(qq_arg) |
180 | if let sym = as_symbolQ(qq_list.first()) where sym == kSymbolUnquote { | |
181 | return qq_list.count >= 2 ? try! qq_list.nth(1) : make_nil() | |
2539e6af KR |
182 | } |
183 | ||
184 | // If the first item from the list is itself a non-empty list starting with | |
185 | // "splice-unquote"-- that is, ((splice-unquote item ignored...) rest...): | |
186 | // | |
187 | // Return: (concat item quasiquote(rest...)) | |
188 | ||
189 | if is_pair(qq_list.first()) { | |
425305df KR |
190 | let qq_list_item0 = as_sequence(qq_list.first()) |
191 | if let sym = as_symbolQ(qq_list_item0.first()) where sym == kSymbolSpliceUnquote { | |
192 | let result = try quasiquote(qq_list.rest()) | |
193 | return make_list_from(kValConcat, try! qq_list_item0.nth(1), result) | |
2539e6af KR |
194 | } |
195 | } | |
196 | ||
197 | // General case: (item rest...): | |
198 | // | |
199 | // Return: (cons (quasiquote item) (quasiquote (rest...)) | |
200 | ||
425305df KR |
201 | let first = try quasiquote(qq_list.first()) |
202 | let rest = try quasiquote(qq_list.rest()) | |
203 | return make_list_from(kValCons, first, rest) | |
2539e6af KR |
204 | } |
205 | ||
206 | // Perform a simple evaluation of the `ast` object. If it's a symbol, | |
207 | // dereference it and return its value. If it's a collection, call EVAL on all | |
208 | // elements (or just the values, in the case of the hashmap). Otherwise, return | |
209 | // the object unchanged. | |
210 | // | |
425305df KR |
211 | private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal { |
212 | if let symbol = as_symbolQ(ast) { | |
213 | guard let val = env.get(symbol) else { | |
214 | try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests | |
215 | } | |
216 | return val | |
217 | } | |
218 | if let list = as_listQ(ast) { | |
219 | var result = [MalVal]() | |
220 | result.reserveCapacity(Int(list.count)) | |
221 | for item in list { | |
222 | let eval = try EVAL(item, env) | |
223 | result.append(eval) | |
224 | } | |
225 | return make_list(result) | |
226 | } | |
227 | if let vec = as_vectorQ(ast) { | |
228 | var result = [MalVal]() | |
229 | result.reserveCapacity(Int(vec.count)) | |
230 | for item in vec { | |
231 | let eval = try EVAL(item, env) | |
232 | result.append(eval) | |
233 | } | |
234 | return make_vector(result) | |
235 | } | |
236 | if let hash = as_hashmapQ(ast) { | |
237 | var result = [MalVal]() | |
238 | result.reserveCapacity(Int(hash.count) * 2) | |
239 | for (k, v) in hash { | |
240 | let new_v = try EVAL(v, env) | |
241 | result.append(k) | |
242 | result.append(new_v) | |
243 | } | |
244 | return make_hashmap(result) | |
2539e6af | 245 | } |
425305df | 246 | return ast |
2539e6af KR |
247 | } |
248 | ||
425305df | 249 | private enum TCOVal { |
b3ec2290 KR |
250 | case NoResult |
251 | case Return(MalVal) | |
252 | case Continue(MalVal, Environment) | |
253 | ||
254 | init() { self = .NoResult } | |
255 | init(_ result: MalVal) { self = .Return(result) } | |
256 | init(_ ast: MalVal, _ env: Environment) { self = .Continue(ast, env) } | |
b3ec2290 KR |
257 | } |
258 | ||
259 | // EVALuate "def!" and "defmacro!". | |
260 | // | |
425305df KR |
261 | private func eval_def(list: MalSequence, _ env: Environment) throws -> TCOVal { |
262 | guard list.count == 3 else { | |
263 | try throw_error("expected 2 arguments to def!, got \(list.count - 1)") | |
264 | } | |
265 | let arg0 = try! list.nth(0) | |
266 | let arg1 = try! list.nth(1) | |
267 | let arg2 = try! list.nth(2) | |
268 | guard let sym = as_symbolQ(arg1) else { | |
269 | try throw_error("expected symbol for first argument to def!") | |
270 | } | |
271 | var value = try EVAL(arg2, env) | |
272 | if as_symbol(arg0) == kSymbolDefMacro { | |
273 | guard let closure = as_closureQ(value) else { | |
274 | try throw_error("expected closure, got \(value)") | |
b3ec2290 | 275 | } |
425305df | 276 | value = make_macro(closure) |
b3ec2290 KR |
277 | } |
278 | return TCOVal(env.set(sym, value)) | |
279 | } | |
280 | ||
281 | // EVALuate "let*". | |
282 | // | |
425305df KR |
283 | private func eval_let(list: MalSequence, _ env: Environment) throws -> TCOVal { |
284 | guard list.count == 3 else { | |
285 | try throw_error("expected 2 arguments to let*, got \(list.count - 1)") | |
b3ec2290 | 286 | } |
425305df KR |
287 | let arg1 = try! list.nth(1) |
288 | let arg2 = try! list.nth(2) | |
289 | guard let bindings = as_sequenceQ(arg1) else { | |
290 | try throw_error("expected list for first argument to let*") | |
b3ec2290 | 291 | } |
425305df KR |
292 | guard bindings.count % 2 == 0 else { |
293 | try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)") | |
b3ec2290 | 294 | } |
425305df KR |
295 | let new_env = Environment(outer: env) |
296 | for var index: MalIntType = 0; index < bindings.count; index += 2 { | |
297 | let binding_name = try! bindings.nth(index) | |
298 | let binding_value = try! bindings.nth(index + 1) | |
299 | guard let binding_symbol = as_symbolQ(binding_name) else { | |
300 | try throw_error("expected symbol for first element in binding pair") | |
b3ec2290 | 301 | } |
425305df | 302 | let evaluated_value = try EVAL(binding_value, new_env) |
b3ec2290 KR |
303 | new_env.set(binding_symbol, evaluated_value) |
304 | } | |
305 | if TCO { | |
306 | return TCOVal(arg2, new_env) | |
307 | } | |
425305df | 308 | return TCOVal(try EVAL(arg2, new_env)) |
b3ec2290 KR |
309 | } |
310 | ||
311 | // EVALuate "do". | |
312 | // | |
425305df | 313 | private func eval_do(list: MalSequence, _ env: Environment) throws -> TCOVal { |
b3ec2290 | 314 | if TCO { |
425305df | 315 | let _ = try eval_ast(list.range_from(1, to: list.count-1), env) |
b3ec2290 KR |
316 | return TCOVal(list.last(), env) |
317 | } | |
318 | ||
425305df KR |
319 | let evaluated_ast = try eval_ast(list.rest(), env) |
320 | let evaluated_seq = as_sequence(evaluated_ast) | |
b3ec2290 KR |
321 | return TCOVal(evaluated_seq.last()) |
322 | } | |
323 | ||
324 | // EVALuate "if". | |
325 | // | |
425305df KR |
326 | private func eval_if(list: MalSequence, _ env: Environment) throws -> TCOVal { |
327 | guard list.count >= 3 else { | |
328 | try throw_error("expected at least 2 arguments to if, got \(list.count - 1)") | |
b3ec2290 | 329 | } |
425305df KR |
330 | let cond_result = try EVAL(try! list.nth(1), env) |
331 | var new_ast: MalVal | |
b3ec2290 | 332 | if is_truthy(cond_result) { |
425305df | 333 | new_ast = try! list.nth(2) |
b3ec2290 | 334 | } else if list.count == 4 { |
425305df | 335 | new_ast = try! list.nth(3) |
b3ec2290 | 336 | } else { |
425305df | 337 | return TCOVal(make_nil()) |
b3ec2290 KR |
338 | } |
339 | if TCO { | |
340 | return TCOVal(new_ast, env) | |
341 | } | |
425305df | 342 | return TCOVal(try EVAL(new_ast, env)) |
b3ec2290 KR |
343 | } |
344 | ||
345 | // EVALuate "fn*". | |
346 | // | |
425305df KR |
347 | private func eval_fn(list: MalSequence, _ env: Environment) throws -> TCOVal { |
348 | guard list.count == 3 else { | |
349 | try throw_error("expected 2 arguments to fn*, got \(list.count - 1)") | |
b3ec2290 | 350 | } |
425305df KR |
351 | guard let seq = as_sequenceQ(try! list.nth(1)) else { |
352 | try throw_error("expected list or vector for first argument to fn*") | |
b3ec2290 | 353 | } |
425305df | 354 | return TCOVal(make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env))) |
b3ec2290 KR |
355 | } |
356 | ||
357 | // EVALuate "quote". | |
358 | // | |
425305df | 359 | private func eval_quote(list: MalSequence, _ env: Environment) throws -> TCOVal { |
b3ec2290 | 360 | if list.count >= 2 { |
425305df | 361 | return TCOVal(try! list.nth(1)) |
b3ec2290 | 362 | } |
425305df | 363 | return TCOVal(make_nil()) |
b3ec2290 KR |
364 | } |
365 | ||
366 | // EVALuate "quasiquote". | |
367 | // | |
425305df KR |
368 | private func eval_quasiquote(list: MalSequence, _ env: Environment) throws -> TCOVal { |
369 | guard list.count >= 2 else { | |
370 | try throw_error("Expected non-nil parameter to 'quasiquote'") | |
371 | } | |
372 | if TCO { | |
373 | return TCOVal(try quasiquote(try! list.nth(1)), env) | |
b3ec2290 | 374 | } |
425305df | 375 | return TCOVal(try EVAL(try quasiquote(try! list.nth(1)), env)) |
b3ec2290 KR |
376 | } |
377 | ||
378 | // EVALuate "macroexpand". | |
379 | // | |
425305df KR |
380 | private func eval_macroexpand(list: MalSequence, _ env: Environment) throws -> TCOVal { |
381 | guard list.count >= 2 else { | |
382 | try throw_error("Expected parameter to 'macroexpand'") | |
b3ec2290 | 383 | } |
425305df | 384 | return TCOVal(try macroexpand(try! list.nth(1), env)) |
b3ec2290 KR |
385 | } |
386 | ||
2539e6af KR |
387 | // Walk the AST and completely evaluate it, handling macro expansions, special |
388 | // forms and function calls. | |
389 | // | |
425305df KR |
390 | private func EVAL(var ast: MalVal, var _ env: Environment) throws -> MalVal { |
391 | EVAL_level++ | |
392 | defer { EVAL_level-- } | |
393 | guard EVAL_level <= EVAL_leval_max else { | |
394 | try throw_error("Recursing too many levels (> \(EVAL_leval_max))") | |
2539e6af KR |
395 | } |
396 | ||
2c4b8bd1 | 397 | if DEBUG_EVAL { |
425305df | 398 | indent = substring(INDENT_TEMPLATE, 0, EVAL_level) |
2c4b8bd1 | 399 | } |
2539e6af KR |
400 | |
401 | while true { | |
425305df | 402 | if DEBUG_EVAL { print("\(indent)> \(ast)") } |
2539e6af | 403 | |
b3ec2290 | 404 | if !is_list(ast) { |
2539e6af | 405 | |
b3ec2290 | 406 | // Not a list -- just evaluate and return. |
2539e6af | 407 | |
425305df KR |
408 | let answer = try eval_ast(ast, env) |
409 | if DEBUG_EVAL { print("\(indent)>>> \(answer)") } | |
b3ec2290 KR |
410 | return answer |
411 | } | |
2539e6af | 412 | |
b3ec2290 | 413 | // Special handling if it's a list. |
2539e6af | 414 | |
425305df KR |
415 | var list = as_list(ast) |
416 | ast = try macroexpand(ast, env) | |
d5b81cc0 JM |
417 | if !is_list(ast) { |
418 | ||
419 | // Not a list -- just evaluate and return. | |
420 | ||
421 | let answer = try eval_ast(ast, env) | |
422 | if DEBUG_EVAL { print("\(indent)>>> \(answer)") } | |
423 | return answer | |
424 | } | |
425305df | 425 | list = as_list(ast) |
2539e6af | 426 | |
425305df | 427 | if DEBUG_EVAL { print("\(indent)>. \(list)") } |
2539e6af | 428 | |
b3ec2290 | 429 | if list.isEmpty { |
425305df | 430 | return ast |
b3ec2290 | 431 | } |
2539e6af | 432 | |
b3ec2290 KR |
433 | // Check for special forms, where we want to check the operation |
434 | // before evaluating all of the parameters. | |
435 | ||
436 | let arg0 = list.first() | |
425305df KR |
437 | if let fn_symbol = as_symbolQ(arg0) { |
438 | let res: TCOVal | |
b3ec2290 KR |
439 | |
440 | switch fn_symbol { | |
425305df KR |
441 | case kSymbolDef: res = try eval_def(list, env) |
442 | case kSymbolDefMacro: res = try eval_def(list, env) | |
443 | case kSymbolLet: res = try eval_let(list, env) | |
444 | case kSymbolDo: res = try eval_do(list, env) | |
445 | case kSymbolIf: res = try eval_if(list, env) | |
446 | case kSymbolFn: res = try eval_fn(list, env) | |
447 | case kSymbolQuote: res = try eval_quote(list, env) | |
448 | case kSymbolQuasiQuote: res = try eval_quasiquote(list, env) | |
449 | case kSymbolMacroExpand: res = try eval_macroexpand(list, env) | |
b3ec2290 | 450 | default: res = TCOVal() |
2539e6af | 451 | } |
b3ec2290 KR |
452 | switch res { |
453 | case let .Return(result): return result | |
454 | case let .Continue(new_ast, new_env): ast = new_ast; env = new_env; continue | |
455 | case .NoResult: break | |
456 | } | |
457 | } | |
2539e6af | 458 | |
b3ec2290 | 459 | // Standard list to be applied. Evaluate all the elements first. |
2539e6af | 460 | |
425305df | 461 | let eval = try eval_ast(ast, env) |
2539e6af | 462 | |
b3ec2290 | 463 | // The result had better be a list and better be non-empty. |
2539e6af | 464 | |
425305df | 465 | let eval_list = as_list(eval) |
b3ec2290 | 466 | if eval_list.isEmpty { |
425305df | 467 | return eval |
b3ec2290 | 468 | } |
2539e6af | 469 | |
425305df | 470 | if DEBUG_EVAL { print("\(indent)>> \(eval)") } |
b3ec2290 KR |
471 | |
472 | // Get the first element of the list and execute it. | |
473 | ||
474 | let first = eval_list.first() | |
425305df | 475 | let rest = as_sequence(eval_list.rest()) |
b3ec2290 | 476 | |
425305df KR |
477 | if let fn = as_builtinQ(first) { |
478 | let answer = try fn.apply(rest) | |
479 | if DEBUG_EVAL { print("\(indent)>>> \(answer)") } | |
b3ec2290 | 480 | return answer |
425305df KR |
481 | } else if let fn = as_closureQ(first) { |
482 | let new_env = Environment(outer: fn.env) | |
483 | let _ = try new_env.set_bindings(fn.args, with_exprs: rest) | |
b3ec2290 KR |
484 | if TCO { |
485 | env = new_env | |
486 | ast = fn.body | |
487 | continue | |
488 | } | |
425305df KR |
489 | let answer = try EVAL(fn.body, new_env) |
490 | if DEBUG_EVAL { print("\(indent)>>> \(answer)") } | |
b3ec2290 | 491 | return answer |
2539e6af KR |
492 | } |
493 | ||
b3ec2290 KR |
494 | // The first element wasn't a function to be executed. Return an |
495 | // error saying so. | |
2539e6af | 496 | |
425305df | 497 | try throw_error("first list item does not evaluate to a function: \(first)") |
2539e6af KR |
498 | } |
499 | } | |
500 | ||
501 | // Convert the value into a human-readable string for printing. | |
502 | // | |
425305df | 503 | private func PRINT(exp: MalVal) -> String { |
2539e6af KR |
504 | return pr_str(exp, true) |
505 | } | |
506 | ||
507 | // Perform the READ and EVAL steps. Useful for when you don't care about the | |
508 | // printable result. | |
509 | // | |
425305df KR |
510 | private func RE(text: String, _ env: Environment) -> MalVal? { |
511 | if !text.isEmpty { | |
512 | do { | |
513 | let ast = try READ(text) | |
514 | do { | |
515 | return try EVAL(ast, env) | |
516 | } catch let error as MalException { | |
517 | print("Error evaluating input: \(error)") | |
518 | } catch { | |
519 | print("Error evaluating input: \(error)") | |
520 | } | |
521 | } catch let error as MalException { | |
522 | print("Error parsing input: \(error)") | |
523 | } catch { | |
524 | print("Error parsing input: \(error)") | |
525 | } | |
2539e6af | 526 | } |
425305df | 527 | return nil |
2539e6af KR |
528 | } |
529 | ||
530 | // Perform the full READ/EVAL/PRINT, returning a printable string. | |
531 | // | |
425305df | 532 | private func REP(text: String, _ env: Environment) -> String? { |
2539e6af KR |
533 | let exp = RE(text, env) |
534 | if exp == nil { return nil } | |
535 | return PRINT(exp!) | |
536 | } | |
537 | ||
538 | // Perform the full REPL. | |
539 | // | |
425305df | 540 | private func REPL(env: Environment) { |
2539e6af KR |
541 | while true { |
542 | if let text = _readline("user> ") { | |
543 | if let output = REP(text, env) { | |
425305df | 544 | print("\(output)") |
2539e6af KR |
545 | } |
546 | } else { | |
425305df | 547 | print("") |
2539e6af KR |
548 | break |
549 | } | |
550 | } | |
551 | } | |
552 | ||
553 | // Process any command line arguments. Any trailing arguments are incorporated | |
554 | // into the environment. Any argument immediately after the process name is | |
555 | // taken as a script to execute. If one exists, it is executed in lieu of | |
556 | // running the REPL. | |
557 | // | |
425305df KR |
558 | private func process_command_line(args: [String], _ env: Environment) -> Bool { |
559 | var argv = make_list() | |
2539e6af KR |
560 | if args.count > 2 { |
561 | let args1 = args[2..<args.count] | |
425305df | 562 | let args2 = args1.map { make_string($0) } |
2539e6af | 563 | let args3 = [MalVal](args2) |
425305df | 564 | argv = make_list(args3) |
2539e6af KR |
565 | } |
566 | env.set(kSymbolArgv, argv) | |
567 | ||
568 | if args.count > 1 { | |
569 | RE("(load-file \"\(args[1])\")", env) | |
570 | return false | |
571 | } | |
572 | ||
573 | return true | |
574 | } | |
575 | ||
576 | func main() { | |
425305df | 577 | let env = Environment(outer: nil) |
2539e6af KR |
578 | |
579 | load_history_file() | |
580 | load_builtins(env) | |
581 | ||
582 | RE("(def! not (fn* (a) (if a false true)))", env) | |
583 | RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", env) | |
584 | RE("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) " + | |
585 | "(throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", env) | |
2539e6af | 586 | |
425305df KR |
587 | env.set(kSymbolEval, make_builtin({ |
588 | try! unwrap_args($0) { | |
589 | (ast: MalVal) -> MalVal in | |
590 | try EVAL(ast, env) | |
2539e6af KR |
591 | } |
592 | })) | |
593 | ||
594 | if process_command_line(Process.arguments, env) { | |
595 | REPL(env) | |
596 | } | |
597 | ||
598 | save_history_file() | |
599 | } |