Commit | Line | Data |
---|---|---|
bdda2112 | 1 | |
2 | import Foundation | |
3 | ||
4 | func READ(_ input: String) throws -> MalData { | |
5 | return try read_str(input) | |
6 | } | |
7 | ||
fbfe6784 NB |
8 | func starts_with(_ ast: MalData, _ sym: String) -> MalData? { |
9 | if let list = ast as? [MalData], | |
10 | 2 == list.count, | |
11 | let a0 = list[0] as? Symbol, | |
12 | a0.name == sym { | |
13 | return list[1] | |
14 | } else { | |
15 | return nil | |
bdda2112 | 16 | } |
fbfe6784 NB |
17 | } |
18 | ||
19 | func qqIter(_ lst: [MalData]) -> MalData { | |
20 | var result:MalData = [] | |
21 | for elt in lst.reversed() { | |
22 | if let x = starts_with(elt, "splice-unquote") { | |
23 | result = [Symbol("concat"), x, result] | |
24 | } else { | |
25 | result = [Symbol("cons"), quasiquote(elt), result] | |
bdda2112 | 26 | } |
fbfe6784 NB |
27 | } |
28 | return result | |
29 | } | |
30 | ||
31 | func quasiquote(_ ast: MalData) -> MalData { | |
32 | switch ast.dataType { | |
33 | case .List: | |
34 | if let x = starts_with(ast, "unquote") { | |
35 | return x | |
36 | } else { | |
37 | return qqIter (ast.listForm) | |
bdda2112 | 38 | } |
fbfe6784 NB |
39 | case .Vector: |
40 | return [Symbol("vec"), qqIter (ast.listForm)] | |
41 | case .Symbol: | |
42 | return [Symbol("quote"), ast] | |
43 | case .HashMap: | |
44 | return [Symbol("quote"), ast] | |
45 | default: | |
46 | return ast | |
bdda2112 | 47 | } |
fbfe6784 | 48 | } |
bdda2112 | 49 | |
fbfe6784 | 50 | func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData { |
bdda2112 | 51 | var ast = anAst, env = anEnv |
52 | while true { | |
53 | switch ast.dataType { | |
54 | case .List: | |
55 | let list = ast as! [MalData] | |
56 | guard !list.isEmpty else { return list } | |
57 | if let sym = list[0] as? Symbol { | |
58 | switch sym.name { | |
59 | case "def!": | |
60 | let value = try EVAL(list[2], env: env), key = list[1] as! Symbol | |
61 | env.set(value, forKey: key) | |
62 | return value | |
63 | case "let*": | |
64 | let newEnv = Env(outer: env), expr = list[2] | |
65 | let bindings = list[1].listForm | |
66 | for i in stride(from: 0, to: bindings.count-1, by: 2) { | |
67 | let key = bindings[i], value = bindings[i+1] | |
68 | let result = try EVAL(value, env: newEnv) | |
69 | newEnv.set(result, forKey: key as! Symbol) | |
70 | } | |
71 | env = newEnv | |
72 | ast = expr | |
73 | continue | |
74 | case "do": | |
75 | try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) } | |
76 | ast = list.last ?? Nil() | |
77 | continue | |
78 | case "if": | |
79 | let predicate = try EVAL(list[1], env: env) | |
80 | if predicate as? Bool == false || predicate is Nil { | |
81 | ast = list.count>3 ? list[3] : Nil() | |
82 | } else { | |
83 | ast = list[2] | |
84 | } | |
85 | continue | |
90ca8485 | 86 | case "fn*": |
bdda2112 | 87 | let fn = {(params: [MalData]) -> MalData in |
88 | let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env) | |
89 | return try EVAL(list[2], env: newEnv) | |
90ca8485 | 90 | } |
bdda2112 | 91 | return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn) |
92 | case "quote": | |
93 | return list[1] | |
fbfe6784 NB |
94 | case "quasiquoteexpand": |
95 | return quasiquote(list[1]) | |
bdda2112 | 96 | case "quasiquote": |
97 | ast = quasiquote(list[1]) | |
98 | continue | |
99 | default: | |
100 | break | |
101 | } | |
102 | } | |
103 | // not a symbol. maybe: function, list, or some wrong type | |
104 | let evaluated = try eval_ast(list, env: env) as! [MalData] | |
105 | guard let function = evaluated[0] as? Function else { | |
106 | throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol")) | |
107 | } | |
108 | if let fnAst = function.ast { // a full fn | |
109 | ast = fnAst | |
110 | env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!) | |
111 | } else { // normal function | |
112 | return try function.fn(evaluated.dropFirst().listForm) | |
113 | } | |
114 | continue | |
115 | case .Vector: | |
116 | let vector = ast as! ContiguousArray<MalData> | |
117 | return try ContiguousArray(vector.map { element in try EVAL(element, env: env) }) | |
118 | case .HashMap: | |
119 | let hashMap = ast as! HashMap<String, MalData> | |
120 | return try hashMap.mapValues { value in try EVAL(value, env: env) } | |
121 | default: | |
122 | return try eval_ast(ast, env: env) | |
123 | } | |
124 | } | |
125 | } | |
126 | ||
127 | func PRINT(_ input: MalData) -> String { | |
128 | return pr_str(input, print_readably: true) | |
129 | } | |
130 | ||
131 | @discardableResult func rep(_ input: String, env: Env) throws -> String { | |
132 | return try PRINT(EVAL(READ(input), env: env)) | |
133 | } | |
134 | ||
135 | func eval_ast(_ ast: MalData, env: Env) throws -> MalData { | |
136 | switch ast.dataType { | |
137 | case .Symbol: | |
138 | let sym = ast as! Symbol | |
139 | if let function = try? env.get(forKey: sym) { | |
140 | return function | |
141 | } else { | |
142 | throw MalError.SymbolNotFound(sym) | |
143 | } | |
144 | case .List: | |
145 | let list = ast as! [MalData] | |
146 | return try list.map { element in try EVAL(element, env: env) } | |
147 | case .Atom: | |
148 | return (ast as! Atom).value | |
149 | default: | |
150 | return ast | |
151 | } | |
152 | } | |
153 | ||
154 | ||
155 | ||
156 | var repl_env = Env() | |
157 | for (key, value) in ns { | |
158 | repl_env.set(Function(fn: value), forKey: Symbol(key)) | |
159 | } | |
160 | repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval")) | |
161 | repl_env.set([], forKey: Symbol("*ARGV*")) | |
162 | ||
163 | try rep("(def! not (fn* (a) (if a false true)))", env: repl_env) | |
e6d41de4 | 164 | try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", env: repl_env) |
bdda2112 | 165 | |
166 | if CommandLine.argc > 1 { | |
167 | let fileName = CommandLine.arguments[1], | |
168 | args = List(CommandLine.arguments.dropFirst(2)) | |
169 | repl_env.set(args, forKey: Symbol("*ARGV*")) | |
170 | try rep("(load-file \"\(fileName)\")", env: repl_env) | |
171 | exit(0) | |
172 | } | |
173 | ||
174 | while true { | |
175 | print("user> ", terminator: "") | |
176 | if let input = readLine(strippingNewline: true) { | |
177 | guard input != "" else { continue } | |
178 | do { | |
179 | try print(rep(input, env: repl_env)) | |
180 | } catch let error as MalError { | |
181 | print(error.info()) | |
182 | } | |
df6c9561 JM |
183 | } else { |
184 | exit(0); | |
bdda2112 | 185 | } |
186 | } |