Use the new data structures in the other steps
[jackhill/mal.git] / elixir / lib / mix / tasks / step3_env.ex
1 defmodule Mix.Tasks.Step3Env do
2 import Mal.Types
3 alias Mal.Function
4
5 @initial_env %{
6 "+" => &+/2,
7 "-" => &-/2,
8 "*" => &*/2,
9 "/" => &div/2
10 }
11
12 def run(_) do
13 env = Mal.Env.initialize()
14 Mal.Env.merge(env, @initial_env)
15 loop(env)
16 end
17
18 defp loop(env) do
19 IO.write(:stdio, "user> ")
20 IO.read(:stdio, :line)
21 |> read_eval_print(env)
22 |> IO.puts
23
24 loop(env)
25 end
26
27 defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
28 {:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
29 end
30
31 defp eval_ast({:map, ast, meta}, env) do
32 map = for {key, value} <- ast, into: %{} do
33 {eval(key, env), eval(value, env)}
34 end
35
36 {:map, map, meta}
37 end
38
39 defp eval_ast({:vector, ast, meta}, env) do
40 {:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
41 end
42
43 defp eval_ast({:symbol, symbol}, env) do
44 case Mal.Env.get(env, symbol) do
45 {:ok, value} -> value
46 :not_found -> throw({:error, "'#{symbol}' not found"})
47 end
48 end
49
50 defp eval_ast(ast, _env), do: ast
51
52 defp read(input) do
53 Mal.Reader.read_str(input)
54 end
55
56 defp eval_bindings([], _env), do: _env
57 defp eval_bindings([{:symbol, key}, binding | tail], env) do
58 evaluated = eval(binding, env)
59 Mal.Env.set(env, key, evaluated)
60 eval_bindings(tail, env)
61 end
62 defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
63
64 defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta)
65 defp eval(ast, env), do: eval_ast(ast, env)
66
67 defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do
68 evaluated = eval(value, env)
69 Mal.Env.set(env, key, evaluated)
70 evaluated
71 end
72
73 defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _)
74 when list_type == :list or list_type == :vector do
75 let_env = Mal.Env.initialize(env)
76 eval_bindings(bindings, let_env)
77 eval(body, let_env)
78 end
79
80 defp eval_list(ast, env, meta) do
81 {:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
82 apply(func, args)
83 end
84
85 defp print(value) do
86 Mal.Printer.print_str(value)
87 end
88
89 defp read_eval_print(:eof, _env), do: exit(:normal)
90 defp read_eval_print(line, env) do
91 read(line)
92 |> eval(env)
93 |> print
94 catch
95 {:error, message} -> IO.puts("Error: #{message}")
96 end
97 end