Add reading and printing of hash-maps
[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
1bc4ac2b 54 def eval_ast({:vector, ast}, env) do
55 {:vector, Enum.map(ast, fn elem -> eval(elem, env) end)}
56 end
57
4115c430 58 def eval_ast({:symbol, symbol}, env) do
59 case Mal.Env.get(env, symbol) do
60 {:ok, value} -> value
61 :not_found -> throw({:error, "invalid symbol #{symbol}"})
62 end
63 end
64
65 def eval_ast(ast, _env), do: ast
66
67 def read(input) do
68 Mal.Reader.read_str(input)
69 end
70
1bc4ac2b 71 defp eval_bindings({:vector, vector}, env), do: eval_bindings(vector, env)
4115c430 72 defp eval_bindings([], _env), do: _env
73 defp eval_bindings([{:symbol, key}, binding | tail], env) do
74 evaluated = eval(binding, env)
75 Mal.Env.set(env, key, evaluated)
76 eval_bindings(tail, env)
77 end
78 defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
79
1bc4ac2b 80 defp quasiquote({:vector, list}, _env), do: quasiquote(list, _env)
0568566f 81 defp quasiquote(ast, _env) when not is_list(ast), do: [{:symbol, "quote"}, ast]
82 defp quasiquote([], _env), do: [{:symbol, "quote"}, []]
83 defp quasiquote([{:symbol, "unquote"}, arg], _env), do: arg
84 defp quasiquote([[{:symbol, "splice-unquote"}, first] | tail], env) do
85 [{:symbol, "concat"}, first, quasiquote(tail, env)]
86 end
87 defp quasiquote([head | tail], env) do
88 [{:symbol, "cons"}, quasiquote(head, env), quasiquote(tail, env)]
89 end
90
4115c430 91 def eval([{:symbol, "if"}, condition, if_true | if_false], env) do
92 result = eval(condition, env)
93 if result == nil or result == false do
94 case if_false do
95 [] -> nil
96 [body] -> eval(body, env)
97 end
98 else
99 eval(if_true, env)
100 end
101 end
102
103 def eval([{:symbol, "do"} | ast], env) do
104 eval_ast(List.delete_at(ast, -1), env)
105 eval(List.last(ast), env)
106 end
107
108 def eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
109 evaluated = eval(value, env)
110 Mal.Env.set(env, key, evaluated)
111 evaluated
112 end
113
114 def eval([{:symbol, "let*"}, bindings, body], env) do
115 let_env = Mal.Env.initialize(env)
116 eval_bindings(bindings, let_env)
117 eval(body, let_env)
118 end
119
1bc4ac2b 120 def eval([{:symbol, "fn*"}, {:vector, params}, body], env) do
121 eval([{:symbol, "fn*"}, params, body], env)
122 end
4115c430 123 def eval([{:symbol, "fn*"}, params, body], env) do
124 param_symbols = for {:symbol, symbol} <- params, do: symbol
125
126 closure = fn args ->
127 inner = Mal.Env.initialize(env, param_symbols, args)
128 eval(body, inner)
129 end
130
131 {:closure, closure}
132 end
133
0568566f 134 def eval([{:symbol, "quote"}, arg], _env), do: arg
135
136 def eval([{:symbol, "quasiquote"}, ast], env) do
137 quasiquote(ast, env)
138 |> eval(env)
139 end
140
4115c430 141 def eval(ast, env) when is_list(ast) do
142 [func | args] = eval_ast(ast, env)
143 case func do
144 {:closure, closure} -> closure.(args)
145 _ -> func.(args)
146 end
147 end
148
149 def eval(ast, env), do: eval_ast(ast, env)
150
151 def print(value) do
152 IO.puts(Mal.Printer.print_str(value))
153 end
154
155 def read_eval_print(:eof, _env), do: exit(:normal)
156 def read_eval_print(line, env) do
157 read(line)
158 |> eval(env)
159 |> print
160 catch
161 {:error, message} -> IO.puts("Error: #{message}")
162 end
163end