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