Add map core functions, step 1 to 9 passing
[jackhill/mal.git] / elixir / lib / mix / tasks / step7_quote.ex
CommitLineData
4115c430 1defmodule Mix.Tasks.Step7Quote 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
0568566f 11 defp load_file([file_name | _args], env) do
4115c430 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 Mal.Env.set(env, "eval", fn [ast] ->
33 eval(ast, env)
34 end)
35
36 case args do
37 [_file_name | rest] -> Mal.Env.set(env, "*ARGV*", rest)
38 [] -> Mal.Env.set(env, "*ARGV*", [])
39 end
40 end
41
42 def main(env) do
43 IO.write(:stdio, "user> ")
44 IO.read(:stdio, :line)
45 |> read_eval_print(env)
46
47 main(env)
48 end
49
50 def eval_ast(ast, env) when is_list(ast) do
51 Enum.map(ast, fn elem -> eval(elem, env) end)
52 end
53
4f16e21e 54 def eval_ast(ast, env) when is_map(ast) do
55 for {key, value} <- ast, into: %{} do
56 {eval(key, env), eval(value, env)}
57 end
58 end
59
1bc4ac2b 60 def eval_ast({:vector, ast}, env) do
61 {:vector, Enum.map(ast, fn elem -> eval(elem, env) end)}
62 end
63
4115c430 64 def eval_ast({:symbol, symbol}, env) do
65 case Mal.Env.get(env, symbol) do
66 {:ok, value} -> value
67 :not_found -> throw({:error, "invalid symbol #{symbol}"})
68 end
69 end
70
71 def eval_ast(ast, _env), do: ast
72
73 def read(input) do
74 Mal.Reader.read_str(input)
75 end
76
1bc4ac2b 77 defp eval_bindings({:vector, vector}, env), do: eval_bindings(vector, env)
4115c430 78 defp eval_bindings([], _env), do: _env
79 defp eval_bindings([{:symbol, key}, binding | tail], env) do
80 evaluated = eval(binding, env)
81 Mal.Env.set(env, key, evaluated)
82 eval_bindings(tail, env)
83 end
84 defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
85
1bc4ac2b 86 defp quasiquote({:vector, list}, _env), do: quasiquote(list, _env)
0568566f 87 defp quasiquote(ast, _env) when not is_list(ast), do: [{:symbol, "quote"}, ast]
88 defp quasiquote([], _env), do: [{:symbol, "quote"}, []]
89 defp quasiquote([{:symbol, "unquote"}, arg], _env), do: arg
90 defp quasiquote([[{:symbol, "splice-unquote"}, first] | tail], env) do
91 [{:symbol, "concat"}, first, quasiquote(tail, env)]
92 end
93 defp quasiquote([head | tail], env) do
94 [{:symbol, "cons"}, quasiquote(head, env), quasiquote(tail, env)]
95 end
96
4115c430 97 def eval([{:symbol, "if"}, condition, if_true | if_false], env) do
98 result = eval(condition, env)
99 if result == nil or result == false do
100 case if_false do
101 [] -> nil
102 [body] -> eval(body, env)
103 end
104 else
105 eval(if_true, env)
106 end
107 end
108
109 def eval([{:symbol, "do"} | ast], env) do
110 eval_ast(List.delete_at(ast, -1), env)
111 eval(List.last(ast), env)
112 end
113
114 def eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
115 evaluated = eval(value, env)
116 Mal.Env.set(env, key, evaluated)
117 evaluated
118 end
119
120 def eval([{:symbol, "let*"}, bindings, body], env) do
121 let_env = Mal.Env.initialize(env)
122 eval_bindings(bindings, let_env)
123 eval(body, let_env)
124 end
125
1bc4ac2b 126 def eval([{:symbol, "fn*"}, {:vector, params}, body], env) do
127 eval([{:symbol, "fn*"}, params, body], env)
128 end
4115c430 129 def eval([{:symbol, "fn*"}, params, body], env) do
130 param_symbols = for {:symbol, symbol} <- params, do: symbol
131
132 closure = fn args ->
133 inner = Mal.Env.initialize(env, param_symbols, args)
134 eval(body, inner)
135 end
136
137 {:closure, closure}
138 end
139
0568566f 140 def eval([{:symbol, "quote"}, arg], _env), do: arg
141
142 def eval([{:symbol, "quasiquote"}, ast], env) do
143 quasiquote(ast, env)
144 |> eval(env)
145 end
146
4115c430 147 def eval(ast, env) when is_list(ast) do
148 [func | args] = eval_ast(ast, env)
149 case func do
150 {:closure, closure} -> closure.(args)
151 _ -> func.(args)
152 end
153 end
154
155 def eval(ast, env), do: eval_ast(ast, env)
156
157 def print(value) do
158 IO.puts(Mal.Printer.print_str(value))
159 end
160
161 def read_eval_print(:eof, _env), do: exit(:normal)
162 def read_eval_print(line, env) do
163 read(line)
164 |> eval(env)
165 |> print
166 catch
167 {:error, message} -> IO.puts("Error: #{message}")
168 end
169end