4115c430 |
1 | defmodule 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 |
187 | end |