Move implementations into impls/ dir
[jackhill/mal.git] / impls / elixir / lib / mix / tasks / stepA_mal.ex
1 defmodule Mix.Tasks.StepAMal do
2 import Mal.Types
3 alias Mal.Function
4
5 # for escript execution
6 def main(args) do
7 run(args)
8 end
9
10 def run(args) do
11 env = Mal.Env.new()
12 Mal.Env.merge(env, Mal.Core.namespace)
13 bootstrap(args, env)
14 loop(env)
15 end
16
17 defp load_file(file_name, env) do
18 read_eval_print("""
19 (load-file "#{file_name}")
20 """, env)
21 exit(:normal)
22 end
23
24 defp bootstrap(args, env) do
25 # *host-language*
26 read_eval_print("(def! *host-language* \"Elixir\")", env)
27
28 # not:
29 read_eval_print("""
30 (def! not
31 (fn* (a) (if a false true)))
32 """, env)
33
34 # load-file:
35 read_eval_print("""
36 (def! load-file
37 (fn* (f)
38 (eval (read-string (str "(do " (slurp f) "\nnil)")))))
39 """, env)
40
41 # cond
42 read_eval_print("""
43 (defmacro! cond
44 (fn* (& xs)
45 (if (> (count xs) 0)
46 (list 'if (first xs)
47 (if (> (count xs) 1)
48 (nth xs 1)
49 (throw \"odd number of forms to cond\"))
50 (cons 'cond (rest (rest xs)))))))"
51 """, env)
52
53 Mal.Env.set(env, "eval", %Function{value: fn [ast] ->
54 eval(ast, env)
55 end})
56
57 case args do
58 [file_name | rest] ->
59 Mal.Env.set(env, "*ARGV*", list(rest))
60 load_file(file_name, env)
61
62 [] ->
63 Mal.Env.set(env, "*ARGV*", list([]))
64 read_eval_print("(println (str \"Mal [\" *host-language* \"]\"))", env)
65 end
66 end
67
68 defp loop(env) do
69 IO.write(:stdio, "user> ")
70 IO.read(:stdio, :line)
71 |> read_eval_print(env)
72 |> IO.puts
73
74 loop(env)
75 end
76
77 defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
78 {:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
79 end
80
81 defp eval_ast({:map, ast, meta}, env) do
82 map = for {key, value} <- ast, into: %{} do
83 {eval(key, env), eval(value, env)}
84 end
85
86 {:map, map, meta}
87 end
88
89 defp eval_ast({:vector, ast, meta}, env) do
90 {:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
91 end
92
93 defp eval_ast({:symbol, symbol}, env) do
94 case Mal.Env.get(env, symbol) do
95 {:ok, value} -> value
96 :not_found -> throw({:error, "'#{symbol}' not found"})
97 end
98 end
99
100 defp eval_ast(ast, _env), do: ast
101
102 defp read(input) do
103 Mal.Reader.read_str(input)
104 end
105
106 defp eval_bindings([], env), do: env
107 defp eval_bindings([{:symbol, key}, binding | tail], env) do
108 evaluated = eval(binding, env)
109 Mal.Env.set(env, key, evaluated)
110 eval_bindings(tail, env)
111 end
112 defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
113
114 defp quasi_list([], _env), do: list([{:symbol, "quote"}, list([])])
115 defp quasi_list([{:symbol, "unquote"}, arg], _env), do: arg
116 defp quasi_list([{:list, [{:symbol, "splice-unquote"}, first], _meta} | tail], env) do
117 right = tail
118 |> list
119 |> quasiquote(env)
120
121 list([{:symbol, "concat"}, first, right])
122 end
123 defp quasi_list([head | tail], env) do
124 left = quasiquote(head, env)
125 right = tail
126 |> list
127 |> quasiquote(env)
128
129 list([{:symbol, "cons"}, left, right])
130 end
131
132 defp quasiquote({list_type, ast, _}, env)
133 when list_type in [:list, :vector] do
134 quasi_list(ast, env)
135 end
136 defp quasiquote(ast, _env), do: list([{:symbol, "quote"}, ast])
137
138 defp macro_call?({:list, [{:symbol, key} | _tail], _}, env) do
139 case Mal.Env.get(env, key) do
140 {:ok, %Function{macro: true}} -> true
141 _ -> false
142 end
143 end
144 defp macro_call?(_ast, _env), do: false
145
146 defp do_macro_call({:list, [{:symbol, key} | tail], _}, env) do
147 {:ok, %Function{value: macro, macro: true}} = Mal.Env.get(env, key)
148 macro.(tail)
149 |> macroexpand(env)
150 end
151
152 defp macroexpand(ast, env) do
153 if macro_call?(ast, env) do
154 do_macro_call(ast, env)
155 else
156 ast
157 end
158 end
159
160 defp eval({:list, [], _} = empty_ast, _env), do: empty_ast
161 defp eval({:list, _list, _meta} = ast, env) do
162 case macroexpand(ast, env) do
163 {:list, list, meta} -> eval_list(list, env, meta)
164 result -> eval_ast(result, env)
165 end
166 end
167 defp eval(ast, env), do: eval_ast(ast, env)
168
169 defp eval_list([{:symbol, "macroexpand"}, ast], env, _), do: macroexpand(ast, env)
170
171 defp eval_list([{:symbol, "if"}, condition, if_true | if_false], env, _) do
172 result = eval(condition, env)
173 if result == nil or result == false do
174 case if_false do
175 [] -> nil
176 [body] -> eval(body, env)
177 end
178 else
179 eval(if_true, env)
180 end
181 end
182
183 defp eval_list([{:symbol, "do"} | ast], env, _) do
184 ast
185 |> List.delete_at(-1)
186 |> list
187 |> eval_ast(env)
188 eval(List.last(ast), env)
189 end
190
191 defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do
192 evaluated = eval(value, env)
193 Mal.Env.set(env, key, evaluated)
194 evaluated
195 end
196
197 defp eval_list([{:symbol, "defmacro!"}, {:symbol, key}, function], env, _) do
198 macro = %{eval(function, env) | macro: true}
199 Mal.Env.set(env, key, macro)
200 macro
201 end
202
203 defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _)
204 when list_type == :list or list_type == :vector do
205 let_env = Mal.Env.new(env)
206 eval_bindings(bindings, let_env)
207 eval(body, let_env)
208 end
209
210 defp eval_list([{:symbol, "fn*"}, {list_type, params, _}, body], env, _)
211 when list_type == :list or list_type == :vector do
212 param_symbols = for {:symbol, symbol} <- params, do: symbol
213
214 closure = fn args ->
215 inner = Mal.Env.new(env, param_symbols, args)
216 eval(body, inner)
217 end
218
219 %Function{value: closure}
220 end
221
222 defp eval_list([{:symbol, "quote"}, arg], _env, _), do: arg
223
224 defp eval_list([{:symbol, "quasiquote"}, ast], env, _) do
225 quasiquote(ast, env)
226 |> eval(env)
227 end
228
229 # (try* A (catch* B C))
230 defp eval_list([{:symbol, "try*"}, try_form, {:list, catch_list, _meta}], env, _) do
231 eval_try(try_form, catch_list, env)
232 end
233 defp eval_list([{:symbol, "try*"}, try_form], env, _) do
234 eval(try_form, env)
235 end
236 defp eval_list([{:symbol, "try*"}, _try_form, _], _env, _) do
237 throw({:error, "try* requires a list as the second parameter"})
238 end
239
240 defp eval_list(ast, env, meta) do
241 {:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
242 func.value.(args)
243 end
244
245 defp eval_try(try_form,
246 [{:symbol, "catch*"}, {:symbol, exception}, catch_form], env) do
247 try do
248 eval(try_form, env)
249 catch
250 {:error, message}->
251 catch_env = Mal.Env.new(env)
252 Mal.Env.set(catch_env, exception, {:exception, message})
253 eval(catch_form, catch_env)
254 end
255 end
256 defp eval_try(_try_form, _catch_list, _env) do
257 throw({:error, "catch* requires two arguments"})
258 end
259
260 defp print(value) do
261 Mal.Printer.print_str(value)
262 end
263
264 defp read_eval_print(:eof, _env), do: exit(:normal)
265 defp read_eval_print(line, env) do
266 read(line)
267 |> eval(env)
268 |> print
269 catch
270 {:error, exception} ->
271 IO.puts("Error: #{Mal.Printer.print_str(exception)}")
272 end
273 end