Add support for vectors in all steps
[jackhill/mal.git] / elixir / lib / mix / tasks / step8_macros.ex
1 defmodule Mix.Tasks.Step8Macros do
2 def run(args) do
3 env = Mal.Env.initialize()
4 Mal.Env.merge(env, Mal.Core.namespace)
5 bootstrap(args, env)
6 load_file(args, env)
7 main(env)
8 end
9
10 defp load_file([], _env), do: nil
11 defp load_file([file_name | _args], env) do
12 read_eval_print("""
13 (load-file "#{file_name}")
14 """, env)
15 exit(:normal)
16 end
17
18 defp bootstrap(args, env) do
19 # not:
20 read_eval_print("""
21 (def! not
22 (fn* (a) (if a false true)))
23 """, env)
24
25 # load-file:
26 read_eval_print("""
27 (def! load-file
28 (fn* (f)
29 (eval (read-string (str "(do " (slurp f) ")")))))
30 """, env)
31
32 # cond
33 read_eval_print("""
34 (defmacro! cond
35 (fn* (& xs)
36 (if (> (count xs) 0)
37 (list 'if (first xs)
38 (if (> (count xs) 1)
39 (nth xs 1)
40 (throw \"odd number of forms to cond\"))
41 (cons 'cond (rest (rest xs)))))))"
42 """, env)
43
44 # or:
45 read_eval_print("""
46 (defmacro! or
47 (fn* (& xs)
48 (if (empty? xs)
49 nil
50 (if (= 1 (count xs))
51 (first xs)
52 `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))
53 """, env)
54
55 Mal.Env.set(env, "eval", fn [ast] ->
56 eval(ast, env)
57 end)
58
59 case args do
60 [_file_name | rest] -> Mal.Env.set(env, "*ARGV*", rest)
61 [] -> Mal.Env.set(env, "*ARGV*", [])
62 end
63 end
64
65 def main(env) do
66 IO.write(:stdio, "user> ")
67 IO.read(:stdio, :line)
68 |> read_eval_print(env)
69
70 main(env)
71 end
72
73 def eval_ast(ast, env) when is_list(ast) do
74 Enum.map(ast, fn elem -> eval(elem, env) end)
75 end
76
77 def eval_ast({:vector, ast}, env) do
78 {:vector, Enum.map(ast, fn elem -> eval(elem, env) end)}
79 end
80
81 def eval_ast({:symbol, symbol}, env) do
82 case Mal.Env.get(env, symbol) do
83 {:ok, value} -> value
84 :not_found -> throw({:error, "invalid symbol #{symbol}"})
85 end
86 end
87
88 def eval_ast(ast, _env), do: ast
89
90 def read(input) do
91 Mal.Reader.read_str(input)
92 end
93
94 defp eval_bindings({:vector, vector}, env), do: eval_bindings(vector, env)
95 defp eval_bindings([], _env), do: _env
96 defp eval_bindings([{:symbol, key}, binding | tail], env) do
97 evaluated = eval(binding, env)
98 Mal.Env.set(env, key, evaluated)
99 eval_bindings(tail, env)
100 end
101 defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
102
103 defp quasiquote({:vector, list}, _env), do: quasiquote(list, _env)
104 defp quasiquote(ast, _env) when not is_list(ast), do: [{:symbol, "quote"}, ast]
105 defp quasiquote([], _env), do: [{:symbol, "quote"}, []]
106 defp quasiquote([{:symbol, "unquote"}, arg], _env), do: arg
107 defp quasiquote([[{:symbol, "splice-unquote"}, first] | tail], env) do
108 [{:symbol, "concat"}, first, quasiquote(tail, env)]
109 end
110 defp quasiquote([head | tail], env) do
111 [{:symbol, "cons"}, quasiquote(head, env), quasiquote(tail, env)]
112 end
113
114 defp macro_call?([{:symbol, key} | _tail], env) do
115 case Mal.Env.get(env, key) do
116 {:ok, {:macro, _}} -> true
117 _ -> false
118 end
119 end
120 defp macro_call?(_ast, _env), do: false
121
122 defp do_macro_call([{:symbol, key} | tail], env) do
123 {:ok, {:macro, macro}} = Mal.Env.get(env, key)
124 macro.(tail)
125 |> macroexpand(env)
126 end
127
128 def macroexpand(ast, env) do
129 if macro_call?(ast, env) do
130 do_macro_call(ast, env)
131 else
132 ast
133 end
134 end
135
136 def eval(ast, env) when not is_list(ast), do: eval_ast(ast, env)
137 def eval(ast, env) when is_list(ast) do
138 case macroexpand(ast, env) do
139 result when is_list(result) -> eval_list(result, env)
140 result -> result
141 end
142 end
143
144 def eval_list([{:symbol, "macroexpand"}, ast], env), do: macroexpand(ast, env)
145
146 def eval_list([{:symbol, "if"}, condition, if_true | if_false], env) do
147 result = eval(condition, env)
148 if result == nil or result == false do
149 case if_false do
150 [] -> nil
151 [body] -> eval(body, env)
152 end
153 else
154 eval(if_true, env)
155 end
156 end
157
158 def eval_list([{:symbol, "do"} | ast], env) do
159 eval_ast(List.delete_at(ast, -1), env)
160 eval(List.last(ast), env)
161 end
162
163 def eval_list([{:symbol, "def!"}, {:symbol, key}, value], env) do
164 evaluated = eval(value, env)
165 Mal.Env.set(env, key, evaluated)
166 evaluated
167 end
168
169 def eval_list([{:symbol, "defmacro!"}, {:symbol, key}, function], env) do
170 {:closure, evaluated} = eval(function, env)
171 macro = {:macro, evaluated}
172 Mal.Env.set(env, key, macro)
173 macro
174 end
175
176 def eval_list([{:symbol, "let*"}, bindings, body], env) do
177 let_env = Mal.Env.initialize(env)
178 eval_bindings(bindings, let_env)
179 eval(body, let_env)
180 end
181
182 def eval_list([{:symbol, "fn*"}, {:vector, params}, body], env) do
183 eval_list([{:symbol, "fn*"}, params, body], env)
184 end
185 def eval_list([{:symbol, "fn*"}, params, body], env) do
186 param_symbols = for {:symbol, symbol} <- params, do: symbol
187
188 closure = fn args ->
189 inner = Mal.Env.initialize(env, param_symbols, args)
190 eval(body, inner)
191 end
192
193 {:closure, closure}
194 end
195
196 def eval_list([{:symbol, "quote"}, arg], _env), do: arg
197
198 def eval_list([{:symbol, "quasiquote"}, ast], env) do
199 quasiquote(ast, env)
200 |> eval(env)
201 end
202
203 def eval_list(ast, env) do
204 [func | args] = eval_ast(ast, env)
205 case func do
206 {:closure, closure} -> closure.(args)
207 _ -> func.(args)
208 end
209 end
210
211 def print(value) do
212 IO.puts(Mal.Printer.print_str(value))
213 end
214
215 def read_eval_print(:eof, _env), do: exit(:normal)
216 def read_eval_print(line, env) do
217 read(line)
218 |> eval(env)
219 |> print
220 catch
221 {:error, message} -> IO.puts("Error: #{message}")
222 end
223 end