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