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") | |
fbfe6784 | 66 | private let kValQuasiQuoteExp = make_symbol("quasiquoteexpand") |
425305df KR |
67 | private let kValQuote = make_symbol("quote") |
68 | private let kValSpliceUnquote = make_symbol("splice-unquote") | |
69 | private let kValUnquote = make_symbol("unquote") | |
70 | private let kValTry = make_symbol("try*") | |
fbfe6784 | 71 | private let kValVec = make_symbol("vec") |
425305df KR |
72 | |
73 | private let kSymbolArgv = as_symbol(kValArgv) | |
74 | private let kSymbolConcat = as_symbol(kValConcat) | |
75 | private let kSymbolCons = as_symbol(kValCons) | |
76 | private let kSymbolDef = as_symbol(kValDef) | |
77 | private let kSymbolDefMacro = as_symbol(kValDefMacro) | |
78 | private let kSymbolDo = as_symbol(kValDo) | |
79 | private let kSymbolEval = as_symbol(kValEval) | |
80 | private let kSymbolFn = as_symbol(kValFn) | |
81 | private let kSymbolIf = as_symbol(kValIf) | |
82 | private let kSymbolLet = as_symbol(kValLet) | |
83 | private let kSymbolMacroExpand = as_symbol(kValMacroExpand) | |
84 | private let kSymbolQuasiQuote = as_symbol(kValQuasiQuote) | |
fbfe6784 | 85 | private let kSymbolQuasiQuoteExp = as_symbol(kValQuasiQuoteExp) |
425305df KR |
86 | private let kSymbolQuote = as_symbol(kValQuote) |
87 | private let kSymbolSpliceUnquote = as_symbol(kValSpliceUnquote) | |
88 | private let kSymbolUnquote = as_symbol(kValUnquote) | |
fbfe6784 | 89 | private let kSymbolVec = as_symbol(kValVec) |
425305df KR |
90 | |
91 | func substring(s: String, _ begin: Int, _ end: Int) -> String { | |
92 | return s[s.startIndex.advancedBy(begin) ..< s.startIndex.advancedBy(end)] | |
2539e6af KR |
93 | } |
94 | ||
95 | // Parse the string into an AST. | |
96 | // | |
425305df KR |
97 | private func READ(str: String) throws -> MalVal { |
98 | return try read_str(str) | |
2539e6af KR |
99 | } |
100 | ||
2539e6af KR |
101 | // Expand macros for as long as the expression looks like a macro invocation. |
102 | // | |
425305df | 103 | private func macroexpand(var ast: MalVal, _ env: Environment) throws -> MalVal { |
2539e6af | 104 | while true { |
425305df KR |
105 | if let ast_as_list = as_listQ(ast) where !ast_as_list.isEmpty, |
106 | let macro_name = as_symbolQ(ast_as_list.first()), | |
107 | let obj = env.get(macro_name), | |
108 | let macro = as_macroQ(obj) | |
109 | { | |
110 | let new_env = Environment(outer: macro.env) | |
111 | let rest = as_sequence(ast_as_list.rest()) | |
112 | let _ = try new_env.set_bindings(macro.args, with_exprs: rest) | |
113 | ast = try EVAL(macro.body, new_env) | |
114 | continue | |
115 | } | |
116 | return ast | |
2539e6af | 117 | } |
2539e6af KR |
118 | } |
119 | ||
fbfe6784 | 120 | // Return whether or not `ast` is a list and first element is the required symbol. |
2539e6af | 121 | // |
fbfe6784 NB |
122 | private func starts_with(ast: MalVal, sym: MalSymbol) -> MalVal? { |
123 | if let list = as_listQ(ast) where 1 < list.count, | |
124 | let a0 = as_symbolQ(try! list.nth(0)) where a0 == sym { | |
125 | return try! list.nth(1) | |
126 | } else { | |
127 | return nil | |
128 | } | |
129 | } | |
130 | ||
131 | // Evaluate `quasiquote`, possibly recursing in the process. | |
425305df KR |
132 | // |
133 | private func quasiquote(qq_arg: MalVal) throws -> MalVal { | |
2539e6af KR |
134 | |
135 | // If the argument is an atom or empty list: | |
136 | // | |
137 | // Return: (quote <argument>) | |
138 | ||
fbfe6784 | 139 | if is_symbol(qq_arg) || is_hashmap(qq_arg) { |
425305df | 140 | return make_list_from(kValQuote, qq_arg) |
2539e6af KR |
141 | } |
142 | ||
fbfe6784 NB |
143 | guard let seq = as_sequenceQ(qq_arg) else { |
144 | return qq_arg | |
145 | } | |
146 | ||
2539e6af KR |
147 | // The argument is a non-empty list -- that is (item rest...) |
148 | ||
149 | // If the first item from the list is a symbol and it's "unquote" -- that | |
150 | // is, (unquote item ignored...): | |
151 | // | |
152 | // Return: item | |
153 | ||
fbfe6784 NB |
154 | if let x = starts_with(qq_arg, sym: kSymbolUnquote) { |
155 | return x | |
2539e6af KR |
156 | } |
157 | ||
fbfe6784 NB |
158 | var result = make_list_from() |
159 | for elt in seq.reverse() { | |
160 | if let x = starts_with(elt, sym: kSymbolSpliceUnquote) { | |
161 | result = make_list_from(kValConcat, x, result) | |
162 | } else { | |
163 | result = make_list_from(kValCons, try quasiquote (elt), result) | |
2539e6af KR |
164 | } |
165 | } | |
fbfe6784 NB |
166 | if is_vector(qq_arg) { |
167 | return make_list_from(kValVec, result) | |
168 | } | |
169 | return result | |
2539e6af KR |
170 | } |
171 | ||
172 | // Perform a simple evaluation of the `ast` object. If it's a symbol, | |
173 | // dereference it and return its value. If it's a collection, call EVAL on all | |
174 | // elements (or just the values, in the case of the hashmap). Otherwise, return | |
175 | // the object unchanged. | |
176 | // | |
425305df KR |
177 | private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal { |
178 | if let symbol = as_symbolQ(ast) { | |
179 | guard let val = env.get(symbol) else { | |
180 | try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests | |
181 | } | |
182 | return val | |
183 | } | |
184 | if let list = as_listQ(ast) { | |
185 | var result = [MalVal]() | |
186 | result.reserveCapacity(Int(list.count)) | |
187 | for item in list { | |
188 | let eval = try EVAL(item, env) | |
189 | result.append(eval) | |
190 | } | |
191 | return make_list(result) | |
192 | } | |
193 | if let vec = as_vectorQ(ast) { | |
194 | var result = [MalVal]() | |
195 | result.reserveCapacity(Int(vec.count)) | |
196 | for item in vec { | |
197 | let eval = try EVAL(item, env) | |
198 | result.append(eval) | |
199 | } | |
200 | return make_vector(result) | |
201 | } | |
202 | if let hash = as_hashmapQ(ast) { | |
203 | var result = [MalVal]() | |
204 | result.reserveCapacity(Int(hash.count) * 2) | |
205 | for (k, v) in hash { | |
206 | let new_v = try EVAL(v, env) | |
207 | result.append(k) | |
208 | result.append(new_v) | |
209 | } | |
210 | return make_hashmap(result) | |
2539e6af | 211 | } |
425305df | 212 | return ast |
2539e6af KR |
213 | } |
214 | ||
425305df | 215 | private enum TCOVal { |
b3ec2290 KR |
216 | case NoResult |
217 | case Return(MalVal) | |
218 | case Continue(MalVal, Environment) | |
219 | ||
220 | init() { self = .NoResult } | |
221 | init(_ result: MalVal) { self = .Return(result) } | |
222 | init(_ ast: MalVal, _ env: Environment) { self = .Continue(ast, env) } | |
b3ec2290 KR |
223 | } |
224 | ||
225 | // EVALuate "def!" and "defmacro!". | |
226 | // | |
425305df KR |
227 | private func eval_def(list: MalSequence, _ env: Environment) throws -> TCOVal { |
228 | guard list.count == 3 else { | |
229 | try throw_error("expected 2 arguments to def!, got \(list.count - 1)") | |
230 | } | |
231 | let arg0 = try! list.nth(0) | |
232 | let arg1 = try! list.nth(1) | |
233 | let arg2 = try! list.nth(2) | |
234 | guard let sym = as_symbolQ(arg1) else { | |
235 | try throw_error("expected symbol for first argument to def!") | |
236 | } | |
237 | var value = try EVAL(arg2, env) | |
238 | if as_symbol(arg0) == kSymbolDefMacro { | |
239 | guard let closure = as_closureQ(value) else { | |
240 | try throw_error("expected closure, got \(value)") | |
b3ec2290 | 241 | } |
425305df | 242 | value = make_macro(closure) |
b3ec2290 KR |
243 | } |
244 | return TCOVal(env.set(sym, value)) | |
245 | } | |
246 | ||
247 | // EVALuate "let*". | |
248 | // | |
425305df KR |
249 | private func eval_let(list: MalSequence, _ env: Environment) throws -> TCOVal { |
250 | guard list.count == 3 else { | |
251 | try throw_error("expected 2 arguments to let*, got \(list.count - 1)") | |
b3ec2290 | 252 | } |
425305df KR |
253 | let arg1 = try! list.nth(1) |
254 | let arg2 = try! list.nth(2) | |
255 | guard let bindings = as_sequenceQ(arg1) else { | |
256 | try throw_error("expected list for first argument to let*") | |
b3ec2290 | 257 | } |
425305df KR |
258 | guard bindings.count % 2 == 0 else { |
259 | try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)") | |
b3ec2290 | 260 | } |
425305df KR |
261 | let new_env = Environment(outer: env) |
262 | for var index: MalIntType = 0; index < bindings.count; index += 2 { | |
263 | let binding_name = try! bindings.nth(index) | |
264 | let binding_value = try! bindings.nth(index + 1) | |
265 | guard let binding_symbol = as_symbolQ(binding_name) else { | |
266 | try throw_error("expected symbol for first element in binding pair") | |
b3ec2290 | 267 | } |
425305df | 268 | let evaluated_value = try EVAL(binding_value, new_env) |
b3ec2290 KR |
269 | new_env.set(binding_symbol, evaluated_value) |
270 | } | |
271 | if TCO { | |
272 | return TCOVal(arg2, new_env) | |
273 | } | |
425305df | 274 | return TCOVal(try EVAL(arg2, new_env)) |
b3ec2290 KR |
275 | } |
276 | ||
277 | // EVALuate "do". | |
278 | // | |
425305df | 279 | private func eval_do(list: MalSequence, _ env: Environment) throws -> TCOVal { |
b3ec2290 | 280 | if TCO { |
425305df | 281 | let _ = try eval_ast(list.range_from(1, to: list.count-1), env) |
b3ec2290 KR |
282 | return TCOVal(list.last(), env) |
283 | } | |
284 | ||
425305df KR |
285 | let evaluated_ast = try eval_ast(list.rest(), env) |
286 | let evaluated_seq = as_sequence(evaluated_ast) | |
b3ec2290 KR |
287 | return TCOVal(evaluated_seq.last()) |
288 | } | |
289 | ||
290 | // EVALuate "if". | |
291 | // | |
425305df KR |
292 | private func eval_if(list: MalSequence, _ env: Environment) throws -> TCOVal { |
293 | guard list.count >= 3 else { | |
294 | try throw_error("expected at least 2 arguments to if, got \(list.count - 1)") | |
b3ec2290 | 295 | } |
425305df KR |
296 | let cond_result = try EVAL(try! list.nth(1), env) |
297 | var new_ast: MalVal | |
b3ec2290 | 298 | if is_truthy(cond_result) { |
425305df | 299 | new_ast = try! list.nth(2) |
b3ec2290 | 300 | } else if list.count == 4 { |
425305df | 301 | new_ast = try! list.nth(3) |
b3ec2290 | 302 | } else { |
425305df | 303 | return TCOVal(make_nil()) |
b3ec2290 KR |
304 | } |
305 | if TCO { | |
306 | return TCOVal(new_ast, env) | |
307 | } | |
425305df | 308 | return TCOVal(try EVAL(new_ast, env)) |
b3ec2290 KR |
309 | } |
310 | ||
311 | // EVALuate "fn*". | |
312 | // | |
425305df KR |
313 | private func eval_fn(list: MalSequence, _ env: Environment) throws -> TCOVal { |
314 | guard list.count == 3 else { | |
315 | try throw_error("expected 2 arguments to fn*, got \(list.count - 1)") | |
b3ec2290 | 316 | } |
425305df KR |
317 | guard let seq = as_sequenceQ(try! list.nth(1)) else { |
318 | try throw_error("expected list or vector for first argument to fn*") | |
b3ec2290 | 319 | } |
425305df | 320 | return TCOVal(make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env))) |
b3ec2290 KR |
321 | } |
322 | ||
323 | // EVALuate "quote". | |
324 | // | |
425305df | 325 | private func eval_quote(list: MalSequence, _ env: Environment) throws -> TCOVal { |
b3ec2290 | 326 | if list.count >= 2 { |
425305df | 327 | return TCOVal(try! list.nth(1)) |
b3ec2290 | 328 | } |
425305df | 329 | return TCOVal(make_nil()) |
b3ec2290 KR |
330 | } |
331 | ||
fbfe6784 NB |
332 | // EVALuate "quasiquoteexpand". |
333 | // | |
334 | private func eval_quasiquoteexp(list: MalSequence) throws -> TCOVal { | |
335 | if list.count < 2 { | |
336 | try throw_error("quasiquoteexpand: arg count") | |
337 | } | |
338 | return TCOVal(try! quasiquote(try! list.nth(1))) | |
339 | } | |
340 | ||
b3ec2290 KR |
341 | // EVALuate "quasiquote". |
342 | // | |
425305df KR |
343 | private func eval_quasiquote(list: MalSequence, _ env: Environment) throws -> TCOVal { |
344 | guard list.count >= 2 else { | |
345 | try throw_error("Expected non-nil parameter to 'quasiquote'") | |
346 | } | |
347 | if TCO { | |
348 | return TCOVal(try quasiquote(try! list.nth(1)), env) | |
b3ec2290 | 349 | } |
425305df | 350 | return TCOVal(try EVAL(try quasiquote(try! list.nth(1)), env)) |
b3ec2290 KR |
351 | } |
352 | ||
353 | // EVALuate "macroexpand". | |
354 | // | |
425305df KR |
355 | private func eval_macroexpand(list: MalSequence, _ env: Environment) throws -> TCOVal { |
356 | guard list.count >= 2 else { | |
357 | try throw_error("Expected parameter to 'macroexpand'") | |
b3ec2290 | 358 | } |
425305df | 359 | return TCOVal(try macroexpand(try! list.nth(1), env)) |
b3ec2290 KR |
360 | } |
361 | ||
2539e6af KR |
362 | // Walk the AST and completely evaluate it, handling macro expansions, special |
363 | // forms and function calls. | |
364 | // | |
425305df KR |
365 | private func EVAL(var ast: MalVal, var _ env: Environment) throws -> MalVal { |
366 | EVAL_level++ | |
367 | defer { EVAL_level-- } | |
368 | guard EVAL_level <= EVAL_leval_max else { | |
369 | try throw_error("Recursing too many levels (> \(EVAL_leval_max))") | |
2539e6af KR |
370 | } |
371 | ||
2c4b8bd1 | 372 | if DEBUG_EVAL { |
425305df | 373 | indent = substring(INDENT_TEMPLATE, 0, EVAL_level) |
2c4b8bd1 | 374 | } |
2539e6af KR |
375 | |
376 | while true { | |
425305df | 377 | if DEBUG_EVAL { print("\(indent)> \(ast)") } |
2539e6af | 378 | |
b3ec2290 | 379 | if !is_list(ast) { |
2539e6af | 380 | |
b3ec2290 | 381 | // Not a list -- just evaluate and return. |
2539e6af | 382 | |
425305df KR |
383 | let answer = try eval_ast(ast, env) |
384 | if DEBUG_EVAL { print("\(indent)>>> \(answer)") } | |
b3ec2290 KR |
385 | return answer |
386 | } | |
2539e6af | 387 | |
b3ec2290 | 388 | // Special handling if it's a list. |
2539e6af | 389 | |
425305df KR |
390 | var list = as_list(ast) |
391 | ast = try macroexpand(ast, env) | |
d5b81cc0 JM |
392 | if !is_list(ast) { |
393 | ||
394 | // Not a list -- just evaluate and return. | |
395 | ||
396 | let answer = try eval_ast(ast, env) | |
397 | if DEBUG_EVAL { print("\(indent)>>> \(answer)") } | |
398 | return answer | |
399 | } | |
425305df | 400 | list = as_list(ast) |
2539e6af | 401 | |
425305df | 402 | if DEBUG_EVAL { print("\(indent)>. \(list)") } |
2539e6af | 403 | |
b3ec2290 | 404 | if list.isEmpty { |
425305df | 405 | return ast |
b3ec2290 | 406 | } |
2539e6af | 407 | |
b3ec2290 KR |
408 | // Check for special forms, where we want to check the operation |
409 | // before evaluating all of the parameters. | |
410 | ||
411 | let arg0 = list.first() | |
425305df KR |
412 | if let fn_symbol = as_symbolQ(arg0) { |
413 | let res: TCOVal | |
b3ec2290 KR |
414 | |
415 | switch fn_symbol { | |
425305df KR |
416 | case kSymbolDef: res = try eval_def(list, env) |
417 | case kSymbolDefMacro: res = try eval_def(list, env) | |
418 | case kSymbolLet: res = try eval_let(list, env) | |
419 | case kSymbolDo: res = try eval_do(list, env) | |
420 | case kSymbolIf: res = try eval_if(list, env) | |
421 | case kSymbolFn: res = try eval_fn(list, env) | |
422 | case kSymbolQuote: res = try eval_quote(list, env) | |
423 | case kSymbolQuasiQuote: res = try eval_quasiquote(list, env) | |
fbfe6784 | 424 | case kSymbolQuasiQuoteExp: res = try eval_quasiquoteexp(list) |
425305df | 425 | case kSymbolMacroExpand: res = try eval_macroexpand(list, env) |
b3ec2290 | 426 | default: res = TCOVal() |
2539e6af | 427 | } |
b3ec2290 KR |
428 | switch res { |
429 | case let .Return(result): return result | |
430 | case let .Continue(new_ast, new_env): ast = new_ast; env = new_env; continue | |
431 | case .NoResult: break | |
432 | } | |
433 | } | |
2539e6af | 434 | |
b3ec2290 | 435 | // Standard list to be applied. Evaluate all the elements first. |
2539e6af | 436 | |
425305df | 437 | let eval = try eval_ast(ast, env) |
2539e6af | 438 | |
b3ec2290 | 439 | // The result had better be a list and better be non-empty. |
2539e6af | 440 | |
425305df | 441 | let eval_list = as_list(eval) |
b3ec2290 | 442 | if eval_list.isEmpty { |
425305df | 443 | return eval |
b3ec2290 | 444 | } |
2539e6af | 445 | |
425305df | 446 | if DEBUG_EVAL { print("\(indent)>> \(eval)") } |
b3ec2290 KR |
447 | |
448 | // Get the first element of the list and execute it. | |
449 | ||
450 | let first = eval_list.first() | |
425305df | 451 | let rest = as_sequence(eval_list.rest()) |
b3ec2290 | 452 | |
425305df KR |
453 | if let fn = as_builtinQ(first) { |
454 | let answer = try fn.apply(rest) | |
455 | if DEBUG_EVAL { print("\(indent)>>> \(answer)") } | |
b3ec2290 | 456 | return answer |
425305df KR |
457 | } else if let fn = as_closureQ(first) { |
458 | let new_env = Environment(outer: fn.env) | |
459 | let _ = try new_env.set_bindings(fn.args, with_exprs: rest) | |
b3ec2290 KR |
460 | if TCO { |
461 | env = new_env | |
462 | ast = fn.body | |
463 | continue | |
464 | } | |
425305df KR |
465 | let answer = try EVAL(fn.body, new_env) |
466 | if DEBUG_EVAL { print("\(indent)>>> \(answer)") } | |
b3ec2290 | 467 | return answer |
2539e6af KR |
468 | } |
469 | ||
b3ec2290 KR |
470 | // The first element wasn't a function to be executed. Return an |
471 | // error saying so. | |
2539e6af | 472 | |
425305df | 473 | try throw_error("first list item does not evaluate to a function: \(first)") |
2539e6af KR |
474 | } |
475 | } | |
476 | ||
477 | // Convert the value into a human-readable string for printing. | |
478 | // | |
425305df | 479 | private func PRINT(exp: MalVal) -> String { |
2539e6af KR |
480 | return pr_str(exp, true) |
481 | } | |
482 | ||
483 | // Perform the READ and EVAL steps. Useful for when you don't care about the | |
484 | // printable result. | |
485 | // | |
425305df KR |
486 | private func RE(text: String, _ env: Environment) -> MalVal? { |
487 | if !text.isEmpty { | |
488 | do { | |
489 | let ast = try READ(text) | |
490 | do { | |
491 | return try EVAL(ast, env) | |
492 | } catch let error as MalException { | |
493 | print("Error evaluating input: \(error)") | |
494 | } catch { | |
495 | print("Error evaluating input: \(error)") | |
496 | } | |
497 | } catch let error as MalException { | |
498 | print("Error parsing input: \(error)") | |
499 | } catch { | |
500 | print("Error parsing input: \(error)") | |
501 | } | |
2539e6af | 502 | } |
425305df | 503 | return nil |
2539e6af KR |
504 | } |
505 | ||
506 | // Perform the full READ/EVAL/PRINT, returning a printable string. | |
507 | // | |
425305df | 508 | private func REP(text: String, _ env: Environment) -> String? { |
2539e6af KR |
509 | let exp = RE(text, env) |
510 | if exp == nil { return nil } | |
511 | return PRINT(exp!) | |
512 | } | |
513 | ||
514 | // Perform the full REPL. | |
515 | // | |
425305df | 516 | private func REPL(env: Environment) { |
2539e6af KR |
517 | while true { |
518 | if let text = _readline("user> ") { | |
519 | if let output = REP(text, env) { | |
425305df | 520 | print("\(output)") |
2539e6af KR |
521 | } |
522 | } else { | |
425305df | 523 | print("") |
2539e6af KR |
524 | break |
525 | } | |
526 | } | |
527 | } | |
528 | ||
529 | // Process any command line arguments. Any trailing arguments are incorporated | |
530 | // into the environment. Any argument immediately after the process name is | |
531 | // taken as a script to execute. If one exists, it is executed in lieu of | |
532 | // running the REPL. | |
533 | // | |
425305df KR |
534 | private func process_command_line(args: [String], _ env: Environment) -> Bool { |
535 | var argv = make_list() | |
2539e6af KR |
536 | if args.count > 2 { |
537 | let args1 = args[2..<args.count] | |
425305df | 538 | let args2 = args1.map { make_string($0) } |
2539e6af | 539 | let args3 = [MalVal](args2) |
425305df | 540 | argv = make_list(args3) |
2539e6af KR |
541 | } |
542 | env.set(kSymbolArgv, argv) | |
543 | ||
544 | if args.count > 1 { | |
545 | RE("(load-file \"\(args[1])\")", env) | |
546 | return false | |
547 | } | |
548 | ||
549 | return true | |
550 | } | |
551 | ||
552 | func main() { | |
425305df | 553 | let env = Environment(outer: nil) |
2539e6af KR |
554 | |
555 | load_history_file() | |
556 | load_builtins(env) | |
557 | ||
558 | RE("(def! not (fn* (a) (if a false true)))", env) | |
e6d41de4 | 559 | RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))", env) |
2539e6af KR |
560 | RE("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) " + |
561 | "(throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", env) | |
2539e6af | 562 | |
425305df KR |
563 | env.set(kSymbolEval, make_builtin({ |
564 | try! unwrap_args($0) { | |
565 | (ast: MalVal) -> MalVal in | |
566 | try EVAL(ast, env) | |
2539e6af KR |
567 | } |
568 | })) | |
569 | ||
570 | if process_command_line(Process.arguments, env) { | |
571 | REPL(env) | |
572 | } | |
573 | ||
574 | save_history_file() | |
575 | } |