Add if support
[jackhill/mal.git] / elixir / lib / mix / tasks / step4_if_fn_do.ex
1 defmodule Mix.Tasks.Step4IfFnDo 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 defp eval_if_false([], _env), do: nil
49 defp eval_if_false([body], env), do: eval(body, env)
50
51 def eval([{:symbol, "if"}, condition, if_true | if_false], env) do
52 result = eval(condition, env)
53 if result == nil or result == false do
54 eval_if_false(if_false, env)
55 else
56 eval(if_true, env)
57 end
58 end
59
60 def eval([{:symbol, "do"} | ast], env) do
61 eval_ast(ast, env)
62 |> List.last
63 end
64
65 def eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
66 evaluated = eval(value, env)
67 Mal.Env.set(env, key, evaluated)
68 evaluated
69 end
70
71 def eval([{:symbol, "let*"}, bindings, body], env) do
72 {:ok, let_env} = Mal.Env.initialize(env)
73 eval_bindings(bindings, let_env)
74 eval(body, let_env)
75 end
76
77 def eval(ast, env) when is_list(ast) do
78 [func | args] = eval_ast(ast, env)
79 apply(func, args)
80 end
81
82 def eval(ast, env), do: eval_ast(ast, env)
83
84 def print(value) do
85 IO.puts(Mal.Printer.print_str(value))
86 end
87
88 def read_eval_print(:eof, _env), do: exit(0)
89 def read_eval_print(line, env) do
90 read(line)
91 |> eval(env)
92 |> print
93 catch
94 {:error, message} -> IO.puts("Error: #{message}")
95 end
96 end