1
1
mirror of https://github.com/kanaka/mal.git synced 2024-11-13 01:43:50 +03:00
mal/elixir/lib/mix/tasks/step5_tco.ex
2015-09-06 15:51:25 +02:00

109 lines
2.5 KiB
Elixir

defmodule Mix.Tasks.Step5Tco do
def run(_) do
env = Mal.Env.initialize()
Mal.Env.merge(env, Mal.Core.namespace)
bootstrap(env)
main(env)
end
def bootstrap(env) do
read_eval_print("(def! not (fn* (a) (if a false true)))", env)
end
def main(env) do
IO.write(:stdio, "user> ")
IO.read(:stdio, :line)
|> read_eval_print(env)
main(env)
end
def eval_ast(ast, env) when is_list(ast) do
Enum.map(ast, fn elem -> eval(elem, env) end)
end
def eval_ast({:symbol, symbol}, env) do
case Mal.Env.get(env, symbol) do
{:ok, value} -> value
:not_found -> throw({:error, "invalid symbol #{symbol}"})
end
end
def eval_ast(ast, _env), do: ast
def read(input) do
Mal.Reader.read_str(input)
end
defp eval_bindings([], _env), do: _env
defp eval_bindings([{:symbol, key}, binding | tail], env) do
evaluated = eval(binding, env)
Mal.Env.set(env, key, evaluated)
eval_bindings(tail, env)
end
defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
def eval([{:symbol, "if"}, condition, if_true | if_false], env) do
result = eval(condition, env)
if result == nil or result == false do
case if_false do
[] -> nil
[body] -> eval(body, env)
end
else
eval(if_true, env)
end
end
def eval([{:symbol, "do"} | ast], env) do
eval_ast(List.delete_at(ast, -1), env)
eval(List.last(ast), env)
end
def eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
evaluated = eval(value, env)
Mal.Env.set(env, key, evaluated)
evaluated
end
def eval([{:symbol, "let*"}, bindings, body], env) do
let_env = Mal.Env.initialize(env)
eval_bindings(bindings, let_env)
eval(body, let_env)
end
def eval([{:symbol, "fn*"}, params, body], env) do
param_symbols = for {:symbol, symbol} <- params, do: symbol
closure = fn args ->
inner = Mal.Env.initialize(env, param_symbols, args)
eval(body, inner)
end
{:closure, closure}
end
def eval(ast, env) when is_list(ast) do
[func | args] = eval_ast(ast, env)
case func do
{:closure, closure} -> closure.(args)
_ -> func.(args)
end
end
def eval(ast, env), do: eval_ast(ast, env)
def print(value) do
IO.puts(Mal.Printer.print_str(value))
end
def read_eval_print(:eof, _env), do: exit(:normal)
def read_eval_print(line, env) do
read(line)
|> eval(env)
|> print
catch
{:error, message} -> IO.puts("Error: #{message}")
end
end