Merge pull request #358 from bjh21/bjh21-extra-tests
[jackhill/mal.git] / swift / step2_eval.swift
1 //******************************************************************************
2 // MAL - step 2 - eval
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 // Parse the string into an AST.
13 //
14 private func READ(str: String) throws -> MalVal {
15 return try read_str(str)
16 }
17
18 // Perform a simple evaluation of the `ast` object. If it's a symbol,
19 // dereference it and return its value. If it's a collection, call EVAL on all
20 // elements (or just the values, in the case of the hashmap). Otherwise, return
21 // the object unchanged.
22 //
23 private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal {
24 if let symbol = as_symbolQ(ast) {
25 guard let val = env.get(symbol) else {
26 try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests
27 }
28 return val
29 }
30 if let list = as_listQ(ast) {
31 var result = [MalVal]()
32 result.reserveCapacity(Int(list.count))
33 for item in list {
34 let eval = try EVAL(item, env)
35 result.append(eval)
36 }
37 return make_list(result)
38 }
39 if let vec = as_vectorQ(ast) {
40 var result = [MalVal]()
41 result.reserveCapacity(Int(vec.count))
42 for item in vec {
43 let eval = try EVAL(item, env)
44 result.append(eval)
45 }
46 return make_vector(result)
47 }
48 if let hash = as_hashmapQ(ast) {
49 var result = [MalVal]()
50 result.reserveCapacity(Int(hash.count) * 2)
51 for (k, v) in hash {
52 let new_v = try EVAL(v, env)
53 result.append(k)
54 result.append(new_v)
55 }
56 return make_hashmap(result)
57 }
58 return ast
59 }
60
61 // Walk the AST and completely evaluate it, handling macro expansions, special
62 // forms and function calls.
63 //
64 private func EVAL(ast: MalVal, _ env: Environment) throws -> MalVal {
65
66 if !is_list(ast) {
67
68 // Not a list -- just evaluate and return.
69
70 let answer = try eval_ast(ast, env)
71 return answer
72 }
73
74 // Special handling if it's a list.
75
76 let list = as_list(ast)
77
78 if list.isEmpty {
79 return ast
80 }
81
82 // Standard list to be applied. Evaluate all the elements first.
83
84 let eval = try eval_ast(ast, env)
85
86 // The result had better be a list and better be non-empty.
87
88 let eval_list = as_list(eval)
89 if eval_list.isEmpty {
90 return eval
91 }
92
93 // Get the first element of the list and execute it.
94
95 let first = eval_list.first()
96 let rest = as_sequence(eval_list.rest())
97
98 if let fn = as_builtinQ(first) {
99 let answer = try fn.apply(rest)
100 return answer
101 }
102
103 // The first element wasn't a function to be executed. Return an
104 // error saying so.
105
106 try throw_error("first list item does not evaluate to a function: \(first)")
107 }
108
109 // Convert the value into a human-readable string for printing.
110 //
111 private func PRINT(exp: MalVal) -> String {
112 return pr_str(exp, true)
113 }
114
115 // Perform the READ and EVAL steps. Useful for when you don't care about the
116 // printable result.
117 //
118 private func RE(text: String, _ env: Environment) -> MalVal? {
119 if !text.isEmpty {
120 do {
121 let ast = try READ(text)
122 do {
123 return try EVAL(ast, env)
124 } catch let error as MalException {
125 print("Error evaluating input: \(error)")
126 } catch {
127 print("Error evaluating input: \(error)")
128 }
129 } catch let error as MalException {
130 print("Error parsing input: \(error)")
131 } catch {
132 print("Error parsing input: \(error)")
133 }
134 }
135 return nil
136 }
137
138 // Perform the full READ/EVAL/PRINT, returning a printable string.
139 //
140 private func REP(text: String, _ env: Environment) -> String? {
141 let exp = RE(text, env)
142 if exp == nil { return nil }
143 return PRINT(exp!)
144 }
145
146 // Perform the full REPL.
147 //
148 private func REPL(env: Environment) {
149 while true {
150 if let text = _readline("user> ") {
151 if let output = REP(text, env) {
152 print("\(output)")
153 }
154 } else {
155 print("")
156 break
157 }
158 }
159 }
160
161 func main() {
162 let env = Environment(outer: nil)
163
164 load_history_file()
165 load_builtins(env)
166
167 REPL(env)
168
169 save_history_file()
170 }