Change quasiquote algorithm
[jackhill/mal.git] / impls / elixir / lib / mix / tasks / step7_quote.ex
CommitLineData
4115c430 1defmodule Mix.Tasks.Step7Quote do
eccae82a 2 import Mal.Types
3 alias Mal.Function
4
4115c430 5 def run(args) do
e497903f 6 env = Mal.Env.new()
4115c430 7 Mal.Env.merge(env, Mal.Core.namespace)
8 bootstrap(args, env)
bd0c3504 9 loop(env)
4115c430 10 end
11
230dc1aa 12 defp load_file(file_name, env) do
4115c430 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)
e6d41de4 30 (eval (read-string (str "(do " (slurp f) "\nnil)")))))
4115c430 31 """, env)
32
6c7c9e6f 33 Mal.Env.set(env, "eval", %Function{value: fn [ast] ->
4115c430 34 eval(ast, env)
6c7c9e6f 35 end})
4115c430 36
37 case args do
230dc1aa 38 [file_name | rest] ->
39 Mal.Env.set(env, "*ARGV*", list(rest))
40 load_file(file_name, env)
41
42 [] ->
43 Mal.Env.set(env, "*ARGV*", list([]))
4115c430 44 end
45 end
46
bd0c3504 47 defp loop(env) do
eccae82a 48 IO.write(:stdio, "user> ")
49 IO.read(:stdio, :line)
4115c430 50 |> read_eval_print(env)
bd0c3504 51 |> IO.puts
4115c430 52
bd0c3504 53 loop(env)
4115c430 54 end
55
eccae82a 56 defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
57 {:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
4115c430 58 end
59
eccae82a 60 defp eval_ast({:map, ast, meta}, env) do
61 map = for {key, value} <- ast, into: %{} do
4f16e21e 62 {eval(key, env), eval(value, env)}
63 end
eccae82a 64
65 {:map, map, meta}
4f16e21e 66 end
67
eccae82a 68 defp eval_ast({:vector, ast, meta}, env) do
69 {:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
1bc4ac2b 70 end
71
1cd421f4 72 defp eval_ast({:symbol, symbol}, env) do
4115c430 73 case Mal.Env.get(env, symbol) do
74 {:ok, value} -> value
eccae82a 75 :not_found -> throw({:error, "'#{symbol}' not found"})
4115c430 76 end
77 end
78
1cd421f4 79 defp eval_ast(ast, _env), do: ast
4115c430 80
1cd421f4 81 defp read(input) do
4115c430 82 Mal.Reader.read_str(input)
83 end
84
e68e138f 85 defp eval_bindings([], env), do: env
4115c430 86 defp eval_bindings([{:symbol, key}, binding | tail], env) do
87 evaluated = eval(binding, env)
88 Mal.Env.set(env, key, evaluated)
89 eval_bindings(tail, env)
90 end
91 defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
92
fbfe6784
NB
93 defp quasiquote({:list, [{:symbol, "unquote"}, arg], _}), do: arg
94 defp quasiquote({:list, [{:symbol, "unquote"}| _], _}), do: throw({:error, "unquote: arg count"})
95 defp quasiquote({:list, xs, _}), do: qq_foldr(xs)
96 defp quasiquote({:vector, xs, _}), do: list([{:symbol, "vec"}, qq_foldr(xs)])
97 defp quasiquote({:symbol, sym}), do: list([{:symbol, "quote"}, {:symbol, sym}])
98 defp quasiquote({:map, ast, meta}), do: list([{:symbol, "quote"}, {:map, ast, meta}])
99 defp quasiquote(ast), do: ast
eccae82a 100
fbfe6784
NB
101 defp qq_foldr([]), do: list([])
102 defp qq_foldr([x|xs]), do: qq_loop(x, qq_foldr xs)
eccae82a 103
fbfe6784
NB
104 defp qq_loop({:list, [{:symbol, "splice-unquote"}, arg], _}, acc), do: list([{:symbol, "concat"}, arg, acc])
105 defp qq_loop({:list, [{:symbol, "splice-unquote"}| _], _}, _), do: throw({:error, "splice-unquote: arg count"})
106 defp qq_loop(elt, acc), do: list([{:symbol, "cons"}, quasiquote(elt), acc])
0568566f 107
e68e138f 108 defp eval({:list, [], _} = empty_ast, _env), do: empty_ast
eccae82a 109 defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta)
110 defp eval(ast, env), do: eval_ast(ast, env)
111
112 defp eval_list([{:symbol, "if"}, condition, if_true | if_false], env, _) do
4115c430 113 result = eval(condition, env)
114 if result == nil or result == false do
115 case if_false do
116 [] -> nil
117 [body] -> eval(body, env)
118 end
119 else
120 eval(if_true, env)
121 end
122 end
123
eccae82a 124 defp eval_list([{:symbol, "do"} | ast], env, _) do
125 ast
126 |> List.delete_at(-1)
127 |> list
128 |> eval_ast(env)
4115c430 129 eval(List.last(ast), env)
130 end
131
eccae82a 132 defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do
4115c430 133 evaluated = eval(value, env)
134 Mal.Env.set(env, key, evaluated)
135 evaluated
136 end
137
eccae82a 138 defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _)
139 when list_type == :list or list_type == :vector do
e497903f 140 let_env = Mal.Env.new(env)
4115c430 141 eval_bindings(bindings, let_env)
142 eval(body, let_env)
143 end
144
eccae82a 145 defp eval_list([{:symbol, "fn*"}, {list_type, params, _}, body], env, _)
146 when list_type == :list or list_type == :vector do
4115c430 147 param_symbols = for {:symbol, symbol} <- params, do: symbol
148
149 closure = fn args ->
e497903f 150 inner = Mal.Env.new(env, param_symbols, args)
4115c430 151 eval(body, inner)
152 end
153
eccae82a 154 %Function{value: closure}
4115c430 155 end
156
eccae82a 157 defp eval_list([{:symbol, "quote"}, arg], _env, _), do: arg
0568566f 158
fbfe6784
NB
159 defp eval_list([{:symbol, "quasiquoteexpand"}, ast], _, _) do
160 quasiquote(ast)
161 end
162
eccae82a 163 defp eval_list([{:symbol, "quasiquote"}, ast], env, _) do
fbfe6784 164 ast |> quasiquote
0568566f 165 |> eval(env)
166 end
167
eccae82a 168 defp eval_list(ast, env, meta) do
169 {:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
6c7c9e6f 170 func.value.(args)
4115c430 171 end
172
1cd421f4 173 defp print(value) do
bd0c3504 174 Mal.Printer.print_str(value)
4115c430 175 end
176
1cd421f4 177 defp read_eval_print(:eof, _env), do: exit(:normal)
178 defp read_eval_print(line, env) do
4115c430 179 read(line)
180 |> eval(env)
181 |> print
182 catch
183 {:error, message} -> IO.puts("Error: #{message}")
184 end
185end