Commit | Line | Data |
---|---|---|
0eace3df JM |
1 | import Foundation |
2 | ||
3 | // read | |
8903188f | 4 | func READ(_ str: String) throws -> MalVal { |
0eace3df JM |
5 | return try read_str(str) |
6 | } | |
7 | ||
8 | // eval | |
fbfe6784 NB |
9 | |
10 | func starts_with(_ ast: MalVal, _ sym: String) -> MalVal? { | |
0eace3df | 11 | switch ast { |
fbfe6784 NB |
12 | case MalVal.MalList(let lst, _) where 1 < lst.count: |
13 | switch lst[0] { | |
14 | case MalVal.MalSymbol(sym): | |
15 | return lst[1] | |
16 | default: | |
17 | return nil | |
18 | } | |
19 | default: | |
20 | return nil | |
0eace3df JM |
21 | } |
22 | } | |
23 | ||
fbfe6784 NB |
24 | func qqIter(_ lst: [MalVal]) -> MalVal { |
25 | var result = list([]) | |
26 | for elt in lst.reversed() { | |
27 | if let elt1 = starts_with(elt, "splice-unquote") { | |
28 | result = list([MalVal.MalSymbol("concat"), elt1, result]) | |
29 | } else { | |
30 | result = list([MalVal.MalSymbol("cons"), quasiquote(elt), result]) | |
31 | } | |
0eace3df | 32 | } |
fbfe6784 NB |
33 | return result |
34 | } | |
35 | ||
36 | func quasiquote(_ ast: MalVal) -> MalVal { | |
37 | if let a1 = starts_with(ast, "unquote") { | |
38 | return a1 | |
0eace3df | 39 | } |
fbfe6784 NB |
40 | switch ast { |
41 | case MalVal.MalList(let lst, _): | |
42 | return qqIter(lst) | |
43 | case MalVal.MalVector(let lst, _): | |
44 | return list([MalVal.MalSymbol("vec"), qqIter(lst)]) | |
45 | case MalVal.MalSymbol: | |
46 | return list([MalVal.MalSymbol("quote"), ast]) | |
47 | case MalVal.MalHashMap: | |
48 | return list([MalVal.MalSymbol("quote"), ast]) | |
49 | default: | |
50 | return ast | |
0eace3df | 51 | } |
0eace3df JM |
52 | } |
53 | ||
8903188f | 54 | func is_macro(_ ast: MalVal, _ env: Env) -> Bool { |
0eace3df | 55 | switch ast { |
b8ab80ba | 56 | case MalVal.MalList(let lst, _) where lst.count > 0: |
0eace3df JM |
57 | let a0 = lst[lst.startIndex] |
58 | switch a0 { | |
59 | case MalVal.MalSymbol: | |
60 | let e = try! env.find(a0) | |
61 | if e != nil { | |
62 | let mac = try! e!.get(a0) | |
63 | switch mac { | |
64 | case MalVal.MalFunc(_,_,_,_,let macro,_): return macro | |
65 | default: return false | |
66 | } | |
67 | } else { | |
68 | return false | |
69 | } | |
70 | default: return false | |
71 | } | |
72 | default: return false | |
73 | } | |
74 | } | |
75 | ||
8903188f | 76 | func macroexpand(_ orig_ast: MalVal, _ env: Env) throws -> MalVal { |
0eace3df JM |
77 | var ast: MalVal = orig_ast |
78 | while is_macro(ast, env) { | |
79 | switch try! env.get(try! _nth(ast, 0)) { | |
80 | case MalVal.MalFunc(let mac,_,_,_,_,_): | |
81 | ast = try mac(_rest(ast)) | |
82 | default: throw MalError.General(msg: "impossible state in macroexpand") | |
83 | } | |
84 | } | |
85 | return ast | |
86 | } | |
87 | ||
8903188f | 88 | func eval_ast(_ ast: MalVal, _ env: Env) throws -> MalVal { |
0eace3df JM |
89 | switch ast { |
90 | case MalVal.MalSymbol: | |
91 | return try env.get(ast) | |
b8ab80ba JM |
92 | case MalVal.MalList(let lst, _): |
93 | return list(try lst.map { try EVAL($0, env) }) | |
94 | case MalVal.MalVector(let lst, _): | |
95 | return vector(try lst.map { try EVAL($0, env) }) | |
96 | case MalVal.MalHashMap(let dict, _): | |
0eace3df JM |
97 | var new_dict = Dictionary<String,MalVal>() |
98 | for (k,v) in dict { new_dict[k] = try EVAL(v, env) } | |
b8ab80ba | 99 | return hash_map(new_dict) |
0eace3df JM |
100 | default: |
101 | return ast | |
102 | } | |
103 | } | |
104 | ||
8903188f | 105 | func EVAL(_ orig_ast: MalVal, _ orig_env: Env) throws -> MalVal { |
0eace3df JM |
106 | var ast = orig_ast, env = orig_env |
107 | while true { | |
108 | switch ast { | |
fc0cddfd | 109 | case MalVal.MalList(let lst, _): if lst.count == 0 { return ast } |
0eace3df JM |
110 | default: return try eval_ast(ast, env) |
111 | } | |
112 | ||
113 | ast = try macroexpand(ast, env) | |
114 | switch ast { | |
a3a6f680 | 115 | case MalVal.MalList: break |
0eace3df JM |
116 | default: return try eval_ast(ast, env) |
117 | } | |
118 | ||
119 | switch ast { | |
b8ab80ba | 120 | case MalVal.MalList(let lst, _): |
0eace3df JM |
121 | switch lst[0] { |
122 | case MalVal.MalSymbol("def!"): | |
123 | return try env.set(lst[1], try EVAL(lst[2], env)) | |
124 | case MalVal.MalSymbol("let*"): | |
125 | let let_env = try Env(env) | |
126 | var binds = Array<MalVal>() | |
127 | switch lst[1] { | |
b8ab80ba JM |
128 | case MalVal.MalList(let l, _): binds = l |
129 | case MalVal.MalVector(let l, _): binds = l | |
0eace3df JM |
130 | default: |
131 | throw MalError.General(msg: "Invalid let* bindings") | |
132 | } | |
133 | var idx = binds.startIndex | |
134 | while idx < binds.endIndex { | |
8903188f | 135 | let v = try EVAL(binds[binds.index(after: idx)], let_env) |
0eace3df | 136 | try let_env.set(binds[idx], v) |
8903188f | 137 | idx = binds.index(idx, offsetBy: 2) |
0eace3df JM |
138 | } |
139 | env = let_env | |
140 | ast = lst[2] // TCO | |
141 | case MalVal.MalSymbol("quote"): | |
142 | return lst[1] | |
fbfe6784 NB |
143 | case MalVal.MalSymbol("quasiquoteexpand"): |
144 | return quasiquote(lst[1]) | |
0eace3df JM |
145 | case MalVal.MalSymbol("quasiquote"): |
146 | ast = quasiquote(lst[1]) // TCO | |
147 | case MalVal.MalSymbol("defmacro!"): | |
148 | var mac = try EVAL(lst[2], env) | |
149 | switch mac { | |
150 | case MalVal.MalFunc(let fn, let a, let e, let p, _, let m): | |
b8ab80ba | 151 | mac = malfunc(fn,ast:a,env:e,params:p,macro:true,meta:m) |
0eace3df JM |
152 | default: throw MalError.General(msg: "invalid defmacro! form") |
153 | } | |
154 | return try env.set(lst[1], mac) | |
155 | case MalVal.MalSymbol("macroexpand"): | |
156 | return try macroexpand(lst[1], env) | |
157 | case MalVal.MalSymbol("do"): | |
8903188f | 158 | let slc = lst[1..<lst.index(before: lst.endIndex)] |
a3a6f680 | 159 | try _ = eval_ast(list(Array(slc)), env) |
8903188f | 160 | ast = lst[lst.index(before: lst.endIndex)] // TCO |
0eace3df JM |
161 | case MalVal.MalSymbol("if"): |
162 | switch try EVAL(lst[1], env) { | |
163 | case MalVal.MalFalse, MalVal.MalNil: | |
164 | if lst.count > 3 { | |
165 | ast = lst[3] // TCO | |
166 | } else { | |
167 | return MalVal.MalNil | |
168 | } | |
169 | default: | |
170 | ast = lst[2] // TCO | |
171 | } | |
172 | case MalVal.MalSymbol("fn*"): | |
b8ab80ba | 173 | return malfunc( { |
0eace3df | 174 | return try EVAL(lst[2], Env(env, binds: lst[1], |
b8ab80ba JM |
175 | exprs: list($0))) |
176 | }, ast:[lst[2]], env:env, params:[lst[1]]) | |
0eace3df JM |
177 | default: |
178 | switch try eval_ast(ast, env) { | |
b8ab80ba | 179 | case MalVal.MalList(let elst, _): |
0eace3df JM |
180 | switch elst[0] { |
181 | case MalVal.MalFunc(let fn, nil, _, _, _, _): | |
182 | let args = Array(elst[1..<elst.count]) | |
183 | return try fn(args) | |
184 | case MalVal.MalFunc(_, let a, let e, let p, _, _): | |
185 | let args = Array(elst[1..<elst.count]) | |
186 | env = try Env(e, binds: p![0], | |
b8ab80ba | 187 | exprs: list(args)) // TCO |
0eace3df JM |
188 | ast = a![0] // TCO |
189 | default: | |
190 | throw MalError.General(msg: "Cannot apply on '\(elst[0])'") | |
191 | } | |
192 | default: throw MalError.General(msg: "Invalid apply") | |
193 | } | |
194 | } | |
195 | default: | |
196 | throw MalError.General(msg: "Invalid apply") | |
197 | } | |
198 | } | |
199 | } | |
200 | ||
201 | ||
8903188f | 202 | func PRINT(_ exp: MalVal) -> String { |
0eace3df JM |
203 | return pr_str(exp, true) |
204 | } | |
205 | ||
206 | ||
207 | // repl | |
a3a6f680 | 208 | @discardableResult |
8903188f | 209 | func rep(_ str:String) throws -> String { |
0eace3df JM |
210 | return PRINT(try EVAL(try READ(str), repl_env)) |
211 | } | |
212 | ||
213 | var repl_env: Env = try Env() | |
214 | ||
215 | // core.swift: defined using Swift | |
216 | for (k, fn) in core_ns { | |
b8ab80ba | 217 | try repl_env.set(MalVal.MalSymbol(k), malfunc(fn)) |
0eace3df JM |
218 | } |
219 | try repl_env.set(MalVal.MalSymbol("eval"), | |
b8ab80ba | 220 | malfunc({ try EVAL($0[0], repl_env) })) |
42b8fe16 | 221 | let pargs = CommandLine.arguments.map { MalVal.MalString($0) } |
0eace3df JM |
222 | // TODO: weird way to get empty list, fix this |
223 | var args = pargs[pargs.startIndex..<pargs.startIndex] | |
8903188f FI |
224 | if pargs.index(pargs.startIndex, offsetBy:2) < pargs.endIndex { |
225 | args = pargs[pargs.index(pargs.startIndex, offsetBy:2)..<pargs.endIndex] | |
0eace3df | 226 | } |
b8ab80ba | 227 | try repl_env.set(MalVal.MalSymbol("*ARGV*"), list(Array(args))) |
0eace3df JM |
228 | |
229 | // core.mal: defined using the language itself | |
230 | try rep("(def! not (fn* (a) (if a false true)))") | |
e6d41de4 | 231 | try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))") |
0eace3df | 232 | try rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") |
0eace3df JM |
233 | |
234 | ||
42b8fe16 JM |
235 | if CommandLine.arguments.count > 1 { |
236 | try rep("(load-file \"" + CommandLine.arguments[1] + "\")") | |
0eace3df JM |
237 | exit(0) |
238 | } | |
239 | ||
240 | while true { | |
241 | print("user> ", terminator: "") | |
8903188f | 242 | let line = readLine(strippingNewline: true) |
0eace3df JM |
243 | if line == nil { break } |
244 | if line == "" { continue } | |
245 | ||
246 | do { | |
247 | print(try rep(line!)) | |
248 | } catch (MalError.Reader(let msg)) { | |
249 | print("Error: \(msg)") | |
250 | } catch (MalError.General(let msg)) { | |
251 | print("Error: \(msg)") | |
dd7a4f55 JM |
252 | } catch (MalError.MalException(let obj)) { |
253 | print("Error: \(pr_str(obj, true))") | |
0eace3df JM |
254 | } |
255 | } |