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 | |
eccae82a |
93 | defp quasi_list([], _env), do: list([{:symbol, "quote"}, list([])]) |
94 | defp quasi_list([{:symbol, "unquote"}, arg], _env), do: arg |
95 | defp quasi_list([{:list, [{:symbol, "splice-unquote"}, first], _meta} | tail], env) do |
96 | right = tail |
97 | |> list |
98 | |> quasiquote(env) |
99 | |
100 | list([{:symbol, "concat"}, first, right]) |
0568566f |
101 | end |
eccae82a |
102 | defp quasi_list([head | tail], env) do |
103 | left = quasiquote(head, env) |
104 | right = tail |
105 | |> list |
106 | |> quasiquote(env) |
107 | |
108 | list([{:symbol, "cons"}, left, right]) |
109 | end |
110 | |
111 | defp quasiquote({list_type, ast, _}, env) |
112 | when list_type in [:list, :vector] do |
113 | quasi_list(ast, env) |
0568566f |
114 | end |
eccae82a |
115 | defp quasiquote(ast, _env), do: list([{:symbol, "quote"}, ast]) |
0568566f |
116 | |
e68e138f |
117 | defp eval({:list, [], _} = empty_ast, _env), do: empty_ast |
eccae82a |
118 | defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta) |
119 | defp eval(ast, env), do: eval_ast(ast, env) |
120 | |
121 | defp eval_list([{:symbol, "if"}, condition, if_true | if_false], env, _) do |
4115c430 |
122 | result = eval(condition, env) |
123 | if result == nil or result == false do |
124 | case if_false do |
125 | [] -> nil |
126 | [body] -> eval(body, env) |
127 | end |
128 | else |
129 | eval(if_true, env) |
130 | end |
131 | end |
132 | |
eccae82a |
133 | defp eval_list([{:symbol, "do"} | ast], env, _) do |
134 | ast |
135 | |> List.delete_at(-1) |
136 | |> list |
137 | |> eval_ast(env) |
4115c430 |
138 | eval(List.last(ast), env) |
139 | end |
140 | |
eccae82a |
141 | defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do |
4115c430 |
142 | evaluated = eval(value, env) |
143 | Mal.Env.set(env, key, evaluated) |
144 | evaluated |
145 | end |
146 | |
eccae82a |
147 | defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _) |
148 | when list_type == :list or list_type == :vector do |
e497903f |
149 | let_env = Mal.Env.new(env) |
4115c430 |
150 | eval_bindings(bindings, let_env) |
151 | eval(body, let_env) |
152 | end |
153 | |
eccae82a |
154 | defp eval_list([{:symbol, "fn*"}, {list_type, params, _}, body], env, _) |
155 | when list_type == :list or list_type == :vector do |
4115c430 |
156 | param_symbols = for {:symbol, symbol} <- params, do: symbol |
157 | |
158 | closure = fn args -> |
e497903f |
159 | inner = Mal.Env.new(env, param_symbols, args) |
4115c430 |
160 | eval(body, inner) |
161 | end |
162 | |
eccae82a |
163 | %Function{value: closure} |
4115c430 |
164 | end |
165 | |
eccae82a |
166 | defp eval_list([{:symbol, "quote"}, arg], _env, _), do: arg |
0568566f |
167 | |
eccae82a |
168 | defp eval_list([{:symbol, "quasiquote"}, ast], env, _) do |
0568566f |
169 | quasiquote(ast, env) |
170 | |> eval(env) |
171 | end |
172 | |
eccae82a |
173 | defp eval_list(ast, env, meta) do |
174 | {:list, [func | args], _} = eval_ast({:list, ast, meta}, env) |
6c7c9e6f |
175 | func.value.(args) |
4115c430 |
176 | end |
177 | |
1cd421f4 |
178 | defp print(value) do |
bd0c3504 |
179 | Mal.Printer.print_str(value) |
4115c430 |
180 | end |
181 | |
1cd421f4 |
182 | defp read_eval_print(:eof, _env), do: exit(:normal) |
183 | defp read_eval_print(line, env) do |
4115c430 |
184 | read(line) |
185 | |> eval(env) |
186 | |> print |
187 | catch |
188 | {:error, message} -> IO.puts("Error: #{message}") |
189 | end |
190 | end |