Add cons and concat to core
[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
11 defp load_file([file_name | args], env) do
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
75 def eval([{:symbol, "if"}, condition, if_true | if_false], env) do
76 result = eval(condition, env)
77 if result == nil or result == false do
78 case if_false do
79 [] -> nil
80 [body] -> eval(body, env)
81 end
82 else
83 eval(if_true, env)
84 end
85 end
86
87 def eval([{:symbol, "do"} | ast], env) do
88 eval_ast(List.delete_at(ast, -1), env)
89 eval(List.last(ast), env)
90 end
91
92 def eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
93 evaluated = eval(value, env)
94 Mal.Env.set(env, key, evaluated)
95 evaluated
96 end
97
98 def eval([{:symbol, "let*"}, bindings, body], env) do
99 let_env = Mal.Env.initialize(env)
100 eval_bindings(bindings, let_env)
101 eval(body, let_env)
102 end
103
104 def eval([{:symbol, "fn*"}, params, body], env) do
105 param_symbols = for {:symbol, symbol} <- params, do: symbol
106
107 closure = fn args ->
108 inner = Mal.Env.initialize(env, param_symbols, args)
109 eval(body, inner)
110 end
111
112 {:closure, closure}
113 end
114
115 def eval(ast, env) when is_list(ast) do
116 [func | args] = eval_ast(ast, env)
117 case func do
118 {:closure, closure} -> closure.(args)
119 _ -> func.(args)
120 end
121 end
122
123 def eval(ast, env), do: eval_ast(ast, env)
124
125 def print(value) do
126 IO.puts(Mal.Printer.print_str(value))
127 end
128
129 def read_eval_print(:eof, _env), do: exit(:normal)
130 def read_eval_print(line, env) do
131 read(line)
132 |> eval(env)
133 |> print
134 catch
135 {:error, message} -> IO.puts("Error: #{message}")
136 end
137end