optimization: for small Environment records, store names/values in hard-coded slots...
[jackhill/mal.git] / swift / step4_if_fn_do.swift
CommitLineData
2539e6af
KR
1//******************************************************************************
2// MAL - step 4 - if/fn/do
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
10import Foundation
11
12let kSymbolDef = MalSymbol(symbol: "def!")
13let kSymbolDo = MalSymbol(symbol: "do")
14let kSymbolFunction = MalSymbol(symbol: "fn*")
15let kSymbolIf = MalSymbol(symbol: "if")
16let kSymbolLet = MalSymbol(symbol: "let*")
17
18// Parse the string into an AST.
19//
20func READ(str: String) -> MalVal {
21 return read_str(str)
22}
23
24// Perform a simple evaluation of the `ast` object. If it's a symbol,
25// dereference it and return its value. If it's a collection, call EVAL on all
26// elements (or just the values, in the case of the hashmap). Otherwise, return
27// the object unchanged.
28//
29func eval_ast(ast: MalVal, env: Environment) -> MalVal {
30 if is_symbol(ast) {
31 let symbol = ast as MalSymbol
32 if let val = env.get(symbol) {
33 return val
34 }
35 return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
36 }
37 if is_list(ast) {
38 let list = ast as MalList
39 var result = [MalVal]()
40 result.reserveCapacity(list.count)
41 for item in list {
42 let eval = EVAL(item, env)
43 if is_error(eval) { return eval }
44 result.append(eval)
45 }
46 return MalList(array: result)
47 }
48 if is_vector(ast) {
49 let vec = ast as MalVector
50 var result = [MalVal]()
51 result.reserveCapacity(vec.count)
52 for item in vec {
53 let eval = EVAL(item, env)
54 if is_error(eval) { return eval }
55 result.append(eval)
56 }
57 return MalVector(array: result)
58 }
59 if is_hashmap(ast) {
60 let hash = ast as MalHashMap
61 var result = [MalVal]()
62 result.reserveCapacity(hash.count * 2)
63 for (k, v) in hash {
64 let new_v = EVAL(v, env)
65 if is_error(new_v) { return new_v }
66 result.append(k)
67 result.append(new_v)
68 }
69 return MalHashMap(array: result)
70 }
71 return ast
72}
73
74// Walk the AST and completely evaluate it, handling macro expansions, special
75// forms and function calls.
76//
77func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
78 if is_error(ast) { return ast }
79
80 // Special handling if it's a list.
81
82 if is_list(ast) {
83 var list = ast as MalList
84
85 if list.isEmpty {
86 return ast
87 }
88
89 let arg1 = list.first()
90 if is_symbol(arg1) {
91 let fn_symbol = arg1 as MalSymbol
92
93 // Check for special forms, where we want to check the operation
94 // before evaluating all of the parameters.
95
96 if fn_symbol == kSymbolDef {
97 if list.count != 3 {
98 return MalError(message: "expected 2 arguments to def!, got \(list.count - 1)")
99 }
100 let arg1 = list[1]
101 let arg2 = list[2]
102 if !is_symbol(arg1) {
103 return MalError(message: "expected symbol for first argument to def!")
104 }
105 let sym = arg1 as MalSymbol
106 let value = EVAL(arg2, env)
107 if is_error(value) { return value }
108 return env.set(sym, value)
109 } else if fn_symbol == kSymbolLet {
110 if list.count != 3 {
111 return MalError(message: "expected 2 arguments to let*, got \(list.count - 1)")
112 }
113 let arg1 = list[1]
114 let arg2 = list[2]
115 if !is_sequence(arg1) {
116 return MalError(message: "expected list for first argument to let*")
117 }
118 let bindings = arg1 as MalSequence
119 if bindings.count % 2 == 1 {
120 return MalError(message: "expected even number of elements in bindings to let*, got \(bindings.count)")
121 }
122 var new_env = Environment(outer: env)
123 for var index = 0; index < bindings.count; index += 2 {
124 let binding_name = bindings[index]
125 let binding_value = bindings[index + 1]
126
127 if !is_symbol(binding_name) {
128 return MalError(message: "expected symbol for first element in binding pair")
129 }
130 let binding_symbol = binding_name as MalSymbol
131 let evaluated_value = EVAL(binding_value, new_env)
132 if is_error(evaluated_value) { return evaluated_value }
133 new_env.set(binding_symbol, evaluated_value)
134 }
135 return EVAL(arg2, new_env)
136 } else if fn_symbol == kSymbolDo {
137 let evaluated_ast = eval_ast(list.rest(), env)
138 if is_error(evaluated_ast) { return evaluated_ast }
139 let evaluated_seq = evaluated_ast as MalSequence
140 return evaluated_seq.last()
141 } else if fn_symbol == kSymbolIf {
142 if list.count < 3 {
143 return MalError(message: "expected at least 2 arguments to if, got \(list.count - 1)")
144 }
145 let cond_result = EVAL(list[1], env)
146 var new_ast = MalVal()
147 if is_truthy(cond_result) {
148 new_ast = list[2]
149 } else if list.count == 4 {
150 new_ast = list[3]
151 } else {
152 return MalNil()
153 }
154 return EVAL(new_ast, env)
155 } else if fn_symbol == kSymbolFunction {
156 if list.count != 3 {
157 return MalError(message: "expected 2 arguments to fn*, got \(list.count - 1)")
158 }
159 if !is_sequence(list[1]) {
160 return MalError(message: "expected list or vector for first argument to fn*")
161 }
162 return MalClosure(eval: EVAL, args:list[1] as MalSequence, body:list[2], env:env)
163 }
164 }
165
166 // Standard list to be applied. Evaluate all the elements first.
167
168 let eval = eval_ast(ast, env)
169 if is_error(eval) { return eval }
170
171 // The result had better be a list and better be non-empty.
172
173 let eval_list = eval as MalList
174 if eval_list.isEmpty {
175 return eval_list
176 }
177
178 // Get the first element of the list and execute it.
179
180 let first = eval_list.first()
181 let rest = eval_list.rest()
182
183 if is_builtin(first) {
184 let fn = first as MalBuiltin
185 let answer = fn.apply(rest)
186 return answer
187 } else if is_closure(first) {
188 let fn = first as MalClosure
189 var new_env = Environment(outer: fn.env)
190 let result = new_env.set_bindings(fn.args, with_exprs:rest)
191 if is_error(result) { return result }
192 let answer = EVAL(fn.body, new_env)
193 return answer
194 }
195
196 // The first element wasn't a function to be executed. Return an
197 // error saying so.
198
199 return MalError(message: "first list item does not evaluate to a function: \(first)")
200 }
201
202 // Not a list -- just evaluate and return.
203
204 let answer = eval_ast(ast, env)
205 return answer
206}
207
208// Convert the value into a human-readable string for printing.
209//
210func PRINT(exp: MalVal) -> String? {
211 if is_error(exp) { return nil }
212 return pr_str(exp, true)
213}
214
215// Perform the READ and EVAL steps. Useful for when you don't care about the
216// printable result.
217//
218func RE(text: String, env: Environment) -> MalVal? {
219 if text.isEmpty { return nil }
220 let ast = READ(text)
221 if is_error(ast) {
222 println("Error parsing input: \(ast)")
223 return nil
224 }
225 let exp = EVAL(ast, env)
226 if is_error(exp) {
227 println("Error evaluating input: \(exp)")
228 return nil
229 }
230 return exp
231}
232
233// Perform the full READ/EVAL/PRINT, returning a printable string.
234//
235func REP(text: String, env: Environment) -> String? {
236 let exp = RE(text, env)
237 if exp == nil { return nil }
238 return PRINT(exp!)
239}
240
241// Perform the full REPL.
242//
243func REPL(env: Environment) {
244 while true {
245 if let text = _readline("user> ") {
246 if let output = REP(text, env) {
247 println("\(output)")
248 }
249 } else {
250 println()
251 break
252 }
253 }
254}
255
256func main() {
257 var env = Environment(outer: nil)
258
259 load_history_file()
260 load_builtins(env)
261
262 RE("(def! not (fn* (a) (if a false true)))", env)
263 REPL(env)
264
265 save_history_file()
266}