Change quasiquote algorithm
[jackhill/mal.git] / impls / swift3 / Sources / step8_macros / main.swift
CommitLineData
0eace3df
JM
1import Foundation
2
3// read
8903188f 4func READ(_ str: String) throws -> MalVal {
0eace3df
JM
5 return try read_str(str)
6}
7
8// eval
fbfe6784
NB
9
10func 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
24func 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
36func 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 54func 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 76func 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 88func 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 105func 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// print
8903188f 202func PRINT(_ exp: MalVal) -> String {
0eace3df
JM
203 return pr_str(exp, true)
204}
205
206
207// repl
a3a6f680 208@discardableResult
8903188f 209func rep(_ str:String) throws -> String {
0eace3df
JM
210 return PRINT(try EVAL(try READ(str), repl_env))
211}
212
213var repl_env: Env = try Env()
214
215// core.swift: defined using Swift
216for (k, fn) in core_ns {
b8ab80ba 217 try repl_env.set(MalVal.MalSymbol(k), malfunc(fn))
0eace3df
JM
218}
219try repl_env.set(MalVal.MalSymbol("eval"),
b8ab80ba 220 malfunc({ try EVAL($0[0], repl_env) }))
42b8fe16 221let pargs = CommandLine.arguments.map { MalVal.MalString($0) }
0eace3df
JM
222// TODO: weird way to get empty list, fix this
223var args = pargs[pargs.startIndex..<pargs.startIndex]
8903188f
FI
224if pargs.index(pargs.startIndex, offsetBy:2) < pargs.endIndex {
225 args = pargs[pargs.index(pargs.startIndex, offsetBy:2)..<pargs.endIndex]
0eace3df 226}
b8ab80ba 227try repl_env.set(MalVal.MalSymbol("*ARGV*"), list(Array(args)))
0eace3df
JM
228
229// core.mal: defined using the language itself
230try rep("(def! not (fn* (a) (if a false true)))")
e6d41de4 231try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
0eace3df 232try 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
235if CommandLine.arguments.count > 1 {
236 try rep("(load-file \"" + CommandLine.arguments[1] + "\")")
0eace3df
JM
237 exit(0)
238}
239
240while 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}