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