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