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