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