Make sure eval of do is in tail position and skip step 5
[jackhill/mal.git] / elixir / lib / mix / tasks / step4_if_fn_do.ex
1 defmodule Mix.Tasks.Step4IfFnDo do
2 def run(_) do
3 env = Mal.Env.initialize()
4 Mal.Env.merge(env, Mal.Core.namespace)
5 bootstrap(env)
6 main(env)
7 end
8
9 def bootstrap(env) do
10 read_eval_print("(def! not (fn* (a) (if a false true)))", env)
11 end
12
13 def main(env) do
14 IO.write(:stdio, "user> ")
15 IO.read(:stdio, :line)
16 |> read_eval_print(env)
17
18 main(env)
19 end
20
21 def eval_ast(ast, env) when is_list(ast) do
22 Enum.map(ast, fn elem -> eval(elem, env) end)
23 end
24
25 def eval_ast({:symbol, symbol}, env) do
26 case Mal.Env.get(env, symbol) do
27 {:ok, value} -> value
28 :not_found -> throw({:error, "invalid symbol #{symbol}"})
29 end
30 end
31
32 def eval_ast(ast, _env), do: ast
33
34 def read(input) do
35 Mal.Reader.read_str(input)
36 end
37
38 defp eval_bindings([], _env), do: _env
39 defp eval_bindings([{:symbol, key}, binding | tail], env) do
40 evaluated = eval(binding, env)
41 Mal.Env.set(env, key, evaluated)
42 eval_bindings(tail, env)
43 end
44 defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
45
46 def eval([{:symbol, "if"}, condition, if_true | if_false], env) do
47 result = eval(condition, env)
48 if result == nil or result == false do
49 case if_false do
50 [] -> nil
51 [body] -> eval(body, env)
52 end
53 else
54 eval(if_true, env)
55 end
56 end
57
58 def eval([{:symbol, "do"} | ast], env) do
59 eval_ast(List.delete_at(ast, -1), env)
60 eval(List.last(ast), env)
61 end
62
63 def eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
64 evaluated = eval(value, env)
65 Mal.Env.set(env, key, evaluated)
66 evaluated
67 end
68
69 def eval([{:symbol, "let*"}, bindings, body], env) do
70 let_env = Mal.Env.initialize(env)
71 eval_bindings(bindings, let_env)
72 eval(body, let_env)
73 end
74
75 def eval([{:symbol, "fn*"}, params, body], env) do
76 param_symbols = for {:symbol, symbol} <- params, do: symbol
77
78 closure = fn args ->
79 inner = Mal.Env.initialize(env, param_symbols, args)
80 eval(body, inner)
81 end
82
83 {:closure, closure}
84 end
85
86 def eval(ast, env) when is_list(ast) do
87 [func | args] = eval_ast(ast, env)
88 case func do
89 {:closure, closure} -> closure.(args)
90 _ -> func.(args)
91 end
92 end
93
94 def eval(ast, env), do: eval_ast(ast, env)
95
96 def print(value) do
97 IO.puts(Mal.Printer.print_str(value))
98 end
99
100 def read_eval_print(:eof, _env), do: exit(0)
101 def read_eval_print(line, env) do
102 read(line)
103 |> eval(env)
104 |> print
105 catch
106 {:error, message} -> IO.puts("Error: #{message}")
107 end
108 end