Change core functions to be mal functions and add meta/with-meta
[jackhill/mal.git] / elixir / lib / mix / tasks / step7_quote.ex
CommitLineData
4115c430 1defmodule Mix.Tasks.Step7Quote do
eccae82a 2 import Mal.Types
3 alias Mal.Function
4
4115c430 5 def run(args) do
6 env = Mal.Env.initialize()
7 Mal.Env.merge(env, Mal.Core.namespace)
8 bootstrap(args, env)
9 load_file(args, env)
bd0c3504 10 loop(env)
4115c430 11 end
12
13 defp load_file([], _env), do: nil
0568566f 14 defp load_file([file_name | _args], env) do
4115c430 15 read_eval_print("""
16 (load-file "#{file_name}")
17 """, env)
18 exit(:normal)
19 end
20
21 defp bootstrap(args, env) do
22 # not:
23 read_eval_print("""
24 (def! not
25 (fn* (a) (if a false true)))
26 """, env)
27
28 # load-file:
29 read_eval_print("""
30 (def! load-file
31 (fn* (f)
32 (eval (read-string (str "(do " (slurp f) ")")))))
33 """, env)
34
6c7c9e6f 35 Mal.Env.set(env, "eval", %Function{value: fn [ast] ->
4115c430 36 eval(ast, env)
6c7c9e6f 37 end})
4115c430 38
39 case args do
eccae82a 40 [_file_name | rest] -> Mal.Env.set(env, "*ARGV*", list(rest))
41 [] -> Mal.Env.set(env, "*ARGV*", list([]))
4115c430 42 end
43 end
44
bd0c3504 45 defp loop(env) do
eccae82a 46 IO.write(:stdio, "user> ")
47 IO.read(:stdio, :line)
4115c430 48 |> read_eval_print(env)
bd0c3504 49 |> IO.puts
4115c430 50
bd0c3504 51 loop(env)
4115c430 52 end
53
eccae82a 54 defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
55 {:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
4115c430 56 end
57
eccae82a 58 defp eval_ast({:map, ast, meta}, env) do
59 map = for {key, value} <- ast, into: %{} do
4f16e21e 60 {eval(key, env), eval(value, env)}
61 end
eccae82a 62
63 {:map, map, meta}
4f16e21e 64 end
65
eccae82a 66 defp eval_ast({:vector, ast, meta}, env) do
67 {:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
1bc4ac2b 68 end
69
1cd421f4 70 defp eval_ast({:symbol, symbol}, env) do
4115c430 71 case Mal.Env.get(env, symbol) do
72 {:ok, value} -> value
eccae82a 73 :not_found -> throw({:error, "'#{symbol}' not found"})
4115c430 74 end
75 end
76
1cd421f4 77 defp eval_ast(ast, _env), do: ast
4115c430 78
1cd421f4 79 defp read(input) do
4115c430 80 Mal.Reader.read_str(input)
81 end
82
83 defp eval_bindings([], _env), do: _env
84 defp eval_bindings([{:symbol, key}, binding | tail], env) do
85 evaluated = eval(binding, env)
86 Mal.Env.set(env, key, evaluated)
87 eval_bindings(tail, env)
88 end
89 defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
90
eccae82a 91 defp quasi_list([], _env), do: list([{:symbol, "quote"}, list([])])
92 defp quasi_list([{:symbol, "unquote"}, arg], _env), do: arg
93 defp quasi_list([{:list, [{:symbol, "splice-unquote"}, first], _meta} | tail], env) do
94 right = tail
95 |> list
96 |> quasiquote(env)
97
98 list([{:symbol, "concat"}, first, right])
0568566f 99 end
eccae82a 100 defp quasi_list([head | tail], env) do
101 left = quasiquote(head, env)
102 right = tail
103 |> list
104 |> quasiquote(env)
105
106 list([{:symbol, "cons"}, left, right])
107 end
108
109 defp quasiquote({list_type, ast, _}, env)
110 when list_type in [:list, :vector] do
111 quasi_list(ast, env)
0568566f 112 end
eccae82a 113 defp quasiquote(ast, _env), do: list([{:symbol, "quote"}, ast])
0568566f 114
eccae82a 115 defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta)
116 defp eval(ast, env), do: eval_ast(ast, env)
117
118 defp eval_list([{:symbol, "if"}, condition, if_true | if_false], env, _) do
4115c430 119 result = eval(condition, env)
120 if result == nil or result == false do
121 case if_false do
122 [] -> nil
123 [body] -> eval(body, env)
124 end
125 else
126 eval(if_true, env)
127 end
128 end
129
eccae82a 130 defp eval_list([{:symbol, "do"} | ast], env, _) do
131 ast
132 |> List.delete_at(-1)
133 |> list
134 |> eval_ast(env)
4115c430 135 eval(List.last(ast), env)
136 end
137
eccae82a 138 defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do
4115c430 139 evaluated = eval(value, env)
140 Mal.Env.set(env, key, evaluated)
141 evaluated
142 end
143
eccae82a 144 defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _)
145 when list_type == :list or list_type == :vector do
4115c430 146 let_env = Mal.Env.initialize(env)
147 eval_bindings(bindings, let_env)
148 eval(body, let_env)
149 end
150
eccae82a 151 defp eval_list([{:symbol, "fn*"}, {list_type, params, _}, body], env, _)
152 when list_type == :list or list_type == :vector do
4115c430 153 param_symbols = for {:symbol, symbol} <- params, do: symbol
154
155 closure = fn args ->
156 inner = Mal.Env.initialize(env, param_symbols, args)
157 eval(body, inner)
158 end
159
eccae82a 160 %Function{value: closure}
4115c430 161 end
162
eccae82a 163 defp eval_list([{:symbol, "quote"}, arg], _env, _), do: arg
0568566f 164
eccae82a 165 defp eval_list([{:symbol, "quasiquote"}, ast], env, _) do
0568566f 166 quasiquote(ast, env)
167 |> eval(env)
168 end
169
eccae82a 170 defp eval_list(ast, env, meta) do
171 {:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
6c7c9e6f 172 func.value.(args)
4115c430 173 end
174
1cd421f4 175 defp print(value) do
bd0c3504 176 Mal.Printer.print_str(value)
4115c430 177 end
178
1cd421f4 179 defp read_eval_print(:eof, _env), do: exit(:normal)
180 defp read_eval_print(line, env) do
4115c430 181 read(line)
182 |> eval(env)
183 |> print
184 catch
185 {:error, message} -> IO.puts("Error: #{message}")
186 end
187end