| 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 | // |
| 15 | private var EVAL_level = 0 |
| 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 | // |
| 21 | private let EVAL_leval_max = 500 |
| 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 | // |
| 27 | private let TCO = true |
| 28 | |
| 29 | // Control whether or not we emit debugging statements in EVAL. |
| 30 | // |
| 31 | private let DEBUG_EVAL = false |
| 32 | |
| 33 | // String used to prefix information logged in EVAL. Increasing lengths of the |
| 34 | // string are used the more EVAL is recursed. |
| 35 | // |
| 36 | private let INDENT_TEMPLATE = "|----|----|----|----|----|----|----|----|" + |
| 37 | "----|----|----|----|----|----|----|----|----|----|----|" + |
| 38 | "----|----|----|----|----|----|----|----|----|----|----|" + |
| 39 | "----|----|----|----|----|----|----|----|----|----|----|" + |
| 40 | "----|----|----|----|----|----|----|----|----|----|----|" + |
| 41 | "----|----|----|----|----|----|----|----|----|----|----|" + |
| 42 | "----|----|----|----|----|----|----|----|----|----|----|" + |
| 43 | "----|----|----|----|----|----|----|----|----|----|----|" + |
| 44 | "----|----|----|----|----|----|----|----|----|----|----|" + |
| 45 | "----|----|----|----|----|----|----|----|----|----|----|" + |
| 46 | "----|----|----|----|----|----|----|----|----|----|----|" |
| 47 | |
| 48 | // Holds the prefix of INDENT_TEMPLATE used for actual logging. |
| 49 | // |
| 50 | private var indent = String() |
| 51 | |
| 52 | // Symbols used in this module. |
| 53 | // |
| 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 kValQuasiQuoteExp = make_symbol("quasiquoteexpand") |
| 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*") |
| 71 | private let kValVec = make_symbol("vec") |
| 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) |
| 85 | private let kSymbolQuasiQuoteExp = as_symbol(kValQuasiQuoteExp) |
| 86 | private let kSymbolQuote = as_symbol(kValQuote) |
| 87 | private let kSymbolSpliceUnquote = as_symbol(kValSpliceUnquote) |
| 88 | private let kSymbolUnquote = as_symbol(kValUnquote) |
| 89 | private let kSymbolVec = as_symbol(kValVec) |
| 90 | |
| 91 | func substring(s: String, _ begin: Int, _ end: Int) -> String { |
| 92 | return s[s.startIndex.advancedBy(begin) ..< s.startIndex.advancedBy(end)] |
| 93 | } |
| 94 | |
| 95 | // Parse the string into an AST. |
| 96 | // |
| 97 | private func READ(str: String) throws -> MalVal { |
| 98 | return try read_str(str) |
| 99 | } |
| 100 | |
| 101 | // Expand macros for as long as the expression looks like a macro invocation. |
| 102 | // |
| 103 | private func macroexpand(var ast: MalVal, _ env: Environment) throws -> MalVal { |
| 104 | while true { |
| 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 |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | // Return whether or not `ast` is a list and first element is the required symbol. |
| 121 | // |
| 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. |
| 132 | // |
| 133 | private func quasiquote(qq_arg: MalVal) throws -> MalVal { |
| 134 | |
| 135 | // If the argument is an atom or empty list: |
| 136 | // |
| 137 | // Return: (quote <argument>) |
| 138 | |
| 139 | if is_symbol(qq_arg) || is_hashmap(qq_arg) { |
| 140 | return make_list_from(kValQuote, qq_arg) |
| 141 | } |
| 142 | |
| 143 | guard let seq = as_sequenceQ(qq_arg) else { |
| 144 | return qq_arg |
| 145 | } |
| 146 | |
| 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 | |
| 154 | if let x = starts_with(qq_arg, sym: kSymbolUnquote) { |
| 155 | return x |
| 156 | } |
| 157 | |
| 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) |
| 164 | } |
| 165 | } |
| 166 | if is_vector(qq_arg) { |
| 167 | return make_list_from(kValVec, result) |
| 168 | } |
| 169 | return result |
| 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 | // |
| 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) |
| 211 | } |
| 212 | return ast |
| 213 | } |
| 214 | |
| 215 | private enum TCOVal { |
| 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) } |
| 223 | } |
| 224 | |
| 225 | // EVALuate "def!" and "defmacro!". |
| 226 | // |
| 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)") |
| 241 | } |
| 242 | value = make_macro(closure) |
| 243 | } |
| 244 | return TCOVal(env.set(sym, value)) |
| 245 | } |
| 246 | |
| 247 | // EVALuate "let*". |
| 248 | // |
| 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)") |
| 252 | } |
| 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*") |
| 257 | } |
| 258 | guard bindings.count % 2 == 0 else { |
| 259 | try throw_error("expected even number of elements in bindings to let*, got \(bindings.count)") |
| 260 | } |
| 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") |
| 267 | } |
| 268 | let evaluated_value = try EVAL(binding_value, new_env) |
| 269 | new_env.set(binding_symbol, evaluated_value) |
| 270 | } |
| 271 | if TCO { |
| 272 | return TCOVal(arg2, new_env) |
| 273 | } |
| 274 | return TCOVal(try EVAL(arg2, new_env)) |
| 275 | } |
| 276 | |
| 277 | // EVALuate "do". |
| 278 | // |
| 279 | private func eval_do(list: MalSequence, _ env: Environment) throws -> TCOVal { |
| 280 | if TCO { |
| 281 | let _ = try eval_ast(list.range_from(1, to: list.count-1), env) |
| 282 | return TCOVal(list.last(), env) |
| 283 | } |
| 284 | |
| 285 | let evaluated_ast = try eval_ast(list.rest(), env) |
| 286 | let evaluated_seq = as_sequence(evaluated_ast) |
| 287 | return TCOVal(evaluated_seq.last()) |
| 288 | } |
| 289 | |
| 290 | // EVALuate "if". |
| 291 | // |
| 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)") |
| 295 | } |
| 296 | let cond_result = try EVAL(try! list.nth(1), env) |
| 297 | var new_ast: MalVal |
| 298 | if is_truthy(cond_result) { |
| 299 | new_ast = try! list.nth(2) |
| 300 | } else if list.count == 4 { |
| 301 | new_ast = try! list.nth(3) |
| 302 | } else { |
| 303 | return TCOVal(make_nil()) |
| 304 | } |
| 305 | if TCO { |
| 306 | return TCOVal(new_ast, env) |
| 307 | } |
| 308 | return TCOVal(try EVAL(new_ast, env)) |
| 309 | } |
| 310 | |
| 311 | // EVALuate "fn*". |
| 312 | // |
| 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)") |
| 316 | } |
| 317 | guard let seq = as_sequenceQ(try! list.nth(1)) else { |
| 318 | try throw_error("expected list or vector for first argument to fn*") |
| 319 | } |
| 320 | return TCOVal(make_closure((eval: EVAL, args: seq, body: try! list.nth(2), env: env))) |
| 321 | } |
| 322 | |
| 323 | // EVALuate "quote". |
| 324 | // |
| 325 | private func eval_quote(list: MalSequence, _ env: Environment) throws -> TCOVal { |
| 326 | if list.count >= 2 { |
| 327 | return TCOVal(try! list.nth(1)) |
| 328 | } |
| 329 | return TCOVal(make_nil()) |
| 330 | } |
| 331 | |
| 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 | |
| 341 | // EVALuate "quasiquote". |
| 342 | // |
| 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) |
| 349 | } |
| 350 | return TCOVal(try EVAL(try quasiquote(try! list.nth(1)), env)) |
| 351 | } |
| 352 | |
| 353 | // EVALuate "macroexpand". |
| 354 | // |
| 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'") |
| 358 | } |
| 359 | return TCOVal(try macroexpand(try! list.nth(1), env)) |
| 360 | } |
| 361 | |
| 362 | // Walk the AST and completely evaluate it, handling macro expansions, special |
| 363 | // forms and function calls. |
| 364 | // |
| 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))") |
| 370 | } |
| 371 | |
| 372 | if DEBUG_EVAL { |
| 373 | indent = substring(INDENT_TEMPLATE, 0, EVAL_level) |
| 374 | } |
| 375 | |
| 376 | while true { |
| 377 | if DEBUG_EVAL { print("\(indent)> \(ast)") } |
| 378 | |
| 379 | if !is_list(ast) { |
| 380 | |
| 381 | // Not a list -- just evaluate and return. |
| 382 | |
| 383 | let answer = try eval_ast(ast, env) |
| 384 | if DEBUG_EVAL { print("\(indent)>>> \(answer)") } |
| 385 | return answer |
| 386 | } |
| 387 | |
| 388 | // Special handling if it's a list. |
| 389 | |
| 390 | var list = as_list(ast) |
| 391 | ast = try macroexpand(ast, env) |
| 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 | } |
| 400 | list = as_list(ast) |
| 401 | |
| 402 | if DEBUG_EVAL { print("\(indent)>. \(list)") } |
| 403 | |
| 404 | if list.isEmpty { |
| 405 | return ast |
| 406 | } |
| 407 | |
| 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() |
| 412 | if let fn_symbol = as_symbolQ(arg0) { |
| 413 | let res: TCOVal |
| 414 | |
| 415 | switch fn_symbol { |
| 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) |
| 424 | case kSymbolQuasiQuoteExp: res = try eval_quasiquoteexp(list) |
| 425 | case kSymbolMacroExpand: res = try eval_macroexpand(list, env) |
| 426 | default: res = TCOVal() |
| 427 | } |
| 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 | } |
| 434 | |
| 435 | // Standard list to be applied. Evaluate all the elements first. |
| 436 | |
| 437 | let eval = try eval_ast(ast, env) |
| 438 | |
| 439 | // The result had better be a list and better be non-empty. |
| 440 | |
| 441 | let eval_list = as_list(eval) |
| 442 | if eval_list.isEmpty { |
| 443 | return eval |
| 444 | } |
| 445 | |
| 446 | if DEBUG_EVAL { print("\(indent)>> \(eval)") } |
| 447 | |
| 448 | // Get the first element of the list and execute it. |
| 449 | |
| 450 | let first = eval_list.first() |
| 451 | let rest = as_sequence(eval_list.rest()) |
| 452 | |
| 453 | if let fn = as_builtinQ(first) { |
| 454 | let answer = try fn.apply(rest) |
| 455 | if DEBUG_EVAL { print("\(indent)>>> \(answer)") } |
| 456 | return answer |
| 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) |
| 460 | if TCO { |
| 461 | env = new_env |
| 462 | ast = fn.body |
| 463 | continue |
| 464 | } |
| 465 | let answer = try EVAL(fn.body, new_env) |
| 466 | if DEBUG_EVAL { print("\(indent)>>> \(answer)") } |
| 467 | return answer |
| 468 | } |
| 469 | |
| 470 | // The first element wasn't a function to be executed. Return an |
| 471 | // error saying so. |
| 472 | |
| 473 | try throw_error("first list item does not evaluate to a function: \(first)") |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | // Convert the value into a human-readable string for printing. |
| 478 | // |
| 479 | private func PRINT(exp: MalVal) -> String { |
| 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 | // |
| 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 | } |
| 502 | } |
| 503 | return nil |
| 504 | } |
| 505 | |
| 506 | // Perform the full READ/EVAL/PRINT, returning a printable string. |
| 507 | // |
| 508 | private func REP(text: String, _ env: Environment) -> String? { |
| 509 | let exp = RE(text, env) |
| 510 | if exp == nil { return nil } |
| 511 | return PRINT(exp!) |
| 512 | } |
| 513 | |
| 514 | // Perform the full REPL. |
| 515 | // |
| 516 | private func REPL(env: Environment) { |
| 517 | while true { |
| 518 | if let text = _readline("user> ") { |
| 519 | if let output = REP(text, env) { |
| 520 | print("\(output)") |
| 521 | } |
| 522 | } else { |
| 523 | print("") |
| 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 | // |
| 534 | private func process_command_line(args: [String], _ env: Environment) -> Bool { |
| 535 | var argv = make_list() |
| 536 | if args.count > 2 { |
| 537 | let args1 = args[2..<args.count] |
| 538 | let args2 = args1.map { make_string($0) } |
| 539 | let args3 = [MalVal](args2) |
| 540 | argv = make_list(args3) |
| 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() { |
| 553 | let env = Environment(outer: nil) |
| 554 | |
| 555 | load_history_file() |
| 556 | load_builtins(env) |
| 557 | |
| 558 | RE("(def! not (fn* (a) (if a false true)))", env) |
| 559 | RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))", env) |
| 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) |
| 562 | |
| 563 | env.set(kSymbolEval, make_builtin({ |
| 564 | try! unwrap_args($0) { |
| 565 | (ast: MalVal) -> MalVal in |
| 566 | try EVAL(ast, env) |
| 567 | } |
| 568 | })) |
| 569 | |
| 570 | if process_command_line(Process.arguments, env) { |
| 571 | REPL(env) |
| 572 | } |
| 573 | |
| 574 | save_history_file() |
| 575 | } |