Support multiple let* binding clauses
[jackhill/mal.git] / elixir / lib / mix / tasks / step3_env.ex
1 defmodule Mix.Tasks.Step3Env do
2 @initial_env %{
3 "+" => &+/2,
4 "-" => &-/2,
5 "*" => &*/2,
6 "/" => &div/2
7 }
8
9 def run(_) do
10 {:ok, env} = Mal.Env.initialize()
11 Mal.Env.merge(env, @initial_env)
12 main(env)
13 end
14
15 def main(env) do
16 IO.write(:stdio, "user> ")
17 IO.read(:stdio, :line)
18 |> read_eval_print(env)
19
20 main(env)
21 end
22
23 def eval_ast(ast, env) when is_list(ast) do
24 Enum.map(ast, fn elem -> eval(elem, env) end)
25 end
26
27 def eval_ast({:symbol, symbol}, env) do
28 case Mal.Env.get(env, symbol) do
29 {:ok, value} -> value
30 :not_found -> throw({:error, "invalid symbol #{symbol}"})
31 end
32 end
33
34 def eval_ast(ast, _env), do: ast
35
36 def read(input) do
37 Mal.Reader.read_str(input)
38 end
39
40 defp eval_bindings([], _env), do: _env
41 defp eval_bindings([{:symbol, key}, binding | tail], env) do
42 evaluated = eval(binding, env)
43 Mal.Env.set(env, key, evaluated)
44 eval_bindings(tail, env)
45 end
46 defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
47
48
49 def eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
50 evaluated = eval(value, env)
51 Mal.Env.set(env, key, evaluated)
52 evaluated
53 end
54
55 def eval([{:symbol, "let*"}, bindings, body], env) do
56 {:ok, let_env} = Mal.Env.initialize(env)
57 eval_bindings(bindings, let_env)
58 eval(body, let_env)
59 end
60
61 def eval(ast, env) when is_list(ast) do
62 [func | args] = eval_ast(ast, env)
63 apply(func, args)
64 end
65
66 def eval(ast, env), do: eval_ast(ast, env)
67
68 def print(value) do
69 IO.puts(Mal.Printer.print_str(value))
70 end
71
72 def read_eval_print(:eof, _env), do: exit(0)
73 def read_eval_print(line, env) do
74 read(line)
75 |> eval(env)
76 |> print
77 catch
78 {:error, message} -> IO.puts("Error: #{message}")
79 end
80 end